Skip to content
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

MultiRoot Workspace support #663

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions packages/vscode-extension/src/builders/BuildCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from "path";
import fs from "fs";
import { createFingerprintAsync } from "@expo/fingerprint";
import { Logger } from "../Logger";
import { extensionContext, getAppRootFolder } from "../utilities/extensionContext";
import { AppRootFolder, extensionContext } from "../utilities/extensionContext";
import { DevicePlatform } from "../common/DeviceManager";
import { IOSBuildResult } from "./buildIOS";
import { AndroidBuildResult } from "./buildAndroid";
Expand Down Expand Up @@ -37,12 +37,15 @@ function makeCacheKey(platform: DevicePlatform, appRoot: string) {
}

export class BuildCache {
private readonly cacheKey: string;

constructor(private readonly platform: DevicePlatform, private readonly appRoot: string) {
this.cacheKey = makeCacheKey(platform, appRoot);
private get cacheKey() {
return makeCacheKey(this.platform, this.appRootFolder.getAppRoot());
}

constructor(
private readonly platform: DevicePlatform,
private readonly appRootFolder: AppRootFolder
) {}

/**
* Passed fingerprint should be calculated at the time build is started.
*/
Expand Down Expand Up @@ -112,7 +115,7 @@ export class BuildCache {
return customFingerprint;
}

const fingerprint = await createFingerprintAsync(getAppRootFolder(), {
const fingerprint = await createFingerprintAsync(this.appRootFolder.getAppRoot(), {
ignorePaths: IGNORE_PATHS,
});
Logger.debug("App folder fingerprint", fingerprint.hash);
Expand All @@ -134,7 +137,11 @@ export class BuildCache {
}

Logger.debug(`Using custom fingerprint script '${fingerprintCommand}'`);
const fingerprint = await runfingerprintCommand(fingerprintCommand, env);
const fingerprint = await runfingerprintCommand(
fingerprintCommand,
env,
this.appRootFolder.getAppRoot()
);

if (!fingerprint) {
throw new Error("Failed to generate application fingerprint using custom script.");
Expand All @@ -153,10 +160,8 @@ async function getAppHash(appPath: string) {
return (await calculateMD5(appPath)).digest("hex");
}

export async function migrateOldBuildCachesToNewStorage() {
export async function migrateOldBuildCachesToNewStorage(appRoot: string) {
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;
Expand Down
8 changes: 4 additions & 4 deletions packages/vscode-extension/src/builders/BuildManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { BuildCache } from "./BuildCache";
import { AndroidBuildResult, buildAndroid } from "./buildAndroid";
import { IOSBuildResult, buildIos } from "./buildIOS";
import { DeviceInfo, DevicePlatform } from "../common/DeviceManager";
import { getAppRootFolder } from "../utilities/extensionContext";
import { DependencyManager } from "../dependency/DependencyManager";
import { CancelToken } from "./cancelToken";
import { getTelemetryReporter } from "../utilities/telemetry";
Expand All @@ -16,6 +15,7 @@ export interface DisposableBuild<R> extends Disposable {
}

type BuildOptions = {
appRoot: string;
clean: boolean;
progressListener: (newProgress: number) => void;
onSuccess: () => void;
Expand Down Expand Up @@ -47,7 +47,7 @@ export class BuildManager {
}

public startBuild(deviceInfo: DeviceInfo, options: BuildOptions): DisposableBuild<BuildResult> {
const { clean: forceCleanBuild, progressListener, onSuccess } = options;
const { clean: forceCleanBuild, progressListener, onSuccess, appRoot } = options;
const { platform } = deviceInfo;

getTelemetryReporter().sendTelemetryEvent("build:requested", {
Expand Down Expand Up @@ -95,7 +95,7 @@ export class BuildManager {
});
this.buildOutputChannel.clear();
buildResult = await buildAndroid(
getAppRootFolder(),
appRoot,
forceCleanBuild,
cancelToken,
this.buildOutputChannel,
Expand Down Expand Up @@ -128,7 +128,7 @@ export class BuildManager {
}
};
buildResult = await buildIos(
getAppRootFolder(),
appRoot,
forceCleanBuild,
cancelToken,
this.buildOutputChannel,
Expand Down
17 changes: 9 additions & 8 deletions packages/vscode-extension/src/builders/buildAndroid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function makeBuildTaskName(productFlavor: string, buildType: string) {
}

export async function buildAndroid(
appRootFolder: string,
appRoot: string,
forceCleanBuild: boolean,
cancelToken: CancelToken,
outputChannel: OutputChannel,
Expand All @@ -97,7 +97,8 @@ export async function buildAndroid(
cancelToken,
customBuild.android.buildCommand,
env,
DevicePlatform.Android
DevicePlatform.Android,
appRoot
);
if (!apkPath) {
throw new Error("Failed to build Android app using custom script.");
Expand All @@ -114,7 +115,7 @@ export async function buildAndroid(
getTelemetryReporter().sendTelemetryEvent("build:eas-build-requested", {
platform: DevicePlatform.Android,
});
const apkPath = await fetchEasBuild(cancelToken, eas.android, DevicePlatform.Android);
const apkPath = await fetchEasBuild(cancelToken, eas.android, DevicePlatform.Android, appRoot);
if (!apkPath) {
throw new Error("Failed to build Android app using EAS build.");
}
Expand All @@ -126,11 +127,11 @@ export async function buildAndroid(
};
}

if (await isExpoGoProject()) {
if (await isExpoGoProject(appRoot)) {
getTelemetryReporter().sendTelemetryEvent("build:expo-go-requested", {
platform: DevicePlatform.Android,
});
const apkPath = await downloadExpoGo(DevicePlatform.Android, cancelToken);
const apkPath = await downloadExpoGo(DevicePlatform.Android, cancelToken, appRoot);
return { apkPath, packageName: EXPO_GO_PACKAGE_NAME, platform: DevicePlatform.Android };
}

Expand All @@ -140,7 +141,7 @@ export async function buildAndroid(
);
}

const androidSourceDir = getAndroidSourceDir(appRootFolder);
const androidSourceDir = getAndroidSourceDir(appRoot);
const productFlavor = android?.productFlavor || "";
const buildType = android?.buildType || "debug";
const gradleArgs = [
Expand All @@ -158,7 +159,7 @@ export async function buildAndroid(
),
];
// configureReactNativeOverrides init script is only necessary for RN versions older then 0.74.0 see comments in configureReactNativeOverrides.gradle for more details
if (semver.lt(getReactNativeVersion(), "0.74.0")) {
if (semver.lt(getReactNativeVersion(appRoot), "0.74.0")) {
gradleArgs.push(
"--init-script", // configureReactNativeOverrides init script is used to patch React Android project, see comments in configureReactNativeOverrides.gradle for more details
path.join(
Expand Down Expand Up @@ -186,6 +187,6 @@ export async function buildAndroid(

await buildProcess;
Logger.debug("Android build successful");
const apkInfo = await getAndroidBuildPaths(appRootFolder, cancelToken, productFlavor, buildType);
const apkInfo = await getAndroidBuildPaths(appRoot, cancelToken, productFlavor, buildType);
return { ...apkInfo, platform: DevicePlatform.Android };
}
15 changes: 8 additions & 7 deletions packages/vscode-extension/src/builders/buildIOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function buildProject(
}

export async function buildIos(
appRootFolder: string,
appRoot: string,
forceCleanBuild: boolean,
cancelToken: CancelToken,
outputChannel: OutputChannel,
Expand All @@ -97,7 +97,8 @@ export async function buildIos(
cancelToken,
customBuild.ios.buildCommand,
env,
DevicePlatform.IOS
DevicePlatform.IOS,
appRoot
);
if (!appPath) {
throw new Error("Failed to build iOS app using custom script.");
Expand All @@ -114,7 +115,7 @@ export async function buildIos(
getTelemetryReporter().sendTelemetryEvent("build:eas-build-requested", {
platform: DevicePlatform.IOS,
});
const appPath = await fetchEasBuild(cancelToken, eas.ios, DevicePlatform.IOS);
const appPath = await fetchEasBuild(cancelToken, eas.ios, DevicePlatform.IOS, appRoot);
if (!appPath) {
throw new Error("Failed to build iOS app using EAS build.");
}
Expand All @@ -126,11 +127,11 @@ export async function buildIos(
};
}

if (await isExpoGoProject()) {
if (await isExpoGoProject(appRoot)) {
getTelemetryReporter().sendTelemetryEvent("build:expo-go-requested", {
platform: DevicePlatform.IOS,
});
const appPath = await downloadExpoGo(DevicePlatform.IOS, cancelToken);
const appPath = await downloadExpoGo(DevicePlatform.IOS, cancelToken, appRoot);
return { appPath, bundleID: EXPO_GO_BUNDLE_ID, platform: DevicePlatform.IOS };
}

Expand All @@ -140,11 +141,11 @@ export async function buildIos(
);
}

const sourceDir = getIosSourceDir(appRootFolder);
const sourceDir = getIosSourceDir(appRoot);

await installPodsIfNeeded();

const xcodeProject = await findXcodeProject(appRootFolder);
const xcodeProject = await findXcodeProject(appRoot);

if (!xcodeProject) {
throw new Error(`Could not find Xcode project files in "${sourceDir}" folder`);
Expand Down
19 changes: 12 additions & 7 deletions packages/vscode-extension/src/builders/customBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { mkdtemp } from "fs/promises";
import { Logger } from "../Logger";
import { command, lineReader } from "../utilities/subprocess";
import { CancelToken } from "./cancelToken";
import { getAppRootFolder } from "../utilities/extensionContext";
import { extractTarApp, isApkFile, isAppFile } from "./utils";
import { DevicePlatform } from "../common/DeviceManager";

Expand All @@ -18,9 +17,10 @@ export async function runExternalBuild(
cancelToken: CancelToken,
buildCommand: string,
env: Env,
platform: DevicePlatform
platform: DevicePlatform,
appRoot: string
) {
const output = await runExternalScript(buildCommand, env, cancelToken);
const output = await runExternalScript(buildCommand, env, appRoot, cancelToken);

if (!output) {
return undefined;
Expand Down Expand Up @@ -64,16 +64,21 @@ export async function runExternalBuild(
return binaryPath;
}

export async function runfingerprintCommand(externalCommand: string, env: Env) {
const output = await runExternalScript(externalCommand, env);
export async function runfingerprintCommand(externalCommand: string, env: Env, appRoot: string) {
const output = await runExternalScript(externalCommand, env, appRoot);
if (!output) {
return undefined;
}
return output.lastLine;
}

async function runExternalScript(externalCommand: string, env: Env, cancelToken?: CancelToken) {
let process = command(externalCommand, { cwd: getAppRootFolder(), env });
async function runExternalScript(
externalCommand: string,
env: Env,
appRoot: string,
cancelToken?: CancelToken
) {
let process = command(externalCommand, { cwd: appRoot, env });
process = cancelToken ? cancelToken.adapt(process) : process;
Logger.info(`Running external script: ${externalCommand}`);

Expand Down
11 changes: 6 additions & 5 deletions packages/vscode-extension/src/builders/eas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import { extractTarApp } from "./utils";
export async function fetchEasBuild(
cancelToken: CancelToken,
config: EasConfig,
platform: DevicePlatform
platform: DevicePlatform,
appRoot: string
): Promise<string | undefined> {
const build = await fetchBuild(config, platform);
const build = await fetchBuild(config, platform, appRoot);
if (!build) {
return undefined;
}
Expand All @@ -30,9 +31,9 @@ export async function fetchEasBuild(
return easBinaryPath;
}

async function fetchBuild(config: EasConfig, platform: DevicePlatform) {
async function fetchBuild(config: EasConfig, platform: DevicePlatform, appRoot: string) {
if (config.buildUUID) {
const build = await viewEasBuild(config.buildUUID, platform);
const build = await viewEasBuild(config.buildUUID, platform, appRoot);
if (!build) {
Logger.error(
`Failed to find EAS build artifact with ID ${config.buildUUID} for platform ${platform}.`
Expand All @@ -48,7 +49,7 @@ async function fetchBuild(config: EasConfig, platform: DevicePlatform) {
return build;
}

const builds = await listEasBuilds(platform, config.profile);
const builds = await listEasBuilds(platform, config.profile, appRoot);
if (!builds || builds.length === 0) {
Logger.error(
`Failed to find any EAS build artifacts for ${platform} with ${config.profile} profile. If you're building iOS app, make sure you set '"ios.simulator": true' option in eas.json.`
Expand Down
9 changes: 4 additions & 5 deletions packages/vscode-extension/src/builders/easCommand.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { DevicePlatform } from "../common/DeviceManager";
import { getAppRootFolder } from "../utilities/extensionContext";
import { exec } from "../utilities/subprocess";

type UnixTimestamp = number;
Expand Down Expand Up @@ -53,7 +52,7 @@ type EASBuildJson = {
isForIosSimulator: false;
};

export async function listEasBuilds(platform: DevicePlatform, profile: string) {
export async function listEasBuilds(platform: DevicePlatform, profile: string, appRoot: string) {
const platformMapping = { [DevicePlatform.Android]: "android", [DevicePlatform.IOS]: "ios" };

const { stdout } = await exec(
Expand All @@ -67,14 +66,14 @@ export async function listEasBuilds(platform: DevicePlatform, profile: string) {
"--profile",
profile,
],
{ cwd: getAppRootFolder() }
{ cwd: appRoot }
);
return parseEasBuildOutput(stdout, platform);
}

export async function viewEasBuild(buildUUID: UUID, platform: DevicePlatform) {
export async function viewEasBuild(buildUUID: UUID, platform: DevicePlatform, appRoot: string) {
const { stdout } = await exec("eas", ["build:view", buildUUID, "--json"], {
cwd: getAppRootFolder(),
cwd: appRoot,
});
return parseEasBuildOutput(stdout, platform)?.at(0);
}
Expand Down
15 changes: 9 additions & 6 deletions packages/vscode-extension/src/builders/expoGo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from "path";
import http from "http";
import fs from "fs";
import { extensionContext, getAppRootFolder } from "../utilities/extensionContext";
import { extensionContext } from "../utilities/extensionContext";
import { exec } from "../utilities/subprocess";
import { DevicePlatform } from "../common/DeviceManager";
import { CancelToken } from "./cancelToken";
Expand All @@ -15,7 +15,7 @@ function fileExists(filePath: string, ...additionalPaths: string[]) {
return fs.existsSync(path.join(filePath, ...additionalPaths));
}

export async function isExpoGoProject(): Promise<boolean> {
export async function isExpoGoProject(appRoot: string): Promise<boolean> {
// There is no straightforward way to tell apart different react native project
// setups. i.e. expo-go, expo-dev-client, bare react native, etc.
// Here, we are using a heuristic to determine if the project is expo-go based
Expand All @@ -24,7 +24,6 @@ export async function isExpoGoProject(): Promise<boolean> {
// 2) The project doesn't have an android or ios folder
// 3) The expo_go_project_tester.js script runs successfully – the script uses expo-cli
// internals to resolve project config and tells expo-go and dev-client apart.
const appRoot = getAppRootFolder();

if (!fileExists(appRoot, "app.json") && !fileExists(appRoot, "app.config.js")) {
// app.json or app.config.js is required for expo-go projects
Expand All @@ -43,7 +42,7 @@ export async function isExpoGoProject(): Promise<boolean> {
);
try {
const result = await exec("node", [expoGoProjectTesterScript], {
cwd: getAppRootFolder(),
cwd: appRoot,
allowNonZeroExit: true,
});
return result.exitCode === 0;
Expand Down Expand Up @@ -89,11 +88,15 @@ export function fetchExpoLaunchDeeplink(
});
}

export async function downloadExpoGo(platform: DevicePlatform, cancelToken: CancelToken) {
export async function downloadExpoGo(
platform: DevicePlatform,
cancelToken: CancelToken,
appRoot: string
) {
const downloadScript = path.join(extensionContext.extensionPath, "lib", "expo_go_download.js");
const { stdout } = await cancelToken.adapt(
exec("node", [downloadScript, platform], {
cwd: getAppRootFolder(),
cwd: appRoot,
})
);

Expand Down
Loading
Loading