Skip to content

Incremental build correctness: stale artifacts, destructive renames and undeclared build-phase I/O #422

Description

@kraenhansen

Three TODOs that individually look like nits but share a cause: the pipeline moves and renames build outputs, and none of the tooling around it knows that.

Stale build artifacts are no longer cleaned

// TODO: Consider if this is still important 😬
// // Delete any stale build artifacts before building
// // This is important, since we might rename the output files
// await fs.promises.rm(context.outputPath, {
// recursive: true,
// force: true,
// });
await platform.build(context, baseOptions);

The cleanup is commented out with "Consider if this is still important 😬", and its own comment answers the question: it is important because we rename the output files. If a previous build left libfoo.so and the current one produces a differently named artifact, the stale file survives in outputPath and can be picked up downstream. Either restore it, or work out what made it unnecessary and delete the dead code with a note — leaving it commented out means neither.

createFramework renames its input instead of copying

});
const newLibraryPath = path.join(frameworkPath, libraryName);
// TODO: Consider copying the library instead of renaming it
await fs.promises.rename(libraryPath, newLibraryPath);
await updateLibraryInstallName({

fs.promises.rename moves the library out of the CMake build directory and into the framework, then rewrites its install name. From CMake's point of view its own output has vanished, so the next build has to relink — and any second consumer of that artifact finds it missing. Copying costs one file write and makes the step idempotent.

The Android side has the same shape but already copies (prebuilds/android.ts), so this is an inconsistency as much as a bug.

The Xcode build phase declares no inputs or outputs

if (!foundBuildPhase) {
console.log("Adding new build phase");
// TODO: Consider declaring input and output files to prevent unnecessary runs
target.createBuildPhase(xcode.PBXShellScriptBuildPhase, {
name: BUILD_PHASE_NAME,
shellScript,
});
saveProject = true;

A PBXShellScriptBuildPhase without inputPaths/outputPaths runs on every single build, and Xcode says so in the build log. Declaring them lets Xcode skip the phase when nothing changed — the difference between a no-op incremental build and re-running the addon pipeline on every ⌘B. This one interacts with the two above: declaring outputs is only sound once the artifacts stay where they are claimed to be.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Apple 🍎Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.)CMake RNOur `cmake` wrapping CLIHost 🏡Our `react-native-node-api-modules` package

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions