feat!: API 34 Support (#1678)

* feat!: Upgrade to Gradle and AGP 8

* java 17

* feat!: API 34 Support

API 34: Upgrade AGP from 8.2.0-rc01 to 8.2.0-rc02

API 34: Upgrade AGP from 8.2.0-rc02 to 8.2.0-rc03

API 34: Upgrade AGP from 8.2.0-rc03 to 8.2.0

feat: add AndroidKotlinJVMTarget preference to set the kotlin JVM target
This is in addition to the java source / target compatibility preferences.
AndroidKotlinJVMTarget is only affective if Kotlin is enabled.

chore: Upgrade Gradle from 8.4 to 8.5

AGP 8.2.0 -> 8.2.1

Gradle 8.5 -> 8.7

fix: Add --validate-url to gradle wrapper commands

AGP 8.4.0

* fix(test): ProjectBuilder using Gradle 8.3, no longer supported version

* API 34: Change Kotlin JVM Target default.

The new default value is null. When null, it will by default
to the Java Target compatibility. Updating AndroidJavaTargetCompatibility
will also influence the Kotlin JVM target, unless if AndroidKotlinJVMTarget
is also explicitly defined.

* removed leftover debug prints

* API 34: Gradle Wrapper

* API 34: ratignore generated gradle wrapper files

* fix gradle wrapper jar via git attributes

* fix(test): normalise gradle paths

* fix(windows): Gradle paths

* fix(windows): Keep CRLF endings for bat files

* chore: Updated license for Gradle Wrapper 8.7 pointer

* API 34 Support Gradle Tools project

* API 34: omit --validate-url on installing the wrapper

* revert: LICENSE notice on bundling the gradle wrapper jar

* Revert: AGP 8.4 -> 8.3

* test(ci): Added NodeJS 22 to the test matrix

---------

Co-authored-by: jcesarmobile <jcesarmobile@gmail.com>
This commit is contained in:
Norman Breau 2024-05-13 10:28:57 -03:00 committed by GitHub
parent ed8e5d2f0a
commit 89a0a72da5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 155 additions and 131 deletions

3
.gitattributes vendored
View File

@ -23,7 +23,7 @@
*.scm text
*.sql text
*.sh text
*.bat text
*.bat text eol=crlf
# templates
*.ejs text
@ -92,3 +92,4 @@ AUTHORS text
*.woff binary
*.pyc binary
*.pdf binary
*.jar binary

View File

@ -27,7 +27,7 @@ jobs:
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [16.x, 18.x, 20.x, 22.x]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
@ -39,7 +39,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
java-version: '17'
- name: Environment Information
run: |

6
.gitignore vendored
View File

@ -29,12 +29,8 @@ example
**/assets/www/cordova.js
/test/.externalNativeBuild
/test/androidx/gradle
/test/androidx/gradlew
/test/androidx/gradlew.bat
/test/androidx/cdv-gradle-config.json
/test/androidx/tools
/test/assets/www/.tmp*
/test/assets/www/cordova.js
/test/bin

View File

@ -8,3 +8,6 @@ intermediates
reports
test-results
node_modules
gradle
gradlew
gradlew.bat

View File

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015-2020 Apache Cordova
Copyright 2015-2024 Apache Cordova
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -200,3 +200,4 @@
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.

View File

@ -50,8 +50,8 @@ android {
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_SOURCE_COMPATIBILITY)
targetCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY)
}
// For the Android Cordova Lib, we allow changing the minSdkVersion, but it is at the users own risk

View File

@ -1,10 +1,10 @@
{
"MIN_SDK_VERSION": 24,
"SDK_VERSION": 33,
"SDK_VERSION": 34,
"COMPILE_SDK_VERSION": null,
"GRADLE_VERSION": "7.6",
"MIN_BUILD_TOOLS_VERSION": "33.0.2",
"AGP_VERSION": "7.4.2",
"GRADLE_VERSION": "8.7",
"MIN_BUILD_TOOLS_VERSION": "34.0.0",
"AGP_VERSION": "8.3.0",
"KOTLIN_VERSION": "1.7.21",
"ANDROIDX_APP_COMPAT_VERSION": "1.6.1",
"ANDROIDX_WEBKIT_VERSION": "1.6.0",
@ -12,5 +12,8 @@
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.15",
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false,
"PACKAGE_NAMESPACE": "io.cordova.helloCordova"
"PACKAGE_NAMESPACE": "io.cordova.helloCordova",
"JAVA_SOURCE_COMPATIBILITY": 8,
"JAVA_TARGET_COMPATIBILITY": 8,
"KOTLIN_JVM_TARGET": null
}

