diff --git a/lib/commands/build.ts b/lib/commands/build.ts index b3b732e2d6..5928ba51e0 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -52,7 +52,11 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { const buildData = this.$buildDataService.getBuildData( this.$projectData.projectDir, platform, - this.$options + { + ...this.$options.argv, + // we disable buildFilterDevicesArch for build only to ensure we dont use it in production builds + buildFilterDevicesArch: false + } ); const outputPath = await this.$buildController.prepareAndBuild(buildData); diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index c0edb53bcb..3babdd7200 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -99,6 +99,12 @@ declare global { * For iOS simulators - same as the identifier. */ imageIdentifier?: string; + + /** + * Optional property describing the architecture of the device + * Available for Android only + */ + abis?: string[]; } interface IDeviceError extends Error, IDeviceIdentifier {} diff --git a/lib/common/mobile/android/android-device.ts b/lib/common/mobile/android/android-device.ts index b230e02827..74a56e672b 100644 --- a/lib/common/mobile/android/android-device.ts +++ b/lib/common/mobile/android/android-device.ts @@ -13,6 +13,9 @@ interface IAndroidDeviceDetails { name: string; release: string; brand: string; + 'cpu.abi': string; + 'cpu.abilist64': string; + 'cpu.abilist32': string; } interface IAdbDeviceStatusInfo { @@ -96,6 +99,7 @@ export class AndroidDevice implements Mobile.IAndroidDevice { identifier: this.identifier, displayName: details.name, model: details.model, + abis: details['cpu.abilist64'].split(',').concat(details['cpu.abilist32'].split(',')), version, vendor: details.brand, platform: this.$devicePlatformsConstants.Android, diff --git a/lib/controllers/build-controller.ts b/lib/controllers/build-controller.ts index f5ebb1666c..291ed88f12 100644 --- a/lib/controllers/build-controller.ts +++ b/lib/controllers/build-controller.ts @@ -116,7 +116,7 @@ export class BuildController extends EventEmitter implements IBuildController { ); if (buildData.copyTo) { - this.$buildArtifactsService.copyLatestAppPackage( + this.$buildArtifactsService.copyAppPackages( buildData.copyTo, platformData, buildData diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index beea13ef08..02a983c8e8 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -23,11 +23,10 @@ export class DeployController { }, }; await this.$prepareController.prepare(prepareData); - const packageFilePath = await deviceDescriptor.buildAction(); + await deviceDescriptor.buildAction(); await this.$deviceInstallAppService.installOnDevice( device, - { ...deviceDescriptor.buildData, buildForDevice: !device.isEmulator }, - packageFilePath + { ...deviceDescriptor.buildData, buildForDevice: !device.isEmulator } ); }; diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index b05805884b..467cf44076 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -471,7 +471,6 @@ export class RunController extends EventEmitter implements IRunController { deviceDescriptors: ILiveSyncDeviceDescriptor[] ): Promise { const rebuiltInformation: IDictionary<{ - packageFilePath: string; platform: string; isEmulator: boolean; }> = {}; @@ -508,8 +507,6 @@ export class RunController extends EventEmitter implements IRunController { ); try { - let packageFilePath: string = null; - // Case where we have three devices attached, a change that requires build is found, // we'll rebuild the app only for the first device, but we should install new package on all three devices. if ( @@ -520,13 +517,9 @@ export class RunController extends EventEmitter implements IRunController { rebuiltInformation[platformData.platformNameLowerCase] .isEmulator === device.isEmulator) ) { - packageFilePath = - rebuiltInformation[platformData.platformNameLowerCase] - .packageFilePath; await this.$deviceInstallAppService.installOnDevice( device, - buildData, - packageFilePath + buildData ); } else { const shouldBuild = @@ -534,11 +527,10 @@ export class RunController extends EventEmitter implements IRunController { buildData.nativePrepare.forceRebuildNativeApp || (await this.$buildController.shouldBuild(buildData)); if (shouldBuild) { - packageFilePath = await deviceDescriptor.buildAction(); + await deviceDescriptor.buildAction(); rebuiltInformation[platformData.platformNameLowerCase] = { isEmulator: device.isEmulator, - platform: platformData.platformNameLowerCase, - packageFilePath, + platform: platformData.platformNameLowerCase }; } else { await this.$analyticsService.trackEventActionInGoogleAnalytics({ @@ -550,8 +542,7 @@ export class RunController extends EventEmitter implements IRunController { await this.$deviceInstallAppService.installOnDeviceIfNeeded( device, - buildData, - packageFilePath + buildData ); } @@ -713,9 +704,7 @@ export class RunController extends EventEmitter implements IRunController { await this.$deviceInstallAppService.installOnDevice( device, - deviceDescriptor.buildData, - rebuiltInformation[platformData.platformNameLowerCase] - .packageFilePath + deviceDescriptor.buildData ); await platformLiveSyncService.syncAfterInstall(device, watchInfo); await this.refreshApplication( diff --git a/lib/data/build-data.ts b/lib/data/build-data.ts index 87af14d166..d623ef7eb0 100644 --- a/lib/data/build-data.ts +++ b/lib/data/build-data.ts @@ -6,6 +6,7 @@ export class BuildData extends PrepareData implements IBuildData { public emulator?: boolean; public clean: boolean; public buildForDevice?: boolean; + public buildFilterDevicesArch?: boolean; public buildOutputStdio?: string; public outputPath?: string; public copyTo?: string; @@ -58,6 +59,7 @@ export class AndroidBuildData extends BuildData { this.keyStoreAliasPassword = data.keyStoreAliasPassword; this.keyStorePassword = data.keyStorePassword; this.androidBundle = data.androidBundle || data.aab; + this.buildFilterDevicesArch = !this.androidBundle && data.filterDevicesArch !== false ; this.gradlePath = data.gradlePath; this.gradleArgs = data.gradleArgs; } diff --git a/lib/definitions/android-plugin-migrator.d.ts b/lib/definitions/android-plugin-migrator.d.ts index f5ad873307..bea8371d00 100644 --- a/lib/definitions/android-plugin-migrator.d.ts +++ b/lib/definitions/android-plugin-migrator.d.ts @@ -10,8 +10,8 @@ interface IAndroidBuildOptions { pluginName: string; aarOutputDir: string; tempPluginDirPath: string; - gradlePath?: string; gradleArgs?: string; + gradlePath?: string; } interface IAndroidPluginBuildService { diff --git a/lib/definitions/build.d.ts b/lib/definitions/build.d.ts index 0ef831745c..41bab096ca 100644 --- a/lib/definitions/build.d.ts +++ b/lib/definitions/build.d.ts @@ -30,6 +30,7 @@ interface IAndroidBuildData extends IBuildData, IAndroidSigningData, IHasAndroidBundle { + buildFilterDevicesArch?: boolean; gradlePath?: string; gradleArgs?: string; } @@ -61,7 +62,7 @@ interface IBuildArtifactsService { platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions ): Promise; - copyLatestAppPackage( + copyAppPackages( targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions diff --git a/lib/definitions/run.d.ts b/lib/definitions/run.d.ts index 1e29b16a3b..cac2c93de4 100644 --- a/lib/definitions/run.d.ts +++ b/lib/definitions/run.d.ts @@ -31,13 +31,11 @@ declare global { interface IDeviceInstallAppService { installOnDevice( device: Mobile.IDevice, - buildData: IBuildData, - packageFile?: string + buildData: IBuildData ): Promise; installOnDeviceIfNeeded( device: Mobile.IDevice, - buildData: IBuildData, - packageFile?: string + buildData: IBuildData ): Promise; shouldInstall( device: Mobile.IDevice, diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index ee2a33473a..2c0d5284f4 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -53,6 +53,7 @@ export class DeployCommandHelper { { ...this.$options.argv, outputPath, + buildFilterDevicesArch: false, buildForDevice: !d.isEmulator, skipWatcher: !this.$options.watch, nativePrepare: { diff --git a/lib/options.ts b/lib/options.ts index 26451d8264..13a84ed0d9 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -222,6 +222,7 @@ export class Options { gradlePath: { type: OptionType.String, hasSensitiveValue: false }, gradleArgs: { type: OptionType.String, hasSensitiveValue: false }, aab: { type: OptionType.Boolean, hasSensitiveValue: false }, + filterDevicesArch: { type: OptionType.Boolean, hasSensitiveValue: false }, performance: { type: OptionType.Object, hasSensitiveValue: true }, appleApplicationSpecificPassword: { type: OptionType.String, diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 71335ef5b6..5e1d1e8b1e 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -47,6 +47,9 @@ import { import { IInjector } from "../common/definitions/yok"; import { injector } from "../common/yok"; import { INotConfiguredEnvOptions } from "../common/definitions/commands"; +import { IProjectChangesInfo } from "../definitions/project-changes"; +import { AndroidPrepareData } from "../data/prepare-data"; +import { AndroidBuildData } from "../data/build-data"; interface NativeDependency { name: string; @@ -148,6 +151,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $androidPluginBuildService: IAndroidPluginBuildService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $androidResourcesMigrationService: IAndroidResourcesMigrationService, + private $liveSyncProcessDataService: ILiveSyncProcessDataService, + private $devicesService: Mobile.IDevicesService, private $filesHashService: IFilesHashService, private $gradleCommandService: IGradleCommandService, private $gradleBuildService: IGradleBuildService, @@ -826,8 +831,39 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject await adb.executeShellCommand(["rm", "-rf", deviceRootPath]); } - public async checkForChanges(): Promise { - // Nothing android specific to check yet. + public async checkForChanges( + changesInfo: IProjectChangesInfo, + prepareData: AndroidPrepareData, + projectData: IProjectData + ): Promise { + //we need to check for abi change in connected device vs last built + const deviceDescriptors = this.$liveSyncProcessDataService.getDeviceDescriptors( + projectData.projectDir + ); + const platformData = this.getPlatformData(projectData); + deviceDescriptors.forEach(deviceDescriptor=>{ + const buildData = deviceDescriptor.buildData as AndroidBuildData; + if (buildData.buildFilterDevicesArch) { + const outputPath = platformData.getBuildOutputPath(deviceDescriptor.buildData); + const apkOutputPath = path.join(outputPath, prepareData.release ? "release" : "debug"); + if (!this.$fs.exists(outputPath)) { + return; + } + // check if we already build this arch + // if not we need to say native has changed + const device = this.$devicesService.getDevicesForPlatform(deviceDescriptor.buildData.platform).filter(d=>d.deviceInfo.identifier === deviceDescriptor.identifier)[0]; + const abis = device.deviceInfo.abis.filter(a=>!!a && a.length)[0]; + + const directoryContent = this.$fs.readDirectory(apkOutputPath); + const regexp = new RegExp(`${abis}.*\.apk`); + const files = _.filter(directoryContent, (entry: string) => { + return regexp.test(entry); + }); + if (files.length === 0) { + changesInfo.nativeChanged = true; + } + } + }) } public getDeploymentTarget(projectData: IProjectData): semver.SemVer { diff --git a/lib/services/android/gradle-build-args-service.ts b/lib/services/android/gradle-build-args-service.ts index d3c5dac1bc..b5c68382d0 100644 --- a/lib/services/android/gradle-build-args-service.ts +++ b/lib/services/android/gradle-build-args-service.ts @@ -30,7 +30,6 @@ export class GradleBuildArgsService implements IGradleBuildArgsService { ) { args.push("-PgatherAnalyticsData=true"); } - // allow modifying gradle args from a `before-build-task-args` hook await this.$hooksService.executeBeforeHooks("build-task-args", { hookArgs: { args }, diff --git a/lib/services/android/gradle-build-service.ts b/lib/services/android/gradle-build-service.ts index 4ce97ab89a..002d6db6c9 100644 --- a/lib/services/android/gradle-build-service.ts +++ b/lib/services/android/gradle-build-service.ts @@ -16,7 +16,8 @@ export class GradleBuildService constructor( private $childProcess: IChildProcess, private $gradleBuildArgsService: IGradleBuildArgsService, - private $gradleCommandService: IGradleCommandService + private $gradleCommandService: IGradleCommandService, + private $devicesService: Mobile.IDevicesService ) { super(); } @@ -28,6 +29,17 @@ export class GradleBuildService const buildTaskArgs = await this.$gradleBuildArgsService.getBuildTaskArgs( buildData ); + if (buildData.buildFilterDevicesArch) { + let devices = this.$devicesService.getDevicesForPlatform(buildData.platform); + if(buildData.emulator) { + devices = devices.filter(d=>d.isEmulator); + } + const abis = devices.map(d=>d.deviceInfo.abis[0]); + if (abis.length > 0) { + buildTaskArgs.push(`-PabiFilters=${abis.join(',')}`); + } + } + console.log('buildTaskArgs', buildTaskArgs); const spawnOptions = { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true, diff --git a/lib/services/build-artifacts-service.ts b/lib/services/build-artifacts-service.ts index 1a2bfc94af..fcfe8db442 100644 --- a/lib/services/build-artifacts-service.ts +++ b/lib/services/build-artifacts-service.ts @@ -75,7 +75,7 @@ export class BuildArtifactsService implements IBuildArtifactsService { return []; } - public copyLatestAppPackage( + public copyAppPackages( targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions @@ -85,31 +85,43 @@ export class BuildArtifactsService implements IBuildArtifactsService { const outputPath = buildOutputOptions.outputPath || platformData.getBuildOutputPath(buildOutputOptions); - const applicationPackage = this.getLatestApplicationPackage( + const applicationPackages = this.getAllAppPackages( outputPath, platformData.getValidBuildOutputData(buildOutputOptions) ); - const packageFile = applicationPackage.packageName; this.$fs.ensureDirectoryExists(path.dirname(targetPath)); + let filterRegex: RegExp; + let targetIsDirectory = false; if ( this.$fs.exists(targetPath) && this.$fs.getFsStats(targetPath).isDirectory() ) { - const sourceFileName = path.basename(packageFile); - this.$logger.trace( - `Specified target path: '${targetPath}' is directory. Same filename will be used: '${sourceFileName}'.` - ); - targetPath = path.join(targetPath, sourceFileName); + targetIsDirectory = true; + } else if (targetPath.match(/\.(ipa|aab|apk)/)){ + if (applicationPackages.length > 1){ + filterRegex = new RegExp('universal'); + this.$logger.trace( + `Multiple packages were built but only the universal one will be copied if existing'.` + ); + } + } else { + targetIsDirectory = true; } - this.$fs.copyFile(packageFile, targetPath); - this.$logger.info(`Copied file '${packageFile}' to '${targetPath}'.`); + applicationPackages.forEach(pack => { + const targetFilePath = targetIsDirectory ? path.join(targetPath, path.basename(pack.packageName)) : targetPath; + if (!filterRegex || filterRegex.test(pack.packageName)) { + this.$fs.copyFile(pack.packageName, targetFilePath); + this.$logger.info(`Copied file '${pack.packageName}' to '${targetFilePath}'.`); + } + }); } private getLatestApplicationPackage( buildOutputPath: string, - validBuildOutputData: IValidBuildOutputData + validBuildOutputData: IValidBuildOutputData, + abis?: string[] ): IApplicationPackage { let packages = this.getAllAppPackages( buildOutputPath, diff --git a/lib/services/device/device-install-app-service.ts b/lib/services/device/device-install-app-service.ts index 4c5d16b6f3..f413841e7f 100644 --- a/lib/services/device/device-install-app-service.ts +++ b/lib/services/device/device-install-app-service.ts @@ -28,8 +28,7 @@ export class DeviceInstallAppService { public async installOnDevice( device: Mobile.IDevice, - buildData: IBuildData, - packageFile?: string + buildData: IBuildData ): Promise { this.$logger.info( `Installing on device ${device.deviceInfo.identifier}...` @@ -49,12 +48,38 @@ export class DeviceInstallAppService { device, projectDir: projectData.projectDir, }); + const buildOutputOptions = platformData.getValidBuildOutputData(buildData); + const outputPath = buildData.outputPath || platformData.getBuildOutputPath(buildData); + const packages = await this.$buildArtifactsService.getAllAppPackages( + outputPath, + buildOutputOptions + ); + let packageFile; + if (packages.length === 1) { + // will always be the case on iOS + packageFile = packages[0].packageName; + } else if (device.deviceInfo.abis) { + device.deviceInfo.abis.every(abi=>{ + const index = packages.findIndex(p => p.packageName.indexOf(abi) !== -1); + if (index !== -1) { + packageFile = packages[index].packageName; + return false; + } + return true; + }) + } else { + //we did not find corresponding abi let's try universal + const index = packages.findIndex(p => p.packageName.indexOf('universal') !== -1); + if (index !== -1) { + packageFile = packages[index].packageName; + } + } if (!packageFile) { - packageFile = await this.$buildArtifactsService.getLatestAppPackagePath( - platformData, - buildData + this.$logger.error( + `Could not find a package corresponding to the device with identifier '${device.deviceInfo.identifier}'.` ); + return; } await platformData.platformProjectService.cleanDeviceTempFolder( @@ -88,18 +113,17 @@ export class DeviceInstallAppService { } this.$logger.info( - `Successfully installed on device with identifier '${device.deviceInfo.identifier}'.` + `Successfully installed on device with identifier '${device.deviceInfo.identifier} using package ${packageFile}'.` ); } public async installOnDeviceIfNeeded( device: Mobile.IDevice, - buildData: IBuildData, - packageFile?: string + buildData: IBuildData ): Promise { const shouldInstall = await this.shouldInstall(device, buildData); if (shouldInstall) { - await this.installOnDevice(device, buildData, packageFile); + await this.installOnDevice(device, buildData); } }