Skip to content

Commit 89a0a72

Browse files
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>
1 parent ed8e5d2 commit 89a0a72

17 files changed

+155
-131
lines changed

.gitattributes

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*.scm text
2424
*.sql text
2525
*.sh text
26-
*.bat text
26+
*.bat text eol=crlf
2727

2828
# templates
2929
*.ejs text
@@ -92,3 +92,4 @@ AUTHORS text
9292
*.woff binary
9393
*.pyc binary
9494
*.pdf binary
95+
*.jar binary

.github/workflows/ci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727

2828
strategy:
2929
matrix:
30-
node-version: [16.x, 18.x, 20.x]
30+
node-version: [16.x, 18.x, 20.x, 22.x]
3131
os: [ubuntu-latest, windows-latest, macos-latest]
3232

3333
steps:
@@ -39,7 +39,7 @@ jobs:
3939
- uses: actions/setup-java@v4
4040
with:
4141
distribution: 'temurin'
42-
java-version: '11'
42+
java-version: '17'
4343

4444
- name: Environment Information
4545
run: |

.gitignore

+1-5
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,8 @@ example
2929
**/assets/www/cordova.js
3030

3131
/test/.externalNativeBuild
32-
33-
/test/androidx/gradle
34-
/test/androidx/gradlew
35-
/test/androidx/gradlew.bat
3632
/test/androidx/cdv-gradle-config.json
37-
33+
/test/androidx/tools
3834
/test/assets/www/.tmp*
3935
/test/assets/www/cordova.js
4036
/test/bin

.ratignore

+3
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ intermediates
88
reports
99
test-results
1010
node_modules
11+
gradle
12+
gradlew
13+
gradlew.bat

LICENSE

+2-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
same "printed page" as the copyright notice for easier
188188
identification within third-party archives.
189189

190-
Copyright 2015-2020 Apache Cordova
190+
Copyright 2015-2024 Apache Cordova
191191

192192
Licensed under the Apache License, Version 2.0 (the "License");
193193
you may not use this file except in compliance with the License.
@@ -200,3 +200,4 @@
200200
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201201
See the License for the specific language governing permissions and
202202
limitations under the License.
203+

framework/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ android {
5050
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION
5151

5252
compileOptions {
53-
sourceCompatibility JavaVersion.VERSION_1_8
54-
targetCompatibility JavaVersion.VERSION_1_8
53+
sourceCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_SOURCE_COMPATIBILITY)
54+
targetCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY)
5555
}
5656

5757
// For the Android Cordova Lib, we allow changing the minSdkVersion, but it is at the users own risk
+8-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
{
22
"MIN_SDK_VERSION": 24,
3-
"SDK_VERSION": 33,
3+
"SDK_VERSION": 34,
44
"COMPILE_SDK_VERSION": null,
5-
"GRADLE_VERSION": "7.6",
6-
"MIN_BUILD_TOOLS_VERSION": "33.0.2",
7-
"AGP_VERSION": "7.4.2",
5+
"GRADLE_VERSION": "8.7",
6+
"MIN_BUILD_TOOLS_VERSION": "34.0.0",
7+
"AGP_VERSION": "8.3.0",
88
"KOTLIN_VERSION": "1.7.21",
99
"ANDROIDX_APP_COMPAT_VERSION": "1.6.1",
1010
"ANDROIDX_WEBKIT_VERSION": "1.6.0",
1111
"ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.0",
1212
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.15",
1313
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
1414
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false,
15-
"PACKAGE_NAMESPACE": "io.cordova.helloCordova"
15+
"PACKAGE_NAMESPACE": "io.cordova.helloCordova",
16+
"JAVA_SOURCE_COMPATIBILITY": 8,
17+
"JAVA_TARGET_COMPATIBILITY": 8,
18+
"KOTLIN_JVM_TARGET": null
1619
}

lib/builders/ProjectBuilder.js

+28-28
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const events = require('cordova-common').events;
2525
const CordovaError = require('cordova-common').CordovaError;
2626
const check_reqs = require('../check_reqs');
2727
const PackageType = require('../PackageType');
28-
const { compareByAll } = require('../utils');
28+
const { compareByAll, isWindows } = require('../utils');
2929
const { createEditor } = require('properties-parser');
3030
const CordovaGradleConfigParserFactory = require('../config/CordovaGradleConfigParserFactory');
3131

@@ -85,9 +85,7 @@ class ProjectBuilder {
8585
}
8686

