Skip to content

Commit

Permalink
Use bundle and add proper logging for pod installation (#617)
Browse files Browse the repository at this point in the history
This is a draft as it is build on top of #615

This PR refactors the way we install pods in the IDE. The main objective
is to optionally use `bundle` command. This is similar to the changes
proposed in #304 but apart from using bundle for the diagnostics check,
we also use it when installing (which is a mechanism that wasn't in
place when #304 was created).

The second main change that we're making is that we trigger pod
installation when clean build is requested. Before that wasn't
happening.

On top of that, we are including pod command output in the iOS build
log. This will help diagnose potential problems better and will also
provide some feedback for the scenarios when installation take very
long.

Finally, we are fixing the startup message component such that it resets
long wait flag and we are also adding a more prominent message "open
logs" for the native build phase. This way it should be easier to spot
for the users that they can see the build output in case the build takes
long:
<img width="360" alt="image"
src="https://github.com/user-attachments/assets/64ea681c-1b9a-498c-98ef-c4afbe2826fc">


### How Has This Been Tested: 
1. Run clean build in on of the test apps for iOS. See cocoapods in the
log output
2. Run RN 76 example on iOS to see bundle being used there instead of
pod install (check logs)
3. When building on iOS, see that the "open logs" text appears in the
startup message and you can click it to open the logs output.
  • Loading branch information
kmagiera authored Oct 14, 2024
1 parent 0284dd7 commit ddd99f8
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 31 deletions.
23 changes: 17 additions & 6 deletions packages/vscode-extension/src/builders/BuildManager.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;

Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand Down
1 change: 0 additions & 1 deletion packages/vscode-extension/src/builders/buildAndroid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 0 additions & 1 deletion packages/vscode-extension/src/builders/buildIOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
32 changes: 22 additions & 10 deletions packages/vscode-extension/src/dependency/DependencyManager.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);

Expand All @@ -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 });
Expand Down Expand Up @@ -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()),
Expand Down
28 changes: 15 additions & 13 deletions packages/vscode-extension/src/webview/components/PreviewLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<NodeJS.Timeout | undefined>(undefined);

useEffect(() => {
if (projectState.startupMessage === StartupMessage.Restarting) {
Expand All @@ -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() {
Expand All @@ -83,6 +82,9 @@ function PreviewLoader({ onRequestShowPreview }: { onRequestShowPreview: () => v
isLoadingSlowly && "preview-loader-slow-progress"
)}>
{projectState.startupMessage}
{isLoadingSlowly && projectState.startupMessage === StartupMessage.Building
? " (open logs)"
: ""}
</StartupMessageComponent>
{projectState.stageProgress !== undefined && (
<div className="preview-loader-stage-progress">
Expand Down

0 comments on commit ddd99f8

Please sign in to comment.