Skip to content

feat: only install corresponding abi package when possible #5635

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
6 changes: 5 additions & 1 deletion lib/commands/build.ts
Original file line number Diff line number Diff line change
@@ -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);

6 changes: 6 additions & 0 deletions lib/common/definitions/mobile.d.ts
Original file line number Diff line number Diff line change
@@ -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 {}
4 changes: 4 additions & 0 deletions lib/common/mobile/android/android-device.ts
Original file line number Diff line number Diff line change
@@ -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,
2 changes: 1 addition & 1 deletion lib/controllers/build-controller.ts
Original file line number Diff line number Diff line change
@@ -116,7 +116,7 @@ export class BuildController extends EventEmitter implements IBuildController {
);

if (buildData.copyTo) {
this.$buildArtifactsService.copyLatestAppPackage(
this.$buildArtifactsService.copyAppPackages(
buildData.copyTo,
platformData,
buildData
5 changes: 2 additions & 3 deletions lib/controllers/deploy-controller.ts
Original file line number Diff line number Diff line change
@@ -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 }
);
};

21 changes: 5 additions & 16 deletions lib/controllers/run-controller.ts
Original file line number Diff line number Diff line change
@@ -471,7 +471,6 @@ export class RunController extends EventEmitter implements IRunController {
deviceDescriptors: ILiveSyncDeviceDescriptor[]
): Promise<void> {
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,25 +517,20 @@ 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 =
prepareResultData.hasNativeChanges ||
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(
2 changes: 2 additions & 0 deletions lib/data/build-data.ts
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion lib/definitions/android-plugin-migrator.d.ts
Original file line number Diff line number Diff line change
@@ -10,8 +10,8 @@ interface IAndroidBuildOptions {
pluginName: string;
aarOutputDir: string;
tempPluginDirPath: string;
gradlePath?: string;
gradleArgs?: string;
gradlePath?: string;
}

interface IAndroidPluginBuildService {
3 changes: 2 additions & 1 deletion lib/definitions/build.d.ts
Original file line number Diff line number Diff line change
@@ -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<string>;
copyLatestAppPackage(
copyAppPackages(
targetPath: string,
platformData: IPlatformData,
buildOutputOptions: IBuildOutputOptions
6 changes: 2 additions & 4 deletions lib/definitions/run.d.ts
Original file line number Diff line number Diff line change
@@ -31,13 +31,11 @@ declare global {
interface IDeviceInstallAppService {
installOnDevice(
device: Mobile.IDevice,
buildData: IBuildData,
packageFile?: string
buildData: IBuildData
): Promise<void>;
installOnDeviceIfNeeded(
device: Mobile.IDevice,
buildData: IBuildData,
packageFile?: string
buildData: IBuildData
): Promise<void>;
shouldInstall(
device: Mobile.IDevice,
1 change: 1 addition & 0 deletions lib/helpers/deploy-command-helper.ts
Original file line number Diff line number Diff line change
@@ -53,6 +53,7 @@ export class DeployCommandHelper {
{
...this.$options.argv,
outputPath,
buildFilterDevicesArch: false,
buildForDevice: !d.isEmulator,
skipWatcher: !this.$options.watch,
nativePrepare: {
1 change: 1 addition & 0 deletions lib/options.ts
Original file line number Diff line number Diff line change
@@ -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,
40 changes: 38 additions & 2 deletions lib/services/android-project-service.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
// Nothing android specific to check yet.
public async checkForChanges(
changesInfo: IProjectChangesInfo,
prepareData: AndroidPrepareData,
projectData: IProjectData
): Promise<void> {
//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 {
1 change: 0 additions & 1 deletion lib/services/android/gradle-build-args-service.ts
Original file line number Diff line number Diff line change
@@ -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 },
14 changes: 13 additions & 1 deletion lib/services/android/gradle-build-service.ts
Original file line number Diff line number Diff line change
@@ -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,
34 changes: 23 additions & 11 deletions lib/services/build-artifacts-service.ts
Original file line number Diff line number Diff line change
@@ -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,
42 changes: 33 additions & 9 deletions lib/services/device/device-install-app-service.ts
Original file line number Diff line number Diff line change
@@ -28,8 +28,7 @@ export class DeviceInstallAppService {

public async installOnDevice(
device: Mobile.IDevice,
buildData: IBuildData,
packageFile?: string
buildData: IBuildData
): Promise<void> {
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<void> {
const shouldInstall = await this.shouldInstall(device, buildData);
if (shouldInstall) {
await this.installOnDevice(device, buildData, packageFile);
await this.installOnDevice(device, buildData);
}
}