diff --git a/packages/stim-cli/shim/android-optimizations.gradle b/packages/stim-cli/shim/android-optimizations.gradle index edbc6b1a9..3ee667431 100644 --- a/packages/stim-cli/shim/android-optimizations.gradle +++ b/packages/stim-cli/shim/android-optimizations.gradle @@ -5,12 +5,14 @@ gradle.beforeProject { project -> def reactApplication = project.plugins.hasPlugin("com.android.application") && project.plugins.hasPlugin("com.facebook.react") if (!android.externalNativeBuild.cmake.path && !reactApplication) return def profile = System.getenv("STIM_ANDROID_NATIVE_PROFILE") + def windows = System.getProperty("os.name").toLowerCase().startsWith("windows") def staging = android.externalNativeBuild.cmake.buildStagingDirectory ?: new File(project.projectDir, ".cxx") - android.externalNativeBuild.cmake.buildStagingDirectory = new File(staging, "stim-" + profile) - // Windows caps a path at 260 characters and the SDK's ninja is not long-path aware, so the - // staging directory added above has to buy its room back: CMake replaces the mangled absolute - // source directory in an over-long object path with a hash of it. - if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { + // Windows caps a path at 260 characters and the SDK's ninja is not long-path aware, so every + // character of the staging directory comes out of the project root's budget: there the profile + // directory keeps 4 hex characters, and CMake replaces the mangled absolute source directory in an + // over-long object path with a hash of it. + android.externalNativeBuild.cmake.buildStagingDirectory = new File(staging, windows ? profile.take(4) : "stim-" + profile) + if (windows) { android.defaultConfig.externalNativeBuild.cmake.arguments.add("-DCMAKE_OBJECT_PATH_MAX=240") } def configs = [android.defaultConfig] + android.buildTypes.toList() + android.productFlavors.toList() diff --git a/packages/stim-cli/src/__tests__/android-path-limit.test.ts b/packages/stim-cli/src/__tests__/android-path-limit.test.ts index 0e18723f6..76ea4d886 100644 --- a/packages/stim-cli/src/__tests__/android-path-limit.test.ts +++ b/packages/stim-cli/src/__tests__/android-path-limit.test.ts @@ -1,31 +1,62 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { expect, test } from 'vitest'; import { ANDROID_OBJECT_PATH_MAX, - LONGEST_ANDROID_OBJECT_PATH_TAIL, + GESTURE_HANDLER_OBJECT, + SAFE_AREA_CONTEXT_OBJECT, androidPathRoom, androidPathRoomMessage, androidPathRoomRemedy, + longestAndroidObject, } from '../engine/android-path-limit.ts'; import { checkAndroidPathRoom } from '../diagnostics/doctor.ts'; const rootOf = (length: number) => `D:\\${'a'.repeat(length - 3)}`; -const maxRoot = (abi: string) => ANDROID_OBJECT_PATH_MAX - 1 - LONGEST_ANDROID_OBJECT_PATH_TAIL - abi.length; +const maxRoot = (abi: string) => ANDROID_OBJECT_PATH_MAX - 2 - SAFE_AREA_CONTEXT_OBJECT.tail - abi.length; -test('the e2e fixture builds at its 33-character CI root and fails at the 36-character root measured on the debug host', () => { +test('the e2e fixture builds at its 33-character CI root', () => { expect(androidPathRoom('D:\\e\\sn-36AFO0\\e2e-1-1c17ed8f9e83', { abi: 'x86_64', platform: 'win32' })).toBeNull(); - const room = androidPathRoom('D:\\a\\stim\\stim\\e2e-tmp\\sn-nBa2Bi\\app', { abi: 'x86_64', platform: 'win32' }); - expect(room).toEqual({ - root: 'D:\\a\\stim\\stim\\e2e-tmp\\sn-nBa2Bi\\app', +}); + +test('react-native-gesture-handler 3 builds at the 24-character root measured on Windows and refuses the 25-character one that failed', () => { + const object = GESTURE_HANDLER_OBJECT; + expect(androidPathRoom('C:\\src\\trailhead-abcdefg', { abi: 'x86_64', platform: 'win32', object })).toBeNull(); + expect(androidPathRoom('C:\\stimw\\954971c6', { abi: 'arm64-v8a', platform: 'win32', object })).toBeNull(); + const root = 'C:\\src\\trailhead-abcdefgh'; + expect(androidPathRoom(root, { abi: 'x86_64', platform: 'win32', object })).toEqual({ + root, abi: 'x86_64', - longest: ANDROID_OBJECT_PATH_MAX, - maxRootLength: 35, + target: 'react_codegen_rngesturehandler_codegen', + longest: 239, + maxRootLength: 24, }); }); +test('the gesture handler object applies only where its shared shadow nodes resolve from the project', () => { + const dir = mkdtempSync(join(tmpdir(), 'stim-path-room-')); + try { + const app = join(dir, 'apps', 'mobile'); + mkdirSync(app, { recursive: true }); + expect(longestAndroidObject(app)).toBe(SAFE_AREA_CONTEXT_OBJECT); + const gestureHandler = join(dir, 'node_modules', 'react-native-gesture-handler'); + mkdirSync(join(gestureHandler, 'src'), { recursive: true }); + writeFileSync(join(gestureHandler, 'package.json'), '{}'); + expect(longestAndroidObject(app)).toBe(SAFE_AREA_CONTEXT_OBJECT); + mkdirSync(join(gestureHandler, 'shared', 'shadowNodes'), { recursive: true }); + expect(longestAndroidObject(app)).toBe(GESTURE_HANDLER_OBJECT); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + test('the last root that fits is one character shorter than the one that reaches the limit', () => { const abi = 'x86_64'; expect(androidPathRoom(rootOf(maxRoot(abi)), { abi, platform: 'win32' })).toBeNull(); - expect(androidPathRoom(rootOf(maxRoot(abi) + 1), { abi, platform: 'win32' })?.longest).toBe(ANDROID_OBJECT_PATH_MAX); + expect(androidPathRoom(rootOf(maxRoot(abi) + 1), { abi, platform: 'win32' })?.longest).toBe( + ANDROID_OBJECT_PATH_MAX - 1, + ); }); test('release variants account for the longer RelWithDebInfo directory', () => { @@ -34,7 +65,7 @@ test('release variants account for the longer RelWithDebInfo directory', () => { expect(androidPathRoom(rootOf(releaseRoot), { abi, variant: 'productionRelease', platform: 'win32' })).toBeNull(); expect( androidPathRoom(rootOf(releaseRoot + 1), { abi, variant: 'productionRelease', platform: 'win32' })?.longest, - ).toBe(ANDROID_OBJECT_PATH_MAX); + ).toBe(ANDROID_OBJECT_PATH_MAX - 1); expect(androidPathRoom(rootOf(maxRoot(abi)), { abi, variant: 'productionDebug', platform: 'win32' })).toBeNull(); }); @@ -53,21 +84,18 @@ test('only win32 has the limit', () => { expect(androidPathRoom(long, { platform: 'win32' })).not.toBeNull(); }); -test('the message names the root, its length, the ABI and the root cap; the remedy names subst', () => { +test('the message names the root, its length, the object, the ABI and the root cap; the remedy names a short real path', () => { const root = rootOf(maxRoot('x86_64') + 12); const room = androidPathRoom(root, { abi: 'x86_64', platform: 'win32' }); expect(room).not.toBeNull(); expect(androidPathRoomMessage(room!)).toBe( `The project root ${root} is ${root.length} characters, so the longest known Android native object path ` + - `(react_codegen_safeareacontext for x86_64) reaches ${ANDROID_OBJECT_PATH_MAX + 11} characters, past the ` + - `${ANDROID_OBJECT_PATH_MAX - 1} the NDK's ninja can be given on Windows; the root can be at most 35 characters.`, - ); - expect(androidPathRoomRemedy(room!)).toBe( - `Map the project to a drive letter (\`subst X: "${root}"\`) and run Stim from X:\\, or move it under a shorter root.`, + `(react_codegen_safeareacontext for x86_64) reaches ${ANDROID_OBJECT_PATH_MAX + 10} characters, past the ` + + `${ANDROID_OBJECT_PATH_MAX - 2} CMake can shorten it to for the NDK's ninja on Windows; the root can be at most ${maxRoot('x86_64')} characters.`, ); - const spacedRoot = `${root} folder`; - const spacedRoom = androidPathRoom(spacedRoot, { abi: 'x86_64', platform: 'win32' }); - expect(androidPathRoomRemedy(spacedRoom!)).toContain(`subst X: "${spacedRoot}"`); + const remedy = androidPathRoomRemedy(room!); + expect(remedy).toContain(`at most ${maxRoot('x86_64')} characters, such as C:\\src\\`); + expect(remedy).not.toContain('subst X:'); }); test('doctor reports the room as an Android cost finding on win32 only, for the emulator ABI', () => { @@ -82,7 +110,7 @@ test('doctor reports the room as an Android cost finding on win32 only, for the expect(finding?.detail).toContain(`${root} is ${root.length} characters`); expect(finding?.detail).toContain('for x86_64'); expect(finding?.detail).toContain('STIM_PATH_TOO_LONG'); - expect(finding?.fix).toContain(`subst X: "${root}"`); + expect(finding?.fix).toContain(`at most ${maxRoot('x86_64')} characters`); }); test('doctor uses the Windows ARM64 emulator ABI when checking path room', () => { diff --git a/packages/stim-cli/src/__tests__/doctor-cxx.test.ts b/packages/stim-cli/src/__tests__/doctor-cxx.test.ts index 788c73db3..89f3abb0b 100644 --- a/packages/stim-cli/src/__tests__/doctor-cxx.test.ts +++ b/packages/stim-cli/src/__tests__/doctor-cxx.test.ts @@ -233,18 +233,22 @@ test('doctor preserves native configurations while a CAS toolchain Stim can use }); test.skipIf(process.platform === 'win32')( - 'legacy launcher repair preserves managed compiler profiles alongside stale legacy configurations (ccache PATH lookup is POSIX-only; skipped on win32)', + 'legacy launcher repair preserves managed compiler profiles, including the short Windows form, alongside stale legacy configurations (ccache PATH lookup is POSIX-only; skipped on win32)', () => { const legacy = cache('android/app', null); - const managed = join(root, 'android/app/.cxx/stim-0123456789abcdef/Debug/abc123/arm64-v8a'); - mkdirSync(managed, { recursive: true }); - writeFileSync( - join(managed, 'CMakeCache.txt'), - 'CMAKE_BUILD_TYPE:STRING=Debug\nCMAKE_CXX_COMPILER_LAUNCHER:STRING=\n', + const managed = ['stim-0123456789abcdef', '0123'].map((profile) => + join(root, `android/app/.cxx/${profile}/Debug/abc123/arm64-v8a`), ); + for (const dir of managed) { + mkdirSync(dir, { recursive: true }); + writeFileSync( + join(dir, 'CMakeCache.txt'), + 'CMAKE_BUILD_TYPE:STRING=Debug\nCMAKE_CXX_COMPILER_LAUNCHER:STRING=\n', + ); + } expect(repairCxxLauncherState(root, projectSettings()).removed).toHaveLength(1); expect(existsSync(legacy)).toBe(false); - expect(existsSync(managed)).toBe(true); + for (const dir of managed) expect(existsSync(dir)).toBe(true); }, ); diff --git a/packages/stim-cli/src/__tests__/engine-gradle.test.ts b/packages/stim-cli/src/__tests__/engine-gradle.test.ts index a7ce74808..6d42a6974 100644 --- a/packages/stim-cli/src/__tests__/engine-gradle.test.ts +++ b/packages/stim-cli/src/__tests__/engine-gradle.test.ts @@ -403,7 +403,7 @@ const nativeScript = join(import.meta.dirname, '../../shim/android-optimizations describe('buildAndroid', () => { test('refuses a Windows native build with an object path that cannot fit before invoking Gradle', async () => { - const longRoot = join(root, 'windows-object-path-limit'); + const longRoot = join(root, 'windows-object-path-limit'.padEnd(60, '-')); mkdirSync(join(longRoot, 'android'), { recursive: true }); writeFileSync(gradlewPath(defaultAndroidLayout(longRoot)), ''); let spawned = false; diff --git a/packages/stim-cli/src/diagnostics/doctor-cxx.ts b/packages/stim-cli/src/diagnostics/doctor-cxx.ts index 6ed40d45f..c94e25fa5 100644 --- a/packages/stim-cli/src/diagnostics/doctor-cxx.ts +++ b/packages/stim-cli/src/diagnostics/doctor-cxx.ts @@ -43,7 +43,7 @@ function moduleRoots(root: string, layout: AndroidLayout): string[] { function cacheFiles(base: string, depth = 0): string[] { const files: string[] = []; for (const name of children(base)) { - if (depth === 0 && /^stim-[a-f0-9]{16}$/.test(name)) continue; + if (depth === 0 && /^(?:stim-[a-f0-9]{16}|[a-f0-9]{4})$/.test(name)) continue; const path = join(base, name); const entry = lstatSync(path); if (entry.isSymbolicLink()) continue; diff --git a/packages/stim-cli/src/engine/android-path-limit.ts b/packages/stim-cli/src/engine/android-path-limit.ts index e1d60e9d5..105966666 100644 --- a/packages/stim-cli/src/engine/android-path-limit.ts +++ b/packages/stim-cli/src/engine/android-path-limit.ts @@ -1,3 +1,7 @@ +import { existsSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { resolvePackageJson } from '../workspace/project-files.ts'; + /** * The NDK's ninja is not long-path aware, so every object path it opens has to stay under Windows' * MAX_PATH. CMake names the object for a source outside its source directory after that source's @@ -10,13 +14,42 @@ export const ANDROID_OBJECT_PATH_MAX = 240; /** - * Characters the longest known shortened object path adds to the project root and the ABI name, - * measured on the bare e2e fixture at a 36-character root: - * `\android\app\.cxx\stim-<16 hex>\Debug\<8 chars>\\safeareacontext_autolinked_build/CMakeFiles/react_codegen_safeareacontext.dir//RNCSafeAreaViewShadowNode.cpp.o`. - * At that root the path reaches ANDROID_OBJECT_PATH_MAX and CMake leaves it unshortened (341 - * characters); the same fixture builds at a 33-character root. + * CMake's single-config Ninja generator measures an object against CMAKE_OBJECT_PATH_MAX from + * `.dir/./`, two characters longer than the directory the object is written to, so a + * shortened object path fits only up to this length. + * https://github.com/Kitware/CMake/blob/v3.22.1/Source/cmGlobalNinjaGenerator.cxx#L1005-L1013 */ -export const LONGEST_ANDROID_OBJECT_PATH_TAIL = 198; +const LONGEST_SHORTENED_OBJECT_PATH = ANDROID_OBJECT_PATH_MAX - './'.length; + +export interface AndroidLongestObject { + target: string; + /** Characters the shortened object path adds to the project root and the ABI name. */ + tail: number; +} + +/** + * `\android\app\.cxx\<4 hex>\Debug\<8 chars>\\safeareacontext_autolinked_build/CMakeFiles/react_codegen_safeareacontext.dir//RNCSafeAreaViewShadowNode.cpp.o`, + * measured on the bare e2e fixture and on trailhead (Expo SDK 58, RN 0.88). + */ +export const SAFE_AREA_CONTEXT_OBJECT: AndroidLongestObject = { target: 'react_codegen_safeareacontext', tail: 181 }; + +/** + * react-native-gesture-handler 3 compiles shadow nodes from its `shared/shadowNodes` directory into + * its codegen library: + * `\android\app\.cxx\<4 hex>\Debug\<8 chars>\\rngesturehandler_codegen_autolinked_build/CMakeFiles/react_codegen_rngesturehandler_codegen.dir//RNGestureHandlerDetectorShadowNode.cpp.o`, + * measured on trailhead. https://github.com/appandflow/stim/issues/3641 + */ +export const GESTURE_HANDLER_OBJECT: AndroidLongestObject = { + target: 'react_codegen_rngesturehandler_codegen', + tail: 208, +}; + +export function longestAndroidObject(root: string): AndroidLongestObject { + const gestureHandler = resolvePackageJson(root, 'react-native-gesture-handler'); + return gestureHandler && existsSync(join(dirname(gestureHandler), 'shared', 'shadowNodes')) + ? GESTURE_HANDLER_OBJECT + : SAFE_AREA_CONTEXT_OBJECT; +} /** The ABI name a build for every ABI has to fit; `stim android` narrows a build to the device's. */ const LONGEST_ANDROID_ABI = 'armeabi-v7a'; @@ -24,6 +57,7 @@ const LONGEST_ANDROID_ABI = 'armeabi-v7a'; export interface AndroidPathRoom { root: string; abi: string; + target: string; longest: number; maxRootLength: number; } @@ -34,25 +68,35 @@ export function androidPathRoom( abi = null, variant = null, platform = process.platform, - }: { abi?: string | null; variant?: string | null; platform?: NodeJS.Platform } = {}, + object = null, + }: { + abi?: string | null; + variant?: string | null; + platform?: NodeJS.Platform; + object?: AndroidLongestObject | null; + } = {}, ): AndroidPathRoom | null { if (platform !== 'win32') return null; + const { target, tail: objectTail } = object ?? longestAndroidObject(root); const abiName = abi ?? LONGEST_ANDROID_ABI; const configurationLength = /release$/i.test(variant ?? '') ? 'RelWithDebInfo'.length : 'Debug'.length; - const tail = LONGEST_ANDROID_OBJECT_PATH_TAIL + configurationLength - 'Debug'.length + abiName.length; + const tail = objectTail + configurationLength - 'Debug'.length + abiName.length; const longest = root.length + tail; - if (longest < ANDROID_OBJECT_PATH_MAX) return null; - return { root, abi: abiName, longest, maxRootLength: ANDROID_OBJECT_PATH_MAX - 1 - tail }; + if (longest <= LONGEST_SHORTENED_OBJECT_PATH) return null; + return { root, abi: abiName, target, longest, maxRootLength: LONGEST_SHORTENED_OBJECT_PATH - tail }; } -export function androidPathRoomMessage({ root, abi, longest, maxRootLength }: AndroidPathRoom): string { +export function androidPathRoomMessage({ root, abi, target, longest, maxRootLength }: AndroidPathRoom): string { return ( `The project root ${root} is ${root.length} characters, so the longest known Android native object path ` + - `(react_codegen_safeareacontext for ${abi}) reaches ${longest} characters, past the ${ANDROID_OBJECT_PATH_MAX - 1} ` + - `the NDK's ninja can be given on Windows; the root can be at most ${maxRootLength} characters.` + `(${target} for ${abi}) reaches ${longest} characters, past the ${LONGEST_SHORTENED_OBJECT_PATH} ` + + `CMake can shorten it to for the NDK's ninja on Windows; the root can be at most ${maxRootLength} characters.` ); } -export function androidPathRoomRemedy({ root }: AndroidPathRoom): string { - return `Map the project to a drive letter (\`subst X: "${root}"\`) and run Stim from X:\\, or move it under a shorter root.`; +export function androidPathRoomRemedy({ maxRootLength }: AndroidPathRoom): string { + return ( + `Move or clone the project to a real path of at most ${maxRootLength} characters, such as C:\\src\\, ` + + 'and run Stim there. A subst drive does not help: Node and Expo autolinking resolve it back to the long path.' + ); } diff --git a/packages/stim-cli/src/guide/errors.ts b/packages/stim-cli/src/guide/errors.ts index ab47bd3f5..35e4c595f 100644 --- a/packages/stim-cli/src/guide/errors.ts +++ b/packages/stim-cli/src/guide/errors.ts @@ -184,12 +184,17 @@ Branch on the code, never on the message.`, after their mangled absolute source path, so each carries the project root twice unless CMake shortens it, and CMake only shortens a name that then fits the object path limit Stim's Gradle shim sets. The message names the - longest known object (measured on the e2e fixture, for the ABI being - built) and the longest root it leaves room for. Refused BEFORE Gradle runs - -- a cache hit still installs -- because the build would otherwise fail - deep inside ninja ("mkdir ... No such file or directory" or "Filename - longer than 260 characters"). Map the project to a drive letter (\`subst - X: \`, then run Stim from X:\\) or move it under a shorter root. + longest known object for the ABI being built and the longest root it + leaves room for. A project with react-native-gesture-handler 3, whose + shared shadow nodes have the longest known object path, fits a root of at + most 24 characters for an x86_64 Debug build and 21 for arm64-v8a; other + projects fit 51 and 48. Refused BEFORE Gradle + runs -- a cache hit still installs -- because the build would otherwise + fail deep inside ninja ("mkdir ... No such file or directory" or + "Filename longer than 260 characters"). Move or clone the project under a + short real path such as C:\\src\\ and run Stim there. A subst drive + does not help: Node's realpath and Expo autolinking resolve it back to the + long path, and Gradle then sees two roots. A custom Gradle buildStagingDirectory can be shorter than the default; this preflight does not inspect that configured path and may refuse it too. \`doctor --platform android\` reports the same root as a finding for the diff --git a/packages/stim-cli/src/guide/lifecycle.ts b/packages/stim-cli/src/guide/lifecycle.ts index bb34e880f..564a2f11a 100644 --- a/packages/stim-cli/src/guide/lifecycle.ts +++ b/packages/stim-cli/src/guide/lifecycle.ts @@ -1597,7 +1597,8 @@ then compiles through the cache the selection leaves -- ccache, or none when compilerCache is none. It never refuses. See https://stim.appandflow.com/docs/android-cas for setup, evidence, and limits. Compiler/PCH modes have separate generated directories under each module's -.cxx/stim- (or custom staging root). Switching modes in Stim selects +.cxx/stim- (.cxx/ on Windows, or custom +staging root). Switching modes in Stim selects the matching directory; direct Gradle builds keep their own configuration. Generated CAS directories still depend on Stim's environment and adapter paths. diff --git a/packages/stim-cli/src/guide/settings.ts b/packages/stim-cli/src/guide/settings.ts index 7e30d5e7b..a6da2791e 100644 --- a/packages/stim-cli/src/guide/settings.ts +++ b/packages/stim-cli/src/guide/settings.ts @@ -1194,7 +1194,9 @@ PCH mode matches; disable artifact caching too to force native compilation. Lega cannot key these compiler profiles, so Stim skips that tier for custom profiles and Android CAS. Providers implementing the Stim key contract remain usable. Android compiler/PCH profiles also get separate CMake staging directories under -/.cxx/stim-, or under the project's custom staging root. Switching +/.cxx/stim- (/.cxx/ on +Windows, where every character counts against MAX_PATH), or under the project's +custom staging root. Switching backends in Stim selects the matching directory without deleting previous builds. These directories accumulate across profile changes and shim upgrades. Stim does not prune them; remove an obsolete generated profile only with all native diff --git a/scripts/type-assertion-baseline.json b/scripts/type-assertion-baseline.json index 46e9289ce..1e46187e7 100644 --- a/scripts/type-assertion-baseline.json +++ b/scripts/type-assertion-baseline.json @@ -106,7 +106,7 @@ }, "test": { "as": 543, - "nonNull": 793 + "nonNull": 792 } } } diff --git a/website/docs/android-cas.md b/website/docs/android-cas.md index fd0ccb008..0220f225f 100644 --- a/website/docs/android-cas.md +++ b/website/docs/android-cas.md @@ -222,6 +222,7 @@ probe is the independently runnable correctness evidence. and different build-system layouts need coverage. - Generated CAS configurations retain adapter paths and depend on the CAS environment. Stim separates compiler/PCH modes under `.cxx/stim-` + (`.cxx/` on Windows) so switching back to `compilerCache: "ccache"` or `"none"` uses a different configuration without deleting evidence. Direct Gradle uses its own staging directory. Legacy unprofiled configurations need their previous cleanup flow. diff --git a/website/docs/build-optimizations.md b/website/docs/build-optimizations.md index c2726213f..5e3121c11 100644 --- a/website/docs/build-optimizations.md +++ b/website/docs/build-optimizations.md @@ -161,7 +161,9 @@ Legacy Expo cache providers cannot distinguish these custom compiler profiles and are skipped for them; providers using Stim's cache-key contract remain usable. Android compiler and PCH profiles also use separate generated CMake directories -under each module's `.cxx/stim-` or its custom staging root. Switching +under each module's `.cxx/stim-` (`.cxx/` +on Windows, where every character counts against `MAX_PATH`) or its custom +staging root. Switching profiles keeps previous output for reuse. These directories can accumulate; there is no profile-pruning command. Remove obsolete generated profiles only after all native builds have stopped, or let worktree removal reclaim them. diff --git a/website/docs/requirements.md b/website/docs/requirements.md index 89920b152..83118ac2f 100644 --- a/website/docs/requirements.md +++ b/website/docs/requirements.md @@ -70,11 +70,16 @@ android` reports it and `stim android` refuses with `STIM_BUILD_FAILED` - A working Java and Gradle setup for the project. - On Windows, a project root short enough for the NDK's object paths: its ninja cannot open a path of 260 characters or more, and a React Native - codegen object path only fits when CMake can shorten it. `stim doctor ---platform android` reports a root that leaves no room and `stim android` - refuses it with `STIM_PATH_TOO_LONG` before Gradle runs; `subst X: ""` - and working from `X:\` is the usual fix. The check assumes the default native - staging path and may also refuse a shorter custom `buildStagingDirectory`. + codegen object path only fits when CMake can shorten it. A project with + react-native-gesture-handler 3 fits a root of at most 24 characters for an + x86_64 emulator build and 21 for arm64-v8a, such as `C:\src\trailhead`; + other projects fit about twice that. `stim doctor --platform android` + reports a root that leaves no room and `stim android` refuses it with + `STIM_PATH_TOO_LONG` before Gradle runs. Move or clone the project under a + short real path such as `C:\src\`. A `subst` drive does not work: Node + and Expo autolinking resolve it back to the long path. The check + assumes the default native staging path and may also refuse a shorter + custom `buildStagingDirectory`. ## Windows @@ -90,8 +95,7 @@ is no local Xcode path. Requirements on top of the Android list: - Emulator acceleration: WHPX (Windows Hypervisor Platform) or the Android Emulator hypervisor driver. `emulator -accel-check` reports which is active. - JDK 17 on `JAVA_HOME` or `PATH`. -- A short project path or a `subst` drive, as described in the Android path - requirement above. +- A short project path, as described in the Android path requirement above. On Windows, `stim doctor --platform android` checks path room, and `stim doctor --platform ios` points at `--remote eas` for iOS.