feat: Add platform arg to force building a specific architecture
This commit is contained in:
parent
20d1950530
commit
75eae89e4a
22 changed files with 408 additions and 71 deletions
52
.github/workflows/build-pr.yml
vendored
52
.github/workflows/build-pr.yml
vendored
|
|
@ -258,6 +258,58 @@ jobs:
|
||||||
grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1
|
grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1
|
||||||
bluebuild build --retry-push -B docker -I docker -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml
|
bluebuild build --retry-push -B docker -I docker -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml
|
||||||
|
|
||||||
|
arm64-build:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
if: needs.build.outputs.push == 'true'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Maximize build space
|
||||||
|
uses: ublue-os/remove-unwanted-software@v6
|
||||||
|
|
||||||
|
- uses: sigstore/cosign-installer@v3.3.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
|
- name: Install bluebuild
|
||||||
|
run: |
|
||||||
|
cargo install --path . --debug --all-features
|
||||||
|
|
||||||
|
- name: Expose GitHub Runtime
|
||||||
|
uses: crazy-max/ghaction-github-runtime@v3
|
||||||
|
|
||||||
|
- name: Run Build
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_PR_EVENT_NUMBER: ${{ github.event.number }}
|
||||||
|
COSIGN_PRIVATE_KEY: ${{ secrets.TEST_SIGNING_SECRET }}
|
||||||
|
BB_BUILDKIT_CACHE_GHA: true
|
||||||
|
run: |
|
||||||
|
cd integration-tests/test-repo
|
||||||
|
bluebuild build \
|
||||||
|
--retry-push \
|
||||||
|
--platform linux/arm64 \
|
||||||
|
--push \
|
||||||
|
-vv \
|
||||||
|
recipes/recipe-arm64.yml
|
||||||
|
|
||||||
docker-build-external-login:
|
docker-build-external-login:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
||||||
52
.github/workflows/build.yml
vendored
52
.github/workflows/build.yml
vendored
|
|
@ -256,6 +256,58 @@ jobs:
|
||||||
grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1
|
grep -q 'ARG IMAGE_REGISTRY=ghcr.io/blue-build' Containerfile || exit 1
|
||||||
bluebuild build --retry-push -B docker -I docker -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml
|
bluebuild build --retry-push -B docker -I docker -S sigstore --push -vv recipes/recipe.yml recipes/recipe-39.yml
|
||||||
|
|
||||||
|
arm64-build:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
if: github.repository == 'blue-build/cli'
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Maximize build space
|
||||||
|
uses: ublue-os/remove-unwanted-software@v6
|
||||||
|
|
||||||
|
- uses: sigstore/cosign-installer@v3.3.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
|
- name: Install bluebuild
|
||||||
|
run: |
|
||||||
|
cargo install --path . --debug --all-features
|
||||||
|
|
||||||
|
- name: Expose GitHub Runtime
|
||||||
|
uses: crazy-max/ghaction-github-runtime@v3
|
||||||
|
|
||||||
|
- name: Run Build
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_PR_EVENT_NUMBER: ${{ github.event.number }}
|
||||||
|
COSIGN_PRIVATE_KEY: ${{ secrets.TEST_SIGNING_SECRET }}
|
||||||
|
BB_BUILDKIT_CACHE_GHA: true
|
||||||
|
run: |
|
||||||
|
cd integration-tests/test-repo
|
||||||
|
bluebuild build \
|
||||||
|
--retry-push \
|
||||||
|
--platform linux/arm64 \
|
||||||
|
--push \
|
||||||
|
-vv \
|
||||||
|
recipes/recipe-arm64.yml
|
||||||
|
|
||||||
docker-build-external-login:
|
docker-build-external-login:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
||||||
38
integration-tests/test-repo/recipes/recipe-arm64.yml
Normal file
38
integration-tests/test-repo/recipes/recipe-arm64.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
name: cli/test
|
||||||
|
description: This is my personal OS image.
|
||||||
|
base-image: quay.io/fedora/fedora-silverblue
|
||||||
|
image-version: 40
|
||||||
|
alt_tags:
|
||||||
|
- arm64
|
||||||
|
stages:
|
||||||
|
modules:
|
||||||
|
- from-file: akmods.yml
|
||||||
|
- from-file: flatpaks.yml
|
||||||
|
|
||||||
|
- type: files
|
||||||
|
files:
|
||||||
|
- usr: /usr
|
||||||
|
|
||||||
|
- type: script
|
||||||
|
scripts:
|
||||||
|
- example.sh
|
||||||
|
|
||||||
|
- type: rpm-ostree
|
||||||
|
repos:
|
||||||
|
- https://copr.fedorainfracloud.org/coprs/atim/starship/repo/fedora-%OS_VERSION%/atim-starship-fedora-%OS_VERSION%.repo
|
||||||
|
install:
|
||||||
|
- micro
|
||||||
|
- starship
|
||||||
|
remove:
|
||||||
|
- firefox
|
||||||
|
- firefox-langpacks
|
||||||
|
|
||||||
|
- type: signing
|
||||||
|
|
||||||
|
- type: test-module
|
||||||
|
|
||||||
|
- type: containerfile
|
||||||
|
containerfiles:
|
||||||
|
- labels
|
||||||
|
snippets:
|
||||||
|
- RUN echo "This is a snippet" && ostree container commit
|
||||||
|
|
@ -12,7 +12,7 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bon::Builder;
|
use bon::{bon, Builder};
|
||||||
use cached::proc_macro::cached;
|
use cached::proc_macro::cached;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
@ -24,6 +24,7 @@ use once_cell::sync::Lazy;
|
||||||
use opts::{GenerateImageNameOpts, GenerateTagsOpts};
|
use opts::{GenerateImageNameOpts, GenerateTagsOpts};
|
||||||
#[cfg(feature = "sigstore")]
|
#[cfg(feature = "sigstore")]
|
||||||
use sigstore_driver::SigstoreDriver;
|
use sigstore_driver::SigstoreDriver;
|
||||||
|
use types::Platform;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::logging::Logger;
|
use crate::logging::Logger;
|
||||||
|
|
@ -152,6 +153,7 @@ macro_rules! impl_driver_init {
|
||||||
|
|
||||||
pub struct Driver;
|
pub struct Driver;
|
||||||
|
|
||||||
|
#[bon]
|
||||||
impl Driver {
|
impl Driver {
|
||||||
/// Initializes the Strategy with user provided credentials.
|
/// Initializes the Strategy with user provided credentials.
|
||||||
///
|
///
|
||||||
|
|
@ -192,7 +194,14 @@ impl Driver {
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if the mutex fails to lock.
|
/// Panics if the mutex fails to lock.
|
||||||
pub fn get_os_version(oci_ref: &Reference) -> Result<u64> {
|
#[builder]
|
||||||
|
pub fn get_os_version(
|
||||||
|
/// The OCI image reference.
|
||||||
|
oci_ref: &Reference,
|
||||||
|
/// The platform of the image to pull the version info from.
|
||||||
|
#[builder(default)]
|
||||||
|
platform: Platform,
|
||||||
|
) -> Result<u64> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
{
|
||||||
let _ = oci_ref; // silence lint
|
let _ = oci_ref; // silence lint
|
||||||
|
|
@ -203,7 +212,7 @@ impl Driver {
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("Driver::get_os_version({oci_ref:#?})");
|
trace!("Driver::get_os_version({oci_ref:#?})");
|
||||||
get_version(oci_ref)
|
get_version(oci_ref, platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_build_driver() -> BuildDriverType {
|
fn get_build_driver() -> BuildDriverType {
|
||||||
|
|
@ -230,10 +239,10 @@ impl Driver {
|
||||||
#[cached(
|
#[cached(
|
||||||
result = true,
|
result = true,
|
||||||
key = "String",
|
key = "String",
|
||||||
convert = "{ oci_ref.to_string() }",
|
convert = r#"{ format!("{oci_ref}-{platform}") }"#,
|
||||||
sync_writes = true
|
sync_writes = true
|
||||||
)]
|
)]
|
||||||
fn get_version(oci_ref: &Reference) -> Result<u64> {
|
fn get_version(oci_ref: &Reference, platform: Platform) -> Result<u64> {
|
||||||
info!("Retrieving OS version from {oci_ref}. This might take a bit");
|
info!("Retrieving OS version from {oci_ref}. This might take a bit");
|
||||||
let inspect_opts = GetMetadataOpts::builder()
|
let inspect_opts = GetMetadataOpts::builder()
|
||||||
.image(format!(
|
.image(format!(
|
||||||
|
|
@ -242,6 +251,7 @@ fn get_version(oci_ref: &Reference) -> Result<u64> {
|
||||||
oci_ref.repository()
|
oci_ref.repository()
|
||||||
))
|
))
|
||||||
.tag(oci_ref.tag().unwrap_or("latest"))
|
.tag(oci_ref.tag().unwrap_or("latest"))
|
||||||
|
.platform(platform)
|
||||||
.build();
|
.build();
|
||||||
let os_version = Driver::get_metadata(&inspect_opts)
|
let os_version = Driver::get_metadata(&inspect_opts)
|
||||||
.and_then(|inspection| {
|
.and_then(|inspection| {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use miette::{bail, miette, IntoDiagnostic, Result};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::logging::CommandLogging;
|
use crate::{drivers::types::Platform, logging::CommandLogging};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
opts::{BuildOpts, PushOpts, TagOpts},
|
opts::{BuildOpts, PushOpts, TagOpts},
|
||||||
|
|
@ -50,6 +50,10 @@ impl BuildDriver for BuildahDriver {
|
||||||
let command = cmd!(
|
let command = cmd!(
|
||||||
"buildah",
|
"buildah",
|
||||||
"build",
|
"build",
|
||||||
|
if !matches!(opts.platform, Platform::Native) => [
|
||||||
|
"--platform",
|
||||||
|
opts.platform.to_string(),
|
||||||
|
],
|
||||||
"--pull=true",
|
"--pull=true",
|
||||||
format!("--layers={}", !opts.squash),
|
format!("--layers={}", !opts.squash),
|
||||||
"-f",
|
"-f",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use blue_build_utils::{
|
||||||
constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST, SKOPEO_IMAGE},
|
constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST, SKOPEO_IMAGE},
|
||||||
credentials::Credentials,
|
credentials::Credentials,
|
||||||
string_vec,
|
string_vec,
|
||||||
|
traits::IntoCollector,
|
||||||
};
|
};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
@ -26,6 +27,7 @@ use crate::{
|
||||||
drivers::{
|
drivers::{
|
||||||
image_metadata::ImageMetadata,
|
image_metadata::ImageMetadata,
|
||||||
opts::{RunOptsEnv, RunOptsVolume},
|
opts::{RunOptsEnv, RunOptsVolume},
|
||||||
|
types::Platform,
|
||||||
},
|
},
|
||||||
logging::{CommandLogging, Logger},
|
logging::{CommandLogging, Logger},
|
||||||
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
|
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
|
||||||
|
|
@ -130,6 +132,10 @@ impl BuildDriver for DockerDriver {
|
||||||
let status = cmd!(
|
let status = cmd!(
|
||||||
"docker",
|
"docker",
|
||||||
"build",
|
"build",
|
||||||
|
if !matches!(opts.platform, Platform::Native) => [
|
||||||
|
"--platform",
|
||||||
|
opts.platform.to_string(),
|
||||||
|
],
|
||||||
"-t",
|
"-t",
|
||||||
&*opts.image,
|
&*opts.image,
|
||||||
"-f",
|
"-f",
|
||||||
|
|
@ -235,6 +241,10 @@ impl BuildDriver for DockerDriver {
|
||||||
},
|
},
|
||||||
"build",
|
"build",
|
||||||
"--pull",
|
"--pull",
|
||||||
|
if !matches!(opts.platform, Platform::Native) => [
|
||||||
|
"--platform",
|
||||||
|
opts.platform.to_string(),
|
||||||
|
],
|
||||||
"-f",
|
"-f",
|
||||||
&*opts.containerfile,
|
&*opts.containerfile,
|
||||||
// https://github.com/moby/buildkit?tab=readme-ov-file#github-actions-cache-experimental
|
// https://github.com/moby/buildkit?tab=readme-ov-file#github-actions-cache-experimental
|
||||||
|
|
@ -322,10 +332,16 @@ impl InspectDriver for DockerDriver {
|
||||||
);
|
);
|
||||||
progress.enable_steady_tick(Duration::from_millis(100));
|
progress.enable_steady_tick(Duration::from_millis(100));
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
if !matches!(opts.platform, Platform::Native) {
|
||||||
|
args.extend(["--override-arch", opts.platform.arch()]);
|
||||||
|
}
|
||||||
|
args.extend(["inspect", &url]);
|
||||||
|
|
||||||
let output = Self::run_output(
|
let output = Self::run_output(
|
||||||
&RunOpts::builder()
|
&RunOpts::builder()
|
||||||
.image(SKOPEO_IMAGE)
|
.image(SKOPEO_IMAGE)
|
||||||
.args(bon::vec!["inspect", &url])
|
.args(args.collect_into_vec())
|
||||||
.remove(true)
|
.remove(true)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,11 @@ impl CiDriver for GithubDriver {
|
||||||
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||||
const PR_EVENT: &str = "pull_request";
|
const PR_EVENT: &str = "pull_request";
|
||||||
let timestamp = blue_build_utils::get_tag_timestamp();
|
let timestamp = blue_build_utils::get_tag_timestamp();
|
||||||
let os_version =
|
let os_version = Driver::get_os_version()
|
||||||
Driver::get_os_version(opts.oci_ref).inspect(|v| trace!("os_version={v}"))?;
|
.oci_ref(opts.oci_ref)
|
||||||
|
.platform(opts.platform)
|
||||||
|
.call()
|
||||||
|
.inspect(|v| trace!("os_version={v}"))?;
|
||||||
let ref_name = get_env_var(GITHUB_REF_NAME).inspect(|v| trace!("{GITHUB_REF_NAME}={v}"))?;
|
let ref_name = get_env_var(GITHUB_REF_NAME).inspect(|v| trace!("{GITHUB_REF_NAME}={v}"))?;
|
||||||
let short_sha = {
|
let short_sha = {
|
||||||
let mut short_sha = get_env_var(GITHUB_SHA).inspect(|v| trace!("{GITHUB_SHA}={v}"))?;
|
let mut short_sha = get_env_var(GITHUB_SHA).inspect(|v| trace!("{GITHUB_SHA}={v}"))?;
|
||||||
|
|
@ -144,7 +147,7 @@ mod test {
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
drivers::{opts::GenerateTagsOpts, CiDriver},
|
drivers::{opts::GenerateTagsOpts, types::Platform, CiDriver},
|
||||||
test::{TEST_TAG_1, TEST_TAG_2, TIMESTAMP},
|
test::{TEST_TAG_1, TEST_TAG_2, TIMESTAMP},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -285,6 +288,7 @@ mod test {
|
||||||
&GenerateTagsOpts::builder()
|
&GenerateTagsOpts::builder()
|
||||||
.oci_ref(&oci_ref)
|
.oci_ref(&oci_ref)
|
||||||
.maybe_alt_tags(alt_tags)
|
.maybe_alt_tags(alt_tags)
|
||||||
|
.platform(Platform::LinuxAmd64)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,10 @@ impl CiDriver for GitlabDriver {
|
||||||
|
|
||||||
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||||
const MR_EVENT: &str = "merge_request_event";
|
const MR_EVENT: &str = "merge_request_event";
|
||||||
let os_version = Driver::get_os_version(opts.oci_ref)?;
|
let os_version = Driver::get_os_version()
|
||||||
|
.oci_ref(opts.oci_ref)
|
||||||
|
.platform(opts.platform)
|
||||||
|
.call()?;
|
||||||
let timestamp = blue_build_utils::get_tag_timestamp();
|
let timestamp = blue_build_utils::get_tag_timestamp();
|
||||||
let short_sha =
|
let short_sha =
|
||||||
get_env_var(CI_COMMIT_SHORT_SHA).inspect(|v| trace!("{CI_COMMIT_SHORT_SHA}={v}"))?;
|
get_env_var(CI_COMMIT_SHORT_SHA).inspect(|v| trace!("{CI_COMMIT_SHORT_SHA}={v}"))?;
|
||||||
|
|
@ -293,6 +296,7 @@ mod test {
|
||||||
&GenerateTagsOpts::builder()
|
&GenerateTagsOpts::builder()
|
||||||
.oci_ref(&oci_ref)
|
.oci_ref(&oci_ref)
|
||||||
.maybe_alt_tags(alt_tags)
|
.maybe_alt_tags(alt_tags)
|
||||||
|
.platform(crate::drivers::types::Platform::LinuxAmd64)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,10 @@ impl CiDriver for LocalDriver {
|
||||||
|
|
||||||
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||||
trace!("LocalDriver::generate_tags({opts:?})");
|
trace!("LocalDriver::generate_tags({opts:?})");
|
||||||
let os_version = Driver::get_os_version(opts.oci_ref)?;
|
let os_version = Driver::get_os_version()
|
||||||
|
.oci_ref(opts.oci_ref)
|
||||||
|
.platform(opts.platform)
|
||||||
|
.call()?;
|
||||||
let timestamp = blue_build_utils::get_tag_timestamp();
|
let timestamp = blue_build_utils::get_tag_timestamp();
|
||||||
let short_sha = commit_sha();
|
let short_sha = commit_sha();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use std::{borrow::Cow, path::Path};
|
||||||
|
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
|
|
||||||
|
use crate::drivers::types::Platform;
|
||||||
|
|
||||||
use super::CompressionType;
|
use super::CompressionType;
|
||||||
|
|
||||||
/// Options for building
|
/// Options for building
|
||||||
|
|
@ -15,14 +17,15 @@ pub struct BuildOpts<'scope> {
|
||||||
|
|
||||||
#[builder(into)]
|
#[builder(into)]
|
||||||
pub containerfile: Cow<'scope, Path>,
|
pub containerfile: Cow<'scope, Path>,
|
||||||
|
|
||||||
|
#[builder(default)]
|
||||||
|
pub platform: Platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Builder)]
|
||||||
|
#[builder(on(Cow<'_, str>, into))]
|
||||||
pub struct TagOpts<'scope> {
|
pub struct TagOpts<'scope> {
|
||||||
#[builder(into)]
|
|
||||||
pub src_image: Cow<'scope, str>,
|
pub src_image: Cow<'scope, str>,
|
||||||
|
|
||||||
#[builder(into)]
|
|
||||||
pub dest_image: Cow<'scope, str>,
|
pub dest_image: Cow<'scope, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,4 +83,8 @@ pub struct BuildTagPushOpts<'scope> {
|
||||||
/// Run all steps in a single layer.
|
/// Run all steps in a single layer.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub squash: bool,
|
pub squash: bool,
|
||||||
|
|
||||||
|
/// The platform to build the image on.
|
||||||
|
#[builder(default)]
|
||||||
|
pub platform: Platform,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,17 @@ use std::borrow::Cow;
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
use oci_distribution::Reference;
|
use oci_distribution::Reference;
|
||||||
|
|
||||||
|
use crate::drivers::types::Platform;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Builder)]
|
||||||
pub struct GenerateTagsOpts<'scope> {
|
pub struct GenerateTagsOpts<'scope> {
|
||||||
pub oci_ref: &'scope Reference,
|
pub oci_ref: &'scope Reference,
|
||||||
|
|
||||||
#[builder(into)]
|
#[builder(into)]
|
||||||
pub alt_tags: Option<Vec<Cow<'scope, str>>>,
|
pub alt_tags: Option<Vec<Cow<'scope, str>>>,
|
||||||
|
|
||||||
|
#[builder(default)]
|
||||||
|
pub platform: Platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Builder)]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use bon::Builder;
|
use bon::Builder;
|
||||||
|
|
||||||
|
use crate::drivers::types::Platform;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Builder)]
|
||||||
pub struct GetMetadataOpts<'scope> {
|
pub struct GetMetadataOpts<'scope> {
|
||||||
#[builder(into)]
|
#[builder(into)]
|
||||||
|
|
@ -9,4 +11,7 @@ pub struct GetMetadataOpts<'scope> {
|
||||||
|
|
||||||
#[builder(into)]
|
#[builder(into)]
|
||||||
pub tag: Option<Cow<'scope, str>>,
|
pub tag: Option<Cow<'scope, str>>,
|
||||||
|
|
||||||
|
#[builder(default)]
|
||||||
|
pub platform: Platform,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ use bon::Builder;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
|
use crate::drivers::types::Platform;
|
||||||
|
|
||||||
pub enum PrivateKey {
|
pub enum PrivateKey {
|
||||||
Env(String),
|
Env(String),
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
|
|
@ -112,4 +114,7 @@ pub struct SignVerifyOpts<'scope> {
|
||||||
/// Defaults to 1.
|
/// Defaults to 1.
|
||||||
#[builder(default = 1)]
|
#[builder(default = 1)]
|
||||||
pub retry_count: u8,
|
pub retry_count: u8,
|
||||||
|
|
||||||
|
#[builder(default)]
|
||||||
|
pub platform: Platform,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use blue_build_utils::{cmd, constants::SKOPEO_IMAGE, credentials::Credentials};
|
use blue_build_utils::{
|
||||||
|
cmd, constants::SKOPEO_IMAGE, credentials::Credentials, traits::IntoCollector,
|
||||||
|
};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
@ -18,6 +20,7 @@ use crate::{
|
||||||
drivers::{
|
drivers::{
|
||||||
image_metadata::ImageMetadata,
|
image_metadata::ImageMetadata,
|
||||||
opts::{RunOptsEnv, RunOptsVolume},
|
opts::{RunOptsEnv, RunOptsVolume},
|
||||||
|
types::Platform,
|
||||||
},
|
},
|
||||||
logging::{CommandLogging, Logger},
|
logging::{CommandLogging, Logger},
|
||||||
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
|
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
|
||||||
|
|
@ -72,6 +75,10 @@ impl BuildDriver for PodmanDriver {
|
||||||
let command = cmd!(
|
let command = cmd!(
|
||||||
"podman",
|
"podman",
|
||||||
"build",
|
"build",
|
||||||
|
if !matches!(opts.platform, Platform::Native) => [
|
||||||
|
"--platform",
|
||||||
|
opts.platform.to_string(),
|
||||||
|
],
|
||||||
"--pull=true",
|
"--pull=true",
|
||||||
format!("--layers={}", !opts.squash),
|
format!("--layers={}", !opts.squash),
|
||||||
"-f",
|
"-f",
|
||||||
|
|
@ -198,10 +205,16 @@ impl InspectDriver for PodmanDriver {
|
||||||
);
|
);
|
||||||
progress.enable_steady_tick(Duration::from_millis(100));
|
progress.enable_steady_tick(Duration::from_millis(100));
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
if !matches!(opts.platform, Platform::Native) {
|
||||||
|
args.extend(["--override-arch", opts.platform.arch()]);
|
||||||
|
}
|
||||||
|
args.extend(["inspect", &url]);
|
||||||
|
|
||||||
let output = Self::run_output(
|
let output = Self::run_output(
|
||||||
&RunOpts::builder()
|
&RunOpts::builder()
|
||||||
.image(SKOPEO_IMAGE)
|
.image(SKOPEO_IMAGE)
|
||||||
.args(bon::vec!["inspect", &url])
|
.args(args.collect_into_vec())
|
||||||
.remove(true)
|
.remove(true)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use miette::{bail, IntoDiagnostic, Result};
|
use miette::{bail, IntoDiagnostic, Result};
|
||||||
|
|
||||||
use crate::logging::Logger;
|
use crate::{drivers::types::Platform, logging::Logger};
|
||||||
|
|
||||||
use super::{image_metadata::ImageMetadata, opts::GetMetadataOpts, InspectDriver};
|
use super::{image_metadata::ImageMetadata, opts::GetMetadataOpts, InspectDriver};
|
||||||
|
|
||||||
|
|
@ -29,11 +29,19 @@ impl InspectDriver for SkopeoDriver {
|
||||||
);
|
);
|
||||||
progress.enable_steady_tick(Duration::from_millis(100));
|
progress.enable_steady_tick(Duration::from_millis(100));
|
||||||
|
|
||||||
trace!("skopeo inspect {url}");
|
let mut command = cmd!(
|
||||||
let output = cmd!("skopeo", "inspect", &url)
|
"skopeo",
|
||||||
.stderr(Stdio::inherit())
|
if !matches!(opts.platform, Platform::Native) => [
|
||||||
.output()
|
"--override-arch",
|
||||||
.into_diagnostic()?;
|
opts.platform.arch(),
|
||||||
|
],
|
||||||
|
"inspect",
|
||||||
|
&url,
|
||||||
|
stderr = Stdio::inherit(),
|
||||||
|
);
|
||||||
|
trace!("{command:?}");
|
||||||
|
|
||||||
|
let output = command.output().into_diagnostic()?;
|
||||||
|
|
||||||
progress.finish_and_clear();
|
progress.finish_and_clear();
|
||||||
Logger::multi_progress().remove(&progress);
|
Logger::multi_progress().remove(&progress);
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ pub trait BuildDriver {
|
||||||
let build_opts = BuildOpts::builder()
|
let build_opts = BuildOpts::builder()
|
||||||
.image(&full_image)
|
.image(&full_image)
|
||||||
.containerfile(opts.containerfile.as_ref())
|
.containerfile(opts.containerfile.as_ref())
|
||||||
|
.platform(opts.platform)
|
||||||
.squash(opts.squash)
|
.squash(opts.squash)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
@ -212,10 +213,12 @@ pub trait SigningDriver {
|
||||||
.map_or_else(|| PathBuf::from("."), |d| d.to_path_buf());
|
.map_or_else(|| PathBuf::from("."), |d| d.to_path_buf());
|
||||||
|
|
||||||
let image_name: &str = opts.image.as_ref();
|
let image_name: &str = opts.image.as_ref();
|
||||||
let inspect_opts = GetMetadataOpts::builder().image(image_name);
|
let inspect_opts = GetMetadataOpts::builder()
|
||||||
|
.image(image_name)
|
||||||
|
.platform(opts.platform);
|
||||||
|
|
||||||
let inspect_opts = if let Some(ref tag) = opts.tag {
|
let inspect_opts = if let Some(ref tag) = opts.tag {
|
||||||
inspect_opts.tag(tag.as_ref() as &str).build()
|
inspect_opts.tag(&**tag).build()
|
||||||
} else {
|
} else {
|
||||||
inspect_opts.build()
|
inspect_opts.build()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -167,3 +167,41 @@ impl DetermineDriver<CiDriverType> for Option<CiDriverType> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, ValueEnum)]
|
||||||
|
pub enum Platform {
|
||||||
|
#[default]
|
||||||
|
#[value(name = "native")]
|
||||||
|
Native,
|
||||||
|
#[value(name = "linux/amd64")]
|
||||||
|
LinuxAmd64,
|
||||||
|
|
||||||
|
#[value(name = "linux/arm64")]
|
||||||
|
LinuxArm64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform {
|
||||||
|
/// The architecture of the platform.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn arch(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
Self::Native => "native",
|
||||||
|
Self::LinuxAmd64 => "amd64",
|
||||||
|
Self::LinuxArm64 => "arm64",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Platform {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match *self {
|
||||||
|
Self::Native => "native",
|
||||||
|
Self::LinuxAmd64 => "linux/amd64",
|
||||||
|
Self::LinuxArm64 => "linux/arm64",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ color_string() {
|
||||||
|
|
||||||
# Parse OS version and export it
|
# Parse OS version and export it
|
||||||
export OS_VERSION=$(grep -Po "(?<=VERSION_ID=)\d+" /usr/lib/os-release)
|
export OS_VERSION=$(grep -Po "(?<=VERSION_ID=)\d+" /usr/lib/os-release)
|
||||||
|
export OS_ARCH=$(uname -m)
|
||||||
|
|
||||||
# Export functions for use in sub-shells or sourced scripts
|
# Export functions for use in sub-shells or sourced scripts
|
||||||
export -f get_yaml_array
|
export -f get_yaml_array
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use blue_build_process_management::{
|
||||||
BuildTagPushOpts, CheckKeyPairOpts, CompressionType, GenerateImageNameOpts,
|
BuildTagPushOpts, CheckKeyPairOpts, CompressionType, GenerateImageNameOpts,
|
||||||
GenerateTagsOpts, SignVerifyOpts,
|
GenerateTagsOpts, SignVerifyOpts,
|
||||||
},
|
},
|
||||||
|
types::Platform,
|
||||||
BuildDriver, CiDriver, Driver, DriverArgs, SigningDriver,
|
BuildDriver, CiDriver, Driver, DriverArgs, SigningDriver,
|
||||||
},
|
},
|
||||||
logging::{color_str, gen_random_ansi_color},
|
logging::{color_str, gen_random_ansi_color},
|
||||||
|
|
@ -58,6 +59,16 @@ pub struct BuildCommand {
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
push: bool,
|
push: bool,
|
||||||
|
|
||||||
|
/// Build for a specific platform.
|
||||||
|
///
|
||||||
|
/// NOTE: Building for a different architecture
|
||||||
|
/// than your hardware will require installing
|
||||||
|
/// qemu. Build times will be much greater when
|
||||||
|
/// building for a non-native architecture.
|
||||||
|
#[arg(long, default_value = "native")]
|
||||||
|
#[builder(default)]
|
||||||
|
platform: Platform,
|
||||||
|
|
||||||
/// The compression format the images
|
/// The compression format the images
|
||||||
/// will be pushed in.
|
/// will be pushed in.
|
||||||
#[arg(short, long, default_value_t = CompressionType::Gzip)]
|
#[arg(short, long, default_value_t = CompressionType::Gzip)]
|
||||||
|
|
@ -260,7 +271,8 @@ impl BuildCommand {
|
||||||
let tags = Driver::generate_tags(
|
let tags = Driver::generate_tags(
|
||||||
&GenerateTagsOpts::builder()
|
&GenerateTagsOpts::builder()
|
||||||
.oci_ref(&recipe.base_image_ref()?)
|
.oci_ref(&recipe.base_image_ref()?)
|
||||||
.maybe_alt_tags(recipe.alt_tags.as_ref().map(CowCollecter::to_cow_vec))
|
.maybe_alt_tags(recipe.alt_tags.as_ref().map(CowCollecter::collect_cow_vec))
|
||||||
|
.platform(self.platform)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
let image_name = self.image_name(&recipe)?;
|
let image_name = self.image_name(&recipe)?;
|
||||||
|
|
@ -268,6 +280,7 @@ impl BuildCommand {
|
||||||
let opts = if let Some(archive_dir) = self.archive.as_ref() {
|
let opts = if let Some(archive_dir) = self.archive.as_ref() {
|
||||||
BuildTagPushOpts::builder()
|
BuildTagPushOpts::builder()
|
||||||
.containerfile(containerfile)
|
.containerfile(containerfile)
|
||||||
|
.platform(self.platform)
|
||||||
.archive_path(format!(
|
.archive_path(format!(
|
||||||
"{}/{}.{ARCHIVE_SUFFIX}",
|
"{}/{}.{ARCHIVE_SUFFIX}",
|
||||||
archive_dir.to_string_lossy().trim_end_matches('/'),
|
archive_dir.to_string_lossy().trim_end_matches('/'),
|
||||||
|
|
@ -279,7 +292,8 @@ impl BuildCommand {
|
||||||
BuildTagPushOpts::builder()
|
BuildTagPushOpts::builder()
|
||||||
.image(&image_name)
|
.image(&image_name)
|
||||||
.containerfile(containerfile)
|
.containerfile(containerfile)
|
||||||
.tags(tags.to_cow_vec())
|
.platform(self.platform)
|
||||||
|
.tags(tags.collect_cow_vec())
|
||||||
.push(self.push)
|
.push(self.push)
|
||||||
.retry_push(self.retry_push)
|
.retry_push(self.retry_push)
|
||||||
.retry_count(self.retry_count)
|
.retry_count(self.retry_count)
|
||||||
|
|
@ -297,6 +311,7 @@ impl BuildCommand {
|
||||||
.retry_push(self.retry_push)
|
.retry_push(self.retry_push)
|
||||||
.retry_count(self.retry_count)
|
.retry_count(self.retry_count)
|
||||||
.maybe_tag(tags.first())
|
.maybe_tag(tags.first())
|
||||||
|
.platform(self.platform)
|
||||||
.build(),
|
.build(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use blue_build_process_management::drivers::{CiDriver, Driver, DriverArgs};
|
use blue_build_process_management::drivers::{types::Platform, CiDriver, Driver, DriverArgs};
|
||||||
use blue_build_recipe::Recipe;
|
use blue_build_recipe::Recipe;
|
||||||
use blue_build_template::{ContainerFileTemplate, Template};
|
use blue_build_template::{ContainerFileTemplate, Template};
|
||||||
use blue_build_utils::{
|
use blue_build_utils::{
|
||||||
|
|
@ -63,6 +63,12 @@ pub struct GenerateCommand {
|
||||||
#[arg(short = 't', long)]
|
#[arg(short = 't', long)]
|
||||||
syntax_theme: Option<DefaultThemes>,
|
syntax_theme: Option<DefaultThemes>,
|
||||||
|
|
||||||
|
/// Inspect the image for a specific platform
|
||||||
|
/// when retrieving the version.
|
||||||
|
#[arg(long, default_value = "native")]
|
||||||
|
#[builder(default)]
|
||||||
|
platform: Platform,
|
||||||
|
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
drivers: DriverArgs,
|
drivers: DriverArgs,
|
||||||
|
|
@ -115,7 +121,12 @@ impl GenerateCommand {
|
||||||
info!("Templating for recipe at {}", recipe_path.display());
|
info!("Templating for recipe at {}", recipe_path.display());
|
||||||
|
|
||||||
let template = ContainerFileTemplate::builder()
|
let template = ContainerFileTemplate::builder()
|
||||||
.os_version(Driver::get_os_version(&recipe.base_image_ref()?)?)
|
.os_version(
|
||||||
|
Driver::get_os_version()
|
||||||
|
.oci_ref(&recipe.base_image_ref()?)
|
||||||
|
.platform(self.platform)
|
||||||
|
.call()?,
|
||||||
|
)
|
||||||
.build_id(Driver::get_build_id())
|
.build_id(Driver::get_build_id())
|
||||||
.recipe(&recipe)
|
.recipe(&recipe)
|
||||||
.recipe_path(recipe_path.as_path())
|
.recipe_path(recipe_path.as_path())
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,10 @@ impl GenerateIsoCommand {
|
||||||
format!("IMAGE_NAME={image_name}",),
|
format!("IMAGE_NAME={image_name}",),
|
||||||
format!("IMAGE_REPO={image_repo}"),
|
format!("IMAGE_REPO={image_repo}"),
|
||||||
format!("IMAGE_TAG={}", image.tag().unwrap_or("latest")),
|
format!("IMAGE_TAG={}", image.tag().unwrap_or("latest")),
|
||||||
format!("VERSION={}", Driver::get_os_version(&image)?),
|
format!(
|
||||||
|
"VERSION={}",
|
||||||
|
Driver::get_os_version().oci_ref(&image).call()?
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
GenIsoSubcommand::Recipe { recipe } => {
|
GenIsoSubcommand::Recipe { recipe } => {
|
||||||
|
|
@ -216,7 +219,9 @@ impl GenerateIsoCommand {
|
||||||
),
|
),
|
||||||
format!(
|
format!(
|
||||||
"VERSION={}",
|
"VERSION={}",
|
||||||
Driver::get_os_version(&recipe.base_image_ref()?)?,
|
Driver::get_os_version()
|
||||||
|
.oci_ref(&recipe.base_image_ref()?)
|
||||||
|
.call()?,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
vols.extend(run_volumes![
|
vols.extend(run_volumes![
|
||||||
|
|
@ -230,7 +235,7 @@ impl GenerateIsoCommand {
|
||||||
.image("ghcr.io/jasonn3/build-container-installer")
|
.image("ghcr.io/jasonn3/build-container-installer")
|
||||||
.privileged(true)
|
.privileged(true)
|
||||||
.remove(true)
|
.remove(true)
|
||||||
.args(args.to_cow_vec())
|
.args(args.collect_cow_vec())
|
||||||
.volumes(vols)
|
.volumes(vols)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,8 @@
|
||||||
use std::{
|
use std::{borrow::Cow, ffi::OsStr, path::Path};
|
||||||
borrow::Cow,
|
|
||||||
ffi::{OsStr, OsString},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
trait PrivateTrait<T: ToOwned + ?Sized> {}
|
trait PrivateTrait<T: ?Sized>: IntoIterator {}
|
||||||
|
|
||||||
macro_rules! impl_private_trait {
|
impl<T, R> PrivateTrait<R> for T where T: IntoIterator {}
|
||||||
($lt:lifetime, $type:ty) => {
|
|
||||||
impl<$lt, T> PrivateTrait<$type> for T where T: AsRef<[&$lt $type]> {}
|
|
||||||
};
|
|
||||||
($type:ty) => {
|
|
||||||
impl<T> PrivateTrait<$type> for T where T: AsRef<[$type]> {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_private_trait!(String);
|
|
||||||
impl_private_trait!('a, str);
|
|
||||||
impl_private_trait!(PathBuf);
|
|
||||||
impl_private_trait!('a, Path);
|
|
||||||
impl_private_trait!(OsString);
|
|
||||||
impl_private_trait!('a, OsStr);
|
|
||||||
|
|
||||||
#[allow(private_bounds)]
|
#[allow(private_bounds)]
|
||||||
pub trait CowCollecter<'a, IN, OUT>: PrivateTrait<IN>
|
pub trait CowCollecter<'a, IN, OUT>: PrivateTrait<IN>
|
||||||
|
|
@ -28,35 +10,91 @@ where
|
||||||
IN: ToOwned + ?Sized,
|
IN: ToOwned + ?Sized,
|
||||||
OUT: ToOwned + ?Sized,
|
OUT: ToOwned + ?Sized,
|
||||||
{
|
{
|
||||||
fn to_cow_vec(&'a self) -> Vec<Cow<'a, OUT>>;
|
fn collect_cow_vec(&'a self) -> Vec<Cow<'a, OUT>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, R> CowCollecter<'a, R, R> for T
|
||||||
|
where
|
||||||
|
T: AsRef<[R]> + IntoIterator,
|
||||||
|
R: ToOwned,
|
||||||
|
{
|
||||||
|
fn collect_cow_vec(&'a self) -> Vec<Cow<'a, R>> {
|
||||||
|
self.as_ref().iter().map(Cow::Borrowed).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_cow_collector {
|
macro_rules! impl_cow_collector {
|
||||||
($type:ty) => {
|
($type:ty) => {
|
||||||
impl<'a, T> CowCollecter<'a, $type, $type> for T
|
impl<'a, T, R> CowCollecter<'a, R, $type> for T
|
||||||
where
|
where
|
||||||
T: AsRef<[&'a $type]>,
|
T: AsRef<[R]> + IntoIterator,
|
||||||
|
R: AsRef<$type> + ToOwned + 'a,
|
||||||
{
|
{
|
||||||
fn to_cow_vec(&'a self) -> Vec<Cow<'a, $type>> {
|
fn collect_cow_vec(&'a self) -> Vec<Cow<'a, $type>> {
|
||||||
self.as_ref().iter().map(|v| Cow::Borrowed(*v)).collect()
|
self.as_ref()
|
||||||
}
|
.iter()
|
||||||
}
|
.map(|v| v.as_ref())
|
||||||
};
|
.map(Cow::from)
|
||||||
($in:ty, $out:ty) => {
|
.collect()
|
||||||
impl<'a, T> CowCollecter<'a, $in, $out> for T
|
|
||||||
where
|
|
||||||
T: AsRef<[$in]>,
|
|
||||||
{
|
|
||||||
fn to_cow_vec(&'a self) -> Vec<Cow<'a, $out>> {
|
|
||||||
self.as_ref().iter().map(Cow::from).collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_cow_collector!(String, str);
|
|
||||||
impl_cow_collector!(str);
|
impl_cow_collector!(str);
|
||||||
impl_cow_collector!(PathBuf, Path);
|
|
||||||
impl_cow_collector!(Path);
|
impl_cow_collector!(Path);
|
||||||
impl_cow_collector!(OsString, OsStr);
|
|
||||||
impl_cow_collector!(OsStr);
|
impl_cow_collector!(OsStr);
|
||||||
|
|
||||||
|
#[allow(private_bounds)]
|
||||||
|
pub trait AsRefCollector<'a, IN, OUT>: PrivateTrait<IN>
|
||||||
|
where
|
||||||
|
IN: ?Sized,
|
||||||
|
OUT: ?Sized,
|
||||||
|
{
|
||||||
|
fn collect_as_ref_vec(&'a self) -> Vec<&'a OUT>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, R> AsRefCollector<'a, R, R> for T
|
||||||
|
where
|
||||||
|
T: AsRef<[R]> + IntoIterator,
|
||||||
|
{
|
||||||
|
fn collect_as_ref_vec(&'a self) -> Vec<&'a R> {
|
||||||
|
self.as_ref().iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_asref_collector {
|
||||||
|
($type:ty) => {
|
||||||
|
impl<'a, T, R> AsRefCollector<'a, R, $type> for T
|
||||||
|
where
|
||||||
|
T: AsRef<[R]> + IntoIterator,
|
||||||
|
R: AsRef<$type> + 'a,
|
||||||
|
{
|
||||||
|
fn collect_as_ref_vec(&'a self) -> Vec<&'a $type> {
|
||||||
|
self.as_ref().iter().map(AsRef::as_ref).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_asref_collector!(str);
|
||||||
|
impl_asref_collector!(Path);
|
||||||
|
impl_asref_collector!(OsStr);
|
||||||
|
|
||||||
|
#[allow(private_bounds)]
|
||||||
|
pub trait IntoCollector<IN, OUT>: PrivateTrait<IN>
|
||||||
|
where
|
||||||
|
IN: Into<OUT>,
|
||||||
|
{
|
||||||
|
fn collect_into_vec(self) -> Vec<OUT>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U, R> IntoCollector<U, R> for T
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = U>,
|
||||||
|
U: Into<R>,
|
||||||
|
{
|
||||||
|
fn collect_into_vec(self) -> Vec<R> {
|
||||||
|
self.into_iter().map(Into::into).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue