From b7ae6dd7f9c8791feeec9fdd37eaa56948327e74 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 4 May 2018 11:58:42 +0300 Subject: [PATCH] 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. --- lib/definitions/android-plugin-migrator.d.ts | 22 ++++++++ lib/services/android-plugin-build-service.ts | 55 +++++++++++-------- test/services/android-plugin-build-service.ts | 21 ++++--- 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/lib/definitions/android-plugin-migrator.d.ts b/lib/definitions/android-plugin-migrator.d.ts index fbbd73246e..4ede88b918 100644 --- a/lib/definitions/android-plugin-migrator.d.ts +++ b/lib/definitions/android-plugin-migrator.d.ts @@ -13,3 +13,25 @@ interface IAndroidPluginBuildService { buildAar(options: IBuildOptions): Promise; migrateIncludeGradle(options: IBuildOptions): boolean; } + +/** + * Describes data required for building plugin for Android. + * The data can be consumed in the buildAndroidPlugin hook. + */ +interface IBuildAndroidPluginData { + /** + * Directory where the plugin will be build. + * Usually this is the `/platforms/tempPlugin/` dir. + */ + pluginDir: string; + + /** + * The name of the plugin. + */ + pluginName: string; + + /** + * Information about tools that will be used to build the plugin, for example compile SDK version, build tools version, etc. + */ + androidToolsInfo: IAndroidToolsInfoData; +} diff --git a/lib/services/android-plugin-build-service.ts b/lib/services/android-plugin-build-service.ts index 3e71fd57d1..6532e4bede 100644 --- a/lib/services/android-plugin-build-service.ts +++ b/lib/services/android-plugin-build-service.ts @@ -1,12 +1,20 @@ import * as path from "path"; import { MANIFEST_FILE_NAME, INCLUDE_GRADLE_NAME, ASSETS_DIR, RESOURCES_DIR } from "../constants"; -import { getShortPluginName } from "../common/helpers"; +import { getShortPluginName, hook } from "../common/helpers"; import { Builder, parseString } from "xml2js"; import { ILogger } from "log4js"; export class AndroidPluginBuildService implements IAndroidPluginBuildService { - constructor(private $fs: IFileSystem, + /** + * Required for hooks execution to work. + */ + private get $hooksService(): IHooksService { + return this.$injector.resolve("hooksService"); + } + + constructor(private $injector: IInjector, + private $fs: IFileSystem, private $childProcess: IChildProcess, private $hostInfo: IHostInfo, private $androidToolsInfo: IAndroidToolsInfo, @@ -240,30 +248,9 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { } // finally build the plugin - const gradlew = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew"; - const localArgs = [ - gradlew, - "-p", - newPluginDir, - "assembleRelease" - ]; - this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true }); - const androidToolsInfo = this.$androidToolsInfo.getToolsInfo(); - const compileSdk = androidToolsInfo.compileSdkVersion; - const buildToolsVersion = androidToolsInfo.buildToolsVersion; - const supportVersion = androidToolsInfo.supportRepositoryVersion; - - localArgs.push(`-PcompileSdk=android-${compileSdk}`); - localArgs.push(`-PbuildToolsVersion=${buildToolsVersion}`); - localArgs.push(`-PsupportVersion=${supportVersion}`); - - try { - await this.$childProcess.exec(localArgs.join(" "), { cwd: newPluginDir }); - } catch (err) { - throw new Error(`Failed to build plugin ${options.pluginName} : \n${err}`); - } + await this.buildPlugin( { pluginDir: newPluginDir, pluginName: options.pluginName, androidToolsInfo }); const finalAarName = `${shortPluginName}-release.aar`; const pathToBuiltAar = path.join(newPluginDir, "build", "outputs", "aar", finalAarName); @@ -318,6 +305,26 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { return false; } + @hook("buildAndroidPlugin") + private async buildPlugin(pluginBuildSettings: IBuildAndroidPluginData): Promise { + const gradlew = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew"; + + const localArgs = [ + "-p", + pluginBuildSettings.pluginDir, + "assembleRelease", + `-PcompileSdk=android-${pluginBuildSettings.androidToolsInfo.compileSdkVersion}`, + `-PbuildToolsVersion=${pluginBuildSettings.androidToolsInfo.buildToolsVersion}`, + `-PsupportVersion=${pluginBuildSettings.androidToolsInfo.supportRepositoryVersion}` + ]; + + try { + await this.$childProcess.spawnFromEvent(gradlew, localArgs, "close", { cwd: pluginBuildSettings.pluginDir }); + } catch (err) { + throw new Error(`Failed to build plugin ${pluginBuildSettings.pluginName} : \n${err}`); + } + } + private validateOptions(options: IBuildOptions): void { if (!options) { throw new Error("Android plugin cannot be built without passing an 'options' object."); diff --git a/test/services/android-plugin-build-service.ts b/test/services/android-plugin-build-service.ts index da3c77049f..b8dc65f670 100644 --- a/test/services/android-plugin-build-service.ts +++ b/test/services/android-plugin-build-service.ts @@ -12,14 +12,15 @@ temp.track(); describe('androiPluginBuildService', () => { - let execCalled = false; + let spawnFromEventCalled = false; const createTestInjector = (): IInjector => { const testInjector = new Yok(); testInjector.register("fs", FsLib.FileSystem); testInjector.register("childProcess", { - exec: () => { - execCalled = true; + spawnFromEvent: async (command: string, args: string[], event: string, options?: any, spawnFromEventOptions?: ISpawnFromEventOptions): Promise => { + spawnFromEventCalled = command.indexOf("gradlew") !== -1; + return null; } }); testInjector.register("hostInfo", HostInfo); @@ -36,6 +37,10 @@ describe('androiPluginBuildService', () => { testInjector.register("options", {}); testInjector.register("config", {}); testInjector.register("staticConfig", {}); + testInjector.register("hooksService", { + executeBeforeHooks: async (commandName: string, hookArguments?: IDictionary): Promise => undefined, + executeAfterHooks: async (commandName: string, hookArguments?: IDictionary): Promise => undefined + }); return testInjector; }; @@ -107,7 +112,7 @@ dependencies { }); beforeEach(() => { - execCalled = false; + spawnFromEventCalled = false; }); describe('builds aar', () => { @@ -127,7 +132,7 @@ dependencies { /* intentionally left blank */ } - assert.isTrue(execCalled); + assert.isTrue(spawnFromEventCalled); }); it('if android manifest is missing', async () => { @@ -145,7 +150,7 @@ dependencies { /* intentionally left blank */ } - assert.isTrue(execCalled); + assert.isTrue(spawnFromEventCalled); }); it('if there is only an android manifest file', async () => { @@ -163,7 +168,7 @@ dependencies { /* intentionally left blank */ } - assert.isTrue(execCalled); + assert.isTrue(spawnFromEventCalled); }); }); @@ -183,7 +188,7 @@ dependencies { /* intentionally left blank */ } - assert.isFalse(execCalled); + assert.isFalse(spawnFromEventCalled); }); });