Skip to content

Commit ae4afdf

Browse files
authored
Merge pull request #25 from T3pp31/cursor/strengthen-cicd-bdf4
ci: GitHub Actions の CI/CD を強化
2 parents 7b5295e + 8774dfa commit ae4afdf

9 files changed

Lines changed: 607 additions & 16 deletions

File tree

.github/dependabot.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: cargo
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
8+
- package-ecosystem: github-actions
9+
directory: /
10+
schedule:
11+
interval: weekly

.github/workflows/ci.yml

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,94 @@ on:
66
pull_request:
77
branches: [main, master]
88

9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
permissions:
14+
contents: read
15+
916
jobs:
1017
test:
11-
runs-on: ubuntu-latest
18+
name: test (${{ matrix.os }})
19+
runs-on: ${{ matrix.os }}
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
os: [ubuntu-latest, macos-latest, windows-latest]
1224
steps:
1325
- name: Checkout
1426
uses: actions/checkout@v4
1527

1628
- name: Install Rust toolchain
1729
uses: dtolnay/rust-toolchain@stable
1830

19-
- name: Cache cargo registry and build
20-
uses: actions/cache@v4
21-
with:
22-
path: |
23-
~/.cargo/registry
24-
~/.cargo/git
25-
target
26-
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
27-
restore-keys: |
28-
${{ runner.os }}-cargo-
31+
- name: Cache Rust
32+
uses: Swatinem/rust-cache@v2
2933

3034
- name: Run tests
3135
run: cargo test --verbose
3236

33-
- name: Run clippy
34-
run: cargo clippy --all-targets --all-features -- -D warnings
37+
- name: Run release-version shell tests
38+
if: runner.os == 'Linux'
39+
run: bash tests/check_release_version_tests.sh
40+
41+
fmt:
42+
runs-on: ubuntu-latest
43+
steps:
44+
- name: Checkout
45+
uses: actions/checkout@v4
46+
47+
- name: Install Rust toolchain
48+
uses: dtolnay/rust-toolchain@stable
49+
with:
50+
components: rustfmt
51+
52+
- name: Cache Rust
53+
uses: Swatinem/rust-cache@v2
3554

3655
- name: Check formatting
3756
run: cargo fmt --check
3857

58+
clippy:
59+
runs-on: ubuntu-latest
60+
steps:
61+
- name: Checkout
62+
uses: actions/checkout@v4
63+
64+
- name: Install Rust toolchain
65+
uses: dtolnay/rust-toolchain@stable
66+
with:
67+
components: clippy
68+
69+
- name: Cache Rust
70+
uses: Swatinem/rust-cache@v2
71+
72+
- name: Run clippy
73+
run: cargo clippy --all-targets --all-features -- -D warnings
74+
75+
audit:
76+
runs-on: ubuntu-latest
77+
steps:
78+
- name: Checkout
79+
uses: actions/checkout@v4
80+
3981
- name: Audit dependencies
4082
uses: rustsec/audit-check@v2.0.0
4183
with:
4284
token: ${{ secrets.GITHUB_TOKEN }}
4385

44-
- name: Build
86+
build:
87+
runs-on: ubuntu-latest
88+
steps:
89+
- name: Checkout
90+
uses: actions/checkout@v4
91+
92+
- name: Install Rust toolchain
93+
uses: dtolnay/rust-toolchain@stable
94+
95+
- name: Cache Rust
96+
uses: Swatinem/rust-cache@v2
97+
98+
- name: Build release
4599
run: cargo build --release

.github/workflows/release.yml

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,23 @@ on:
99
tags:
1010
- v*
1111

12+
concurrency:
13+
group: release-${{ github.ref }}
14+
cancel-in-progress: true
15+
1216
jobs:
17+
version-check:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
- name: Verify tag matches Cargo.toml version
24+
shell: bash
25+
run: ./scripts/check_release_version.sh "${{ github.ref_name }}"
26+
1327
test:
28+
needs: [version-check]
1429
runs-on: ubuntu-latest
1530
steps:
1631
- name: Checkout
@@ -19,6 +34,9 @@ jobs:
1934
- name: Install Rust toolchain
2035
uses: dtolnay/rust-toolchain@stable
2136

37+
- name: Cache Rust
38+
uses: Swatinem/rust-cache@v2
39+
2240
- name: Run tests
2341
run: cargo test --verbose
2442

@@ -54,6 +72,11 @@ jobs:
5472
with:
5573
targets: ${{ matrix.job.target }}
5674

75+
- name: Cache Rust
76+
uses: Swatinem/rust-cache@v2
77+
with:
78+
key: release-${{ matrix.job.target }}
79+
5780
# crossが必要な場合はインストール
5881
- name: Install cross
5982
if: matrix.job.use-cross
@@ -102,15 +125,22 @@ jobs:
102125

103126
# crates.ioへの自動公開
104127
publish:
105-
needs: [test]
128+
needs: [test, build]
106129
runs-on: ubuntu-latest
107130
steps:
108131
- name: Checkout
109132
uses: actions/checkout@v4
110133

134+
- name: Verify tag matches Cargo.toml version
135+
shell: bash
136+
run: ./scripts/check_release_version.sh "${{ github.ref_name }}"
137+
111138
- name: Install Rust toolchain
112139
uses: dtolnay/rust-toolchain@stable
113140

141+
- name: Cache Rust
142+
uses: Swatinem/rust-cache@v2
143+
114144
- name: Extract crate metadata
115145
id: crate_metadata
116146
shell: bash
@@ -120,13 +150,23 @@ jobs:
120150
echo "crate_name=${CRATE_NAME}" >> "${GITHUB_OUTPUT}"
121151
echo "crate_version=${CRATE_VERSION}" >> "${GITHUB_OUTPUT}"
122152
153+
- name: Load crates.io User-Agent from config
154+
id: ci_config
155+
shell: bash
156+
run: |
157+
set -euo pipefail
158+
# shellcheck source=scripts/lib/ci_config.sh
159+
source ./scripts/lib/ci_config.sh
160+
UA="$(ci_config_get crates_io user_agent)"
161+
echo "user_agent=${UA}" >> "${GITHUB_OUTPUT}"
162+
123163
- name: Check if version is already published
124164
id: publish_guard
125165
shell: bash
126166
run: |
127167
set -euo pipefail
128168
PUBLISH_STATUS=$(curl --silent --output /dev/null --write-out "%{http_code}" \
129-
-H "User-Agent: caesar_cipher_enc_dec-release (github-actions)" \
169+
-H "User-Agent: ${{ steps.ci_config.outputs.user_agent }}" \
130170
"https://crates.io/api/v1/crates/${{ steps.crate_metadata.outputs.crate_name }}/${{ steps.crate_metadata.outputs.crate_version }}")
131171
if [ "${PUBLISH_STATUS}" = "200" ]; then
132172
echo "should_publish=false" >> "${GITHUB_OUTPUT}"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Caesar Cipher Encryption/Decryption
22

