Skip to content

Commit

Permalink
feat: run android 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 d4dc268 commit 343f9f2
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 56 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repack it with the new JS bundle.
For now, the developers should choose if the native build should be forced or not. In the future, the cli tool may
add some heuristics to decide when to force the native build.

![Run android with build cached](./docs/assets/run-android-build-cached.png)
![Run android with build cached](./packages/cli/docs/assets/run-android-build-cached.png)

## Disclaimer

Expand Down
26 changes: 16 additions & 10 deletions packages/cli/src/application/androidUtils.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import { getProjectRootDir, getRootDestinationFolder } from './utils';
import path from 'path';
import fs from 'fs';
import { getBuildFolderByBuildId, getProjectRootDir, getRootDestinationFolder } from "./utils";
import path from "path";
import fs from "fs";

export function getAppBuildFolder(flavorName?: string, release?: boolean) {
const buildType = release ? 'release' : 'debug';
export function getAppBuildFolder(flavorName?: string, release?: boolean, buildId?: string) {
const buildType = release ? "release" : "debug";
const baseBuildFolder = buildId ? getBuildFolderByBuildId(buildId) : getRootDestinationFolder();

const appPath = `${getRootDestinationFolder()}/android/${flavorName ? `${flavorName}/` : 'default/'}${buildType}`;
const appPath = path.join(
baseBuildFolder,
'android',
`${flavorName ? `${flavorName}` : "default"}`,
buildType
);
return appPath;
}

export function getAndroidIndexJsPath() {
const androidSpecific = path.join(getProjectRootDir(), 'index.android.js');
const androidSpecific = path.join(getProjectRootDir(), "index.android.js");
if (fs.existsSync(androidSpecific)) {
return androidSpecific;
} else {
return path.join(getProjectRootDir(), 'index.js');
return path.join(getProjectRootDir(), "index.js");
}
}

export function checkBuildPresent(buildFlavor?: string, release?: boolean) {
const appPath = getAppBuildFolder(buildFlavor, release);
export function checkBuildPresent(buildFlavor?: string, release?: boolean, buildId?: string) {
const appPath = getAppBuildFolder(buildFlavor, release, buildId);
return fs.existsSync(appPath);
}
1 change: 0 additions & 1 deletion packages/cli/src/application/buildIos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ function _buildIos(buildType?: string, platform: IosPlatform = iosBuildPlatforms
executeCommand(`mkdir -p ${destinationDir}`);
executeCommand(`rm -rf ${destination}`);
const copyCommand = `cp -a '${source}' '${destination}'`;
console.log(`Copying: ${copyCommand}`);
executeCommand(copyCommand);
return destination;
} else {
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/application/cloud/buildsManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Build, RemoteStorage } from '@rn-buildhub/storage-interface';

async function zipFolder(folderPath: string, outputZipPath: string) {
if (!fs.existsSync(folderPath)) {
console.error(`The folder "${folderPath}" does not exist.`);
logger.error(`The folder "${folderPath}" does not exist.`);
return;
}
const newZip = new AdmZip();
Expand Down Expand Up @@ -65,7 +65,6 @@ function unzipFile(zipFilePath: string, destinationFolder: string): void {

zip.extractAllTo(destinationFolder, true);

console.log(`File extracted to ${destinationFolder}`);
}

async function downloadZipBuild(buildInfo: Build, buildId: string, adapter: RemoteStorage) {
Expand Down
26 changes: 14 additions & 12 deletions packages/cli/src/application/runAndroid.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import childProcess, { execSync } from 'child_process';
import fs from 'fs';
import { getAppName, getProjectRootDir, getRootDestinationFolder } from './utils';
import { getAppName, getProjectRootDir, getRootDestinationFolder, launchEmulator } from "./utils";
import listAndroidDevices from '@react-native-community/cli-platform-android/build/commands/runAndroid/listAndroidDevices';
import tryRunAdbReverse from '@react-native-community/cli-platform-android/build/commands/runAndroid/tryRunAdbReverse';
import adb from '@react-native-community/cli-platform-android/build/commands/runAndroid/adb';
import _getAdbPath from '@react-native-community/cli-platform-android/build/commands/runAndroid/getAdbPath';
import tryLaunchEmulator from '@react-native-community/cli-platform-android/build/commands/runAndroid/tryLaunchEmulator';
import { buildAndroid } from './buildAndroid';
import { checkBuildPresent, getAppBuildFolder } from './androidUtils';
import path from 'path';
Expand Down Expand Up @@ -48,8 +47,8 @@ export function findBestApkInFolder(dir: string, arc?: string) {
}
}

function installApp(device: string, engineDir: string, buildType?: string) {
const appDir = getAppBuildFolder(buildType);
function installApp(device: string, engineDir: string, buildType?: string, buildId?:string) {
const appDir = getAppBuildFolder(buildType, false, buildId);

const cpu = adb.getCPU(getAdbPath(), device);

Expand Down Expand Up @@ -85,18 +84,21 @@ function getBundleIdentifier(appBuildFolder: string): string {
return 'todo';
}

function installAndLaunch(port: string, deviceId: string, buildType: string | undefined, appIdentifier: string) {
function installAndLaunch(port: string, deviceId: string, buildType: string | undefined, appIdentifier: string, buildId?: string) {
tryRunAdbReverse(port, deviceId);

logger.info('Installing app...');
installApp(deviceId, getProjectRootDir(), buildType);
installApp(deviceId, getProjectRootDir(), buildType, buildId);
logger.info('Launching app...');
launchApp(deviceId, appIdentifier);
}

export async function runApp(buildFlavor?: string, port = '8081', forceBuild?: boolean, buildId: string = 'local') {
if (forceBuild || !checkBuildPresent(buildFlavor)) {
export async function runApp(buildFlavor?: string, port = '8081', forceBuild?: boolean, buildId?: string) {
if (forceBuild || !checkBuildPresent(buildFlavor,false, buildId)) {
logger.info('Build not present, starting build');
if(buildId) {
throw new Error(`The requested build id ${buildId} does not contain an android build for flavor ${buildFlavor}`);
}
await buildAndroid(buildFlavor);
} else {
logger.info('Build already present, skipping build');
Expand All @@ -105,19 +107,19 @@ export async function runApp(buildFlavor?: string, port = '8081', forceBuild?: b
// todo improvement: if there is only one device, use it directly
const device = await listAndroidDevices();

const appIdentifier = getBundleIdentifier(getAppBuildFolder(buildFlavor));
const appIdentifier = getBundleIdentifier(getAppBuildFolder(buildFlavor, false, buildId));

if (!device) {
throw new Error('No android devices available');
} else {
if (device.connected) {
installAndLaunch(port, device.deviceId!, buildFlavor, appIdentifier);
installAndLaunch(port, device.deviceId!, buildFlavor, appIdentifier, buildId);
} else {
const newEmulatorPort = await getAvailableDevicePort();
const emulator = `emulator-${newEmulatorPort}`;
const result = await tryLaunchEmulator(getAdbPath(), device.readableName, newEmulatorPort);
const result = await launchEmulator(getAdbPath(), device.readableName, newEmulatorPort, emulator);
if (result.success) {
installAndLaunch(port, emulator, buildFlavor, appIdentifier);
installAndLaunch(port, emulator, buildFlavor, appIdentifier, buildId);
}
}
}
Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/application/runIos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ function checkBuildPresent(buildType: string, target: any) {
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' });
console.log('res', res);
}

function launchApp(deviceUid: string, bundleId: string) {
Expand Down
60 changes: 41 additions & 19 deletions packages/cli/src/application/utils.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
import process from 'process';
import fs from 'fs';
import path from 'path';
import { execSync, ExecSyncOptions, ExecSyncOptionsWithBufferEncoding } from 'child_process';
import { ProjectConfiguration } from './cloud/projectsManagement';
import { RemoteStorage } from '@rn-buildhub/storage-interface';
import util from 'util';

const execAsync = util.promisify(require('child_process').exec);
import process from "process";
import fs from "fs";
import path from "path";
import { execSync, ExecSyncOptions, ExecSyncOptionsWithBufferEncoding } from "child_process";
import { ProjectConfiguration } from "./cloud/projectsManagement";
import { RemoteStorage } from "@rn-buildhub/storage-interface";
import util from "util";
import tryLaunchEmulator
from "@react-native-community/cli-platform-android/build/commands/runAndroid/tryLaunchEmulator";
import getAdbPath from "@react-native-community/cli-platform-android/build/commands/runAndroid/getAdbPath";

const execAsync = util.promisify(require("child_process").exec);

export function executeCommand(command: string, options?: ExecSyncOptionsWithBufferEncoding) {
execSync(command, { stdio: 'inherit', ...(options || {}) });
execSync(command, { stdio: "inherit", ...(options || {}) });
}

export function executeCommandWithOutPut(command: string, options?: ExecSyncOptions) {
return execSync(command, { encoding: 'utf8', ...(options || {}) }) as string;
return execSync(command, { encoding: "utf8", ...(options || {}) }) as string;
}

export function executeCommandAsync(command: string, options?: ExecSyncOptionsWithBufferEncoding) {
return execAsync(command, options);
}

export function getRootDestinationFolder() {
return path.join(getProjectRootDir(), '.rn-build-hub');
return path.join(getProjectRootDir(), ".rn-build-hub");
}

export function getConfigFile() {
return path.join(getProjectRootDir(), '.rn-build-hub.json');
return path.join(getProjectRootDir(), ".rn-build-hub.json");
}

export function getProjectRootDir() {
Expand All @@ -36,7 +39,7 @@ export function getProjectRootDir() {
export function getAppName() {
const projectRootDir = getProjectRootDir();

const packageJson = JSON.parse(fs.readFileSync(path.join(projectRootDir, 'package.json'), 'utf8'));
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRootDir, "package.json"), "utf8"));
// todo default app name improve passing from command line
return packageJson.name;
}
Expand All @@ -46,19 +49,38 @@ export function sleep(ms: number) {
}

export function getRootModuleDir() {
return path.join(__dirname, '..', '..');
return path.join(__dirname, "..", "..");
}

export function getApkToolExecutable() {
return path.join(getRootModuleDir(), 'apktool');
return path.join(getRootModuleDir(), "apktool");
}

export function getUberSignJava() {
return path.join(getRootModuleDir(), 'uber-apk-signer.jar');
return path.join(getRootModuleDir(), "uber-apk-signer.jar");
}

function waitFormEmulatorBoot(emulatorName: string): Promise<void> {
const output = execSync(`${getAdbPath()} -s ${emulatorName} shell getprop sys.boot_completed`);
if (output.toString().trim() !== "1") {
return sleep(1000).then(() => waitFormEmulatorBoot(emulatorName));
} else {
return Promise.resolve();
}
}

export async function launchEmulator(adbPath: string, emulatorName: string, port: number, emulatorId:string) {
const result = await tryLaunchEmulator(adbPath, emulatorName, port);
if (!result.success) {
throw new Error("Unable to launch emulator");
} else {
await waitFormEmulatorBoot(emulatorId);
return result;
}
}

export function getBuildFolderByBuildId(buildId: string) {
return path.join(getRootDestinationFolder(), 'builds', buildId);
return path.join(getRootDestinationFolder(), "builds", buildId);
}

export function capitalize(str: string) {
Expand All @@ -67,7 +89,7 @@ export function capitalize(str: string) {

export function getRemoteStorage(config: ProjectConfiguration): RemoteStorage {
if (!config.remote.name) {
throw new Error('Remote adapter name is required');
throw new Error("Remote adapter name is required");
}
try {
const connectorPackage = require(config.remote.name);
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/src/commands/makeBuildCurrent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import logger from '../application/logger';
import { getBuildFolderByBuildId } from '../application/utils';
import { ProjectConfiguration, updateCurrentBuildInFile } from '../application/cloud/projectsManagement';

export async function updateCurrentBuild(buildId: string, config: ProjectConfiguration) {
type RequestedBuildId = 'last' | string & {};
export async function downloadBuildIfNotPresent(buildId: RequestedBuildId, config: ProjectConfiguration) {
let buildIdToDownload: string;

if (buildId === 'last') {
if (buildId === "last") {
const buildId = await getLastBuild(config);
buildIdToDownload = buildId;
} else {
Expand All @@ -21,6 +22,11 @@ export async function updateCurrentBuild(buildId: string, config: ProjectConfigu
} else {
await downloadBuild(buildIdToDownload, config);
}
return buildIdToDownload;
}

export async function updateCurrentBuild(requestedBuildId: RequestedBuildId, config: ProjectConfiguration) {
let buildIdToDownload = await downloadBuildIfNotPresent(requestedBuildId, config);

await makeCurrentBuild(buildIdToDownload);

Expand Down
14 changes: 6 additions & 8 deletions packages/cli/src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { runApp as runIos } from '../application/runIos';
import { startMetro, checkIsMetroRunning } from '../application/metroManager';
import logger from '../application/logger';
import { iosBuildPlatforms } from '../application/iosUtils';
import { updateCurrentBuild } from './makeBuildCurrent';
import { downloadBuildIfNotPresent, updateCurrentBuild } from "./makeBuildCurrent";
import RemoteAwareCommand from '../_projectAwareCommand';

export default class Run extends RemoteAwareCommand {
Expand All @@ -18,7 +18,7 @@ export default class Run extends RemoteAwareCommand {
flavor: Flags.string({ char: 'f', description: 'Specify the android flavor or the ios scheme to build' }),
verbose: Flags.boolean({ description: 'Verbose output' }),
forceBuild: Flags.boolean({ aliases: ['fb', 'force-build'], description: 'Force a native rebuild' }),
buildId: Flags.string({ description: 'Specify the build id. Can be local, last or a buildId', default: 'local' }),
buildId: Flags.string({ description: 'Specify the build id. Can be local, last or a buildId', default: undefined }),
};

static args = {
Expand All @@ -32,17 +32,15 @@ export default class Run extends RemoteAwareCommand {
const shouldRunIos = flags.ios ?? flags.all;
const buildFlavor = flags.flavor;
const forceBuild = flags.forceBuild;
const buildId = flags.buildId;
let buildId = flags.buildId;

logger.setVerbose(flags.verbose);
const start = performance.now();
logger.info('Checking if metro is running...');

if (buildId !== 'local') {
if (buildId) {
logger.info(`Requested to run specific id ${buildId}`);
// todo remote command only if needed?
// do we have to copy to local? can't we just run from build folder?
await updateCurrentBuild(buildId, this.currentProject);
buildId = await downloadBuildIfNotPresent(buildId, this.currentProject);
}

const isMetroRunning = await checkIsMetroRunning();
Expand All @@ -56,7 +54,7 @@ export default class Run extends RemoteAwareCommand {

if (shouldRunAndroid) {
logger.info(`Running android app ${buildFlavor ? `with flavor ${buildFlavor}` : ''}`);
await runAndroid(buildFlavor, undefined, forceBuild);
await runAndroid(buildFlavor, undefined, forceBuild, buildId);
}
if (shouldRunIos) {
logger.info(`Running ios app ${buildFlavor ? `with flavor ${buildFlavor}` : ''}`);
Expand Down

0 comments on commit 343f9f2

Please sign in to comment.