Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/build_config/buildMacOSInstaller.py
193904 views
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
4
# Copyright (C) 2008-2026 German Aerospace Center (DLR) and others.
5
# This program and the accompanying materials are made available under the
6
# terms of the Eclipse Public License 2.0 which is available at
7
# https://www.eclipse.org/legal/epl-2.0/
8
# This Source Code may also be made available under the following Secondary
9
# Licenses when the conditions for such availability set forth in the Eclipse
10
# Public License 2.0 are satisfied: GNU General Public License, version 2
11
# or later which is available at
12
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
13
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
14
15
# @file buildMacOSInstaller.py
16
# @author Robert Hilbrich
17
# @date 2024-07-16
18
19
# Creates the macOS installer for the current version of SUMO.
20
21
import os
22
import plistlib
23
import shutil
24
import subprocess
25
import sys
26
import tempfile
27
from glob import iglob
28
29
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
30
from sumolib.options import ArgumentParser # noqa
31
32
from build_config.version import get_pep440_version # noqa
33
34
try:
35
from delocate.cmd.delocate_path import delocate_path
36
except ImportError:
37
print("Error: delocate module is not installed. Please install it using 'pip install delocate'.")
38
sys.exit(1)
39
40
try:
41
from dmgbuild.core import build_dmg
42
except ImportError:
43
print("Error: dmgbuild module is not installed. Please install it using 'pip install dmgbuild'.")
44
sys.exit(1)
45
46
47
def parse_args(def_dmg_name, def_pkg_name):
48
op = ArgumentParser(description="Build an installer for macOS (dmg file)")
49
50
# We can set one these actions exclusively
51
action_group = op.add_mutually_exclusive_group("Actions")
52
action_group.add_argument("--create-framework-dir", action="store_true")
53
action_group.add_argument("--create-framework-pkg", action="store_true")
54
action_group.add_argument("--create-apps-dir", action="store_true")
55
action_group.add_argument("--create-apps-pkg", action="store_true")
56
action_group.add_argument("--create-installer-pkg", action="store_true")
57
action_group.add_argument("--create-installer-dmg", action="store_true")
58
action_group.add_argument("--all", action="store_true")
59
60
# ... and supply some arguments
61
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
62
def_output_dmg_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", def_dmg_name))
63
op.add_argument("--build-dir", default=os.path.join(base_dir, "sumo-build"))
64
op.add_argument("--framework-dir", default=os.path.join(base_dir, "framework"))
65
op.add_argument("--framework-pkg-dir", default=os.path.join(base_dir, "framework-pkg"))
66
op.add_argument("--apps-dir", default=os.path.join(base_dir, "apps"))
67
op.add_argument("--apps-pkg-dir", default=os.path.join(base_dir, "apps-pkg"))
68
op.add_argument("--installer-pkg-file", default=os.path.join(base_dir, "installer", def_pkg_name))
69
op.add_argument("--installer-dmg-file", default=def_output_dmg_path)
70
op.add_argument("--clean", action="store_true")
71
72
args = op.parse_args()
73
74
# Validate the basic argument logic
75
if args.build_dir is not None and args.create_framework_dir is None and args.all is None:
76
print("Error: build directory can only be set when creating the framework directory.", file=sys.stderr)
77
sys.exit(1)
78
79
if args.framework_pkg_dir is not None and args.create_framework_pkg is None and args.all is None:
80
print("Error: framework pkg directory can only be set when creating the framework pkg.", file=sys.stderr)
81
sys.exit(1)
82
83
if args.apps_dir is not None and args.create_apps_dir is None and args.all is None:
84
print("Error: apps directory can only be set when creating the apps.", file=sys.stderr)
85
sys.exit(1)
86
87
return args
88
89
90
def create_framework_dir(name, longname, pkg_id, version, sumo_build_directory, framework_output_dir):
91
# Create the directory structure for the framework bundle
92
# see: https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html # noqa
93
#
94
# EclipseSUMO.framework/
95
# ├── EclipseSUMO --> Versions/Current/EclipseSUMO
96
# ├── Resources --> Versions/Current/Resources
97
# └── Versions
98
# ├── v1_20_0
99
# │ ├── EclipseSUMO
100
# │ └── Resources
101
# │ └── Info.plist
102
# └── Current --> v_1_20_0
103
104
print(" - Creating directory structure")
105
os.makedirs(framework_output_dir, exist_ok=False)
106
107
framework_dir = os.path.join(framework_output_dir, f"{name}.framework")
108
version_dir = os.path.join(framework_dir, f"Versions/{version}")
109
os.makedirs(os.path.join(version_dir, name), exist_ok=True)
110
os.makedirs(os.path.join(version_dir, "Resources"), exist_ok=True)
111
112
os.symlink(f"{version}/", os.path.join(framework_dir, "Versions/Current"), True)
113
os.symlink(f"Versions/Current/{name}/", os.path.join(framework_dir, name), True)
114
os.symlink("Versions/Current/Resources/", os.path.join(framework_dir, "Resources"), True)
115
116
# Create the Info.plist file
117
plist_file = os.path.join(version_dir, "Resources", "Info.plist")
118
print(" - Creating properties list")
119
plist_content = {
120
"CFBundleExecutable": longname,
121
"CFBundleIdentifier": pkg_id,
122
"CFBundleName": longname,
123
"CFBundleVersion": version,
124
"CFBundleShortVersionString": version,
125
}
126
with open(plist_file, "wb") as f:
127
plistlib.dump(plist_content, f)
128
129
# Copy files from the current repository clone to version_dir/EclipseSUMO
130
print(" - Installing Eclipse SUMO build")
131
cmake_install_command = [
132
"cmake",
133
"--install",
134
sumo_build_directory,
135
"--prefix",
136
os.path.join(version_dir, name),
137
]
138
subprocess.run(cmake_install_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
139
140
# Remove unneeded libsumo and libtraci folders from the tools directory
141
# (If the user needs libsumo or libtraci, they should install these tools with pip)
142
print(" - Removing libsumo and libtraci folders from tools")
143
shutil.rmtree(os.path.join(version_dir, name, "share", "sumo", "tools", "libsumo"), ignore_errors=True)
144
shutil.rmtree(os.path.join(version_dir, name, "share", "sumo", "tools", "libtraci"), ignore_errors=True)
145
146
# We need to add a symlink to the binary folder to have the same folder structure
147
os.symlink("../../bin", os.path.join(version_dir, name, "share", "sumo", "bin"))
148
149
# Determine library dependencies
150
print(" - Delocating binaries and libraries")
151
os.chdir(os.path.join(version_dir, name))
152
153
# - libraries that landed in the lib folder need to be delocated as well
154
lib_dir = os.path.join(version_dir, name, "lib")
155
bin_dir = os.path.join(version_dir, name, "bin")
156
for pattern in ("*.jnilib", "*.dylib"):
157
for file in iglob(os.path.join(lib_dir, pattern)):
158
file_name = os.path.basename(file)
159
shutil.move(os.path.join(lib_dir, file_name), os.path.join(bin_dir, file_name))
160
161
# Start the delocation of the libraries and binaries
162
delocate_path("./bin", lib_filt_func=None, lib_path="./lib", sanitize_rpaths=True)
163
164
# - and we need to move them back to the lib folder
165
for pattern in ("*.jnilib", "*.dylib"):
166
for file in iglob(os.path.join(bin_dir, pattern)):
167
file_name = os.path.basename(file)
168
shutil.move(os.path.join(bin_dir, file_name), os.path.join(lib_dir, file_name))
169
170
# Add proj db files from /opt/homebrew/Cellar/proj/<X.Y.Z>/share/proj
171
print(" - Copying additional files (e.g. proj.db)")
172
proj_dir = "/opt/homebrew/Cellar/proj"
173
proj_file_list = ["GL27", "ITRF2000", "ITRF2008", "ITRF2014", "nad.lst", "nad27", "nad83", "other.extra", "proj.db",
174
"proj.ini", "projjson.schema.json", "triangulation.schema.json", "world", "CH", "deformation_model.schema.json"] # noqa
175
dest_dir = os.path.join(version_dir, name, "share", "proj")
176
if os.path.exists(proj_dir):
177
first_dir = next(iter(os.listdir(proj_dir)), None)
178
if first_dir:
179
source_dir = os.path.join(proj_dir, first_dir, "share/proj")
180
if os.path.exists(source_dir):
181
os.makedirs(dest_dir, exist_ok=True)
182
for file in proj_file_list:
183
shutil.copy2(os.path.join(source_dir, file), os.path.join(dest_dir, file))
184
185
186
def create_framework_pkg(name, pkg_id, version, framework_dir, framework_pkg_dir):
187
# Build the framework package
188
os.makedirs(framework_pkg_dir, exist_ok=False)
189
pkg_name = f"{name}-{version}.pkg"
190
pkg_path = os.path.join(framework_pkg_dir, pkg_name)
191
pkg_build_command = [
192
"pkgbuild",
193
"--root",
194
os.path.join(framework_dir, f"{name}.framework"),
195
"--identifier",
196
pkg_id,
197
"--version",
198
version,
199
"--install-location",
200
f"/Library/Frameworks/{name}.framework",
201
f"{pkg_path}",
202
]
203
print(f" - Using the call {pkg_build_command}")
204
print(f" - Calling pkgbuild to create \"{pkg_path}\"")
205
subprocess.run(pkg_build_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
206
pkg_size = os.path.getsize(pkg_path)
207
return name, pkg_name, pkg_id, pkg_path, pkg_size
208
209
210
def create_app_dir(app_name, exec_call, framework_name, pkg_id, version, icns_path, app_output_dir):
211
# Example app structure:
212
# SUMO-GUI.app
213
# └── Contents
214
# └── Info.plist
215
# ├── MacOS
216
# │   └── SUMO-GUI
217
# └── Resources
218
# └── iconsfile.icns
219
220
print(" . Creating directory structure")
221
os.makedirs(os.path.join(app_output_dir, f"{app_name}.app", "Contents", "MacOS"))
222
os.makedirs(os.path.join(app_output_dir, f"{app_name}.app", "Contents", "Resources"))
223
224
print(" . Creating launcher")
225
launcher_content = f"""#!/bin/bash
226
export SUMO_HOME="/Library/Frameworks/{framework_name}.framework/Versions/Current/{framework_name}/share/sumo"
227
{exec_call}
228
"""
229
launcher_path = os.path.join(app_output_dir, f"{app_name}.app", "Contents", "MacOS", app_name)
230
with open(launcher_path, "w") as launcher:
231
launcher.write(launcher_content)
232
os.chmod(launcher_path, 0o755)
233
234
# Copy the icons
235
print(" . Copying icons")
236
shutil.copy(icns_path, os.path.join(app_output_dir, f"{app_name}.app", "Contents", "Resources", "iconfile.icns"))
237
238
# Create plist file
239
print(" . Creating properties file")
240
plist_file = os.path.join(app_output_dir, f"{app_name}.app", "Contents", "Info.plist")
241
plist_content = {
242
"CFBundleExecutable": app_name,
243
"CFBundleIdentifier": pkg_id,
244
"CFBundleName": app_name,
245
"CFBundleVersion": version,
246
"CFBundleShortVersionString": version,
247
"CFBundleIconFile": "iconfile.icns",
248
}
249
with open(plist_file, "wb") as f:
250
plistlib.dump(plist_content, f)
251
252
253
def create_app_pkg(app_name, pkg_id, version, app_dir, apps_pkg_dir):
254
pkg_name = f"Launcher-{app_name}-{version}.pkg"
255
pkg_path = os.path.join(apps_pkg_dir, pkg_name)
256
pkg_build_command = [
257
"pkgbuild",
258
"--root",
259
os.path.join(app_dir, f"{app_name}.app"),
260
"--identifier",
261
pkg_id,
262
"--version",
263
version,
264
"--install-location",
265
f"/Applications/{app_name}.app",
266
f"{pkg_path}",
267
]
268
subprocess.run(pkg_build_command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
269
pkg_size = os.path.getsize(pkg_path)
270
return app_name, pkg_name, pkg_id, pkg_path, pkg_size
271
272
273
def create_installer(framework_pkg, apps_pkg, version, installer_pkg_file):
274
""""Creates the installer package
275
276
framework_pkg: framework info [framework_path, framework_id]
277
apps_pkg: apps info [[app1_path, app1_id], [app2_path, app2_id], ...]
278
id: id of the pkg-file for the installer
279
version: 1.20.0
280
installer_pkg_file: name of the output pkg file
281
"""
282
283
# Create a temporary directory to assemble everything for the installer
284
temp_dir = tempfile.mkdtemp()
285
286
# Copy the framework pkg file and the launcher apps pkg files
287
shutil.copy(framework_pkg[0], temp_dir)
288
for app_pkg in apps_pkg:
289
shutil.copy(app_pkg[0], temp_dir)
290
291
# Add license, background and other nice stuff
292
resources_dir = os.path.join(temp_dir, "Resources")
293
os.makedirs(resources_dir)
294
sumo_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..")
295
sumo_data_installer_dir = os.path.join(sumo_dir, "build_config", "macos", "installer")
296
shutil.copy(os.path.join(sumo_data_installer_dir, "background.png"), resources_dir)
297
shutil.copy(os.path.join(sumo_dir, "LICENSE"), os.path.join(resources_dir, "LICENSE.txt"))
298
299
# Create conclusion.html in the installer resources folder
300
with open(os.path.join(resources_dir, "conclusion.html"), "w") as file:
301
file.write("""<!DOCTYPE html>
302
<html lang="en">
303
<head>
304
<meta charset="UTF-8">
305
<style>
306
body {
307
font-family: Helvetica;
308
font-size: 14px;
309
}
310
</style>
311
</head>
312
<body>
313
<div>
314
<h4>Important:</h4>
315
<ul>
316
<li>
317
For applications with a graphical user interface to function properly, please ensure
318
you have <b>XQuartz</b> installed.
319
It can be obtained from: <a href="https://www.xquartz.org" target="_blank">XQuartz</a>.
320
</li>
321
<li>
322
You may need to install Python 3, if it is not installed yet. Python is required for the
323
Scenario Wizard and other tools.
324
</li>
325
<li>
326
If you intend to use SUMO from the command line, please remember to set
327
the <b>SUMO_HOME</b> environment variable and add it to the <b>PATH</b> variable.
328
<br>
329
For more details, visit the
330
<a href="https://sumo.dlr.de/docs/Installing/index.html#macos" target="_blank">
331
SUMO macOS installation guide
332
</a>.
333
</li>
334
</ul>
335
</p>
336
<p>For support options, including the "sumo-user" mailing list, please visit:
337
<a href="https://eclipse.dev/sumo/contact/" target="_blank">SUMO Contact</a>.
338
</p>
339
</div>
340
</body>
341
</html>
342
""")
343
344
# Create distribution.xml
345
print(" - Creating distribution.xml")
346
size = os.path.getsize(framework_pkg[0]) // 1024
347
path = os.path.basename(framework_pkg[0])
348
refs = f" <pkg-ref id='{framework_pkg[1]}' version='{version}' installKBytes='{size}'>{path}</pkg-ref>"
349
350
for app_pkg in apps_pkg:
351
size = os.path.getsize(app_pkg[0]) // 1024
352
path = os.path.basename(app_pkg[0])
353
refs += f"\n <pkg-ref id='{app_pkg[1]}' version='{version}' installKBytes='{size}'>{path}</pkg-ref>"
354
355
# See: https://developer.apple.com/library/archive/documentation/
356
# DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html
357
distribution_content = f"""<?xml version="1.0" encoding="utf-8"?>
358
<installer-gui-script minSpecVersion="2">
359
<title>Eclipse SUMO</title>
360
<allowed-os-versions><os-version min="10.14"/></allowed-os-versions>
361
<license file="LICENSE.txt"/>
362
<background file="background.png" alignment="bottomleft" mime-type="image/png" scaling="none" />
363
<conclusion file="conclusion.html" mime-type="text/html"/>
364
<options customize="allow" require-scripts="false" rootVolumeOnly="true" hostArchitectures="arm64"/>
365
<choices-outline>
366
<line choice="default"/>
367
</choices-outline>
368
<choice id="default" title="Eclipse SUMO {version}">
369
{refs}
370
</choice>
371
</installer-gui-script>
372
"""
373
distribution_path = os.path.join(temp_dir, "distribution.xml")
374
with open(distribution_path, "w") as f:
375
f.write(distribution_content)
376
377
# Call productbuild
378
print(" - Calling productbuild")
379
productbuild_command = [
380
"productbuild",
381
"--distribution",
382
distribution_path,
383
"--package-path",
384
temp_dir,
385
"--resources",
386
resources_dir,
387
installer_pkg_file,
388
]
389
subprocess.run(productbuild_command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
390
391
# Removing temporary build directory
392
print(" - Cleaning up")
393
shutil.rmtree(temp_dir)
394
395
396
def create_dmg(dmg_title, version, installer_pkg_path, installer_dmg_path):
397
398
if os.path.exists(installer_dmg_path):
399
print(" - Removing existing disk image before creating a new disk image")
400
os.remove(installer_dmg_path)
401
402
print(" - Preparing disk image folder")
403
dmg_prep_folder = tempfile.mkdtemp()
404
405
# Copy the installer pkg
406
shutil.copy(installer_pkg_path, dmg_prep_folder)
407
408
# Add the uninstall script
409
uninstall_script_path = os.path.join(dmg_prep_folder, "uninstall.command")
410
with open(uninstall_script_path, "w") as f:
411
f.write("""#!/bin/bash
412
echo "This will uninstall Eclipse SUMO and its components."
413
read -p "Are you sure? (y/N): " CONFIRM
414
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
415
echo "Uninstallation aborted."
416
exit 0
417
fi
418
419
# Request sudo privileges
420
osascript -e 'do shell script "sudo -v" with administrator privileges'
421
422
# Define installed paths
423
FRAMEWORK="/Library/Frameworks/EclipseSUMO.framework"
424
APP1="/Applications/SUMO sumo-gui.app"
425
APP2="/Applications/SUMO netedit.app"
426
APP3="/Applications/SUMO Scenario Wizard.app"
427
428
# Remove framework
429
if [ -d "$FRAMEWORK" ]; then
430
echo "Removing framework: $FRAMEWORK"
431
sudo rm -rf "$FRAMEWORK"
432
else
433
echo "Framework not found: $FRAMEWORK"
434
fi
435
436
# Remove apps
437
for APP in "$APP1" "$APP2" "$APP3"; do
438
if [ -d "$APP" ]; then
439
echo "Removing application: $APP"
440
sudo rm -rf "$APP"
441
else
442
echo "Application not found: $APP"
443
fi
444
done
445
446
echo "Eclipse SUMO has been successfully uninstalled!"
447
exit 0
448
""")
449
# Make the script executable
450
os.chmod(uninstall_script_path, 0o755)
451
452
# Collect all files and add to the dmg
453
print(" - Collecting files and calculating file size")
454
files_to_store = []
455
total_size = 0
456
for root, _, files in os.walk(dmg_prep_folder):
457
for file in files:
458
files_to_store.append((os.path.join(root, file), file))
459
total_size += os.path.getsize(os.path.join(root, file))
460
461
print(" - Building diskimage")
462
settings = {
463
"volume_name": f"Eclipse SUMO {version}",
464
"size": f"{total_size // 1024 * 1.2}K",
465
"files": files_to_store,
466
# FIXME: add background and badge
467
}
468
build_dmg(installer_dmg_path, dmg_title, settings=settings)
469
470
print(" - Cleaning up")
471
shutil.rmtree(dmg_prep_folder)
472
473
474
def main():
475
base_id = "org.eclipse.sumo"
476
default_framework_name = "EclipseSUMO"
477
default_framework_long_name = "Eclipse SUMO"
478
version = get_pep440_version()
479
if ".post" in version:
480
version = "git"
481
default_pkg_name = f"sumo-{version}.pkg"
482
default_dmg_name = f"sumo-{version}.dmg"
483
484
# Which launcher apps do we have?
485
app_list = [
486
(
487
"SUMO sumo-gui",
488
'exec "$SUMO_HOME/bin/sumo-gui" "$@" &',
489
default_framework_name,
490
f"{base_id}.apps.sumo-gui",
491
version,
492
"sumo-gui.icns",
493
"sumo-gui"
494
),
495
(
496
"SUMO netedit",
497
'exec "$SUMO_HOME/bin/netedit" "$@" &',
498
default_framework_name,
499
f"{base_id}.apps.netedit",
500
version,
501
"netedit.icns",
502
"netedit"
503
),
504
(
505
"SUMO Scenario Wizard",
506
(
507
"python $SUMO_HOME/tools/osmWebWizard.py ||"
508
"python3 $SUMO_HOME/tools/osmWebWizard.py &"
509
),
510
default_framework_name,
511
f"{base_id}.apps.scenario-wizard",
512
version,
513
"scenario-wizard.icns",
514
"scenario-wizard"
515
),
516
]
517
518
# Parse and check the command line arguments
519
opts = parse_args(default_dmg_name, default_pkg_name)
520
521
if opts.clean:
522
for d in (opts.framework_dir, opts.framework_pkg_dir, opts.apps_dir, opts.apps_pkg_dir,
523
os.path.dirname(opts.installer_pkg_file)):
524
shutil.rmtree(d, ignore_errors=True)
525
# Let's see what we need to do
526
if opts.all or opts.create_framework_dir:
527
if os.path.exists(opts.framework_dir):
528
print(f"Directory {opts.framework_dir} already exists. Aborting.")
529
sys.exit(1)
530
if not os.path.exists(opts.build_dir):
531
print(f"Error: build directory '{opts.build_dir}' does not exist.", file=sys.stderr)
532
sys.exit(1)
533
if not os.path.exists(os.path.join(opts.build_dir, "CMakeCache.txt")):
534
print(f"Error: directory '{opts.build_dir}' is not a build directory.", file=sys.stderr)
535
sys.exit(1)
536
537
print(f"Creating {default_framework_name} framework directory: \"{opts.framework_dir}\"")
538
create_framework_dir(default_framework_name, default_framework_long_name, f"{base_id}.framework", version,
539
opts.build_dir, opts.framework_dir)
540
print(f"Successfully created {default_framework_name} framework directory")
541
542
if opts.all or opts.create_framework_pkg:
543
if os.path.exists(opts.framework_pkg_dir):
544
print(f"Directory {opts.framework_pkg_dir} already exists. Aborting.")
545
sys.exit(1)
546
if not os.path.exists(opts.framework_dir):
547
print(f"Error: framework directory '{opts.framework_dir}' does not exist.", file=sys.stderr)
548
sys.exit(1)
549
550
print(f"Creating {default_framework_name} framework *.pkg file")
551
print(f" - Using framework directory: \"{opts.framework_dir}\"")
552
_, pkg_name, _, _, pkg_size = create_framework_pkg(default_framework_name, f"{base_id}.framework", version,
553
opts.framework_dir, opts.framework_pkg_dir)
554
print(f"Successfully created \"{pkg_name}\" ({pkg_size / (1024 * 1024):.2f} MB)")
555
556
if opts.all or opts.create_apps_dir:
557
if os.path.exists(opts.apps_dir):
558
print(f"Directory {opts.apps_dir} already exists. Aborting.")
559
sys.exit(1)
560
561
print(f"Creating {default_framework_name} launcher apps directories")
562
os.makedirs(opts.apps_dir, exist_ok=False)
563
for app_name, app_binary, app_framework, app_id, app_ver, app_icons, app_folder in app_list:
564
app_dir = os.path.join(opts.apps_dir, app_folder)
565
print(f" - Building app directory for '{app_name}' in folder {app_dir}")
566
os.makedirs(app_dir)
567
icon_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..",
568
"build_config", "macos", "installer", app_icons)
569
create_app_dir(app_name, app_binary, app_framework, app_id, app_ver, icon_path, app_dir)
570
print(f" - Successfully created app directory for '{app_name}'")
571
572
if opts.all or opts.create_apps_pkg:
573
if os.path.exists(opts.apps_pkg_dir):
574
print(f"Directory {opts.apps_pkg_dir} already exists. Aborting.")
575
sys.exit(1)
576
577
print(f"Creating {default_framework_name} launcher app pkg files")
578
os.makedirs(opts.apps_pkg_dir, exist_ok=False)
579
580
for app_name, app_binary, app_framework, app_id, app_ver, app_icons, app_folder in app_list:
581
app_dir = os.path.join(opts.apps_dir, app_folder)
582
_, pkg_name, _, _, pkg_size = create_app_pkg(app_name, app_id, app_ver, app_dir, opts.apps_pkg_dir)
583
print(f" - Created \"{pkg_name}\" ({pkg_size / (1024 * 1024):.2f} MB)")
584
585
if opts.all or opts.create_installer_pkg:
586
if os.path.exists(os.path.dirname(opts.installer_pkg_file)):
587
print(f"Error: pkg output directory '{os.path.dirname(opts.installer_pkg_file)}' exists.",
588
file=sys.stderr)
589
sys.exit(1)
590
591
# Create the output directory for the installer pkg
592
os.makedirs(os.path.dirname(opts.installer_pkg_file))
593
594
print("Building installer pkg file")
595
# Where do we find our pkgs?
596
fw_pkg = [os.path.join(opts.framework_pkg_dir, f"{default_framework_name}-{version}.pkg"),
597
f"{base_id}.framework"]
598
app_pkgs = []
599
for app_name, app_binary, app_framework, app_id, app_ver, app_icons, app_folder in app_list:
600
app_pkgs.append([os.path.join(opts.apps_pkg_dir, f"Launcher-{app_name}-{version}.pkg"), app_id])
601
602
# Build the installer pkg file
603
create_installer(fw_pkg, app_pkgs, version, opts.installer_pkg_file)
604
pkg_size = os.path.getsize(opts.installer_pkg_file)
605
606
print(f"Installer pkg file created: \"{opts.installer_pkg_file}\" ({pkg_size / (1024 * 1024):.2f} MB)")
607
608
if opts.all or opts.create_installer_dmg:
609
if not os.path.exists(os.path.dirname(opts.installer_dmg_file)):
610
print(f"Error: output directory '{os.path.dirname(opts.installer_dmg_file)}' does not exist.",
611
file=sys.stderr)
612
sys.exit(1)
613
614
if not os.path.exists(opts.installer_pkg_file):
615
print(f"Error: installer pkg file '{opts.installer_pkg_file}' does not exist.",
616
file=sys.stderr)
617
sys.exit(1)
618
619
print("Building installer disk image (dmg file)")
620
create_dmg(default_framework_long_name, version, opts.installer_pkg_file, opts.installer_dmg_file)
621
pkg_size = os.path.getsize(opts.installer_dmg_file)
622
print(f"Successfully built disk image: \"{opts.installer_dmg_file}\" ({pkg_size / (1024 * 1024):.2f} MB)")
623
624
625
if __name__ == "__main__":
626
main()
627
628