Skip to content
Merged
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
10 changes: 10 additions & 0 deletions __e2e__/__snapshots__/config.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ exports[`shows up current config without unnecessary output 1`] = `
"<<REPLACED>>"
]
},
{
"name": "build-ios",
"description": "builds your app on iOS simulator",
"examples": [
"<<REPLACED>>"
],
"options": [
"<<REPLACED>>"
]
},
{
"name": "log-android",
"description": "starts logkitty"
Expand Down
2 changes: 1 addition & 1 deletion __e2e__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function createCorruptedSetupEnvScript() {

beforeAll(() => {
// Register all packages to be linked
for (const pkg of ['platform-ios', 'platform-android']) {
for (const pkg of ['cli-platform-ios', 'cli-platform-android']) {
spawnScript('yarn', ['link'], {
cwd: path.join(__dirname, `../packages/${pkg}`),
});
Expand Down
2 changes: 1 addition & 1 deletion __e2e__/root.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const cwd = getTempDirectory('test_different_roots');

beforeAll(() => {
// Register all packages to be linked
for (const pkg of ['platform-ios', 'platform-android']) {
for (const pkg of ['cli-platform-ios', 'cli-platform-android']) {
spawnScript('yarn', ['link'], {
cwd: path.join(__dirname, `../packages/${pkg}`),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ async function buildAndroid(
args.mode || args.variant,
tasks,
'assemble',
androidProject.sourceDir,
);

if (args.extraParams) {
gradleArgs = [...gradleArgs, ...args.extraParams];
gradleArgs.push(...args.extraParams);
}

if (args.activeArchOnly) {
Expand Down Expand Up @@ -183,5 +184,5 @@ export default {
name: 'build-android',
description: 'builds your app',
func: buildAndroid,
options: options,
options,
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,41 @@ import execa from 'execa';
import {Flags} from '..';
import {AndroidProjectConfig} from '@react-native-community/cli-types';

const gradleTaskOutput = `
> Task :tasks

------------------------------------------------------------
Tasks runnable from root project 'Bar'
------------------------------------------------------------

Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for the base and test modules
sourceSets - Prints out all the source sets defined in this project.

Build tasks
-----------
assemble - Assemble main outputs for all the variants.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles main outputs for all Debug variants.
assembleRelease - Assembles main outputs for all Release variants.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
bundle - Assemble bundles for all the variants.
bundleDebug - Assembles bundles for all Debug variants.
bundleRelease - Assembles bundles for all Release variants.


