為了確保我們commit到GitHub上的Rust程式是可以正常工作的,我們可以替程式專案加入CI(持續整合,Continuous Integration)。Rust的crates.io線上套件庫也可以放上各種CI的狀態徽章(badge),提供開發者在選用套件時一個參考,避免用到連套件作者自己寫的測試都測不過的套件。Travis CI是對於Rust程式語言親和力比較高的CI,這篇文章將會針對Rust的Cargo程式專案來介紹Travis CI的設定檔撰寫方式。
Travis CI支援Linux、macOS和Windows作業系統,換句話說,它可以協助開發者在commit程式到GitHub上後,自動把程式拿到這三個系統上做測試。開發者就不用自己弄台實體機器或是虛擬機器來安裝這些系統來測試程式啦!Travis CI可以免費使用在GitHub的開源專案上,若要用在GitHub的閉源專案上,就得使用它的付費(pro)版本。
Travis CI的開源專案網站:
Travis CI的付費版網站:
註冊Travis CI的開源專案帳號
進入Travis CI的開源專案網站後,按下Sign Up
即可註冊。
接著要確認提供GitHub帳號的權限給Travis CI。
如此一來Travis CI帳號就註冊成功了!以後只要使用GitHub帳號就可以登入。
加入GitHub上的倉庫(Repository)
在Travis CI的帳號頁面中,可以按下Sync Account
來同步GitHub上的倉庫,注意按下之後頁面可能不會跟著刷新,可以手動重新整理頁面,或是直接使用搜尋功能。
在GitHub倉庫的列表中,可以藉由撥動開關來設定該GitHub倉庫是否要使用Travis CI。
替Cargo程式專案加上Travis CI的設定檔
Travis CI會去讀取GitHub倉庫下的.travis.yml
檔案來執行測試程式專案的腳本。這個.travis.yml
檔案的基本撰寫方式如下:
language: rust
rust:
- stable
- beta
- nightly
os:
- linux
- osx
- windows
以上的設定,會讓Travis CI在Linux、macOS和Windows作業系統上,以不同的容器(container)分別使用最新的Rust Stable、Beta和Nightly版本來測試程式專案。(所以這個設定會有9個容器。)
我們可以在Travis CI的網頁上直接看到不同腳本運行的即時情況。
替Cargo程式專案加上Travis CI的徽章
為了要在crates.io上顯示出如下的Travis CI的徽章:
我們可以在Cargo.toml
設定檔中加上[badges.travis-ci]
區塊,並設定其中的repository
和branch
項目。舉例來說,若我要顯示GitHub上magiclen/rust-short-crypt
這個倉庫的Travis CI狀態,[badges.travis-ci]
區塊的撰寫方式如下:
[badges.travis-ci]
repository = "magiclen/rust-short-crypt"
branch = "master"
手動觸發測試腳本
在一般情況下,GitHub倉庫只要有新的commit,Travis CI就會被觸發而開始執行測試腳本。不過如果想要手動觸發的話,可以在Travis CI上的GitHub倉庫頁面中,或是Dashboard
頁面中,按下Trigger a build
。
手動觸發腳本時,可以自行決定要使用的分支(branch)和commit訊息(用來辨識用,可以不輸入)。最重要的是,它可以無視.travis.yml
設定檔的設定,而用臨時的設定值(與.travis.yml
一樣的格式)來運行腳本。
進階的.travis.yml
設定檔撰寫方式
指定Rust編譯器的版本
Travis CI除了可以使用最新的Rust Stable、Beta和Nightly版本外,還可以直接指定版本號碼。
例如要使用1.35.0的穩定版、2019-07-23的Beta版,和2019-07-25的Nightly版,.travis.yml
設定檔的寫法如下:
language: rust
rust:
- 1.35.0
- beta-2019-07-23
- nightly-2019-07-25
修改Travis CI所執行的指令
在預設的情況下,Travis CI只會使用以下指令來執行Cargo程式專案的測試:
cargo test --verbose
如果要修改Travis CI在運行腳本時會執行的指令,可以在.travis.yml
設定檔加上script
這個設定項目。例如要確保在運行測試時只使用一個執行緒,設定檔撰寫方式如下:
language: rust
script:
- cargo test --verbose -- --test-threads=1
這個script
設定項目所設定的指令,會以從上到下的順序來執行。當然,如果有需要的話,我們也可以使用cargo
之外的指令。
指定環境變數
在.travis.yml
設定檔加上env
這個設定項目,可以設定Travis CI容器在運行時的環境變數,而每個環境變數設定,都會建立出新的容器。
例如:
language: rust
env:
-
- FOO=bar BAR=foo
- BAR=foo
如果需要指定能夠用在所有容器的環境變數,寫法如下:
language: rust
env:
global:
- GLOBAL=foobar
matrix:
-
- FOO=bar BAR=foo
- BAR=foo
指定多種不同的特色組合
雖然我們可以利用script
這個設定項目在同一個Travis CI容器內執行多次的cargo test
來測試多種不同的特色組合,例如:
language: rust
script:
- cargo test --verbose
- cargo test --verbose --no-default-features
- cargo test --verbose --no-default-features --features std
但這樣我們就不太方便知道究竟是哪行指令的測試出了問題。利用env
設定項目,我們可以將以上設定檔改寫為:
language: rust
env:
-
- NO_DEFAULT_FEATURES=1
- NO_DEFAULT_FEATURES=1 FEATURES="std"
script:
- if [ "$NO_DEFAULT_FEATURES" = "1" ]; then NO_DEFAULT_FEATURES="--no-default-features"; else NO_DEFAULT_FEATURES=""; fi
- cargo test --verbose $NO_DEFAULT_FEATURES --features "$FEATURES"
如此一來就可以區分不同的容器來運行不同特色的測試了!
在開始測試程式專案前先安裝必要的函式庫
在.travis.yml
設定檔加上install
這個設定項目,可以讓Travis CI在測試程式專案前,先去執行某些指令。由於Rust程式時常會用到外部的C/C++函式庫,因此我們也可以利用這個install
設定項目來執行安裝相依函式庫所要執行的指令。
例如以下設定,可以在Linux和macOS的環境中安裝支援HDRI和WebP的ImageMagick。
language: rust
os:
- linux
- osx
install:
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt update ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt install libwebp-dev ; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update ; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew list webp || brew install webp ; fi
- wget http://www.imagemagick.org/download/ImageMagick.tar.gz
- tar xf ImageMagick.tar.gz
- mkdir /tmp/ImageMagick-lib
- cd ImageMagick*
- ./configure --enable-hdri --with-webp
- make
- sudo make install
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo ldconfig ; fi
Travis CI內建的TRAVIS_OS_NAME
環境變數所儲存的值及為os
設定項目所使用的值,用來表示當前容器是用哪個作業系統。
指定不同的目標
Travis CI的Linux預設的編譯目標為x86_64-unknown-linux-gnu
,Windows預設的編譯目標為x86_64-pc-windows-msvc
。然而在Linux上也很常用musl libc來取代gcc,在Windows上有時也會用gcc來取代微軟的Visual C++。
若要指定新的目標來測試,可以在.travis.yml
設定檔加上matrix
設定項目,再去設定其中的include
設定項目。
完整的.travis.yml
設定檔寫法如下:
language: rust
rust:
- stable
- beta
- nightly
os:
- linux
- osx
- windows
matrix:
include:
- rust: stable
os: linux
env: TARGET=x86_64-unknown-linux-musl
install: rustup target add $TARGET
script: cargo test --verbose --target $TARGET
- rust: beta
os: linux
env: TARGET=x86_64-unknown-linux-musl
install: rustup target add $TARGET
script: cargo test --verbose --target $TARGET
- rust: nightly
os: linux
env: TARGET=x86_64-unknown-linux-musl
install: rustup target add $TARGET
script: cargo test --verbose --target $TARGET
- rust: stable
os: windows
env: TARGET=x86_64-pc-windows-gnu
install:
- rustup set default-host $TARGET
- rustup default $TRAVIS_RUST_VERSION
- rustup target add $TARGET
- mkdir -p ~/.cargo
- printf '\n[target.'$TARGET']\nlinker = "x86_64-w64-mingw32-gcc"\nar = "x86_64-w64-mingw32-ar"\n' >> ~/.cargo/config
script: cargo test --verbose --target $TARGET
- rust: beta
os: windows
env: TARGET=x86_64-pc-windows-gnu
install:
- rustup set default-host $TARGET
- rustup default $TRAVIS_RUST_VERSION
- rustup target add $TARGET
- mkdir -p ~/.cargo
- printf '\n[target.'$TARGET']\nlinker = "x86_64-w64-mingw32-gcc"\nar = "x86_64-w64-mingw32-ar"\n' >> ~/.cargo/config
script: cargo test --verbose --target $TARGET
- rust: nightly
os: windows
env: TARGET=x86_64-pc-windows-gnu
install:
- rustup set default-host $TARGET
- rustup default $TRAVIS_RUST_VERSION
- rustup target add $TARGET
- mkdir -p ~/.cargo
- printf '\n[target.'$TARGET']\nlinker = "x86_64-w64-mingw32-gcc"\nar = "x86_64-w64-mingw32-ar"\n' >> ~/.cargo/config
script: cargo test --verbose --target $TARGET
不過由於使用musl時很有可能會使用到musl-gcc
,所以如果需要的話還要先安裝musl-tools
這個Linux套件。另外,許多套件在需要使用C編譯器來編譯程式的時候,會去讀取CC
環境變數,但Travis CI的Windows環境,預設並不會去設定CC
環境變數,所以很多套件就會去使用通用的cc
指令來編譯C語言程式,然而,Travis CI的Windows環境也沒有cc
指令可以用,也就是說,這時候我們就得透過設定CC
環境變數來設定要使用的C語言編譯器。在編譯目標為x86_64-pc-windows-gnu
時,建議就使用gcc編譯器作為C語言編譯器,正好Travis CI的Windows環境就有提供gcc
指令。
此時的完整.travis.yml
設定檔寫法如下:
language: rust
rust:
- stable
- beta
- nightly
os:
- linux
- osx
- windows
matrix:
include:
- rust: stable
os: linux
env: TARGET=x86_64-unknown-linux-musl
install:
- sudo apt update
- sudo apt install musl-tools
- rustup target add $TARGET
script: cargo test --verbose --target $TARGET
- rust: beta
os: linux
env: TARGET=x86_64-unknown-linux-musl
install:
- sudo apt update
- sudo apt install musl-tools
- rustup target add $TARGET
script: cargo test --verbose --target $TARGET
- rust: nightly
os: linux
env: TARGET=x86_64-unknown-linux-musl
install:
- sudo apt update
- sudo apt install musl-tools
- rustup target add $TARGET
script: cargo test --verbose --target $TARGET
- rust: stable
os: windows
env: TARGET=x86_64-pc-windows-gnu CC=gcc
install:
- rustup set default-host $TARGET
- rustup default $TRAVIS_RUST_VERSION
- rustup target add $TARGET
- mkdir -p ~/.cargo
- printf '\n[target.'$TARGET']\nlinker = "x86_64-w64-mingw32-gcc"\nar = "x86_64-w64-mingw32-ar"\n' >> ~/.cargo/config
script: cargo test --verbose --target $TARGET
- rust: beta
os: windows
env: TARGET=x86_64-pc-windows-gnu CC=gcc
install:
- rustup set default-host $TARGET
- rustup default $TRAVIS_RUST_VERSION
- rustup target add $TARGET
- mkdir -p ~/.cargo
- printf '\n[target.'$TARGET']\nlinker = "x86_64-w64-mingw32-gcc"\nar = "x86_64-w64-mingw32-ar"\n' >> ~/.cargo/config
script: cargo test --verbose --target $TARGET
- rust: nightly
os: windows
env: TARGET=x86_64-pc-windows-gnu CC=gcc
install:
- rustup set default-host $TARGET
- rustup default $TRAVIS_RUST_VERSION
- rustup target add $TARGET
- mkdir -p ~/.cargo
- printf '\n[target.'$TARGET']\nlinker = "x86_64-w64-mingw32-gcc"\nar = "x86_64-w64-mingw32-ar"\n' >> ~/.cargo/config
script: cargo test --verbose --target $TARGET
允許失敗(Allow Failures)
有些執行環境可能會有高機率使程式專案測試失敗的可能,如果想要讓Travis CI依然運行這些環境的腳本,但是又不想要讓這些環境的腳本運行結果去影響到最終的GitHub倉庫的Travis CI狀態,我們可以在.travis.yml
設定檔加上matrix
設定項目,再去設定其中的allow_failures
設定項目。
這個功能通常會用在Rust的Nightly版本上,例如:
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
不過筆者是不建議這樣用啦,畢竟Rust的Nightly版本經常會被用到,因為Rust有太多好東西都只能在Nightly版本中啟用。所以應該要儘量讓自己的程式專案能夠永遠成功在最新的Rust Nightly版本下測試。
加快Travis CI處理一次commit的速度
快取(Cache)
Travis CI支援很多種程式語言的套件管理工具的快取功能,Cargo也不例外。在.travis.yml
設定檔加上cache: cargo
這項設定,來啟用Cargo的快取功能。這樣前一次commit時所下載和編譯過的套件就會被快取下來,之後就可以一直重複使用。
language: rust
cache: cargo
不過這個功能可能比較適合用在固定使用相同版本的Rust編譯器時,不然只要Rust版本一有更新,先前快取到的東西就沒什麼用了。
快速結束(Fast Finish)
Travis CI提供快速結束的機制,當這個機制啟用時,Travis CI就不會等到所有任務執行結束後才返回狀態,而是只需要等到所有不在allow_failures
中的任務執行結束後即可返回狀態。在.travis.yml
設定檔的matrix
這個設定項目,加上fast_finish: true
這項設定,來啟用快速結束機制。
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
fast_finish: true
交叉編譯
有關於Rust交叉編譯的方式可以參考這篇文章:
當然,上面這篇文章介紹的cross
也是可以用在Travis CI的。只不過要先讓Travis CI有Docker的環境,可以在.travis.yml
設定檔加上services: docker
這項設定,來啟用Docker服務。然後再用script
設定項目來執行cargo install cross
指令
如果想要在x86和x86_64環境下完成程式專案的測試,完整的.travis.yml
設定檔寫法如下:
language: rust
services: docker
rust:
- stable
- beta
- nightly
os:
- linux
- osx
- windows
env:
-
- TARGET=i686-unknown-linux-gnu
- TARGET=i686-pc-windows-gnu
script:
- if [ -z "$TARGET" ]; then cargo test --verbose; fi
- if [ ! -z "$TARGET" ]; then cargo install cross && cross test --verbose --target $TARGET; fi
matrix:
exclude:
- os: osx
env: TARGET=i686-unknown-linux-gnu
- os: osx
env: TARGET=i686-pc-windows-gnu
- os: windows
env: TARGET=i686-unknown-linux-gnu
- os: windows
env: TARGET=i686-pc-windows-gnu
注意這邊我們在matrix
設定項目中加了exclude
,用來排除帶有指定參數的腳本。