Skip to content

Commit b7ae6dd

Browse files
fix: Build plugins when path to project has space
In case the path to project has space in the name and there's Android plugin that should be build, the build operation fails. The reason is usage of exec - change it to spawn, which handles the spaces correctly. feat: Add `buildAndroidPlugin` hook, which is executed just before spawning the gradle build for specific plugin. The hook can be used to modify plugin's structure before building the plugin.
1 parent 238eb06 commit b7ae6dd

File tree

3 files changed

+66
-32
lines changed

3 files changed

+66
-32
lines changed

lib/definitions/android-plugin-migrator.d.ts

+22
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,25 @@ interface IAndroidPluginBuildService {
1313
buildAar(options: IBuildOptions): Promise<boolean>;
1414
migrateIncludeGradle(options: IBuildOptions): boolean;
1515
}
16+
17+
/**
18+
* Describes data required for building plugin for Android.
19+
* The data can be consumed in the buildAndroidPlugin hook.
20+
*/
21+
interface IBuildAndroidPluginData {
22+
/**
23+
* Directory where the plugin will be build.
24+
* Usually this is the `<project dir>/platforms/tempPlugin/<plugin name>` dir.
25+
*/
26+
pluginDir: string;
27+
28+
/**
29+
* The name of the plugin.
30+
*/
31+
pluginName: string;
32+
33+
/**
34+
* Information about tools that will be used to build the plugin, for example compile SDK version, build tools version, etc.
35+
*/
36+
androidToolsInfo: IAndroidToolsInfoData;
37+
}

lib/services/android-plugin-build-service.ts

+31-24
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import * as path from "path";
22
import { MANIFEST_FILE_NAME, INCLUDE_GRADLE_NAME, ASSETS_DIR, RESOURCES_DIR } from "../constants";
3-
import { getShortPluginName } from "../common/helpers";
3+
import { getShortPluginName, hook } from "../common/helpers";
44
import { Builder, parseString } from "xml2js";
55
import { ILogger } from "log4js";
66

77
export class AndroidPluginBuildService implements IAndroidPluginBuildService {
88

9-
constructor(private $fs: IFileSystem,
9+
/**
10+
* Required for hooks execution to work.
11+
*/
12+
private get $hooksService(): IHooksService {
13+
return this.$injector.resolve("hooksService");
14+
}
15+
16+
constructor(private $injector: IInjector,
17+
private $fs: IFileSystem,
1018
private $childProcess: IChildProcess,
1119
private $hostInfo: IHostInfo,
1220
private $androidToolsInfo: IAndroidToolsInfo,
@@ -240,30 +248,9 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
240248
}
241249

242250
// finally build the plugin
243-
const gradlew = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew";
244-
const localArgs = [
245-
gradlew,
246-
"-p",
247-
newPluginDir,
248-
"assembleRelease"
249-
];
250-
251251
this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true });
252-
253252
const androidToolsInfo = this.$androidToolsInfo.getToolsInfo();
254-
const compileSdk = androidToolsInfo.compileSdkVersion;
255-
const buildToolsVersion = androidToolsInfo.buildToolsVersion;
256-
const supportVersion = androidToolsInfo.supportRepositoryVersion;
257-
258-
localArgs.push(`-PcompileSdk=android-${compileSdk}`);
259-
localArgs.push(`-PbuildToolsVersion=${buildToolsVersion}`);
260-
localArgs.push(`-PsupportVersion=${supportVersion}`);
261-
262-
try {
263-
await this.$childProcess.exec(localArgs.join(" "), { cwd: newPluginDir });
264-
} catch (err) {
265-
throw new Error(`Failed to build plugin ${options.pluginName} : \n${err}`);
266-
}
253+
await this.buildPlugin( { pluginDir: newPluginDir, pluginName: options.pluginName, androidToolsInfo });
267254

268255
const finalAarName = `${shortPluginName}-release.aar`;
269256
const pathToBuiltAar = path.join(newPluginDir, "build", "outputs", "aar", finalAarName);
@@ -318,6 +305,26 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService {
318305
return false;
319306
}
320307

