Skip to content
Merged
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
15 changes: 15 additions & 0 deletions .changeset/gentle-pandas-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"cmake-rn": minor
---

Let a consumer override the Android `ANDROID_STL` CMake cache variable via
the existing `-D`/`--define` option (e.g. `--define ANDROID_STL=c++_static`).
It still defaults to `c++_shared`, matching what React Native itself uses,
but an addon that must match a prebuilt third-party dependency's STL, or one
that's genuinely self-contained, can now ask for a different value.

This also fixes an ordering bug where a `--define` targeting any of the
Android platform's own default CMake variables (including `ANDROID_STL`) was
silently discarded: our hardcoded defaults were appended to the CMake
command line _after_ the user-provided `-D` arguments, and CMake resolves a
cache variable set multiple times via `-D` to its last occurrence.
60 changes: 60 additions & 0 deletions packages/cmake-rn/src/platforms/android.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { toDefineArguments } from "../helpers.js";
import { buildCommonDefinitions } from "./android.js";

function baseArgs(
overrides: Partial<Parameters<typeof buildCommonDefinitions>[0]> = {},
) {
return {
configuration: "Release" as const,
ndkPath: "/opt/ndk",
androidSdkVersion: "24",
ccachePath: null,
define: [],
...overrides,
};
}

describe("buildCommonDefinitions", () => {
it("defaults ANDROID_STL to c++_shared", () => {
const args = toDefineArguments(buildCommonDefinitions(baseArgs()));
const index = args.indexOf("-D");
assert(index >= 0);
assert(args.includes("ANDROID_STL=c++_shared"));
});

it("lets a consumer override ANDROID_STL via --define", () => {
// CMake resolves a cache variable passed multiple times via `-D` to its
// *last* occurrence on the command line, so what matters is which
// ANDROID_STL entry comes last - not merely that c++_static is present.
const args = toDefineArguments(
buildCommonDefinitions(
baseArgs({ define: [{ ANDROID_STL: "c++_static" }] }),
),
);
const stlEntries = args.filter((arg) => arg.startsWith("ANDROID_STL="));
assert.deepEqual(stlEntries, [
"ANDROID_STL=c++_shared",
"ANDROID_STL=c++_static",
]);
});

it("applies the user's --define after (so it wins over) every default", () => {
const definitions = buildCommonDefinitions(
baseArgs({ define: [{ ANDROID_STL: "c++_static" }] }),
);
// The user-provided define must be the last entry, since CMake resolves
// a -D variable passed multiple times to its last occurrence.
assert.deepEqual(definitions.at(-1), { ANDROID_STL: "c++_static" });
});

it("includes ccache launcher variables when a ccache path is given", () => {
const args = toDefineArguments(
buildCommonDefinitions(baseArgs({ ccachePath: "/usr/bin/ccache" })),
);
assert(args.includes("CMAKE_C_COMPILER_LAUNCHER=/usr/bin/ccache"));
assert(args.includes("CMAKE_CXX_COMPILER_LAUNCHER=/usr/bin/ccache"));
});
});
77 changes: 57 additions & 20 deletions packages/cmake-rn/src/platforms/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,56 @@ function getNdkLlvmBinPath(ndkPath: string) {
return path.join(prebuiltPath, platforms[0], "bin");
}

const DEFAULT_ANDROID_STL = "c++_shared";

/**
* Builds the list of CMake cache variable definitions common to every
* triplet's configure step.
*
* `define` (populated from the repeatable `-D`/`--define` CLI option) is
* spread last, so a consumer's explicit `-D ANDROID_STL=c++_static` (or any
* other variable set here by default) takes precedence over our own
* defaults: CMake resolves a cache variable passed multiple times via `-D`
* to its last occurrence on the command line.
*/
export function buildCommonDefinitions({
configuration,
ndkPath,
androidSdkVersion,
ccachePath,
define,
}: {
configuration: BaseOpts["configuration"];
ndkPath: string;
androidSdkVersion: string;
ccachePath: BaseOpts["ccachePath"];
define: BaseOpts["define"];
}) {
return [
{
CMAKE_BUILD_TYPE: configuration,
CMAKE_SYSTEM_NAME: "Android",
// "CMAKE_INSTALL_PREFIX": installPath,
CMAKE_MAKE_PROGRAM: "ninja",
ANDROID_NDK: ndkPath,
ANDROID_TOOLCHAIN: "clang",
ANDROID_PLATFORM: androidSdkVersion,
// Defaults to c++_shared, matching what React Native itself uses.
// Override with -D/--define ANDROID_STL=c++_static (or another value
// accepted by the NDK's CMake toolchain) when an addon must match a
// prebuilt third-party dependency's STL.
ANDROID_STL: DEFAULT_ANDROID_STL,
},
ccachePath
? {
CMAKE_C_COMPILER_LAUNCHER: ccachePath,
CMAKE_CXX_COMPILER_LAUNCHER: ccachePath,
}
: {},
...define,
];
}

export const platform: Platform<Triplet[], AndroidOpts> = {
id: "android",
name: "Android",
Expand Down Expand Up @@ -140,26 +190,13 @@ export const platform: Platform<Triplet[], AndroidOpts> = {
const ndkPath = getNdkPath(ndkVersion);
const toolchainPath = getNdkToolchainPath(ndkPath);

const commonDefinitions = [
...define,
{
CMAKE_BUILD_TYPE: configuration,
CMAKE_SYSTEM_NAME: "Android",
// "CMAKE_INSTALL_PREFIX": installPath,
CMAKE_MAKE_PROGRAM: "ninja",
ANDROID_NDK: ndkPath,
ANDROID_TOOLCHAIN: "clang",
ANDROID_PLATFORM: androidSdkVersion,
// TODO: Make this configurable
ANDROID_STL: "c++_shared",
},
ccachePath
? {
CMAKE_C_COMPILER_LAUNCHER: ccachePath,
CMAKE_CXX_COMPILER_LAUNCHER: ccachePath,
}
: {},
];
const commonDefinitions = buildCommonDefinitions({
configuration,
ndkPath,
androidSdkVersion,
ccachePath,
define,
});

await Promise.all(
triplets.map(async ({ triplet, spawn }) => {
Expand Down
Loading