From 510596f5155c89e1ea911c8677e375c40f3b6646 Mon Sep 17 00:00:00 2001 From: Norman Breau Date: Tue, 6 Jul 2021 03:38:28 -0300 Subject: [PATCH] feat!: unify & fix gradle library/tooling overrides (#1212) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhancement: Control SDK versions and other default projects in one place * fix: target/compile sdk usage * refactor: cleanup gradle process * chore: cleanup and remove unused changes * chore: remove more unneeded FILE_PATH * chore: fix lint error * revert change intended to be part of a different PR * chore: apply changes to revert to fit new changes * fix: Ensure proper types * breaking: Removed TempateFile class * Replaced the one and only usage of it with the properties-parser editor. * Breaking change because we are converting a method into an asynchronous method. * refactor: Use the sync version of properties editor * Gh 1178 fix sdk use gradlearg fix (#2) * fix: readd gradleArg support * fix: variable name * refactor: remove unused mock variables * Update bin/templates/cordova/lib/builders/ProjectBuilder.js * Update bin/lib/create.js * fix: const naming (review suggestion) * fix: use defaults for framework building * chore: apply review suggestion * chore: rename config.json & defaults.json (review suggestions) * refactor: updateUserProjectGradleConfig method * refactor: minor changes in updateUserProjectGradleConfig * refactor: major changes in updateUserProjectGradleConfig * fix: wrong handling of missing preferences * fix: usage of undefined this * fix(create.spec): mocking of getPreference * test(check_reqs): reduce diff size * refactor: add wrapper to load gradle config defaults * fix(check_reqs): get_target * Reads default SDK from default gradle config now * fix(check_reqs.spec): return correct types from mocks * revert to using get_target in create * fix: e2e test Co-authored-by: Erisu Co-authored-by: Raphael von der Grün --- .gitignore | 1 + bin/lib/create.js | 14 +- .../cordova/lib/builders/ProjectBuilder.js | 11 +- bin/templates/cordova/lib/check_reqs.js | 43 ++---- .../cordova/lib/gradle-config-defaults.js | 30 +++++ bin/templates/cordova/lib/plugin-build.gradle | 4 +- bin/templates/cordova/lib/prepare.js | 97 +++++++++++--- bin/templates/project/app/build.gradle | 126 ++++-------------- bin/templates/project/build.gradle | 16 +-- framework/build.gradle | 27 +--- framework/cdv-gradle-config-defaults.json | 12 ++ framework/cordova.gradle | 48 ++++++- framework/default.properties | 14 -- .../gradle/wrapper/gradle-wrapper.properties | 1 - framework/project.properties | 3 +- spec/e2e/plugin.spec.js | 7 + spec/unit/check_reqs.spec.js | 44 +----- spec/unit/prepare.spec.js | 1 + test/androidx/app/build.gradle | 12 +- test/androidx/build.gradle | 4 +- test/androidx/wrapper.gradle | 3 +- test/run_java_unit_tests.js | 4 + 22 files changed, 268 insertions(+), 254 deletions(-) create mode 100644 bin/templates/cordova/lib/gradle-config-defaults.js create mode 100644 framework/cdv-gradle-config-defaults.json delete mode 100644 framework/default.properties diff --git a/.gitignore b/.gitignore index e374ee77..ae182f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ example /test/androidx/gradle /test/androidx/gradlew /test/androidx/gradlew.bat +/test/androidx/cdv-gradle-config.json /test/assets/www/.tmp* /test/assets/www/cordova.js diff --git a/bin/lib/create.js b/bin/lib/create.js index a38ecf97..93be1114 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -22,6 +22,7 @@ var fs = require('fs-extra'); var utils = require('../templates/cordova/lib/utils'); var check_reqs = require('./../templates/cordova/lib/check_reqs'); var ROOT = path.join(__dirname, '..', '..'); +const { createEditor } = require('properties-parser'); var CordovaError = require('cordova-common').CordovaError; var AndroidManifest = require('../templates/cordova/lib/AndroidManifest'); @@ -42,16 +43,12 @@ function getFrameworkDir (projectPath, shared) { return shared ? path.join(ROOT, 'framework') : path.join(projectPath, 'CordovaLib'); } -function copyJsAndLibrary (projectPath, shared, projectName, isLegacy) { +function copyJsAndLibrary (projectPath, shared, projectName, targetAPI) { var nestedCordovaLibPath = getFrameworkDir(projectPath, false); var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js'); var app_path = path.join(projectPath, 'app', 'src', 'main'); const platform_www = path.join(projectPath, 'platform_www'); - if (isLegacy) { - app_path = projectPath; - } - fs.copySync(srcCordovaJsPath, path.join(app_path, 'assets', 'www', 'cordova.js')); // Copy the cordova.js file to platforms//platform_www/ @@ -69,11 +66,14 @@ function copyJsAndLibrary (projectPath, shared, projectName, isLegacy) { } else { fs.ensureDirSync(nestedCordovaLibPath); fs.copySync(path.join(ROOT, 'framework', 'AndroidManifest.xml'), path.join(nestedCordovaLibPath, 'AndroidManifest.xml')); - fs.copySync(path.join(ROOT, 'framework', 'project.properties'), path.join(nestedCordovaLibPath, 'project.properties')); + const propertiesEditor = createEditor(path.join(ROOT, 'framework', 'project.properties')); + propertiesEditor.set('target', targetAPI); + propertiesEditor.save(path.join(nestedCordovaLibPath, 'project.properties')); fs.copySync(path.join(ROOT, 'framework', 'build.gradle'), path.join(nestedCordovaLibPath, 'build.gradle')); fs.copySync(path.join(ROOT, 'framework', 'cordova.gradle'), path.join(nestedCordovaLibPath, 'cordova.gradle')); fs.copySync(path.join(ROOT, 'framework', 'repositories.gradle'), path.join(nestedCordovaLibPath, 'repositories.gradle')); fs.copySync(path.join(ROOT, 'framework', 'src'), path.join(nestedCordovaLibPath, 'src')); + fs.copySync(path.join(ROOT, 'framework', 'cdv-gradle-config-defaults.json'), path.join(projectPath, 'cdv-gradle-config.json')); } } @@ -277,7 +277,7 @@ exports.create = function (project_path, config, options, events) { fs.ensureDirSync(path.join(app_path, 'libs')); // copy cordova.js, cordova.jar - exports.copyJsAndLibrary(project_path, options.link, safe_activity_name); + exports.copyJsAndLibrary(project_path, options.link, safe_activity_name, target_api); // Set up ther Android Studio paths var java_path = path.join(app_path, 'java'); diff --git a/bin/templates/cordova/lib/builders/ProjectBuilder.js b/bin/templates/cordova/lib/builders/ProjectBuilder.js index f4be4aeb..129a01bd 100644 --- a/bin/templates/cordova/lib/builders/ProjectBuilder.js +++ b/bin/templates/cordova/lib/builders/ProjectBuilder.js @@ -265,10 +265,11 @@ class ProjectBuilder { }).then(function () { return self.prepBuildFiles(); }).then(() => { + const config = this._getCordovaConfig(); // update/set the distributionUrl in the gradle-wrapper.properties const gradleWrapperPropertiesPath = path.join(self.root, 'gradle/wrapper/gradle-wrapper.properties'); const gradleWrapperProperties = createEditor(gradleWrapperPropertiesPath); - const distributionUrl = process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL || 'https://services.gradle.org/distributions/gradle-6.8.3-all.zip'; + const distributionUrl = process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL || `https://services.gradle.org/distributions/gradle-${config.GRADLE_VERSION}-all.zip`; gradleWrapperProperties.set('distributionUrl', distributionUrl); gradleWrapperProperties.save(); @@ -287,6 +288,14 @@ class ProjectBuilder { }); } + /** + * @private + * @returns The user defined configs + */ + _getCordovaConfig () { + return fs.readJSONSync(path.join(this.root, 'cdv-gradle-config.json')); + } + /* * Builds the project with gradle. * Returns a promise. diff --git a/bin/templates/cordova/lib/check_reqs.js b/bin/templates/cordova/lib/check_reqs.js index cc62b6da..3a9ee166 100644 --- a/bin/templates/cordova/lib/check_reqs.js +++ b/bin/templates/cordova/lib/check_reqs.js @@ -23,11 +23,10 @@ var fs = require('fs-extra'); const { forgivingWhichSync, isWindows, isDarwin } = require('./utils'); const java = require('./env/java'); var REPO_ROOT = path.join(__dirname, '..', '..', '..', '..'); -var PROJECT_ROOT = path.join(__dirname, '..', '..'); const { CordovaError, ConfigParser, events } = require('cordova-common'); var android_sdk = require('./android_sdk'); -const { createEditor } = require('properties-parser'); const semver = require('semver'); +const { SDK_VERSION } = require('./gradle-config-defaults'); const EXPECTED_JAVA_VERSION = '1.8.x'; @@ -36,44 +35,28 @@ const EXPECTED_JAVA_VERSION = '1.8.x'; Object.assign(module.exports, { isWindows, isDarwin }); /** - * @description Get valid target from framework/project.properties if run from this repo - * Otherwise get target from project.properties file within a generated cordova-android project * @returns {string} The android target in format "android-${target}" */ module.exports.get_target = function () { - const projectPropertiesPaths = [ - path.join(REPO_ROOT, 'framework', 'project.properties'), - path.join(PROJECT_ROOT, 'project.properties') - ]; + const userTargetSdkVersion = getUserTargetSdkVersion(); - // Get the minimum required target API from the framework. - let target = projectPropertiesPaths.filter(filePath => fs.existsSync(filePath)) - .map(filePath => createEditor(filePath).get('target')) - .pop(); - - if (!target) { - throw new Error(`We could not locate the target from the "project.properties" at either "${projectPropertiesPaths.join('", "')}".`); + if (userTargetSdkVersion && userTargetSdkVersion < SDK_VERSION) { + events.emit('warn', `android-targetSdkVersion should be greater than or equal to ${SDK_VERSION}.`); } + return `android-${Math.max(userTargetSdkVersion, SDK_VERSION)}`; +}; + +/** @returns {number} target sdk or 0 if undefined */ +function getUserTargetSdkVersion () { // If the repo config.xml file exists, find the desired targetSdkVersion. const configFile = path.join(REPO_ROOT, 'config.xml'); - if (!fs.existsSync(configFile)) return target; + if (!fs.existsSync(configFile)) return 0; const configParser = new ConfigParser(configFile); - const desiredAPI = parseInt(configParser.getPreference('android-targetSdkVersion', 'android'), 10); - - if (!isNaN(desiredAPI)) { - const minimumAPI = parseInt(target.split('-').pop(), 10); - - if (desiredAPI >= minimumAPI) { - target = `android-${desiredAPI}`; - } else { - events.emit('warn', `android-targetSdkVersion should be greater than or equal to ${minimumAPI}.`); - } - } - - return target; -}; + const targetSdkVersion = parseInt(configParser.getPreference('android-targetSdkVersion', 'android'), 10); + return isNaN(targetSdkVersion) ? 0 : targetSdkVersion; +} module.exports.get_gradle_wrapper = function () { var androidStudioPath; diff --git a/bin/templates/cordova/lib/gradle-config-defaults.js b/bin/templates/cordova/lib/gradle-config-defaults.js new file mode 100644 index 00000000..be3be3a4 --- /dev/null +++ b/bin/templates/cordova/lib/gradle-config-defaults.js @@ -0,0 +1,30 @@ +/*! + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +const ABS_MODULE_PATH = '/framework/cdv-gradle-config-defaults.json'; + +try { + // Try relative require first, … + const REPO_ROOT = '../../../..'; + module.exports = require(REPO_ROOT + ABS_MODULE_PATH); +} catch (error) { + // … then fall back to installed-package require + if (error.code !== 'MODULE_NOT_FOUND') throw error; + module.exports = require('cordova-android' + ABS_MODULE_PATH); +} diff --git a/bin/templates/cordova/lib/plugin-build.gradle b/bin/templates/cordova/lib/plugin-build.gradle index 0cda9f0a..985f1d6e 100644 --- a/bin/templates/cordova/lib/plugin-build.gradle +++ b/bin/templates/cordova/lib/plugin-build.gradle @@ -42,8 +42,8 @@ dependencies { } android { - compileSdkVersion cdvCompileSdkVersion - buildToolsVersion cdvBuildToolsVersion + compileSdkVersion cordovaConfig.SDK_VERSION + buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION compileOptions { sourceCompatibility JavaVersion.VERSION_1_6 diff --git a/bin/templates/cordova/lib/prepare.js b/bin/templates/cordova/lib/prepare.js index b01ce9a3..9f4b4cfc 100644 --- a/bin/templates/cordova/lib/prepare.js +++ b/bin/templates/cordova/lib/prepare.js @@ -32,6 +32,7 @@ var PlatformJson = require('cordova-common').PlatformJson; var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger; var PluginInfoProvider = require('cordova-common').PluginInfoProvider; const utils = require('./utils'); +const gradleConfigDefaults = require('./gradle-config-defaults'); const GradlePropertiesParser = require('./config/GradlePropertiesParser'); @@ -55,29 +56,11 @@ module.exports.prepare = function (cordovaProject, options) { this._config = updateConfigFilesFrom(cordovaProject.projectConfig, munger, this.locations); - // Get the min SDK version from config.xml - const minSdkVersion = this._config.getPreference('android-minSdkVersion', 'android'); - const maxSdkVersion = this._config.getPreference('android-maxSdkVersion', 'android'); - const targetSdkVersion = this._config.getPreference('android-targetSdkVersion', 'android'); - const isGradlePluginKotlinEnabled = this._config.getPreference('GradlePluginKotlinEnabled', 'android'); - const gradlePluginKotlinCodeStyle = this._config.getPreference('GradlePluginKotlinCodeStyle', 'android'); - const androidXAppCompatVersion = this._config.getPreference('AndroidXAppCompatVersion', 'android'); + // Update Gradle cdv-gradle-config.json + updateUserProjectGradleConfig(this); - const gradlePropertiesUserConfig = {}; - if (minSdkVersion) gradlePropertiesUserConfig.cdvMinSdkVersion = minSdkVersion; - if (maxSdkVersion) gradlePropertiesUserConfig.cdvMaxSdkVersion = maxSdkVersion; - if (targetSdkVersion) gradlePropertiesUserConfig.cdvTargetSdkVersion = targetSdkVersion; - if (args.jvmargs) gradlePropertiesUserConfig['org.gradle.jvmargs'] = args.jvmargs; - if (isGradlePluginKotlinEnabled) { - gradlePropertiesUserConfig['kotlin.code.style'] = gradlePluginKotlinCodeStyle || 'official'; - } - - if (androidXAppCompatVersion) { - gradlePropertiesUserConfig.cdvAndroidXAppCompatVersion = androidXAppCompatVersion; - } - - const gradlePropertiesParser = new GradlePropertiesParser(this.locations.root); - gradlePropertiesParser.configure(gradlePropertiesUserConfig); + // Update Project's Gradle Properties + updateUserProjectGradlePropertiesConfig(this, args); // Update own www dir with project's www assets and plugins' assets and js-files return Promise.resolve(updateWww(cordovaProject, this.locations)).then(function () { @@ -92,6 +75,76 @@ module.exports.prepare = function (cordovaProject, options) { }); }; +/** @param {PlatformApi} project */ +function updateUserProjectGradleConfig (project) { + // Generate project gradle config + const projectGradleConfig = { + ...gradleConfigDefaults, + ...getUserGradleConfig(project._config) + }; + + // Write out changes + const projectGradleConfigPath = path.join(project.root, 'cdv-gradle-config.json'); + fs.writeJSONSync(projectGradleConfigPath, projectGradleConfig, { spaces: 2 }); +} + +function getUserGradleConfig (configXml) { + const configXmlToGradleMapping = [ + { xmlKey: 'android-minSdkVersion', gradleKey: 'MIN_SDK_VERSION', type: Number }, + { xmlKey: 'android-maxSdkVersion', gradleKey: 'MAX_SDK_VERSION', type: Number }, + { xmlKey: 'android-targetSdkVersion', gradleKey: 'SDK_VERSION', type: Number }, + { xmlKey: 'android-buildToolsVersion', gradleKey: 'BUILD_TOOLS_VERSION', type: String }, + { xmlKey: 'GradleVersion', gradleKey: 'GRADLE_VERSION', type: String }, + { xmlKey: 'AndroidGradlePluginVersion', gradleKey: 'AGP_VERSION', type: String }, + { xmlKey: 'GradlePluginKotlinVersion', gradleKey: 'KOTLIN_VERSION', type: String }, + { xmlKey: 'AndroidXAppCompatVersion', gradleKey: 'ANDROIDX_APP_COMPAT_VERSION', type: String }, + { xmlKey: 'GradlePluginGoogleServicesVersion', gradleKey: 'GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION', type: String }, + { xmlKey: 'GradlePluginGoogleServicesEnabled', gradleKey: 'IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED', type: Boolean }, + { xmlKey: 'GradlePluginKotlinEnabled', gradleKey: 'IS_GRADLE_PLUGIN_KOTLIN_ENABLED', type: Boolean } + ]; + + return configXmlToGradleMapping.reduce((config, mapping) => { + const rawValue = configXml.getPreference(mapping.xmlKey, 'android'); + + // ignore missing preferences (which occur as '') + if (rawValue) { + config[mapping.gradleKey] = parseStringAsType(rawValue, mapping.type); + } + + return config; + }, {}); +} + +/** Converts given string to given type */ +function parseStringAsType (value, type) { + switch (type) { + case String: + return String(value); + case Number: + return parseFloat(value); + case Boolean: + return value.toLowerCase() === 'true'; + default: + throw new CordovaError('Invalid type: ' + type); + } +} + +function updateUserProjectGradlePropertiesConfig (project, args) { + const gradlePropertiesUserConfig = {}; + + // Get the min SDK version from config.xml + if (args.jvmargs) gradlePropertiesUserConfig['org.gradle.jvmargs'] = args.jvmargs; + + const isGradlePluginKotlinEnabled = project._config.getPreference('GradlePluginKotlinEnabled', 'android'); + if (isGradlePluginKotlinEnabled) { + const gradlePluginKotlinCodeStyle = project._config.getPreference('GradlePluginKotlinCodeStyle', 'android'); + gradlePropertiesUserConfig['kotlin.code.style'] = gradlePluginKotlinCodeStyle || 'official'; + } + + const gradlePropertiesParser = new GradlePropertiesParser(project.root); + gradlePropertiesParser.configure(gradlePropertiesUserConfig); +} + module.exports.clean = function (options) { // A cordovaProject isn't passed into the clean() function, because it might have // been called from the platform shell script rather than the CLI. Check for the diff --git a/bin/templates/project/app/build.gradle b/bin/templates/project/app/build.gradle index 580a0af1..8854fead 100644 --- a/bin/templates/project/app/build.gradle +++ b/bin/templates/project/app/build.gradle @@ -19,7 +19,7 @@ apply plugin: 'com.android.application' -if (cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoolean()) { +if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) { apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' } @@ -27,50 +27,31 @@ if (cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoole buildscript { apply from: '../CordovaLib/cordova.gradle' - if(cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoolean()) { - String defaultGradlePluginKotlinVersion = kotlin_version - - /** - * Fetches the user's defined Kotlin Version from config.xml. - * If the version is not set or invalid, it will default to the ${defaultGradlePluginKotlinVersion} - */ - String gradlePluginKotlinVersion = cdvHelpers.getConfigPreference('GradlePluginKotlinVersion', defaultGradlePluginKotlinVersion) - if(!cdvHelpers.isVersionValid(gradlePluginKotlinVersion)) { - println("The defined Kotlin version (${gradlePluginKotlinVersion}) does not appear to be a valid version. Falling back to version: ${defaultGradlePluginKotlinVersion}.") - gradlePluginKotlinVersion = defaultGradlePluginKotlinVersion + // Checks if the kotlin version format is valid. + if(cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) { + if(!cdvHelpers.isVersionValid(cordovaConfig.KOTLIN_VERSION)) { + throw new GradleException("The defined Kotlin version (${cordovaConfig.KOTLIN_VERSION}) does not appear to be a valid version.") } - - // Change the version to be used. - ext.kotlin_version = gradlePluginKotlinVersion } apply from: 'repositories.gradle' repositories repos dependencies { - apply from: '../CordovaLib/cordova.gradle' + classpath "com.android.tools.build:gradle:${cordovaConfig.AGP_VERSION}" - classpath 'com.android.tools.build:gradle:4.2.1' - - if (cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoolean()) { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${cordovaConfig.KOTLIN_VERSION}" } - if(cdvHelpers.getConfigPreference('GradlePluginGoogleServicesEnabled', 'false').toBoolean()) { - String defaultGradlePluginGoogleServicesVersion = '4.3.5' - - /** - * Fetches the user's defined Google Services Plugin Version from config.xml. - * If the version is not set or invalid, it will default to the ${defaultGradlePluginGoogleServicesVersion} - */ - String gradlePluginGoogleServicesVersion = cdvHelpers.getConfigPreference('GradlePluginGoogleServicesVersion', defaultGradlePluginGoogleServicesVersion) + if(cordovaConfig.IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED) { + // Checks if the kotlin version format is valid. if(!cdvHelpers.isVersionValid(gradlePluginGoogleServicesVersion)) { - println("The defined Google Services plugin version (${gradlePluginGoogleServicesVersion}) does not appear to be a valid version. Falling back to version: ${defaultGradlePluginGoogleServicesVersion}.") - gradlePluginGoogleServicesVersion = defaultGradlePluginGoogleServicesVersion + throw new GradleException("The defined Google Services plugin version (${cordovaConfig.GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION}) does not appear to be a valid version.") } // Create the Google Services classpath and set it. - String gradlePluginGoogleServicesClassPath = "com.google.gms:google-services:${gradlePluginGoogleServicesVersion}" + String gradlePluginGoogleServicesClassPath = "com.google.gms:google-services:${cordovaConfig.GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION}" println "Adding classpath: ${gradlePluginGoogleServicesClassPath}" classpath gradlePluginGoogleServicesClassPath } @@ -84,7 +65,7 @@ allprojects { } task wrapper(type: Wrapper) { - gradleVersion = '6.8.3' + gradleVersion = cordovaConfig.GRADLE_VERSION } // Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties. @@ -92,30 +73,10 @@ task wrapper(type: Wrapper) { ext { apply from: '../CordovaLib/cordova.gradle' - // The value for android.compileSdkVersion. - if (!project.hasProperty('cdvCompileSdkVersion')) { - cdvCompileSdkVersion = null; - } - // The value for android.buildToolsVersion. - if (!project.hasProperty('cdvBuildToolsVersion')) { - cdvBuildToolsVersion = null; - } // Sets the versionCode to the given value. if (!project.hasProperty('cdvVersionCode')) { cdvVersionCode = null } - // Sets the minSdkVersion to the given value. - if (!project.hasProperty('cdvMinSdkVersion')) { - cdvMinSdkVersion = null - } - // Sets the maxSdkVersion to the given value. - if (!project.hasProperty('cdvMaxSdkVersion')) { - cdvMaxSdkVersion = null - } - // The value for android.targetSdkVersion. - if (!project.hasProperty('cdvTargetSdkVersion')) { - cdvTargetSdkVersion = null; - } // Whether to build architecture-specific APKs. if (!project.hasProperty('cdvBuildMultipleApks')) { cdvBuildMultipleApks = null @@ -137,11 +98,6 @@ ext { cdvBuildArch = null } - // Sets the default cdvAndroidXAppCompatVersion to the given value. - if (!project.hasProperty('cdvAndroidXAppCompatVersion')) { - cdvAndroidXAppCompatVersion = null - } - // Plugin gradle extensions can append to this to have code run at the end. cdvPluginPostBuildExtras = [] } @@ -159,17 +115,10 @@ if (hasBuildExtras2) { apply from: '../build-extras.gradle' } -// Set property defaults after extension .gradle files. -ext.cdvCompileSdkVersion = cdvCompileSdkVersion == null ? ( - defaultCompileSdkVersion == null - ? privateHelpers.getProjectTarget() - : defaultCompileSdkVersion -) : Integer.parseInt('' + cdvCompileSdkVersion); +// Apply updates that might come from build-extra. +privateHelpers.applyCordovaConfigCustomization() -if (ext.cdvBuildToolsVersion == null) { - ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools() - //ext.cdvBuildToolsVersion = project.ext.defaultBuildToolsVersion -} +// Set property defaults after extension .gradle files. if (ext.cdvDebugSigningPropertiesFile == null && file('../debug-signing.properties').exists()) { ext.cdvDebugSigningPropertiesFile = '../debug-signing.properties' } @@ -180,18 +129,8 @@ if (ext.cdvReleaseSigningPropertiesFile == null && file('../release-signing.prop // Cast to appropriate types. ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean(); ext.cdvVersionCodeForceAbiDigit = cdvVersionCodeForceAbiDigit == null ? false : cdvVersionCodeForceAbiDigit.toBoolean(); - -// minSdkVersion, maxSdkVersion and targetSdkVersion -ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? defaultMinSdkVersion : Integer.parseInt('' + cdvMinSdkVersion) -if (cdvMaxSdkVersion != null) { - ext.cdvMaxSdkVersion = Integer.parseInt('' + cdvMaxSdkVersion) -} -ext.cdvTargetSdkVersion = cdvTargetSdkVersion == null ? defaultTargetSdkVersion : Integer.parseInt('' + cdvTargetSdkVersion) - ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode) -ext.cdvAndroidXAppCompatVersion = cdvAndroidXAppCompatVersion == null ? defaultAndroidXAppCompatVersion : cdvAndroidXAppCompatVersion - def computeBuildTargetName(debugBuild) { def ret = 'assemble' if (cdvBuildMultipleApks && cdvBuildArch) { @@ -214,13 +153,12 @@ cdvBuildRelease.dependsOn { task cdvPrintProps { doLast { - println('cdvCompileSdkVersion=' + cdvCompileSdkVersion) println('cdvBuildToolsVersion=' + cdvBuildToolsVersion) println('cdvVersionCode=' + cdvVersionCode) println('cdvVersionCodeForceAbiDigit=' + cdvVersionCodeForceAbiDigit) + println('cdvSdkVersion=' + cdvSdkVersion) println('cdvMinSdkVersion=' + cdvMinSdkVersion) println('cdvMaxSdkVersion=' + cdvMaxSdkVersion) - println('cdvTargetSdkVersion=' + cdvTargetSdkVersion) println('cdvBuildMultipleApks=' + cdvBuildMultipleApks) println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile) println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile) @@ -238,25 +176,19 @@ android { versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode")) applicationId privateHelpers.extractStringFromManifest("package") - if (cdvMinSdkVersion != null) { - minSdkVersion cdvMinSdkVersion - } - - if (cdvMaxSdkVersion != null) { - maxSdkVersion cdvMaxSdkVersion - } - - if(cdvTargetSdkVersion != null) { - targetSdkVersion cdvTargetSdkVersion + minSdkVersion cordovaConfig.MIN_SDK_VERSION + if (cordovaConfig.MAX_SDK_VERSION != null) { + maxSdkVersion cordovaConfig.MAX_SDK_VERSION } + targetSdkVersion cordovaConfig.SDK_VERSION + compileSdkVersion cordovaConfig.SDK_VERSION } lintOptions { - abortOnError false; + abortOnError false } - compileSdkVersion cdvCompileSdkVersion - buildToolsVersion cdvBuildToolsVersion + buildToolsVersion cordovaConfig.LATEST_INSTALLED_BUILD_TOOLS // This code exists for Crosswalk and other Native APIs. // By default, we multiply the existing version code in the @@ -347,10 +279,10 @@ android { dependencies { implementation fileTree(dir: 'libs', include: '*.jar') - implementation "androidx.appcompat:appcompat:$cdvAndroidXAppCompatVersion" + implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}" - if (cdvHelpers.getConfigPreference('GradlePluginKotlinEnabled', 'false').toBoolean()) { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${cordovaConfig.KOTLIN_VERSION}" } // SUB-PROJECT DEPENDENCIES START @@ -379,7 +311,7 @@ def addSigningProps(propsFilePath, signingConfig) { signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword)) def storeType = props.get('storeType', props.get('key.store.type', '')) if (!storeType) { - def filename = storeFile.getName().toLowerCase(); + def filename = storeFile.getName().toLowerCase() if (filename.endsWith('.p12') || filename.endsWith('.pfx')) { storeType = 'pkcs12' } else { @@ -399,6 +331,6 @@ if (hasProperty('postBuildExtras')) { postBuildExtras() } -if (cdvHelpers.getConfigPreference('GradlePluginGoogleServicesEnabled', 'false').toBoolean()) { +if (cordovaConfig.IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED) { apply plugin: 'com.google.gms.google-services' } diff --git a/bin/templates/project/build.gradle b/bin/templates/project/build.gradle index 62a82e82..1e01f06e 100644 --- a/bin/templates/project/build.gradle +++ b/bin/templates/project/build.gradle @@ -17,14 +17,13 @@ */ // Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { - ext.kotlin_version = '1.4.32' + apply from: 'CordovaLib/cordova.gradle' apply from: 'repositories.gradle' repositories repos dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "com.android.tools.build:gradle:${cordovaConfig.AGP_VERSION}" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${cordovaConfig.KOTLIN_VERSION}" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -33,15 +32,6 @@ buildscript { allprojects { apply from: 'repositories.gradle' repositories repos - - //This replaces project.properties w.r.t. build settings - project.ext { - defaultBuildToolsVersion="30.0.3" //String - defaultMinSdkVersion=22 //Integer - Minimum requirement is Android 5.1 - defaultTargetSdkVersion=30 //Integer - We ALWAYS target the latest by default - defaultCompileSdkVersion=30 //Integer - We ALWAYS compile with the latest by default - defaultAndroidXAppCompatVersion="1.2.0" //String - We ALWAYS compile with the latest stable by default - } } task clean(type: Delete) { diff --git a/framework/build.gradle b/framework/build.gradle index 8296fca9..55dd4e0f 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -18,30 +18,17 @@ ext { apply from: 'cordova.gradle' - cdvCompileSdkVersion = privateHelpers.getProjectTarget() - cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools() - if (project.hasProperty('cdvMinSdkVersion') && cdvMinSdkVersion.isInteger()) { - cdvMinSdkVersion = cdvMinSdkVersion as int - println '[Cordova] cdvMinSdkVersion is overridden, try it at your own risk.' - } else { - cdvMinSdkVersion = 22; // current Cordova's default - } - - if (project.hasProperty('cdvAndroidXAppCompatVersion')) { - cdvAndroidXAppCompatVersion = cdvAndroidXAppCompatVersion - println '[Cordova] cdvAndroidXAppCompatVersion is overridden, try it at your own risk.' - } else { - cdvAndroidXAppCompatVersion = "1.2.0"; // current Cordova's default - } } buildscript { + apply from: 'cordova.gradle' apply from: 'repositories.gradle' + repositories repos dependencies { // Android Gradle Plugin (AGP) Build Tools - classpath 'com.android.tools.build:gradle:4.2.1' + classpath "com.android.tools.build:gradle:${cordovaConfig.AGP_VERSION}" // @todo remove this abandoned plugin. maven-publish-plugin is now supported by Android Gradle plugin 3.6.0 and higher classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' @@ -62,8 +49,8 @@ group = 'org.apache.cordova' version = '10.0.0-dev' android { - compileSdkVersion cdvCompileSdkVersion - buildToolsVersion cdvBuildToolsVersion + compileSdkVersion cordovaConfig.SDK_VERSION + buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -72,7 +59,7 @@ android { // For the Android Cordova Lib, we allow changing the minSdkVersion, but it is at the users own risk defaultConfig { - minSdkVersion cdvMinSdkVersion + minSdkVersion cordovaConfig.MIN_SDK_VERSION } sourceSets { @@ -132,7 +119,7 @@ task sourcesJar(type: Jar) { } dependencies { - implementation "androidx.appcompat:appcompat:$cdvAndroidXAppCompatVersion" + implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}" } artifacts { diff --git a/framework/cdv-gradle-config-defaults.json b/framework/cdv-gradle-config-defaults.json new file mode 100644 index 00000000..daf912a4 --- /dev/null +++ b/framework/cdv-gradle-config-defaults.json @@ -0,0 +1,12 @@ +{ + "MIN_SDK_VERSION": 22, + "SDK_VERSION": 30, + "GRADLE_VERSION": "6.8.3", + "BUILD_TOOLS_VERSION": "30.0.3", + "AGP_VERSION": "4.2.1", + "KOTLIN_VERSION": "1.4.32", + "ANDROIDX_APP_COMPAT_VERSION": "1.2.0", + "GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.5", + "IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false, + "IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false +} diff --git a/framework/cordova.gradle b/framework/cordova.gradle index 19d4be56..915357e1 100644 --- a/framework/cordova.gradle +++ b/framework/cordova.gradle @@ -18,7 +18,6 @@ */ import java.util.regex.Pattern -import groovy.swing.SwingBuilder import io.github.g00fy2.versioncompare.Version String doEnsureValueExists(filePath, props, key) { @@ -151,12 +150,57 @@ def doGetConfigPreference(name, defaultValue) { return ret } +def doApplyCordovaConfigCustomization() { + // Apply user overide properties that comes from the "--gradleArg=-P" parameters + if (project.hasProperty('cdvMinSdkVersion')) { + cordovaConfig.MIN_SDK_VERSION = Integer.parseInt('' + cdvMinSdkVersion) + } + if (project.hasProperty('cdvSdkVersion')) { + cordovaConfig.SDK_VERSION = Integer.parseInt('' + cdvSdkVersion) + } + if (project.hasProperty('cdvMaxSdkVersion')) { + cordovaConfig.MAX_SDK_VERSION = Integer.parseInt('' + cdvMaxSdkVersion) + } + if (project.hasProperty('cdvBuildToolsVersion')) { + cordovaConfig.BUILD_TOOLS_VERSION = cdvBuildToolsVersion + } + if (project.hasProperty('cdvAndroidXAppCompatVersion')) { + cordovaConfig.ANDROIDX_APP_COMPAT_VERSION = cdvAndroidXAppCompatVersion + } + + // Ensure the latest installed build tools is selected, with or without defined override + cordovaConfig.LATEST_INSTALLED_BUILD_TOOLS = doFindLatestInstalledBuildTools( + cordovaConfig.BUILD_TOOLS_VERSION + ) +} + // Properties exported here are visible to all plugins. ext { + def defaultsFilePath = './cdv-gradle-config-defaults.json' + def projectConfigFilePath = "$rootDir/cdv-gradle-config.json" + def targetConfigFilePath = null + + /** + * Check if the project config file path exists. This file will exist if coming from CLI project. + * If this file does not exist, falls back onto the default file. + * This scenario can occur if building the framework's AAR package for publishing. + */ + if(file(projectConfigFilePath).exists()) { + targetConfigFilePath = projectConfigFilePath + } else { + targetConfigFilePath = defaultsFilePath + } + + def jsonFile = new File(targetConfigFilePath) + cordovaConfig = new groovy.json.JsonSlurper().parseText(jsonFile.text) + + // Apply Gradle Properties + doApplyCordovaConfigCustomization() + // These helpers are shared, but are not guaranteed to be stable / unchanged. privateHelpers = {} privateHelpers.getProjectTarget = { doGetProjectTarget() } - privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') } + privateHelpers.applyCordovaConfigCustomization = { doApplyCordovaConfigCustomization() } privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) } privateHelpers.extractStringFromManifest = { name -> doExtractStringFromManifest(name) } privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) } diff --git a/framework/default.properties b/framework/default.properties deleted file mode 100644 index d4e24dcb..00000000 --- a/framework/default.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "build.properties", and override values to adapt the script to your -# project structure. - -# Indicates whether an apk should be generated for each density. -split.density=false -# Project target. -target=android-14 -apk-configurations= diff --git a/framework/gradle/wrapper/gradle-wrapper.properties b/framework/gradle/wrapper/gradle-wrapper.properties index d4c7ae16..46d87b85 100644 --- a/framework/gradle/wrapper/gradle-wrapper.properties +++ b/framework/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,3 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/framework/project.properties b/framework/project.properties index 52d09c30..85327550 100644 --- a/framework/project.properties +++ b/framework/project.properties @@ -1,3 +1,5 @@ +# GENERATED FILE! DO NOT EDIT! + # This file was originally created by the Android Tools, but is now # used by cordova-android to manage the project configuration. @@ -5,7 +7,6 @@ split.density=false # Project target. -target=android-30 apk-configurations= renderscript.opt.level=O0 android.library=true diff --git a/spec/e2e/plugin.spec.js b/spec/e2e/plugin.spec.js index 88e74be7..7debf887 100644 --- a/spec/e2e/plugin.spec.js +++ b/spec/e2e/plugin.spec.js @@ -46,6 +46,13 @@ describe('plugin add', function () { return Promise.resolve() .then(() => execa(createBin, [projectPath, projectid, projectname])) .then(() => { + // Allow test project to find the `cordova-android` module + fs.ensureSymlinkSync( + path.join(__dirname, '../..'), + path.join(projectPath, 'node_modules/cordova-android'), + 'junction' + ); + const Api = require(path.join(projectPath, 'cordova/Api.js')); return new Api('android', projectPath).addPlugin(pluginInfo); }); diff --git a/spec/unit/check_reqs.spec.js b/spec/unit/check_reqs.spec.js index 65472000..dba00178 100644 --- a/spec/unit/check_reqs.spec.js +++ b/spec/unit/check_reqs.spec.js @@ -281,30 +281,15 @@ describe('check_reqs', function () { check_reqs.__set__('ConfigParser', ConfigParser); }); - it('should retrieve target from framework project.properties file', function () { + it('should retrieve DEFAULT_TARGET_API', function () { var target = check_reqs.get_target(); expect(target).toBeDefined(); expect(target).toContain('android-' + DEFAULT_TARGET_API); }); - it('should throw error if target cannot be found', function () { - spyOn(fs, 'existsSync').and.returnValue(false); - expect(function () { - check_reqs.get_target(); - }).toThrow(); - }); - it('should override target from config.xml preference', () => { - var realExistsSync = fs.existsSync; - spyOn(fs, 'existsSync').and.callFake(function (path) { - if (path.indexOf('config.xml') > -1) { - return true; - } else { - return realExistsSync.call(fs, path); - } - }); - - getPreferenceSpy.and.returnValue(DEFAULT_TARGET_API + 1); + spyOn(fs, 'existsSync').and.returnValue(true); + getPreferenceSpy.and.returnValue(String(DEFAULT_TARGET_API + 1)); var target = check_reqs.get_target(); @@ -313,16 +298,8 @@ describe('check_reqs', function () { }); it('should fallback to default target if config.xml has invalid preference', () => { - var realExistsSync = fs.existsSync; - spyOn(fs, 'existsSync').and.callFake(function (path) { - if (path.indexOf('config.xml') > -1) { - return true; - } else { - return realExistsSync.call(fs, path); - } - }); - - getPreferenceSpy.and.returnValue(NaN); + spyOn(fs, 'existsSync').and.returnValue(true); + getPreferenceSpy.and.returnValue('android-99'); var target = check_reqs.get_target(); @@ -331,18 +308,11 @@ describe('check_reqs', function () { }); it('should warn if target sdk preference is lower than the minimum required target SDK', () => { - var realExistsSync = fs.existsSync; - spyOn(fs, 'existsSync').and.callFake(function (path) { - if (path.indexOf('config.xml') > -1) { - return true; - } else { - return realExistsSync.call(fs, path); - } - }); + spyOn(fs, 'existsSync').and.returnValue(true); spyOn(events, 'emit'); - getPreferenceSpy.and.returnValue(DEFAULT_TARGET_API - 1); + getPreferenceSpy.and.returnValue(String(DEFAULT_TARGET_API - 1)); var target = check_reqs.get_target(); diff --git a/spec/unit/prepare.spec.js b/spec/unit/prepare.spec.js index a16130ac..3b197089 100644 --- a/spec/unit/prepare.spec.js +++ b/spec/unit/prepare.spec.js @@ -809,6 +809,7 @@ describe('prepare', () => { prepare.__set__('events', { emit: jasmine.createSpy('emit') }); + prepare.__set__('updateUserProjectGradleConfig', jasmine.createSpy()); prepare.__set__('updateWww', jasmine.createSpy()); prepare.__set__('updateProjectAccordingTo', jasmine.createSpy('updateProjectAccordingTo') .and.returnValue(Promise.resolve())); diff --git a/test/androidx/app/build.gradle b/test/androidx/app/build.gradle index e6921dcf..747cecb1 100644 --- a/test/androidx/app/build.gradle +++ b/test/androidx/app/build.gradle @@ -18,14 +18,16 @@ apply plugin: 'com.android.application' +apply from: '../../../framework/cordova.gradle' + android { - compileSdkVersion 30 - buildToolsVersion "30.0.3" + compileSdkVersion cordovaConfig.SDK_VERSION + buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION defaultConfig { applicationId "org.apache.cordova.unittests" - minSdkVersion 22 - targetSdkVersion 30 + minSdkVersion cordovaConfig.MIN_SDK_VERSION + targetSdkVersion cordovaConfig.SDK_VERSION versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -42,7 +44,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(path: ':CordovaLib') - implementation 'androidx.appcompat:appcompat:1.0.2' + implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}" testImplementation 'org.json:json:20140107' testImplementation 'junit:junit:4.12' diff --git a/test/androidx/build.gradle b/test/androidx/build.gradle index 160d3522..24ef1dca 100644 --- a/test/androidx/build.gradle +++ b/test/androidx/build.gradle @@ -19,6 +19,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + apply from: '../../framework/cordova.gradle' + repositories { google() mavenCentral() @@ -28,7 +30,7 @@ buildscript { // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - classpath 'com.android.tools.build:gradle:4.2.1' + classpath "com.android.tools.build:gradle:${cordovaConfig.AGP_VERSION}" } } diff --git a/test/androidx/wrapper.gradle b/test/androidx/wrapper.gradle index 80394fa7..6cef6051 100644 --- a/test/androidx/wrapper.gradle +++ b/test/androidx/wrapper.gradle @@ -17,5 +17,6 @@ */ wrapper { - gradleVersion = '6.8.3' + apply from: '../../framework/cordova.gradle' + gradleVersion = cordovaConfig.GRADLE_VERSION } diff --git a/test/run_java_unit_tests.js b/test/run_java_unit_tests.js index 752dbca2..acab1e62 100644 --- a/test/run_java_unit_tests.js +++ b/test/run_java_unit_tests.js @@ -21,6 +21,7 @@ const path = require('path'); const execa = require('execa'); +const fs = require('fs-extra'); const ProjectBuilder = require('../bin/templates/cordova/lib/builders/ProjectBuilder'); class AndroidTestRunner { @@ -48,6 +49,9 @@ class AndroidTestRunner { run () { return Promise.resolve() .then(_ => console.log(`[${this.testTitle}] Preparing Gradle wrapper for Java unit tests.`)) + .then(_ => { + fs.copyFileSync(path.resolve(this.projectDir, '../../framework/cdv-gradle-config-defaults.json'), path.resolve(this.projectDir, 'cdv-gradle-config.json')); + }) .then(_ => this._createProjectBuilder()) .then(_ => this._gradlew('--version')) .then(_ => console.log(`[${this.testTitle}] Gradle wrapper is ready. Running tests now.`))