3+
[![CI](https://github.com/T3pp31/caesar_cipher_enc_dec/actions/workflows/ci.yml/badge.svg)](https://github.com/T3pp31/caesar_cipher_enc_dec/actions/workflows/ci.yml)
34
[![Crates.io](https://img.shields.io/crates/v/caesar_cipher_enc_dec.svg)](https://crates.io/crates/caesar_cipher_enc_dec)
45
[![Documentation](https://docs.rs/caesar_cipher_enc_dec/badge.svg)](https://docs.rs/caesar_cipher_enc_dec)
56

config/ci.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# CI / Release 向けの設定(ワークフロー・スクリプトから参照)
2+
3+
[release]
4+
# Git タグは tag_prefix + Cargo.toml の version(例: v1.0.10)
5+
tag_prefix = "v"
6+
7+
[crates_io]
8+
# crates.io API 呼び出し時の User-Agent(未設定だと 403 になることがある)
9+
user_agent = "caesar_cipher_enc_dec-release (github-actions)"
10+
11+
[paths]
12+
cargo_toml = "Cargo.toml"
13+
ci_config = "config/ci.toml"

rust-toolchain.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[toolchain]
2+
channel = "stable"
3+
components = ["clippy", "rustfmt"]

scripts/check_release_version.sh

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/usr/bin/env bash
2+
# Git タグと Cargo.toml の version が一致するか検証する
3+
#
4+
# 使い方:
5+
# ./scripts/check_release_version.sh [tag]
6+
#
7+
# タグの取得優先順位:
8+
# 1. 第1引数
9+
# 2. 環境変数 CHECK_RELEASE_TAG
10+
# 3. 環境変数 GITHUB_REF_NAME
11+
#
12+
# 終了コード:
13+
# 0: タグが {tag_prefix}{Cargo.toml version} と一致
14+
# 非0: 不一致・空・不正形式・設定/ファイル不備
15+
#
16+
# 上書き用環境変数:
17+
# CI_CONFIG_PATH - config/ci.toml のパス
18+
# CARGO_TOML_PATH - Cargo.toml のパス
19+
20+
set -euo pipefail
21+
22+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23+
# shellcheck source=lib/ci_config.sh
24+
source "${SCRIPT_DIR}/lib/ci_config.sh"
25+
26+
REPO_ROOT="$(ci_config_repo_root)"
27+
28+
read_cargo_version() {
29+
local cargo_toml="${CARGO_TOML_PATH:-}"
30+
if [ -z "${cargo_toml}" ]; then
31+
local relative
32+
relative="$(ci_config_get paths cargo_toml 2>/dev/null || echo "Cargo.toml")"
33+
cargo_toml="${REPO_ROOT}/${relative}"
34+
fi
35+
36+
if [ ! -f "${cargo_toml}" ]; then
37+
echo "error: Cargo.toml not found: ${cargo_toml}" >&2
38+
return 1
39+
fi
40+
41+
local version
42+
version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' "${cargo_toml}" | head -n1)"
43+
if [ -z "${version}" ]; then
44+
echo "error: could not read package version from ${cargo_toml}" >&2
45+
return 1
46+
fi
47+
printf '%s\n' "${version}"
48+
}
49+
50+
resolve_tag() {
51+
if [ "${#}" -ge 1 ] && [ -n "${1}" ]; then
52+
printf '%s\n' "${1}"
53+
return 0
54+
fi
55+
if [ -n "${CHECK_RELEASE_TAG:-}" ]; then
56+
printf '%s\n' "${CHECK_RELEASE_TAG}"
57+
return 0
58+
fi
59+
if [ -n "${GITHUB_REF_NAME:-}" ]; then
60+
printf '%s\n' "${GITHUB_REF_NAME}"
61+
return 0
62+
fi
63+
echo "error: release tag is empty; pass as argument or set CHECK_RELEASE_TAG / GITHUB_REF_NAME" >&2
64+
return 1
65+
}
66+
67+
is_valid_semver_like() {
68+
local version="$1"
69+
# 簡易: MAJOR.MINOR.PATCH(追加ラベルなしのセマンティック版)
70+
[[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
71+
}
72+
73+
main() {
74+
local tag crate_version tag_prefix expected
75+
76+
tag="$(resolve_tag "${@:-}")" || exit 1
77+
if [ -z "${tag}" ]; then
78+
echo "error: release tag is empty" >&2
79+
exit 1
80+
fi
81+
82+
tag_prefix="$(ci_config_get release tag_prefix)" || exit 1
83+
crate_version="$(read_cargo_version)" || exit 1
84+
85+
if ! is_valid_semver_like "${crate_version}"; then
86+
echo "error: invalid Cargo.toml version format: '${crate_version}' (expected MAJOR.MINOR.PATCH)" >&2
87+
exit 1
88+
fi
89+
90+
expected="${tag_prefix}${crate_version}"
91+
92+
if [[ ! "${tag}" =~ ^${tag_prefix} ]]; then
93+
echo "error: tag '${tag}' does not start with configured prefix '${tag_prefix}'" >&2
94+
echo " Cargo.toml version: ${crate_version}" >&2
95+
echo " expected tag: ${expected}" >&2
96+
exit 1
97+
fi
98+
99+
local tag_version="${tag#"${tag_prefix}"}"
100+
if [ -z "${tag_version}" ]; then
101+
echo "error: tag '${tag}' has empty version after prefix '${tag_prefix}'" >&2
102+
exit 1
103+
fi
104+
105+
if ! is_valid_semver_like "${tag_version}"; then
106+
echo "error: invalid tag version format: '${tag}' (expected ${tag_prefix}MAJOR.MINOR.PATCH)" >&2
107+
echo " Cargo.toml version: ${crate_version}" >&2
108+
exit 1
109+
fi
110+
111+
if [ "${tag}" != "${expected}" ]; then
112+
echo "error: git tag does not match Cargo.toml version" >&2
113+
echo " tag: ${tag}" >&2
114+
echo " Cargo.toml version: ${crate_version}" >&2
115+
echo " expected tag: ${expected}" >&2
116+
exit 1
117+
fi
118+
119+
echo "OK: tag '${tag}' matches Cargo.toml version '${crate_version}' (prefix='${tag_prefix}')"
120+
}
121+
122+
main "$@"

0 commit comments

Comments
 (0)