Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions packages/host/src/node/cli/apple.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import path from "node:path";
import { readInfoPlist } from "./apple";
import { setupTempDirectory } from "../test-utils";

describe("apple", () => {
describe("Info.plist lookup", () => {
it("should find Info.plist files in unversioned frameworks", async (context) => {
const infoPlistContents = `<?xml version="1.0" encoding="UTF-8"?>...`;
const infoPlistSubPath = "Info.plist";
const tempDirectoryPath = setupTempDirectory(context, {
[infoPlistSubPath]: infoPlistContents,
});

const result = await readInfoPlist(tempDirectoryPath);

assert.strictEqual(result.contents, infoPlistContents);
assert.strictEqual(
result.infoPlistPath,
path.join(tempDirectoryPath, infoPlistSubPath),
);
});

it("should find Info.plist files in versioned frameworks", async (context) => {
const infoPlistContents = `<?xml version="1.0" encoding="UTF-8"?>...`;
const infoPlistSubPath = "Versions/Current/Resources/Info.plist";
const tempDirectoryPath = setupTempDirectory(context, {
[infoPlistSubPath]: infoPlistContents,
});

const result = await readInfoPlist(tempDirectoryPath);

assert.strictEqual(result.contents, infoPlistContents);
assert.strictEqual(
result.infoPlistPath,
path.join(tempDirectoryPath, infoPlistSubPath),
);
});

it("should throw if Info.plist is missing from framework", async (context) => {
const tempDirectoryPath = setupTempDirectory(context, {});

await assert.rejects(
async () => readInfoPlist(tempDirectoryPath),
/Unable to read Info.plist for framework at path ".*?", as an Info.plist file couldn't be found./,
);
});
});
});
75 changes: 66 additions & 9 deletions packages/host/src/node/cli/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,67 @@ import {
LinkModuleResult,
} from "./link-modules.js";

function determineInfoPlistPath(frameworkPath: string) {
const checkedPaths = new Array<string>();

// First, assume it is an "unversioned" framework that keeps its Info.plist in
// the root. This is the convention for iOS, tvOS, and friends.
let infoPlistPath = path.join(frameworkPath, "Info.plist");

if (fs.existsSync(infoPlistPath)) {
return infoPlistPath;
}
checkedPaths.push(infoPlistPath);

// Next, assume it is a "versioned" framework that keeps its Info.plist
// under a subdirectory. This is the convention for macOS.
infoPlistPath = path.join(
frameworkPath,
"Versions/Current/Resources/Info.plist",
);

if (fs.existsSync(infoPlistPath)) {
return infoPlistPath;
}
checkedPaths.push(infoPlistPath);

throw new Error(
[
`Unable to locate an Info.plist file within framework. Checked the following paths:`,
...checkedPaths.map((checkedPath) => `- ${checkedPath}`),
].join("\n"),
);
}

/**
* Resolves the Info.plist file within a framework and reads its contents.
*/
export async function readInfoPlist(frameworkPath: string) {
let infoPlistPath: string;
try {
infoPlistPath = determineInfoPlistPath(frameworkPath);
} catch (cause) {
throw new Error(
`Unable to read Info.plist for framework at path "${frameworkPath}", as an Info.plist file couldn't be found.`,
{ cause },
);
}

let contents: string;
try {
contents = await fs.promises.readFile(infoPlistPath, "utf-8");
} catch (cause) {
throw new Error(
`Unable to read Info.plist for framework at path "${frameworkPath}", due to a file system error.`,
{ cause },
);
}

return { infoPlistPath, contents };
}

type UpdateInfoPlistOptions = {
filePath: string;
frameworkPath: string;
oldLibraryName: string;
newLibraryName: string;
};
Expand All @@ -22,17 +81,15 @@ type UpdateInfoPlistOptions = {
* Update the Info.plist file of an xcframework to use the new library name.
*/
export async function updateInfoPlist({
filePath,
frameworkPath,
oldLibraryName,
newLibraryName,
}: UpdateInfoPlistOptions) {
const infoPlistContents = await fs.promises.readFile(filePath, "utf-8");
const { infoPlistPath, contents } = await readInfoPlist(frameworkPath);

// TODO: Use a proper plist parser
const updatedContents = infoPlistContents.replaceAll(
oldLibraryName,
newLibraryName,
);
await fs.promises.writeFile(filePath, updatedContents, "utf-8");
const updatedContents = contents.replaceAll(oldLibraryName, newLibraryName);
await fs.promises.writeFile(infoPlistPath, updatedContents, "utf-8");
}

export async function linkXcframework({
Expand Down Expand Up @@ -126,7 +183,7 @@ export async function linkXcframework({
);
// Update the Info.plist file for the framework
await updateInfoPlist({
filePath: path.join(newFrameworkPath, "Info.plist"),
frameworkPath: newFrameworkPath,
oldLibraryName,
newLibraryName,
});
Expand Down
Loading