Skip to content

Commit 31838bd

Browse files
authored
Handle Info.plist lookup in versioned frameworks (#261)
* handle Info.plist lookup in versioned frameworks * whoops * extract out readInfoPlist() helper * rethrow with context * add tests
1 parent a23af5a commit 31838bd

File tree

2 files changed

+116
-9
lines changed

2 files changed

+116
-9
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import assert from "node:assert/strict";
2+
import { describe, it } from "node:test";
3+
import path from "node:path";
4+
import { readInfoPlist } from "./apple";
5+
import { setupTempDirectory } from "../test-utils";
6+
7+
describe("apple", () => {
8+
describe("Info.plist lookup", () => {
9+
it("should find Info.plist files in unversioned frameworks", async (context) => {
10+
const infoPlistContents = `<?xml version="1.0" encoding="UTF-8"?>...`;
11+
const infoPlistSubPath = "Info.plist";
12+
const tempDirectoryPath = setupTempDirectory(context, {
13+
[infoPlistSubPath]: infoPlistContents,
14+
});
15+
16+
const result = await readInfoPlist(tempDirectoryPath);
17+
18+
assert.strictEqual(result.contents, infoPlistContents);
19+
assert.strictEqual(
20+
result.infoPlistPath,
21+
path.join(tempDirectoryPath, infoPlistSubPath),
22+
);
23+
});
24+
25+
it("should find Info.plist files in versioned frameworks", async (context) => {
26+
const infoPlistContents = `<?xml version="1.0" encoding="UTF-8"?>...`;
27+
const infoPlistSubPath = "Versions/Current/Resources/Info.plist";
28+
const tempDirectoryPath = setupTempDirectory(context, {
29+
[infoPlistSubPath]: infoPlistContents,
30+
});
31+
32+
const result = await readInfoPlist(tempDirectoryPath);
33+
34+
assert.strictEqual(result.contents, infoPlistContents);
35+
assert.strictEqual(
36+
result.infoPlistPath,
37+
path.join(tempDirectoryPath, infoPlistSubPath),
38+
);
39+
});
40+
41+
it("should throw if Info.plist is missing from framework", async (context) => {
42+
const tempDirectoryPath = setupTempDirectory(context, {});
43+
44+
await assert.rejects(
45+
async () => readInfoPlist(tempDirectoryPath),
46+
/Unable to read Info.plist for framework at path ".*?", as an Info.plist file couldn't be found./,
47+
);
48+
});
49+
});
50+
});

packages/host/src/node/cli/apple.ts

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,67 @@ import {
1212
LinkModuleResult,
1313
} from "./link-modules.js";
1414

15+
function determineInfoPlistPath(frameworkPath: string) {
16+
const checkedPaths = new Array<string>();
17+
18+
// First, assume it is an "unversioned" framework that keeps its Info.plist in
19+
// the root. This is the convention for iOS, tvOS, and friends.
20+
let infoPlistPath = path.join(frameworkPath, "Info.plist");
21+
22+
if (fs.existsSync(infoPlistPath)) {
23+
return infoPlistPath;
24+
}
25+
checkedPaths.push(infoPlistPath);
26+
27+
// Next, assume it is a "versioned" framework that keeps its Info.plist
28+
// under a subdirectory. This is the convention for macOS.
29+
infoPlistPath = path.join(
30+
frameworkPath,
31+
"Versions/Current/Resources/Info.plist",
32+
);
33+
34+
if (fs.existsSync(infoPlistPath)) {
35+
return infoPlistPath;
36+
}
37+
checkedPaths.push(infoPlistPath);
38+
39+
throw new Error(
40+
[
41+
`Unable to locate an Info.plist file within framework. Checked the following paths:`,
42+
...checkedPaths.map((checkedPath) => `- ${checkedPath}`),
43+
].join("\n"),
44+
);
45+
}
46+
47+
/**
48+
* Resolves the Info.plist file within a framework and reads its contents.
49+
*/
50+
export async function readInfoPlist(frameworkPath: string) {
51+
let infoPlistPath: string;
52+
try {
53+
infoPlistPath = determineInfoPlistPath(frameworkPath);
54+
} catch (cause) {
55+
throw new Error(
56+
`Unable to read Info.plist for framework at path "${frameworkPath}", as an Info.plist file couldn't be found.`,
57+
{ cause },
58+
);
59+
}
60+
61+
let contents: string;
62+
try {
63+
contents = await fs.promises.readFile(infoPlistPath, "utf-8");
64+
} catch (cause) {
65+
throw new Error(
66+
`Unable to read Info.plist for framework at path "${frameworkPath}", due to a file system error.`,
67+
{ cause },
68+
);
69+
}
70+
71+
return { infoPlistPath, contents };
72+
}
73+
1574
type UpdateInfoPlistOptions = {
16-
filePath: string;
75+
frameworkPath: string;
1776
oldLibraryName: string;
1877
newLibraryName: string;
1978
};
@@ -22,17 +81,15 @@ type UpdateInfoPlistOptions = {
2281
* Update the Info.plist file of an xcframework to use the new library name.
2382
*/
2483
export async function updateInfoPlist({
25-
filePath,
84+
frameworkPath,
2685
oldLibraryName,
2786
newLibraryName,
2887
}: UpdateInfoPlistOptions) {
29-
const infoPlistContents = await fs.promises.readFile(filePath, "utf-8");
88+
const { infoPlistPath, contents } = await readInfoPlist(frameworkPath);
89+
3090
// TODO: Use a proper plist parser
31-
const updatedContents = infoPlistContents.replaceAll(
32-
oldLibraryName,
33-
newLibraryName,
34-
);
35-
await fs.promises.writeFile(filePath, updatedContents, "utf-8");
91+
const updatedContents = contents.replaceAll(oldLibraryName, newLibraryName);
92+
await fs.promises.writeFile(infoPlistPath, updatedContents, "utf-8");
3693
}
3794

3895
export async function linkXcframework({
@@ -126,7 +183,7 @@ export async function linkXcframework({
126183
);
127184
// Update the Info.plist file for the framework
128185
await updateInfoPlist({
129-
filePath: path.join(newFrameworkPath, "Info.plist"),
186+
frameworkPath: newFrameworkPath,
130187
oldLibraryName,
131188
newLibraryName,
132189
});

0 commit comments

Comments
 (0)