diff --git a/App_Resources/Android/app.gradle b/App_Resources/Android/app.gradle index 7baf43d7..28bf7c45 100644 --- a/App_Resources/Android/app.gradle +++ b/App_Resources/Android/app.gradle @@ -111,7 +111,12 @@ tasks.register("prepareFaceclawNativeLibs") { if (ndkRoot == null || !ndkRoot.exists()) { throw new GradleException("Android NDK is required to build libfaceclaw_lc3.so") } - def clang = file("$ndkRoot/toolchains/llvm/prebuilt/${faceclawNdkHostTag()}/bin/aarch64-linux-android24-clang") + // The NDK ships the clang driver twice: extensionless (a Unix shell script) + // and as .cmd (the CreateProcess-launchable one Windows needs). + def clangBin = "$ndkRoot/toolchains/llvm/prebuilt/${faceclawNdkHostTag()}/bin/aarch64-linux-android24-clang" + def clang = Os.isFamily(Os.FAMILY_WINDOWS) + ? file("${clangBin}.cmd") + : file(clangBin) if (!clang.exists()) { throw new GradleException("Could not find Android clang at $clang") } @@ -229,7 +234,11 @@ tasks.register("prepareFaceclawLlama") { // strip it so the APK only pays for the code (~8MB). def ndkRootForStrip = faceclawFindNdkRoot() if (ndkRootForStrip != null) { - def llvmStrip = file("$ndkRootForStrip/toolchains/llvm/prebuilt/${faceclawNdkHostTag()}/bin/llvm-strip") + // Windows wants llvm-strip.exe; the extensionless file is a Unix script. + def llvmStripBin = "$ndkRootForStrip/toolchains/llvm/prebuilt/${faceclawNdkHostTag()}/bin/llvm-strip" + def llvmStrip = Os.isFamily(Os.FAMILY_WINDOWS) && file("${llvmStripBin}.exe").exists() + ? file("${llvmStripBin}.exe") + : file(llvmStripBin) if (llvmStrip.exists()) { exec { commandLine([llvmStrip.absolutePath, "--strip-unneeded", llamaSo.absolutePath]) diff --git a/scripts/kotlin-build.cjs b/scripts/kotlin-build.cjs index 1d4d07d3..5e216181 100644 --- a/scripts/kotlin-build.cjs +++ b/scripts/kotlin-build.cjs @@ -33,7 +33,16 @@ async function buildKotlin(platform, configuration = 'debug') { const wrapper = path.join(root, 'wear', process.platform === 'win32' ? 'gradlew.bat' : 'gradlew'); console.log(`[kotlin] Building ${platform} (${configuration})`); await new Promise((resolve, reject) => { - const child = spawn(wrapper, [task, '--console=plain'], { cwd: kotlin, stdio: 'inherit' }); + // A .bat cannot be exec'd directly: Node passes it to CreateProcess, which + // rejects it with EINVAL. cmd.exe has to run it, so the Windows path goes + // through a shell, quoted because the checkout may sit under a path with + // spaces in it. + const windows = process.platform === 'win32'; + const child = spawn(windows ? `"${wrapper}"` : wrapper, [task, '--console=plain'], { + cwd: kotlin, + stdio: 'inherit', + shell: windows, + }); child.on('error', reject); child.on('exit', (code, signal) => { if (code === 0) resolve(); diff --git a/tests/evenhub-extensions-shim.test.cjs b/tests/evenhub-extensions-shim.test.cjs index bb43d0bb..ef70aff3 100644 --- a/tests/evenhub-extensions-shim.test.cjs +++ b/tests/evenhub-extensions-shim.test.cjs @@ -7,7 +7,7 @@ const vm = require('node:vm'); // The injected shim (app/apps/evenhub/session.ts) and the published types // (evenhub-extensions/src/index.ts) describe the same API from two sides. const source = fs.readFileSync(path.join(__dirname, '../app/apps/evenhub/session.ts'), 'utf8'); -const template = source.match(/export function buildFaceclawExtensionsScript[^`]*`([\s\S]*?)`;\n}/)[1]; +const template = source.match(/export function buildFaceclawExtensionsScript[^`]*`([\s\S]*?)`;\r?\n}/)[1]; const shim = template.replace('${JSON.stringify(versionString)}', JSON.stringify('Faceclaw/9.9.9')); const types = fs.readFileSync(path.join(__dirname, '../evenhub-extensions/src/index.ts'), 'utf8'); diff --git a/tests/kotlin-build.test.cjs b/tests/kotlin-build.test.cjs index ba68d135..e101ba8d 100644 --- a/tests/kotlin-build.test.cjs +++ b/tests/kotlin-build.test.cjs @@ -28,7 +28,7 @@ test('every remaining Java source has an explicit Android boundary and every mig const java = path.join(root, 'App_Resources/Android/src/main/java'); const list = directory => fs.readdirSync(directory, { withFileTypes: true }).flatMap(entry => { const file = path.join(directory, entry.name); - return entry.isDirectory() ? list(file) : file.endsWith('.java') ? [path.relative(java, file)] : []; + return entry.isDirectory() ? list(file) : file.endsWith('.java') ? [path.relative(java, file).split(path.sep).join('/')] : []; }); const boundaries = require('../native/kotlin/android-java-boundaries.json'); const retained = boundaries.flatMap(group => { assert.ok(group.reason.length > 20); return group.files; });