Java集成GradleToolingAPI编译Gradle项目

解决方案

使用Java代码控制Gradle编译与Maven Embedder编译有所不同,需要额外安装Gradle工具到本地,MavenEmbedder则是直接通过集成jar的方式实现。所以在编译Gradle项目之前,需要先在本地安装对应版本的Gradle,无需配置环境变量。

安装Gradle

下载Gradle:https://gradle.org/releases/ 选择需要的版本安装即可
image.png

  • binary-only: 只有二进制文件
  • complete: 在二进制包基础上增加了 文档 , 源码

建议选择binary-only版本,我们只需要实现代码编译即可,无需引入其他内容。

引入依赖

1
2
3
4
5
6
<!-- gradle版本查询:https://mvnrepository.com/artifact/org.netbeans.external/gradle-tooling-api -->
<dependency>
<groupId>org.netbeans.external</groupId>
<artifactId>gradle-tooling-api</artifactId>
<version>${gradle-tooling-api.version}</version>
</dependency>

笔者使用的版本为:RELEASE170

代码实现

GradleCommand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* @author: wick
* @date: 2024/6/19 21:12
* @description: gradle编译相关命令
*/

public class GradleCommand {
/**
* gradle命令
*/
public static final String GRADLE = "gradle";

/**
* 清理构建产物
*/
public static final String CLEAN = "clean";

/**
* 执行测试
*/
public static final String TEST = "test";

/**
* 排除某个任务
*/
public static final String EXCLUDE = "-x";

/**
* 编译 class
*/
public static final String CLASSES = "classes";

public static final List<String> COMMAND = new ArrayList<>() {{
add(CLEAN);
add(CLASSES);
}};
}

Constant

1
2
public static final String JAVA_HOME = "java.home";
public static final File DEFAULT_GRADLE_USER_HOME = new File("~/.gradle");

GradleManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author: wick
* @date: 2024/6/19 21:06
* @description: gradle编译接口
*/

public interface GradleBuildManager {
/**
* 代码编译
*
* @param dto dto
*/
void compiler(CompileDTO dto);

/**
* 获取模块列表
*
* @param jdkPath jdk安装路径
* @param gradlePath gradle 安装路径
* @param codePath 代码路径
* @return 模块列表
*/
List<String> modules(String jdkPath,String gradlePath, String codePath);
}

GradleBuildManagerImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Component
@Slf4j
public class GradleBuildManagerImpl implements GradleBuildManager {

@Override
public void compiler(CompileDTO dto) {
log.info("开始编译Gradle项目,编译工具路径: {},代码路径: {}, 编译参数: {}", dto.getBuildToolPath(), dto.getCodePath(), dto.getCommands());
long startTime = System.currentTimeMillis();
// 重定向标准错误输出流
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
PrintStream originalErrStream = System.err;
// 重定向标准输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PrintStream originalOutStream = System.out;
try (ProjectConnection connection = GradleConnector.newConnector()
.forProjectDirectory(new File(dto.getCodePath()))
.useGradleUserHomeDir(CoverageConstant.DEFAULT_GRADLE_USER_HOME)
.useInstallation(new File(dto.getBuildToolPath()))
.connect()) {
BuildLauncher build = connection.newBuild();
build.setJavaHome(new File(dto.getJdkPath()));
System.setErr(new PrintStream(errorStream));
System.setOut(new PrintStream(outputStream));
build.forTasks(dto.getCommands().toArray(new String[0]))
.setStandardOutput(System.out)
.setStandardError(System.err)
// 跳过单测,多线程编译
.withArguments(GradleCommand.EXCLUDE, GradleCommand.TEST)
// 不使用彩色日志,否则会导致日志中的颜色代码被打印出来,导致日志不易阅读
.setColorOutput(false)
// 限制 gradle 内存,防止编译过程中内存溢出,具体配置视服务器内存而定
.setJvmArguments("-Xmx512m");
build.run();
log.info("编译日志:\n {}", outputStream);
} catch (Exception e) {
log.error("代码: {} 编译失败, 异常详情: {}", dto.getCodePath(), ExceptionUtils.getRootCauseMessage(e));
log.error("编译异常日志:\n {}", errorStream);
throw new ServiceException("编译失败");
} finally {
System.setErr(originalErrStream);
System.setOut(originalOutStream);
}
log.info("结束编译Gradle项目,编译耗时: {} s", (System.currentTimeMillis() - startTime) / 1000);
}

@Override
public List<String> modules(String jdkPath, String gradlePath, String codePath) {
String originJavaHome = System.getProperty(SystemPropertiesConstant.JAVA_HOME);
System.setProperty(SystemPropertiesConstant.JAVA_HOME, jdkPath);
log.info("开始扫描Gradle项目模块,编译工具路径: {},代码路径: {}", gradlePath, codePath);
try (ProjectConnection connection = GradleConnector.newConnector()
.forProjectDirectory(new File(codePath))
.useInstallation(new File(gradlePath))
.connect()) {
GradleProject model = connection.getModel(GradleProject.class);
return model.getChildren().stream().map(GradleProject::getName).collect(Collectors.toList());
} catch (Exception e) {
log.error("代码: {} 模块扫描失败, 异常详情: {}", codePath, ExceptionUtils.getRootCauseMessage(e));
throw new ServiceException("模块扫描失败");
} finally {
System.setProperty(SystemPropertiesConstant.JAVA_HOME, originJavaHome);
}
}
}