如何在CircleCI上构建支持Graal的JDK8?
引用:博客上的專題圖片可以在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 Hub或其他有權訪問的公共/私有注冊表中使用的Docker映像。 為了成功構建,我們將必須在此可用環境中安裝必要的依賴項。 我們希望構建第一次運行會更長,并且根據緩存的級別,它將加快運行速度。
要了解如何做到這一點,我們將通過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.shCircleCI配置文件統計信息:方法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 )上建立的項目的構建狀態(特定于分支的,即master或您創建的另一個分支)(有關如何執行此操作)。
結論
我們探索了兩種使用CircleCI環境構建GraalVM編譯器的方法。 他們是比較兩種方法的性能以及如何輕松實現它們的良好實驗。 我們還看到了許多需要避免或不要做的事情 ,還看到了一些CircleCI功能的有用性。 當嘗試進行構建工作或遇到某些問題時,文檔和論壇會很好地說明問題。
一旦我們了解了CircleCI環境,就可以輕松使用它,并且每次運行它時,都能始終給我們完全相同的響應(一致的行為)。 它的短暫性質意味著我們可以保證在每次運行之前都保持干凈的環境,并在完成之后進行清理。 我們還可以為構建的每個步驟設置構建時間檢查,如果完成某個步驟所花費的時間超過了閾值時間周期,則可以中止構建。
在CircleCI上使用預構建的Docker映像和Docker Layer Caching的能力可以極大地提高性能(為我們節省了在每次構建時重新安裝任何必要依賴項所需的構建時間)。 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
總結
以上是生活随笔為你收集整理的如何在CircleCI上构建支持Graal的JDK8?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CUBA平台:TypeScript SD
- 下一篇: lucene自动补全_使用自动机的Luc