Skip to content

Commit 7ff8a54

Browse files
author
Fatme
authored
Merge pull request #3467 from NativeScript/fatme/multiple-apks
Support multiple .apk files produced from gradle build
2 parents 5fe7282 + 6a912c0 commit 7ff8a54

9 files changed

+172
-80
lines changed

lib/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const CONFIG_NS_FILE_NAME = "nsconfig.json";
3333
export const CONFIG_NS_APP_RESOURCES_ENTRY = "appResourcesPath";
3434
export const CONFIG_NS_APP_ENTRY = "appPath";
3535
export const DEPENDENCIES_JSON_NAME = "dependencies.json";
36+
export const APK_EXTENSION_NAME = ".apk";
3637

3738
export class PackageVersion {
3839
static NEXT = "next";

lib/definitions/platform.d.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,9 @@ interface IPlatformData {
260260
projectRoot: string;
261261
normalizedPlatformName: string;
262262
appDestinationDirectoryPath: string;
263-
getDeviceBuildOutputPath(options: IRelease): string;
263+
deviceBuildOutputPath: string;
264264
emulatorBuildOutputPath?: string;
265-
getValidPackageNames(buildOptions: { isReleaseBuild?: boolean, isForDevice?: boolean }): string[];
265+
getValidBuildOutputData(buildOptions: IBuildOutputOptions): IValidBuildOutputData;
266266
frameworkFilesExtensions: string[];
267267
frameworkDirectoriesExtensions?: string[];
268268
frameworkDirectoriesNames?: string[];
@@ -273,6 +273,16 @@ interface IPlatformData {
273273
fastLivesyncFileExtensions: string[];
274274
}
275275

276+
interface IValidBuildOutputData {
277+
packageNames: string[];
278+
regexes?: RegExp[];
279+
}
280+
281+
interface IBuildOutputOptions {
282+
isReleaseBuild?: boolean;
283+
isForDevice?: boolean;
284+
}
285+
276286
interface IPlatformsData {
277287
availablePlatforms: any;
278288
platformsNames: string[];

lib/definitions/project.d.ts

-4
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,6 @@ interface IBuildForDevice {
142142
buildForDevice: boolean;
143143
}
144144

145-
interface IShouldInstall extends IBuildForDevice, IRelease {
146-
147-
}
148-
149145
interface INativePrepare {
150146
skipNativePrepare: boolean;
151147
}

lib/services/android-project-service.ts

+11-27
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,19 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
8383
platformProjectService: this,
8484
emulatorServices: this.$androidEmulatorServices,
8585
projectRoot: projectRoot,
86-
getDeviceBuildOutputPath: (options: IRelease): string => {
87-
return this.getDeviceBuildOutputPath(path.join(...deviceBuildOutputArr), projectData, options);
88-
},
89-
getValidPackageNames: (buildOptions: { isReleaseBuild?: boolean, isForDevice?: boolean }): string[] => {
86+
deviceBuildOutputPath: path.join(...deviceBuildOutputArr),
87+
getValidBuildOutputData: (buildOptions: IBuildOutputOptions): IValidBuildOutputData => {
9088
const buildMode = buildOptions.isReleaseBuild ? Configurations.Release.toLowerCase() : Configurations.Debug.toLowerCase();
9189

92-
return [
93-
`${packageName}-${buildMode}.apk`,
94-
`${projectData.projectName}-${buildMode}.apk`,
95-
`${projectData.projectName}.apk`,
96-
`app-${buildMode}.apk`
97-
];
90+
return {
91+
packageNames: [
92+
`${packageName}-${buildMode}${constants.APK_EXTENSION_NAME}`,
93+
`${projectData.projectName}-${buildMode}${constants.APK_EXTENSION_NAME}`,
94+
`${projectData.projectName}${constants.APK_EXTENSION_NAME}`,
95+
`${constants.APP_FOLDER_NAME}-${buildMode}${constants.APK_EXTENSION_NAME}`
96+
],
97+
regexes: [new RegExp(`${constants.APP_FOLDER_NAME}-.*-(${Configurations.Debug}|${Configurations.Release})${constants.APK_EXTENSION_NAME}`, "i"), new RegExp(`${packageName}-.*-(${Configurations.Debug}|${Configurations.Release})${constants.APK_EXTENSION_NAME}`, "i")]
98+
};
9899
},
99100
frameworkFilesExtensions: [".jar", ".dat", ".so"],
100101
configurationFileName: constants.MANIFEST_FILE_NAME,
@@ -108,23 +109,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
108109
return this._platformData;
109110
}
110111

111-
private getDeviceBuildOutputPath(currentPath: string, projectData: IProjectData, options: IRelease): string {
112-
const currentPlatformData: IDictionary<any> = this.$projectDataService.getNSValue(projectData.projectDir, constants.TNS_ANDROID_RUNTIME_NAME);
113-
const platformVersion = currentPlatformData && currentPlatformData[constants.VERSION_STRING];
114-
const buildType = options.release === true ? "release" : "debug";
115-
const normalizedPath = path.join(currentPath, buildType);
116-
117-
if (semver.valid(platformVersion)) {
118-
const gradleAndroidPluginVersion3xx = "4.0.0";
119-
const normalizedPlatformVersion = `${semver.major(platformVersion)}.${semver.minor(platformVersion)}.0`;
120-
if (semver.lt(normalizedPlatformVersion, gradleAndroidPluginVersion3xx)) {
121-
return currentPath;
122-
}
123-
}
124-
125-
return normalizedPath;
126-
}
127-
128112
// TODO: Remove prior to the 4.0 CLI release @Pip3r4o @PanayotCankov
129113
// Similar to the private method of the same name in platform-service.
130114
private getCurrentPlatformVersion(platformData: IPlatformData, projectData: IProjectData): string {

lib/services/ios-project-service.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,18 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
7272
platformProjectService: this,
7373
emulatorServices: this.$iOSEmulatorServices,
7474
projectRoot: projectRoot,
75-
getDeviceBuildOutputPath: (options: IRelease): string => {
76-
return path.join(projectRoot, "build", "device");
77-
},
78-
emulatorBuildOutputPath: path.join(projectRoot, "build", "emulator"),
79-
getValidPackageNames: (buildOptions: { isReleaseBuild?: boolean, isForDevice?: boolean }): string[] => {
75+
deviceBuildOutputPath: path.join(projectRoot, constants.BUILD_DIR, "device"),
76+
emulatorBuildOutputPath: path.join(projectRoot, constants.BUILD_DIR, "emulator"),
77+
getValidBuildOutputData: (buildOptions: IBuildOutputOptions): IValidBuildOutputData => {
8078
if (buildOptions.isForDevice) {
81-
return [`${projectData.projectName}.ipa`];
79+
return {
80+
packageNames: [`${projectData.projectName}.ipa`]
81+
};
8282
}
8383

84-
return [`${projectData.projectName}.app`, `${projectData.projectName}.zip`];
84+
return {
85+
packageNames: [`${projectData.projectName}.app`, `${projectData.projectName}.zip`]
86+
};
8587
},
8688
frameworkFilesExtensions: [".a", ".framework", ".bin"],
8789
frameworkDirectoriesExtensions: [".framework"],

lib/services/platform-service.ts

+49-27
Original file line numberDiff line numberDiff line change
@@ -342,13 +342,13 @@ export class PlatformService extends EventEmitter implements IPlatformService {
342342

343343
const platformData = this.$platformsData.getPlatformData(platform, projectData);
344344
const forDevice = !buildConfig || buildConfig.buildForDevice;
345-
outputPath = outputPath || (forDevice ? platformData.getDeviceBuildOutputPath(buildConfig) : platformData.emulatorBuildOutputPath || platformData.getDeviceBuildOutputPath(buildConfig));
345+
outputPath = outputPath || (forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath);
346346
if (!this.$fs.exists(outputPath)) {
347347
return true;
348348
}
349349

350-
const packageNames = platformData.getValidPackageNames({ isForDevice: forDevice });
351-
const packages = this.getApplicationPackages(outputPath, packageNames);
350+
const validBuildOutputData = platformData.getValidBuildOutputData({ isForDevice: forDevice });
351+
const packages = this.getApplicationPackages(outputPath, validBuildOutputData);
352352
if (packages.length === 0) {
353353
return true;
354354
}
@@ -450,7 +450,7 @@ export class PlatformService extends EventEmitter implements IPlatformService {
450450

451451
const platformData = this.$platformsData.getPlatformData(platform, projectData);
452452
const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData);
453-
const localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath);
453+
const localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator }, outputPath);
454454
return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime;
455455
}
456456

@@ -561,12 +561,12 @@ export class PlatformService extends EventEmitter implements IPlatformService {
561561
await this.$devicesService.execute(action, this.getCanExecuteAction(platform, runOptions));
562562
}
563563

564-
private getBuildOutputPath(platform: string, platformData: IPlatformData, options: IShouldInstall): string {
564+
private getBuildOutputPath(platform: string, platformData: IPlatformData, options: IBuildForDevice): string {
565565
if (platform.toLowerCase() === this.$devicePlatformsConstants.iOS.toLowerCase()) {
566-
return options.buildForDevice ? platformData.getDeviceBuildOutputPath(options) : platformData.emulatorBuildOutputPath;
566+
return options.buildForDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath;
567567
}
568568

569-
return platformData.getDeviceBuildOutputPath(options);
569+
return platformData.deviceBuildOutputPath;
570570
}
571571

572572
private async getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise<string> {
@@ -586,7 +586,7 @@ export class PlatformService extends EventEmitter implements IPlatformService {
586586
}
587587
}
588588

589-
private getBuildInfo(platform: string, platformData: IPlatformData, options: IShouldInstall, buildOutputPath?: string): IBuildInfo {
589+
private getBuildInfo(platform: string, platformData: IPlatformData, options: IBuildForDevice, buildOutputPath?: string): IBuildInfo {
590590
buildOutputPath = buildOutputPath || this.getBuildOutputPath(platform, platformData, options);
591591
const buildInfoFile = path.join(buildOutputPath, buildInfoFileName);
592592
if (this.$fs.exists(buildInfoFile)) {
@@ -746,27 +746,50 @@ export class PlatformService extends EventEmitter implements IPlatformService {
746746
return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot, projectData);
747747
}
748748

749-
private getApplicationPackages(buildOutputPath: string, validPackageNames: string[]): IApplicationPackage[] {
749+
private getApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] {
750750
// Get latest package` that is produced from build
751-
const candidates = this.$fs.readDirectory(buildOutputPath);
752-
const packages = _.filter(candidates, candidate => {
753-
return _.includes(validPackageNames, candidate);
754-
}).map(currentPackage => {
755-
currentPackage = path.join(buildOutputPath, currentPackage);
756-
757-
return {
758-
packageName: currentPackage,
759-
time: this.$fs.getFsStats(currentPackage).mtime
760-
};
761-
});
751+
let result = this.getApplicationPackagesCore(this.$fs.readDirectory(buildOutputPath).map(filename => path.join(buildOutputPath, filename)), validBuildOutputData.packageNames);
752+
if (result) {
753+
return result;
754+
}
755+
756+
const candidates = this.$fs.enumerateFilesInDirectorySync(buildOutputPath);
757+
result = this.getApplicationPackagesCore(candidates, validBuildOutputData.packageNames);
758+
if (result) {
759+
return result;
760+
}
761+
762+
if (validBuildOutputData.regexes && validBuildOutputData.regexes.length) {
763+
return this.createApplicationPackages(candidates.filter(filepath => _.some(validBuildOutputData.regexes, regex => regex.test(path.basename(filepath)))));
764+
}
765+
766+
return [];
767+
}
762768

763-
return packages;
769+
private getApplicationPackagesCore(candidates: string[], validPackageNames: string[]): IApplicationPackage[] {
770+
const packages = candidates.filter(filePath => _.includes(validPackageNames, path.basename(filePath)));
771+
if (packages.length > 0) {
772+
return this.createApplicationPackages(packages);
773+
}
774+
775+
return null;
776+
}
777+
778+
private createApplicationPackages(packages: string[]): IApplicationPackage[] {
779+
return packages.map(filepath => this.createApplicationPackage(filepath));
764780
}
765781

766-
private getLatestApplicationPackage(buildOutputPath: string, validPackageNames: string[]): IApplicationPackage {
767-
let packages = this.getApplicationPackages(buildOutputPath, validPackageNames);
782+
private createApplicationPackage(packageName: string): IApplicationPackage {
783+
return {
784+
packageName,
785+
time: this.$fs.getFsStats(packageName).mtime
786+
};
787+
}
788+
789+
private getLatestApplicationPackage(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage {
790+
let packages = this.getApplicationPackages(buildOutputPath, validBuildOutputData);
768791
if (packages.length === 0) {
769-
const packageExtName = path.extname(validPackageNames[0]);
792+
const packageExtName = path.extname(validBuildOutputData.packageNames[0]);
770793
this.$errors.fail("No %s found in %s directory", packageExtName, buildOutputPath);
771794
}
772795

@@ -776,11 +799,11 @@ export class PlatformService extends EventEmitter implements IPlatformService {
776799
}
777800

778801
public getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage {
779-
return this.getLatestApplicationPackage(outputPath || platformData.getDeviceBuildOutputPath(buildConfig), platformData.getValidPackageNames({ isForDevice: true, isReleaseBuild: buildConfig.release }));
802+
return this.getLatestApplicationPackage(outputPath || platformData.deviceBuildOutputPath, platformData.getValidBuildOutputData({ isForDevice: true, isReleaseBuild: buildConfig.release }));
780803
}
781804

782805
public getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage {
783-
return this.getLatestApplicationPackage(outputPath || platformData.emulatorBuildOutputPath || platformData.getDeviceBuildOutputPath(buildConfig), platformData.getValidPackageNames({ isForDevice: false, isReleaseBuild: buildConfig.release }));
806+
return this.getLatestApplicationPackage(outputPath || platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath, platformData.getValidBuildOutputData({ isForDevice: false, isReleaseBuild: buildConfig.release }));
784807
}
785808

786809
private async updatePlatform(platform: string, version: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise<void> {
@@ -813,7 +836,6 @@ export class PlatformService extends EventEmitter implements IPlatformService {
813836
} else {
814837
this.$errors.failWithoutHelp("Native Platform cannot be updated.");
815838
}
816-
817839
}
818840

819841
private async updatePlatformCore(platformData: IPlatformData, updateOptions: IUpdatePlatformOptions, projectData: IProjectData, config: IPlatformOptions): Promise<void> {

test/platform-commands.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,8 @@ class PlatformData implements IPlatformData {
3535
};
3636
emulatorServices: Mobile.IEmulatorPlatformServices = null;
3737
projectRoot = "";
38-
getDeviceBuildOutputPath = (buildTypeOption: IRelease) => {
39-
return "";
40-
}
41-
getValidPackageNames = (buildOptions: { isForDevice?: boolean, isReleaseBuild?: boolean }) => [""];
38+
deviceBuildOutputPath = "";
39+
getValidBuildOutputData = (buildOptions: IBuildOutputOptions) => ({ packageNames: [""] });
4240
validPackageNamesForDevice: string[] = [];
4341
frameworkFilesExtensions = [".jar", ".dat"];
4442
appDestinationDirectoryPath = "";

test/platform-service.ts

+84-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ function createTestInjector() {
8888
testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService);
8989
testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService);
9090
testInjector.register("analyticsService", {
91-
track: async (): Promise<any[]> => undefined
91+
track: async (): Promise<any[]> => undefined,
92+
trackEventActionInGoogleAnalytics: () => Promise.resolve()
9293
});
9394
testInjector.register("messages", Messages);
9495
testInjector.register("devicePathProvider", {});
@@ -914,4 +915,86 @@ describe('Platform Service Tests', () => {
914915
assert.isFalse(warnings.indexOf("has errors") !== -1);
915916
});
916917
});
918+
919+
describe("build", () => {
920+
function mockData(buildOutput: string[], projectName: string): void {
921+
mockPlatformsData(projectName);
922+
mockFileSystem(buildOutput);
923+
platformService.saveBuildInfoFile = () => undefined;
924+
}
925+
926+
function mockPlatformsData(projectName: string): void {
927+
const platformsData = testInjector.resolve("platformsData");
928+
platformsData.getPlatformData = (platform: string) => {
929+
return {
930+
deviceBuildOutputPath: "",
931+
platformProjectService: {
932+
buildProject: () => Promise.resolve(),
933+
on: () => ({}),
934+
removeListener: () => ({})
935+
},
936+
getValidBuildOutputData: () => ({
937+
packageNames: ["app-debug.apk", "app-release.apk", `${projectName}-debug.apk`, `${projectName}-release.apk`],
938+
regexes: [/app-.*-(debug|release).apk/, new RegExp(`${projectName}-.*-(debug|release).apk`)]
939+
})
940+
};
941+
};
942+
}
943+
944+
function mockFileSystem(enumeratedFiles: string[]): void {
945+
const fs = testInjector.resolve<IFileSystem>("fs");
946+
fs.enumerateFilesInDirectorySync = () => enumeratedFiles;
947+
fs.readDirectory = () => [];
948+
fs.getFsStats = () => (<any>({ mtime: new Date() }));
949+
}
950+
951+
describe("android platform", () => {
952+
function getTestCases(configuration: string, apkName: string) {
953+
return [{
954+
name: "no additional options are specified in .gradle file",
955+
buildOutput: [`/my/path/${configuration}/${apkName}-${configuration}.apk`],
956+
expectedResult: `/my/path/${configuration}/${apkName}-${configuration}.apk`
957+
}, {
958+
name: "productFlavors are specified in .gradle file",
959+
buildOutput: [`/my/path/arm64Demo/${configuration}/${apkName}-arm64-demo-${configuration}.apk`,
960+
`/my/path/arm64Full/${configuration}/${apkName}-arm64-full-${configuration}.apk`,
961+
`/my/path/armDemo/${configuration}/${apkName}-arm-demo-${configuration}.apk`,
962+
`/my/path/armFull/${configuration}/${apkName}-arm-full-${configuration}.apk`,
963+
`/my/path/x86Demo/${configuration}/${apkName}-x86-demo-${configuration}.apk`,
964+
`/my/path/x86Full/${configuration}/${apkName}-x86-full-${configuration}.apk`],
965+
expectedResult: `/my/path/x86Full/${configuration}/${apkName}-x86-full-${configuration}.apk`
966+
}, {
967+
name: "split options are specified in .gradle file",
968+
buildOutput: [`/my/path/${configuration}/${apkName}-arm64-v8a-${configuration}.apk`,
969+
`/my/path/${configuration}/${apkName}-armeabi-v7a-${configuration}.apk`,
970+
`/my/path/${configuration}/${apkName}-universal-${configuration}.apk`,
971+
`/my/path/${configuration}/${apkName}-x86-${configuration}.apk`],
972+
expectedResult: `/my/path/${configuration}/${apkName}-x86-${configuration}.apk`
973+
}, {
974+
name: "android-runtime has version < 4.0.0",
975+
buildOutput: [`/my/path/apk/${apkName}-${configuration}.apk`],
976+
expectedResult: `/my/path/apk/${apkName}-${configuration}.apk`
977+
}];
978+
}
979+
980+
const platform = "Android";
981+
const buildConfigs = [{buildForDevice: false}, {buildForDevice: true}];
982+
const apkNames = ["app", "testProj"];
983+
const configurations = ["debug", "release"];
984+
985+
_.each(apkNames, apkName => {
986+
_.each(buildConfigs, buildConfig => {
987+
_.each(configurations, configuration => {
988+
_.each(getTestCases(configuration, apkName), testCase => {
989+
it(`should find correct ${configuration} ${apkName}.apk when ${testCase.name} and buildConfig is ${JSON.stringify(buildConfig)}`, async () => {
990+
mockData(testCase.buildOutput, apkName);
991+
const actualResult = await platformService.buildPlatform(platform, <IBuildConfig>buildConfig, <IProjectData>{projectName: ""});
992+
assert.deepEqual(actualResult, testCase.expectedResult);
993+
});
994+
});
995+
});
996+
});
997+
});
998+
});
999+
});
9171000
});

0 commit comments

Comments
 (0)