Skip to content

Commit 8c680e5

Browse files
authored
Merge pull request #381 from LNPANDY/fix/shell-package-path
Fix the shared app install: pass DM_SHELL_PACKAGE and bundle frontend-ios
2 parents 947156c + 2dc3706 commit 8c680e5

3 files changed

Lines changed: 73 additions & 3 deletions

File tree

‎.github/workflows/build-mac-app.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,9 @@ jobs:
283283
# the bundled engine (own_compilers.py exit code 1). sign_engine_tree.sh signs only a Mach-O file without any signature and checks the rest.
284284
ditto "$RUNNER_TEMP/engine-tree" "$APP/Contents/Resources/engine"
285285
tools/sign_engine_tree.sh "$APP/Contents/Resources/engine" "$RUNNER_TEMP/engine-tree"
286+
# The iPhone shell's Swift package: build_shell.sh links it by DM_SHELL_PACKAGE and the launcher passes this folder (the engine has
287+
# none since #214). The step runs in frontend/, the package is one level up. No .build or .swiftpm.
288+
rsync -a --exclude .build --exclude .swiftpm ../frontend-ios/ "$APP/Contents/Resources/frontend-ios/"
286289
V="${{ steps.ver.outputs.version }}"
287290
cat > "$APP/Contents/Info.plist" <<PLIST
288291
<?xml version="1.0" encoding="UTF-8"?>
@@ -326,6 +329,8 @@ jobs:
326329
cmp "$E/.engine-id" "$RUNNER_TEMP/engine-tree/.engine-id"
327330
cmp "$E/.upstream-ref" ../.upstream-ref
328331
test -f "$E/scripts/build_ipa_from_nsp.py" && test -f "$E/dm/tools/romfs_image.py" && test -f "$E/dm/fork_patches.txt"
332+
# The iPhone shell's package (DM_SHELL_PACKAGE): the project template links it and its AppIcon.icon.
333+
test -f "NX2Apple.app/Contents/Resources/frontend-ios/Package.swift" && test -d "NX2Apple.app/Contents/Resources/frontend-ios/AppIcon.icon"
329334
test ! -e "$E/.git"
330335
# The engine files are the zip asset's, byte for byte, and spirv-cross matches dmsh_chain.lock, so a shader build from the bundled engine starts.
331336
tools/sign_engine_tree.sh --check "$E" "$RUNNER_TEMP/engine-tree"

‎frontend/Sources/NSPortBuilder/ShellInstall.swift‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,17 @@ enum ShellInstall {
8686
["/bin/bash", repo + "/dm/ios/tools/build_shell.sh", "--prepare-xcode"]
8787
}
8888

