Skip to content

Commit

Permalink
feat: run ios build directly from build folder
Browse files Browse the repository at this point in the history
  • Loading branch information
rams23 committed Oct 17, 2023
1 parent aa25a73 commit 850de25
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 59 deletions.
46 changes: 29 additions & 17 deletions packages/cli/src/application/iosUtils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { executeCommandWithOutPut, getAppName, getRootDestinationFolder } from './utils';
import { executeCommandWithOutPut, getAppName, getBuildFolderByBuildId, getRootDestinationFolder } from "./utils";
import path from "path";

export const iosBuildPlatforms = {
simulator: {
name: 'iphonesimulator',
ext: 'app',
buildCmd: 'build',
name: "iphonesimulator",
ext: "app",
buildCmd: "build"
},
iphone: {
name: 'iphoneos',
ext: 'ipa',
buildCmd: 'archive',
},
name: "iphoneos",
ext: "ipa",
buildCmd: "archive"
}
};

export type IosPlatform = {
Expand All @@ -19,10 +20,21 @@ export type IosPlatform = {
buildCmd: string;
};

export function getIosBuildDestination(platform: { ext: string; buildCmd: string; name: string }, buildType: string) {
export function getIosBuildDestination(platform: {
ext: string;
buildCmd: string;
name: string
}, buildType: string, buildId?: string) {
const baseBuildFolder = buildId ? getBuildFolderByBuildId(buildId) : getRootDestinationFolder();

// todo handle Debug/release
const destinationDir = `${getRootDestinationFolder()}/ios/${platform.name}-${buildType}/Debug`;
const destination = `${destinationDir}/${getAppName()}.${platform.ext}`;
const destinationDir = path.join(
baseBuildFolder,
'ios',
`${platform.name}-${buildType}`,
'Debug'
);
const destination = path.join(destinationDir, `${getAppName()}.${platform.ext}`);
return { destinationDir, destination };
}

Expand All @@ -32,14 +44,14 @@ function getTargetPaths(buildSettings: string) {
// Find app in all building settings - look for WRAPPER_EXTENSION: 'app',
for (const i in settings) {
const wrapperExtension = settings[i].buildSettings.WRAPPER_EXTENSION;
if (wrapperExtension === 'app') {
if (wrapperExtension === "app") {
return {
targetBuildDir: settings[i].buildSettings.TARGET_BUILD_DIR,
executableFolderPath: settings[i].buildSettings.EXECUTABLE_FOLDER_PATH,
executableFolderPath: settings[i].buildSettings.EXECUTABLE_FOLDER_PATH
};
}
}
throw new Error('app destination not found');
throw new Error("app destination not found");
}

export function getBuildFolder(projectName: string, release: boolean, buildFlavor: string, sdk: string) {
Expand All @@ -49,12 +61,12 @@ export function getBuildFolder(projectName: string, release: boolean, buildFlavo
-workspace ./ios/${projectName}.xcworkspace \
-scheme ${buildFlavor} \
-sdk ${sdk}\
-configuration ${release ? 'Release' : 'Debug'} \
-configuration ${release ? "Release" : "Debug"} \
-showBuildSettings\
-json`,
{
encoding: 'utf8',
},
encoding: "utf8"
}
);
const { executableFolderPath } = getTargetPaths(buildSettings as string);

Expand Down
90 changes: 49 additions & 41 deletions packages/cli/src/application/runIos.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,64 @@
import childProcess, { execFileSync } from 'child_process';
import fs from 'fs';
import prompts from 'prompts';
import chalk from 'chalk';
import { executeCommand, getAppName, getProjectRootDir } from './utils';
import { buildIos } from './buildIos';
import { getIosBuildDestination, iosBuildPlatforms, IosPlatform } from './iosUtils';
import path from 'path';
import { getIosFlavors } from './config';
import logger from './logger';
import childProcess, { execFileSync } from "child_process";
import fs from "fs";
import prompts from "prompts";
import chalk from "chalk";
import { executeCommand, getAppName, getProjectRootDir } from "./utils";
import { buildIos } from "./buildIos";
import { getIosBuildDestination, iosBuildPlatforms, IosPlatform } from "./iosUtils";
import path from "path";
import { getIosFlavors } from "./config";
import logger from "./logger";

// todo default app name improve passing from command line

type Device = {
state: 'Booted' | 'shutdown';
state: "Booted" | "shutdown";
name: string;
udid: string;
type: 'simulator'; // todo
type: "simulator"; // todo
};

function getDevices(): Device[] {
const devicesJson = JSON.parse(childProcess.execSync('xcrun simctl list -j devices', { encoding: 'utf-8' }));
const devicesJson = JSON.parse(childProcess.execSync("xcrun simctl list -j devices", { encoding: "utf-8" }));
return Object.values(devicesJson.devices).flat() as Device[];
}

function getBootedDevices(allDevices: Device[]) {
return allDevices.filter(d => d.state === 'Booted');
return allDevices.filter(d => d.state === "Booted");
}

function checkBuildPresent(buildType: string, target: any) {
const { destination } = getIosBuildDestination(target, buildType);
function checkBuildPresent(buildType: string, target: {
ext: string;
buildCmd: string;
name: string
}, buildId?: string) {
const { destination } = getIosBuildDestination(target, buildType, buildId);
return fs.existsSync(destination);
}

function installApp(deviceUdid: string, buildType: string, target: any) {
const { destination } = getIosBuildDestination(target, buildType);
const res = childProcess.execSync(`xcrun simctl install ${deviceUdid} ${destination}`, { encoding: 'utf-8' });
function installApp(deviceUdid: string, buildType: string, target: { ext: string; buildCmd: string; name: string }, buildId?: string) {
const { destination } = getIosBuildDestination(target, buildType, buildId);
const res = childProcess.execSync(`xcrun simctl install ${deviceUdid} ${destination}`, { encoding: "utf-8" });
}

function launchApp(deviceUid: string, bundleId: string) {
childProcess.execSync(`xcrun simctl launch ${deviceUid} ${bundleId}`);
}

function isErrorWithStderr(e: unknown): e is { stderr: { toString(): string } } {
if (typeof e === 'object' && e !== null && 'stderr' in e) {
return typeof (e as any).stderr.toString === 'function';
if (typeof e === "object" && e !== null && "stderr" in e) {
return typeof (e as any).stderr.toString === "function";
}
return false;
}

function launchDevice(deviceUid: string) {
try {
childProcess.execSync(['xcrun', 'simctl', 'boot', deviceUid].join(' '));
childProcess.execSync(["xcrun", "simctl", "boot", deviceUid].join(" "));
} catch (e) {
if (isErrorWithStderr(e)) {
const err = e.stderr.toString();
if (err.includes('Unable to boot device in current state: Booted')) {
if (err.includes("Unable to boot device in current state: Booted")) {
return;
}
}
Expand All @@ -64,16 +68,16 @@ function launchDevice(deviceUid: string) {

async function promptForDeviceSelection(allDevices: Device[]): Promise<Device[]> {
const { devices } = await prompts({
type: 'multiselect',
name: 'devices',
message: 'Select the device / emulator you want to use',
type: "multiselect",
name: "devices",
message: "Select the device / emulator you want to use",
choices: allDevices.map(d => ({
title: `${chalk.bold(`(${d.type})`)} ${chalk.green(`${d.name}`)} (${
d.state === 'Booted' ? 'connected' : 'disconnected'
d.state === "Booted" ? "connected" : "disconnected"
})`,
value: d,
value: d
})),
min: 1,
min: 1
});
return devices;
}
Expand All @@ -82,24 +86,28 @@ export async function runApp(
buildType?: string,
iosPlatform: IosPlatform = iosBuildPlatforms.simulator,
forceBuild?: boolean,
buildId?: string
) {
const buildFlavor = getIosFlavors(buildType);

if (forceBuild || !checkBuildPresent(buildFlavor.scheme, iosPlatform)) {
logger.info('Build not present, starting build');
if (forceBuild || !checkBuildPresent(buildFlavor.scheme, iosPlatform, buildId)) {
logger.info("Build not present, starting build");
if (buildId) {
throw new Error(`The requested build id ${buildId} does not contain an ios build for scheme ${buildFlavor}`);
}
buildIos(buildType, iosPlatform);
} else {
logger.info('Build already present, skipping build');
logger.info("Build already present, skipping build");
}

const devices = getDevices();

if (devices.length === 0) {
throw new Error('No iOS devices available');
throw new Error("No iOS devices available");
}

// todo improve run on device
executeCommand('open -a Simulator');
executeCommand("open -a Simulator");

let bootedDevices = getBootedDevices(devices);

Expand All @@ -111,27 +119,27 @@ export async function runApp(
const requestedDevices = await promptForDeviceSelection(devices);

for (const requestedDevice of requestedDevices) {
if (!(requestedDevice.state === 'Booted')) {
if (!(requestedDevice.state === "Booted")) {
launchDevice(requestedDevice.udid);
}
}
// start not connected ones
devicesToRun.push(...requestedDevices);
}

const { destination } = getIosBuildDestination(iosPlatform, buildFlavor.scheme);
const { destination } = getIosBuildDestination(iosPlatform, buildFlavor.scheme, buildId);

const bundleID = execFileSync(
'/usr/libexec/PlistBuddy',
['-c', 'Print:CFBundleIdentifier', path.join(destination, 'Info.plist')],
"/usr/libexec/PlistBuddy",
["-c", "Print:CFBundleIdentifier", path.join(destination, "Info.plist")],
{
encoding: 'utf8',
},
encoding: "utf8"
}
).trim();

for (const device of devicesToRun) {
const id = device.udid;
installApp(id, buildFlavor.scheme, iosPlatform);
installApp(id, buildFlavor.scheme, iosPlatform, buildId);
launchApp(id, bundleID);
}
}
6 changes: 5 additions & 1 deletion packages/cli/src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export default class Run extends RemoteAwareCommand {
const start = performance.now();
logger.info('Checking if metro is running...');

if (buildId && forceBuild){
throw new Error('You cannot specify a buildId and force a rebuild at the same time');
}

if (buildId) {
logger.info(`Requested to run specific id ${buildId}`);
buildId = await downloadBuildIfNotPresent(buildId, this.currentProject);
Expand All @@ -58,7 +62,7 @@ export default class Run extends RemoteAwareCommand {
}
if (shouldRunIos) {
logger.info(`Running ios app ${buildFlavor ? `with flavor ${buildFlavor}` : ''}`);
await runIos(buildFlavor!, iosBuildPlatforms.simulator, forceBuild);
await runIos(buildFlavor!, iosBuildPlatforms.simulator, forceBuild, buildId);
}
logger.info(`Run finished in ${((performance.now() - start) / 1000).toFixed(1)} seconds`);
this.exit(0);
Expand Down

0 comments on commit 850de25

Please sign in to comment.