Skip to content

Commit ce87944

Browse files
committed
feat(cordova): instructions on how to fix faulty Android SDKs
1 parent c332c6d commit ce87944

File tree

3 files changed

+53
-26
lines changed

3 files changed

+53
-26
lines changed

packages/ionic/src/definitions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
PromptModule,
1616
} from '@ionic/cli-framework';
1717
import { NetworkInterface } from '@ionic/utils-network';
18-
import { SubprocessOptions, WhichOptions } from '@ionic/utils-subprocess';
18+
import { Subprocess, SubprocessOptions, WhichOptions } from '@ionic/utils-subprocess';
1919
import { ChildProcess, SpawnOptions } from 'child_process';
2020
import * as fs from 'fs';
2121

@@ -385,6 +385,7 @@ export interface IShell {
385385
spawn(command: string, args: readonly string[], options: IShellSpawnOptions): Promise<ChildProcess>;
386386
cmdinfo(cmd: string, args?: readonly string[], options?: SubprocessOptions): Promise<string | undefined>;
387387
which(command: string, options?: WhichOptions): Promise<string>;
388+
createSubprocess(command: string, args: readonly string[], options?: SubprocessOptions): Promise<Subprocess>;
388389
}
389390

390391
export interface ITelemetry {

packages/ionic/src/lib/native-run.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { processExit } from '@ionic/utils-process';
2-
import { ERROR_COMMAND_NOT_FOUND, SubprocessError, which } from '@ionic/utils-subprocess';
2+
import { ERROR_COMMAND_NOT_FOUND, ERROR_NON_ZERO_EXIT, SubprocessError, which } from '@ionic/utils-subprocess';
33

44
import { CommandLineOptions, IConfig, ILogger, IShell, IShellRunOptions, NpmClient } from '../definitions';
55

@@ -139,8 +139,32 @@ export interface NativeTargetPlatform {
139139
virtualDevices: NativeVirtualDeviceTarget[];
140140
}
141141

142-
export async function getNativeTargets({ shell }: RunNativeRunDeps, platform: string): Promise<NativeTargetPlatform> {
143-
const output = await shell.output('native-run', [platform, '--list', '--json'], { showCommand: false });
142+
export async function getNativeTargets({ log, shell }: RunNativeRunDeps, platform: string): Promise<NativeTargetPlatform> {
143+
try {
144+
const proc = await shell.createSubprocess('native-run', [platform, '--list', '--json']);
145+
const output = await proc.output();
146+
147+
return JSON.parse(output);
148+
} catch (e) {
149+
if (e instanceof SubprocessError && e.code === ERROR_NON_ZERO_EXIT) {
150+
const output = e.output ? JSON.parse(e.output) : {};
151+
152+
throw new FatalException(
153+
`Error while getting native targets for ${input(platform)}: ${output.error || output.code}\n` +
154+
(
155+
platform === 'android' && output.code === 'ERR_UNSUITABLE_API_INSTALLATION' ?
156+
(
157+
`\n${input('native-run')} needs a fully installed SDK Platform to run your app.\n` +
158+
`- Run ${input('native-run android --sdk-info')} to see missing packages for each API level.\n` +
159+
`- Install missing packages in Android Studio by opening the SDK manager.\n`
160+
) : ''
161+
) +
162+
`\nThis error occurred while using ${input('native-run')}. You can try running this command with ${input('--no-native-run')}, which will revert to using Cordova.\n`
163+
);
164+
}
165+
166+
log.warn(`Error while getting native targets for ${input(platform)}:\n${e.stack ? e.stack : e}`);
167+
}
144168

145-
return JSON.parse(output);
169+
return { devices: [], virtualDevices: [] };
146170
}

packages/ionic/src/lib/shell.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { LOGGER_LEVELS } from '@ionic/cli-framework';
22
import { createProcessEnv, killProcessTree, onBeforeExit } from '@ionic/utils-process';
33
import { combineStreams } from '@ionic/utils-stream';
4-
import { ERROR_COMMAND_NOT_FOUND, Subprocess, SubprocessError, WhichOptions, which } from '@ionic/utils-subprocess';
4+
import { ERROR_COMMAND_NOT_FOUND, Subprocess, SubprocessError, SubprocessOptions, WhichOptions, which } from '@ionic/utils-subprocess';
55
import { TERMINAL_INFO } from '@ionic/utils-terminal';
66
import chalk from 'chalk';
77
import { ChildProcess, SpawnOptions } from 'child_process';
@@ -32,13 +32,12 @@ export class Shell implements IShell {
3232
this.alterPath = options && options.alterPath ? options.alterPath : (p: string) => p;
3333
}
3434

35-
async run(command: string, args: string[], { stream, killOnExit = true, showCommand = true, showError = true, fatalOnNotFound = true, fatalOnError = true, truncateErrorOutput, ...crossSpawnOptions }: IShellRunOptions): Promise<void> {
35+
async run(command: string, args: readonly string[], { stream, killOnExit = true, showCommand = true, showError = true, fatalOnNotFound = true, fatalOnError = true, truncateErrorOutput, ...crossSpawnOptions }: IShellRunOptions): Promise<void> {
3636
this.prepareSpawnOptions(crossSpawnOptions);
3737

38-
const cmdpath = await this.resolveCommandPath(command, crossSpawnOptions);
39-
const cmd = new Subprocess(cmdpath, args, crossSpawnOptions);
38+
const proc = await this.createSubprocess(command, args, crossSpawnOptions);
4039

41-
const fullCmd = cmd.bashify();
40+
const fullCmd = proc.bashify();
4241
const truncatedCmd = fullCmd.length > 80 ? fullCmd.substring(0, 80) + '...' : fullCmd;
4342

4443
if (showCommand && this.e.log.level >= LOGGER_LEVELS.INFO) {
@@ -48,7 +47,7 @@ export class Shell implements IShell {
4847
const ws = stream ? stream : this.e.log.createWriteStream(LOGGER_LEVELS.INFO, false);
4948

5049
try {
51-
const promise = cmd.run();
50+
const promise = proc.run();
5251

5352
if (promise.p.stdout) {
5453
const s = combineStreams(split2(), ws);
@@ -124,19 +123,17 @@ export class Shell implements IShell {
124123
}
125124
}
126125

127-
async output(command: string, args: string[], { fatalOnNotFound = true, fatalOnError = true, showError = true, showCommand = false, ...crossSpawnOptions }: IShellOutputOptions): Promise<string> {
128-
const cmdpath = await this.resolveCommandPath(command, crossSpawnOptions);
129-
const cmd = new Subprocess(cmdpath, args, crossSpawnOptions);
130-
131-
const fullCmd = cmd.bashify();
126+
async output(command: string, args: readonly string[], { fatalOnNotFound = true, fatalOnError = true, showError = true, showCommand = false, ...crossSpawnOptions }: IShellOutputOptions): Promise<string> {
127+
const proc = await this.createSubprocess(command, args, crossSpawnOptions);
128+
const fullCmd = proc.bashify();
132129
const truncatedCmd = fullCmd.length > 80 ? fullCmd.substring(0, 80) + '...' : fullCmd;
133130

134131
if (showCommand && this.e.log.level >= LOGGER_LEVELS.INFO) {
135132
this.e.log.rawmsg(`> ${input(fullCmd)}`);
136133
}
137134

138135
try {
139-
return await cmd.output();
136+
return await proc.output();
140137
} catch (e) {
141138
if (e instanceof SubprocessError && e.code === ERROR_COMMAND_NOT_FOUND) {
142139
if (fatalOnNotFound) {
@@ -188,35 +185,40 @@ export class Shell implements IShell {
188185
return which(command, { PATH: this.alterPath(PATH || '') });
189186
}
190187

191-
async spawn(command: string, args: string[], { showCommand = true, ...crossSpawnOptions }: IShellSpawnOptions): Promise<ChildProcess> {
188+
async spawn(command: string, args: readonly string[], { showCommand = true, ...crossSpawnOptions }: IShellSpawnOptions): Promise<ChildProcess> {
192189
this.prepareSpawnOptions(crossSpawnOptions);
193190

194-
const cmdpath = await this.resolveCommandPath(command, crossSpawnOptions);
195-
const cmd = new Subprocess(cmdpath, args, crossSpawnOptions);
196-
const p = cmd.spawn();
191+
const proc = await this.createSubprocess(command, args, crossSpawnOptions);
192+
const p = proc.spawn();
197193

198194
if (showCommand && this.e.log.level >= LOGGER_LEVELS.INFO) {
199-
this.e.log.rawmsg(`> ${input(cmd.bashify())}`);
195+
this.e.log.rawmsg(`> ${input(proc.bashify())}`);
200196
}
201197

202198
return p;
203199
}
204200

205-
async cmdinfo(command: string, args: string[] = []): Promise<string | undefined> {
201+
async cmdinfo(command: string, args: readonly string[] = []): Promise<string | undefined> {
206202
const opts: IShellSpawnOptions = {};
207203
this.prepareSpawnOptions(opts);
208204

209-
const cmdpath = await this.resolveCommandPath(command, opts);
210-
const cmd = new Subprocess(cmdpath, args, opts);
205+
const proc = await this.createSubprocess(command, args, opts);
211206

212207
try {
213-
const out = await cmd.output();
208+
const out = await proc.output();
214209
return out.split('\n').join(' ').trim();
215210
} catch (e) {
216211
// no command info at this point
217212
}
218213
}
219214

215+
async createSubprocess(command: string, args: readonly string[] = [], options: SubprocessOptions = {}): Promise<Subprocess> {
216+
const cmdpath = await this.resolveCommandPath(command, options);
217+
const proc = new Subprocess(cmdpath, args, options);
218+
219+
return proc;
220+
}
221+
220222
protected prepareSpawnOptions(options: IShellSpawnOptions) {
221223
// Create a `process.env`-type object from all key/values of `process.env`,
222224
// then `options.env`, then add several key/values. PATH is supplemented

0 commit comments

Comments
 (0)