View File

@ -25,7 +25,7 @@ const events = require('cordova-common').events;
const CordovaError = require('cordova-common').CordovaError;
const check_reqs = require('../check_reqs');
const PackageType = require('../PackageType');
const { compareByAll } = require('../utils');
const { compareByAll, isWindows } = require('../utils');
const { createEditor } = require('properties-parser');
const CordovaGradleConfigParserFactory = require('../config/CordovaGradleConfigParserFactory');
@ -85,9 +85,7 @@ class ProjectBuilder {
}
getArgs (cmd, opts) {
let args = [
'-b', path.join(this.root, 'build.gradle')
];
let args = [];
if (opts.extraArgs) {
args = args.concat(opts.extraArgs);
}
@ -116,16 +114,27 @@ class ProjectBuilder {
return args;
}
/*
* This returns a promise
getGradleWrapperPath () {
let wrapper = path.join(this.root, 'tools', 'gradlew');
if (isWindows()) {
wrapper += '.bat';
}
return wrapper;
}
/**
* Installs/updates the gradle wrapper
* @param {string} gradleVersion The gradle version to install. Ignored if CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL environment variable is defined
* @returns {Promise<void>}
*/
runGradleWrapper (gradle_cmd) {
const gradlePath = path.join(this.root, 'gradlew');
const wrapperGradle = path.join(this.root, 'wrapper.gradle');
if (fs.existsSync(gradlePath)) {
// Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
async installGradleWrapper (gradleVersion) {
if (process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL) {
events.emit('verbose', `Overriding Gradle Version via CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL (${process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL})`);
await execa('gradle', ['-p', path.join(this.root, 'tools'), 'wrapper', '--gradle-distribution-url', process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL], { stdio: 'inherit' });
} else {
return execa(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], { stdio: 'inherit' });
await execa('gradle', ['-p', path.join(this.root, 'tools'), 'wrapper', '--gradle-version', gradleVersion], { stdio: 'inherit' });
}
}
@ -275,23 +284,14 @@ class ProjectBuilder {
prepEnv (opts) {
const self = this;
const config = this._getCordovaConfig();
return check_reqs.check_gradle()
.then(function (gradlePath) {
return self.runGradleWrapper(gradlePath);
.then(function () {
events.emit('verbose', `Using Gradle: ${config.GRADLE_VERSION}`);
return self.installGradleWrapper(config.GRADLE_VERSION);
}).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-${config.GRADLE_VERSION}-all.zip`;
gradleWrapperProperties.set('distributionUrl', distributionUrl);
gradleWrapperProperties.save();
events.emit('verbose', `Gradle Distribution URL: ${distributionUrl}`);
})
.then(() => {
const signingPropertiesPath = path.join(self.root, `${opts.buildType}${SIGNING_PROPERTIES}`);
if (fs.existsSync(signingPropertiesPath)) fs.removeSync(signingPropertiesPath);
@ -317,7 +317,7 @@ class ProjectBuilder {
* Returns a promise.
*/
async build (opts) {
const wrapper = path.join(this.root, 'gradlew');
const wrapper = this.getGradleWrapperPath();
const args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
events.emit('verbose', `Running Gradle Build: ${wrapper} ${args.join(' ')}`);
@ -338,7 +338,7 @@ class ProjectBuilder {
}
clean (opts) {
const wrapper = path.join(this.root, 'gradlew');
const wrapper = this.getGradleWrapperPath();
const args = this.getArgs('clean', opts);
return execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) })
.then(() => {

View File

@ -113,20 +113,15 @@ function prepBuildFiles (projectPath) {
buildModule.getBuilder(projectPath).prepBuildFiles();
}
function copyBuildRules (projectPath, isLegacy) {
function copyBuildRules (projectPath) {
const srcDir = path.join(ROOT, 'templates', 'project');
if (isLegacy) {
// The project's build.gradle is identical to the earlier build.gradle, so it should still work
fs.copySync(path.join(srcDir, 'legacy', 'build.gradle'), path.join(projectPath, 'legacy', 'build.gradle'));
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
} else {
fs.copySync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle'));
fs.copySync(path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app', 'build.gradle'));
fs.copySync(path.join(srcDir, 'app', 'repositories.gradle'), path.join(projectPath, 'app', 'repositories.gradle'));
fs.copySync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
}
copyGradleTools(projectPath);
}
function copyScripts (projectPath) {
@ -176,6 +171,12 @@ function validateProjectName (project_name) {
return Promise.resolve();
}
function copyGradleTools (projectPath) {
const srcDir = path.join(ROOT, 'templates', 'project');
fs.copySync(path.resolve(srcDir, 'tools'), path.resolve(projectPath, 'tools'));
}
/**
* Creates an android application with the given options.
*

View File

@ -110,7 +110,10 @@ function getUserGradleConfig (configXml) {
{ xmlKey: 'AndroidXWebKitVersion', gradleKey: 'ANDROIDX_WEBKIT_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 }
{ xmlKey: 'GradlePluginKotlinEnabled', gradleKey: 'IS_GRADLE_PLUGIN_KOTLIN_ENABLED', type: Boolean },
{ xmlKey: 'AndroidJavaSourceCompatibility', gradleKey: 'JAVA_SOURCE_COMPATIBILITY', type: Number },
{ xmlKey: 'AndroidJavaTargetCompatibility', gradleKey: 'JAVA_TARGET_COMPATIBILITY', type: Number },
{ xmlKey: 'AndroidKotlinJVMTarget', gradleKey: 'KOTLIN_JVM_TARGET', type: String }
];
return configXmlToGradleMapping.reduce((config, mapping) => {

View File

@ -19,8 +19,7 @@
"unit-tests": "jasmine --config=spec/unit/jasmine.json",
"cover": "nyc jasmine --config=spec/coverage.json",
"e2e-tests": "jasmine --config=spec/e2e/jasmine.json",
"java-unit-tests": "node test/run_java_unit_tests.js",
"clean:java-unit-tests": "node test/clean.js"
"java-unit-tests": "node test/run_java_unit_tests.js"
},
"author": "Apache Software Foundation",
"license": "Apache-2.0",

View File

@ -20,6 +20,7 @@
const fs = require('fs-extra');
const path = require('path');
const rewire = require('rewire');
const { isWindows } = require('../../../lib/utils');
describe('ProjectBuilder', () => {
const rootDir = '/root';
@ -128,19 +129,24 @@ describe('ProjectBuilder', () => {
});
});
describe('runGradleWrapper', () => {
it('should run the provided gradle command if a gradle wrapper does not already exist', () => {
spyOn(fs, 'existsSync').and.returnValue(false);
builder.runGradleWrapper('/my/sweet/gradle');
expect(execaSpy).toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
describe('installGradleWrapper', () => {
beforeEach(() => {
execaSpy.and.resolveTo();
});
it('should do nothing if a gradle wrapper exists in the project directory', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
builder.runGradleWrapper('/my/sweet/gradle');
expect(execaSpy).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
it('should run gradle wrapper 8.7', async () => {
await builder.installGradleWrapper('8.7');
expect(execaSpy).toHaveBeenCalledWith('gradle', ['-p', path.normalize('/root/tools'), 'wrapper', '--gradle-version', '8.7'], jasmine.any(Object));
});
it('CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL should override gradle version', async () => {
process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL = 'https://dist.local';
await builder.installGradleWrapper('8.7');
delete process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL;
expect(execaSpy).toHaveBeenCalledWith('gradle', ['-p', path.normalize('/root/tools'), 'wrapper', '--gradle-distribution-url', 'https://dist.local'], jasmine.any(Object));
});
});
describe('build', () => {
beforeEach(() => {
spyOn(builder, 'getArgs');
@ -170,7 +176,12 @@ describe('ProjectBuilder', () => {
builder.build({});
expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), testArgs, jasmine.anything());
let gradle = path.join(rootDir, 'tools', 'gradlew');
if (isWindows()) {
gradle += '.bat';
}
expect(execaSpy).toHaveBeenCalledWith(gradle, testArgs, jasmine.anything());
});
it('should reject if the spawn fails', () => {
@ -227,8 +238,13 @@ describe('ProjectBuilder', () => {
const gradleArgs = ['test', 'args', '-f'];
builder.getArgs.and.returnValue(gradleArgs);
let gradle = path.join(rootDir, 'tools', 'gradlew');
if (isWindows()) {
gradle += '.bat';
}
return builder.clean(opts).then(() => {
expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), gradleArgs, jasmine.anything());
expect(execaSpy).toHaveBeenCalledWith(gradle, gradleArgs, jasmine.anything());
});
});

View File

@ -181,6 +181,10 @@ task cdvPrintProps {
android {
namespace cordovaConfig.PACKAGE_NAMESPACE
buildFeatures {
buildConfig true
}
defaultConfig {
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
applicationId cordovaConfig.PACKAGE_NAMESPACE
@ -248,8 +252,39 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_SOURCE_COMPATIBILITY)
targetCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY)
}
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) {
if (cordovaConfig.KOTLIN_JVM_TARGET == null) {
// If the value is null, fallback to JAVA_TARGET_COMPATIBILITY,
// as they generally should be equal
def javaTarget = JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY)
// check if javaTarget is <= 8; if so, we need to prefix it with "1."
// Starting with 9 and later, the value can be used as is.
if (javaTarget.compareTo(JavaLanguageVersion.of(8)) <= 0) {
javaTarget = "1." + javaTarget
}
cordovaConfig.KOTLIN_JVM_TARGET = javaTarget
}
// Similar to above, check if kotlin target is <= 8, if so prefix it.
// This allows the user to use consistent set of values in config.xml
// Rather than having to be aware whether the "1."" prefix is needed.
// This check is only done if the value isn't already prefixed with 1.
if (
!cordovaConfig.KOTLIN_JVM_TARGET.startsWith("1.") &&
JavaLanguageVersion.of(cordovaConfig.KOTLIN_JVM_TARGET).compareTo(JavaLanguageVersion.of(8)) <= 0
) {
cordovaConfig.KOTLIN_JVM_TARGET = "1." + cordovaConfig.KOTLIN_JVM_TARGET
}
kotlinOptions {
jvmTarget = cordovaConfig.KOTLIN_JVM_TARGET
}
}
if (cdvReleaseSigningPropertiesFile) {

View File

@ -17,16 +17,13 @@
under the License.
*/
const fs = require('fs-extra');
const path = require('path');
/**
* This script is to be run manually (e.g. by npm run clean:java-unit-tests) if
* you want to upgrade gradlew or test its proper generation.
*/
for (const variant of ['androidx']) {
for (const file of ['gradlew', 'gradlew.bat']) {
fs.removeSync(path.join(__dirname, variant, file));
}
}
// This is an empty project used to provide Gradle Tooling
// Using the main project which loads AGP will enforce a minimum version
// requirement on the end-user, requiring a gradle install that satisfies AGP
// version requirements.
// To avoid that, we utilise this empty project of which we can
// freely run the gradle wrapper task against to obtain the
// wrapper at the desired version, without being restricted by AGP's version
// requirements.
// Of course, the installed wrapper must still be of at least the minimum
// required version of AGP for the build to work correctly.

View File

@ -1,20 +0,0 @@
/*
* 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.
*/
//This file is intentionally just a comment

View File

@ -1,22 +0,0 @@
/* 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.
*/
wrapper {
apply from: '../../framework/cordova.gradle'
gradleVersion = cordovaConfig.GRADLE_VERSION
}

View File

@ -28,7 +28,7 @@ class AndroidTestRunner {
constructor (testTitle, projectDir) {
this.testTitle = testTitle;
this.projectDir = projectDir;
this.gradleWrapper = path.join(this.projectDir, 'gradlew');
this.gradleWrapper = path.join(this.projectDir, 'tools/gradlew');
}
_gradlew (...args) {
@ -42,8 +42,18 @@ class AndroidTestRunner {
);
}
_getGradleVersion () {
const config = JSON.parse(
fs.readFileSync(path.resolve(this.projectDir, '../../framework/cdv-gradle-config-defaults.json'), {
encoding: 'utf-8'
})
);
return config.GRADLE_VERSION;
}
_createProjectBuilder () {
return new ProjectBuilder(this.projectDir).runGradleWrapper('gradle');
return new ProjectBuilder(this.projectDir).installGradleWrapper(this._getGradleVersion());
}
run () {
@ -52,15 +62,16 @@ class AndroidTestRunner {
.then(_ => {
// TODO we should probably not only copy these files, but instead create a new project from scratch
fs.copyFileSync(path.resolve(this.projectDir, '../../framework/cdv-gradle-config-defaults.json'), path.resolve(this.projectDir, 'cdv-gradle-config.json'));
fs.copySync(path.resolve(this.projectDir, '../../templates/project/tools'), path.resolve(this.projectDir, 'tools'));
fs.copyFileSync(
path.join(__dirname, '../templates/project/assets/www/cordova.js'),
path.join(this.projectDir, 'app/src/main/assets/www/cordova.js')
);
})
.then(_ => this._createProjectBuilder())
.then(_ => this._gradlew('--version'))
.then(_ => this._gradlew(['--version']))
.then(_ => console.log(`[${this.testTitle}] Gradle wrapper is ready. Running tests now.`))
.then(_ => this._gradlew('test'))
.then(_ => this._gradlew(['test']))
.then(_ => console.log(`[${this.testTitle}] Java unit tests completed successfully`));
}
}