Bazel
google开源的一个多语言编译工具,可以编译cpp、go、java等语言,通过定义WORKSPACE和BUILD文件进行编译。 官网地址如下: docs.bazel.build/versions/4.… 使用bazel编译大的代码仓库会有比较多的好处,官网有更具体的介绍,而java使用bazel编译,由于文章比较少,由于工作需要,记录一下一个springboot项目的编译过程
基于bazel的Linux远程开发环境
创建springboot项目
通过idea的spring初始化项目
该项目主要使用springboot + lombok,所以参考maven如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.haokaizhao</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
复制代码
添加相应实现代码
代码结构如下
// HelloController.java
@RestController
public class HelloController {
@Autowired
private HelloService service;
@GetMapping("/hello")
public HelloDTO hello() {
return service.hello();
}
}
复制代码
// HelloDTO.java
@Data
@Accessors(chain = true)
public class HelloDTO {
private String userName;
private String password;
private String msg;
}
复制代码
// HelloService.java
@Service
public class HelloService {
public HelloDTO hello() {
return new HelloDTO()
.setMsg("hello")
.setPassword("123456")
.setUserName("hk");
}
}
复制代码
// HelloServiceTest.java
@SpringBootTest
public class HelloServiceTest {
@Autowired
private HelloService service;
@Test
public void testHello() {
HelloDTO hello = service.hello();
Assert.notNull(hello);
Assertions.assertEquals("123456", hello.getPassword());
}
}
复制代码
添加完相应代码后,运行项目
接口也有相应返回
bash-4.4# curl http://127.0.0.1:8080/hello
{"userName":"hk","password":"123456","msg":"hello"}
复制代码
单测也能够pass
基于Bazel改造maven项目
首先参考bazel的教程的java-maven项目,地址如下:github.com/bazelbuild/… 的java-maven项目,主要关注两个文件,一个WORKSPACE文件,一个BUILD文件
WORKSPACE文件
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
RULES_JVM_EXTERNAL_TAG = "2.5"
RULES_JVM_EXTERNAL_SHA = "249e8129914be6d987ca57754516be35a14ea866c616041ff0cd32ea94d2f3a1"
http_archive(
name = "rules_jvm_external",
sha256 = RULES_JVM_EXTERNAL_SHA,
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
artifacts = [
"junit:junit:4.12",
"com.google.guava:guava:28.0-jre",
],
fetch_sources = True,
repositories = [
"http://uk.maven.org/maven2",
"https://jcenter.bintray.com/",
],
)
复制代码
BUILD文件
load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test")
package(default_visibility = ["//visibility:public"])
java_library(
name = "java-maven-lib",
srcs = glob(["src/main/java/com/example/myproject/*.java"]),
deps = ["@maven//:com_google_guava_guava"],
)
java_binary(
name = "java-maven",
main_class = "com.example.myproject.App",
runtime_deps = [":java-maven-lib"],
)
java_test(
name = "tests",
srcs = glob(["src/test/java/com/example/myproject/*.java"]),
test_class = "com.example.myproject.TestApp",
deps = [
":java-maven-lib",
"@maven//:com_google_guava_guava",
"@maven//:junit_junit",
],
)
复制代码
我们先看WORKSPACE文件,参考官网给出的定义
直白点说,WORKSPACE一个项目只会有一个这样的文件,他必须在该项目的跟目录中,用于标识Bazel的工作区,执行bazel的功能,同时所有外部依赖共用一个WORKSPACE。也就是说我们现在需要的maven依赖可以在WORKSPACE中声明。 @load(),作用就是从远程rule的仓库中或者本地写的一些rule中,加载一个自定义的功能。bazel是可以自定义一些功能的实现的,可以自定义bzl文件,类似于以下实现,实现的语言是python的一个子集,这一部分不做介绍(没怎么写过)具体连接如下:docs.bazel.build/versions/4.…
def fct(name, srcs):
filtered_srcs = my_filter(source = srcs)
native.cc_library(
name = name,
srcs = filtered_srcs,
testonly = True,
)
复制代码
java-maven的WORKSPACE文件的作用就是从远程仓库load了一个maven_install的这个功能,用于maven依赖的拉取。
然后再看BUILD文件,BUILD文件其实就是类似CMAKE的一种形式吧,声明哪个文件依赖于哪个文件,然后进行编译,用maven_install拉取下来的依赖需要用@maven//:标识 上述的BUILD文件包含3个功能 java_library: 声明了项目中所有依赖到的文件 java_binary: 声明了项目需要编译的一个二进制文件,也就是可执行文件(类似于cmake的add_executable) java_test: 声明了项目中的测试文件
简单了解了bazel的机制之后,我们来改造WORKSPACE和BUILD 根据maven的依赖,我们来改造WORKSPACE文件
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
RULES_JVM_EXTERNAL_TAG = "2.5"
RULES_JVM_EXTERNAL_SHA = "249e8129914be6d987ca57754516be35a14ea866c616041ff0cd32ea94d2f3a1"
http_archive(
name = "rules_jvm_external",
sha256 = RULES_JVM_EXTERNAL_SHA,
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
artifacts = [
"org.springframework.boot:spring-boot-starter-web:2.6.2",
"org.springframework.boot:spring-boot-starter-test:2.6.2",
"org.projectlombok:lombok:1.18.22"
],
fetch_sources = True,
repositories = [
"http://uk.maven.org/maven2",
"https://jcenter.bintray.com/",
],
)
复制代码
将pom中声明的3个依赖添加到maven_install的artifacts中
然后构造BUILD文件,也是同样的过程将我们代码中的依赖通过deps的参数声明上去
load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test")
package(default_visibility = ["//visibility:public"])
java_library(
name = "java-maven-lib",
srcs = glob(["src/main/java/com/hk/demo/*.java",
"src/main/java/com/hk/demo/controller/*.java",
"src/main/java/com/hk/demo/service/*.java",
"src/main/java/com/hk/demo/dao/*.java"]),
deps = ["@maven//:org_springframework_boot_spring_boot_starter_web",
"@maven//:org_projectlombok_lombok",
],
)
java_binary(
name = "java-maven",
main_class = "com.hk.demo.DemoApplication",
runtime_deps = [":java-maven-lib"],
)
java_test(
name = "tests",
srcs = glob(["src/test/java/com/hk/demo/*.java"]),
test_class = "com.hk.demo.DemoApplicationTests",
deps = [
":java-maven-lib",
"@maven//:org_springframework_boot_spring_boot_starter_test",
],
)
复制代码
然后执行编译
#在WORKSPACE的同级目录下
bash-4.4# tree
.
|-- BUILD
|-- HELP.md
|-- WORKSPACE
bash-4.4# bazel run //:java-maven
复制代码
bazel的编译结果如下:
//部分报错结果如下
@SpringBootApplication
^
src/main/java/com/hk/demo/DemoApplication.java:10: error: [strict] Using type org.springframework.boot.SpringApplication from an indirect dependency (TOOL_INFO: "external/maven/v1/https/jcenter.bintray.com/org/springframework/boot/spring-boot/2.6.2/spring-boot-2.6.2.jar").
SpringApplication.run(DemoApplication.class, args);
^
src/main/java/com/hk/demo/controller/HelloController.java:9: error: [strict] Using type org.springframework.web.bind.annotation.RestController from an indirect dependency (TOOL_INFO: "external/maven/v1/https/jcenter.bintray.com/org/springframework/spring-web/5.3.14/spring-web-5.3.14.jar").
@RestController
^
src/main/java/com//demo/controller/HelloController.java:13: error: [strict] Using type org.springframework.beans.factory.annotation.Autowired from an indirect dependency (TOOL_INFO: "external/maven/v1/https/jcenter.bintray.com/org/springframework/spring-beans/5.3.14/spring-beans-5.3.14.jar").
@Autowired
^
src/main/java/com/hk/demo/service/HelloService.java:6: error: [strict] Using type org.springframework.stereotype.Service from an indirect dependency (TOOL_INFO: "external/maven/v1/https/jcenter.bintray.com/org/springframework/spring-context/5.3.14/spring-context-5.3.14.jar").
@Service
复制代码
经过查看报错信息,间接依赖对bazel来说也是要声明的,所以修改BUILD文件 将报错信息中的那些缺失jar包补充到deps中
load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test")
package(default_visibility = ["//visibility:public"])
java_library(
name = "java-maven-lib",
srcs = glob(["src/main/java/com/hk/demo/*.java",
"src/main/java/com/hk/demo/controller/*.java",
"src/main/java/com/hk/demo/service/*.java",
"src/main/java/com/hk/demo/dao/*.java"]),
deps = ["@maven//:org_springframework_boot_spring_boot_starter_web",
"@maven//:org_springframework_boot_spring_boot",
"@maven//:org_springframework_boot_spring_boot_autoconfigure",
"@maven//:org_projectlombok_lombok",
"@maven//:org_springframework_spring_beans",
"@maven//:org_springframework_spring_web",
"@maven//:org_springframework_spring_context"
],
)
java_binary(
name = "java-maven",
main_class = "com.hk.demo.DemoApplication",
runtime_deps = [":java-maven-lib"],
)
java_test(
name = "tests",
srcs = glob(["src/test/java/com/hk/demo/*.java"]),
test_class = "com.hk.demo.DemoApplicationTests",
deps = [
":java-maven-lib",
"@maven//:org_springframework_boot_spring_boot_starter_test",
],
)
复制代码
我们再执行编译,结果如下
src/main/java/com/hk/demo/service/HelloService.java:11: error: cannot find symbol
.setMsg("hello")
^
symbol: method setMsg(String)
location: class HelloDTO
Target //:java-maven failed to build
复制代码
其实这里是挺坑的,因为lombok这个库,其实是一个改字节码的库,就是说通过添加注解的方式去实现生成的class文件有对应的功能的实现,maven编译的情况下我们是不需要做什么处理的,但是在bazel下,就得做一些特殊处理,不然生成的class是没有对应的功能的,这里有两种解决办法,另外一种我没有尝试,具体解决方法如下: github.com/bazelbuild/… 有兴趣的可以去看一下通过定义bzl的解决方式
根据issue中的解决办法,修改BUILD文件
load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test")
package(default_visibility = ["//visibility:public"])
java_import(
name = "lombok_jar",
jars = [
"@maven//:v1/https/jcenter.bintray.com/org/projectlombok/lombok/1.18.22/lombok-1.18.22.jar"
],
)
java_plugin(
name = "lombok_plugin",
processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor",
deps = [
":lombok_jar",
],
)
java_library(
name = "lombok",
exports = [
"@maven//:org_projectlombok_lombok",
],
exported_plugins = [
":lombok_plugin"
],
)
java_library(
name = "java-maven-lib",
srcs = glob(["src/main/java/com/hk/demo/*.java",
"src/main/java/com/hk/demo/controller/*.java",
"src/main/java/com/hk/demo/service/*.java",
"src/main/java/com/hk/demo/dao/*.java"]),
deps = ["@maven//:org_springframework_boot_spring_boot_starter_web",
"@maven//:org_springframework_boot_spring_boot",
"@maven//:org_springframework_boot_spring_boot_autoconfigure",
":lombok",
"@maven//:org_springframework_spring_beans",
"@maven//:org_springframework_spring_web",
"@maven//:org_springframework_spring_context"
],
)
java_binary(
name = "java-maven",
main_class = "com.hk.demo.DemoApplication",
runtime_deps = [":java-maven-lib"],
)
java_test(
name = "tests",
srcs = glob(["src/test/java/com/hk/demo/*.java"]),
test_class = "com.hk.demo.DemoApplicationTests",
deps = [
":java-maven-lib",
"@maven//:org_springframework_boot_spring_boot_starter_test",
],
)
复制代码
这种方式其实就是指定了一个在java编译时运行的一个插件,插件通过process_class指定,然后就可以解决之前bazel编译出来的class没有@Data的功能的问题。具体的java的rules的作用参考这个: docs.bazel.build/versions/ma…
我们再执行编译
bash-4.4# bazel run //:java-maven
INFO: Analyzed target //:java-maven (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:java-maven up-to-date:
bazel-bin/java-maven.jar
bazel-bin/java-maven
INFO: Elapsed time: 0.050s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Build completed successfully, 1 total action
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.2)
2022-01-16 10:10:29.067 INFO 43591 --- [ main] com.haokaizhao.demo.DemoApplication : Starting DemoApplication using Java 1.8.0_312 on 3275efc5b377 with PID 43591 (/usr/.cache/bazel/_bazel_root/d53f91814e84f121c225441a8f9fbc52/execroot/__main__/bazel-out/k8-fastbuild/bin/libjava-maven-lib.jar started by root in /usr/.cache/bazel/_bazel_root/d53f91814e84f121c225441a8f9fbc52/execroot/__main__/bazel-out/k8-fastbuild/bin/java-maven.runfiles/__main__)
2022-01-16 10:10:29.069 INFO 43591 --- [ main] com.haokaizhao.demo.DemoApplication : No active profile set, falling back to default profiles: default
2022-01-16 10:10:29.753 INFO 43591 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-01-16 10:10:29.761 INFO 43591 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-01-16 10:10:29.762 INFO 43591 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.56]
2022-01-16 10:10:29.805 INFO 43591 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-01-16 10:10:29.805 INFO 43591 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 695 ms
2022-01-16 10:10:30.059 INFO 43591 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-01-16 10:10:30.067 INFO 43591 --- [ main] com.hk.demo.DemoApplication : Started DemoApplication in 1.273 seconds (JVM running for 1.498)
复制代码
然后就跑成功了~
这样就完成了一个maven项目到bazel项目的改造~
总结
bazel的编译速度确实是要比maven快的,但是bazel-java的环境或者是一些问题的解决都比较少,需要不断的试才能试出来,学习成本比maven要大很多。不过bazel确实是个很强大的编译工具