Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(NODE-6727): Add builds on Alpine linux #65

Merged
merged 5 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/docker/Dockerfile.musl
Original file line number Diff line number Diff line change
@@ -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/ /
63 changes: 45 additions & 18 deletions .github/scripts/utils.mjs
Original file line number Diff line number Diff line change
@@ -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));

Expand Down Expand Up @@ -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 */
Expand All @@ -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}`);
}
}

/**
* @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';
}
44 changes: 42 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
41 changes: 40 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
shell: bash
run: npm run test

container_tests:
container_tests_glibc:
runs-on: ubuntu-latest
strategy:
matrix:
Expand Down Expand Up @@ -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 \
.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions etc/docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#! /bin/bash

# script to aid in local testing of linux platforms
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to remove this, but I find this immensely useful when working on ubuntu or alpine builds locally. We have this script in our zstd repo.

# 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
Loading