rastrillo / native Public

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

Plain git — no account needed to clone.

Download

Download this file

1"""A packaged app must load resources after its original build tree is gone."""
2import plistlib
3import json
4from pathlib import Path
5import shutil
6import struct
7import subprocess
8import tempfile
9import unittest
10import zlib
11
12
13class BundleMacTests(unittest.TestCase):
14 def test_relocated_resource_bundle_and_deployment_target(self):
15 tool = Path(__file__).resolve().parents[1] / "bundle-mac.py"
16 with tempfile.TemporaryDirectory(prefix="native-bundle-test-") as temporary:
17 root = Path(temporary)
18 source = root / "apple/Sources/Probe"
19 (source / "Resources").mkdir(parents=True)
20 (root / "apple/Package.swift").write_text('''// swift-tools-version: 5.9
21import PackageDescription
22let package = Package(name: "Probe", platforms: [.macOS("26.0")],
23 products: [.executable(name: "ProbeMac", targets: ["Probe"])],
24 dependencies: [.package(name: "native", path: SHARED_NATIVE_PATH)],
25 targets: [.executableTarget(name: "Probe", dependencies: [.product(name: "RastrilloMacUpdates", package: "native")], resources: [.process("Resources")])])
26'''.replace('SHARED_NATIVE_PATH', json.dumps(str(tool.parents[1]))))
27 (source / "main.swift").write_text('''import Foundation
28import RastrilloMacUpdates
29let updater = MainActor.assumeIsolated { NativeUpdater() }
30MainActor.assumeIsolated {
31 precondition(!updater.isConfigured)
32 _ = NativeUpdateCommands(updater: updater).body
33}
34let resources = Bundle.main.url(forResource: "Probe_Probe", withExtension: "bundle").flatMap(Bundle.init(url:)) ?? Bundle.module
35guard let url = resources.url(forResource: "sentinel", withExtension: "txt"),
36 let text = try? String(contentsOf: url, encoding: .utf8) else { exit(7) }
37print(text)
38''')
39 (source / "Resources/sentinel.txt").write_text("portable native resources")
40 icons = root / "ios/Assets.xcassets/AppIcon.appiconset"
41 icons.mkdir(parents=True)
42
43 def chunk(kind, payload):
44 return struct.pack(">I", len(payload)) + kind + payload + struct.pack(">I", zlib.crc32(kind + payload))
45
46 png = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", 1024, 1024, 8, 6, 0, 0, 0))
47 png += chunk(b"IDAT", zlib.compress((b"\0" + b"\x30\x70\xb0\xff" * 1024) * 1024)) + chunk(b"IEND", b"")
48 (icons / "AppIcon.png").write_bytes(png)
49 # An app that draws no Lucide icon carries no Lucide notice: the
50 # notice is copied only when the app has one to give.
51 subprocess.run(["python3", str(tool), str(root), "Probe"], check=True, stdout=subprocess.DEVNULL)
52 self.assertFalse((root / "build/Oficina Probe.app/Contents/Resources/LICENSE.lucide").exists())
53 (root / "LICENSE.lucide").write_text("Synthetic packaging test fixture")
54 subprocess.run(["python3", str(tool), str(root), "Probe"], check=True, stdout=subprocess.DEVNULL)
55 self.assertEqual((root / "build/Oficina Probe.app/Contents/Resources/LICENSE.lucide").read_text(), "Synthetic packaging test fixture")
56 app = root / "relocated/Probe.app"
57 app.parent.mkdir()
58 shutil.move(root / "build/Oficina Probe.app", app)
59 shutil.rmtree(root / "apple")
60 info = plistlib.loads((app / "Contents/Info.plist").read_bytes())
61 self.assertEqual(info["LSMinimumSystemVersion"], "26.0")
62 output = subprocess.check_output([str(app / "Contents/MacOS/ProbeMac")], text=True)
63 self.assertEqual(output.strip(), "portable native resources")
64 subprocess.run(["codesign", "--verify", "--strict", "--deep", str(app)], check=True)
65
66 def test_an_app_that_draws_lucide_cannot_lose_its_notice(self):
67 """A notice owed and missing stops the bundle rather than shipping without it."""
68 tool = Path(__file__).resolve().parents[1] / "bundle-mac.py"
69 with tempfile.TemporaryDirectory(prefix="native-bundle-lucide-") as temporary:
70 root = Path(temporary)
71 source = root / "apple/Sources/Probe"
72 source.mkdir(parents=True)
73 (root / "apple/Package.swift").write_text("// swift-tools-version: 5.9\n")
74 # A Lucide drawing in the app's sources, and no LICENSE.lucide.
75 (source / "Icons.swift").write_text("// Path drawn from Lucide's \"file-text\" icon.\nlet x = 1\n")
76 result = subprocess.run(["python3", str(tool), str(root), "Probe"], capture_output=True, text=True)
77 self.assertNotEqual(result.returncode, 0)
78 self.assertIn("LICENSE.lucide", result.stderr)
79 self.assertIn("Icons.swift", result.stderr)
80 self.assertFalse((root / "build").exists(), "nothing is built before the refusal")
81
82
83if __name__ == "__main__":
84 unittest.main()
85