├── .gitignore ├── .dockerignore ├── examples ├── workspace │ ├── binary-2 │ │ ├── src │ │ │ └── main.rs │ │ └── Cargo.toml │ ├── lib-1 │ │ ├── build.rs │ │ ├── src │ │ │ └── lib.rs │ │ └── Cargo.toml │ ├── binary-1 │ │ ├── src │ │ │ └── main.rs │ │ └── Cargo.toml │ ├── Cargo.toml │ └── Cargo.lock ├── multi-bin │ ├── src │ │ └── bin │ │ │ ├── bin-1.rs │ │ │ └── bin-2.rs │ ├── build.rs │ ├── Cargo.toml │ └── Cargo.lock └── single-bin │ ├── Cargo.lock │ ├── src │ └── main.rs │ └── Cargo.toml ├── cargo-container-tools ├── src │ ├── lib.rs │ ├── metadata.rs │ ├── env.rs │ ├── bin │ │ ├── cargo-test-runner │ │ │ └── main.rs │ │ ├── cargo-buildscript-apply │ │ │ └── main.rs │ │ ├── cargo-metadata-collector │ │ │ └── main.rs │ │ ├── cargo-buildscript-capture │ │ │ └── main.rs │ │ └── cargo-build-plan │ │ │ └── main.rs │ └── output.rs ├── README.md ├── Cargo.toml └── CHANGELOG.md ├── tests └── integration │ ├── helpers │ ├── registry.bash │ └── images.bash │ ├── debug.bats │ ├── example-multi-bin.bats │ ├── metadata.bats │ ├── example-single-bin.bats │ ├── pretty-output.bats │ ├── example-workspace.bats │ └── features.bats ├── .ci └── install-buildx.sh ├── Cargo.toml ├── cargo-wharf-frontend ├── src │ ├── main.rs │ ├── query │ │ ├── profile.rs │ │ ├── print.rs │ │ ├── source.rs │ │ ├── mod.rs │ │ ├── serialization.rs │ │ └── terminal.rs │ ├── shared.rs │ ├── graph │ │ ├── mod.rs │ │ ├── ops.rs │ │ └── node.rs │ ├── frontend.rs │ ├── debug.rs │ ├── plan.rs │ └── config │ │ ├── builder.rs │ │ ├── mod.rs │ │ └── output.rs ├── build.rs ├── CHANGELOG.md ├── Cargo.toml ├── tests │ ├── merged-buildscript-producers.json │ ├── merged-buildscript-consumers.json │ └── build-plan.json └── README.md ├── .github └── workflows │ ├── publish-frontend.yml │ ├── publish-tools.yml │ ├── docker-hub-description.yml │ └── ci.yml ├── LICENSE-MIT ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | debug-output 3 | tests/integration/.cache 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | debug-output 3 | examples 4 | tests/integration/** 5 | 6 | Makefile 7 | .dockerignore 8 | -------------------------------------------------------------------------------- /examples/workspace/binary-2/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | lib_1::hello(); 3 | 4 | println!("Hello from binary 2"); 5 | } 6 | -------------------------------------------------------------------------------- /examples/multi-bin/src/bin/bin-1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello from the bin-1!"); 3 | } 4 | 5 | #[test] 6 | fn bin_1_test_case() { 7 | assert!(true); 8 | } 9 | -------------------------------------------------------------------------------- /examples/multi-bin/src/bin/bin-2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello from the bin-2!"); 3 | } 4 | 5 | #[test] 6 | fn bin_2_test_case() { 7 | assert!(true); 8 | } 9 | -------------------------------------------------------------------------------- /examples/workspace/lib-1/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-env=LIB_MSG=Hello from build script"); 3 | println!("cargo:rustc-cfg=lib_msg"); 4 | } 5 | -------------------------------------------------------------------------------- /examples/single-bin/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "single-bin" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /examples/workspace/lib-1/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(lib_msg)] 2 | pub fn hello() { 3 | println!(env!("LIB_MSG")); 4 | } 5 | 6 | #[test] 7 | fn faulty_test() { 8 | assert!(false, "this should fail"); 9 | } 10 | -------------------------------------------------------------------------------- /examples/workspace/binary-1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | lib_1::hello(); 3 | 4 | println!("Hello from binary 1"); 5 | 6 | #[cfg(feature = "the-special-feature")] 7 | println!("the-special-feature is on"); 8 | } 9 | -------------------------------------------------------------------------------- /cargo-container-tools/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | #![deny(clippy::all)] 3 | 4 | mod env; 5 | pub use self::env::RuntimeEnv; 6 | 7 | mod output; 8 | pub use self::output::BuildScriptOutput; 9 | 10 | pub mod metadata; 11 | -------------------------------------------------------------------------------- /tests/integration/helpers/registry.bash: -------------------------------------------------------------------------------- 1 | function install_registry { 2 | docker run -d -p 10395:5000 --restart=always --name cargo-wharf-integration-tests-registry registry:2 3 | sleep 2 4 | } 5 | 6 | function remove_registry { 7 | docker rm -f cargo-wharf-integration-tests-registry 8 | } 9 | -------------------------------------------------------------------------------- /.ci/install-buildx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p ~/.docker/cli-plugins 4 | curl -L https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx 5 | chmod a+x ~/.docker/cli-plugins/docker-buildx 6 | 7 | docker buildx create --driver-opt network=host --name ci-builder --use 8 | -------------------------------------------------------------------------------- /examples/workspace/lib-1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib-1" 3 | version = "0.1.0" 4 | authors = ["Denys Zariaiev "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | lazy_static = "=1.1" 9 | lexical-core = "0.4" 10 | 11 | [dependencies.log] 12 | git = "https://github.com/rust-lang-nursery/log.git" 13 | -------------------------------------------------------------------------------- /examples/workspace/binary-1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "binary-1" 3 | version = "0.1.0" 4 | authors = ["Denys Zariaiev "] 5 | edition = "2018" 6 | 7 | [features] 8 | the-special-feature = [] 9 | 10 | [dependencies] 11 | clap = "2.33" 12 | 13 | [dependencies.lib-1] 14 | path = "../lib-1" 15 | 16 | [[package.metadata.wharf.binary]] 17 | name = "binary-1" 18 | destination = "/bin/binary-1" 19 | -------------------------------------------------------------------------------- /examples/workspace/binary-2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "binary-2" 3 | version = "0.1.0" 4 | authors = ["Denys Zariaiev "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | nix = { git = "ssh://git@github.com/nix-rust/nix.git" } 9 | 10 | [dependencies.lib-1] 11 | path = "../lib-1" 12 | 13 | [[package.metadata.wharf.binary]] 14 | name = "binary-2" 15 | destination = "/usr/local/bin/binary-2" 16 | -------------------------------------------------------------------------------- /examples/multi-bin/build.rs: -------------------------------------------------------------------------------- 1 | use std::fs::metadata; 2 | 3 | fn main() { 4 | // Check if build dependencies were installed. Used for the integration testing. 5 | 6 | metadata("/usr/bin/protoc") 7 | .expect("Unable to find `/usr/bin/protoc`. Custom builder setup failed!"); 8 | 9 | metadata("/custom-output") 10 | .expect("Unable to find `/tmp/custom-output`. Custom builder setup failed!"); 11 | } 12 | -------------------------------------------------------------------------------- /examples/single-bin/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::env::{args, vars}; 3 | 4 | fn main() { 5 | println!("Hello from the container!"); 6 | println!("Args: {:?}", args().collect::>()); 7 | println!("Env: {:#?}", vars().collect::>()); 8 | 9 | #[cfg(feature = "feature-1")] 10 | println!("feature-1 is on"); 11 | 12 | #[cfg(feature = "feature-2")] 13 | println!("feature-2 is on"); 14 | } 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "cargo-container-tools", 4 | "cargo-wharf-frontend", 5 | ] 6 | 7 | exclude = [ 8 | "examples/workspace", 9 | "examples/single-bin", 10 | "examples/multi-bin", 11 | ] 12 | 13 | [profile.release] 14 | lto = true 15 | 16 | # [patch.crates-io] 17 | # buildkit-frontend = { path = "../rust-buildkit/buildkit-frontend" } 18 | # buildkit-llb = { path = "../rust-buildkit/buildkit-llb" } 19 | # buildkit-proto = { path = "../rust-buildkit/buildkit-proto" } 20 | -------------------------------------------------------------------------------- /examples/workspace/Cargo.toml: -------------------------------------------------------------------------------- 1 | # syntax = localhost:10395/denzp/cargo-wharf-frontend:local 2 | 3 | [workspace] 4 | members = [ 5 | "binary-1", 6 | "binary-2", 7 | "lib-1", 8 | ] 9 | 10 | [workspace.metadata.wharf.builder] 11 | image = "rust" 12 | 13 | [workspace.metadata.wharf.output] 14 | image = "debian:stable-slim" 15 | workdir = "/root" 16 | user = "root" 17 | pre-install-commands = [ 18 | { shell = "echo 'pre-install' > /custom-setup", display = "My custom pre-install command" }, 19 | ] 20 | post-install-commands = [ 21 | { shell = "echo 'post-install' > /custom-post-setup", display = "My custom post-install command" }, 22 | ] 23 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all)] 2 | #![deny(warnings)] 3 | 4 | use env_logger::Env; 5 | use log::*; 6 | 7 | use buildkit_frontend::run_frontend; 8 | 9 | mod config; 10 | mod debug; 11 | mod frontend; 12 | mod graph; 13 | mod plan; 14 | mod query; 15 | mod shared; 16 | 17 | use self::frontend::CargoFrontend; 18 | 19 | #[tokio::main] 20 | async fn main() { 21 | env_logger::init_from_env(Env::default().filter_or("RUST_LOG", "info")); 22 | 23 | if let Err(error) = run_frontend(CargoFrontend).await { 24 | error!("{}", error); 25 | 26 | for cause in error.iter_causes() { 27 | error!(" caused by: {}", cause); 28 | } 29 | 30 | std::process::exit(1); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cargo-container-tools/src/metadata.rs: -------------------------------------------------------------------------------- 1 | use semver::Version; 2 | use serde::{Deserialize, Serialize}; 3 | use toml::value::Value; 4 | 5 | #[derive(Deserialize, Serialize, Clone, Debug)] 6 | #[serde(rename_all = "kebab-case")] 7 | #[serde(tag = "type")] 8 | pub enum Origin { 9 | WorkspaceRoot, 10 | Package { name: String, version: Version }, 11 | } 12 | 13 | #[derive(Deserialize, Serialize, Clone, Debug)] 14 | pub struct Metadata { 15 | pub origin: Origin, 16 | pub metadata: Option, 17 | } 18 | 19 | pub mod manifest { 20 | use super::*; 21 | 22 | #[derive(Deserialize, Clone, Debug)] 23 | pub struct Root { 24 | pub workspace: Option, 25 | } 26 | 27 | #[derive(Deserialize, Clone, Debug)] 28 | pub struct Workspace { 29 | pub metadata: Option, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!( 3 | "cargo:rustc-env=CONTAINER_TOOLS_REF={}", 4 | get_container_tools_ref() 5 | ); 6 | } 7 | 8 | cfg_if::cfg_if! { 9 | if #[cfg(feature = "container-tools-testing")] { 10 | fn get_container_tools_ref() -> &'static str { 11 | "localhost:10395/denzp/cargo-container-tools:local" 12 | } 13 | } else if #[cfg(feature = "container-tools-local")] { 14 | fn get_container_tools_ref() -> &'static str { 15 | "denzp/cargo-container-tools:local" 16 | } 17 | } else if #[cfg(feature = "container-tools-master")] { 18 | fn get_container_tools_ref() -> &'static str { 19 | "denzp/cargo-container-tools:master" 20 | } 21 | } else { 22 | fn get_container_tools_ref() -> &'static str { 23 | "denzp/cargo-container-tools:v0.2.0-alpha.1" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/multi-bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | # syntax = localhost:10395/denzp/cargo-wharf-frontend:local 2 | 3 | [package] 4 | name = "multi-bin" 5 | version = "0.1.0" 6 | authors = ["Denys Zariaiev "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | openssl-sys = "0.9" 11 | 12 | [build-dependencies] 13 | openssl-sys = "0.9" 14 | 15 | [package.metadata.wharf.builder] 16 | image = "clux/muslrust:nightly-2019-09-28" 17 | target = "x86_64-unknown-linux-musl" 18 | setup-commands = [ 19 | { command = ["apt-get", "update"], display = "Update apt-get cache" }, 20 | { command = ["apt-get", "install", "-y", "protobuf-compiler"] }, 21 | { shell = "echo '' > /custom-output" }, 22 | ] 23 | 24 | [package.metadata.wharf.output] 25 | image = "scratch" 26 | workdir = "/" 27 | 28 | [[package.metadata.wharf.binary]] 29 | name = "bin-1" 30 | destination = "/bin/bin-1" 31 | 32 | [[package.metadata.wharf.binary]] 33 | name = "bin-2" 34 | destination = "/bin/bin-2" 35 | -------------------------------------------------------------------------------- /cargo-container-tools/README.md: -------------------------------------------------------------------------------- 1 | # cargo-container-tools 2 | 3 | Container helpers collection, primarily to be used by `cargo-wharf-frontend`. 4 | All of the executables are statically linked. 5 | 6 | ## Build plan collector 7 | Path: `/usr/local/bin/cargo-build-plan` 8 | 9 | Collects build plan of the crate or workspace as a JSON. 10 | 11 | ## Crate metadata collector 12 | Path: `/usr/local/bin/cargo-metadata-collector` 13 | 14 | Collects `[package.metadata]` and *(unofficial)* `[workspace.metadata]` of the crate or workspace as a JSON. 15 | 16 | ## Test runner 17 | Path: `/usr/local/bin/cargo-test-runner` 18 | 19 | Runs tests just like `cargo test`. 20 | 21 | ## Build script output capture 22 | Path: `/usr/local/bin/cargo-buildscript-capture` 23 | 24 | Runs and captures build script output for further usage. 25 | 26 | ## Build script output apply 27 | Path: `/usr/local/bin/cargo-buildscript-apply` 28 | 29 | Applies a captured build script output into subsequent `rustc` calls. 30 | -------------------------------------------------------------------------------- /examples/single-bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | # syntax = localhost:10395/denzp/cargo-wharf-frontend:local 2 | 3 | [package] 4 | name = "single-bin" 5 | version = "0.1.0" 6 | authors = ["Denys Zariaiev "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | 11 | [features] 12 | default = ["feature-1"] 13 | feature-1 = [] 14 | feature-2 = [] 15 | 16 | [package.metadata.wharf.builder] 17 | image = "clux/muslrust:nightly-2019-09-28" 18 | target = "x86_64-unknown-linux-musl" 19 | 20 | [package.metadata.wharf.output] 21 | image = "scratch" 22 | workdir = "/" 23 | entrypoint = ["/bin/wharf-output"] 24 | args = ["predefined arg"] 25 | expose = ["3500/tcp", "3600/udp", "3700"] 26 | volumes = ["/local", "/data"] 27 | stop-signal = "SIGINT" 28 | 29 | [package.metadata.wharf.output.env] 30 | "NAME 1" = "VALUE 1" 31 | 32 | [package.metadata.wharf.output.labels] 33 | "simple-label" = "simple value" 34 | "my.awesome.label" = "another value" 35 | 36 | [[package.metadata.wharf.binary]] 37 | name = "single-bin" 38 | destination = "/bin/wharf-output" 39 | -------------------------------------------------------------------------------- /.github/workflows/publish-frontend.yml: -------------------------------------------------------------------------------- 1 | name: Publish the cargo-wharf-frontend 2 | on: 3 | push: 4 | tags: 5 | - cargo-wharf-frontend-* 6 | 7 | jobs: 8 | publish-requested-tag: 9 | name: Publish denzp/cargo-wharf-frontend 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout sources 13 | uses: actions/checkout@v1 14 | 15 | - name: Print docker info 16 | run: docker info 17 | 18 | - name: Login to Docker Hub Registry 19 | run: docker login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD 20 | env: 21 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 22 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 23 | 24 | - name: Installing buildx 25 | run: .ci/install-buildx.sh 26 | 27 | - name: Build and push the image 28 | env: 29 | TAG: ${{ github.ref }} 30 | run: >- 31 | docker buildx build 32 | --push -f cargo-wharf-frontend/Cargo.toml . 33 | --tag denzp/cargo-wharf-frontend:${TAG#"refs/tags/cargo-wharf-frontend-"} 34 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/query/profile.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use failure::{bail, Error}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | #[serde(try_from = "String")] 9 | pub enum Profile { 10 | ReleaseBinaries, 11 | DebugBinaries, 12 | 13 | ReleaseTests, 14 | DebugTests, 15 | } 16 | 17 | impl TryFrom for Profile { 18 | type Error = Error; 19 | 20 | fn try_from(value: String) -> Result { 21 | match value.as_str() { 22 | "release" | "release-binaries" => Ok(Profile::ReleaseBinaries), 23 | "debug" | "debug-binaries" => Ok(Profile::DebugBinaries), 24 | "test" | "release-test" => Ok(Profile::ReleaseTests), 25 | "debug-test" => Ok(Profile::DebugTests), 26 | 27 | other => bail!("Unknown mode: {}", other), 28 | } 29 | } 30 | } 31 | 32 | impl Default for Profile { 33 | fn default() -> Self { 34 | Profile::ReleaseBinaries 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/query/print.rs: -------------------------------------------------------------------------------- 1 | pub enum PrintKind { 2 | CustomCommand(S), 3 | CompileBuildScript(S), 4 | CompileBinary(S), 5 | CompileTest(S), 6 | CompileCrate(S), 7 | 8 | RunBuildScript(S), 9 | } 10 | 11 | pub trait PrettyPrintQuery { 12 | fn pretty_print(&self, kind: PrintKind) -> String 13 | where 14 | S: AsRef, 15 | { 16 | match kind { 17 | PrintKind::CustomCommand(display) => format!("Running `{}`", display.as_ref()), 18 | PrintKind::CompileBinary(name) => format!("Compiling binary {}", name.as_ref()), 19 | PrintKind::CompileTest(name) => format!("Compiling test {}", name.as_ref()), 20 | PrintKind::CompileCrate(name) => format!("Compiling {}", name.as_ref()), 21 | 22 | PrintKind::CompileBuildScript(name) => { 23 | format!("Compiling {} [build script]", name.as_ref()) 24 | } 25 | 26 | PrintKind::RunBuildScript(name) => { 27 | format!("Running {} [build script]", name.as_ref()) 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Denys Zariaiev 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /cargo-container-tools/src/env.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | 4 | use cargo::util::CargoResult; 5 | use failure::format_err; 6 | use lazy_static::*; 7 | 8 | lazy_static! { 9 | static ref OUT_DIR: Option = env::var("OUT_DIR").ok(); 10 | static ref CARGO_PKG_NAME: Option = env::var("CARGO_PKG_NAME").ok(); 11 | static ref CARGO_MANIFEST_LINKS: Option = env::var("CARGO_MANIFEST_LINKS").ok(); 12 | } 13 | 14 | pub struct RuntimeEnv; 15 | 16 | impl RuntimeEnv { 17 | pub fn output_dir() -> CargoResult<&'static Path> { 18 | OUT_DIR 19 | .as_ref() 20 | .map(|value| Path::new(value)) 21 | .ok_or_else(|| format_err!("unable to find OUT_DIR env variable")) 22 | } 23 | 24 | pub fn package_name() -> CargoResult<&'static str> { 25 | CARGO_PKG_NAME 26 | .as_ref() 27 | .map(|value| value.as_str()) 28 | .ok_or_else(|| format_err!("unable to find CARGO_PKG_NAME env variable")) 29 | } 30 | 31 | pub fn manifest_link_name() -> Option<&'static str> { 32 | CARGO_MANIFEST_LINKS.as_ref().map(|value| value.as_str()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/publish-tools.yml: -------------------------------------------------------------------------------- 1 | name: Publish the cargo-container-tools 2 | on: 3 | push: 4 | tags: 5 | - cargo-container-tools-* 6 | 7 | jobs: 8 | publish-requested-tag: 9 | name: Publish denzp/cargo-container-tools 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout sources 13 | uses: actions/checkout@v1 14 | 15 | - name: Print docker info 16 | run: docker info 17 | 18 | - name: Login to Docker Hub Registry 19 | run: docker login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD 20 | env: 21 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 22 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 23 | 24 | - name: Installing buildx 25 | run: .ci/install-buildx.sh 26 | 27 | - name: Build and push the image 28 | env: 29 | TAG: ${{ github.ref }} 30 | run: >- 31 | docker buildx build 32 | --push -f cargo-container-tools/Cargo.toml . 33 | --tag denzp/cargo-container-tools:${TAG#"refs/tags/cargo-container-tools-"} 34 | --cache-from type=registry,ref=denzp/cargo-container-tools:cache 35 | -------------------------------------------------------------------------------- /tests/integration/debug.bats: -------------------------------------------------------------------------------- 1 | load helpers/images 2 | load helpers/registry 3 | 4 | function setup() { 5 | install_registry 6 | maybe_build_container_tools_image 7 | maybe_build_frontend_image 8 | } 9 | 10 | function teardown() { 11 | remove_registry 12 | } 13 | 14 | @test "debug output" { 15 | rm -f $BATS_TMPDIR/*.json 16 | rm -f $BATS_TMPDIR/*.pb 17 | 18 | docker buildx build -f examples/single-bin/Cargo.toml examples/single-bin \ 19 | -o type=local,dest=$BATS_TMPDIR \ 20 | --build-arg debug=all 21 | 22 | [ -r $BATS_TMPDIR/build-graph.json ] 23 | [ -r $BATS_TMPDIR/build-plan.json ] 24 | [ -r $BATS_TMPDIR/config.json ] 25 | [ -r $BATS_TMPDIR/llb.pb ] 26 | } 27 | 28 | @test "specific debug output" { 29 | rm -f $BATS_TMPDIR/*.json 30 | rm -f $BATS_TMPDIR/*.pb 31 | 32 | docker buildx build -f examples/single-bin/Cargo.toml examples/single-bin \ 33 | -o type=local,dest=$BATS_TMPDIR \ 34 | --build-arg debug=build-plan,build-graph 35 | 36 | [ -r $BATS_TMPDIR/build-graph.json ] 37 | [ -r $BATS_TMPDIR/build-plan.json ] 38 | [ ! -r $BATS_TMPDIR/config.json ] 39 | [ ! -r $BATS_TMPDIR/llb.pb ] 40 | } 41 | -------------------------------------------------------------------------------- /tests/integration/example-multi-bin.bats: -------------------------------------------------------------------------------- 1 | load helpers/images 2 | load helpers/registry 3 | 4 | function setup() { 5 | install_registry 6 | maybe_build_container_tools_image 7 | maybe_build_frontend_image 8 | } 9 | 10 | function teardown() { 11 | remove_registry 12 | 13 | docker rmi -f cargo-wharf/example-multi-bin:test || true 14 | docker rmi -f cargo-wharf/example-multi-bin || true 15 | } 16 | 17 | @test "multi-bin :: binaries" { 18 | docker buildx build --load -f examples/multi-bin/Cargo.toml -t cargo-wharf/example-multi-bin examples/multi-bin 19 | 20 | run docker run --rm cargo-wharf/example-multi-bin /bin/bin-1 21 | [ "$status" -eq 0 ] 22 | [ "$output" = "Hello from the bin-1!" ] 23 | 24 | run docker run --rm cargo-wharf/example-multi-bin /bin/bin-2 25 | [ "$status" -eq 0 ] 26 | [ "$output" = "Hello from the bin-2!" ] 27 | } 28 | 29 | @test "multi-bin :: tests" { 30 | docker buildx build --load -f examples/multi-bin/Cargo.toml -t cargo-wharf/example-multi-bin:test examples/multi-bin --build-arg profile=test 31 | 32 | run docker run --rm cargo-wharf/example-multi-bin:test 33 | [ "$status" -eq 0 ] 34 | [[ "$output" == *"test bin_1_test_case ... ok"* ]] 35 | [[ "$output" == *"test bin_2_test_case ... ok"* ]] 36 | } 37 | -------------------------------------------------------------------------------- /tests/integration/metadata.bats: -------------------------------------------------------------------------------- 1 | load helpers/images 2 | load helpers/registry 3 | 4 | function setup() { 5 | install_registry 6 | maybe_build_container_tools_image 7 | maybe_build_frontend_image 8 | } 9 | 10 | function teardown() { 11 | remove_registry 12 | 13 | docker rmi -f cargo-wharf/example-single-bin || true 14 | } 15 | 16 | @test "labels" { 17 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin examples/single-bin 18 | 19 | run docker image inspect cargo-wharf/example-single-bin -f "{{.Config.Labels}}" 20 | [[ "$output" == "map[my.awesome.label:another value simple-label:simple value]" ]] 21 | } 22 | 23 | @test "volumes" { 24 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin examples/single-bin 25 | 26 | run docker image inspect cargo-wharf/example-single-bin -f "{{.Config.Volumes}}" 27 | [[ "$output" == "map[/data:{} /local:{}]" ]] 28 | } 29 | 30 | @test "exposed ports" { 31 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin examples/single-bin 32 | 33 | run docker image inspect cargo-wharf/example-single-bin -f "{{.Config.ExposedPorts}}" 34 | [[ "$output" == "map[3500/tcp:{} 3600/udp:{} 3700/tcp:{}]" ]] 35 | } 36 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/shared.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::*; 2 | 3 | use buildkit_llb::ops::source::{ImageSource, LocalSource}; 4 | use buildkit_llb::prelude::*; 5 | 6 | lazy_static! { 7 | pub static ref CONTEXT: LocalSource = { 8 | Source::local("context") 9 | .custom_name("Using build context") 10 | .add_exclude_pattern("**/target") 11 | }; 12 | pub static ref DOCKERFILE: LocalSource = { 13 | Source::local("dockerfile") 14 | .custom_name("Using build context") 15 | .add_exclude_pattern("**/target") 16 | }; 17 | } 18 | 19 | pub const CONTEXT_PATH: &str = "/context"; 20 | pub const DOCKERFILE_PATH: &str = "/dockerfile"; 21 | pub const TARGET_PATH: &str = "/target"; 22 | 23 | pub mod tools { 24 | use super::*; 25 | 26 | lazy_static! { 27 | pub static ref IMAGE: ImageSource = Source::image(env!("CONTAINER_TOOLS_REF")); 28 | } 29 | 30 | pub const METADATA_COLLECTOR: &str = "/usr/local/bin/cargo-metadata-collector"; 31 | pub const BUILDSCRIPT_CAPTURE: &str = "/usr/local/bin/cargo-buildscript-capture"; 32 | pub const BUILDSCRIPT_APPLY: &str = "/usr/local/bin/cargo-buildscript-apply"; 33 | pub const BUILD_PLAN: &str = "/usr/local/bin/cargo-build-plan"; 34 | pub const TEST_RUNNER: &str = "/usr/local/bin/cargo-test-runner"; 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/docker-hub-description.yml: -------------------------------------------------------------------------------- 1 | name: Docker Hub description 2 | on: 3 | push: 4 | tags: 5 | - cargo-wharf-frontend-latest 6 | - cargo-container-tools-latest 7 | 8 | jobs: 9 | frontend-description: 10 | runs-on: ubuntu-latest 11 | if: github.ref == 'refs/tags/cargo-wharf-frontend-latest' 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v1 15 | 16 | - name: Frontend description 17 | uses: peter-evans/dockerhub-description@v2.1.0 18 | env: 19 | DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} 20 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 21 | DOCKERHUB_REPOSITORY: denzp/cargo-wharf-frontend 22 | README_FILEPATH: cargo-wharf-frontend/README.md 23 | 24 | tools-description: 25 | runs-on: ubuntu-latest 26 | if: github.ref == 'refs/tags/cargo-container-tools-latest' 27 | steps: 28 | - name: Checkout sources 29 | uses: actions/checkout@v1 30 | 31 | - name: Container Tools description 32 | uses: peter-evans/dockerhub-description@v2.1.0 33 | env: 34 | DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} 35 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 36 | DOCKERHUB_REPOSITORY: denzp/cargo-container-tools 37 | README_FILEPATH: cargo-container-tools/README.md 38 | -------------------------------------------------------------------------------- /cargo-container-tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | # syntax = denzp/cargo-wharf-frontend:v0.1.0-alpha.2 2 | 3 | [package] 4 | name = "cargo-container-tools" 5 | version = "0.2.0-alpha.2" 6 | authors = ["Denys Zariaiev "] 7 | edition = "2018" 8 | publish = false 9 | license = "MIT/Apache-2.0" 10 | 11 | [dependencies] 12 | cargo = "0.40" 13 | clap = "2.33" 14 | either = "1.5" 15 | failure = "0.1" 16 | lazy_static = "1.4" 17 | semver = "0.9" 18 | toml = "0.5" 19 | 20 | serde = "1.0" 21 | serde_json = "1.0" 22 | serde_derive = "1.0" 23 | 24 | [package.metadata.wharf.builder] 25 | image = "clux/muslrust:stable" 26 | target = "x86_64-unknown-linux-musl" 27 | 28 | [package.metadata.wharf.output] 29 | image = "alpine" 30 | workdir = "/" 31 | 32 | [[package.metadata.wharf.binary]] 33 | name = "cargo-build-plan" 34 | destination = "/usr/local/bin/cargo-build-plan" 35 | 36 | [[package.metadata.wharf.binary]] 37 | name = "cargo-buildscript-capture" 38 | destination = "/usr/local/bin/cargo-buildscript-capture" 39 | 40 | [[package.metadata.wharf.binary]] 41 | name = "cargo-buildscript-apply" 42 | destination = "/usr/local/bin/cargo-buildscript-apply" 43 | 44 | [[package.metadata.wharf.binary]] 45 | name = "cargo-metadata-collector" 46 | destination = "/usr/local/bin/cargo-metadata-collector" 47 | 48 | [[package.metadata.wharf.binary]] 49 | name = "cargo-test-runner" 50 | destination = "/usr/local/bin/cargo-test-runner" 51 | -------------------------------------------------------------------------------- /tests/integration/example-single-bin.bats: -------------------------------------------------------------------------------- 1 | load helpers/images 2 | load helpers/registry 3 | 4 | function setup() { 5 | install_registry 6 | maybe_build_container_tools_image 7 | maybe_build_frontend_image 8 | } 9 | 10 | function teardown() { 11 | remove_registry 12 | 13 | docker rmi -f cargo-wharf/example-single-bin:test || true 14 | docker rmi -f cargo-wharf/example-single-bin || true 15 | } 16 | 17 | @test "single-bin :: binaries" { 18 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin examples/single-bin 19 | 20 | run docker run --rm cargo-wharf/example-single-bin 21 | [ "$status" -eq 0 ] 22 | [ "${lines[0]}" = "Hello from the container!" ] 23 | [ "${lines[1]}" = "Args: [\"/bin/wharf-output\", \"predefined arg\"]" ] 24 | [[ "$output" == *"\"NAME 1\": \"VALUE 1\""* ]] 25 | 26 | run docker run --rm cargo-wharf/example-single-bin extra-arg 27 | [ "$status" -eq 0 ] 28 | [ "${lines[1]}" = "Args: [\"/bin/wharf-output\", \"extra-arg\"]" ] 29 | } 30 | 31 | @test "single-bin :: tests" { 32 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin:test examples/single-bin --build-arg profile=test 33 | 34 | run docker run --rm cargo-wharf/example-single-bin:test 35 | [ "$status" -eq 0 ] 36 | [[ "$output" == *"running 0 tests"* ]] 37 | [[ "$output" == *"test result: ok"* ]] 38 | } 39 | -------------------------------------------------------------------------------- /cargo-container-tools/src/bin/cargo-test-runner/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | #![deny(clippy::all)] 3 | 4 | use std::process::{exit, Command, Stdio}; 5 | 6 | use cargo::core::Shell; 7 | use cargo::util::CargoResult; 8 | 9 | use clap::{crate_authors, crate_version, App, Arg, ArgMatches}; 10 | use failure::{bail, ResultExt}; 11 | 12 | fn main() { 13 | let matches = get_cli_app().get_matches(); 14 | 15 | if let Err(error) = run(&matches) { 16 | cargo::handle_error(&error, &mut Shell::new()); 17 | exit(1); 18 | } 19 | } 20 | 21 | fn get_cli_app() -> App<'static, 'static> { 22 | App::new("cargo-test-runner") 23 | .version(crate_version!()) 24 | .author(crate_authors!()) 25 | .about("Tiny Rust tests runner") 26 | .arg( 27 | Arg::with_name("binaries") 28 | .value_name("BINARY") 29 | .multiple(true) 30 | .help("Test binaries to run"), 31 | ) 32 | } 33 | 34 | fn run(matches: &ArgMatches<'static>) -> CargoResult<()> { 35 | for binary in matches.values_of("binaries").unwrap_or_default() { 36 | let child = Command::new(binary) 37 | .stdout(Stdio::inherit()) 38 | .stderr(Stdio::inherit()) 39 | .output() 40 | .context("Unable to start a test")?; 41 | 42 | if !child.status.success() { 43 | bail!("Test failed!"); 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /cargo-container-tools/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.0-alpha.1] - 2019-12-01 10 | ### Changed 11 | - Switched to stable Rust channel. 12 | - Cargo updated to `v0.40.0`. 13 | 14 | ## [0.2.0-alpha.0] - 2019-11-07 15 | ### Added 16 | - Build plan generation tool. 17 | - Test runner tool. 18 | - Metadata collector tool. 19 | 20 | ### Changed 21 | - The crate was shaped and redesigned almost from scratch to faciliate needs of `cargo-wharf-frontend`. 22 | - Buildscript helper was rewritten from scratch as 2 components: capturing and applying. 23 | 24 | ### Removed 25 | - Temporarily removed `cargo-ldd` helper to check dynamic dependencies. 26 | 27 | ## [0.1.0] - 2018-11-07 28 | ### Added 29 | - Basic tools needed to support legacy approach with dynamically-generated Dockerfile. 30 | 31 | [Unreleased]: https://github.com/denzp/cargo-wharf/compare/cargo-container-tools-v0.2.0-alpha.1...HEAD 32 | [0.2.0-alpha.1]: https://github.com/denzp/cargo-wharf/compare/cargo-container-tools-v0.2.0-alpha.0...cargo-container-tools-v0.2.0-alpha.1 33 | [0.2.0-alpha.0]: https://github.com/denzp/cargo-wharf/compare/legacy-dockerfile...cargo-container-tools-v0.2.0-alpha.0 34 | [0.1.0]: https://github.com/denzp/cargo-wharf/releases/tag/legacy-dockerfile 35 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Changed 9 | - Update dependencies to use `tonic` for gRPC. 10 | 11 | ## [0.1.0-alpha.2] - 2020-01-26 12 | ### Changed 13 | - Use host SSH Agent when available (exactly, when `--ssh=default` flag is passed for `docker build`). 14 | - Reduce logging verbosity. 15 | 16 | ## [0.1.0-alpha.1] - 2019-12-01 17 | ### Added 18 | - Advanced output image metadata (Dockerfile's `VOLUME`, `EXPOSE`, `LABEL`, `STOPSIGNAL`). 19 | - Custom image setup commands (Dockerfile's `RUN`). 20 | - Support `staticlib` dependencies. 21 | 22 | ### Changed 23 | - README and usage guide. 24 | - Switched to stable Rust channel. 25 | 26 | ## [0.1.0-alpha.0] - 2019-11-07 27 | ### Added 28 | - The frontend itself: first public release. 29 | 30 | [Unreleased]: https://github.com/denzp/cargo-wharf/compare/cargo-wharf-frontend-v0.1.0-alpha.2...HEAD 31 | [0.1.0-alpha.2]: https://github.com/denzp/cargo-wharf/compare/cargo-wharf-frontend-v0.1.0-alpha.1...cargo-wharf-frontend-v0.1.0-alpha.2 32 | [0.1.0-alpha.1]: https://github.com/denzp/cargo-wharf/compare/cargo-wharf-frontend-v0.1.0-alpha.0...cargo-wharf-frontend-v0.1.0-alpha.1 33 | [0.1.0-alpha.0]: https://github.com/denzp/cargo-wharf/releases/tag/cargo-wharf-frontend-v0.1.0-alpha.0 34 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/Cargo.toml: -------------------------------------------------------------------------------- 1 | # syntax = denzp/cargo-wharf-frontend:v0.1.0-alpha.2 2 | 3 | [package] 4 | name = "cargo-wharf-frontend" 5 | version = "0.1.0-alpha.3" 6 | authors = ["Denys Zariaiev "] 7 | edition = "2018" 8 | publish = false 9 | license = "MIT/Apache-2.0" 10 | 11 | [dependencies] 12 | async-trait = "0.1" 13 | buildkit-frontend = "0.3" 14 | buildkit-llb = "0.2" 15 | buildkit-proto = "0.2" 16 | chrono = "0.4" 17 | either = "1.5" 18 | env_logger = "0.7" 19 | failure = "0.1" 20 | futures = "0.3" 21 | lazy_static = "1.0" 22 | log = "0.4" 23 | petgraph = { version = "0.5", features = ["serde-1"] } 24 | prost = "0.6" 25 | semver = { version = "0.9", features = ["serde"] } 26 | serde = { version = "1.0", features = ["derive"] } 27 | serde_json = "1.0" 28 | take_mut = "0.2" 29 | 30 | [dependencies.tokio] 31 | version = "0.2" 32 | features = ["rt-core", "macros"] 33 | 34 | [build-dependencies] 35 | cfg-if = "0.1" 36 | 37 | [dev-dependencies] 38 | maplit = "1.0" 39 | pretty_assertions = "0.6" 40 | 41 | [features] 42 | container-tools-testing = [] 43 | container-tools-local = [] 44 | container-tools-master = [] 45 | 46 | [package.metadata.wharf.builder] 47 | image = "clux/muslrust:stable" 48 | target = "x86_64-unknown-linux-musl" 49 | 50 | [package.metadata.wharf.output] 51 | image = "scratch" 52 | workdir = "/" 53 | entrypoint = ["/usr/local/bin/cargo-wharf-frontend"] 54 | 55 | [[package.metadata.wharf.binary]] 56 | name = "cargo-wharf-frontend" 57 | destination = "/usr/local/bin/cargo-wharf-frontend" 58 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/graph/mod.rs: -------------------------------------------------------------------------------- 1 | use petgraph::prelude::*; 2 | use serde::Serialize; 3 | 4 | use crate::plan::RawBuildPlan; 5 | 6 | mod node; 7 | mod ops; 8 | 9 | pub use self::node::*; 10 | 11 | #[derive(Debug, Serialize)] 12 | pub struct BuildGraph { 13 | graph: StableGraph, 14 | } 15 | 16 | impl BuildGraph { 17 | pub fn inner(&self) -> &StableGraph { 18 | &self.graph 19 | } 20 | 21 | #[cfg(test)] 22 | pub fn into_inner(self) -> StableGraph { 23 | self.graph 24 | } 25 | } 26 | 27 | impl From for BuildGraph { 28 | fn from(plan: RawBuildPlan) -> Self { 29 | let mut graph = StableGraph::from(plan); 30 | 31 | self::ops::merge_buildscript_nodes(&mut graph); 32 | self::ops::apply_buildscript_outputs(&mut graph); 33 | 34 | Self { graph } 35 | } 36 | } 37 | 38 | impl From for StableGraph { 39 | fn from(plan: RawBuildPlan) -> Self { 40 | let mut graph = StableGraph::::new(); 41 | 42 | let nodes = { 43 | plan.invocations 44 | .iter() 45 | .map(|item| graph.add_node(item.into())) 46 | .collect::>() 47 | }; 48 | 49 | for (item, index) in plan.invocations.iter().zip(0..) { 50 | let mut deps = item.deps.clone(); 51 | 52 | deps.sort(); 53 | for dep in item.deps.iter() { 54 | graph.add_edge(nodes[*dep as usize], nodes[index], ()); 55 | } 56 | } 57 | 58 | graph 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/integration/pretty-output.bats: -------------------------------------------------------------------------------- 1 | load helpers/images 2 | load helpers/registry 3 | 4 | function setup() { 5 | install_registry 6 | maybe_build_container_tools_image 7 | maybe_build_frontend_image 8 | } 9 | 10 | function teardown() { 11 | remove_registry 12 | 13 | docker rmi -f cargo-wharf/example-multi-bin || true 14 | } 15 | 16 | @test "pretty print binaries" { 17 | run docker buildx build --load -f examples/multi-bin/Cargo.toml -t cargo-wharf/example-multi-bin examples/multi-bin 18 | 19 | [ "$status" -eq 0 ] 20 | [[ "$output" == *"Running \`Update apt-get cache\`"* ]] 21 | [[ "$output" == *"Running \`apt-get install -y protobuf-compiler\`"* ]] 22 | [[ "$output" == *"Running \`echo '' > /custom-output\`"* ]] 23 | [[ "$output" == *"Compiling pkg-config"* ]] 24 | [[ "$output" == *"Compiling cc"* ]] 25 | [[ "$output" == *"Compiling openssl-sys [build script]"* ]] 26 | [[ "$output" == *"Running openssl-sys [build script]"* ]] 27 | [[ "$output" == *"Compiling openssl-sys"* ]] 28 | [[ "$output" == *"Compiling binary bin-1"* ]] 29 | [[ "$output" == *"Compiling binary bin-2"* ]] 30 | } 31 | 32 | @test "pretty print tests" { 33 | run docker buildx build --load -f examples/multi-bin/Cargo.toml -t cargo-wharf/example-multi-bin examples/multi-bin --build-arg profile=test 34 | 35 | [ "$status" -eq 0 ] 36 | [[ "$output" == *"Running \`Update apt-get cache\`"* ]] 37 | [[ "$output" == *"Running \`apt-get install -y protobuf-compiler\`"* ]] 38 | [[ "$output" == *"Running \`echo '' > /custom-output\`"* ]] 39 | [[ "$output" == *"Compiling pkg-config"* ]] 40 | [[ "$output" == *"Compiling cc"* ]] 41 | [[ "$output" == *"Compiling openssl-sys [build script]"* ]] 42 | [[ "$output" == *"Running openssl-sys [build script]"* ]] 43 | [[ "$output" == *"Compiling openssl-sys"* ]] 44 | [[ "$output" == *"Compiling test bin_1"* ]] 45 | [[ "$output" == *"Compiling test bin_2"* ]] 46 | } 47 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/query/source.rs: -------------------------------------------------------------------------------- 1 | use buildkit_llb::prelude::*; 2 | 3 | use super::print::{PrettyPrintQuery, PrintKind}; 4 | use super::WharfDatabase; 5 | 6 | use crate::config::{BaseImageConfig, CustomCommand}; 7 | 8 | pub trait SourceQuery: WharfDatabase + PrettyPrintQuery { 9 | fn builder_source(&self) -> Option> { 10 | self.source_llb( 11 | self.config().builder(), 12 | self.config() 13 | .builder() 14 | .setup_commands() 15 | .map(Vec::as_ref) 16 | .unwrap_or_default(), 17 | ) 18 | } 19 | 20 | fn output_source(&self) -> Option> { 21 | self.source_llb( 22 | self.config().output(), 23 | self.config() 24 | .output() 25 | .pre_install_commands() 26 | .map(Vec::as_ref) 27 | .unwrap_or_default(), 28 | ) 29 | } 30 | 31 | fn source_llb<'a>( 32 | &self, 33 | config: &'a dyn BaseImageConfig, 34 | commands: &'a [CustomCommand], 35 | ) -> Option> { 36 | if !commands.is_empty() { 37 | let mut last_output = config.image_source().map(|source| source.output()); 38 | 39 | for (name, args, display) in commands.iter().map(From::from) { 40 | last_output = Some( 41 | config 42 | .populate_env(Command::run(name)) 43 | .args(args.iter()) 44 | .mount(match last_output { 45 | Some(output) => Mount::Layer(OutputIdx(0), output, "/"), 46 | None => Mount::Scratch(OutputIdx(0), "/"), 47 | }) 48 | .custom_name(self.pretty_print(PrintKind::CustomCommand(display))) 49 | .ref_counted() 50 | .output(0), 51 | ); 52 | } 53 | 54 | last_output 55 | } else { 56 | config.image_source().map(|source| source.output()) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/integration/example-workspace.bats: -------------------------------------------------------------------------------- 1 | load helpers/images 2 | load helpers/registry 3 | 4 | function setup() { 5 | install_registry 6 | maybe_build_container_tools_image 7 | maybe_build_frontend_image 8 | } 9 | 10 | function teardown() { 11 | remove_registry 12 | 13 | docker rmi -f cargo-wharf/example-workspace:test || true 14 | docker rmi -f cargo-wharf/example-workspace || true 15 | } 16 | 17 | @test "workspace example :: binaries" { 18 | docker buildx build --load -f examples/workspace/Cargo.toml -t cargo-wharf/example-workspace examples/workspace --ssh=default 19 | 20 | run docker run --rm cargo-wharf/example-workspace 21 | [ "$status" -eq 0 ] 22 | [ "$output" = "" ] 23 | 24 | run docker run --rm cargo-wharf/example-workspace binary-1 25 | [ "$status" -eq 0 ] 26 | [ "${lines[0]}" = "Hello from build script" ] 27 | [ "${lines[1]}" = "Hello from binary 1" ] 28 | 29 | run docker run --rm cargo-wharf/example-workspace binary-2 30 | [ "$status" -eq 0 ] 31 | [ "${lines[0]}" = "Hello from build script" ] 32 | [ "${lines[1]}" = "Hello from binary 2" ] 33 | } 34 | 35 | @test "workspace example :: custom commands" { 36 | docker buildx build --load -f examples/workspace/Cargo.toml -t cargo-wharf/example-workspace examples/workspace --ssh=default 37 | 38 | run docker run --rm cargo-wharf/example-workspace cat /custom-setup 39 | [ "$status" -eq 0 ] 40 | [ "${lines[0]}" = "pre-install" ] 41 | 42 | run docker run --rm cargo-wharf/example-workspace cat /custom-post-setup 43 | [ "$status" -eq 0 ] 44 | [ "${lines[0]}" = "post-install" ] 45 | } 46 | 47 | @test "workspace example :: tests" { 48 | docker buildx build --load -f examples/workspace/Cargo.toml -t cargo-wharf/example-workspace:test examples/workspace --build-arg profile=test --ssh=default 49 | 50 | run docker run --rm cargo-wharf/example-workspace:test 51 | [ "$status" -eq 1 ] 52 | [[ "$output" == *"running 1 test"* ]] 53 | [[ "$output" == *"test faulty_test ... FAILED"* ]] 54 | [[ "$output" == *"'faulty_test' panicked at 'this should fail'"* ]] 55 | [[ "$output" == *"test result: FAILED"* ]] 56 | } 57 | -------------------------------------------------------------------------------- /tests/integration/features.bats: -------------------------------------------------------------------------------- 1 | load helpers/images 2 | load helpers/registry 3 | 4 | function setup() { 5 | install_registry 6 | maybe_build_container_tools_image 7 | maybe_build_frontend_image 8 | } 9 | 10 | function teardown() { 11 | remove_registry 12 | 13 | docker rmi -f cargo-wharf/example-single-bin || true 14 | docker rmi -f cargo-wharf/example-workspace || true 15 | } 16 | 17 | @test "default behavior" { 18 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin examples/single-bin 19 | 20 | run docker run --rm cargo-wharf/example-single-bin 21 | [[ "$output" == *"feature-1 is on"* ]] 22 | [[ "$output" != *"feature-2 is on"* ]] 23 | } 24 | 25 | @test "no-default-features" { 26 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin examples/single-bin --build-arg no-default-features=true 27 | 28 | run docker run --rm cargo-wharf/example-single-bin 29 | [[ "$output" != *"feature-1 is on"* ]] 30 | [[ "$output" != *"feature-2 is on"* ]] 31 | } 32 | 33 | @test "no-default-features + features" { 34 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin examples/single-bin --build-arg no-default-features=true --build-arg features=feature-2 35 | 36 | run docker run --rm cargo-wharf/example-single-bin 37 | [[ "$output" != *"feature-1 is on"* ]] 38 | [[ "$output" == *"feature-2 is on"* ]] 39 | } 40 | 41 | @test "features" { 42 | docker buildx build --load -f examples/single-bin/Cargo.toml -t cargo-wharf/example-single-bin examples/single-bin --build-arg features=feature-2 43 | 44 | run docker run --rm cargo-wharf/example-single-bin 45 | [[ "$output" == *"feature-1 is on"* ]] 46 | [[ "$output" == *"feature-2 is on"* ]] 47 | } 48 | 49 | @test "workspace features" { 50 | docker buildx build --load -f examples/workspace/Cargo.toml -t cargo-wharf/example-workspace examples/workspace --build-arg features=the-special-feature --build-arg manifest-path=binary-1/Cargo.toml 51 | 52 | run docker run --rm cargo-wharf/example-workspace binary-1 53 | [[ "$output" == *"the-special-feature is on"* ]] 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cargo Wharf - efficient Docker images builder for Rust 2 | 3 | ## Features 4 | * **Efficiently cache crate dependencies.**
5 | *Every dependency is built in its isolated environment and cached independently from others.* 6 | * **Small output images.**
7 | *Only binaries (and eventually mandatory static assets) in the output image. No useless build artifacts.* 8 | * **No extra host tools has to be installed.**
9 | *Only Docker!* 10 | * **Ability to produce test images.**
11 | *The container created from that image will do the same as `cargo test` but in a safe isolated environment.* 12 | 13 |

