graal java
引用:博客上的专题图片可以在flickr上找到,并由Luca Galli创建。 以下部分之一中的图像也可以在flickr上找到,并由fklv(过时的时髦)创建。
GraalVM编译器替代了HotSpot的服务器端JIT编译器 ,该编译 器被广泛称为C2编译器 。 它是用Java编写的,目的是与C2编译器相比,具有更好的性能(除其他目标外)。 从Java 9开始的新更改意味着现在有了JVMCI ,我们就可以将自己的手写C2编译器插入JVM。 Oracle实验室的研究人员和工程师)创建了启用JVMCI的JDK8变种,可用于构建GraalVM编译器 。 GraalVM编译器是开源的, 可在GitHub上使用 (以及构建GraalVM编译器所需的HotSpot JVMCI源代码 )。 这使我们能够分叉/克隆它并构建我们自己的GraalVM编译器版本。
在本文中,我们将在CircleCI上使用JDK8构建GraalVM编译器 。 产生的工件将是:
–嵌入GraalVM编译器的JDK8 ,以及
–包含Graal&Truffle模块/组件的zip存档。
注意:本文不讨论如何构建整个GraalVM套件,这可以通过另一篇文章来完成。 尽管可以使用这些脚本,但是存在一个 包含其余步骤 的 分支。
为什么要使用CI工具来构建GraalVM编译器?
持续集成(CI)和持续部署(CD)工具有很多好处。 最大的功能之一就是能够检查代码库的运行状况。 了解构建失败的原因,可以为您提供更快进行修复的机会。 对于这个项目,重要的是我们能够验证和验证在本地和Docker容器中构建用于Linux和macOS的GraalVM编译器所需的脚本。
CI / CD工具使我们可以添加自动化测试,以确保在合并每个PR时,可以从脚本中获得所需的结果。 除了确保我们的新代码不会带来重大变化之外,CI / CD工具的另一个重要功能是我们可以自动化二进制文件的创建和这些二进制文件的自动部署,从而使它们可用于开源分发。
让我们开始吧
在研究CircleCI作为构建GraalVM编译器的CI / CD解决方案的过程中,我了解到可以通过两种不同的方法来运行构建,即:
–使用标准Docker容器的CircleCI构建(更长的构建时间,更长的配置脚本)
–带有预先构建的,优化的Docker容器的CircleCI构建(更短的构建时间,更短的配置脚本)
现在,我们将通过上面提到的两种方法,来了解两种方法的利弊。
方法1:使用标准Docker容器
对于这种方法,CircleCI需要一个Docker映像,该映像可在Docker Hub或它有权访问的另一个公共/私有注册表中使用。 为了成功构建,我们将必须在此可用环境中安装必要的依赖项。 我们希望构建第一次运行会更长,并且根据缓存级别的不同,它将加快运行速度。
要了解如何做到这一点,我们将通过CircleCI配置文件中去节逐节(存储在.circleci / circle.yml),见config.yml在.circleci的完整列表,请参阅提交df28ee7为源变化。
解释配置文件的各个部分
配置文件中的以下几行将确保缓存我们已安装的应用程序(指两个特定的目录),这样我们就不必在每次构建时都重新安装依赖项:
dependencies:
cache_directories:
- "vendor/apt"
- "vendor/apt/archives"
我们将通过其全名来引用Docker映像(在http://hub.docker.com上可使用所使用的帐户名– acceptopenjdk )。 在这种情况下,这是一个包含JDK8的标准docker映像,由Adopt OpenJDK构建场背后的好人提供。 从理论上讲,只要支持构建过程,我们就可以使用任何图像。 它将充当我们将在其上安装必要依赖项的基础层:
docker:
- image: adoptopenjdk/openjdk8:jdk8u152-b16
接下来,在预安装Os依赖项步骤中,我们将还原缓存(如果已经存在),这看起来可能有些奇怪,但是对于唯一的键标签, docs建议使用以下实现):
- restore_cache:
keys:
- os-deps-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
- os-deps-{{ arch }}-{{ .Branch }}
然后,在“ 安装操作系统依赖项”步骤中,我们运行相应的Shell脚本来安装所需的依赖项。 如果操作完成时间超过2分钟,则我们将此步骤设置为超时(请参阅docs以了解超时信息 ):
- run:
name: Install Os dependencies
command: ./build/x86_64/linux_macos/osDependencies.sh
timeout: 2m
然后,在随后的“ 安装Os依赖项”步骤中,我们保存上一步的结果–上述运行步骤中的层(键名的格式设置为确保唯一性,并且包括要保存的特定路径):
- save_cache:
key: os-deps-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- vendor/apt
- vendor/apt/archives
然后,在通过脚本构建并安装make的预步骤中,如果缓存已经存在,我们将还原缓存:
- restore_cache:
keys:
- make- 382 -{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
- make- 382 -{{ arch }}-{{ .Branch }}
然后,在“ 通过脚本构建并安装make”步骤中,我们运行shell脚本以安装特定版本的make ,如果该步骤花费的时间超过1分钟,则将其设置为超时:
- run:
name: Build and install make via script
command: ./build/x86_64/linux_macos/installMake.sh
timeout: 1m
然后,在通过脚本构建并安装make步骤中,我们将上述操作的结果保存到缓存中:
- save_cache:
key: make- 382 -{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- /make- 3.82 /
- /usr/bin/make
- /usr/local/bin/make
- /usr/share/man/man1/make. 1 .gz
- /lib/
然后,我们定义环境变量以在运行时更新JAVA_HOME和PATH 。 这里是环境变量的来源,以便我们在后续的后续步骤中记住它们,直到构建过程结束为止(请记住这一点):
- run:
name: Define Environment Variables and update JAVA_HOME and PATH at Runtime
command: |
echo '....' <== a number of echo-es displaying env variable values
source ${BASH_ENV}
然后,在显示硬件,软件,运行时环境和依赖项版本的步骤中 ,作为最佳实践,我们显示特定于环境的信息,并将其记录到日志中以供后代使用(在出错时进行调试时也很有用):
- run:
name: Display HW, SW, Runtime env. info and versions of dependencies
command: ./build/x86_64/linux_macos/lib/displayDependencyVersion.sh
然后,我们运行设置MX的步骤-从GraalVM编译器 ( mx )的角度来看,这一点很重要,该编译器是一个专门构建的系统,旨在帮助编译和构建Graal / GraalVM及其组件:
- run:
name: Setup MX
command: ./build/x86_64/linux_macos/lib/setupMX.sh ${BASEDIR}
然后,如果该过程花费了超过15分钟的时间(没有任何输出)或者该过程总共花费了超过20分钟的时间,那么我们将执行重要的步骤以构建JDK JVMCI (在此处启用了JVMCI的情况下构建JDK)并超时:
- run:
name: Build JDK JVMCI
command: ./build/x86_64/linux_macos/lib/build_JDK_JVMCI.sh ${BASEDIR} ${MX}
timeout: 20m
no_output_timeout: 15m
然后,我们运行步骤Run JDK JVMCI Tests ,该步骤在构建JDK JVMCI之后运行测试作为健全性检查的一部分:
- run:
name: Run JDK JVMCI Tests
command: ./build/x86_64/linux_macos/lib/run_JDK_JVMCI_Tests.sh ${BASEDIR} ${MX}
然后,我们运行设置环境和Build GraalVM Compiler步骤 ,以使用必要的环境变量来设置构建环境,后续步骤将使用这些变量:
- run:
name: Setting up environment and Build GraalVM Compiler
command: |
echo ">>>> Currently JAVA_HOME=${JAVA_HOME}"
JDK8_JVMCI_HOME= "$(cd ${BASEDIR}/graal-jvmci-8/ && ${MX} --java-home ${JAVA_HOME} jdkhome)"
echo "export JVMCI_VERSION_CHECK='ignore'" >> ${BASH_ENV}
echo "export JAVA_HOME=${JDK8_JVMCI_HOME}" >> ${BASH_ENV}
source ${BASH_ENV}
然后,运行步骤Build GraalVM Compiler并将其嵌入到JDK(启用JVMCI的JDK8)中 ,如果该过程花费了超过7分钟的时间(没有任何输出)或总共花费了10分钟以上的时间来完成,则超时:
- run:
name: Build the GraalVM Compiler and embed it into the JDK (JDK8 with JVMCI enabled)
command: |
echo ">>>> Using JDK8_JVMCI_HOME as JAVA_HOME (${JAVA_HOME})"
./build/x86_64/linux_macos/lib/buildGraalCompiler.sh ${BASEDIR} ${MX} ${BUILD_ARTIFACTS_DIR}
timeout: 10m
no_output_timeout: 7m
然后,在归档工件之前,我们运行简单的健全性检查以验证构建完成后创建的工件的有效性:
- run:
name: Sanity check artifacts
command: |
./build/x86_64/linux_macos/lib/sanityCheckArtifacts.sh ${BASEDIR} ${JDK_GRAAL_FOLDER_NAME}
timeout: 3m
no_output_timeout: 2m
然后,我们运行步骤归档工件 (意味着将最终工件压缩并将其复制到一个单独的文件夹中),如果该过程花费了超过2分钟的时间却没有任何输出或总共花费了超过3分钟的时间来完成,则会超时:
- run:
name: Archiving artifacts
command: |
./build/x86_64/linux_macos/lib/archivingArtifacts.sh ${BASEDIR} ${MX} ${JDK_GRAAL_FOLDER_NAME} ${BUILD_ARTIFACTS_DIR}
timeout: 3m
no_output_timeout: 2m
为了后代和调试目的,我们从各个文件夹中捕获生成的日志并将其存档:
- run:
name: Collecting and archiving logs (debug and error logs)
command: |
./build/x86_64/linux_macos/lib/archivingLogs.sh ${BASEDIR}
timeout: 3m
no_output_timeout: 2m
when: always
- store_artifacts:
name: Uploading logs
path: logs/
最后,我们将生成的工件存储在指定的位置–以下几行将使该位置在CircleCI界面上可用(我们可以从此处下载工件):
- store_artifacts:
name: Uploading artifacts in jdk8-with-graal-local
path: jdk8-with-graal-local/
方法2:使用预先构建的优化Docker容器
对于方法2,我们将使用预先构建的Docker容器,该容器已在本地创建并构建了所有必要的依赖项,保存了Docker映像,然后将其推送到远程注册表(例如Docker Hub) 。 然后,我们将通过配置文件在CircleCI环境中引用此docker映像。 这为我们节省了时间和精力来运行所有命令来安装必要的依赖关系,以创建用于此方法的必要环境(请参见方法1部分中的详细步骤)。
我们希望该构建与之前的构建相比可以运行更短的时间,并且这种加速是预先构建的docker映像的结果(我们将在构建预先构建的docker映像的步骤部分中看到),以了解如何这个做完了)。 额外的速度优势来自CircleCI缓存docker映像层的事实,这反过来又导致了构建环境的更快启动。
我们将通过CircleCI配置文件中去节逐节(存储在.circleci / circle.yml)这种方法,请参阅config.yml在.circleci的完整列表,请参阅提交e5916f1为源的变化。
解释配置文件的各个部分
再次在这里,我们将通过全名引用docker映像。 这是neomatrix369提供的预构建的docker映像neomatrix369 / graalvm-suite- jdk8 。 在CircleCI构建开始之前,它已预先构建并上传到Docker Hub 。 它包含用于构建GraalVM编译器的必要依赖项:
docker:
- image: neomatrix369/graal-jdk8:${IMAGE_VERSION:-python- 2.7 }
steps:
- checkout
下面的所有部分执行与方法1完全相同的任务(并出于相同的目的),请参阅解释配置文件部分的部分。
除此之外,我们删除了以下部分,因为方法2不再需要它们:
- restore_cache:
keys:
- os-deps-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
- os-deps-{{ arch }}-{{ .Branch }}
- run:
name: Install Os dependencies
command: ./build/x86_64/linux_macos/osDependencies.sh
timeout: 2m
- save_cache:
key: os-deps-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- vendor/apt
- vendor/apt/archives
- restore_cache:
keys:
- make- 382 -{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
- make- 382 -{{ arch }}-{{ .Branch }}
- run:
name: Build and install make via script
command: ./build/x86_64/linux_macos/installMake.sh
timeout: 1m
- save_cache:
key: make- 382 -{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- /make- 3.82 /
- /usr/bin/make
- /usr/local/bin/make
- /usr/share/man/man1/make. 1 .gz
在下一节中,我将逐步介绍如何构建预构建的Docker映像。 这将涉及运行bash脚本-./build/x86_64/linux_macos/osDependencies.sh和./build/x86_64/linux_macos/installMake.sh,以安装必要的依赖项,作为构建docker映像的一部分。 并且,最后将映像推送到Docker Hub (可以推送到您选择的任何其他远程注册表)。
构建预构建的Docker映像的步骤
–运行build-docker-image.sh (请参阅bash脚本源代码),具体取决于Dockerfile的存在(请参阅docker脚本源代码)。 Dockerfile完成了运行容器内依赖项的所有必要任务,即运行bash脚本./build/x86_64/linux_macos/osDependencies.sh和./build/x86_64/linux_macos/installMake.sh:
$ ./build-docker-image.sh
–成功构建映像后,在设置USER_NAME和IMAGE_NAME (请参阅源代码)之后运行push-graal-docker-image-to-hub.sh ,否则它将使用bash脚本中设置的默认值:
$ USER_NAME= "[your docker hub username]" IMAGE_NAME= "[any image name]" \
./push-graal-docker-image-to-hub.sh
CircleCI配置文件统计信息:方法1与方法2
兴趣范围 | 方法1 | 方法2 |
---|---|---|
配置文件(完整的源列表) | 循环建立 | 使用预构建的docker-image进行构建 |
提交点(SHA) | df28ee7 | e5916f1 |
代码行(位置) | 110线 | 85线 |
源行(sloc) | 110格 | 85座 |
步骤(步骤:部分) | 19 | 15 |
效果(请参见效果部分) | 由于缓存, 速度有所提高,但比方法2慢 | 由于预构建的docker映像以及不同步骤的缓存,因此提速。 比方法1更快的速度 确保已启用DLC分层(其付费功能) |
该怎么办?
方法1的问题
我遇到了最初无法解决的问题,但后来通过更改配置文件或脚本进行了修复:
- 请确保.circleci / config.yml始终位于文件夹的根目录中
- 在.circleci / config.yml文件设置中使用store_artifacts指令时,请将值设置为固定的文件夹名称,即jdk8-with-graal-local / –在我们的示例中,将路径设置为$ {BASEDIR} / project / jdk8构建完成后-with-graal不会创建结果工件,因此建议使用固定的路径名。
- 环境变量:使用环境变量时,请记住,每个命令都在自己的外壳中运行,因此在外壳执行环境内部看不到设置为环境变量的值,请遵循本文所使用的方法。 设置环境变量,以使所有命令都能看到其所需的值,以避免在每个步骤结束时出现异常行为或意外结果。
- 缓存:阅读有关缓存的内容后,请使用缓存功能,有关CircleCI缓存的更多详细信息,请参阅缓存文档 。 在本文中了解如何实现它。 这将有助于避免混淆,还可以更好地利用CircleCI提供的功能。
方法2问题
- 缓存:尝试使用Docker Layer Caching (DLC)选项时,请检查文档,因为它是一项付费功能,一旦知道这一点,对于Docker Layer的“ CircleCI为什么会在每次构建期间继续下载所有层”的疑问将得到澄清。缓存详细信息请参考docs 。 它还可以说明为什么在非付费模式下我的构建仍然不如我期望的那样快。
一般说明:
- 轻量级实例:为避免沉思,我们可以运行重型构建,请查看有关实例技术规格的文档。 如果我们运行标准Linux命令来探查实例的技术规范,我们可能会认为它们是高规格机器而被误导了。 请参阅列出实例的硬件和软件详细信息的步骤(请参阅“ 显示硬件,软件,运行时环境信息和依赖性版本”部分)。 实例实际上是具有2CPU / 4096MB资源的虚拟机或类似容器的环境。 这意味着我们不能像构建GraalVM套件那样运行长期运行或繁重的构建。 也许有另一种方式来处理这类构建,或者可能需要将这些构建分解为较小的部分。
- 全局环境变量:由于config.yml中的每个运行行都在其自己的shell上下文中运行,因此在该上下文中,其他正在执行的上下文所设置的环境变量无法访问这些值。 因此,为了克服这一点,我们采用了两种方法:
- 作为变量作为参数传递给调用bash / shell脚本,以确保脚本能够访问环境变量中的值
- 使用source命令作为运行步骤,以使环境变量可全局访问
最终结果和摘要
构建成功完成后,我们将看到以下屏幕(最后一步,即更新工件,列出了复制工件的位置):
现在,工件被放置在正确的文件夹中以进行下载。 我们主要关注jdk8-with-graal.tar.gz工件。
性能
在写这篇文章之前,我对这两种方法进行了多次测试,并记下了完成构建所花费的时间,如下所示:
– 方法1:标准CircleCI构建(启用缓存)
– 13分28秒
– 13分钟59秒 – 14分钟52秒 – 10分钟38秒 – 10分钟26秒 – 10分钟23秒 – 方法2:使用预先构建的docker映像(启用缓存, DLC )功能不可用) – 13分钟15秒 – 15分钟16秒 – 15分钟29秒 – 15分钟58秒 – 10分钟20秒 – 9分钟49秒
注意:使用付费层时,方法2应该表现出更好的性能,因为该计划中提供了Docker Layer Caching )。
完整性检查
为了确保通过使用以上两种方法,我们实际上已经构建了嵌入GraalVM编译器的有效JDK,我们对创建的工件执行以下步骤:
–首先,从CircleCI仪表板上的“ 工件”选项卡下下载jdk8-with-graal.tar.gz工件(需要登录):
–然后,解压缩.tar.gz文件并执行以下操作:
配置文件(完整的源列表)
–此后,运行以下命令以检查JDK二进制文件是否有效:
配置文件(完整的源列表)
–最后检查是否得到以下输出:
配置文件(完整的源列表)
–同样,要确认JRE是否有效并且内置了GraalVM编译器 ,我们可以这样做:
配置文件(完整的源列表)
–并检查是否获得与上述类似的输出:
配置文件(完整的源列表)
这样,我们就成功地构建了内嵌GraalVM编译器的JDK8 ,并将Graal和Truffle组件捆绑在一个存档文件中,两者均可通过CircleCI界面下载。
注意:您会注意到,在将构建的二进制文件打包到压缩档案中之前,我们确实对其进行了完整性检查,这是构建步骤的一部分(请参阅CircleCI的底部部分的配置文件部分)。
漂亮的徽章!
我们都喜欢炫耀,也想知道我们建造工作的当前状态。 绿色的构建状态图标很好地表示成功,它类似于markdown README页面上的以下内容:
我们可以很容易地嵌入这两个状态标记,以显示我们在CircleCI上构建的项目(特定于分支的,即您创建的主分支或其他分支)的构建状态(请参阅docs ),以了解如何执行此操作。
结论
我们探索了使用CircleCI环境构建GraalVM编译器的两种方法。 他们是比较两种方法的性能以及如何轻松实现它们的良好实验。 我们还看到了许多需要避免或不要做的事情 ,还看到了一些CircleCI功能的有用性。 当尝试进行构建工作或遇到某些问题时,文档和论坛会很好地说明问题。
一旦我们了解了CircleCI环境,就可以轻松使用它,并且每次运行它时都能始终为我们提供完全相同的响应(一致的行为)。 它的短暂性质意味着我们可以保证每次运行前都保持清洁的环境,并在完成后进行清理。 我们还可以对构建的每个步骤进行构建时间检查,如果完成一个步骤所花费的时间超过了阈值时间周期,则中止构建。
在CircleCI上使用预构建的Docker映像和Docker层缓存的功能可以大大提高性能(为我们节省了在每次构建时重新安装任何必要依赖项所需的构建时间)。 CircleCI可以通过构建步骤的缓存来提高性能,这再次节省了构建时间,因为它们无需更改就无需重新运行相同的步骤。
CircleCI上提供了许多有用的功能,其中包含大量的文档,社区论坛上的每个人都很有帮助,问题很快就会得到解答。
接下来,让我们在另一个构建环境/构建场上进行相同的构建,以及更多的内容-提示,提示,您是否认为与我相同? 采用OpenJDK构建场 )? 我们可以试试看!
感谢并感谢 CircleCI的 Ron Powell 和Oracle Labs的 OlegŠelajev进行了校对并提供了建设性的反馈。
请通过在下面的评论中添加一行或在 @ theNeomatrix369上发布 推文来 告知 我是否有 帮助 ,并且我也欢迎您提供反馈,看看您如何与 我联系 ,首先请查看上面提到的链接。
有用的资源
–链接到有用的CircleCI文档
– 关于入门 | 影片
– 关于Docker – Docker层缓存 – 关于缓存 – 关于通过SSH调试 – CircleCI速查表 – CircleCI社区(讨论) – 最新的社区话题 – CircleCI配置和支持文件 – 方法1: https : //github.com/neomatrix369/awesome-graal/tree/build-on-circleci (配置文件和其他支持文件,例如脚本,目录布局等) – 方法2: https : //github.com/neomatrix369/awesome-graal/tree/build-on-circleci-using-pre-built-docker-container (配置文件和其他支持文件,例如脚本,目录布局等… ) – 在Linux,macOS和Docker容器中构建Graal的脚本 – 松露在圣餐中使用:Graal和Truffle在JVM上进行多语种语言解释 – 学习使用Whally GraalVM! – 用松露打造全盛!
翻译自: https://www.javacodegeeks.com/2019/12/how-to-build-graal-enabled-jdk8-on-circleci.html
graal java