diff --git a/.github/docker/Dockerfile.musl b/.github/docker/Dockerfile.musl new file mode 100644 index 0000000..6bee364 --- /dev/null +++ b/.github/docker/Dockerfile.musl @@ -0,0 +1,18 @@ +ARG PLATFORM=arm64 +ARG NODE_VERSION=16.20.1 + +FROM ${PLATFORM}/node:${NODE_VERSION}-alpine AS build + +WORKDIR /mongodb-client-encryption +COPY . . + +RUN apk --no-cache add make g++ libc-dev curl bash python3 py3-pip cmake git +RUN npm run install:libmongocrypt +RUN npm run prebuild + +ARG RUN_TEST +RUN if [ -n "$RUN_TEST" ]; then npm test ; else echo "skipping tests" ; fi + +FROM scratch + +COPY --from=build /mongodb-client-encryption/prebuilds/ / diff --git a/.github/scripts/utils.mjs b/.github/scripts/utils.mjs index 1a54385..45d408d 100644 --- a/.github/scripts/utils.mjs +++ b/.github/scripts/utils.mjs @@ -1,10 +1,10 @@ // @ts-check -import { execSync } from "child_process"; import path from "path"; import url from 'node:url'; import { spawn } from "node:child_process"; import { once } from "node:events"; +import { execSync } from "child_process"; const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); @@ -55,22 +55,25 @@ export function buildLibmongocryptDownloadUrl(ref, platform) { } export function getLibmongocryptPrebuildName() { - const platformMatrix = { - ['darwin-arm64']: 'macos', - ['darwin-x64']: 'macos', - ['linux-ppc64']: 'rhel-71-ppc64el', - ['linux-s390x']: 'rhel72-zseries-test', - ['linux-arm64']: 'ubuntu1804-arm64', - ['linux-x64']: 'rhel-70-64-bit', - ['win32-x64']: 'windows-test' - }; - - const detectedPlatform = `${process.platform}-${process.arch}`; - const prebuild = platformMatrix[detectedPlatform]; - - if (prebuild == null) throw new Error(`Unsupported: ${detectedPlatform}`); - - return prebuild; + const prebuildIdentifierFactory = { + 'darwin': () => 'macos', + 'win32': () => 'windows-test', + 'linux': () => { + const key = `${getLibc()}-${process.arch}`; + return { + ['musl-x64']: 'alpine-amd64-earthly', + ['musl-arm64']: 'alpine-arm64-earthly', + ['glibc-ppc64']: 'rhel-71-ppc64el', + ['glibc-s390x']: 'rhel72-zseries-test', + ['glibc-arm64']: 'ubuntu1804-arm64', + ['glibc-x64']: 'rhel-70-64-bit', + }[key] + } + }[process.platform] ?? (() => { + throw new Error(`Unsupported platform`); + }); + + return prebuildIdentifierFactory(); } /** `xtrace` style command runner, uses spawn so that stdio is inherited */ @@ -86,4 +89,28 @@ export async function run(command, args = [], options = {}) { await once(proc, 'exit'); if (proc.exitCode != 0) throw new Error(`CRASH(${proc.exitCode}): ${commandDetails}`); -} \ No newline at end of file +} + +/** + * @returns the libc (`musl` or `glibc`), if the platform is linux, otherwise null. + */ +function getLibc() { + if (process.platform !== 'linux') return null; + + /** + * executes `ldd --version`. on Alpine linux, `ldd` and `ldd --version` return exit code 1 and print the version + * info to stderr, but on other platforms, `ldd --version` prints to stdout and returns exit code 0. + * + * So, this script works on both by return stderr if the command returns a non-zero exit code, otherwise stdout. + */ + function lddVersion() { + try { + return execSync('ldd --version', { encoding: 'utf-8' }); + } catch (error) { + return error.stderr; + } + } + + console.error({ ldd: lddVersion() }); + return lddVersion().includes('musl') ? 'musl' : 'glibc'; +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2e37bd..07c2461 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: retention-days: 1 compression-level: 0 - container_builds: + container_builds_glibc: outputs: artifact_id: ${{ steps.upload.outputs.artifact-id }} runs-on: ubuntu-latest @@ -64,8 +64,48 @@ jobs: name: Upload prebuild uses: actions/upload-artifact@v4 with: - name: build-linux-${{ matrix.linux_arch }} + name: build-linux-glibc-${{ matrix.linux_arch }} path: prebuilds/ if-no-files-found: 'error' retention-days: 1 compression-level: 0 + + container_tests_musl: + runs-on: ubuntu-latest + strategy: + matrix: + linux_arch: [amd64, arm64] + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - name: Get Full Node.js Version + id: get_nodejs_version + shell: bash + run: | + echo "version=$(node --print 'process.version.slice(1)')" >> "$GITHUB_OUTPUT" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Run Buildx + run: | + docker buildx create --name builder --bootstrap --use + docker --debug buildx build --progress=plain --no-cache \ + --platform linux/${{ matrix.linux_arch }} \ + --build-arg="PLATFORM=${{ matrix.linux_arch == 'arm64' && 'arm64v8' || matrix.linux_arch }}" \ + --output type=local,dest=./prebuilds,platform-split=false \ + -f ./.github/docker/Dockerfile.musl \ + . + - id: upload + name: Upload prebuild + uses: actions/upload-artifact@v4 + with: + name: build-linux-musl-${{ matrix.linux_arch }} + path: prebuilds/ + if-no-files-found: "error" + retention-days: 1 + compression-level: 0 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c74b60..7b73c26 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: shell: bash run: npm run test - container_tests: + container_tests_glibc: runs-on: ubuntu-latest strategy: matrix: @@ -71,3 +71,42 @@ jobs: --output type=local,dest=./prebuilds,platform-split=false \ -f ./.github/docker/Dockerfile.glibc \ . + + + container_tests_musl: + runs-on: ubuntu-latest + strategy: + matrix: + linux_arch: [amd64, arm64] + node: [16.20.1, 18.x, 20.x, 22.x] + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + + - name: Get Full Node.js Version + id: get_nodejs_version + shell: bash + run: | + echo "version=$(node --print 'process.version.slice(1)')" >> "$GITHUB_OUTPUT" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Run Buildx + run: | + docker buildx create --name builder --bootstrap --use + docker --debug buildx build --progress=plain --no-cache \ + --platform linux/${{ matrix.linux_arch }} \ + --build-arg="PLATFORM=${{ matrix.linux_arch == 'arm64' && 'arm64v8' || matrix.linux_arch }}" \ + --build-arg="NODE_VERSION=${{ steps.get_nodejs_version.outputs.version }}" \ + --build-arg="RUN_TEST=true" \ + --output type=local,dest=./prebuilds,platform-split=false \ + -f ./.github/docker/Dockerfile.musl \ + . \ No newline at end of file diff --git a/README.md b/README.md index 66c7521..1b04ee8 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,9 @@ Below are the platforms that are available as prebuilds on each github release. - s390x - arm64 - x64 +- Linux MUSL 1.1.20 + - arm64 + - x64 - MacOS universal binary - x64 - arm64 diff --git a/etc/docker.sh b/etc/docker.sh new file mode 100644 index 0000000..880ee72 --- /dev/null +++ b/etc/docker.sh @@ -0,0 +1,46 @@ +#! /bin/bash + +# script to aid in local testing of linux platforms +# requires a running docker instance + +# s390x, arm64, amd64 for ubuntu +# amd64 or arm64v8 for alpine +LINUX_ARCH=amd64 + +# 16.20.1+, default 16.20.1 +NODE_VERSION=20.0.0 + +SCRIPT_DIR=$(dirname ${BASH_SOURCE:-$0}) +PROJECT_DIR=$SCRIPT_DIR/.. + +build_and_test_musl() { + docker buildx create --name builder --bootstrap --use + + docker --debug buildx build --load --progress=plain --no-cache \ + --platform linux/$LINUX_ARCH --output=type=docker \ + --build-arg="PLATFORM=$LINUX_ARCH" \ + --build-arg="NODE_VERSION=$NODE_VERSION" \ + --build-arg="RUN_TEST=true" \ + -f ./.github/docker/Dockerfile.musl -t musl-zstd-base \ + . +} + +build_and_test_glibc() { + docker buildx create --name builder --bootstrap --use + + UBUNTU_VERSION=$(node --print 'Number(process.argv[1].split(`.`).at(0)) > 16 ? `noble` : `bionic`' $NODE_VERSION) + NODE_ARCH=$(node -p 'process.argv[1] === `amd64` && `x64` || process.argv[1]' $LINUX_ARCH) + echo $UBUNTU_VERSION + docker buildx build --progress=plain --no-cache \ + --platform linux/$LINUX_ARCH \ + --build-arg="NODE_ARCH=$NODE_ARCH" \ + --build-arg="NODE_VERSION=$NODE_VERSION" \ + --build-arg="UBUNTU_VERSION=$UBUNTU_VERSION" \ + --build-arg="RUN_TEST=true" \ + --output type=local,dest=./prebuilds,platform-split=false \ + -f ./.github/docker/Dockerfile.glibc \ + $PROJECT_DIR +} + +build_and_test_musl +# build_and_test_glibc