89+
/// The `frontend-ios` Swift package the shell project links (build_shell.sh's DM_SHELL_PACKAGE). The script defaults to
90+
/// `frontend-ios` beside `dm/`, which is the engine root, and an engine tree has none since #214. So: the release app's
91+
/// Contents/Resources/frontend-ios, else the source checkout's `frontend-ios` (`repo`). nil when neither has a Package.swift.
92+
static func shellPackage(resources: String?, repo: String, fm: FileManager = .default) -> String? {
93+
([resources, repo].compactMap { $0 }.filter { !$0.isEmpty }).map { $0 + "/frontend-ios" }
94+
.first { fm.fileExists(atPath: $0 + "/Package.swift") }
95+
}
96+
8997
/// What build_shell.sh reads. `path` is the build's own tool PATH (xcodegen must be found on it).
9098
static func projectEnvironment(root: String, frameworks: [String], bundleID: String, team: String, python: String,
91-
path: String, home: String, user: String, tmp: String? = nil) -> [String: String] {
99+
path: String, home: String, user: String, tmp: String? = nil, package: String? = nil) -> [String: String] {
92100
var env = ["SHELL_DIR": projectFolder(root: root),
93101
"DM_SHELL_FRAMEWORKS": frameworks.joined(separator: "\n"),
94102
"DM_BUNDLE_ID": bundleID,
@@ -97,6 +105,7 @@ enum ShellInstall {
97105
"FONT_PY": python,
98106
"PATH": path, "HOME": home, "USER": user, "LANG": "en_US.UTF-8"]
99107
if let tmp { env["TMPDIR"] = tmp }
108+
if let package { env["DM_SHELL_PACKAGE"] = package }
100109
return env
101110
}
102111

@@ -290,6 +299,8 @@ struct ShellStage {
290299
struct ShellInstaller {
291300
var xcrun = "/usr/bin/xcrun"
292301
var repo: String
302+
/// The app's Contents/Resources, where a release app carries frontend-ios. Nil for a launcher run from a checkout.
303+
var resources: String? = Bundle.main.resourceURL?.path
293304
var python: String
294305
var root: String
295306
var device: String
@@ -361,9 +372,13 @@ struct ShellInstaller {
361372
}
362373

363374
// 4: the project
375+
guard let package = ShellInstall.shellPackage(resources: resources, repo: repo) else {
376+
let looked = ([resources, repo].compactMap { $0 }.filter { !$0.isEmpty }).map { $0 + "/frontend-ios" }.joined(separator: ", ")
377+
return Outcome(ok: false, message: fmt("shell.projectFailed", "no frontend-ios Swift package (Package.swift) in " + looked))
378+
}
364379
var env = ShellInstall.projectEnvironment(root: root, frameworks: library.games.map(\.framework), bundleID: library.bundleID,
365380
team: team, python: python, path: toolPath, home: NSHomeDirectory(), user: NSUserName(),
366-
tmp: NSTemporaryDirectory())
381+
tmp: NSTemporaryDirectory(), package: package)
367382
env.merge(BundledTools.environment(for: python)) { $1 }
368383
let projectName = t("shell.projectStage")
369384
begin(Self.projectStageID, projectName)

‎frontend/Tests/NSPortBuilderTests/ShellInstallTests.swift‎

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,45 @@ final class ShellInstallTests: XCTestCase {
6363

6464
// ---- the commands ----
6565

66+
// ---- the iOS shell package (DM_SHELL_PACKAGE) ----
67+
68+
private func makeTree(_ files: [String]) throws -> String {
69+
let dir = NSTemporaryDirectory() + "shellpkg-" + UUID().uuidString
70+
for f in files {
71+
try FileManager.default.createDirectory(atPath: (dir + "/" + f as NSString).deletingLastPathComponent, withIntermediateDirectories: true)
72+
FileManager.default.createFile(atPath: dir + "/" + f, contents: Data())
73+
}
74+
try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true)
75+
addTeardownBlock { try? FileManager.default.removeItem(atPath: dir) }
76+
return dir
77+
}
78+
79+
func testShellPackagePrefersTheBundleThenTheCheckout() throws {
80+
let bundle = try makeTree(["frontend-ios/Package.swift"])
81+
let checkout = try makeTree(["frontend-ios/Package.swift"])
82+
XCTAssertEqual(ShellInstall.shellPackage(resources: bundle, repo: checkout), bundle + "/frontend-ios")
83+
XCTAssertEqual(ShellInstall.shellPackage(resources: nil, repo: checkout), checkout + "/frontend-ios")
84+
let bare = try makeTree([])
85+
XCTAssertEqual(ShellInstall.shellPackage(resources: bare, repo: checkout), checkout + "/frontend-ios")
86+
XCTAssertNil(ShellInstall.shellPackage(resources: bare, repo: bare))
87+
XCTAssertNil(ShellInstall.shellPackage(resources: nil, repo: ""))
88+
}
89+
90+
/// The failure of the report: the repo is an engine in the store (no frontend-ios in it), the release app carries the package.
91+
func testStoreEngineGetsTheBundledPackage() throws {
92+
let store = try makeTree(["engine/id/dm/ios/tools/build_shell.sh"]) + "/engine/id"
93+
let bundle = try makeTree(["frontend-ios/Package.swift", "frontend-ios/AppIcon.icon/icon.json"])
94+
let package = try XCTUnwrap(ShellInstall.shellPackage(resources: bundle, repo: store))
95+
let env = ShellInstall.projectEnvironment(root: "/R", frameworks: [], bundleID: "b", team: "T", python: "/py", path: "/p",
96+
home: "/h", user: "u", package: package)
97+
let path = try XCTUnwrap(env["DM_SHELL_PACKAGE"])
98+
XCTAssertTrue(FileManager.default.fileExists(atPath: path + "/Package.swift"))
99+
XCTAssertTrue(FileManager.default.fileExists(atPath: path + "/AppIcon.icon"))
100+
XCTAssertFalse(path.hasPrefix(store))
101+
XCTAssertNil(ShellInstall.projectEnvironment(root: "/R", frameworks: [], bundleID: "b", team: "T", python: "/py", path: "/p",
102+
home: "/h", user: "u")["DM_SHELL_PACKAGE"])
103+
}
104+
66105
func testCommandsAreExact() {
67106
let root = "/R/NX2Apple-shell"
68107
XCTAssertEqual(ShellInstall.buildArguments(workDir: "/W", team: "T"), ["--shell-framework", "/W/shell-framework", "--team-id", "T"])
@@ -272,6 +311,7 @@ final class ShellInstallTests: XCTestCase {
272311
env | sort > "\(state)/shell-env.txt"
273312
if [ "${DM_ENTITLEMENTS+set}" = set ] && [ -z "$DM_ENTITLEMENTS" ]; then touch "$SHELL_DIR/entitlements-empty"; else rm -f "$SHELL_DIR/entitlements-empty"; fi
274313
""")
314+
try write(repo + "/frontend-ios/Package.swift", "// swift-tools-version: 5.9\n")
275315
return repo
276316
}
277317

@@ -313,6 +353,7 @@ final class ShellInstallTests: XCTestCase {
313353
var i = ShellInstaller(xcrun: f.xcrun, repo: f.repo, python: "/usr/bin/python3", root: f.root, device: device, team: team)
314354
i.deviceName = "Z"
315355
i.pollInterval = 0.02
356+
i.resources = nil
316357
return i
317358
}
318359

@@ -362,7 +403,7 @@ final class ShellInstallTests: XCTestCase {
362403
XCTAssertEqual(try String(contentsOfFile: save), "my save")
363404
let env = try String(contentsOfFile: state + "/shell-env.txt")
364405
for line in ["DM_BUNDLE_ID=org.nxnative.shell", "DM_SHELL_FRAMEWORKS=\(f.framework)", "DM_TEAM_ID=TEAM123456",
365-
"SHELL_DIR=\(f.root)/project", "DM_XCODE_DERIVED_DATA=\(f.root)/derived-data", "FONT_PY=/usr/bin/python3"] {
406+
"SHELL_DIR=\(f.root)/project", "DM_XCODE_DERIVED_DATA=\(f.root)/derived-data", "FONT_PY=/usr/bin/python3", "DM_SHELL_PACKAGE=\(f.repo)/frontend-ios"] {
366407
XCTAssertTrue(env.contains(line), line)
367408
}
368409
let library = ShellLibrary.load(root: f.root)
@@ -425,6 +466,15 @@ final class ShellInstallTests: XCTestCase {
425466
XCTAssertFalse(calls.contains { $0.hasPrefix("xcodebuild") || $0.hasPrefix("devicectl device") }) // nothing was signed or installed
426467
}
427468

469+
func testMissingShellPackageStopsBeforeTheScript() throws {
470+
let f = try makeFixture()
471+
try fm.removeItem(atPath: f.repo + "/frontend-ios")
472+
let r = run(f)
473+
XCTAssertEqual(r.status, 1)
474+
XCTAssertTrue(r.output.contains("FAIL: The shared app could not be prepared: no frontend-ios Swift package"), r.output)
475+
XCTAssertFalse(fm.fileExists(atPath: state + "/shell-env.txt"), "build_shell.sh must not run")
476+
}
477+
428478
func testCLIChecksItsArguments() throws {
429479
var output = ""
430480
XCTAssertEqual(ShellCLI.run(["--install-shell", "--framework", "/x"], defaults: UserDefaults(suiteName: "nspb-shell-" + UUID().uuidString)!) { output += $0 }, 1)

0 commit comments

Comments
 (0)