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