diff --git a/lib/commands/build.ts b/lib/commands/build.ts index b3b732e2d6..eb06f239f9 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/commands/clean.ts b/lib/commands/clean.ts index 8868f59092..b9dfcbcbf1 100644 --- a/lib/commands/clean.ts +++ b/lib/commands/clean.ts @@ -6,6 +6,7 @@ import { IProjectCleanupResult, IProjectCleanupService, IProjectConfigService, + IProjectData, IProjectService, } from "../definitions/project"; @@ -83,6 +84,7 @@ export class CleanCommand implements ICommand { constructor( private $projectCleanupService: IProjectCleanupService, private $projectConfigService: IProjectConfigService, + private $projectData: IProjectData, private $terminalSpinnerService: ITerminalSpinnerService, private $projectService: IProjectService, private $prompter: IPrompter, @@ -108,7 +110,7 @@ export class CleanCommand implements ICommand { let pathsToClean = [ constants.HOOKS_DIR_NAME, - constants.PLATFORMS_DIR_NAME, + this.$projectData.getBuildRelativeDirectoryPath(), constants.NODE_MODULES_FOLDER_NAME, constants.PACKAGE_LOCK_JSON_FILE_NAME, ]; diff --git a/lib/commands/config.ts b/lib/commands/config.ts index 80839a1cfa..306779b3b8 100644 --- a/lib/commands/config.ts +++ b/lib/commands/config.ts @@ -4,19 +4,26 @@ import { IProjectConfigService } from "../definitions/project"; import { SupportedConfigValues } from "../tools/config-manipulation/config-transformer"; import { IErrors } from "../common/declarations"; import { color } from "../color"; +import { IOptions } from "../declarations"; export class ConfigListCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor( private $projectConfigService: IProjectConfigService, + private $options: IOptions, private $logger: ILogger ) {} public async execute(args: string[]): Promise { try { const config = this.$projectConfigService.readConfig(); - this.$logger.info(this.getValueString(config as SupportedConfigValues)); + if (this.$options.json) { + console.log(JSON.stringify(config)) + } else { + this.$logger.info(this.getValueString(config as SupportedConfigValues)); + + } } catch (error) { this.$logger.info("Failed to read config. Error is: ", error); } diff --git a/lib/commands/typings.ts b/lib/commands/typings.ts index 0f2c81028b..f9a511ac8c 100644 --- a/lib/commands/typings.ts +++ b/lib/commands/typings.ts @@ -67,7 +67,7 @@ export class TypingsCommand implements ICommand { const dtsGeneratorPath = path.resolve( this.$projectData.projectDir, - "platforms", + this.$projectData.getBuildRelativeDirectoryPath(), "android", "build-tools", "dts-generator.jar" diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index 9102cdea65..9c19a6b6d0 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -1276,6 +1276,11 @@ interface IDashedOption { * Specifies either a single option key (string), or an array of options that must be followed by option values. */ requiresArg?: any; + + /** + * Set to true to define the option as an array option https://github.com/yargs/yargs/blob/main/docs/api.md#array + */ + array?: any; } /** 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..c3cdf279ba 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,10 @@ export class AndroidDevice implements Mobile.IAndroidDevice { identifier: this.identifier, displayName: details.name, model: details.model, + abis: [ + ...details["cpu.abilist64"].split(","), + ...details["cpu.abilist32"].split(","), + ], version, vendor: details.brand, platform: this.$devicePlatformsConstants.Android, @@ -111,12 +118,14 @@ export class AndroidDevice implements Mobile.IAndroidDevice { : [DeviceConnectionType.USB]; if (this.isEmulator) { - this.deviceInfo.displayName = await this.$androidEmulatorServices.getRunningEmulatorName( - this.identifier - ); - this.deviceInfo.imageIdentifier = await this.$androidEmulatorServices.getRunningEmulatorImageIdentifier( - this.identifier - ); + this.deviceInfo.displayName = + await this.$androidEmulatorServices.getRunningEmulatorName( + this.identifier + ); + this.deviceInfo.imageIdentifier = + await this.$androidEmulatorServices.getRunningEmulatorImageIdentifier( + this.identifier + ); } this.$logger.trace(this.deviceInfo); @@ -161,9 +170,10 @@ export class AndroidDevice implements Mobile.IAndroidDevice { // sample line is "ro.build.version.release=4.4" in /system/build.prop // sample line from getprop is: [ro.build.version.release]: [6.0] // NOTE: some props do not have value: [ro.build.version.base_os]: [] - const match = /(?:\[?ro\.build\.version|ro\.product|ro\.build)\.(.+?)]?(?:\:|=)(?:\s*?\[)?(.*?)]?$/.exec( - value - ); + const match = + /(?:\[?ro\.build\.version|ro\.product|ro\.build)\.(.+?)]?(?:\:|=)(?:\s*?\[)?(.*?)]?$/.exec( + value + ); if (match) { parsedDetails[match[1]] = match[2]; } @@ -189,7 +199,8 @@ export class AndroidDevice implements Mobile.IAndroidDevice { } private async getType(): Promise { - const runningEmulatorIds = await this.$androidEmulatorServices.getRunningEmulatorIds(); + const runningEmulatorIds = + await this.$androidEmulatorServices.getRunningEmulatorIds(); if ( _.find(runningEmulatorIds, (emulatorId) => emulatorId === this.identifier) ) { diff --git a/lib/constants.ts b/lib/constants.ts index bb1cf94aa5..6a89b792f2 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -58,6 +58,7 @@ export const BUNDLE_DIR = "bundle"; export const RESOURCES_DIR = "res"; export const CONFIG_NS_FILE_NAME = "nsconfig.json"; export const CONFIG_NS_APP_RESOURCES_ENTRY = "appResourcesPath"; +export const CONFIG_NS_BUILD_ENTRY = "buildPath"; export const CONFIG_NS_APP_ENTRY = "appPath"; export const CONFIG_FILE_NAME_DISPLAY = "nativescript.config.(js|ts)"; export const CONFIG_FILE_NAME_JS = "nativescript.config.js"; diff --git a/lib/controllers/build-controller.ts b/lib/controllers/build-controller.ts index f5ebb1666c..e37f5ed842 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 @@ -165,9 +165,8 @@ export class BuildController extends EventEmitter implements IBuildController { return true; } - const validBuildOutputData = platformData.getValidBuildOutputData( - buildData - ); + const validBuildOutputData = + platformData.getValidBuildOutputData(buildData); const packages = this.$buildArtifactsService.getAllAppPackages( outputPath, validBuildOutputData @@ -176,9 +175,8 @@ export class BuildController extends EventEmitter implements IBuildController { return true; } - const prepareInfo = this.$projectChangesService.getPrepareInfo( - platformData - ); + const prepareInfo = + this.$projectChangesService.getPrepareInfo(platformData); const buildInfo = this.$buildInfoFileService.getLocalBuildInfo( platformData, buildData diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index beea13ef08..6133c46ee1 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -23,12 +23,11 @@ export class DeployController { }, }; await this.$prepareController.prepare(prepareData); - const packageFilePath = await deviceDescriptor.buildAction(); - await this.$deviceInstallAppService.installOnDevice( - device, - { ...deviceDescriptor.buildData, buildForDevice: !device.isEmulator }, - packageFilePath - ); + await deviceDescriptor.buildAction(); + await this.$deviceInstallAppService.installOnDevice(device, { + ...deviceDescriptor.buildData, + buildForDevice: !device.isEmulator, + }); }; await this.$devicesService.execute( diff --git a/lib/controllers/migrate-controller.ts b/lib/controllers/migrate-controller.ts index 50f6e3c219..79851a7b17 100644 --- a/lib/controllers/migrate-controller.ts +++ b/lib/controllers/migrate-controller.ts @@ -67,6 +67,7 @@ export class MigrateController private $pluginsService: IPluginsService, private $projectDataService: IProjectDataService, private $projectConfigService: IProjectConfigService, + private $projectData: IProjectData, private $options: IOptions, private $resources: IResourceLoader, private $injector: IInjector, @@ -721,7 +722,7 @@ export class MigrateController private async cleanUpProject(projectData: IProjectData): Promise { await this.$projectCleanupService.clean([ constants.HOOKS_DIR_NAME, - constants.PLATFORMS_DIR_NAME, + this.$projectData.getBuildRelativeDirectoryPath(), constants.NODE_MODULES_FOLDER_NAME, constants.PACKAGE_LOCK_JSON_FILE_NAME, ]); diff --git a/lib/controllers/platform-controller.ts b/lib/controllers/platform-controller.ts index 8b98e80af0..a827fbadc5 100644 --- a/lib/controllers/platform-controller.ts +++ b/lib/controllers/platform-controller.ts @@ -62,16 +62,15 @@ export class PlatformController implements IPlatformController { this.$logger.trace("Determined package to install is", packageToInstall); - const installedPlatformVersion = await this.$addPlatformService.addPlatformSafe( - projectData, - platformData, - packageToInstall, - addPlatformData - ); - - this.$fs.ensureDirectoryExists( - path.join(projectData.platformsDir, platform) - ); + const installedPlatformVersion = + await this.$addPlatformService.addPlatformSafe( + projectData, + platformData, + packageToInstall, + addPlatformData + ); + const buildPath = projectData.platformsDir; + this.$fs.ensureDirectoryExists(path.join(buildPath, platform)); if (this.$mobileHelper.isAndroidPlatform(platform)) { const gradlePropertiesPath = path.resolve( @@ -80,7 +79,8 @@ export class PlatformController implements IPlatformController { ); const commentHeader = "# App configuration"; const appPath = projectData.getAppDirectoryRelativePath(); - const appResourcesPath = projectData.getAppResourcesRelativeDirectoryPath(); + const appResourcesPath = + projectData.getAppResourcesRelativeDirectoryPath(); let gradlePropertiesContents = ""; if (this.$fs.exists(gradlePropertiesPath)) { @@ -96,6 +96,7 @@ export class PlatformController implements IPlatformController { commentHeader, `appPath = ${appPath}`, `appResourcesPath = ${appResourcesPath}`, + `buildPath = ${buildPath}`, "", ].join("\n"); @@ -161,9 +162,10 @@ export class PlatformController implements IPlatformController { if (!desiredRuntimePackage.version) { // if no version is explicitly added, then we use the latest - desiredRuntimePackage.version = await this.$packageInstallationManager.getLatestCompatibleVersion( - desiredRuntimePackage.name - ); + desiredRuntimePackage.version = + await this.$packageInstallationManager.getLatestCompatibleVersion( + desiredRuntimePackage.name + ); } // const currentPlatformData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); // version = (currentPlatformData && currentPlatformData.version) || @@ -186,9 +188,8 @@ export class PlatformController implements IPlatformController { const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; - const prepareInfo = this.$projectChangesService.getPrepareInfo( - platformData - ); + const prepareInfo = + this.$projectChangesService.getPrepareInfo(platformData); const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 8c22fe3e61..87933a6c35 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -289,7 +289,11 @@ export class PrepareController extends EventEmitter { data.platform.toLowerCase() === platformData.platformNameLowerCase ) { if (this.isFileWatcherPaused()) return; - this.emitPrepareEvent({ ...data, hasNativeChanges: false }); + this.emitPrepareEvent({ + ...data, + files: data.files ?? [], + hasNativeChanges: false, + }); } }; @@ -371,7 +375,7 @@ export class PrepareController extends EventEmitter { this.$logger.info(`Chokidar raised event ${event} for ${filePath}.`); await this.writeRuntimePackageJson(projectData, platformData); this.emitPrepareEvent({ - files: [], + files: [filePath], staleFiles: [], hasOnlyHotUpdateFiles: false, hmrData: null, diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index b05805884b..6b0fa17931 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -12,7 +12,7 @@ import * as util from "util"; import * as _ from "lodash"; import { IProjectDataService, IProjectData } from "../definitions/project"; import { IBuildController } from "../definitions/build"; -import { IPlatformsDataService } from "../definitions/platform"; +import { IPlatformData, IPlatformsDataService } from "../definitions/platform"; import { IDebugController } from "../definitions/debug"; import { IPluginsService } from "../definitions/plugins"; import { @@ -23,6 +23,7 @@ import { } from "../common/declarations"; import { IInjector } from "../common/definitions/yok"; import { injector } from "../common/yok"; +import { hook } from "../common/helpers"; export class RunController extends EventEmitter implements IRunController { private prepareReadyEventHandler: any = null; @@ -95,17 +96,28 @@ export class RunController extends EventEmitter implements IRunController { const changesInfo = await this.$projectChangesService.checkForChanges( platformData, projectData, - prepareData + prepareData, + data ); if (changesInfo.hasChanges) { await this.syncChangedDataOnDevices( data, projectData, + platformData, liveSyncInfo ); } } else { - await this.syncChangedDataOnDevices(data, projectData, liveSyncInfo); + const platformData = this.$platformsDataService.getPlatformData( + data.platform, + projectData + ); + await this.syncChangedDataOnDevices( + data, + projectData, + platformData, + liveSyncInfo + ); } }; @@ -471,7 +483,6 @@ export class RunController extends EventEmitter implements IRunController { deviceDescriptors: ILiveSyncDeviceDescriptor[] ): Promise { const rebuiltInformation: IDictionary<{ - packageFilePath: string; platform: string; isEmulator: boolean; }> = {}; @@ -508,8 +519,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 +529,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 +539,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, }; } else { await this.$analyticsService.trackEventActionInGoogleAnalytics({ @@ -550,8 +554,7 @@ export class RunController extends EventEmitter implements IRunController { await this.$deviceInstallAppService.installOnDeviceIfNeeded( device, - buildData, - packageFilePath + buildData ); } @@ -623,9 +626,11 @@ export class RunController extends EventEmitter implements IRunController { ); } + @hook("syncChangedDataOnDevices") private async syncChangedDataOnDevices( data: IFilesChangeEventData, projectData: IProjectData, + platformData: IPlatformData, liveSyncInfo: ILiveSyncInfo ): Promise { const successfullySyncedMessageFormat = `Successfully synced application %s on device %s.`; @@ -713,9 +718,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/controllers/update-controller.ts b/lib/controllers/update-controller.ts index bcb4a5abf0..fbcb1228f4 100644 --- a/lib/controllers/update-controller.ts +++ b/lib/controllers/update-controller.ts @@ -296,7 +296,7 @@ export class UpdateController private async cleanUpProject(): Promise { await this.$projectCleanupService.clean([ constants.HOOKS_DIR_NAME, - constants.PLATFORMS_DIR_NAME, + this.$projectDataService.getProjectData().getBuildRelativeDirectoryPath(), constants.NODE_MODULES_FOLDER_NAME, constants.PACKAGE_LOCK_JSON_FILE_NAME, ]); diff --git a/lib/data/build-data.ts b/lib/data/build-data.ts index 87af14d166..0a7c4d3392 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; @@ -47,6 +48,7 @@ export class AndroidBuildData extends BuildData { public keyStoreAliasPassword: string; public keyStorePassword: string; public androidBundle: boolean; + public gradleFlavor: string; public gradlePath: string; public gradleArgs: string; @@ -58,6 +60,8 @@ 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.gradleFlavor = data.gradleFlavor; this.gradlePath = data.gradlePath; this.gradleArgs = data.gradleArgs; } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 9f627a9202..b1d2a7940c 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -579,8 +579,9 @@ interface IAndroidBundleOptions { } interface IAndroidOptions { + gradleArgs: string[]; + gradleFlavor: string; gradlePath: string; - gradleArgs: string; } interface ITypingsOptions { diff --git a/lib/definitions/android-plugin-migrator.d.ts b/lib/definitions/android-plugin-migrator.d.ts index f5ad873307..832b3e7e64 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; + gradleArgs?: string[]; gradlePath?: string; - gradleArgs?: string; } interface IAndroidPluginBuildService { @@ -48,5 +48,5 @@ interface IBuildAndroidPluginData extends Partial { /** * Optional custom Gradle arguments. */ - gradleArgs?: string; + gradleArgs?: string[]; } diff --git a/lib/definitions/build.d.ts b/lib/definitions/build.d.ts index 0ef831745c..25f292e5f8 100644 --- a/lib/definitions/build.d.ts +++ b/lib/definitions/build.d.ts @@ -30,8 +30,10 @@ interface IAndroidBuildData extends IBuildData, IAndroidSigningData, IHasAndroidBundle { + buildFilterDevicesArch?: boolean; + gradleArgs?: string[]; + gradleFlavor?: string; gradlePath?: string; - gradleArgs?: string; } interface IAndroidSigningData { @@ -61,7 +63,7 @@ interface IBuildArtifactsService { platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions ): Promise; - copyLatestAppPackage( + copyAppPackages( targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index a25346adfa..2c51c825a7 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -139,6 +139,8 @@ interface INsConfigAndroid extends INsConfigPlaform { enableLineBreakpoints?: boolean; enableMultithreadedJavascript?: boolean; + + gradleVersion?: string; } interface INsConfigHooks { @@ -151,6 +153,7 @@ interface INsConfig { main?: string; appPath?: string; appResourcesPath?: string; + buildPath?: string; shared?: boolean; overridePods?: string; webpackConfigPath?: string; @@ -207,6 +210,7 @@ interface IProjectData extends ICreateProjectData { getAppDirectoryRelativePath(): string; getAppResourcesDirectoryPath(projectDir?: string): string; getAppResourcesRelativeDirectoryPath(): string; + getBuildRelativeDirectoryPath(): string; } interface IProjectDataService { 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..5258d16b12 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -219,9 +219,15 @@ export class Options { default: false, hasSensitiveValue: false, }, + gradleArgs: { + type: OptionType.String, + hasSensitiveValue: false, + array: true, + }, + gradleFlavor: { type: OptionType.String, hasSensitiveValue: false }, 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, @@ -422,9 +428,8 @@ export class Options { this.$settingsService.setSettings({ profileDir: this.argv.profileDir, }); - this.argv.profileDir = this.argv[ - "profile-dir" - ] = this.$settingsService.getProfileDir(); + this.argv.profileDir = this.argv["profile-dir"] = + this.$settingsService.getProfileDir(); // if justlaunch is set, it takes precedence over the --watch flag and the default true value if (this.argv.justlaunch) { diff --git a/lib/project-data.ts b/lib/project-data.ts index f8bee9a09c..c46f16ce3d 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -164,14 +164,17 @@ export class ProjectData implements IProjectData { this.projectName = this.$projectHelper.sanitizeName( path.basename(projectDir) ); - this.platformsDir = path.join(projectDir, constants.PLATFORMS_DIR_NAME); + this.nsConfig = nsConfig; + this.platformsDir = path.join( + projectDir, + this.getBuildRelativeDirectoryPath() + ); this.projectFilePath = projectFilePath; this.projectIdentifiers = this.initializeProjectIdentifiers(nsConfig); this.packageJsonData = packageJsonData; this.dependencies = packageJsonData.dependencies; this.devDependencies = packageJsonData.devDependencies; this.projectType = this.getProjectType(); - this.nsConfig = nsConfig; this.ignoredDependencies = nsConfig?.ignoredNativeDependencies; this.appDirectoryPath = this.getAppDirectoryPath(); this.appResourcesDirectoryPath = this.getAppResourcesDirectoryPath(); @@ -217,11 +220,10 @@ export class ProjectData implements IProjectData { appResourcesDir, this.$devicePlatformsConstants.Android ); - const androidManifestDir = this.$androidResourcesMigrationService.hasMigrated( - appResourcesDir - ) - ? path.join(androidDirPath, constants.SRC_DIR, constants.MAIN_DIR) - : androidDirPath; + const androidManifestDir = + this.$androidResourcesMigrationService.hasMigrated(appResourcesDir) + ? path.join(androidDirPath, constants.SRC_DIR, constants.MAIN_DIR) + : androidDirPath; return path.join(androidManifestDir, constants.MANIFEST_FILE_NAME); } @@ -244,7 +246,8 @@ export class ProjectData implements IProjectData { } public getAppResourcesDirectoryPath(projectDir?: string): string { - const appResourcesRelativePath = this.getAppResourcesRelativeDirectoryPath(); + const appResourcesRelativePath = + this.getAppResourcesRelativeDirectoryPath(); return this.resolveToProjectDir(appResourcesRelativePath, projectDir); } @@ -270,6 +273,14 @@ export class ProjectData implements IProjectData { return this.resolveToProjectDir(appRelativePath, projectDir); } + public getBuildRelativeDirectoryPath(): string { + if (this.nsConfig && this.nsConfig[constants.CONFIG_NS_BUILD_ENTRY]) { + return this.nsConfig[constants.CONFIG_NS_BUILD_ENTRY]; + } + + return constants.PLATFORMS_DIR_NAME; + } + public getAppDirectoryRelativePath(): string { if (this.nsConfig && this.nsConfig[constants.CONFIG_NS_APP_ENTRY]) { return this.nsConfig[constants.CONFIG_NS_APP_ENTRY]; diff --git a/lib/services/android-plugin-build-service.ts b/lib/services/android-plugin-build-service.ts index 0ba08ec83c..314d94ceca 100644 --- a/lib/services/android-plugin-build-service.ts +++ b/lib/services/android-plugin-build-service.ts @@ -144,38 +144,6 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { return promise; } - private getIncludeGradleCompileDependenciesScope( - includeGradleFileContent: string - ): Array { - const indexOfDependenciesScope = - includeGradleFileContent.indexOf("dependencies"); - const result: Array = []; - - if (indexOfDependenciesScope === -1) { - return result; - } - - const indexOfRepositoriesScope = - includeGradleFileContent.indexOf("repositories"); - - let repositoriesScope = ""; - if (indexOfRepositoriesScope >= 0) { - repositoriesScope = this.getScope( - "repositories", - includeGradleFileContent - ); - result.push(repositoriesScope); - } - - const dependenciesScope = this.getScope( - "dependencies", - includeGradleFileContent - ); - result.push(dependenciesScope); - - return result; - } - private getScope(scopeName: string, content: string): string { const indexOfScopeName = content.indexOf(scopeName); const openingBracket = "{"; @@ -389,12 +357,22 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { for (const dir of androidSourceSetDirectories) { const dirName = path.basename(dir); const destination = path.join(pluginTempMainSrcDir, dirName); - this.$fs.ensureDirectoryExists(destination); this.$fs.copyFile(path.join(dir, "*"), destination); } } - + private extractNamespaceFromManifest(manifestPath: string): string { + const fileContent = this.$fs.readText(manifestPath); + const contentRegex = new RegExp('package="(.*?)"'); + const match = fileContent.match(contentRegex); + let namespace: string; + if (match) { + namespace = match[1]; + const replacedFileContent = fileContent.replace(contentRegex, ""); + this.$fs.writeFile(manifestPath, replacedFileContent); + } + return namespace; + } private async setupGradle( pluginTempDir: string, platformsAndroidDirPath: string, @@ -409,18 +387,36 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { const settingsGradlePath = path.join(pluginTempDir, "settings.gradle"); this.$fs.copyFile(allGradleTemplateFiles, pluginTempDir); - this.addCompileDependencies(platformsAndroidDirPath, buildGradlePath); const runtimeGradleVersions = await this.getRuntimeGradleVersions( projectDir ); - this.replaceGradleVersion( - pluginTempDir, - runtimeGradleVersions.gradleVersion - ); + let gradleVersion = runtimeGradleVersions.gradleVersion; + if (this.$projectData.nsConfig.android.gradleVersion) { + gradleVersion = this.$projectData.nsConfig.android.gradleVersion; + } + this.replaceGradleVersion(pluginTempDir, gradleVersion); + this.replaceGradleAndroidPluginVersion( buildGradlePath, runtimeGradleVersions.gradleAndroidPluginVersion ); + + // In gradle 8 every android project must have a namespace in "android" + // and the package property in manifest is now forbidden + // let s replace it + const manifestFilePath = this.getManifest( + path.join(pluginTempDir, "src", "main") + ); + let pluginNamespace = this.extractNamespaceFromManifest(manifestFilePath); + if (!pluginNamespace) { + pluginNamespace = pluginName.replace(/@/g, "").replace(/[/-]/g, "."); + } + + this.replaceFileContent( + buildGradlePath, + "{{pluginNamespace}}", + pluginNamespace + ); this.replaceFileContent(buildGradlePath, "{{pluginName}}", pluginName); this.replaceFileContent(settingsGradlePath, "{{pluginName}}", pluginName); } @@ -667,28 +663,6 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { this.$fs.writeFile(filePath, replacedFileContent); } - private addCompileDependencies( - platformsAndroidDirPath: string, - buildGradlePath: string - ): void { - const includeGradlePath = path.join( - platformsAndroidDirPath, - INCLUDE_GRADLE_NAME - ); - if (this.$fs.exists(includeGradlePath)) { - const includeGradleContent = this.$fs.readText(includeGradlePath); - const compileDependencies = - this.getIncludeGradleCompileDependenciesScope(includeGradleContent); - - if (compileDependencies.length) { - this.$fs.appendFile( - buildGradlePath, - "\n" + compileDependencies.join("\n") - ); - } - } - } - private copyAar( shortPluginName: string, pluginTempDir: string, @@ -800,11 +774,21 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { `-PtempBuild=true`, `-PcompileSdk=android-${pluginBuildSettings.androidToolsInfo.compileSdkVersion}`, `-PbuildToolsVersion=${pluginBuildSettings.androidToolsInfo.buildToolsVersion}`, + `-PprojectRoot=${this.$projectData.projectDir}`, + `-DprojectRoot=${this.$projectData.projectDir}`, // we need it as a -D to be able to read it from settings.gradle `-PappPath=${this.$projectData.getAppDirectoryPath()}`, + `-PappBuildPath=${this.$projectData.getBuildRelativeDirectoryPath()}`, + `-DappBuildPath=${this.$projectData.getBuildRelativeDirectoryPath()}`, // we need it as a -D to be able to read it from settings.gradle `-PappResourcesPath=${this.$projectData.getAppResourcesDirectoryPath()}`, ]; if (pluginBuildSettings.gradleArgs) { - localArgs.push(pluginBuildSettings.gradleArgs); + const additionalArgs: string[] = []; + pluginBuildSettings.gradleArgs.forEach((arg) => { + additionalArgs.push( + ...arg.split(" -P").map((a, i) => (i === 0 ? a : `-P${a}`)) + ); + }); + localArgs.push(...additionalArgs); } if (this.$logger.getLevel() === "INFO") { diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 71335ef5b6..d14cd194f7 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, @@ -266,10 +271,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject platformData: IPlatformData, projectData: IProjectData ): string { - const currentPlatformData: IDictionary = this.$projectDataService.getRuntimePackage( - projectData.projectDir, - platformData.platformNameLowerCase - ); + const currentPlatformData: IDictionary = + this.$projectDataService.getRuntimePackage( + projectData.projectDir, + platformData.platformNameLowerCase + ); return currentPlatformData && currentPlatformData[constants.VERSION_STRING]; } @@ -281,9 +287,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject public getAppResourcesDestinationDirectoryPath( projectData: IProjectData ): string { - const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated( - projectData.getAppResourcesDirectoryPath() - ); + const appResourcesDirStructureHasMigrated = + this.$androidResourcesMigrationService.hasMigrated( + projectData.getAppResourcesDirectoryPath() + ); if (appResourcesDirStructureHasMigrated) { return this.getUpdatedAppResourcesDestinationDirPath(projectData); @@ -300,14 +307,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.validatePackageName(projectData.projectIdentifiers.android); this.validateProjectName(projectData.projectName); - const checkEnvironmentRequirementsOutput = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements( - { + const checkEnvironmentRequirementsOutput = + await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ platform: this.getPlatformData(projectData).normalizedPlatformName, projectDir: projectData.projectDir, options, notConfiguredEnvOptions, - } - ); + }); this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, @@ -353,19 +359,30 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject "-R" ); + // override app build.gradle from cli vendor to allow updates faster than the runtime + const gradleTemplatePath = path.resolve( + path.join(__dirname, "../../vendor/gradle-app") + ); + const allGradleTemplateFiles = path.join(gradleTemplatePath, "*"); + + this.$fs.copyFile( + allGradleTemplateFiles, + path.join(this.getPlatformData(projectData).projectRoot) + ); + // TODO: Check if we actually need this and if it should be targetSdk or compileSdk this.cleanResValues(targetSdkVersion, projectData); } private getResDestinationDir(projectData: IProjectData): string { - const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated( - projectData.getAppResourcesDirectoryPath() - ); + const appResourcesDirStructureHasMigrated = + this.$androidResourcesMigrationService.hasMigrated( + projectData.getAppResourcesDirectoryPath() + ); if (appResourcesDirStructureHasMigrated) { - const appResourcesDestinationPath = this.getUpdatedAppResourcesDestinationDirPath( - projectData - ); + const appResourcesDestinationPath = + this.getUpdatedAppResourcesDestinationDirPath(projectData); return path.join( appResourcesDestinationPath, constants.MAIN_DIR, @@ -413,13 +430,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject public async interpolateData(projectData: IProjectData): Promise { // Interpolate the apilevel and package this.interpolateConfigurationFile(projectData); - const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); + const appResourcesDirectoryPath = + projectData.getAppResourcesDirectoryPath(); let stringsFilePath: string; - const appResourcesDestinationDirectoryPath = this.getAppResourcesDestinationDirectoryPath( - projectData - ); + const appResourcesDestinationDirectoryPath = + this.getAppResourcesDestinationDirectoryPath(projectData); if ( this.$androidResourcesMigrationService.hasMigrated( appResourcesDirectoryPath @@ -452,6 +469,17 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.getPlatformData(projectData).projectRoot, "settings.gradle" ); + const relativePath = path.relative( + this.getPlatformData(projectData).projectRoot, + projectData.projectDir + ); + shell.sed( + "-i", + /def USER_PROJECT_ROOT = \"\$rootDir\/..\/..\/\"/, + `def USER_PROJECT_ROOT = "$rootDir/${relativePath}"`, + gradleSettingsFilePath + ); + shell.sed( "-i", /__PROJECT_NAME__/, @@ -459,6 +487,23 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject gradleSettingsFilePath ); + const gradleVersion = projectData.nsConfig.android.gradleVersion; + if (gradleVersion) { + // user defined a custom gradle version, let's apply it + const gradleWrapperFilePath = path.join( + this.getPlatformData(projectData).projectRoot, + "gradle", + "wrapper", + "gradle-wrapper.properties" + ); + shell.sed( + "-i", + /gradle-([0-9.]+)-bin.zip/, + `gradle-${gradleVersion}-bin.zip`, + gradleWrapperFilePath + ); + } + try { // will replace applicationId in app/App_Resources/Android/app.gradle if it has not been edited by the user const appGradleContent = this.$fs.readText(projectData.appGradlePath); @@ -479,14 +524,39 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public interpolateConfigurationFile(projectData: IProjectData): void { - const manifestPath = this.getPlatformData(projectData) - .configurationFilePath; + const manifestPath = + this.getPlatformData(projectData).configurationFilePath; shell.sed( "-i", /__PACKAGE__/, projectData.projectIdentifiers.android, manifestPath ); + const buildAppGradlePath = path.join( + this.getPlatformData(projectData).projectRoot, + "app", + "build.gradle" + ); + const buildGradlePath = path.join( + this.getPlatformData(projectData).projectRoot, + "build.gradle" + ); + shell.sed( + "-i", + /__PACKAGE__/, + projectData.projectIdentifiers.android, + buildAppGradlePath + ); + const relativePath = path.relative( + this.getPlatformData(projectData).projectRoot, + projectData.projectDir + ); + shell.sed( + "-i", + /project.ext.USER_PROJECT_ROOT = \"\$rootDir\/..\/..\"/, + `project.ext.USER_PROJECT_ROOT = "$rootDir/${relativePath}"`, + buildGradlePath + ); } private getProjectNameFromId(projectData: IProjectData): string { @@ -521,9 +591,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE ) ) { - const platformLowercase = this.getPlatformData( - projectData - ).normalizedPlatformName.toLowerCase(); + const platformLowercase = + this.getPlatformData(projectData).normalizedPlatformName.toLowerCase(); await removePlatforms([platformLowercase.split("@")[0]]); await addPlatform(platformLowercase); return false; @@ -585,9 +654,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject projectData: IProjectData ): void { const appResourcesDirectoryPath = projectData.appResourcesDirectoryPath; - const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated( - appResourcesDirectoryPath - ); + const appResourcesDirStructureHasMigrated = + this.$androidResourcesMigrationService.hasMigrated( + appResourcesDirectoryPath + ); let originalAndroidManifestFilePath; if (appResourcesDirStructureHasMigrated) { @@ -628,27 +698,37 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject const projectAppResourcesPath = projectData.getAppResourcesDirectoryPath( projectData.projectDir ); - const platformsAppResourcesPath = this.getAppResourcesDestinationDirectoryPath( - projectData - ); + const platformsAppResourcesPath = + this.getAppResourcesDestinationDirectoryPath(projectData); this.cleanUpPreparedResources(projectData); this.$fs.ensureDirectoryExists(platformsAppResourcesPath); - const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated( - projectAppResourcesPath - ); + const appResourcesDirStructureHasMigrated = + this.$androidResourcesMigrationService.hasMigrated( + projectAppResourcesPath + ); if (appResourcesDirStructureHasMigrated) { + const resourcesPath = path.join( + projectAppResourcesPath, + platformData.normalizedPlatformName + ); this.$fs.copyFile( - path.join( - projectAppResourcesPath, - platformData.normalizedPlatformName, - constants.SRC_DIR, - "*" - ), + path.join(resourcesPath, constants.SRC_DIR, "*"), platformsAppResourcesPath ); + + const destinationFolder = this.getPlatformData(projectData).projectRoot; + const contents = this.$fs.readDirectory(resourcesPath); + _.each(contents, (fileName) => { + const filePath = path.join(resourcesPath, fileName); + const fsStat = this.$fs.getFsStats(filePath); + if (fsStat.isDirectory() && fileName !== constants.SRC_DIR) { + console.log("copying folder", filePath); + this.$fs.copyFile(filePath, destinationFolder); + } + }); } else { this.$fs.copyFile( path.join( @@ -826,8 +906,49 @@ 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(apkOutputPath)) { + 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..1ea266bf98 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 }, @@ -57,15 +56,25 @@ export class GradleBuildArgsService implements IGradleBuildArgsService { this.$projectData.initializeProjectData(buildData.projectDir); args.push( + `--stacktrace`, `-PcompileSdk=android-${toolsInfo.compileSdkVersion}`, `-PtargetSdk=${toolsInfo.targetSdkVersion}`, `-PbuildToolsVersion=${toolsInfo.buildToolsVersion}`, `-PgenerateTypings=${toolsInfo.generateTypings}`, + `-PprojectRoot=${this.$projectData.projectDir}`, + `-DprojectRoot=${this.$projectData.projectDir}`, // we need it as a -D to be able to read it from settings.gradle + `-PappPath=${this.$projectData.getAppDirectoryPath()}`, + `-PappBuildPath=${this.$projectData.getBuildRelativeDirectoryPath()}`, + `-DappBuildPath=${this.$projectData.getBuildRelativeDirectoryPath()}`, // we need it as a -D to be able to read it from settings.gradle `-PappPath=${this.$projectData.getAppDirectoryPath()}`, `-PappResourcesPath=${this.$projectData.getAppResourcesDirectoryPath()}` ); if (buildData.gradleArgs) { - args.push(buildData.gradleArgs); + const additionalArgs: string[] = []; + buildData.gradleArgs.forEach((arg) => { + additionalArgs.push(...arg.split(" ").map((a) => a.trim())); + }); + args.push(...additionalArgs); } if (buildData.release) { @@ -86,7 +95,7 @@ export class GradleBuildArgsService implements IGradleBuildArgsService { const logLevel = this.$logger.getLevel(); if (logLevel === "TRACE") { - args.push("--stacktrace", "--debug"); + args.push("--debug"); } else if (logLevel === "INFO") { args.push("--quiet"); } @@ -95,7 +104,12 @@ export class GradleBuildArgsService implements IGradleBuildArgsService { } private getBuildTaskName(buildData: IAndroidBuildData): string { - const baseTaskName = buildData.androidBundle ? "bundle" : "assemble"; + let baseTaskName = buildData.androidBundle ? "bundle" : "assemble"; + if (buildData.gradleFlavor) { + baseTaskName += + buildData.gradleFlavor[0].toUpperCase() + + buildData.gradleFlavor.slice(1); + } const buildTaskName = buildData.release ? `${baseTaskName}${Configurations.Release}` : `${baseTaskName}${Configurations.Debug}`; diff --git a/lib/services/android/gradle-build-service.ts b/lib/services/android/gradle-build-service.ts index 4ce97ab89a..11cb25391e 100644 --- a/lib/services/android/gradle-build-service.ts +++ b/lib/services/android/gradle-build-service.ts @@ -12,11 +12,13 @@ import { injector } from "../../common/yok"; export class GradleBuildService extends EventEmitter - implements IGradleBuildService { + implements IGradleBuildService +{ constructor( private $childProcess: IChildProcess, private $gradleBuildArgsService: IGradleBuildArgsService, - private $gradleCommandService: IGradleCommandService + private $gradleCommandService: IGradleCommandService, + private $devicesService: Mobile.IDevicesService ) { super(); } @@ -28,6 +30,23 @@ 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.filter((a) => !!a && a.length)[0]) + .filter((a) => !!a); + if ( + abis.length > 0 && + buildTaskArgs.findIndex((b) => b.startsWith("-PabiFilters")) === -1 + ) { + buildTaskArgs.push(`-PabiFilters=${abis.join(",")}`); + } + } const spawnOptions = { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true, @@ -55,9 +74,8 @@ export class GradleBuildService projectRoot: string, buildData: IAndroidBuildData ): Promise { - const cleanTaskArgs = this.$gradleBuildArgsService.getCleanTaskArgs( - buildData - ); + const cleanTaskArgs = + this.$gradleBuildArgsService.getCleanTaskArgs(buildData); const gradleCommandOptions = { cwd: projectRoot, message: "Gradle clean...", diff --git a/lib/services/build-artifacts-service.ts b/lib/services/build-artifacts-service.ts index 1a2bfc94af..5485077661 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,47 @@ 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/cocoapods-service.ts b/lib/services/cocoapods-service.ts index 17e433feae..02f03eeec5 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -353,7 +353,7 @@ end`.trim(); const regExpToRemove = new RegExp( `${this.getPluginPodfileHeader( podfilePath - )}[\\s\\S]*?${this.getPluginPodfileEnd()}`, + ).replace(/\+/g, "\\+")}[\\s\\S]*?${this.getPluginPodfileEnd()}`, "mg" ); projectPodFileContent = projectPodFileContent.replace(regExpToRemove, ""); diff --git a/lib/services/device/device-install-app-service.ts b/lib/services/device/device-install-app-service.ts index 4c5d16b6f3..12231aefb5 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,39 @@ 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.at(0).packageName; + } else if (device.deviceInfo.abis) { + packages.find(({ packageName }) => { + if (device.deviceInfo.abis.some((abi) => packageName.includes(abi))) { + packageFile = packageName; + return true; + } + }); + } else { + //we did not find corresponding abi let's try universal + const universalPackage = packages.find((p) => + p.packageName.includes("universal") + ); + if (universalPackage) { + packageFile = universalPackage.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 +114,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); } } @@ -123,10 +148,8 @@ export class DeviceInstallAppService { return true; } - const deviceBuildInfo: IBuildInfo = await this.$buildInfoFileService.getDeviceBuildInfo( - device, - projectData - ); + const deviceBuildInfo: IBuildInfo = + await this.$buildInfoFileService.getDeviceBuildInfo(device, projectData); const localBuildInfo = this.$buildInfoFileService.getLocalBuildInfo( platformData, { ...buildData, buildForDevice: !device.isEmulator } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index dafe91ebad..b678937d0d 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -1103,15 +1103,23 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private validateFramework(libraryPath: string): void { - const infoPlistPath = path.join( + let infoPlistPath = path.join( libraryPath, constants.INFO_PLIST_FILE_NAME ); if (!this.$fs.exists(infoPlistPath)) { - this.$errors.fail( - "The bundle at %s does not contain an Info.plist file.", - libraryPath + infoPlistPath = path.join( + libraryPath, + "Resources", + constants.INFO_PLIST_FILE_NAME ); + if (!this.$fs.exists(infoPlistPath)) { + this.$errors.fail( + "The bundle at %s does not contain an Info.plist file.", + libraryPath + ); + } + } const plistJson = this.$plistParser.parseFileSync(infoPlistPath); diff --git a/lib/services/metadata-filtering-service.ts b/lib/services/metadata-filtering-service.ts index 3d46c5fe0e..4c23d842b0 100644 --- a/lib/services/metadata-filtering-service.ts +++ b/lib/services/metadata-filtering-service.ts @@ -49,9 +49,8 @@ export class MetadataFilteringService implements IMetadataFilteringService { platform ); for (const pluginData of plugins) { - const pathToPlatformsDir = pluginData.pluginPlatformsFolderPath( - platform - ); + const pathToPlatformsDir = + pluginData.pluginPlatformsFolderPath(platform); const pathToPluginsMetadataConfig = path.join( pathToPlatformsDir, MetadataFilteringConstants.NATIVE_API_USAGE_FILE_NAME @@ -122,9 +121,8 @@ export class MetadataFilteringService implements IMetadataFilteringService { platform ); for (const pluginData of plugins) { - const pathToPlatformsDir = pluginData.pluginPlatformsFolderPath( - platform - ); + const pathToPlatformsDir = + pluginData.pluginPlatformsFolderPath(platform); const pathToPluginsMetadataConfig = path.join( pathToPlatformsDir, MetadataFilteringConstants.NATIVE_API_USAGE_FILE_NAME @@ -167,10 +165,8 @@ export class MetadataFilteringService implements IMetadataFilteringService { platform: string ): INativeApiUsageConfiguration { let config: INativeApiUsageConfiguration = null; - const pathToApplicationConfigurationFile = this.getPathToApplicationConfigurationForPlatform( - projectData, - platform - ); + const pathToApplicationConfigurationFile = + this.getPathToApplicationConfigurationForPlatform(projectData, platform); if (this.$fs.exists(pathToApplicationConfigurationFile)) { config = this.$fs.readJson(pathToApplicationConfigurationFile); } diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 6bb14996af..ab32a11b42 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -492,7 +492,11 @@ export class PluginsService implements IPluginsService { ): IDependencyData[] { const dependenciesWithFrameworks: any[] = []; _.each(productionDependencies, (d) => { - const pathToPlatforms = path.join(d.directory, "platforms", platform); + const pathToPlatforms = path.join( + d.directory, + constants.PLATFORMS_DIR_NAME, + platform + ); if (this.$fs.exists(pathToPlatforms)) { const contents = this.$fs.readDirectory(pathToPlatforms); _.each(contents, (file) => { diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index edb9b2593d..5785167ee5 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -74,7 +74,8 @@ export class ProjectChangesService implements IProjectChangesService { public async checkForChanges( platformData: IPlatformData, projectData: IProjectData, - prepareData: IPrepareData + prepareData: IPrepareData, + filesChangedData?: IFilesChangeEventData ): Promise { this._changesInfo = new ProjectChangesInfo(); const isNewPrepareInfo = await this.ensurePrepareInfo( diff --git a/lib/services/timeline-profiler-service.ts b/lib/services/timeline-profiler-service.ts index 31b5805548..d652cfd303 100644 --- a/lib/services/timeline-profiler-service.ts +++ b/lib/services/timeline-profiler-service.ts @@ -69,8 +69,8 @@ export class TimelineProfilerService implements ITimelineProfilerService { data.split("\n").forEach((line) => { const trace = this.toTrace(line.trim()); if (trace) { - deviceTimeline.startPoint ??= trace.from; - deviceTimeline.timeline.push(trace); + deviceTimeline.startPoint ??= trace[0].ts; + deviceTimeline.timeline.push(...trace); } }); } @@ -80,28 +80,36 @@ export class TimelineProfilerService implements ITimelineProfilerService { return this.$projectConfigService.getValue("profiling") === "timeline"; } - private toTrace(text: string): ChromeTraceEvent | undefined { + private toTrace(text: string): ChromeTraceEvent[] | undefined { const result = text.match(TIMELINE_LOG_RE); if (!result) { return; } const trace = { - domain: result[2]?.trim().replace(":", ""), + domain: result[2]?.trim().replace(":", ".").toLowerCase(), name: result[3].trim(), from: parseFloat(result[4]), to: parseFloat(result[5]), }; - return { + return [{ + args:{}, pid: 1, tid: 1, ts: trace.from * 1000, - dur: (trace.to - trace.from) * 1000, name: trace.name, cat: trace.domain ?? "default", - ph: ChromeTraceEventPhase.COMPLETE, - }; + ph: ChromeTraceEventPhase.BEGIN, + }, { + args:{}, + pid: 1, + tid: 1, + ts: trace.to * 1000, + name: trace.name, + cat: trace.domain ?? "default", + ph: ChromeTraceEventPhase.END, + }]; } private writeTimelines() { diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 205aa8faea..0a7189b7e7 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -364,12 +364,14 @@ export class WebpackCompilerService const appId = projectData.projectIdentifiers[platform]; const appPath = projectData.getAppDirectoryRelativePath(); const appResourcesPath = projectData.getAppResourcesRelativeDirectoryPath(); + const buildPath = projectData.getBuildRelativeDirectoryPath(); Object.assign( envData, appId && { appId }, appPath && { appPath }, appResourcesPath && { appResourcesPath }, + buildPath && { buildPath }, { nativescriptLibPath: path.resolve( __dirname, diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index e3a8dddd8b..606b3efbb5 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -42,7 +42,8 @@ declare global { checkForChanges( platformData: IPlatformData, projectData: IProjectData, - prepareData: IPrepareData + prepareData: IPrepareData, + filesChangedData?: IFilesChangeEventData ): Promise; getPrepareInfoFilePath(platformData: IPlatformData): string; getPrepareInfo(platformData: IPlatformData): IPrepareInfo; diff --git a/package.json b/package.json index b9905fea6d..5c127411f5 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ "author": "NativeScript ", "description": "Command-line interface for building NativeScript projects", "bin": { - "tns": "./bin/tns", "nativescript": "./bin/tns", + "ns": "./bin/tns", "nsc": "./bin/tns", - "ns": "./bin/tns" + "tns": "./bin/tns" }, "main": "./lib/nativescript-cli-lib.js", "files": [ diff --git a/test/controllers/update-controller.ts b/test/controllers/update-controller.ts index a99cf9802d..0c35d85dc1 100644 --- a/test/controllers/update-controller.ts +++ b/test/controllers/update-controller.ts @@ -20,6 +20,9 @@ function createTestInjector(projectDir: string = projectFolder): IInjector { initializeProjectData: () => { /* empty */ }, + getBuildRelativeDirectoryPath: () => { + /* empty */ + }, dependencies: { "@nativescript/core": "next", }, @@ -154,9 +157,8 @@ describe("update controller method tests", () => { it("handles exact versions", async () => { const testInjector = createTestInjector(); const updateController = testInjector.resolve("updateController"); - const pluginsService = testInjector.resolve( - "pluginsService" - ); + const pluginsService = + testInjector.resolve("pluginsService"); const stub = sinon.stub(pluginsService, "addToPackageJson"); @@ -175,9 +177,8 @@ describe("update controller method tests", () => { it("handles range versions", async () => { const testInjector = createTestInjector(); const updateController = testInjector.resolve("updateController"); - const pluginsService = testInjector.resolve( - "pluginsService" - ); + const pluginsService = + testInjector.resolve("pluginsService"); const stub = sinon.stub(pluginsService, "addToPackageJson"); @@ -196,9 +197,8 @@ describe("update controller method tests", () => { it("handles range versions", async () => { const testInjector = createTestInjector(); const updateController = testInjector.resolve("updateController"); - const pluginsService = testInjector.resolve( - "pluginsService" - ); + const pluginsService = + testInjector.resolve("pluginsService"); const stub = sinon.stub(pluginsService, "addToPackageJson"); @@ -217,9 +217,8 @@ describe("update controller method tests", () => { it("handles latest tag versions", async () => { const testInjector = createTestInjector(); const updateController = testInjector.resolve("updateController"); - const pluginsService = testInjector.resolve( - "pluginsService" - ); + const pluginsService = + testInjector.resolve("pluginsService"); const stub = sinon.stub(pluginsService, "addToPackageJson"); @@ -238,9 +237,8 @@ describe("update controller method tests", () => { it("handles existing tag versions", async () => { const testInjector = createTestInjector(); const updateController = testInjector.resolve("updateController"); - const pluginsService = testInjector.resolve( - "pluginsService" - ); + const pluginsService = + testInjector.resolve("pluginsService"); const stub = sinon.stub(pluginsService, "addToPackageJson"); @@ -259,9 +257,8 @@ describe("update controller method tests", () => { it("handles non-existing tag versions", async () => { const testInjector = createTestInjector(); const updateController = testInjector.resolve("updateController"); - const pluginsService = testInjector.resolve( - "pluginsService" - ); + const pluginsService = + testInjector.resolve("pluginsService"); const stub = sinon.stub(pluginsService, "addToPackageJson"); @@ -276,9 +273,8 @@ describe("update controller method tests", () => { it("handles partially existing tag versions", async () => { const testInjector = createTestInjector(); const updateController = testInjector.resolve("updateController"); - const pluginsService = testInjector.resolve( - "pluginsService" - ); + const pluginsService = + testInjector.resolve("pluginsService"); const stub = sinon.stub(pluginsService, "addToPackageJson"); @@ -294,9 +290,8 @@ describe("update controller method tests", () => { it("handles no version - falls back to latest", async () => { const testInjector = createTestInjector(); const updateController = testInjector.resolve("updateController"); - const pluginsService = testInjector.resolve( - "pluginsService" - ); + const pluginsService = + testInjector.resolve("pluginsService"); const stub = sinon.stub(pluginsService, "addToPackageJson"); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 9c6dd5f9be..859d8abe91 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -30,7 +30,7 @@ import { YarnPackageManager } from "../lib/yarn-package-manager"; import { assert } from "chai"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; -import { BUILD_XCCONFIG_FILE_NAME } from "../lib/constants"; +import { BUILD_XCCONFIG_FILE_NAME, PLATFORMS_DIR_NAME } from "../lib/constants"; import { ProjectDataStub, TempServiceStub, @@ -88,7 +88,7 @@ function createTestInjector( testInjector.register("options", OptionsLib.Options); testInjector.register("cocoaPodsPlatformManager", CocoaPodsPlatformManager); const projectData = Object.assign({}, ProjectDataStub, { - platformsDir: join(projectPath, "platforms"), + platformsDir: join(projectPath, PLATFORMS_DIR_NAME), projectName: projectName, projectPath: projectPath, projectFilePath: join(projectPath, "package.json"), @@ -309,7 +309,7 @@ describe("Cocoapods support", () => { // fs.writeJson(join(projectPath, "package.json"), packageJsonData); createPackageJson(testInjector, projectPath, "myProject"); - const platformsFolderPath = join(projectPath, "platforms", "ios"); + const platformsFolderPath = join(projectPath, PLATFORMS_DIR_NAME, "ios"); fs.createDirectory(platformsFolderPath); const iOSProjectService = testInjector.resolve("iOSProjectService"); @@ -411,7 +411,7 @@ describe("Cocoapods support", () => { }; fs.writeJson(join(projectPath, "package.json"), packageJsonData); - const platformsFolderPath = join(projectPath, "platforms", "ios"); + const platformsFolderPath = join(projectPath, PLATFORMS_DIR_NAME, "ios"); fs.createDirectory(platformsFolderPath); const iOSProjectService = testInjector.resolve("iOSProjectService"); @@ -442,7 +442,7 @@ describe("Cocoapods support", () => { const pluginPath = temp.mkdirSync("pluginDirectory"); const samplePluginPlatformsFolderPath = join( pluginPath, - "platforms", + PLATFORMS_DIR_NAME, "ios" ); const pluginPodfilePath = join( @@ -521,7 +521,7 @@ describe("Cocoapods support", () => { }; fs.writeJson(join(projectPath, "package.json"), packageJsonData); - const platformsFolderPath = join(projectPath, "platforms", "ios"); + const platformsFolderPath = join(projectPath, PLATFORMS_DIR_NAME, "ios"); fs.createDirectory(platformsFolderPath); const iOSProjectService = testInjector.resolve("iOSProjectService"); @@ -570,7 +570,7 @@ describe("Cocoapods support", () => { const pluginPath = temp.mkdirSync("pluginDirectory"); const samplePluginPlatformsFolderPath = join( pluginPath, - "platforms", + PLATFORMS_DIR_NAME, "ios" ); const pluginPodfilePath = join( @@ -679,7 +679,7 @@ describe("Source code support", () => { }; fs.writeJson(join(projectPath, "package.json"), packageJsonData); - const platformsFolderPath = join(projectPath, "platforms", "ios"); + const platformsFolderPath = join(projectPath, PLATFORMS_DIR_NAME, "ios"); fs.createDirectory(platformsFolderPath); const xcprojService = testInjector.resolve("xcprojService"); @@ -738,7 +738,7 @@ describe("Source code support", () => { }; fs.writeJson(join(projectPath, "package.json"), packageJsonData); - const platformsFolderPath = join(projectPath, "platforms", "ios"); + const platformsFolderPath = join(projectPath, PLATFORMS_DIR_NAME, "ios"); fs.createDirectory(platformsFolderPath); const iOSProjectService = testInjector.resolve("iOSProjectService"); @@ -775,7 +775,7 @@ describe("Source code support", () => { const pluginPath = temp.mkdirSync("pluginDirectory"); const samplePluginPlatformsFolderPath = join( pluginPath, - "platforms", + PLATFORMS_DIR_NAME, "ios" ); files.forEach((file) => { @@ -820,7 +820,7 @@ describe("Source code support", () => { const testInjector = createTestInjector(projectPath, projectName, xcode); const fs: IFileSystem = testInjector.resolve("fs"); - const platformsFolderPath = join(projectPath, "platforms", "ios"); + const platformsFolderPath = join(projectPath, PLATFORMS_DIR_NAME, "ios"); fs.createDirectory(platformsFolderPath); const pbxProj = await await getProjectWithoutPlugins(sourceFileNames); @@ -988,7 +988,7 @@ describe("Static libraries support", () => { const testInjector = createTestInjector(projectPath, projectName); const fs: IFileSystem = testInjector.resolve("fs"); const staticLibraryPath = join( - join(temp.mkdirSync("pluginDirectory"), "platforms", "ios") + join(temp.mkdirSync("pluginDirectory"), PLATFORMS_DIR_NAME, "ios") ); const staticLibraryHeadersPath = join( staticLibraryPath, @@ -1159,9 +1159,8 @@ describe("Merge Project XCConfig files", () => { for (const release in [true, false]) { await (iOSProjectService).mergeProjectXcconfigFiles(projectData); - const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths( - projectRoot - ); + const destinationFilePaths = + xcconfigService.getPluginsXcconfigFilePaths(projectRoot); _.each(destinationFilePaths, (destinationFilePath) => { assert.isTrue( @@ -1197,9 +1196,8 @@ describe("Merge Project XCConfig files", () => { release, }); - const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths( - projectRoot - ); + const destinationFilePaths = + xcconfigService.getPluginsXcconfigFilePaths(projectRoot); _.each(destinationFilePaths, (destinationFilePath) => { assert.isTrue( @@ -1207,9 +1205,10 @@ describe("Merge Project XCConfig files", () => { "Target build xcconfig is missing for release: " + release ); const expected = { - CODE_SIGN_ENTITLEMENTS: iOSEntitlementsService.getPlatformsEntitlementsRelativePath( - projectData - ), + CODE_SIGN_ENTITLEMENTS: + iOSEntitlementsService.getPlatformsEntitlementsRelativePath( + projectData + ), }; assertPropertyValues(expected, destinationFilePath, testInjector); }); @@ -1226,9 +1225,8 @@ describe("Merge Project XCConfig files", () => { await (iOSProjectService).mergeProjectXcconfigFiles(projectData); - const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths( - projectRoot - ); + const destinationFilePaths = + xcconfigService.getPluginsXcconfigFilePaths(projectRoot); _.each(destinationFilePaths, (destinationFilePath) => { assert.isTrue( @@ -1248,9 +1246,8 @@ describe("Merge Project XCConfig files", () => { it("creates empty plugins-.xcconfig in case there are no build.xcconfig in App_Resources and in plugins", async () => { await (iOSProjectService).mergeProjectXcconfigFiles(projectData); - const destinationFilePaths = xcconfigService.getPluginsXcconfigFilePaths( - projectRoot - ); + const destinationFilePaths = + xcconfigService.getPluginsXcconfigFilePaths(projectRoot); _.each(destinationFilePaths, (destinationFilePath) => { assert.isTrue( @@ -1279,7 +1276,8 @@ describe("handleNativeDependenciesChange", () => { cocoapodsService.mergePodXcconfigFile = async () => executedCocoapodsMethods.push("podMerge"); cocoapodsService.applyPodfileFromAppResources = async () => ({}); - cocoapodsService.removeDuplicatedPlatfomsFromProjectPodFile = async () => ({}); + cocoapodsService.removeDuplicatedPlatfomsFromProjectPodFile = + async () => ({}); cocoapodsService.getProjectPodfilePath = () => projectPodfilePath; const fs = testInjector.resolve("fs"); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index b2a3f51250..0f98bd5e9e 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -33,7 +33,11 @@ import * as StaticConfigLib from "../lib/config"; import * as path from "path"; import * as temp from "temp"; import * as _ from "lodash"; -import { PLUGINS_BUILD_DATA_FILENAME, PlatformTypes } from "../lib/constants"; // PACKAGE_JSON_FILE_NAME, CONFIG_FILE_NAME_JS, CONFIG_FILE_NAME_TS +import { + PLATFORMS_DIR_NAME, + PLUGINS_BUILD_DATA_FILENAME, + PlatformTypes, +} from "../lib/constants"; // PACKAGE_JSON_FILE_NAME, CONFIG_FILE_NAME_JS, CONFIG_FILE_NAME_TS import { GradleCommandService } from "../lib/services/android/gradle-command-service"; import { GradleBuildService } from "../lib/services/android/gradle-build-service"; import { GradleBuildArgsService } from "../lib/services/android/gradle-build-args-service"; @@ -49,6 +53,7 @@ import { // import { ProjectConfigService } from "../lib/services/project-config-service"; import { FileSystem } from "../lib/common/file-system"; import { ProjectHelper } from "../lib/common/project-helper"; +import { LiveSyncProcessDataService } from "../lib/services/livesync-process-data-service"; // import { basename } from 'path'; temp.track(); @@ -236,6 +241,10 @@ function createTestInjector() { id: "org.nativescript.Test", }) ); + testInjector.register( + "liveSyncProcessDataService", + LiveSyncProcessDataService + ); return testInjector; } @@ -399,10 +408,12 @@ describe("Plugins service", () => { ); // Adds android platform - fs.createDirectory(path.join(projectFolder, "platforms")); - fs.createDirectory(path.join(projectFolder, "platforms", "android")); + fs.createDirectory(path.join(projectFolder, PLATFORMS_DIR_NAME)); + fs.createDirectory( + path.join(projectFolder, PLATFORMS_DIR_NAME, "android") + ); fs.createDirectory( - path.join(projectFolder, "platforms", "android", "app") + path.join(projectFolder, PLATFORMS_DIR_NAME, "android", "app") ); // Mock logger.warn @@ -431,7 +442,7 @@ describe("Plugins service", () => { return { appDestinationDirectoryPath: path.join( projectFolder, - "platforms", + PLATFORMS_DIR_NAME, "android" ), frameworkPackageName: "tns-android", @@ -715,7 +726,7 @@ describe("Plugins service", () => { fullPath: "plugin_full_path", name: "plugin_name", pluginPlatformsFolderPath: (_platform: string) => - path.join("plugin_dir", "platforms", _platform.toLowerCase()), + path.join("plugin_dir", PLATFORMS_DIR_NAME, _platform.toLowerCase()), }; unitTestsInjector.register("filesHashService", { @@ -736,7 +747,7 @@ describe("Plugins service", () => { return !!opts.buildDataFileExists; } - if (file.indexOf("platforms") !== -1) { + if (file.indexOf(PLATFORMS_DIR_NAME) !== -1) { return !!opts.hasPluginPlatformsDir; } @@ -856,6 +867,10 @@ describe("Plugins service", () => { ); unitTestsInjector.register("nodeModulesDependenciesBuilder", {}); unitTestsInjector.register("tempService", stubs.TempServiceStub); + unitTestsInjector.register( + "liveSyncProcessDataService", + LiveSyncProcessDataService + ); return unitTestsInjector; }; @@ -902,8 +917,12 @@ describe("Plugins service", () => { "my project dir" ); - const expectediOSPath = path.join(pluginDir, "platforms", "ios"); - const expectedAndroidPath = path.join(pluginDir, "platforms", "android"); + const expectediOSPath = path.join(pluginDir, PLATFORMS_DIR_NAME, "ios"); + const expectedAndroidPath = path.join( + pluginDir, + PLATFORMS_DIR_NAME, + "android" + ); assert.equal( pluginData.pluginPlatformsFolderPath("iOS"), expectediOSPath diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index afb7a42835..432a32aefa 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -70,7 +70,7 @@ class ProjectChangesServiceTest extends BaseServiceTest { return { projectRoot: path.join( this.projectDir, - "platforms", + Constants.PLATFORMS_DIR_NAME, platform.toLowerCase() ), platformProjectService: { @@ -124,9 +124,10 @@ describe("Project Changes Service Tests", () => { describe("Get Prepare Info File Path", () => { it("Gets the correct Prepare Info path for ios/android", () => { for (const platform of ["ios", "android"]) { - const actualPrepareInfoPath = serviceTest.projectChangesService.getPrepareInfoFilePath( - serviceTest.getPlatformData(platform) - ); + const actualPrepareInfoPath = + serviceTest.projectChangesService.getPrepareInfoFilePath( + serviceTest.getPlatformData(platform) + ); const expectedPrepareInfoPath = path.join( serviceTest.projectDir, @@ -175,9 +176,10 @@ describe("Project Changes Service Tests", () => { fs.writeJson(prepareInfoPath, expectedPrepareInfo); // act - const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo( - serviceTest.getPlatformData(platform) - ); + const actualPrepareInfo = + serviceTest.projectChangesService.getPrepareInfo( + serviceTest.getPlatformData(platform) + ); // assert assert.deepStrictEqual(actualPrepareInfo, expectedPrepareInfo); @@ -187,14 +189,15 @@ describe("Project Changes Service Tests", () => { describe("Accumulates Changes From Project Services", () => { it("accumulates changes from the project service", async () => { - const iOSChanges = await serviceTest.projectChangesService.checkForChanges( - serviceTest.getPlatformData("ios"), - serviceTest.projectData, - { - provision: undefined, - teamId: undefined, - } - ); + const iOSChanges = + await serviceTest.projectChangesService.checkForChanges( + serviceTest.getPlatformData("ios"), + serviceTest.projectData, + { + provision: undefined, + teamId: undefined, + } + ); assert.isTrue( !!iOSChanges.signingChanged, "iOS signingChanged expected to be true" @@ -214,9 +217,10 @@ describe("Project Changes Service Tests", () => { } ); - const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo( - serviceTest.getPlatformData(platform) - ); + const actualPrepareInfo = + serviceTest.projectChangesService.getPrepareInfo( + serviceTest.getPlatformData(platform) + ); assert.deepStrictEqual(actualPrepareInfo, { nativePlatformStatus: Constants.NativePlatformStatus.requiresPrepare, @@ -249,9 +253,10 @@ describe("Project Changes Service Tests", () => { } ); - const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo( - serviceTest.getPlatformData(platform) - ); + const actualPrepareInfo = + serviceTest.projectChangesService.getPrepareInfo( + serviceTest.getPlatformData(platform) + ); prepareInfo.nativePlatformStatus = Constants.NativePlatformStatus.alreadyPrepared; assert.deepStrictEqual(actualPrepareInfo, prepareInfo); @@ -277,9 +282,10 @@ describe("Project Changes Service Tests", () => { { nativePlatformStatus: nativePlatformStatus } ); - const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo( - serviceTest.getPlatformData(platform) - ); + const actualPrepareInfo = + serviceTest.projectChangesService.getPrepareInfo( + serviceTest.getPlatformData(platform) + ); assert.deepStrictEqual(actualPrepareInfo, { nativePlatformStatus: nativePlatformStatus, }); diff --git a/test/services/android-project-service.ts b/test/services/android-project-service.ts index ed199e47ca..e2fb5e2a52 100644 --- a/test/services/android-project-service.ts +++ b/test/services/android-project-service.ts @@ -19,6 +19,8 @@ import { IFileSystem, IProjectDir, } from "../../lib/common/declarations"; +import { PLATFORMS_DIR_NAME } from "../../lib/constants"; +import { LiveSyncProcessDataService } from "../../lib/services/livesync-process-data-service"; const createTestInjector = (): IInjector => { const testInjector = new Yok(); @@ -60,6 +62,12 @@ const createTestInjector = (): IInjector => { testInjector.register("staticConfig", { TRACK_FEATURE_USAGE_SETTING_NAME: "TrackFeatureUsage", }); + + testInjector.register( + "liveSyncProcessDataService", + LiveSyncProcessDataService + ); + testInjector.register("devicesService", {}); return testInjector; }; @@ -184,7 +192,7 @@ describe("androidProjectService", () => { ); const pathToPlatformsAndroid = path.join( projectDir, - "platforms", + PLATFORMS_DIR_NAME, "android" ); const pathToResDirInPlatforms = path.join( @@ -240,9 +248,8 @@ describe("androidProjectService", () => { compileSdkVersion = 29; - const androidToolsInfo = injector.resolve( - "androidToolsInfo" - ); + const androidToolsInfo = + injector.resolve("androidToolsInfo"); androidToolsInfo.getToolsInfo = ( config?: IProjectDir ): IAndroidToolsInfoData => { @@ -258,9 +265,10 @@ describe("androidProjectService", () => { "src" ); beforeEach(() => { - const androidResourcesMigrationService = injector.resolve< - IAndroidResourcesMigrationService - >("androidResourcesMigrationService"); + const androidResourcesMigrationService = + injector.resolve( + "androidResourcesMigrationService" + ); androidResourcesMigrationService.hasMigrated = () => true; }); @@ -272,7 +280,7 @@ describe("androidProjectService", () => { sourceFileName: path.join(pathToSrcDirInAppResources, "*"), destinationFileName: path.join( projectData.projectDir, - "platforms", + PLATFORMS_DIR_NAME, "android", "app", "src" @@ -329,9 +337,10 @@ describe("androidProjectService", () => { describe("when old Android App_Resources structure is detected (post {N} 4.0 structure)", () => { beforeEach(() => { - const androidResourcesMigrationService = injector.resolve< - IAndroidResourcesMigrationService - >("androidResourcesMigrationService"); + const androidResourcesMigrationService = + injector.resolve( + "androidResourcesMigrationService" + ); androidResourcesMigrationService.hasMigrated = () => false; }); diff --git a/test/services/android/gradle-build-args-service.ts b/test/services/android/gradle-build-args-service.ts index e09a33478e..e13adeebab 100644 --- a/test/services/android/gradle-build-args-service.ts +++ b/test/services/android/gradle-build-args-service.ts @@ -60,13 +60,19 @@ async function executeTests( } const ksPath = temp.path({ prefix: "ksPath" }); const expectedInfoLoggingArgs = ["--quiet"]; -const expectedTraceLoggingArgs = ["--stacktrace", "--debug"]; +const expectedTraceLoggingArgs = ["--debug"]; const expectedDebugBuildArgs = [ + "--stacktrace", "-PcompileSdk=android-28", "-PtargetSdk=26", "-PbuildToolsVersion=my-build-tools-version", "-PgenerateTypings=true", + "-PprojectRoot=/path/to/projectDir", + "-DprojectRoot=/path/to/projectDir", "-PappPath=/path/to/projectDir/app".replace(/\//g, path.sep), + "-PappBuildPath=platforms", + "-DappBuildPath=platforms", + "-PappPath=/path/to/projectDir/app", "-PappResourcesPath=/path/to/projectDir/app/App_Resources".replace( /\//g, path.sep @@ -124,8 +130,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedReleaseBuildArgs), }, { - name: - "should return correct args for debug build with info log and android bundle", + name: "should return correct args for debug build with info log and android bundle", buildConfig: { release: false, androidBundle: true }, logLevel: "INFO", expectedResult: ["bundleDebug"] @@ -133,8 +138,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedDebugBuildArgs), }, { - name: - "should return correct args for debug build with trace log and android bundle", + name: "should return correct args for debug build with trace log and android bundle", buildConfig: { release: false, androidBundle: true }, logLevel: "TRACE", expectedResult: ["bundleDebug"] @@ -142,8 +146,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedDebugBuildArgs), }, { - name: - "should return correct args for release build with info log and android bundle", + name: "should return correct args for release build with info log and android bundle", buildConfig: { ...releaseBuildConfig, androidBundle: true }, logLevel: "INFO", expectedResult: ["bundleRelease"] @@ -151,8 +154,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedReleaseBuildArgs), }, { - name: - "should return correct args for release build with trace log and android bundle", + name: "should return correct args for release build with trace log and android bundle", buildConfig: { ...releaseBuildConfig, androidBundle: true }, logLevel: "TRACE", expectedResult: ["bundleRelease"] @@ -189,8 +191,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedDebugBuildArgs), }, { - name: - "should return correct args for release clean build with info log", + name: "should return correct args for release clean build with info log", buildConfig: releaseBuildConfig, logLevel: "INFO", expectedResult: ["clean"] @@ -198,8 +199,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedReleaseBuildArgs), }, { - name: - "should return correct args for release clean build with trace log", + name: "should return correct args for release clean build with trace log", buildConfig: releaseBuildConfig, logLevel: "TRACE", expectedResult: ["clean"] @@ -207,8 +207,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedReleaseBuildArgs), }, { - name: - "should return correct args for debug clean build with info log and android bundle", + name: "should return correct args for debug clean build with info log and android bundle", buildConfig: { release: false, androidBundle: true }, logLevel: "INFO", expectedResult: ["clean"] @@ -216,8 +215,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedDebugBuildArgs), }, { - name: - "should return correct args for debug clean build with trace log and android bundle", + name: "should return correct args for debug clean build with trace log and android bundle", buildConfig: { release: false, androidBundle: true }, logLevel: "TRACE", expectedResult: ["clean"] @@ -225,8 +223,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedDebugBuildArgs), }, { - name: - "should return correct args for release clean build with info log and android bundle", + name: "should return correct args for release clean build with info log and android bundle", buildConfig: { ...releaseBuildConfig, androidBundle: true }, logLevel: "INFO", expectedResult: ["clean"] @@ -234,8 +231,7 @@ describe("GradleBuildArgsService", () => { .concat(expectedReleaseBuildArgs), }, { - name: - "should return correct args for release clean build with trace log and android bundle", + name: "should return correct args for release clean build with trace log and android bundle", buildConfig: { ...releaseBuildConfig, androidBundle: true }, logLevel: "TRACE", expectedResult: ["clean"] diff --git a/test/services/livesync/android-livesync-tool.ts b/test/services/livesync/android-livesync-tool.ts index b4f169b3cf..e358200ac3 100644 --- a/test/services/livesync/android-livesync-tool.ts +++ b/test/services/livesync/android-livesync-tool.ts @@ -13,6 +13,7 @@ import * as temp from "temp"; import * as crypto from "crypto"; import { IInjector } from "../../../lib/common/definitions/yok"; import { IDictionary } from "../../../lib/common/declarations"; +import { PLATFORMS_DIR_NAME } from "../../../lib/constants"; temp.track(); const protocolVersion = "0.2.0"; @@ -61,7 +62,7 @@ const projectCreated = false; const testAppPath = temp.mkdirSync("testsyncapp"); const testAppPlatformPath = path.join( testAppPath, - "platforms", + PLATFORMS_DIR_NAME, "android", "app", "src", diff --git a/test/services/metadata-filtering-service.ts b/test/services/metadata-filtering-service.ts index f092e24db0..d0aff1e675 100644 --- a/test/services/metadata-filtering-service.ts +++ b/test/services/metadata-filtering-service.ts @@ -3,7 +3,10 @@ import { Yok } from "../../lib/common/yok"; import { LoggerStub, FileSystemStub } from "../stubs"; import { assert } from "chai"; import * as path from "path"; -import { MetadataFilteringConstants } from "../../lib/constants"; +import { + MetadataFilteringConstants, + PLATFORMS_DIR_NAME, +} from "../../lib/constants"; import { EOL } from "os"; import { IProjectData } from "../../lib/definitions/project"; import { IDependencyData } from "../../lib/declarations"; @@ -16,7 +19,7 @@ import { IDictionary } from "../../lib/common/declarations"; describe("metadataFilteringService", () => { const platform = "platform"; const projectDir = "projectDir"; - const projectRoot = path.join(projectDir, "platforms", platform); + const projectRoot = path.join(projectDir, PLATFORMS_DIR_NAME, platform); const projectData: any = { appResourcesDirectoryPath: path.join(projectDir, "App_Resources"), }; @@ -96,9 +99,8 @@ describe("metadataFilteringService", () => { it("deletes previously generated files for metadata filtering", () => { const testInjector = createTestInjector(); - const metadataFilteringService: IMetadataFilteringService = testInjector.resolve( - MetadataFilteringService - ); + const metadataFilteringService: IMetadataFilteringService = + testInjector.resolve(MetadataFilteringService); const { fs } = mockFs({ testInjector, writeFileAction: (filePath: string, data: string) => { @@ -119,9 +121,8 @@ describe("metadataFilteringService", () => { it(`generates ${MetadataFilteringConstants.BLACKLIST_FILE_NAME} when the file ${MetadataFilteringConstants.NATIVE_API_USAGE_FILE_NAME} exists in App_Resources/`, () => { const testInjector = createTestInjector(); - const metadataFilteringService: IMetadataFilteringService = testInjector.resolve( - MetadataFilteringService - ); + const metadataFilteringService: IMetadataFilteringService = + testInjector.resolve(MetadataFilteringService); const { dataWritten } = mockFs({ testInjector, existingFiles: [appResourcesNativeApiUsageFilePath], @@ -168,9 +169,8 @@ describe("metadataFilteringService", () => { it(`generates ${MetadataFilteringConstants.WHITELIST_FILE_NAME} when the file ${MetadataFilteringConstants.NATIVE_API_USAGE_FILE_NAME} exists in App_Resources/`, () => { const testInjector = createTestInjector(); - const metadataFilteringService: IMetadataFilteringService = testInjector.resolve( - MetadataFilteringService - ); + const metadataFilteringService: IMetadataFilteringService = + testInjector.resolve(MetadataFilteringService); const { dataWritten } = mockFs({ testInjector, existingFiles: [appResourcesNativeApiUsageFilePath], @@ -194,9 +194,8 @@ describe("metadataFilteringService", () => { it(`generates ${MetadataFilteringConstants.WHITELIST_FILE_NAME} with content from plugins when the file ${MetadataFilteringConstants.NATIVE_API_USAGE_FILE_NAME} exists in App_Resources/ and whitelist-plugins-usages is true`, () => { const testInjector = createTestInjector({ hasPlugins: true }); - const metadataFilteringService: IMetadataFilteringService = testInjector.resolve( - MetadataFilteringService - ); + const metadataFilteringService: IMetadataFilteringService = + testInjector.resolve(MetadataFilteringService); const { dataWritten } = mockFs({ testInjector, existingFiles: [ @@ -222,9 +221,8 @@ describe("metadataFilteringService", () => { it(`generates all files when both plugins and applications filters are included`, () => { const testInjector = createTestInjector({ hasPlugins: true }); - const metadataFilteringService: IMetadataFilteringService = testInjector.resolve( - MetadataFilteringService - ); + const metadataFilteringService: IMetadataFilteringService = + testInjector.resolve(MetadataFilteringService); const { dataWritten } = mockFs({ testInjector, existingFiles: [ @@ -261,9 +259,8 @@ describe("metadataFilteringService", () => { it(`skips plugins ${MetadataFilteringConstants.NATIVE_API_USAGE_FILE_NAME} files when whitelist-plugins-usages in App_Resources is false`, () => { const testInjector = createTestInjector({ hasPlugins: true }); - const metadataFilteringService: IMetadataFilteringService = testInjector.resolve( - MetadataFilteringService - ); + const metadataFilteringService: IMetadataFilteringService = + testInjector.resolve(MetadataFilteringService); const { dataWritten } = mockFs({ testInjector, existingFiles: [ diff --git a/test/stubs.ts b/test/stubs.ts index 82aafdd215..25999d9660 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -387,7 +387,8 @@ export class ErrorsStub implements IErrors { } export class PackageInstallationManagerStub - implements IPackageInstallationManager { + implements IPackageInstallationManager +{ clearInspectorCache(): void { return undefined; } @@ -653,6 +654,8 @@ export class ProjectConfigServiceStub implements IProjectConfigService { } export class ProjectDataStub implements IProjectData { + ignoredDependencies?: string[]; + initialized?: boolean; packageJsonData: any; projectDir: string; projectName: string; @@ -661,7 +664,8 @@ export class ProjectDataStub implements IProjectData { get platformsDir(): string { return ( this.platformsDirCache || - (this.projectDir && join(this.projectDir, "platforms")) || + (this.projectDir && + join(this.projectDir, constants.PLATFORMS_DIR_NAME)) || "" ); } @@ -696,6 +700,9 @@ export class ProjectDataStub implements IProjectData { this.projectIdentifiers = { android: "", ios: "" }; this.projectId = ""; this.projectName = ""; + this.nsConfig = { + android: {}, + }; } public initializeProjectDataFromContent(): void { @@ -730,10 +737,15 @@ export class ProjectDataStub implements IProjectData { public getAppDirectoryRelativePath(): string { return "app"; } + + getBuildRelativeDirectoryPath(): string { + return "platforms"; + } } export class AndroidPluginBuildServiceStub - implements IAndroidPluginBuildService { + implements IAndroidPluginBuildService +{ buildAar(options: IPluginBuildOptions): Promise { return Promise.resolve(true); } @@ -745,7 +757,8 @@ export class AndroidPluginBuildServiceStub export class PlatformProjectServiceStub extends EventEmitter - implements IPlatformProjectService { + implements IPlatformProjectService +{ constructor(private platform: string) { super(); } @@ -888,7 +901,8 @@ export class PlatformProjectServiceStub export class NativeProjectDataStub extends EventEmitter - implements IPlatformsDataService { + implements IPlatformsDataService +{ public platformNames: string[]; public getPlatformData( @@ -1126,7 +1140,8 @@ function unexpected(msg: string): Error { export class DebugServiceStub extends EventEmitter - implements IDeviceDebugService { + implements IDeviceDebugService +{ public async debug(): Promise { return; } @@ -1140,7 +1155,8 @@ export class DebugServiceStub export class LiveSyncServiceStub extends EventEmitter - implements ILiveSyncService { + implements ILiveSyncService +{ public async liveSync( deviceDescriptors: ILiveSyncDeviceDescriptor[], liveSyncData: ILiveSyncInfo @@ -1308,7 +1324,8 @@ export class CommandsService implements ICommandsService { } export class AndroidResourcesMigrationServiceStub - implements IAndroidResourcesMigrationService { + implements IAndroidResourcesMigrationService +{ canMigrate(platformString: string): boolean { return true; } @@ -1323,7 +1340,8 @@ export class AndroidResourcesMigrationServiceStub } export class AndroidBundleValidatorHelper - implements IAndroidBundleValidatorHelper { + implements IAndroidBundleValidatorHelper +{ validateDeviceApiLevel(device: Mobile.IDevice, buildData: IBuildData): void { return; } diff --git a/vendor/gradle-app/app/build.gradle b/vendor/gradle-app/app/build.gradle new file mode 100644 index 0000000000..d65369415d --- /dev/null +++ b/vendor/gradle-app/app/build.gradle @@ -0,0 +1,1043 @@ +/* +* Script builds apk in release or debug mode +* To run: +* gradle assembleRelease -Prelease (release mode) +* gradle assembleDebug (debug mode -> default) +* Options: +* -Prelease //this flag will run build in release mode +* -PksPath=[path_to_keystore_file] +* -PksPassword=[password_for_keystore_file] +* -Palias=[alias_to_use_from_keystore_file] +* -Ppassword=[password_for_alias] +* +* -PtargetSdk=[target_sdk] +* -PbuildToolsVersion=[build_tools_version] +* -PcompileSdk=[compile_sdk_version] +* -PandroidXLegacy=[androidx_legacy_version] +* -PandroidXAppCompat=[androidx_appcompat_version] +* -PandroidXMaterial=[androidx_material_version] +* -PappPath=[app_path] +* -PappResourcesPath=[app_resources_path] +*/ + + +import groovy.io.FileType +import groovy.json.JsonSlurper +import org.apache.commons.io.FileUtils + +import javax.inject.Inject +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.security.MessageDigest + +import java.util.jar.JarEntry +import java.util.jar.JarFile + +import static org.gradle.internal.logging.text.StyledTextOutput.Style +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +apply plugin: "com.android.application" +apply from: "gradle-helpers/BuildToolTask.gradle" +apply from: "gradle-helpers/CustomExecutionLogger.gradle" +apply from: "gradle-helpers/AnalyticsCollector.gradle" + +def enableKotlin = (project.hasProperty("useKotlin") && project.useKotlin == "true") + +if (enableKotlin) { + apply plugin: 'kotlin-android' + apply plugin: 'kotlin-parcelize' + apply plugin: "org.jetbrains.kotlin.android" +} + +def onlyX86 = project.hasProperty("onlyX86") +if (onlyX86) { + outLogger.withStyle(Style.Info).println "OnlyX86 build triggered." +} + +//common +def BUILD_TOOLS_PATH = "$rootDir/build-tools" +def PASSED_TYPINGS_PATH = System.getenv("TNS_TYPESCRIPT_DECLARATIONS_PATH") +def TYPINGS_PATH = "$BUILD_TOOLS_PATH/typings" +if (PASSED_TYPINGS_PATH != null) { + TYPINGS_PATH = PASSED_TYPINGS_PATH +} + +def PACKAGE_JSON = "package.json" + +//static binding generator +def SBG_JAVA_DEPENDENCIES = "sbg-java-dependencies.txt" +def SBG_INPUT_FILE = "sbg-input-file.txt" +def SBG_OUTPUT_FILE = "sbg-output-file.txt" +def SBG_JS_PARSED_FILES = "sbg-js-parsed-files.txt" +def SBG_BINDINGS_NAME = "sbg-bindings.txt" +def SBG_INTERFACE_NAMES = "sbg-interface-names.txt" +def INPUT_JS_DIR = "$projectDir/src/main/assets/app" +def OUTPUT_JAVA_DIR = "$projectDir/src/main/java" + +//metadata generator +def MDG_OUTPUT_DIR = "mdg-output-dir.txt" +def MDG_JAVA_DEPENDENCIES = "mdg-java-dependencies.txt" +def METADATA_OUT_PATH = "$projectDir/src/main/assets/metadata" +def METADATA_JAVA_OUT = "mdg-java-out.txt" + +// paths to jar libraries +def pluginsJarLibraries = new LinkedList() +def allJarLibraries = new LinkedList() + +def computeKotlinVersion = { -> project.hasProperty("kotlinVersion") ? kotlinVersion : "${ns_default_kotlin_version}" } +def computeCompileSdkVersion = { -> project.hasProperty("compileSdk") ? compileSdk : NS_DEFAULT_COMPILE_SDK_VERSION as int } +def computeTargetSdkVersion = { -> project.hasProperty("targetSdk") ? targetSdk : NS_DEFAULT_COMPILE_SDK_VERSION as int } +def computeBuildToolsVersion = { -> + project.hasProperty("buildToolsVersion") ? buildToolsVersion : NS_DEFAULT_BUILD_TOOLS_VERSION as String +} + +def enableAnalytics = (project.hasProperty("gatherAnalyticsData") && project.gatherAnalyticsData == "true") +def enableVerboseMDG = project.gradle.startParameter.logLevel.name() == 'DEBUG' +def analyticsFilePath = "$rootDir/analytics/build-statistics.json" +def analyticsCollector = project.ext.AnalyticsCollector.withOutputPath(analyticsFilePath) +if (enableAnalytics) { + analyticsCollector.markUseKotlinPropertyInApp(enableKotlin) + analyticsCollector.writeAnalyticsFile() +} + +project.ext.selectedBuildType = project.hasProperty("release") ? "release" : "debug" + +buildscript { + def applyBuildScriptConfigurations = { -> + def absolutePathToAppResources = getAppResourcesPath() + def pathToBuildScriptGradle = "$absolutePathToAppResources/Android/buildscript.gradle" + def buildScriptGradle = file(pathToBuildScriptGradle) + if (buildScriptGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined buildscript from ${buildScriptGradle}" + apply from: pathToBuildScriptGradle, to: buildscript + } + + nativescriptDependencies.each { dep -> + def pathToPluginBuildScriptGradle = "$rootDir/${dep.directory}/$PLATFORMS_ANDROID/buildscript.gradle" + def pluginBuildScriptGradle = file(pathToPluginBuildScriptGradle) + if (pluginBuildScriptGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined buildscript from dependency ${pluginBuildScriptGradle}" + apply from: pathToPluginBuildScriptGradle, to: buildscript + } + } + } + applyBuildScriptConfigurations() +} +//////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// CONFIGURATIONS /////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +def applyBeforePluginGradleConfiguration = { -> + def appResourcesPath = getAppResourcesPath() + def pathToBeforePluginGradle = "$appResourcesPath/Android/before-plugins.gradle" + def beforePluginGradle = file(pathToBeforePluginGradle) + if (beforePluginGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${beforePluginGradle}" + apply from: pathToBeforePluginGradle + } +} + +def applyAppGradleConfiguration = { -> + def appResourcesPath = getAppResourcesPath() + def pathToAppGradle = "$appResourcesPath/Android/app.gradle" + def appGradle = file(pathToAppGradle) + if (appGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${appGradle}" + apply from: pathToAppGradle + } else { + outLogger.withStyle(Style.Info).println "\t + couldn't load user-defined configuration from ${appGradle}. File doesn't exist." + } +} + +def applyPluginGradleConfigurations = { -> + nativescriptDependencies.each { dep -> + def includeGradlePath = "$rootDir/${dep.directory}/$PLATFORMS_ANDROID/include.gradle" + if (file(includeGradlePath).exists()) { + apply from: includeGradlePath + } + } +} + +def getAppIdentifier = { packageJsonMap -> + def appIdentifier = "" + if (packageJsonMap && packageJsonMap.nativescript) { + appIdentifier = packageJsonMap.nativescript.id + if (!(appIdentifier instanceof String)) { + appIdentifier = appIdentifier.android + } + } + + return appIdentifier +} + +def setAppIdentifier = { -> + outLogger.withStyle(Style.SuccessHeader).println "\t + setting applicationId" + File packageJsonFile = new File("$USER_PROJECT_ROOT/$PACKAGE_JSON") + + if (packageJsonFile.exists()) { + def content = packageJsonFile.getText("UTF-8") + def jsonSlurper = new JsonSlurper() + def packageJsonMap = jsonSlurper.parseText(content) + def appIdentifier = getAppIdentifier(packageJsonMap) + + if (appIdentifier) { + project.ext.nsApplicationIdentifier = appIdentifier + android.defaultConfig.applicationId = appIdentifier + } + } +} + +android { + + applyBeforePluginGradleConfiguration() + namespace "__PACKAGE__" + + if (enableKotlin) { + kotlin { + jvmToolchain(17) + } + } + + compileSdkVersion computeCompileSdkVersion() + buildToolsVersion computeBuildToolsVersion() + + defaultConfig { + def manifest = new XmlSlurper().parse(file(android.sourceSets.main.manifest.srcFile)) + def minSdkVer = manifest."uses-sdk"."@android:minSdkVersion".text() ?: NS_DEFAULT_MIN_SDK_VERSION + minSdkVersion minSdkVer + targetSdkVersion computeTargetSdkVersion() + ndk { + if (onlyX86) { + abiFilters 'x86' + } else { + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + } + } + } + + if (project.hasProperty("ndkVersion")) { + ndkVersion project.ndkVersion + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + sourceSets.main { + jniLibs.srcDirs = ["$projectDir/libs/jni", "$projectDir/snapshot-build/build/ndk-build/libs"] + } + + signingConfigs { + release { + if (project.hasProperty("release")) { + if (project.hasProperty("ksPath") && + project.hasProperty("ksPassword") && + project.hasProperty("alias") && + project.hasProperty("password")) { + + storeFile file(ksPath) + storePassword ksPassword + keyAlias alias + keyPassword password + } + } + } + } + buildTypes { + release { + signingConfig signingConfigs.release + } + } + + setAppIdentifier() + applyPluginGradleConfigurations() + applyAppGradleConfiguration() + + def initializeMergedAssetsOutputPath = { -> + android.applicationVariants.all { variant -> + if (variant.buildType.name == project.selectedBuildType) { + def task + if (variant.metaClass.respondsTo(variant, "getMergeAssetsProvider")) { + def provider = variant.getMergeAssetsProvider() + task = provider.get(); + } else { + // fallback for older android gradle plugin versions + task = variant.getMergeAssets() + } + for (File file : task.getOutputs().getFiles()) { + if (!file.getPath().contains("${File.separator}incremental${File.separator}")) { + project.ext.mergedAssetsOutputPath = file.getPath() + break; + } + } + } + } + } + + initializeMergedAssetsOutputPath() +} + +def externalRuntimeExists = !findProject(':runtime').is(null) +def pluginDependencies + +repositories { + // used for local *.AAR files + pluginDependencies = nativescriptDependencies.collect { + "$rootDir/${it.directory}/$PLATFORMS_ANDROID" + } + + // some plugins may have their android dependencies in a /libs subdirectory + pluginDependencies.addAll(nativescriptDependencies.collect { + "$rootDir/${it.directory}/$PLATFORMS_ANDROID/libs" + }) + + if (!externalRuntimeExists) { + pluginDependencies.add("$rootDir/app/libs/runtime-libs") + } + + def appResourcesPath = getAppResourcesPath() + def localAppResourcesLibraries = "$appResourcesPath/Android/libs" + + pluginDependencies.add(localAppResourcesLibraries) + + if (pluginDependencies.size() > 0) { + flatDir { + dirs pluginDependencies + } + } + + mavenCentral() +} + +dependencies { + // println "\t ~ [DEBUG][app] build.gradle - ns_default_androidx_appcompat_version = ${ns_default_androidx_appcompat_version}..." + + def androidXAppCompatVersion = "${ns_default_androidx_appcompat_version}" + if (project.hasProperty("androidXAppCompat")) { + androidXAppCompatVersion = androidXAppCompat + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.appcompat:appcompat:$androidXAppCompatVersion" + } + + def androidXMaterialVersion = "${ns_default_androidx_material_version}" + if (project.hasProperty("androidXMaterial")) { + androidXMaterialVersion = androidXMaterial + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library com.google.android.material:material:$androidXMaterialVersion" + } + + def androidXExifInterfaceVersion = "${ns_default_androidx_exifinterface_version}" + if (project.hasProperty("androidXExifInterface")) { + androidXExifInterfaceVersion = androidXExifInterface + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion" + } + + def androidXViewPagerVersion = "${ns_default_androidx_viewpager_version}" + if (project.hasProperty("androidXViewPager")) { + androidXViewPagerVersion = androidXViewPager + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.viewpager2:viewpager2:$androidXViewPagerVersion" + } + + def androidXFragmentVersion = "${ns_default_androidx_fragment_version}" + if (project.hasProperty("androidXFragment")) { + androidXFragmentVersion = androidXFragment + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.fragment:fragment:$androidXFragmentVersion" + } + + def androidXTransitionVersion = "${ns_default_androidx_transition_version}" + if (project.hasProperty("androidXTransition")) { + androidXTransitionVersion = androidXTransition + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.transition:transition:$androidXTransitionVersion" + } + + def androidXMultidexVersion = "${ns_default_androidx_multidex_version}" + if (project.hasProperty("androidXMultidex")) { + androidXMultidexVersion = androidXMultidex + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.multidex:multidex:$androidXMultidexVersion" + } + + implementation "androidx.multidex:multidex:$androidXMultidexVersion" + implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion" + debugImplementation "com.google.android.material:material:$androidXMaterialVersion" + implementation "androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion" + implementation "androidx.viewpager2:viewpager2:$androidXViewPagerVersion" + //noinspection KtxExtensionAvailable + implementation "androidx.fragment:fragment:$androidXFragmentVersion" + implementation "androidx.transition:transition:$androidXTransitionVersion" + + def useV8Symbols = false + + def appPackageJsonFile = file("${getAppPath()}/$PACKAGE_JSON") + if (appPackageJsonFile.exists()) { + def appPackageJson = new JsonSlurper().parseText(appPackageJsonFile.text) + useV8Symbols = appPackageJson.android && appPackageJson.android.useV8Symbols + } + + if (!useV8Symbols) { + // check whether any of the dependencies require v8 symbols + useV8Symbols = nativescriptDependencies.any { + def packageJsonFile = file("$rootDir/${it.directory}/$PACKAGE_JSON") + def packageJson = new JsonSlurper().parseText(packageJsonFile.text) + return packageJson.nativescript && packageJson.nativescript.useV8Symbols + } + } + + if (!externalRuntimeExists) { + def runtime = "nativescript-optimized-with-inspector" + + if (project.gradle.startParameter.taskNames.any { it.toLowerCase().contains('release') }) { + runtime = "nativescript-optimized" + } + + if (useV8Symbols) { + runtime = "nativescript-regular" + } + + outLogger.withStyle(Style.SuccessHeader).println "\t + adding nativescript runtime package dependency: $runtime" + project.dependencies.add("implementation", [name: runtime, ext: "aar"]) + } else { + implementation project(':runtime') + } + + def kotlinVersion = computeKotlinVersion() + if (enableKotlin) { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + } + +} + +//////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// CONFIGURATION PHASE ////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +task addDependenciesFromNativeScriptPlugins { + nativescriptDependencies.each { dep -> + def aarFiles = fileTree(dir: file("$rootDir/${dep.directory}/$PLATFORMS_ANDROID"), include: ["**/*.aar"]) + aarFiles.each { aarFile -> + def length = aarFile.name.length() - 4 + def fileName = aarFile.name[0.. + def jarFileAbsolutePath = jarFile.getAbsolutePath() + outLogger.withStyle(Style.SuccessHeader).println "\t + adding jar plugin dependency: $jarFileAbsolutePath" + pluginsJarLibraries.add(jarFile.getAbsolutePath()) + } + + project.dependencies.add("implementation", jarFiles) + } +} + +task addDependenciesFromAppResourcesLibraries { + def appResourcesPath = getAppResourcesPath() + def appResourcesLibraries = file("$appResourcesPath/Android/libs") + if (appResourcesLibraries.exists()) { + def aarFiles = fileTree(dir: appResourcesLibraries, include: ["**/*.aar"]) + aarFiles.each { aarFile -> + def length = aarFile.name.length() - 4 + def fileName = aarFile.name[0.. + def jarFileAbsolutePath = jarFile.getAbsolutePath() + outLogger.withStyle(Style.SuccessHeader).println "\t + adding jar plugin dependency: $jarFileAbsolutePath" + pluginsJarLibraries.add(jarFile.getAbsolutePath()) + } + + project.dependencies.add("implementation", jarFiles) + } +} + +if (failOnCompilationWarningsEnabled()) { + tasks.withType(JavaCompile) { + options.compilerArgs << '-Xlint:all' << "-Werror" + options.deprecation = true + } +} + +//////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// EXECUTUION PHASE ///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +task runSbg(type: BuildToolTask) { + dependsOn "collectAllJars" + def rootPath = ""; + if (!findProject(':static-binding-generator').is(null)) { + rootPath = Paths.get(project(':static-binding-generator').projectDir.path, "build/libs").toString() + dependsOn ':static-binding-generator:jar' + } + + outputs.dir("$OUTPUT_JAVA_DIR/com/tns/gen") + inputs.dir(INPUT_JS_DIR) + inputs.dir(extractedDependenciesDir) + + workingDir "$BUILD_TOOLS_PATH" + mainClass = "-jar" + + def paramz = new ArrayList() + paramz.add(Paths.get(rootPath,"static-binding-generator.jar")) + + if (failOnCompilationWarningsEnabled()) { + paramz.add("-show-deprecation-warnings") + } + + setOutputs outLogger + + args paramz + + doFirst { + new File("$OUTPUT_JAVA_DIR/com/tns/gen").deleteDir() + } +} + +def failOnCompilationWarningsEnabled() { + return project.hasProperty("failOnCompilationWarnings") && (failOnCompilationWarnings || failOnCompilationWarnings.toBoolean()) +} + +def explodeAar(File compileDependency, File outputDir) { + logger.info("explodeAar: Extracting ${compileDependency.path} -> ${outputDir.path}") + + if (compileDependency.name.endsWith(".aar")) { + JarFile jar = new JarFile(compileDependency) + Enumeration enumEntries = jar.entries() + while (enumEntries.hasMoreElements()) { + JarEntry file = (JarEntry) enumEntries.nextElement() + if (file.isDirectory()) { + continue + } + if (file.name.endsWith(".jar")) { + def targetFile = new File(outputDir, file.name) + InputStream inputStream = jar.getInputStream(file) + new File(targetFile.parent).mkdirs() + Files.copy(inputStream, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + } + jar.close() + } else if (compileDependency.name.endsWith(".jar")) { + copy { + from compileDependency.absolutePath + into outputDir + } + } +} + +def md5(String string) { + MessageDigest digest = MessageDigest.getInstance("MD5") + digest.update(string.bytes) + return new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0') +} + +class WorkerTask extends DefaultTask { + @Inject + WorkerExecutor getWorkerExecutor() { + throw new UnsupportedOperationException() + } +} + +class EmptyRunnable implements Runnable { + void run() { + } +} + +def getMergedAssetsOutputPath() { + if (!project.hasProperty("mergedAssetsOutputPath")) { + // mergedAssetsOutputPath not found fallback to the default value for android gradle plugin 3.5.1 + project.ext.mergedAssetsOutputPath = "$projectDir/build/intermediates/merged_assets/" + project.selectedBuildType + "/out" + } + return project.ext.mergedAssetsOutputPath +} + +// Discover all jars and dynamically create tasks for the extraction of each of them +project.ext.allJars = [] +allprojects { + afterEvaluate { project -> + def buildType = project.selectedBuildType + def jars = [] + def artifactType = Attribute.of('artifactType', String) + android.applicationVariants.all { variant -> + if (variant.buildType.name == buildType) { + variant.getCompileClasspath().each { fileDependency -> + processJar(fileDependency, jars) + } + } + } + } +} + +def processJar(File jar, jars) { + if (!jars.contains(jar)) { + jars.add(jar) + def destDir = md5(jar.path) + def outputDir = new File(Paths.get(extractedDependenciesDir, destDir).normalize().toString()) + + def taskName = "extract_${jar.name}_to_${destDir}" + logger.debug("Creating dynamic task ${taskName}") + + // Add discovered jars as dependencies of cleanupAllJars. + // This is cruicial for cloud builds because they are different + // on each incremental build (as each time the gradle user home + // directory is a randomly generated string) + cleanupAllJars.inputs.files jar + + task "${taskName}"(type: WorkerTask) { + dependsOn cleanupAllJars + extractAllJars.dependsOn it + + // This dependency seems redundant but probably due to some Gradle issue with workers, + // without it `runSbg` sporadically starts before all extraction tasks have finished and + // fails due to missing JARs + runSbg.dependsOn it + + inputs.files jar + outputs.dir outputDir + + doLast { + // Runing in parallel no longer seems to bring any benefit. + // It mattered only when we were extracting JARs from AARs. + // To try it simply remove the following comments. + // workerExecutor.submit(EmptyRunnable.class) { + explodeAar(jar, outputDir) + // } + } + } + project.ext.allJars.add([file: jar, outputDir: outputDir]) + } +} + +task cleanupAllJars { + // We depend on the list of libs directories that might contain aar or jar files + // and on the list of all discovered jars + inputs.files(pluginDependencies) + + outputs.files cleanupAllJarsTimestamp + + doLast { + def allDests = project.ext.allJars*.outputDir*.name + def dir = new File(extractedDependenciesDir) + if (dir.exists()) { + dir.eachDir { + // An old directory which is no longer a dependency (e.g. orphaned by a deleted plugin) + if (!allDests.contains(it.name)) { + logger.info("Task cleanupAllJars: Deleting orphaned ${it.path}") + FileUtils.deleteDirectory(it) + } + } + } + new File(cleanupAllJarsTimestamp).write "" + } +} + + +// Placeholder task which depends on all dynamically generated extraction tasks +task extractAllJars { + dependsOn cleanupAllJars + outputs.files extractAllJarsTimestamp + + doLast { + new File(cleanupAllJarsTimestamp).write "" + } +} + +task collectAllJars { + dependsOn extractAllJars + description "gathers all paths to jar dependencies before building metadata with them" + + def sdkPath = android.sdkDirectory.getAbsolutePath() + def androidJar = sdkPath + "/platforms/" + android.compileSdkVersion + "/android.jar" + + doFirst { + def allJarPaths = new LinkedList() + allJarPaths.add(androidJar) + allJarPaths.addAll(pluginsJarLibraries) + def ft = fileTree(dir: extractedDependenciesDir, include: "**/*.jar") + ft.each { currentJarFile -> + allJarPaths.add(currentJarFile.getAbsolutePath()) + } + + new File("$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES").withWriter { out -> + allJarPaths.each { out.println it } + } + new File("$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES").withWriter { out -> + allJarPaths.each { + if (it.endsWith(".jar")) { + out.println it + } + } + } + + new File("$BUILD_TOOLS_PATH/$SBG_INPUT_FILE").withWriter { out -> + out.println INPUT_JS_DIR + } + new File("$BUILD_TOOLS_PATH/$SBG_OUTPUT_FILE").withWriter { out -> + out.println OUTPUT_JAVA_DIR + } + + allJarLibraries.addAll(allJarPaths) + } +} + +task copyMetadataFilters(type: Copy) { + from "$rootDir/whitelist.mdg", "$rootDir/blacklist.mdg" + into "$BUILD_TOOLS_PATH" +} + +task copyMetadata { + doLast { + copy { + from "$projectDir/src/main/assets/metadata" + into getMergedAssetsOutputPath() + "/metadata" + } + } +} + +def listf(String directoryName, ArrayList store) { + def directory = new File(directoryName); + + def resultList = new ArrayList(); + + def fList = directory.listFiles(); + resultList.addAll(Arrays.asList(fList)); + for (File file : fList) { + if (file.isFile()) { + store.add(file) + } else if (file.isDirectory()) { + resultList.addAll(listf(file.getAbsolutePath(), store)) + } + } + return resultList +} + +task buildMetadata(type: BuildToolTask) { + def rootPath = ""; + if (!findProject(':android-metadata-generator').is(null)) { + rootPath = Paths.get(project(':android-metadata-generator').projectDir.path, "build/libs").toString() + dependsOn ':android-metadata-generator:jar' + } + + dependsOn copyMetadataFilters + dependsOn collectAllJars + + // As some external gradle plugins can reorder the execution order of the tasks it may happen that buildMetadata is executed after merge{Debug/Release}Assets + // in that case the metadata won't be included in the result apk and it will crash, so to avoid this we are adding the copyMetadata task which will manually copy + // the metadata files in the merge assets folder and they will be added to the result apk + + // The next line is added to avoid adding another copyData implementation from the firebase plugin - https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/3943bb9147f43c41599e801d026378eba93d3f3a/publish/scripts/installer.js#L1105 + //buildMetadata.finalizedBy(copyMetadata) + finalizedBy copyMetadata + + description "builds metadata with provided jar dependencies" + + inputs.files("$MDG_JAVA_DEPENDENCIES") + + // make MDG aware of whitelist.mdg and blacklist.mdg files + inputs.files(project.fileTree(dir: "$rootDir", include: "**/*.mdg")) + + def classesDir = "$buildDir/intermediates/javac" + if (file(classesDir).exists()) { + inputs.dir(classesDir) + } + + def kotlinClassesDir = "$buildDir/tmp/kotlin-classes" + if (file(kotlinClassesDir).exists()) { + inputs.dir(kotlinClassesDir) + } + + outputs.files("$METADATA_OUT_PATH/treeNodeStream.dat", "$METADATA_OUT_PATH/treeStringsStream.dat", "$METADATA_OUT_PATH/treeValueStream.dat") + + workingDir "$BUILD_TOOLS_PATH" + mainClass = "-jar" + + doFirst { + // get compiled classes to pass to metadata generator + // these need to be called after the classes have compiled + new File(getMergedAssetsOutputPath() + "/metadata").deleteDir() + + def classesSubDirs = [] + def kotlinClassesSubDirs = [] + def selectedBuildType = project.ext.selectedBuildType + + rootProject.subprojects { + + def projectClassesDir = new File("$it.buildDir/intermediates/javac") + def projectKotlinClassesDir = new File("$it.buildDir/tmp/kotlin-classes") + + if (projectClassesDir.exists()) { + def projectClassesSubDirs = projectClassesDir.listFiles() + for (File subDir : projectClassesSubDirs) { + if (!classesSubDirs.contains(subDir)) { + classesSubDirs.add(subDir) + } + } + } + + if (projectKotlinClassesDir.exists()) { + def projectKotlinClassesSubDirs = projectKotlinClassesDir.listFiles(); + for (File subDir : projectKotlinClassesSubDirs) { + if (!kotlinClassesSubDirs.contains(subDir)) { + kotlinClassesSubDirs.add(subDir) + } + } + } + } + + def generatedClasses = new LinkedList() + for (File subDir : classesSubDirs) { + if (subDir.getName() == selectedBuildType) { + generatedClasses.add(subDir.getAbsolutePath()) + } + } + + for (File subDir : kotlinClassesSubDirs) { + if (subDir.getName() == selectedBuildType) { + generatedClasses.add(subDir.getAbsolutePath()) + } + } + + def store = new ArrayList() + for (String dir: generatedClasses){ + listf(dir, store) + } + + + new File("$BUILD_TOOLS_PATH/$METADATA_JAVA_OUT").withWriter { out -> + store.each { + out.println it.absolutePath + } + } + + + new File("$BUILD_TOOLS_PATH/$MDG_OUTPUT_DIR").withWriter { out -> + out.println "$METADATA_OUT_PATH" + } + + new File("$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES").withWriterAppend { out -> + generatedClasses.each { out.println it } + } + + setOutputs outLogger + + def paramz = new ArrayList() + paramz.add(Paths.get(rootPath, "android-metadata-generator.jar")) + + if(enableAnalytics){ + paramz.add("analyticsFilePath=$analyticsFilePath") + } + + if(enableVerboseMDG){ + paramz.add("verbose") + } + + args paramz.toArray() + } +} + +task generateTypescriptDefinitions(type: BuildToolTask) { + if (!findProject(':dts-generator').is(null)) { + dependsOn ':dts-generator:jar' + } + + def paramz = new ArrayList() + def includeDirs = ["com.android.support", "/platforms/" + android.compileSdkVersion] + + workingDir "$BUILD_TOOLS_PATH" + mainClass = "-jar" + + doFirst { + delete "$TYPINGS_PATH" + + paramz.add("dts-generator.jar") + paramz.add("-input") + + for (String jarPath : allJarLibraries) { + // don't generate typings for runtime jars and classes + if (shouldIncludeDirForTypings(jarPath, includeDirs)) { + paramz.add(jarPath) + } + } + + paramz.add("-output") + paramz.add("$TYPINGS_PATH") + + new File("$TYPINGS_PATH").mkdirs() + + logger.info("Task generateTypescriptDefinitions: Call dts-generator.jar with arguments: " + paramz.toString().replaceAll(',', '')) + outLogger.withStyle(Style.SuccessHeader).println "Task generateTypescriptDefinitions: Call dts-generator.jar with arguments: " + paramz.toString().replaceAll(',', '') + + setOutputs outLogger + + args paramz.toArray() + } +} + +generateTypescriptDefinitions.onlyIf { + (project.hasProperty("generateTypings") && Boolean.parseBoolean(project.generateTypings)) || PASSED_TYPINGS_PATH != null +} + +collectAllJars.finalizedBy(generateTypescriptDefinitions) + +static def shouldIncludeDirForTypings(path, includeDirs) { + for (String p : includeDirs) { + if (path.indexOf(p) > -1) { + return true + } + } + + return false +} + +task copyTypings { + doLast { + outLogger.withStyle(Style.Info).println "Copied generated typings to application root level. Make sure to import android.d.ts in reference.d.ts" + + copy { + from "$TYPINGS_PATH" + into "$USER_PROJECT_ROOT" + } + } +} + +copyTypings.onlyIf { generateTypescriptDefinitions.didWork } +generateTypescriptDefinitions.finalizedBy(copyTypings) + +task validateAppIdMatch { + doLast { + def lineSeparator = System.getProperty("line.separator") + + if (project.hasProperty("nsApplicationIdentifier") && !project.hasProperty("release")) { + if (project.nsApplicationIdentifier != android.defaultConfig.applicationId) { + def errorMessage = "${lineSeparator}WARNING: The Application identifier is different from the one inside \"package.json\" file.$lineSeparator" + + "NativeScript CLI might not work properly.$lineSeparator" + + "Remove applicationId from app.gradle and update the \"nativescript.id\" in package.json.$lineSeparator" + + "Actual: ${android.defaultConfig.applicationId}$lineSeparator" + + "Expected(from \"package.json\"): ${project.nsApplicationIdentifier}$lineSeparator" + + logger.error(errorMessage) + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////// +////////////////////////////// OPTIONAL TASKS ////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +//////// custom clean /////////// +task cleanSbg(type: Delete) { + delete "$BUILD_TOOLS_PATH/$SBG_JS_PARSED_FILES", + "$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES", + "$BUILD_TOOLS_PATH/$SBG_INTERFACE_NAMES", + "$BUILD_TOOLS_PATH/$SBG_BINDINGS_NAME", + "$BUILD_TOOLS_PATH/$SBG_INPUT_FILE", + "$BUILD_TOOLS_PATH/$SBG_OUTPUT_FILE", + "$BUILD_TOOLS_PATH/$METADATA_JAVA_OUT", + "$OUTPUT_JAVA_DIR/com/tns/gen" +} + +task cleanMdg(type: Delete) { + delete "$BUILD_TOOLS_PATH/$MDG_OUTPUT_DIR", + "$BUILD_TOOLS_PATH/whitelist.mdg", + "$BUILD_TOOLS_PATH/blacklist.mdg", + "$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES", + "$METADATA_OUT_PATH" +} + +cleanSbg.dependsOn(cleanMdg) +clean.dependsOn(cleanSbg) + + +project.tasks.configureEach { + if (name =~ /generate.+BuildConfig/) { + it.finalizedBy(extractAllJars) + extractAllJars.finalizedBy(collectAllJars) + } + if (name =~ /compile.+JavaWithJavac/) { + it.dependsOn(runSbg) + it.finalizedBy(buildMetadata) + } + + + if (name =~ /compile.+Kotlin.+/) { + it.dependsOn(runSbg) + it.finalizedBy(buildMetadata) + } + + if (name =~ /merge.*Assets/) { + it.dependsOn(buildMetadata) + } + // ensure buildMetadata is done before R8 to allow custom proguard from metadata + if (name =~ /minify.*WithR8/) { + it.dependsOn(buildMetadata) + } + if (name =~ /assemble.*Debug/ || name =~ /assemble.*Release/) { + it.finalizedBy("validateAppIdMatch") + } + + // added because of a error with gradle 8 telling us to add this + // because buildMetadata uses the output of those + // TODO: there must be a way to make it simpler + if (!(name =~ /AndroidTest/) && + (name =~ /merge(Debug|Release)Shaders/ || + name =~ /desugar(Debug|Release)FileDependencies/ || + name =~ /merge(Debug|Release)JavaResource/ || + name =~ /dexBuilder(Debug|Release)/ || + name =~ /check(Debug|Release)DuplicateClasses/ || + name =~ /merge(Debug|Release)GlobalSynthetics/ || + name =~ /mergeLibDex(Debug|Release)/ || + name =~ /mergeExtDex(Debug|Release)/ || + name =~ /mergeProjectDex(Debug|Release)/ || + name =~ /merge(Debug|Release)JniLibFolders/ || + name =~ /merge(Debug|Release)NativeLibs/ || + name =~ /strip(Debug|Release)DebugSymbols/ || + name =~ /bundle(Debug|Release)Resources/ || + name =~ /extract(Debug|Release)NativeSymbolTables/ || + name =~ /extractProguardFiles/ || + name =~ /desugarBenchmarkFileDependencies/ || + name =~ /mergeExtDexBenchmark/ || + name =~ /mergeDexBenchmark/ || + name =~ /mergeBenchmarkShaders/ || + name =~ /mergeBenchmarkJavaResource/ || + name =~ /mergeBenchmarkJniLibFolders/ || + name =~ /checkBenchmarkDuplicateClasses/ || + name =~ /bundleBenchmarkResources/ || + name =~ /extractBenchmarkNativeSymbolTables/ || + // name =~ /compressBenchmarkAssets/ || + name =~ /mergeBenchmarkNativeLibs/ || + name =~ /stripBenchmark(Debug|Release)Symbols/ || + name =~ /buildKotlinToolingMetadata/ || + name =~ /validateSigning/ || + name =~ /mergeExtDexNonMinified(Debug|Release)/ || + name =~ /mergeDexNonMinified(Debug|Release)/ || + name =~ /mergeNonMinified(Debug|Release)Shaders/ || + name =~ /processNonMinified(Debug|Release)JavaRes/ || + name =~ /mergeNonMinified(Debug|Release)JavaResource/ || + name =~ /optimizeNonMinified(Debug|Release)Resources/ || + name =~ /mergeNonMinified(Debug|Release)JniLibFolders/ || + name =~ /mergeNonMinified(Debug|Release)NativeLibs/ || + name =~ /stripNonMinified(Debug|Release)DebugSymbols/ || + name =~ /checkBenchmark(Debug|Release)DuplicateClasses/ || + name =~ /processBenchmark(Debug|Release)JavaRes/ || + name =~ /mergeBenchmark(Debug|Release)JavaResource/ || + name =~ /transform(Debug|Release)ClassesWithAsm/ || + // because of sentry plugin. There must be a better way + name =~ /collectExternal(Debug|Release)/ + )) { + it.finalizedBy(buildMetadata) + } + // added because of a error with gradle 8 telling us to add this + // because buildMetadata uses the output of those + // TODO: there must be a way to make it simpler + if (!(name =~ /AndroidTest/) && ( + name =~ /process(Debug|Release)Resources/ || + name =~ /compile(Debug|Release)Kotlin/ + )) { + cleanupAllJars.dependsOn(it) + } +} \ No newline at end of file diff --git a/vendor/gradle-app/app/gradle-helpers/AnalyticsCollector.gradle b/vendor/gradle-app/app/gradle-helpers/AnalyticsCollector.gradle new file mode 100644 index 0000000000..72fa363a73 --- /dev/null +++ b/vendor/gradle-app/app/gradle-helpers/AnalyticsCollector.gradle @@ -0,0 +1,48 @@ +import groovy.json.JsonBuilder + +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.Path + +class AnalyticsCollector{ + + private final String analyticsFilePath + private boolean hasUseKotlinPropertyInApp = false + private boolean hasKotlinRuntimeClasses = false + + private AnalyticsCollector(String analyticsFilePath){ + this.analyticsFilePath = analyticsFilePath + } + + static AnalyticsCollector withOutputPath(String analyticsFilePath){ + return new AnalyticsCollector(analyticsFilePath) + } + + void markUseKotlinPropertyInApp(boolean useKotlin) { + hasUseKotlinPropertyInApp = useKotlin + } + + void writeAnalyticsFile() { + def jsonBuilder = new JsonBuilder() + def kotlinUsageData = new Object() + kotlinUsageData.metaClass.hasUseKotlinPropertyInApp = hasUseKotlinPropertyInApp + kotlinUsageData.metaClass.hasKotlinRuntimeClasses = hasKotlinRuntimeClasses + jsonBuilder(kotlinUsage: kotlinUsageData) + def prettyJson = jsonBuilder.toPrettyString() + + + + Path statisticsFilePath = Paths.get(analyticsFilePath) + + if (Files.notExists(statisticsFilePath)) { + Files.createDirectories(statisticsFilePath.getParent()) + Files.createFile(statisticsFilePath) + } + + Files.write(statisticsFilePath, prettyJson.getBytes(StandardCharsets.UTF_8)) + + } +} + +ext.AnalyticsCollector = AnalyticsCollector diff --git a/vendor/gradle-app/app/gradle-helpers/BuildToolTask.gradle b/vendor/gradle-app/app/gradle-helpers/BuildToolTask.gradle new file mode 100644 index 0000000000..7b6052f4c2 --- /dev/null +++ b/vendor/gradle-app/app/gradle-helpers/BuildToolTask.gradle @@ -0,0 +1,50 @@ +import static org.gradle.internal.logging.text.StyledTextOutput.Style + +class BuildToolTask extends JavaExec { + void setOutputs(def logger) { + def logFile = new File("$workingDir/${name}.log") + if(logFile.exists()) { + logFile.delete() + } + standardOutput new FileOutputStream(logFile) + errorOutput new FailureOutputStream(logger, logFile) + } +} + +class FailureOutputStream extends OutputStream { + private logger + private File logFile + private currentLine = "" + private firstWrite = true + FailureOutputStream(inLogger, inLogFile) { + logger = inLogger + logFile = inLogFile + } + + @Override + void write(int i) throws IOException { + if(firstWrite) { + println "" + firstWrite = false + } + currentLine += String.valueOf((char) i) + } + + @Override + void flush() { + if(currentLine?.trim()) { + logger.withStyle(Style.Failure).println currentLine.trim() + currentLine = "" + } + } + + @Override + void close() { + if(!firstWrite && logFile.exists()) { + logger.withStyle(Style.Info).println "Detailed log here: ${logFile.getAbsolutePath()}\n" + } + super.close() + } +} + +ext.BuildToolTask = BuildToolTask \ No newline at end of file diff --git a/vendor/gradle-app/app/gradle-helpers/CustomExecutionLogger.gradle b/vendor/gradle-app/app/gradle-helpers/CustomExecutionLogger.gradle new file mode 100644 index 0000000000..ec8ea6c427 --- /dev/null +++ b/vendor/gradle-app/app/gradle-helpers/CustomExecutionLogger.gradle @@ -0,0 +1,52 @@ +import org.gradle.internal.logging.text.StyledTextOutputFactory + +import static org.gradle.internal.logging.text.StyledTextOutput.Style +def outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger") + +class CustomExecutionLogger extends BuildAdapter implements TaskExecutionListener { + private logger + private failedTask + + CustomExecutionLogger(passedLogger) { + logger = passedLogger + } + + void buildStarted(Gradle gradle) { + failedTask = null + } + + void beforeExecute(Task task) { + } + + void afterExecute(Task task, TaskState state) { + def failure = state.getFailure() + if(failure) { + failedTask = task + } + } + + void buildFinished(BuildResult result) { + def failure = result.getFailure() + if(failure) { + if(failedTask && (failedTask.getClass().getName().contains("BuildToolTask"))) { + // the error from this task is already logged + return + } + + println "" + logger.withStyle(Style.FailureHeader).println failure.getMessage() + + def causeException = failure.getCause() + while (causeException != null) { + failure = causeException + causeException = failure.getCause() + } + if(failure != causeException) { + logger.withStyle(Style.Failure).println failure.getMessage() + } + println "" + } + } +} + +gradle.useLogger(new CustomExecutionLogger(outLogger)) \ No newline at end of file diff --git a/vendor/gradle-app/build.gradle b/vendor/gradle-app/build.gradle new file mode 100644 index 0000000000..55ca4c66d7 --- /dev/null +++ b/vendor/gradle-app/build.gradle @@ -0,0 +1,170 @@ +import org.gradle.internal.logging.text.StyledTextOutputFactory + +import java.nio.file.Paths + +import org.gradle.internal.logging.text.StyledTextOutputFactory +import groovy.json.JsonSlurper +import static org.gradle.internal.logging.text.StyledTextOutput.Style + +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + def initialize = { -> + // set up our logger + project.ext.outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger") + def userDir = "${rootProject.projectDir}/../.." + apply from: "$rootDir/gradle-helpers/user_properties_reader.gradle" + apply from: "$rootDir/gradle-helpers/paths.gradle" + rootProject.ext.userDefinedGradleProperties = getUserProperties("${getAppResourcesPath(userDir)}/Android") + + loadPropertyFile("$rootDir/additional_gradle.properties") + + if (rootProject.hasProperty("userDefinedGradleProperties")) { + rootProject.ext.userDefinedGradleProperties.each { entry -> + def propertyName = entry.getKey() + def propertyValue = entry.getValue() + project.ext.set(propertyName, propertyValue) + } + } + + // the build script will not work with previous versions of the CLI (3.1 or earlier) + def dependenciesJson = file("$rootDir/dependencies.json") + if (!dependenciesJson.exists()) { + throw new BuildCancelledException(""" +'dependencies.json' file not found. Check whether the NativeScript CLI has prepared the project beforehand, +and that your NativeScript version is 3.3, or a more recent one. To build an android project with the current +version of the {N} CLI install a previous version of the runtime package - 'tns platform add android@3.2'. +""") + } + + project.ext.extractedDependenciesDir = "${project.buildDir}/exploded-dependencies" + project.ext.cleanupAllJarsTimestamp = "${project.buildDir}/cleanupAllJars.timestamp" + project.ext.extractAllJarsTimestamp = "${project.buildDir}/extractAllJars.timestamp" + + + project.ext.nativescriptDependencies = new JsonSlurper().parseText(dependenciesJson.text) + project.ext.PLATFORMS_ANDROID = "platforms/android" + project.ext.USER_PROJECT_ROOT = "$rootDir/../.." + + project.ext.getAppPath = { -> + def relativePathToApp = "app" + def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + def nsConfig + + if (nsConfigFile.exists()) { + nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + } + + if (project.hasProperty("appPath")) { + // when appPath is passed through -PappPath=/path/to/app + // the path could be relative or absolute - either case will work + relativePathToApp = appPath + } else if (nsConfig != null && nsConfig.appPath != null) { + relativePathToApp = nsConfig.appPath + } + + project.ext.appPath = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToApp).toAbsolutePath() + + return project.ext.appPath + } + + project.ext.getAppResourcesPath = { -> + def relativePathToAppResources + def absolutePathToAppResources + def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + def nsConfig + + if (nsConfigFile.exists()) { + nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + } + + if (project.hasProperty("appResourcesPath")) { + // when appResourcesPath is passed through -PappResourcesPath=/path/to/App_Resources + // the path could be relative or absolute - either case will work + relativePathToAppResources = appResourcesPath + absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() + } else if (nsConfig != null && nsConfig.appResourcesPath != null) { + relativePathToAppResources = nsConfig.appResourcesPath + absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() + } else { + absolutePathToAppResources = "${getAppPath()}/App_Resources" + } + + project.ext.appResourcesPath = absolutePathToAppResources + + return absolutePathToAppResources + } + + + } + + def applyBeforePluginGradleConfiguration = { -> + def appResourcesPath = getAppResourcesPath() + def pathToBeforePluginGradle = "$appResourcesPath/Android/before-plugins.gradle" + def beforePluginGradle = file(pathToBeforePluginGradle) + if (beforePluginGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${beforePluginGradle}" + apply from: pathToBeforePluginGradle + } + } + + def applyBuildScriptConfigurations = { -> + def absolutePathToAppResources = getAppResourcesPath() + def pathToBuildScriptGradle = "$absolutePathToAppResources/Android/rootbuildscript.gradle" + def buildScriptGradle = file(pathToBuildScriptGradle) + if (buildScriptGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined root buildscript from ${buildScriptGradle}" + apply from: pathToBuildScriptGradle, to: buildscript + } + + nativescriptDependencies.each { dep -> + def pathToPluginBuildScriptGradle = "$rootDir/${dep.directory}/$PLATFORMS_ANDROID/rootbuildscript.gradle" + def pluginBuildScriptGradle = file(pathToPluginBuildScriptGradle) + if (pluginBuildScriptGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined rootbuildscript from dependency ${pluginBuildScriptGradle}" + apply from: pathToPluginBuildScriptGradle, to: buildscript + } + } + } + + initialize() + applyBuildScriptConfigurations() + applyBeforePluginGradleConfiguration() + + def computeKotlinVersion = { -> project.hasProperty("kotlinVersion") ? kotlinVersion : "${ns_default_kotlin_version}" } + def computeBuildToolsVersion = { -> project.hasProperty("androidBuildToolsVersion") ? androidBuildToolsVersion : "${NS_DEFAULT_ANDROID_BUILD_TOOLS_VERSION}" } + def kotlinVersion = computeKotlinVersion() + def androidBuildToolsVersion = computeBuildToolsVersion() + + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:$androidBuildToolsVersion" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + classpath "org.codehaus.groovy:groovy-all:3.0.8" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } + beforeEvaluate { project -> + if (rootProject.hasProperty("userDefinedGradleProperties")) { + rootProject.ext.userDefinedGradleProperties.each { entry -> + def propertyName = entry.getKey() + def propertyValue = entry.getValue() + project.ext.set(propertyName, propertyValue) + } + } + + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/vendor/gradle-app/settings.gradle b/vendor/gradle-app/settings.gradle new file mode 100644 index 0000000000..e1226cf416 --- /dev/null +++ b/vendor/gradle-app/settings.gradle @@ -0,0 +1,78 @@ +rootProject.name = "__PROJECT_NAME__" +include ':app'//, ':runtime', ':runtime-binding-generator' + +//project(':runtime').projectDir = new File("${System.env.ANDROID_RUNTIME_HOME}/test-app/runtime") +//project(':runtime-binding-generator').projectDir = new File("${System.env.ANDROID_RUNTIME_HOME}/test-app/runtime-binding-generator") + +file("google-services.json").renameTo(file("./app/google-services.json")) + +import org.gradle.internal.logging.text.StyledTextOutputFactory + +import java.nio.file.Paths + +import org.gradle.internal.logging.text.StyledTextOutputFactory +import groovy.json.JsonSlurper +import static org.gradle.internal.logging.text.StyledTextOutput.Style + +def USER_PROJECT_ROOT = "$rootDir/../../" +def outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger") +def ext = { + appResourcesPath = getProperty("appResourcesPath") + appPath = getProperty("appPath") +} + +def getAppPath = { -> + def relativePathToApp = "app" + def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + def nsConfig + + if (nsConfigFile.exists()) { + nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + } + + if (ext.appPath) { + // when appPath is passed through -PappPath=/path/to/app + // the path could be relative or absolute - either case will work + relativePathToApp = ext.appPath + } else if (nsConfig != null && nsConfig.appPath != null) { + relativePathToApp = nsConfig.appPath + } + + return Paths.get(USER_PROJECT_ROOT).resolve(relativePathToApp).toAbsolutePath() + } + + +def getAppResourcesPath = { -> + def relativePathToAppResources + def absolutePathToAppResources + def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + def nsConfig + + if (nsConfigFile.exists()) { + nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + } + if (ext.appResourcesPath) { + // when appResourcesPath is passed through -PappResourcesPath=/path/to/App_Resources + // the path could be relative or absolute - either case will work + relativePathToAppResources = ext.appResourcesPath + absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() + } else if (nsConfig != null && nsConfig.appResourcesPath != null) { + relativePathToAppResources = nsConfig.appResourcesPath + absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() + } else { + absolutePathToAppResources = "${getAppPath()}/App_Resources" + } + return absolutePathToAppResources +} + +def applySettingsGradleConfiguration = { -> + def appResourcesPath = getAppResourcesPath() + def pathToSettingsGradle = "$appResourcesPath/Android/settings.gradle" + def settingsGradle = file(pathToSettingsGradle) + if (settingsGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${settingsGradle}" + apply from: pathToSettingsGradle + } +} + +applySettingsGradleConfiguration() diff --git a/vendor/gradle-plugin/build.gradle b/vendor/gradle-plugin/build.gradle index 38092efde4..1c964d4609 100644 --- a/vendor/gradle-plugin/build.gradle +++ b/vendor/gradle-plugin/build.gradle @@ -7,79 +7,130 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' -buildscript { - def getDepPlatformDir = { dep -> - file("${project.ext.USER_PROJECT_ROOT}/${project.ext.PLATFORMS_ANDROID}/${dep.directory}/$PLATFORMS_ANDROID") +def loadPropertyFile = { path -> + try { + if(project.hasProperty("loadedProperties_${path}")) { + logger.info "\t + gradle properties already loaded. SKIPPING" + } else { + logger.info "\t + trying to load gradle properties from \"$path\"" + + Properties properties = new Properties() + properties.load(new FileInputStream("$path")) + properties.each { prop -> + logger.info "\t + [$path] setting ${prop.key} = ${prop.value}" + project.ext.set(prop.key, prop.value) + } + project.ext.set("loadedProperties_${path}", true) + + outLogger.withStyle(Style.SuccessHeader).println "\t + loaded gradle properties from \"$path\"" + } + } catch(Exception ex) { + logger.warn "\t + failed to load gradle properties from \"$path\". Error is: ${ex.getMessage()}" } - def computeKotlinVersion = { -> project.hasProperty("kotlinVersion") ? kotlinVersion : "1.8.20" } - def kotlinVersion = computeKotlinVersion() - repositories { - google() - jcenter() +} + +buildscript { + def GRADLE_PROPERTIES_FILENAME = "gradle.properties" + + def getFile = { dir, filename -> + File file = new File("$dir$File.separator$filename") + file?.exists() ? file : null } - dependencies { - classpath 'com.android.tools.build:gradle:{{runtimeAndroidPluginVersion}}' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + def getPropertyFile = { dir -> + return getFile(dir, GRADLE_PROPERTIES_FILENAME) } + def getUserProperties = { dir -> + def file = getPropertyFile(dir) + if (!file) { + return null + } - // Set up styled logger - project.ext.getDepPlatformDir = getDepPlatformDir - project.ext.outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger") + Properties properties = new Properties() + properties.load(file.newInputStream()) - project.ext.USER_PROJECT_ROOT = "$rootDir/../../.." - project.ext.PLATFORMS_ANDROID = "platforms/android" - project.ext.PLUGIN_NAME = "{{pluginName}}" + return properties + } + def loadPropertyFile = { path -> + try { + if(project.hasProperty("loadedProperties_${path}")) { + logger.info "\t + gradle properties already loaded. SKIPPING" + } else { + logger.info "\t + trying to load gradle properties from \"$path\"" + + Properties properties = new Properties() + properties.load(new FileInputStream("$path")) + properties.each { prop -> + logger.info "\t + [$path] setting ${prop.key} = ${prop.value}" + project.ext.set(prop.key, prop.value) + } + project.ext.set("loadedProperties_${path}", true) + + outLogger.withStyle(Style.SuccessHeader).println "\t + loaded gradle properties from \"$path\"" + } + } catch(Exception ex) { + logger.warn "\t + failed to load gradle properties from \"$path\". Error is: ${ex.getMessage()}" + } + } + - // the build script will not work with previous versions of the CLI (3.1 or earlier) - def dependenciesJson = file("${project.ext.USER_PROJECT_ROOT}/${project.ext.PLATFORMS_ANDROID}/dependencies.json") - def appDependencies = new JsonSlurper().parseText(dependenciesJson.text) - def pluginData = appDependencies.find { it.name == project.ext.PLUGIN_NAME } - project.ext.nativescriptDependencies = appDependencies.findAll{pluginData.dependencies.contains(it.name)} project.ext.getAppPath = { -> - def relativePathToApp = "app" - def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") - def nsConfig - - if (nsConfigFile.exists()) { - nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) - } + // def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + // def nsConfig + // if (nsConfigFile.exists()) { + // nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + // } + def relativePathToApp = "app" if (project.hasProperty("appPath")) { // when appPath is passed through -PappPath=/path/to/app // the path could be relative or absolute - either case will work relativePathToApp = appPath - } else if (nsConfig != null && nsConfig.appPath != null) { - relativePathToApp = nsConfig.appPath + // } else if (nsConfig != null && nsConfig.appPath != null) { + // relativePathToApp = nsConfig.appPath } project.ext.appPath = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToApp).toAbsolutePath() return project.ext.appPath } - + project.ext.getBuildPath = { -> + // def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + // def nsConfig + + // if (nsConfigFile.exists()) { + // nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + // } + def relativeBuildToApp = "platforms" + if (project.getProperty("appBuildPath")) { + relativeBuildToApp = appBuildPath + // } else if (nsConfig != null && nsConfig.buildPath != null) { + // relativeBuildToApp = nsConfig.buildPath + } + project.ext.relativeBuildPath = relativeBuildToApp + project.ext.buildPath = Paths.get(USER_PROJECT_ROOT).resolve(relativeBuildToApp).toAbsolutePath() + return project.ext.relativeBuildPath + } project.ext.getAppResourcesPath = { -> + // def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + // def nsConfig + + // if (nsConfigFile.exists()) { + // nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + // } def relativePathToAppResources def absolutePathToAppResources - def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") - def nsConfig - - if (nsConfigFile.exists()) { - nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) - } if (project.hasProperty("appResourcesPath")) { // when appResourcesPath is passed through -PappResourcesPath=/path/to/App_Resources // the path could be relative or absolute - either case will work relativePathToAppResources = appResourcesPath absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() - } else if (nsConfig != null && nsConfig.appResourcesPath != null) { - relativePathToAppResources = nsConfig.appResourcesPath - absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() + // } else if (nsConfig != null && nsConfig.appResourcesPath != null) { + // relativePathToAppResources = nsConfig.appResourcesPath + // absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() } else { - absolutePathToAppResources = "${getAppPath()}/App_Resources" + absolutePathToAppResources = "${project.ext.getAppPath()}/App_Resources" } project.ext.appResourcesPath = absolutePathToAppResources @@ -87,8 +138,61 @@ buildscript { return absolutePathToAppResources } + project.ext.applyBeforePluginGradleConfiguration = { -> + def appResourcesPath = project.ext.getAppResourcesPath() + def pathToBeforePluginGradle = "$appResourcesPath/Android/before-plugins.gradle" + def beforePluginGradle = file(pathToBeforePluginGradle) + if (beforePluginGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t ~ applying user-defined configuration from ${beforePluginGradle}" + apply from: pathToBeforePluginGradle + } + } + + + def initialize = { -> + // set up our logger + project.ext.outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger") + outLogger.withStyle(Style.SuccessHeader).println "\t ~initialize" + + + project.ext.USER_PROJECT_ROOT = "$rootDir/../.." + if (project.hasProperty('projectRoot')) { + project.ext.USER_PROJECT_ROOT = projectRoot + } + project.ext.PLATFORMS_ANDROID = project.ext.getBuildPath() + "/android" + project.ext.PLUGIN_NAME = "{{pluginName}}" + + def userDir = "$USER_PROJECT_ROOT" + rootProject.ext.userDefinedGradleProperties = getUserProperties("${project.ext.getAppResourcesPath()}/Android") + + loadPropertyFile("$USER_PROJECT_ROOT/${project.ext.PLATFORMS_ANDROID}/gradle.properties") + loadPropertyFile("$USER_PROJECT_ROOT/${project.ext.PLATFORMS_ANDROID}/additional_gradle.properties") + + if (rootProject.hasProperty("userDefinedGradleProperties")) { + rootProject.ext.userDefinedGradleProperties.each { entry -> + def propertyName = entry.getKey() + def propertyValue = entry.getValue() + project.ext.set(propertyName, propertyValue) + } + } + + def getDepPlatformDir = { dep -> + file("${project.ext.USER_PROJECT_ROOT}/${project.ext.PLATFORMS_ANDROID}/${dep.directory}/platforms/android") + } + + // Set up styled logger + project.ext.getDepPlatformDir = getDepPlatformDir + project.ext.outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger") + + + // the build script will not work with previous versions of the CLI (3.1 or earlier) + def dependenciesJson = file("${project.ext.USER_PROJECT_ROOT}/${project.ext.PLATFORMS_ANDROID}/dependencies.json") + def appDependencies = new JsonSlurper().parseText(dependenciesJson.text) + def pluginData = appDependencies.find { it.name == project.ext.PLUGIN_NAME } + project.ext.nativescriptDependencies = appDependencies.findAll{pluginData.dependencies.contains(it.name)}.plus([pluginData]) + } def applyBuildScriptConfigurations = { -> - def absolutePathToAppResources = getAppResourcesPath() + def absolutePathToAppResources = project.ext.getAppResourcesPath() def pathToBuildScriptGradle = "$absolutePathToAppResources/Android/buildscript.gradle" def buildScriptGradle = file(pathToBuildScriptGradle) if (buildScriptGradle.exists()) { @@ -103,6 +207,12 @@ buildscript { outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined buildscript from dependency ${pluginBuildScriptGradle}" apply from: pathToPluginBuildScriptGradle, to: buildscript } + // def pathToPluginProjectBuildScriptGradle = "${getDepPlatformDir(dep)}/project-buildscript.gradle" + // def pluginProjectBuildScriptGradle = file(pathToPluginProjectBuildScriptGradle) + // if (pluginProjectBuildScriptGradle.exists()) { + // outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined project-buildscript from dependency ${pluginProjectBuildScriptGradle}" + // apply from: pathToPluginProjectBuildScriptGradle, to: project + // } } def pathToPluginBuildScriptGradle = "$rootDir/buildscript.gradle" @@ -112,8 +222,26 @@ buildscript { apply from: pathToPluginBuildScriptGradle, to: buildscript } } + + initialize() + project.ext.applyBeforePluginGradleConfiguration() applyBuildScriptConfigurations() + def computeKotlinVersion = { -> project.hasProperty("kotlinVersion") ? kotlinVersion : "${ns_default_kotlin_version}" } + def computeBuildToolsVersion = { -> project.hasProperty("androidBuildToolsVersion") ? androidBuildToolsVersion : "{{runtimeAndroidPluginVersion}}" } + def kotlinVersion = computeKotlinVersion() + def androidBuildToolsVersion = computeBuildToolsVersion() + + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:$androidBuildToolsVersion" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + classpath "org.codehaus.groovy:groovy-all:3.0.8" + } + } def pluginDependencies @@ -144,85 +272,92 @@ allprojects { } } - -def computeCompileSdkVersion = { -> project.hasProperty("compileSdk") ? compileSdk : 31 } -def computeTargetSdkVersion = { -> project.hasProperty("targetSdk") ? targetSdk : 31 as int } -def computeBuildToolsVersion = { -> - project.hasProperty("buildToolsVersion") ? buildToolsVersion : "31.0.0" -} - android { + namespace "{{pluginNamespace}}" def applyPluginGradleConfigurations = { -> nativescriptDependencies.each { dep -> def includeGradlePath = "${getDepPlatformDir(dep)}/include.gradle" if (file(includeGradlePath).exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying plugin include.gradle from dependency ${includeGradlePath}" apply from: includeGradlePath } } } - applyBeforePluginGradleConfiguration() + + def computeCompileSdkVersion = { -> project.hasProperty("compileSdk") ? project.compileSdk : 31 } + def computeTargetSdkVersion = { -> project.hasProperty("targetSdk") ? project.targetSdk : 31 as int } + def computeBuildToolsVersion = { -> + project.hasProperty("buildToolsVersion") ? project.buildToolsVersion : "31.0.0" + } + + project.ext.applyBeforePluginGradleConfiguration() applyPluginGradleConfigurations() compileSdkVersion computeCompileSdkVersion() buildToolsVersion computeBuildToolsVersion() + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + if (project.hasProperty("ndkVersion")) { + ndkVersion project.ndkVersion + } + defaultConfig { targetSdkVersion computeTargetSdkVersion() versionCode 1 versionName "1.0" } -} - - -def applyBeforePluginGradleConfiguration() { - def appResourcesPath = getAppResourcesPath() - def pathToBeforePluginGradle = "$appResourcesPath/Android/before-plugins.gradle" - def beforePluginGradle = file(pathToBeforePluginGradle) - if (beforePluginGradle.exists()) { - outLogger.withStyle(Style.SuccessHeader).println "\t ~ applying user-defined configuration from ${beforePluginGradle}" - apply from: pathToBeforePluginGradle + lintOptions { + checkReleaseBuilds false + abortOnError false } } task addDependenciesFromNativeScriptPlugins { nativescriptDependencies.each { dep -> def aarFiles = fileTree(dir: getDepPlatformDir(dep), include: ["**/*.aar"]) + def currentDirname = file(project.buildscript.sourceFile).getParentFile().getName() aarFiles.each { aarFile -> def length = aarFile.name.length() - 4 def fileName = aarFile.name[0.. def jarFileAbsolutePath = jarFile.getAbsolutePath() outLogger.withStyle(Style.SuccessHeader).println "\t + adding jar plugin dependency: $jarFileAbsolutePath" - pluginsJarLibraries.add(jarFile.getAbsolutePath()) + // pluginsJarLibraries.add(jarFile.getAbsolutePath()) } project.dependencies.add("implementation", jarFiles) } } -afterEvaluate { - def generateBuildConfig = project.hasProperty("generateBuildConfig") ? project.generateBuildConfig : false - def generateR = project.hasProperty("generateR") ? project.generateR : false - generateReleaseBuildConfig.enabled = generateBuildConfig - generateDebugBuildConfig.enabled = generateBuildConfig - generateReleaseResValues.enabled = generateR - generateDebugResValues.enabled = generateR -} - -tasks.whenTaskAdded({ DefaultTask currentTask -> - if (currentTask.name == 'bundleRelease' || currentTask.name == 'bundleDebug') { +// This wont work with gradle 8 + should be the same as the code just after +// afterEvaluate { +// def generateBuildConfig = project.hasProperty("generateBuildConfig") ? project.generateBuildConfig : false +// def generateR = project.hasProperty("generateR") ? project.generateR : false +// generateReleaseBuildConfig.enabled = generateBuildConfig +// generateDebugBuildConfig.enabled = generateBuildConfig +// generateReleaseResValues.enabled = generateR +// generateDebugResValues.enabled = generateR +// } +project.tasks.configureEach { + if (name == 'bundleRelease') { def generateBuildConfig = project.hasProperty("generateBuildConfig") ? project.generateBuildConfig : false def generateR = project.hasProperty("generateR") ? project.generateR : false if (!generateBuildConfig) { - currentTask.exclude '**/BuildConfig.class' + it.exclude '**/BuildConfig.class' } if (!generateR) { - currentTask.exclude '**/R.class', '**/R$*.class' + it.exclude '**/R.class', '**/R$*.class' } } -}) +} diff --git a/vendor/gradle-plugin/gradle.properties b/vendor/gradle-plugin/gradle.properties index 84a8b08c9c..5cccde2d62 100644 --- a/vendor/gradle-plugin/gradle.properties +++ b/vendor/gradle-plugin/gradle.properties @@ -1,22 +1,6 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -#org.gradle.parallel=true - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. +# Nativescript CLI plugin build gradle properties org.gradle.jvmargs=-Xmx16384M android.enableJetifier=true android.useAndroidX=true -android.nonTransitiveRClass=true -android.enableSeparateRClassCompilation=true \ No newline at end of file +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/vendor/gradle-plugin/settings.gradle b/vendor/gradle-plugin/settings.gradle index ac173898e3..e119ac9c80 100644 --- a/vendor/gradle-plugin/settings.gradle +++ b/vendor/gradle-plugin/settings.gradle @@ -1,9 +1,28 @@ import groovy.json.JsonSlurper -def USER_PROJECT_ROOT = "$rootDir/../../../" -def PLATFORMS_ANDROID = "platforms/android" + +def getProjectRoot = { -> + def projectRoot = "$rootDir/../../" + if (System.getProperties().projectRoot != null) { + projectRoot = System.getProperties().projectRoot + } + return projectRoot +} + +def getBuildPath = { -> + def relativeBuildToApp = "platforms" + if (System.getProperties().appBuildPath != null) { + relativeBuildToApp = System.getProperties().appBuildPath + } + return relativeBuildToApp +} + +def USER_PROJECT_ROOT = getProjectRoot() +def PLATFORMS_ANDROID = getBuildPath() + "/android" def PLUGIN_NAME = "{{pluginName}}" + + def dependenciesJson = file("${USER_PROJECT_ROOT}/${PLATFORMS_ANDROID}/dependencies.json") def appDependencies = new JsonSlurper().parseText(dependenciesJson.text) def pluginData = appDependencies.find { it.name == PLUGIN_NAME } @@ -13,6 +32,7 @@ def getDepPlatformDir = { dep -> file("$USER_PROJECT_ROOT/$PLATFORMS_ANDROID/${dep.directory}/$PLATFORMS_ANDROID") } + def applyIncludeSettingsGradlePlugin = { nativescriptDependencies.each { dep -> def includeSettingsGradlePath = "${getDepPlatformDir(dep)}/include-settings.gradle"