|
| 1 | +# ────────────────────────────────────────────────────────────────────────────── |
| 2 | +# Build mpt-crypto Native Libraries |
| 3 | +# |
| 4 | +# Builds the mpt-crypto C library as a self-contained shared library for each |
| 5 | +# supported platform. Dependencies (secp256k1, OpenSSL) are statically linked |
| 6 | +# so the resulting binaries can be loaded directly by JNA at runtime without |
| 7 | +# requiring any system-level installs. |
| 8 | +# |
| 9 | +# The output artifacts follow JNA's platform naming convention: |
| 10 | +# darwin-aarch64/libmptcrypto.dylib (macOS ARM64) |
| 11 | +# darwin-x86-64/libmptcrypto.dylib (macOS x86_64) |
| 12 | +# linux-aarch64/libmptcrypto.so (Linux ARM64) |
| 13 | +# linux-x86-64/libmptcrypto.so (Linux x86_64) |
| 14 | +# win32-x86-64/mptcrypto.dll (Windows x86_64) |
| 15 | +# |
| 16 | +# These artifacts are intended to be downloaded and placed into |
| 17 | +# xrpl4j-mpt-crypto/src/main/resources/ for JNA to discover at runtime. |
| 18 | +# ────────────────────────────────────────────────────────────────────────────── |
| 19 | + |
| 20 | +name: Build mpt-crypto Native Libraries |
| 21 | + |
| 22 | +on: |
| 23 | + workflow_dispatch: |
| 24 | + inputs: |
| 25 | + mpt_crypto_ref: |
| 26 | + # Prefer an immutable release tag (e.g. 1.0.4) so published native |
| 27 | + # binaries are built from a known, auditable source rather than a moving |
| 28 | + # branch like main. Branch/commit refs are still accepted for testing. |
| 29 | + description: 'Release tag (preferred), branch, or commit SHA of XRPLF/mpt-crypto to build' |
| 30 | + required: true |
| 31 | + default: '1.0.4' |
| 32 | + |
| 33 | +jobs: |
| 34 | + build: |
| 35 | + name: Build ${{ matrix.platform }} |
| 36 | + strategy: |
| 37 | + fail-fast: false |
| 38 | + matrix: |
| 39 | + include: |
| 40 | + # ── macOS ARM64 (Apple Silicon) ── |
| 41 | + - os: macos-14 |
| 42 | + platform: darwin-aarch64 |
| 43 | + lib_filename: libmptcrypto.dylib |
| 44 | + cmake_lib_filename: libmpt-crypto.dylib |
| 45 | + |
| 46 | + # ── macOS x86_64 (Intel) ── |
| 47 | + - os: macos-15-intel |
| 48 | + platform: darwin-x86-64 |
| 49 | + lib_filename: libmptcrypto.dylib |
| 50 | + cmake_lib_filename: libmpt-crypto.dylib |
| 51 | + |
| 52 | + # ── Linux ARM64 ── |
| 53 | + - os: ubuntu-24.04-arm |
| 54 | + platform: linux-aarch64 |
| 55 | + lib_filename: libmptcrypto.so |
| 56 | + cmake_lib_filename: libmpt-crypto.so |
| 57 | + |
| 58 | + # ── Linux x86_64 ── |
| 59 | + - os: ubuntu-latest |
| 60 | + platform: linux-x86-64 |
| 61 | + lib_filename: libmptcrypto.so |
| 62 | + cmake_lib_filename: libmpt-crypto.so |
| 63 | + |
| 64 | + # ── Windows x86_64 ── |
| 65 | + - os: windows-latest |
| 66 | + platform: win32-x86-64 |
| 67 | + lib_filename: mptcrypto.dll |
| 68 | + cmake_lib_filename: mpt-crypto.dll |
| 69 | + |
| 70 | + runs-on: ${{ matrix.os }} |
| 71 | + |
| 72 | + steps: |
| 73 | + # ── Checkout mpt-crypto source at the requested ref ── |
| 74 | + # actions/checkout doesn't support arbitrary commit SHAs for external repos |
| 75 | + # (it only matches branches/tags). So we do a full clone + checkout instead. |
| 76 | + - name: Clone mpt-crypto |
| 77 | + shell: bash |
| 78 | + env: |
| 79 | + MPT_REF: ${{ inputs.mpt_crypto_ref }} |
| 80 | + # Immutable commit the pinned release tag must resolve to. Keep in sync |
| 81 | + # with MPT_CRYPTO_VERSION / MPT_CRYPTO_COMMIT in |
| 82 | + # packaging/confidential/version.env (the single source of truth for |
| 83 | + # the xrpl-py packaging path; this JNA-natives workflow does not check |
| 84 | + # out xrpl-py, so the value is mirrored here). |
| 85 | + PINNED_VERSION: "1.0.4" |
| 86 | + PINNED_COMMIT: "85dbac73d033241b73f0565ae7cce86d07005c70" |
| 87 | + run: | |
| 88 | + git clone https://github.com/XRPLF/mpt-crypto.git . |
| 89 | + git checkout "${MPT_REF}" |
| 90 | + echo "==> Checked out mpt-crypto at:" |
| 91 | + git log --oneline -1 |
| 92 | +
|
| 93 | + # Supply-chain: git tags are mutable (force-pushable), so pinning a tag |
| 94 | + # is NOT the same as pinning a commit. When building the pinned release |
| 95 | + # (the default), verify the resolved commit matches the immutable SHA so |
| 96 | + # a moved/tampered tag can't inject code into the built native |
| 97 | + # artifacts. Mirrors the check in |
| 98 | + # packaging/confidential/scripts/build-mpt-crypto-lib.sh. A custom ref |
| 99 | + # is an explicit testing override and is allowed with a warning (a |
| 100 | + # 40-char commit SHA is already immutable). |
| 101 | + if [ "${MPT_REF}" = "${PINNED_VERSION}" ]; then |
| 102 | + ACTUAL_SHA="$(git rev-parse HEAD)" |
| 103 | + if [ "${ACTUAL_SHA}" != "${PINNED_COMMIT}" ]; then |
| 104 | + echo "ERROR: tag ${MPT_REF} resolved to ${ACTUAL_SHA}," >&2 |
| 105 | + echo " expected pinned commit ${PINNED_COMMIT}." >&2 |
| 106 | + echo " The tag may have been moved or tampered with; refusing to build." >&2 |
| 107 | + exit 1 |
| 108 | + fi |
| 109 | + echo "==> Verified ${MPT_REF} resolves to pinned commit ${PINNED_COMMIT}." |
| 110 | + else |
| 111 | + echo "==> WARNING: building custom ref '${MPT_REF}' (not the pinned" \ |
| 112 | + "release ${PINNED_VERSION}); skipping commit-SHA verification." |
| 113 | + fi |
| 114 | +
|
| 115 | + # ── Set up build tools ── |
| 116 | + - name: Set up Python |
| 117 | + uses: actions/setup-python@v5 |
| 118 | + with: |
| 119 | + python-version: '3.12' |
| 120 | + |
| 121 | + - name: Install Conan and Ninja |
| 122 | + run: pip install "conan>=2.0.0" ninja |
| 123 | + |
| 124 | + # Windows needs the MSVC developer environment for cl.exe, link.exe, etc. |
| 125 | + - name: Set up MSVC environment |
| 126 | + if: runner.os == 'Windows' |
| 127 | + uses: ilammy/msvc-dev-cmd@v1 |
| 128 | + |
| 129 | + # ── Configure Conan ── |
| 130 | + # Use auto-detected profile so we don't need to hand-craft compiler |
| 131 | + # settings for each platform. Then add the XRPLF remote where secp256k1 |
| 132 | + # packages are hosted. |
| 133 | + - name: Configure Conan |
| 134 | + shell: bash |
| 135 | + run: | |
| 136 | + conan profile detect --force |
| 137 | + echo "==> Detected Conan profile:" |
| 138 | + conan profile show |
| 139 | + conan remote add --index 0 xrplf https://conan.ripplex.io || true |
| 140 | +
|
| 141 | + # ── Install dependencies via Conan ── |
| 142 | + # |
| 143 | + # Key flags: |
| 144 | + # -o "&:shared=True" → Build mpt-crypto as a SHARED library |
| 145 | + # -o "&:tests=False" → Skip building test executables |
| 146 | + # -o "secp256k1/*:shared=False" → Static secp256k1, linked INTO the shared lib |
| 147 | + # -o "openssl/*:shared=False" → Static OpenSSL, linked INTO the shared lib |
| 148 | + # -o "*/fPIC=True" → Position Independent Code for static deps |
| 149 | + # (required when linking static libs into a .so/.dylib) |
| 150 | + # -b missing → Build deps from source if no pre-built binary |
| 151 | + # |
| 152 | + # On Windows, fPIC doesn't apply — Conan ignores it for MSVC targets. |
| 153 | + - name: Install dependencies (Unix) |
| 154 | + if: runner.os != 'Windows' |
| 155 | + shell: bash |
| 156 | + run: | |
| 157 | + conan install . \ |
| 158 | + -of build \ |
| 159 | + -b missing \ |
| 160 | + -s build_type=Release \ |
| 161 | + -o "&:shared=True" \ |
| 162 | + -o "&:tests=False" \ |
| 163 | + -o "secp256k1/*:shared=False" \ |
| 164 | + -o "secp256k1/*:fPIC=True" \ |
| 165 | + -o "openssl/*:shared=False" \ |
| 166 | + -o "openssl/*:fPIC=True" |
| 167 | +
|
| 168 | + - name: Install dependencies (Windows) |
| 169 | + if: runner.os == 'Windows' |
| 170 | + shell: bash |
| 171 | + run: | |
| 172 | + conan install . \ |
| 173 | + -of build \ |
| 174 | + -b missing \ |
| 175 | + -s build_type=Release \ |
| 176 | + -o "&:shared=True" \ |
| 177 | + -o "&:tests=False" \ |
| 178 | + -o "secp256k1/*:shared=False" \ |
| 179 | + -o "openssl/*:shared=False" |
| 180 | +
|
| 181 | + # ── Configure CMake ── |
| 182 | + # |
| 183 | + # The Conan toolchain file (conan_toolchain.cmake) configures: |
| 184 | + # - CMAKE_BUILD_TYPE=Release (single-config generators) |
| 185 | + # - BUILD_SHARED_LIBS=ON (from shared=True) |
| 186 | + # - Find-package paths for secp256k1 and OpenSSL |
| 187 | + # |
| 188 | + # Windows uses the VS generator because Ninja doesn't support |
| 189 | + # CMAKE_GENERATOR_PLATFORM which Conan's MSVC toolchain sets. |
| 190 | + - name: Configure CMake (Unix) |
| 191 | + if: runner.os != 'Windows' |
| 192 | + shell: bash |
| 193 | + run: | |
| 194 | + TOOLCHAIN=$(find build -name "conan_toolchain.cmake" -print -quit) |
| 195 | + if [ -z "${TOOLCHAIN}" ]; then |
| 196 | + echo "ERROR: Could not find conan_toolchain.cmake under build/" |
| 197 | + find build -type f -name "*.cmake" | head -20 |
| 198 | + exit 1 |
| 199 | + fi |
| 200 | + echo "==> Using toolchain: ${TOOLCHAIN}" |
| 201 | +
|
| 202 | + cmake -B build -S . \ |
| 203 | + -G Ninja \ |
| 204 | + -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN}" \ |
| 205 | + -DCMAKE_BUILD_TYPE=Release |
| 206 | +
|
| 207 | + - name: Configure CMake (Windows) |
| 208 | + if: runner.os == 'Windows' |
| 209 | + shell: bash |
| 210 | + run: | |
| 211 | + TOOLCHAIN=$(find build -name "conan_toolchain.cmake" -print -quit) |
| 212 | + if [ -z "${TOOLCHAIN}" ]; then |
| 213 | + echo "ERROR: Could not find conan_toolchain.cmake under build/" |
| 214 | + find build -type f -name "*.cmake" | head -20 |
| 215 | + exit 1 |
| 216 | + fi |
| 217 | + echo "==> Using toolchain: ${TOOLCHAIN}" |
| 218 | +
|
| 219 | + cmake -B build -S . \ |
| 220 | + -G "Visual Studio 17 2022" \ |
| 221 | + -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN}" \ |
| 222 | + -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON |
| 223 | +
|
| 224 | + # ── Build ── |
| 225 | + - name: Build shared library |
| 226 | + shell: bash |
| 227 | + run: cmake --build build --config Release |
| 228 | + |
| 229 | + # ── Package ── |
| 230 | + # CMake produces "mpt-crypto" (with hyphen) as the library name. |
| 231 | + # We rename to "mptcrypto" (no hyphen) because JNA's Native.load("mptcrypto") |
| 232 | + # expects this convention. |
| 233 | + - name: Package library |
| 234 | + shell: bash |
| 235 | + run: | |
| 236 | + mkdir -p output/${{ matrix.platform }} |
| 237 | +
|
| 238 | + BUILT_LIB=$(find build -maxdepth 2 \( \ |
| 239 | + -name "${{ matrix.cmake_lib_filename }}" \ |
| 240 | + \) -print -quit) |
| 241 | +
|
| 242 | + if [ -z "${BUILT_LIB}" ]; then |
| 243 | + echo "ERROR: Could not find ${{ matrix.cmake_lib_filename }} in build/" |
| 244 | + echo "Build directory contents:" |
| 245 | + find build -type f \( -name "*.dylib" -o -name "*.so" -o -name "*.dll" \) 2>/dev/null |
| 246 | + exit 1 |
| 247 | + fi |
| 248 | +
|
| 249 | + echo "==> Found: ${BUILT_LIB}" |
| 250 | + cp "${BUILT_LIB}" "output/${{ matrix.platform }}/${{ matrix.lib_filename }}" |
| 251 | + echo "==> Packaged: output/${{ matrix.platform }}/${{ matrix.lib_filename }}" |
| 252 | +
|
| 253 | + # ── Verify ── |
| 254 | + # Confirm the library is self-contained: linked libraries should only be |
| 255 | + # system libs (no secp256k1 or libcrypto as external shared deps). |
| 256 | + - name: Verify library (macOS) |
| 257 | + if: startsWith(matrix.platform, 'darwin') |
| 258 | + run: | |
| 259 | + echo "==> Linked libraries (should only show system libs):" |
| 260 | + otool -L output/${{ matrix.platform }}/${{ matrix.lib_filename }} |
| 261 | + echo "" |
| 262 | + echo "==> Architecture:" |
| 263 | + file output/${{ matrix.platform }}/${{ matrix.lib_filename }} |
| 264 | +
|
| 265 | + - name: Verify library (Linux) |
| 266 | + if: startsWith(matrix.platform, 'linux') |
| 267 | + run: | |
| 268 | + echo "==> Linked libraries (should only show system libs):" |
| 269 | + ldd output/${{ matrix.platform }}/${{ matrix.lib_filename }} || true |
| 270 | + echo "" |
| 271 | + echo "==> Architecture:" |
| 272 | + file output/${{ matrix.platform }}/${{ matrix.lib_filename }} |
| 273 | +
|
| 274 | + - name: Verify library (Windows) |
| 275 | + if: startsWith(matrix.platform, 'win32') |
| 276 | + shell: bash |
| 277 | + run: | |
| 278 | + echo "==> File info:" |
| 279 | + file output/${{ matrix.platform }}/${{ matrix.lib_filename }} |
| 280 | +
|
| 281 | + # ── Upload per-platform artifact ── |
| 282 | + # Upload output/ (not output/<platform>/) so the platform subdirectory |
| 283 | + # is preserved inside the artifact. This ensures merge-multiple in the |
| 284 | + # bundle job doesn't overwrite files with the same name across platforms. |
| 285 | + - name: Upload artifact |
| 286 | + uses: actions/upload-artifact@v4 |
| 287 | + with: |
| 288 | + name: mptcrypto-${{ matrix.platform }} |
| 289 | + path: output/ |
| 290 | + if-no-files-found: error |
| 291 | + |
| 292 | + # ────────────────────────────────────────────────────────────────────────── |
| 293 | + # Bundle all platform artifacts into a single downloadable archive. |
| 294 | + # The structure matches what JNA expects on the classpath: |
| 295 | + # darwin-aarch64/libmptcrypto.dylib |
| 296 | + # darwin-x86-64/libmptcrypto.dylib |
| 297 | + # linux-aarch64/libmptcrypto.so |
| 298 | + # linux-x86-64/libmptcrypto.so |
| 299 | + # win32-x86-64/mptcrypto.dll |
| 300 | + # ────────────────────────────────────────────────────────────────────────── |
| 301 | + bundle: |
| 302 | + name: Bundle all native libraries |
| 303 | + needs: build |
| 304 | + runs-on: ubuntu-latest |
| 305 | + steps: |
| 306 | + - name: Download all platform artifacts |
| 307 | + uses: actions/download-artifact@v4 |
| 308 | + with: |
| 309 | + pattern: mptcrypto-* |
| 310 | + path: natives/ |
| 311 | + merge-multiple: true |
| 312 | + |
| 313 | + - name: Display bundled structure |
| 314 | + run: | |
| 315 | + echo "==> Bundled native libraries:" |
| 316 | + find natives/ -type f | sort |
| 317 | + echo "" |
| 318 | + echo "==> File sizes:" |
| 319 | + find natives/ -type f -exec ls -lh {} \; |
| 320 | +
|
| 321 | + - name: Upload combined artifact |
| 322 | + uses: actions/upload-artifact@v4 |
| 323 | + with: |
| 324 | + name: mptcrypto-all-platforms |
| 325 | + path: natives/ |
| 326 | + if-no-files-found: error |
0 commit comments