stim 1.19.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/{activity-BSw0x84R.mjs → activity-BrGh9EAI.mjs} +3 -3
- package/dist/{agent-device-usage-output-Bjxu_VmX.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
- package/dist/android-0vv-OruW.mjs +6 -0
- package/dist/android-Brx6EKvJ.mjs +1258 -0
- package/dist/{android-BdQC6zs1.mjs → android-BsYNR81W.mjs} +103 -45
- package/dist/{android-DE5g_5gv.mjs → android-CK3fpEyZ.mjs} +2 -2
- package/dist/{android-cas-C87geAg-.mjs → android-cas-DCAuXryb.mjs} +9 -5
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/api-run.mjs +159 -8
- package/dist/{api-DIf_s86s.mjs → api-zuX3ynYN.mjs} +5 -1
- package/dist/api.d.mts +98 -4
- package/dist/api.mjs +1 -1
- package/dist/{app-install-CFI4ptUL.mjs → app-install-0LgaQRT7.mjs} +9 -8
- package/dist/artifact-CnC0mfov.mjs +807 -0
- package/dist/artifact-DClXJNWx.mjs +772 -0
- package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
- package/dist/{browser-web-CDhqQxhv.mjs → browser-web-CzBmhmgs.mjs} +19 -19
- package/dist/{budget-Dz-kyXLv.mjs → budget-BONq9vXY.mjs} +3101 -3062
- package/dist/build-BixRI6sB.mjs +462 -0
- package/dist/{build-facts-B87vo-Im.d.mts → build-facts-DcLv2HSd.d.mts} +1 -32
- package/dist/build-plan-CP0tckNU.mjs +553 -0
- package/dist/{cache-manifest-DiwKSyv9.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{manifest-CX0d1IkZ.mjs → ccache-CjHQ5F8g.mjs} +13 -19
- package/dist/{chrome-BTUOo8p2.mjs → chrome-Dp2lMLBe.mjs} +1 -1
- package/dist/{cli-C9Zvgbdc.mjs → cli-B2R8wH1F.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/{client-CuUo8Nvg.mjs → client-BLy1Pw2V.mjs} +89 -307
- package/dist/collector-run.d.mts +4 -0
- package/dist/collector-run.mjs +7 -7
- package/dist/{command-output-Bt8ygHE7.mjs → command-output-BjPN99ZB.mjs} +2 -2
- package/dist/{config-DC5jEtd1.mjs → config-D0LhTY3S.mjs} +8 -12
- package/dist/{created-devices-D0s26Hx5.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
- package/dist/{dependency-state-DKaS1OM9.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
- package/dist/{detached-entry-pB_IPfV4.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
- package/dist/{dev-client-CDY_zAmD.mjs → dev-client-ClATaJoH.mjs} +2 -2
- package/dist/{device-BHw0WlHl.mjs → device-B7lsC0K1.mjs} +8 -8
- package/dist/device-DVCwAFlX.mjs +727 -0
- package/dist/{device-capacity-BoN1jQkO.mjs → device-capacity-Ca_ke1LE.mjs} +13 -14
- package/dist/device-host-worker.mjs +38 -18
- package/dist/device-ios-CRUqPxKw.mjs +413 -0
- package/dist/{device-lease-Cu7PtQpZ.mjs → device-lease-DHOBXGgS.mjs} +135 -22
- package/dist/{device-lease-run-D38SnZtt.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
- package/dist/{device-pool-CP4rAsHx.mjs → device-pool-L2-5GxMy.mjs} +3 -3
- package/dist/{device-remote-B5GiUifk.mjs → device-remote-Bmg42063.mjs} +17 -68
- package/dist/doctor-C2MSuMMg.mjs +515 -0
- package/dist/{doctor-PRJH94v6.mjs → doctor-DPAqhVCK.mjs} +333 -744
- package/dist/{doctor-watchman-wZhyTdp4.mjs → doctor-watchman-CB4NnVZm.mjs} +13 -12
- package/dist/{simslim-D9LkJxIO.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
- package/dist/{stim-installations-DbmUjsLv.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
- package/dist/{error-diagnostics--v5HGOU0.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
- package/dist/errors-DzkMYh8Q.mjs +205 -0
- package/dist/{exec-i8GDPwq9.mjs → exec-B81Qodjn.mjs} +40 -10
- package/dist/{gc-CyHMcKcn.mjs → gc-PWp34zGb.mjs} +21 -20
- package/dist/{device-ios-B2ruohE-.mjs → gradle-C2OEKCCI.mjs} +21 -650
- package/dist/{guide-dRaQJJk9.mjs → guide-Dai9lphv.mjs} +504 -117
- package/dist/{hosted-android-Bs7fytj7.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
- package/dist/{hosted-client-C5G4h_OJ.mjs → hosted-client-Io01s5By.mjs} +9 -3
- package/dist/{hosted-ios-Bik035eL.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
- package/dist/{hosted-logs-CI9Az9a4.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
- package/dist/{hosted-macos-DABm3ScO.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
- package/dist/{hosted-native-DbV2aUVw.mjs → hosted-native-BdAQi21M.mjs} +17 -14
- package/dist/{idle-shutdown-DYiMBn34.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
- package/dist/{ios-DYUr9LGc.mjs → ios-BZ9dg2Fr.mjs} +8 -7
- package/dist/{ios-BWbHFZVz.mjs → ios-CXLIUxXJ.mjs} +180 -24
- package/dist/ios-CZuv6gRv.mjs +1112 -0
- package/dist/{ios-DCrkgXEM.mjs → ios-VGvNdsv2.mjs} +1 -1
- package/dist/{ios-device-CrW8aMb5.mjs → ios-device-B1B14xND.mjs} +1 -1
- package/dist/{ios-device-CJ_ZOGky.mjs → ios-device-BreZOpM0.mjs} +3 -3
- package/dist/ios-project-B9drt_aY.mjs +197 -0
- package/dist/{ios-state-By0XuLTQ.mjs → ios-state-CjrL2qv-.mjs} +1 -1
- package/dist/js-swap-C8icNYIY.mjs +432 -0
- package/dist/launch-DxH0THLP.mjs +745 -0
- package/dist/{react-native-ios-CSpIxexZ.mjs → launch-YrbqaIFj.mjs} +59 -612
- package/dist/{launch-verify-DCveSE6d.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
- package/dist/{logs-Bao1HOMy.mjs → logs-DHaRNfg7.mjs} +11 -11
- package/dist/macos-C0iljhsN.mjs +2 -0
- package/dist/{macos-ciyBdTY5.mjs → macos-V_3Ui7g4.mjs} +42 -50
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +13 -10
- package/dist/{metro-DI2AjPF_.mjs → metro-D_j9sN-B.mjs} +18 -5
- package/dist/{metro-gateway-BxpJLIwh.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
- package/dist/miss-reason-BPAzZS0r.mjs +238 -0
- package/dist/{named-ports-B4iWH4E-.mjs → named-ports-C2lvoPsb.mjs} +3 -3
- package/dist/native-android-lHzaew7r.mjs +226 -0
- package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
- package/dist/{native-runtime-BGxsbfny.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
- package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
- package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
- package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
- package/dist/offload-worker.d.mts +41 -7
- package/dist/offload-worker.mjs +409 -74
- package/dist/{ownership-EB0ySdGK.mjs → ownership-DXzMMSx6.mjs} +2 -2
- package/dist/{placement-log-Dxe34rJm.mjs → placement-log-reFbjqEr.mjs} +1 -1
- package/dist/plan-BDk2AeY_.mjs +260 -0
- package/dist/plan-placement-D_YYlE6N.mjs +73 -0
- package/dist/{ports-ByPc0_97.mjs → ports-jzvAJXXL.mjs} +3 -3
- package/dist/{prebuild-BKJ3wJI1.mjs → prebuild-Bw44QxVh.mjs} +4 -4
- package/dist/{preview-BbSottmO.mjs → preview-BaBJ72Al.mjs} +50 -32
- package/dist/project-B8_duAzW.mjs +1067 -0
- package/dist/project-plan-BPwbLwQu.mjs +10 -0
- package/dist/{pull-request-IHNdMnG8.mjs → pull-request-vOhLNmIb.mjs} +1 -1
- package/dist/pull-requests.mjs +1 -1
- package/dist/react-native-android-chm1DMlA.mjs +921 -0
- package/dist/react-native-build-oAw6gQeW.mjs +239 -0
- package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
- package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
- package/dist/{recordings-Bv8McZN4.mjs → recordings-Bk-VivUV.mjs} +1 -1
- package/dist/{reload-rAPdqeRR.mjs → reload-D34GkY78.mjs} +17 -13
- package/dist/remote-BU9UyhCJ.mjs +192 -0
- package/dist/{remote-cache-N3hAEaAL.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
- package/dist/result-BDoRk1k3.mjs +186 -0
- package/dist/{run-lAkle1-c.mjs → run-qvff78hv.mjs} +3 -3
- package/dist/{server-bare-CkIjnegq.mjs → server-bare-dWjAarki.mjs} +5 -5
- package/dist/server-command-7XIKlWtO.mjs +69 -0
- package/dist/{server-expo-n3tUi3Em.mjs → server-expo-CsYu2aKL.mjs} +107 -82
- package/dist/server-expo-DV43vep4.mjs +2 -0
- package/dist/{settings-CxgngUZk.mjs → settings-Bbztm4oV.mjs} +179 -129
- package/dist/{settings-2am7TSxp.mjs → settings-CT3E71-i.mjs} +6 -6
- package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
- package/dist/settings.schema.json +84 -0
- package/dist/simslim-ChMfe8rg.mjs +149 -0
- package/dist/{slot-launch-HV_KtPIw.mjs → slot-launch-B9hn96wd.mjs} +6 -6
- package/dist/{stage-BgaZJVZY.mjs → stage-BNKKdDNc.mjs} +6 -3
- package/dist/start-CbQCSnXQ.mjs +2 -0
- package/dist/{start-DtxthMfI.mjs → start-CdvcTaXr.mjs} +97 -68
- package/dist/{state-Tp4uImDS.mjs → state-Boo0IMJq.mjs} +1 -1
- package/dist/{state-D2KtnALG.mjs → state-DI84A-uu.mjs} +29 -10
- package/dist/{stats-8EJ5ivFT.mjs → stats-BIIDrjA8.mjs} +5 -5
- package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
- package/dist/{status-BOyp0CUl.mjs → status-DARMkc9q.mjs} +81 -293
- package/dist/status-watch-DvmI3SmN.mjs +296 -0
- package/dist/{stim-desktop-C65jk_q2.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
- package/dist/{stop-DRipAg-e.mjs → stop-CCZS-C9m.mjs} +2 -2
- package/dist/{stop-D0UIpZuh.mjs → stop-WQPQHLuy.mjs} +5 -5
- package/dist/{stop-zDlknFoz.mjs → stop-iNemHsJC.mjs} +21 -20
- package/dist/supervisor-run.d.mts +7 -2
- package/dist/supervisor-run.mjs +32 -20
- package/dist/{support-DsNaFg6D.mjs → support-CnE_SIjt.mjs} +17 -7
- package/dist/{support-CNqYkV0y.mjs → support-DCco5fut.mjs} +61 -8
- package/dist/{swiftpm-macos-aJzPAj1t.mjs → swiftpm-macos-afeNUExW.mjs} +5 -5
- package/dist/tailnet-CoJUb2tB.mjs +265 -0
- package/dist/toolchain-BIupIro1.mjs +2 -0
- package/dist/{toolchain-C3xSoyUl.mjs → toolchain-DqyDXs30.mjs} +206 -53
- package/dist/{trigger-CAhmG-4s.mjs → trigger-C0MWBi8O.mjs} +2 -2
- package/dist/trigger-SHLOWJ4O.mjs +2 -0
- package/dist/{warm-progress-pFLB2e6P.mjs → warm-progress-C3ASD6z5.mjs} +6 -6
- package/dist/{watchman-BL1IdOtq.mjs → watchman-CQl51YZu.mjs} +6 -6
- package/dist/{web-DbbrPUn0.mjs → web-B3eldHFH.mjs} +9 -13
- package/dist/web-BLXpOe6b.mjs +2 -0
- package/dist/web-run.mjs +10 -10
- package/dist/{workspace-state-CR9X86xJ.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
- package/dist/{workspaces-Djb2gfi9.mjs → workspaces-D2xSHBDY.mjs} +1260 -1258
- package/dist/{worktree-BL4M1rhB.mjs → worktree-BxDqaTev.mjs} +84 -48
- package/dist/worktree-q7_OZNY0.mjs +2 -0
- package/package.json +8 -6
- package/shim/native-android.gradle +101 -0
- package/dist/android-BXc6BmEi.mjs +0 -3732
- package/dist/android-BvV1gGdN.mjs +0 -4
- package/dist/ios-ITYno9rO.mjs +0 -2241
- package/dist/macos-nIOATJmR.mjs +0 -2
- package/dist/metro-store-BAHFcJm6.mjs +0 -106
- package/dist/miss-reason-CjQCOKqq.mjs +0 -1743
- package/dist/plan-placement-BVY59qa8.mjs +0 -290
- package/dist/project-B5eQ1USs.mjs +0 -612
- package/dist/react-native-ios-BMh2i25I.mjs +0 -2
- package/dist/server-expo-DGsdUGX2.mjs +0 -2
- package/dist/start-D8NbxNBb.mjs +0 -2
- package/dist/trigger-CoalsMTV.mjs +0 -2
- package/dist/web-DFpFeZu5.mjs +0 -2
- package/dist/worktree-DV-A0LNL.mjs +0 -2
|
@@ -1,3732 +0,0 @@
|
|
|
1
|
-
import { n as ownership_claim_exports } from "./process-identity-Biv3arCO.mjs";
|
|
2
|
-
import { t as getExecutor } from "./exec-i8GDPwq9.mjs";
|
|
3
|
-
import { t as spawnEntry } from "./spawn-entry-Dcb7sDUz.mjs";
|
|
4
|
-
import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
|
|
5
|
-
import { _ as stepTimer, c as launchErrorReport, f as refuseNoProject, g as stepClock, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-Bt8ygHE7.mjs";
|
|
6
|
-
import { C as saveConfig, D as withConfigLock$1, E as upsertProject, L as deviceSlotFileKey, R as deviceSlotKey, V as parseDeviceSlotOption, W as validateDeviceSlot, _ as loadConfig$1, j as formatBytes, p as getProject, u as getConcurrencyLimits, w as setDevice, z as deviceSlotPlatforms } from "./config-DC5jEtd1.mjs";
|
|
7
|
-
import { c as writeWorkspaceState, g as workspaceAgentDeviceDir, i as recordWorkspaceUse, l as emulatorLogFile, r as readWorkspaceState$1, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-CR9X86xJ.mjs";
|
|
8
|
-
import { M as writeWorkspaceLaunch, b as MODE_EXPO, c as projectShortcut, p as detectIsExpo, r as findProjectRoot, s as projectProblem, t as NO_PROJECT_REFUSAL, y as MODE_BARE } from "./project-B5eQ1USs.mjs";
|
|
9
|
-
import { A as publicUrlSetting, B as settingShapeErrors, C as metroWarmupUrlSetting, Ct as removeTemporaryEntry, Dt as optimizationBuildProfile, Et as compilerCacheFallbackMessage, F as resolveCacheProviderConfig, H as settingsLayers, I as resolveSettings, L as settingFile, M as remoteAndroidSetting, N as remoteEasFallbackSetting, Ot as resolveOptimizations, St as makeTemporaryDirectory, T as parseIosRemote, Tt as artifactCachePolicy, U as tunnelModeSetting, W as unknownSettingKeys, i as SETTING_SHAPE_REMEDY, l as androidDataPartitionSizeGbSettingError, mt as repoRoot, nt as gitCommonDir, o as androidAvdConfigSettingError, q as detectProviders, u as cacheProviderSettingError, x as metroPortSetting, z as settingOriginScope } from "./settings-CxgngUZk.mjs";
|
|
10
|
-
import { d as signalProcessTree, s as pidExists, u as resolveProjectMetro } from "./metro-DI2AjPF_.mjs";
|
|
11
|
-
import { F as recordFinishedBuild, I as startBuildProgress, L as tapBuildLog, O as NO_BUILD_PROGRESS, R as createRunRecorder, U as recordRunStats, V as recordCapacityRefusal, W as statsProjectKey$1, t as budgetGate, z as readRunEstimates } from "./budget-Dz-kyXLv.mjs";
|
|
12
|
-
import { D as teardownOwnedAvd, M as acquireBuildSlot, P as releaseBuildSlot } from "./workspaces-Djb2gfi9.mjs";
|
|
13
|
-
import { A as providerDownloadPath, B as captureAssetManifest, D as fingerprintProject, E as fingerprintDiffRecord, F as storedAssetManifest, H as readAssetManifest, L as untrackedMissLine, M as refingerprintAfterMutation, N as resolveBuild, O as inputsChangedDuringBuild, P as storeBuild, R as untrackedNativeFiles, S as configInputsChanged, T as filesystemBuildCapability, V as compareAssetManifests, _ as waitForBuild, b as buildCacheKey, g as releaseBuildLock, j as providerUploadOutcome, k as prepareProviderDownloadDir, m as acquireBuildLock, v as waitForSharedBuild, x as changedDuringBuildLine, z as assetDiffReason } from "./device-lease-Cu7PtQpZ.mjs";
|
|
14
|
-
import { $ as trimAndroidCaches, A as listAvds, B as ownedAvdSystemImage, D as hostSystemImageArch, F as ownedAvdDeviceProfile, G as probeEmulatorSerial, I as ownedAvdDirectory, J as resolveOwnedAvdSerial, K as profileNeedsFoldFeature, O as listAdbDevices, Q as systemImageSupportsFold, T as findBuildTool, U as physicalDeviceModel, W as pickDefaultSystemImage, Y as resolvePhysicalDevice, a as androidDeviceAbi, ft as parkedMaxSetting, i as androidDataFreeBytes, j as listInstalledSystemImages, k as listAvdDeviceProfiles, l as androidSystemImageAbi, q as resetAdoptedAvd, r as androidBuildToolName, rt as configuredAndroidEmulatorApp, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, u as androidToolCwd } from "./android-BdQC6zs1.mjs";
|
|
15
|
-
import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-C65jk_q2.mjs";
|
|
16
|
-
import { r as createNdjsonWriter } from "./ndjson-LGZAMsqa.mjs";
|
|
17
|
-
import { A as resolveBuildPlacement, C as setRemoteLogSink, D as parseBuildMachineOption, E as namedBuildMachine, O as requireLocalBuild, S as withNativeBuildRun, a as offloadBuild, b as runCancellation, c as placementLoad, i as closeOffload, l as remotePhaseText, n as buildPlacementCandidates, p as cancelledFailure, r as chooseBuildMachine, s as offloadPlacement, w as OffloadRefusal, x as runCancellationSignal } from "./client-CuUo8Nvg.mjs";
|
|
18
|
-
import { n as DeviceAdmissionRefusal, r as checkDeviceCapacity, t as DEFAULT_DEVICE_SLOT_WAIT_MS } from "./device-capacity-BoN1jQkO.mjs";
|
|
19
|
-
import { i as ccacheActivityLine, n as CCACHE_NOT_RUN, r as CCACHE_UNAVAILABLE, s as resolveCcache } from "./manifest-CX0d1IkZ.mjs";
|
|
20
|
-
import { E as startBuildHeartbeat, O as tailLines, h as HEARTBEAT_INTERVAL_MS, n as androidToolchain, t as androidRequirements } from "./toolchain-C3xSoyUl.mjs";
|
|
21
|
-
import { t as cleanLine } from "./server-expo-n3tUi3Em.mjs";
|
|
22
|
-
import { r as hostedMetroSettings } from "./metro-gateway-BxpJLIwh.mjs";
|
|
23
|
-
import { a as writeHostedAndroid, i as writeDevicePlacement, t as readHostedAndroid } from "./ios-state-By0XuLTQ.mjs";
|
|
24
|
-
import { _ as devicePlacementLine, a as UPDATES_MANIFEST_NAME, d as ensureOwnedDevice, f as AvdBootError, g as automaticDevicePlacement, h as checkEasFallback, i as skippedMissReason, l as didSetUpDevice, m as warmMetro, n as fingerprintErrorMissReason, o as detectEntryFile, p as AvdRecoveryError, s as refreshUpdatesManifestFile, t as explainBuildMiss, u as ensureBooted } from "./miss-reason-CjQCOKqq.mjs";
|
|
25
|
-
import { n as devicePlacementRecord, t as buildPlacementRecord } from "./placement-log-Dxe34rJm.mjs";
|
|
26
|
-
import { n as prepareHostedAndroid, t as placeHostedAndroid } from "./hosted-android-Bs7fytj7.mjs";
|
|
27
|
-
import { S as restartedAppNote, b as launchAndroidReleaseApp, c as androidDevClientUrl, g as installConflictKind, h as installAndroidApp, r as DEFAULT_METRO_PORT, s as androidAppProcess, t as ADB_INSTALL_TIMEOUT_MS, y as launchAndroidApp } from "./app-install-CFI4ptUL.mjs";
|
|
28
|
-
import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-HV_KtPIw.mjs";
|
|
29
|
-
import { a as VERIFY_TIMEOUT_MS, d as verifyAndroidReleaseLaunch, f as verifyLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unattributedLaunchLines, n as LAUNCH_FATAL, r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING, u as unverifiedLaunchLines } from "./launch-verify-DCveSE6d.mjs";
|
|
30
|
-
import { a as easAuthNote, c as loadProjectProvider, f as resolveEasCliBin, i as checkEasAuth, m as uploadRemote, n as UPLOAD_TIMEOUT_MS, o as exitAfterFlush, p as resolveRemote, r as cacheLevel, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS } from "./remote-cache-N3hAEaAL.mjs";
|
|
31
|
-
import { C as resolveSystemImage, S as resolveDeviceProfile, T as systemImageRefusal, _ as isReleaseVariant, a as PLATFORM, b as pooledAndroidDevice, d as apkPackage, f as deviceProfileRefusal, g as foldableImageRefusal, i as NO_FINGERPRINT, m as dumpApkManifest, n as LAUNCH_FAILED, o as androidBuildOptions, p as displayPath, r as NO_DEVICE, s as androidDevClientScheme, t as INSTALL_FAILED, v as noDeviceDiagnostic, w as resolveVariant, x as remoteAvdFlagRefusal } from "./support-DsNaFg6D.mjs";
|
|
32
|
-
import { d as productFlavorRefusal, f as readProductFlavors, h as formatDiagnostic, o as buildAndroid } from "./device-ios-B2ruohE-.mjs";
|
|
33
|
-
import { T as ownedSessionName, a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext } from "./device-remote-B5GiUifk.mjs";
|
|
34
|
-
import { a as isEasBuildFailure, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-D9LkJxIO.mjs";
|
|
35
|
-
import { c as startDevServer, m as detectAppIds, p as detectAndroidPackage } from "./start-DtxthMfI.mjs";
|
|
36
|
-
import { a as acquireRunLease, c as lostRefusal, d as runLease, f as waitFlagConflict, l as parseDeviceWait, o as leaseExpiryText, s as lostLine, t as DEBUG_VERIFY_STEP_MS, u as releaseLeaseOnSignal } from "./device-lease-run-D38SnZtt.mjs";
|
|
37
|
-
import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-EB0ySdGK.mjs";
|
|
38
|
-
import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-BGxsbfny.mjs";
|
|
39
|
-
import { t as selectFromPool } from "./device-pool-CP4rAsHx.mjs";
|
|
40
|
-
import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-BKJ3wJI1.mjs";
|
|
41
|
-
import { a as printPlan, i as planPayload, n as planCachedBuild, o as refusePlan, r as planFlagRefusal, s as runArtifactLifecycle, t as planHostedDevice } from "./plan-placement-BVY59qa8.mjs";
|
|
42
|
-
import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics--v5HGOU0.mjs";
|
|
43
|
-
import { n as resolveAndroidCas, r as resolveAndroidCompilerCache } from "./android-cas-C87geAg-.mjs";
|
|
44
|
-
import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
|
|
45
|
-
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
46
|
-
import { buildDiagnostics, hostedAndroidStatus, machineCapacity, parseMachine } from "@stim-cli/core/state";
|
|
47
|
-
import chalk from "chalk";
|
|
48
|
-
import { InvalidArgumentError } from "commander";
|
|
49
|
-
import { workspaceId } from "@stim-cli/core";
|
|
50
|
-
import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
|
|
51
|
-
//#region src/commands/android/remote.ts
|
|
52
|
-
async function connectAndroidHosting({ root, slot, machine, release, metroCheck, selectors, read = readHostedAndroid, resolveSerial = resolveOwnedAvdSerial, prepare = prepareHostedAndroid }) {
|
|
53
|
-
let recorded;
|
|
54
|
-
try {
|
|
55
|
-
recorded = read(root, slot)[slot];
|
|
56
|
-
} catch (error) {
|
|
57
|
-
return { failure: {
|
|
58
|
-
code: "STIM_HOSTING_REFUSED",
|
|
59
|
-
message: error.message,
|
|
60
|
-
remedy: `Restore that slot's recorded machine and session from the host, then run stim stop${slot === "default" ? "" : ` --slot ${slot}`} to reconcile it.`
|
|
61
|
-
} };
|
|
62
|
-
}
|
|
63
|
-
if (recorded && recorded.machine !== machine) return { failure: {
|
|
64
|
-
code: "STIM_BAD_ARG",
|
|
65
|
-
message: `This workspace's Android emulator runs on ${recorded.machine}; run stim stop first.`
|
|
66
|
-
} };
|
|
67
|
-
if (!machine) return { target: null };
|
|
68
|
-
if (!release && !metroCheck) return { failure: {
|
|
69
|
-
code: "STIM_BAD_ARG",
|
|
70
|
-
message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
|
|
71
|
-
remedy: "Run stim stop; stim start, then retry without --no-metro-check."
|
|
72
|
-
} };
|
|
73
|
-
const local = deviceSlotPlatforms(getProject(root), slot)?.android;
|
|
74
|
-
if (local?.owned && local.avdName) {
|
|
75
|
-
let running = true;
|
|
76
|
-
try {
|
|
77
|
-
const resolved = resolveSerial(local.avdName, { timeoutMs: 5e3 });
|
|
78
|
-
running = Boolean(resolved.serial) || !resolved.notRunning && !resolved.missing;
|
|
79
|
-
} catch {}
|
|
80
|
-
if (running) return { failure: {
|
|
81
|
-
code: "STIM_BAD_ARG",
|
|
82
|
-
message: `This workspace's Android emulator for slot ${slot} runs on this Mac; run stim stop first.`
|
|
83
|
-
} };
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
return { target: await prepare(machine, selectors, recorded) };
|
|
87
|
-
} catch (error) {
|
|
88
|
-
return { failure: {
|
|
89
|
-
code: error.code ?? "STIM_HOSTING_REFUSED",
|
|
90
|
-
message: error.message,
|
|
91
|
-
...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
|
|
92
|
-
} };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async function selectAndroidPlacement({ target: selected, automatic = automaticDevicePlacement, checkBudget = budgetGate, note, phase, log, fromFlag, noWait, buildMachine, localSelectors, eas, ...args }) {
|
|
96
|
-
const selectors = selected.kind === "hosted" ? {
|
|
97
|
-
...selected.systemImage ? { systemImage: selected.systemImage } : {},
|
|
98
|
-
...selected.deviceProfile ? { deviceProfile: selected.deviceProfile } : {}
|
|
99
|
-
} : {};
|
|
100
|
-
if (selected.kind !== "hosted" || selected.machine !== "auto") {
|
|
101
|
-
const connected = await connectAndroidHosting({
|
|
102
|
-
...args,
|
|
103
|
-
selectors,
|
|
104
|
-
machine: selected.kind === "hosted" ? selected.machine : null
|
|
105
|
-
});
|
|
106
|
-
if ("failure" in connected) return connected;
|
|
107
|
-
return {
|
|
108
|
-
target: selected,
|
|
109
|
-
hostedTarget: connected.target,
|
|
110
|
-
selectors,
|
|
111
|
-
budget: connected.target ? {
|
|
112
|
-
reclaimed: [],
|
|
113
|
-
refusal: null
|
|
114
|
-
} : await checkBudget({
|
|
115
|
-
root: args.root,
|
|
116
|
-
note
|
|
117
|
-
})
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
const refusal = localSelectors(selected);
|
|
121
|
-
if (refusal) return { failure: refusal };
|
|
122
|
-
try {
|
|
123
|
-
const placed = await automatic({
|
|
124
|
-
root: args.root,
|
|
125
|
-
slot: args.slot,
|
|
126
|
-
platform: "android",
|
|
127
|
-
selectors,
|
|
128
|
-
buildMachine,
|
|
129
|
-
noWait,
|
|
130
|
-
...eas ? { eas } : {}
|
|
131
|
-
});
|
|
132
|
-
phase("placement:", devicePlacementLine(placed.placement, placed.skipped));
|
|
133
|
-
log?.(devicePlacementRecord({
|
|
134
|
-
platform: "android",
|
|
135
|
-
fromFlag,
|
|
136
|
-
easFallback: Boolean(eas),
|
|
137
|
-
placed
|
|
138
|
-
}));
|
|
139
|
-
if (placed.placement.decision === "eas") return {
|
|
140
|
-
target: {
|
|
141
|
-
kind: "remote",
|
|
142
|
-
backend: "eas",
|
|
143
|
-
systemImage: selected.systemImage,
|
|
144
|
-
deviceProfile: selected.deviceProfile
|
|
145
|
-
},
|
|
146
|
-
hostedTarget: null,
|
|
147
|
-
selectors,
|
|
148
|
-
budget: await checkBudget({
|
|
149
|
-
root: args.root,
|
|
150
|
-
note
|
|
151
|
-
}),
|
|
152
|
-
devicePlacement: placed.placement
|
|
153
|
-
};
|
|
154
|
-
if (placed.target && !args.release && !args.metroCheck) return { failure: {
|
|
155
|
-
code: "STIM_BAD_ARG",
|
|
156
|
-
message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
|
|
157
|
-
remedy: "Run stim stop; stim start, then retry without --no-metro-check."
|
|
158
|
-
} };
|
|
159
|
-
return {
|
|
160
|
-
target: placed.target ? {
|
|
161
|
-
...selected,
|
|
162
|
-
machine: placed.target.host.machine
|
|
163
|
-
} : {
|
|
164
|
-
kind: "emulator",
|
|
165
|
-
systemImage: selected.systemImage,
|
|
166
|
-
deviceProfile: selected.deviceProfile
|
|
167
|
-
},
|
|
168
|
-
hostedTarget: placed.target,
|
|
169
|
-
selectors,
|
|
170
|
-
budget: placed.target ? {
|
|
171
|
-
reclaimed: [],
|
|
172
|
-
refusal: null
|
|
173
|
-
} : await checkBudget({
|
|
174
|
-
root: args.root,
|
|
175
|
-
note
|
|
176
|
-
}),
|
|
177
|
-
devicePlacement: placed.placement
|
|
178
|
-
};
|
|
179
|
-
} catch (error) {
|
|
180
|
-
return { failure: {
|
|
181
|
-
code: error.code ?? "STIM_HOSTING_REFUSED",
|
|
182
|
-
message: error.message,
|
|
183
|
-
...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
|
|
184
|
-
} };
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
//#endregion
|
|
188
|
-
//#region src/commands/android/result.ts
|
|
189
|
-
function androidFacts({ root, slot, serial, avdName = null, deviceName = null, systemImage = null, deviceProfile = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease, devServer = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null, host }) {
|
|
190
|
-
return {
|
|
191
|
-
platform: PLATFORM,
|
|
192
|
-
...host ? { host } : {},
|
|
193
|
-
...slot && slot !== "default" ? { slot } : {},
|
|
194
|
-
serial: serial ?? null,
|
|
195
|
-
avdName: avdName ?? null,
|
|
196
|
-
deviceName: deviceName ?? avdName ?? null,
|
|
197
|
-
systemImage: systemImage ?? null,
|
|
198
|
-
deviceProfile: deviceProfile ?? null,
|
|
199
|
-
fingerprint: fingerprint ?? null,
|
|
200
|
-
cacheKey: cacheKey ?? null,
|
|
201
|
-
variant: variant ?? null,
|
|
202
|
-
metroPort: metroPort ?? null,
|
|
203
|
-
cacheHit: cacheLevel(cacheHit),
|
|
204
|
-
cacheSkipped: Boolean(cacheSkipped),
|
|
205
|
-
buildMachine,
|
|
206
|
-
...builtOn ? { builtOn } : {},
|
|
207
|
-
...offloadedTo ? { offloadedTo } : {},
|
|
208
|
-
...offloadFallback ? { offloadFallback } : {},
|
|
209
|
-
waitedForBuild: waitedForBuild ? {
|
|
210
|
-
pid: waitedForBuild.pid ?? null,
|
|
211
|
-
ms: waitedForBuild.ms ?? 0
|
|
212
|
-
} : null,
|
|
213
|
-
appPath: appPath ?? null,
|
|
214
|
-
bundleId: bundleId ?? null,
|
|
215
|
-
installSkipped: Boolean(installSkipped),
|
|
216
|
-
launched: launched === "unverified" || launched === "bundling" ? launched : Boolean(launched),
|
|
217
|
-
ccache,
|
|
218
|
-
debugHttpHost: debugHttpHost ?? null,
|
|
219
|
-
debugHttpHostNote: debugHttpHostNote ?? null,
|
|
220
|
-
devClientUrl: devClientUrl ?? null,
|
|
221
|
-
logs: logs ?? null,
|
|
222
|
-
...root && !host ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
|
|
223
|
-
durationMs: typeof durationMs === "number" && Number.isFinite(durationMs) ? durationMs : null,
|
|
224
|
-
...lease === void 0 ? {} : { lease },
|
|
225
|
-
...devServer ? { devServer } : {}
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
function lastBuildRecord({ devicePlacement, fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null, missReason = null, diagnostics = null, configuration = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
|
|
229
|
-
const record = {
|
|
230
|
-
platform: PLATFORM,
|
|
231
|
-
avdName: avdName ?? null,
|
|
232
|
-
deviceName: deviceName ?? avdName ?? null,
|
|
233
|
-
fingerprint: fingerprint ?? null,
|
|
234
|
-
cacheKey: cacheKey ?? null,
|
|
235
|
-
cacheHit: cacheLevel(cacheHit),
|
|
236
|
-
cacheSkipped: Boolean(cacheSkipped),
|
|
237
|
-
durationMs: Number.isFinite(durationMs) ? durationMs : null,
|
|
238
|
-
appPath: appPath ?? null,
|
|
239
|
-
bundleId: bundleId ?? null,
|
|
240
|
-
startedAt,
|
|
241
|
-
status,
|
|
242
|
-
configuration: configuration ?? null
|
|
243
|
-
};
|
|
244
|
-
if (errorCode) record.errorCode = errorCode;
|
|
245
|
-
if (devicePlacement) record.devicePlacement = devicePlacement;
|
|
246
|
-
record.buildMachine = buildMachine;
|
|
247
|
-
if (builtOn) record.builtOn = builtOn;
|
|
248
|
-
if (offloadedTo) record.offloadedTo = offloadedTo;
|
|
249
|
-
if (offloadFallback) record.offloadFallback = offloadFallback;
|
|
250
|
-
if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
|
|
251
|
-
const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
|
|
252
|
-
if (recorded.length) record.diagnostics = recorded;
|
|
253
|
-
return record;
|
|
254
|
-
}
|
|
255
|
-
async function finishAndroidUpload(uploadPending, remote, phase) {
|
|
256
|
-
if (!uploadPending) return false;
|
|
257
|
-
const upload = await uploadPending;
|
|
258
|
-
if (upload?.uploaded) phase("cache", `uploaded (${remote?.name})`);
|
|
259
|
-
else if (upload?.timedOut) {
|
|
260
|
-
phase("cache", chalk.yellow(`${remote?.name} upload still running after ${formatDuration(UPLOAD_TIMEOUT_MS)}; not waiting`));
|
|
261
|
-
return true;
|
|
262
|
-
} else if (upload?.failed) {
|
|
263
|
-
const authNote = remote?.name === "eas" && isEasAuthFailureText(upload.failed) ? easAuthNote({
|
|
264
|
-
code: "logged-out",
|
|
265
|
-
reason: upload.failed,
|
|
266
|
-
phase: "upload"
|
|
267
|
-
}) : null;
|
|
268
|
-
phase("cache", chalk.yellow(authNote || `${remote?.name} upload failed: ${upload.failed}`));
|
|
269
|
-
}
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
function reportAndroidResult({ host, root, slot, json, useBuildCache, variant, release, metroCheck, metroPort, logsDir, serial, apkPath, androidPackage, installSkipped, record, waitedForBuild, remote, providerName, launchState, launchWarning, launched, ccache, durationMs, writer, emit, lease, recordRun, reclaimed = [], devServer = null, links }) {
|
|
273
|
-
const offloadedTo = record.offloadedTo ?? null;
|
|
274
|
-
recordRun({
|
|
275
|
-
failed: false,
|
|
276
|
-
cacheHit: cacheLevel(record.cacheHit),
|
|
277
|
-
waited: waitedForBuild,
|
|
278
|
-
durationMs,
|
|
279
|
-
offloadedTo
|
|
280
|
-
});
|
|
281
|
-
const facts = androidFacts({
|
|
282
|
-
host,
|
|
283
|
-
root,
|
|
284
|
-
slot,
|
|
285
|
-
serial,
|
|
286
|
-
avdName: record.avdName,
|
|
287
|
-
deviceName: record.deviceName,
|
|
288
|
-
systemImage: record.systemImage,
|
|
289
|
-
deviceProfile: record.deviceProfile,
|
|
290
|
-
debugHttpHost: launched.debugHttpHost ?? null,
|
|
291
|
-
debugHttpHostNote: launched.debugHttpHostNote ?? null,
|
|
292
|
-
devClientUrl: launched.devClientUrl ?? null,
|
|
293
|
-
fingerprint: record.fingerprint,
|
|
294
|
-
cacheKey: record.cacheKey,
|
|
295
|
-
variant,
|
|
296
|
-
metroPort,
|
|
297
|
-
cacheHit: record.cacheHit,
|
|
298
|
-
cacheSkipped: !useBuildCache,
|
|
299
|
-
waitedForBuild,
|
|
300
|
-
appPath: apkPath,
|
|
301
|
-
bundleId: androidPackage,
|
|
302
|
-
installSkipped,
|
|
303
|
-
launched: launchState,
|
|
304
|
-
ccache,
|
|
305
|
-
logs: logsDir,
|
|
306
|
-
durationMs,
|
|
307
|
-
lease,
|
|
308
|
-
devServer,
|
|
309
|
-
buildMachine: record.buildMachine,
|
|
310
|
-
builtOn: record.builtOn ?? void 0,
|
|
311
|
-
offloadedTo,
|
|
312
|
-
offloadFallback: record.offloadFallback ?? null
|
|
313
|
-
});
|
|
314
|
-
writer.close();
|
|
315
|
-
if (record.devicePlacement) facts.devicePlacement = record.devicePlacement;
|
|
316
|
-
if (json) emit(JSON.stringify({
|
|
317
|
-
...facts,
|
|
318
|
-
...links ? { links } : {},
|
|
319
|
-
...reclaimed.length ? { reclaimed } : {}
|
|
320
|
-
}));
|
|
321
|
-
else {
|
|
322
|
-
const summary = `${launchWarning ? "WARNING" : "OK"}: ${androidPackage} launched on ${host ? `${host.device?.name ?? "Android emulator"} on ${host.machine}` : serial}, ${release ? `${variant} (embedded JS, no Metro)` : `Metro port ${metroPort}`} (${cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo)})`;
|
|
323
|
-
const outcome = launchWarning ? chalk.yellow(`${summary} -- ${launchWarning}`) : launchState === "unverified" ? chalk.yellow(`${summary} -- launch UNVERIFIED`) : launchState === "bundling" ? chalk.green(`${summary} -- bundle requested, still building`) : chalk.green(summary);
|
|
324
|
-
const deviceName = record.avdName || record.deviceName || serial;
|
|
325
|
-
const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo) : `bypassed; ${cacheOutcome(false, null, offloadedTo)}`;
|
|
326
|
-
const metroResult = release ? `embedded (${variant})` : !metroCheck ? `check skipped on port ${metroPort}` : launchState === "unverified" ? `state unverified on port ${metroPort}` : `running on port ${metroPort}${devServer ? ` (started: ${devServer.reason})` : ""}`;
|
|
327
|
-
emit([
|
|
328
|
-
outcome,
|
|
329
|
-
phaseLine("device", host ? `${deviceName} on ${host.machine}` : `${deviceName} (${serial})`),
|
|
330
|
-
phaseLine("app", androidPackage),
|
|
331
|
-
phaseLine("metro", metroResult),
|
|
332
|
-
phaseLine("cache", cacheResult),
|
|
333
|
-
...ccache.status === "not-run" ? [phaseLine("compilation cache", ccacheActivityLine(ccache))] : [],
|
|
334
|
-
phaseLine("logs", logsDir || "unavailable (remote device)"),
|
|
335
|
-
...!host ? [phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${workspaceAgentDeviceDir(root)}`)] : []
|
|
336
|
-
].join("\n"));
|
|
337
|
-
if (links) console.error(chalk.dim(workspaceLinkLine(links)));
|
|
338
|
-
}
|
|
339
|
-
return facts;
|
|
340
|
-
}
|
|
341
|
-
function cacheOutcome(cacheHit, providerName = null, offloadedTo = null) {
|
|
342
|
-
if (cacheHit === "remote") return `cache hit from ${providerName || "the remote cache"}`;
|
|
343
|
-
if (cacheHit === "local") return "cache hit";
|
|
344
|
-
return offloadedTo ? `built on ${offloadedTo}` : "built";
|
|
345
|
-
}
|
|
346
|
-
function persistLastBuild({ recordBuild = recordFinishedBuild, root, record, startedAt, durationMs, status, errorCode = null, diagnostics = null, out }) {
|
|
347
|
-
const lastBuild = lastBuildRecord({
|
|
348
|
-
...record,
|
|
349
|
-
startedAt,
|
|
350
|
-
durationMs,
|
|
351
|
-
status,
|
|
352
|
-
errorCode,
|
|
353
|
-
diagnostics
|
|
354
|
-
});
|
|
355
|
-
try {
|
|
356
|
-
recordBuild(root, lastBuild);
|
|
357
|
-
} catch (err) {
|
|
358
|
-
out(phaseLine("state", chalk.yellow(`could not record lastBuild: ${err?.message || err}`)));
|
|
359
|
-
}
|
|
360
|
-
return lastBuild;
|
|
361
|
-
}
|
|
362
|
-
//#endregion
|
|
363
|
-
//#region src/commands/android/hosted.ts
|
|
364
|
-
async function finishHostedAndroidRun({ target, artifact, root, slot = "default", release, isExpo, metroPort, logsDir, selectors, place, writePlacement, verifyLaunch: verify, writeLaunch, readApkPackage, resolveDevClientScheme, now, started, startedAt, fail, phase, enterPhase, out, recordBuild, ...report }) {
|
|
365
|
-
const packageName = artifact.androidPackage ?? readApkPackage(artifact.apkPath);
|
|
366
|
-
if (!artifact.apkPath || !packageName) return fail("STIM_INSTALL_FAILED", "The built APK has no package identity.");
|
|
367
|
-
const launchedAt = now();
|
|
368
|
-
try {
|
|
369
|
-
const run = await place(target, {
|
|
370
|
-
root,
|
|
371
|
-
slot,
|
|
372
|
-
bundle: artifact.apkPath,
|
|
373
|
-
handoff: artifact.handoff,
|
|
374
|
-
bundleId: packageName,
|
|
375
|
-
release,
|
|
376
|
-
metroPort,
|
|
377
|
-
selectors,
|
|
378
|
-
...!release && isExpo ? { devClientScheme: resolveDevClientScheme(root, artifact.apkPath) ?? void 0 } : {},
|
|
379
|
-
reserved: (placement) => writePlacement(root, slot, placement),
|
|
380
|
-
note: out,
|
|
381
|
-
enterPhase
|
|
382
|
-
});
|
|
383
|
-
writePlacement(root, slot, run.placement);
|
|
384
|
-
writeLaunch(root, "android", {
|
|
385
|
-
appId: packageName,
|
|
386
|
-
deviceId: run.placement.session,
|
|
387
|
-
metroPort,
|
|
388
|
-
release,
|
|
389
|
-
launchedAt: new Date(launchedAt).toISOString()
|
|
390
|
-
});
|
|
391
|
-
let launched = run.launched;
|
|
392
|
-
if (!release) {
|
|
393
|
-
const evidence = await verify({
|
|
394
|
-
requireBundleResponse: true,
|
|
395
|
-
platform: "android",
|
|
396
|
-
logsDir: logsDir ?? void 0,
|
|
397
|
-
since: launchedAt,
|
|
398
|
-
metroPort,
|
|
399
|
-
mode: isExpo ? MODE_EXPO : MODE_BARE,
|
|
400
|
-
slot: launchSlotScope(root, slot),
|
|
401
|
-
platformShared: siblingPlatformSlots(root, "android", slot).length > 0
|
|
402
|
-
});
|
|
403
|
-
if (evidence.fatal) return fail("STIM_LAUNCH_FAILED", "Metro could not deliver the hosted app bundle.", "Run stim logs --errors and fix the bundle error.");
|
|
404
|
-
launched = evidence.verified ? true : evidence.requested ? "bundling" : "unverified";
|
|
405
|
-
}
|
|
406
|
-
if (launched === "unverified") out(`Launch on ${target.host.machine} is unverified; check the hosted app, then retry stim android --remote ${target.host.machine}.`);
|
|
407
|
-
const host = hostedAndroidStatus(run.placement);
|
|
408
|
-
report.record.deviceName = host.device?.name ?? null;
|
|
409
|
-
report.record.bundleId = packageName;
|
|
410
|
-
persistLastBuild({
|
|
411
|
-
recordBuild,
|
|
412
|
-
root,
|
|
413
|
-
record: report.record,
|
|
414
|
-
startedAt,
|
|
415
|
-
durationMs: now() - started,
|
|
416
|
-
status: "success",
|
|
417
|
-
out
|
|
418
|
-
});
|
|
419
|
-
await finishAndroidUpload(artifact.uploadPending, artifact.remote, phase);
|
|
420
|
-
await artifact.providerUpload;
|
|
421
|
-
return {
|
|
422
|
-
ok: true,
|
|
423
|
-
facts: reportAndroidResult({
|
|
424
|
-
...report,
|
|
425
|
-
root,
|
|
426
|
-
slot,
|
|
427
|
-
release,
|
|
428
|
-
metroPort,
|
|
429
|
-
logsDir,
|
|
430
|
-
host,
|
|
431
|
-
serial: null,
|
|
432
|
-
apkPath: artifact.apkPath,
|
|
433
|
-
androidPackage: packageName,
|
|
434
|
-
installSkipped: false,
|
|
435
|
-
waitedForBuild: artifact.waitedForBuild,
|
|
436
|
-
remote: artifact.remote,
|
|
437
|
-
providerName: artifact.providerName,
|
|
438
|
-
launchState: launched,
|
|
439
|
-
launched: {},
|
|
440
|
-
ccache: artifact.ccache,
|
|
441
|
-
durationMs: now() - started
|
|
442
|
-
})
|
|
443
|
-
};
|
|
444
|
-
} catch (error) {
|
|
445
|
-
return fail(error.code ?? "STIM_HOSTING_REFUSED", error.message, `Retry stim android --remote ${target.host.machine}, or run stim stop to reconcile the recorded placement.`);
|
|
446
|
-
} finally {
|
|
447
|
-
if (artifact.swapDir) rmSync(artifact.swapDir, {
|
|
448
|
-
recursive: true,
|
|
449
|
-
force: true
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
//#endregion
|
|
454
|
-
//#region src/engine/apk-swap.ts
|
|
455
|
-
const ANDROID_BUNDLE_NAME = "index.android.bundle";
|
|
456
|
-
const LAST_LINES = 5;
|
|
457
|
-
function hermesEnabledFromGradleProperties(text) {
|
|
458
|
-
if (typeof text !== "string") return true;
|
|
459
|
-
let value = null;
|
|
460
|
-
for (const raw of text.split("\n")) {
|
|
461
|
-
const line = raw.trim();
|
|
462
|
-
if (line === "" || line.startsWith("#") || line.startsWith("!")) continue;
|
|
463
|
-
const match = /^hermesEnabled\s*[=:]\s*(.*)$/.exec(line);
|
|
464
|
-
if (match) value = match[1].trim();
|
|
465
|
-
}
|
|
466
|
-
return value === null || value.toLowerCase() !== "false";
|
|
467
|
-
}
|
|
468
|
-
function readAndroidHermesEnabled(root) {
|
|
469
|
-
try {
|
|
470
|
-
return hermesEnabledFromGradleProperties(readFileSync(join(root, "android", "gradle.properties"), "utf-8"));
|
|
471
|
-
} catch {
|
|
472
|
-
return true;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
function hermescBinDir(platform = process.platform) {
|
|
476
|
-
if (platform === "darwin") return "osx-bin";
|
|
477
|
-
return platform === "win32" ? "win64-bin" : "linux64-bin";
|
|
478
|
-
}
|
|
479
|
-
function hermescName(platform) {
|
|
480
|
-
return platform === "win32" ? "hermesc.exe" : "hermesc";
|
|
481
|
-
}
|
|
482
|
-
function hermescCandidates(root, { platform = process.platform, reactNativePath = null } = {}) {
|
|
483
|
-
const bin = hermescBinDir(platform);
|
|
484
|
-
const name = hermescName(platform);
|
|
485
|
-
const rn = reactNativePath ?? join(root, "node_modules", "react-native");
|
|
486
|
-
return [.../* @__PURE__ */ new Set([
|
|
487
|
-
join(dirname(rn), "hermes-compiler", "hermesc", bin, name),
|
|
488
|
-
join(root, "node_modules", "hermes-compiler", "hermesc", bin, name),
|
|
489
|
-
join(root, "node_modules", "react-native", "sdks", "hermesc", bin, name),
|
|
490
|
-
join(root, "node_modules", "react-native", "sdks", "hermes", "build", "bin", name)
|
|
491
|
-
])];
|
|
492
|
-
}
|
|
493
|
-
function androidHermescPath(root, { exists = existsSync, platform = process.platform, reactNativePath = null } = {}) {
|
|
494
|
-
const candidates = hermescCandidates(root, {
|
|
495
|
-
platform,
|
|
496
|
-
reactNativePath
|
|
497
|
-
});
|
|
498
|
-
return candidates.find(exists) ?? candidates[candidates.length - 1];
|
|
499
|
-
}
|
|
500
|
-
function androidHermescArgs({ bundle, out }) {
|
|
501
|
-
return [
|
|
502
|
-
"-emit-binary",
|
|
503
|
-
"-O",
|
|
504
|
-
"-w",
|
|
505
|
-
"-out",
|
|
506
|
-
out,
|
|
507
|
-
bundle
|
|
508
|
-
];
|
|
509
|
-
}
|
|
510
|
-
function androidBundleCommand({ isExpo, entryFile, bundleOutput, assetsDest }) {
|
|
511
|
-
if (isExpo) return {
|
|
512
|
-
file: "npx",
|
|
513
|
-
args: [
|
|
514
|
-
"expo",
|
|
515
|
-
"export:embed",
|
|
516
|
-
"--platform",
|
|
517
|
-
"android",
|
|
518
|
-
"--dev",
|
|
519
|
-
"false",
|
|
520
|
-
"--bundle-output",
|
|
521
|
-
bundleOutput,
|
|
522
|
-
"--assets-dest",
|
|
523
|
-
assetsDest
|
|
524
|
-
]
|
|
525
|
-
};
|
|
526
|
-
return {
|
|
527
|
-
file: "npx",
|
|
528
|
-
args: [
|
|
529
|
-
"react-native",
|
|
530
|
-
"bundle",
|
|
531
|
-
"--platform",
|
|
532
|
-
"android",
|
|
533
|
-
"--dev",
|
|
534
|
-
"false",
|
|
535
|
-
"--entry-file",
|
|
536
|
-
entryFile,
|
|
537
|
-
"--bundle-output",
|
|
538
|
-
bundleOutput,
|
|
539
|
-
"--assets-dest",
|
|
540
|
-
assetsDest
|
|
541
|
-
]
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
function jarPath({ javaHome = process.env.JAVA_HOME } = {}) {
|
|
545
|
-
return javaHome ? join(javaHome, "bin", "jar") : "jar";
|
|
546
|
-
}
|
|
547
|
-
function zipalignArgs({ buildToolsMajor, input, output }) {
|
|
548
|
-
return [
|
|
549
|
-
...buildToolsMajor >= 35 ? ["-P", "16"] : ["-p"],
|
|
550
|
-
"-f",
|
|
551
|
-
"-v",
|
|
552
|
-
"4",
|
|
553
|
-
input,
|
|
554
|
-
output
|
|
555
|
-
];
|
|
556
|
-
}
|
|
557
|
-
function keystorePassArg(value) {
|
|
558
|
-
const text = typeof value === "string" ? value.trim() : "";
|
|
559
|
-
if (text === "") return "pass:android";
|
|
560
|
-
if (/^(?:pass|file|env):/.test(text) || text === "stdin") return text;
|
|
561
|
-
return `pass:${text}`;
|
|
562
|
-
}
|
|
563
|
-
function resolveKeystore(root, settings) {
|
|
564
|
-
const android = settings?.["android"];
|
|
565
|
-
const bag = android && typeof android === "object" && !Array.isArray(android) ? android : {};
|
|
566
|
-
const configured = bag["keystore"];
|
|
567
|
-
return {
|
|
568
|
-
path: typeof configured === "string" && configured.trim() !== "" ? isAbsolute(configured.trim()) ? configured.trim() : join(root, configured.trim()) : join(root, "android", "app", "debug.keystore"),
|
|
569
|
-
pass: keystorePassArg(bag["keystorePassword"])
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
function apksignerArgs({ keystore, apkPath }) {
|
|
573
|
-
return [
|
|
574
|
-
"sign",
|
|
575
|
-
"--ks",
|
|
576
|
-
keystore.path,
|
|
577
|
-
"--ks-pass",
|
|
578
|
-
keystore.pass,
|
|
579
|
-
apkPath
|
|
580
|
-
];
|
|
581
|
-
}
|
|
582
|
-
async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter = null, exec = null, spawnFn = null, mkdtemp = () => makeTemporaryDirectory(cachedApkPath, "stim-apk-swap-"), exists = existsSync, hermesEnabled = null, buildTools = null, findTool = findBuildTool, storedAssets = null, readManifest = readAssetManifest, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, onHeartbeat = (line) => console.error(line), platform = process.platform }) {
|
|
583
|
-
const e = exec || getExecutor();
|
|
584
|
-
const startedAt = now();
|
|
585
|
-
const elapsed = () => now() - startedAt;
|
|
586
|
-
let tmp;
|
|
587
|
-
const fail = (step, reason, lastLines = []) => {
|
|
588
|
-
logWriter?.write?.({
|
|
589
|
-
src: "build",
|
|
590
|
-
level: "error",
|
|
591
|
-
msg: `APK swap failed at ${step}: ${reason}`,
|
|
592
|
-
event: "apk_swap"
|
|
593
|
-
});
|
|
594
|
-
if (tmp) removeTemporaryEntry(tmp);
|
|
595
|
-
return {
|
|
596
|
-
failed: true,
|
|
597
|
-
step,
|
|
598
|
-
reason,
|
|
599
|
-
lastLines,
|
|
600
|
-
durationMs: elapsed()
|
|
601
|
-
};
|
|
602
|
-
};
|
|
603
|
-
let work;
|
|
604
|
-
let final;
|
|
605
|
-
try {
|
|
606
|
-
tmp = mkdtemp();
|
|
607
|
-
const base = basename(cachedApkPath);
|
|
608
|
-
work = join(tmp, `unaligned-${base}`);
|
|
609
|
-
final = join(tmp, base);
|
|
610
|
-
copyApk(e, cachedApkPath, work, platform);
|
|
611
|
-
} catch (err) {
|
|
612
|
-
return fail("copy", `could not copy ${cachedApkPath} aside: ${describe(err)}`);
|
|
613
|
-
}
|
|
614
|
-
const stage = join(tmp, "stage");
|
|
615
|
-
const bundleOutput = join(stage, "assets", ANDROID_BUNDLE_NAME);
|
|
616
|
-
const assetsDest = join(stage, "res");
|
|
617
|
-
const command = androidBundleCommand({
|
|
618
|
-
isExpo,
|
|
619
|
-
entryFile: isExpo ? "index.js" : detectEntryFile(root),
|
|
620
|
-
bundleOutput,
|
|
621
|
-
assetsDest
|
|
622
|
-
});
|
|
623
|
-
try {
|
|
624
|
-
mkdirSync(join(stage, "assets"), { recursive: true });
|
|
625
|
-
mkdirSync(assetsDest, { recursive: true });
|
|
626
|
-
} catch (err) {
|
|
627
|
-
return fail("bundle", `could not create ${stage}: ${describe(err)}`);
|
|
628
|
-
}
|
|
629
|
-
logWriter?.write?.({
|
|
630
|
-
src: "build",
|
|
631
|
-
level: "info",
|
|
632
|
-
msg: `${command.file} ${command.args.join(" ")}`,
|
|
633
|
-
event: "apk_swap"
|
|
634
|
-
});
|
|
635
|
-
const spawn = spawnFn || ((cmd, args, opts) => e.spawn(cmd, args, opts));
|
|
636
|
-
const transcript = [];
|
|
637
|
-
const push = (line) => {
|
|
638
|
-
const msg = cleanLine(line);
|
|
639
|
-
if (msg.trim() === "") return;
|
|
640
|
-
transcript.push(msg);
|
|
641
|
-
logWriter?.write?.({
|
|
642
|
-
src: "build",
|
|
643
|
-
level: "debug",
|
|
644
|
-
msg,
|
|
645
|
-
raw: true,
|
|
646
|
-
event: "apk_swap"
|
|
647
|
-
});
|
|
648
|
-
};
|
|
649
|
-
let child;
|
|
650
|
-
try {
|
|
651
|
-
child = spawn(command.file, command.args, {
|
|
652
|
-
cwd: root,
|
|
653
|
-
stdio: [
|
|
654
|
-
"ignore",
|
|
655
|
-
"pipe",
|
|
656
|
-
"pipe"
|
|
657
|
-
],
|
|
658
|
-
env: {
|
|
659
|
-
...process.env,
|
|
660
|
-
FORCE_COLOR: "0"
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
} catch (err) {
|
|
664
|
-
return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(err)}`);
|
|
665
|
-
}
|
|
666
|
-
const reader = {
|
|
667
|
-
out: createLineReader(push),
|
|
668
|
-
err: createLineReader(push)
|
|
669
|
-
};
|
|
670
|
-
child.stdout?.setEncoding?.("utf-8");
|
|
671
|
-
child.stderr?.setEncoding?.("utf-8");
|
|
672
|
-
child.stdout?.on("data", (chunk) => reader.out.push(chunk));
|
|
673
|
-
child.stderr?.on("data", (chunk) => reader.err.push(chunk));
|
|
674
|
-
const stopHeartbeat = startBuildHeartbeat({
|
|
675
|
-
intervalMs: heartbeatMs,
|
|
676
|
-
elapsed,
|
|
677
|
-
emit: onHeartbeat,
|
|
678
|
-
label: "swap"
|
|
679
|
-
});
|
|
680
|
-
let outcome;
|
|
681
|
-
try {
|
|
682
|
-
outcome = await waitForChild(child);
|
|
683
|
-
} finally {
|
|
684
|
-
stopHeartbeat();
|
|
685
|
-
}
|
|
686
|
-
reader.out.flush();
|
|
687
|
-
reader.err.flush();
|
|
688
|
-
if (outcome.error) return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(outcome.error)}`);
|
|
689
|
-
if (outcome.code !== 0) {
|
|
690
|
-
const how = outcome.signal ? `signal ${outcome.signal}` : `exit code ${outcome.code}`;
|
|
691
|
-
return fail("bundle", `\`${command.args.slice(0, 2).join(" ")}\` failed (${how})`, tailLines(transcript, LAST_LINES));
|
|
692
|
-
}
|
|
693
|
-
if (!exists(bundleOutput)) return fail("bundle", `the bundle command exited 0 but wrote no ${ANDROID_BUNDLE_NAME} at ${bundleOutput}`);
|
|
694
|
-
let hermes = false;
|
|
695
|
-
let note;
|
|
696
|
-
if (hermesEnabled ?? readAndroidHermesEnabled(root)) {
|
|
697
|
-
const hermesc = androidHermescPath(root);
|
|
698
|
-
if (!exists(hermesc)) note = `hermesc not found at ${hermesc}; embedding the plain JS bundle instead of Hermes bytecode`;
|
|
699
|
-
else {
|
|
700
|
-
const hbc = `${bundleOutput}.hbc`;
|
|
701
|
-
try {
|
|
702
|
-
e.runFile(hermesc, androidHermescArgs({
|
|
703
|
-
bundle: bundleOutput,
|
|
704
|
-
out: hbc
|
|
705
|
-
}));
|
|
706
|
-
renameSync(hbc, bundleOutput);
|
|
707
|
-
hermes = true;
|
|
708
|
-
} catch (err) {
|
|
709
|
-
return fail("hermesc", `hermesc failed on ${bundleOutput}: ${describe(err)}`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
const refuse = (reason, assetDiff) => {
|
|
714
|
-
logWriter?.write?.({
|
|
715
|
-
src: "build",
|
|
716
|
-
level: "warn",
|
|
717
|
-
msg: `APK swap refused: ${reason}`,
|
|
718
|
-
event: "apk_swap"
|
|
719
|
-
});
|
|
720
|
-
removeTemporaryEntry(tmp);
|
|
721
|
-
const result = {
|
|
722
|
-
assetMismatch: true,
|
|
723
|
-
reason,
|
|
724
|
-
durationMs: elapsed()
|
|
725
|
-
};
|
|
726
|
-
if (assetDiff) result.assetDiff = assetDiff;
|
|
727
|
-
return result;
|
|
728
|
-
};
|
|
729
|
-
if (!storedAssets) return refuse("this cache entry predates asset tracking (no assets-manifest.json beside the artifact), so its asset set cannot be proven to match this one");
|
|
730
|
-
const fresh = readManifest(assetsDest);
|
|
731
|
-
if (!fresh) return fail("assets", `could not hash the assets emitted into ${assetsDest}, so the asset set cannot be verified`);
|
|
732
|
-
const diff = compareAssetManifests(fresh, storedAssets);
|
|
733
|
-
if (!diff.same) return refuse(assetDiffReason(diff), diff);
|
|
734
|
-
const jar = jarPath();
|
|
735
|
-
const updatesManifest = join(stage, "assets", UPDATES_MANIFEST_NAME);
|
|
736
|
-
try {
|
|
737
|
-
e.runFile(jar, [
|
|
738
|
-
"--extract",
|
|
739
|
-
"--file",
|
|
740
|
-
work,
|
|
741
|
-
`assets/${UPDATES_MANIFEST_NAME}`
|
|
742
|
-
], { cwd: stage });
|
|
743
|
-
if (exists(updatesManifest)) refreshUpdatesManifestFile(updatesManifest, now());
|
|
744
|
-
} catch (err) {
|
|
745
|
-
return fail("updates", `could not refresh the expo-updates manifest from ${work}: ${describe(err)}`);
|
|
746
|
-
}
|
|
747
|
-
try {
|
|
748
|
-
e.runFile(jar, [
|
|
749
|
-
"--update",
|
|
750
|
-
"--file",
|
|
751
|
-
work,
|
|
752
|
-
"--no-compress",
|
|
753
|
-
"-C",
|
|
754
|
-
stage,
|
|
755
|
-
"assets"
|
|
756
|
-
]);
|
|
757
|
-
} catch (err) {
|
|
758
|
-
return fail("zip", `${jar} --update ${work} failed: ${describe(err)}`);
|
|
759
|
-
}
|
|
760
|
-
const tools = buildTools ?? findTool(["zipalign"]);
|
|
761
|
-
if (!tools) return fail("zipalign", "no zipalign found under the Android SDK build-tools; install one with `sdkmanager \"build-tools;36.0.0\"`");
|
|
762
|
-
try {
|
|
763
|
-
e.runFile(tools.path, zipalignArgs({
|
|
764
|
-
buildToolsMajor: tools.major,
|
|
765
|
-
input: work,
|
|
766
|
-
output: final
|
|
767
|
-
}));
|
|
768
|
-
} catch (err) {
|
|
769
|
-
return fail("zipalign", `zipalign failed on ${work}: ${describe(err)}`);
|
|
770
|
-
}
|
|
771
|
-
const signer = buildTools ? {
|
|
772
|
-
...buildTools,
|
|
773
|
-
path: join(dirname(buildTools.path), androidBuildToolName("apksigner"))
|
|
774
|
-
} : findTool(["apksigner"]);
|
|
775
|
-
if (!signer) return fail("apksigner", "no apksigner found under the Android SDK build-tools; install one with `sdkmanager \"build-tools;36.0.0\"`");
|
|
776
|
-
try {
|
|
777
|
-
e.runFile(signer.path, apksignerArgs({
|
|
778
|
-
keystore,
|
|
779
|
-
apkPath: final
|
|
780
|
-
}));
|
|
781
|
-
} catch (err) {
|
|
782
|
-
return fail("apksigner", `apksigner sign failed on ${final} with ${keystore.path}: ${describe(err)}`);
|
|
783
|
-
}
|
|
784
|
-
logWriter?.write?.({
|
|
785
|
-
src: "build",
|
|
786
|
-
level: "info",
|
|
787
|
-
msg: `APK swap done: ${hermes ? "hermes bytecode" : "plain JS"} into ${final} in ${elapsed()}ms`,
|
|
788
|
-
event: "apk_swap"
|
|
789
|
-
});
|
|
790
|
-
const result = {
|
|
791
|
-
ok: true,
|
|
792
|
-
apkPath: final,
|
|
793
|
-
tmpDir: tmp,
|
|
794
|
-
hermes,
|
|
795
|
-
durationMs: elapsed()
|
|
796
|
-
};
|
|
797
|
-
if (note) result.note = note;
|
|
798
|
-
return result;
|
|
799
|
-
}
|
|
800
|
-
function copyApk(e, from, to, platform) {
|
|
801
|
-
if (platform === "darwin") try {
|
|
802
|
-
e.runFile("cp", [
|
|
803
|
-
"-c",
|
|
804
|
-
from,
|
|
805
|
-
to
|
|
806
|
-
]);
|
|
807
|
-
return;
|
|
808
|
-
} catch {}
|
|
809
|
-
copyFileSync(from, to, constants.COPYFILE_FICLONE);
|
|
810
|
-
}
|
|
811
|
-
function describe(err) {
|
|
812
|
-
const e = err;
|
|
813
|
-
const streams = [e?.stdout, e?.stderr].map((s) => s ? String(s).trim() : "").filter(Boolean).join("\n");
|
|
814
|
-
const message = e?.message ? String(e.message).trim() : String(err);
|
|
815
|
-
return streams ? `${message}: ${streams}` : message;
|
|
816
|
-
}
|
|
817
|
-
//#endregion
|
|
818
|
-
//#region src/commands/android/artifact.ts
|
|
819
|
-
const FALLBACK_LINES = 5;
|
|
820
|
-
function fail$1(code, message, remedy, extra = {}) {
|
|
821
|
-
return {
|
|
822
|
-
code,
|
|
823
|
-
message,
|
|
824
|
-
remedy,
|
|
825
|
-
extra
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
var ArtifactRefusal = class extends Error {
|
|
829
|
-
failure;
|
|
830
|
-
constructor(failure) {
|
|
831
|
-
super(failure.message ?? failure.code);
|
|
832
|
-
this.failure = failure;
|
|
833
|
-
}
|
|
834
|
-
};
|
|
835
|
-
async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo, device, physical, hostedAbi, remote: remoteTarget, buildPlan, cacheProviderConfig, requestedBuildCache, easBuild, androidPackage: initialPackage, record, maxBuilds, progress }, { deviceAbi, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, loadCacheProviderModule, acquireSlot, releaseSlot, planPrebuildFor, prebuild, build, ccacheFor, swapApk, readState, now }) {
|
|
836
|
-
const { phase, out, estimates, stats, step, miss, hit: lateHit, place, waitingOn, waitingFor } = progress;
|
|
837
|
-
let fallbackMachine = null;
|
|
838
|
-
let hereReason = "no remote Mac is paired";
|
|
839
|
-
let slotWaitMs;
|
|
840
|
-
const buildMachine = record.buildMachine ?? "auto";
|
|
841
|
-
const fallBack = (reason, line = reason, info = {}) => {
|
|
842
|
-
writer.write(buildPlacementRecord({
|
|
843
|
-
platform: PLATFORM,
|
|
844
|
-
buildMachine,
|
|
845
|
-
candidates: info.candidates,
|
|
846
|
-
event: "placement_fallback",
|
|
847
|
-
fallback: {
|
|
848
|
-
code: info.code ?? "fallback",
|
|
849
|
-
reason,
|
|
850
|
-
machine: info.machine
|
|
851
|
-
}
|
|
852
|
-
}));
|
|
853
|
-
if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
|
|
854
|
-
requireLocalBuild(buildMachine);
|
|
855
|
-
record.offloadFallback = reason;
|
|
856
|
-
phase("build", `${line} -> building here`);
|
|
857
|
-
};
|
|
858
|
-
const { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
|
|
859
|
-
const useBuildCache = cachePolicy.read;
|
|
860
|
-
let androidPackage = initialPackage;
|
|
861
|
-
let ccacheActivity = CCACHE_NOT_RUN;
|
|
862
|
-
let phaseFailure = null;
|
|
863
|
-
let buildLock = null;
|
|
864
|
-
const releaseHeldLock = () => {
|
|
865
|
-
if (!buildLock) return;
|
|
866
|
-
const held = buildLock;
|
|
867
|
-
buildLock = null;
|
|
868
|
-
try {
|
|
869
|
-
releaseLock(held);
|
|
870
|
-
} catch (err) {
|
|
871
|
-
out(phaseLine("build", chalk.dim(`could not release the build lock at ${held.path}: ${err?.message || err}`)));
|
|
872
|
-
}
|
|
873
|
-
};
|
|
874
|
-
let buildSlot = null;
|
|
875
|
-
const releaseHeldSlot = () => {
|
|
876
|
-
if (!buildSlot) return;
|
|
877
|
-
const held = buildSlot;
|
|
878
|
-
buildSlot = null;
|
|
879
|
-
try {
|
|
880
|
-
releaseSlot(held);
|
|
881
|
-
} catch (err) {
|
|
882
|
-
out(phaseLine("build", chalk.dim(`could not release the build slot: ${err?.message || err}`)));
|
|
883
|
-
}
|
|
884
|
-
};
|
|
885
|
-
const { abi: buildAbi, runOptions: buildRunOptions, remoteRunOptions } = androidBuildOptions({
|
|
886
|
-
release,
|
|
887
|
-
physical,
|
|
888
|
-
device,
|
|
889
|
-
variant,
|
|
890
|
-
deviceAbi,
|
|
891
|
-
compiler: cas?.id,
|
|
892
|
-
buildProfile,
|
|
893
|
-
targetAbiOnly: buildPlan.targetAbiOnly,
|
|
894
|
-
hostedAbi
|
|
895
|
-
});
|
|
896
|
-
let hash = "";
|
|
897
|
-
let providerUpload = null;
|
|
898
|
-
let providerName = null;
|
|
899
|
-
let providerLoad = null;
|
|
900
|
-
const cacheWarn = createWarnOnce((line) => phase("cache", chalk.yellow(line)));
|
|
901
|
-
const loadTieredProvider = cachePolicy.remote && cacheProviderConfig ? () => providerLoad ??= loadCacheProviderModule({
|
|
902
|
-
projectRoot: root,
|
|
903
|
-
config: cacheProviderConfig
|
|
904
|
-
}) : null;
|
|
905
|
-
let fingerprintSources = [];
|
|
906
|
-
let cacheKey = "";
|
|
907
|
-
let storeHash = "";
|
|
908
|
-
let storeKey = "";
|
|
909
|
-
let storeSources = [];
|
|
910
|
-
let apkPath = null;
|
|
911
|
-
let handoff = null;
|
|
912
|
-
let swapFellBack = false;
|
|
913
|
-
async function resolveInitialFingerprint() {
|
|
914
|
-
if (easBuild?.ok) {
|
|
915
|
-
hash = storeHash = easBuild.fingerprint;
|
|
916
|
-
cacheKey = storeKey = easBuild.cacheKey;
|
|
917
|
-
apkPath = easBuild.path;
|
|
918
|
-
record.fingerprint = hash;
|
|
919
|
-
record.cacheKey = cacheKey;
|
|
920
|
-
record.cacheHit = easBuild.cacheHit;
|
|
921
|
-
record.cacheSkipped = !useBuildCache;
|
|
922
|
-
providerName = "eas";
|
|
923
|
-
stats.setCacheKey(cacheKey);
|
|
924
|
-
return true;
|
|
925
|
-
}
|
|
926
|
-
if (useBuildCache) step("cache-lookup");
|
|
927
|
-
const fingerprintTimer = stepTimer(now);
|
|
928
|
-
try {
|
|
929
|
-
const computed = await fingerprint(root, { platform: PLATFORM });
|
|
930
|
-
hash = computed?.hash ?? "";
|
|
931
|
-
fingerprintSources = computed?.sources ?? [];
|
|
932
|
-
} catch (err) {
|
|
933
|
-
const message = String(err?.message || err);
|
|
934
|
-
record.cacheSkipped = !useBuildCache;
|
|
935
|
-
record.missReason = fingerprintErrorMissReason(message);
|
|
936
|
-
phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint could not fingerprint ${root}: ${message}`, "Fix the @expo/fingerprint error above, then retry.", { lastBuildStatus: true });
|
|
937
|
-
return false;
|
|
938
|
-
}
|
|
939
|
-
if (!hash) {
|
|
940
|
-
record.cacheSkipped = !useBuildCache;
|
|
941
|
-
record.missReason = fingerprintErrorMissReason("no hash");
|
|
942
|
-
phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`, "Check the project native inputs and the @expo/fingerprint error above, then retry.", { lastBuildStatus: true });
|
|
943
|
-
return false;
|
|
944
|
-
}
|
|
945
|
-
record.fingerprint = hash;
|
|
946
|
-
cacheKey = buildCacheKey(PLATFORM, hash, buildRunOptions);
|
|
947
|
-
stats.setCacheKey(cacheKey);
|
|
948
|
-
record.cacheKey = cacheKey;
|
|
949
|
-
storeHash = hash;
|
|
950
|
-
storeKey = cacheKey;
|
|
951
|
-
storeSources = fingerprintSources;
|
|
952
|
-
const found = await resolveTieredBuild({
|
|
953
|
-
local: filesystemBuildCapability({
|
|
954
|
-
resolve: resolveCached,
|
|
955
|
-
store: storeCached,
|
|
956
|
-
sources: fingerprintSources
|
|
957
|
-
}),
|
|
958
|
-
loadProvider: loadTieredProvider,
|
|
959
|
-
target: {
|
|
960
|
-
projectRoot: root,
|
|
961
|
-
platform: PLATFORM,
|
|
962
|
-
key: cacheKey
|
|
963
|
-
},
|
|
964
|
-
destinationDir: providerDownloadPath(workspaceDir(root)),
|
|
965
|
-
ensureDestination: prepareProviderDownloadDir,
|
|
966
|
-
skipRead: !useBuildCache,
|
|
967
|
-
warn: cacheWarn
|
|
968
|
-
});
|
|
969
|
-
const cached = found?.tier === "local" ? found.path : null;
|
|
970
|
-
record.cacheHit = cached ? "local" : false;
|
|
971
|
-
record.cacheSkipped = !useBuildCache;
|
|
972
|
-
phase("fingerprint", `${shortHash(hash)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : !requestedBuildCache ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}`);
|
|
973
|
-
if (found?.tier === "provider") {
|
|
974
|
-
record.cacheHit = "remote";
|
|
975
|
-
providerName = found.providerName ?? null;
|
|
976
|
-
phase("cache", `provider hit (${providerName})${found.storedLocally ? " -> stored locally" : ""}`);
|
|
977
|
-
}
|
|
978
|
-
apkPath = found?.path ?? null;
|
|
979
|
-
return true;
|
|
980
|
-
}
|
|
981
|
-
let remote = null;
|
|
982
|
-
let abandonedRemote = false;
|
|
983
|
-
let uploadPending = null;
|
|
984
|
-
async function resolveRemoteArtifact() {
|
|
985
|
-
if (buildAbi || cas || buildProfile || !cachePolicy.remote) return;
|
|
986
|
-
if (!apkPath) {
|
|
987
|
-
const loaded = await loadProvider(root, { isExpo });
|
|
988
|
-
if (loaded?.unavailable) phase("cache", chalk.yellow(`provider not usable: ${loaded.unavailable}`));
|
|
989
|
-
else if (loaded?.provider) remote = loaded;
|
|
990
|
-
if (remote?.name === "eas") {
|
|
991
|
-
const auth = easAuth({
|
|
992
|
-
projectRoot: root,
|
|
993
|
-
owner: loaded?.owner || null
|
|
994
|
-
});
|
|
995
|
-
const authNote = easAuthNote(auth);
|
|
996
|
-
if (authNote) phase("cache", chalk.yellow(authNote));
|
|
997
|
-
if (auth?.code === "logged-out") remote = null;
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
if (remote && useBuildCache) {
|
|
1001
|
-
const remoteTimer = stepTimer(now);
|
|
1002
|
-
const hit = await resolveRemoteBuild({
|
|
1003
|
-
logWriter: writer,
|
|
1004
|
-
provider: remote.provider,
|
|
1005
|
-
platform: PLATFORM,
|
|
1006
|
-
projectRoot: root,
|
|
1007
|
-
fingerprintHash: hash,
|
|
1008
|
-
runOptions: remoteRunOptions
|
|
1009
|
-
});
|
|
1010
|
-
if (hit?.appPath) {
|
|
1011
|
-
let stored = null;
|
|
1012
|
-
try {
|
|
1013
|
-
stored = storeCached(PLATFORM, cacheKey, hit.appPath, { sources: fingerprintSources });
|
|
1014
|
-
} catch (err) {
|
|
1015
|
-
phase("cache", chalk.yellow(`remote hit could not be stored locally: ${err?.message || err}`));
|
|
1016
|
-
}
|
|
1017
|
-
apkPath = stored || hit.appPath;
|
|
1018
|
-
record.cacheHit = "remote";
|
|
1019
|
-
phase("cache", `remote hit (${remote.name})${stored ? " -> stored locally" : ""} ${remoteTimer()}`);
|
|
1020
|
-
} else if (hit?.timedOut) {
|
|
1021
|
-
abandonedRemote = true;
|
|
1022
|
-
phase("cache", chalk.yellow(`${remote.name} did not answer within ${formatDuration(RESOLVE_TIMEOUT_MS)}; building instead`));
|
|
1023
|
-
} else if (hit?.failed) {
|
|
1024
|
-
const authNote = remote.name === "eas" && isEasAuthFailureText(hit.failed) ? easAuthNote({
|
|
1025
|
-
code: "logged-out",
|
|
1026
|
-
reason: hit.failed
|
|
1027
|
-
}) : null;
|
|
1028
|
-
phase("cache", chalk.yellow(authNote || `${remote.name} could not be used: ${hit.failed}; building instead`));
|
|
1029
|
-
} else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
let waitedForBuild = null;
|
|
1033
|
-
let releasedWait = null;
|
|
1034
|
-
async function awaitSharedBuild() {
|
|
1035
|
-
const shared = await waitForSharedBuild({
|
|
1036
|
-
platform: PLATFORM,
|
|
1037
|
-
key: cacheKey,
|
|
1038
|
-
fingerprint: hash,
|
|
1039
|
-
root,
|
|
1040
|
-
logFile: buildLog,
|
|
1041
|
-
command: "stim android",
|
|
1042
|
-
acquire: acquireLock,
|
|
1043
|
-
wait: waitForBuild,
|
|
1044
|
-
now,
|
|
1045
|
-
phase: (text) => {
|
|
1046
|
-
step("wait");
|
|
1047
|
-
phase("build", text);
|
|
1048
|
-
},
|
|
1049
|
-
waitingOn,
|
|
1050
|
-
warn: (text) => phase("build", chalk.yellow(text)),
|
|
1051
|
-
out
|
|
1052
|
-
});
|
|
1053
|
-
if (shared.refusal) {
|
|
1054
|
-
const { code, message, remedy } = shared.refusal;
|
|
1055
|
-
phaseFailure = fail$1(code, message, remedy, { lastBuildStatus: true });
|
|
1056
|
-
throw new ArtifactRefusal(phaseFailure);
|
|
1057
|
-
}
|
|
1058
|
-
buildLock = shared.lock;
|
|
1059
|
-
releasedWait = shared.released;
|
|
1060
|
-
if (shared.hit) {
|
|
1061
|
-
apkPath = shared.hit.path;
|
|
1062
|
-
record.cacheHit = "local";
|
|
1063
|
-
waitedForBuild = shared.hit.waited;
|
|
1064
|
-
}
|
|
1065
|
-
return apkPath;
|
|
1066
|
-
}
|
|
1067
|
-
let swapDir = null;
|
|
1068
|
-
const installableCachedApk = async (key, cachedPath) => {
|
|
1069
|
-
if (!release) return cachedPath;
|
|
1070
|
-
phase("swap", `regenerating this workspace's JS for the cached ${variant} APK`);
|
|
1071
|
-
const swap = await swapApk({
|
|
1072
|
-
root,
|
|
1073
|
-
isExpo,
|
|
1074
|
-
cachedApkPath: cachedPath,
|
|
1075
|
-
keystore: resolveKeystore(root, settings),
|
|
1076
|
-
logWriter: writer,
|
|
1077
|
-
storedAssets: storedAssets(PLATFORM, key)
|
|
1078
|
-
});
|
|
1079
|
-
if (swap?.ok && swap.apkPath) {
|
|
1080
|
-
if (swap.note) phase("swap", chalk.yellow(swap.note));
|
|
1081
|
-
swapDir = swap.tmpDir ?? null;
|
|
1082
|
-
phase("swap", `${swap.hermes ? "hermes bytecode" : "plain JS"} repacked (store), zipaligned and re-signed (${formatDuration(swap.durationMs)})`);
|
|
1083
|
-
return swap.apkPath;
|
|
1084
|
-
}
|
|
1085
|
-
if (swap?.assetMismatch) phase("swap", chalk.yellow(`${swap.reason} -- building fresh instead` + (swap.assetDiff ? " (an APK cannot be made to carry an asset AAPT did not package)" : "")));
|
|
1086
|
-
else phase("swap", chalk.yellow(`failed at ${swap?.step || "unknown step"}: ${swap?.reason || "unknown reason"} -- building fresh instead (a cached ${variant} APK carries its builder's JS; it is never installed after a failed swap)`));
|
|
1087
|
-
for (const line of swap?.lastLines ?? []) phase("", chalk.dim(line));
|
|
1088
|
-
swapFellBack = true;
|
|
1089
|
-
return null;
|
|
1090
|
-
};
|
|
1091
|
-
async function prepareCachedArtifact(cachedPath) {
|
|
1092
|
-
const prepared = await installableCachedApk(cacheKey, cachedPath);
|
|
1093
|
-
if (!prepared) {
|
|
1094
|
-
record.cacheHit = false;
|
|
1095
|
-
waitedForBuild = null;
|
|
1096
|
-
}
|
|
1097
|
-
return prepared;
|
|
1098
|
-
}
|
|
1099
|
-
function reasonForMiss(rekeyedBy) {
|
|
1100
|
-
if (swapFellBack) return {
|
|
1101
|
-
reason: skippedMissReason("the cached APK could not be reused, so this run built it fresh"),
|
|
1102
|
-
diff: null
|
|
1103
|
-
};
|
|
1104
|
-
if (!useBuildCache) return {
|
|
1105
|
-
reason: skippedMissReason(requestedBuildCache ? "cache reuse off in config" : "cache reuse turned off by --no-build-cache"),
|
|
1106
|
-
diff: null
|
|
1107
|
-
};
|
|
1108
|
-
const current = {
|
|
1109
|
-
hash: storeHash,
|
|
1110
|
-
sources: storeSources
|
|
1111
|
-
};
|
|
1112
|
-
const explained = explainBuildMiss({
|
|
1113
|
-
root,
|
|
1114
|
-
platform: PLATFORM,
|
|
1115
|
-
current,
|
|
1116
|
-
rekeyedBy,
|
|
1117
|
-
baselineDeps: { readState }
|
|
1118
|
-
});
|
|
1119
|
-
return {
|
|
1120
|
-
reason: explained.reason,
|
|
1121
|
-
diff: explained.previousHash && explained.changedNames.length ? fingerprintDiffRecord({
|
|
1122
|
-
changed: explained.changedNames,
|
|
1123
|
-
previousHash: explained.previousHash,
|
|
1124
|
-
hash: current.hash
|
|
1125
|
-
}) : null
|
|
1126
|
-
};
|
|
1127
|
-
}
|
|
1128
|
-
function explainMiss(rekeyedBy) {
|
|
1129
|
-
const explained = reasonForMiss(rekeyedBy);
|
|
1130
|
-
record.missReason = explained.reason;
|
|
1131
|
-
if (explained.diff) writer.write(explained.diff);
|
|
1132
|
-
miss(record.missReason);
|
|
1133
|
-
phase("cache", `miss: ${record.missReason.summary}`);
|
|
1134
|
-
if (record.missReason.kind === "no-baseline") {
|
|
1135
|
-
const line = untrackedMissLine(untracked({ projectRoot: root }));
|
|
1136
|
-
if (line) phase("fingerprint", chalk.dim(line));
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
async function takeBuildSlot() {
|
|
1140
|
-
requireLocalBuild(buildMachine);
|
|
1141
|
-
if (!maxBuilds) return true;
|
|
1142
|
-
try {
|
|
1143
|
-
buildSlot = await acquireSlot({
|
|
1144
|
-
automatic: buildMachine === "auto",
|
|
1145
|
-
max: maxBuilds,
|
|
1146
|
-
root,
|
|
1147
|
-
logFile: buildLog,
|
|
1148
|
-
out,
|
|
1149
|
-
waitingFor: (info) => waitingFor?.(info, "build-slot")
|
|
1150
|
-
});
|
|
1151
|
-
slotWaitMs = buildSlot.slotWaitMs;
|
|
1152
|
-
} catch (err) {
|
|
1153
|
-
requireLocalBuild(buildMachine);
|
|
1154
|
-
const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
|
|
1155
|
-
if (refusal) {
|
|
1156
|
-
phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
|
|
1157
|
-
return false;
|
|
1158
|
-
}
|
|
1159
|
-
phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
|
|
1160
|
-
}
|
|
1161
|
-
return true;
|
|
1162
|
-
}
|
|
1163
|
-
/** Whether this build should leave this Mac, before any machine is asked; null builds here. */
|
|
1164
|
-
function placeBuild() {
|
|
1165
|
-
const { mode, machines, localEnabled } = buildPlacementCandidates(buildMachine);
|
|
1166
|
-
if (localEnabled && machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
|
|
1167
|
-
const unsupported = physical ? "device builds build here" : remoteTarget ? "remote device builds build here" : release ? `${variant} builds build here` : cas ? "Apple Clang CAS builds build here" : !cachePolicy.write ? "the build cache is off" : null;
|
|
1168
|
-
const here = machineCapacity();
|
|
1169
|
-
const placement = offloadPlacement({
|
|
1170
|
-
mode,
|
|
1171
|
-
localEnabled,
|
|
1172
|
-
machines: machines.length,
|
|
1173
|
-
here,
|
|
1174
|
-
unsupported,
|
|
1175
|
-
selected: buildMachine
|
|
1176
|
-
});
|
|
1177
|
-
if (!placement.offload) {
|
|
1178
|
-
hereReason = placement.reason;
|
|
1179
|
-
writer.write(buildPlacementRecord({
|
|
1180
|
-
platform: PLATFORM,
|
|
1181
|
-
buildMachine,
|
|
1182
|
-
stays: {
|
|
1183
|
-
code: placement.code,
|
|
1184
|
-
reason: placement.reason
|
|
1185
|
-
}
|
|
1186
|
-
}));
|
|
1187
|
-
if (mode !== "off") phase("build", `placement: here (${placement.reason})`);
|
|
1188
|
-
return null;
|
|
1189
|
-
}
|
|
1190
|
-
return {
|
|
1191
|
-
mode,
|
|
1192
|
-
here,
|
|
1193
|
-
code: placement.code,
|
|
1194
|
-
reason: placement.reason,
|
|
1195
|
-
machines
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
const openOffload = { choice: null };
|
|
1199
|
-
/** Asks the paired machines once the post-mutation key is known; null builds here. */
|
|
1200
|
-
async function chooseMachine(candidate) {
|
|
1201
|
-
let asked = [];
|
|
1202
|
-
const choice = await chooseBuildMachine({
|
|
1203
|
-
projectRoot: root,
|
|
1204
|
-
target: {
|
|
1205
|
-
platform: "android",
|
|
1206
|
-
local: androidToolchain(),
|
|
1207
|
-
requires: androidRequirements(root)
|
|
1208
|
-
},
|
|
1209
|
-
mode: candidate.mode,
|
|
1210
|
-
here: candidate.here,
|
|
1211
|
-
note: (line) => phase("build", chalk.dim(`offload: ${line}`)),
|
|
1212
|
-
machines: candidate.machines,
|
|
1213
|
-
selected: buildMachine,
|
|
1214
|
-
onCandidates: (each) => asked = each
|
|
1215
|
-
});
|
|
1216
|
-
if (typeof choice === "string") {
|
|
1217
|
-
const only = candidate.machines.length === 1 ? candidate.machines[0].machine : null;
|
|
1218
|
-
if (only && choice.startsWith(`${only}: `)) fallbackMachine = only;
|
|
1219
|
-
fallBack(choice, choice, {
|
|
1220
|
-
code: "no-remote-mac-took-it",
|
|
1221
|
-
candidates: asked
|
|
1222
|
-
});
|
|
1223
|
-
return null;
|
|
1224
|
-
}
|
|
1225
|
-
openOffload.choice = choice;
|
|
1226
|
-
writer.write(buildPlacementRecord({
|
|
1227
|
-
platform: PLATFORM,
|
|
1228
|
-
buildMachine,
|
|
1229
|
-
candidates: asked,
|
|
1230
|
-
chose: {
|
|
1231
|
-
machine: choice.machine,
|
|
1232
|
-
reason: `${candidate.reason}${placementLoad(choice)}`
|
|
1233
|
-
}
|
|
1234
|
-
}));
|
|
1235
|
-
phase("build", `placement: ${choice.machine} (${candidate.reason}${placementLoad(choice)})`);
|
|
1236
|
-
return choice;
|
|
1237
|
-
}
|
|
1238
|
-
/** Builds on the chosen machine and stores the APK under the post-mutation key; false builds here instead unless a machine was named. */
|
|
1239
|
-
async function compileElsewhere(choice, candidate) {
|
|
1240
|
-
if (!storeKey || !storeHash) {
|
|
1241
|
-
const reason = "the build fingerprint or cache key is unavailable";
|
|
1242
|
-
if (namedBuildMachine(buildMachine)) fallBack(reason, reason, { machine: choice.machine });
|
|
1243
|
-
else writer.write(buildPlacementRecord({
|
|
1244
|
-
platform: PLATFORM,
|
|
1245
|
-
buildMachine,
|
|
1246
|
-
event: "placement_fallback",
|
|
1247
|
-
fallback: {
|
|
1248
|
-
code: "fallback",
|
|
1249
|
-
reason,
|
|
1250
|
-
machine: choice.machine
|
|
1251
|
-
}
|
|
1252
|
-
}));
|
|
1253
|
-
return false;
|
|
1254
|
-
}
|
|
1255
|
-
const stagingDir = join(workspaceDir(root), "offload", PLATFORM);
|
|
1256
|
-
const outcome = await offloadBuild({
|
|
1257
|
-
choice,
|
|
1258
|
-
expectedFingerprint: storeHash,
|
|
1259
|
-
request: {
|
|
1260
|
-
platform: "android",
|
|
1261
|
-
isExpo,
|
|
1262
|
-
android: {
|
|
1263
|
-
variant,
|
|
1264
|
-
abi: buildAbi,
|
|
1265
|
-
gradleBuildCache: buildPlan.gradleBuildCache,
|
|
1266
|
-
pch: buildPlan.pch,
|
|
1267
|
-
compilerCache: buildPlan.compilerCache === "none" ? "none" : "ccache"
|
|
1268
|
-
}
|
|
1269
|
-
},
|
|
1270
|
-
stagingDir,
|
|
1271
|
-
onPhase: (name, msg) => phase(name, remotePhaseText(name, msg, choice.machine)),
|
|
1272
|
-
onEnter: (name) => {
|
|
1273
|
-
if (name === "compile" || name === "build" || name === "prebuild" || name === "pods") record.builtOn = choice.machine;
|
|
1274
|
-
place({
|
|
1275
|
-
host: choice.machine,
|
|
1276
|
-
phase: name
|
|
1277
|
-
});
|
|
1278
|
-
step(name === "prebuild" ? name : "compile");
|
|
1279
|
-
},
|
|
1280
|
-
onRecord: (entry) => writer.write({
|
|
1281
|
-
...entry,
|
|
1282
|
-
offloadedTo: choice.machine
|
|
1283
|
-
}),
|
|
1284
|
-
note: (line) => phase("build", line)
|
|
1285
|
-
});
|
|
1286
|
-
place(null);
|
|
1287
|
-
let stored = null;
|
|
1288
|
-
let reason = outcome.ok ? null : outcome.reason;
|
|
1289
|
-
if (outcome.ok) {
|
|
1290
|
-
const settled = await refingerprintAfterMutation({
|
|
1291
|
-
projectRoot: root,
|
|
1292
|
-
platform: PLATFORM,
|
|
1293
|
-
previousHash: storeHash,
|
|
1294
|
-
fingerprint
|
|
1295
|
-
});
|
|
1296
|
-
if (!settled || settled.moved) reason = "the checkout here changed while it built";
|
|
1297
|
-
else try {
|
|
1298
|
-
stored = storeCached(PLATFORM, storeKey, outcome.artifactPath, {
|
|
1299
|
-
sources: storeSources,
|
|
1300
|
-
overwrite: !useBuildCache
|
|
1301
|
-
});
|
|
1302
|
-
} catch (err) {
|
|
1303
|
-
reason = `could not store the APK: ${err?.message || err}`;
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
try {
|
|
1307
|
-
rmSync(stagingDir, {
|
|
1308
|
-
recursive: true,
|
|
1309
|
-
force: true
|
|
1310
|
-
});
|
|
1311
|
-
} catch {}
|
|
1312
|
-
step("compile");
|
|
1313
|
-
if (!outcome.ok || !stored) {
|
|
1314
|
-
const why = reason ?? "the APK was not stored";
|
|
1315
|
-
fallbackMachine = choice.machine;
|
|
1316
|
-
fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`, {
|
|
1317
|
-
code: "offload-failed",
|
|
1318
|
-
machine: choice.machine
|
|
1319
|
-
});
|
|
1320
|
-
writer.write({
|
|
1321
|
-
src: "build",
|
|
1322
|
-
level: "warn",
|
|
1323
|
-
event: "offload_failed",
|
|
1324
|
-
msg: reason,
|
|
1325
|
-
machine: choice.machine
|
|
1326
|
-
});
|
|
1327
|
-
return false;
|
|
1328
|
-
}
|
|
1329
|
-
const { timings } = outcome;
|
|
1330
|
-
apkPath = stored;
|
|
1331
|
-
handoff = outcome.handoff ?? null;
|
|
1332
|
-
record.builtOn = outcome.machine;
|
|
1333
|
-
record.offloadedTo = outcome.machine;
|
|
1334
|
-
stats.setPlacement({
|
|
1335
|
-
decision: "offloaded",
|
|
1336
|
-
machine: outcome.machine,
|
|
1337
|
-
reason: `${candidate.reason}${placementLoad(choice)}`,
|
|
1338
|
-
buildMs: timings.totalMs
|
|
1339
|
-
});
|
|
1340
|
-
ccacheActivity = outcome.ccache;
|
|
1341
|
-
phase("build", `built on ${outcome.machine} in ${formatDuration(timings.totalMs)}: offer ${formatDuration(timings.offerMs)}, sync ${formatDuration(timings.syncMs)}, build ${formatDuration(timings.workerMs)}, fetch ${formatDuration(timings.fetchMs)}`);
|
|
1342
|
-
phase("cache", `compilation cache on ${outcome.machine} ${ccacheActivityLine(ccacheActivity)}`);
|
|
1343
|
-
writer.write({
|
|
1344
|
-
src: "build",
|
|
1345
|
-
level: "info",
|
|
1346
|
-
event: "offload_done",
|
|
1347
|
-
msg: `built on ${outcome.machine}`,
|
|
1348
|
-
timings
|
|
1349
|
-
});
|
|
1350
|
-
return true;
|
|
1351
|
-
}
|
|
1352
|
-
async function prepareSource() {
|
|
1353
|
-
const rekeyedBy = [];
|
|
1354
|
-
const prebuildPlan = planPrebuildFor(root, PLATFORM, {
|
|
1355
|
-
isExpo,
|
|
1356
|
-
fingerprint: hash,
|
|
1357
|
-
sources: fingerprintSources
|
|
1358
|
-
});
|
|
1359
|
-
const prebuildRan = prebuildPlan === "generate" || prebuildPlan === "regenerate";
|
|
1360
|
-
if (prebuildPlan === "refuse") {
|
|
1361
|
-
const refusal = staleNativeDirRefusal(PLATFORM);
|
|
1362
|
-
phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
|
|
1363
|
-
throw new ArtifactRefusal(phaseFailure);
|
|
1364
|
-
}
|
|
1365
|
-
if (prebuildRan) {
|
|
1366
|
-
miss(reasonForMiss([]).reason, true);
|
|
1367
|
-
step("prebuild");
|
|
1368
|
-
recordPrebuild(root, PLATFORM, null);
|
|
1369
|
-
const pre = await prebuild(root, PLATFORM, writer, {
|
|
1370
|
-
isExpo,
|
|
1371
|
-
clean: prebuildPlan === "regenerate"
|
|
1372
|
-
});
|
|
1373
|
-
if (pre.failed) {
|
|
1374
|
-
phaseFailure = fail$1(pre.code, pre.reason, pre.remedy, {
|
|
1375
|
-
lastBuildStatus: true,
|
|
1376
|
-
lines: tail(pre.lastLines),
|
|
1377
|
-
logPath: displayPath(root, buildLog)
|
|
1378
|
-
});
|
|
1379
|
-
throw new ArtifactRefusal(phaseFailure);
|
|
1380
|
-
}
|
|
1381
|
-
phase("prebuild", `${prebuildPlan === "generate" ? "android/ generated" : "android/ not generated from this fingerprint -> regenerated with --clean"} (${formatDuration(pre.durationMs)})`);
|
|
1382
|
-
androidPackage = detectAndroidPackage(root) || androidPackage;
|
|
1383
|
-
record.bundleId = androidPackage;
|
|
1384
|
-
}
|
|
1385
|
-
return {
|
|
1386
|
-
prebuildRan,
|
|
1387
|
-
rekeyedBy,
|
|
1388
|
-
editedConfig: []
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
async function revalidateSource(preparation) {
|
|
1392
|
-
const { prebuildRan, rekeyedBy } = preparation;
|
|
1393
|
-
if (prebuildRan) {
|
|
1394
|
-
const after = await refingerprintAfterMutation({
|
|
1395
|
-
projectRoot: root,
|
|
1396
|
-
platform: PLATFORM,
|
|
1397
|
-
previousHash: hash,
|
|
1398
|
-
fingerprint
|
|
1399
|
-
});
|
|
1400
|
-
preparation.editedConfig = after ? configInputsChanged(fingerprintSources, after.sources, { prebuildRan }) : [];
|
|
1401
|
-
const { editedConfig } = preparation;
|
|
1402
|
-
if (after && !editedConfig.length) recordPrebuild(root, PLATFORM, after.hash);
|
|
1403
|
-
if (after?.moved && !editedConfig.length) {
|
|
1404
|
-
rekeyedBy.push("prebuild");
|
|
1405
|
-
storeHash = after.hash;
|
|
1406
|
-
storeSources = after.sources;
|
|
1407
|
-
storeKey = buildCacheKey(PLATFORM, after.hash, buildRunOptions);
|
|
1408
|
-
record.fingerprint = storeHash;
|
|
1409
|
-
record.cacheKey = storeKey;
|
|
1410
|
-
phase("fingerprint", chalk.dim(`${shortHash(hash)} -> ${shortHash(storeHash)} (after prebuild)`));
|
|
1411
|
-
const late = useBuildCache ? resolveCached(PLATFORM, storeKey) : null;
|
|
1412
|
-
if (late) {
|
|
1413
|
-
const prepared = await installableCachedApk(storeKey, late);
|
|
1414
|
-
if (prepared) {
|
|
1415
|
-
apkPath = prepared;
|
|
1416
|
-
record.cacheHit = "local";
|
|
1417
|
-
lateHit();
|
|
1418
|
-
phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
|
|
1419
|
-
if (releasedWait) {
|
|
1420
|
-
waitedForBuild = releasedWait.facts;
|
|
1421
|
-
phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
return apkPath ? { ready: apkPath } : null;
|
|
1428
|
-
}
|
|
1429
|
-
async function acquireRemoteArtifact(offload, { rekeyedBy, editedConfig }) {
|
|
1430
|
-
explainMiss(rekeyedBy);
|
|
1431
|
-
step("compile");
|
|
1432
|
-
if (editedConfig.length) fallBack("prebuild changed config inputs, so the APK cannot be cached", "offload failed: prebuild changed config inputs, so the APK cannot be cached");
|
|
1433
|
-
else {
|
|
1434
|
-
const choice = await chooseMachine(offload);
|
|
1435
|
-
if (choice) await compileElsewhere(choice, offload);
|
|
1436
|
-
}
|
|
1437
|
-
return apkPath ? { ready: apkPath } : null;
|
|
1438
|
-
}
|
|
1439
|
-
async function compileHere(offload, { rekeyedBy }) {
|
|
1440
|
-
if (!offload) {
|
|
1441
|
-
explainMiss(rekeyedBy);
|
|
1442
|
-
step("compile");
|
|
1443
|
-
}
|
|
1444
|
-
if (offload) stats.setPlacement({
|
|
1445
|
-
decision: "fell-back",
|
|
1446
|
-
slotWaitMs,
|
|
1447
|
-
deviceSlotWaitMs: stats.deviceSlotWaitMs(),
|
|
1448
|
-
reason: record.offloadFallback ?? "offload failed",
|
|
1449
|
-
...fallbackMachine ? { machine: fallbackMachine } : {}
|
|
1450
|
-
});
|
|
1451
|
-
else stats.setPlacement({
|
|
1452
|
-
decision: "here",
|
|
1453
|
-
reason: hereReason,
|
|
1454
|
-
slotWaitMs,
|
|
1455
|
-
deviceSlotWaitMs: stats.deviceSlotWaitMs()
|
|
1456
|
-
});
|
|
1457
|
-
requireLocalBuild(buildMachine);
|
|
1458
|
-
record.builtOn = "here";
|
|
1459
|
-
phase("build", `compiling ${variant || "debug"} with Gradle`);
|
|
1460
|
-
const built = await build({
|
|
1461
|
-
root,
|
|
1462
|
-
logWriter: writer,
|
|
1463
|
-
variant,
|
|
1464
|
-
abi: buildAbi
|
|
1465
|
-
}, {
|
|
1466
|
-
estimateMs: estimates().coldBuildMs,
|
|
1467
|
-
ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
|
|
1468
|
-
root,
|
|
1469
|
-
onNote: out
|
|
1470
|
-
}) : null,
|
|
1471
|
-
cas,
|
|
1472
|
-
buildCache: buildPlan.gradleBuildCache,
|
|
1473
|
-
pch: buildPlan.pch,
|
|
1474
|
-
compilerCacheDisabled: buildPlan.compilerCache === "none"
|
|
1475
|
-
});
|
|
1476
|
-
ccacheActivity = built.ccache ?? CCACHE_UNAVAILABLE;
|
|
1477
|
-
phase("cache", `compilation cache ${ccacheActivityLine(ccacheActivity)}`);
|
|
1478
|
-
if (!built.ok) {
|
|
1479
|
-
const diagnostics = built.diagnostics;
|
|
1480
|
-
for (const diag of diagnostics) writer.write({
|
|
1481
|
-
src: "build",
|
|
1482
|
-
level: "error",
|
|
1483
|
-
event: "gradle_diagnostic",
|
|
1484
|
-
msg: formatDiagnostic(diag)
|
|
1485
|
-
});
|
|
1486
|
-
phase("build", chalk.red(`FAILED after ${formatDuration(built.durationMs)}`));
|
|
1487
|
-
const extracted = diagnostics.map(formatDiagnostic);
|
|
1488
|
-
if (built.truncated > 0) extracted.push(`... and ${built.truncated} more diagnostic(s) in the log`);
|
|
1489
|
-
phaseFailure = fail$1(built.code, built.reason, diagnostics.find((d) => d.remedy)?.remedy || built.remedy || null, {
|
|
1490
|
-
lastBuildStatus: true,
|
|
1491
|
-
diagnostics: extracted,
|
|
1492
|
-
buildDiagnostics: diagnostics,
|
|
1493
|
-
lines: extracted.length ? [] : tail(built.lastLines),
|
|
1494
|
-
logPath: displayPath(root, buildLog)
|
|
1495
|
-
});
|
|
1496
|
-
throw new ArtifactRefusal(phaseFailure);
|
|
1497
|
-
}
|
|
1498
|
-
apkPath = built.apkPath;
|
|
1499
|
-
stats.setBuildMs(built.durationMs);
|
|
1500
|
-
phase("build", `ok (${formatDuration(built.durationMs)})`);
|
|
1501
|
-
if (built.apkNote) phase("build", chalk.yellow(built.apkNote));
|
|
1502
|
-
return apkPath;
|
|
1503
|
-
}
|
|
1504
|
-
async function validateCompiled({ prebuildRan, editedConfig }) {
|
|
1505
|
-
const beforeBuildHash = storeHash;
|
|
1506
|
-
const afterBuild = editedConfig.length ? null : await refingerprintAfterMutation({
|
|
1507
|
-
projectRoot: root,
|
|
1508
|
-
platform: PLATFORM,
|
|
1509
|
-
previousHash: beforeBuildHash,
|
|
1510
|
-
fingerprint
|
|
1511
|
-
});
|
|
1512
|
-
const changedDuringBuild = afterBuild ? inputsChangedDuringBuild({
|
|
1513
|
-
platform: PLATFORM,
|
|
1514
|
-
lookup: fingerprintSources,
|
|
1515
|
-
prebuildRan,
|
|
1516
|
-
compiled: storeSources,
|
|
1517
|
-
current: afterBuild.sources
|
|
1518
|
-
}) : editedConfig;
|
|
1519
|
-
if (!afterBuild || changedDuringBuild.length) {
|
|
1520
|
-
record.fingerprint = null;
|
|
1521
|
-
record.cacheKey = null;
|
|
1522
|
-
phase("fingerprint", chalk.yellow(changedDuringBuild.length ? changedDuringBuildLine(changedDuringBuild) : "unavailable after Gradle; the build will be installed but not cached"));
|
|
1523
|
-
return "uncacheable";
|
|
1524
|
-
}
|
|
1525
|
-
if (afterBuild.moved) {
|
|
1526
|
-
storeHash = afterBuild.hash;
|
|
1527
|
-
storeSources = afterBuild.sources;
|
|
1528
|
-
storeKey = buildCacheKey(PLATFORM, afterBuild.hash, buildRunOptions);
|
|
1529
|
-
record.fingerprint = storeHash;
|
|
1530
|
-
record.cacheKey = storeKey;
|
|
1531
|
-
phase("fingerprint", chalk.dim(`${shortHash(beforeBuildHash)} -> ${shortHash(storeHash)} (after Gradle)`));
|
|
1532
|
-
}
|
|
1533
|
-
return "cacheable";
|
|
1534
|
-
}
|
|
1535
|
-
async function storeArtifact(artifactPath) {
|
|
1536
|
-
if (cachePolicy.write) {
|
|
1537
|
-
const assetManifest = release ? captureAssets(root, { variant }) : null;
|
|
1538
|
-
try {
|
|
1539
|
-
const stored = await storeTieredBuild({
|
|
1540
|
-
local: filesystemBuildCapability({
|
|
1541
|
-
resolve: resolveCached,
|
|
1542
|
-
store: storeCached,
|
|
1543
|
-
sources: storeSources,
|
|
1544
|
-
assetManifest
|
|
1545
|
-
}),
|
|
1546
|
-
loadProvider: loadTieredProvider,
|
|
1547
|
-
target: {
|
|
1548
|
-
projectRoot: root,
|
|
1549
|
-
platform: PLATFORM,
|
|
1550
|
-
key: storeKey
|
|
1551
|
-
},
|
|
1552
|
-
sourcePath: artifactPath,
|
|
1553
|
-
overwrite: !useBuildCache || swapFellBack,
|
|
1554
|
-
warn: cacheWarn
|
|
1555
|
-
});
|
|
1556
|
-
providerUpload = stored.providerUpload;
|
|
1557
|
-
providerName = stored.providerName ?? providerName;
|
|
1558
|
-
} catch (err) {
|
|
1559
|
-
phase("cache", chalk.yellow(`could not store the build: ${err?.message || err}`));
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
if (remote) uploadPending = uploadRemoteBuild({
|
|
1563
|
-
logWriter: writer,
|
|
1564
|
-
provider: remote.provider,
|
|
1565
|
-
platform: PLATFORM,
|
|
1566
|
-
projectRoot: root,
|
|
1567
|
-
fingerprintHash: storeHash,
|
|
1568
|
-
buildPath: artifactPath,
|
|
1569
|
-
runOptions: remoteRunOptions
|
|
1570
|
-
});
|
|
1571
|
-
}
|
|
1572
|
-
try {
|
|
1573
|
-
apkPath = await runArtifactLifecycle({
|
|
1574
|
-
resolve: async () => {
|
|
1575
|
-
if (!await resolveInitialFingerprint()) throw new ArtifactRefusal(phaseFailure);
|
|
1576
|
-
if (easBuild) return {
|
|
1577
|
-
kind: "ready",
|
|
1578
|
-
artifact: apkPath
|
|
1579
|
-
};
|
|
1580
|
-
await resolveRemoteArtifact();
|
|
1581
|
-
if (apkPath) return {
|
|
1582
|
-
kind: "cached",
|
|
1583
|
-
artifact: apkPath
|
|
1584
|
-
};
|
|
1585
|
-
miss(reasonForMiss([]).reason);
|
|
1586
|
-
return { kind: "miss" };
|
|
1587
|
-
},
|
|
1588
|
-
claim: useBuildCache ? awaitSharedBuild : void 0,
|
|
1589
|
-
reuse: async (cached) => {
|
|
1590
|
-
apkPath = await prepareCachedArtifact(cached);
|
|
1591
|
-
if (apkPath) lateHit();
|
|
1592
|
-
else if (swapFellBack) miss(reasonForMiss([]).reason);
|
|
1593
|
-
return apkPath;
|
|
1594
|
-
},
|
|
1595
|
-
build: {
|
|
1596
|
-
placement: {
|
|
1597
|
-
select: placeBuild,
|
|
1598
|
-
acquire: acquireRemoteArtifact
|
|
1599
|
-
},
|
|
1600
|
-
admit: async () => {
|
|
1601
|
-
if (!await takeBuildSlot()) throw new ArtifactRefusal(phaseFailure);
|
|
1602
|
-
},
|
|
1603
|
-
prepare: prepareSource,
|
|
1604
|
-
revalidate: revalidateSource,
|
|
1605
|
-
compile: compileHere,
|
|
1606
|
-
validate: validateCompiled,
|
|
1607
|
-
store: storeArtifact
|
|
1608
|
-
},
|
|
1609
|
-
release: () => {
|
|
1610
|
-
if (openOffload.choice) closeOffload(openOffload.choice);
|
|
1611
|
-
releaseHeldLock();
|
|
1612
|
-
releaseHeldSlot();
|
|
1613
|
-
}
|
|
1614
|
-
});
|
|
1615
|
-
} catch (error) {
|
|
1616
|
-
if (error instanceof OffloadRefusal) {
|
|
1617
|
-
const { code, message, remedy } = error;
|
|
1618
|
-
return {
|
|
1619
|
-
ok: false,
|
|
1620
|
-
failure: fail$1(code, message, remedy, { lastBuildStatus: true }),
|
|
1621
|
-
ccache: ccacheActivity
|
|
1622
|
-
};
|
|
1623
|
-
}
|
|
1624
|
-
if (error instanceof ArtifactRefusal) return {
|
|
1625
|
-
ok: false,
|
|
1626
|
-
failure: error.failure,
|
|
1627
|
-
ccache: ccacheActivity
|
|
1628
|
-
};
|
|
1629
|
-
throw error;
|
|
1630
|
-
}
|
|
1631
|
-
return {
|
|
1632
|
-
ok: true,
|
|
1633
|
-
artifact: {
|
|
1634
|
-
apkPath,
|
|
1635
|
-
handoff,
|
|
1636
|
-
androidPackage,
|
|
1637
|
-
swapDir,
|
|
1638
|
-
waitedForBuild,
|
|
1639
|
-
ccache: ccacheActivity,
|
|
1640
|
-
uploadPending,
|
|
1641
|
-
providerUpload,
|
|
1642
|
-
providerName,
|
|
1643
|
-
remote,
|
|
1644
|
-
abandonedRemote
|
|
1645
|
-
}
|
|
1646
|
-
};
|
|
1647
|
-
}
|
|
1648
|
-
function tail(lines, n = FALLBACK_LINES) {
|
|
1649
|
-
return (Array.isArray(lines) ? lines : []).slice(-n);
|
|
1650
|
-
}
|
|
1651
|
-
//#endregion
|
|
1652
|
-
//#region src/commands/android/collector.ts
|
|
1653
|
-
function collectorEntry() {
|
|
1654
|
-
return spawnEntry("collector-run");
|
|
1655
|
-
}
|
|
1656
|
-
function collectorLogFile(root, slot = "default") {
|
|
1657
|
-
return join(workspaceLogsDir$1(root), `collector-${deviceSlotFileKey(PLATFORM, slot)}.log`);
|
|
1658
|
-
}
|
|
1659
|
-
const COLLECTOR_EXIT_WAIT_MS = 2e3;
|
|
1660
|
-
const COLLECTOR_POLL_MS = 25;
|
|
1661
|
-
function killPreviousCollector(root, { platform = PLATFORM, slot = "default", kill = (pid, signal) => signalProcessTree(pid, signal), collectors = null, verify = verifyCollectorOwnership, isAlive = pidExists, note = (_line) => {} } = {}) {
|
|
1662
|
-
const record = (collectors ?? readCollectors(root))?.[deviceSlotKey(platform, slot)];
|
|
1663
|
-
const pid = Number(record?.pid);
|
|
1664
|
-
if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) return null;
|
|
1665
|
-
const ownership = verify({
|
|
1666
|
-
pid,
|
|
1667
|
-
platform: deviceSlotKey(platform, slot),
|
|
1668
|
-
root,
|
|
1669
|
-
isAlive,
|
|
1670
|
-
expected: record
|
|
1671
|
-
});
|
|
1672
|
-
if (ownership.status === "gone") return null;
|
|
1673
|
-
if (ownership.status === "unverified") {
|
|
1674
|
-
note(chalk.dim(`Previous ${platform} log collector (pid ${pid}): ${ownership.reason}, so it was not signalled -- starting a replacement anyway`));
|
|
1675
|
-
return null;
|
|
1676
|
-
}
|
|
1677
|
-
try {
|
|
1678
|
-
kill(pid, "SIGTERM");
|
|
1679
|
-
return pid;
|
|
1680
|
-
} catch {
|
|
1681
|
-
return null;
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
async function startCollector({ root, slot = "default", serial, packageName, spawn, kill, alive = pidExists, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), out }) {
|
|
1685
|
-
const previousPid = killPreviousCollector(root, {
|
|
1686
|
-
slot,
|
|
1687
|
-
kill,
|
|
1688
|
-
isAlive: alive,
|
|
1689
|
-
verify,
|
|
1690
|
-
note: (line) => out(phaseLine("logs", line))
|
|
1691
|
-
});
|
|
1692
|
-
if (previousPid) {
|
|
1693
|
-
const deadline = Date.now() + waitMs;
|
|
1694
|
-
while (Date.now() < deadline && alive(previousPid)) await sleep(COLLECTOR_POLL_MS);
|
|
1695
|
-
}
|
|
1696
|
-
let stdio = "ignore";
|
|
1697
|
-
try {
|
|
1698
|
-
mkdirSync(workspaceLogsDir$1(root), { recursive: true });
|
|
1699
|
-
const fd = openSync(collectorLogFile(root, slot), "a");
|
|
1700
|
-
stdio = [
|
|
1701
|
-
"ignore",
|
|
1702
|
-
fd,
|
|
1703
|
-
fd
|
|
1704
|
-
];
|
|
1705
|
-
} catch {}
|
|
1706
|
-
try {
|
|
1707
|
-
const child = spawn(process.execPath, [
|
|
1708
|
-
collectorEntry(),
|
|
1709
|
-
"--platform",
|
|
1710
|
-
PLATFORM,
|
|
1711
|
-
"--root",
|
|
1712
|
-
root,
|
|
1713
|
-
...slot === "default" ? [] : ["--slot", slot],
|
|
1714
|
-
"--serial",
|
|
1715
|
-
serial,
|
|
1716
|
-
"--package",
|
|
1717
|
-
packageName
|
|
1718
|
-
], {
|
|
1719
|
-
cwd: androidToolCwd() ?? root,
|
|
1720
|
-
detached: true,
|
|
1721
|
-
stdio,
|
|
1722
|
-
env: process.env
|
|
1723
|
-
});
|
|
1724
|
-
child?.unref?.();
|
|
1725
|
-
return child?.pid ?? null;
|
|
1726
|
-
} catch (err) {
|
|
1727
|
-
out(phaseLine("logs", chalk.yellow(`could not start the device log collector: ${err?.message || err}`)));
|
|
1728
|
-
return null;
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
//#endregion
|
|
1732
|
-
//#region src/commands/android/launch.ts
|
|
1733
|
-
function androidProcessRuntime(prepare) {
|
|
1734
|
-
return {
|
|
1735
|
-
prepare,
|
|
1736
|
-
scheme: () => void 0,
|
|
1737
|
-
verificationWaitMs: RELEASE_VERIFY_WAIT_MS,
|
|
1738
|
-
launch: async (_prepared, { serial, androidPackage, remoteDevice, launchRelease }) => remoteDevice ? remoteDevice.launch({
|
|
1739
|
-
serial,
|
|
1740
|
-
packageName: androidPackage,
|
|
1741
|
-
metroPort: null
|
|
1742
|
-
}) : launchRelease({
|
|
1743
|
-
serial,
|
|
1744
|
-
packageName: androidPackage
|
|
1745
|
-
}),
|
|
1746
|
-
verify: async (_prepared, context) => verifyAndroidProcessRun(context)
|
|
1747
|
-
};
|
|
1748
|
-
}
|
|
1749
|
-
function androidMetroRuntime(prepare) {
|
|
1750
|
-
return {
|
|
1751
|
-
prepare,
|
|
1752
|
-
scheme: (root, apkPath, resolve) => resolve(root, apkPath),
|
|
1753
|
-
verificationWaitMs: DEBUG_VERIFY_STEP_MS,
|
|
1754
|
-
launch: async ({ metroPort }, { serial, androidPackage, scheme, physical, launch }) => launch({
|
|
1755
|
-
serial,
|
|
1756
|
-
packageName: androidPackage,
|
|
1757
|
-
metroPort: metroPort ?? 8081,
|
|
1758
|
-
devClientScheme: scheme,
|
|
1759
|
-
physical
|
|
1760
|
-
}),
|
|
1761
|
-
verify: async ({ metroPort }, context) => verifyAndroidMetroRun({
|
|
1762
|
-
...context,
|
|
1763
|
-
metroPort
|
|
1764
|
-
})
|
|
1765
|
-
};
|
|
1766
|
-
}
|
|
1767
|
-
async function verifyAndroidProcessRun({ root, slot, remoteDevice, verifyReleaseLaunched, serial, androidPackage, variant, logsDir, launchedAt, phase }) {
|
|
1768
|
-
const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
|
|
1769
|
-
root,
|
|
1770
|
-
slot,
|
|
1771
|
-
platform: "android",
|
|
1772
|
-
deviceId: serial,
|
|
1773
|
-
appId: androidPackage,
|
|
1774
|
-
since: launchedAt
|
|
1775
|
-
}, logsDir);
|
|
1776
|
-
if (remoteDevice) {
|
|
1777
|
-
phase("verify", chalk.yellow("UNVERIFIED: remote adapter launch accepted; process verification is unavailable"));
|
|
1778
|
-
return { state: LAUNCH_UNVERIFIED };
|
|
1779
|
-
}
|
|
1780
|
-
const processCheck = await verifyReleaseLaunched({
|
|
1781
|
-
serial,
|
|
1782
|
-
packageName: androidPackage
|
|
1783
|
-
});
|
|
1784
|
-
const crashes = readNativeCrashes();
|
|
1785
|
-
if (processCheck?.verified && !crashes.length) {
|
|
1786
|
-
phase("verify", `process alive ${formatDuration(processCheck.waitedMs ?? 0)} after launch (${variant}: no bundle fetch to observe)`);
|
|
1787
|
-
return { state: true };
|
|
1788
|
-
}
|
|
1789
|
-
if (processCheck?.reason === "probe-failed" && !crashes.length) {
|
|
1790
|
-
phase("verify", chalk.yellow("UNVERIFIED: the app process check failed"));
|
|
1791
|
-
return { state: LAUNCH_UNVERIFIED };
|
|
1792
|
-
}
|
|
1793
|
-
for (const line of launchErrorPreview(crashes, root)) phase("launch", chalk.red(line));
|
|
1794
|
-
if (!crashes.length) phase("logs", "Process missing; no attributable native crash report captured. Read `stim logs --source device` for available output.");
|
|
1795
|
-
phase("verify", chalk.yellow(crashes.length ? "FATAL: the app reported a native crash" : `FATAL: no ${androidPackage} process on ${serial} ${formatDuration(processCheck?.waitedMs ?? 0)} after launch`));
|
|
1796
|
-
phase("", chalk.yellow("A release process exited before readiness. Run `stim logs --errors` for captured crash reports, or `stim logs --source device` for the full device output."));
|
|
1797
|
-
return { state: LAUNCH_FATAL };
|
|
1798
|
-
}
|
|
1799
|
-
async function verifyAndroidMetroRun({ root, slot, remoteDevice, verifyLaunched, serial, androidPackage, metroCheck, logsDir, launchedAt, metroPort, isExpo, physical, device, scheme, component = null, phase }) {
|
|
1800
|
-
const runCommand = nativeRunCommand("android", slot, {
|
|
1801
|
-
physical,
|
|
1802
|
-
deviceId: serial
|
|
1803
|
-
});
|
|
1804
|
-
const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
|
|
1805
|
-
root,
|
|
1806
|
-
slot,
|
|
1807
|
-
platform: "android",
|
|
1808
|
-
deviceId: serial,
|
|
1809
|
-
appId: androidPackage,
|
|
1810
|
-
since: launchedAt
|
|
1811
|
-
}, logsDir);
|
|
1812
|
-
const freshEmulator = Boolean((device.created || device.adoptionPending) && device.owned && !remoteDevice);
|
|
1813
|
-
const timeoutMs = freshEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
|
|
1814
|
-
if (metroCheck && freshEmulator) phase("verify", `waiting up to 60s for bundle load (${device.created ? "new" : "adopted"} emulator)`);
|
|
1815
|
-
const siblings = metroCheck ? siblingPlatformSlots(root, "android", slot) : [];
|
|
1816
|
-
const verification = metroCheck ? await verifyLaunched({
|
|
1817
|
-
timeoutMs,
|
|
1818
|
-
requireBundleResponse: true,
|
|
1819
|
-
slot: launchSlotScope(root, slot),
|
|
1820
|
-
platformShared: siblings.length > 0,
|
|
1821
|
-
onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
|
|
1822
|
-
logsDir,
|
|
1823
|
-
since: launchedAt,
|
|
1824
|
-
metroPort,
|
|
1825
|
-
platform: "android",
|
|
1826
|
-
mode: isExpo ? MODE_EXPO : MODE_BARE,
|
|
1827
|
-
readNativeCrashes,
|
|
1828
|
-
processAlive: () => {
|
|
1829
|
-
const pid = androidAppProcess(serial, androidPackage);
|
|
1830
|
-
return pid === void 0 ? null : pid !== null;
|
|
1831
|
-
}
|
|
1832
|
-
}) : {
|
|
1833
|
-
verified: false,
|
|
1834
|
-
skipped: true
|
|
1835
|
-
};
|
|
1836
|
-
const nativeCrashes = verification.errors?.some((record) => record.event === "native_crash") ? [] : readNativeCrashes();
|
|
1837
|
-
if (nativeCrashes.length) verification.fatal = true;
|
|
1838
|
-
verification.errors = await errorDiagnostics([...verification.errors ?? [], ...nativeCrashes], {
|
|
1839
|
-
root,
|
|
1840
|
-
logsDir,
|
|
1841
|
-
port: metroPort,
|
|
1842
|
-
allowRequest: true
|
|
1843
|
-
});
|
|
1844
|
-
if (verification.readiness) phase("readiness", appReadinessMessage(verification.readiness, verification.waitedMs ?? 0));
|
|
1845
|
-
if (verification?.fatal) {
|
|
1846
|
-
const nativeFatal = verification.errors?.some((record) => record.event === "native_crash");
|
|
1847
|
-
const deliveryFailed = verification.record?.event === "bundle_response_failed";
|
|
1848
|
-
const reason = nativeFatal ? "the app reported a native crash" : verification.processAlive === false ? "the app process exited" : deliveryFailed ? "Metro bundle delivery failed" : "Metro could not build the bundle";
|
|
1849
|
-
phase("verify", chalk.red(`FATAL after ${formatDuration(verification.waitedMs ?? 0)}: ${reason}`));
|
|
1850
|
-
for (const line of launchErrorPreview(verification.errors ?? [], root)) phase("", chalk.red(line));
|
|
1851
|
-
if (verification.processAlive === false && !verification.errors?.some((record) => record.event === "native_crash")) phase("logs", "No attributable native crash report captured yet. Run `stim logs --errors` again for delayed reports, or `stim logs --source device` for available output.");
|
|
1852
|
-
if (nativeFatal || verification.processAlive === false) phase("remedy", chalk.yellow(`Fix the crash, then run \`${runCommand}\` again; it force-stops a crashed process that remains alive behind the system crash dialog. Metro reload cannot recover it.`));
|
|
1853
|
-
else if (verification.processAlive === true && metroPort !== null) {
|
|
1854
|
-
const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
|
|
1855
|
-
phase("remedy", chalk.yellow(`The native app is still running. ${deliveryFailed ? "Check the Metro logs and device connection, then" : "Fix the JavaScript or TypeScript error, then"} ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`));
|
|
1856
|
-
}
|
|
1857
|
-
return { state: LAUNCH_FATAL };
|
|
1858
|
-
}
|
|
1859
|
-
if (verification?.verified) {
|
|
1860
|
-
phase("verify", `bundle loaded` + (verification.processAlive === true ? ", process alive" : "") + (verification.readiness ? "" : ", stable for 3s -- the first screen may still be rendering") + ` (${formatDuration(verification.waitedMs ?? 0)} total)`);
|
|
1861
|
-
const report = launchErrorReport(verification.errors ?? [], root);
|
|
1862
|
-
if (report.summary) phase("launch", chalk.dim(report.summary));
|
|
1863
|
-
for (const line of report.lines) phase("launch", chalk.yellow(line));
|
|
1864
|
-
if (report.lines.length > 0 && verification.processAlive === true && metroPort !== null) {
|
|
1865
|
-
const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
|
|
1866
|
-
phase("remedy", chalk.yellow(`The native app is still running. Fix the JavaScript or TypeScript error; Fast Refresh should apply the edit. If the error screen remains, ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`));
|
|
1867
|
-
}
|
|
1868
|
-
return {
|
|
1869
|
-
state: true,
|
|
1870
|
-
warning: report.lines.length > 0 ? "app errors detected; inspect the error above" : verification.readiness === "timed-out" || verification.readiness === "error" ? "app readiness not confirmed; inspect the UI and logs" : void 0
|
|
1871
|
-
};
|
|
1872
|
-
}
|
|
1873
|
-
if (verification?.skipped) {
|
|
1874
|
-
phase("verify", "skipped (--no-metro-check): the launch is reported as unverified");
|
|
1875
|
-
return { state: LAUNCH_UNVERIFIED };
|
|
1876
|
-
}
|
|
1877
|
-
if (verification?.requested) {
|
|
1878
|
-
phase("verify", `BUNDLING: the app asked port ${metroPort} for its bundle; build, delivery, or JavaScript loading was still pending after ${formatDuration(verification.waitedMs ?? 0)} (a cold bundle on a large graph outlasts this window)`);
|
|
1879
|
-
phase("", chalk.dim("Nothing to do: `stim logs --source metro` shows the build finishing, usually within a minute."));
|
|
1880
|
-
return { state: LAUNCH_BUNDLING };
|
|
1881
|
-
}
|
|
1882
|
-
if (verification?.unattributed) {
|
|
1883
|
-
const [headline, ...lines] = unattributedLaunchLines({
|
|
1884
|
-
platform: "android",
|
|
1885
|
-
metroPort,
|
|
1886
|
-
slot,
|
|
1887
|
-
siblings
|
|
1888
|
-
});
|
|
1889
|
-
phase("verify", chalk.yellow(headline));
|
|
1890
|
-
for (const line of lines) phase("", chalk.yellow(line));
|
|
1891
|
-
return {
|
|
1892
|
-
state: LAUNCH_UNVERIFIED,
|
|
1893
|
-
unattributed: true
|
|
1894
|
-
};
|
|
1895
|
-
}
|
|
1896
|
-
phase("verify", chalk.yellow("UNVERIFIED: no bundle request reached this workspace's Metro"));
|
|
1897
|
-
for (const line of unverifiedLaunchLines({
|
|
1898
|
-
platform: PLATFORM,
|
|
1899
|
-
metroPort: metroPort ?? 8081,
|
|
1900
|
-
waitedMs: verification?.waitedMs,
|
|
1901
|
-
bundleId: androidPackage,
|
|
1902
|
-
serial,
|
|
1903
|
-
component,
|
|
1904
|
-
devClientUrl: scheme ? androidDevClientUrl(scheme, metroPort ?? 8081, physical) : null,
|
|
1905
|
-
devClient: Boolean(scheme),
|
|
1906
|
-
mode: isExpo ? MODE_EXPO : MODE_BARE
|
|
1907
|
-
})) phase("", chalk.yellow(line));
|
|
1908
|
-
return { state: LAUNCH_UNVERIFIED };
|
|
1909
|
-
}
|
|
1910
|
-
const ADOPTED_INSTALL_HEADROOM_BYTES = 536870912;
|
|
1911
|
-
const INSUFFICIENT_STORAGE = /INSTALL_FAILED_INSUFFICIENT_STORAGE/;
|
|
1912
|
-
async function wipeAdoptedAvd(room, free) {
|
|
1913
|
-
room.phase("device", chalk.yellow(`${room.avdName} has ${free === null ? "too little space" : `${formatBytes(free)} free`} on /data after removing other apps and trimming caches; wiping its user data`));
|
|
1914
|
-
const expectedRecord = deviceSlotPlatforms(loadConfig$1()?.projects?.[room.root], room.slot)?.android;
|
|
1915
|
-
if (!expectedRecord?.adoptionPending || expectedRecord.avdName !== room.avdName) throw new Error("the emulator assignment changed before the wipe; its data was kept");
|
|
1916
|
-
const wiped = room.wipeDevice(room.avdName, {
|
|
1917
|
-
wipe: true,
|
|
1918
|
-
owner: {
|
|
1919
|
-
projectPath: room.root,
|
|
1920
|
-
slot: room.slot,
|
|
1921
|
-
expectedRecord
|
|
1922
|
-
},
|
|
1923
|
-
workspace: room.root
|
|
1924
|
-
});
|
|
1925
|
-
if (wiped.status !== "torn-down") throw new Error(`could not wipe it: ${wiped.reason ?? wiped.status}`);
|
|
1926
|
-
const rebooted = await room.rebootDevice();
|
|
1927
|
-
if (!rebooted.ok || !rebooted.serial) throw new Error(`it did not boot after its user data was wiped: ${rebooted.reason ?? "no serial"}`);
|
|
1928
|
-
if (room.resolveAvdSerial(room.avdName).serial !== rebooted.serial) throw new Error(`${rebooted.serial} is not running ${room.avdName} after the wipe`);
|
|
1929
|
-
room.phase("device", `${room.avdName} (${rebooted.serial}) booted with wiped user data`);
|
|
1930
|
-
return rebooted.serial;
|
|
1931
|
-
}
|
|
1932
|
-
async function installOnAdoptedAvd(room, serial, installApk) {
|
|
1933
|
-
const neededBytes = 2 * statSync(room.apkPath).size + ADOPTED_INSTALL_HEADROOM_BYTES;
|
|
1934
|
-
const roomy = (free) => free === null || free >= neededBytes;
|
|
1935
|
-
const outOfSpace = (result) => Boolean(result.failed) && INSUFFICIENT_STORAGE.test(result.reason ?? "");
|
|
1936
|
-
let target = serial;
|
|
1937
|
-
let wiped = false;
|
|
1938
|
-
if (!roomy(room.dataFreeBytes(target))) {
|
|
1939
|
-
room.trimCaches(target, neededBytes);
|
|
1940
|
-
const free = room.dataFreeBytes(target);
|
|
1941
|
-
if (roomy(free)) room.phase("install", `trimmed app caches on ${room.avdName} to make room for the APK`);
|
|
1942
|
-
else {
|
|
1943
|
-
target = await wipeAdoptedAvd(room, free);
|
|
1944
|
-
wiped = true;
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
let installed = installApk(target);
|
|
1948
|
-
if (wiped || !outOfSpace(installed)) return {
|
|
1949
|
-
serial: target,
|
|
1950
|
-
installed
|
|
1951
|
-
};
|
|
1952
|
-
room.trimCaches(target, neededBytes);
|
|
1953
|
-
installed = installApk(target);
|
|
1954
|
-
if (!outOfSpace(installed)) return {
|
|
1955
|
-
serial: target,
|
|
1956
|
-
installed
|
|
1957
|
-
};
|
|
1958
|
-
target = await wipeAdoptedAvd(room, room.dataFreeBytes(target));
|
|
1959
|
-
return {
|
|
1960
|
-
serial: target,
|
|
1961
|
-
installed: installApk(target)
|
|
1962
|
-
};
|
|
1963
|
-
}
|
|
1964
|
-
async function resolveInstallSerial({ root, slot, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
|
|
1965
|
-
if (physical || remoteDevice || !device.owned || !device.avdName) return serial;
|
|
1966
|
-
const resolved = resolveAvdSerial(device.avdName, { timeoutMs: 5e3 });
|
|
1967
|
-
if (!resolved.serial) throw new Error(`Could not verify a running owned AVD ${device.avdName} before installation.`);
|
|
1968
|
-
if (resolved.serial !== serial) {
|
|
1969
|
-
phase("device", `${device.avdName} changed serial (${serial} -> ${resolved.serial}); checking readiness before installation`);
|
|
1970
|
-
const ready = await waitForDeviceBoot(resolved.serial, 6e4, { commandTimeoutMs: 5e3 });
|
|
1971
|
-
if (!ready.ok) throw new Error(`Emulator ${resolved.serial} never reported boot completion. Diagnostic: ${JSON.stringify(ready.diagnostic)}`);
|
|
1972
|
-
if (resolveAvdSerial(device.avdName, { timeoutMs: 5e3 }).serial !== resolved.serial) throw new Error(`Could not verify AVD ${device.avdName} still runs on ${resolved.serial} after checking readiness.`);
|
|
1973
|
-
phase("device", `reopen agent-device on ${resolved.serial}`);
|
|
1974
|
-
}
|
|
1975
|
-
const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
|
|
1976
|
-
withConfigLock$1(() => {
|
|
1977
|
-
const current = deviceSlotPlatforms(loadConfig$1()?.projects?.[root], slot)?.android;
|
|
1978
|
-
if (!current?.owned || current.avdName !== device.avdName) throw new Error("The owned emulator assignment changed before installation.");
|
|
1979
|
-
if (current.consolePort !== consolePort) setDevice(root, PLATFORM, {
|
|
1980
|
-
...current,
|
|
1981
|
-
consolePort
|
|
1982
|
-
}, slot);
|
|
1983
|
-
});
|
|
1984
|
-
return resolved.serial;
|
|
1985
|
-
}
|
|
1986
|
-
async function finishAndroidRun({ runtime, runtimePreparation, lease, releaseLease, root, slot, json, metroCheck, useBuildCache, variant, release, isExpo, metroPort, logsDir, emuLog, device, physical, remoteDevice, bootPromise, resolveAvdSerial, waitForDeviceBoot, bootDuration, apkPath, androidPackage: initialPackage, swapDir, record, waitedForBuild, ccache, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, started, startedAt, writer, phase, fail, readApkPackage, install, launch, launchRelease, resolveDevClientScheme, verifyLaunched, verifyReleaseLaunched, spawn, kill, pidAlive, verifyCollector, writeLaunch, recordBuild, now, out, emit, recordRun, reclaimed, devServer, enterPhase, rebootDevice, dataFreeBytes = androidDataFreeBytes, trimCaches = trimAndroidCaches, wipeDevice = teardownOwnedAvd }) {
|
|
1987
|
-
let androidPackage = initialPackage;
|
|
1988
|
-
let leaseWarned = false;
|
|
1989
|
-
const raiseLeaseFor = (boundMs, beforeInstall) => {
|
|
1990
|
-
const step = lease?.raise(boundMs);
|
|
1991
|
-
if (!step || step.ok) return null;
|
|
1992
|
-
if (beforeInstall) {
|
|
1993
|
-
const refusal = lostRefusal(step.holder, step.expiresAt, now());
|
|
1994
|
-
return fail(refusal.code, refusal.message, refusal.remedy, { lease: refusal.lease });
|
|
1995
|
-
}
|
|
1996
|
-
if (!leaseWarned) {
|
|
1997
|
-
leaseWarned = true;
|
|
1998
|
-
phase("lease", chalk.yellow(lostLine(step.holder, step.expiresAt, now())));
|
|
1999
|
-
}
|
|
2000
|
-
return null;
|
|
2001
|
-
};
|
|
2002
|
-
enterPhase("device");
|
|
2003
|
-
const booted = await bootPromise;
|
|
2004
|
-
const runCommand = nativeRunCommand("android", slot, {
|
|
2005
|
-
physical,
|
|
2006
|
-
deviceId: booted.serial
|
|
2007
|
-
});
|
|
2008
|
-
if (booted.failed) {
|
|
2009
|
-
const diag = diagnoseBootFailure(booted, emuLog, runCommand, physical);
|
|
2010
|
-
return fail(diag.code, diag.message, diag.remedy, {
|
|
2011
|
-
lines: diag.lines,
|
|
2012
|
-
logPath: diag.logPath ? displayPath(root, diag.logPath) : null
|
|
2013
|
-
});
|
|
2014
|
-
}
|
|
2015
|
-
let serial;
|
|
2016
|
-
try {
|
|
2017
|
-
serial = await resolveInstallSerial({
|
|
2018
|
-
root,
|
|
2019
|
-
slot,
|
|
2020
|
-
device,
|
|
2021
|
-
physical,
|
|
2022
|
-
remoteDevice,
|
|
2023
|
-
serial: booted.serial,
|
|
2024
|
-
resolveAvdSerial,
|
|
2025
|
-
waitForDeviceBoot,
|
|
2026
|
-
phase
|
|
2027
|
-
});
|
|
2028
|
-
} catch (error) {
|
|
2029
|
-
return fail(NO_DEVICE, error instanceof Error ? error.message : String(error), `Run \`stim status\` to inspect the owned AVD, then retry \`${runCommand}\`; the APK was not installed.`);
|
|
2030
|
-
}
|
|
2031
|
-
phase("device", physical ? `${device.deviceName || serial} (${serial}) connected, not owned by Stim` : `${device.avdName || serial} (${serial}) ${device.adopted || device.adoptionPending ? "adopted" : "booted"} ${bootDuration()}`);
|
|
2032
|
-
const packageFromApk = readApkPackage(apkPath);
|
|
2033
|
-
if (packageFromApk && androidPackage && packageFromApk !== androidPackage) phase("install", chalk.dim(`applicationId ${packageFromApk} (from the APK; project files say ${androidPackage})`));
|
|
2034
|
-
androidPackage = packageFromApk || androidPackage || detectAndroidPackage(root);
|
|
2035
|
-
const adopting = !physical && !remoteDevice && Boolean(device.adoptionPending);
|
|
2036
|
-
if (adopting) {
|
|
2037
|
-
if (!androidPackage || !device.avdName) return fail(LAUNCH_FAILED, "Cannot clean an adopted emulator without its app package and AVD name.", "Retry once the APK applicationId can be read.");
|
|
2038
|
-
try {
|
|
2039
|
-
await resetAdoptedAvd(device.avdName, serial, androidPackage);
|
|
2040
|
-
} catch (error) {
|
|
2041
|
-
return fail(LAUNCH_FAILED, `Could not clean adopted emulator ${device.avdName}: ${String(error?.message || error)}`, "Fix the device cleanup error and retry; adoption remains pending and the app was not launched.");
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
const lostBeforeInstall = physical ? raiseLeaseFor(ADB_INSTALL_TIMEOUT_MS, true) : null;
|
|
2045
|
-
if (lostBeforeInstall) return lostBeforeInstall;
|
|
2046
|
-
enterPhase("install");
|
|
2047
|
-
let installTimer = stepTimer(now);
|
|
2048
|
-
const installApk = (target) => {
|
|
2049
|
-
installTimer = stepTimer(now);
|
|
2050
|
-
return install({
|
|
2051
|
-
serial: target,
|
|
2052
|
-
apkPath,
|
|
2053
|
-
packageName: androidPackage,
|
|
2054
|
-
allowUninstall: release || adopting
|
|
2055
|
-
});
|
|
2056
|
-
};
|
|
2057
|
-
let installed;
|
|
2058
|
-
if (adopting && device.avdName && apkPath) {
|
|
2059
|
-
const room = {
|
|
2060
|
-
root,
|
|
2061
|
-
slot,
|
|
2062
|
-
avdName: device.avdName,
|
|
2063
|
-
apkPath,
|
|
2064
|
-
phase,
|
|
2065
|
-
rebootDevice,
|
|
2066
|
-
dataFreeBytes,
|
|
2067
|
-
trimCaches,
|
|
2068
|
-
wipeDevice,
|
|
2069
|
-
resolveAvdSerial
|
|
2070
|
-
};
|
|
2071
|
-
try {
|
|
2072
|
-
({serial, installed} = await installOnAdoptedAvd(room, serial, installApk));
|
|
2073
|
-
} catch (error) {
|
|
2074
|
-
return fail(INSTALL_FAILED, `Could not make room on adopted emulator ${device.avdName}: ${String(error?.message || error)}`, `Run \`${runCommand}\` again; adoption remains pending, so it retries the cleanup.`);
|
|
2075
|
-
}
|
|
2076
|
-
} else installed = installApk(serial);
|
|
2077
|
-
if (installed.failed) {
|
|
2078
|
-
const installRemedy = remoteDevice?.failureRemedy() ?? localInstallRemedy({
|
|
2079
|
-
reason: installed.reason,
|
|
2080
|
-
packageName: androidPackage,
|
|
2081
|
-
serial,
|
|
2082
|
-
useBuildCache
|
|
2083
|
-
});
|
|
2084
|
-
return fail(installed.code || "STIM_INSTALL_FAILED", installed.reason, installRemedy, { lastBuildStatus: true });
|
|
2085
|
-
}
|
|
2086
|
-
if (adopting) try {
|
|
2087
|
-
withConfigLock$1(() => {
|
|
2088
|
-
const config = loadConfig$1();
|
|
2089
|
-
const current = deviceSlotPlatforms(config?.projects?.[root], slot)?.android;
|
|
2090
|
-
if (!config || !current || current.avdName !== device.avdName) throw new Error("The adopted emulator assignment changed during installation.");
|
|
2091
|
-
delete current.adoptionPending;
|
|
2092
|
-
saveConfig(config);
|
|
2093
|
-
});
|
|
2094
|
-
} catch (error) {
|
|
2095
|
-
return fail(LAUNCH_FAILED, `Could not finish adopting emulator ${device.avdName}: ${String(error?.message || error)}`, "Retry to reconcile the emulator assignment; the app was not launched.");
|
|
2096
|
-
}
|
|
2097
|
-
const installSkipped = Boolean(installed.skipped);
|
|
2098
|
-
phase("install", installSkipped ? `unchanged (${serial} already has this build) ${installTimer()}` : `${basename(apkPath)} -> ${serial} ${installTimer()}`);
|
|
2099
|
-
if (installed.note) {
|
|
2100
|
-
phase("install", chalk.yellow(installed.note));
|
|
2101
|
-
writer.write({
|
|
2102
|
-
src: "build",
|
|
2103
|
-
level: "warn",
|
|
2104
|
-
event: "install_uninstalled_first",
|
|
2105
|
-
msg: installed.note
|
|
2106
|
-
});
|
|
2107
|
-
}
|
|
2108
|
-
if (swapDir) try {
|
|
2109
|
-
rmSync(swapDir, {
|
|
2110
|
-
recursive: true,
|
|
2111
|
-
force: true
|
|
2112
|
-
});
|
|
2113
|
-
} catch {}
|
|
2114
|
-
if (androidPackage) upsertProject(root, { androidPackage });
|
|
2115
|
-
record.bundleId = androidPackage;
|
|
2116
|
-
if (!androidPackage) return fail(LAUNCH_FAILED, "Could not determine this app's Android package name, so there is nothing to launch.", "Set `expo.android.package` in app.json / app.config.js, or `namespace` in android/app/build.gradle.", { lastBuildStatus: true });
|
|
2117
|
-
if (physical) raiseLeaseFor(0, false);
|
|
2118
|
-
const scheme = runtime.scheme(root, apkPath, resolveDevClientScheme);
|
|
2119
|
-
enterPhase("launch");
|
|
2120
|
-
const launchTimer = stepTimer(now);
|
|
2121
|
-
const launchedAt = now();
|
|
2122
|
-
writer.write({
|
|
2123
|
-
src: "build",
|
|
2124
|
-
level: "info",
|
|
2125
|
-
marker: true,
|
|
2126
|
-
event: "launch_attempt",
|
|
2127
|
-
ts: launchedAt,
|
|
2128
|
-
platform: "android",
|
|
2129
|
-
appId: androidPackage,
|
|
2130
|
-
deviceId: serial,
|
|
2131
|
-
remote: Boolean(remoteDevice),
|
|
2132
|
-
physical,
|
|
2133
|
-
msg: `launching ${androidPackage} on ${serial}`
|
|
2134
|
-
});
|
|
2135
|
-
const launched = await runtime.launch(runtimePreparation, {
|
|
2136
|
-
serial,
|
|
2137
|
-
androidPackage,
|
|
2138
|
-
scheme,
|
|
2139
|
-
physical,
|
|
2140
|
-
remoteDevice,
|
|
2141
|
-
launch,
|
|
2142
|
-
launchRelease
|
|
2143
|
-
});
|
|
2144
|
-
if (launched.failed) {
|
|
2145
|
-
printNativeCrashReport({
|
|
2146
|
-
root,
|
|
2147
|
-
slot,
|
|
2148
|
-
platform: "android",
|
|
2149
|
-
deviceId: serial,
|
|
2150
|
-
appId: androidPackage,
|
|
2151
|
-
since: launchedAt
|
|
2152
|
-
}, logsDir, (line) => phase("launch", chalk.red(line)), Boolean(remoteDevice));
|
|
2153
|
-
return fail(launched.code || "STIM_LAUNCH_FAILED", launched.reason, launchRemedy(remoteDevice, serial, androidPackage), { lastBuildStatus: true });
|
|
2154
|
-
}
|
|
2155
|
-
writer.write({
|
|
2156
|
-
src: "build",
|
|
2157
|
-
level: "info",
|
|
2158
|
-
event: "app_launched",
|
|
2159
|
-
msg: (release ? `launched ${androidPackage} on ${serial} (${variant}, embedded JS bundle, no Metro)` : `launched ${androidPackage} on ${serial} against Metro port ${metroPort}`) + restartedAppNote(launched.restartedPid, "; ")
|
|
2160
|
-
});
|
|
2161
|
-
phase("launch", `${androidPackage}${restartedAppNote(launched.restartedPid)} ${launchTimer()}`);
|
|
2162
|
-
if (!physical && !remoteDevice) try {
|
|
2163
|
-
writeLaunch(root, "android", {
|
|
2164
|
-
appId: androidPackage,
|
|
2165
|
-
deviceId: serial,
|
|
2166
|
-
metroPort,
|
|
2167
|
-
release,
|
|
2168
|
-
launchedAt: new Date(launchedAt).toISOString()
|
|
2169
|
-
});
|
|
2170
|
-
} catch (error) {
|
|
2171
|
-
out(phaseLine("state", chalk.yellow(`could not record Android launch: ${error?.message || error}`)));
|
|
2172
|
-
}
|
|
2173
|
-
if (!release) reportMetroRoute({
|
|
2174
|
-
launched,
|
|
2175
|
-
remote: Boolean(remoteDevice),
|
|
2176
|
-
metroPort,
|
|
2177
|
-
androidPackage,
|
|
2178
|
-
serial,
|
|
2179
|
-
phase,
|
|
2180
|
-
writer
|
|
2181
|
-
});
|
|
2182
|
-
if (launched.devClientNote) {
|
|
2183
|
-
phase("metro", chalk.yellow(launched.devClientNote));
|
|
2184
|
-
writer.write({
|
|
2185
|
-
src: "build",
|
|
2186
|
-
level: "warn",
|
|
2187
|
-
event: "dev_client_link_failed",
|
|
2188
|
-
msg: launched.devClientNote
|
|
2189
|
-
});
|
|
2190
|
-
}
|
|
2191
|
-
const uploadWasAbandoned = await finishAndroidUpload(uploadPending, remote, phase);
|
|
2192
|
-
const providerOutcome = providerUploadOutcome(providerUpload ? await providerUpload : null, providerName);
|
|
2193
|
-
if (providerOutcome) phase("cache", providerOutcome.warn ? chalk.yellow(providerOutcome.line) : providerOutcome.line);
|
|
2194
|
-
persistLastBuild({
|
|
2195
|
-
recordBuild,
|
|
2196
|
-
root,
|
|
2197
|
-
record,
|
|
2198
|
-
startedAt,
|
|
2199
|
-
durationMs: now() - started,
|
|
2200
|
-
status: "ok",
|
|
2201
|
-
out
|
|
2202
|
-
});
|
|
2203
|
-
const remoteRelease = Boolean(remoteDevice && release);
|
|
2204
|
-
if (remoteDevice) {
|
|
2205
|
-
killPreviousCollector(root, {
|
|
2206
|
-
slot,
|
|
2207
|
-
kill,
|
|
2208
|
-
isAlive: pidAlive,
|
|
2209
|
-
verify: verifyCollector,
|
|
2210
|
-
note: (line) => out(phaseLine("logs", line))
|
|
2211
|
-
});
|
|
2212
|
-
writer.write({
|
|
2213
|
-
src: "build",
|
|
2214
|
-
level: "info",
|
|
2215
|
-
event: "collector_skipped",
|
|
2216
|
-
msg: `remote session ${serial}: device logs come from agent-device/EAS, so no adb logcat collector is attached`
|
|
2217
|
-
});
|
|
2218
|
-
} else {
|
|
2219
|
-
if (physical) raiseLeaseFor(0, false);
|
|
2220
|
-
const collectorPid = await startCollector({
|
|
2221
|
-
root,
|
|
2222
|
-
slot,
|
|
2223
|
-
serial,
|
|
2224
|
-
packageName: androidPackage,
|
|
2225
|
-
spawn,
|
|
2226
|
-
kill,
|
|
2227
|
-
alive: pidAlive,
|
|
2228
|
-
verify: verifyCollector,
|
|
2229
|
-
out
|
|
2230
|
-
});
|
|
2231
|
-
phase("logs", `${displayPath(root, logsDir)}${collectorPid ? ` (collector pid ${collectorPid})` : ""}`);
|
|
2232
|
-
}
|
|
2233
|
-
if (physical) raiseLeaseFor(runtime.verificationWaitMs, false);
|
|
2234
|
-
const { state: launchState, warning: launchWarning, unattributed } = await runtime.verify(runtimePreparation, {
|
|
2235
|
-
root,
|
|
2236
|
-
slot,
|
|
2237
|
-
remoteDevice: Boolean(remoteDevice),
|
|
2238
|
-
verifyReleaseLaunched,
|
|
2239
|
-
verifyLaunched,
|
|
2240
|
-
serial,
|
|
2241
|
-
androidPackage,
|
|
2242
|
-
variant,
|
|
2243
|
-
metroCheck,
|
|
2244
|
-
logsDir,
|
|
2245
|
-
launchedAt,
|
|
2246
|
-
metroPort,
|
|
2247
|
-
isExpo,
|
|
2248
|
-
physical,
|
|
2249
|
-
device,
|
|
2250
|
-
scheme,
|
|
2251
|
-
component: launched.component ?? null,
|
|
2252
|
-
phase
|
|
2253
|
-
});
|
|
2254
|
-
if (launchState === "fatal") return fail(LAUNCH_FAILED, "The app failed its launch readiness check.", `Read the launch error above or run \`stim logs --errors\`. The full timeline is in ${logsDir}.`, {
|
|
2255
|
-
lastBuildStatus: true,
|
|
2256
|
-
logPath: displayPath(root, logsDir)
|
|
2257
|
-
});
|
|
2258
|
-
writer.write(launchOutcomeRecord({
|
|
2259
|
-
launchState,
|
|
2260
|
-
release,
|
|
2261
|
-
bundleId: androidPackage,
|
|
2262
|
-
configuration: variant,
|
|
2263
|
-
metroPort,
|
|
2264
|
-
unattributed
|
|
2265
|
-
}));
|
|
2266
|
-
const leaseFacts = lease?.facts() ?? null;
|
|
2267
|
-
releaseLease();
|
|
2268
|
-
const facts = reportAndroidResult({
|
|
2269
|
-
root,
|
|
2270
|
-
slot,
|
|
2271
|
-
json,
|
|
2272
|
-
useBuildCache,
|
|
2273
|
-
variant,
|
|
2274
|
-
release,
|
|
2275
|
-
metroCheck,
|
|
2276
|
-
metroPort,
|
|
2277
|
-
logsDir: remoteRelease ? null : logsDir,
|
|
2278
|
-
serial,
|
|
2279
|
-
apkPath,
|
|
2280
|
-
androidPackage,
|
|
2281
|
-
installSkipped,
|
|
2282
|
-
record,
|
|
2283
|
-
waitedForBuild,
|
|
2284
|
-
remote,
|
|
2285
|
-
providerName,
|
|
2286
|
-
launchState,
|
|
2287
|
-
launchWarning,
|
|
2288
|
-
launched,
|
|
2289
|
-
ccache,
|
|
2290
|
-
durationMs: now() - started,
|
|
2291
|
-
writer,
|
|
2292
|
-
emit,
|
|
2293
|
-
lease: physical ? leaseFacts : void 0,
|
|
2294
|
-
recordRun,
|
|
2295
|
-
reclaimed,
|
|
2296
|
-
devServer,
|
|
2297
|
-
links: workspaceLinks(root, {
|
|
2298
|
-
platform: "android",
|
|
2299
|
-
slot
|
|
2300
|
-
})
|
|
2301
|
-
});
|
|
2302
|
-
if (remoteWasAbandoned || uploadWasAbandoned) exitAfterFlush(0);
|
|
2303
|
-
return {
|
|
2304
|
-
ok: true,
|
|
2305
|
-
facts
|
|
2306
|
-
};
|
|
2307
|
-
}
|
|
2308
|
-
function localInstallRemedy({ reason, packageName, serial, useBuildCache }) {
|
|
2309
|
-
const conflict = installConflictKind(reason);
|
|
2310
|
-
if (!conflict) return `Check that ${serial} is still connected (\`adb devices\`) and has room for the APK.`;
|
|
2311
|
-
const rerun = useBuildCache ? " Then run this command again; it installs the APK from cache without building it again." : " Then run this command again.";
|
|
2312
|
-
return `${packageName} is already installed on ${serial} ` + (conflict === "signature" ? "with a different signer" : "at a higher versionCode") + `. Uninstall it first (\`adb -s ${serial} uninstall ${packageName}\`); its data goes with it.` + rerun;
|
|
2313
|
-
}
|
|
2314
|
-
function launchRemedy(remoteDevice, serial, packageName) {
|
|
2315
|
-
return remoteDevice?.failureRemedy() ?? `Check the app installed correctly (\`adb -s ${serial} shell pm list packages ${packageName}\`).`;
|
|
2316
|
-
}
|
|
2317
|
-
function reportMetroRoute({ launched, remote, metroPort, androidPackage, serial, phase, writer }) {
|
|
2318
|
-
if (remote) {
|
|
2319
|
-
phase("metro", `public origin ${launched.jsLocation}`);
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
|
-
const reversedSummary = (launched.reversed ?? []).join(", ") || `tcp:${metroPort}->tcp:${metroPort}`;
|
|
2323
|
-
if (launched.debugHttpHost) {
|
|
2324
|
-
phase("metro", `debug_http_host ${launched.debugHttpHost} + adb reverse ${reversedSummary}`);
|
|
2325
|
-
return;
|
|
2326
|
-
}
|
|
2327
|
-
phase("metro", chalk.yellow(`adb reverse ${reversedSummary}; ${launched.debugHttpHostNote || "debug_http_host not written"}`));
|
|
2328
|
-
writer.write({
|
|
2329
|
-
src: "build",
|
|
2330
|
-
level: "warn",
|
|
2331
|
-
event: "debug_http_host_failed",
|
|
2332
|
-
msg: `debug_http_host was not written for ${androidPackage} on ${serial}: ${launched.debugHttpHostNote || "unknown reason"}`
|
|
2333
|
-
});
|
|
2334
|
-
}
|
|
2335
|
-
function diagnoseBootFailure(booted, logFile, runCommand, physical) {
|
|
2336
|
-
if (booted.code) return {
|
|
2337
|
-
code: booted.code,
|
|
2338
|
-
message: booted.reason ?? "The emulator did not boot.",
|
|
2339
|
-
remedy: booted.remedy ?? null,
|
|
2340
|
-
lines: [],
|
|
2341
|
-
logPath: null
|
|
2342
|
-
};
|
|
2343
|
-
const diag = noDeviceDiagnostic({
|
|
2344
|
-
reason: booted.reason ?? "The emulator did not boot.",
|
|
2345
|
-
logFile,
|
|
2346
|
-
remedy: booted.remedy ?? `Run \`stim status\` to see what Stim thinks it owns; re-running \`${runCommand}\` creates a fresh owned AVD.`,
|
|
2347
|
-
localEmulator: !physical
|
|
2348
|
-
});
|
|
2349
|
-
return {
|
|
2350
|
-
code: NO_DEVICE,
|
|
2351
|
-
...diag
|
|
2352
|
-
};
|
|
2353
|
-
}
|
|
2354
|
-
//#endregion
|
|
2355
|
-
//#region src/commands/android/plan.ts
|
|
2356
|
-
function slotOwnedAvd(projectPath, slot) {
|
|
2357
|
-
const android = deviceSlotPlatforms(getProject(projectPath), slot)?.android;
|
|
2358
|
-
if (!android?.owned || !android.avdName || android.setupIncomplete) return null;
|
|
2359
|
-
const systemImage = ownedAvdSystemImage(android.avdName);
|
|
2360
|
-
return systemImage ? {
|
|
2361
|
-
avdName: android.avdName,
|
|
2362
|
-
systemImage,
|
|
2363
|
-
deviceProfile: ownedAvdDeviceProfile(android.avdName)
|
|
2364
|
-
} : null;
|
|
2365
|
-
}
|
|
2366
|
-
function fail(code, message, remedy, { lines = [] } = {}) {
|
|
2367
|
-
return {
|
|
2368
|
-
ok: false,
|
|
2369
|
-
code,
|
|
2370
|
-
message,
|
|
2371
|
-
remedy,
|
|
2372
|
-
lines
|
|
2373
|
-
};
|
|
2374
|
-
}
|
|
2375
|
-
function androidCompilerCache({ root, optimizations, settingsContext }) {
|
|
2376
|
-
const { cas, optimizations: resolved } = resolveAndroidCompilerCache({
|
|
2377
|
-
optimizations,
|
|
2378
|
-
use: (manifest) => resolveAndroidCas(root, {
|
|
2379
|
-
...process.env,
|
|
2380
|
-
STIM_ANDROID_CAS_TOOLCHAIN: manifest
|
|
2381
|
-
})
|
|
2382
|
-
});
|
|
2383
|
-
const fallback = resolved.android.compilerCacheFallback;
|
|
2384
|
-
if (!fallback) return {
|
|
2385
|
-
cas,
|
|
2386
|
-
optimizations: resolved,
|
|
2387
|
-
warning: null
|
|
2388
|
-
};
|
|
2389
|
-
return {
|
|
2390
|
-
cas,
|
|
2391
|
-
optimizations: resolved,
|
|
2392
|
-
warning: `Warning: ${compilerCacheFallbackMessage({
|
|
2393
|
-
fallback,
|
|
2394
|
-
compilerCache: resolved.android.compilerCache === "none" ? "none" : "ccache",
|
|
2395
|
-
file: settingFile(settingsContext, fallback.key)
|
|
2396
|
-
})}`
|
|
2397
|
-
};
|
|
2398
|
-
}
|
|
2399
|
-
function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, variant: variantFlag, systemImage: systemImageFlag, deviceProfile: deviceProfileFlag, device: deviceFlag, wait: waitFlag, waitConflict, remote: commandRemoteBackend, buildCache: requestedBuildCache }, { warn, resolveCompilerCache = androidCompilerCache, resolveCacheProvider = resolveCacheProviderConfig, validateAvdConfig = androidAvdConfigSettingError, readFlavors = readProductFlavors, detectExpo = detectIsExpo, listSystemImages = listInstalledSystemImages, listDeviceProfiles = listAvdDeviceProfiles, parkedLimit = parkedMaxSetting, supportsFold = systemImageSupportsFold, ownedAvd = slotOwnedAvd }) {
|
|
2400
|
-
const root = settingsContext.projectPath;
|
|
2401
|
-
const [shapeError, ...moreShapeErrors] = [parkedLimit("android").error, ...settingShapeErrors(settings)].filter((error) => Boolean(error));
|
|
2402
|
-
if (shapeError) return fail("STIM_BAD_ARG", shapeError, SETTING_SHAPE_REMEDY, { lines: moreShapeErrors });
|
|
2403
|
-
for (const key of unknownSettingKeys(settings)) warn("setting", `Warning: setting "${key}" is not read by Stim and will be ignored.`);
|
|
2404
|
-
let optimizations;
|
|
2405
|
-
try {
|
|
2406
|
-
optimizations = resolveOptimizations(settings);
|
|
2407
|
-
} catch (error) {
|
|
2408
|
-
return fail("STIM_BAD_ARG", `Could not configure Android build: ${error.message}`, SETTING_SHAPE_REMEDY);
|
|
2409
|
-
}
|
|
2410
|
-
const compilerCache = resolveCompilerCache({
|
|
2411
|
-
root,
|
|
2412
|
-
optimizations,
|
|
2413
|
-
settingsContext
|
|
2414
|
-
});
|
|
2415
|
-
const cas = compilerCache.cas;
|
|
2416
|
-
optimizations = compilerCache.optimizations;
|
|
2417
|
-
if (compilerCache.warning) warn("cache", compilerCache.warning);
|
|
2418
|
-
const buildProfile = optimizationBuildProfile("android", optimizations);
|
|
2419
|
-
const cacheProviderConfig = resolveCacheProvider(settingsContext);
|
|
2420
|
-
const cacheProviderError = cacheProviderSettingError(settings);
|
|
2421
|
-
if (cacheProviderError) warn("cache", `${cacheProviderError} Using the local cache.`);
|
|
2422
|
-
const dataPartitionSizeError = androidDataPartitionSizeGbSettingError(settings);
|
|
2423
|
-
if (dataPartitionSizeError) return fail("STIM_BAD_ARG", dataPartitionSizeError, "Set android.dataPartitionSizeGb to a whole number of GiB from 6 through 16384.");
|
|
2424
|
-
const avdConfigError = validateAvdConfig(settings, root);
|
|
2425
|
-
if (avdConfigError) return fail("STIM_BAD_ARG", avdConfigError, "Use only documented android.avdConfig keys, or an android.avdConfigFile fragment contained by the app directory.");
|
|
2426
|
-
const systemImage = resolveSystemImage(systemImageFlag, settings);
|
|
2427
|
-
const deviceProfile = resolveDeviceProfile(deviceProfileFlag, settings);
|
|
2428
|
-
const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
|
|
2429
|
-
const flavorRefusal = productFlavorRefusal({
|
|
2430
|
-
flavors: readFlavors(root),
|
|
2431
|
-
variant
|
|
2432
|
-
});
|
|
2433
|
-
if (flavorRefusal) return fail(flavorRefusal.code, flavorRefusal.reason, flavorRefusal.remedy);
|
|
2434
|
-
const release = isReleaseVariant(variant);
|
|
2435
|
-
const cachePolicy = artifactCachePolicy(optimizations, requestedBuildCache, release);
|
|
2436
|
-
const isExpo = detectExpo(root);
|
|
2437
|
-
const physical = isPhysicalDeviceRequest(deviceFlag);
|
|
2438
|
-
if (physical && deviceFlag === "") return fail("STIM_BAD_ARG", "--device was given an empty serial.", "Pass `--device` on its own to take the first connected device this workspace can lease, or `--device <serial>` to name one.");
|
|
2439
|
-
if (physical && commandRemoteBackend) return fail("STIM_BAD_ARG", "--device installs on a device connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote.");
|
|
2440
|
-
const noWait = waitFlag === false;
|
|
2441
|
-
if (waitConflict) return fail("STIM_BAD_ARG", "--wait and --no-wait ask for opposite things.", "Pass only one of `--wait <seconds>` and `--no-wait`.");
|
|
2442
|
-
const waitParsed = parseDeviceWait(noWait ? void 0 : waitFlag);
|
|
2443
|
-
if ("error" in waitParsed) return fail("STIM_BAD_ARG", waitParsed.error, "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a busy lease or device slot at once.");
|
|
2444
|
-
const waitSeconds = waitParsed.seconds;
|
|
2445
|
-
const remoteTarget = commandRemoteBackend !== null ? parseIosRemote(commandRemoteBackend) : remoteAndroidSetting(settings);
|
|
2446
|
-
if (physical && remoteTarget?.kind === "machine") return fail("STIM_BAD_ARG", "--device installs on a phone connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote; unset android.remote for a local phone.");
|
|
2447
|
-
const machine = remoteTarget?.kind === "machine" ? remoteTarget.machine : null;
|
|
2448
|
-
const remoteBackend = !physical && remoteTarget?.kind === "backend" ? remoteTarget.backend : null;
|
|
2449
|
-
if (!machine) {
|
|
2450
|
-
const refusal = androidDeviceSelectorRefusal({
|
|
2451
|
-
settingsContext,
|
|
2452
|
-
slot,
|
|
2453
|
-
systemImageFlag,
|
|
2454
|
-
deviceProfileFlag,
|
|
2455
|
-
systemImage,
|
|
2456
|
-
deviceProfile,
|
|
2457
|
-
physical,
|
|
2458
|
-
remoteBackend
|
|
2459
|
-
}, {
|
|
2460
|
-
listSystemImages,
|
|
2461
|
-
listDeviceProfiles,
|
|
2462
|
-
supportsFold,
|
|
2463
|
-
ownedAvd
|
|
2464
|
-
});
|
|
2465
|
-
if (refusal) return refusal;
|
|
2466
|
-
}
|
|
2467
|
-
const target = physical ? {
|
|
2468
|
-
kind: "physical",
|
|
2469
|
-
serial: typeof deviceFlag === "string" ? deviceFlag : null,
|
|
2470
|
-
lease: {
|
|
2471
|
-
waitSeconds,
|
|
2472
|
-
noWait
|
|
2473
|
-
}
|
|
2474
|
-
} : machine ? {
|
|
2475
|
-
kind: "hosted",
|
|
2476
|
-
machine,
|
|
2477
|
-
systemImage,
|
|
2478
|
-
deviceProfile
|
|
2479
|
-
} : remoteBackend ? {
|
|
2480
|
-
kind: "remote",
|
|
2481
|
-
backend: remoteBackend,
|
|
2482
|
-
systemImage,
|
|
2483
|
-
deviceProfile
|
|
2484
|
-
} : {
|
|
2485
|
-
kind: "emulator",
|
|
2486
|
-
systemImage,
|
|
2487
|
-
deviceProfile
|
|
2488
|
-
};
|
|
2489
|
-
return {
|
|
2490
|
-
ok: true,
|
|
2491
|
-
plan: {
|
|
2492
|
-
build: {
|
|
2493
|
-
variant,
|
|
2494
|
-
release,
|
|
2495
|
-
profile: buildProfile,
|
|
2496
|
-
cas,
|
|
2497
|
-
cache: cachePolicy,
|
|
2498
|
-
compilerCache: optimizations.android.compilerCache,
|
|
2499
|
-
gradleBuildCache: optimizations.android.gradleBuildCache,
|
|
2500
|
-
pch: optimizations.android.pch,
|
|
2501
|
-
targetAbiOnly: optimizations.android.targetAbiOnly
|
|
2502
|
-
},
|
|
2503
|
-
target,
|
|
2504
|
-
deviceSlotWaitMs: noWait ? 0 : waitFlag === void 0 && !physical ? DEFAULT_DEVICE_SLOT_WAIT_MS : waitSeconds * 1e3,
|
|
2505
|
-
isExpo,
|
|
2506
|
-
metroWarmup: optimizations.metroWarmup,
|
|
2507
|
-
cacheProviderConfig
|
|
2508
|
-
}
|
|
2509
|
-
};
|
|
2510
|
-
}
|
|
2511
|
-
function androidDeviceSelectorRefusal({ settingsContext, slot, systemImageFlag, deviceProfileFlag, systemImage, deviceProfile, physical, remoteBackend }, { listSystemImages = listInstalledSystemImages, listDeviceProfiles = listAvdDeviceProfiles, supportsFold = systemImageSupportsFold, ownedAvd = slotOwnedAvd } = {}) {
|
|
2512
|
-
const root = settingsContext.projectPath;
|
|
2513
|
-
const settingsLayersForOrigin = settingsLayers(settingsContext);
|
|
2514
|
-
const imageRefusal = systemImageRefusal({
|
|
2515
|
-
slot,
|
|
2516
|
-
flag: systemImageFlag,
|
|
2517
|
-
resolved: systemImage,
|
|
2518
|
-
origin: settingOriginScope(settingsLayersForOrigin, "android.systemImage"),
|
|
2519
|
-
physical,
|
|
2520
|
-
remoteBackend,
|
|
2521
|
-
listImages: listSystemImages
|
|
2522
|
-
});
|
|
2523
|
-
if (imageRefusal) return fail(imageRefusal.code, imageRefusal.message, imageRefusal.remedy);
|
|
2524
|
-
const profileRefusal = deviceProfileRefusal({
|
|
2525
|
-
flag: deviceProfileFlag,
|
|
2526
|
-
resolved: deviceProfile,
|
|
2527
|
-
origin: settingOriginScope(settingsLayersForOrigin, "android.deviceProfile"),
|
|
2528
|
-
physical,
|
|
2529
|
-
remoteBackend,
|
|
2530
|
-
listProfiles: listDeviceProfiles
|
|
2531
|
-
});
|
|
2532
|
-
if (profileRefusal) return fail(profileRefusal.code, profileRefusal.message, profileRefusal.remedy);
|
|
2533
|
-
const avdFlagRefusal = remoteAvdFlagRefusal({
|
|
2534
|
-
systemImageFlag,
|
|
2535
|
-
deviceProfileFlag,
|
|
2536
|
-
remoteBackend
|
|
2537
|
-
});
|
|
2538
|
-
if (avdFlagRefusal) return fail(avdFlagRefusal.code, avdFlagRefusal.message, avdFlagRefusal.remedy);
|
|
2539
|
-
if (!physical && !remoteBackend) {
|
|
2540
|
-
const flagImage = typeof systemImageFlag === "string" && systemImageFlag.trim() ? systemImageFlag.trim() : null;
|
|
2541
|
-
const existing = ownedAvd(root, slot);
|
|
2542
|
-
const profile = deviceProfile ?? existing?.deviceProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
|
|
2543
|
-
const image = () => flagImage ?? existing?.systemImage ?? systemImage ?? pickDefaultSystemImage(listSystemImages())?.pkg ?? null;
|
|
2544
|
-
const checked = profileNeedsFoldFeature(profile) ? image() : null;
|
|
2545
|
-
const foldRefusal = checked !== null && foldableImageRefusal({
|
|
2546
|
-
profile,
|
|
2547
|
-
image: checked,
|
|
2548
|
-
avdName: existing && existing.systemImage === checked && existing.deviceProfile === profile ? existing.avdName : null,
|
|
2549
|
-
images: listSystemImages,
|
|
2550
|
-
supportsFold
|
|
2551
|
-
});
|
|
2552
|
-
if (foldRefusal) return fail(foldRefusal.code, foldRefusal.message, foldRefusal.remedy);
|
|
2553
|
-
}
|
|
2554
|
-
return null;
|
|
2555
|
-
}
|
|
2556
|
-
//#endregion
|
|
2557
|
-
//#region src/commands/android/next-build.ts
|
|
2558
|
-
const DEFAULT_PLAN_DEPS = {
|
|
2559
|
-
findRoot: findProjectRoot,
|
|
2560
|
-
fingerprint: fingerprintProject,
|
|
2561
|
-
listSystemImages: listInstalledSystemImages,
|
|
2562
|
-
avdSystemImage: ownedAvdSystemImage,
|
|
2563
|
-
avdDirectory: ownedAvdDirectory,
|
|
2564
|
-
listAvds,
|
|
2565
|
-
loadCacheProvider,
|
|
2566
|
-
loadProjectProvider,
|
|
2567
|
-
checkEasAuth,
|
|
2568
|
-
resolveRemote,
|
|
2569
|
-
planPrebuild
|
|
2570
|
-
};
|
|
2571
|
-
function note(line) {
|
|
2572
|
-
console.error(line);
|
|
2573
|
-
}
|
|
2574
|
-
function runOnlyFlag(opts) {
|
|
2575
|
-
if (isPhysicalDeviceRequest(opts.device)) return "--device";
|
|
2576
|
-
if (opts.remote) return "--remote";
|
|
2577
|
-
if (opts.wait !== void 0) return opts.wait === false ? "--no-wait" : "--wait";
|
|
2578
|
-
if (opts.metroCheck === false) return "--no-metro-check";
|
|
2579
|
-
return null;
|
|
2580
|
-
}
|
|
2581
|
-
function emulatorImage(root, slot, requested, deps) {
|
|
2582
|
-
const record = deviceSlotPlatforms(getProject(root), slot)?.android;
|
|
2583
|
-
if (record?.avdName && !record.setupIncomplete) {
|
|
2584
|
-
if (record.owned && deps.avdDirectory(record.avdName)) return { systemImage: deps.avdSystemImage(record.avdName) };
|
|
2585
|
-
if (!record.owned && avdListed(record.avdName, deps.listAvds)) return { systemImage: null };
|
|
2586
|
-
}
|
|
2587
|
-
let images;
|
|
2588
|
-
try {
|
|
2589
|
-
images = deps.listSystemImages();
|
|
2590
|
-
} catch (error) {
|
|
2591
|
-
return { refusal: {
|
|
2592
|
-
code: NO_DEVICE,
|
|
2593
|
-
message: `Could not read the installed Android system images: ${error?.message || error}`,
|
|
2594
|
-
remedy: "Check that ANDROID_HOME points at a readable SDK, then try again."
|
|
2595
|
-
} };
|
|
2596
|
-
}
|
|
2597
|
-
const picked = pickDefaultSystemImage(images, requested ? { systemImage: requested } : {});
|
|
2598
|
-
if (picked) return { systemImage: picked.pkg };
|
|
2599
|
-
const arch = hostSystemImageArch();
|
|
2600
|
-
return { refusal: {
|
|
2601
|
-
code: NO_DEVICE,
|
|
2602
|
-
message: `No ${arch} Android system image is installed, so the emulator build's ABI and cache key are unknown.`,
|
|
2603
|
-
remedy: `Install one, e.g.: sdkmanager "system-images;android-36;google_apis;${arch}", then try again.`
|
|
2604
|
-
} };
|
|
2605
|
-
}
|
|
2606
|
-
function avdListed(avdName, list) {
|
|
2607
|
-
try {
|
|
2608
|
-
return list({ timeoutMs: 1e4 }).includes(avdName);
|
|
2609
|
-
} catch {
|
|
2610
|
-
return false;
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
|
-
async function planAndroid(opts, overrides = {}) {
|
|
2614
|
-
const deps = {
|
|
2615
|
-
...DEFAULT_PLAN_DEPS,
|
|
2616
|
-
...overrides
|
|
2617
|
-
};
|
|
2618
|
-
const json = Boolean(opts.json);
|
|
2619
|
-
const refuse = (refusal, lines = []) => refusePlan(refusal, json, lines);
|
|
2620
|
-
const flag = runOnlyFlag(opts);
|
|
2621
|
-
if (flag) return refuse(planFlagRefusal(flag));
|
|
2622
|
-
const root = deps.findRoot(process.cwd());
|
|
2623
|
-
if (!root) return refuse(NO_PROJECT_REFUSAL);
|
|
2624
|
-
const problem = projectProblem(root, "android");
|
|
2625
|
-
if (problem) return refuse({
|
|
2626
|
-
code: "STIM_NO_PROJECT",
|
|
2627
|
-
...problem
|
|
2628
|
-
});
|
|
2629
|
-
const slot = validateDeviceSlot(opts.slot);
|
|
2630
|
-
const settingsContext = {
|
|
2631
|
-
projectPath: root,
|
|
2632
|
-
gitCommonDir: gitCommonDir(root),
|
|
2633
|
-
repoRoot: repoRoot(root)
|
|
2634
|
-
};
|
|
2635
|
-
const settings = resolveSettings(settingsContext);
|
|
2636
|
-
const planned = resolveAndroidRunPlan({
|
|
2637
|
-
settings,
|
|
2638
|
-
settingsContext,
|
|
2639
|
-
slot,
|
|
2640
|
-
easProfile: opts.easProfile,
|
|
2641
|
-
variant: opts.variant ?? null,
|
|
2642
|
-
systemImage: opts.systemImage ?? null,
|
|
2643
|
-
device: null,
|
|
2644
|
-
wait: void 0,
|
|
2645
|
-
waitConflict: false,
|
|
2646
|
-
remote: null,
|
|
2647
|
-
buildCache: opts.buildCache !== false
|
|
2648
|
-
}, {
|
|
2649
|
-
warn: (label, message) => note(phaseLine(label, chalk.yellow(message))),
|
|
2650
|
-
resolveCompilerCache: ({ optimizations }) => ({
|
|
2651
|
-
cas: null,
|
|
2652
|
-
optimizations,
|
|
2653
|
-
warning: null
|
|
2654
|
-
}),
|
|
2655
|
-
listSystemImages: deps.listSystemImages
|
|
2656
|
-
});
|
|
2657
|
-
if (!planned.ok) return refuse(planned, planned.lines);
|
|
2658
|
-
const { build, target, isExpo, cacheProviderConfig } = planned.plan;
|
|
2659
|
-
const planTarget = {
|
|
2660
|
-
platform: PLATFORM,
|
|
2661
|
-
slot,
|
|
2662
|
-
root,
|
|
2663
|
-
projectKey: statsProjectKey$1({
|
|
2664
|
-
root,
|
|
2665
|
-
commonDir: settingsContext.gitCommonDir,
|
|
2666
|
-
repoRoot: settingsContext.repoRoot
|
|
2667
|
-
})
|
|
2668
|
-
};
|
|
2669
|
-
if (opts.easProfile !== void 0) {
|
|
2670
|
-
const eas = await planEasDevelopmentBuild({
|
|
2671
|
-
root,
|
|
2672
|
-
platform: PLATFORM,
|
|
2673
|
-
profile: opts.easProfile,
|
|
2674
|
-
note,
|
|
2675
|
-
isExpo,
|
|
2676
|
-
selectors: [opts.variant],
|
|
2677
|
-
buildCache: opts.buildCache
|
|
2678
|
-
});
|
|
2679
|
-
if (!eas.ok) return refuse(eas);
|
|
2680
|
-
return printPlan(planPayload(planTarget, {
|
|
2681
|
-
fingerprint: eas.fingerprint,
|
|
2682
|
-
cacheKey: eas.cacheKey,
|
|
2683
|
-
cacheHit: eas.buildId ? "remote" : false,
|
|
2684
|
-
provider: "eas",
|
|
2685
|
-
cacheSkipped: false,
|
|
2686
|
-
prebuild: null,
|
|
2687
|
-
refusal: eas.missing
|
|
2688
|
-
}), json);
|
|
2689
|
-
}
|
|
2690
|
-
if (target.kind === "physical") return refuse(planFlagRefusal("--device"));
|
|
2691
|
-
if (target.kind === "remote") return refuse({
|
|
2692
|
-
code: "STIM_BAD_ARG",
|
|
2693
|
-
message: `android.remote is ${target.backend}, whose ABI --plan cannot read without a session.`,
|
|
2694
|
-
remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
|
|
2695
|
-
});
|
|
2696
|
-
if (build.compilerCache === "cas") return refuse({
|
|
2697
|
-
code: "STIM_BAD_ARG",
|
|
2698
|
-
message: "The experimental Android compiler CAS keys builds by a toolchain Stim sets up, which --plan does not.",
|
|
2699
|
-
remedy: "Run `stim android` to build with the CAS, or set optimizations.android.compilerCache to ccache."
|
|
2700
|
-
});
|
|
2701
|
-
const abiKeyed = build.targetAbiOnly && !build.release;
|
|
2702
|
-
const localImage = () => abiKeyed ? emulatorImage(root, slot, target.systemImage, deps) : { systemImage: null };
|
|
2703
|
-
let image;
|
|
2704
|
-
let hostedAbi = null;
|
|
2705
|
-
let placement;
|
|
2706
|
-
if (target.kind === "hosted") {
|
|
2707
|
-
const hostedPlan = await planHostedDevice({
|
|
2708
|
-
root,
|
|
2709
|
-
slot,
|
|
2710
|
-
platform: "android",
|
|
2711
|
-
machine: target.machine,
|
|
2712
|
-
selectors: {
|
|
2713
|
-
...target.systemImage ? { systemImage: target.systemImage } : {},
|
|
2714
|
-
...target.deviceProfile ? { deviceProfile: target.deviceProfile } : {}
|
|
2715
|
-
},
|
|
2716
|
-
sameKey: (choice) => {
|
|
2717
|
-
if (build.targetAbiOnly && build.release) return !("architecture" in choice && choice.architecture);
|
|
2718
|
-
if (!abiKeyed) return true;
|
|
2719
|
-
const local = localImage();
|
|
2720
|
-
if ("refusal" in local || !("systemImage" in choice)) return null;
|
|
2721
|
-
return androidSystemImageAbi(local.systemImage) === androidSystemImageAbi(choice.systemImage);
|
|
2722
|
-
},
|
|
2723
|
-
...remoteEasFallbackSetting(settings) ? { eas: () => checkEasFallback({
|
|
2724
|
-
root,
|
|
2725
|
-
platform: "android",
|
|
2726
|
-
slot,
|
|
2727
|
-
release: build.release,
|
|
2728
|
-
isExpo,
|
|
2729
|
-
tunnelMode: tunnelModeSetting(settings),
|
|
2730
|
-
publicUrl: publicUrlSetting(settings),
|
|
2731
|
-
localOnlyFlags: typeof opts.systemImage === "string" ? ["--system-image"] : []
|
|
2732
|
-
}) } : {}
|
|
2733
|
-
});
|
|
2734
|
-
if (hostedPlan.kind === "unknown") return refuse({
|
|
2735
|
-
code: "STIM_BAD_ARG",
|
|
2736
|
-
message: `android.remote is ${target.machine}, and its emulator ABI is unknown here: ${hostedPlan.reason}.`,
|
|
2737
|
-
remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
|
|
2738
|
-
});
|
|
2739
|
-
placement = hostedPlan.placement;
|
|
2740
|
-
if (hostedPlan.kind === "hosted" && "architecture" in hostedPlan.choice) hostedAbi = hostedPlan.choice.architecture ?? null;
|
|
2741
|
-
image = hostedPlan.kind === "hosted" ? { systemImage: abiKeyed && "systemImage" in hostedPlan.choice ? hostedPlan.choice.systemImage : null } : localImage();
|
|
2742
|
-
} else image = localImage();
|
|
2743
|
-
if ("refusal" in image) return refuse(image.refusal);
|
|
2744
|
-
const { abi, runOptions, remoteRunOptions } = androidBuildOptions({
|
|
2745
|
-
release: build.release,
|
|
2746
|
-
physical: false,
|
|
2747
|
-
device: { systemImage: image.systemImage },
|
|
2748
|
-
variant: build.variant,
|
|
2749
|
-
deviceAbi: () => null,
|
|
2750
|
-
buildProfile: build.profile,
|
|
2751
|
-
targetAbiOnly: build.targetAbiOnly,
|
|
2752
|
-
hostedAbi
|
|
2753
|
-
});
|
|
2754
|
-
let fingerprint;
|
|
2755
|
-
try {
|
|
2756
|
-
fingerprint = await deps.fingerprint(root, { platform: PLATFORM });
|
|
2757
|
-
} catch (error) {
|
|
2758
|
-
return refuse({
|
|
2759
|
-
code: NO_FINGERPRINT,
|
|
2760
|
-
message: `@expo/fingerprint could not fingerprint ${root}: ${error?.message || error}`,
|
|
2761
|
-
remedy: "Fix the @expo/fingerprint error above, then retry."
|
|
2762
|
-
});
|
|
2763
|
-
}
|
|
2764
|
-
if (!fingerprint?.hash) return refuse({
|
|
2765
|
-
code: NO_FINGERPRINT,
|
|
2766
|
-
message: `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`,
|
|
2767
|
-
remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
|
|
2768
|
-
});
|
|
2769
|
-
const plan = await planCachedBuild({
|
|
2770
|
-
...planTarget,
|
|
2771
|
-
isExpo,
|
|
2772
|
-
fingerprint: fingerprint.hash,
|
|
2773
|
-
sources: fingerprint.sources ?? [],
|
|
2774
|
-
cacheKey: buildCacheKey(PLATFORM, fingerprint.hash, runOptions),
|
|
2775
|
-
cachePolicy: build.cache,
|
|
2776
|
-
providerConfig: cacheProviderConfig,
|
|
2777
|
-
expoRemote: abi || build.profile || !build.cache.remote ? null : { runOptions: remoteRunOptions }
|
|
2778
|
-
}, {
|
|
2779
|
-
...deps,
|
|
2780
|
-
note
|
|
2781
|
-
});
|
|
2782
|
-
printPlan(placement ? {
|
|
2783
|
-
...plan,
|
|
2784
|
-
placement
|
|
2785
|
-
} : plan, json);
|
|
2786
|
-
}
|
|
2787
|
-
//#endregion
|
|
2788
|
-
//#region src/commands/android.ts
|
|
2789
|
-
function androidCommand(program) {
|
|
2790
|
-
registerAndroid(program);
|
|
2791
|
-
}
|
|
2792
|
-
function registerAndroid(program) {
|
|
2793
|
-
program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator, wired to the reserved Metro port. A Debug run starts the dev server when it is not running.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", parseDeviceSlotOption).option("--remote-build <value>", "Build on auto, local, or one named machine; a name refuses without fallback", parseBuildMachineOption).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--plan", "Predict the next build without building, booting or installing: resolve the fingerprint, check the local then remote cache, and print the expected cache result and duration. Writes no Stim state.").option("--no-metro-check", "Skip the reserved-port Metro health check and do not start it (the app will load no bundle unless something else serves it)").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device-profile <id>", "Hardware profile to create this workspace's owned AVD with, as an `avdmanager list device -c` id (e.g. \"pixel_fold\", \"pixel_tablet\"). Overrides the android.deviceProfile setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the available ids.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <target>", "Run on eas, proxy, or a named approved Mac from remote.machines; auto places on an approved Mac when this Mac is full or busy; local runs here, whatever android.remote says", (value) => {
|
|
2794
|
-
if (!parseMachine(value)) throw new InvalidArgumentError("expected eas, proxy, auto, local, or a tailnet machine name");
|
|
2795
|
-
return value;
|
|
2796
|
-
}).option("--wait <seconds>", "How long to wait for another workspace to release the device it leases, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once). Without --device, wait for an owned-device slot (default 600 seconds; --no-wait refuses immediately).").option("--no-wait", "Install on a device another workspace leases instead of waiting: this run takes no lease and, when both workspaces build the same app id, the install terminates the holder's running app. Without --device, refuse a full device cap or existing slot queue immediately.").action(async (opts) => {
|
|
2797
|
-
if (opts.plan) return planAndroid(opts);
|
|
2798
|
-
const root = findProjectRoot(process.cwd());
|
|
2799
|
-
if (!root) {
|
|
2800
|
-
refuseNoProject({ json: Boolean(opts.json) });
|
|
2801
|
-
return;
|
|
2802
|
-
}
|
|
2803
|
-
if (!(await runAndroidOperation(root, {
|
|
2804
|
-
...opts,
|
|
2805
|
-
waitConflict: waitFlagConflict(process.argv)
|
|
2806
|
-
})).ok) process.exit(runCancellation() ? 130 : 1);
|
|
2807
|
-
});
|
|
2808
|
-
}
|
|
2809
|
-
function runAndroidOperation(root, opts = {}) {
|
|
2810
|
-
return withNativeBuildRun(root, {
|
|
2811
|
-
command: "android",
|
|
2812
|
-
platform: PLATFORM,
|
|
2813
|
-
slot: opts.slot ?? "default"
|
|
2814
|
-
}, async (claim) => {
|
|
2815
|
-
recordWorkspaceUse(root);
|
|
2816
|
-
const progress = startBuildProgress({
|
|
2817
|
-
root,
|
|
2818
|
-
platform: PLATFORM,
|
|
2819
|
-
slot: opts.slot ?? "default",
|
|
2820
|
-
claim,
|
|
2821
|
-
note: (line) => console.error(chalk.dim(line))
|
|
2822
|
-
});
|
|
2823
|
-
try {
|
|
2824
|
-
return await runAndroid({
|
|
2825
|
-
root,
|
|
2826
|
-
buildMachine: opts.remoteBuild,
|
|
2827
|
-
slot: opts.slot,
|
|
2828
|
-
easProfile: opts.easProfile,
|
|
2829
|
-
json: Boolean(opts.json),
|
|
2830
|
-
metroCheck: opts.metroCheck !== false,
|
|
2831
|
-
useBuildCache: opts.buildCache !== false,
|
|
2832
|
-
variant: opts.variant ?? null,
|
|
2833
|
-
systemImage: opts.systemImage ?? null,
|
|
2834
|
-
deviceProfile: opts.deviceProfile ?? null,
|
|
2835
|
-
remoteDevice: opts.remote ?? null,
|
|
2836
|
-
device: opts.device ?? null,
|
|
2837
|
-
wait: opts.wait,
|
|
2838
|
-
waitConflict: opts.waitConflict,
|
|
2839
|
-
progress
|
|
2840
|
-
});
|
|
2841
|
-
} finally {
|
|
2842
|
-
progress.clear();
|
|
2843
|
-
}
|
|
2844
|
-
}, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) });
|
|
2845
|
-
}
|
|
2846
|
-
function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRemoteBackend = null, resolveSettingsFor = resolveSettings, resolveRemoteDeviceContext = resolveRemoteContext, remoteDeviceDeps: makeRemoteDeviceDeps = remoteAndroidDeps, resolveEasBin = resolveEasCliBin, ensureMetroReachable: ensureRemoteMetro = ensureMetroReachable, ensureRemoteBootOwned: ensureRemoteOwned = ensureRemoteBootOwned, detectRemoteProviders = detectProviders, metroCheck = true, useBuildCache = true, easProfile, resolveEasDevelopmentBuild: resolveEasBuild = resolveEasDevelopmentBuild, variant: variantFlag = null, systemImage: systemImageFlag = null, deviceProfile: deviceProfileFlag, device: deviceFlag = null, wait: waitFlag = void 0, waitConflict = false, acquireLease = acquireRunLease, makeRunLease = runLease, selectPool = selectFromPool, onLeaseSignal = releaseLeaseOnSignal, listDevices = listAdbDevices, deviceModel = physicalDeviceModel, deviceAbi = androidDeviceAbi, isEmulatorDevice = probeEmulatorSerial, readApkPackage = (apkPath) => apkPackage(dumpApkManifest(apkPath)), getLimits = getConcurrencyLimits, checkCapacity = checkDeviceCapacity, acquireSlot = acquireBuildSlot, releaseSlot = releaseBuildSlot, ensureDevice = ensureOwnedDevice, listSystemImages = listInstalledSystemImages, listDeviceProfiles, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, startServer = startDevServer, readState = readWorkspaceState$1, pidAlive = pidExists, verifyCollector = verifyCollectorOwnership, verifyLaunched = verifyLaunch, ensureStorage = ensureWorkspaceStorageSafely, fingerprint = fingerprintProject, untracked = untrackedNativeFiles, resolveCached = resolveBuild, storeCached = storeBuild, storedAssets = storedAssetManifest, captureAssets = captureAssetManifest, acquireLock = acquireBuildLock, releaseLock = releaseBuildLock, waitForBuild: waitForBuild$1 = waitForBuild, loadProvider = loadProjectProvider, easAuth = checkEasAuth, resolveRemoteBuild = resolveRemote, uploadRemoteBuild = uploadRemote, resolveCacheProvider = resolveCacheProviderConfig, loadCacheProviderModule = loadCacheProvider, planPrebuildFor = planPrebuild, prebuild = runPrebuild, build = buildAndroid, ccacheFor = resolveCcache, install = installAndroidApp, launch = launchAndroidApp, launchRelease = launchAndroidReleaseApp, verifyReleaseLaunched = verifyAndroidReleaseLaunch, swapApk = swapApkBundle, resolveDevClientScheme = androidDevClientScheme, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), createWriter = createNdjsonWriter, writeLaunch = writeWorkspaceLaunch, writeState = writeWorkspaceState, recordBuild, recordStats = recordRunStats, recordCapacityRefusal: recordRefusal, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
|
|
2847
|
-
return {
|
|
2848
|
-
root,
|
|
2849
|
-
json,
|
|
2850
|
-
commandRemoteBackend,
|
|
2851
|
-
resolveSettingsFor,
|
|
2852
|
-
resolveRemoteDeviceContext,
|
|
2853
|
-
makeRemoteDeviceDeps,
|
|
2854
|
-
resolveEasBin,
|
|
2855
|
-
ensureRemoteMetro,
|
|
2856
|
-
ensureRemoteOwned,
|
|
2857
|
-
detectRemoteProviders,
|
|
2858
|
-
metroCheck,
|
|
2859
|
-
useBuildCache,
|
|
2860
|
-
easProfile,
|
|
2861
|
-
resolveEasBuild,
|
|
2862
|
-
variantFlag,
|
|
2863
|
-
systemImageFlag,
|
|
2864
|
-
deviceProfileFlag,
|
|
2865
|
-
deviceFlag,
|
|
2866
|
-
waitFlag,
|
|
2867
|
-
waitConflict,
|
|
2868
|
-
acquireLease,
|
|
2869
|
-
makeRunLease,
|
|
2870
|
-
selectPool,
|
|
2871
|
-
onLeaseSignal,
|
|
2872
|
-
listDevices,
|
|
2873
|
-
deviceModel,
|
|
2874
|
-
deviceAbi,
|
|
2875
|
-
isEmulatorDevice,
|
|
2876
|
-
readApkPackage,
|
|
2877
|
-
getLimits,
|
|
2878
|
-
checkCapacity,
|
|
2879
|
-
acquireSlot,
|
|
2880
|
-
releaseSlot,
|
|
2881
|
-
ensureDevice,
|
|
2882
|
-
listSystemImages,
|
|
2883
|
-
listDeviceProfiles,
|
|
2884
|
-
ensureDeviceBooted,
|
|
2885
|
-
resolveAvdSerial,
|
|
2886
|
-
waitForDeviceBoot,
|
|
2887
|
-
dataFreeBytes,
|
|
2888
|
-
trimCaches,
|
|
2889
|
-
wipeDevice,
|
|
2890
|
-
resolveMetro,
|
|
2891
|
-
prewarmMetro,
|
|
2892
|
-
startServer,
|
|
2893
|
-
readState,
|
|
2894
|
-
pidAlive,
|
|
2895
|
-
verifyCollector,
|
|
2896
|
-
verifyLaunched,
|
|
2897
|
-
ensureStorage,
|
|
2898
|
-
fingerprint,
|
|
2899
|
-
untracked,
|
|
2900
|
-
resolveCached,
|
|
2901
|
-
storeCached,
|
|
2902
|
-
storedAssets,
|
|
2903
|
-
captureAssets,
|
|
2904
|
-
acquireLock,
|
|
2905
|
-
releaseLock,
|
|
2906
|
-
waitForBuild: waitForBuild$1,
|
|
2907
|
-
loadProvider,
|
|
2908
|
-
easAuth,
|
|
2909
|
-
resolveRemoteBuild,
|
|
2910
|
-
uploadRemoteBuild,
|
|
2911
|
-
resolveCacheProvider,
|
|
2912
|
-
loadCacheProviderModule,
|
|
2913
|
-
planPrebuildFor,
|
|
2914
|
-
prebuild,
|
|
2915
|
-
build,
|
|
2916
|
-
ccacheFor,
|
|
2917
|
-
install,
|
|
2918
|
-
launch,
|
|
2919
|
-
launchRelease,
|
|
2920
|
-
verifyReleaseLaunched,
|
|
2921
|
-
swapApk,
|
|
2922
|
-
resolveDevClientScheme,
|
|
2923
|
-
spawn,
|
|
2924
|
-
kill,
|
|
2925
|
-
createWriter,
|
|
2926
|
-
writeLaunch,
|
|
2927
|
-
writeState,
|
|
2928
|
-
recordBuild,
|
|
2929
|
-
recordStats,
|
|
2930
|
-
recordCapacityRefusal: recordRefusal,
|
|
2931
|
-
readEstimates,
|
|
2932
|
-
now,
|
|
2933
|
-
out,
|
|
2934
|
-
emit
|
|
2935
|
-
};
|
|
2936
|
-
}
|
|
2937
|
-
function androidSlotOptions(options) {
|
|
2938
|
-
const base = resolveRunAndroidOptions(options);
|
|
2939
|
-
const slot = validateDeviceSlot(options.slot);
|
|
2940
|
-
if (slot === "default") return base;
|
|
2941
|
-
return {
|
|
2942
|
-
...base,
|
|
2943
|
-
ensureDevice: (args) => base.ensureDevice({
|
|
2944
|
-
...args,
|
|
2945
|
-
slot
|
|
2946
|
-
}),
|
|
2947
|
-
ensureDeviceBooted: (args) => base.ensureDeviceBooted({
|
|
2948
|
-
...args,
|
|
2949
|
-
slot
|
|
2950
|
-
}),
|
|
2951
|
-
checkCapacity: (args) => base.checkCapacity({
|
|
2952
|
-
...args,
|
|
2953
|
-
slot
|
|
2954
|
-
}),
|
|
2955
|
-
selectPool: (args) => base.selectPool({
|
|
2956
|
-
...args,
|
|
2957
|
-
slot
|
|
2958
|
-
}),
|
|
2959
|
-
acquireLease: (args) => base.acquireLease({
|
|
2960
|
-
...args,
|
|
2961
|
-
slot
|
|
2962
|
-
}),
|
|
2963
|
-
makeRunLease: (args) => base.makeRunLease({
|
|
2964
|
-
...args,
|
|
2965
|
-
slot
|
|
2966
|
-
}),
|
|
2967
|
-
writeLaunch: ((projectRoot, platform, record) => base.writeLaunch(projectRoot, platform, record, slot))
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
function avdSetupFailure(error, root, logFile) {
|
|
2971
|
-
const refusal = (0, ownership_claim_exports.claimFailure)(error, "stim android");
|
|
2972
|
-
if (refusal) return refusal;
|
|
2973
|
-
if (error instanceof DeviceAdmissionRefusal) return {
|
|
2974
|
-
code: error.code,
|
|
2975
|
-
message: error.message,
|
|
2976
|
-
remedy: error.remedy
|
|
2977
|
-
};
|
|
2978
|
-
const diag = noDeviceDiagnostic({
|
|
2979
|
-
reason: `Could not ensure an owned Android emulator: ${error?.message || error}`,
|
|
2980
|
-
logFile,
|
|
2981
|
-
localEmulator: !(error instanceof AvdRecoveryError),
|
|
2982
|
-
remedy: error instanceof AvdBootError ? error.remedy : "Check that JAVA_HOME and ANDROID_HOME are set correctly, and that an arm64 system image is installed (`sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`)."
|
|
2983
|
-
});
|
|
2984
|
-
return {
|
|
2985
|
-
code: NO_DEVICE,
|
|
2986
|
-
message: diag.message,
|
|
2987
|
-
remedy: diag.remedy,
|
|
2988
|
-
extra: {
|
|
2989
|
-
lines: diag.lines,
|
|
2990
|
-
logPath: diag.logPath ? displayPath(root, diag.logPath) : null
|
|
2991
|
-
}
|
|
2992
|
-
};
|
|
2993
|
-
}
|
|
2994
|
-
async function runAndroid(options = {}) {
|
|
2995
|
-
let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceProfileFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, listDeviceProfiles, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro, prewarmMetro, startServer, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, planPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordBuild, recordStats, recordCapacityRefusal: recordRefusal = recordCapacityRefusal, readEstimates, now, out, emit } = androidSlotOptions(options);
|
|
2996
|
-
const progress = options.progress ?? NO_BUILD_PROGRESS;
|
|
2997
|
-
const slot = validateDeviceSlot(options.slot);
|
|
2998
|
-
const started = now();
|
|
2999
|
-
const startedAt = new Date(started).toISOString();
|
|
3000
|
-
const problem = projectProblem(root, "android");
|
|
3001
|
-
if (problem) {
|
|
3002
|
-
const { message, remedy } = problem;
|
|
3003
|
-
out(phaseLine("error", chalk.red(`STIM_NO_PROJECT: ${message}`)));
|
|
3004
|
-
out(phaseLine("remedy", remedy));
|
|
3005
|
-
if (json) emit(JSON.stringify({
|
|
3006
|
-
code: "STIM_NO_PROJECT",
|
|
3007
|
-
message,
|
|
3008
|
-
remedy
|
|
3009
|
-
}));
|
|
3010
|
-
return {
|
|
3011
|
-
ok: false,
|
|
3012
|
-
error: {
|
|
3013
|
-
code: "STIM_NO_PROJECT",
|
|
3014
|
-
message,
|
|
3015
|
-
remedy
|
|
3016
|
-
}
|
|
3017
|
-
};
|
|
3018
|
-
}
|
|
3019
|
-
try {
|
|
3020
|
-
await ensureStorage(root, { note: out });
|
|
3021
|
-
} catch (error) {
|
|
3022
|
-
const code = error?.code || "STIM_WORKSPACE_STATE";
|
|
3023
|
-
const message = `Could not prepare this workspace's Stim state: ${error?.message || error}`;
|
|
3024
|
-
const remedy = "Check that STIM_HOME is writable and has free space. An EPERM on a directory you can write is a sandbox: allow writes to STIM_HOME, or run Stim with the sandbox disabled (`stim guide errors sandbox`).";
|
|
3025
|
-
out(phaseLine("error", chalk.red(`${code}: ${message}`)));
|
|
3026
|
-
out(phaseLine("remedy", remedy));
|
|
3027
|
-
if (json) emit(JSON.stringify({
|
|
3028
|
-
code,
|
|
3029
|
-
message,
|
|
3030
|
-
remedy
|
|
3031
|
-
}));
|
|
3032
|
-
return {
|
|
3033
|
-
ok: false,
|
|
3034
|
-
error: {
|
|
3035
|
-
code,
|
|
3036
|
-
message,
|
|
3037
|
-
remedy
|
|
3038
|
-
}
|
|
3039
|
-
};
|
|
3040
|
-
}
|
|
3041
|
-
const logsDir = workspaceLogsDir$1(root);
|
|
3042
|
-
const buildLog = join(logsDir, `build-${deviceSlotFileKey("android", slot)}.ndjson`);
|
|
3043
|
-
const writer = tapBuildLog(createWriter(buildLog, {
|
|
3044
|
-
truncate: true,
|
|
3045
|
-
fields: {
|
|
3046
|
-
platform: PLATFORM,
|
|
3047
|
-
slot
|
|
3048
|
-
}
|
|
3049
|
-
}), progress);
|
|
3050
|
-
setRemoteLogSink((entry) => writer.write(entry));
|
|
3051
|
-
const record = {
|
|
3052
|
-
fingerprint: null,
|
|
3053
|
-
cacheKey: null,
|
|
3054
|
-
cacheHit: false,
|
|
3055
|
-
appPath: null,
|
|
3056
|
-
bundleId: null,
|
|
3057
|
-
avdName: null,
|
|
3058
|
-
deviceName: null
|
|
3059
|
-
};
|
|
3060
|
-
const phase = (label, text) => out(phaseLine(label, text));
|
|
3061
|
-
const stats = createRunRecorder({
|
|
3062
|
-
platform: PLATFORM,
|
|
3063
|
-
write: recordStats,
|
|
3064
|
-
now,
|
|
3065
|
-
note: (line) => out(phaseLine("stats", chalk.dim(line))),
|
|
3066
|
-
phases: () => progress.durations(),
|
|
3067
|
-
deviceSetup: () => progress.deviceSetupKnown()
|
|
3068
|
-
});
|
|
3069
|
-
const recordRun = stats.record;
|
|
3070
|
-
let ccacheActivity = CCACHE_NOT_RUN;
|
|
3071
|
-
let reclaimed = [];
|
|
3072
|
-
const fail = (code, message, remedy, { lastBuildStatus = false, setup = false, diagnostics = [], buildDiagnostics: rawDiagnostics = [], lines = [], logPath = null, lease } = {}) => {
|
|
3073
|
-
const cancellation = cancelledFailure(PLATFORM, {
|
|
3074
|
-
code,
|
|
3075
|
-
message
|
|
3076
|
-
});
|
|
3077
|
-
if (cancellation) ({code, message, remedy, lines} = cancellation);
|
|
3078
|
-
if (lastBuildStatus) persistLastBuild({
|
|
3079
|
-
recordBuild,
|
|
3080
|
-
root,
|
|
3081
|
-
record,
|
|
3082
|
-
startedAt,
|
|
3083
|
-
durationMs: now() - started,
|
|
3084
|
-
status: "failed",
|
|
3085
|
-
errorCode: code,
|
|
3086
|
-
diagnostics: rawDiagnostics,
|
|
3087
|
-
out
|
|
3088
|
-
});
|
|
3089
|
-
out(phaseLine("error", chalk.red(`${code}: ${message}`)));
|
|
3090
|
-
for (const diagnostic of diagnostics) out(phaseLine("error", chalk.red(diagnostic)));
|
|
3091
|
-
for (const line of lines) out(phaseLine("", chalk.dim(line)));
|
|
3092
|
-
if (remedy) out(phaseLine("remedy", remedy));
|
|
3093
|
-
if (logPath) out(phaseLine("log", logPath));
|
|
3094
|
-
if (!setup) recordRun({
|
|
3095
|
-
failed: true,
|
|
3096
|
-
durationMs: now() - started
|
|
3097
|
-
});
|
|
3098
|
-
if (json) emit(JSON.stringify({
|
|
3099
|
-
code,
|
|
3100
|
-
message,
|
|
3101
|
-
remedy: remedy ?? null,
|
|
3102
|
-
...record.devicePlacement ? { devicePlacement: record.devicePlacement } : {},
|
|
3103
|
-
...ccacheActivity.status === "not-run" ? {} : { ccache: ccacheActivity },
|
|
3104
|
-
...lease === void 0 ? {} : { lease },
|
|
3105
|
-
...reclaimed.length ? { reclaimed } : {}
|
|
3106
|
-
}));
|
|
3107
|
-
writer.close();
|
|
3108
|
-
return {
|
|
3109
|
-
ok: false,
|
|
3110
|
-
error: {
|
|
3111
|
-
code,
|
|
3112
|
-
message,
|
|
3113
|
-
remedy: remedy ?? null
|
|
3114
|
-
}
|
|
3115
|
-
};
|
|
3116
|
-
};
|
|
3117
|
-
const settingsRepoRoot = repoRoot(root);
|
|
3118
|
-
const settingsRoot = root;
|
|
3119
|
-
const settingsContext = {
|
|
3120
|
-
projectPath: root,
|
|
3121
|
-
gitCommonDir: gitCommonDir(root),
|
|
3122
|
-
repoRoot: settingsRepoRoot
|
|
3123
|
-
};
|
|
3124
|
-
const projectKey = statsProjectKey$1({
|
|
3125
|
-
root,
|
|
3126
|
-
commonDir: settingsContext.gitCommonDir,
|
|
3127
|
-
repoRoot: settingsRepoRoot
|
|
3128
|
-
});
|
|
3129
|
-
stats.setProject(projectKey);
|
|
3130
|
-
progress.estimate(projectKey);
|
|
3131
|
-
let estimatesRead = null;
|
|
3132
|
-
const estimates = () => estimatesRead ??= readEstimates({
|
|
3133
|
-
projectKey,
|
|
3134
|
-
platform: PLATFORM
|
|
3135
|
-
});
|
|
3136
|
-
const settings = resolveSettingsFor(settingsContext);
|
|
3137
|
-
const placement = resolveBuildPlacement(options.buildMachine);
|
|
3138
|
-
record.buildMachine = placement.selected;
|
|
3139
|
-
if (placement.failure) {
|
|
3140
|
-
const { code, message, remedy } = placement.failure;
|
|
3141
|
-
return fail(code, message, remedy, { setup: true });
|
|
3142
|
-
}
|
|
3143
|
-
const planned = resolveAndroidRunPlan({
|
|
3144
|
-
settings,
|
|
3145
|
-
settingsContext,
|
|
3146
|
-
slot,
|
|
3147
|
-
easProfile,
|
|
3148
|
-
variant: variantFlag,
|
|
3149
|
-
systemImage: systemImageFlag,
|
|
3150
|
-
deviceProfile: deviceProfileFlag,
|
|
3151
|
-
device: deviceFlag,
|
|
3152
|
-
wait: waitFlag,
|
|
3153
|
-
waitConflict,
|
|
3154
|
-
remote: commandRemoteBackend,
|
|
3155
|
-
buildCache: requestedBuildCache
|
|
3156
|
-
}, {
|
|
3157
|
-
resolveCacheProvider,
|
|
3158
|
-
listSystemImages,
|
|
3159
|
-
listDeviceProfiles,
|
|
3160
|
-
warn: (label, message) => out(phaseLine(label, chalk.yellow(message)))
|
|
3161
|
-
});
|
|
3162
|
-
if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
|
|
3163
|
-
const { plan } = planned;
|
|
3164
|
-
const deviceSlotWait = {
|
|
3165
|
-
automatic: plan.target.kind === "hosted" && plan.target.machine === "auto",
|
|
3166
|
-
signal: runCancellationSignal(),
|
|
3167
|
-
waitMs: plan.deviceSlotWaitMs,
|
|
3168
|
-
displayName: basename(root),
|
|
3169
|
-
waitingFor: (info) => progress.waitingFor(info, "device-slot"),
|
|
3170
|
-
onWait: (ms) => {
|
|
3171
|
-
stats.addDeviceSlotWaitMs(ms);
|
|
3172
|
-
if (record.devicePlacement?.decision === "local" && ms > 0) record.devicePlacement.decision = "waited-locally";
|
|
3173
|
-
}
|
|
3174
|
-
};
|
|
3175
|
-
const { build: buildPlan, isExpo, cacheProviderConfig } = plan;
|
|
3176
|
-
const { variant, release, cache: cachePolicy } = buildPlan;
|
|
3177
|
-
record.configuration = variant ?? "debug";
|
|
3178
|
-
const useBuildCache = cachePolicy.read;
|
|
3179
|
-
const hosting = await selectAndroidPlacement({
|
|
3180
|
-
root,
|
|
3181
|
-
slot,
|
|
3182
|
-
target: plan.target,
|
|
3183
|
-
release,
|
|
3184
|
-
metroCheck,
|
|
3185
|
-
noWait: plan.deviceSlotWaitMs === 0,
|
|
3186
|
-
buildMachine: options.buildMachine,
|
|
3187
|
-
read: options.readHostedAndroid,
|
|
3188
|
-
resolveSerial: resolveAvdSerial,
|
|
3189
|
-
prepare: options.prepareHostedAndroid,
|
|
3190
|
-
automatic: options.automaticDevicePlacement,
|
|
3191
|
-
...remoteEasFallbackSetting(settings) ? { eas: () => (options.checkEasFallback ?? checkEasFallback)({
|
|
3192
|
-
root,
|
|
3193
|
-
platform: PLATFORM,
|
|
3194
|
-
slot,
|
|
3195
|
-
release,
|
|
3196
|
-
isExpo,
|
|
3197
|
-
tunnelMode: tunnelModeSetting(settings),
|
|
3198
|
-
publicUrl: publicUrlSetting(settings),
|
|
3199
|
-
localOnlyFlags: [...typeof systemImageFlag === "string" ? ["--system-image"] : [], ...typeof deviceProfileFlag === "string" ? ["--device-profile"] : []]
|
|
3200
|
-
}) } : {},
|
|
3201
|
-
localSelectors: (target) => androidDeviceSelectorRefusal({
|
|
3202
|
-
settingsContext,
|
|
3203
|
-
slot,
|
|
3204
|
-
systemImageFlag,
|
|
3205
|
-
deviceProfileFlag,
|
|
3206
|
-
systemImage: target.systemImage,
|
|
3207
|
-
deviceProfile: target.deviceProfile,
|
|
3208
|
-
physical: false,
|
|
3209
|
-
remoteBackend: null
|
|
3210
|
-
}, {
|
|
3211
|
-
listSystemImages,
|
|
3212
|
-
listDeviceProfiles
|
|
3213
|
-
}),
|
|
3214
|
-
checkBudget: options.checkBudget,
|
|
3215
|
-
note: out,
|
|
3216
|
-
phase,
|
|
3217
|
-
log: (entry) => writer.write(entry),
|
|
3218
|
-
fromFlag: commandRemoteBackend !== null
|
|
3219
|
-
});
|
|
3220
|
-
if ("failure" in hosting) return fail(hosting.failure.code, hosting.failure.message, hosting.failure.remedy);
|
|
3221
|
-
const { target, hostedTarget, budget, selectors } = hosting;
|
|
3222
|
-
record.devicePlacement = hosting.devicePlacement;
|
|
3223
|
-
writeDevicePlacement(root, slot, PLATFORM, record.devicePlacement);
|
|
3224
|
-
const physical = target.kind === "physical";
|
|
3225
|
-
const remoteBackend = target.kind === "remote" ? target.backend : null;
|
|
3226
|
-
if (hostedTarget) {
|
|
3227
|
-
if (settings.androidEmulatorApp !== void 0) phase("device", "androidEmulatorApp is ignored on a hosting Mac; the emulator boots headless.");
|
|
3228
|
-
if (publicUrlSetting(settings) || tunnelModeSetting(settings)) phase("metro", "metro.publicUrl and metro.tunnel are ignored on a hosting Mac; Metro uses the private tailnet bridge.");
|
|
3229
|
-
}
|
|
3230
|
-
reclaimed = budget.reclaimed;
|
|
3231
|
-
if (budget.refusal) return fail(budget.refusal.code, budget.refusal.message, budget.refusal.remedy);
|
|
3232
|
-
const remoteContext = remoteBackend ? await resolveRemoteDeviceContext({
|
|
3233
|
-
root,
|
|
3234
|
-
platform: PLATFORM,
|
|
3235
|
-
backend: remoteBackend,
|
|
3236
|
-
easBin: resolveEasBin(root)?.file ?? null
|
|
3237
|
-
}) : null;
|
|
3238
|
-
if (remoteContext && "failed" in remoteContext) return fail(remoteContext.code ?? "STIM_NO_REMOTE_SESSION", remoteContext.failed, remoteContext.remedy);
|
|
3239
|
-
const easBuild = await resolveEasBuild({
|
|
3240
|
-
root,
|
|
3241
|
-
platform: PLATFORM,
|
|
3242
|
-
profile: easProfile,
|
|
3243
|
-
note: out,
|
|
3244
|
-
isExpo,
|
|
3245
|
-
physical,
|
|
3246
|
-
selectors: [variantFlag],
|
|
3247
|
-
buildCache: requestedBuildCache
|
|
3248
|
-
});
|
|
3249
|
-
if (isEasBuildFailure(easBuild)) return fail(easBuild.code, easBuild.message, easBuild.remedy);
|
|
3250
|
-
const appIds = detectAppIds(root);
|
|
3251
|
-
let androidPackage = appIds.androidPackage;
|
|
3252
|
-
record.bundleId = androidPackage;
|
|
3253
|
-
const registerProject = () => upsertProject(root, {
|
|
3254
|
-
bundleId: appIds.bundleId ?? void 0,
|
|
3255
|
-
androidPackage: androidPackage ?? void 0,
|
|
3256
|
-
isExpo
|
|
3257
|
-
});
|
|
3258
|
-
if (remoteBackend !== "eas") registerProject();
|
|
3259
|
-
const project = getProject(root);
|
|
3260
|
-
const label = projectShortcut(root, project);
|
|
3261
|
-
let remoteDevice = null;
|
|
3262
|
-
const reservedPort = project?.metroPort ?? null;
|
|
3263
|
-
let metroPort = null;
|
|
3264
|
-
let devServer = null;
|
|
3265
|
-
async function prepareMetro() {
|
|
3266
|
-
if (metroCheck) {
|
|
3267
|
-
const gate = await ensureDevServer({
|
|
3268
|
-
root,
|
|
3269
|
-
port: reservedPort,
|
|
3270
|
-
settings: hostedMetroSettings(settings, Boolean(hostedTarget)),
|
|
3271
|
-
remote: remoteContext !== null,
|
|
3272
|
-
note: out,
|
|
3273
|
-
resolve: resolveMetro,
|
|
3274
|
-
start: startServer,
|
|
3275
|
-
readState
|
|
3276
|
-
});
|
|
3277
|
-
reclaimed = [...reclaimed, ...gate.reclaimed];
|
|
3278
|
-
if (!gate.ok) return {
|
|
3279
|
-
ok: false,
|
|
3280
|
-
error: {
|
|
3281
|
-
code: gate.code,
|
|
3282
|
-
message: gate.message,
|
|
3283
|
-
remedy: gate.remedy,
|
|
3284
|
-
lines: gate.lines
|
|
3285
|
-
}
|
|
3286
|
-
};
|
|
3287
|
-
metroPort = gate.port;
|
|
3288
|
-
devServer = gate.devServer;
|
|
3289
|
-
phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
|
|
3290
|
-
return {
|
|
3291
|
-
ok: true,
|
|
3292
|
-
prepared: { metroPort }
|
|
3293
|
-
};
|
|
3294
|
-
} else {
|
|
3295
|
-
const pin = metroPortSetting(root);
|
|
3296
|
-
if (pin.error) return {
|
|
3297
|
-
ok: false,
|
|
3298
|
-
error: {
|
|
3299
|
-
code: "STIM_BAD_ARG",
|
|
3300
|
-
message: pin.error,
|
|
3301
|
-
remedy: SETTING_SHAPE_REMEDY
|
|
3302
|
-
}
|
|
3303
|
-
};
|
|
3304
|
-
metroPort = pin.port ?? reservedPort ?? 8081;
|
|
3305
|
-
phase("metro", pin.port ?? reservedPort ? `port ${metroPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
|
|
3306
|
-
}
|
|
3307
|
-
return {
|
|
3308
|
-
ok: true,
|
|
3309
|
-
prepared: { metroPort }
|
|
3310
|
-
};
|
|
3311
|
-
}
|
|
3312
|
-
const runtime = options.runtimePlan ?? (release ? androidProcessRuntime(async () => {
|
|
3313
|
-
phase("metro", `skipped (${variant}: the JS bundle is embedded, no dev server is used)`);
|
|
3314
|
-
return {
|
|
3315
|
-
ok: true,
|
|
3316
|
-
prepared: { metroPort: null }
|
|
3317
|
-
};
|
|
3318
|
-
}) : androidMetroRuntime(prepareMetro));
|
|
3319
|
-
const preparation = await runtime.prepare();
|
|
3320
|
-
if (!preparation.ok) {
|
|
3321
|
-
const { code, message, remedy, lines } = preparation.error;
|
|
3322
|
-
return fail(code, message, remedy, { lines });
|
|
3323
|
-
}
|
|
3324
|
-
const runtimePreparation = preparation.prepared;
|
|
3325
|
-
metroPort = runtimePreparation.metroPort;
|
|
3326
|
-
if (remoteContext) {
|
|
3327
|
-
remoteDevice = makeRemoteDeviceDeps(remoteContext.ctx);
|
|
3328
|
-
if (metroPort !== null) {
|
|
3329
|
-
const reachable = await ensureRemoteMetro({
|
|
3330
|
-
ctx: remoteDevice.ctx,
|
|
3331
|
-
metroPort,
|
|
3332
|
-
isExpo,
|
|
3333
|
-
tunnelMode: tunnelModeSetting(settings) ?? void 0,
|
|
3334
|
-
publicUrl: publicUrlSetting(settings),
|
|
3335
|
-
available: detectRemoteProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
|
|
3336
|
-
});
|
|
3337
|
-
if ("failed" in reachable) return fail(reachable.code ?? "STIM_NO_REMOTE_SESSION", reachable.failed, reachable.remedy);
|
|
3338
|
-
}
|
|
3339
|
-
checkCapacity = remoteDevice.checkCapacity;
|
|
3340
|
-
ensureDevice = remoteDevice.ensureDevice;
|
|
3341
|
-
ensureDeviceBooted = remoteDevice.ensureDeviceBooted;
|
|
3342
|
-
install = remoteDevice.install;
|
|
3343
|
-
launch = remoteDevice.launch;
|
|
3344
|
-
}
|
|
3345
|
-
const emuLog = emulatorLogFile(root);
|
|
3346
|
-
const limits = getLimits();
|
|
3347
|
-
let device;
|
|
3348
|
-
let bootDuration = "";
|
|
3349
|
-
let bootPromise;
|
|
3350
|
-
let startRemoteBoot = null;
|
|
3351
|
-
if (hostedTarget) {
|
|
3352
|
-
device = {
|
|
3353
|
-
owned: false,
|
|
3354
|
-
systemImage: hostedTarget.choice.systemImage,
|
|
3355
|
-
deviceProfile: hostedTarget.choice.deviceProfile
|
|
3356
|
-
};
|
|
3357
|
-
bootPromise = Promise.resolve({ ok: true });
|
|
3358
|
-
} else if (target.kind === "physical" && !target.serial) {
|
|
3359
|
-
const pooled = await pooledAndroidDevice({
|
|
3360
|
-
root,
|
|
3361
|
-
selectPool,
|
|
3362
|
-
listDevices,
|
|
3363
|
-
isEmulatorDevice,
|
|
3364
|
-
deviceModel,
|
|
3365
|
-
waitSeconds: target.lease.waitSeconds,
|
|
3366
|
-
noWait: target.lease.noWait,
|
|
3367
|
-
now,
|
|
3368
|
-
warn: (line) => out(phaseLine("lease", chalk.yellow(line)))
|
|
3369
|
-
});
|
|
3370
|
-
if ("code" in pooled) return fail(pooled.code, pooled.message, pooled.remedy, pooled.extra);
|
|
3371
|
-
device = pooled.device;
|
|
3372
|
-
bootPromise = Promise.resolve({
|
|
3373
|
-
ok: true,
|
|
3374
|
-
serial: pooled.device.serial
|
|
3375
|
-
});
|
|
3376
|
-
} else if (target.kind === "physical") {
|
|
3377
|
-
const resolved = resolvePhysicalDevice(target.serial, listDevices(), isEmulatorDevice);
|
|
3378
|
-
if (!resolved.serial) return fail(NO_DEVICE, resolved.error, resolved.remedy);
|
|
3379
|
-
device = {
|
|
3380
|
-
serial: resolved.serial,
|
|
3381
|
-
deviceName: deviceModel(resolved.serial) ?? resolved.serial,
|
|
3382
|
-
owned: false
|
|
3383
|
-
};
|
|
3384
|
-
bootPromise = Promise.resolve({
|
|
3385
|
-
ok: true,
|
|
3386
|
-
serial: resolved.serial
|
|
3387
|
-
});
|
|
3388
|
-
} else {
|
|
3389
|
-
if (!remoteDevice) try {
|
|
3390
|
-
configuredAndroidEmulatorApp();
|
|
3391
|
-
} catch (err) {
|
|
3392
|
-
return fail("STIM_BAD_ARG", err.message, "Run `stim settings set androidEmulatorApp emulator` or `stim settings set androidEmulatorApp stim-desktop`.");
|
|
3393
|
-
}
|
|
3394
|
-
const capacity = plan.deviceSlotWaitMs === 0 ? checkCapacity({
|
|
3395
|
-
platform: PLATFORM,
|
|
3396
|
-
project,
|
|
3397
|
-
max: limits.maxDevices
|
|
3398
|
-
}) : null;
|
|
3399
|
-
if (capacity) {
|
|
3400
|
-
if (capacity.code === "STIM_AT_CAPACITY") recordRefusal({
|
|
3401
|
-
platform: PLATFORM,
|
|
3402
|
-
max: limits.maxDevices,
|
|
3403
|
-
workspace: workspaceId(root)
|
|
3404
|
-
}, now());
|
|
3405
|
-
return fail(capacity.code, capacity.message, capacity.remedy);
|
|
3406
|
-
}
|
|
3407
|
-
const prepare = stepClock(now);
|
|
3408
|
-
try {
|
|
3409
|
-
device = await ensureDevice({
|
|
3410
|
-
platform: PLATFORM,
|
|
3411
|
-
project,
|
|
3412
|
-
projectPath: root,
|
|
3413
|
-
settingsRoot,
|
|
3414
|
-
settings,
|
|
3415
|
-
flags: {
|
|
3416
|
-
deviceSlotWait,
|
|
3417
|
-
systemImage: target.systemImage,
|
|
3418
|
-
systemImageFlag: systemImageFlag?.trim() || null,
|
|
3419
|
-
deviceProfile: target.deviceProfile
|
|
3420
|
-
},
|
|
3421
|
-
note: out,
|
|
3422
|
-
out,
|
|
3423
|
-
logFile: emuLog
|
|
3424
|
-
});
|
|
3425
|
-
} catch (err) {
|
|
3426
|
-
const failure = avdSetupFailure(err, root, emuLog);
|
|
3427
|
-
return fail(failure.code, failure.message, failure.remedy, failure.extra);
|
|
3428
|
-
}
|
|
3429
|
-
progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
|
|
3430
|
-
const prepareMs = prepare();
|
|
3431
|
-
if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
|
|
3432
|
-
const boot = () => Promise.resolve(ensureDeviceBooted({
|
|
3433
|
-
platform: PLATFORM,
|
|
3434
|
-
device,
|
|
3435
|
-
projectPath: root,
|
|
3436
|
-
deviceSlotWait,
|
|
3437
|
-
out,
|
|
3438
|
-
logFile: emuLog
|
|
3439
|
-
})).catch((e) => ({
|
|
3440
|
-
failed: true,
|
|
3441
|
-
reason: String(e?.message || e),
|
|
3442
|
-
serial: void 0
|
|
3443
|
-
}));
|
|
3444
|
-
const startBoot = () => {
|
|
3445
|
-
const bootTimer = stepTimer(now);
|
|
3446
|
-
return (remoteDevice?.ctx.backend === "eas" ? ensureRemoteOwned({
|
|
3447
|
-
root,
|
|
3448
|
-
platform: PLATFORM,
|
|
3449
|
-
sessionName: ownedSessionName(remoteDevice.ctx.label),
|
|
3450
|
-
startedAt: new Date(now()).toISOString(),
|
|
3451
|
-
boot,
|
|
3452
|
-
createdSessionId: remoteDevice.createdSessionId,
|
|
3453
|
-
abandonCreatedSession: remoteDevice.abandonCreatedSession,
|
|
3454
|
-
webPreviewUrl: remoteDevice.webPreviewUrl,
|
|
3455
|
-
writeState,
|
|
3456
|
-
register: registerProject,
|
|
3457
|
-
notice: (line) => out(chalk.dim(phaseLine("lock", line)))
|
|
3458
|
-
}) : boot()).then((result) => {
|
|
3459
|
-
bootDuration = bootTimer();
|
|
3460
|
-
return result;
|
|
3461
|
-
});
|
|
3462
|
-
};
|
|
3463
|
-
if (remoteDevice) startRemoteBoot = startBoot;
|
|
3464
|
-
else bootPromise = startBoot();
|
|
3465
|
-
}
|
|
3466
|
-
record.avdName = device.avdName ?? null;
|
|
3467
|
-
record.deviceName = device.deviceName ?? device.avdName ?? null;
|
|
3468
|
-
record.systemImage = device.systemImage;
|
|
3469
|
-
record.deviceProfile = device.deviceProfile;
|
|
3470
|
-
const runFromFingerprint = async () => {
|
|
3471
|
-
if (metroCheck && metroPort !== null && plan.metroWarmup) prewarmMetro({
|
|
3472
|
-
port: metroPort,
|
|
3473
|
-
platform: "android",
|
|
3474
|
-
isExpo,
|
|
3475
|
-
appId: androidPackage,
|
|
3476
|
-
bundleUrl: metroWarmupUrlSetting(settings, "android")
|
|
3477
|
-
});
|
|
3478
|
-
const acquiredArtifact = await acquireAndroidArtifact({
|
|
3479
|
-
root,
|
|
3480
|
-
buildLog,
|
|
3481
|
-
writer,
|
|
3482
|
-
settings,
|
|
3483
|
-
isExpo,
|
|
3484
|
-
device,
|
|
3485
|
-
physical,
|
|
3486
|
-
hostedAbi: hostedTarget?.choice.architecture,
|
|
3487
|
-
remote: Boolean(remoteDevice),
|
|
3488
|
-
buildPlan,
|
|
3489
|
-
cacheProviderConfig,
|
|
3490
|
-
requestedBuildCache,
|
|
3491
|
-
easBuild,
|
|
3492
|
-
androidPackage,
|
|
3493
|
-
record,
|
|
3494
|
-
maxBuilds: limits.maxBuilds,
|
|
3495
|
-
progress: {
|
|
3496
|
-
phase,
|
|
3497
|
-
out,
|
|
3498
|
-
estimates,
|
|
3499
|
-
stats,
|
|
3500
|
-
step: progress.step,
|
|
3501
|
-
miss: progress.miss,
|
|
3502
|
-
hit: progress.hit,
|
|
3503
|
-
place: progress.place,
|
|
3504
|
-
waitingOn: progress.waitingOn,
|
|
3505
|
-
waitingFor: progress.waitingFor
|
|
3506
|
-
}
|
|
3507
|
-
}, {
|
|
3508
|
-
deviceAbi,
|
|
3509
|
-
fingerprint,
|
|
3510
|
-
untracked,
|
|
3511
|
-
resolveCached,
|
|
3512
|
-
storeCached,
|
|
3513
|
-
storedAssets,
|
|
3514
|
-
captureAssets,
|
|
3515
|
-
acquireLock,
|
|
3516
|
-
releaseLock,
|
|
3517
|
-
waitForBuild,
|
|
3518
|
-
loadProvider,
|
|
3519
|
-
easAuth,
|
|
3520
|
-
resolveRemoteBuild,
|
|
3521
|
-
uploadRemoteBuild,
|
|
3522
|
-
loadCacheProviderModule,
|
|
3523
|
-
acquireSlot,
|
|
3524
|
-
releaseSlot,
|
|
3525
|
-
planPrebuildFor,
|
|
3526
|
-
prebuild,
|
|
3527
|
-
build,
|
|
3528
|
-
ccacheFor,
|
|
3529
|
-
swapApk,
|
|
3530
|
-
readState,
|
|
3531
|
-
now
|
|
3532
|
-
});
|
|
3533
|
-
if (!acquiredArtifact.ok) {
|
|
3534
|
-
ccacheActivity = acquiredArtifact.ccache;
|
|
3535
|
-
const { failure } = acquiredArtifact;
|
|
3536
|
-
return fail(failure.code, failure.message, failure.remedy, failure.extra);
|
|
3537
|
-
}
|
|
3538
|
-
const { artifact } = acquiredArtifact;
|
|
3539
|
-
const { apkPath } = artifact;
|
|
3540
|
-
ccacheActivity = artifact.ccache;
|
|
3541
|
-
androidPackage = artifact.androidPackage;
|
|
3542
|
-
record.appPath = apkPath;
|
|
3543
|
-
if (runCancellation()) return fail("STIM_CANCELLED", "before install");
|
|
3544
|
-
if (hostedTarget) return finishHostedAndroidRun({
|
|
3545
|
-
target: hostedTarget,
|
|
3546
|
-
artifact,
|
|
3547
|
-
root,
|
|
3548
|
-
slot,
|
|
3549
|
-
release,
|
|
3550
|
-
isExpo,
|
|
3551
|
-
metroPort,
|
|
3552
|
-
logsDir,
|
|
3553
|
-
selectors,
|
|
3554
|
-
place: options.placeHostedAndroid ?? placeHostedAndroid,
|
|
3555
|
-
writePlacement: options.writeHostedAndroid ?? writeHostedAndroid,
|
|
3556
|
-
verifyLaunch: verifyLaunched,
|
|
3557
|
-
writeLaunch,
|
|
3558
|
-
readApkPackage,
|
|
3559
|
-
resolveDevClientScheme,
|
|
3560
|
-
now,
|
|
3561
|
-
started,
|
|
3562
|
-
startedAt,
|
|
3563
|
-
record,
|
|
3564
|
-
fail,
|
|
3565
|
-
phase,
|
|
3566
|
-
enterPhase: progress.step,
|
|
3567
|
-
out,
|
|
3568
|
-
json,
|
|
3569
|
-
useBuildCache,
|
|
3570
|
-
variant,
|
|
3571
|
-
metroCheck,
|
|
3572
|
-
writer,
|
|
3573
|
-
emit,
|
|
3574
|
-
recordRun,
|
|
3575
|
-
reclaimed,
|
|
3576
|
-
devServer,
|
|
3577
|
-
recordBuild
|
|
3578
|
-
});
|
|
3579
|
-
if (startRemoteBoot) {
|
|
3580
|
-
progress.step("device");
|
|
3581
|
-
bootPromise = startRemoteBoot();
|
|
3582
|
-
const booted = await bootPromise;
|
|
3583
|
-
if (booted.failed) {
|
|
3584
|
-
if (booted.code) return fail(booted.code, booted.reason ?? "The remote device did not boot.", booted.remedy ?? null);
|
|
3585
|
-
const diag = noDeviceDiagnostic({
|
|
3586
|
-
reason: booted.reason ?? "The remote device did not boot.",
|
|
3587
|
-
logFile: emuLog,
|
|
3588
|
-
remedy: "Run `stim status` to inspect the remote device, then retry the command.",
|
|
3589
|
-
localEmulator: false
|
|
3590
|
-
});
|
|
3591
|
-
return fail(NO_DEVICE, diag.message, diag.remedy, {
|
|
3592
|
-
lines: diag.lines,
|
|
3593
|
-
logPath: diag.logPath ? displayPath(root, diag.logPath) : null
|
|
3594
|
-
});
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
let leaseHandle = null;
|
|
3598
|
-
let stopLeaseSignals = null;
|
|
3599
|
-
const releaseLease = () => {
|
|
3600
|
-
const held = leaseHandle;
|
|
3601
|
-
const stopSignals = stopLeaseSignals;
|
|
3602
|
-
leaseHandle = null;
|
|
3603
|
-
stopLeaseSignals = null;
|
|
3604
|
-
stopSignals?.();
|
|
3605
|
-
try {
|
|
3606
|
-
held?.release();
|
|
3607
|
-
} catch (err) {
|
|
3608
|
-
out(phaseLine("lease", chalk.dim(`could not release this run's lease: ${err?.message || err}`)));
|
|
3609
|
-
}
|
|
3610
|
-
};
|
|
3611
|
-
if (physical) {
|
|
3612
|
-
progress.step("device");
|
|
3613
|
-
const acquired = await acquireLease({
|
|
3614
|
-
root,
|
|
3615
|
-
platform: PLATFORM,
|
|
3616
|
-
id: device.serial,
|
|
3617
|
-
deviceName: device.deviceName ?? null,
|
|
3618
|
-
idLabel: "serial",
|
|
3619
|
-
waitSeconds: target.lease.waitSeconds,
|
|
3620
|
-
noWait: target.lease.noWait,
|
|
3621
|
-
installBoundMs: ADB_INSTALL_TIMEOUT_MS,
|
|
3622
|
-
appId: androidPackage,
|
|
3623
|
-
holderAppId: (holder) => getProject(holder)?.androidPackage ?? null,
|
|
3624
|
-
now,
|
|
3625
|
-
warn: (line) => out(phaseLine("lease", chalk.yellow(line)))
|
|
3626
|
-
});
|
|
3627
|
-
if (acquired.status === "refused") return fail(acquired.refusal.code, acquired.refusal.message, acquired.refusal.remedy, { lease: acquired.refusal.lease });
|
|
3628
|
-
leaseHandle = makeRunLease({
|
|
3629
|
-
root,
|
|
3630
|
-
platform: PLATFORM,
|
|
3631
|
-
kind: acquired.status === "leased" ? acquired.kind : null,
|
|
3632
|
-
expiresAt: acquired.status === "leased" ? acquired.expiresAt : null
|
|
3633
|
-
});
|
|
3634
|
-
if (acquired.status === "leased") {
|
|
3635
|
-
stopLeaseSignals = onLeaseSignal(releaseLease);
|
|
3636
|
-
phase("lease", `${acquired.kind} lease on ${device.serial} until ${leaseExpiryText(acquired.expiresAt, now())}`);
|
|
3637
|
-
}
|
|
3638
|
-
}
|
|
3639
|
-
try {
|
|
3640
|
-
return await finishAndroidRun({
|
|
3641
|
-
runtime,
|
|
3642
|
-
runtimePreparation,
|
|
3643
|
-
slot,
|
|
3644
|
-
lease: leaseHandle,
|
|
3645
|
-
releaseLease,
|
|
3646
|
-
root,
|
|
3647
|
-
json,
|
|
3648
|
-
metroCheck,
|
|
3649
|
-
useBuildCache,
|
|
3650
|
-
variant,
|
|
3651
|
-
release,
|
|
3652
|
-
isExpo,
|
|
3653
|
-
metroPort,
|
|
3654
|
-
logsDir,
|
|
3655
|
-
emuLog,
|
|
3656
|
-
device,
|
|
3657
|
-
physical,
|
|
3658
|
-
remoteDevice,
|
|
3659
|
-
bootPromise,
|
|
3660
|
-
resolveAvdSerial,
|
|
3661
|
-
waitForDeviceBoot,
|
|
3662
|
-
dataFreeBytes,
|
|
3663
|
-
trimCaches,
|
|
3664
|
-
wipeDevice,
|
|
3665
|
-
bootDuration: () => bootDuration,
|
|
3666
|
-
apkPath,
|
|
3667
|
-
androidPackage,
|
|
3668
|
-
swapDir: artifact.swapDir,
|
|
3669
|
-
record,
|
|
3670
|
-
waitedForBuild: artifact.waitedForBuild,
|
|
3671
|
-
ccache: ccacheActivity,
|
|
3672
|
-
uploadPending: artifact.uploadPending,
|
|
3673
|
-
providerUpload: artifact.providerUpload,
|
|
3674
|
-
providerName: artifact.providerName,
|
|
3675
|
-
remote: artifact.remote,
|
|
3676
|
-
abandonedRemote: artifact.abandonedRemote,
|
|
3677
|
-
started,
|
|
3678
|
-
startedAt,
|
|
3679
|
-
writer,
|
|
3680
|
-
phase,
|
|
3681
|
-
fail,
|
|
3682
|
-
readApkPackage,
|
|
3683
|
-
install,
|
|
3684
|
-
launch,
|
|
3685
|
-
launchRelease,
|
|
3686
|
-
resolveDevClientScheme,
|
|
3687
|
-
verifyLaunched,
|
|
3688
|
-
verifyReleaseLaunched,
|
|
3689
|
-
spawn,
|
|
3690
|
-
kill,
|
|
3691
|
-
pidAlive,
|
|
3692
|
-
verifyCollector,
|
|
3693
|
-
writeLaunch,
|
|
3694
|
-
recordBuild,
|
|
3695
|
-
now,
|
|
3696
|
-
out,
|
|
3697
|
-
emit,
|
|
3698
|
-
recordRun,
|
|
3699
|
-
reclaimed,
|
|
3700
|
-
devServer,
|
|
3701
|
-
enterPhase: progress.step,
|
|
3702
|
-
rebootDevice: () => Promise.resolve(ensureDeviceBooted({
|
|
3703
|
-
platform: PLATFORM,
|
|
3704
|
-
device: {
|
|
3705
|
-
...device,
|
|
3706
|
-
serial: void 0
|
|
3707
|
-
},
|
|
3708
|
-
projectPath: root,
|
|
3709
|
-
deviceSlotWait,
|
|
3710
|
-
out,
|
|
3711
|
-
logFile: emuLog
|
|
3712
|
-
})).catch((e) => ({
|
|
3713
|
-
failed: true,
|
|
3714
|
-
reason: String(e?.message || e)
|
|
3715
|
-
}))
|
|
3716
|
-
});
|
|
3717
|
-
} finally {
|
|
3718
|
-
releaseLease();
|
|
3719
|
-
}
|
|
3720
|
-
};
|
|
3721
|
-
try {
|
|
3722
|
-
return await runFromFingerprint();
|
|
3723
|
-
} catch (error) {
|
|
3724
|
-
recordRun({
|
|
3725
|
-
failed: true,
|
|
3726
|
-
durationMs: now() - started
|
|
3727
|
-
});
|
|
3728
|
-
throw error;
|
|
3729
|
-
}
|
|
3730
|
-
}
|
|
3731
|
-
//#endregion
|
|
3732
|
-
export { collectorLogFile as a, androidFacts as c, runAndroidOperation as i, lastBuildRecord as l, registerAndroid as n, killPreviousCollector as o, runAndroid as r, startCollector as s, androidCommand as t };
|