diff --git a/.github/workflows/build_binary.yml b/.github/workflows/build_binary.yml index ba7555b..9ce3d4b 100644 --- a/.github/workflows/build_binary.yml +++ b/.github/workflows/build_binary.yml @@ -5,21 +5,35 @@ description: | on: push: - branches: [ 'main' ] - tags: [ '*' ] + branches: ['main'] + tags: ['*'] pull_request: - branches: [ '*' ] + branches: ['*'] jobs: - build-binary: - name: 'Build binary' - runs-on: ubuntu-latest + generate-target-metadata: + name: 'Generate target metadata (${{ matrix.target }})' + runs-on: ${{ matrix.runner }} env: - ASTREIN_VERSION: '1.1.0' - ASTREIN_SHA256: 'd5964319ea5a3742e4d7223ee5963d6aa3b24c8f67ef75c61d591c635f413497' - permissions: - contents: write + ASTREIN_VERSION: '1.2.0' + ASTREIN_SHA256: ${{ matrix.astrein-sha256 }} + + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-linux-gnu + arch: x86_64 + runner: ubuntu-24.04 + astrein-archive: astrein-linux-x86_64.tar.gz + astrein-sha256: 'cd0bb42f883322d9b7bbf1a34b57acfab83bcf78cf529a5cd122234fc8787582' + + - target: aarch64-linux-gnu + arch: aarch64 + runner: ubuntu-24.04-arm + astrein-archive: astrein-linux-aarch64.tar.gz + astrein-sha256: '6e53ac2f5a8b341e7a49439129790f4b1b82d5073a98ddf44a189fe9a9ebf732' steps: - name: 'Checkout repository' @@ -27,21 +41,30 @@ jobs: with: fetch-depth: 0 - - name: 'Setup GCC' - uses: egor-tensin/setup-gcc@v2 - with: - version: '16' - platform: 'x64' + - name: 'Validate ASTrein checksum' + run: | + if [[ ! "${ASTREIN_SHA256}" =~ ^[[:xdigit:]]{64}$ ]]; then + echo "Set the SHA-256 for ${{ matrix.astrein-archive }} after ASTrein v${ASTREIN_VERSION} is released." >&2 + exit 1 + fi + + - name: 'Install native compiler' + run: | + sudo apt-get update + sudo apt-get install -y \ + gcc-14 \ + g++-14 - name: 'Download ASTrein' run: | curl --fail --location \ - --output "${RUNNER_TEMP}/astrein-linux-x86_64.tar.gz" \ - "https://github.com/Katze719/ASTrein/releases/download/v${ASTREIN_VERSION}/astrein-linux-x86_64.tar.gz" - echo "${ASTREIN_SHA256} ${RUNNER_TEMP}/astrein-linux-x86_64.tar.gz" \ + --output "${RUNNER_TEMP}/${{ matrix.astrein-archive }}" \ + "https://github.com/Katze719/ASTrein/releases/download/v${ASTREIN_VERSION}/${{ matrix.astrein-archive }}" + echo "${ASTREIN_SHA256} ${RUNNER_TEMP}/${{ matrix.astrein-archive }}" \ | sha256sum --check - + mkdir -p "${RUNNER_TEMP}/astrein" - tar -xzf "${RUNNER_TEMP}/astrein-linux-x86_64.tar.gz" \ + tar -xzf "${RUNNER_TEMP}/${{ matrix.astrein-archive }}" \ -C "${RUNNER_TEMP}/astrein" \ --strip-components=1 "${RUNNER_TEMP}/astrein/bin/astrein" --version @@ -51,83 +74,234 @@ jobs: with: cmake-version: '3.31.x' - - name: 'Configure CMake' + - name: 'Configure native metadata context' run: | - cmake --preset linux-gcc-release \ - -DCMAKE_C_COMPILER=gcc-16 \ - -DCMAKE_CXX_COMPILER=g++-16 \ + cmake -S . -B "build/ffi/${{ matrix.arch }}" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=gcc-14 \ + -DCMAKE_CXX_COMPILER=g++-14 \ -DCPP_BINDINGS_LINUX_ENABLE_FFI_JSON_EXPORT=ON \ - -DCPP_BINDINGS_LINUX_ASTREIN_EXECUTABLE="${RUNNER_TEMP}/astrein/bin/astrein" + -DCPP_BINDINGS_LINUX_ASTREIN_EXECUTABLE="${RUNNER_TEMP}/astrein/bin/astrein" \ + -DCPP_BINDINGS_LINUX_FFI_JSON_OUTPUT="${GITHUB_WORKSPACE}/dist/ffi/${{ matrix.arch }}.ffi.json" + + - name: 'Generate metadata' + run: | + cmake --build "build/ffi/${{ matrix.arch }}" \ + --target cpp_bindings_linux_ffi_json + + - name: 'Upload target-specific FFI metadata' + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: cpp-bindings-linux-ffi-${{ matrix.target }} + path: dist/ffi/${{ matrix.arch }}.ffi.json + + generate-metadata: + name: 'Generate metadata' + needs: ['generate-target-metadata'] + runs-on: ubuntu-24.04 + steps: + - name: 'Checkout repository' + uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: 'Build' + - name: 'Setup CMake' + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.31.x' + + - name: 'Install package-context compiler' run: | - cmake --build --preset linux-gcc-release \ - --target cpp_bindings_linux cpp_bindings_linux_ffi_json - - - name: 'Set PACKAGE_VERSION from env.sh' + sudo apt-get update + sudo apt-get install -y \ + gcc-14 \ + g++-14 + + - name: 'Configure package context' + run: | + cmake -S . -B build/package -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=gcc-14 \ + -DCMAKE_CXX_COMPILER=g++-14 + + - name: 'Set package version' id: version run: | - . build/env.sh - echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> $GITHUB_OUTPUT + . build/package/env.sh + echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> "${GITHUB_OUTPUT}" - - name: 'Check tag' - id: 'check-tag' + - name: 'Check package version' + id: check-tag env: PACKAGE_VERSION: ${{ steps.version.outputs.PACKAGE_VERSION }} - run: | - if [[ "$PACKAGE_VERSION" =~ ^(0|[1-9][[:digit:]]*)\.(0|[1-9][[:digit:]]*)\.(0|[1-9][[:digit:]]*)(-(alpha|rc|beta|experimental)\.[1-9][[:digit:]]*)?$ ]]; then - echo "Version \"$PACKAGE_VERSION\" is a valid package version." - echo "IS_VALID_PACKAGE_VERSION=true" >> $GITHUB_OUTPUT + if [[ "${PACKAGE_VERSION}" =~ ^(0|[1-9][[:digit:]]*)\.(0|[1-9][[:digit:]]*)\.(0|[1-9][[:digit:]]*)(-(alpha|rc|beta|experimental)\.[1-9][[:digit:]]*)?$ ]]; then + echo "Version \"${PACKAGE_VERSION}\" is a valid package version." + echo "IS_VALID_PACKAGE_VERSION=true" >> "${GITHUB_OUTPUT}" else - echo "Version \"$PACKAGE_VERSION\" is not a valid package version." - echo "IS_VALID_PACKAGE_VERSION=false" >> $GITHUB_OUTPUT + echo "Version \"${PACKAGE_VERSION}\" is not a valid package version." + echo "IS_VALID_PACKAGE_VERSION=false" >> "${GITHUB_OUTPUT}" fi - - name: 'Upload artifacts' - uses: actions/upload-artifact@v4 + - name: 'Download target-specific FFI metadata' + uses: actions/download-artifact@v4 with: - if-no-files-found: error - name: libcpp_bindings_linux - path: | - build/libcpp_bindings_linux.so - build/cpp_bindings_linux.ffi.json + pattern: cpp-bindings-linux-ffi-* + path: dist/ffi + merge-multiple: true - - name: 'Create GitHub Release' - if: github.ref_type == 'tag' && steps.check-tag.outputs.IS_VALID_PACKAGE_VERSION == 'true' - uses: softprops/action-gh-release@v2 + - name: 'Upload combined FFI metadata' + uses: actions/upload-artifact@v4 with: - name: 'v${{ steps.version.outputs.PACKAGE_VERSION }}' - tag_name: ${{ github.ref_name }} - generate_release_notes: true - files: | - build/libcpp_bindings_windows.so - build/cpp_bindings_linux.ffi.json + if-no-files-found: error + name: cpp-bindings-linux-ffi + path: dist/ffi/*.ffi.json outputs: package_version: ${{ steps.version.outputs.PACKAGE_VERSION }} is_valid_package_version: ${{ steps.check-tag.outputs.IS_VALID_PACKAGE_VERSION }} + build-binary: + name: 'Build binary (${{ matrix.target }})' + runs-on: ${{ matrix.runner }} + container: + image: ${{ matrix.image }} + + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-linux-gnu + runner: ubuntu-24.04 + image: quay.io/pypa/manylinux_2_28_x86_64@sha256:4dc41da7df20400310c80d162a2fe2d2c2f3d9734d8dec20f6b9843711618deb + artifact: libcpp_bindings_linux-x86_64-linux-gnu + + - target: aarch64-linux-gnu + runner: ubuntu-24.04-arm + image: quay.io/pypa/manylinux_2_28_aarch64@sha256:29a6ceb78de5b00494e6e7456a72782a4cb54238de102e7491d15fe47789b4d1 + artifact: libcpp_bindings_linux-aarch64-linux-gnu + + steps: + - name: 'Checkout repository' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 'Configure portable release' + run: | + cmake -S . -B build -G "Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=gcc \ + -DCMAKE_CXX_COMPILER=g++ \ + -DCPP_BINDINGS_LINUX_PORTABLE_CPU_BASELINE=ON \ + -DCPP_BINDINGS_LINUX_STATIC_CXX_RUNTIME=ON + + - name: 'Build and test' + run: | + cmake --build build \ + --target cpp_bindings_linux cpp_bindings_linux_tests \ + --parallel 4 + ctest --test-dir build \ + --output-on-failure \ + --output-junit test-report.xml + + - name: 'Stage and verify binary' + run: | + mkdir -p "dist/${{ matrix.target }}" + cp -L build/libcpp_bindings_linux.so \ + "dist/${{ matrix.target }}/libcpp_bindings_linux.so" + ./scripts/verify_release_binary.sh \ + "dist/${{ matrix.target }}/libcpp_bindings_linux.so" \ + "${{ matrix.target }}" + + - name: 'Upload test report' + if: always() + uses: actions/upload-artifact@v4 + with: + if-no-files-found: warn + name: test-report-${{ matrix.target }} + path: build/test-report.xml + + - name: 'Upload binary' + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: ${{ matrix.artifact }} + path: dist/${{ matrix.target }}/libcpp_bindings_linux.so + test-unit-cpp: name: 'Run: Test Unit C++' needs: ['build-binary'] uses: './.github/workflows/test_unit_cpp.yml' - with: - artifact-name: libcpp_bindings_linux - permissions: contents: read checks: write - + + with: + artifact-name: libcpp_bindings_linux-x86_64-linux-gnu + + create-release: + name: 'Create GitHub release' + needs: ['generate-metadata', 'build-binary', 'test-unit-cpp'] + if: github.ref_type == 'tag' && needs.generate-metadata.outputs.is_valid_package_version == 'true' + runs-on: ubuntu-24.04 + permissions: + contents: write + + steps: + - name: 'Download x86-64 binary' + uses: actions/download-artifact@v4 + with: + name: libcpp_bindings_linux-x86_64-linux-gnu + path: release/x86_64 + + - name: 'Download AArch64 binary' + uses: actions/download-artifact@v4 + with: + name: libcpp_bindings_linux-aarch64-linux-gnu + path: release/aarch64 + + - name: 'Download FFI metadata' + uses: actions/download-artifact@v4 + with: + name: cpp-bindings-linux-ffi + path: release/ffi + + - name: 'Name release assets' + run: | + mv release/x86_64/libcpp_bindings_linux.so \ + release/libcpp_bindings_linux-x86_64-linux-gnu.so + mv release/aarch64/libcpp_bindings_linux.so \ + release/libcpp_bindings_linux-aarch64-linux-gnu.so + mv release/ffi/x86_64.ffi.json \ + release/cpp_bindings_linux-x86_64-linux-gnu.ffi.json + mv release/ffi/aarch64.ffi.json \ + release/cpp_bindings_linux-aarch64-linux-gnu.ffi.json + + - name: 'Create GitHub release' + uses: softprops/action-gh-release@v2 + with: + name: 'v${{ needs.generate-metadata.outputs.package_version }}' + tag_name: ${{ github.ref_name }} + generate_release_notes: true + files: | + release/libcpp_bindings_linux-x86_64-linux-gnu.so + release/libcpp_bindings_linux-aarch64-linux-gnu.so + release/cpp_bindings_linux-x86_64-linux-gnu.ffi.json + release/cpp_bindings_linux-aarch64-linux-gnu.ffi.json + publish-jsr: name: 'Run: Publish JSR' - needs: ['build-binary', 'test-unit-cpp'] + needs: ['generate-metadata', 'build-binary', 'test-unit-cpp'] uses: './.github/workflows/publish_jsr.yml' - with: - publish: ${{ needs.build-binary.outputs.is_valid_package_version == 'true' }} - version: ${{ needs.build-binary.outputs.package_version }} - artifact-name: libcpp_bindings_linux - permissions: contents: read id-token: write + + with: + publish: ${{ needs.generate-metadata.outputs.is_valid_package_version == 'true' }} + version: ${{ needs.generate-metadata.outputs.package_version }} + x86-64-artifact-name: libcpp_bindings_linux-x86_64-linux-gnu + aarch64-artifact-name: libcpp_bindings_linux-aarch64-linux-gnu + ffi-artifact-name: cpp-bindings-linux-ffi diff --git a/.github/workflows/publish_jsr.yml b/.github/workflows/publish_jsr.yml index 62f3630..9510660 100644 --- a/.github/workflows/publish_jsr.yml +++ b/.github/workflows/publish_jsr.yml @@ -16,8 +16,18 @@ on: required: true type: string - artifact-name: - description: 'Name of the artifact' + x86-64-artifact-name: + description: 'Name of the x86-64 binary artifact' + required: true + type: string + + aarch64-artifact-name: + description: 'Name of the AArch64 binary artifact' + required: true + type: string + + ffi-artifact-name: + description: 'Name of the target-specific FFI metadata artifact' required: true type: string @@ -40,22 +50,43 @@ jobs: with: deno-version: '2.x' - - name: 'Download artifact' + - name: 'Download x86-64 binary' + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.x86-64-artifact-name }} + path: 'artifacts/x86_64' + + - name: 'Download AArch64 binary' uses: actions/download-artifact@v4 with: - name: ${{ inputs.artifact-name }} - path: 'artifacts' + name: ${{ inputs.aarch64-artifact-name }} + path: 'artifacts/aarch64' + + - name: 'Download FFI metadata' + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.ffi-artifact-name }} + path: 'artifacts/ffi' - name: 'Prepare files for JSR' run: | - mkdir -p ./jsr/bin - cp ./artifacts/libcpp_bindings_linux.so ./jsr/bin/x86_64.so - cp ./artifacts/cpp_bindings_linux.ffi.json ./jsr/bin/x86_64.ffi.json + mkdir -p ./jsr/bin/x86_64 + cp ./artifacts/x86_64/libcpp_bindings_linux.so ./jsr/bin/x86_64/library.so + cp ./artifacts/ffi/x86_64.ffi.json ./jsr/bin/x86_64/ffi.json + + mkdir -p ./jsr/bin/aarch64 + cp ./artifacts/aarch64/libcpp_bindings_linux.so ./jsr/bin/aarch64/library.so + cp ./artifacts/ffi/aarch64.ffi.json ./jsr/bin/aarch64/ffi.json + + ./jsr/scripts/binary_to_json.sh \ + ./jsr/bin/x86_64/library.so \ + ./jsr/bin/x86_64/library.json \ + x86_64-linux-gnu ./jsr/scripts/binary_to_json.sh \ - ./artifacts/libcpp_bindings_linux.so \ - ./jsr/bin/x86_64.json \ - linux-x86_64 + ./jsr/bin/aarch64/library.so \ + ./jsr/bin/aarch64/library.json \ + aarch64-linux-gnu ./jsr/scripts/set_version.sh \ jsr/jsr.json \ diff --git a/.github/workflows/test_deno.yml b/.github/workflows/test_deno.yml index 5e5fd1d..9b75394 100644 --- a/.github/workflows/test_deno.yml +++ b/.github/workflows/test_deno.yml @@ -3,16 +3,23 @@ name: Deno Integration Tests on: push: branches: [ main, master ] - pull_request: + pull_request: {} jobs: deno-tests: - name: Deno integration tests (deno ${{ matrix.deno }}) - runs-on: ubuntu-latest + name: Deno integration tests (${{ matrix.arch }}, deno ${{ matrix.deno }}) + runs-on: ${{ matrix.runner }} strategy: + fail-fast: false matrix: deno: ["2.6.0", "2.5.0"] + arch: [x86_64, aarch64] + include: + - arch: x86_64 + runner: ubuntu-24.04 + - arch: aarch64 + runner: ubuntu-24.04-arm steps: - name: Checkout repository @@ -23,33 +30,34 @@ jobs: with: deno-version: v${{ matrix.deno }} - - name: Setup GCC - uses: egor-tensin/setup-gcc@v2 + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 with: - version: '16' - platform: 'x64' + cmake-version: '3.31.x' - name: Install build dependencies run: | sudo apt-get update - sudo apt-get install -y cmake ninja-build socat git + sudo apt-get install -y gcc-14 g++-14 ninja-build socat git - name: Configure CMake run: | cmake -S . -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_COMPILER=gcc-16 \ - -DCMAKE_CXX_COMPILER=g++-16 + -DCMAKE_C_COMPILER=gcc-14 \ + -DCMAKE_CXX_COMPILER=g++-14 \ + -DCPP_BINDINGS_LINUX_PORTABLE_CPU_BASELINE=ON - name: Build run: | - cmake --build build --config Release + cmake --build build --config Release --target cpp_bindings_linux - name: Start virtual serial echo (socat) run: | socat -d -d pty,raw,echo=0,link=/tmp/ttyCI_A,mode=666 pty,raw,echo=0,link=/tmp/ttyCI_B,mode=666 & sleep 2 - stdbuf -i0 -o0 cat < /tmp/ttyCI_B > /tmp/ttyCI_B & + exec 3<>/tmp/ttyCI_B + stdbuf -i0 -o0 cat <&3 >&3 & sleep 1 - name: Run Deno integration tests diff --git a/.github/workflows/test_unit_cpp.yml b/.github/workflows/test_unit_cpp.yml index 2e852bf..e3c88e7 100644 --- a/.github/workflows/test_unit_cpp.yml +++ b/.github/workflows/test_unit_cpp.yml @@ -22,6 +22,8 @@ jobs: steps: - name: 'Checkout repository' uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: 'Download artifact' uses: actions/download-artifact@v4 @@ -51,15 +53,23 @@ jobs: run: | cmake --build --preset linux-gcc-release --target cpp_bindings_linux_tests + - name: 'Use portable release binary in tests' + run: | + cp -L artifacts/libcpp_bindings_linux.so \ + "$(readlink -f build/libcpp_bindings_linux.so)" + - name: 'Install test dependencies' run: | sudo apt-get install -y socat - name: Start virtual serial echo (socat) run: | - socat -d -d pty,raw,echo=0,link=$SERIAL_TEST_PORT_IN,mode=666 pty,raw,echo=0,link=$SERIAL_TEST_PORT_OUT,mode=666 & + socat -d -d \ + "pty,raw,echo=0,link=${SERIAL_TEST_PORT_IN},mode=666" \ + "pty,raw,echo=0,link=${SERIAL_TEST_PORT_OUT},mode=666" & sleep 2 - stdbuf -i0 -o0 cat < $SERIAL_TEST_PORT_OUT > $SERIAL_TEST_PORT_OUT & + exec 3<>"${SERIAL_TEST_PORT_OUT}" + stdbuf -i0 -o0 cat <&3 >&3 & sleep 1 - name: 'Run tests' @@ -73,7 +83,7 @@ jobs: ./cpp_bindings_linux_tests \ --gtest_color=yes \ - --gtest_output=xml:$TEST_REPORT_NAME + --gtest_output="xml:${TEST_REPORT_NAME}" - name: 'Upload test report' if: always() @@ -93,4 +103,3 @@ jobs: annotate_notice: true transformers: '[{"searchValue": "${{ github.workspace }}", "replaceValue": ""}]' check_title_template: '{{FILE_NAME}} | {{TEST_NAME}}' - diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e5adc1..783329d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,16 @@ option( "Enable ASTrein JSON export for the cpp-core FFI headers" OFF ) +option( + CPP_BINDINGS_LINUX_PORTABLE_CPU_BASELINE + "Build for the generic CPU baseline of the current architecture" + OFF +) +option( + CPP_BINDINGS_LINUX_STATIC_CXX_RUNTIME + "Statically link the GNU C++ and compiler runtimes into the shared library" + OFF +) set( CPP_BINDINGS_LINUX_ASTREIN_EXECUTABLE "" @@ -169,19 +179,56 @@ set_target_properties( target_include_directories( cpp_bindings_linux - PUBLIC + PRIVATE $ $ + $ $ ) -target_link_libraries( - cpp_bindings_linux - PUBLIC - cpp_core::cpp_core -) +target_compile_features(cpp_bindings_linux PRIVATE cxx_std_26) + +if(CPP_BINDINGS_LINUX_PORTABLE_CPU_BASELINE) + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _cpp_bindings_linux_processor) -target_compile_features(cpp_bindings_linux PUBLIC cxx_std_26) + if(_cpp_bindings_linux_processor MATCHES "^(x86_64|amd64)$") + target_compile_options( + cpp_bindings_linux + PRIVATE + -march=x86-64 + -mtune=generic + ) + elseif(_cpp_bindings_linux_processor MATCHES "^(aarch64|arm64)$") + target_compile_options( + cpp_bindings_linux + PRIVATE + -march=armv8-a + ) + else() + message( + FATAL_ERROR + "CPP_BINDINGS_LINUX_PORTABLE_CPU_BASELINE does not support " + "CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}" + ) + endif() +endif() + +if(CPP_BINDINGS_LINUX_STATIC_CXX_RUNTIME) + if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + message( + FATAL_ERROR + "CPP_BINDINGS_LINUX_STATIC_CXX_RUNTIME currently requires GCC" + ) + endif() + + target_link_options( + cpp_bindings_linux + PRIVATE + -static-libstdc++ + -static-libgcc + "LINKER:--exclude-libs,ALL" + ) +endif() file(GLOB SRC_UNIT_TESTS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.test.cpp") file(GLOB TESTS_INTEGRATION "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.test.cpp") @@ -197,6 +244,7 @@ if(TEST_SOURCES) ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/generated + ${cpp_core_SOURCE_DIR}/include ) target_link_libraries( diff --git a/README.md b/README.md index c72610c..22ebb03 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ configuring, reading from, and writing to serial ports. - Git - CMake 3.30 or newer - Ninja -- A compiler with C++26 support (CI uses GCC 16) +- A compiler with sufficient C++26 support CMake downloads `cpp-core` and GoogleTest automatically during configuration. @@ -28,12 +28,34 @@ cmake --build --preset linux-gcc-release --target cpp_bindings_linux The shared library is written to `build/libcpp_bindings_linux.so`. +Official release and JSR artifacts are built for these GNU/Linux targets: + +| Target | CPU baseline | Minimum glibc | +| --- | --- | --- | +| `x86_64-linux-gnu` | generic x86-64 | 2.28 | +| `aarch64-linux-gnu` | ARMv8-A | 2.28 | + +The release builds statically include the GNU C++ and compiler runtimes. They +still use the target system's glibc and therefore require glibc 2.28 or newer. +To reproduce the portable release configuration for the host architecture: + +```sh +cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCPP_BINDINGS_LINUX_PORTABLE_CPU_BASELINE=ON \ + -DCPP_BINDINGS_LINUX_STATIC_CXX_RUNTIME=ON +cmake --build build --target cpp_bindings_linux +``` + +The CI release builds use pinned `manylinux_2_28` images with GCC 14. GCC 16 +is used separately to generate the ASTrein FFI metadata. + To select a specific compiler, add it while configuring, for example: ```sh cmake --preset linux-gcc-release \ - -DCMAKE_C_COMPILER=gcc-16 \ - -DCMAKE_CXX_COMPILER=g++-16 + -DCMAKE_C_COMPILER=gcc-14 \ + -DCMAKE_CXX_COMPILER=g++-14 ``` ## Tests diff --git a/jsr/README.md b/jsr/README.md index bfac12e..228a6e7 100644 --- a/jsr/README.md +++ b/jsr/README.md @@ -3,27 +3,40 @@ [![Build Binary](https://github.com/Serial-IO/cpp-bindings-linux/actions/workflows/build_binary.yml/badge.svg)](https://github.com/Serial-IO/cpp-bindings-linux/actions/workflows/build_binary.yml) [![JSR](https://jsr.io/badges/@serial/cpp-bindings-linux)](https://jsr.io/@serial/cpp-bindings-linux) -Binaries are provided as a [package on JSR](https://jsr.io/@serial/cpp-bindings-linux). They are serialized as a base64 string inside the JSON file. +Binaries are provided as a +[package on JSR](https://jsr.io/@serial/cpp-bindings-linux). They are serialized +as a base64 string inside the JSON file. -The package also includes cpp-core FFI API metadata generated with -[ASTrein](https://github.com/Katze719/ASTrein) at `bin/x86_64.ffi.json`. -It describes the exported C symbols, parameter and return types, callbacks, -default values, and API documentation used by downstream FFI adapter generators. +The package contains portable binaries for `x86_64-linux-gnu` and +`aarch64-linux-gnu`, both requiring glibc 2.28 or newer. The x86-64 artifact +uses the generic x86-64 baseline. -This package is primarily intended as a dependency for [`@serial/serial`](https://jsr.io/@serial/serial). However, it can also be used independently. +It also includes cpp-core FFI API metadata generated with +[ASTrein](https://github.com/Katze719/ASTrein) at `bin/x86_64/ffi.json` and +`bin/aarch64/ffi.json`. It describes the exported C symbols, parameter and +return types, callbacks, default values, and API documentation used by +downstream FFI adapter generators. +This package is primarily intended as a dependency for +[`@serial/serial`](https://jsr.io/@serial/serial). However, it can also be used +independently. ## Usage -Import the JSON and write the binary data to disk: +Import the JSON and write the binary data to disk. Each architecture export also +contains its matching FFI metadata: ```ts -import {x86_64} from '@serial/cpp-bindings-linux/bin'; +import { aarch64, x86_64 } from "@serial/cpp-bindings-linux/bin"; -Deno.writeFileSync(`./${x86_64.filename}`, Uint8Array.fromBase64(x86_64.data)); +const binary = Deno.build.arch === "aarch64" ? aarch64 : x86_64; +Deno.writeFileSync(`./${binary.filename}`, Uint8Array.fromBase64(binary.data)); +// The matching FFI metadata is available as `binary.ffi`. // Now you can open the binary using for example `Deno.dlopen`... ``` > [!NOTE] -> For a more in depth guide, check out the [Wiki](https://github.com/Serial-IO/cpp-bindings-linux/wiki) section on how to use the C++ bindings for Linux. +> For a more in depth guide, check out the +> [Wiki](https://github.com/Serial-IO/cpp-bindings-linux/wiki) section on how to +> use the C++ bindings for Linux. diff --git a/jsr/src/bin/index.ts b/jsr/src/bin/index.ts index 66555bc..e73e809 100644 --- a/jsr/src/bin/index.ts +++ b/jsr/src/bin/index.ts @@ -1,17 +1,37 @@ /** - * Module that provides exports for serialized binary files. - * + * Module that provides serialized binaries and FFI metadata. + * * @example * Import the corresponding binary and write the file to disk. - * + * * ```ts - * import {x86_64} from '@serial/cpp-bindings-linux/bin' - * - * Deno.writeFileSync(`./${x86_64.filename}`, Uint8Array.fromBase64(x86_64.data)) + * import { aarch64, x86_64 } from "@serial/cpp-bindings-linux/bin"; + * + * const binary = Deno.build.arch === "aarch64" ? aarch64 : x86_64; + * + * Deno.writeFileSync( + * `./${binary.filename}`, + * Uint8Array.fromBase64(binary.data), + * ); + * + * // The matching FFI metadata is available as `binary.ffi`. * ``` * @module */ -import x86_64 from '../../bin/x86_64.json' with { type: "json" } +import aarch64Library from "../../bin/aarch64/library.json" with { type: "json" }; +import aarch64ffi from "../../bin/aarch64/ffi.json" with { type: "json" }; +import x86_64Library from "../../bin/x86_64/library.json" with { type: "json" }; +import x86_64ffi from "../../bin/x86_64/ffi.json" with { type: "json" }; -export {x86_64} +const aarch64 = { + ...aarch64Library, + ffi: aarch64ffi, +}; + +const x86_64 = { + ...x86_64Library, + ffi: x86_64ffi, +}; + +export { aarch64, x86_64 }; diff --git a/scripts/verify_release_binary.sh b/scripts/verify_release_binary.sh new file mode 100755 index 0000000..09b553d --- /dev/null +++ b/scripts/verify_release_binary.sh @@ -0,0 +1,63 @@ +#!/bin/sh +set -eu + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +BINARY_PATH=$1 +TARGET=$2 +MAX_GLIBC_VERSION=2.28 + +if [ ! -f "$BINARY_PATH" ]; then + echo "Binary does not exist: $BINARY_PATH" >&2 + exit 1 +fi + +case "$TARGET" in + x86_64-linux-gnu) + EXPECTED_MACHINE="Advanced Micro Devices X86-64" + ;; + aarch64-linux-gnu) + EXPECTED_MACHINE="AArch64" + ;; + *) + echo "Unsupported target: $TARGET" >&2 + exit 1 + ;; +esac + +ACTUAL_MACHINE=$(readelf --file-header "$BINARY_PATH" | sed -n 's/^ *Machine: *//p') +if [ "$ACTUAL_MACHINE" != "$EXPECTED_MACHINE" ]; then + echo "Expected ELF machine '$EXPECTED_MACHINE', got '$ACTUAL_MACHINE'" >&2 + exit 1 +fi + +if readelf --dynamic "$BINARY_PATH" | grep -Eq 'Shared library: \[(libstdc\+\+|libgcc_s)'; then + echo "Release binary must not depend on the target system's C++ or GCC runtime" >&2 + exit 1 +fi + +GLIBC_VERSIONS=$(readelf --version-info "$BINARY_PATH" | grep -o 'GLIBC_[0-9][0-9.]*' || true) +if [ -n "$GLIBC_VERSIONS" ]; then + HIGHEST_GLIBC_VERSION=$(printf '%s\n' "$GLIBC_VERSIONS" | sed 's/^GLIBC_//' | sort -Vu | tail -n 1) + HIGHEST_ALLOWED_VERSION=$(printf '%s\n%s\n' "$MAX_GLIBC_VERSION" "$HIGHEST_GLIBC_VERSION" | sort -Vu | tail -n 1) + + if [ "$HIGHEST_ALLOWED_VERSION" != "$MAX_GLIBC_VERSION" ]; then + echo "Release binary requires glibc $HIGHEST_GLIBC_VERSION; maximum is $MAX_GLIBC_VERSION" >&2 + exit 1 + fi +else + HIGHEST_GLIBC_VERSION=none +fi + +if [ "$TARGET" = "x86_64-linux-gnu" ]; then + X86_PROPERTIES=$(readelf --notes "$BINARY_PATH" 2>/dev/null) + if printf '%s\n' "$X86_PROPERTIES" | grep -Eq 'x86-64-v[234]'; then + echo "x86-64 release binary requires a higher-than-baseline ISA" >&2 + exit 1 + fi +fi + +echo "Verified $TARGET: machine=$ACTUAL_MACHINE, max-glibc=$HIGHEST_GLIBC_VERSION"