io.github.jcohy.gradle.conventions.JavaConventions Maven / Gradle / Ivy
package io.github.jcohy.gradle.conventions;
import java.net.URI;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import io.github.jcohy.gradle.constant.BomCoordinates;
import io.github.jcohy.gradle.optional.OptionalDependenciesPlugin;
import io.github.jcohy.gradle.testing.TestFailuresPlugin;
import io.github.jcohy.gradle.toolchain.ToolchainPlugin;
import io.spring.gradle.dependencymanagement.DependencyManagementPlugin;
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension;
import io.spring.javaformat.gradle.SpringJavaFormatPlugin;
import io.spring.javaformat.gradle.tasks.FormatterTask;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.quality.CheckstyleExtension;
import org.gradle.api.plugins.quality.CheckstylePlugin;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.javadoc.Javadoc;
import org.gradle.api.tasks.testing.Test;
import org.gradle.external.javadoc.JavadocMemberLevel;
import org.gradle.jvm.tasks.Jar;
import org.gradle.testretry.TestRetryPlugin;
import org.gradle.testretry.TestRetryTaskExtension;
/**
* Copyright: Copyright (c) 2021
* jcohy.com
*
* Description: 当使用 {@link JavaBasePlugin} 时的约定. 当使用此插件时:
*
* - {@code sourceCompatibility} 设置为 {@code 17}
*
- {@code targetCompatibility} 设置为 {@code 17}
*
- 应用 {@link SpringJavaFormatPlugin Spring Java Format}, {@link CheckstylePlugin Checkstyle}, {@link TestFailuresPlugin Test Failures}, 和 {@link TestRetryPlugin TestRetry} 插件。
* {@link CheckstylePlugin Checkstyle} 插件使用我们自定义的代码规则检查配置对项目进行检查。代码检查规则查看 jcohy-checkstyle.xml 文件
*
- {@link Test} 任务使用 JUnit Platform 并且配置最大堆为 1024M
*
- {@link JavaCompile}, {@link Javadoc}, 和 {@link FormatterTask} 任务编码为 UTF-8
*
- {@link JavaCompile} 任务配置为使用 {@code -parameters}, 并且当使用 Java17 时
*
* - 将警告视为错误
*
- 启用 {@code unchecked}, {@code deprecation}, {@code rawtypes}, 和 {@code varags} 警告
*
* - 为每个项目配置以下 maven 仓库。
*
* - https://maven.aliyun.com/repository/public
*
* - 自动为项目添加一下依赖管理 BOM
*
* - 请参阅 {@link BomCoordinates} 类
*
* - 在使用 {@link JavaPlugin} 插件的项目中添加以下测试相关的依赖, 并且测试最大重试次数为 {@code 3} 次
*
* - testRuntimeOnly(org.junit.platform:junit-platform-launcher)
* - testRuntimeOnly(org.junit.jupiter:junit-jupiter)
* - testRuntimeOnly(org.assertj:assertj-core)
* - testImplementation(org.springframework.boot:spring-boot-starter-test)
*
* - {@link Jar} 任务的生成带有 LICENSE.txt,README.txt 和 NOTICE.txt 和以下清单属性:
*
* - {@code Automatic-Module-Name}
*
- {@code Build-Jdk-Spec}
*
- {@code Built-By}
*
- {@code Implementation-Title}
*
- {@code Implementation-Version}
*
*
*
*
* @author jiac
* @version 0.0.5.1 2021/6/11:15:26
* @since 0.0.5.1
* @since 2.7.3.2
*/
class JavaConventions {
void apply(Project project) {
project.getPlugins().withType(JavaBasePlugin.class, (java) -> {
// 此插件用于生成测试失败报告
project.getPlugins().apply(TestFailuresPlugin.class);
// 为插件配置 SpringJavaFromat
configureSpringJavaFormat(project);
configureMavenRepository(project);
configureJavaCompileConventions(project);
configureJavadocConventions(project);
configureTestConventions(project);
configureJarManifestConventions(project);
configureDependencyManagement(project);
configureToolchain(project);
});
}
private void configureMavenRepository(Project project) {
project.getRepositories().maven((mavenRepo) -> {
mavenRepo.setUrl(URI.create("https://maven.aliyun.com/repository/public"));
mavenRepo.setName("ali");
});
project.getRepositories().mavenCentral();
}
private void configureToolchain(Project project) {
project.getPlugins().apply(ToolchainPlugin.class);
}
/**
* 配置项目依赖
*
* @param project project
*/
private void configureDependencyManagement(Project project) {
ConfigurationContainer configurations = project.getConfigurations();
Configuration dependencyManagement = configurations.create("dependencyManagement", (configuration) -> {
configuration.setVisible(false);
configuration.setCanBeConsumed(false);
configuration.setCanBeResolved(false);
});
project.getPlugins().apply(DependencyManagementPlugin.class);
DependencyManagementExtension dependencyManagementExtension = project.getExtensions().getByType(DependencyManagementExtension.class);
dependencyManagementExtension.imports((importsHandler -> {
importsHandler.mavenBom(BomCoordinates.SPRING_CLOUD_BOM_COORDINATES);
importsHandler.mavenBom(BomCoordinates.SPRING_BOOT_ADMIN);
importsHandler.mavenBom(BomCoordinates.SPRING_BOM_COORDINATES);
importsHandler.mavenBom(BomCoordinates.PIVOTAL_SPRING_CLOUD);
importsHandler.mavenBom(BomCoordinates.ALI_CLOUD_BOM_COORDINATES);
importsHandler.mavenBom(BomCoordinates.TENCENT_CLOUD_BOM_COORDINATES);
}));
configurations
.matching((configuration) ->
configuration.getName().endsWith("Classpath") || JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME.equals(configuration.getName()))
.all((configuration) -> {
configuration.extendsFrom(dependencyManagement);
});
project.getPlugins().withType(OptionalDependenciesPlugin.class, (optionalDependencies) -> configurations
.getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME).extendsFrom(dependencyManagement));
}
/**
* 配置生成的 Jar 清单文件
*
* @param project project
*/
private void configureJarManifestConventions(Project project) {
ExtractResources extractLegalResources = project.getTasks().create("extractLegalResources", ExtractResources.class);
extractLegalResources.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("legal"));
extractLegalResources.setResourcesNames(Arrays.asList("LICENSE.txt", "NOTICE.txt", "README.txt"));
extractLegalResources.property("version", project.getVersion().toString());
extractLegalResources.property("copyright", DateTimeFormatter.ofPattern("yyyy").format(LocalDateTime.now()));
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
Set sourceJarTaskNames = sourceSets.stream().map(SourceSet::getSourcesJarTaskName).collect(Collectors.toSet());
Set javadocJarTaskNames = sourceSets.stream().map(SourceSet::getJavadocJarTaskName).collect(Collectors.toSet());
project.getTasks().withType(Jar.class, jar -> project.afterEvaluate((evaluated) -> {
jar.metaInf((metaInf) -> metaInf.from(extractLegalResources));
jar.manifest(manifest -> {
Map attributes = new TreeMap<>();
attributes.put("Automatic-Module-Name", project.getName().replace("-", "."));
attributes.put("Build-Jdk-Spec", project.property("sourceCompatibility"));
attributes.put("Built-By", "Jcohy");
attributes.put("Implementation-Title",
determineImplementationTitle(project, sourceJarTaskNames, javadocJarTaskNames, jar));
attributes.put("Implementation-Version", project.getVersion());
manifest.attributes(attributes);
});
}));
}
private String determineImplementationTitle(Project project, Set sourceJarTaskNames,
Set javadocJarTaskNames, Jar jar) {
if (sourceJarTaskNames.contains(jar.getName())) {
return "Source for " + project.getName();
}
if (javadocJarTaskNames.contains(jar.getName())) {
return "Javadoc for " + project.getName();
}
return project.getDescription();
}
/**
* 配置测试的约定
* 当 系统中存在 System.getenv("CI") 属性时,最大重试次数为 3 次
*
* @param project project
*/
private void configureTestConventions(Project project) {
project.getTasks().withType(Test.class, (test) -> {
test.useJUnitPlatform();
test.setMaxHeapSize("1024M");
});
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
project.getDependencies().add(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME, "org.junit.platform:junit-platform-launcher");
project.getDependencies().add(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME, "org.junit.jupiter:junit-jupiter");
project.getDependencies().add(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME, "org.assertj:assertj-core");
project.getDependencies().add(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, "org.springframework.boot:spring-boot-starter-test");
});
project.getPlugins().apply(TestRetryPlugin.class);
project.getTasks().withType(Test.class, (test -> {
project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
testRetry.getFailOnPassedAfterRetry().set(true);
testRetry.getMaxRetries().set(isCi() ? 3 : 0);
});
}));
}
private boolean isCi() {
return Boolean.parseBoolean(System.getenv("CI"));
}
/**
* 配置 JavaDoc 约定
*
* @param project project
*/
private void configureJavadocConventions(Project project) {
project.getTasks().withType(Javadoc.class, javadoc -> {
javadoc.setDescription("Generates project-level javadoc for use in -javadoc jar");
javadoc.options((option) -> {
option.encoding("UTF-8");
option.source("17");
option.setMemberLevel(JavadocMemberLevel.PROTECTED);
option.header(project.getName());
});
// 跨模块的 @see 和 @link 引用消除警告
javadoc.getLogging().captureStandardError(LogLevel.INFO);
// 消除 "## warnings" 消息
javadoc.getLogging().captureStandardOutput(LogLevel.INFO);
});
}
/**
* 配置 Java 编译
*
* @param project project
*/
private void configureJavaCompileConventions(Project project) {
project.getTasks().withType(JavaCompile.class, compile -> {
compile.getOptions().setEncoding("UTF-8");
compile.setSourceCompatibility("17");
compile.setTargetCompatibility("17");
List args = compile.getOptions().getCompilerArgs();
if (!args.contains("-parameters")) {
args.add("-parameters");
}
if (buildingWithJava17(project)) {
args.addAll(Arrays.asList("-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes",
"-Xlint:varargs"));
}
});
}
private boolean buildingWithJava17(Project project) {
return !project.hasProperty("toolchainVersion") && JavaVersion.current() == JavaVersion.VERSION_17;
}
/**
* 配置 {@link SpringJavaFormatPlugin} 和 {@link CheckstylePlugin} 插件
*
* @param project project
*/
private void configureSpringJavaFormat(Project project) {
project.getPlugins().apply(SpringJavaFormatPlugin.class);
project.getTasks().withType(FormatterTask.class, (formatTask -> formatTask.setEncoding("UTF-8")));
project.getPlugins().apply(CheckstylePlugin.class);
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("8.44");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
checkstyleDependencies.add(project.getDependencies().create(BomCoordinates.JCOHY_CHECKSTYLE));
}
}