diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..33e4152c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.x86_64-unknown-linux-gnu] +linker = "scripts/zig-cc-x86_64-unknown-linux-gnu.sh" + +[target.aarch64-unknown-linux-gnu] +linker = "scripts/zig-cc-aarch64-unknown-linux-gnu.sh" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index aaa2489f..64114031 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -13,7 +13,7 @@ repository contains: ## Tech Stack - **Languages**: Rust (2021 edition), TypeScript, JavaScript -- **Build Tools**: Cargo, wasm-pack, Bun (v1.3.3) +- **Build Tools**: Cargo, Zig, cargo-zigbuild, wasm-pack, Bun (v1.3.3) - **Testing**: Cargo test, wasm-pack test, Bun test - **Key Dependencies**: - Rust: clap (v4.5+), crossterm (v0.28+), filedescriptor @@ -48,6 +48,10 @@ bun run build:page # Web page bun run build:websl:rust ``` +Release CI cross-compiles the CLI from Linux. Linux GNU targets use the checked-in Zig linker wrappers under `scripts/` +and `.cargo/config.toml`; Windows targets use `cargo-zigbuild` for `x86_64-pc-windows-gnu` and +`aarch64-pc-windows-gnullvm` instead of MSVC-hosted compilation. + ### Testing ```bash diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9e3e1895..8fef7633 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,20 +15,142 @@ concurrency: cancel-in-progress: true jobs: + cross-build: + name: Cross Build (${{ matrix.target }}) + runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + arch: x86_64 + package: deb + builder: cargo + linker_env: CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER + linker: scripts/zig-cc-x86_64-unknown-linux-gnu.sh + - target: aarch64-unknown-linux-gnu + arch: aarch64 + package: deb + builder: cargo + linker_env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER + linker: scripts/zig-cc-aarch64-unknown-linux-gnu.sh + - target: x86_64-pc-windows-gnu + arch: x86_64 + package: nsis + builder: zigbuild + - target: aarch64-pc-windows-gnullvm + arch: aarch64 + package: nsis + builder: zigbuild + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Cache Cargo Registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ubuntu-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Setup Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - name: Install Rust target + run: rustup target add ${{ matrix.target }} + - name: Configure Zig linker + if: matrix.builder == 'cargo' + run: | + echo "${{ matrix.linker_env }}=${{ matrix.linker }}" >> $GITHUB_ENV + - name: Install cargo-zigbuild + if: matrix.builder == 'zigbuild' + run: cargo install cargo-zigbuild --version 0.23.0 --locked + - name: Build Debug with Cargo + if: matrix.builder == 'cargo' + run: cargo build --manifest-path=apps/sl/Cargo.toml --target=${{ matrix.target }} + - name: Build Release with Cargo + if: matrix.builder == 'cargo' + run: COMPLETION_DIR="$(pwd)/completions" cargo build --manifest-path=apps/sl/Cargo.toml --release --target=${{ matrix.target }} + - name: Build Debug with cargo-zigbuild + if: matrix.builder == 'zigbuild' + run: cargo zigbuild --manifest-path=apps/sl/Cargo.toml --target=${{ matrix.target }} + - name: Build Release with cargo-zigbuild + if: matrix.builder == 'zigbuild' + run: COMPLETION_DIR="$(pwd)/completions" cargo zigbuild --manifest-path=apps/sl/Cargo.toml --release --target=${{ matrix.target }} + - name: Install cargo-deb + if: matrix.package == 'deb' + run: cargo install cargo-deb + - name: Build .deb Package + if: matrix.package == 'deb' + id: deb_build + run: | + cargo deb --manifest-path=apps/sl/Cargo.toml --target=${{ matrix.target }} --no-build + DEB_FILE=$(ls target/${{ matrix.target }}/debian/sl_*.deb) + echo "deb_file=${DEB_FILE}" >> $GITHUB_OUTPUT + echo "deb_name=$(basename "${DEB_FILE}")" >> $GITHUB_OUTPUT + - name: Test .deb Package + if: matrix.package == 'deb' + run: | + dpkg -I ${{ steps.deb_build.outputs.deb_file }} + dpkg -c ${{ steps.deb_build.outputs.deb_file }} + - name: Upload .deb Package + if: matrix.package == 'deb' + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.deb_build.outputs.deb_name }} + path: ${{ steps.deb_build.outputs.deb_file }} + - name: Install NSIS + if: matrix.package == 'nsis' + run: | + sudo apt-get update + sudo apt-get install -y nsis + - name: Build Windows installer + if: matrix.package == 'nsis' + id: installer_build + run: | + cmake -S . -B target/installer-${{ matrix.target }} -DSL_TARGET="${{ matrix.target }}" -DARCH="${{ matrix.arch }}" + cpack --config target/installer-${{ matrix.target }}/CPackConfig.cmake -C Release + INSTALLER=$(find target/installer-${{ matrix.target }} -maxdepth 1 -name 'sl-*.exe' -print -quit) + if [ -z "$INSTALLER" ]; then + echo "No installer produced" >&2 + exit 1 + fi + echo "installer=${INSTALLER}" >> $GITHUB_OUTPUT + echo "installer_name=$(basename "${INSTALLER}")" >> $GITHUB_OUTPUT + - name: Upload Installer + if: matrix.package == 'nsis' + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.installer_build.outputs.installer_name }} + path: ${{ steps.installer_build.outputs.installer }} + + macos-smoke: + name: macOS Smoke + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Cache Cargo Registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: macos-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Build Debug + run: cargo build + - name: Build Release + run: cargo build --release + - name: Test CLI + run: | + ./target/debug/sl -h + ./target/debug/sl -V + ./target/release/sl -h + ./target/release/sl -V + build-fedora: uses: ./.github/workflows/fedora-build.yml - build-posix: - uses: ./.github/workflows/posix-build.yml - build-ubuntu: - uses: ./.github/workflows/ubuntu-build.yml - build-windows: - uses: ./.github/workflows/win-build.yml build-pages: uses: ./.github/workflows/site.yml release: name: Release - needs: [build-fedora, build-posix, build-ubuntu, build-windows] + needs: [cross-build, macos-smoke, build-fedora] if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/sl-') runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/posix-build.yml b/.github/workflows/posix-build.yml deleted file mode 100644 index 9f070eaf..00000000 --- a/.github/workflows/posix-build.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Posix Build SL -on: - workflow_call: - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-latest, ubuntu-latest] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Cache Cargo Registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Build Debug - run: cargo build - - name: Build Release - run: cargo build --release - - name: Test - run: | - ./target/debug/sl -h - ./target/debug/sl -V - ./target/release/sl -h - ./target/release/sl -V diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml deleted file mode 100644 index 474e45d5..00000000 --- a/.github/workflows/ubuntu-build.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Ubuntu Build SL - -on: - workflow_call: - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Cache Cargo Registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ubuntu-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Install cargo-deb - run: cargo install cargo-deb - - name: Install cross-compilation toolchain - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - sudo apt-get update - sudo apt-get install -y gcc-aarch64-linux-gnu - - name: Install Rust target - run: rustup target add ${{ matrix.target }} - - name: Build Release - run: | - if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then - export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc - fi - COMPLETION_DIR="$(pwd)/completions" cargo build --release --target=${{ matrix.target }} - - name: Build .deb Package - id: deb_build - run: | - cargo deb --manifest-path=apps/sl/Cargo.toml --target=${{ matrix.target }} --no-build - DEB_FILE=$(ls target/${{ matrix.target }}/debian/sl_*.deb) - echo "deb_file=${DEB_FILE}" >> $GITHUB_OUTPUT - echo "deb_name=$(basename ${DEB_FILE})" >> $GITHUB_OUTPUT - - name: Test .deb Package - run: | - dpkg -I ${{ steps.deb_build.outputs.deb_file }} - dpkg -c ${{ steps.deb_build.outputs.deb_file }} - - name: Upload .deb Package - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.deb_build.outputs.deb_name }} - path: ${{ steps.deb_build.outputs.deb_file }} diff --git a/.github/workflows/win-build.yml b/.github/workflows/win-build.yml deleted file mode 100644 index 5552b4e0..00000000 --- a/.github/workflows/win-build.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Windows Build SL - -on: - workflow_call: - -jobs: - build: - runs-on: windows-latest - strategy: - matrix: - target: [aarch64, x86_64] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Cache Cargo Registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Install NSIS - run: | - winget install --id NSIS.NSIS --silent --accept-source-agreements --accept-package-agreements - if (Test-Path "C:\Program Files (x86)\NSIS") { - Add-Content $env:GITHUB_PATH "C:\Program Files (x86)\NSIS" - } elseif (Test-Path "C:\Program Files\NSIS") { - Add-Content $env:GITHUB_PATH "C:\Program Files\NSIS" - } - shell: pwsh - - name: Install toolchain - run: rustup target install ${{ matrix.target }}-pc-windows-msvc - - name: Build Debug - run: cargo build --target=${{ matrix.target }}-pc-windows-msvc - - name: Build Release - run: cargo build --target=${{ matrix.target }}-pc-windows-msvc --release - - name: Build and Process Installer - id: installer_build - working-directory: ${{github.workspace}}/target - shell: bash - run: | - cmake .. -DARCH="${{ matrix.target }}" - cpack -C Release - if [ "${{ matrix.target }}" == "aarch64" ]; then - INSTALLER=$(ls sl-*.exe) - NEW_INSTALLER=$(echo $INSTALLER | sed 's/win64/woa64/') - mv $INSTALLER $NEW_INSTALLER - fi - INSTALLER=$(ls sl-*.exe) - echo "installer=${INSTALLER}" >> $GITHUB_OUTPUT - - name: Upload Installer - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.installer_build.outputs.installer }} - path: target/${{ steps.installer_build.outputs.installer }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 6019c805..48f7c3e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,10 +2,27 @@ cmake_minimum_required(VERSION 3.28) project("SL" VERSION 6.1.5 DESCRIPTION "CHOO CHOO" - LANGUAGES C) + LANGUAGES NONE) -find_program(SL_EXE NAMES sl.exe PATHS "${PROJECT_BINARY_DIR}/release" "${PROJECT_BINARY_DIR}/${ARCH}-pc-windows-msvc/release" REQUIRED) -install(FILES ${CURSES_DLL} ${SL_EXE} DESTINATION bin COMPONENT applications) +set(SL_TARGET "" CACHE STRING "Rust target triple containing the sl.exe release build") +set(ARCH "" CACHE STRING "Release artifact architecture label") + +set(SL_EXE_SEARCH_PATHS "${CMAKE_SOURCE_DIR}/target/release") +if(SL_TARGET) + list(APPEND SL_EXE_SEARCH_PATHS "${CMAKE_SOURCE_DIR}/target/${SL_TARGET}/release") +endif() +if(ARCH) + list(APPEND SL_EXE_SEARCH_PATHS "${CMAKE_SOURCE_DIR}/target/${ARCH}-pc-windows-msvc/release") +endif() + +find_program(SL_EXE NAMES sl.exe PATHS ${SL_EXE_SEARCH_PATHS} NO_DEFAULT_PATH REQUIRED) + +set(SL_INSTALL_FILES "${SL_EXE}") +if(DEFINED CURSES_DLL AND NOT "${CURSES_DLL}" STREQUAL "") + list(APPEND SL_INSTALL_FILES "${CURSES_DLL}") +endif() + +install(FILES ${SL_INSTALL_FILES} DESTINATION bin COMPONENT applications) set(CPACK_PACKAGE_NAME "sl") set(CPACK_PACKAGE_VENDOR "sl") @@ -15,6 +32,16 @@ set(CPACK_PACKAGE_VERSION_MAJOR "6") set(CPACK_PACKAGE_VERSION_MINOR "1") set(CPACK_PACKAGE_VERSION_PATCH "5") set(CPACK_PACKAGE_INSTALL_DIRECTORY "sl") +set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}") +set(CPACK_GENERATOR "NSIS") +if(ARCH STREQUAL "aarch64") + set(CPACK_SYSTEM_NAME "woa64") +elseif(ARCH STREQUAL "x86_64") + set(CPACK_SYSTEM_NAME "win64") +endif() +if(CPACK_SYSTEM_NAME) + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_SYSTEM_NAME}") +endif() set(CPACK_COMPONENTS_ALL applications) set(CPACK_COMPONENT_APPLICATIONS_DISPLAY_NAME "sl") set(CPACK_COMPONENT_APPLICATIONS_DESCRIPTION "SL (Steam Locomotive) runs across your terminal") diff --git a/README.ja.md b/README.ja.md index 6d2855fd..bbf2a153 100644 --- a/README.ja.md +++ b/README.ja.md @@ -15,3 +15,16 @@ bun run setup `bun run setup` は、Web サイトのビルドに必要な Rust の `wasm32-unknown-unknown` ターゲットと `wasm-pack` をインストールします。ビルドや開発用スクリプトは、これらが足りない場合にセットアップコマンドを表示します。 + +### Zig によるクロスコンパイル + +リリース CI では [Zig](https://ziglang.org/) +を Rust の明示的なクロスターゲット向け C リンカードライバーとして使います。ローカルで同じビルドを行う場合は、Zig をインストールし、Rust ターゲットを追加してください。 + +```sh +rustup target add aarch64-unknown-linux-gnu +cargo build --manifest-path apps/sl/Cargo.toml --release --target aarch64-unknown-linux-gnu +``` + +チェックインされた Cargo 設定は Linux GNU クロスターゲットに Zig を使います。CI では Windows の `x86_64-pc-windows-gnu` +と `aarch64-pc-windows-gnullvm` ビルドに `cargo-zigbuild` を使い、MSVC 固有のコンパイルを避けます。 diff --git a/README.md b/README.md index ad3df895..c5d46e0d 100644 --- a/README.md +++ b/README.md @@ -111,3 +111,16 @@ bun run setup `bun run setup` installs the Rust `wasm32-unknown-unknown` target and `wasm-pack`, which are required by the website build. The build and dev scripts also check for these prerequisites and print the setup command if anything is missing. + +### Cross-compiling with Zig + +Release CI uses [Zig](https://ziglang.org/) as the C linker driver for explicit Rust cross targets. Install Zig and add +the Rust target before building locally: + +```sh +rustup target add aarch64-unknown-linux-gnu +cargo build --manifest-path apps/sl/Cargo.toml --release --target aarch64-unknown-linux-gnu +``` + +The checked-in Cargo config uses Zig for Linux GNU cross targets. CI uses `cargo-zigbuild` for Windows +`x86_64-pc-windows-gnu` and `aarch64-pc-windows-gnullvm` builds, avoiding MSVC-specific compilation. diff --git a/scripts/zig-cc-aarch64-unknown-linux-gnu.sh b/scripts/zig-cc-aarch64-unknown-linux-gnu.sh new file mode 100755 index 00000000..7fbec4d7 --- /dev/null +++ b/scripts/zig-cc-aarch64-unknown-linux-gnu.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +set -eu + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec "$script_dir/zig-cc-linker.sh" aarch64-unknown-linux-gnu "$@" diff --git a/scripts/zig-cc-linker.sh b/scripts/zig-cc-linker.sh new file mode 100755 index 00000000..c38e8c36 --- /dev/null +++ b/scripts/zig-cc-linker.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh +set -eu + +if [ "$#" -lt 1 ]; then + echo "usage: zig-cc-linker.sh [linker-args...]" >&2 + exit 64 +fi + +rust_target=$1 +shift + +case "$rust_target" in + x86_64-unknown-linux-gnu) + zig_target=x86_64-linux-gnu.2.17 + ;; + aarch64-unknown-linux-gnu) + zig_target=aarch64-linux-gnu.2.17 + ;; + *) + echo "unsupported Rust target for Zig linker: $rust_target" >&2 + exit 65 + ;; +esac + +exec "${ZIG:-zig}" cc -target "$zig_target" "$@" diff --git a/scripts/zig-cc-x86_64-unknown-linux-gnu.sh b/scripts/zig-cc-x86_64-unknown-linux-gnu.sh new file mode 100755 index 00000000..56f44f72 --- /dev/null +++ b/scripts/zig-cc-x86_64-unknown-linux-gnu.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +set -eu + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +exec "$script_dir/zig-cc-linker.sh" x86_64-unknown-linux-gnu "$@" diff --git a/win-build.ps1 b/win-build.ps1 deleted file mode 100644 index 7f1b1148..00000000 --- a/win-build.ps1 +++ /dev/null @@ -1 +0,0 @@ -cargo build --release \ No newline at end of file