8787
getArgs (cmd, opts) {
88-
let args = [
89-
'-b', path.join(this.root, 'build.gradle')
90-
];
88+
let args = [];
9189
if (opts.extraArgs) {
9290
args = args.concat(opts.extraArgs);
9391
}
@@ -116,16 +114,27 @@ class ProjectBuilder {
116114
return args;
117115
}
118116

119-
/*
120-
* This returns a promise
121-
*/
122-
runGradleWrapper (gradle_cmd) {
123-
const gradlePath = path.join(this.root, 'gradlew');
124-
const wrapperGradle = path.join(this.root, 'wrapper.gradle');
125-
if (fs.existsSync(gradlePath)) {
126-
// Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
117+
getGradleWrapperPath () {
118+
let wrapper = path.join(this.root, 'tools', 'gradlew');
119+
120+
if (isWindows()) {
121+
wrapper += '.bat';
122+
}
123+
124+
return wrapper;
125+
}
126+
127+
/**
128+
* Installs/updates the gradle wrapper
129+
* @param {string} gradleVersion The gradle version to install. Ignored if CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL environment variable is defined
130+
* @returns {Promise<void>}
131+
*/
132+
async installGradleWrapper (gradleVersion) {
133+
if (process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL) {
134+
events.emit('verbose', `Overriding Gradle Version via CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL (${process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL})`);
135+
await execa('gradle', ['-p', path.join(this.root, 'tools'), 'wrapper', '--gradle-distribution-url', process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL], { stdio: 'inherit' });
127136
} else {
128-
return execa(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], { stdio: 'inherit' });
137+
await execa('gradle', ['-p', path.join(this.root, 'tools'), 'wrapper', '--gradle-version', gradleVersion], { stdio: 'inherit' });
129138
}
130139
}
131140

@@ -275,23 +284,14 @@ class ProjectBuilder {
275284

276285
prepEnv (opts) {
277286
const self = this;
287+
const config = this._getCordovaConfig();
278288
return check_reqs.check_gradle()
279-
.then(function (gradlePath) {
280-
return self.runGradleWrapper(gradlePath);
289+
.then(function () {
290+
events.emit('verbose', `Using Gradle: ${config.GRADLE_VERSION}`);
291+
return self.installGradleWrapper(config.GRADLE_VERSION);
281292
}).then(function () {
282293
return self.prepBuildFiles();
283294
}).then(() => {
284-
const config = this._getCordovaConfig();
285-
// update/set the distributionUrl in the gradle-wrapper.properties
286-
const gradleWrapperPropertiesPath = path.join(self.root, 'gradle/wrapper/gradle-wrapper.properties');
287-
const gradleWrapperProperties = createEditor(gradleWrapperPropertiesPath);
288-
const distributionUrl = process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL || `https://services.gradle.org/distributions/gradle-${config.GRADLE_VERSION}-all.zip`;
289-
gradleWrapperProperties.set('distributionUrl', distributionUrl);
290-
gradleWrapperProperties.save();
291-
292-
events.emit('verbose', `Gradle Distribution URL: ${distributionUrl}`);
293-
})
294-
.then(() => {
295295
const signingPropertiesPath = path.join(self.root, `${opts.buildType}${SIGNING_PROPERTIES}`);
296296

297297
if (fs.existsSync(signingPropertiesPath)) fs.removeSync(signingPropertiesPath);
@@ -317,7 +317,7 @@ class ProjectBuilder {
317317
* Returns a promise.
318318
*/
319319
async build (opts) {
320-
const wrapper = path.join(this.root, 'gradlew');
320+
const wrapper = this.getGradleWrapperPath();
321321
const args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
322322

323323
events.emit('verbose', `Running Gradle Build: ${wrapper} ${args.join(' ')}`);
@@ -338,7 +338,7 @@ class ProjectBuilder {
338338
}
339339

340340
clean (opts) {
341-
const wrapper = path.join(this.root, 'gradlew');
341+
const wrapper = this.getGradleWrapperPath();
342342
const args = this.getArgs('clean', opts);
343343
return execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) })
344344
.then(() => {

lib/create.js

+13-12
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,15 @@ function prepBuildFiles (projectPath) {
113113
buildModule.getBuilder(projectPath).prepBuildFiles();
114114
}
115115

116-
function copyBuildRules (projectPath, isLegacy) {
116+
function copyBuildRules (projectPath) {
117117
const srcDir = path.join(ROOT, 'templates', 'project');
118118

119-
if (isLegacy) {
120-
// The project's build.gradle is identical to the earlier build.gradle, so it should still work
121-
fs.copySync(path.join(srcDir, 'legacy', 'build.gradle'), path.join(projectPath, 'legacy', 'build.gradle'));
122-
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
123-
} else {
124-
fs.copySync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle'));
125-
fs.copySync(path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app', 'build.gradle'));
126-
fs.copySync(path.join(srcDir, 'app', 'repositories.gradle'), path.join(projectPath, 'app', 'repositories.gradle'));
127-
fs.copySync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
128-
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
129-
}
119+
fs.copySync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle'));
120+
fs.copySync(path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app', 'build.gradle'));
121+
fs.copySync(path.join(srcDir, 'app', 'repositories.gradle'), path.join(projectPath, 'app', 'repositories.gradle'));
122+
fs.copySync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
123+
124+
copyGradleTools(projectPath);
130125
}
131126

132127
function copyScripts (projectPath) {
@@ -176,6 +171,12 @@ function validateProjectName (project_name) {
176171
return Promise.resolve();
177172
}
178173

174+
function copyGradleTools (projectPath) {
175+
const srcDir = path.join(ROOT, 'templates', 'project');
176+
177+
fs.copySync(path.resolve(srcDir, 'tools'), path.resolve(projectPath, 'tools'));
178+
}
179+
179180
/**
180181
* Creates an android application with the given options.
181182
*

lib/prepare.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ function getUserGradleConfig (configXml) {
110110
{ xmlKey: 'AndroidXWebKitVersion', gradleKey: 'ANDROIDX_WEBKIT_VERSION', type: String },
111111
{ xmlKey: 'GradlePluginGoogleServicesVersion', gradleKey: 'GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION', type: String },
112112
{ xmlKey: 'GradlePluginGoogleServicesEnabled', gradleKey: 'IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED', type: Boolean },
113-
{ xmlKey: 'GradlePluginKotlinEnabled', gradleKey: 'IS_GRADLE_PLUGIN_KOTLIN_ENABLED', type: Boolean }
113+
{ xmlKey: 'GradlePluginKotlinEnabled', gradleKey: 'IS_GRADLE_PLUGIN_KOTLIN_ENABLED', type: Boolean },
114+
{ xmlKey: 'AndroidJavaSourceCompatibility', gradleKey: 'JAVA_SOURCE_COMPATIBILITY', type: Number },
115+
{ xmlKey: 'AndroidJavaTargetCompatibility', gradleKey: 'JAVA_TARGET_COMPATIBILITY', type: Number },
116+
{ xmlKey: 'AndroidKotlinJVMTarget', gradleKey: 'KOTLIN_JVM_TARGET', type: String }
114117
];
115118

116119
return configXmlToGradleMapping.reduce((config, mapping) => {

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
"unit-tests": "jasmine --config=spec/unit/jasmine.json",
2020
"cover": "nyc jasmine --config=spec/coverage.json",
2121
"e2e-tests": "jasmine --config=spec/e2e/jasmine.json",
22-
"java-unit-tests": "node test/run_java_unit_tests.js",
23-
"clean:java-unit-tests": "node test/clean.js"
22+
"java-unit-tests": "node test/run_java_unit_tests.js"
2423
},
2524
"author": "Apache Software Foundation",
2625
"license": "Apache-2.0",

spec/unit/builders/ProjectBuilder.spec.js

+27-11
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
const fs = require('fs-extra');
2121
const path = require('path');
2222
const rewire = require('rewire');
23+
const { isWindows } = require('../../../lib/utils');
2324

2425
describe('ProjectBuilder', () => {
2526
const rootDir = '/root';
@@ -128,19 +129,24 @@ describe('ProjectBuilder', () => {
128129
});
129130
});
130131

131-
describe('runGradleWrapper', () => {
132-
it('should run the provided gradle command if a gradle wrapper does not already exist', () => {
133-
spyOn(fs, 'existsSync').and.returnValue(false);
134-
builder.runGradleWrapper('/my/sweet/gradle');
135-
expect(execaSpy).toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
132+
describe('installGradleWrapper', () => {
133+
beforeEach(() => {
134+
execaSpy.and.resolveTo();
136135
});
137136

138-
it('should do nothing if a gradle wrapper exists in the project directory', () => {
139-
spyOn(fs, 'existsSync').and.returnValue(true);
140-
builder.runGradleWrapper('/my/sweet/gradle');
141-
expect(execaSpy).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
137+
it('should run gradle wrapper 8.7', async () => {
138+
await builder.installGradleWrapper('8.7');
139+
expect(execaSpy).toHaveBeenCalledWith('gradle', ['-p', path.normalize('/root/tools'), 'wrapper', '--gradle-version', '8.7'], jasmine.any(Object));
140+
});
141+
142+
it('CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL should override gradle version', async () => {
143+
process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL = 'https://dist.local';
144+
await builder.installGradleWrapper('8.7');
145+
delete process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL;
146+
expect(execaSpy).toHaveBeenCalledWith('gradle', ['-p', path.normalize('/root/tools'), 'wrapper', '--gradle-distribution-url', 'https://dist.local'], jasmine.any(Object));
142147
});
143148
});
149+
144150
describe('build', () => {
145151
beforeEach(() => {
146152
spyOn(builder, 'getArgs');
@@ -170,7 +176,12 @@ describe('ProjectBuilder', () => {
170176

171177
builder.build({});
172178

173-
expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), testArgs, jasmine.anything());
179+
let gradle = path.join(rootDir, 'tools', 'gradlew');
180+
if (isWindows()) {
181+
gradle += '.bat';
182+
}
183+
184+
expect(execaSpy).toHaveBeenCalledWith(gradle, testArgs, jasmine.anything());
174185
});
175186

176187
it('should reject if the spawn fails', () => {
@@ -227,8 +238,13 @@ describe('ProjectBuilder', () => {
227238
const gradleArgs = ['test', 'args', '-f'];
228239
builder.getArgs.and.returnValue(gradleArgs);
229240

241+
let gradle = path.join(rootDir, 'tools', 'gradlew');
242+
if (isWindows()) {
243+
gradle += '.bat';
244+
}
245+
230246
return builder.clean(opts).then(() => {
231-
expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), gradleArgs, jasmine.anything());
247+
expect(execaSpy).toHaveBeenCalledWith(gradle, gradleArgs, jasmine.anything());
232248
});
233249
});
234250

templates/project/app/build.gradle

+37-2
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ task cdvPrintProps {
181181
android {
182182
namespace cordovaConfig.PACKAGE_NAMESPACE
183183

184+
buildFeatures {
185+
buildConfig true
186+
}
187+
184188
defaultConfig {
185189
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
186190
applicationId cordovaConfig.PACKAGE_NAMESPACE
@@ -248,8 +252,39 @@ android {
248252
}
249253

250254
compileOptions {
251-
sourceCompatibility JavaVersion.VERSION_1_8
252-
targetCompatibility JavaVersion.VERSION_1_8
255+
sourceCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_SOURCE_COMPATIBILITY)
256+
targetCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY)
257+
}
258+
259+
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) {
260+
if (cordovaConfig.KOTLIN_JVM_TARGET == null) {
261+
// If the value is null, fallback to JAVA_TARGET_COMPATIBILITY,
262+
// as they generally should be equal
263+
def javaTarget = JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY)
264+
265+
// check if javaTarget is <= 8; if so, we need to prefix it with "1."
266+
// Starting with 9 and later, the value can be used as is.
267+
if (javaTarget.compareTo(JavaLanguageVersion.of(8)) <= 0) {
268+
javaTarget = "1." + javaTarget
269+
}
270+
271+
cordovaConfig.KOTLIN_JVM_TARGET = javaTarget
272+
}
273+
274+
// Similar to above, check if kotlin target is <= 8, if so prefix it.
275+
// This allows the user to use consistent set of values in config.xml
276+
// Rather than having to be aware whether the "1."" prefix is needed.
277+
// This check is only done if the value isn't already prefixed with 1.
278+
if (
279+
!cordovaConfig.KOTLIN_JVM_TARGET.startsWith("1.") &&
280+
JavaLanguageVersion.of(cordovaConfig.KOTLIN_JVM_TARGET).compareTo(JavaLanguageVersion.of(8)) <= 0
281+
) {
282+
cordovaConfig.KOTLIN_JVM_TARGET = "1." + cordovaConfig.KOTLIN_JVM_TARGET
283+
}
284+
285+
kotlinOptions {
286+
jvmTarget = cordovaConfig.KOTLIN_JVM_TARGET
287+
}
253288
}
254289

255290
if (cdvReleaseSigningPropertiesFile) {

0 commit comments

Comments
 (0)