diff --git a/.changes/1636.json b/.changes/1636.json new file mode 100644 index 000000000..4e0366c4f --- /dev/null +++ b/.changes/1636.json @@ -0,0 +1,4 @@ +{ + "type": "added", + "description": "Add provided docker images for `linux/arm64/v8` target for many Ubuntu-based targets" +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d3f498f8..bcc1f3a46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,10 +123,27 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + generate-matrix-for-build: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.generate-matrix.outputs.matrix }} + tests: ${{ steps.generate-matrix.outputs.tests }} + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.checkout-ref }} + - uses: ./.github/actions/setup-rust + + - name: Generate matrix + id: generate-matrix + run: cargo xtask ci-job target-matrix ${{ github.event_name == 'merge_group' && format('--merge-group {0}', github.ref) || '' }} ${{ inputs.matrix-args || '' }} --for-build + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + build: name: target (${{ matrix.pretty }},${{ matrix.os }}) runs-on: ${{ matrix.os }} - needs: [shellcheck, test, generate-matrix, check] + needs: [shellcheck, test, generate-matrix-for-build, check] if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' || github.event_name == 'issue_comment' || github.event_name == 'schedule') && needs.generate-matrix.outputs.matrix != '{}' && needs.generate-matrix.outputs.matrix != '[]' && needs.generate-matrix.outputs.matrix != '' concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.pretty }} @@ -186,7 +203,7 @@ jobs: id: build-docker-image if: steps.prepare-meta.outputs.has-image timeout-minutes: 120 - run: cargo xtask build-docker-image -v "${TARGET}${SUB:+.$SUB}" ${{ matrix.verbose && '-v' || '' }} + run: cargo xtask build-docker-image -v "${TARGET}${SUB:+.$SUB}" ${{ matrix.verbose && '-v' || '' }} --platform ${{ matrix.platform }} env: TARGET: ${{ matrix.target }} SUB: ${{ matrix.sub }} @@ -232,6 +249,66 @@ jobs: IMAGE: 'ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main' shell: bash + push_image: + name: target (${{ matrix.pretty }},${{ matrix.os }}) + runs-on: ${{ matrix.os }} + needs: [shellcheck, test, generate-matrix, check, build] + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group' || github.event_name == 'issue_comment' || github.event_name == 'schedule') && needs.generate-matrix.outputs.matrix != '{}' && needs.generate-matrix.outputs.matrix != '[]' && needs.generate-matrix.outputs.matrix != '' + concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.pretty }} + cancel-in-progress: false + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + outputs: + has-image: ${{ steps.prepare-meta.outputs.has-image }} + images: ${{ steps.build-docker-image.outputs.images && fromJSON(steps.build-docker-image.outputs.images) }} + coverage-artifact: ${{ steps.cov.outputs.artifact-name }} + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.checkout-ref }} + + - uses: ./.github/actions/setup-rust + + - name: Set up Docker Buildx + if: runner.os == 'Linux' + uses: docker/setup-buildx-action@v2 + + - name: Build xtask + run: cargo build -p xtask + + - name: Prepare Meta + id: prepare-meta + timeout-minutes: 60 + run: cargo xtask ci-job prepare-meta "${TARGET}${SUB:+.$SUB}" + env: + TARGET: ${{ matrix.target }} + SUB: ${{ matrix.sub }} + PLATFORMS: ${{ join(matrix.platforms, ',') }} + shell: bash + + - name: LLVM instrument coverage + id: cov + uses: ./.github/actions/cargo-llvm-cov + if: steps.prepare-meta.outputs.has-image && steps.prepare-meta.outputs.test-variant != 'zig' + with: + name: cross-${{matrix.pretty}} + + - name: Install cross + if: matrix.deploy + run: cargo install --path . --force --debug + + - name: Docker Meta + if: steps.prepare-meta.outputs.has-image + id: docker-meta + uses: docker/metadata-action@v4 + with: + images: | + name=${{ steps.prepare-meta.outputs.image }} + labels: | + ${{ fromJSON(steps.prepare-meta.outputs.labels) }} - name: Login to GitHub Container Registry if: steps.prepare-meta.outputs.has-image uses: docker/login-action@v2 @@ -246,7 +323,7 @@ jobs: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) || startsWith(github.ref, 'refs/tags/v') ) - run: cargo xtask build-docker-image -v --push "${TARGET}${SUB:+.$SUB}" + run: cargo xtask build-docker-image -v --push "${TARGET}${SUB:+.$SUB}" --platform ${{ join(matrix.platforms, ',') }} env: TARGET: ${{ matrix.target }} SUB: ${{ matrix.sub }} diff --git a/src/docker/build.rs b/src/docker/build.rs index 9a98c2839..db620adbc 100644 --- a/src/docker/build.rs +++ b/src/docker/build.rs @@ -41,7 +41,7 @@ pub trait BuildCommandExt { fn progress(&mut self, progress: Option) -> Result<&mut Self>; fn verbose(&mut self, verbosity: Verbosity) -> &mut Self; fn disable_scan_suggest(&mut self) -> &mut Self; - fn cross_labels(&mut self, target: &str, platform: &str) -> &mut Self; + fn cross_labels(&mut self, target: &str, platform: &[&str]) -> &mut Self; } impl BuildCommandExt for Command { @@ -74,15 +74,18 @@ impl BuildCommandExt for Command { self.env("DOCKER_SCAN_SUGGEST", "false") } - fn cross_labels(&mut self, target: &str, platform: &str) -> &mut Self { + fn cross_labels(&mut self, target: &str, platforms: &[&str]) -> &mut Self { self.args([ "--label", &format!("{}.for-cross-target={target}", crate::CROSS_LABEL_DOMAIN,), ]); - self.args([ - "--label", - &format!("{}.runs-with={platform}", crate::CROSS_LABEL_DOMAIN,), - ]) + for platform in platforms { + self.args([ + "--label", + &format!("{}.runs-with={platform}", crate::CROSS_LABEL_DOMAIN,), + ]); + } + self } } diff --git a/src/docker/custom.rs b/src/docker/custom.rs index 4e2a4245e..2c03a1cf2 100644 --- a/src/docker/custom.rs +++ b/src/docker/custom.rs @@ -97,7 +97,7 @@ impl<'a> Dockerfile<'a> { docker_build.progress(None)?; docker_build.verbose(msg_info.verbosity); - docker_build.cross_labels(options.target.triple(), self.runs_with().target.triple()); + docker_build.cross_labels(options.target.triple(), &[self.runs_with().target.triple()]); docker_build.args([ "--label", diff --git a/src/docker/provided_images.rs b/src/docker/provided_images.rs index ba55026a9..a7f2b0106 100644 --- a/src/docker/provided_images.rs +++ b/src/docker/provided_images.rs @@ -20,42 +20,42 @@ pub static PROVIDED_IMAGES: &[ProvidedImage] = &[ }, ProvidedImage { name: "aarch64-unknown-linux-gnu", - platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU], + platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU, ImagePlatform::AARCH64_UNKNOWN_LINUX_GNU], sub: None }, ProvidedImage { name: "arm-unknown-linux-gnueabi", - platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU], + platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU, ImagePlatform::AARCH64_UNKNOWN_LINUX_GNU], sub: None }, ProvidedImage { name: "arm-unknown-linux-gnueabihf", - platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU], + platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU, ImagePlatform::AARCH64_UNKNOWN_LINUX_GNU], sub: None }, ProvidedImage { name: "armv7-unknown-linux-gnueabi", - platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU], + platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU, ImagePlatform::AARCH64_UNKNOWN_LINUX_GNU], sub: None }, ProvidedImage { name: "armv7-unknown-linux-gnueabihf", - platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU], + platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU, ImagePlatform::AARCH64_UNKNOWN_LINUX_GNU], sub: None }, ProvidedImage { name: "thumbv7neon-unknown-linux-gnueabihf", - platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU], + platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU, ImagePlatform::AARCH64_UNKNOWN_LINUX_GNU], sub: None }, ProvidedImage { name: "i586-unknown-linux-gnu", - platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU], + platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU, ImagePlatform::AARCH64_UNKNOWN_LINUX_GNU], sub: None }, ProvidedImage { name: "i686-unknown-linux-gnu", - platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU], + platforms: &[ImagePlatform::X86_64_UNKNOWN_LINUX_GNU, ImagePlatform::AARCH64_UNKNOWN_LINUX_GNU], sub: None }, ProvidedImage { diff --git a/targets.toml b/targets.toml index 759e6867a..609698573 100644 --- a/targets.toml +++ b/targets.toml @@ -53,6 +53,7 @@ dylib = true std = true run = true runners = "qemu-user qemu-system" +platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] [[target]] target = "arm-unknown-linux-gnueabi" @@ -61,6 +62,7 @@ cpp = true dylib = true std = true run = true +platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] [[target]] target = "arm-unknown-linux-gnueabihf" @@ -69,6 +71,7 @@ cpp = true dylib = true std = true run = true +platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] [[target]] target = "armv7-unknown-linux-gnueabi" @@ -78,6 +81,7 @@ dylib = true std = true run = true runners = "qemu-user" +platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] [[target]] target = "armv7-unknown-linux-gnueabihf" @@ -87,6 +91,7 @@ dylib = true std = true run = true runners = "qemu-user qemu-system" +platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] [[target]] target = "thumbv7neon-unknown-linux-gnueabihf" @@ -96,6 +101,7 @@ dylib = true std = true run = true runners = "qemu-user qemu-system" +platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] [[target]] target = "i586-unknown-linux-gnu" @@ -104,6 +110,7 @@ cpp = true dylib = true std = true run = true +platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] [[target]] target = "i686-unknown-linux-gnu" @@ -113,6 +120,7 @@ dylib = true std = true run = true runners = "native qemu-user qemu-system" +platforms = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] [[target]] target = "loongarch64-unknown-linux-gnu" diff --git a/xtask/src/build_docker_image.rs b/xtask/src/build_docker_image.rs index c747aba50..374596980 100644 --- a/xtask/src/build_docker_image.rs +++ b/xtask/src/build_docker_image.rs @@ -75,7 +75,7 @@ pub struct BuildDockerImage { #[clap(long)] pub build_arg: Vec, // [os/arch[/variant]=]toolchain - #[clap(long, short = 'a', action = clap::builder::ArgAction::Append)] + #[clap(long, short = 'a', action = clap::builder::ArgAction::Append, value_delimiter = ',')] pub platform: Vec, /// Targets to build for #[clap()] @@ -176,30 +176,35 @@ pub fn build_docker_image( }; let mut results = vec![]; - for (platform, (target, dockerfile)) in targets - .iter() - .flat_map(|t| platforms.iter().map(move |p| (p, t))) - { + for (target, dockerfile) in targets.iter() { if gha && targets.len() > 1 { gha_print("::group::Build {target}"); } else { - msg_info.note(format_args!("Build {target} for {}", platform.target))?; + msg_info.note(format_args!( + "Build {target} for {}", + platforms + .iter() + .map(|p| p.target.to_string()) + .collect::>() + .join(" and ") + ))?; } let mut docker_build = engine.command(); docker_build.invoke_build_command(); let has_buildkit = docker::Engine::has_buildkit(); docker_build.current_dir(&docker_root); - let docker_platform = platform.docker_platform(); + let docker_platforms: Vec = platforms.iter().map(|p| p.docker_platform()).collect(); let mut dockerfile = dockerfile.clone(); - docker_build.args(["--platform", &docker_platform]); let uppercase_triple = target.name.to_ascii_uppercase().replace('-', "_"); docker_build.args([ "--build-arg", &format!("CROSS_TARGET_TRIPLE={}", uppercase_triple), ]); // add our platform, and determine if we need to use a native docker image - if has_native_image(docker_platform.as_str(), target, msg_info)? { + if docker_platforms.len() == 1 + && has_native_image(docker_platforms[0].as_str(), target, msg_info)? + { let dockerfile_name = match target.sub.as_deref() { Some(sub) => format!("Dockerfile.native.{sub}"), None => "Dockerfile.native".to_owned(), @@ -212,6 +217,7 @@ pub fn build_docker_image( } dockerfile = dockerfile_path.to_utf8()?.to_string(); } + docker_build.args(["--platform", &docker_platforms.join(",")]); if push { docker_build.arg("--push"); @@ -220,6 +226,7 @@ pub fn build_docker_image( } else if no_output { msg_info.fatal("cannot specify `--no-output` with engine that does not support the `--output` flag", 1); } else if has_buildkit { + // TODO: docker daemon doesn't support loading multi-arch, so it will fail when platforms.len() > 1 docker_build.arg("--load"); } @@ -287,7 +294,13 @@ pub fn build_docker_image( docker_build.args(["--label", label]); } - docker_build.cross_labels(&target.name, platform.target.triple()); + docker_build.cross_labels( + &target.name, + &platforms + .iter() + .map(|p| p.target.triple()) + .collect::>(), + ); docker_build.args(["--file", &dockerfile]); docker_build.progress(progress)?; diff --git a/xtask/src/ci/target_matrix.rs b/xtask/src/ci/target_matrix.rs index 4b8a518c3..19865ffe0 100644 --- a/xtask/src/ci/target_matrix.rs +++ b/xtask/src/ci/target_matrix.rs @@ -1,12 +1,12 @@ use std::process::Command; +use crate::util::{get_matrix, gha_output, gha_print, CiTarget, ImageTarget}; use clap::builder::{BoolishValueParser, PossibleValuesParser}; use clap::Parser; +use cross::docker::ImagePlatform; use cross::{shell::Verbosity, CommandExt}; use serde::{Deserialize, Serialize}; -use crate::util::{get_matrix, gha_output, gha_print, CiTarget, ImageTarget}; - #[derive(Parser, Debug)] pub struct TargetMatrix { /// check is being run as part of a weekly check @@ -17,6 +17,8 @@ pub struct TargetMatrix { pub merge_group: Option, #[clap(subcommand)] pub subcommand: Option, + #[clap(long)] + pub for_build: bool, } #[derive(Parser, Debug)] @@ -91,6 +93,7 @@ impl TargetMatrix { weekly: false, merge_group: Some(_) | None, subcommand: None, + for_build: false | true, } ) || is_default_try { @@ -99,26 +102,54 @@ impl TargetMatrix { app.filter(&mut matrix); - let matrix = matrix - .iter() - .map(|target| TargetMatrixElement { - pretty: target.to_image_target().alt(), - platforms: target.platforms(), - target: &target.target, - sub: target.sub.as_deref(), - os: &target.os, - run: target.run.map(|b| b as u8), - deploy: target.deploy.map(|b| b as u8), - build_std: target.build_std.map(|b| b as u8), - cpp: target.cpp.map(|b| b as u8), - dylib: target.dylib.map(|b| b as u8), - runners: target.runners.as_deref(), - std: target.std.map(|b| b as u8), - verbose: app.verbose, - }) - .collect::>(); + let json = if self.for_build { + let matrix = matrix + .iter() + .flat_map(|target| { + target + .platforms() + .iter() + .map(move |platform| (target, platform)) + }) + .map(|(target, platform)| TargetMatrixElementForBuild { + pretty: target.to_image_target().alt(), + platform: platform.clone(), + target: &target.target, + sub: target.sub.as_deref(), + os: &target.os, + run: target.run.map(|b| b as u8), + deploy: target.deploy.map(|b| b as u8), + build_std: target.build_std.map(|b| b as u8), + cpp: target.cpp.map(|b| b as u8), + dylib: target.dylib.map(|b| b as u8), + runners: target.runners.as_deref(), + std: target.std.map(|b| b as u8), + verbose: app.verbose, + }) + .collect::>(); + serde_json::to_string(&matrix)? + } else { + let matrix = matrix + .iter() + .map(|target| TargetMatrixElement { + pretty: target.to_image_target().alt(), + platforms: target.platforms(), + target: &target.target, + sub: target.sub.as_deref(), + os: &target.os, + run: target.run.map(|b| b as u8), + deploy: target.deploy.map(|b| b as u8), + build_std: target.build_std.map(|b| b as u8), + cpp: target.cpp.map(|b| b as u8), + dylib: target.dylib.map(|b| b as u8), + runners: target.runners.as_deref(), + std: target.std.map(|b| b as u8), + verbose: app.verbose, + }) + .collect::>(); + serde_json::to_string(&matrix)? + }; - let json = serde_json::to_string(&matrix)?; gha_output("matrix", &json)?; let tests = serde_json::to_string(&app.tests()?)?; gha_output("tests", &tests)?; @@ -244,7 +275,32 @@ fn process_try_comment(message: &str) -> cross::Result<(bool, TargetMatrixArgs)> #[serde(rename_all = "kebab-case")] struct TargetMatrixElement<'a> { pretty: String, - platforms: &'a [String], + platforms: &'a [ImagePlatform], + target: &'a str, + #[serde(skip_serializing_if = "Option::is_none")] + sub: Option<&'a str>, + os: &'a str, + #[serde(skip_serializing_if = "Option::is_none")] + run: Option, + #[serde(skip_serializing_if = "Option::is_none")] + deploy: Option, + #[serde(skip_serializing_if = "Option::is_none")] + build_std: Option, + #[serde(skip_serializing_if = "Option::is_none")] + cpp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + dylib: Option, + #[serde(skip_serializing_if = "Option::is_none")] + runners: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + std: Option, + verbose: bool, +} +#[derive(Serialize)] +#[serde(rename_all = "kebab-case")] +struct TargetMatrixElementForBuild<'a> { + pretty: String, + platform: ImagePlatform, target: &'a str, #[serde(skip_serializing_if = "Option::is_none")] sub: Option<&'a str>, diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index 1fe5217b9..5d92a0d90 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs @@ -1,5 +1,4 @@ use clap::Args; -use cross::docker::ImagePlatform; use eyre::Context; use std::fmt::Write; @@ -39,10 +38,7 @@ pub static PROVIDED_IMAGES: &[ProvidedImage] = &["#, platform = &image_target .platforms() .iter() - .map(|p| { - let image_platform: ImagePlatform = - p.parse().expect("should be a valid platform"); - + .map(|image_platform| { image_platform .to_codegen_string() .expect("should be a valid platform") diff --git a/xtask/src/util.rs b/xtask/src/util.rs index 04f1b8cec..899cffdd8 100644 --- a/xtask/src/util.rs +++ b/xtask/src/util.rs @@ -7,6 +7,7 @@ use std::process::Command; use cross::shell::MessageInfo; use cross::{docker, CommandExt, ToUtf8}; +use cross::docker::ImagePlatform; use once_cell::sync::{Lazy, OnceCell}; use serde::Deserialize; @@ -48,7 +49,7 @@ pub struct CiTarget { pub deploy: Option, /// the platform to build this image for, defaults to `["linux/amd64"]`, takes multiple #[serde(skip_serializing_if = "Option::is_none")] - platforms: Option>, + platforms: Option>, /// if `true` signal that this target requires `-Zbuild-std` #[serde(skip_serializing_if = "Option::is_none")] pub build_std: Option, @@ -89,8 +90,8 @@ impl CiTarget { self.os == "ubuntu-latest" } - pub fn platforms(&self) -> &[String] { - self.platforms.as_ref().unwrap_or(&DEFAULT_PLATFORMS_STRING) + pub fn platforms(&self) -> &[ImagePlatform] { + self.platforms.as_deref().unwrap_or(DEFAULT_PLATFORMS) } }