Install tasks
-------------
installDebug - Installs the Debug build.
installDebugAndroidTest - Installs the android (on device) tests for the Debug build.
installRelease - Installs the Release build.
uninstallAll - Uninstall all applications.
`;

jest.mock('execa');
jest.mock('../getAdbPath');
jest.mock('../tryLaunchEmulator');
Expand All @@ -35,6 +70,7 @@ describe('--appFolder', () => {
};
beforeEach(() => {
jest.clearAllMocks();
(execa.sync as jest.Mock).mockReturnValueOnce({stdout: gradleTaskOutput});
});

it('uses task "install[Variant]" as default task', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
import {toPascalCase} from './toPascalCase';
import type {BuildFlags} from '../buildAndroid';
import {getGradleTasks} from './listAndroidTasks';
import {CLIError, logger} from '@react-native-community/cli-tools';

export function getTaskNames(
appName: string,
mode: BuildFlags['mode'] = 'debug',
tasks: BuildFlags['tasks'],
taskPrefix: 'assemble' | 'install',
sourceDir: string,
): Array<string> {
const appTasks = tasks || [taskPrefix + toPascalCase(mode)];
let appTasks = tasks || [taskPrefix + toPascalCase(mode)];

// Check against build flavors for "install" task ("assemble" don't care about it so much and will build all)
if (!tasks && taskPrefix === 'install') {
const actionableInstallTasks = getGradleTasks('install', sourceDir);
if (!actionableInstallTasks.find((t) => t.task.includes(appTasks[0]))) {
const installTasksForMode = actionableInstallTasks.filter((t) =>
t.task.toLowerCase().includes(mode),
);
if (!installTasksForMode.length) {
throw new CLIError(
`Couldn't find "${appTasks
.map((taskName) => taskName.replace(taskPrefix, ''))
.join(
', ',
)}" build variant. Available variants are: ${actionableInstallTasks
.map((t) => `"${t.task.replace(taskPrefix, '')}"`)
.join(', ')}.`,
);
}
logger.warn(
`Found multiple tasks for "install" command: ${installTasksForMode
.map((t) => t.task)
.join(', ')}.\nSelecting first available: ${
installTasksForMode[0].task
}.`,
);
appTasks = [installTasksForMode[0].task];
}
}

return appName
? appTasks.map((command) => `${appName}:${command}`)
Expand Down
60 changes: 47 additions & 13 deletions packages/cli-platform-android/src/commands/runAndroid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import chalk from 'chalk';
import path from 'path';
import {build, runPackager, BuildFlags, options} from '../buildAndroid';
import {promptForTaskSelection} from './listAndroidTasks';
import {getTaskNames} from './getTaskNames';

export interface Flags extends BuildFlags {
appId: string;
Expand Down Expand Up @@ -86,19 +87,19 @@ async function buildAndRun(args: Flags, androidProject: AndroidProject) {

const adbPath = getAdbPath();

let {tasks} = args;
let selectedTask;

if (args.interactive) {
const selectedTask = await promptForTaskSelection(
const task = await promptForTaskSelection(
'install',
androidProject.sourceDir,
);
if (selectedTask) {
tasks = [selectedTask];
if (task) {
selectedTask = task;
}
}

if (args.listDevices) {
if (args.listDevices || args.interactive) {
if (args.deviceId) {
logger.warn(
'Both "deviceId" and "list-devices" parameters were passed to "run" command. We will list available devices and let you choose from one',
Expand All @@ -108,7 +109,9 @@ async function buildAndRun(args: Flags, androidProject: AndroidProject) {
const device = await listAndroidDevices();
if (!device) {
throw new CLIError(
'Failed to select device, please try to run app without "list-devices" command.',
`Failed to select device, please try to run app without ${
args.listDevices ? 'list-devices' : 'interactive'
} command.`,
);
}

Expand All @@ -117,6 +120,7 @@ async function buildAndRun(args: Flags, androidProject: AndroidProject) {
{...args, deviceId: device.deviceId},
adbPath,
androidProject,
selectedTask,
);
}

Expand All @@ -130,33 +134,49 @@ async function buildAndRun(args: Flags, androidProject: AndroidProject) {
{...args, deviceId: emulator},
adbPath,
androidProject,
selectedTask,
);
}
throw new CLIError(
`Failed to launch emulator. Reason: ${chalk.dim(result.error || '')}`,
);
}

if (args.deviceId) {
return runOnSpecificDevice({...args, tasks}, adbPath, androidProject);
return runOnSpecificDevice(args, adbPath, androidProject, selectedTask);
} else {
return runOnAllDevices({...args, tasks}, cmd, adbPath, androidProject);
return runOnAllDevices(args, cmd, adbPath, androidProject);
}
}

function runOnSpecificDevice(
args: Flags,
adbPath: string,
androidProject: AndroidProject,
selectedTask?: string,
) {
const devices = adb.getDevices(adbPath);
const {deviceId} = args;

// if coming from run-android command and we have selected task
// from interactive mode we need to create appropriate build task
// eg 'installRelease' -> 'assembleRelease'
const buildTask = selectedTask?.replace('install', 'assemble') ?? 'build';

if (devices.length > 0 && deviceId) {
if (devices.indexOf(deviceId) !== -1) {
// using '-x lint' in order to ignore linting errors while building the apk
let gradleArgs = ['build', '-x', 'lint'];
let gradleArgs = getTaskNames(
androidProject.appName,
args.mode || args.variant,
args.tasks ?? [buildTask],
'install',
androidProject.sourceDir,
);

// using '-x lint' in order to ignore linting errors while building the apk
gradleArgs.push('-x', 'lint');
if (args.extraParams) {
gradleArgs = [...gradleArgs, ...args.extraParams];
gradleArgs.push(...args.extraParams);
}

if (args.port) {
Expand All @@ -179,7 +199,13 @@ function runOnSpecificDevice(
build(gradleArgs, androidProject.sourceDir);
}

installAndLaunchOnDevice(args, deviceId, adbPath, androidProject);
installAndLaunchOnDevice(
args,
deviceId,
adbPath,
androidProject,
selectedTask,
);
} else {
logger.error(
`Could not find device with the id: "${deviceId}". Please choose one of the following:`,
Expand All @@ -196,9 +222,17 @@ function installAndLaunchOnDevice(
selectedDevice: string,
adbPath: string,
androidProject: AndroidProject,
selectedTask?: string,
) {
tryRunAdbReverse(args.port, selectedDevice);
tryInstallAppOnDevice(args, adbPath, selectedDevice, androidProject);

tryInstallAppOnDevice(
args,
adbPath,
selectedDevice,
androidProject,
selectedTask,
);
tryLaunchAppOnDevice(
selectedDevice,
androidProject.packageName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const parseTasksFromGradleFile = (
return instalTasks;
};

export const promptForTaskSelection = async (
export const getGradleTasks = (
taskType: 'install' | 'build',
sourceDir: string,
) => {
Expand All @@ -37,15 +37,22 @@ export const promptForTaskSelection = async (
const out = execa.sync(cmd, ['tasks'], {
cwd: sourceDir,
}).stdout;
const installTasks = parseTasksFromGradleFile(taskType, out);
if (!installTasks.length) {
return parseTasksFromGradleFile(taskType, out);
};

export const promptForTaskSelection = async (
taskType: 'install' | 'build',
sourceDir: string,
): Promise<string | undefined> => {
const tasks = getGradleTasks(taskType, sourceDir);
if (!tasks.length) {
throw new CLIError(`No actionable ${taskType} tasks were found...`);
}
const {task} = await prompts({
const {task}: {task: string} = await prompts({
type: 'select',
name: 'task',
message: `Select ${taskType} task you want to perform`,
choices: installTasks.map((t: GradleTask) => ({
choices: tasks.map((t: GradleTask) => ({
title: `${chalk.bold(t.task)} - ${t.description}`,
value: t.task,
})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ async function runOnAllDevices(
args.mode || args.variant,
args.tasks,
'install',
androidProject.sourceDir,
);

if (args.extraParams) {
gradleArgs = [...gradleArgs, ...args.extraParams];
gradleArgs.push(...args.extraParams);
}

if (args.port != null) {
Expand Down Expand Up @@ -120,8 +121,7 @@ async function runOnAllDevices(

function createInstallError(error: Error & {stderr: string}) {
const stderr = (error.stderr || '').toString();
let message = '';

let message = error.message ?? '';
// Pass the error message from the command to stdout because we pipe it to
// parent process so it's not visible
logger.log(stderr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,33 @@ function tryInstallAppOnDevice(
adbPath: string,
device: string,
androidProject: AndroidProject,
selectedTask?: string,
) {
try {
// "app" is usually the default value for Android apps with only 1 app
const {appName, sourceDir} = androidProject;
const variant = (args.mode || 'debug').toLowerCase();

const defaultVariant = (args.mode || 'debug').toLowerCase();

// handle if selected task from interactive mode includes build flavour as well, eg. installProductionDebug should create ['production','debug'] array
const variantFromSelectedTask = selectedTask
?.replace('install', '')
.split(/(?=[A-Z])/);

// create path to output file, eg. `production/debug`
const variantPath =
variantFromSelectedTask?.join('/')?.toLowerCase() ?? defaultVariant;
// create output file name, eg. `production-debug`
const variantAppName =
variantFromSelectedTask?.join('-')?.toLowerCase() ?? defaultVariant;

let pathToApk;
if (!args.binaryPath) {
const buildDirectory = `${sourceDir}/${appName}/build/outputs/apk/${variant}`;
const buildDirectory = `${sourceDir}/${appName}/build/outputs/apk/${variantPath}`;
const apkFile = getInstallApkName(
appName,
adbPath,
variant,
variantAppName,
device,
buildDirectory,
);
Expand Down
Loading