11using System . Text ;
2+ using System . Linq ;
23using DiscUtils . Iso9660 ;
34
45namespace DotnetPackaging . Dmg ;
@@ -32,10 +33,19 @@ public static Task Create(string sourceFolder, string outputPath, string volumeN
3233 builder . AddDirectory ( bundle ) ;
3334 builder . AddDirectory ( $ "{ bundle } /Contents") ;
3435 builder . AddDirectory ( $ "{ bundle } /Contents/MacOS") ;
36+ builder . AddDirectory ( $ "{ bundle } /Contents/Resources") ;
3537
3638 // Copy application payload under Contents/MacOS
3739 AddDirectoryRecursive ( builder , sourceFolder , "." , prefix : $ "{ bundle } /Contents/MacOS") ;
3840
41+ var appIcon = FindIcnsIcon ( sourceFolder ) ;
42+ if ( appIcon != null )
43+ {
44+ var iconName = Path . GetFileName ( appIcon ) ;
45+ var iconBytes = File . ReadAllBytes ( appIcon ) ;
46+ builder . AddFile ( $ "{ bundle } /Contents/Resources/{ iconName } ", new MemoryStream ( iconBytes , writable : false ) ) ;
47+ }
48+
3949 // Hoist DMG adornments (if present) at image root for macOS Finder niceties
4050 var volIcon = Path . Combine ( sourceFolder , ".VolumeIcon.icns" ) ;
4151 if ( File . Exists ( volIcon ) )
@@ -51,18 +61,8 @@ public static Task Create(string sourceFolder, string outputPath, string volumeN
5161
5262 // Add a minimal Info.plist
5363 var exeName = GuessExecutableName ( sourceFolder , volumeName ) ;
54- var plist = GenerateMinimalPlist ( volumeName , exeName ) ;
64+ var plist = GenerateMinimalPlist ( volumeName , exeName , appIcon == null ? null : Path . GetFileNameWithoutExtension ( appIcon ) ) ;
5565 builder . AddFile ( $ "{ bundle } /Contents/Info.plist", new MemoryStream ( Encoding . UTF8 . GetBytes ( plist ) , writable : false ) ) ;
56-
57- // Also place top-level files at the image root (convenience), excluding already-hoisted adornments
58- foreach ( var file in Directory . EnumerateFiles ( sourceFolder ) )
59- {
60- var name = Path . GetFileName ( file ) ;
61- if ( name is null ) continue ;
62- if ( name . Equals ( ".VolumeIcon.icns" , StringComparison . OrdinalIgnoreCase ) ) continue ;
63- var bytes = File . ReadAllBytes ( file ) ;
64- builder . AddFile ( name , new MemoryStream ( bytes , writable : false ) ) ;
65- }
6666 }
6767
6868 builder . Build ( fs ) ;
@@ -112,8 +112,17 @@ private static string GuessExecutableName(string sourceFolder, string volumeName
112112 return match ;
113113 }
114114
115- private static string GenerateMinimalPlist ( string displayName , string executable )
115+ private static string ? FindIcnsIcon ( string sourceFolder )
116+ {
117+ var icons = Directory . EnumerateFiles ( sourceFolder , "*.icns" , SearchOption . TopDirectoryOnly )
118+ . Where ( path => ! Path . GetFileName ( path ) ! . Equals ( ".VolumeIcon.icns" , StringComparison . OrdinalIgnoreCase ) ) ;
119+
120+ return icons . FirstOrDefault ( ) ;
121+ }
122+
123+ private static string GenerateMinimalPlist ( string displayName , string executable , string ? iconName )
116124 {
125+ var identifier = $ "com.{ SanitizeBundleName ( displayName ) . Trim ( '-' ) . Trim ( '_' ) . ToLowerInvariant ( ) } ";
117126 return $ """
118127<?xml version=\"1.0\" encoding=\"UTF-8\"?>
119128<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
@@ -122,7 +131,7 @@ private static string GenerateMinimalPlist(string displayName, string executable
122131 <key>CFBundleName</key>
123132 <string>{ System . Security . SecurityElement . Escape ( displayName ) } </string>
124133 <key>CFBundleIdentifier</key>
125- <string>com.example. { System . Security . SecurityElement . Escape ( displayName ) . Replace ( " " , "" ) } </string>
134+ <string>{ System . Security . SecurityElement . Escape ( identifier ) } </string>
126135 <key>CFBundleVersion</key>
127136 <string>1.0</string>
128137 <key>CFBundleShortVersionString</key>
@@ -131,6 +140,7 @@ private static string GenerateMinimalPlist(string displayName, string executable
131140 <string>APPL</string>
132141 <key>CFBundleExecutable</key>
133142 <string>{ System . Security . SecurityElement . Escape ( executable ) } </string>
143+ { ( iconName == null ? string . Empty : $ " <key>CFBundleIconFile</key>\n <string>{ System . Security . SecurityElement . Escape ( iconName ) } </string>\n ") }
134144 </dict>
135145</plist>
136146""" ;
0 commit comments