diff --git a/packages/vscode-extension/src/builders/BuildManager.ts b/packages/vscode-extension/src/builders/BuildManager.ts index a574cf854..02f9a459e 100644 --- a/packages/vscode-extension/src/builders/BuildManager.ts +++ b/packages/vscode-extension/src/builders/BuildManager.ts @@ -1,5 +1,4 @@ import { Disposable, OutputChannel, window } from "vscode"; -import { Logger } from "../Logger"; import { PlatformBuildCache } from "./PlatformBuildCache"; import { AndroidBuildResult, buildAndroid } from "./buildAndroid"; import { IOSBuildResult, buildIos } from "./buildIOS"; @@ -8,6 +7,7 @@ import { getAppRootFolder } from "../utilities/extensionContext"; import { DependencyManager } from "../dependency/DependencyManager"; import { CancelToken } from "./cancelToken"; import { getTelemetryReporter } from "../utilities/telemetry"; +import { Logger } from "../Logger"; export type BuildResult = IOSBuildResult | AndroidBuildResult; @@ -69,6 +69,7 @@ export class BuildManager { this.buildOutputChannel = window.createOutputChannel("Radon IDE (Android build)", { log: true, }); + this.buildOutputChannel.clear(); buildResult = await buildAndroid( getAppRootFolder(), forceCleanBuild, @@ -78,15 +79,25 @@ export class BuildManager { this.dependencyManager ); } else { - this.buildOutputChannel = window.createOutputChannel("Radon IDE (iOS build)", { + const iOSBuildOutputChannel = window.createOutputChannel("Radon IDE (iOS build)", { log: true, }); + this.buildOutputChannel = iOSBuildOutputChannel; + this.buildOutputChannel.clear(); const installPodsIfNeeded = async () => { - const podsInstalled = await this.dependencyManager.checkPodsInstallationStatus(); - if (!podsInstalled) { - Logger.info("Pods installation is missing or outdated. Installing Pods."); + let installPods = forceCleanBuild; + if (installPods) { + Logger.info("Clean build requested: installing pods"); + } else { + const podsInstalled = await this.dependencyManager.checkPodsInstallationStatus(); + if (!podsInstalled) { + Logger.info("Pods installation is missing or outdated. Installing Pods."); + installPods = true; + } + } + if (installPods) { getTelemetryReporter().sendTelemetryEvent("build:install-pods", { platform }); - await this.dependencyManager.installPods(cancelToken); + 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(); diff --git a/packages/vscode-extension/src/builders/buildAndroid.ts b/packages/vscode-extension/src/builders/buildAndroid.ts index 586884896..4a0af62f2 100644 --- a/packages/vscode-extension/src/builders/buildAndroid.ts +++ b/packages/vscode-extension/src/builders/buildAndroid.ts @@ -163,7 +163,6 @@ export async function buildAndroid( }) ); const buildAndroidProgressProcessor = new BuildAndroidProgressProcessor(progressListener); - outputChannel.clear(); lineReader(buildProcess).onLineRead((line) => { outputChannel.appendLine(line); buildAndroidProgressProcessor.processLine(line); diff --git a/packages/vscode-extension/src/builders/buildIOS.ts b/packages/vscode-extension/src/builders/buildIOS.ts index c53dd56ef..7491b8f11 100644 --- a/packages/vscode-extension/src/builders/buildIOS.ts +++ b/packages/vscode-extension/src/builders/buildIOS.ts @@ -158,7 +158,6 @@ export async function buildIos( ); const buildIOSProgressProcessor = new BuildIOSProgressProcessor(progressListener); - outputChannel.clear(); lineReader(buildProcess).onLineRead((line) => { outputChannel.appendLine(line); buildIOSProgressProcessor.processLine(line); diff --git a/packages/vscode-extension/src/dependency/DependencyManager.ts b/packages/vscode-extension/src/dependency/DependencyManager.ts index cc9dc32cb..e86e619be 100644 --- a/packages/vscode-extension/src/dependency/DependencyManager.ts +++ b/packages/vscode-extension/src/dependency/DependencyManager.ts @@ -1,11 +1,11 @@ import fs from "fs"; import path from "path"; import { EventEmitter } from "stream"; -import { Disposable } from "vscode"; +import { Disposable, OutputChannel } from "vscode"; import semver, { SemVer } from "semver"; import { Logger } from "../Logger"; import { EMULATOR_BINARY } from "../devices/AndroidEmulatorDevice"; -import { command } from "../utilities/subprocess"; +import { command, lineReader } from "../utilities/subprocess"; import { extensionContext, getAppRootFolder } from "../utilities/extensionContext"; import { getIosSourceDir } from "../builders/buildIOS"; import { isExpoGoProject } from "../builders/expoGo"; @@ -155,7 +155,7 @@ export class DependencyManager implements Disposable, DependencyManagerInterface return true; } - public async installPods(cancelToken: CancelToken) { + public async installPods(buildOutputChannel: OutputChannel, cancelToken: CancelToken) { const appRootFolder = getAppRootFolder(); const iosDirPath = getIosSourceDir(appRootFolder); @@ -164,16 +164,15 @@ export class DependencyManager implements Disposable, DependencyManagerInterface throw new Error("ios directory was not found inside the workspace."); } - const commandInIosDir = (args: string) => { + try { const env = getLaunchConfiguration().env; - return command(args, { + const shouldUseBundle = await this.shouldUseBundleCommand(); + const process = command(shouldUseBundle ? "bundle exec pod install" : "pod install", { cwd: iosDirPath, env: { ...env, LANG: "en_US.UTF-8" }, }); - }; - - try { - await cancelToken.adapt(commandInIosDir("pod install")); + lineReader(process).onLineRead((line) => buildOutputChannel.appendLine(line)); + await cancelToken.adapt(process); } catch (e) { Logger.error("Pods not installed", e); this.emitEvent("pods", { status: "notInstalled", isOptional: false }); @@ -219,8 +218,21 @@ export class DependencyManager implements Disposable, DependencyManagerInterface }); } + private async shouldUseBundleCommand() { + const gemfile = path.join(getAppRootFolder(), "Gemfile"); + try { + await fs.promises.access(gemfile); + return true; + } catch (e) { + return false; + } + } + private async checkPodsCommandStatus() { - const installed = await testCommand("pod --version"); + const shouldUseBundle = await this.shouldUseBundleCommand(); + const installed = await testCommand( + shouldUseBundle ? "bundle exec pod --version" : "pod --version" + ); this.emitEvent("cocoaPods", { status: installed ? "installed" : "notInstalled", isOptional: !(await projectRequiresNativeBuild()), diff --git a/packages/vscode-extension/src/webview/components/PreviewLoader.tsx b/packages/vscode-extension/src/webview/components/PreviewLoader.tsx index 69979cffb..88bc1f793 100644 --- a/packages/vscode-extension/src/webview/components/PreviewLoader.tsx +++ b/packages/vscode-extension/src/webview/components/PreviewLoader.tsx @@ -14,13 +14,11 @@ const startupStageWeightSum = StartupStageWeight.map((item) => item.weight).redu 0 ); -const slowLoadingThresholdMs = 30_000; function PreviewLoader({ onRequestShowPreview }: { onRequestShowPreview: () => void }) { const { projectState, project } = useProject(); const [progress, setProgress] = useState(0); const [isLoadingSlowly, setIsLoadingSlowly] = useState(false); - const isLoadingSlowlyTimeout = useRef(undefined); useEffect(() => { if (projectState.startupMessage === StartupMessage.Restarting) { @@ -47,20 +45,21 @@ function PreviewLoader({ onRequestShowPreview }: { onRequestShowPreview: () => v } }, [projectState]); - // Order of status and startupMessage effects must be preserved. useEffect(() => { - clearTimeout(isLoadingSlowlyTimeout.current); - }, [projectState.status]); + setIsLoadingSlowly(false); - useEffect(() => { - clearTimeout(isLoadingSlowlyTimeout.current); - // We skip reporting slow builds, this is the only most time-consuming - // task and times varies from project to project. - if (projectState.startupMessage !== StartupMessage.Building) { - isLoadingSlowlyTimeout.current = setTimeout(() => { - setIsLoadingSlowly(true); - }, slowLoadingThresholdMs); + // we show the slow loading message after 30 seconds for each phase, + // but for the native build phase we show it after 5 seconds. + let timeoutMs = 30_000; + if (projectState.startupMessage === StartupMessage.Building) { + timeoutMs = 5_000; } + + const timeoutHandle = setTimeout(() => { + setIsLoadingSlowly(true); + }, timeoutMs); + + return () => timeoutHandle && clearTimeout(timeoutHandle); }, [projectState.startupMessage]); function handleLoaderClick() { @@ -83,6 +82,9 @@ function PreviewLoader({ onRequestShowPreview }: { onRequestShowPreview: () => v isLoadingSlowly && "preview-loader-slow-progress" )}> {projectState.startupMessage} + {isLoadingSlowly && projectState.startupMessage === StartupMessage.Building + ? " (open logs)" + : ""} {projectState.stageProgress !== undefined && (