diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49becc70..55da228e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,26 +28,32 @@ jobs: - x86-mingw32 - x64-mingw-ucrt - x64-mingw32 - - x86-linux + # - x86-linux - x86_64-linux - x86_64-darwin - arm64-darwin - arm-linux - aarch64-linux - jruby + docker-platform-cpu: + - amd64 + - arm64 runs-on: ubuntu-latest env: PLATFORM: ${{ matrix.platform }} + DOCKER_BUILD_PLATFORM: linux/${{ matrix.docker-platform-cpu }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Cache Docker layers - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: tmp/build-cache - key: ${{ runner.os }}-${{ matrix.platform }}-buildx-${{ github.sha }} + key: ${{ runner.os }}-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }}-buildx-${{ github.sha }} + # TODO: remove last key restore-keys: | + ${{ runner.os }}-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }}-buildx ${{ runner.os }}-${{ matrix.platform }}-buildx - uses: ruby/setup-ruby@v1 @@ -55,10 +61,37 @@ jobs: ruby-version: "3.0" bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Build docker image + id: buildx run: | + echo "::group::Preparing docker build" docker buildx create --driver docker-container --use - bundle exec rake build:${PLATFORM} RCD_DOCKER_BUILD="docker buildx build --cache-from=type=local,src=tmp/build-cache --cache-to=type=local,dest=tmp/build-cache-new --load" + extra_tag="rcd-${PLATFORM}-${{ matrix.docker-platform-cpu }}-${{ github.sha }}" + docker_build="docker buildx build --platform=$DOCKER_BUILD_PLATFORM --cache-from=type=local,src=tmp/build-cache --cache-to=type=local,dest=tmp/build-cache-new --load -t $extra_tag" + if bundle exec rake -T | grep -q "prepare:${PLATFORM}"; then + echo "::info::Preparing docker image for ${PLATFORM}" + bundle exec rake prepare:${PLATFORM} RCD_DOCKER_BUILD="$docker_build" + fi + echo "::endgroup::" + + bundle exec rake build:${PLATFORM} RCD_DOCKER_BUILD="$docker_build" + + if [[ "${{ contains(github.event.head_commit.message, matrix.docker-platform-cpu) }}" == "true" ]]; then + echo "::info::Saving docker image $extra_tag" + docker save -o "tmp/${extra_tag}.tar" $extra_tag + echo "image-tarball=tmp/${extra_tag}.tar" >> $GITHUB_OUTPUT + fi + + - name: Upload Docker image tarball + if: ${{ steps.buildx.outputs.image-tarball != '' }} + uses: actions/upload-artifact@v3 + with: + name: docker-save-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }}-${{ github.sha }} + path: ${{ steps.buildx.outputs.image-tarball }} + retention-days: 1 - name: Move build cache and remove outdated layers run: | @@ -77,7 +110,7 @@ jobs: - name: Upload binary gem uses: actions/upload-artifact@v2 with: - name: gem-${{ matrix.platform }} + name: gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }} path: test/rcd_test/pkg/*-*-*.gem - if: matrix.platform == 'jruby' @@ -103,11 +136,11 @@ jobs: name: Upload static binary gem uses: actions/upload-artifact@v2 with: - name: gem-${{ matrix.platform }}-static + name: gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }}-static path: test/rcd_test/pkg/*-*-*.gem job_test_native: - name: Test (${{matrix.ruby}}, ${{matrix.platform}}) + name: Test (${{matrix.ruby}}, ${{matrix.platform}}, ${{ matrix.docker-platform-cpu }}) needs: docker_build strategy: fail-fast: false @@ -144,16 +177,16 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - run: ruby --version - - name: Download gem-${{matrix.platform}} + - name: Download gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }} uses: actions/download-artifact@v2 with: - name: gem-${{ matrix.platform }} - - name: Install gem-${{matrix.platform}} + name: gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }} + - name: Install gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }} run: gem install --local *.gem --verbose - name: Run tests run: | @@ -188,16 +221,16 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - run: ruby --version - - name: Download gem-${{matrix.platform}}-static + - name: Download gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }}-static uses: actions/download-artifact@v2 with: - name: gem-${{ matrix.platform }}-static - - name: Install gem-${{matrix.platform}}-static + name: gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }}-static + - name: Install gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }}-static run: gem install --local *.gem --verbose - name: Run tests run: | @@ -230,13 +263,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Download gem-${{matrix.platform}} + - uses: actions/checkout@v3 + - name: Download gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }} uses: actions/download-artifact@v2 with: - name: gem-${{ matrix.platform }} + name: gem-${{ matrix.platform }}-${{ matrix.docker-platform-cpu }} - name: Build image and Run tests run: | + docker buildx create --driver docker-container --use docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - docker build --rm --build-arg from_image=${{matrix.from_image}} -t ruby-test -f test/env/Dockerfile.${{matrix.dockerfile}} . - docker run --rm -t --network=host -v `pwd`:/build ruby-test + docker buildx build --platform=$DOCKER_BUILD_PLATFORM --rm --build-arg from_image=${{matrix.from_image}} -t ruby-test -f test/env/Dockerfile.${{matrix.dockerfile}} . + docker run --platform=$DOCKER_BUILD_PLATFORM --rm -t --network=host -v `pwd`:/build ruby-test diff --git a/Dockerfile.mri.erb b/Dockerfile.mri.erb index 46361869..7df1d281 100644 --- a/Dockerfile.mri.erb +++ b/Dockerfile.mri.erb @@ -1,9 +1,11 @@ <% image = case platform - when /x86_64-linux/ then "quay.io/pypa/manylinux2014_x86_64" - when /x86-linux/ then "quay.io/pypa/manylinux2014_i686" - else "ubuntu:20.04" + when /x86_64-linux/, /x86-linux/ + manylinux_image + else + "ubuntu:20.04" end + manylinux = !!(image =~ /manylinux/) %> FROM <%= image %> @@ -21,9 +23,13 @@ RUN rm -f /usr/local/bin/sudo && \ echo "%sudo ALL=(ALL) ALL" >> /etc/sudoers <% else %> ENV DEBIAN_FRONTEND noninteractive -RUN apt-get -y update && \ - apt-get install -y sudo wget autoconf cmake curl git-core pkg-config build-essential xz-utils unzip gnupg2 dirmngr zlib1g-dev libreadline-dev libsqlite0-dev libssl-dev libyaml-dev libffi-dev && \ - rm -rf /var/lib/apt/lists/* + +COPY ./build/apt_install_multiarch.sh / +RUN /apt_install_multiarch.sh <%= foreign_dpkg_arch %> zlib1g-dev libreadline-dev libsqlite0-dev libssl-dev libyaml-dev libffi-dev + +RUN apt-get -y -qq update && \ + apt-get -y -qq install sudo wget autoconf cmake curl git-core pkg-config build-essential xz-utils unzip gnupg2 dirmngr && \ + rm -rf /var/lib/apt/lists/*; <% end %> # Add "rvm" as system group, to avoid conflicts with host GIDs typically starting with 1000 @@ -74,10 +80,10 @@ USER root <% if platform=~/x64-mingw-ucrt/ %> COPY --from=larskanis/mingw64-ucrt:20.04 \ - /build/binutils-mingw-w64-x86-64_2.34-6ubuntu1.3+8.8_amd64.deb \ - /build/g++-mingw-w64-x86-64_9.3.0-17ubuntu1~20.04+22~exp1ubuntu4_amd64.deb \ - /build/gcc-mingw-w64-base_9.3.0-17ubuntu1~20.04+22~exp1ubuntu4_amd64.deb \ - /build/gcc-mingw-w64-x86-64_9.3.0-17ubuntu1~20.04+22~exp1ubuntu4_amd64.deb \ + /build/binutils-mingw-w64-x86-64_2.34-6ubuntu1.3+8.8_<%= host_dpkg_arch %>.deb \ + /build/g++-mingw-w64-x86-64_9.3.0-17ubuntu1~20.04+22~exp1ubuntu4_<%= host_dpkg_arch %>.deb \ + /build/gcc-mingw-w64-base_9.3.0-17ubuntu1~20.04+22~exp1ubuntu4_<%= host_dpkg_arch %>.deb \ + /build/gcc-mingw-w64-x86-64_9.3.0-17ubuntu1~20.04+22~exp1ubuntu4_<%= host_dpkg_arch %>.deb \ /build/mingw-w64-common_7.0.0-2_all.deb \ /build/mingw-w64-x86-64-dev_7.0.0-2_all.deb \ /debs/ diff --git a/Rakefile b/Rakefile index 6c46f47f..e5b15172 100644 --- a/Rakefile +++ b/Rakefile @@ -10,22 +10,45 @@ DOCKERHUB_USER = ENV['DOCKERHUB_USER'] || "larskanis" docker_build_cmd = Shellwords.split(ENV['RCD_DOCKER_BUILD'] || "docker build") platforms = [ - ["x86-mingw32", "i686-w64-mingw32"], - ["x64-mingw32", "x86_64-w64-mingw32"], - ["x64-mingw-ucrt", "x86_64-w64-mingw32"], - ["x86-linux", "i686-redhat-linux"], - ["x86_64-linux", "x86_64-redhat-linux"], - ["x86_64-darwin", "x86_64-apple-darwin"], - ["arm64-darwin", "aarch64-apple-darwin"], - ["arm-linux", "arm-linux-gnueabihf"], - ["aarch64-linux", "aarch64-linux-gnu"], + ["x86-mingw32", "i686-w64-mingw32", "i386"], + ["x64-mingw32", "x86_64-w64-mingw32", "amd64"], + ["x64-mingw-ucrt", "x86_64-w64-mingw32", "amd64"], + ["x86-linux", "i686-redhat-linux", "i386"], + ["x86_64-linux", "x86_64-redhat-linux", "amd64"], + ["x86_64-darwin", "x86_64-apple-darwin", "amd64"], + ["arm64-darwin", "aarch64-apple-darwin", "arm64"], + ["arm-linux", "arm-linux-gnueabihf", "armhf"], + ["aarch64-linux", "aarch64-linux-gnu", "arm64"], ] namespace :build do - platforms.each do |platform, target| + platforms.each do |platform, target, foreign_dpkg_arch| sdf = "Dockerfile.mri.#{platform}" + host_dpkg_arch = case ENV["DOCKER_BUILD_PLATFORM"] + when /arm64/ + "arm64" + when /amd64/ + "amd64" + else + if ENV["CI"] + raise "Couldnt infer dpkg arch for #{ENV["DOCKER_BUILD_PLATFORM"].inspect}" + else + "amd64" + end + end + + # Native images to alleviate qemu slowness, and manylinux2014 provides per-arch + # images. But they are not yet conformant to the Docker platform spec (i.e. + # amd64/linux). We generate our own platformed manifests now (using + # scrip/remanifest-manylinux-multiplatform.sh), but you should be able to + # nuke that code soon, and rely on only the buildx `--platform` feature once + # manylinux finishes the feature. + # + # See: https://github.com/pypa/manylinux/issues/1306 + manylinux_image = "rbsys/manylinux2014:2022-12-11-145d107" + desc "Build image for platform #{platform}" task platform => sdf task sdf do diff --git a/build/apt_install_multiarch.sh b/build/apt_install_multiarch.sh new file mode 100755 index 00000000..561f816d --- /dev/null +++ b/build/apt_install_multiarch.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -exuo pipefail +IFS=$'\n\t' + +main() { + deb_host_arch="$(dpkg --print-architecture)" + lsb_release="$(grep VERSION_CODENAME < /etc/os-release | cut -d= -f2 | tr -d '"')" + deb_target_arch="$1" + + shift + + cross_packages=() + packages=("$@") + + if [ "$deb_target_arch" != "$deb_host_arch" ] && [ "$deb_host_arch" = "arm64" ]; then + echo "Setting up multiarch support for $deb_target_arch" >&2 + dpkg --add-architecture "$deb_target_arch" + cross_packages=("${packages[@]/%/:$deb_target_arch}") + + # Qualify our current source lists to make sure debian doesn't infer stuff + sed -i "s/^deb http/deb [arch=$deb_host_arch] http/" /etc/apt/sources.list + + # Add sources for ported target libs + echo "deb [arch=$deb_target_arch] http://ports.ubuntu.com/ubuntu-ports $lsb_release main universe restricted multiverse" + echo "deb [arch=$deb_target_arch] http://ports.ubuntu.com/ubuntu-ports $lsb_release-updates main universe restricted multiverse" + echo "deb [arch=$deb_target_arch] http://ports.ubuntu.com/ubuntu-ports $lsb_release-security main universe restricted multiverse" + else + echo "No need to set up multiarch support for $deb_target_arch" + fi + + apt-get -qq -y update + apt-get -qq -y install "${packages[@]}" "${cross_packages[@]}" + rm -rf /var/lib/apt/lists/* + + rm "$0" +} + +main "$@" diff --git a/build/mk_osxcross.sh b/build/mk_osxcross.sh index 410b84eb..af7ec630 100755 --- a/build/mk_osxcross.sh +++ b/build/mk_osxcross.sh @@ -13,7 +13,7 @@ set -x curl -L -o MacOSX11.1.sdk.tar.xz https://github.com/larskanis/MacOSX-SDKs/releases/download/11.1/MacOSX11.1.sdk.tar.xz tar -xf MacOSX11.1.sdk.tar.xz -C . cp -rf /usr/lib/llvm-10/include/c++ MacOSX11.1.sdk/usr/include/c++ -cp -rf /usr/include/x86_64-linux-gnu/c++/9/bits/ MacOSX11.1.sdk/usr/include/c++/v1/bits +cp -rf /usr/include/"$(uname -m)"-linux-gnu/c++/9/bits/ MacOSX11.1.sdk/usr/include/c++/v1/bits tar -cJf MacOSX11.1.sdk.tar.xz MacOSX11.1.sdk set +x diff --git a/mingw64-ucrt/Dockerfile b/mingw64-ucrt/Dockerfile index eb07f5ab..60333c42 100644 --- a/mingw64-ucrt/Dockerfile +++ b/mingw64-ucrt/Dockerfile @@ -1,3 +1,4 @@ + FROM ubuntu:20.04 ARG DEBIAN_FRONTEND=noninteractive @@ -32,7 +33,7 @@ RUN cd mingw-w64-* && \ # Install UCRT enabled deb-packages RUN dpkg -i mingw-w64-common_7.0.0-2_all.deb \ - mingw-w64-tools_7.0.0-2_amd64.deb \ + mingw-w64-tools_7.0.0-2_$(dpkg --print-architecture).deb \ mingw-w64-x86-64-dev_7.0.0-2_all.deb # Download gcc-binutils sources for mingw diff --git a/script/remanifest-manylinux-multiplatform.sh b/script/remanifest-manylinux-multiplatform.sh new file mode 100755 index 00000000..0e228679 --- /dev/null +++ b/script/remanifest-manylinux-multiplatform.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -euo pipefail +IFS=$'\n\t' + +# A script to build and push a multi-arch image to Docker Hub, based on +# https://github.com/pypa/manylinux/issues/1306 + +repo="${DOCKERHUB_USER:-larskanis}" +tags=("latest" "2022-12-11-145d107") +base_images=("manylinux2014" "manylinux_2_24") + +for base_image in "${base_images[@]}"; do + for tag in "${tags[@]}"; do + echo Re-manifesting "$base_image":"$tag" >&2 + + docker pull --quiet quay.io/pypa/"$base_image"_aarch64:"$tag" + docker pull --quiet quay.io/pypa/"$base_image"_x86_64:"$tag" + + docker tag quay.io/pypa/"$base_image"_x86_64:"$tag" "$repo"/"$base_image"_x86_64:"$tag" + docker tag quay.io/pypa/"$base_image"_aarch64:"$tag" "$repo"/"$base_image"_aarch64:"$tag" + + docker push --quiet "$repo"/"$base_image"_x86_64:"$tag" + docker push --quiet "$repo"/"$base_image"_aarch64:"$tag" + + docker manifest create "$repo"/"$base_image":"$tag" \ + --amend "$repo"/"$base_image"_x86_64:"$tag" \ + --amend "$repo"/"$base_image"_aarch64:"$tag" + + docker manifest push "$repo"/"$base_image":"$tag" + done +done