-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix build cache management in workspaces #675
base: main
Are you sure you want to change the base?
Changes from 4 commits
ea64ab8
d947162
750017b
3b53a39
74b439f
0b033a5
d5412e0
1475e12
bad7a58
3e347c2
cd19698
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,17 @@ | ||
import path from "path"; | ||
import path, { join } from "path"; | ||
import fs from "fs"; | ||
import { createHash } from "crypto"; | ||
import { createFingerprintAsync } from "@expo/fingerprint"; | ||
import { Logger } from "../Logger"; | ||
import { extensionContext, getAppRootFolder } from "../utilities/extensionContext"; | ||
import { getAppRootFolder } from "../utilities/extensionContext"; | ||
import { DevicePlatform } from "../common/DeviceManager"; | ||
import { IOSBuildResult } from "./buildIOS"; | ||
import { AndroidBuildResult } from "./buildAndroid"; | ||
import { getLaunchConfiguration } from "../utilities/launchConfiguration"; | ||
import { runfingerprintCommand } from "./customBuild"; | ||
import { calculateMD5 } from "../utilities/common"; | ||
import { calculateMD5, getOrCreateBuildCachesDir } from "../utilities/common"; | ||
import { BuildResult } from "./BuildManager"; | ||
|
||
const ANDROID_BUILD_CACHE_KEY = "android_build_cache"; | ||
const IOS_BUILD_CACHE_KEY = "ios_build_cache"; | ||
|
||
const IGNORE_PATHS = [ | ||
path.join("android", ".gradle/**/*"), | ||
path.join("android", "build/**/*"), | ||
|
@@ -46,28 +44,60 @@ export class PlatformBuildCache { | |
|
||
private constructor(private readonly platform: DevicePlatform) {} | ||
|
||
get cacheKey() { | ||
return this.platform === DevicePlatform.Android ? ANDROID_BUILD_CACHE_KEY : IOS_BUILD_CACHE_KEY; | ||
get appBuildCachePath() { | ||
const appBuildCachesDir = getOrCreateAppBuildCachesDir(); | ||
|
||
let appBuildCachePath; | ||
|
||
if (this.platform === DevicePlatform.Android) { | ||
appBuildCachePath = join(appBuildCachesDir, "android.json"); | ||
} else { | ||
appBuildCachePath = join(appBuildCachesDir, "ios.json"); | ||
} | ||
|
||
return appBuildCachePath; | ||
} | ||
|
||
/** | ||
* Passed fingerprint should be calculated at the time build is started. | ||
*/ | ||
public async storeBuild(buildFingerprint: string, build: BuildResult) { | ||
public async storeCache(buildFingerprint: string, build: BuildResult) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why has this been renamed? The method takes fingerprint and build result that gets stored. We don't store any cache here |
||
const appPath = await getAppHash(getAppPath(build)); | ||
await extensionContext.workspaceState.update(this.cacheKey, { | ||
|
||
const cache = JSON.stringify({ | ||
fingerprint: buildFingerprint, | ||
buildHash: appPath, | ||
buildResult: build, | ||
}); | ||
|
||
fs.writeFileSync(this.appBuildCachePath, cache); | ||
} | ||
|
||
public async clearCache() { | ||
await extensionContext.workspaceState.update(this.cacheKey, undefined); | ||
if (!fs.existsSync(this.appBuildCachePath)) { | ||
return; | ||
} | ||
fs.rmSync(this.appBuildCachePath); | ||
} | ||
|
||
public getCache() { | ||
if (!fs.existsSync(this.appBuildCachePath)) { | ||
Logger.debug("No cache found."); | ||
return undefined; | ||
} | ||
let cache: BuildCacheInfo; | ||
try { | ||
cache = JSON.parse(fs.readFileSync(this.appBuildCachePath).toString()); | ||
} catch (e) { | ||
Logger.warn("Error parsing build cache", e); | ||
return undefined; | ||
} | ||
return cache; | ||
} | ||
|
||
public async getBuild(currentFingerprint: string) { | ||
const cache = extensionContext.workspaceState.get<BuildCacheInfo>(this.cacheKey); | ||
const cache: BuildCacheInfo | undefined = this.getCache(); | ||
|
||
if (!cache) { | ||
Logger.debug("No cached build found."); | ||
return undefined; | ||
|
@@ -105,8 +135,7 @@ export class PlatformBuildCache { | |
|
||
public async isCacheStale() { | ||
const currentFingerprint = await this.calculateFingerprint(); | ||
const { fingerprint } = | ||
extensionContext.workspaceState.get<BuildCacheInfo>(this.cacheKey) ?? {}; | ||
const { fingerprint } = this.getCache() ?? {}; | ||
|
||
return currentFingerprint !== fingerprint; | ||
} | ||
|
@@ -158,3 +187,15 @@ function getAppPath(build: BuildResult) { | |
async function getAppHash(appPath: string) { | ||
return (await calculateMD5(appPath)).digest("hex"); | ||
} | ||
|
||
function getAppRootPathHash() { | ||
return createHash("md5").update(getAppRootFolder()).digest("hex"); | ||
} | ||
|
||
function getOrCreateAppBuildCachesDir() { | ||
const appBuildCachesDirLocation = join(getOrCreateBuildCachesDir(), getAppRootPathHash()); | ||
if (!fs.existsSync(appBuildCachesDirLocation)) { | ||
fs.mkdirSync(appBuildCachesDirLocation, { recursive: true }); | ||
} | ||
return appBuildCachesDirLocation; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,6 +94,14 @@ export function getOrCreateAppDownloadsDir() { | |
return downloadsDirLocation; | ||
} | ||
|
||
export function getOrCreateBuildCachesDir() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you think about creating abstract utils for app root level cache management? I believe it would be enough to support storing json data? It would be nice to use something like that in #640 to store recently opened deep links on high level it could look something like this: // src/utilities/appCache.ts
const readAppLevelCache = (key: string): Record<string, unknown> | null => {
return JSON.parse(fs.read(join(appCacheDir), key)));
}
const writeAppLevelCache = (key: string, data: Record<string, unknown>) => {
fs.write(join(appCacheDir, key), JSON.stringify(data));
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right it would be great to it build like that, thank you |
||
const buildCachesDirLocation = join(getAppCachesDir(), "buildCaches"); | ||
if (!fs.existsSync(buildCachesDirLocation)) { | ||
fs.mkdirSync(buildCachesDirLocation, { recursive: true }); | ||
} | ||
return buildCachesDirLocation; | ||
} | ||
|
||
export function isDeviceIOS(deviceId: string) { | ||
return deviceId.startsWith("ios"); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like the naming being so mixed up in this whole PR. We should have naming that better maps to the intuition what cache is rather than trying to provide a super-accurate naming.
Here, it sound very weird that we are "storing cache" using "build cache". Intuitively, what this line does is it stores some build metadata (build result) in a cache storage. IMO "storeBuild" just much better describes what this method does than "storeCache".