diff --git a/packages/vscode-extension/src/builders/PlatformBuildCache.ts b/packages/vscode-extension/src/builders/BuildCache.ts similarity index 71% rename from packages/vscode-extension/src/builders/PlatformBuildCache.ts rename to packages/vscode-extension/src/builders/BuildCache.ts index 1063c4a11..73dae4ee9 100644 --- a/packages/vscode-extension/src/builders/PlatformBuildCache.ts +++ b/packages/vscode-extension/src/builders/BuildCache.ts @@ -30,24 +30,17 @@ export type BuildCacheInfo = { buildResult: AndroidBuildResult | IOSBuildResult; }; -export class PlatformBuildCache { - static instances: Record = { - [DevicePlatform.Android]: undefined, - [DevicePlatform.IOS]: undefined, - }; - - static forPlatform(platform: DevicePlatform): PlatformBuildCache { - if (!this.instances[platform]) { - this.instances[platform] = new PlatformBuildCache(platform); - } - - return this.instances[platform]; - } +function makeCacheKey(platform: DevicePlatform, appRoot: string) { + const keyPrefix = + platform === DevicePlatform.Android ? ANDROID_BUILD_CACHE_KEY : IOS_BUILD_CACHE_KEY; + return `${keyPrefix}:${appRoot}`; +} - private constructor(private readonly platform: DevicePlatform) {} +export class BuildCache { + private readonly cacheKey: string; - get cacheKey() { - return this.platform === DevicePlatform.Android ? ANDROID_BUILD_CACHE_KEY : IOS_BUILD_CACHE_KEY; + constructor(private readonly platform: DevicePlatform, private readonly appRoot: string) { + this.cacheKey = makeCacheKey(platform, appRoot); } /** @@ -55,7 +48,7 @@ export class PlatformBuildCache { */ public async storeBuild(buildFingerprint: string, build: BuildResult) { const appPath = await getAppHash(getAppPath(build)); - await extensionContext.workspaceState.update(this.cacheKey, { + await extensionContext.globalState.update(this.cacheKey, { fingerprint: buildFingerprint, buildHash: appPath, buildResult: build, @@ -63,11 +56,11 @@ export class PlatformBuildCache { } public async clearCache() { - await extensionContext.workspaceState.update(this.cacheKey, undefined); + await extensionContext.globalState.update(this.cacheKey, undefined); } public async getBuild(currentFingerprint: string) { - const cache = extensionContext.workspaceState.get(this.cacheKey); + const cache = extensionContext.globalState.get(this.cacheKey); if (!cache) { Logger.debug("No cached build found."); return undefined; @@ -105,8 +98,7 @@ export class PlatformBuildCache { public async isCacheStale() { const currentFingerprint = await this.calculateFingerprint(); - const { fingerprint } = - extensionContext.workspaceState.get(this.cacheKey) ?? {}; + const { fingerprint } = extensionContext.globalState.get(this.cacheKey) ?? {}; return currentFingerprint !== fingerprint; } @@ -145,10 +137,10 @@ export class PlatformBuildCache { const fingerprint = await runfingerprintCommand(fingerprintCommand, env); if (!fingerprint) { - throw new Error("Failed to generate workspace fingerprint using custom script."); + throw new Error("Failed to generate application fingerprint using custom script."); } - Logger.debug("Workspace fingerprint", fingerprint); + Logger.debug("Application fingerprint", fingerprint); return fingerprint; } } @@ -160,3 +152,23 @@ function getAppPath(build: BuildResult) { async function getAppHash(appPath: string) { return (await calculateMD5(appPath)).digest("hex"); } + +export async function migrateOldBuildCachesToNewStorage() { + try { + const appRoot = getAppRootFolder(); + + for (const platform of [DevicePlatform.Android, DevicePlatform.IOS]) { + const oldKey = + platform === DevicePlatform.Android ? ANDROID_BUILD_CACHE_KEY : IOS_BUILD_CACHE_KEY; + const cache = extensionContext.workspaceState.get(oldKey); + if (cache) { + await extensionContext.globalState.update(makeCacheKey(platform, appRoot), cache); + await extensionContext.workspaceState.update(oldKey, undefined); + } + } + } catch (e) { + // we ignore all potential errors in this phase as it isn't critical and it is + // better to not block the extension from starting in case of any issues when + // migrating the caches + } +} diff --git a/packages/vscode-extension/src/builders/BuildManager.ts b/packages/vscode-extension/src/builders/BuildManager.ts index dd0291577..72fec3402 100644 --- a/packages/vscode-extension/src/builders/BuildManager.ts +++ b/packages/vscode-extension/src/builders/BuildManager.ts @@ -1,5 +1,5 @@ import { Disposable, OutputChannel, window } from "vscode"; -import { PlatformBuildCache } from "./PlatformBuildCache"; +import { BuildCache } from "./BuildCache"; import { AndroidBuildResult, buildAndroid } from "./buildAndroid"; import { IOSBuildResult, buildIos } from "./buildIOS"; import { DeviceInfo, DevicePlatform } from "../common/DeviceManager"; @@ -22,7 +22,10 @@ type BuildOptions = { }; export class BuildManager { - constructor(private readonly dependencyManager: DependencyManager) {} + constructor( + private readonly dependencyManager: DependencyManager, + private readonly buildCache: BuildCache + ) {} private buildOutputChannel: OutputChannel | undefined; @@ -53,10 +56,9 @@ export class BuildManager { }); const cancelToken = new CancelToken(); - const buildCache = PlatformBuildCache.forPlatform(platform); const buildApp = async () => { - const currentFingerprint = await buildCache.calculateFingerprint(); + const currentFingerprint = await this.buildCache.calculateFingerprint(); // Native build dependencies when changed, should invalidate cached build (even if the fingerprint is the same) const buildDependenciesChanged = await this.checkBuildDependenciesChanged(deviceInfo); @@ -68,9 +70,9 @@ export class BuildManager { "Build cache is being invalidated", forceCleanBuild ? "on request" : "due to build dependencies change" ); - await buildCache.clearCache(); + await this.buildCache.clearCache(); } else { - const cachedBuild = await buildCache.getBuild(currentFingerprint); + const cachedBuild = await this.buildCache.getBuild(currentFingerprint); if (cachedBuild) { Logger.debug("Skipping native build – using cached"); getTelemetryReporter().sendTelemetryEvent("build:cache-hit", { platform }); @@ -122,7 +124,7 @@ export class BuildManager { await this.dependencyManager.installPods(iOSBuildOutputChannel, cancelToken); // Installing pods may impact the fingerprint as new pods may be created under the project directory. // For this reason we need to recalculate the fingerprint after installing pods. - buildFingerprint = await buildCache.calculateFingerprint(); + buildFingerprint = await this.buildCache.calculateFingerprint(); } }; buildResult = await buildIos( @@ -136,7 +138,7 @@ export class BuildManager { ); } - await buildCache.storeBuild(buildFingerprint, buildResult); + await this.buildCache.storeBuild(buildFingerprint, buildResult); return buildResult; }; diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index 1c803a4d0..02a222dfe 100644 --- a/packages/vscode-extension/src/extension.ts +++ b/packages/vscode-extension/src/extension.ts @@ -31,6 +31,7 @@ import { getLaunchConfiguration } from "./utilities/launchConfiguration"; import { Project } from "./project/project"; import { findFilesInWorkspace, isWorkspaceRoot } from "./utilities/common"; import { Platform } from "./utilities/platform"; +import { migrateOldBuildCachesToNewStorage } from "./builders/BuildCache"; const OPEN_PANEL_ON_ACTIVATION = "open_panel_on_activation"; @@ -73,7 +74,7 @@ export async function activate(context: ExtensionContext) { enableDevModeLogging(); } - migrateOldConfiguration(); + await migrateOldConfiguration(); commands.executeCommand("setContext", "RNIDE.sidePanelIsClosed", false); @@ -245,6 +246,9 @@ export async function activate(context: ExtensionContext) { } } + // this needs to be run after app root is set + migrateOldBuildCachesToNewStorage(); + extensionActivated(); } diff --git a/packages/vscode-extension/src/project/deviceSession.ts b/packages/vscode-extension/src/project/deviceSession.ts index 8fa3a1ea2..f99730510 100644 --- a/packages/vscode-extension/src/project/deviceSession.ts +++ b/packages/vscode-extension/src/project/deviceSession.ts @@ -16,6 +16,7 @@ import { DebugSession, DebugSessionDelegate } from "../debugging/DebugSession"; import { throttle } from "../utilities/throttle"; import { DependencyManager } from "../dependency/DependencyManager"; import { getTelemetryReporter } from "../utilities/telemetry"; +import { BuildCache } from "../builders/BuildCache"; type PreviewReadyCallback = (previewURL: string) => void; type StartOptions = { cleanBuild: boolean; previewReadyCallback: PreviewReadyCallback }; @@ -54,10 +55,11 @@ export class DeviceSession implements Disposable { private readonly devtools: Devtools, private readonly metro: Metro, readonly dependencyManager: DependencyManager, + readonly buildCache: BuildCache, private readonly debugEventDelegate: DebugSessionDelegate, private readonly eventDelegate: EventDelegate ) { - this.buildManager = new BuildManager(dependencyManager); + this.buildManager = new BuildManager(dependencyManager, buildCache); this.devtools.addListener((event, payload) => { switch (event) { case "RNIDE_appReady": diff --git a/packages/vscode-extension/src/project/project.ts b/packages/vscode-extension/src/project/project.ts index fbc1b76e6..7b47c303a 100644 --- a/packages/vscode-extension/src/project/project.ts +++ b/packages/vscode-extension/src/project/project.ts @@ -22,7 +22,7 @@ import { import { Logger } from "../Logger"; import { DeviceInfo } from "../common/DeviceManager"; import { DeviceAlreadyUsedError, DeviceManager } from "../devices/DeviceManager"; -import { extensionContext } from "../utilities/extensionContext"; +import { extensionContext, getAppRootFolder } from "../utilities/extensionContext"; import { IosSimulatorDevice } from "../devices/IosSimulatorDevice"; import { AndroidEmulatorDevice } from "../devices/AndroidEmulatorDevice"; import { DependencyManager } from "../dependency/DependencyManager"; @@ -31,7 +31,7 @@ import { DebugSessionDelegate } from "../debugging/DebugSession"; import { Metro, MetroDelegate } from "./metro"; import { Devtools } from "./devtools"; import { AppEvent, DeviceSession, EventDelegate } from "./deviceSession"; -import { PlatformBuildCache } from "../builders/PlatformBuildCache"; +import { BuildCache } from "../builders/BuildCache"; import { PanelLocation } from "../common/WorkspaceConfig"; import { activateDevice, getLicenseToken } from "../utilities/license"; @@ -632,6 +632,7 @@ export class Project this.devtools, this.metro, this.dependencyManager, + new BuildCache(device.platform, getAppRootFolder()), this, this ); @@ -669,9 +670,8 @@ export class Project }; private checkIfNativeChanged = throttleAsync(async () => { - if (!this.isCachedBuildStale && this.projectState.selectedDevice) { - const platform = this.projectState.selectedDevice.platform; - const isCacheStale = await PlatformBuildCache.forPlatform(platform).isCacheStale(); + if (!this.isCachedBuildStale && this.deviceSession) { + const isCacheStale = await this.deviceSession.buildCache.isCacheStale(); if (isCacheStale) { this.isCachedBuildStale = true;