14 | 15 |

16 | 17 | ## Usage 18 | **Disclaimer #1** Modern Docker with [BuildKit] enabled is required! 19 | 20 | **Disclaimer #2** Due to active development and expected breaking changes, `cargo-wharf` should not be used in production yet. 21 | 22 | **Usage guide can be found in the BuildKit frontend [README](cargo-wharf-frontend/README.md).** 23 | 24 | ### `cargo-wharf-frontend` 25 | [[Docker Hub](https://hub.docker.com/r/denzp/cargo-wharf-frontend)] 26 | [[README](cargo-wharf-frontend/README.md)] 27 | [[CHANGELOG](cargo-wharf-frontend/CHANGELOG.md)] 28 | 29 | The custom frontend for BuildKit that produces LLB graph out of Cargo's build plan. 30 | 31 | ### `cargo-container-tools` 32 | [[Docker Hub](https://hub.docker.com/r/denzp/cargo-container-tools)] 33 | [[README](cargo-container-tools/README.md)] 34 | [[CHANGELOG](cargo-container-tools/CHANGELOG.md)] 35 | 36 | Auxiliary tools that are useful for building Docker images of Rust crates and for `cargo-wharf-frontend` in particular. 37 | 38 | ## License 39 | `cargo-wharf` is primarily distributed under the terms of both the MIT license and 40 | the Apache License (Version 2.0), with portions covered by various BSD-like 41 | licenses. 42 | 43 | See LICENSE-APACHE, and LICENSE-MIT for details. 44 | 45 | ## Contribution 46 | Unless you explicitly state otherwise, any contribution intentionally submitted 47 | for inclusion in `cargo-wharf` by you, as defined in the Apache-2.0 license, 48 | shall be dual licensed as above, without any additional terms or conditions. 49 | 50 | [BuildKit]: https://github.com/moby/buildkit 51 | -------------------------------------------------------------------------------- /examples/multi-bin/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "0.1.6" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "cc" 10 | version = "1.0.45" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | 13 | [[package]] 14 | name = "libc" 15 | version = "0.2.62" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | 18 | [[package]] 19 | name = "multi-bin" 20 | version = "0.1.0" 21 | dependencies = [ 22 | "openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "openssl-sys" 27 | version = "0.9.50" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | dependencies = [ 30 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 35 | ] 36 | 37 | [[package]] 38 | name = "pkg-config" 39 | version = "0.3.16" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | 42 | [[package]] 43 | name = "vcpkg" 44 | version = "0.2.7" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | 47 | [metadata] 48 | "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" 49 | "checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" 50 | "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" 51 | "checksum openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)" = "2c42dcccb832556b5926bc9ae61e8775f2a61e725ab07ab3d1e7fcf8ae62c3b6" 52 | "checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea" 53 | "checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" 54 | -------------------------------------------------------------------------------- /cargo-container-tools/src/output.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufReader, BufWriter}; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use cargo::core::compiler::BuildOutput; 6 | use cargo::util::CargoResult; 7 | use failure::ResultExt; 8 | use serde_derive::{Deserialize, Serialize}; 9 | 10 | use crate::env::RuntimeEnv; 11 | 12 | const DEFAULT_FILE_NAME: &str = "buildscript-output.json"; 13 | 14 | #[derive(Debug, Serialize, Deserialize)] 15 | pub struct BuildScriptOutput { 16 | /// Paths to pass to rustc with the `-L` flag. 17 | pub library_paths: Vec, 18 | 19 | /// Names and link kinds of libraries, suitable for the `-l` flag. 20 | pub library_links: Vec, 21 | 22 | /// Linker arguments suitable to be passed to `-C link-arg=` 23 | pub linker_args: Vec, 24 | 25 | /// Various `--cfg` flags to pass to the compiler. 26 | pub cfgs: Vec, 27 | 28 | /// Additional environment variables to run the compiler with. 29 | pub env: Vec<(String, String)>, 30 | 31 | /// Additional metadata for the build scripts of dependent crates. 32 | pub metadata: Vec<(String, String)>, 33 | 34 | /// The manifest links value. 35 | pub link_name: Option, 36 | } 37 | 38 | impl BuildScriptOutput { 39 | pub fn serialize(&self) -> CargoResult<()> { 40 | let writer = BufWriter::new( 41 | File::create(RuntimeEnv::output_dir()?.join(DEFAULT_FILE_NAME)) 42 | .context("Unable to open output JSON file for writing")?, 43 | ); 44 | 45 | serde_json::to_writer(writer, self).context("Unable to serialize the build output")?; 46 | Ok(()) 47 | } 48 | 49 | pub fn deserialize() -> CargoResult { 50 | Self::deserialize_from_dir(RuntimeEnv::output_dir()?) 51 | } 52 | 53 | pub fn deserialize_from_dir(dir: &Path) -> CargoResult { 54 | let reader = BufReader::new( 55 | File::open(dir.join(DEFAULT_FILE_NAME)) 56 | .context("Unable to open output JSON file for reading")?, 57 | ); 58 | 59 | Ok(serde_json::from_reader(reader).context("Unable to deserialize the build output")?) 60 | } 61 | } 62 | 63 | impl From for BuildScriptOutput { 64 | fn from(output: BuildOutput) -> Self { 65 | Self { 66 | library_paths: output.library_paths, 67 | library_links: output.library_links, 68 | linker_args: output.linker_args, 69 | cfgs: output.cfgs, 70 | env: output.env, 71 | metadata: output.metadata, 72 | link_name: RuntimeEnv::manifest_link_name().map(String::from), 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /cargo-container-tools/src/bin/cargo-buildscript-apply/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | #![deny(clippy::all)] 3 | 4 | use std::process::{exit, Command, Stdio}; 5 | 6 | use cargo::core::Shell; 7 | use cargo::util::CargoResult; 8 | use clap::{crate_authors, crate_version, App, Arg, ArgMatches}; 9 | use failure::{bail, ResultExt}; 10 | 11 | use cargo_container_tools::BuildScriptOutput; 12 | 13 | fn main() { 14 | let matches = get_cli_app().get_matches(); 15 | 16 | if let Err(error) = run(&matches) { 17 | cargo::handle_error(&error, &mut Shell::new()); 18 | exit(1); 19 | } 20 | } 21 | 22 | fn get_cli_app() -> App<'static, 'static> { 23 | App::new("cargo-buildscript-apply") 24 | .version(crate_version!()) 25 | .author(crate_authors!()) 26 | .about("Tiny Rust buildscript output adapter") 27 | .args(&[ 28 | { 29 | Arg::with_name("rustc_path") 30 | .required(true) 31 | .value_name("RUSTC") 32 | .help("Path to a rustc binary") 33 | }, 34 | { 35 | Arg::with_name("rustc_args") 36 | .value_name("ARG") 37 | .multiple(true) 38 | .help("Args to pass into rustc") 39 | }, 40 | ]) 41 | } 42 | 43 | fn run(matches: &ArgMatches<'static>) -> CargoResult<()> { 44 | let output = BuildScriptOutput::deserialize()?; 45 | 46 | invoke_rustc( 47 | matches.value_of("rustc_path").unwrap(), 48 | matches.values_of("rustc_args").unwrap_or_default(), 49 | output, 50 | ) 51 | } 52 | 53 | fn invoke_rustc<'a>( 54 | bin_path: &'a str, 55 | bin_args: impl Iterator, 56 | overrides: BuildScriptOutput, 57 | ) -> CargoResult<()> { 58 | let mut command = Command::new(bin_path); 59 | 60 | command.stderr(Stdio::inherit()); 61 | command.stdout(Stdio::inherit()); 62 | 63 | command.envs(overrides.env.into_iter()); 64 | command.args(bin_args); 65 | 66 | for cfg in overrides.cfgs { 67 | command.arg("--cfg").arg(cfg); 68 | } 69 | 70 | for path in overrides.library_paths { 71 | command.arg("-L").arg(path); 72 | } 73 | 74 | for library in overrides.library_links { 75 | command.arg("-l").arg(library); 76 | } 77 | 78 | let output = command 79 | .output() 80 | .with_context(|_| format!("Unable to spawn '{}'", bin_path))?; 81 | 82 | if output.status.success() { 83 | Ok(()) 84 | } else { 85 | eprintln!("{}", String::from_utf8_lossy(&output.stderr)); 86 | bail!("Compilation failed. Exit status: {}", output.status); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/integration/helpers/images.bash: -------------------------------------------------------------------------------- 1 | function maybe_build_container_tools_image { 2 | if_changed cargo-container-tools build_container_tools_image 3 | if_image_missing localhost:10395/denzp/cargo-container-tools:local build_container_tools_image 4 | 5 | docker push localhost:10395/denzp/cargo-container-tools:local 6 | } 7 | 8 | function maybe_build_frontend_image() { 9 | if_changed cargo-wharf-frontend build_frontend_image 10 | if_image_missing localhost:10395/denzp/cargo-wharf-frontend:local build_frontend_image 11 | 12 | docker push localhost:10395/denzp/cargo-wharf-frontend:local 13 | } 14 | 15 | function build_container_tools_image { 16 | echo -e '# \rbuilding the container-tools docker image...' >&3 17 | 18 | extra_buildx_args=() 19 | if [[ ! -z "${EXPORT_DOCKER_CACHE}" ]]; then 20 | extra_buildx_args+=(--cache-to type=registry,ref=denzp/cargo-container-tools:cache,mode=max) 21 | fi 22 | 23 | docker buildx build --load \ 24 | -f cargo-container-tools/Cargo.toml . \ 25 | --tag localhost:10395/denzp/cargo-container-tools:local \ 26 | --cache-from type=registry,ref=denzp/cargo-container-tools:cache \ 27 | "${extra_buildx_args[@]}" 2>&3 28 | 29 | echo -e '# \rbuilding the container-tools docker image... done' >&3 30 | } 31 | 32 | function build_frontend_image() { 33 | echo -e '# \rbuilding the frontend docker image...' >&3 34 | 35 | extra_buildx_args=() 36 | if [[ ! -z "${EXPORT_DOCKER_CACHE}" ]]; then 37 | extra_buildx_args+=(--cache-to type=registry,ref=denzp/cargo-wharf-frontend:cache,mode=max) 38 | fi 39 | 40 | docker buildx build --load \ 41 | -f cargo-wharf-frontend/Cargo.toml . \ 42 | --tag localhost:10395/denzp/cargo-wharf-frontend:local \ 43 | --build-arg manifest-path=cargo-wharf-frontend/Cargo.toml \ 44 | --build-arg features=container-tools-testing \ 45 | "${extra_buildx_args[@]}" 2>&3 46 | 47 | echo -e '# \rbuilding the frontend docker image... done' >&3 48 | } 49 | 50 | function if_changed() { 51 | CACHE_SOURCE=$1 52 | CACHE_FILE=tests/integration/.cache/$(echo "$CACHE_SOURCE" | sha1sum | awk '{print $1}') 53 | 54 | if [ ! -r $CACHE_FILE ] || [ ! $(cat $CACHE_FILE) == $(get_hash $1) ]; then 55 | echo "if_changed: '$1' changed since last run..." >&3 56 | 57 | shift 58 | $@ 59 | 60 | mkdir -p $(dirname $CACHE_FILE) 61 | echo -n $(get_hash $CACHE_SOURCE) > $CACHE_FILE 62 | fi; 63 | } 64 | 65 | function if_image_missing() { 66 | if ! docker image inspect $1 >&2 > /dev/null; then 67 | echo "if_image_missing: '$1' is missing..." >&3 68 | 69 | shift 70 | $@ 71 | fi; 72 | } 73 | 74 | function get_hash() { 75 | find $1 -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum | awk '{print $1}' 76 | } 77 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/frontend.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use async_trait::async_trait; 4 | use failure::{Error, ResultExt}; 5 | use serde::Deserialize; 6 | 7 | use buildkit_frontend::options::common::CacheOptionsEntry; 8 | use buildkit_frontend::{Bridge, Frontend, FrontendOutput}; 9 | 10 | use crate::config::Config; 11 | use crate::debug::{DebugKind, DebugOperation}; 12 | use crate::graph::BuildGraph; 13 | use crate::plan::RawBuildPlan; 14 | use crate::query::{Profile, WharfQuery, WharfStorage}; 15 | 16 | pub struct CargoFrontend; 17 | 18 | #[derive(Debug, Default, Deserialize)] 19 | #[serde(default, rename_all = "kebab-case")] 20 | pub struct Options { 21 | /// Path to the `Dockerfile` - in our case it's a path to `Cargo.toml`. 22 | pub filename: Option, 23 | 24 | /// Overriden crate manifest path. 25 | pub manifest_path: Option, 26 | 27 | pub features: Vec, 28 | pub no_default_features: bool, 29 | pub profile: Profile, 30 | 31 | /// Debugging features of the frontend. 32 | pub debug: Vec, 33 | 34 | /// New approach to specify cache imports. 35 | pub cache_imports: Vec, 36 | 37 | /// Legacy convention to specify cache imports. 38 | #[serde(deserialize_with = "CacheOptionsEntry::from_legacy_list")] 39 | pub cache_from: Vec, 40 | } 41 | 42 | #[async_trait] 43 | impl Frontend for CargoFrontend { 44 | async fn run(self, mut bridge: Bridge, options: Options) -> Result { 45 | let mut debug = DebugOperation::new(); 46 | 47 | let config = { 48 | Config::analyse(&mut bridge, &options) 49 | .await 50 | .context("Unable to analyse config")? 51 | }; 52 | 53 | debug.maybe(&options, || &config); 54 | 55 | let plan = { 56 | RawBuildPlan::evaluate(&mut bridge, &config) 57 | .await 58 | .context("Unable to evaluate the Cargo build plan")? 59 | }; 60 | 61 | debug.maybe(&options, || &plan); 62 | 63 | let graph: BuildGraph = plan.into(); 64 | let storage = WharfStorage::new(&graph, &config); 65 | 66 | debug.maybe(&options, || &graph); 67 | debug.maybe(&options, || storage.definition().unwrap()); 68 | 69 | if !options.debug.is_empty() { 70 | return Ok(FrontendOutput::with_ref( 71 | bridge 72 | .solve(debug.terminal()) 73 | .await 74 | .context("Unable to write debug output")?, 75 | )); 76 | } 77 | 78 | Ok(FrontendOutput::with_spec_and_ref( 79 | storage.image_spec().context("Unable to build image spec")?, 80 | storage 81 | .solve(&mut bridge, &options) 82 | .await 83 | .context("Unable to build the crate")?, 84 | )) 85 | } 86 | } 87 | 88 | impl Options { 89 | pub fn cache_entries(&self) -> &[CacheOptionsEntry] { 90 | if !self.cache_imports.is_empty() { 91 | return &self.cache_imports; 92 | } 93 | 94 | &self.cache_from 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::mem::replace; 2 | use std::path::Path; 3 | 4 | use prost::Message; 5 | use serde::Deserialize; 6 | 7 | use buildkit_llb::ops::fs::SequenceOperation; 8 | use buildkit_llb::prelude::*; 9 | use buildkit_proto::pb; 10 | 11 | use crate::frontend::Options; 12 | 13 | pub struct DebugOperation { 14 | inner: SequenceOperation<'static>, 15 | } 16 | 17 | #[derive(Debug, Deserialize, PartialEq, Clone, Copy)] 18 | #[serde(untagged)] 19 | #[serde(field_identifier, rename_all = "kebab-case")] 20 | pub enum DebugKind { 21 | All, 22 | Config, 23 | BuildPlan, 24 | BuildGraph, 25 | 26 | #[serde(rename = "llb")] 27 | LLB, 28 | } 29 | 30 | impl DebugOperation { 31 | pub fn new() -> Self { 32 | Self { 33 | inner: FileSystem::sequence().custom_name("Writing the debug output"), 34 | } 35 | } 36 | 37 | pub fn maybe(&mut self, options: &Options, getter: G) 38 | where 39 | G: FnOnce() -> O, 40 | O: DebugOutput, 41 | { 42 | if options.debug.contains(&O::KEY) || options.debug.contains(&DebugKind::All) { 43 | self.append_debug_output(O::PATH, &getter()); 44 | } 45 | } 46 | 47 | pub fn terminal(&self) -> Terminal<'_> { 48 | Terminal::with(self.inner.last_output().unwrap()) 49 | } 50 | 51 | fn append_debug_output(&mut self, path: P, output: &O) 52 | where 53 | P: AsRef, 54 | O: DebugOutput, 55 | { 56 | let (index, layer_path) = match self.inner.last_output_index() { 57 | Some(index) => (index + 1, LayerPath::Own(OwnOutputIdx(index), path)), 58 | None => (0, LayerPath::Scratch(path)), 59 | }; 60 | 61 | self.inner = replace(&mut self.inner, FileSystem::sequence()) 62 | .append(FileSystem::mkfile(OutputIdx(index), layer_path).data(output.as_bytes())); 63 | } 64 | } 65 | 66 | pub trait DebugOutput { 67 | const KEY: DebugKind; 68 | const PATH: &'static str; 69 | 70 | fn as_bytes(&self) -> Vec; 71 | } 72 | 73 | impl<'a> DebugOutput for &'a crate::config::Config { 74 | const KEY: DebugKind = DebugKind::Config; 75 | const PATH: &'static str = "config.json"; 76 | 77 | fn as_bytes(&self) -> Vec { 78 | serde_json::to_vec_pretty(self).unwrap() 79 | } 80 | } 81 | 82 | impl<'a> DebugOutput for &'a crate::plan::RawBuildPlan { 83 | const KEY: DebugKind = DebugKind::BuildPlan; 84 | const PATH: &'static str = "build-plan.json"; 85 | 86 | fn as_bytes(&self) -> Vec { 87 | serde_json::to_vec_pretty(self).unwrap() 88 | } 89 | } 90 | 91 | impl<'a> DebugOutput for &'a crate::graph::BuildGraph { 92 | const KEY: DebugKind = DebugKind::BuildGraph; 93 | const PATH: &'static str = "build-graph.json"; 94 | 95 | fn as_bytes(&self) -> Vec { 96 | serde_json::to_vec_pretty(self).unwrap() 97 | } 98 | } 99 | 100 | impl DebugOutput for pb::Definition { 101 | const KEY: DebugKind = DebugKind::LLB; 102 | const PATH: &'static str = "llb.pb"; 103 | 104 | fn as_bytes(&self) -> Vec { 105 | let mut bytes = Vec::new(); 106 | self.encode(&mut bytes).unwrap(); 107 | 108 | bytes 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /cargo-container-tools/src/bin/cargo-metadata-collector/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | #![deny(clippy::all)] 3 | 4 | use std::env::current_dir; 5 | use std::fs::{read_to_string, File}; 6 | use std::io::{stdout, BufWriter}; 7 | use std::iter::once; 8 | use std::process::exit; 9 | 10 | use cargo::core::package::Package; 11 | use cargo::core::{Shell, Workspace}; 12 | use cargo::util::{config::Config, CargoResult}; 13 | 14 | use clap::{crate_authors, crate_version, App, Arg, ArgMatches}; 15 | use either::Either; 16 | use toml::Value; 17 | 18 | use cargo_container_tools::metadata::*; 19 | 20 | fn main() { 21 | let matches = get_cli_app().get_matches(); 22 | 23 | if let Err(error) = run(&matches) { 24 | cargo::handle_error(&error, &mut Shell::new()); 25 | exit(1); 26 | } 27 | } 28 | 29 | fn get_cli_app() -> App<'static, 'static> { 30 | App::new("cargo-metadata-collector") 31 | .version(crate_version!()) 32 | .author(crate_authors!()) 33 | .about("Tiny Rust [package.metadata] collector") 34 | .args(&[ 35 | { 36 | Arg::with_name("output") 37 | .long("output") 38 | .takes_value(true) 39 | .value_name("PATH") 40 | .default_value("-") 41 | .help("Metadata output path (or '-' for STDOUT)") 42 | }, 43 | { 44 | Arg::with_name("manifest") 45 | .long("manifest-path") 46 | .takes_value(true) 47 | .value_name("PATH") 48 | .default_value("Cargo.toml") 49 | .help("Path to Cargo.toml") 50 | }, 51 | ]) 52 | } 53 | 54 | fn run(matches: &ArgMatches<'static>) -> CargoResult<()> { 55 | let config = Config::default()?; 56 | let ws = Workspace::new( 57 | ¤t_dir()?.join(matches.value_of("manifest").unwrap()), 58 | &config, 59 | )?; 60 | 61 | let metadata = { 62 | ws.current_opt() 63 | .map(package_metadata) 64 | .map(|metadata| vec![metadata]) 65 | .unwrap_or_else(|| workspace_metadata(&ws)) 66 | }; 67 | 68 | let writer = BufWriter::new(match matches.value_of("output").unwrap() { 69 | "-" => Either::Left(stdout()), 70 | path => Either::Right(File::create(path)?), 71 | }); 72 | 73 | serde_json::to_writer_pretty(writer, &metadata)?; 74 | 75 | Ok(()) 76 | } 77 | 78 | fn package_metadata(package: &Package) -> Metadata { 79 | Metadata { 80 | origin: Origin::Package { 81 | name: package.name().to_string(), 82 | version: package.version().clone(), 83 | }, 84 | 85 | metadata: package.manifest().custom_metadata().cloned(), 86 | } 87 | } 88 | 89 | fn workspace_metadata(ws: &Workspace) -> Vec { 90 | let root_metadata = Metadata { 91 | origin: Origin::WorkspaceRoot, 92 | metadata: workspace_root_inner_metadata(ws), 93 | }; 94 | 95 | once(root_metadata) 96 | .chain(ws.members().map(package_metadata)) 97 | .collect() 98 | } 99 | 100 | fn workspace_root_inner_metadata(ws: &Workspace) -> Option { 101 | let manifest_contents = read_to_string(ws.root().join("Cargo.toml")).ok()?; 102 | let manifest: manifest::Root = toml::from_str(&manifest_contents).ok()?; 103 | 104 | manifest.workspace?.metadata 105 | } 106 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | name: Unit tests 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout sources 17 | uses: actions/checkout@v2 18 | 19 | - name: Install Rust stable toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | 26 | - name: Run cargo test 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: test 30 | 31 | lints: 32 | name: Lints 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout sources 36 | uses: actions/checkout@v2 37 | 38 | - name: Install stable toolchain 39 | uses: actions-rs/toolchain@v1 40 | with: 41 | profile: minimal 42 | toolchain: stable 43 | override: true 44 | components: rustfmt, clippy 45 | 46 | - name: Run cargo fmt 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: fmt 50 | args: --all -- --check 51 | 52 | - name: Run cargo clippy 53 | uses: actions-rs/clippy-check@v1 54 | with: 55 | token: ${{ secrets.GITHUB_TOKEN }} 56 | args: --all-features 57 | 58 | integration: 59 | name: Run integration tests 60 | runs-on: ubuntu-latest 61 | steps: 62 | - name: Checkout sources 63 | uses: actions/checkout@v1 64 | 65 | - name: Print docker info 66 | run: docker info 67 | 68 | - name: Login to Docker Hub Registry 69 | run: docker login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD 70 | env: 71 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 72 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 73 | 74 | - name: Installing buildx 75 | run: .ci/install-buildx.sh 76 | 77 | - name: Install bats 78 | run: sudo npm install -g bats 79 | 80 | - name: Initialize SSH Agent 81 | uses: webfactory/ssh-agent@v0.2.0 82 | with: 83 | ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} 84 | 85 | - name: Run integration tests 86 | run: bats -t tests/integration 87 | env: 88 | DOCKER_BUILDKIT: 1 89 | EXPORT_DOCKER_CACHE: 1 90 | 91 | - name: Build and push 'denzp/cargo-wharf-frontend:master' 92 | run: >- 93 | docker buildx build 94 | --push -f cargo-wharf-frontend/Cargo.toml . 95 | --tag denzp/cargo-wharf-frontend:master 96 | --build-arg manifest-path=cargo-wharf-frontend/Cargo.toml 97 | --build-arg features=container-tools-master 98 | 99 | - name: Build and push 'denzp/cargo-container-tools:master' 100 | run: >- 101 | docker buildx build 102 | --push -f cargo-container-tools/Cargo.toml . 103 | --tag denzp/cargo-container-tools:master 104 | --cache-from type=registry,ref=denzp/cargo-container-tools:cache 105 | 106 | - name: Dump dmesg output 107 | if: failure() 108 | run: dmesg 109 | 110 | - name: Dump buildkit logs 111 | if: failure() 112 | run: docker logs buildx_buildkit_ci-builder0 113 | 114 | - name: Dump docker logs 115 | if: failure() 116 | run: sudo journalctl -eu docker 117 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/plan.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::path::PathBuf; 3 | 4 | use failure::{Error, ResultExt}; 5 | use semver::Version; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use buildkit_frontend::Bridge; 9 | use buildkit_llb::prelude::*; 10 | 11 | use crate::config::{BaseImageConfig, Config}; 12 | use crate::query::Profile; 13 | use crate::shared::{tools, CONTEXT, CONTEXT_PATH}; 14 | 15 | const OUTPUT_LAYER_PATH: &str = "/output"; 16 | const OUTPUT_NAME: &str = "build-plan.json"; 17 | 18 | #[derive(Debug, Deserialize, Serialize)] 19 | pub struct RawInvocation { 20 | pub package_name: String, 21 | pub package_version: Version, 22 | pub target_kind: Vec, 23 | pub deps: Vec, 24 | pub outputs: Vec, 25 | pub links: BTreeMap, 26 | pub program: String, 27 | pub args: Vec, 28 | pub env: BTreeMap, 29 | pub cwd: PathBuf, // TODO(denzp): should this really be an "Option" like in Cargo? 30 | } 31 | 32 | #[derive(Debug, Deserialize, PartialEq, Serialize)] 33 | #[serde(rename_all = "kebab-case")] 34 | pub enum RawTargetKind { 35 | Bin, 36 | Test, 37 | CustomBuild, 38 | Example, 39 | 40 | #[serde(other)] 41 | Other, 42 | } 43 | 44 | #[derive(Debug, Deserialize, Serialize)] 45 | pub struct RawBuildPlan { 46 | pub invocations: Vec, 47 | } 48 | 49 | impl RawBuildPlan { 50 | pub async fn evaluate<'a, 'b: 'a>( 51 | bridge: &'a mut Bridge, 52 | config: &'b Config, 53 | ) -> Result { 54 | let builder = config.builder(); 55 | 56 | let mut args = vec![ 57 | String::from("--manifest-path"), 58 | PathBuf::from(CONTEXT_PATH) 59 | .join(config.manifest_path()) 60 | .to_string_lossy() 61 | .into(), 62 | String::from("--output"), 63 | PathBuf::from(OUTPUT_LAYER_PATH) 64 | .join(OUTPUT_NAME) 65 | .to_string_lossy() 66 | .into(), 67 | ]; 68 | 69 | if let Some(target) = builder.target() { 70 | args.push("--target".into()); 71 | args.push(target.into()); 72 | } 73 | 74 | if !config.default_features() { 75 | args.push("--no-default-features".into()); 76 | } 77 | 78 | for feature in config.enabled_features() { 79 | args.push("--feature".into()); 80 | args.push(feature.into()); 81 | } 82 | 83 | match config.profile() { 84 | Profile::DebugBinaries | Profile::DebugTests => {} 85 | Profile::ReleaseBinaries | Profile::ReleaseTests => { 86 | args.push("--release".into()); 87 | } 88 | } 89 | 90 | let command = { 91 | builder 92 | .populate_env(Command::run(tools::BUILD_PLAN)) 93 | .args(&args) 94 | .cwd(CONTEXT_PATH) 95 | .mount(Mount::Layer(OutputIdx(0), builder.source().output(), "/")) 96 | .mount(Mount::ReadOnlyLayer(CONTEXT.output(), CONTEXT_PATH)) 97 | .mount(Mount::ReadOnlySelector( 98 | tools::IMAGE.output(), 99 | tools::BUILD_PLAN, 100 | tools::BUILD_PLAN, 101 | )) 102 | .mount(Mount::Scratch(OutputIdx(1), OUTPUT_LAYER_PATH)) 103 | .mount(Mount::OptionalSshAgent("/run/cargo-wharf/ssh-agent-0")) 104 | .env("SSH_AUTH_SOCK", "/run/cargo-wharf/ssh-agent-0") 105 | .custom_name("Evaluating the build plan") 106 | }; 107 | 108 | let build_plan_layer = { 109 | bridge 110 | .solve(Terminal::with(command.output(1))) 111 | .await 112 | .context("Unable to evaluate the build plan")? 113 | }; 114 | 115 | let build_plan = { 116 | bridge 117 | .read_file(&build_plan_layer, OUTPUT_NAME, None) 118 | .await 119 | .context("Unable to read Cargo build plan")? 120 | }; 121 | 122 | Ok(serde_json::from_slice(&build_plan).context("Unable to parse Cargo build plan")?) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /cargo-container-tools/src/bin/cargo-buildscript-capture/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | #![deny(clippy::all)] 3 | 4 | use std::collections::BTreeMap; 5 | use std::path::Path; 6 | use std::process::{exit, Command, Stdio}; 7 | 8 | use cargo::core::{compiler::BuildOutput, Shell}; 9 | use cargo::util::CargoResult; 10 | use clap::{crate_authors, crate_version, App, Arg, ArgMatches}; 11 | use failure::{bail, ResultExt}; 12 | 13 | use cargo_container_tools::{BuildScriptOutput, RuntimeEnv}; 14 | 15 | fn main() { 16 | let matches = get_cli_app().get_matches(); 17 | 18 | if let Err(error) = run(&matches) { 19 | cargo::handle_error(&error, &mut Shell::new()); 20 | exit(1); 21 | } 22 | } 23 | 24 | fn get_cli_app() -> App<'static, 'static> { 25 | App::new("cargo-buildscript-capture") 26 | .version(crate_version!()) 27 | .author(crate_authors!()) 28 | .about("Tiny Rust buildscript output collector") 29 | .args(&[ 30 | { 31 | Arg::with_name("buildscript_path") 32 | .required(true) 33 | .value_name("BUILDSCRIPT") 34 | .help("Path to a buildscript binary") 35 | }, 36 | { 37 | Arg::with_name("buildscript_args") 38 | .value_name("ARG") 39 | .multiple(true) 40 | .help("Args to pass into the buildscript") 41 | }, 42 | { 43 | Arg::with_name("metadata_from") 44 | .long("with-metadata-from") 45 | .takes_value(true) 46 | .value_name("PATH") 47 | .multiple(true) 48 | .help("Dependency build script outputs") 49 | }, 50 | ]) 51 | } 52 | 53 | fn run(matches: &ArgMatches<'static>) -> CargoResult<()> { 54 | let metadata = get_dependencies_metadata( 55 | matches 56 | .values_of("metadata_from") 57 | .unwrap_or_default() 58 | .map(Path::new), 59 | )?; 60 | 61 | let output = get_buildscript_output( 62 | matches.value_of("buildscript_path").unwrap(), 63 | matches.values_of("buildscript_args").unwrap_or_default(), 64 | metadata, 65 | )?; 66 | 67 | output.serialize() 68 | } 69 | 70 | fn get_buildscript_output<'a>( 71 | bin_path: &'a str, 72 | bin_args: impl Iterator, 73 | metadata: BTreeMap, 74 | ) -> CargoResult { 75 | let mut command = Command::new(bin_path); 76 | 77 | command.stderr(Stdio::inherit()); 78 | command.stdout(Stdio::piped()); 79 | command.envs(metadata); 80 | 81 | let output = { 82 | command 83 | .args(bin_args) 84 | .output() 85 | .with_context(|_| format!("Unable to spawn '{}'", bin_path))? 86 | }; 87 | 88 | let cargo_output_result = BuildOutput::parse( 89 | &output.stdout, 90 | RuntimeEnv::package_name()?, 91 | RuntimeEnv::output_dir()?, 92 | RuntimeEnv::output_dir()?, 93 | ); 94 | 95 | let cargo_output = match cargo_output_result { 96 | Ok(output) => output, 97 | 98 | Err(error) => { 99 | eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 100 | eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 101 | return Err(error); 102 | } 103 | }; 104 | 105 | for msg in &cargo_output.warnings { 106 | eprintln!("warning: {}", msg); 107 | } 108 | 109 | if !output.status.success() { 110 | eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 111 | bail!("Buildscript failed. Exit status: {}", output.status); 112 | } 113 | 114 | Ok(cargo_output.into()) 115 | } 116 | 117 | fn get_dependencies_metadata<'a>( 118 | paths: impl Iterator, 119 | ) -> CargoResult> { 120 | let mut metadata = BTreeMap::default(); 121 | 122 | for path in paths { 123 | let output = BuildScriptOutput::deserialize_from_dir(path) 124 | .context("Unable to open dependency build script output")?; 125 | 126 | if let Some(ref name) = output.link_name { 127 | let name = envify(&name); 128 | 129 | for (key, value) in output.metadata { 130 | metadata.insert(format!("DEP_{}_{}", name, envify(&key)), value); 131 | } 132 | } 133 | } 134 | 135 | Ok(metadata) 136 | } 137 | 138 | fn envify(s: &str) -> String { 139 | s.chars() 140 | .flat_map(|c| c.to_uppercase()) 141 | .map(|c| if c == '-' { '_' } else { c }) 142 | .collect() 143 | } 144 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/config/builder.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use failure::{format_err, Error, ResultExt}; 5 | use log::*; 6 | use serde::Serialize; 7 | 8 | use buildkit_frontend::oci; 9 | use buildkit_frontend::Bridge; 10 | use buildkit_llb::ops::source::ImageSource; 11 | use buildkit_llb::prelude::*; 12 | 13 | use super::base::{BaseBuilderConfig, CustomCommand}; 14 | use super::{merge_spec_and_overriden_env, BaseImageConfig}; 15 | use crate::shared::TARGET_PATH; 16 | 17 | #[derive(Debug, Serialize)] 18 | pub struct BuilderConfig { 19 | #[serde(skip_serializing)] 20 | source: ImageSource, 21 | 22 | overrides: BaseBuilderConfig, 23 | defaults: BuilderConfigDefaults, 24 | 25 | merged_env: BTreeMap, 26 | cargo_home: PathBuf, 27 | } 28 | 29 | #[derive(Debug, Serialize, Default)] 30 | struct BuilderConfigDefaults { 31 | env: Option>, 32 | user: Option, 33 | } 34 | 35 | impl BuilderConfig { 36 | pub async fn analyse(bridge: &mut Bridge, config: BaseBuilderConfig) -> Result { 37 | let source = config.source(); 38 | 39 | let (digest, spec) = { 40 | bridge 41 | .resolve_image_config(&source, Some("Resolving builder image")) 42 | .await 43 | .context("Unable to resolve image config")? 44 | }; 45 | 46 | debug!("resolved builder image config: {:#?}", spec.config); 47 | 48 | let spec = { 49 | spec.config 50 | .ok_or_else(|| format_err!("Missing source image config"))? 51 | }; 52 | 53 | let source = if !digest.is_empty() { 54 | source.with_digest(digest) 55 | } else { 56 | source 57 | }; 58 | 59 | let merged_env = merge_spec_and_overriden_env(&spec.env, &config.env); 60 | let user = { 61 | config 62 | .user 63 | .as_ref() 64 | .or_else(|| spec.user.as_ref()) 65 | .map(String::as_str) 66 | }; 67 | 68 | let cargo_home = PathBuf::from( 69 | merged_env 70 | .get("CARGO_HOME") 71 | .cloned() 72 | .or_else(|| guess_cargo_home(user)) 73 | .ok_or_else(|| format_err!("Unable to find or guess CARGO_HOME env variable"))?, 74 | ); 75 | 76 | Ok(Self { 77 | source, 78 | overrides: config, 79 | defaults: spec.into(), 80 | 81 | cargo_home, 82 | merged_env, 83 | }) 84 | } 85 | 86 | #[cfg(test)] 87 | pub fn mocked_new(source: ImageSource, cargo_home: PathBuf) -> Self { 88 | BuilderConfig { 89 | source, 90 | 91 | defaults: Default::default(), 92 | overrides: Default::default(), 93 | 94 | cargo_home, 95 | merged_env: Default::default(), 96 | } 97 | } 98 | 99 | pub fn cargo_home(&self) -> &Path { 100 | &self.cargo_home 101 | } 102 | 103 | pub fn source(&self) -> &ImageSource { 104 | &self.source 105 | } 106 | 107 | pub fn target(&self) -> Option<&str> { 108 | self.overrides.target.as_ref().map(String::as_str) 109 | } 110 | 111 | pub fn user(&self) -> Option<&str> { 112 | self.overrides 113 | .user 114 | .as_ref() 115 | .or_else(|| self.defaults.user.as_ref()) 116 | .map(String::as_str) 117 | } 118 | 119 | pub fn env(&self) -> impl Iterator { 120 | self.merged_env 121 | .iter() 122 | .map(|(key, value)| (key.as_str(), value.as_str())) 123 | } 124 | 125 | pub fn setup_commands(&self) -> Option<&Vec> { 126 | self.overrides.setup_commands.as_ref() 127 | } 128 | } 129 | 130 | impl BaseImageConfig for BuilderConfig { 131 | fn populate_env<'a>(&self, mut command: Command<'a>) -> Command<'a> { 132 | command = command.env("CARGO_TARGET_DIR", TARGET_PATH); 133 | 134 | if let Some(user) = self.user() { 135 | command = command.user(user); 136 | } 137 | 138 | for (name, value) in self.env() { 139 | command = command.env(name, value); 140 | } 141 | 142 | command 143 | .env("CARGO_HOME", self.cargo_home().display().to_string()) 144 | .mount(Mount::SharedCache(self.cargo_home().join("git"))) 145 | .mount(Mount::SharedCache(self.cargo_home().join("registry"))) 146 | } 147 | 148 | fn image_source(&self) -> Option<&ImageSource> { 149 | Some(&self.source) 150 | } 151 | } 152 | 153 | fn guess_cargo_home(user: Option<&str>) -> Option { 154 | match user { 155 | Some("root") => Some("/root/.cargo".into()), 156 | Some(user) => Some(format!("/home/{}/.cargo", user)), 157 | None => None, 158 | } 159 | } 160 | 161 | impl From for BuilderConfigDefaults { 162 | fn from(config: oci::ImageConfig) -> Self { 163 | Self { 164 | env: config.env, 165 | user: config.user, 166 | } 167 | } 168 | } 169 | 170 | #[test] 171 | fn cargo_home_guessing() { 172 | assert_eq!(guess_cargo_home(None), None); 173 | assert_eq!(guess_cargo_home(Some("root")), Some("/root/.cargo".into())); 174 | assert_eq!( 175 | guess_cargo_home(Some("den")), 176 | Some("/home/den/.cargo".into()) 177 | ); 178 | } 179 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use failure::{Error, ResultExt}; 5 | use serde::Serialize; 6 | 7 | use buildkit_frontend::Bridge; 8 | use buildkit_llb::ops::source::ImageSource; 9 | use buildkit_llb::prelude::*; 10 | 11 | use crate::query::Profile; 12 | use crate::shared::{tools, DOCKERFILE, DOCKERFILE_PATH}; 13 | 14 | mod base; 15 | mod builder; 16 | mod output; 17 | 18 | pub use self::base::{BaseConfig, BinaryDefinition, CustomCommand, CustomCommandKind}; 19 | pub use self::builder::BuilderConfig; 20 | pub use self::output::OutputConfig; 21 | pub use crate::frontend::Options; 22 | 23 | const OUTPUT_LAYER_PATH: &str = "/output"; 24 | const OUTPUT_NAME: &str = "build-config.json"; 25 | 26 | #[derive(Debug, Serialize)] 27 | pub struct Config { 28 | builder: BuilderConfig, 29 | output: OutputConfig, 30 | profile: Profile, 31 | manifest_path: PathBuf, 32 | 33 | default_features: bool, 34 | enabled_features: Vec, 35 | 36 | binaries: Vec, 37 | } 38 | 39 | pub trait BaseImageConfig { 40 | fn populate_env<'a>(&self, command: Command<'a>) -> Command<'a>; 41 | fn image_source(&self) -> Option<&ImageSource>; 42 | } 43 | 44 | impl Config { 45 | pub async fn analyse(bridge: &mut Bridge, options: &Options) -> Result { 46 | let metadata_manifest_path = { 47 | options 48 | .filename 49 | .clone() 50 | .unwrap_or_else(|| PathBuf::from("Cargo.toml")) 51 | }; 52 | 53 | let args = vec![ 54 | String::from("--manifest-path"), 55 | PathBuf::from(DOCKERFILE_PATH) 56 | .join(&metadata_manifest_path) 57 | .to_string_lossy() 58 | .into(), 59 | String::from("--output"), 60 | PathBuf::from(OUTPUT_LAYER_PATH) 61 | .join(OUTPUT_NAME) 62 | .to_string_lossy() 63 | .into(), 64 | ]; 65 | 66 | let command = { 67 | Command::run(tools::METADATA_COLLECTOR) 68 | .args(args) 69 | .cwd(DOCKERFILE_PATH) 70 | .mount(Mount::Layer(OutputIdx(0), tools::IMAGE.output(), "/")) 71 | .mount(Mount::ReadOnlyLayer(DOCKERFILE.output(), DOCKERFILE_PATH)) 72 | .mount(Mount::Scratch(OutputIdx(1), OUTPUT_LAYER_PATH)) 73 | .custom_name("Collecting configuration metadata") 74 | }; 75 | 76 | let metadata_layer = { 77 | bridge 78 | .solve(Terminal::with(command.output(1))) 79 | .await 80 | .context("Unable to collect metadata")? 81 | }; 82 | 83 | let metadata = { 84 | bridge 85 | .read_file(&metadata_layer, OUTPUT_NAME, None) 86 | .await 87 | .context("Unable to read metadata output")? 88 | }; 89 | 90 | let base: BaseConfig = { 91 | serde_json::from_slice(&metadata).context("Unable to parse configuration metadata")? 92 | }; 93 | 94 | let builder = { 95 | BuilderConfig::analyse(bridge, base.builder) 96 | .await 97 | .context("Unable to analyse builder image")? 98 | }; 99 | 100 | let output = { 101 | OutputConfig::analyse(bridge, base.output) 102 | .await 103 | .context("Unable to analyse output image")? 104 | }; 105 | 106 | let manifest_path = { 107 | options 108 | .manifest_path 109 | .clone() 110 | .unwrap_or(metadata_manifest_path) 111 | }; 112 | 113 | Ok(Self { 114 | builder, 115 | output, 116 | manifest_path, 117 | 118 | profile: options.profile, 119 | default_features: !options.no_default_features, 120 | enabled_features: options.features.clone(), 121 | 122 | binaries: base.binaries, 123 | }) 124 | } 125 | 126 | #[cfg(test)] 127 | pub fn mocked_new( 128 | builder: BuilderConfig, 129 | output: OutputConfig, 130 | profile: Profile, 131 | binaries: Vec, 132 | ) -> Self { 133 | Self { 134 | builder, 135 | output, 136 | profile, 137 | binaries, 138 | manifest_path: PathBuf::from("Cargo.toml"), 139 | default_features: false, 140 | enabled_features: vec![], 141 | } 142 | } 143 | 144 | pub fn builder(&self) -> &BuilderConfig { 145 | &self.builder 146 | } 147 | 148 | pub fn output(&self) -> &OutputConfig { 149 | &self.output 150 | } 151 | 152 | pub fn find_binary(&self, name: &str) -> Option<&BinaryDefinition> { 153 | self.binaries.iter().find(|bin| bin.name == name) 154 | } 155 | 156 | pub fn profile(&self) -> Profile { 157 | self.profile 158 | } 159 | 160 | pub fn default_features(&self) -> bool { 161 | self.default_features 162 | } 163 | 164 | pub fn enabled_features(&self) -> impl Iterator { 165 | self.enabled_features.iter().map(String::as_str) 166 | } 167 | 168 | pub fn manifest_path(&self) -> &Path { 169 | self.manifest_path.as_path() 170 | } 171 | } 172 | 173 | fn merge_spec_and_overriden_env( 174 | spec_env: &Option>, 175 | overriden_env: &Option>, 176 | ) -> BTreeMap { 177 | match (spec_env.clone(), overriden_env.clone()) { 178 | (Some(mut spec), Some(mut config)) => { 179 | spec.append(&mut config); 180 | spec 181 | } 182 | 183 | (spec, config) => spec.or(config).unwrap_or_default(), 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/query/mod.rs: -------------------------------------------------------------------------------- 1 | use std::iter::once; 2 | 3 | use async_trait::*; 4 | use chrono::prelude::*; 5 | use failure::Error; 6 | 7 | use petgraph::prelude::*; 8 | use petgraph::visit::Reversed; 9 | 10 | use buildkit_frontend::oci::*; 11 | use buildkit_frontend::{Bridge, OutputRef}; 12 | use buildkit_proto::pb; 13 | 14 | use crate::config::Config; 15 | use crate::frontend::Options; 16 | use crate::graph::{BuildGraph, Node}; 17 | use crate::shared::tools; 18 | 19 | mod print; 20 | mod profile; 21 | mod serialization; 22 | mod source; 23 | mod terminal; 24 | 25 | pub use self::profile::Profile; 26 | 27 | use self::print::PrettyPrintQuery; 28 | use self::serialization::SerializationQuery; 29 | use self::source::SourceQuery; 30 | use self::terminal::{BuildOutput, TerminalQuery}; 31 | 32 | pub trait WharfDatabase { 33 | fn config(&self) -> &Config; 34 | 35 | fn graph(&self) -> &StableGraph; 36 | 37 | fn reversed_graph(&self) -> Reversed<&StableGraph> { 38 | Reversed(self.graph()) 39 | } 40 | } 41 | 42 | pub struct WharfStorage<'a> { 43 | graph: &'a StableGraph, 44 | config: &'a Config, 45 | } 46 | 47 | #[async_trait] 48 | pub trait WharfQuery: TerminalQuery { 49 | fn definition(&self) -> Result { 50 | Ok(self.terminal()?.into_definition()) 51 | } 52 | 53 | async fn solve(&self, bridge: &mut Bridge, options: &Options) -> Result { 54 | bridge 55 | .solve_with_cache(self.terminal()?, options.cache_entries()) 56 | .await 57 | } 58 | 59 | fn image_spec(&self) -> Result { 60 | let output = self.config().output(); 61 | 62 | let config = match self.config().profile() { 63 | Profile::ReleaseBinaries | Profile::DebugBinaries => self.config().output().into(), 64 | Profile::ReleaseTests | Profile::DebugTests => ImageConfig { 65 | entrypoint: Some( 66 | once(tools::TEST_RUNNER.into()) 67 | .chain( 68 | self.outputs() 69 | .map(|BuildOutput { path, .. }| path.to_string_lossy().into()), 70 | ) 71 | .collect(), 72 | ), 73 | 74 | env: Some( 75 | output 76 | .env() 77 | .map(|(name, value)| (name.into(), value.into())) 78 | .collect(), 79 | ), 80 | 81 | cmd: None, 82 | user: output.user().map(String::from), 83 | working_dir: None, 84 | 85 | labels: None, 86 | volumes: None, 87 | exposed_ports: None, 88 | stop_signal: None, 89 | }, 90 | }; 91 | 92 | Ok(ImageSpecification { 93 | created: Some(Utc::now()), 94 | author: None, 95 | 96 | // TODO: don't hardcode this 97 | architecture: Architecture::Amd64, 98 | os: OperatingSystem::Linux, 99 | 100 | config: Some(config), 101 | rootfs: None, 102 | history: None, 103 | }) 104 | } 105 | } 106 | 107 | impl<'a> WharfDatabase for WharfStorage<'a> { 108 | fn config(&self) -> &Config { 109 | self.config 110 | } 111 | 112 | fn graph(&self) -> &StableGraph { 113 | self.graph 114 | } 115 | } 116 | 117 | impl<'a> WharfQuery for WharfStorage<'a> {} 118 | impl<'a> TerminalQuery for WharfStorage<'a> {} 119 | impl<'a> SerializationQuery for WharfStorage<'a> {} 120 | impl<'a> SourceQuery for WharfStorage<'a> {} 121 | impl<'a> PrettyPrintQuery for WharfStorage<'a> {} 122 | 123 | impl<'a> WharfStorage<'a> { 124 | pub fn new(graph: &'a BuildGraph, config: &'a Config) -> Self { 125 | Self { 126 | graph: graph.inner(), 127 | config, 128 | } 129 | } 130 | } 131 | 132 | #[cfg(test)] 133 | mod tests { 134 | use serde_json::from_slice; 135 | 136 | use buildkit_llb::prelude::*; 137 | 138 | use super::*; 139 | use crate::config::{BinaryDefinition, BuilderConfig, OutputConfig}; 140 | use crate::plan::RawBuildPlan; 141 | 142 | pub struct MockStorage { 143 | config: Config, 144 | graph: StableGraph, 145 | } 146 | 147 | impl MockStorage { 148 | pub fn mocked(profile: Profile) -> Self { 149 | let graph = BuildGraph::from( 150 | from_slice::(include_bytes!("../../tests/build-plan.json")).unwrap(), 151 | ); 152 | 153 | let builder = BuilderConfig::mocked_new(Source::image("rust"), "/root/.cargo".into()); 154 | let output = OutputConfig::mocked_new(); 155 | 156 | let binaries = vec![ 157 | BinaryDefinition { 158 | name: "bin-1".into(), 159 | destination: "/usr/bin/mock-binary-1".into(), 160 | }, 161 | BinaryDefinition { 162 | name: "bin-3".into(), 163 | destination: "/bin/binary-3".into(), 164 | }, 165 | ]; 166 | 167 | let config = Config::mocked_new(builder, output, profile, binaries); 168 | 169 | Self { 170 | graph: graph.into_inner(), 171 | config, 172 | } 173 | } 174 | } 175 | 176 | impl WharfDatabase for MockStorage { 177 | fn config(&self) -> &Config { 178 | &self.config 179 | } 180 | 181 | fn graph(&self) -> &StableGraph { 182 | &self.graph 183 | } 184 | } 185 | 186 | impl WharfQuery for MockStorage {} 187 | impl TerminalQuery for MockStorage {} 188 | impl SerializationQuery for MockStorage {} 189 | impl SourceQuery for MockStorage {} 190 | impl PrettyPrintQuery for MockStorage {} 191 | } 192 | -------------------------------------------------------------------------------- /cargo-container-tools/src/bin/cargo-build-plan/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | #![deny(clippy::all)] 3 | 4 | use std::env::{current_dir, current_exe}; 5 | use std::fs::File; 6 | use std::io::{copy, BufWriter}; 7 | use std::process::{exit, Command, Stdio}; 8 | use std::sync::Arc; 9 | 10 | use cargo::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor}; 11 | use cargo::core::{Shell, Workspace}; 12 | use cargo::ops::{CompileFilter, CompileOptions, FilterRule, LibRule, Packages}; 13 | use cargo::util::{config::Config, CargoResult}; 14 | 15 | use clap::{crate_authors, crate_version, App, Arg, ArgMatches}; 16 | use failure::{bail, ResultExt}; 17 | 18 | fn main() { 19 | let matches = get_cli_app().get_matches(); 20 | 21 | if let Err(error) = run(&matches) { 22 | cargo::handle_error(&error, &mut Shell::new()); 23 | exit(1); 24 | } 25 | } 26 | 27 | fn get_cli_app() -> App<'static, 'static> { 28 | App::new("cargo-build-plan") 29 | .version(crate_version!()) 30 | .author(crate_authors!()) 31 | .about("Tiny Rust build plan writer") 32 | .args(&[ 33 | { 34 | Arg::with_name("output") 35 | .long("output") 36 | .takes_value(true) 37 | .value_name("PATH") 38 | .default_value("-") 39 | .help("Build plan output path (or '-' for STDOUT)") 40 | }, 41 | { 42 | Arg::with_name("manifest") 43 | .long("manifest-path") 44 | .takes_value(true) 45 | .value_name("PATH") 46 | .default_value("Cargo.toml") 47 | .help("Path to Cargo.toml") 48 | }, 49 | { 50 | Arg::with_name("target") 51 | .long("target") 52 | .takes_value(true) 53 | .value_name("TARGET") 54 | .help("Target triple for which the code is compiled") 55 | }, 56 | { 57 | Arg::with_name("release") 58 | .long("release") 59 | .takes_value(false) 60 | .help("Build artifacts in release mode, with optimizations") 61 | }, 62 | { 63 | Arg::with_name("no_default_features") 64 | .long("no-default-features") 65 | .takes_value(false) 66 | .help("Disable crate default features") 67 | }, 68 | { 69 | Arg::with_name("features") 70 | .long("feature") 71 | .takes_value(true) 72 | .value_name("NAME") 73 | .multiple(true) 74 | .help("Target triple for which the code is compiled") 75 | }, 76 | ]) 77 | } 78 | 79 | fn run(matches: &ArgMatches<'static>) -> CargoResult<()> { 80 | let mut writer = BufWriter::new(match matches.value_of("output").unwrap() { 81 | "-" => return run_stdout(matches), 82 | path => File::create(path)?, 83 | }); 84 | 85 | let mut process = Command::new(current_exe()?); 86 | 87 | process.stdout(Stdio::piped()); 88 | process.stderr(Stdio::inherit()); 89 | 90 | if matches.is_present("release") { 91 | process.arg("--release"); 92 | } 93 | 94 | if matches.is_present("no_default_features") { 95 | process.arg("--no-default-features"); 96 | } 97 | 98 | if let Some(path) = matches.value_of("manifest") { 99 | process.arg("--manifest-path").arg(path); 100 | } 101 | 102 | if let Some(target) = matches.value_of("target") { 103 | process.arg("--target").arg(target); 104 | } 105 | 106 | for feature in matches.values_of("features").unwrap_or_default() { 107 | process.arg("--feature").arg(feature); 108 | } 109 | 110 | let mut child = process.spawn()?; 111 | 112 | copy(&mut child.stdout.take().unwrap(), &mut writer) 113 | .context("Unable to copy child stdout into output")?; 114 | 115 | let exit_code = child.wait().context("Failed to wait on child")?; 116 | 117 | if !exit_code.success() { 118 | bail!("Child process failed"); 119 | } 120 | 121 | Ok(()) 122 | } 123 | 124 | fn run_stdout(matches: &ArgMatches<'static>) -> CargoResult<()> { 125 | let mut config = Config::default()?; 126 | config.configure(0, None, &None, false, true, false, &None, &[])?; 127 | 128 | let mut build_config = BuildConfig::new(&config, Some(1), &None, CompileMode::Build)?; 129 | build_config.release = matches.is_present("release"); 130 | build_config.force_rebuild = true; 131 | build_config.build_plan = true; 132 | build_config.requested_target = matches.value_of("target").map(String::from); 133 | 134 | let features = { 135 | matches 136 | .values_of("features") 137 | .unwrap_or_default() 138 | .map(String::from) 139 | .collect() 140 | }; 141 | 142 | let options = CompileOptions { 143 | config: &config, 144 | build_config, 145 | 146 | features, 147 | all_features: false, 148 | no_default_features: matches.is_present("no_default_features"), 149 | 150 | spec: Packages::All, 151 | filter: CompileFilter::Only { 152 | all_targets: true, 153 | lib: LibRule::Default, 154 | bins: FilterRule::All, 155 | examples: FilterRule::All, 156 | tests: FilterRule::All, 157 | benches: FilterRule::All, 158 | }, 159 | 160 | target_rustdoc_args: None, 161 | target_rustc_args: None, 162 | local_rustdoc_args: None, 163 | export_dir: None, 164 | }; 165 | 166 | let executor: Arc = Arc::new(DefaultExecutor); 167 | let ws = Workspace::new( 168 | ¤t_dir()?.join(matches.value_of("manifest").unwrap()), 169 | &config, 170 | )?; 171 | 172 | cargo::ops::compile_ws(&ws, &options, &executor)?; 173 | Ok(()) 174 | } 175 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/config/output.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use failure::{format_err, Error, ResultExt}; 5 | use log::*; 6 | use serde::Serialize; 7 | 8 | use buildkit_frontend::oci::{self, Signal}; 9 | use buildkit_frontend::Bridge; 10 | use buildkit_llb::ops::source::ImageSource; 11 | use buildkit_llb::prelude::*; 12 | 13 | use super::base::{BaseOutputConfig, CustomCommand}; 14 | use super::{merge_spec_and_overriden_env, BaseImageConfig}; 15 | 16 | #[derive(Debug, Serialize)] 17 | pub struct OutputConfig { 18 | #[serde(skip_serializing)] 19 | source: Option, 20 | 21 | overrides: BaseOutputConfig, 22 | defaults: OutputConfigDefaults, 23 | merged_env: BTreeMap, 24 | } 25 | 26 | #[derive(Debug, Serialize, Default)] 27 | struct OutputConfigDefaults { 28 | env: Option>, 29 | user: Option, 30 | workdir: Option, 31 | entrypoint: Option>, 32 | cmd: Option>, 33 | stop_signal: Option, 34 | } 35 | 36 | impl OutputConfig { 37 | pub async fn analyse(bridge: &mut Bridge, config: BaseOutputConfig) -> Result { 38 | if config.image == "scratch" { 39 | return Ok(Self::scratch(config)); 40 | } 41 | 42 | let source = config.source(); 43 | 44 | let (digest, spec) = { 45 | bridge 46 | .resolve_image_config(&source, Some("Resolving output image")) 47 | .await 48 | .context("Unable to resolve image config")? 49 | }; 50 | 51 | debug!("resolved output image config: {:#?}", spec.config); 52 | 53 | let spec = { 54 | spec.config 55 | .ok_or_else(|| format_err!("Missing source image config"))? 56 | }; 57 | 58 | let merged_env = merge_spec_and_overriden_env(&spec.env, &config.env); 59 | 60 | let source = if !digest.is_empty() { 61 | source.with_digest(digest) 62 | } else { 63 | source 64 | }; 65 | 66 | Ok(Self { 67 | source: Some(source), 68 | overrides: config, 69 | defaults: spec.into(), 70 | merged_env, 71 | }) 72 | } 73 | 74 | fn scratch(config: BaseOutputConfig) -> Self { 75 | Self { 76 | source: None, 77 | merged_env: config.env.clone().unwrap_or_default(), 78 | overrides: config, 79 | defaults: Default::default(), 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | pub fn mocked_new() -> Self { 85 | Self { 86 | source: None, 87 | 88 | overrides: Default::default(), 89 | defaults: Default::default(), 90 | merged_env: Default::default(), 91 | } 92 | } 93 | 94 | pub fn layer_path