308+
@hook("buildAndroidPlugin")
309+
private async buildPlugin(pluginBuildSettings: IBuildAndroidPluginData): Promise<void> {
310+
const gradlew = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew";
311+
312+
const localArgs = [
313+
"-p",
314+
pluginBuildSettings.pluginDir,
315+
"assembleRelease",
316+
`-PcompileSdk=android-${pluginBuildSettings.androidToolsInfo.compileSdkVersion}`,
317+
`-PbuildToolsVersion=${pluginBuildSettings.androidToolsInfo.buildToolsVersion}`,
318+
`-PsupportVersion=${pluginBuildSettings.androidToolsInfo.supportRepositoryVersion}`
319+
];
320+
321+
try {
322+
await this.$childProcess.spawnFromEvent(gradlew, localArgs, "close", { cwd: pluginBuildSettings.pluginDir });
323+
} catch (err) {
324+
throw new Error(`Failed to build plugin ${pluginBuildSettings.pluginName} : \n${err}`);
325+
}
326+
}
327+
321328
private validateOptions(options: IBuildOptions): void {
322329
if (!options) {
323330
throw new Error("Android plugin cannot be built without passing an 'options' object.");

test/services/android-plugin-build-service.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ temp.track();
1212

1313
describe('androiPluginBuildService', () => {
1414

15-
let execCalled = false;
15+
let spawnFromEventCalled = false;
1616
const createTestInjector = (): IInjector => {
1717
const testInjector = new Yok();
1818

1919
testInjector.register("fs", FsLib.FileSystem);
2020
testInjector.register("childProcess", {
21-
exec: () => {
22-
execCalled = true;
21+
spawnFromEvent: async (command: string, args: string[], event: string, options?: any, spawnFromEventOptions?: ISpawnFromEventOptions): Promise<ISpawnResult> => {
22+
spawnFromEventCalled = command.indexOf("gradlew") !== -1;
23+
return null;
2324
}
2425
});
2526
testInjector.register("hostInfo", HostInfo);
@@ -36,6 +37,10 @@ describe('androiPluginBuildService', () => {
3637
testInjector.register("options", {});
3738
testInjector.register("config", {});
3839
testInjector.register("staticConfig", {});
40+
testInjector.register("hooksService", {
41+
executeBeforeHooks: async (commandName: string, hookArguments?: IDictionary<any>): Promise<void> => undefined,
42+
executeAfterHooks: async (commandName: string, hookArguments?: IDictionary<any>): Promise<void> => undefined
43+
});
3944

4045
return testInjector;
4146
};
@@ -107,7 +112,7 @@ dependencies {
107112
});
108113

109114
beforeEach(() => {
110-
execCalled = false;
115+
spawnFromEventCalled = false;
111116
});
112117

113118
describe('builds aar', () => {
@@ -127,7 +132,7 @@ dependencies {
127132
/* intentionally left blank */
128133
}
129134

130-
assert.isTrue(execCalled);
135+
assert.isTrue(spawnFromEventCalled);
131136
});
132137

133138
it('if android manifest is missing', async () => {
@@ -145,7 +150,7 @@ dependencies {
145150
/* intentionally left blank */
146151
}
147152

148-
assert.isTrue(execCalled);
153+
assert.isTrue(spawnFromEventCalled);
149154
});
150155

151156
it('if there is only an android manifest file', async () => {
@@ -163,7 +168,7 @@ dependencies {
163168
/* intentionally left blank */
164169
}
165170

166-
assert.isTrue(execCalled);
171+
assert.isTrue(spawnFromEventCalled);
167172
});
168173
});
169174

@@ -183,7 +188,7 @@ dependencies {
183188
/* intentionally left blank */
184189
}
185190

186-
assert.isFalse(execCalled);
191+
assert.isFalse(spawnFromEventCalled);
187192
});
188193
});
189194

0 commit comments

Comments
 (0)