Skip to content

Commit dbb1b9b

Browse files
authored
[ABLD-28] Replace macOS osacript by dmgbuild to build DMGs (#239)
* Replace macOS `osacript` by `dmgbuild` to build DMGs This is to build DMG files for AArch64/ARM64 without having to tweak SIP (macOS System Integrity Protection) which happens to be only relaxed for x86_64/AMD64 (AWS default), see: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/mac-sip-settings.html#mac-sip-defaults For the record, here's how to run corresponding unit tests: ```bash bundle install --path=.bundle # once bundle exec rspec spec/unit/compressors/dmg_spec.rb ``` * Add missing conversion from `window_bounds` to `window_rect`
1 parent 27b5132 commit dbb1b9b

4 files changed

Lines changed: 79 additions & 348 deletions

File tree

lib/omnibus/compressors/dmg.rb

Lines changed: 20 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,13 @@ class Compressor::DMG < Compressor::Base
2121
setup do
2222
# Clean any previously mounted disks
2323
clean_disks
24-
25-
# Create the resources directory
26-
create_directory(resources_dir)
27-
28-
# Copy the compiled pkg into the dmg
29-
copy_file(packager.package_path, "#{resources_dir}/")
30-
31-
# Copy support files
32-
support = create_directory("#{resources_dir}/.support")
33-
copy_file(resource_path("background.png"), "#{support}/background.png")
3424
end
3525

3626
build do
37-
create_writable_dmg
38-
attach_dmg
39-
copy_assets_to_dmg
40-
# Give some time to the system so attached dmg shows up in Finder
41-
sleep 5
42-
set_volume_icon
43-
prettify_dmg
44-
compress_dmg
27+
create_volume_icon
28+
create_compressed_dmg
4529
set_dmg_icon
4630
verify_dmg
47-
remove_writable_dmg
4831
end
4932

5033
#
@@ -99,15 +82,6 @@ def pkg_position(val = NULL)
9982
# @!endgroup
10083
# --------------------------------------------------
10184

102-
#
103-
# The path where the MSI resources will live.
104-
#
105-
# @return [String]
106-
#
107-
def resources_dir
108-
File.expand_path("#{staging_dir}/Resources")
109-
end
110-
11185
#
11286
# Cleans any previously left over mounted disks.
11387
#
@@ -133,64 +107,13 @@ def clean_disks
133107
end
134108
end
135109

136-
#
137-
# Create a writable dmg we can put assets on.
138-
#
139-
def create_writable_dmg
140-
log.info(log_key) { "Creating writable dmg" }
141-
142-
shellout! <<-EOH.gsub(/^ {8}/, "")
143-
hdiutil create \\
144-
-volname "#{volume_name}" \\
145-
-fs HFS+ \\
146-
-fsargs "-c c=64,a=16,e=16" \\
147-
-size 512000k \\
148-
"#{writable_dmg}" \\
149-
-puppetstrings
150-
EOH
151-
end
152-
153-
#
154-
# Attach the dmg, storing a reference to the device for later use.
155-
#
156-
# @return [String]
157-
# the name of the attached device
158-
#
159-
def attach_dmg
160-
@device ||= Dir.chdir(staging_dir) do
161-
log.info(log_key) { "Attaching dmg as disk" }
162-
163-
cmd = shellout! <<-EOH.gsub(/^ {10}/, "")
164-
hdiutil attach \\
165-
-puppetstrings \\
166-
-readwrite \\
167-
-noverify \\
168-
-noautoopen \\
169-
"#{writable_dmg}" | egrep '^/dev/' | sed 1q | awk '{print $1}'
170-
EOH
171-
172-
cmd.stdout.strip
173-
end
174-
end
175-
176-
#
177-
# Copy assets to dmg
178-
#
179-
def copy_assets_to_dmg
180-
log.info(log_key) { "Copying assets into dmg" }
181-
182-
FileSyncer.glob("#{resources_dir}/*").each do |file|
183-
FileUtils.cp_r(file, "/Volumes/#{volume_name}")
184-
end
185-
end
186-
187110
#
188111
# Create the icon for the volume using sips.
189112
#
190113
# @return [void]
191114
#
192-
def set_volume_icon
193-
log.info(log_key) { "Setting volume icon" }
115+
def create_volume_icon
116+
log.info(log_key) { "Creating volume icon" }
194117

195118
icon = resource_path("icon.png")
196119

@@ -209,70 +132,29 @@ def set_volume_icon
209132
sips -z 512 512 #{icon} --out tmp.iconset/icon_512x512.png
210133
sips -z 1024 1024 #{icon} --out tmp.iconset/icon_512x512@2x.png
211134
iconutil -c icns tmp.iconset
212-
213-
# Copy it over
214-
cp tmp.icns "/Volumes/#{volume_name}/.VolumeIcon.icns"
215-
216-
# Source the icon
217-
SetFile -a C "/Volumes/#{volume_name}"
218135
EOH
219136
end
220137
end
221138

222139
#
223-
# Use Applescript to setup the DMG with pretty logos and colors.
140+
# Create a compressed dmg.
224141
#
225-
# @return [void]
226-
#
227-
def prettify_dmg
228-
log.info(log_key) { "Making the dmg all pretty and stuff" }
229-
230-
render_template(resource_path("create_dmg.osascript.erb"),
231-
destination: "#{staging_dir}/create_dmg.osascript",
232-
variables: {
233-
volume_name: volume_name,
234-
pkg_name: packager.package_name,
235-
window_bounds: window_bounds,
236-
pkg_position: pkg_position,
237-
})
238-
239-
Dir.chdir(staging_dir) do
240-
shellout! <<-EOH.gsub(/^ {10}/, "")
241-
osascript "#{staging_dir}/create_dmg.osascript"
242-
EOH
243-
end
244-
end
245-
246-
#
247-
# Compress the dmg using hdiutil and zlib. zlib offers better compression
248-
# levels than bzip2 (10.4+) or LZFSE (10.11+), but takes longer to compress.
249-
# We're willing to trade slightly longer build times for smaller package sizes.
250-
#
251-
# @return [void]
252-
#
253-
def compress_dmg
254-
log.info(log_key) { "Compressing dmg" }
142+
def create_compressed_dmg
143+
log.info(log_key) { "Creating compressed dmg" }
255144

256-
Dir.chdir(staging_dir) do
257-
shellout! <<-EOH.gsub(/^ {10}/, "")
258-
chmod -Rf go-w "/Volumes/#{volume_name}"
259-
sync
260-
hdiutil unmount "#{@device}"
261-
# Give some time to the system so unmount dmg
262-
ATTEMPTS=1
263-
until [ $ATTEMPTS -eq 6 ] || hdiutil detach "#{@device}"; do
264-
sleep 10
265-
echo Attempt number $(( ATTEMPTS++ ))
266-
done
267-
hdiutil convert \\
268-
"#{writable_dmg}" \\
269-
-format UDZO \\
270-
-imagekey \\
271-
zlib-level=9 \\
272-
-o "#{package_path}" \\
273-
-puppetstrings
274-
EOH
275-
end
145+
shellout! <<-EOH.gsub(/^ {8}/, "")
146+
pip install dmgbuild==1.6.5
147+
dmgbuild \\
148+
--detach-retries=5 \\
149+
--settings="#{resource_path('settings.py')}" \\
150+
-Dbackground="#{resource_path('background.png')}" \\
151+
-Dpkg="#{packager.package_path}" \\
152+
-Dpkg_position="#{pkg_position}" \\
153+
-Dvolume_icon="#{staging_dir}/tmp.icns" \\
154+
-Dwindow_bounds="#{window_bounds}" \\
155+
"#{volume_name}" \\
156+
"#{package_path}"
157+
EOH
276158
end
277159

278160
#
@@ -292,21 +174,6 @@ def verify_dmg
292174
end
293175
end
294176

295-
#
296-
# Remove writable dmg.
297-
#
298-
# @return [void]
299-
#
300-
def remove_writable_dmg
301-
log.info(log_key) { "Removing writable dmg" }
302-
303-
Dir.chdir(staging_dir) do
304-
shellout! <<-EOH.gsub(/^ {10}/, "")
305-
rm -rf "#{writable_dmg}"
306-
EOH
307-
end
308-
end
309-
310177
#
311178
# Set the dmg icon to our custom icon.
312179
#
@@ -338,13 +205,6 @@ def package_name
338205
packager.package_name.sub(extname, ".dmg")
339206
end
340207

341-
# The path to the writable dmg on disk.
342-
#
343-
# @return [String]
344-
def writable_dmg
345-
File.expand_path("#{staging_dir}/#{project.name}-writable.dmg")
346-
end
347-
348208
#
349209
# The name of the volume to create. By defauly, this is the project's
350210
# friendly name.

resources/dmg/create_dmg.osascript.erb

Lines changed: 0 additions & 17 deletions
This file was deleted.

resources/dmg/settings.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from os.path import basename as _basename
2+
3+
4+
def _to_rect(bounds):
5+
"""See the TIP section of: https://www.macosxautomation.com/applescript/firsttutorial/11.html"""
6+
bounds = [int(s) for s in bounds.split(",")]
7+
return [bounds[:2], [bounds[2] - bounds[0], bounds[3] - bounds[1]]]
8+
9+
10+
# Settings reference: https://dmgbuild.readthedocs.io/en/latest/settings.html
11+
# Sample w/ defaults: https://dmgbuild.readthedocs.io/en/latest/example.html
12+
13+
format = "UDZO"
14+
compression_level = 9
15+
filesystem = "HFS+"
16+
size = "512000k"
17+
18+
icon = defines["volume_icon"]
19+
_pkg = defines["pkg"]
20+
files = [_pkg]
21+
22+
# set current view of container window to icon view
23+
default_view = "icon-view"
24+
# set toolbar visible of container window to false
25+
show_toolbar = False
26+
# set statusbar visible of container window to false
27+
show_status_bar = False
28+
# set the bounds of container window to {<%= window_bounds %>}
29+
window_rect = _to_rect(defines["window_bounds"])
30+
# set theViewOptions to the icon view options of container window
31+
include_icon_view_settings = True
32+
# set arrangement of theViewOptions to not arranged
33+
arrange_by = None
34+
# set icon size of theViewOptions to 72
35+
icon_size = 72
36+
# set background picture of theViewOptions to file ".support:background.png"
37+
background = defines["background"]
38+
# set position of item "<%= pkg_name %>" of container window to {<%= pkg_position %>}
39+
icon_locations = {(_basename(_pkg)): [int(s) for s in defines["pkg_position"].split(",")]}

0 commit comments

Comments
 (0)