(&self, path: P) -> LayerPath

95 | where 96 | P: AsRef, 97 | { 98 | match self.source { 99 | Some(ref source) => LayerPath::Other(source.output(), path), 100 | None => LayerPath::Scratch(path), 101 | } 102 | } 103 | 104 | pub fn user(&self) -> Option<&str> { 105 | self.overrides 106 | .user 107 | .as_ref() 108 | .or_else(|| self.defaults.user.as_ref()) 109 | .map(String::as_str) 110 | } 111 | 112 | pub fn env(&self) -> impl Iterator { 113 | self.merged_env 114 | .iter() 115 | .map(|(key, value)| (key.as_str(), value.as_str())) 116 | } 117 | 118 | pub fn pre_install_commands(&self) -> Option<&Vec> { 119 | self.overrides.pre_install_commands.as_ref() 120 | } 121 | 122 | pub fn post_install_commands(&self) -> Option<&Vec> { 123 | self.overrides.post_install_commands.as_ref() 124 | } 125 | } 126 | 127 | impl BaseImageConfig for OutputConfig { 128 | fn populate_env<'a>(&self, mut command: Command<'a>) -> Command<'a> { 129 | if let Some(user) = self.user() { 130 | command = command.user(user); 131 | } 132 | 133 | for (name, value) in self.env() { 134 | command = command.env(name, value); 135 | } 136 | 137 | command 138 | } 139 | 140 | fn image_source(&self) -> Option<&ImageSource> { 141 | self.source.as_ref() 142 | } 143 | } 144 | 145 | impl From for OutputConfigDefaults { 146 | fn from(config: oci::ImageConfig) -> Self { 147 | Self { 148 | env: config.env, 149 | user: config.user, 150 | entrypoint: config.entrypoint, 151 | cmd: config.cmd, 152 | workdir: config.working_dir, 153 | stop_signal: config.stop_signal, 154 | } 155 | } 156 | } 157 | 158 | impl<'a> Into for &'a OutputConfig { 159 | fn into(self) -> oci::ImageConfig { 160 | oci::ImageConfig { 161 | entrypoint: self 162 | .overrides 163 | .entrypoint 164 | .clone() 165 | .or_else(|| self.defaults.entrypoint.clone()), 166 | 167 | cmd: self 168 | .overrides 169 | .args 170 | .clone() 171 | .or_else(|| self.defaults.cmd.clone()), 172 | 173 | user: self 174 | .overrides 175 | .user 176 | .clone() 177 | .or_else(|| self.defaults.user.clone()), 178 | 179 | working_dir: self 180 | .overrides 181 | .workdir 182 | .clone() 183 | .or_else(|| self.defaults.workdir.clone()), 184 | 185 | env: Some(self.merged_env.clone()), 186 | labels: self.overrides.labels.clone(), 187 | volumes: self.overrides.volumes.clone(), 188 | exposed_ports: self.overrides.expose.clone(), 189 | stop_signal: self.overrides.stop_signal.or(self.defaults.stop_signal), 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/graph/ops.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::path::PathBuf; 3 | 4 | use log::*; 5 | use petgraph::prelude::*; 6 | 7 | use super::node::BuildScriptMergeResult::*; 8 | use super::node::{Node, NodeKind, PrimitiveNodeKind}; 9 | 10 | pub fn merge_buildscript_nodes(graph: &mut StableGraph) { 11 | debug!("merging build script nodes"); 12 | let indices = graph.node_indices().collect::>(); 13 | let mut nodes_for_removal = BTreeSet::new(); 14 | 15 | for index in indices { 16 | let mut dependency_build_scripts = BTreeSet::new(); 17 | 18 | match graph.node_weight(index) { 19 | Some(node) if node.kind() == NodeKind::Primitive(PrimitiveNodeKind::BuildScriptRun) => { 20 | let mut compile_indexes = { 21 | graph 22 | .neighbors_directed(index, Direction::Incoming) 23 | .detach() 24 | }; 25 | 26 | let run_index = index; 27 | while let Some(compile_index) = compile_indexes.next_node(&graph) { 28 | let compile_node = graph[compile_index].clone(); 29 | 30 | match graph[run_index].add_buildscript_compile_node(compile_node) { 31 | Ok => { 32 | debug!( 33 | "merged buildscript compile '{:?}' with run '{:?}'", 34 | compile_index, run_index 35 | ); 36 | 37 | move_edges(graph, compile_index, run_index); 38 | nodes_for_removal.insert(compile_index); 39 | break; 40 | } 41 | 42 | DependencyBuildScript => { 43 | dependency_build_scripts.insert(compile_index); 44 | } 45 | 46 | AlreadyMerged => { 47 | break; 48 | } 49 | } 50 | } 51 | } 52 | 53 | _ => { 54 | debug!("skipping non-buildscript node: {:?}", index); 55 | } 56 | } 57 | 58 | for dep_index in dependency_build_scripts { 59 | debug!("adding dependency buildscript: {:?}", index); 60 | 61 | let dep = graph[dep_index].clone(); 62 | graph[index].add_dependency_buildscript(dep); 63 | graph.add_edge(dep_index, index, ()); 64 | } 65 | } 66 | 67 | for index in nodes_for_removal { 68 | debug!("removing old buildscript node: {:#?}", index); 69 | graph.remove_node(index); 70 | } 71 | } 72 | 73 | pub fn apply_buildscript_outputs(graph: &mut StableGraph) { 74 | debug!("applying build script output"); 75 | let indices = graph.node_indices().collect::>(); 76 | 77 | for index in indices { 78 | if let Some(node) = graph.node_weight(index) { 79 | if let NodeKind::MergedBuildScript(path) = node.kind() { 80 | let path = PathBuf::from(path); 81 | let mut dependenents = { 82 | graph 83 | .neighbors_directed(index, Direction::Outgoing) 84 | .detach() 85 | }; 86 | 87 | while let Some(dependent) = dependenents.next_node(&graph) { 88 | match graph[dependent].kind() { 89 | NodeKind::MergedBuildScript { .. } => {} 90 | 91 | _ => { 92 | debug!("transforming buildscript consumer: {:#?}", dependent); 93 | graph[dependent].transform_into_buildscript_consumer(&path); 94 | } 95 | } 96 | } 97 | } else { 98 | debug!("skipping non-buildscript node: {:?}", index); 99 | } 100 | } 101 | } 102 | } 103 | 104 | fn move_edges(graph: &mut StableGraph, from: NodeIndex, to: NodeIndex) { 105 | debug!("moving edges from '{:?}' to '{:?}'", from, to); 106 | 107 | let mut dependencies = graph.neighbors_directed(from, Direction::Incoming).detach(); 108 | 109 | while let Some(dependency) = dependencies.next_node(&graph) { 110 | graph.add_edge(dependency, to, ()); 111 | } 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use pretty_assertions::assert_eq; 117 | use serde_json::from_slice; 118 | 119 | use super::*; 120 | use crate::graph::NodeKind; 121 | use crate::plan::RawBuildPlan; 122 | 123 | #[test] 124 | fn buildscript_merging() { 125 | let mut graph = mock_buildscript_graph(); 126 | assert_eq!(graph.node_count(), 19); 127 | 128 | merge_buildscript_nodes(&mut graph); 129 | assert_eq!(graph.node_count(), 16); 130 | 131 | let buildscript_nodes = { 132 | graph 133 | .node_indices() 134 | .filter_map(|index| match graph[index].kind() { 135 | NodeKind::MergedBuildScript { .. } => Some(graph[index].clone()), 136 | _ => None, 137 | }) 138 | }; 139 | 140 | assert_eq!( 141 | buildscript_nodes.collect::>(), 142 | from_slice::>(include_bytes!( 143 | "../../tests/merged-buildscript-producers.json" 144 | )) 145 | .unwrap(), 146 | ); 147 | } 148 | 149 | #[test] 150 | fn buildscript_result_applying() { 151 | let mut graph = mock_buildscript_graph(); 152 | assert_eq!(graph.node_count(), 19); 153 | 154 | merge_buildscript_nodes(&mut graph); 155 | apply_buildscript_outputs(&mut graph); 156 | assert_eq!(graph.node_count(), 16); 157 | 158 | let consumer_nodes = { 159 | graph 160 | .node_indices() 161 | .filter_map(|index| match graph[index].kind() { 162 | NodeKind::BuildScriptOutputConsumer(_, _) => Some(graph[index].clone()), 163 | _ => None, 164 | }) 165 | }; 166 | 167 | assert_eq!( 168 | consumer_nodes.collect::>(), 169 | from_slice::>(include_bytes!( 170 | "../../tests/merged-buildscript-consumers.json" 171 | )) 172 | .unwrap(), 173 | ); 174 | } 175 | 176 | fn mock_buildscript_graph() -> StableGraph { 177 | StableGraph::from( 178 | from_slice::(include_bytes!("../../tests/build-plan.json")).unwrap(), 179 | ) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/query/serialization.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use petgraph::prelude::*; 4 | use petgraph::visit::{Topo, Walker}; 5 | 6 | use buildkit_llb::prelude::*; 7 | 8 | use crate::config::{BaseImageConfig, Config}; 9 | use crate::graph::{Node, NodeCommand, NodeCommandDetails, NodeKind, PrimitiveNodeKind}; 10 | use crate::shared::{tools, CONTEXT, CONTEXT_PATH, TARGET_PATH}; 11 | 12 | use super::print::{PrettyPrintQuery, PrintKind}; 13 | use super::{SourceQuery, WharfDatabase}; 14 | 15 | type NodesCache<'a> = Vec>>; 16 | 17 | pub trait SerializationQuery: WharfDatabase + SourceQuery + PrettyPrintQuery { 18 | fn serialize_all_nodes(&self) -> NodesCache<'_> { 19 | let mut nodes = vec![None; self.graph().capacity().0]; 20 | let mut deps = vec![None; self.graph().capacity().0]; 21 | 22 | let mut visitor = Topo::new(self.graph()); 23 | 24 | while let Some(index) = visitor.next(self.graph()) { 25 | self.maybe_cache_dependencies(&nodes, &mut deps, index); 26 | 27 | let (node_llb, output) = self.serialize_node( 28 | &self.config(), 29 | self.builder_source().clone().unwrap(), 30 | deps[index.index()].as_ref().unwrap(), 31 | self.graph().node_weight(index).unwrap(), 32 | ); 33 | 34 | nodes[index.index()] = Some(node_llb.ref_counted().output(output.0)); 35 | } 36 | 37 | nodes 38 | } 39 | 40 | fn maybe_cache_dependencies<'a>( 41 | &self, 42 | nodes: &[Option>], 43 | deps: &mut Vec>>>, 44 | index: NodeIndex, 45 | ) { 46 | if deps[index.index()].is_some() { 47 | return; 48 | } 49 | 50 | let local_deps = DfsPostOrder::new(&self.reversed_graph(), index) 51 | .iter(self.reversed_graph()) 52 | .filter(|dep_index| dep_index.index() != index.index()) 53 | .flat_map(|dep_index| { 54 | self.graph() 55 | .node_weight(dep_index) 56 | .unwrap() 57 | .outputs_iter() 58 | .map(move |path| { 59 | Mount::ReadOnlySelector( 60 | nodes[dep_index.index()].clone().unwrap(), 61 | path.into(), 62 | path.strip_prefix(TARGET_PATH).unwrap().into(), 63 | ) 64 | }) 65 | }); 66 | 67 | deps[index.index()] = Some(local_deps.collect()); 68 | } 69 | 70 | fn serialize_node<'a>( 71 | &self, 72 | config: &'a Config, 73 | source: OperationOutput<'a>, 74 | deps: &[Mount<'a, PathBuf>], 75 | node: &'a Node, 76 | ) -> (Command<'a>, OutputIdx) { 77 | let (mut command, index) = match node.command() { 78 | NodeCommand::Simple(ref details) => self.serialize_command( 79 | config, 80 | source, 81 | self.create_target_dirs(node.output_dirs_iter()), 82 | details, 83 | ), 84 | 85 | NodeCommand::WithBuildscript { compile, run } => { 86 | let (mut compile_command, compile_index) = self.serialize_command( 87 | config, 88 | source.clone(), 89 | self.create_target_dirs(node.output_dirs_iter()), 90 | compile, 91 | ); 92 | 93 | compile_command = compile_command.custom_name( 94 | self.pretty_print(PrintKind::CompileBuildScript(node.package_name())), 95 | ); 96 | 97 | for mount in deps { 98 | compile_command = compile_command.mount(mount.clone()); 99 | } 100 | 101 | self.serialize_command( 102 | config, 103 | source, 104 | compile_command.ref_counted().output(compile_index.0), 105 | run, 106 | ) 107 | } 108 | }; 109 | 110 | for mount in deps { 111 | command = command.mount(mount.clone()); 112 | } 113 | 114 | if let NodeKind::BuildScriptOutputConsumer(_, _) = node.kind() { 115 | command = command.mount(Mount::ReadOnlySelector( 116 | tools::IMAGE.output(), 117 | tools::BUILDSCRIPT_APPLY, 118 | tools::BUILDSCRIPT_APPLY, 119 | )); 120 | } 121 | 122 | if let NodeKind::MergedBuildScript(_) = node.kind() { 123 | command = command.mount(Mount::ReadOnlySelector( 124 | tools::IMAGE.output(), 125 | tools::BUILDSCRIPT_CAPTURE, 126 | tools::BUILDSCRIPT_CAPTURE, 127 | )); 128 | } 129 | 130 | let print_kind = match node.kind() { 131 | NodeKind::BuildScriptOutputConsumer(PrimitiveNodeKind::Binary, _) => { 132 | PrintKind::CompileBinary(node.binary_name().unwrap()) 133 | } 134 | 135 | NodeKind::Primitive(PrimitiveNodeKind::Binary) => { 136 | PrintKind::CompileBinary(node.binary_name().unwrap()) 137 | } 138 | 139 | NodeKind::BuildScriptOutputConsumer(PrimitiveNodeKind::Test, _) => { 140 | PrintKind::CompileTest(node.test_name().unwrap()) 141 | } 142 | 143 | NodeKind::Primitive(PrimitiveNodeKind::Test) => { 144 | PrintKind::CompileTest(node.test_name().unwrap()) 145 | } 146 | 147 | NodeKind::MergedBuildScript(_) => PrintKind::RunBuildScript(node.package_name()), 148 | 149 | _ => PrintKind::CompileCrate(node.package_name()), 150 | }; 151 | 152 | (command.custom_name(self.pretty_print(print_kind)), index) 153 | } 154 | 155 | fn serialize_command<'a, 'b: 'a>( 156 | &self, 157 | config: &'a Config, 158 | source: OperationOutput<'a>, 159 | target_layer: OperationOutput<'b>, 160 | command: &'b NodeCommandDetails, 161 | ) -> (Command<'a>, OutputIdx) { 162 | let builder = config.builder(); 163 | 164 | let mut command_llb = { 165 | builder 166 | .populate_env(Command::run(&command.program)) 167 | .cwd(&command.cwd) 168 | .args(&command.args) 169 | .env_iter(&command.env) 170 | .mount(Mount::ReadOnlyLayer(source, "/")) 171 | .mount(Mount::Layer(OutputIdx(0), target_layer, TARGET_PATH)) 172 | .mount(Mount::Scratch(OutputIdx(1), "/tmp")) 173 | }; 174 | 175 | if command.cwd.starts_with(CONTEXT_PATH) { 176 | command_llb = command_llb.mount(Mount::ReadOnlyLayer(CONTEXT.output(), CONTEXT_PATH)); 177 | } 178 | 179 | (command_llb, OutputIdx(0)) 180 | } 181 | 182 | fn create_target_dirs<'a>( 183 | &self, 184 | outputs: impl Iterator, 185 | ) -> OperationOutput<'static> { 186 | let mut operation = FileSystem::sequence(); 187 | 188 | for output in outputs { 189 | let path = output.strip_prefix(TARGET_PATH).unwrap(); 190 | 191 | let (index, layer_path) = match operation.last_output_index() { 192 | Some(index) => (index + 1, LayerPath::Own(OwnOutputIdx(index), path)), 193 | None => (0, LayerPath::Scratch(path)), 194 | }; 195 | 196 | let inner = FileSystem::mkdir(OutputIdx(index), layer_path).make_parents(true); 197 | 198 | operation = operation.append(inner); 199 | } 200 | 201 | operation.ref_counted().last_output().unwrap() 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/tests/merged-buildscript-producers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "package_name": "libc", 4 | "package_version": "0.2.62", 5 | "command": { 6 | "WithBuildscript": { 7 | "compile": { 8 | "env": {}, 9 | "program": "rustc", 10 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62", 11 | "args": [ 12 | "--crate-name", 13 | "build_script_build", 14 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62/build.rs" 15 | ] 16 | }, 17 | "run": { 18 | "env": { 19 | "HOST": "x86_64-unknown-linux-gnu", 20 | "OUT_DIR": "/target/debug/build/libc-84e59eda7c0f49e4/out", 21 | "TARGET": "x86_64-unknown-linux-gnu" 22 | }, 23 | "program": "/usr/local/bin/cargo-buildscript-capture", 24 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62", 25 | "args": [ 26 | "--", 27 | "/target/debug/build/libc-87cd93070ba33df1/build_script_build-87cd93070ba33df1" 28 | ] 29 | } 30 | } 31 | }, 32 | "kind": { 33 | "MergedBuildScript": "/target/debug/build/libc-84e59eda7c0f49e4/out" 34 | }, 35 | "outputs": [ 36 | "/target/debug/build/libc-84e59eda7c0f49e4/out" 37 | ], 38 | "output_dirs": [ 39 | "/target/debug/build/libc-87cd93070ba33df1", 40 | "/target/debug/build/libc-84e59eda7c0f49e4/out" 41 | ], 42 | "links": {} 43 | }, 44 | { 45 | "package_name": "openssl-sys", 46 | "package_version": "0.9.50", 47 | "command": { 48 | "WithBuildscript": { 49 | "compile": { 50 | "env": {}, 51 | "program": "rustc", 52 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50", 53 | "args": [ 54 | "--crate-name", 55 | "build_script_main", 56 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50/build/main.rs" 57 | ] 58 | }, 59 | "run": { 60 | "env": { 61 | "HOST": "x86_64-unknown-linux-gnu", 62 | "OUT_DIR": "/target/debug/build/openssl-sys-acfa4371985ef2b0/out", 63 | "TARGET": "x86_64-unknown-linux-gnu" 64 | }, 65 | "program": "/usr/local/bin/cargo-buildscript-capture", 66 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50", 67 | "args": [ 68 | "--", 69 | "/target/debug/build/openssl-sys-87b9b219fe0676b7/build_script_main-87b9b219fe0676b7" 70 | ] 71 | } 72 | } 73 | }, 74 | "kind": { 75 | "MergedBuildScript": "/target/debug/build/openssl-sys-acfa4371985ef2b0/out" 76 | }, 77 | "outputs": [ 78 | "/target/debug/build/openssl-sys-acfa4371985ef2b0/out" 79 | ], 80 | "output_dirs": [ 81 | "/target/debug/build/openssl-sys-87b9b219fe0676b7", 82 | "/target/debug/build/openssl-sys-acfa4371985ef2b0/out" 83 | ], 84 | "links": {} 85 | }, 86 | { 87 | "package_name": "openssl-sys", 88 | "package_version": "0.9.50", 89 | "command": { 90 | "WithBuildscript": { 91 | "compile": { 92 | "env": {}, 93 | "program": "rustc", 94 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50", 95 | "args": [ 96 | "--crate-name", 97 | "build_script_main", 98 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50/build/main.rs" 99 | ] 100 | }, 101 | "run": { 102 | "env": { 103 | "HOST": "x86_64-unknown-linux-gnu", 104 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out", 105 | "TARGET": "x86_64-unknown-linux-musl" 106 | }, 107 | "program": "/usr/local/bin/cargo-buildscript-capture", 108 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50", 109 | "args": [ 110 | "--", 111 | "/target/debug/build/openssl-sys-87b9b219fe0676b7/build_script_main-87b9b219fe0676b7" 112 | ] 113 | } 114 | } 115 | }, 116 | "kind": { 117 | "MergedBuildScript": "/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out" 118 | }, 119 | "outputs": [ 120 | "/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out" 121 | ], 122 | "output_dirs": [ 123 | "/target/debug/build/openssl-sys-87b9b219fe0676b7", 124 | "/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out" 125 | ], 126 | "links": {} 127 | }, 128 | { 129 | "package_name": "multi-bin", 130 | "package_version": "0.1.0", 131 | "command": { 132 | "WithBuildscript": { 133 | "compile": { 134 | "env": {}, 135 | "program": "rustc", 136 | "cwd": "/context", 137 | "args": [ 138 | "--edition=2018", 139 | "--crate-name", 140 | "build_script_build", 141 | "build.rs" 142 | ] 143 | }, 144 | "run": { 145 | "env": { 146 | "HOST": "x86_64-unknown-linux-gnu", 147 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out", 148 | "TARGET": "x86_64-unknown-linux-musl" 149 | }, 150 | "program": "/usr/local/bin/cargo-buildscript-capture", 151 | "cwd": "/context", 152 | "args": [ 153 | "--with-metadata-from=/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out", 154 | "--", 155 | "/target/debug/build/multi-bin-6b2014bd97cc5650/build_script_build-6b2014bd97cc5650" 156 | ] 157 | } 158 | } 159 | }, 160 | "kind": { 161 | "MergedBuildScript": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 162 | }, 163 | "outputs": [ 164 | "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 165 | ], 166 | "output_dirs": [ 167 | "/target/debug/build/multi-bin-6b2014bd97cc5650", 168 | "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 169 | ], 170 | "links": {} 171 | }, 172 | { 173 | "package_name": "libc", 174 | "package_version": "0.2.62", 175 | "command": { 176 | "WithBuildscript": { 177 | "compile": { 178 | "env": {}, 179 | "program": "rustc", 180 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62", 181 | "args": [ 182 | "--crate-name", 183 | "build_script_build", 184 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62/build.rs" 185 | ] 186 | }, 187 | "run": { 188 | "env": { 189 | "HOST": "x86_64-unknown-linux-gnu", 190 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/libc-881546618a069ec4/out", 191 | "TARGET": "x86_64-unknown-linux-musl" 192 | }, 193 | "program": "/usr/local/bin/cargo-buildscript-capture", 194 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62", 195 | "args": [ 196 | "--", 197 | "/target/debug/build/libc-87cd93070ba33df1/build_script_build-87cd93070ba33df1" 198 | ] 199 | } 200 | } 201 | }, 202 | "kind": { 203 | "MergedBuildScript": "/target/x86_64-unknown-linux-musl/debug/build/libc-881546618a069ec4/out" 204 | }, 205 | "outputs": [ 206 | "/target/x86_64-unknown-linux-musl/debug/build/libc-881546618a069ec4/out" 207 | ], 208 | "output_dirs": [ 209 | "/target/debug/build/libc-87cd93070ba33df1", 210 | "/target/x86_64-unknown-linux-musl/debug/build/libc-881546618a069ec4/out" 211 | ], 212 | "links": {} 213 | } 214 | ] 215 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/query/terminal.rs: -------------------------------------------------------------------------------- 1 | use std::iter::empty; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use either::Either; 5 | use failure::{bail, Error}; 6 | use log::*; 7 | use petgraph::prelude::*; 8 | 9 | use buildkit_llb::prelude::*; 10 | 11 | use crate::config::BaseImageConfig; 12 | use crate::graph::{Node, NodeKind, PrimitiveNodeKind}; 13 | use crate::shared::{tools, TARGET_PATH}; 14 | 15 | use super::print::{PrettyPrintQuery, PrintKind}; 16 | use super::{Profile, SerializationQuery, WharfDatabase}; 17 | 18 | pub struct OutputMapping<'a> { 19 | from: LayerPath<'a, PathBuf>, 20 | to: PathBuf, 21 | } 22 | 23 | type NodesCache<'a> = Vec>>; 24 | 25 | pub struct BuildOutput<'a> { 26 | pub index: NodeIndex, 27 | pub node: &'a Node, 28 | pub path: PathBuf, 29 | } 30 | 31 | pub trait TerminalQuery: WharfDatabase + SerializationQuery + PrettyPrintQuery { 32 | fn terminal(&self) -> Result, Error> { 33 | debug!("serializing all nodes"); 34 | let nodes = self.serialize_all_nodes(); 35 | let outputs = self.mapped_outputs(nodes); 36 | 37 | if outputs.is_empty() { 38 | bail!("Nothing to do - no binaries were found"); 39 | } 40 | 41 | debug!("preparing the final operation"); 42 | 43 | let operation = FileSystem::sequence().custom_name("Composing the output image"); 44 | let operation = { 45 | outputs.into_iter().fold(operation, |output, mapping| { 46 | let (index, layer_path) = match output.last_output_index() { 47 | Some(index) => (index + 1, LayerPath::Own(OwnOutputIdx(index), mapping.to)), 48 | None => (0, self.output_layer_path(mapping.to)), 49 | }; 50 | 51 | output.append( 52 | FileSystem::copy() 53 | .from(mapping.from) 54 | .to(OutputIdx(index), layer_path) 55 | .create_path(true), 56 | ) 57 | }) 58 | }; 59 | 60 | let mut commands_iter = { 61 | self.config() 62 | .output() 63 | .post_install_commands() 64 | .map(|commands| Either::Left(commands.iter().map(From::from))) 65 | .unwrap_or_else(|| Either::Right(empty())) 66 | }; 67 | 68 | if let Some((name, args, display)) = commands_iter.next() { 69 | let mut output = { 70 | self.config() 71 | .output() 72 | .populate_env(Command::run(name)) 73 | .args(args.iter()) 74 | .mount(Mount::Layer( 75 | OutputIdx(0), 76 | operation.ref_counted().last_output().unwrap(), 77 | "/", 78 | )) 79 | .custom_name(self.pretty_print(PrintKind::CustomCommand(display))) 80 | .ref_counted() 81 | .output(0) 82 | }; 83 | 84 | for (name, args, display) in commands_iter { 85 | output = { 86 | self.config() 87 | .output() 88 | .populate_env(Command::run(name)) 89 | .args(args.iter()) 90 | .mount(Mount::Layer(OutputIdx(0), output, "/")) 91 | .custom_name(self.pretty_print(PrintKind::CustomCommand(display))) 92 | .ref_counted() 93 | .output(0) 94 | }; 95 | } 96 | 97 | return Ok(Terminal::with(output)); 98 | } 99 | 100 | Ok(Terminal::with( 101 | operation.ref_counted().last_output().unwrap(), 102 | )) 103 | } 104 | 105 | fn output_layer_path

(&self, path: P) -> LayerPath<'_, P> 106 | where 107 | P: AsRef, 108 | { 109 | match self.output_source() { 110 | Some(ref output) => LayerPath::Other(output.clone(), path), 111 | None => LayerPath::Scratch(path), 112 | } 113 | } 114 | 115 | fn outputs(&self) -> Box> + '_> { 116 | Box::new(match self.config().profile() { 117 | Profile::ReleaseBinaries | Profile::DebugBinaries => Either::Left( 118 | self.graph() 119 | .node_indices() 120 | .map(move |index| (index, self.graph().node_weight(index).unwrap())) 121 | .filter_map(move |(index, node)| { 122 | match self.config().find_binary(node.binary_name()?) { 123 | Some(found) => { 124 | Some(BuildOutput::new(index, node, found.destination.clone())) 125 | } 126 | 127 | None => None, 128 | } 129 | }), 130 | ), 131 | 132 | Profile::ReleaseTests | Profile::DebugTests => Either::Right( 133 | self.graph() 134 | .node_indices() 135 | .map(move |index| (index, self.graph().node_weight(index).unwrap())) 136 | .filter(|(_, node)| match node.kind() { 137 | NodeKind::Primitive(PrimitiveNodeKind::Test) => true, 138 | NodeKind::BuildScriptOutputConsumer(PrimitiveNodeKind::Test, _) => true, 139 | 140 | _ => false, 141 | }) 142 | .map(|(index, node)| { 143 | let to: PathBuf = { 144 | node.outputs_iter() 145 | .next() 146 | .unwrap() 147 | .strip_prefix(TARGET_PATH) 148 | .unwrap() 149 | .into() 150 | }; 151 | 152 | BuildOutput::new(index, node, PathBuf::from("/test").join(to)) 153 | }), 154 | ), 155 | }) 156 | } 157 | 158 | fn mapped_outputs<'a>(&self, nodes: NodesCache<'a>) -> Vec> { 159 | let profile = self.config().profile(); 160 | let mut mapped_outputs: Vec<_> = { 161 | self.outputs() 162 | .map(move |BuildOutput { index, node, path }| { 163 | let from = LayerPath::Other( 164 | nodes[index.index()].clone().unwrap(), 165 | node.outputs_iter() 166 | .next() 167 | .unwrap() 168 | .strip_prefix(TARGET_PATH) 169 | .unwrap() 170 | .into(), 171 | ); 172 | 173 | OutputMapping { from, to: path } 174 | }) 175 | .collect() 176 | }; 177 | 178 | if profile == Profile::ReleaseTests || profile == Profile::DebugTests { 179 | mapped_outputs.push(OutputMapping { 180 | from: LayerPath::Other(tools::IMAGE.output(), tools::TEST_RUNNER.into()), 181 | to: tools::TEST_RUNNER.into(), 182 | }); 183 | } 184 | 185 | mapped_outputs 186 | } 187 | } 188 | 189 | impl<'a> BuildOutput<'a> { 190 | pub fn new(index: NodeIndex, node: &'a Node, path: PathBuf) -> Self { 191 | Self { index, node, path } 192 | } 193 | } 194 | 195 | #[cfg(test)] 196 | mod tests { 197 | use super::*; 198 | use crate::query::tests::MockStorage; 199 | 200 | #[test] 201 | fn query_binaries() { 202 | let storage = MockStorage::mocked(Profile::ReleaseBinaries); 203 | 204 | assert_eq!( 205 | storage 206 | .outputs() 207 | .map(|BuildOutput { index, path, .. }| (index, path)) 208 | .collect::>(), 209 | vec![(NodeIndex::new(15), "/usr/bin/mock-binary-1".into())] 210 | ); 211 | } 212 | 213 | #[test] 214 | fn query_tests() { 215 | let storage = MockStorage::mocked(Profile::ReleaseTests); 216 | 217 | assert_eq!( 218 | storage 219 | .outputs() 220 | .map(|BuildOutput { index, path, .. }| (index, path)) 221 | .collect::>(), 222 | vec![ 223 | ( 224 | NodeIndex::new(16), 225 | "/test/x86_64-unknown-linux-musl/debug/deps/bin_1-5b5e8a9adfa6ccf4".into() 226 | ), 227 | ( 228 | NodeIndex::new(18), 229 | "/test/x86_64-unknown-linux-musl/debug/deps/bin_2-92b8326325c2f547".into() 230 | ), 231 | ] 232 | ); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/tests/merged-buildscript-consumers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "package_name": "libc", 4 | "package_version": "0.2.62", 5 | "command": { 6 | "Simple": { 7 | "env": { 8 | "OUT_DIR": "/target/debug/build/libc-84e59eda7c0f49e4/out" 9 | }, 10 | "program": "/usr/local/bin/cargo-buildscript-apply", 11 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62", 12 | "args": [ 13 | "--", 14 | "rustc", 15 | "--crate-name", 16 | "libc", 17 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62/src/lib.rs" 18 | ] 19 | } 20 | }, 21 | "kind": { 22 | "BuildScriptOutputConsumer": [ 23 | "Other", 24 | "/target/debug/build/libc-84e59eda7c0f49e4/out" 25 | ] 26 | }, 27 | "outputs": [ 28 | "/target/debug/deps/liblibc-c5d8077f198ec4fc.rlib", 29 | "/target/debug/deps/liblibc-c5d8077f198ec4fc.rmeta" 30 | ], 31 | "output_dirs": [ 32 | "/target/debug/deps", 33 | "/target/debug/deps" 34 | ], 35 | "links": {} 36 | }, 37 | { 38 | "package_name": "openssl-sys", 39 | "package_version": "0.9.50", 40 | "command": { 41 | "Simple": { 42 | "env": { 43 | "OUT_DIR": "/target/debug/build/openssl-sys-acfa4371985ef2b0/out" 44 | }, 45 | "program": "/usr/local/bin/cargo-buildscript-apply", 46 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50", 47 | "args": [ 48 | "--", 49 | "rustc", 50 | "--crate-name", 51 | "openssl_sys", 52 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50/src/lib.rs" 53 | ] 54 | } 55 | }, 56 | "kind": { 57 | "BuildScriptOutputConsumer": [ 58 | "Other", 59 | "/target/debug/build/openssl-sys-acfa4371985ef2b0/out" 60 | ] 61 | }, 62 | "outputs": [ 63 | "/target/debug/deps/libopenssl_sys-19eb4c8cc1419cae.rlib", 64 | "/target/debug/deps/libopenssl_sys-19eb4c8cc1419cae.rmeta" 65 | ], 66 | "output_dirs": [ 67 | "/target/debug/deps", 68 | "/target/debug/deps" 69 | ], 70 | "links": {} 71 | }, 72 | { 73 | "package_name": "libc", 74 | "package_version": "0.2.62", 75 | "command": { 76 | "Simple": { 77 | "env": { 78 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/libc-881546618a069ec4/out" 79 | }, 80 | "program": "/usr/local/bin/cargo-buildscript-apply", 81 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62", 82 | "args": [ 83 | "--", 84 | "rustc", 85 | "--crate-name", 86 | "libc", 87 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62/src/lib.rs" 88 | ] 89 | } 90 | }, 91 | "kind": { 92 | "BuildScriptOutputConsumer": [ 93 | "Other", 94 | "/target/x86_64-unknown-linux-musl/debug/build/libc-881546618a069ec4/out" 95 | ] 96 | }, 97 | "outputs": [ 98 | "/target/x86_64-unknown-linux-musl/debug/deps/liblibc-af6b81a75f6a7aeb.rlib", 99 | "/target/x86_64-unknown-linux-musl/debug/deps/liblibc-af6b81a75f6a7aeb.rmeta" 100 | ], 101 | "output_dirs": [ 102 | "/target/x86_64-unknown-linux-musl/debug/deps", 103 | "/target/x86_64-unknown-linux-musl/debug/deps" 104 | ], 105 | "links": {} 106 | }, 107 | { 108 | "package_name": "openssl-sys", 109 | "package_version": "0.9.50", 110 | "command": { 111 | "Simple": { 112 | "env": { 113 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out" 114 | }, 115 | "program": "/usr/local/bin/cargo-buildscript-apply", 116 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50", 117 | "args": [ 118 | "--", 119 | "rustc", 120 | "--crate-name", 121 | "openssl_sys", 122 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50/src/lib.rs" 123 | ] 124 | } 125 | }, 126 | "kind": { 127 | "BuildScriptOutputConsumer": [ 128 | "Other", 129 | "/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out" 130 | ] 131 | }, 132 | "outputs": [ 133 | "/target/x86_64-unknown-linux-musl/debug/deps/libopenssl_sys-ee7eaf9d748340a6.rlib", 134 | "/target/x86_64-unknown-linux-musl/debug/deps/libopenssl_sys-ee7eaf9d748340a6.rmeta" 135 | ], 136 | "output_dirs": [ 137 | "/target/x86_64-unknown-linux-musl/debug/deps", 138 | "/target/x86_64-unknown-linux-musl/debug/deps" 139 | ], 140 | "links": {} 141 | }, 142 | { 143 | "package_name": "multi-bin", 144 | "package_version": "0.1.0", 145 | "command": { 146 | "Simple": { 147 | "env": { 148 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 149 | }, 150 | "program": "/usr/local/bin/cargo-buildscript-apply", 151 | "cwd": "/context", 152 | "args": [ 153 | "--", 154 | "rustc", 155 | "--edition=2018", 156 | "--crate-name", 157 | "bin_1", 158 | "src/bin/bin-1.rs" 159 | ] 160 | } 161 | }, 162 | "kind": { 163 | "BuildScriptOutputConsumer": [ 164 | "Binary", 165 | "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 166 | ] 167 | }, 168 | "outputs": [ 169 | "/target/x86_64-unknown-linux-musl/debug/deps/bin_1-ed273ffa407baa8b" 170 | ], 171 | "output_dirs": [ 172 | "/target/x86_64-unknown-linux-musl/debug/deps" 173 | ], 174 | "links": { 175 | "/target/x86_64-unknown-linux-musl/debug/bin-1": "/target/x86_64-unknown-linux-musl/debug/deps/bin_1-ed273ffa407baa8b" 176 | } 177 | }, 178 | { 179 | "package_name": "multi-bin", 180 | "package_version": "0.1.0", 181 | "command": { 182 | "Simple": { 183 | "env": { 184 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 185 | }, 186 | "program": "/usr/local/bin/cargo-buildscript-apply", 187 | "cwd": "/context", 188 | "args": [ 189 | "--", 190 | "rustc", 191 | "--edition=2018", 192 | "--crate-name", 193 | "bin_1", 194 | "src/bin/bin-1.rs", 195 | "--test" 196 | ] 197 | } 198 | }, 199 | "kind": { 200 | "BuildScriptOutputConsumer": [ 201 | "Test", 202 | "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 203 | ] 204 | }, 205 | "outputs": [ 206 | "/target/x86_64-unknown-linux-musl/debug/deps/bin_1-5b5e8a9adfa6ccf4" 207 | ], 208 | "output_dirs": [ 209 | "/target/x86_64-unknown-linux-musl/debug/deps" 210 | ], 211 | "links": { 212 | "/target/x86_64-unknown-linux-musl/debug/bin_1-5b5e8a9adfa6ccf4": "/target/x86_64-unknown-linux-musl/debug/deps/bin_1-5b5e8a9adfa6ccf4" 213 | } 214 | }, 215 | { 216 | "package_name": "multi-bin", 217 | "package_version": "0.1.0", 218 | "command": { 219 | "Simple": { 220 | "env": { 221 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 222 | }, 223 | "program": "/usr/local/bin/cargo-buildscript-apply", 224 | "cwd": "/context", 225 | "args": [ 226 | "--", 227 | "rustc", 228 | "--edition=2018", 229 | "--crate-name", 230 | "bin_2", 231 | "src/bin/bin-2.rs" 232 | ] 233 | } 234 | }, 235 | "kind": { 236 | "BuildScriptOutputConsumer": [ 237 | "Binary", 238 | "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 239 | ] 240 | }, 241 | "outputs": [ 242 | "/target/x86_64-unknown-linux-musl/debug/deps/bin_2-d1a1ec213eb8750a" 243 | ], 244 | "output_dirs": [ 245 | "/target/x86_64-unknown-linux-musl/debug/deps" 246 | ], 247 | "links": { 248 | "/target/x86_64-unknown-linux-musl/debug/bin-2": "/target/x86_64-unknown-linux-musl/debug/deps/bin_2-d1a1ec213eb8750a" 249 | } 250 | }, 251 | { 252 | "package_name": "multi-bin", 253 | "package_version": "0.1.0", 254 | "command": { 255 | "Simple": { 256 | "env": { 257 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 258 | }, 259 | "program": "/usr/local/bin/cargo-buildscript-apply", 260 | "cwd": "/context", 261 | "args": [ 262 | "--", 263 | "rustc", 264 | "--edition=2018", 265 | "--crate-name", 266 | "bin_2", 267 | "src/bin/bin-2.rs", 268 | "--test" 269 | ] 270 | } 271 | }, 272 | "kind": { 273 | "BuildScriptOutputConsumer": [ 274 | "Test", 275 | "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 276 | ] 277 | }, 278 | "outputs": [ 279 | "/target/x86_64-unknown-linux-musl/debug/deps/bin_2-92b8326325c2f547" 280 | ], 281 | "output_dirs": [ 282 | "/target/x86_64-unknown-linux-musl/debug/deps" 283 | ], 284 | "links": { 285 | "/target/x86_64-unknown-linux-musl/debug/bin_2-92b8326325c2f547": "/target/x86_64-unknown-linux-musl/debug/deps/bin_2-92b8326325c2f547" 286 | } 287 | } 288 | ] 289 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/src/graph/node.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::mem::replace; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use semver::Version; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::plan::{RawInvocation, RawTargetKind}; 9 | use crate::shared::tools::{BUILDSCRIPT_APPLY, BUILDSCRIPT_CAPTURE}; 10 | 11 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] 12 | pub struct Node { 13 | package_name: String, 14 | package_version: Version, 15 | 16 | command: NodeCommand, 17 | 18 | kind: NodeKind, 19 | outputs: Vec, 20 | output_dirs: Vec, 21 | links: BTreeMap, 22 | } 23 | 24 | #[derive(Debug, PartialEq, Clone, Copy, Deserialize, Serialize)] 25 | pub enum PrimitiveNodeKind { 26 | Test, 27 | Binary, 28 | Example, 29 | Other, 30 | BuildScriptCompile, 31 | BuildScriptRun, 32 | } 33 | 34 | #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 35 | pub enum NodeKind

{ 36 | Primitive(PrimitiveNodeKind), 37 | MergedBuildScript(P), 38 | BuildScriptOutputConsumer(PrimitiveNodeKind, P), 39 | } 40 | 41 | #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 42 | pub enum NodeCommand { 43 | Simple(NodeCommandDetails), 44 | 45 | WithBuildscript { 46 | compile: NodeCommandDetails, 47 | run: NodeCommandDetails, 48 | }, 49 | } 50 | 51 | #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 52 | pub struct NodeCommandDetails { 53 | pub env: BTreeMap, 54 | pub program: String, 55 | pub cwd: PathBuf, 56 | pub args: Vec, 57 | } 58 | 59 | pub enum BuildScriptMergeResult { 60 | Ok, 61 | DependencyBuildScript, 62 | AlreadyMerged, 63 | } 64 | 65 | impl Node { 66 | pub fn outputs_iter(&self) -> impl Iterator { 67 | self.outputs.iter().map(|path| path.as_path()) 68 | } 69 | 70 | pub fn output_dirs_iter(&self) -> impl Iterator { 71 | self.output_dirs.iter().map(|path| path.as_path()) 72 | } 73 | 74 | pub fn links_iter(&self) -> impl Iterator { 75 | self.links 76 | .iter() 77 | .map(|(dest, src)| (dest.as_path(), src.as_path())) 78 | } 79 | 80 | pub fn kind(&self) -> NodeKind<&Path> { 81 | use NodeKind::*; 82 | 83 | match self.kind { 84 | Primitive(kind) => Primitive(kind), 85 | MergedBuildScript(ref buf) => MergedBuildScript(buf.as_path()), 86 | 87 | BuildScriptOutputConsumer(original, ref buf) => { 88 | BuildScriptOutputConsumer(original, buf.as_path()) 89 | } 90 | } 91 | } 92 | 93 | pub fn package_name(&self) -> &str { 94 | &self.package_name 95 | } 96 | 97 | pub fn binary_name(&self) -> Option<&str> { 98 | match self.kind { 99 | NodeKind::Primitive(PrimitiveNodeKind::Binary) => {} 100 | NodeKind::BuildScriptOutputConsumer(PrimitiveNodeKind::Binary, _) => {} 101 | 102 | _ => return None, 103 | }; 104 | 105 | self.links_iter() 106 | .next() 107 | .and_then(|(to, _)| to.file_name().and_then(|name| name.to_str())) 108 | .or_else(|| Some(self.package_name())) 109 | } 110 | 111 | pub fn test_name(&self) -> Option<&str> { 112 | match self.kind { 113 | NodeKind::Primitive(PrimitiveNodeKind::Test) => {} 114 | NodeKind::BuildScriptOutputConsumer(PrimitiveNodeKind::Test, _) => {} 115 | 116 | _ => return None, 117 | }; 118 | 119 | self.links_iter() 120 | .next() 121 | .and_then(|(to, _)| to.file_name().and_then(|name| name.to_str())) 122 | .or_else(|| Some(self.package_name())) 123 | } 124 | 125 | pub fn command(&self) -> &NodeCommand { 126 | &self.command 127 | } 128 | 129 | pub fn into_command_details(self) -> NodeCommandDetails { 130 | match self.command { 131 | NodeCommand::Simple(details) => details, 132 | NodeCommand::WithBuildscript { compile, .. } => compile, 133 | } 134 | } 135 | 136 | pub fn add_buildscript_compile_node( 137 | &mut self, 138 | mut compile_node: Node, 139 | ) -> BuildScriptMergeResult { 140 | let out_dir: PathBuf = match self.command { 141 | NodeCommand::Simple(ref mut details) => { 142 | let real_buildscript_path = { 143 | compile_node 144 | .links_iter() 145 | .filter(|(to, _)| *to == Path::new(&details.program)) 146 | .map(|(_, from)| from) 147 | .next() 148 | }; 149 | 150 | if let Some(path) = real_buildscript_path { 151 | details.program = path.to_string_lossy().into(); 152 | } else { 153 | return BuildScriptMergeResult::DependencyBuildScript; 154 | } 155 | 156 | details.use_wrapper(BUILDSCRIPT_CAPTURE); 157 | details.env.get("OUT_DIR").unwrap().into() 158 | } 159 | 160 | NodeCommand::WithBuildscript { .. } => { 161 | return BuildScriptMergeResult::AlreadyMerged; 162 | } 163 | }; 164 | 165 | self.kind = NodeKind::MergedBuildScript(out_dir.clone()); 166 | self.output_dirs.append(&mut compile_node.output_dirs); 167 | self.output_dirs.push(out_dir.clone()); 168 | self.outputs.push(out_dir); 169 | 170 | take_mut::take(&mut self.command, |command| { 171 | command.add_buildscript_compile(compile_node.into_command_details()) 172 | }); 173 | 174 | BuildScriptMergeResult::Ok 175 | } 176 | 177 | pub fn transform_into_buildscript_consumer(&mut self, out_dir: &Path) { 178 | if let NodeCommand::Simple(ref mut details) = self.command { 179 | details.use_wrapper(BUILDSCRIPT_APPLY); 180 | } 181 | 182 | self.kind = match self.kind { 183 | NodeKind::Primitive(kind) => NodeKind::BuildScriptOutputConsumer(kind, out_dir.into()), 184 | _ => NodeKind::BuildScriptOutputConsumer(PrimitiveNodeKind::Other, out_dir.into()), 185 | }; 186 | } 187 | 188 | pub fn add_dependency_buildscript(&mut self, dependency: Node) { 189 | let out_dir = match dependency.command { 190 | NodeCommand::Simple(ref details) => details.env.get("OUT_DIR"), 191 | NodeCommand::WithBuildscript { ref run, .. } => run.env.get("OUT_DIR"), 192 | }; 193 | 194 | let out_dir = match out_dir { 195 | Some(dir) => dir, 196 | None => { 197 | return; 198 | } 199 | }; 200 | 201 | if let NodeCommand::WithBuildscript { ref mut run, .. } = self.command { 202 | run.args 203 | .insert(0, format!("--with-metadata-from={}", out_dir)); 204 | } 205 | } 206 | } 207 | 208 | impl NodeCommand { 209 | pub fn add_buildscript_compile(self, compile: NodeCommandDetails) -> Self { 210 | match self { 211 | NodeCommand::Simple(run) => NodeCommand::WithBuildscript { compile, run }, 212 | other => other, 213 | } 214 | } 215 | } 216 | 217 | impl NodeCommandDetails { 218 | pub fn use_wrapper(&mut self, wrapper: &str) { 219 | let original = replace(&mut self.program, wrapper.into()); 220 | let mut args = replace(&mut self.args, vec!["--".into(), original]); 221 | 222 | self.args.append(&mut args); 223 | } 224 | } 225 | 226 | impl From<&RawInvocation> for Node { 227 | fn from(invocation: &RawInvocation) -> Self { 228 | Self { 229 | kind: invocation.into(), 230 | 231 | package_name: invocation.package_name.clone(), 232 | package_version: invocation.package_version.clone(), 233 | 234 | command: invocation.into(), 235 | 236 | outputs: invocation.outputs.clone(), 237 | links: invocation.links.clone(), 238 | 239 | output_dirs: { 240 | invocation 241 | .outputs 242 | .iter() 243 | .map(|path| path.parent().unwrap().into()) 244 | .collect() 245 | }, 246 | } 247 | } 248 | } 249 | 250 | impl From<&RawInvocation> for NodeKind { 251 | fn from(invocation: &RawInvocation) -> Self { 252 | if invocation.args.contains(&String::from("--test")) { 253 | return NodeKind::Primitive(PrimitiveNodeKind::Test); 254 | } 255 | 256 | if invocation.target_kind.contains(&RawTargetKind::Bin) { 257 | return NodeKind::Primitive(PrimitiveNodeKind::Binary); 258 | } 259 | 260 | if invocation.target_kind.contains(&RawTargetKind::Example) { 261 | return NodeKind::Primitive(PrimitiveNodeKind::Example); 262 | } 263 | 264 | if invocation.target_kind.contains(&RawTargetKind::CustomBuild) 265 | && invocation.program != "rustc" 266 | { 267 | return NodeKind::Primitive(PrimitiveNodeKind::BuildScriptRun); 268 | } 269 | 270 | if invocation.target_kind.contains(&RawTargetKind::CustomBuild) 271 | && invocation.program == "rustc" 272 | { 273 | return NodeKind::Primitive(PrimitiveNodeKind::BuildScriptCompile); 274 | } 275 | 276 | NodeKind::Primitive(PrimitiveNodeKind::Other) 277 | } 278 | } 279 | 280 | impl From<&RawInvocation> for NodeCommand { 281 | fn from(invocation: &RawInvocation) -> Self { 282 | NodeCommand::Simple(NodeCommandDetails::from(invocation)) 283 | } 284 | } 285 | 286 | impl From<&RawInvocation> for NodeCommandDetails { 287 | fn from(invocation: &RawInvocation) -> Self { 288 | NodeCommandDetails { 289 | program: invocation.program.clone(), 290 | args: invocation.args.clone(), 291 | env: invocation.env.clone(), 292 | cwd: invocation.cwd.clone(), 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /examples/workspace/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "arrayvec" 13 | version = "0.4.12" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "atty" 21 | version = "0.2.13" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | dependencies = [ 24 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 25 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 26 | ] 27 | 28 | [[package]] 29 | name = "binary-1" 30 | version = "0.1.0" 31 | dependencies = [ 32 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "lib-1 0.1.0", 34 | ] 35 | 36 | [[package]] 37 | name = "binary-2" 38 | version = "0.1.0" 39 | dependencies = [ 40 | "lib-1 0.1.0", 41 | "nix 0.16.1 (git+ssh://git@github.com/nix-rust/nix.git)", 42 | ] 43 | 44 | [[package]] 45 | name = "bitflags" 46 | version = "1.2.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "cc" 51 | version = "1.0.50" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "0.1.10" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | 59 | [[package]] 60 | name = "clap" 61 | version = "2.33.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | dependencies = [ 64 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 71 | ] 72 | 73 | [[package]] 74 | name = "lazy_static" 75 | version = "1.1.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "lexical-core" 83 | version = "0.4.6" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | dependencies = [ 86 | "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 91 | ] 92 | 93 | [[package]] 94 | name = "lib-1" 95 | version = "0.1.0" 96 | dependencies = [ 97 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "log 0.4.8 (git+https://github.com/rust-lang-nursery/log.git)", 100 | ] 101 | 102 | [[package]] 103 | name = "libc" 104 | version = "0.2.66" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | 107 | [[package]] 108 | name = "log" 109 | version = "0.4.8" 110 | source = "git+https://github.com/rust-lang-nursery/log.git#2774e4aa7a9d077c351efb43977f787d0897bcd6" 111 | dependencies = [ 112 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 113 | ] 114 | 115 | [[package]] 116 | name = "nix" 117 | version = "0.16.1" 118 | source = "git+ssh://git@github.com/nix-rust/nix.git#90f9301641463f211be7f3599f9a9bdd5e9f03b4" 119 | dependencies = [ 120 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 123 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "nodrop" 129 | version = "0.1.14" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | 132 | [[package]] 133 | name = "rustc_version" 134 | version = "0.2.3" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | dependencies = [ 137 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 138 | ] 139 | 140 | [[package]] 141 | name = "ryu" 142 | version = "1.0.2" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | 145 | [[package]] 146 | name = "semver" 147 | version = "0.9.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | dependencies = [ 150 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 151 | ] 152 | 153 | [[package]] 154 | name = "semver-parser" 155 | version = "0.7.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | 158 | [[package]] 159 | name = "static_assertions" 160 | version = "0.3.4" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | 163 | [[package]] 164 | name = "strsim" 165 | version = "0.8.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | 168 | [[package]] 169 | name = "textwrap" 170 | version = "0.11.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | dependencies = [ 173 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 174 | ] 175 | 176 | [[package]] 177 | name = "unicode-width" 178 | version = "0.1.6" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | 181 | [[package]] 182 | name = "vec_map" 183 | version = "0.8.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | 186 | [[package]] 187 | name = "version_check" 188 | version = "0.1.5" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | 191 | [[package]] 192 | name = "void" 193 | version = "1.0.2" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | 196 | [[package]] 197 | name = "winapi" 198 | version = "0.3.8" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | dependencies = [ 201 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 203 | ] 204 | 205 | [[package]] 206 | name = "winapi-i686-pc-windows-gnu" 207 | version = "0.4.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | 210 | [[package]] 211 | name = "winapi-x86_64-pc-windows-gnu" 212 | version = "0.4.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | 215 | [metadata] 216 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 217 | "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" 218 | "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 219 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 220 | "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 221 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 222 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 223 | "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" 224 | "checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14" 225 | "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 226 | "checksum log 0.4.8 (git+https://github.com/rust-lang-nursery/log.git)" = "" 227 | "checksum nix 0.16.1 (git+ssh://git@github.com/nix-rust/nix.git)" = "" 228 | "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 229 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 230 | "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 231 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 232 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 233 | "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" 234 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 235 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 236 | "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" 237 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 238 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 239 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 240 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 241 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 242 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 243 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/README.md: -------------------------------------------------------------------------------- 1 | # `cargo-wharf-frontend` - BuildKit frontend for Rust 2 | 3 | ## Usage 4 | Almost simple as it is: 5 | ``` 6 | docker build -f Cargo.toml . 7 | ``` 8 | 9 | Although, extra one-time setup has to be made before the build. 10 | 11 | 1. [Make sure BuildKit is enabled](#buildkit-setup) 12 | 2. [Add the frontend directive](#frontend-directive) 13 | 3. [Create a builder image config](#builder-image-config) 14 | 4. [Create an output image config](#output-image-config) 15 | 5. [Specify binaries](#binaries) 16 | 17 | ## BuildKit setup 18 | The possibility to build crates without Dockerfile is only possible thanks to [BuildKit] external frontends feature. 19 | 20 | As for **Docker v19.03.3**, BuildKit can be enabled just by setting `DOCKER_BUILDKIT=1` env variable when running `docker build`. 21 | 22 | ## Frontend directive 23 | To instruct BuildKit to use the frontend, the first line of the `Cargo.toml` should be: 24 | ``` 25 | # syntax = denzp/cargo-wharf-frontend:v0.1.0-alpha.2 26 | ``` 27 | 28 | ## Builder image config 29 | The builder image is an image that contains Rust toolchain and any extra tools that might be needed to build the crate. 30 | 31 | Configuration is made with a `[package.metadata.wharf.builder]` metadata in `Cargo.toml`. 32 | The semantics of the metadata *loosely* tries to follow `Dockerfile` directives. 33 | 34 | *Real life examples can be found [here](../cargo-container-tools/Cargo.toml) and [there](Cargo.toml).* 35 | 36 | | Base image | | 37 | |--:|:--| 38 | | Key | `package.metadata.wharf.builder.image` | 39 | | Data type| `String` | 40 | | Description | Builder base image that contains Rust. | 41 | | `Dockerfile` counterpart | [`FROM`] | 42 | 43 | ``` toml 44 | [package.metadata.wharf.builder] 45 | image = "rust" 46 | ``` 47 | 48 | | Setup commands | | 49 | |--:|:--| 50 | | Key | `package.metadata.wharf.builder.setup-commands` | 51 | | Data type| `Option>` | 52 | | Description | Execute commands to setup the builder image. | 53 | | `Dockerfile` counterpart | [`RUN`] | 54 | 55 | ``` toml 56 | [package.metadata.wharf.builder] 57 | image = "rust" 58 | setup-commands = [ 59 | { shell = "apt-get update && apt-get install -y adb" }, 60 | { command = ["apt-get", "install", "-y", "ffmpeg"], display = "Install ffmpeg" }, 61 | ] 62 | ``` 63 | 64 | | User | | 65 | |--:|:--| 66 | | Key | `package.metadata.wharf.builder.user` | 67 | | Data type| `Option` | 68 | | Description | User which runs `rustc` and build scripts. | 69 | | `Dockerfile` counterpart | [`USER`] | 70 | 71 | ``` toml 72 | [package.metadata.wharf.builder] 73 | image = "rust" 74 | user = "root" 75 | ``` 76 | 77 | | Environment variables | | 78 | |--:|:--| 79 | | Key | `package.metadata.wharf.builder.env` | 80 | | Data type| `Option>` | 81 | | Description | Environment to run the `rustc` and build scripts. | 82 | | `Dockerfile` counterpart | [`ENV`] | 83 | 84 | ``` toml 85 | [package.metadata.wharf.builder] 86 | image = "rust" 87 | env = { NAME_1 = "VALUE_1" } 88 | ``` 89 | 90 | ``` toml 91 | [package.metadata.wharf.builder] 92 | image = "rust" 93 | 94 | [package.metadata.wharf.builder.env] 95 | "NAME 1" = "VALUE 1" 96 | ``` 97 | 98 | | Build target | | 99 | |--:|:--| 100 | | Key | `package.metadata.wharf.builder.target` | 101 | | Data type| `Option` | 102 | | Description | Output target: similar to `cargo build --target ` | 103 | | `Dockerfile` counterpart | - | 104 | 105 | ``` toml 106 | [package.metadata.wharf.builder] 107 | image = "clux/muslrust:nightly-2019-09-28" 108 | target = "x86_64-unknown-linux-musl" 109 | ``` 110 | 111 | ## Output image config 112 | The output image is a base where compiled binaries will be put, and tests will run. 113 | There are no restrictions on which image should be used. 114 | 115 | Configuration is made with a `[package.metadata.wharf.output]` metadata in `Cargo.toml`. 116 | The semantics of the metadata tries to follow `Dockerfile` directives. 117 | 118 | *Real life examples can be found [here](cargo-container-tools/Cargo.toml) and [there](cargo-wharf-frontend/Cargo.toml).* 119 | 120 | | Base image | | 121 | |--:|:--| 122 | | Key | `package.metadata.wharf.output.image` | 123 | | Data type| `String` | 124 | | Description | Base for the output image. | 125 | | `Dockerfile` counterpart | [`FROM`] | 126 | 127 | ``` toml 128 | [package.metadata.wharf.output] 129 | image = "debian:stable-slim" 130 | ``` 131 | 132 | ``` toml 133 | [package.metadata.wharf.output] 134 | image = "scratch" 135 | ``` 136 | 137 | | Pre-install commands | | 138 | |--:|:--| 139 | | Key | `package.metadata.wharf.output.pre-install-commands` | 140 | | Data type| `Option>` | 141 | | Description | Execute commands in the output image before the binaries are copied. | 142 | | `Dockerfile` counterpart | [`RUN`] | 143 | 144 | ``` toml 145 | [package.metadata.wharf.output] 146 | image = "debian" 147 | pre-install-commands = [ 148 | { shell = "apt-get update && apt-get install -y adb", display = "My custom shell command" }, 149 | { command = ["apt-get", "install", "-y", "ffmpeg"], display = "My custom command" }, 150 | ] 151 | ``` 152 | 153 | | Post-install commands | | 154 | |--:|:--| 155 | | Key | `package.metadata.wharf.output.post-install-commands` | 156 | | Data type| `Option>` | 157 | | Description | Execute commands in the output image after the binaries were copied. | 158 | | `Dockerfile` counterpart | [`RUN`] | 159 | 160 | ``` toml 161 | [package.metadata.wharf.output] 162 | image = "debian" 163 | post-install-commands = [ 164 | { shell = "ldd my-binary-1 | grep -qzv 'not found'", display = "Check shared deps" }, 165 | ] 166 | ``` 167 | 168 | | User | | 169 | |--:|:--| 170 | | Key | `package.metadata.wharf.output.user` | 171 | | Data type| `Option` | 172 | | Description | User which runs the entrypoint. | 173 | | `Dockerfile` counterpart | [`USER`] | 174 | 175 | ``` toml 176 | [package.metadata.wharf.output] 177 | image = "scratch" 178 | user = "root" 179 | ``` 180 | 181 | | Working directory | | 182 | |--:|:--| 183 | | Key | `package.metadata.wharf.output.workdir` | 184 | | Data type| `Option` | 185 | | Description | Working directory to run the entrypoint. | 186 | | `Dockerfile` counterpart | [`WORKDIR`] | 187 | 188 | ``` toml 189 | [package.metadata.wharf.output] 190 | image = "debian:stable-slim" 191 | workdir = "/tmp" 192 | ``` 193 | 194 | | Entrypoint | | 195 | |--:|:--| 196 | | Key | `package.metadata.wharf.output.entrypoint` | 197 | | Data type| `Option>` | 198 | | Description | Path and arguments for the container entrypoint. | 199 | | `Dockerfile` counterpart | [`ENTRYPOINT`] | 200 | 201 | ``` toml 202 | [package.metadata.wharf.output] 203 | image = "debian:stable-slim" 204 | entrypoint = ["/bin/sh", "-c"] 205 | ``` 206 | 207 | | Additional arguments | | 208 | |--:|:--| 209 | | Key | `package.metadata.wharf.output.args` | 210 | | Data type| `Option>` | 211 | | Description | Default extra arguments for the entrypoint. | 212 | | `Dockerfile` counterpart | [`CMD`] | 213 | 214 | 215 | ``` toml 216 | [package.metadata.wharf.output] 217 | image = "debian:stable-slim" 218 | entrypoint = ["/bin/echo", "hello"] 219 | args = ["world"] 220 | ``` 221 | 222 | | Environment variables | | 223 | |--:|:--| 224 | | Key | `package.metadata.wharf.output.env` | 225 | | Data type| `Option>` | 226 | | Description | Environment variables to run the entrypoint with. | 227 | | `Dockerfile` counterpart | [`ENV`] | 228 | 229 | ``` toml 230 | [package.metadata.wharf.output] 231 | image = "scratch" 232 | env = { NAME_1 = "VALUE_1" } 233 | ``` 234 | 235 | ``` toml 236 | [package.metadata.wharf.output] 237 | image = "scratch" 238 | 239 | [package.metadata.wharf.output.env] 240 | "NAME 1" = "VALUE 1" 241 | ``` 242 | 243 | | Volumes | | 244 | |--:|:--| 245 | | Key | `package.metadata.wharf.output.volumes` | 246 | | Data type| `Option>` | 247 | | Description | Pathes to the mount points of container volumes. | 248 | | `Dockerfile` counterpart | [`VOLUME`] | 249 | 250 | ``` toml 251 | [package.metadata.wharf.output] 252 | image = "scratch" 253 | volumes = ["/local", "/data"] 254 | ``` 255 | 256 | | Exposed ports | | 257 | |--:|:--| 258 | | Key | `package.metadata.wharf.output.expose` | 259 | | Data type| `Option>` | 260 | | Description | Announce which ports will be listened. | 261 | | `Dockerfile` counterpart | [`EXPOSE`] | 262 | 263 | ``` toml 264 | [package.metadata.wharf.output] 265 | image = "scratch" 266 | expose = ["3500/tcp", "3600/udp", "3700"] 267 | ``` 268 | 269 | | Labels | | 270 | |--:|:--| 271 | | Key | `package.metadata.wharf.output.labels` | 272 | | Data type| `Option>` | 273 | | Description | Labels the output images should be annotated with. | 274 | | `Dockerfile` counterpart | [`LABEL`] | 275 | 276 | ``` toml 277 | [package.metadata.wharf.output] 278 | image = "scratch" 279 | 280 | [package.metadata.wharf.output.labels] 281 | "simple-label" = "simple value" 282 | "my.awesome.label" = "another value" 283 | ``` 284 | 285 | | Stop signal | | 286 | |--:|:--| 287 | | Key | `package.metadata.wharf.output.stop-signal` | 288 | | Data type| `Option` | 289 | | Description | System call signal that will be sent to the container to exit. | 290 | | `Dockerfile` counterpart | [`STOPSIGNAL`] | 291 | 292 | ``` toml 293 | [package.metadata.wharf.output] 294 | image = "scratch" 295 | stop-signal = "SIGINT" 296 | ``` 297 | 298 | ## Binaries 299 | It's also important to specify which binaries should be built and where to put them. 300 | Each crate can use own convention about where the binaries should go. 301 | 302 | For example, with `scratch` output image, it might be usefull to put binaries directly into `/` (root). 303 | 304 | The binaries should be specified in `[[package.metadata.wharf.binary]]` array in `Cargo.toml`: 305 | 306 | | Key | Data type | Description | 307 | |-----|-----------|-------------| 308 | | `name` | `String` | Binary name inside the crate. | 309 | | `destination` | `PathBuf` | Destination path inside the output image. | 310 | 311 | ``` toml 312 | [[package.metadata.wharf.binary]] 313 | name = "cargo-metadata-collector" 314 | destination = "/usr/local/bin/cargo-metadata-collector" 315 | 316 | [[package.metadata.wharf.binary]] 317 | name = "cargo-test-runner" 318 | destination = "/cargo-test-runner" 319 | ``` 320 | 321 | ## Frontend parameters 322 | There is an additional way to control the frontend: build arguments. 323 | 324 | | Profile | | 325 | |--:|:--| 326 | | Name | `profile` | 327 | | Data type| `Option` | 328 | | Description | Defines what will be built and copied into the output image. | 329 | | *Possible values* | `release-binaries`, `release-tests`,
`debug-binaries`, `debug-tests` | 330 | | **Default** | `release-binaries` | 331 | 332 | ``` 333 | docker build -f Cargo.toml --build-arg profile=release-tests 334 | ``` 335 | 336 | | Features | | 337 | |--:|:--| 338 | | Name | `features` | 339 | | Data type| `Option>` | 340 | | Description | Enable the crate's features. | 341 | 342 | ``` 343 | docker build -f Cargo.toml --build-arg features=feature-1,feature-2 344 | ``` 345 | 346 | | Default features | | 347 | |--:|:--| 348 | | Name | `no-default-features` | 349 | | Data type| `Option` | 350 | | Description | Disable crate's default features. | 351 | | *Possible values* | `true`, `false` | 352 | 353 | ``` 354 | docker build -f Cargo.toml --build-arg no-default-features=true 355 | ``` 356 | 357 | | Manifest path | | 358 | |--:|:--| 359 | | Name | `manifest-path` | 360 | | Data type| `Option` | 361 | | Description | Override the path to a crate manifest. Please note, this will not affect configuration collecting behavior. | 362 | 363 | ``` 364 | docker build -f Cargo.toml --build-arg manifest-path=binary-1/Cargo.toml 365 | ``` 366 | 367 | | Debug mode | | 368 | |--:|:--| 369 | | Name | `debug` | 370 | | Data type| `Vec` | 371 | | Description | Special mode of the image - instead of building, dump various debug information. | 372 | | *Possible values* | `all`, `config`, `build-plan`, `build-graph`, `llb` | 373 | 374 | ``` 375 | docker build -f Cargo.toml --build-arg debug=build-graph,llb 376 | ``` 377 | 378 | ``` 379 | docker build -f Cargo.toml --build-arg debug=all 380 | ``` 381 | 382 | **Note about debugging the frontend** 383 | 384 | When `debug=all` is used, every possible debug information will be dumped. 385 | Otherwise, when only a partial dump is needed, several values can be specified: `debug=config,build-plan`. 386 | 387 | By default, Docker will compose an image with those debug artifacts, and it might be tedious to inspect them. 388 | The behavior can be overridden: Docker can be instructed to put outputs into a folder: 389 | ``` 390 | docker build -f Cargo.toml . \ 391 | --output type=local,dest=debug-out \ 392 | --build-arg debug=all 393 | ``` 394 | 395 | [`FROM`]: https://docs.docker.com/engine/reference/builder/#from 396 | [`USER`]: https://docs.docker.com/engine/reference/builder/#from 397 | [`WORKDIR`]: https://docs.docker.com/engine/reference/builder/#workdir 398 | [`ENTRYPOINT`]: https://docs.docker.com/engine/reference/builder/#entrypoint 399 | [`CMD`]: https://docs.docker.com/engine/reference/builder/#cmd 400 | [`ENV`]: https://docs.docker.com/engine/reference/builder/#env 401 | [`LABEL`]: https://docs.docker.com/engine/reference/builder/#label 402 | [`EXPOSE`]: https://docs.docker.com/engine/reference/builder/#expose 403 | [`VOLUME`]: https://docs.docker.com/engine/reference/builder/#volume 404 | [`STOPSIGNAL`]: https://docs.docker.com/engine/reference/builder/#stopsignal 405 | [`RUN`]: https://docs.docker.com/engine/reference/builder/#run 406 | 407 | [BuildKit]: https://github.com/moby/buildkit 408 | ["Note for Docker users" section]: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md#note-for-docker-users 409 | -------------------------------------------------------------------------------- /cargo-wharf-frontend/tests/build-plan.json: -------------------------------------------------------------------------------- 1 | { 2 | "invocations": [ 3 | { 4 | "package_name": "libc", 5 | "package_version": "0.2.62", 6 | "target_kind": [ 7 | "custom-build" 8 | ], 9 | "deps": [], 10 | "outputs": [ 11 | "/target/debug/build/libc-87cd93070ba33df1/build_script_build-87cd93070ba33df1" 12 | ], 13 | "links": { 14 | "/target/debug/build/libc-87cd93070ba33df1/build-script-build": "/target/debug/build/libc-87cd93070ba33df1/build_script_build-87cd93070ba33df1" 15 | }, 16 | "program": "rustc", 17 | "args": [ 18 | "--crate-name", 19 | "build_script_build", 20 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62/build.rs" 21 | ], 22 | "env": {}, 23 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62" 24 | }, 25 | { 26 | "package_name": "libc", 27 | "package_version": "0.2.62", 28 | "target_kind": [ 29 | "custom-build" 30 | ], 31 | "deps": [ 32 | 0 33 | ], 34 | "outputs": [], 35 | "links": {}, 36 | "program": "/target/debug/build/libc-87cd93070ba33df1/build-script-build", 37 | "args": [], 38 | "env": { 39 | "HOST": "x86_64-unknown-linux-gnu", 40 | "OUT_DIR": "/target/debug/build/libc-84e59eda7c0f49e4/out", 41 | "TARGET": "x86_64-unknown-linux-gnu" 42 | }, 43 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62" 44 | }, 45 | { 46 | "package_name": "libc", 47 | "package_version": "0.2.62", 48 | "target_kind": [ 49 | "lib" 50 | ], 51 | "deps": [ 52 | 1 53 | ], 54 | "outputs": [ 55 | "/target/debug/deps/liblibc-c5d8077f198ec4fc.rlib", 56 | "/target/debug/deps/liblibc-c5d8077f198ec4fc.rmeta" 57 | ], 58 | "links": {}, 59 | "program": "rustc", 60 | "args": [ 61 | "--crate-name", 62 | "libc", 63 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62/src/lib.rs" 64 | ], 65 | "env": { 66 | "OUT_DIR": "/target/debug/build/libc-84e59eda7c0f49e4/out" 67 | }, 68 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62" 69 | }, 70 | { 71 | "package_name": "autocfg", 72 | "package_version": "0.1.6", 73 | "target_kind": [ 74 | "lib" 75 | ], 76 | "deps": [], 77 | "outputs": [ 78 | "/target/debug/deps/libautocfg-14b97424de3fa53e.rlib", 79 | "/target/debug/deps/libautocfg-14b97424de3fa53e.rmeta" 80 | ], 81 | "links": {}, 82 | "program": "rustc", 83 | "args": [ 84 | "--crate-name", 85 | "autocfg", 86 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-0.1.6/src/lib.rs" 87 | ], 88 | "env": {}, 89 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-0.1.6" 90 | }, 91 | { 92 | "package_name": "cc", 93 | "package_version": "1.0.45", 94 | "target_kind": [ 95 | "lib" 96 | ], 97 | "deps": [], 98 | "outputs": [ 99 | "/target/debug/deps/libcc-b340713b396565f0.rlib", 100 | "/target/debug/deps/libcc-b340713b396565f0.rmeta" 101 | ], 102 | "links": {}, 103 | "program": "rustc", 104 | "args": [ 105 | "--edition=2018", 106 | "--crate-name", 107 | "cc", 108 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.45/src/lib.rs" 109 | ], 110 | "env": {}, 111 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.45" 112 | }, 113 | { 114 | "package_name": "pkg-config", 115 | "package_version": "0.3.16", 116 | "target_kind": [ 117 | "lib" 118 | ], 119 | "deps": [], 120 | "outputs": [ 121 | "/target/debug/deps/libpkg_config-cd76fe1b536bbc70.rlib", 122 | "/target/debug/deps/libpkg_config-cd76fe1b536bbc70.rmeta" 123 | ], 124 | "links": {}, 125 | "program": "rustc", 126 | "args": [ 127 | "--crate-name", 128 | "pkg_config", 129 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/pkg-config-0.3.16/src/lib.rs" 130 | ], 131 | "env": {}, 132 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/pkg-config-0.3.16" 133 | }, 134 | { 135 | "package_name": "openssl-sys", 136 | "package_version": "0.9.50", 137 | "target_kind": [ 138 | "custom-build" 139 | ], 140 | "deps": [ 141 | 3, 142 | 4, 143 | 5 144 | ], 145 | "outputs": [ 146 | "/target/debug/build/openssl-sys-87b9b219fe0676b7/build_script_main-87b9b219fe0676b7" 147 | ], 148 | "links": { 149 | "/target/debug/build/openssl-sys-87b9b219fe0676b7/build-script-main": "/target/debug/build/openssl-sys-87b9b219fe0676b7/build_script_main-87b9b219fe0676b7" 150 | }, 151 | "program": "rustc", 152 | "args": [ 153 | "--crate-name", 154 | "build_script_main", 155 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50/build/main.rs" 156 | ], 157 | "env": {}, 158 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50" 159 | }, 160 | { 161 | "package_name": "openssl-sys", 162 | "package_version": "0.9.50", 163 | "target_kind": [ 164 | "custom-build" 165 | ], 166 | "deps": [ 167 | 6 168 | ], 169 | "outputs": [], 170 | "links": {}, 171 | "program": "/target/debug/build/openssl-sys-87b9b219fe0676b7/build-script-main", 172 | "args": [], 173 | "env": { 174 | "HOST": "x86_64-unknown-linux-gnu", 175 | "OUT_DIR": "/target/debug/build/openssl-sys-acfa4371985ef2b0/out", 176 | "TARGET": "x86_64-unknown-linux-gnu" 177 | }, 178 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50" 179 | }, 180 | { 181 | "package_name": "openssl-sys", 182 | "package_version": "0.9.50", 183 | "target_kind": [ 184 | "lib" 185 | ], 186 | "deps": [ 187 | 2, 188 | 7 189 | ], 190 | "outputs": [ 191 | "/target/debug/deps/libopenssl_sys-19eb4c8cc1419cae.rlib", 192 | "/target/debug/deps/libopenssl_sys-19eb4c8cc1419cae.rmeta" 193 | ], 194 | "links": {}, 195 | "program": "rustc", 196 | "args": [ 197 | "--crate-name", 198 | "openssl_sys", 199 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50/src/lib.rs" 200 | ], 201 | "env": { 202 | "OUT_DIR": "/target/debug/build/openssl-sys-acfa4371985ef2b0/out" 203 | }, 204 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50" 205 | }, 206 | { 207 | "package_name": "multi-bin", 208 | "package_version": "0.1.0", 209 | "target_kind": [ 210 | "custom-build" 211 | ], 212 | "deps": [ 213 | 8 214 | ], 215 | "outputs": [ 216 | "/target/debug/build/multi-bin-6b2014bd97cc5650/build_script_build-6b2014bd97cc5650" 217 | ], 218 | "links": { 219 | "/target/debug/build/multi-bin-6b2014bd97cc5650/build-script-build": "/target/debug/build/multi-bin-6b2014bd97cc5650/build_script_build-6b2014bd97cc5650" 220 | }, 221 | "program": "rustc", 222 | "args": [ 223 | "--edition=2018", 224 | "--crate-name", 225 | "build_script_build", 226 | "build.rs" 227 | ], 228 | "env": {}, 229 | "cwd": "/context" 230 | }, 231 | { 232 | "package_name": "openssl-sys", 233 | "package_version": "0.9.50", 234 | "target_kind": [ 235 | "custom-build" 236 | ], 237 | "deps": [ 238 | 6 239 | ], 240 | "outputs": [], 241 | "links": {}, 242 | "program": "/target/debug/build/openssl-sys-87b9b219fe0676b7/build-script-main", 243 | "args": [], 244 | "env": { 245 | "HOST": "x86_64-unknown-linux-gnu", 246 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out", 247 | "TARGET": "x86_64-unknown-linux-musl" 248 | }, 249 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50" 250 | }, 251 | { 252 | "package_name": "multi-bin", 253 | "package_version": "0.1.0", 254 | "target_kind": [ 255 | "custom-build" 256 | ], 257 | "deps": [ 258 | 9, 259 | 10 260 | ], 261 | "outputs": [], 262 | "links": {}, 263 | "program": "/target/debug/build/multi-bin-6b2014bd97cc5650/build-script-build", 264 | "args": [], 265 | "env": { 266 | "HOST": "x86_64-unknown-linux-gnu", 267 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out", 268 | "TARGET": "x86_64-unknown-linux-musl" 269 | }, 270 | "cwd": "/context" 271 | }, 272 | { 273 | "package_name": "libc", 274 | "package_version": "0.2.62", 275 | "target_kind": [ 276 | "custom-build" 277 | ], 278 | "deps": [ 279 | 0 280 | ], 281 | "outputs": [], 282 | "links": {}, 283 | "program": "/target/debug/build/libc-87cd93070ba33df1/build-script-build", 284 | "args": [], 285 | "env": { 286 | "HOST": "x86_64-unknown-linux-gnu", 287 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/libc-881546618a069ec4/out", 288 | "TARGET": "x86_64-unknown-linux-musl" 289 | }, 290 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62" 291 | }, 292 | { 293 | "package_name": "libc", 294 | "package_version": "0.2.62", 295 | "target_kind": [ 296 | "lib" 297 | ], 298 | "deps": [ 299 | 12 300 | ], 301 | "outputs": [ 302 | "/target/x86_64-unknown-linux-musl/debug/deps/liblibc-af6b81a75f6a7aeb.rlib", 303 | "/target/x86_64-unknown-linux-musl/debug/deps/liblibc-af6b81a75f6a7aeb.rmeta" 304 | ], 305 | "links": {}, 306 | "program": "rustc", 307 | "args": [ 308 | "--crate-name", 309 | "libc", 310 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62/src/lib.rs" 311 | ], 312 | "env": { 313 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/libc-881546618a069ec4/out" 314 | }, 315 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.62" 316 | }, 317 | { 318 | "package_name": "openssl-sys", 319 | "package_version": "0.9.50", 320 | "target_kind": [ 321 | "lib" 322 | ], 323 | "deps": [ 324 | 13, 325 | 10 326 | ], 327 | "outputs": [ 328 | "/target/x86_64-unknown-linux-musl/debug/deps/libopenssl_sys-ee7eaf9d748340a6.rlib", 329 | "/target/x86_64-unknown-linux-musl/debug/deps/libopenssl_sys-ee7eaf9d748340a6.rmeta" 330 | ], 331 | "links": {}, 332 | "program": "rustc", 333 | "args": [ 334 | "--crate-name", 335 | "openssl_sys", 336 | "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50/src/lib.rs" 337 | ], 338 | "env": { 339 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/openssl-sys-25bfb33e9dd9eec2/out" 340 | }, 341 | "cwd": "/home//.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.50" 342 | }, 343 | { 344 | "package_name": "multi-bin", 345 | "package_version": "0.1.0", 346 | "target_kind": [ 347 | "bin" 348 | ], 349 | "deps": [ 350 | 11, 351 | 14 352 | ], 353 | "outputs": [ 354 | "/target/x86_64-unknown-linux-musl/debug/deps/bin_1-ed273ffa407baa8b" 355 | ], 356 | "links": { 357 | "/target/x86_64-unknown-linux-musl/debug/bin-1": "/target/x86_64-unknown-linux-musl/debug/deps/bin_1-ed273ffa407baa8b" 358 | }, 359 | "program": "rustc", 360 | "args": [ 361 | "--edition=2018", 362 | "--crate-name", 363 | "bin_1", 364 | "src/bin/bin-1.rs" 365 | ], 366 | "env": { 367 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 368 | }, 369 | "cwd": "/context" 370 | }, 371 | { 372 | "package_name": "multi-bin", 373 | "package_version": "0.1.0", 374 | "target_kind": [ 375 | "bin" 376 | ], 377 | "deps": [ 378 | 11, 379 | 14 380 | ], 381 | "outputs": [ 382 | "/target/x86_64-unknown-linux-musl/debug/deps/bin_1-5b5e8a9adfa6ccf4" 383 | ], 384 | "links": { 385 | "/target/x86_64-unknown-linux-musl/debug/bin_1-5b5e8a9adfa6ccf4": "/target/x86_64-unknown-linux-musl/debug/deps/bin_1-5b5e8a9adfa6ccf4" 386 | }, 387 | "program": "rustc", 388 | "args": [ 389 | "--edition=2018", 390 | "--crate-name", 391 | "bin_1", 392 | "src/bin/bin-1.rs", 393 | "--test" 394 | ], 395 | "env": { 396 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 397 | }, 398 | "cwd": "/context" 399 | }, 400 | { 401 | "package_name": "multi-bin", 402 | "package_version": "0.1.0", 403 | "target_kind": [ 404 | "bin" 405 | ], 406 | "deps": [ 407 | 11, 408 | 14 409 | ], 410 | "outputs": [ 411 | "/target/x86_64-unknown-linux-musl/debug/deps/bin_2-d1a1ec213eb8750a" 412 | ], 413 | "links": { 414 | "/target/x86_64-unknown-linux-musl/debug/bin-2": "/target/x86_64-unknown-linux-musl/debug/deps/bin_2-d1a1ec213eb8750a" 415 | }, 416 | "program": "rustc", 417 | "args": [ 418 | "--edition=2018", 419 | "--crate-name", 420 | "bin_2", 421 | "src/bin/bin-2.rs" 422 | ], 423 | "env": { 424 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 425 | }, 426 | "cwd": "/context" 427 | }, 428 | { 429 | "package_name": "multi-bin", 430 | "package_version": "0.1.0", 431 | "target_kind": [ 432 | "bin" 433 | ], 434 | "deps": [ 435 | 11, 436 | 14 437 | ], 438 | "outputs": [ 439 | "/target/x86_64-unknown-linux-musl/debug/deps/bin_2-92b8326325c2f547" 440 | ], 441 | "links": { 442 | "/target/x86_64-unknown-linux-musl/debug/bin_2-92b8326325c2f547": "/target/x86_64-unknown-linux-musl/debug/deps/bin_2-92b8326325c2f547" 443 | }, 444 | "program": "rustc", 445 | "args": [ 446 | "--edition=2018", 447 | "--crate-name", 448 | "bin_2", 449 | "src/bin/bin-2.rs", 450 | "--test" 451 | ], 452 | "env": { 453 | "OUT_DIR": "/target/x86_64-unknown-linux-musl/debug/build/multi-bin-b4c1d99afefc6791/out" 454 | }, 455 | "cwd": "/context" 456 | } 457 | ] 458 | } 459 | --------------------------------------------------------------------------------