rastrillo / native Public

Clone
git clone https://amadan.net/rastrillo/native

Plain git — no account needed to clone.

Download

Download this file

1"""Bundle a SwiftPM app with the suite's padded macOS icon silhouette."""
2from pathlib import Path
3import argparse, json, plistlib, shutil, subprocess
4from mac_updates import configure, embed_sparkle, sign_bundle
5parser = argparse.ArgumentParser()
6parser.add_argument("native", type=Path)
7parser.add_argument("name")
8parser.add_argument("--configuration", choices=["debug", "release"], default="debug")
9parser.add_argument("--updates-config", type=Path)
10parser.add_argument("--version", default="0.1.0")
11parser.add_argument("--build", default="1")
12parser.add_argument("--sign-identity", default="-")
13args = parser.parse_args()
14root = args.native.resolve()
15name = args.name
16# The notice is keyed on the app having LICENSE.lucide (copied below), so an
17# app that draws Lucide and lost the file would ship without the notice it
18# owes, silently. Refuse that before building: any Swift source or SVG in
19# the app that names Lucide means the app draws it, and must carry it.
20lucide = root / "LICENSE.lucide"
21if not lucide.is_file():
22 drawn = [path for directory in ("apple/Sources", "ios", "macos") if (root / directory).is_dir()
23 for path in sorted((root / directory).rglob("*")) if path.suffix in (".swift", ".svg") and path.is_file()
24 and "lucide" in path.read_text(encoding="utf-8", errors="replace").lower()]
25 if drawn:
26 raise SystemExit("bundle-mac: " + ", ".join(str(p.relative_to(root)) for p in drawn)
27 + " draw Lucide, but the app has no LICENSE.lucide to carry its notice; restore it")
28subprocess.run(["swift", "build", "--package-path", str(root / "apple"), "--configuration", args.configuration, "--product", f"{name}Mac"], check=True)
29manifest = json.loads(subprocess.check_output(["swift", "package", "--package-path", str(root / "apple"), "dump-package"]))
30minimum_os = next((p["version"] for p in manifest.get("platforms", []) if p["platformName"] == "macos"), "14.0")
31products = Path(subprocess.check_output(["swift", "build", "--package-path", str(root / "apple"), "--configuration", args.configuration, "--show-bin-path"], text=True).strip())
32bundle = root / f"build/Oficina {name}.app/Contents"
33if bundle.parent.exists():
34 shutil.rmtree(bundle.parent)
35for directory in ["MacOS", "Resources"]:
36 (bundle / directory).mkdir(parents=True, exist_ok=True)
37shutil.copy2(products / f"{name}Mac", bundle / f"MacOS/{name}Mac")
38for resource in products.glob("*.bundle"):
39 target = bundle / "Resources" / resource.name
40 if target.exists():
41 shutil.rmtree(target)
42 shutil.copytree(resource, target)
43# Lucide's notice travels with the app only when the app has one to give:
44# it attaches to copies of Lucide's drawings, and an app that draws none
45# (its icons its own, the rest system symbols) owes no one the notice. An
46# app that draws Lucide without the file was refused above.
47if lucide.is_file():
48 shutil.copy2(lucide, bundle / "Resources/LICENSE.lucide")
49icon = root / "build/MacIcon.png"
50subprocess.run(["swift", str(Path(__file__).with_name("mac-icon.swift")), str(root / "ios/Assets.xcassets/AppIcon.appiconset/AppIcon.png"), str(icon)], check=True)
51iconset = root / "build/AppIcon.iconset"
52iconset.mkdir(exist_ok=True)
53for size in [16, 32, 128, 256, 512]:
54 for scale in [1, 2]:
55 filename = f"icon_{size}x{size}" + ("@2x" if scale == 2 else "") + ".png"
56 subprocess.run(["sips", "-z", str(size * scale), str(size * scale), str(icon), "--out", str(iconset / filename)], check=True, stdout=subprocess.DEVNULL)
57subprocess.run(["iconutil", "-c", "icns", str(iconset), "-o", str(bundle / "Resources/AppIcon.icns")], check=True)
58(bundle / "Info.plist").write_bytes(plistlib.dumps({
59 "CFBundleName": f"Oficina {name}", "CFBundleDisplayName": f"Oficina {name}",
60 "CFBundleIdentifier": f"net.amadan.oficina.{name.lower()}",
61 "CFBundleExecutable": f"{name}Mac", "CFBundlePackageType": "APPL",
62 "CFBundleIconFile": "AppIcon.icns", "CFBundleShortVersionString": "0.1.0",
63 "CFBundleVersion": "1", "LSMinimumSystemVersion": minimum_os,
64 "NSHighResolutionCapable": True,
65}))
66configure(bundle.parent, args.updates_config, args.version, args.build)
67embedded = embed_sparkle(bundle.parent, root / "apple")
68if args.updates_config and not embedded:
69 raise ValueError("configured updater requires an embedded Sparkle framework")
70sign_bundle(bundle.parent, args.sign_identity)
71print(bundle.parent)
72