-
Notifications
You must be signed in to change notification settings - Fork 50
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
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
@@ -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 comment
The 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 comment
The 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
Is this really a scenario we want to optimize for? When I work with monorepo setups I never switch between opening the workspace from different places. I either have the app bit open, or I have the whole monorepo opened. I never switch between these two approaches for a given project. Do you have any insights whether this is what people tend to do? |
@kmagiera This PR as I eluded in description is necessary to enable switching between applications in #663, so the fact that it will help some people that like to change set up is just a bonus, but to answer your question when I talked to @jakex7 about his monorepo set up he seemed interested in being able to use booth approaches. |
let oldCaches; | ||
try { | ||
oldCaches = JSON.parse(fs.readFileSync(appCachesPath).toString()); | ||
} catch (e) { | ||
Logger.warn(`Error parsing old caches file for app ${getAppRootFolder()}`, e); | ||
return; | ||
} |
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.
this logic is repeated several times, you can extract it to new function getAppCaches()
or something like that (without key, it returns the whole json object)
} | ||
|
||
/** | ||
* This function sets app level caches from caches storage. |
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.
What does that even mean?
@@ -135,7 +135,7 @@ export class BuildManager { | |||
); | |||
} | |||
|
|||
await buildCache.storeBuild(buildFingerprint, buildResult); | |||
await buildCache.storeCache(buildFingerprint, buildResult); |
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".
@@ -53,21 +54,34 @@ export class PlatformBuildCache { | |||
/** | |||
* 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 comment
The 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
fingerprint: buildFingerprint, | ||
buildHash: appPath, | ||
buildResult: build, | ||
}); | ||
|
||
setAppCache(this.cacheKey, cache); |
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.
In contrast, this method actually stores stuff, so why is it called "set" – this is a frequently used prefix for setting class members and using it for method that writes to disk is very confusing.
* @param value | ||
* @returns the new value that was set | ||
*/ | ||
export function setAppCache(key: string, value: string) { |
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.
this whole module doesn't have a clear contract. It seem to assume that the values it saves or retrieves are strings, but internal implementation parses those files expecting this is actually a structured data
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 stores strings for given key but the whole caches file is a json
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.
After reviewing this I think it'd be best to restructure this a bit. Below are some things that I think we should do:
-
I don't think we need the whole logic for managing app caches directory etc. We've been using workspaceState, but if we cannot rely on this data on being workspace-specific, we should instead use globalState. For the keys, we could just use the app root directory instead of a hash which should make it more readable and allow for cleaning it up at later time.
-
we should settle on avoiding the use of getAppRoot going forward to make it easier to support cases where we want to switch app roots. The first step we can make could be by adding appRoot as constructor parameter to
PlatformBuildCache
– this way the build cache can reference the right place in the storage and we don't need to add more references to getAppRoot.
cd19698
to
01ee387
Compare
const platformKey = | ||
this.platform === DevicePlatform.Android ? ANDROID_BUILD_CACHE_KEY : IOS_BUILD_CACHE_KEY; | ||
const appRoot = this.appRoot; | ||
return appRoot + platformKey; |
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.
would prefer to add some delimiter here, would be cleaner to read in case we actually need to access this in the future.
Also, because of that, it'd be better to have the path at the end, otherwise it would be harder to get the path as you couldn't tell where the file path actually ends.
Finally, lets make this private
as it is not being accessed from the outside, and I don't think it needs to be exposed.
So I'd go for:
private get cacheKey() {
const keyPrefix = this.platform === DevicePlatform.Android ? ANDROID_BUILD_CACHE_KEY : IOS_BUILD_CACHE_KEY;
return `${keyPrefix}:{this.appRoot}`
}
@@ -671,7 +671,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(); | |||
const buildCache = new BuildCache(platform, getAppRootFolder()); |
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.
would it be possible to restructure the code in a way that we don't have to instantiate the new cache object here? This doesn't matter at this point because the class is stateless, but may be a source of errors in the future if we end up changing that.
|
||
extensionContext.globalState.update(appRoot + platformKey, cache); | ||
|
||
// remove the old cache afterwords |
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.
// remove the old cache afterwords | |
// remove the old cache afterwards |
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.
This PR changes a way we manage build caches. Previous approach was dependent on current vscode workspace, which led to build caches not being accessible when switching from working in the root of the workspace to the root of the application (or doing the same in reverse). The new approach saves this data in global storage, and adds a appRoot identifier to the cache key.
This PR is a dependency for #663
How Has This Been Tested:
Verify that the build cache migration work: