diff --git a/core/build.gradle b/core/build.gradle index 0fea5094ad8..f4811c674ab 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -61,17 +61,23 @@ tasks.withType(Jar) { tasks.trimShadedJar.doLast { // outjars is a file, so only one jar generated for sure def trimmed = tasks.trimShadedJar.outJarFiles[0].toPath() - ant.jar(destfile: trimmed.toString(), update: true) { + + ant.jar(destfile: trimmed.toString(), update: true, duplicate: 'fail') { zipfileset(src: tasks.shadedJar.archivePath) { include(name: 'META-INF/versions/**') } + + // Do not let Ant put the properties that harm the build reproducibility, such as JDK version. + delegate.manifest { + attribute(name: 'Created-By', value: "Gradle ${gradle.gradleVersion}") + } } } tasks.shadedTest.exclude 'META-INF/versions/**' dependencies { - mrJarVersions.each { version-> + mrJarVersions.each { version -> // Common to reference classes in main sourceset from Java 9 one (e.g., to return a common interface) "java${version}Implementation" files(sourceSets.main.output.classesDirs) { builtBy compileJava } diff --git a/gradle.properties b/gradle.properties index ccab06fcaba..f1d0c989a98 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,6 +19,7 @@ publishUrlForSnapshot=https://oss.sonatype.org/content/repositories/snapshots/ publishUsernameProperty=ossrhUsername publishPasswordProperty=ossrhPassword publishSignatureRequired=true +automaticModuleNames=true # Gradle options org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError diff --git a/gradle/scripts/README.md b/gradle/scripts/README.md index 5bbdde12d82..71b9903cad3 100644 --- a/gradle/scripts/README.md +++ b/gradle/scripts/README.md @@ -34,6 +34,7 @@ sensible defaults. By applying them, you can: - [Shading a multi-module project with `relocate` flag](#shading-a-multi-module-project-with-relocate-flag) - [Setting a Java target version with the `java(\\d+)` flag](#setting-a-java-target-version-with-the-javad-flag) - [Setting a Kotlin target version with the `kotlin(\\d+\\.\\d+)` flag](#setting-a-koltin-target-version-with-the-kotlindd-flag) +- [Automatic module names](#automatic-module-names) - [Tagging conveniently with `release` task](#tagging-conveniently-with-release-task) @@ -98,6 +99,7 @@ sensible defaults. By applying them, you can: ``` group=com.doe.john.myexample version=0.0.1-SNAPSHOT + versionPattern=^[0-9]+\\.[0-9]+\\.[0-9]+$ projectName=My Example projectUrl=https://www.example.com/ projectDescription=My example project @@ -118,6 +120,7 @@ sensible defaults. By applying them, you can: googleAnalyticsId=UA-XXXXXXXX javaSourceCompatibility=1.8 javaTargetCompatibility=1.8 + automaticModuleNames=false ``` 5. That's all. You now have two Java subprojects with sensible defaults. @@ -672,6 +675,31 @@ However, if you want to compile a Kotlin module with a different language versio For example, `kotlin1.6` flag makes your Kotlin module compatible with language version 1.6 and API version 1.6. +## Automatic module names + +By specifying the `automaticModuleNames=true` property in `settings.gradle`, every `java` project's JAR +file will contain the `Automatic-Module-Name` property in its `MANIFEST.MF`, auto-generated from the group ID +and artifact ID. For example: + +- groupId: `com.example`, artifactId: `foo-bar` + - module name: `com.example.foo.bar` +- groupId: `com.example.foo`, artifactId: `foo-bar` + - module name: `com.example.foo.bar` + +If enabled, each project with `java` flag will have the `automaticModuleName` property. + +You can override the automatic module name of a certain project via the `automaticModuleNameOverrides` +extension property: + + ```groovy + ext { + // Change the automatic module name of project ':bar' to 'com.example.fubar'. + automaticModuleNameOverrides = [ + ':bar': 'com.example.fubar' + ] + } + ``` + ## Tagging conveniently with `release` task The task called `release` is added at the top level project. It will update the diff --git a/gradle/scripts/lib/common-info.gradle b/gradle/scripts/lib/common-info.gradle index 13666f21cf7..0f29c25407e 100644 --- a/gradle/scripts/lib/common-info.gradle +++ b/gradle/scripts/lib/common-info.gradle @@ -42,17 +42,9 @@ allprojects { ext { artifactId = { // Use the overridden one if available. - if (rootProject.ext.has('artifactIdOverrides')) { - def overrides = rootProject.ext.artifactIdOverrides - if (!(overrides instanceof Map)) { - throw new IllegalStateException("artifactIdOverrides must be a Map: ${overrides}") - } - - for (Map.Entry e : overrides.entrySet()) { - if (rootProject.project(e.key) == project) { - return e.value - } - } + def overriddenArtifactId = findOverridden('artifactIdOverrides', project) + if (overriddenArtifactId != null) { + return overriddenArtifactId } // Generate from the project names otherwise. @@ -70,3 +62,49 @@ allprojects { }.call() } } + +// Check whether to enable automatic module names. +def isAutomaticModuleNameEnabled = 'true' == rootProject.findProperty('automaticModuleNames') + +allprojects { + ext { + automaticModuleName = { + if (!isAutomaticModuleNameEnabled) { + return null + } + + // Use the overridden one if available. + def overriddenAutomaticModuleName = findOverridden('automaticModuleNameOverrides', project) + if (overriddenAutomaticModuleName != null) { + return overriddenAutomaticModuleName + } + + // Generate from the groupId and artifactId otherwise. + def groupIdComponents = String.valueOf(rootProject.group).split("\\.").toList() + def artifactIdComponents = + String.valueOf(project.ext.artifactId).replace('-', '.').split("\\.").toList() + if (groupIdComponents.last() == artifactIdComponents.first()) { + return String.join('.', groupIdComponents + artifactIdComponents.drop(1)) + } else { + return String.join('.', groupIdComponents + artifactIdComponents) + } + }.call() + } +} + +def findOverridden(String overridesPropertyName, Project project) { + if (rootProject.ext.has(overridesPropertyName)) { + def overrides = rootProject.ext.get(overridesPropertyName) + if (!(overrides instanceof Map)) { + throw new IllegalStateException("rootProject.ext.${overridesPropertyName} must be a Map: ${overrides}") + } + + for (Map.Entry e : overrides.entrySet()) { + if (rootProject.project(e.key) == project) { + return String.valueOf(e.value) + } + } + } + + return null +} diff --git a/gradle/scripts/lib/java-shade.gradle b/gradle/scripts/lib/java-shade.gradle index 5ff6909d67c..1a2f41d5236 100644 --- a/gradle/scripts/lib/java-shade.gradle +++ b/gradle/scripts/lib/java-shade.gradle @@ -27,12 +27,23 @@ configure(relocatedProjects) { configureShadowTask(project, delegate, true) archiveBaseName.set("${project.archivesBaseName}-shaded") + // Exclude the legacy file listing. + exclude '/META-INF/INDEX.LIST' // Exclude the class signature files. exclude '/META-INF/*.SF' exclude '/META-INF/*.DSA' exclude '/META-INF/*.RSA' // Exclude the files generated by Maven exclude '/META-INF/maven/**' + // Exclude the module metadata that'll become invalid after relocation. + exclude '**/module-info.class' + + // Set the 'Automatic-Module-Name' property in MANIFEST.MF. + if (project.ext.automaticModuleName != null) { + manifest { + attributes('Automatic-Module-Name': project.ext.automaticModuleName) + } + } } tasks.assemble.dependsOn tasks.shadedJar diff --git a/gradle/scripts/lib/java.gradle b/gradle/scripts/lib/java.gradle index 8f6601e2181..13ddbe58e2c 100644 --- a/gradle/scripts/lib/java.gradle +++ b/gradle/scripts/lib/java.gradle @@ -1,5 +1,6 @@ import java.util.regex.Pattern +// Determine which version of JDK should be used for builds. def buildJdkVersion = Integer.parseInt(JavaVersion.current().getMajorVersion()) if (rootProject.hasProperty('buildJdkVersion')) { def jdkVersion = Integer.parseInt(String.valueOf(rootProject.findProperty('buildJdkVersion'))) @@ -139,6 +140,12 @@ configure(projectsWithFlags('java')) { registerFeature('optional') { usingSourceSet(sourceSets.main) } + + // Do not let Gradle infer the module path if automatic module name is enabled, + // because it means the JAR will rely on JDK's automatic module metadata generation. + if (project.ext.automaticModuleName != null) { + modularity.inferModulePath = false + } } // Set the sensible compiler options. @@ -154,6 +161,15 @@ configure(projectsWithFlags('java')) { options.compilerArgs += '-parameters' } + // Set the 'Automatic-Module-Name' property in 'MANIFEST.MF' if `automaticModuleName` is not null. + if (project.ext.automaticModuleName != null) { + tasks.named('jar') { + manifest { + attributes('Automatic-Module-Name': project.ext.automaticModuleName) + } + } + } + project.ext.configureFlakyTests = { Test testTask -> def flakyTests = rootProject.findProperty('flakyTests') if (flakyTests == 'true') { diff --git a/gradle/scripts/lib/prerequisite.gradle b/gradle/scripts/lib/prerequisite.gradle index 7a94d9b4f8e..713deb42f7b 100644 --- a/gradle/scripts/lib/prerequisite.gradle +++ b/gradle/scripts/lib/prerequisite.gradle @@ -9,12 +9,15 @@ plugins { ''') } -['projectName', 'projectUrl', 'inceptionYear', 'licenseName', 'licenseUrl', 'scmUrl', 'scmConnection', +['group', 'version', 'projectName', 'projectUrl', 'inceptionYear', 'licenseName', 'licenseUrl', 'scmUrl', 'scmConnection', 'scmDeveloperConnection', 'publishUrlForRelease', 'publishUrlForSnapshot', 'publishUsernameProperty', 'publishPasswordProperty'].each { if (rootProject.findProperty(it) == null) { throw new IllegalStateException('''Add project info properties to gradle.properties: +group=com.doe.john.myexample +version=0.0.1-SNAPSHOT +versionPattern=^[0-9]+\\\\.[0-9]+\\\\.[0-9]+$ projectName=My Example projectUrl=https://www.example.com/ projectDescription=My example project @@ -31,7 +34,12 @@ publishUrlForRelease=https://oss.sonatype.org/service/local/staging/deploy/maven publishUrlForSnapshot=https://oss.sonatype.org/content/repositories/snapshots/ publishUsernameProperty=ossrhUsername publishPasswordProperty=ossrhPassword +publishSignatureRequired=true versionPattern=^[0-9]+\\\\.[0-9]+\\\\.[0-9]+$ +googleAnalyticsId=UA-XXXXXXXX +javaSourceCompatibility=1.8 +javaTargetCompatibility=1.8 +automaticModuleNames=false ''') } }