Skip to content

Record which rule produced each braille cell #701

Record which rule produced each braille cell

Record which rule produced each braille cell #701

Workflow file for this run

name: Publish

Check warning on line 1 in .github/workflows/publish.yml

View workflow run for this annotation

GitHub Actions / Publish

Workflow execution policy warning (evaluate mode)

On November 2, 2026, GitHub will restrict `pull_request_target` on public repositories by default. To continue allowing the event trigger, configure an Actions policy. Learn more: https://gh.io/securely-using-pull_request_target#default-policy-for-pull_request_target
on:
push:
branches:
- main
pull_request_target:
branches:
- main
permissions: write-all
jobs:
test:
name: Test
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
python-version:
- "3.11"
- "3.12"
- "3.13"
- "3.14"
platform:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
# pull_request_target는 보안상 기본적으로 base branch(main)를 체크아웃한다.
# PR 코드의 커버리지를 측정하려면 PR head를 명시적으로 지정해야 한다.
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# actions/checkout v7 은 pull_request_target 에서 fork PR 코드 체크아웃을
# 기본 차단한다. 이 opt-in 이 없으면 fork PR 의 모든 job 이 checkout 에서 실패한다.
# 지우면 CI 가 다시 깨진다. https://gh.io/securely-using-pull_request_target
allow-unsafe-pr-checkout: true
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v10.0.1
- name: Install maturin
run: uv pip install maturin --system
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Cargo tarpaulin and fmt
run: |
cargo install cargo-tarpaulin
rustup component add rustfmt clippy
- name: Install dependencies
run: bun install
- name: Build
run: bun run build
- name: Lint
run: bun run lint
- name: reformat
shell: bash
run: |
echo 'max_width = 100000' > .rustfmt.toml
echo 'tab_spaces = 4' >> .rustfmt.toml
echo 'newline_style = "Unix"' >> .rustfmt.toml
echo 'fn_call_width = 100000' >> .rustfmt.toml
echo 'fn_params_layout = "Compressed"' >> .rustfmt.toml
echo 'chain_width = 100000' >> .rustfmt.toml
echo 'merge_derives = true' >> .rustfmt.toml
echo 'use_small_heuristics = "Default"' >> .rustfmt.toml
cargo fmt
- name: Test (Rust coverage)
timeout-minutes: 20
run: bun run test:rust
- name: Test (Node)
timeout-minutes: 10
# Windows에서 중첩된 `bun run`이 테스트 완료 후 간헐적으로 멈추므로 직접 실행한다.
run: bun test
- name: Test (Python)
timeout-minutes: 10
run: bun run test:python
- name: Set up MSVC for C/C++ tests
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
- name: Test (C and C++)
timeout-minutes: 5
run: bun run test:c
- name: Format Rollback
shell: bash
run: |
rm -rf .rustfmt.toml
cargo fmt
- name: Upload to codecov.io
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
if: github.ref == 'refs/heads/main'
dotnet-test:
name: .NET Test - ${{ matrix.rid }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
# Windows x64
- runner: windows-latest
target: x86_64-pc-windows-msvc
rid: win-x64
lib_name: braillify_native.dll
# Windows x86 (32-bit)
- runner: windows-latest
target: i686-pc-windows-msvc
rid: win-x86
lib_name: braillify_native.dll
dotnet_arch: x86
# Linux x64
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
rid: linux-x64
lib_name: libbraillify_native.so
# macOS arm64 (native)
- runner: macos-14
target: aarch64-apple-darwin
rid: osx-arm64
lib_name: libbraillify_native.dylib
# macOS x64 (x64 SDK on macos-14)
- runner: macos-14
target: x86_64-apple-darwin
rid: osx-x64
lib_name: libbraillify_native.dylib
steps:
# pull_request_target에서도 PR 코드를 체크아웃해 테스트한다.
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# actions/checkout v7 은 pull_request_target 에서 fork PR 코드 체크아웃을
# 기본 차단한다. 이 opt-in 이 없으면 fork PR 의 모든 job 이 checkout 에서 실패한다.
# 지우면 CI 가 다시 깨진다. https://gh.io/securely-using-pull_request_target
allow-unsafe-pr-checkout: true
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
- name: Build native library
run: cargo build --release --target ${{ matrix.target }} -p dotnet
- name: Setup .NET
if: matrix.dotnet_arch != 'x86' && matrix.rid != 'osx-x64'
uses: actions/setup-dotnet@v6
with:
dotnet-version: |
9.0.x
8.0.x
- name: Setup .NET x86
if: matrix.dotnet_arch == 'x86'
shell: pwsh
run: |
$installDir = "$env:RUNNER_TEMP\dotnet-x86"
Invoke-WebRequest -Uri https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1
./dotnet-install.ps1 -Channel 8.0 -Architecture x86 -InstallDir $installDir
./dotnet-install.ps1 -Channel 9.0 -Architecture x86 -InstallDir $installDir
./dotnet-install.ps1 -Channel 10.0 -Architecture x86 -InstallDir $installDir -Quality preview
# Add to PATH and set DOTNET_ROOT
echo "$installDir" | Out-File -FilePath $env:GITHUB_PATH -Append
echo "DOTNET_ROOT=$installDir" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Setup .NET x64 (macOS)
if: matrix.rid == 'osx-x64'
run: |
export DOTNET_INSTALL_DIR="$RUNNER_TEMP/dotnet-x64"
curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 8.0 --architecture x64 --install-dir "$DOTNET_INSTALL_DIR"
curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 9.0 --architecture x64 --install-dir "$DOTNET_INSTALL_DIR"
curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0 --architecture x64 --install-dir "$DOTNET_INSTALL_DIR" --quality preview
echo "$DOTNET_INSTALL_DIR" >> $GITHUB_PATH
echo "DOTNET_ROOT=$DOTNET_INSTALL_DIR" >> $GITHUB_ENV
- name: Copy native library
run: |
mkdir -p packages/dotnet/BraillifyNet/runtimes/${{ matrix.rid }}/native
cp target/${{ matrix.target }}/release/${{ matrix.lib_name }} packages/dotnet/BraillifyNet/runtimes/${{ matrix.rid }}/native/
shell: bash
- name: Restore dependencies
run: dotnet restore Braillify.Tests/Braillify.Tests.csproj
working-directory: packages/dotnet
- name: Build
run: dotnet build --no-restore -c Release Braillify.Tests/Braillify.Tests.csproj
working-directory: packages/dotnet
- name: Test (.NET 8.0)
run: dotnet test --no-build -c Release --verbosity normal --framework net8.0 Braillify.Tests/Braillify.Tests.csproj
working-directory: packages/dotnet
- name: Test (.NET 9.0)
run: dotnet test --no-build -c Release --verbosity normal --framework net9.0 Braillify.Tests/Braillify.Tests.csproj
working-directory: packages/dotnet
ruby-test:
name: Ruby Test - ${{ matrix.platform }}
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
platform:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
# pull_request_target에서도 PR 코드를 체크아웃해 테스트한다.
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# actions/checkout v7 은 pull_request_target 에서 fork PR 코드 체크아웃을
# 기본 차단한다. 이 opt-in 이 없으면 fork PR 의 모든 job 이 checkout 에서 실패한다.
# 지우면 CI 가 다시 깨진다. https://gh.io/securely-using-pull_request_target
allow-unsafe-pr-checkout: true
# Ruby 플랫폼(Windows ucrt 등)에 맞는 Rust 타겟/bindgen 환경까지 구성하는
# rb-sys 공식 셋업 액션.
- uses: oxidize-rb/actions/setup-ruby-and-rust@v1
with:
ruby-version: "3.4"
bundler-cache: true
cargo-cache: true
working-directory: packages/ruby
# 실배포 gem 플랫폼(stable Ruby, Windows는 ucrt+GNU)에서의 lint.
# 루트 워크스페이스에서 실행해 [patch.crates-io]로 로컬 libs/braillify를 사용한다.
- name: Lint
run: cargo clippy -p braillify_rb --all-targets -- -D warnings
- name: Compile
run: bundle exec rake compile
working-directory: packages/ruby
- name: Test
run: bundle exec rake test
working-directory: packages/ruby
jvm-test:
name: JVM Test - ${{ matrix.native-target }}
runs-on: ${{ matrix.runner }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
native-target: linux-x86_64
- runner: ubuntu-24.04-arm
native-target: linux-aarch64
- runner: macos-15-intel
native-target: macos-x86_64
- runner: macos-15
native-target: macos-aarch64
- runner: windows-2022
native-target: windows-x86_64
steps:
# pull_request_target에서도 PR 코드를 체크아웃해 실제 JVM 바인딩을 테스트한다.
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
allow-unsafe-pr-checkout: true
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: ${{ github.event_name == 'pull_request_target' }}
- name: Test JVM binding
run: ./gradlew test --no-daemon
working-directory: packages/jvm
deploy-landing:
name: Deploy Landing
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs:
- test
- dotnet-test
- jvm-test
- ruby-test
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Install uv
uses: astral-sh/setup-uv@v10.0.1
- name: Install maturin
run: uv pip install maturin --system
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- run: |
bun i
bun run build
- run: cargo test test_by_testcase
# 실패하지만 어느정도 맞았는지 알려주므로 무시해야합니다.
continue-on-error: true
- name: Build Landing
run: bun run build:landing
- name: Upload artifact
uses: actions/upload-pages-artifact@v5
with:
path: ./apps/landing/out
retention-days: 1
- uses: actions/deploy-pages@v5
# publish
changepacks:
name: changepacks
runs-on: ubuntu-latest
permissions:
# create pull request comments
pull-requests: write
# Actions > General > Workflow permissions for creating pull request
# Create brench to create pull request
contents: write
needs:
- test
- dotnet-test
- go-test
- jvm-test
- ruby-test
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# changepacks 는 base branch 와 견주어 바뀐 manifest 를 찾으므로 전체
# 이력이 필요하다. 기본 shallow checkout 이면 그 비교가 실패한다.
fetch-depth: 0
# actions/checkout v7 은 pull_request_target 에서 fork PR 코드 체크아웃을
# 기본 차단한다. 이 opt-in 이 없으면 fork PR 의 모든 job 이 checkout 에서 실패한다.
allow-unsafe-pr-checkout: true
# changepacks 는 base branch 를 refs/heads/<name> 에서 찾는데, checkout 은 SHA 를
# 주면 detached HEAD 로 두고 refs/remotes/origin/* 만 만든다. 그 로컬 ref 를 세운다.
- name: Materialize the base branch as a local ref
env:
BASE: ${{ github.event.pull_request.base.ref || github.ref_name }}
run: git branch --force "$BASE" "refs/remotes/origin/$BASE"
- uses: changepacks/action@main
id: changepacks
with:
token: ${{ secrets.GITHUB_TOKEN }}
outputs:
changepacks: ${{ steps.changepacks.outputs.changepacks }}
release_assets_urls: ${{ steps.changepacks.outputs.release_assets_urls }}
# node
node-build:
needs:
- test
- dotnet-test
- changepacks
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/node/package.json') }}
strategy:
fail-fast: false
matrix:
settings:
# - host: macos-latest
# target: x86_64-apple-darwin
# build: bun run build --target x86_64-apple-darwin
# - host: windows-latest
# build: bun run build --target x86_64-pc-windows-msvc
# target: x86_64-pc-windows-msvc
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
build: bun run build
# - host: macos-latest
# target: aarch64-apple-darwin
# build: bun run build --target aarch64-apple-darwin
name: Node Build - ${{ matrix.settings.target }}
runs-on: ${{ matrix.settings.host }}
env:
# DEBUG: napi:*
MACOSX_DEPLOYMENT_TARGET: "10.13"
CARGO_INCREMENTAL: "1"
steps:
- uses: actions/checkout@v7
- name: Setup node
uses: actions/setup-node@v7
with:
node-version: 22
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Install uv
uses: astral-sh/setup-uv@v10.0.1
- name: Install maturin
run: uv pip install maturin --system
- name: Install
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: ${{ matrix.settings.target }}
- uses: mlugg/setup-zig@v2
if: ${{ contains(matrix.settings.target, 'musl') }}
with:
version: 0.14.1
- name: Install cargo-zigbuild
uses: taiki-e/install-action@v2
if: ${{ contains(matrix.settings.target, 'musl') }}
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tool: cargo-zigbuild
- name: Setup toolchain
run: ${{ matrix.settings.setup }}
if: ${{ matrix.settings.setup }}
shell: bash
- name: Install dependencies
run: bun install
- name: Build
run: ${{ matrix.settings.build }}
shell: bash
working-directory: packages/node
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: pkg-${{ matrix.settings.target }}
path: |
packages/node/pkg/*
if-no-files-found: error
retention-days: 1
# python
python-build:
runs-on: ${{ matrix.runner }}
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/python/pyproject.toml') }}
needs:
- test
- dotnet-test
- changepacks
strategy:
fail-fast: false
matrix:
include:
# Linux manylinux
- runner: ubuntu-22.04
target: x86_64
manylinux: auto
os: linux
- runner: ubuntu-22.04
target: x86
manylinux: auto
os: linux
- runner: ubuntu-22.04
target: aarch64
manylinux: auto
os: linux
- runner: ubuntu-22.04
target: armv7
manylinux: auto
os: linux
- runner: ubuntu-22.04
target: ppc64le
manylinux: auto
os: linux
# Linux musllinux
- runner: ubuntu-22.04
target: x86_64
manylinux: musllinux_1_2
os: musllinux
- runner: ubuntu-22.04
target: x86
manylinux: musllinux_1_2
os: musllinux
- runner: ubuntu-22.04
target: aarch64
manylinux: musllinux_1_2
os: musllinux
- runner: ubuntu-22.04
target: armv7
manylinux: musllinux_1_2
os: musllinux
# Windows
- runner: windows-latest
target: x64
os: windows
python-target: x64
- runner: windows-latest
target: x86
os: windows
python-target: x86
# macOS
- runner: macos-14
target: x86_64
os: macos
- runner: macos-14
target: aarch64
os: macos
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: 3.x
architecture: ${{ matrix.python-target || '' }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter
working-directory: packages/python
manylinux: ${{ matrix.manylinux || '' }}
- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-${{ matrix.os }}-${{ matrix.target }}
path: packages/python/dist
retention-days: 1
node-publish:
name: Node Publish
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
needs:
- changepacks
- node-build
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/node/package.json') }}
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Install uv
uses: astral-sh/setup-uv@v10.0.1
- name: Install maturin
run: uv pip install maturin --system
- name: Install
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Install dependencies
run: bun install
- name: Build
run: bun run build
working-directory: packages/node
# - name: create npm dirs
# run: bunx napi create-npm-dirs
# working-directory: packages/node
- name: Download all artifacts
uses: actions/download-artifact@v8
with:
path: packages/node/pkg/*
# npm (not bun) performs the publish, and it needs `registry-url` here:
# that is what writes the registry line into the job's .npmrc, which is
# what npm exchanges the Actions OIDC token against. Without it npm has
# no registry to authenticate to and falls back to an anonymous PUT,
# which the registry answers with a 404 rather than a 401.
- uses: actions/setup-node@v7
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
# Trusted Publishing (OIDC) requires npm >= 11.5.1. Node 24 bundles the
# 11.x line but not necessarily a new enough patch, so pin it explicitly
# rather than depending on whichever npm the runner image happens to
# carry.
- name: Ensure npm >= 11.5.1 (OIDC floor)
run: npm install -g 'npm@^11'
# - name: Move artifacts
# run: bun run artifacts
# working-directory: packages/node
# - name: List packages
# run: ls -R ./npm
# working-directory: packages/node
# Publishes with Trusted Publishing (OIDC): npm exchanges the job's
# `id-token` for a short-lived registry credential, so no NPM_TOKEN is
# written to .npmrc. Writing one would actually BREAK this - npm prefers
# an explicit `_authToken` and never starts the OIDC exchange when it
# finds one.
#
# This requires a trusted publisher registered for `braillify` on
# npmjs.com pointing at dev-five-git/braillify and this workflow file;
# without it the registry rejects the exchange.
- name: Publish
run: |
# bun install -g @napi-rs/cli
bun run build
# package.json 의 현재 버전이 이미 npm 에 publish 되어 있으면 nothing to do.
# (버전을 bump 하면 새 버전은 아직 존재하지 않으므로 정상 publish 됨)
PKG_NAME=$(node -p "require('./package.json').name")
PKG_VERSION=$(node -p "require('./package.json').version")
if npm view "$PKG_NAME@$PKG_VERSION" version >/dev/null 2>&1; then
echo "::notice::$PKG_NAME@$PKG_VERSION is already on npm. Nothing to publish (bump version to release a new one)."
exit 0
fi
npm publish --access public
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: packages/node
# - name: Upload Asset
# uses: owjs3901/upload-github-release-asset@main
# with:
# upload_url: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['packages/node/package.json'] }}
# asset_path: packages/node/pkg/*
python-publish:
name: Python Publish
runs-on: ubuntu-latest
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/python/pyproject.toml') }}
needs:
- changepacks
- python-build
permissions:
# Use to sign the release artifacts
id-token: write
# Used to upload release artifacts
contents: write
# Used to generate artifact attestation
attestations: write
steps:
- uses: actions/download-artifact@v8
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v4
with:
subject-path: "wheels-*/*"
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing wheels-*/*
- name: List wheels
run: ls -R .
- name: Delete existing release assets (idempotent re-run)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPLOAD_URL: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['packages/python/pyproject.toml'] }}
run: |
# upload_url 형태: https://uploads.github.com/repos/OWNER/REPO/releases/RELEASE_ID/assets{?name,label}
RELEASE_ID=$(echo "$UPLOAD_URL" | sed -E 's#.*/releases/([0-9]+)/assets.*#\1#')
REPO="${GITHUB_REPOSITORY}"
for WHEEL in $(find . -name '*.whl' -type f); do
NAME=$(basename "$WHEEL")
ASSET_ID=$(gh api "repos/$REPO/releases/$RELEASE_ID/assets" \
--paginate --jq ".[] | select(.name == \"$NAME\") | .id" || true)
if [ -n "$ASSET_ID" ]; then
echo "deleting existing asset $NAME (id=$ASSET_ID)"
gh api -X DELETE "repos/$REPO/releases/assets/$ASSET_ID" || true
fi
done
- name: Upload Asset
uses: owjs3901/upload-github-release-asset@main
with:
upload_url: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['packages/python/pyproject.toml'] }}
asset_path: "*/*.whl"
upload-assets:
needs: changepacks
if: ${{ contains(needs.changepacks.outputs.changepacks, 'libs/braillify/Cargo.toml') }}
permissions:
contents: write
id-token: write
name: Upload Assets for ${{ matrix.platform }}
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
include:
- platform: ubuntu-22.04
os: linux
target: x86_64-unknown-linux-gnu
binary_name: braillify-linux-x86_64
- platform: windows-2022
os: windows
target: x86_64-pc-windows-msvc
binary_name: braillify-windows-x86_64.exe
- platform: macos-14
os: macos
target: x86_64-apple-darwin
binary_name: braillify-darwin-x86_64
- platform: macos-14
os: macos
target: aarch64-apple-darwin
binary_name: braillify-darwin-arm64
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
components: clippy, rustfmt
- name: Build Rust binary
run: cargo build --release --target ${{ matrix.target }} -p braillify
- name: Rename binary
run: mv target/${{ matrix.target }}/release/braillify${{ matrix.os == 'windows' && '.exe' || '' }} ${{ matrix.binary_name }}
- name: Delete existing release asset (idempotent re-run)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPLOAD_URL: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['libs/braillify/Cargo.toml'] }}
NAME: ${{ matrix.binary_name }}
shell: bash
run: |
RELEASE_ID=$(echo "$UPLOAD_URL" | sed -E 's#.*/releases/([0-9]+)/assets.*#\1#')
REPO="${GITHUB_REPOSITORY}"
ASSET_ID=$(gh api "repos/$REPO/releases/$RELEASE_ID/assets" \
--paginate --jq ".[] | select(.name == \"$NAME\") | .id" || true)
if [ -n "$ASSET_ID" ]; then
echo "deleting existing asset $NAME (id=$ASSET_ID)"
gh api -X DELETE "repos/$REPO/releases/assets/$ASSET_ID" || true
fi
- name: Upload Asset
uses: owjs3901/upload-github-release-asset@main
with:
upload_url: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['libs/braillify/Cargo.toml'] }}
asset_path: ${{ matrix.binary_name }}
cargo-publish:
name: Cargo Publish
runs-on: ubuntu-latest
if: ${{ contains(needs.changepacks.outputs.changepacks, 'libs/braillify') }}
needs:
- changepacks
steps:
- uses: actions/checkout@v7
- name: Publish to Cargo
run: |
# Cargo.toml 의 현재 버전이 이미 crates.io 에 publish 되어 있으면 nothing to do.
# (버전을 bump 하면 새 버전은 아직 존재하지 않으므로 정상 publish 됨)
VERSION=$(cargo pkgid -p braillify | sed -E 's/.*[#@]([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
if curl -fsSL "https://crates.io/api/v1/crates/braillify/$VERSION" \
-H "User-Agent: braillify-ci" >/dev/null 2>&1; then
echo "::notice::braillify@$VERSION is already on crates.io. Nothing to publish (bump version to release a new one)."
exit 0
fi
cargo publish -p braillify
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
winget-publish:
name: Winget Publish
runs-on: windows-2022
if: ${{ contains(needs.changepacks.outputs.changepacks, 'libs/braillify/Cargo.toml') }}
needs:
- changepacks
steps:
- uses: actions/checkout@v7
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: x86_64-pc-windows-msvc
components: clippy, rustfmt
- name: Build Rust binary
run: cargo build --release --target x86_64-pc-windows-msvc -p braillify
- name: Extract version
run: |
echo "version=$(cargo pkgid -p braillify | sed -E 's/.*#([0-9]+\.[0-9]+\.[0-9]+).*/\1/')" >> $GITHUB_OUTPUT
id: version
- name: Winget Publish
uses: vedantmgoyal9/winget-releaser@v2
continue-on-error: true
with:
identifier: Braillify.Braillify
installers-regex: '\.exe$' # Only .exe files
token: ${{ secrets.WINGET_TOKEN }}
version: ${{ steps.version.outputs.version }}
# dotnet
dotnet-build:
name: .NET Build - ${{ matrix.rid }}
runs-on: ${{ matrix.runner }}
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/dotnet') }}
needs:
- test
- changepacks
strategy:
fail-fast: false
matrix:
include:
# Windows
- runner: windows-latest
target: x86_64-pc-windows-msvc
rid: win-x64
lib_name: braillify_native.dll
- runner: windows-latest
target: i686-pc-windows-msvc
rid: win-x86
lib_name: braillify_native.dll
- runner: windows-latest
target: aarch64-pc-windows-msvc
rid: win-arm64
lib_name: braillify_native.dll
# Linux
- runner: ubuntu-22.04
target: x86_64-unknown-linux-gnu
rid: linux-x64
lib_name: libbraillify_native.so
- runner: ubuntu-22.04
target: aarch64-unknown-linux-gnu
rid: linux-arm64
lib_name: libbraillify_native.so
# macOS
- runner: macos-14
target: x86_64-apple-darwin
rid: osx-x64
lib_name: libbraillify_native.dylib
- runner: macos-14
target: aarch64-apple-darwin
rid: osx-arm64
lib_name: libbraillify_native.dylib
steps:
- uses: actions/checkout@v7
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
- name: Install cross-compilation tools (Linux ARM64)
if: matrix.rid == 'linux-arm64'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Build native library
run: cargo build --release --target ${{ matrix.target }} -p dotnet
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: dotnet-native-${{ matrix.rid }}
path: target/${{ matrix.target }}/release/${{ matrix.lib_name }}
if-no-files-found: error
retention-days: 1
dotnet-publish:
name: .NET Publish
runs-on: ubuntu-latest
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/dotnet') }}
needs:
- changepacks
- dotnet-build
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "9.0.x"
- name: Download all native artifacts
uses: actions/download-artifact@v8
with:
path: native-artifacts
pattern: dotnet-native-*
- name: Prepare native libraries
run: |
# Create runtimes directories
mkdir -p packages/dotnet/BraillifyNet/runtimes/win-x64/native
mkdir -p packages/dotnet/BraillifyNet/runtimes/win-x86/native
mkdir -p packages/dotnet/BraillifyNet/runtimes/win-arm64/native
mkdir -p packages/dotnet/BraillifyNet/runtimes/linux-x64/native
mkdir -p packages/dotnet/BraillifyNet/runtimes/linux-arm64/native
mkdir -p packages/dotnet/BraillifyNet/runtimes/osx-x64/native
mkdir -p packages/dotnet/BraillifyNet/runtimes/osx-arm64/native
# Copy native libraries to runtimes folders
cp native-artifacts/dotnet-native-win-x64/* packages/dotnet/BraillifyNet/runtimes/win-x64/native/
cp native-artifacts/dotnet-native-win-x86/* packages/dotnet/BraillifyNet/runtimes/win-x86/native/
cp native-artifacts/dotnet-native-win-arm64/* packages/dotnet/BraillifyNet/runtimes/win-arm64/native/
cp native-artifacts/dotnet-native-linux-x64/* packages/dotnet/BraillifyNet/runtimes/linux-x64/native/
cp native-artifacts/dotnet-native-linux-arm64/* packages/dotnet/BraillifyNet/runtimes/linux-arm64/native/
cp native-artifacts/dotnet-native-osx-x64/* packages/dotnet/BraillifyNet/runtimes/osx-x64/native/
cp native-artifacts/dotnet-native-osx-arm64/* packages/dotnet/BraillifyNet/runtimes/osx-arm64/native/
# List for verification
echo "=== Native libraries ==="
find packages/dotnet/BraillifyNet/runtimes -type f
- name: Build and Pack BraillifyNet
run: dotnet pack -c Release -o nupkg
working-directory: packages/dotnet/BraillifyNet
- name: Build and Pack Braillify CLI
run: dotnet pack -c Release -o nupkg
working-directory: packages/dotnet/Braillify
- name: List packages
run: |
echo "=== NuGet packages ==="
find packages/dotnet -name "*.nupkg" -type f
- name: Publish to NuGet
run: |
dotnet nuget push packages/dotnet/BraillifyNet/nupkg/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push packages/dotnet/Braillify/nupkg/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Upload NuGet packages as artifacts
uses: actions/upload-artifact@v7
with:
name: nuget-packages
path: packages/dotnet/**/nupkg/*.nupkg
retention-days: 1
# c
c-build:
name: C Build - ${{ matrix.rid }}
runs-on: ${{ matrix.runner }}
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/c/Cargo.toml') }}
needs:
- test
- changepacks
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-22.04
target: x86_64-unknown-linux-gnu
rid: linux-x64
archive: braillify-c-linux-x64.tar.gz
- runner: ubuntu-22.04
target: aarch64-unknown-linux-gnu
rid: linux-arm64
archive: braillify-c-linux-arm64.tar.gz
- runner: macos-14
target: x86_64-apple-darwin
rid: macos-x64
archive: braillify-c-macos-x64.tar.gz
- runner: macos-14
target: aarch64-apple-darwin
rid: macos-arm64
archive: braillify-c-macos-arm64.tar.gz
- runner: windows-2022
target: x86_64-pc-windows-msvc
rid: windows-x64
archive: braillify-c-windows-x64.tar.gz
steps:
- uses: actions/checkout@v7
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
- name: Set up MSVC
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
- name: Install cross-compilation tools (Linux ARM64)
if: matrix.rid == 'linux-arm64'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Build C libraries
run: cargo build --release --target ${{ matrix.target }} -p braillify-c
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
- name: Bundle libraries and header
shell: bash
env:
TARGET: ${{ matrix.target }}
RID: ${{ matrix.rid }}
ARCHIVE: ${{ matrix.archive }}
run: |
mkdir -p "dist/$RID/include" "dist/$RID/lib"
cp packages/c/include/braillify.h "dist/$RID/include/"
cp packages/c/README.md "dist/$RID/"
case "$RUNNER_OS" in
Linux)
cp "target/$TARGET/release/libbraillify_c.a" "dist/$RID/lib/"
cp "target/$TARGET/release/libbraillify_c.so" "dist/$RID/lib/"
;;
macOS)
cp "target/$TARGET/release/libbraillify_c.a" "dist/$RID/lib/"
cp "target/$TARGET/release/libbraillify_c.dylib" "dist/$RID/lib/"
;;
Windows)
cp "target/$TARGET/release/braillify_c.dll" "dist/$RID/lib/"
cp "target/$TARGET/release/braillify_c.dll.lib" "dist/$RID/lib/"
cp "target/$TARGET/release/braillify_c.lib" "dist/$RID/lib/"
;;
esac
tar -C dist -czf "$ARCHIVE" "$RID"
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: c-bindings-${{ matrix.rid }}
path: ${{ matrix.archive }}
if-no-files-found: error
retention-days: 1
c-publish:
name: C Publish
runs-on: ubuntu-latest
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/c/Cargo.toml') }}
needs:
- changepacks
- c-build
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Download C artifacts
uses: actions/download-artifact@v8
with:
pattern: c-bindings-*
path: c-artifacts
merge-multiple: true
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v4
with:
subject-path: "c-artifacts/*.tar.gz"
- name: Delete existing release assets (idempotent re-run)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPLOAD_URL: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['packages/c/Cargo.toml'] }}
run: |
RELEASE_ID=$(echo "$UPLOAD_URL" | sed -E 's#.*/releases/([0-9]+)/assets.*#\1#')
REPO="${GITHUB_REPOSITORY}"
for ARCHIVE in c-artifacts/*.tar.gz; do
NAME=$(basename "$ARCHIVE")
ASSET_ID=$(gh api "repos/$REPO/releases/$RELEASE_ID/assets" \
--paginate --jq ".[] | select(.name == \"$NAME\") | .id" || true)
if [ -n "$ASSET_ID" ]; then
gh api -X DELETE "repos/$REPO/releases/assets/$ASSET_ID" || true
fi
done
- name: Upload release assets
uses: owjs3901/upload-github-release-asset@main
with:
upload_url: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['packages/c/Cargo.toml'] }}
asset_path: "c-artifacts/*.tar.gz"
# ruby
ruby-build:
name: Ruby Build - ${{ matrix.platform }}
runs-on: ubuntu-latest
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/ruby/Cargo.toml') }}
needs:
- test
- dotnet-test
- ruby-test
- changepacks
strategy:
fail-fast: false
matrix:
platform:
- x86_64-linux
- aarch64-linux
- x86_64-darwin
- arm64-darwin
- x64-mingw-ucrt
steps:
- uses: actions/checkout@v7
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
bundler-cache: true
working-directory: packages/ruby
- uses: oxidize-rb/actions/cross-gem@v1
id: cross-gem
with:
platform: ${{ matrix.platform }}
ruby-versions: "3.1,3.2,3.3,3.4"
working-directory: packages/ruby
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ruby-gem-${{ matrix.platform }}
path: ${{ steps.cross-gem.outputs.gem-path }}
if-no-files-found: error
ruby-publish:
name: Ruby Publish
runs-on: ubuntu-latest
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/ruby/Cargo.toml') }}
needs:
- changepacks
- ruby-build
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v7
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
- name: Download platform gems
uses: actions/download-artifact@v8
with:
pattern: ruby-gem-*
path: gems
- name: Build source gem
run: |
gem build braillify.gemspec
mkdir -p ../../gems/source
mv braillify-*.gem ../../gems/source/
working-directory: packages/ruby
- name: Publish
env:
GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
run: |
VERSION=$(ruby -e 'print File.read("packages/ruby/Cargo.toml")[/^version = "([^"]+)"/, 1]')
EXISTING=$(curl -fsSL "https://rubygems.org/api/v1/versions/braillify.json" || echo "[]")
for GEM in $(find gems -name '*.gem' -type f); do
BASE=$(basename "$GEM" .gem)
PLATFORM=${BASE#braillify-$VERSION}
PLATFORM=${PLATFORM#-}
PLATFORM=${PLATFORM:-ruby}
if echo "$EXISTING" | jq -e --arg v "$VERSION" --arg p "$PLATFORM" \
'any(.[]; .number == $v and .platform == $p)' > /dev/null; then
echo "::notice::braillify-$VERSION ($PLATFORM) is already on rubygems.org. Skipping."
continue
fi
gem push "$GEM"
done
# go
go-test:
name: Go Test - ${{ matrix.platform }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
# Windows x64
- runner: windows-latest
target: x86_64-pc-windows-gnu
platform: windows-amd64
# Linux x64
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux-amd64
# Linux ARM64
# go test 는 네이티브로 돌기 때문에 러너 아키텍처가 target 과 같아야 한다.
# amd64 러너에서 크로스 빌드하면 cgo 가 libs/linux-amd64 를 찾다가 링크에 실패한다.
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
platform: linux-arm64
# macOS ARM64
- runner: macos-14
target: aarch64-apple-darwin
platform: darwin-arm64
# macOS x64
- runner: macos-15-intel
target: x86_64-apple-darwin
platform: darwin-amd64
steps:
# pull_request_target에서도 PR 코드를 체크아웃해 테스트한다.
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
allow-unsafe-pr-checkout: true
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
- uses: actions/setup-go@v5
with:
go-version: stable
- name: Build native library
run: cargo build --release --target ${{ matrix.target }} -p braillify-go
- name: Copy native library
run: |
mkdir -p packages/go/libs/${{ matrix.platform }}
cp target/${{ matrix.target }}/release/libbraillify_go.a packages/go/libs/${{ matrix.platform }}/
shell: bash
- name: Test
run: go test -v ./...
working-directory: packages/go
go-build:
name: Go Build - ${{ matrix.platform }}
runs-on: ${{ matrix.runner }}
needs:
- test
- changepacks
if: >-
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
contains(needs.changepacks.outputs.changepacks, 'packages/go/Cargo.toml')
strategy:
fail-fast: false
matrix:
include:
# Windows x64
- runner: windows-latest
target: x86_64-pc-windows-gnu
platform: windows-amd64
# Linux x64
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux-amd64
# Linux ARM64
- runner: ubuntu-latest
target: aarch64-unknown-linux-gnu
platform: linux-arm64
# macOS ARM64
- runner: macos-14
target: aarch64-apple-darwin
platform: darwin-arm64
# macOS x64
- runner: macos-15-intel
target: x86_64-apple-darwin
platform: darwin-amd64
steps:
- uses: actions/checkout@v6
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
- name: Install cross-compilation tools (Linux ARM64)
if: matrix.platform == 'linux-arm64'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Build native library
run: cargo build --release --target ${{ matrix.target }} -p braillify-go
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: go-lib-${{ matrix.platform }}
path: target/${{ matrix.target }}/release/libbraillify_go.a
if-no-files-found: error
go-publish:
name: Go Publish
runs-on: ubuntu-latest
needs:
- changepacks
- go-build
if: >-
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
contains(needs.changepacks.outputs.changepacks, 'packages/go/Cargo.toml')
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Extract version
id: version
working-directory: packages/go
run: |
v=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([0-9.]+)".*/\1/')
echo "version=$v" >> $GITHUB_OUTPUT
echo "Resolved version: $v"
- name: Download platform libs
uses: actions/download-artifact@v8
with:
pattern: go-lib-*
path: libs-raw
merge-multiple: false
- name: Place libs
run: |
for d in libs-raw/go-lib-*; do
[ -d "$d" ] || continue
plat="${d##*go-lib-}"
mkdir -p "packages/go/libs/$plat"
cp "$d/libbraillify_go.a" "packages/go/libs/$plat/"
done
echo "=== Placed libs ==="
find packages/go/libs -type f
- name: Commit libs + new module tag
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
TAG="packages/go/v${{ steps.version.outputs.version }}"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists — skip (no force-move)."
exit 0
fi
git add -f packages/go/libs/
git commit -m "chore(go): publish static libs v${{ steps.version.outputs.version }} [skip ci]"
git pull --rebase origin main
git tag "$TAG"
git push origin main
git push origin "$TAG"
# jvm
# JVM 릴리스 job은 다른 바인딩 게시 흐름과 섞이지 않도록 파일 맨 아래에 둔다.
jvm-native-build:
name: JVM Native Build - ${{ matrix.native-target }}
runs-on: ${{ matrix.runner }}
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(needs.changepacks.outputs.changepacks, 'packages/jvm/build.gradle.kts') }}
needs:
- changepacks
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
native-target: linux-x86_64
rust-target: x86_64-unknown-linux-gnu
library-name: libbraillify_jvm.so
glibc: "2.17"
- runner: ubuntu-24.04
native-target: linux-aarch64
rust-target: aarch64-unknown-linux-gnu
library-name: libbraillify_jvm.so
glibc: "2.17"
- runner: macos-15-intel
native-target: macos-x86_64
rust-target: x86_64-apple-darwin
library-name: libbraillify_jvm.dylib
deployment-target: "10.13"
- runner: macos-15
native-target: macos-aarch64
rust-target: aarch64-apple-darwin
library-name: libbraillify_jvm.dylib
deployment-target: "11.0"
- runner: windows-2022
native-target: windows-x86_64
rust-target: x86_64-pc-windows-msvc
library-name: braillify_jvm.dll
steps:
- uses: actions/checkout@v7
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.rust-target }}
- name: Set up MSVC
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
- name: Set up Zig
if: matrix.glibc
uses: mlugg/setup-zig@v2
with:
version: 0.14.1
- name: Install cargo-zigbuild
if: matrix.glibc
uses: taiki-e/install-action@v2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tool: cargo-zigbuild
- name: Build Linux native library with glibc 2.17 compatibility
if: matrix.glibc
run: cargo zigbuild --release -p braillify-jvm --target ${{ matrix.rust-target }}.${{ matrix.glibc }}
- name: Build macOS or Windows native library
if: ${{ !matrix.glibc }}
run: cargo build --release -p braillify-jvm --target ${{ matrix.rust-target }}
env:
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.deployment-target }}
- name: Inspect Linux ABI and dependencies
if: runner.os == 'Linux'
shell: bash
env:
LIBRARY: target/${{ matrix.rust-target }}/release/${{ matrix.library-name }}
run: |
readelf -d "$LIBRARY"
readelf --version-info "$LIBRARY"
if readelf --version-info "$LIBRARY" \
| grep -oE 'GLIBC_[0-9]+\.[0-9]+' \
| sort -Vu \
| awk -F'[_.]' '$2 > 2 || ($2 == 2 && $3 > 17) { found = 1 } END { exit !found }'; then
echo "::error::$LIBRARY requires a GLIBC symbol newer than 2.17"
exit 1
fi
if [ "${{ matrix.native-target }}" = "linux-x86_64" ]; then
ldd "$LIBRARY"
fi
- name: Inspect macOS dependencies
if: runner.os == 'macOS'
run: otool -L target/${{ matrix.rust-target }}/release/${{ matrix.library-name }}
- name: Inspect Windows dependencies
if: runner.os == 'Windows'
run: dumpbin /DEPENDENTS target/${{ matrix.rust-target }}/release/${{ matrix.library-name }}
- name: Upload native library
uses: actions/upload-artifact@v7
with:
name: jvm-native-${{ matrix.native-target }}
path: target/${{ matrix.rust-target }}/release/${{ matrix.library-name }}
if-no-files-found: error
retention-days: 1
jvm-jar:
name: JVM Fat JAR
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(needs.changepacks.outputs.changepacks, 'packages/jvm/build.gradle.kts') }}
needs:
- changepacks
- jvm-native-build
steps:
- uses: actions/checkout@v7
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Download native libraries
uses: actions/download-artifact@v8
with:
pattern: jvm-native-*
path: native-artifacts
- name: Stage native libraries
shell: bash
run: |
stage_native() {
local native_target="$1"
local library_name="$2"
local library
library=$(find "native-artifacts/jvm-native-$native_target" -type f -name "$library_name" -print -quit)
if [ -z "$library" ]; then
echo "::error::Missing $library_name for $native_target"
exit 1
fi
./packages/jvm/gradlew -p packages/jvm stageNativeLibrary \
-PnativeTarget="$native_target" \
-PnativeLibrary="$GITHUB_WORKSPACE/$library" \
--no-daemon
}
stage_native linux-x86_64 libbraillify_jvm.so
stage_native linux-aarch64 libbraillify_jvm.so
stage_native macos-x86_64 libbraillify_jvm.dylib
stage_native macos-aarch64 libbraillify_jvm.dylib
stage_native windows-x86_64 braillify_jvm.dll
- name: Assemble fat JAR
run: ./gradlew assembleJvmJar --no-daemon
working-directory: packages/jvm
- name: Verify fat JAR
shell: bash
run: |
JAR=$(find packages/jvm/build/libs -maxdepth 1 -type f -name '*.jar' ! -name '*-sources.jar' ! -name '*-javadoc.jar' -print -quit)
if [ -z "$JAR" ]; then
echo "::error::JVM binary JAR was not created"
exit 1
fi
jar tf "$JAR" | sort > jvm-jar-entries.txt
for entry in \
META-INF/native/linux-x86_64/libbraillify_jvm.so \
META-INF/native/linux-aarch64/libbraillify_jvm.so \
META-INF/native/macos-x86_64/libbraillify_jvm.dylib \
META-INF/native/macos-aarch64/libbraillify_jvm.dylib \
META-INF/native/windows-x86_64/braillify_jvm.dll; do
grep -Fx "$entry" jvm-jar-entries.txt >/dev/null || {
echo "::error::Missing JAR entry: $entry"
exit 1
}
done
if find packages/jvm/build/libs -maxdepth 1 -type f -name '*-debug*' -print -quit | grep -q .; then
echo "::error::Debug artifact found in JVM release output"
exit 1
fi
sha256sum "$JAR"
- name: Upload fat JAR
uses: actions/upload-artifact@v7
with:
name: jvm-fat-jar
path: packages/jvm/build/libs/*.jar
if-no-files-found: error
retention-days: 1
jvm-consumer-test:
name: JVM JAR Consumer - ${{ matrix.native-target }} / Java ${{ matrix.java-version }}
runs-on: ${{ matrix.runner }}
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(needs.changepacks.outputs.changepacks, 'packages/jvm/build.gradle.kts') }}
needs:
- changepacks
- jvm-jar
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
native-target: linux-x86_64
java-version: "17"
path-separator: ":"
- runner: ubuntu-24.04-arm
native-target: linux-aarch64
java-version: "17"
path-separator: ":"
- runner: macos-15-intel
native-target: macos-x86_64
java-version: "17"
path-separator: ":"
- runner: macos-15
native-target: macos-aarch64
java-version: "17"
path-separator: ":"
- runner: windows-2022
native-target: windows-x86_64
java-version: "17"
path-separator: ";"
- runner: ubuntu-24.04
native-target: linux-x86_64
java-version: "8"
path-separator: ":"
- runner: ubuntu-24.04
native-target: linux-x86_64
java-version: "25"
path-separator: ":"
java-options: --enable-native-access=ALL-UNNAMED
steps:
- name: Set up Java ${{ matrix.java-version }}
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: ${{ matrix.java-version }}
- name: Download fat JAR
uses: actions/download-artifact@v8
with:
name: jvm-fat-jar
path: jvm-dist
- name: Compile and run external consumer
shell: bash
run: |
JAR=$(find jvm-dist -maxdepth 1 -type f -name '*.jar' ! -name '*-sources.jar' ! -name '*-javadoc.jar' -print -quit)
if [ -z "$JAR" ]; then
echo "::error::JVM binary JAR artifact is missing"
exit 1
fi
if [ "$RUNNER_OS" = "Windows" ]; then
JAR=$(cygpath -w "$JAR")
fi
printf '%s\n' \
'import io.github.kdyann.braillify.Braillify;' \
'public final class Consumer {' \
' public static void main(String[] args) {' \
' byte[] encoded = Braillify.encode("안녕하세요");' \
' String unicode = Braillify.translateToUnicode("안녕하세요");' \
' String font = Braillify.translateToBrailleFont("안녕하세요");' \
' if (encoded.length == 0 || unicode.isEmpty() || font.isEmpty()) {' \
' throw new AssertionError("Braillify returned an empty result");' \
' }' \
' for (int i = 0; i < unicode.length(); i++) {' \
' char cell = unicode.charAt(i);' \
' if (cell < 0x2800 || cell > 0x28ff) {' \
' throw new AssertionError("Non-braille Unicode result");' \
' }' \
' }' \
' }' \
'}' > Consumer.java
javac -cp "$JAR" Consumer.java
java ${{ matrix.java-options }} -cp "$JAR${{ matrix.path-separator }}." Consumer
- name: Compile and run Kotlin consumer
if: matrix.native-target == 'linux-x86_64' && matrix.java-version == '17'
shell: bash
run: |
JAR=$(find jvm-dist -maxdepth 1 -type f -name '*.jar' ! -name '*-sources.jar' ! -name '*-javadoc.jar' -print -quit)
printf '%s\n' \
'import io.github.kdyann.braillify.Braillify' \
'fun main() {' \
' val encoded = Braillify.encode("안녕하세요")' \
' val unicode = Braillify.translateToUnicode("안녕하세요")' \
' val font = Braillify.translateToBrailleFont("안녕하세요")' \
' check(encoded.isNotEmpty() && unicode.isNotEmpty() && font.isNotEmpty())' \
'}' > Consumer.kt
kotlinc -jvm-target 1.8 -cp "$JAR" Consumer.kt -include-runtime -d consumer-kotlin.jar
java -cp "$JAR:consumer-kotlin.jar" ConsumerKt
jvm-publish:
name: JVM Publish to Maven Central
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(needs.changepacks.outputs.changepacks, 'packages/jvm/build.gradle.kts') }}
needs:
- changepacks
- jvm-native-build
- jvm-consumer-test
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v7
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Download native libraries
uses: actions/download-artifact@v8
with:
pattern: jvm-native-*
path: native-artifacts
- name: Stage native libraries
shell: bash
run: |
stage_native() {
local native_target="$1"
local library_name="$2"
local library
library=$(find "native-artifacts/jvm-native-$native_target" -type f -name "$library_name" -print -quit)
if [ -z "$library" ]; then
echo "::error::Missing $library_name for $native_target"
exit 1
fi
./packages/jvm/gradlew -p packages/jvm stageNativeLibrary \
-PnativeTarget="$native_target" \
-PnativeLibrary="$GITHUB_WORKSPACE/$library" \
--no-daemon
}
stage_native linux-x86_64 libbraillify_jvm.so
stage_native linux-aarch64 libbraillify_jvm.so
stage_native macos-x86_64 libbraillify_jvm.dylib
stage_native macos-aarch64 libbraillify_jvm.dylib
stage_native windows-x86_64 braillify_jvm.dll
- name: Check whether the JVM release already exists
id: jvm-release
shell: bash
run: |
VERSION=$(./gradlew properties --no-daemon --console=plain -q | sed -n 's/^version: //p' | head -1)
if [ -z "$VERSION" ]; then
echo "::error::Could not resolve the JVM publication version"
exit 1
fi
POM_URL="https://repo1.maven.org/maven2/io/github/kdyann/braillify/$VERSION/braillify-$VERSION.pom"
if curl -fsIL "$POM_URL" >/dev/null; then
echo "::notice::io.github.kdyann:braillify:$VERSION is already on Maven Central. Nothing to publish."
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
working-directory: packages/jvm
- name: Create signed Maven Central bundle
if: steps.jvm-release.outputs.exists != 'true'
run: ./gradlew createCentralBundle --no-daemon
working-directory: packages/jvm
env:
MAVEN_SIGNING_KEY: ${{ secrets.MAVEN_SIGNING_KEY }}
MAVEN_SIGNING_PASSWORD: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
- name: Verify Maven Central bundle
if: steps.jvm-release.outputs.exists != 'true'
shell: bash
run: |
test -s packages/jvm/build/central/central-bundle.zip
unzip -l packages/jvm/build/central/central-bundle.zip
unzip -Z1 packages/jvm/build/central/central-bundle.zip > central-bundle-entries.txt
for suffix in .jar .pom -sources.jar -javadoc.jar .asc .md5 .sha1; do
grep -F -- "$suffix" central-bundle-entries.txt >/dev/null || {
echo "::error::Central bundle is missing an artifact ending in $suffix"
exit 1
}
done
sha256sum packages/jvm/build/central/central-bundle.zip
- name: Generate artifact attestation
if: steps.jvm-release.outputs.exists != 'true'
uses: actions/attest-build-provenance@v4
with:
subject-path: packages/jvm/build/central/central-bundle.zip
- name: Publish and validate on Maven Central Portal
if: steps.jvm-release.outputs.exists != 'true'
run: ./gradlew publishToCentralPortal --no-daemon
working-directory: packages/jvm
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
MAVEN_SIGNING_KEY: ${{ secrets.MAVEN_SIGNING_KEY }}
MAVEN_SIGNING_PASSWORD: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
- name: Upload Central bundle
if: steps.jvm-release.outputs.exists != 'true'
uses: actions/upload-artifact@v7
with:
name: jvm-central-bundle
path: packages/jvm/build/central/central-bundle.zip
if-no-files-found: error
retention-days: 7