diff --git a/.gitignore b/.gitignore index 6addfe54..c496243c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ -/target/ -/packaged/ +target/ .DS_Store .idea + +# Since libcnb.rs is a library, we don't commit the lockfile so that CI tests the latest +# in-range dependencies, which is what users will use when installing from scratch. Cargo.lock -**/fixtures/*/target/ -**/fixtures/*/packaged/ + +# The default output directory of `cargo libcnb package`. +packaged/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1b7589..294ebf5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,18 +11,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `libcnb`: + - `LayerTypes` now implements `Copy` and `Clone`. ([#670](https://github.com/heroku/libcnb.rs/pull/670)). - `libcnb-data`: - `ExecDProgramOutputKey`, `ProcessType`, `LayerName`, `BuildpackId` and `StackId` now implement `Ord` and `PartialOrd`. ([#658](https://github.com/heroku/libcnb.rs/pull/658)) - - Add `generic::GenericMetadata` as a generic metadata type. Also makes it the default for `BuildpackDescriptor`, `SingleBuildpackDescriptor`, `MetaBuildpackDescriptor` and `LayerContentMetadata`. ([#664](https://github.com/heroku/libcnb.rs/pull/664)) -- `libcnb`: - - Struct `LayerTypes` now implements `Copy` and `Clone`. This allows injection of layer types into the struct implementing the `Layer` trait ([#670](https://github.com/heroku/libcnb.rs/pull/670)). + - Added `generic::GenericMetadata` as a generic metadata type. Also makes it the default for `BuildpackDescriptor`, `SingleBuildpackDescriptor`, `CompositeBuildpackDescriptor` and `LayerContentMetadata`. ([#664](https://github.com/heroku/libcnb.rs/pull/664)) +- `libcnb-test`: + - Added the `BuildpackReference::WorkspaceBuildpack` enum variant. This allows for the testing of any libcnb.rs or composite buildpack in the Cargo workspace, instead of only the buildpack of the current crate. **Note: The testing of composite buildpacks requires `pack` CLI version `>=0.30`.** ([#666](https://github.com/heroku/libcnb.rs/pull/666)) ### Changed -- Renamed `libcnb-data`'s `Buildpackage` to `PackageDescriptor`. This required changes in many other names across multiple libcnb.rs crates for consistency. See the PR diff for details, but renames should be straightforward and unsurprising. ([#656](https://github.com/heroku/libcnb.rs/pull/656)) +- `libcnb-data`: + - Renamed the `buildpackage` module to `package_descriptor`, and the `Buildpackage*` types within it to `PackageDescriptor*`. ([#656](https://github.com/heroku/libcnb.rs/pull/656)) + - Renamed multiple types to match the new composite vs component buildpack [upstream terminology](https://github.com/buildpacks/spec/blob/main/buildpack.md#cnb-terminology). Renamed `SingleBuildpackDescriptor` to `ComponentBuildpackDescriptor`, `MetaBuildpackDescriptor` to `CompositeBuildpackDescriptor` and `BuildpackDescriptor::{Single,Meta}` to `BuildpackDescriptor::{Component,Composite}`. ([#682](https://github.com/heroku/libcnb.rs/pull/682)) - `libcnb-cargo`: - No longer outputs paths for non-libcnb.rs and non-meta buildpacks. ([#657](https://github.com/heroku/libcnb.rs/pull/657)) - Build output for humans changed slightly, output intended for machines/scripting didn't change. ([#657](https://github.com/heroku/libcnb.rs/pull/657)) + - When performing buildpack detection, standard ignore files (`.ignore` and `.gitignore`) will be respected. ([#673](https://github.com/heroku/libcnb.rs/pull/673)) +- `libcnb-test`: + - Renamed `BuildpackReference::Crate` to `BuildpackReference::CurrentCrate`. ([#666](https://github.com/heroku/libcnb.rs/pull/666)) ## [0.14.0] - 2023-08-18 diff --git a/README.md b/README.md index e59be3b9..ab5e3f5f 100644 --- a/README.md +++ b/README.md @@ -153,13 +153,14 @@ impl Buildpack for HelloWorldBuildpack { BuildResultBuilder::new() .launch( - LaunchBuilder::new().process( - ProcessBuilder::new(process_type!("web"), ["echo"]) - .arg("Hello World!") - .default(true) - .build(), - ) - .build(), + LaunchBuilder::new() + .process( + ProcessBuilder::new(process_type!("web"), ["echo"]) + .arg("Hello World!") + .default(true) + .build(), + ) + .build(), ) .build() } @@ -178,22 +179,23 @@ In your project directory, run `cargo libcnb package` to start packaging: ```shell $ cargo libcnb package -šŸ” Locating buildpacks... -šŸ“¦ [1/1] Building libcnb-examples/my-buildpack -Determining automatic cross-compile settings... -Building binaries (x86_64-unknown-linux-musl)... +šŸšš Preparing package directory... +šŸ–„ļø Gathering Cargo configuration (for x86_64-unknown-linux-musl) +šŸ—ļø Building buildpack dependency graph... +šŸ”€ Determining build order... +šŸšš Building 1 buildpacks... +šŸ“¦ [1/1] Building libcnb-examples/my-buildpack (./) # Omitting compilation output... - Finished dev [unoptimized] target(s) in 8.92s -Writing buildpack directory... -Successfully wrote buildpack directory: packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack (4.06 MiB) + Finished dev [unoptimized] target(s) in 8.24s +Successfully wrote buildpack directory: packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack (4.09 MiB) āœØ Packaging successfully finished! šŸ’” To test your buildpack locally with pack, run: pack build my-image-name \ - --buildpack /home/ponda.baba/my-buildpack/packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack \ + --buildpack packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack \ --path /path/to/application -/home/ponda.baba/my-buildpack/packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack +/Users/example/src/my-buildpack/packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack ``` If you get errors with hints about how to install required tools to cross-compile from your host platform to the diff --git a/examples/execd/test-fixtures/empty-app/.gitkeep b/examples/execd/tests/fixtures/empty-app/.gitkeep similarity index 100% rename from examples/execd/test-fixtures/empty-app/.gitkeep rename to examples/execd/tests/fixtures/empty-app/.gitkeep diff --git a/examples/execd/tests/integration_test.rs b/examples/execd/tests/integration_test.rs index f6e94fed..7d0e80ef 100644 --- a/examples/execd/tests/integration_test.rs +++ b/examples/execd/tests/integration_test.rs @@ -13,7 +13,7 @@ use libcnb_test::{assert_contains, assert_empty, BuildConfig, TestRunner}; #[ignore = "integration test"] fn basic() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/empty-app"), + BuildConfig::new("heroku/builder:22", "tests/fixtures/empty-app"), |context| { let command_output = context.run_shell_command("env"); assert_empty!(command_output.stderr); diff --git a/examples/ruby-sample/src/main.rs b/examples/ruby-sample/src/main.rs index 4f8819f4..82071fa4 100644 --- a/examples/ruby-sample/src/main.rs +++ b/examples/ruby-sample/src/main.rs @@ -51,13 +51,13 @@ impl Buildpack for RubyBuildpack { LaunchBuilder::new() .process( ProcessBuilder::new(process_type!("web"), ["bundle"]) - .args(vec!["exec", "ruby", "app.rb"]) + .args(["exec", "ruby", "app.rb"]) .default(true) .build(), ) .process( ProcessBuilder::new(process_type!("worker"), ["bundle"]) - .args(vec!["exec", "ruby", "worker.rb"]) + .args(["exec", "ruby", "worker.rb"]) .build(), ) .build(), diff --git a/examples/ruby-sample/test-fixtures/simple-ruby-app/Gemfile b/examples/ruby-sample/tests/fixtures/simple-ruby-app/Gemfile similarity index 100% rename from examples/ruby-sample/test-fixtures/simple-ruby-app/Gemfile rename to examples/ruby-sample/tests/fixtures/simple-ruby-app/Gemfile diff --git a/examples/ruby-sample/test-fixtures/simple-ruby-app/Gemfile.lock b/examples/ruby-sample/tests/fixtures/simple-ruby-app/Gemfile.lock similarity index 100% rename from examples/ruby-sample/test-fixtures/simple-ruby-app/Gemfile.lock rename to examples/ruby-sample/tests/fixtures/simple-ruby-app/Gemfile.lock diff --git a/examples/ruby-sample/test-fixtures/simple-ruby-app/app.rb b/examples/ruby-sample/tests/fixtures/simple-ruby-app/app.rb similarity index 100% rename from examples/ruby-sample/test-fixtures/simple-ruby-app/app.rb rename to examples/ruby-sample/tests/fixtures/simple-ruby-app/app.rb diff --git a/examples/ruby-sample/tests/integration_test.rs b/examples/ruby-sample/tests/integration_test.rs index fe19dba4..6296c724 100644 --- a/examples/ruby-sample/tests/integration_test.rs +++ b/examples/ruby-sample/tests/integration_test.rs @@ -19,7 +19,7 @@ use std::{fs, io}; #[test] #[ignore = "integration test"] fn basic() { - let config = BuildConfig::new("heroku/buildpacks:20", "test-fixtures/simple-ruby-app"); + let config = BuildConfig::new("heroku/buildpacks:20", "tests/fixtures/simple-ruby-app"); TestRunner::default().build(&config, |context| { assert_contains!(context.pack_stdout, "---> Ruby Buildpack"); @@ -60,7 +60,7 @@ fn basic() { #[ignore = "integration test"] fn missing_gemfile_lock() { TestRunner::default().build( - BuildConfig::new("heroku/buildpacks:20", "test-fixtures/simple-ruby-app") + BuildConfig::new("heroku/buildpacks:20", "tests/fixtures/simple-ruby-app") .app_dir_preprocessor(|path| fs::remove_file(path.join("Gemfile.lock")).unwrap()) .expected_pack_result(PackResult::Failure), |context| { @@ -80,7 +80,7 @@ where stream.write_all(format!("{}\n", payload.as_ref()).as_bytes())?; - let mut buffer = vec![]; + let mut buffer = Vec::new(); stream.read_to_end(&mut buffer)?; Ok(String::from_utf8_lossy(&buffer).to_string()) diff --git a/libcnb-cargo/README.md b/libcnb-cargo/README.md index 217c0e33..c0518c00 100644 --- a/libcnb-cargo/README.md +++ b/libcnb-cargo/README.md @@ -10,22 +10,45 @@ $ cargo install libcnb-cargo ## Usage -Currently, there is only one sub-command: `package`. It allows users to package their Rust buildpack in a spec-compliant -manner and helps with cross-compilation. Using it is fairly simple, run `cargo libcnb package` inside the buildpack's +Currently, there is only one sub-command: `package`. It allows users to package their +Rust buildpack in a spec-compliant manner and helps with cross-compilation. + +```shell +$ cargo libcnb package --help +Packages a libcnb.rs Cargo project as a Cloud Native Buildpack + +Usage: cargo libcnb package [OPTIONS] + +Options: + --no-cross-compile-assistance Disable cross-compile assistance + --release Build in release mode, with optimizations + --target Build for the target triple [default: x86_64-unknown-linux-musl] + --package-dir Directory for packaged buildpacks, defaults to 'packaged' in Cargo workspace root + -h, --help Print help +``` + +Using it is fairly simple, run `cargo libcnb package` inside the buildpack's project directory: ```shell $ cargo libcnb package -INFO - Reading buildpack metadata... -INFO - Found buildpack libcnb-examples/my-buildpack with version 0.1.0. -INFO - Determining automatic cross-compile settings... -INFO - Building binaries (x86_64-unknown-linux-musl)... +šŸšš Preparing package directory... +šŸ–„ļø Gathering Cargo configuration (for x86_64-unknown-linux-musl) +šŸ—ļø Building buildpack dependency graph... +šŸ”€ Determining build order... +šŸšš Building 1 buildpacks... +šŸ“¦ [1/1] Building libcnb-examples/my-buildpack (./) # Omitting compilation output... - Finished dev [unoptimized + debuginfo] target(s) in 4.29s -INFO - Writing buildpack directory... -INFO - Successfully wrote buildpack directory: target/buildpack/debug/libcnb-examples_my-buildpack (3.26 MiB) -INFO - Packaging successfully finished! -INFO - Hint: To test your buildpack locally with pack, run: pack build my-image --buildpack target/buildpack/debug/libcnb-examples_my-buildpack --path /path/to/application + Finished dev [unoptimized] target(s) in 8.24s +Successfully wrote buildpack directory: packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack (4.09 MiB) +āœØ Packaging successfully finished! + +šŸ’” To test your buildpack locally with pack, run: +pack build my-image-name \ + --buildpack packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack \ + --path /path/to/application + +/Users/example/src/my-buildpack/packaged/x86_64-unknown-linux-musl/debug/libcnb-examples_my-buildpack ``` [Latest Version]: https://img.shields.io/crates/v/libcnb-cargo.svg diff --git a/libcnb-cargo/src/package/command.rs b/libcnb-cargo/src/package/command.rs index 668dbb4b..1bdb4045 100644 --- a/libcnb-cargo/src/package/command.rs +++ b/libcnb-cargo/src/package/command.rs @@ -40,7 +40,7 @@ pub(crate) fn execute(args: &PackageArgs) -> Result<(), Error> { eprintln!("šŸ–„ļø Gathering Cargo configuration (for {})", args.target); let cargo_build_env = if args.no_cross_compile_assistance { - vec![] + Vec::new() } else { match cross_compile_assistance(&args.target) { CrossCompileAssistance::Configuration { cargo_env } => cargo_env, @@ -51,7 +51,7 @@ pub(crate) fn execute(args: &PackageArgs) -> Result<(), Error> { ); eprintln!("This is not an error, but without proper cross-compile settings in your Cargo manifest and locally installed toolchains, compilation might fail."); eprintln!("To disable this warning, pass --no-cross-compile-assistance."); - vec![] + Vec::new() } CrossCompileAssistance::HelpText(help_text) => { eprintln!("{help_text}"); @@ -61,9 +61,8 @@ pub(crate) fn execute(args: &PackageArgs) -> Result<(), Error> { }; eprintln!("šŸ—ļø Building buildpack dependency graph..."); - let buildpack_dependency_graph = - build_libcnb_buildpacks_dependency_graph(&workspace_root_path, &[&package_dir]) - .map_err(Error::CannotBuildBuildpackDependencyGraph)?; + let buildpack_dependency_graph = build_libcnb_buildpacks_dependency_graph(&workspace_root_path) + .map_err(Error::CannotBuildBuildpackDependencyGraph)?; eprintln!("šŸ”€ Determining build order..."); let root_nodes = buildpack_dependency_graph diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/Cargo.toml b/libcnb-cargo/tests/fixtures/multiple_buildpacks/Cargo.toml similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/Cargo.toml rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/Cargo.toml diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/not_libcnb/bin/build b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/not_libcnb/bin/build similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/not_libcnb/bin/build rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/not_libcnb/bin/build diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/not_libcnb/bin/detect b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/not_libcnb/bin/detect similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/not_libcnb/bin/detect rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/not_libcnb/bin/detect diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/not_libcnb/buildpack.toml b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/not_libcnb/buildpack.toml similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/not_libcnb/buildpack.toml rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/not_libcnb/buildpack.toml diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/one/Cargo.toml b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/one/Cargo.toml similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/one/Cargo.toml rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/one/Cargo.toml diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/one/buildpack.toml b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/one/buildpack.toml similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/one/buildpack.toml rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/one/buildpack.toml diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/one/src/main.rs b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/one/src/main.rs similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/one/src/main.rs rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/one/src/main.rs diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/two/Cargo.toml b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/two/Cargo.toml similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/two/Cargo.toml rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/two/Cargo.toml diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/two/buildpack.toml b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/two/buildpack.toml similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/two/buildpack.toml rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/two/buildpack.toml diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/two/src/main.rs b/libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/two/src/main.rs similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/buildpacks/two/src/main.rs rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/buildpacks/two/src/main.rs diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/meta-buildpacks/meta-one/buildpack.toml b/libcnb-cargo/tests/fixtures/multiple_buildpacks/composite-buildpacks/composite-one/buildpack.toml similarity index 87% rename from libcnb-cargo/fixtures/multiple_buildpacks/meta-buildpacks/meta-one/buildpack.toml rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/composite-buildpacks/composite-one/buildpack.toml index 01b40f88..241ebe51 100644 --- a/libcnb-cargo/fixtures/multiple_buildpacks/meta-buildpacks/meta-one/buildpack.toml +++ b/libcnb-cargo/tests/fixtures/multiple_buildpacks/composite-buildpacks/composite-one/buildpack.toml @@ -1,8 +1,8 @@ api = "0.8" [buildpack] -id = "multiple-buildpacks/meta-one" -name = "Meta-buildpack Test" +id = "multiple-buildpacks/composite-one" +name = "Composite Buildpack Test" version = "0.0.0" homepage = "https://example.com" description = "Official test example" diff --git a/libcnb-cargo/fixtures/multiple_buildpacks/meta-buildpacks/meta-one/package.toml b/libcnb-cargo/tests/fixtures/multiple_buildpacks/composite-buildpacks/composite-one/package.toml similarity index 100% rename from libcnb-cargo/fixtures/multiple_buildpacks/meta-buildpacks/meta-one/package.toml rename to libcnb-cargo/tests/fixtures/multiple_buildpacks/composite-buildpacks/composite-one/package.toml diff --git a/libcnb-cargo/fixtures/no_buildpacks/Cargo.toml b/libcnb-cargo/tests/fixtures/no_buildpacks/Cargo.toml similarity index 100% rename from libcnb-cargo/fixtures/no_buildpacks/Cargo.toml rename to libcnb-cargo/tests/fixtures/no_buildpacks/Cargo.toml diff --git a/libcnb-cargo/fixtures/no_buildpacks/not_a_buildpack/Cargo.toml b/libcnb-cargo/tests/fixtures/no_buildpacks/not_a_buildpack/Cargo.toml similarity index 100% rename from libcnb-cargo/fixtures/no_buildpacks/not_a_buildpack/Cargo.toml rename to libcnb-cargo/tests/fixtures/no_buildpacks/not_a_buildpack/Cargo.toml diff --git a/libcnb-cargo/fixtures/no_buildpacks/not_a_buildpack/src/main.rs b/libcnb-cargo/tests/fixtures/no_buildpacks/not_a_buildpack/src/main.rs similarity index 100% rename from libcnb-cargo/fixtures/no_buildpacks/not_a_buildpack/src/main.rs rename to libcnb-cargo/tests/fixtures/no_buildpacks/not_a_buildpack/src/main.rs diff --git a/libcnb-cargo/fixtures/single_buildpack/Cargo.toml b/libcnb-cargo/tests/fixtures/single_buildpack/Cargo.toml similarity index 100% rename from libcnb-cargo/fixtures/single_buildpack/Cargo.toml rename to libcnb-cargo/tests/fixtures/single_buildpack/Cargo.toml diff --git a/libcnb-cargo/fixtures/single_buildpack/buildpack.toml b/libcnb-cargo/tests/fixtures/single_buildpack/buildpack.toml similarity index 100% rename from libcnb-cargo/fixtures/single_buildpack/buildpack.toml rename to libcnb-cargo/tests/fixtures/single_buildpack/buildpack.toml diff --git a/libcnb-cargo/fixtures/single_buildpack/src/main.rs b/libcnb-cargo/tests/fixtures/single_buildpack/src/main.rs similarity index 100% rename from libcnb-cargo/fixtures/single_buildpack/src/main.rs rename to libcnb-cargo/tests/fixtures/single_buildpack/src/main.rs diff --git a/libcnb-cargo/tests/integration_test.rs b/libcnb-cargo/tests/integration_test.rs index fc23897a..a1c27383 100644 --- a/libcnb-cargo/tests/integration_test.rs +++ b/libcnb-cargo/tests/integration_test.rs @@ -42,12 +42,17 @@ fn package_buildpack_in_single_buildpack_project() { #[test] #[ignore = "integration test"] -fn package_single_meta_buildpack_in_monorepo_buildpack_project() { +fn package_single_composite_buildpack_in_monorepo_buildpack_project() { let fixture_dir = copy_fixture_to_temp_dir("multiple_buildpacks").unwrap(); let output = Command::new(CARGO_LIBCNB_BINARY_UNDER_TEST) .args(["libcnb", "package", "--release"]) - .current_dir(fixture_dir.path().join("meta-buildpacks").join("meta-one")) + .current_dir( + fixture_dir + .path() + .join("composite-buildpacks") + .join("composite-one"), + ) .output() .unwrap(); @@ -61,14 +66,14 @@ fn package_single_meta_buildpack_in_monorepo_buildpack_project() { String::from_utf8_lossy(&output.stdout), format!( "{}\n", - packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/meta-one")) + packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/composite-one")) .to_string_lossy() ) ); - validate_packaged_meta_buildpack( - &packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/meta-one")), - &buildpack_id!("multiple-buildpacks/meta-one"), + validate_packaged_composite_buildpack( + &packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/composite-one")), + &buildpack_id!("multiple-buildpacks/composite-one"), &[ PackageDescriptorDependency::try_from(packaged_buildpack_dir_resolver(&buildpack_id!( "multiple-buildpacks/one" @@ -148,7 +153,9 @@ fn package_all_buildpacks_in_monorepo_buildpack_project() { format!( "{}\n", [ - packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/meta-one")), + packaged_buildpack_dir_resolver(&buildpack_id!( + "multiple-buildpacks/composite-one" + )), packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/one")), packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/two")), ] @@ -157,9 +164,9 @@ fn package_all_buildpacks_in_monorepo_buildpack_project() { ) ); - validate_packaged_meta_buildpack( - &packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/meta-one")), - &buildpack_id!("multiple-buildpacks/meta-one"), + validate_packaged_composite_buildpack( + &packaged_buildpack_dir_resolver(&buildpack_id!("multiple-buildpacks/composite-one")), + &buildpack_id!("multiple-buildpacks/composite-one"), &[ PackageDescriptorDependency::try_from(packaged_buildpack_dir_resolver(&buildpack_id!( "multiple-buildpacks/one" @@ -185,7 +192,7 @@ fn package_all_buildpacks_in_monorepo_buildpack_project() { #[test] #[ignore = "integration test"] -fn package_non_libcnb_buildpack_in_meta_buildpack_project() { +fn package_non_libcnb_buildpack_in_composite_buildpack_project() { let fixture_dir = copy_fixture_to_temp_dir("multiple_buildpacks").unwrap(); let output = Command::new(CARGO_LIBCNB_BINARY_UNDER_TEST) @@ -219,6 +226,53 @@ fn package_command_error_when_run_in_project_with_no_buildpacks() { ); } +#[test] +#[ignore = "integration test"] +fn package_command_respects_ignore_files() { + let fixture_dir = copy_fixture_to_temp_dir("multiple_buildpacks").unwrap(); + + // The `ignore` crate supports `.ignore` files. So this first `cargo libcnb package` execution + // just sanity checks that our ignore rules will be respected. + let ignore_file = fixture_dir.path().join(".ignore"); + fs::write(&ignore_file, "composite-buildpacks\nbuildpacks\n").unwrap(); + + let output = Command::new(CARGO_LIBCNB_BINARY_UNDER_TEST) + .args(["libcnb", "package", "--release"]) + .current_dir(fixture_dir.path()) + .output() + .unwrap(); + + assert_ne!(output.status.code(), Some(0)); + assert_eq!( + String::from_utf8_lossy(&output.stderr), + "šŸšš Preparing package directory...\nšŸ–„\u{fe0f} Gathering Cargo configuration (for x86_64-unknown-linux-musl)\nšŸ—\u{fe0f} Building buildpack dependency graph...\nšŸ”€ Determining build order...\nāŒ No buildpacks found!\n" + ); + + fs::remove_file(ignore_file).unwrap(); + + // The `ignore` crate supports `.gitignore` files but only if the folder is within a git repository + // which is the default configuration used in our directory traversal. + // https://docs.rs/ignore/latest/ignore/struct.WalkBuilder.html#method.require_git + // + // So this second `cargo libcnb package` execution just sanity checks that our gitignore rules + // in a git repository will be respected. + fs::create_dir(fixture_dir.path().join(".git")).unwrap(); + let ignore_file = fixture_dir.path().join(".gitignore"); + fs::write(ignore_file, "composite-buildpacks\nbuildpacks\n").unwrap(); + + let output = Command::new(CARGO_LIBCNB_BINARY_UNDER_TEST) + .args(["libcnb", "package", "--release"]) + .current_dir(fixture_dir.path()) + .output() + .unwrap(); + + assert_ne!(output.status.code(), Some(0)); + assert_eq!( + String::from_utf8_lossy(&output.stderr), + "šŸšš Preparing package directory...\nšŸ–„\u{fe0f} Gathering Cargo configuration (for x86_64-unknown-linux-musl)\nšŸ—\u{fe0f} Building buildpack dependency graph...\nšŸ”€ Determining build order...\nāŒ No buildpacks found!\n" + ); +} + fn validate_packaged_buildpack(packaged_buildpack_dir: &Path, buildpack_id: &BuildpackId) { assert!(packaged_buildpack_dir.join("buildpack.toml").exists()); assert!(packaged_buildpack_dir.join("package.toml").exists()); @@ -234,7 +288,7 @@ fn validate_packaged_buildpack(packaged_buildpack_dir: &Path, buildpack_id: &Bui ); } -fn validate_packaged_meta_buildpack( +fn validate_packaged_composite_buildpack( packaged_buildpack_dir: &Path, buildpack_id: &BuildpackId, expected_package_descriptor_dependencies: &[PackageDescriptorDependency], @@ -260,7 +314,7 @@ fn validate_packaged_meta_buildpack( fn copy_fixture_to_temp_dir(name: &str) -> Result { let fixture_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("fixtures") + .join("tests/fixtures") .join(name); // Instead of using `tempfile::tempdir` directly, we get the temporary directory ourselves and @@ -274,7 +328,7 @@ fn copy_fixture_to_temp_dir(name: &str) -> Result { env::temp_dir() .canonicalize() .and_then(tempdir_in) - .and_then(|temp_dir| copy_dir_recursively(&fixture_dir, temp_dir.path()).map(|_| temp_dir)) + .and_then(|temp_dir| copy_dir_recursively(&fixture_dir, temp_dir.path()).map(|()| temp_dir)) } fn copy_dir_recursively(source: &Path, destination: &Path) -> std::io::Result<()> { diff --git a/libcnb-data/src/build_plan.rs b/libcnb-data/src/build_plan.rs index 97922e0b..6802accc 100644 --- a/libcnb-data/src/build_plan.rs +++ b/libcnb-data/src/build_plan.rs @@ -46,8 +46,8 @@ impl BuildPlanBuilder { pub fn or(mut self) -> Self { self.acc .push_back((self.current_provides, self.current_requires)); - self.current_provides = vec![]; - self.current_requires = vec![]; + self.current_provides = Vec::new(); + self.current_requires = Vec::new(); self } diff --git a/libcnb-data/src/buildpack/mod.rs b/libcnb-data/src/buildpack/mod.rs index bdf92f79..950e17c9 100644 --- a/libcnb-data/src/buildpack/mod.rs +++ b/libcnb-data/src/buildpack/mod.rs @@ -19,8 +19,9 @@ pub use version::*; /// For parsing of [buildpack.toml](https://github.com/buildpacks/spec/blob/main/buildpack.md#buildpacktoml-toml) /// files when support for multiple types of buildpack is required. /// -/// When a specific buildpack type is expected, use [`SingleBuildpackDescriptor`] or [`MetaBuildpackDescriptor`] directly instead, -/// since it allows for more detailed error messages if parsing fails. +/// When a specific buildpack type is expected, use [`ComponentBuildpackDescriptor`] or +/// [`CompositeBuildpackDescriptor`] directly instead, since they allow for more detailed +/// error messages if parsing fails. /// /// # Example: /// ``` @@ -49,40 +50,41 @@ pub use version::*; /// toml::from_str::(toml_str) /// .expect("buildpack.toml did not match a known type!"); /// match buildpack_descriptor { -/// BuildpackDescriptor::Single(buildpack) => { -/// println!("Found buildpack: {}", buildpack.buildpack.id); +/// BuildpackDescriptor::Component(buildpack) => { +/// println!("Found component buildpack: {}", buildpack.buildpack.id); /// } -/// BuildpackDescriptor::Meta(buildpack) => { -/// println!("Found meta-buildpack: {}", buildpack.buildpack.id); +/// BuildpackDescriptor::Composite(buildpack) => { +/// println!("Found composite buildpack: {}", buildpack.buildpack.id); /// } /// }; /// ``` #[derive(Deserialize, Debug)] #[serde(untagged)] pub enum BuildpackDescriptor { - Single(SingleBuildpackDescriptor), - Meta(MetaBuildpackDescriptor), + Component(ComponentBuildpackDescriptor), + Composite(CompositeBuildpackDescriptor), } impl BuildpackDescriptor { pub fn buildpack(&self) -> &Buildpack { match self { - BuildpackDescriptor::Single(descriptor) => &descriptor.buildpack, - BuildpackDescriptor::Meta(descriptor) => &descriptor.buildpack, + BuildpackDescriptor::Component(descriptor) => &descriptor.buildpack, + BuildpackDescriptor::Composite(descriptor) => &descriptor.buildpack, } } } -/// Data structure for the Buildpack descriptor (buildpack.toml) of a single buildpack. +/// Data structure for the Buildpack descriptor (buildpack.toml) of a component buildpack. /// /// Representation of [buildpack.toml](https://github.com/buildpacks/spec/blob/main/buildpack.md#buildpacktoml-toml) -/// when the buildpack is a single buildpack that implements the Buildpack Interface (ie: not a meta-buildpack). +/// when the buildpack is a component buildpack - one that implements the Buildpack Interface +/// (ie: contains `/bin/detect` and `/bin/build` executables). /// /// If support for multiple buildpack types is required, use [`BuildpackDescriptor`] instead. /// /// # Example: /// ``` -/// use libcnb_data::buildpack::{SingleBuildpackDescriptor, Stack}; +/// use libcnb_data::buildpack::{ComponentBuildpackDescriptor, Stack}; /// use libcnb_data::buildpack_id; /// /// let toml_str = r#" @@ -105,29 +107,31 @@ impl BuildpackDescriptor { /// "#; /// /// let buildpack_descriptor = -/// toml::from_str::(toml_str).unwrap(); +/// toml::from_str::(toml_str).unwrap(); /// assert_eq!(buildpack_descriptor.buildpack.id, buildpack_id!("foo/bar")); -/// assert_eq!(buildpack_descriptor.stacks, vec![Stack::Any]); +/// assert_eq!(buildpack_descriptor.stacks, [Stack::Any]); /// ``` #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] -pub struct SingleBuildpackDescriptor { +pub struct ComponentBuildpackDescriptor { pub api: BuildpackApi, pub buildpack: Buildpack, pub stacks: Vec, pub metadata: BM, } -/// Data structure for the Buildpack descriptor (buildpack.toml) of a meta-buildpack. +/// Data structure for the Buildpack descriptor (buildpack.toml) of a composite buildpack. /// /// Representation of [buildpack.toml](https://github.com/buildpacks/spec/blob/main/buildpack.md#buildpacktoml-toml) -/// when the buildpack is a meta-buildpack. +/// when the buildpack is a composite buildpack - one that does not implement the Buildpack Interface +/// itself (ie: does not contain `/bin/detect` and `/bin/build` executables) but instead references +/// other buildpacks via an order definition. /// /// If support for multiple buildpack types is required, use [`BuildpackDescriptor`] instead. /// /// # Example: /// ``` -/// use libcnb_data::buildpack::MetaBuildpackDescriptor; +/// use libcnb_data::buildpack::CompositeBuildpackDescriptor; /// use libcnb_data::buildpack_id; /// /// let toml_str = r#" @@ -153,12 +157,12 @@ pub struct SingleBuildpackDescriptor { /// "#; /// /// let buildpack_descriptor = -/// toml::from_str::(toml_str).unwrap(); +/// toml::from_str::(toml_str).unwrap(); /// assert_eq!(buildpack_descriptor.buildpack.id, buildpack_id!("foo/bar")); /// ``` #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] -pub struct MetaBuildpackDescriptor { +pub struct CompositeBuildpackDescriptor { pub api: BuildpackApi, pub buildpack: Buildpack, pub order: Vec, @@ -216,7 +220,7 @@ mod tests { #[test] #[allow(clippy::too_many_lines)] - fn deserialize_singlebuildpack() { + fn deserialize_component_buildpack() { let toml_str = r#" api = "0.9" @@ -261,7 +265,8 @@ id = "*" checksum = "abc123" "#; - let buildpack_descriptor = toml::from_str::(toml_str).unwrap(); + let buildpack_descriptor = + toml::from_str::(toml_str).unwrap(); assert_eq!( buildpack_descriptor.api, @@ -290,11 +295,11 @@ checksum = "abc123" ); assert_eq!( buildpack_descriptor.buildpack.keywords, - vec![String::from("foo"), String::from("bar")] + [String::from("foo"), String::from("bar")] ); assert_eq!( buildpack_descriptor.buildpack.licenses, - vec![ + [ License { r#type: Some(String::from("BSD-3-Clause")), uri: None @@ -319,7 +324,7 @@ checksum = "abc123" ); assert_eq!( buildpack_descriptor.stacks, - vec![ + [ Stack::Specific { // Cannot use the `stack_id!` macro due to: https://github.com/heroku/libcnb.rs/issues/179 id: "heroku-20".parse().unwrap(), @@ -343,7 +348,7 @@ checksum = "abc123" } #[test] - fn deserialize_metabuildpack() { + fn deserialize_composite_buildpack() { let toml_str = r#" api = "0.9" @@ -381,7 +386,8 @@ optional = true checksum = "abc123" "#; - let buildpack_descriptor = toml::from_str::(toml_str).unwrap(); + let buildpack_descriptor = + toml::from_str::(toml_str).unwrap(); assert_eq!( buildpack_descriptor.api, @@ -410,11 +416,11 @@ checksum = "abc123" ); assert_eq!( buildpack_descriptor.buildpack.keywords, - vec![String::from("foo"), String::from("bar")] + [String::from("foo"), String::from("bar")] ); assert_eq!( buildpack_descriptor.buildpack.licenses, - vec![ + [ License { r#type: Some(String::from("BSD-3-Clause")), uri: None @@ -431,7 +437,7 @@ checksum = "abc123" ); assert_eq!( buildpack_descriptor.order, - vec![Order { + [Order { group: vec![ Group { id: "foo/bar".parse().unwrap(), @@ -453,7 +459,7 @@ checksum = "abc123" } #[test] - fn deserialize_minimal_singlebuildpack() { + fn deserialize_minimal_component_buildpack() { let toml_str = r#" api = "0.9" @@ -465,7 +471,8 @@ version = "0.0.1" id = "*" "#; - let buildpack_descriptor = toml::from_str::(toml_str).unwrap(); + let buildpack_descriptor = + toml::from_str::(toml_str).unwrap(); assert_eq!( buildpack_descriptor.api, @@ -489,12 +496,12 @@ id = "*" ); assert_eq!(buildpack_descriptor.buildpack.licenses, Vec::new()); assert_eq!(buildpack_descriptor.buildpack.sbom_formats, HashSet::new()); - assert_eq!(buildpack_descriptor.stacks, vec![Stack::Any]); + assert_eq!(buildpack_descriptor.stacks, [Stack::Any]); assert_eq!(buildpack_descriptor.metadata, None); } #[test] - fn deserialize_minimal_metabuildpack() { + fn deserialize_minimal_composite_buildpack() { let toml_str = r#" api = "0.9" @@ -509,7 +516,8 @@ id = "foo/bar" version = "0.0.1" "#; - let buildpack_descriptor = toml::from_str::(toml_str).unwrap(); + let buildpack_descriptor = + toml::from_str::(toml_str).unwrap(); assert_eq!( buildpack_descriptor.api, @@ -534,7 +542,7 @@ version = "0.0.1" assert_eq!(buildpack_descriptor.buildpack.licenses, Vec::new()); assert_eq!( buildpack_descriptor.order, - vec![Order { + [Order { group: vec![Group { id: "foo/bar".parse().unwrap(), version: BuildpackVersion::new(0, 0, 1), @@ -546,7 +554,7 @@ version = "0.0.1" } #[test] - fn deserialize_buildpackdescriptor_single() { + fn deserialize_buildpackdescriptor_component() { let toml_str = r#" api = "0.9" @@ -561,12 +569,12 @@ id = "*" let buildpack_descriptor = toml::from_str::(toml_str).unwrap(); assert!(matches!( buildpack_descriptor, - BuildpackDescriptor::Single(_) + BuildpackDescriptor::Component(_) )); } #[test] - fn deserialize_buildpackdescriptor_meta() { + fn deserialize_buildpackdescriptor_composite() { let toml_str = r#" api = "0.9" @@ -582,7 +590,10 @@ version = "0.0.1" "#; let buildpack_descriptor = toml::from_str::(toml_str).unwrap(); - assert!(matches!(buildpack_descriptor, BuildpackDescriptor::Meta(_))); + assert!(matches!( + buildpack_descriptor, + BuildpackDescriptor::Composite(_) + )); } #[test] @@ -610,10 +621,10 @@ version = "0.0.1" "data did not match any variant of untagged enum BuildpackDescriptor\n" ); - let err = toml::from_str::(toml_str).unwrap_err(); + let err = toml::from_str::(toml_str).unwrap_err(); assert!(err.to_string().contains("unknown field `order`")); - let err = toml::from_str::(toml_str).unwrap_err(); + let err = toml::from_str::(toml_str).unwrap_err(); assert!(err.to_string().contains("unknown field `stacks`")); } } diff --git a/libcnb-data/src/launch.rs b/libcnb-data/src/launch.rs index d9238b6d..0265a25f 100644 --- a/libcnb-data/src/launch.rs +++ b/libcnb-data/src/launch.rs @@ -24,7 +24,7 @@ pub struct Launch { /// let launch_toml = LaunchBuilder::new() /// .process( /// ProcessBuilder::new(process_type!("web"), ["bundle"]) -/// .args(vec!["exec", "ruby", "app.rb"]) +/// .args(["exec", "ruby", "app.rb"]) /// .build(), /// ) /// .build(); @@ -318,7 +318,7 @@ mod tests { fn launch_builder_add_processes() { let launch = LaunchBuilder::new() .process(ProcessBuilder::new(process_type!("web"), ["web_command"]).build()) - .processes(vec![ + .processes([ ProcessBuilder::new(process_type!("another"), ["another_command"]).build(), ProcessBuilder::new(process_type!("worker"), ["worker_command"]).build(), ]) @@ -326,7 +326,7 @@ mod tests { assert_eq!( launch.processes, - vec![ + [ ProcessBuilder::new(process_type!("web"), ["web_command"]).build(), ProcessBuilder::new(process_type!("another"), ["another_command"]).build(), ProcessBuilder::new(process_type!("worker"), ["worker_command"]).build(), @@ -372,7 +372,7 @@ command = ["foo"] Ok(Process { r#type: process_type!("web"), command: vec![String::from("foo")], - args: vec![], + args: Vec::new(), default: false, working_directory: WorkingDirectory::App }) @@ -419,7 +419,7 @@ working-directory = "dist" Process { r#type: process_type!("web"), command: vec![String::from("java")], - args: vec![], + args: Vec::new(), default: false, working_directory: WorkingDirectory::App } @@ -432,7 +432,7 @@ working-directory = "dist" Process { r#type: process_type!("web"), command: vec![String::from("java")], - args: vec![], + args: Vec::new(), default: true, working_directory: WorkingDirectory::App } @@ -445,7 +445,7 @@ working-directory = "dist" Process { r#type: process_type!("web"), command: vec![String::from("java")], - args: vec![], + args: Vec::new(), default: true, working_directory: WorkingDirectory::Directory(PathBuf::from("dist")) } @@ -457,7 +457,7 @@ working-directory = "dist" assert_eq!( ProcessBuilder::new(process_type!("web"), ["java"]) .arg("foo") - .args(vec!["baz", "eggs"]) + .args(["baz", "eggs"]) .arg("bar") .build(), Process { diff --git a/libcnb-data/src/newtypes.rs b/libcnb-data/src/newtypes.rs index 80a7d935..7a25ce20 100644 --- a/libcnb-data/src/newtypes.rs +++ b/libcnb-data/src/newtypes.rs @@ -242,13 +242,13 @@ mod tests { #[test] fn join() { - let names = vec![capitalized_name!("A"), capitalized_name!("B")]; + let names = [capitalized_name!("A"), capitalized_name!("B")]; assert_eq!("A, B", names.join(", ")); } #[test] fn ord() { - let mut names = vec![ + let mut names = [ capitalized_name!("A"), capitalized_name!("C"), capitalized_name!("B"), @@ -256,7 +256,7 @@ mod tests { names.sort(); assert_eq!( - vec![ + [ capitalized_name!("A"), capitalized_name!("B"), capitalized_name!("C") diff --git a/libcnb-data/src/package_descriptor.rs b/libcnb-data/src/package_descriptor.rs index 71c812ac..89172787 100644 --- a/libcnb-data/src/package_descriptor.rs +++ b/libcnb-data/src/package_descriptor.rs @@ -37,7 +37,9 @@ pub struct PackageDescriptor { /// The buildpack to package. pub buildpack: PackageDescriptorBuildpackReference, - /// A set of dependent buildpack locations, for packaging a meta-buildpack. Each dependent buildpack location must correspond to an order group within the meta-buildpack being packaged. + /// A set of dependent buildpack locations, for packaging a composite buildpack. + /// + /// Each dependent buildpack location must correspond to an order group within the composite buildpack being packaged. #[serde(default)] pub dependencies: Vec, @@ -51,7 +53,7 @@ impl Default for PackageDescriptor { PackageDescriptor { buildpack: PackageDescriptorBuildpackReference::try_from(".") .expect("a package.toml with buildpack.uri=\".\" should be valid"), - dependencies: vec![], + dependencies: Vec::new(), platform: Platform::default(), } } @@ -62,6 +64,7 @@ impl Default for PackageDescriptor { #[serde(deny_unknown_fields)] pub struct PackageDescriptorBuildpackReference { /// A URL or path to an archive, or a path to a directory. + /// /// If the `uri` field is a relative path it will be relative to the `package.toml` file. #[serde(deserialize_with = "deserialize_uri_reference")] #[serde(serialize_with = "serialize_uri_reference")] @@ -83,7 +86,7 @@ impl TryFrom<&str> for PackageDescriptorBuildpackReference { } } -/// A dependent buildpack location for packaging a meta-buildpack. +/// A dependent buildpack location for packaging a composite buildpack. #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] #[serde(deny_unknown_fields)] pub struct PackageDescriptorDependency { @@ -218,7 +221,7 @@ os = "windows" assert_eq!(package_descriptor.platform.os, Windows); assert_eq!( package_descriptor.dependencies, - vec![ + [ PackageDescriptorDependency::try_from("libcnb:buildpack-id").unwrap(), PackageDescriptorDependency::try_from("../relative/path").unwrap(), PackageDescriptorDependency::try_from("/absolute/path").unwrap(), diff --git a/libcnb-package/Cargo.toml b/libcnb-package/Cargo.toml index 2f136e9c..f60779e5 100644 --- a/libcnb-package/Cargo.toml +++ b/libcnb-package/Cargo.toml @@ -13,6 +13,7 @@ include = ["src/**/*", "LICENSE", "README.md"] [dependencies] cargo_metadata = "0.17.0" +ignore = "0.4" libcnb-common.workspace = true libcnb-data.workspace = true petgraph = { version = "0.6.3", default-features = false } diff --git a/libcnb-package/src/buildpack_dependency_graph.rs b/libcnb-package/src/buildpack_dependency_graph.rs index 5021cb4b..ae2901f0 100644 --- a/libcnb-package/src/buildpack_dependency_graph.rs +++ b/libcnb-package/src/buildpack_dependency_graph.rs @@ -12,9 +12,9 @@ use petgraph::Graph; use std::convert::Infallible; use std::path::{Path, PathBuf}; -/// Creates a dependency graph of libcnb.rs and meta buildpacks in a directory. +/// Creates a dependency graph of libcnb.rs and composite buildpacks in a directory. /// -/// Buildpacks that aren't implemented with libcnb.rs or aren't meta-buildpacks will not be part +/// Buildpacks that aren't implemented with libcnb.rs or aren't composite buildpacks will not be part /// of the dependency graph. Examples buildpacks that are not included are docker image URLs or /// directories containing CNBs written in bash. /// @@ -27,9 +27,8 @@ use std::path::{Path, PathBuf}; /// package.toml or an IO error occurred while traversing the given directory. pub fn build_libcnb_buildpacks_dependency_graph( cargo_workspace_root: &Path, - ignore: &[&Path], ) -> Result, BuildBuildpackDependencyGraphError> { - find_buildpack_dirs(cargo_workspace_root, ignore) + find_buildpack_dirs(cargo_workspace_root) .map_err(BuildBuildpackDependencyGraphError::FindBuildpackDirectories) .and_then(|buildpack_directories| { buildpack_directories @@ -37,7 +36,7 @@ pub fn build_libcnb_buildpacks_dependency_graph( .filter(|buildpack_directory| { matches!( determine_buildpack_kind(buildpack_directory), - Some(BuildpackKind::LibCnbRs | BuildpackKind::Meta) + Some(BuildpackKind::LibCnbRs | BuildpackKind::Composite) ) }) .map(|buildpack_directory| { @@ -73,7 +72,7 @@ fn build_libcnb_buildpack_dependency_graph_node( ) }) }) - .unwrap_or(Ok(vec![])) + .unwrap_or(Ok(Vec::new())) }?; Ok(BuildpackDependencyGraphNode { @@ -85,8 +84,8 @@ fn build_libcnb_buildpack_dependency_graph_node( #[derive(thiserror::Error, Debug)] pub enum BuildBuildpackDependencyGraphError { - #[error("IO error while finding buildpack directories: {0}")] - FindBuildpackDirectories(std::io::Error), + #[error("Error while finding buildpack directories: {0}")] + FindBuildpackDirectories(ignore::Error), #[error("Cannot read buildpack descriptor: {0}")] ReadBuildpackDescriptorError(TomlFileError), #[error("Cannot read package descriptor: {0}")] diff --git a/libcnb-package/src/buildpack_kind.rs b/libcnb-package/src/buildpack_kind.rs index d5ab9bb6..2873fbc8 100644 --- a/libcnb-package/src/buildpack_kind.rs +++ b/libcnb-package/src/buildpack_kind.rs @@ -7,19 +7,19 @@ pub fn determine_buildpack_kind(buildpack_dir: &Path) -> Option { read_toml_file::(buildpack_dir.join("buildpack.toml")) .ok() .map(|buildpack_descriptor| match buildpack_descriptor { - BuildpackDescriptor::Single(_) => { + BuildpackDescriptor::Component(_) => { if buildpack_dir.join("Cargo.toml").is_file() { BuildpackKind::LibCnbRs } else { BuildpackKind::Other } } - BuildpackDescriptor::Meta(_) => BuildpackKind::Meta, + BuildpackDescriptor::Composite(_) => BuildpackKind::Composite, }) } pub enum BuildpackKind { + Composite, LibCnbRs, - Meta, Other, } diff --git a/libcnb-package/src/cross_compile.rs b/libcnb-package/src/cross_compile.rs index 43d0c943..3844b224 100644 --- a/libcnb-package/src/cross_compile.rs +++ b/libcnb-package/src/cross_compile.rs @@ -48,7 +48,9 @@ https://github.com/messense/homebrew-macos-cross-toolchains"#, }) } else if target_triple.as_ref() == X86_64_UNKNOWN_LINUX_MUSL && cfg!(target_os = "linux") { match which("musl-gcc") { - Ok(_) => CrossCompileAssistance::Configuration { cargo_env: vec![] }, + Ok(_) => CrossCompileAssistance::Configuration { + cargo_env: Vec::new(), + }, Err(_) => CrossCompileAssistance::HelpText(String::from( r#"For cross-compilation from Linux to x86_64-unknown-linux-musl, a C compiler and linker for the target platform must be installed on your computer. diff --git a/libcnb-package/src/dependency_graph.rs b/libcnb-package/src/dependency_graph.rs index a9bf2018..77a55f0f 100644 --- a/libcnb-package/src/dependency_graph.rs +++ b/libcnb-package/src/dependency_graph.rs @@ -83,7 +83,7 @@ where T: DependencyNode, I: PartialEq, { - let mut order: Vec<&T> = vec![]; + let mut order: Vec<&T> = Vec::new(); let mut dfs = DfsPostOrder::empty(&graph); for root_node in root_nodes { let idx = graph @@ -128,8 +128,8 @@ mod tests { #[test] fn test_get_dependencies_one_level_deep() { - let a = ("a", vec![]); - let b = ("b", vec![]); + let a = ("a", Vec::new()); + let b = ("b", Vec::new()); let c = ("c", vec!["a", "b"]); let graph = create_dependency_graph(vec![a.clone(), b.clone(), c.clone()]).unwrap(); @@ -148,7 +148,7 @@ mod tests { #[test] fn test_get_dependencies_two_levels_deep() { - let a = ("a", vec![]); + let a = ("a", Vec::new()); let b = ("b", vec!["a"]); let c = ("c", vec!["b"]); @@ -169,9 +169,9 @@ mod tests { #[test] #[allow(clippy::many_single_char_names)] fn test_get_dependencies_with_overlap() { - let a = ("a", vec![]); - let b = ("b", vec![]); - let c = ("c", vec![]); + let a = ("a", Vec::new()); + let b = ("b", Vec::new()); + let c = ("c", Vec::new()); let d = ("d", vec!["a", "b"]); let e = ("e", vec!["b", "c"]); diff --git a/libcnb-package/src/lib.rs b/libcnb-package/src/lib.rs index 1fc77eb1..96aea209 100644 --- a/libcnb-package/src/lib.rs +++ b/libcnb-package/src/lib.rs @@ -106,39 +106,23 @@ fn create_file_symlink, Q: AsRef>( /// /// # Errors /// -/// Will return an `Err` if any I/O errors happen while walking the file system. -pub fn find_buildpack_dirs(start_dir: &Path, ignore: &[&Path]) -> std::io::Result> { - fn find_buildpack_dirs_recursive( - path: &Path, - ignore: &[&Path], - accumulator: &mut Vec, - ) -> std::io::Result<()> { - if ignore.contains(&path) { - return Ok(()); - } - - let metadata = path.metadata()?; - if metadata.is_dir() { - let entries = fs::read_dir(path)?; - for entry in entries { - let entry = entry?; - let metadata = entry.metadata()?; - if metadata.is_dir() { - find_buildpack_dirs_recursive(&entry.path(), ignore, accumulator)?; - } else if let Some(file_name) = entry.path().file_name() { - if file_name.to_string_lossy() == "buildpack.toml" { - accumulator.push(path.to_path_buf()); +/// Will return an `Err` if any I/O errors happen while walking the file system or any parsing errors +/// from reading a gitignore file. +pub fn find_buildpack_dirs(start_dir: &Path) -> Result, ignore::Error> { + ignore::Walk::new(start_dir) + .collect::, _>>() + .map(|entries| { + entries + .iter() + .filter_map(|entry| { + if entry.path().is_dir() && entry.path().join("buildpack.toml").exists() { + Some(entry.path().to_path_buf()) + } else { + None } - } - } - } - - Ok(()) - } - - let mut buildpack_dirs: Vec = vec![]; - find_buildpack_dirs_recursive(start_dir, ignore, &mut buildpack_dirs)?; - Ok(buildpack_dirs) + }) + .collect() + }) } /// Returns the path of the root workspace directory for a Rust Cargo project. This is often a useful diff --git a/libcnb-package/src/package.rs b/libcnb-package/src/package.rs index 0bc794c9..5f502167 100644 --- a/libcnb-package/src/package.rs +++ b/libcnb-package/src/package.rs @@ -11,7 +11,7 @@ use std::ffi::OsString; use std::fs; use std::path::{Path, PathBuf}; -/// Packages either a libcnb.rs or a meta-buildpack. +/// Packages either a libcnb.rs or a composite buildpack. /// /// # Errors /// @@ -33,9 +33,9 @@ pub fn package_buildpack( destination, ) .map_err(PackageBuildpackError::PackageLibcnbBuildpackError), - Some(BuildpackKind::Meta) => { - package_meta_buildpack(buildpack_directory, destination, dependencies) - .map_err(PackageBuildpackError::PackageMetaBuildpackError) + Some(BuildpackKind::Composite) => { + package_composite_buildpack(buildpack_directory, destination, dependencies) + .map_err(PackageBuildpackError::PackageCompositeBuildpackError) } _ => Err(PackageBuildpackError::UnsupportedBuildpack), } @@ -44,7 +44,7 @@ pub fn package_buildpack( #[derive(thiserror::Error, Debug)] pub enum PackageBuildpackError { #[error("{0}")] - PackageMetaBuildpackError(PackageMetaBuildpackError), + PackageCompositeBuildpackError(PackageCompositeBuildpackError), #[error("{0}")] PackageLibcnbBuildpackError(PackageLibcnbBuildpackError), #[error("Buildpack is not supported to be packaged")] @@ -103,7 +103,7 @@ pub enum PackageLibcnbBuildpackError { CargoMetadataError(cargo_metadata::Error), } -/// Packages a meta-buildpack. +/// Packages a composite buildpack. /// /// Packaging consists of copying `buildpack.toml` as well as `package.toml` to the given /// destination path. @@ -116,40 +116,40 @@ pub enum PackageLibcnbBuildpackError { /// /// Returns `Err` if a `libcnb:` URI refers to a buildpack not in `buildpack_paths` or packaging /// otherwise failed (i.e. IO errors). -pub fn package_meta_buildpack( +pub fn package_composite_buildpack( buildpack_directory: &Path, destination: &Path, buildpack_paths: &BTreeMap, -) -> Result<(), PackageMetaBuildpackError> { +) -> Result<(), PackageCompositeBuildpackError> { fs::copy( buildpack_directory.join("buildpack.toml"), destination.join("buildpack.toml"), ) - .map_err(PackageMetaBuildpackError::CouldNotCopyBuildpackToml)?; + .map_err(PackageCompositeBuildpackError::CouldNotCopyBuildpackToml)?; let package_descriptor_path = buildpack_directory.join("package.toml"); let normalized_package_descriptor = read_toml_file::(&package_descriptor_path) - .map_err(PackageMetaBuildpackError::CouldNotReadPackageDescriptor) + .map_err(PackageCompositeBuildpackError::CouldNotReadPackageDescriptor) .and_then(|package_descriptor| { normalize_package_descriptor( &package_descriptor, &package_descriptor_path, buildpack_paths, ) - .map_err(PackageMetaBuildpackError::NormalizePackageDescriptorError) + .map_err(PackageCompositeBuildpackError::NormalizePackageDescriptorError) })?; write_toml_file( &normalized_package_descriptor, destination.join("package.toml"), ) - .map_err(PackageMetaBuildpackError::CouldNotWritePackageDescriptor) + .map_err(PackageCompositeBuildpackError::CouldNotWritePackageDescriptor) } #[derive(thiserror::Error, Debug)] -pub enum PackageMetaBuildpackError { +pub enum PackageCompositeBuildpackError { #[error("Could not copy buildpack.toml: {0}")] CouldNotCopyBuildpackToml(std::io::Error), #[error("Could not read package.toml: {0}")] diff --git a/libcnb-test/Cargo.toml b/libcnb-test/Cargo.toml index 9c67538c..ec5dbc04 100644 --- a/libcnb-test/Cargo.toml +++ b/libcnb-test/Cargo.toml @@ -12,9 +12,9 @@ readme = "README.md" include = ["src/**/*", "LICENSE", "README.md"] [dependencies] -cargo_metadata = "0.17.0" fastrand = "2.0.0" fs_extra = "1.3.0" +libcnb-common.workspace = true libcnb-data.workspace = true libcnb-package.workspace = true tempfile = "3.7.1" @@ -22,3 +22,4 @@ tempfile = "3.7.1" [dev-dependencies] indoc = "2.0.3" ureq = { version = "2.7.1", default-features = false } +libcnb.workspace = true diff --git a/libcnb-test/README.md b/libcnb-test/README.md index 8f8d0e28..bdc9ade5 100644 --- a/libcnb-test/README.md +++ b/libcnb-test/README.md @@ -37,7 +37,7 @@ use libcnb_test::{assert_contains, assert_empty, BuildConfig, TestRunner}; // #[test] fn basic() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), |context| { assert_empty!(context.pack_stderr); assert_contains!(context.pack_stdout, "Expected build output"); @@ -54,7 +54,7 @@ use libcnb_test::{assert_contains, BuildConfig, TestRunner}; // #[test] fn rebuild() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), |context| { assert_contains!(context.pack_stdout, "Installing dependencies"); @@ -75,7 +75,7 @@ use libcnb_test::{assert_contains, BuildConfig, PackResult, TestRunner}; // #[test] fn expected_pack_failure() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/invalid-app") + BuildConfig::new("heroku/builder:22", "tests/fixtures/invalid-app") .expected_pack_result(PackResult::Failure), |context| { assert_contains!(context.pack_stderr, "ERROR: Invalid Procfile!"); @@ -92,7 +92,7 @@ use libcnb_test::{assert_empty, BuildConfig, TestRunner}; // #[test] fn run_shell_command() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), |context| { // ... let command_output = context.run_shell_command("python --version"); @@ -115,7 +115,7 @@ const TEST_PORT: u16 = 12345; // #[test] fn starting_web_server_container() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), |context| { // ... context.start_container( @@ -154,7 +154,7 @@ use libcnb_test::{assert_contains, BuildConfig, ContainerConfig, TestRunner}; // #[test] fn shell_exec() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), |context| { // ... context.start_container(ContainerConfig::new(), |container| { @@ -176,7 +176,7 @@ use std::fs; // #[test] fn dynamic_fixture() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/app").app_dir_preprocessor( + BuildConfig::new("heroku/builder:22", "tests/fixtures/app").app_dir_preprocessor( |app_dir| { fs::write(app_dir.join("runtime.txt"), "python-3.10").unwrap(); }, @@ -191,13 +191,15 @@ fn dynamic_fixture() { Building with multiple buildpacks, using [`BuildConfig::buildpacks`]: ```rust,no_run +use libcnb::data::buildpack_id; use libcnb_test::{BuildConfig, BuildpackReference, TestRunner}; // #[test] fn additional_buildpacks() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/app").buildpacks(vec![ - BuildpackReference::Crate, + BuildConfig::new("heroku/builder:22", "tests/fixtures/app").buildpacks([ + BuildpackReference::CurrentCrate, + BuildpackReference::WorkspaceBuildpack(buildpack_id!("my-project/buildpack")), BuildpackReference::Other(String::from("heroku/another-buildpack")), ]), |context| { @@ -211,7 +213,7 @@ fn additional_buildpacks() { - Rust tests are automatically run in parallel, however only if they are in the same crate. For integration tests Rust compiles each file as a separate crate. As such, make sure to - include all integration tests in a single file (either inlined or by including additional + include all integration tests in a single file (either inlined or by including additional test modules) to ensure they run in parallel. - If you would like to be able to more easily run your unit tests and integration tests separately, annotate each integration test with `#[ignore = "integration test"]`, which diff --git a/libcnb-test/src/build.rs b/libcnb-test/src/build.rs index bfde88c9..9114f224 100644 --- a/libcnb-test/src/build.rs +++ b/libcnb-test/src/build.rs @@ -1,27 +1,53 @@ -use cargo_metadata::MetadataCommand; -use libcnb_package::build::{build_buildpack_binaries, BuildBinariesError}; +use libcnb_common::toml_file::{read_toml_file, TomlFileError}; +use libcnb_data::buildpack::{BuildpackDescriptor, BuildpackId}; +use libcnb_package::buildpack_dependency_graph::{ + build_libcnb_buildpacks_dependency_graph, BuildBuildpackDependencyGraphError, +}; use libcnb_package::cross_compile::{cross_compile_assistance, CrossCompileAssistance}; -use libcnb_package::{assemble_buildpack_directory, CargoProfile}; -use std::path::PathBuf; -use tempfile::{tempdir, TempDir}; +use libcnb_package::dependency_graph::{get_dependencies, GetDependenciesError}; +use libcnb_package::output::create_packaged_buildpack_dir_resolver; +use libcnb_package::{find_cargo_workspace_root_dir, CargoProfile, FindCargoWorkspaceRootError}; +use std::collections::BTreeMap; +use std::fs; +use std::path::{Path, PathBuf}; /// Packages the current crate as a buildpack into a temporary directory. pub(crate) fn package_crate_buildpack( cargo_profile: CargoProfile, target_triple: impl AsRef, -) -> Result { - let cargo_manifest_dir = std::env::var("CARGO_MANIFEST_DIR") - .map(PathBuf::from) - .map_err(PackageCrateBuildpackError::CannotDetermineCrateDirectory)?; + cargo_manifest_dir: &Path, + target_buildpack_dir: &Path, +) -> Result { + let buildpack_toml = cargo_manifest_dir.join("buildpack.toml"); - let cargo_metadata = MetadataCommand::new() - .manifest_path(&cargo_manifest_dir.join("Cargo.toml")) - .exec() - .map_err(PackageCrateBuildpackError::CargoMetadataError)?; + assert!( + buildpack_toml.exists(), + "Could not package directory as buildpack! No `buildpack.toml` file exists at {}", + cargo_manifest_dir.display() + ); - let cargo_env = match cross_compile_assistance(target_triple.as_ref()) { + let buildpack_descriptor: BuildpackDescriptor = read_toml_file(buildpack_toml) + .map_err(PackageBuildpackError::CannotReadBuildpackDescriptor)?; + + package_buildpack( + &buildpack_descriptor.buildpack().id, + cargo_profile, + target_triple, + cargo_manifest_dir, + target_buildpack_dir, + ) +} + +pub(crate) fn package_buildpack( + buildpack_id: &BuildpackId, + cargo_profile: CargoProfile, + target_triple: impl AsRef, + cargo_manifest_dir: &Path, + target_buildpack_dir: &Path, +) -> Result { + let cargo_build_env = match cross_compile_assistance(target_triple.as_ref()) { CrossCompileAssistance::HelpText(help_text) => { - return Err(PackageCrateBuildpackError::CrossCompileConfigurationError( + return Err(PackageBuildpackError::CrossCompileConfigurationError( help_text, )); } @@ -29,34 +55,62 @@ pub(crate) fn package_crate_buildpack( CrossCompileAssistance::Configuration { cargo_env } => cargo_env, }; - let buildpack_dir = - tempdir().map_err(PackageCrateBuildpackError::CannotCreateBuildpackTempDirectory)?; + let workspace_root_path = find_cargo_workspace_root_dir(cargo_manifest_dir) + .map_err(PackageBuildpackError::FindCargoWorkspaceRoot)?; - let buildpack_binaries = build_buildpack_binaries( - &cargo_manifest_dir, - &cargo_metadata, + let buildpack_dir_resolver = create_packaged_buildpack_dir_resolver( + target_buildpack_dir, cargo_profile, - &cargo_env, target_triple.as_ref(), - ) - .map_err(PackageCrateBuildpackError::BuildBinariesError)?; + ); - assemble_buildpack_directory( - buildpack_dir.path(), - cargo_manifest_dir.join("buildpack.toml"), - &buildpack_binaries, + let buildpack_dependency_graph = build_libcnb_buildpacks_dependency_graph(&workspace_root_path) + .map_err(PackageBuildpackError::BuildBuildpackDependencyGraph)?; + + let root_node = buildpack_dependency_graph + .node_weights() + .find(|node| node.buildpack_id == buildpack_id.clone()); + + assert!( + root_node.is_some(), + "Could not package directory as buildpack! No buildpack with id `{buildpack_id}` exists in the workspace at {}", + workspace_root_path.display() + ); + + let build_order = get_dependencies( + &buildpack_dependency_graph, + &[root_node.expect("The root node should exist")], ) - .map_err(PackageCrateBuildpackError::CannotAssembleBuildpackDirectory)?; + .map_err(PackageBuildpackError::GetDependencies)?; + + let mut packaged_buildpack_dirs = BTreeMap::new(); + for node in &build_order { + let buildpack_destination_dir = buildpack_dir_resolver(&node.buildpack_id); + + fs::create_dir_all(&buildpack_destination_dir).unwrap(); + + libcnb_package::package::package_buildpack( + &node.path, + cargo_profile, + target_triple.as_ref(), + &cargo_build_env, + &buildpack_destination_dir, + &packaged_buildpack_dirs, + ) + .map_err(PackageBuildpackError::PackageBuildpack)?; + + packaged_buildpack_dirs.insert(node.buildpack_id.clone(), buildpack_destination_dir); + } - Ok(buildpack_dir) + Ok(buildpack_dir_resolver(buildpack_id)) } #[derive(Debug)] -pub(crate) enum PackageCrateBuildpackError { - BuildBinariesError(BuildBinariesError), - CannotAssembleBuildpackDirectory(std::io::Error), - CannotCreateBuildpackTempDirectory(std::io::Error), - CannotDetermineCrateDirectory(std::env::VarError), - CargoMetadataError(cargo_metadata::Error), +pub(crate) enum PackageBuildpackError { + CannotReadBuildpackDescriptor(TomlFileError), + BuildBuildpackDependencyGraph(BuildBuildpackDependencyGraphError), CrossCompileConfigurationError(String), + FindCargoWorkspaceRoot(FindCargoWorkspaceRootError), + GetDependencies(GetDependenciesError), + PackageBuildpack(libcnb_package::package::PackageBuildpackError), } diff --git a/libcnb-test/src/build_config.rs b/libcnb-test/src/build_config.rs index 8466a7fb..aa404d91 100644 --- a/libcnb-test/src/build_config.rs +++ b/libcnb-test/src/build_config.rs @@ -1,3 +1,4 @@ +use libcnb_data::buildpack::BuildpackId; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -29,7 +30,7 @@ impl BuildConfig { /// use libcnb_test::{BuildConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), /// |context| { /// // ... /// }, @@ -41,7 +42,7 @@ impl BuildConfig { cargo_profile: CargoProfile::Dev, target_triple: String::from("x86_64-unknown-linux-musl"), builder_name: builder_name.into(), - buildpacks: vec![BuildpackReference::Crate], + buildpacks: vec![BuildpackReference::CurrentCrate], env: HashMap::new(), app_dir_preprocessor: None, expected_pack_result: PackResult::Success, @@ -50,16 +51,18 @@ impl BuildConfig { /// Sets the buildpacks (and their ordering) to use when building the app. /// - /// Defaults to [`BuildpackReference::Crate`]. + /// Defaults to [`BuildpackReference::CurrentCrate`]. /// /// # Example /// ```no_run + /// use libcnb::data::buildpack_id; /// use libcnb_test::{BuildConfig, BuildpackReference, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app").buildpacks(vec![ + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app").buildpacks([ + /// BuildpackReference::CurrentCrate, + /// BuildpackReference::WorkspaceBuildpack(buildpack_id!("my-project/buildpack")), /// BuildpackReference::Other(String::from("heroku/another-buildpack")), - /// BuildpackReference::Crate, /// ]), /// |context| { /// // ... @@ -80,7 +83,7 @@ impl BuildConfig { /// use libcnb_test::{BuildConfig, CargoProfile, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app") + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app") /// .cargo_profile(CargoProfile::Release), /// |context| { /// // ... @@ -101,7 +104,7 @@ impl BuildConfig { /// use libcnb_test::{BuildConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app") + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app") /// .target_triple("x86_64-unknown-linux-musl"), /// |context| { /// // ... @@ -123,7 +126,7 @@ impl BuildConfig { /// use libcnb_test::{BuildConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app") + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app") /// .env("ENV_VAR_ONE", "VALUE ONE") /// .env("ENV_VAR_TWO", "SOME OTHER VALUE"), /// |context| { @@ -146,7 +149,7 @@ impl BuildConfig { /// use libcnb_test::{BuildConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app").envs(vec![ + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app").envs([ /// ("ENV_VAR_ONE", "VALUE ONE"), /// ("ENV_VAR_TWO", "SOME OTHER VALUE"), /// ]), @@ -179,7 +182,7 @@ impl BuildConfig { /// use libcnb_test::{BuildConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app").app_dir_preprocessor( + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app").app_dir_preprocessor( /// |app_dir| { /// std::fs::remove_file(app_dir.join("Procfile")).unwrap(); /// }, @@ -205,11 +208,11 @@ impl BuildConfig { /// use libcnb_test::{BuildConfig, TestRunner}; /// /// fn default_config() -> BuildConfig { - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app") + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app") /// } /// /// TestRunner::default().build( - /// default_config().app_dir("test-fixtures/a-different-app"), + /// default_config().app_dir("tests/fixtures/a-different-app"), /// |context| { /// // ... /// }, @@ -233,7 +236,7 @@ impl BuildConfig { /// use libcnb_test::{assert_contains, BuildConfig, PackResult, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app") + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app") /// .expected_pack_result(PackResult::Failure), /// |context| { /// assert_contains!(context.pack_stderr, "ERROR: Invalid Procfile!"); @@ -250,7 +253,11 @@ impl BuildConfig { #[derive(Debug, Clone, Eq, PartialEq)] pub enum BuildpackReference { /// References the buildpack in the Rust Crate currently being tested. - Crate, + /// + /// Is equivalent to `BuildpackReference::WorkspaceBuildpack(buildpack_id!(">(), - vec!["run", "--name", "my-container", "my-image"] + ["run", "--name", "my-container", "my-image"] ); // With optional flag/arguments set @@ -285,7 +285,7 @@ mod tests { let command: Command = docker_run_command.clone().into(); assert_eq!( command.get_args().collect::>(), - vec![ + [ "run", "--name", "my-container", @@ -317,7 +317,7 @@ mod tests { assert_eq!(command.get_program(), "docker"); assert_eq!( command.get_args().collect::>(), - vec!["exec", "my-container", "ps"] + ["exec", "my-container", "ps"] ); } @@ -330,7 +330,7 @@ mod tests { assert_eq!(command.get_program(), "docker"); assert_eq!( command.get_args().collect::>(), - vec!["logs", "my-container"] + ["logs", "my-container"] ); // With optional flag/arguments set @@ -339,7 +339,7 @@ mod tests { let command: Command = docker_logs_command.clone().into(); assert_eq!( command.get_args().collect::>(), - vec!["logs", "my-container", "--follow"] + ["logs", "my-container", "--follow"] ); } @@ -350,7 +350,7 @@ mod tests { assert_eq!(command.get_program(), "docker"); assert_eq!( command.get_args().collect::>(), - vec!["port", "my-container", "12345"] + ["port", "my-container", "12345"] ); } @@ -361,7 +361,7 @@ mod tests { assert_eq!(command.get_program(), "docker"); assert_eq!( command.get_args().collect::>(), - vec!["rm", "my-container", "--force"] + ["rm", "my-container", "--force"] ); } @@ -372,7 +372,7 @@ mod tests { assert_eq!(command.get_program(), "docker"); assert_eq!( command.get_args().collect::>(), - vec!["rmi", "my-image", "--force"] + ["rmi", "my-image", "--force"] ); } } diff --git a/libcnb-test/src/lib.rs b/libcnb-test/src/lib.rs index dcf7a9f8..9ad2cc71 100644 --- a/libcnb-test/src/lib.rs +++ b/libcnb-test/src/lib.rs @@ -29,4 +29,6 @@ pub use crate::test_runner::*; #[cfg(test)] use indoc as _; #[cfg(test)] +use libcnb as _; +#[cfg(test)] use ureq as _; diff --git a/libcnb-test/src/pack.rs b/libcnb-test/src/pack.rs index da2fc6d0..c2c406c2 100644 --- a/libcnb-test/src/pack.rs +++ b/libcnb-test/src/pack.rs @@ -192,7 +192,7 @@ mod tests { assert_eq!( command.get_args().collect::>(), - vec![ + [ "build", "my-image", "--builder", @@ -214,7 +214,7 @@ mod tests { ] ); - assert_eq!(command.get_envs().collect::>(), vec![]); + assert_eq!(command.get_envs().collect::>(), Vec::new()); // Assert conditional '--trust-builder' flag works as expected: input.trust_builder = false; @@ -242,10 +242,10 @@ mod tests { assert_eq!( command.get_args().collect::>(), - vec!["sbom", "download", "my-image"] + ["sbom", "download", "my-image"] ); - assert_eq!(command.get_envs().collect::>(), vec![]); + assert_eq!(command.get_envs().collect::>(), Vec::new()); // Assert conditional '--output-dir' flag works as expected: input.output_dir = Some(PathBuf::from("/tmp/sboms")); @@ -255,9 +255,9 @@ mod tests { assert_eq!( command.get_args().collect::>(), - vec!["sbom", "download", "my-image", "--output-dir", "/tmp/sboms"] + ["sbom", "download", "my-image", "--output-dir", "/tmp/sboms"] ); - assert_eq!(command.get_envs().collect::>(), vec![]); + assert_eq!(command.get_envs().collect::>(), Vec::new()); } } diff --git a/libcnb-test/src/test_context.rs b/libcnb-test/src/test_context.rs index bc08deb6..77bdaf7b 100644 --- a/libcnb-test/src/test_context.rs +++ b/libcnb-test/src/test_context.rs @@ -32,7 +32,7 @@ impl<'a> TestContext<'a> { /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), /// |context| { /// // Start the container using the default process-type: /// // https://buildpacks.io/docs/app-developer-guide/run-an-app/#default-process-type @@ -126,7 +126,7 @@ impl<'a> TestContext<'a> { /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), /// |context| { /// // ... /// let command_output = @@ -141,7 +141,7 @@ impl<'a> TestContext<'a> { /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), /// |context| { /// // ... /// context.start_container( @@ -203,7 +203,7 @@ impl<'a> TestContext<'a> { /// use libcnb_test::{BuildConfig, ContainerConfig, SbomType, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), /// |context| { /// context.download_sbom_files(|sbom_files| { /// assert!(sbom_files @@ -254,7 +254,7 @@ impl<'a> TestContext<'a> { /// use libcnb_test::{assert_contains, BuildConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), /// |context| { /// assert_contains!(context.pack_stdout, "---> Installing dependencies"); /// diff --git a/libcnb-test/src/test_runner.rs b/libcnb-test/src/test_runner.rs index 67284e71..eb3a1974 100644 --- a/libcnb-test/src/test_runner.rs +++ b/libcnb-test/src/test_runner.rs @@ -5,6 +5,7 @@ use crate::{app, build, util, BuildConfig, BuildpackReference, PackResult, TestC use std::borrow::Borrow; use std::env; use std::path::PathBuf; +use tempfile::tempdir; /// Runner for libcnb integration tests. /// @@ -13,7 +14,7 @@ use std::path::PathBuf; /// use libcnb_test::{assert_contains, assert_empty, BuildConfig, TestRunner}; /// /// TestRunner::default().build( -/// BuildConfig::new("heroku/builder:22", "test-fixtures/app"), +/// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), /// |context| { /// assert_empty!(context.pack_stderr); /// assert_contains!(context.pack_stdout, "Expected build output"); @@ -39,7 +40,7 @@ impl TestRunner { /// use libcnb_test::{assert_contains, assert_empty, BuildConfig, TestRunner}; /// /// TestRunner::default().build( - /// BuildConfig::new("heroku/builder:22", "test-fixtures/app"), + /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"), /// |context| { /// assert_empty!(context.pack_stderr); /// assert_contains!(context.pack_stdout, "Expected build output"); @@ -58,12 +59,13 @@ impl TestRunner { ) { let config = config.borrow(); + let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .expect("Could not determine Cargo manifest directory"); + let app_dir = { let normalized_app_dir_path = if config.app_dir.is_relative() { - env::var("CARGO_MANIFEST_DIR") - .map(PathBuf::from) - .expect("Could not determine Cargo manifest directory") - .join(&config.app_dir) + cargo_manifest_dir.join(&config.app_dir) } else { config.app_dir.clone() }; @@ -88,14 +90,8 @@ impl TestRunner { } }; - let temp_crate_buildpack_dir = - config - .buildpacks - .contains(&BuildpackReference::Crate) - .then(|| { - build::package_crate_buildpack(config.cargo_profile, &config.target_triple) - .expect("Could not package current crate as buildpack") - }); + let buildpacks_target_dir = + tempdir().expect("Could not create a temporary directory for compiled buildpacks"); let mut pack_command = PackBuildCommand::new(&config.builder_name, &app_dir, &image_name); @@ -105,11 +101,30 @@ impl TestRunner { for buildpack in &config.buildpacks { match buildpack { - BuildpackReference::Crate => { - pack_command.buildpack(temp_crate_buildpack_dir.as_ref() - .expect("Test references crate buildpack, but crate wasn't packaged as a buildpack. This is an internal libcnb-test error, please report any occurrences.")) + BuildpackReference::CurrentCrate => { + let crate_buildpack_dir = build::package_crate_buildpack( + config.cargo_profile, + &config.target_triple, + &cargo_manifest_dir, + buildpacks_target_dir.path(), + ).expect("Test references crate buildpack, but crate wasn't packaged as a buildpack. This is an internal libcnb-test error, please report any occurrences"); + pack_command.buildpack(crate_buildpack_dir); + } + + BuildpackReference::WorkspaceBuildpack(builpack_id) => { + let buildpack_dir = build::package_buildpack( + builpack_id, + config.cargo_profile, + &config.target_triple, + &cargo_manifest_dir, + buildpacks_target_dir.path() + ).unwrap_or_else(|_| panic!("Test references buildpack `{builpack_id}`, but this directory wasn't packaged as a buildpack. This is an internal libcnb-test error, please report any occurrences")); + pack_command.buildpack(buildpack_dir); + } + + BuildpackReference::Other(id) => { + pack_command.buildpack(id.clone()); } - BuildpackReference::Other(id) => pack_command.buildpack(id.clone()), }; } diff --git a/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/Cargo.toml b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/Cargo.toml new file mode 100644 index 00000000..d8e77621 --- /dev/null +++ b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "one" +version = "0.0.0" + +[workspace] diff --git a/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/buildpack.toml b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/buildpack.toml new file mode 100644 index 00000000..f2e10539 --- /dev/null +++ b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/buildpack.toml @@ -0,0 +1,8 @@ +api = "0.8" + +[buildpack] +id = "libcnb-test/a" +version = "0.0.0" + +[[stacks]] +id = "*" diff --git a/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/src/main.rs b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/src/main.rs new file mode 100644 index 00000000..89cd3f18 --- /dev/null +++ b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-a/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Buildpack A"); +} diff --git a/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/Cargo.toml b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/Cargo.toml new file mode 100644 index 00000000..504aeb84 --- /dev/null +++ b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "two" +version = "0.0.0" + +[workspace] diff --git a/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/buildpack.toml b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/buildpack.toml new file mode 100644 index 00000000..fd1940bf --- /dev/null +++ b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/buildpack.toml @@ -0,0 +1,8 @@ +api = "0.8" + +[buildpack] +id = "libcnb-test/b" +version = "0.0.0" + +[[stacks]] +id = "*" diff --git a/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/src/main.rs b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/src/main.rs new file mode 100644 index 00000000..2e9f2ee2 --- /dev/null +++ b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Buildpack B"); +} diff --git a/libcnb-test/tests/fixtures/buildpacks/libcnb-test-composite/buildpack.toml b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-composite/buildpack.toml new file mode 100644 index 00000000..95706626 --- /dev/null +++ b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-composite/buildpack.toml @@ -0,0 +1,22 @@ +api = "0.8" + +[buildpack] +id = "libcnb-test/composite" +name = "Composite Buildpack Test" +version = "0.0.0" +homepage = "https://example.com" +description = "Official test example" +keywords = ["test"] + +[[buildpack.licenses]] +type = "BSD-3-Clause" + +[[order]] + +[[order.group]] +id = "libcnb-test/a" +version = "0.0.0" + +[[order.group]] +id = "libcnb-test/b" +version = "0.0.0" diff --git a/libcnb-test/tests/fixtures/buildpacks/libcnb-test-composite/package.toml b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-composite/package.toml new file mode 100644 index 00000000..5371f16a --- /dev/null +++ b/libcnb-test/tests/fixtures/buildpacks/libcnb-test-composite/package.toml @@ -0,0 +1,9 @@ +[buildpack] +uri = "." + +[[dependencies]] +uri = "libcnb:libcnb-test/a" + +[[dependencies]] +uri = "libcnb:libcnb-test/b" + diff --git a/libcnb-test/test-fixtures/empty/.gitkeep b/libcnb-test/tests/fixtures/empty/.gitkeep similarity index 100% rename from libcnb-test/test-fixtures/empty/.gitkeep rename to libcnb-test/tests/fixtures/empty/.gitkeep diff --git a/libcnb-test/test-fixtures/nested_dirs/file1.txt b/libcnb-test/tests/fixtures/nested_dirs/file1.txt similarity index 100% rename from libcnb-test/test-fixtures/nested_dirs/file1.txt rename to libcnb-test/tests/fixtures/nested_dirs/file1.txt diff --git a/libcnb-test/test-fixtures/nested_dirs/subdir1/file2.txt b/libcnb-test/tests/fixtures/nested_dirs/subdir1/file2.txt similarity index 100% rename from libcnb-test/test-fixtures/nested_dirs/subdir1/file2.txt rename to libcnb-test/tests/fixtures/nested_dirs/subdir1/file2.txt diff --git a/libcnb-test/test-fixtures/nested_dirs/subdir1/subdir2/subdir3/file3.txt b/libcnb-test/tests/fixtures/nested_dirs/subdir1/subdir2/subdir3/file3.txt similarity index 100% rename from libcnb-test/test-fixtures/nested_dirs/subdir1/subdir2/subdir3/file3.txt rename to libcnb-test/tests/fixtures/nested_dirs/subdir1/subdir2/subdir3/file3.txt diff --git a/libcnb-test/test-fixtures/procfile/Procfile b/libcnb-test/tests/fixtures/procfile/Procfile similarity index 100% rename from libcnb-test/test-fixtures/procfile/Procfile rename to libcnb-test/tests/fixtures/procfile/Procfile diff --git a/libcnb-test/tests/integration_test.rs b/libcnb-test/tests/integration_test.rs index 0eabc1b3..c50d7c5d 100644 --- a/libcnb-test/tests/integration_test.rs +++ b/libcnb-test/tests/integration_test.rs @@ -8,6 +8,7 @@ #![warn(clippy::pedantic)] use indoc::indoc; +use libcnb_data::buildpack_id; use libcnb_test::{ assert_contains, assert_empty, assert_not_contains, BuildConfig, BuildpackReference, ContainerConfig, PackResult, TestRunner, @@ -27,7 +28,7 @@ const TEST_PORT: u16 = 12345; #[ignore = "integration test"] fn basic_build() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { assert_empty!(context.pack_stderr); @@ -46,7 +47,7 @@ fn basic_build() { #[ignore = "integration test"] fn rebuild() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { assert_empty!(context.pack_stderr); @@ -63,13 +64,21 @@ fn rebuild() { #[test] #[ignore = "integration test"] -#[should_panic( - expected = "Could not package current crate as buildpack: BuildBinariesError(CannotDetermineBuildpackCargoTargetName(NoBinTargets))" -)] fn buildpack_packaging_failure() { - TestRunner::default().build(BuildConfig::new("invalid!", "test-fixtures/empty"), |_| { - unreachable!("The test should panic prior to the TestContext being invoked."); - }); + let err = std::panic::catch_unwind(|| { + TestRunner::default().build(BuildConfig::new("invalid!", "tests/fixtures/empty"), |_| { + unreachable!("The test should panic prior to the TestContext being invoked."); + }); + }) + .unwrap_err(); + + assert_eq!( + err.downcast_ref::().unwrap().to_string(), + format!( + "Could not package directory as buildpack! No `buildpack.toml` file exists at {}", + env::var("CARGO_MANIFEST_DIR").unwrap() + ) + ); } #[test] @@ -83,7 +92,7 @@ pack command failed with exit code 1! ERROR: failed to build: invalid builder 'invalid!'")] fn unexpected_pack_failure() { TestRunner::default().build( - BuildConfig::new("invalid!", "test-fixtures/empty").buildpacks(Vec::new()), + BuildConfig::new("invalid!", "tests/fixtures/empty").buildpacks(Vec::new()), |_| { unreachable!("The test should panic prior to the TestContext being invoked."); }, @@ -103,7 +112,7 @@ fn unexpected_pack_failure() { ")] fn unexpected_pack_success() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]) .expected_pack_result(PackResult::Failure), |_| { @@ -116,7 +125,7 @@ fn unexpected_pack_success() { #[ignore = "integration test"] fn expected_pack_failure() { TestRunner::default().build( - BuildConfig::new("invalid!", "test-fixtures/empty") + BuildConfig::new("invalid!", "tests/fixtures/empty") .buildpacks(Vec::new()) .expected_pack_result(PackResult::Failure), |context| { @@ -131,14 +140,24 @@ fn expected_pack_failure() { #[test] #[ignore = "integration test"] -#[should_panic( - expected = "Could not package current crate as buildpack: BuildBinariesError(CannotDetermineBuildpackCargoTargetName(NoBinTargets))" -)] fn expected_pack_failure_still_panics_for_non_pack_failure() { - TestRunner::default().build( - BuildConfig::new("invalid!", "test-fixtures/empty") - .expected_pack_result(PackResult::Failure), - |_| {}, + let err = std::panic::catch_unwind(|| { + TestRunner::default().build( + BuildConfig::new("invalid!", "tests/fixtures/empty") + .expected_pack_result(PackResult::Failure), + |_| { + unreachable!("The test should panic prior to the TestContext being invoked."); + }, + ); + }) + .unwrap_err(); + + assert_eq!( + err.downcast_ref::().unwrap().to_string(), + format!( + "Could not package directory as buildpack! No `buildpack.toml` file exists at {}", + env::var("CARGO_MANIFEST_DIR").unwrap() + ) ); } @@ -146,7 +165,7 @@ fn expected_pack_failure_still_panics_for_non_pack_failure() { #[ignore = "integration test"] fn app_dir_preprocessor() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/nested_dirs") + BuildConfig::new("heroku/builder:22", "tests/fixtures/nested_dirs") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]) .app_dir_preprocessor(|app_dir| { assert!(app_dir.join("file1.txt").exists()); @@ -183,7 +202,7 @@ fn app_dir_preprocessor() { let fixture_dir = env::var("CARGO_MANIFEST_DIR") .map(PathBuf::from) .unwrap() - .join("test-fixtures/nested_dirs"); + .join("tests/fixtures/nested_dirs"); assert!(fixture_dir.join("file1.txt").exists()); assert!(!fixture_dir.join("Procfile").exists()); } @@ -194,7 +213,7 @@ fn app_dir_absolute_path() { let absolute_app_dir = env::var("CARGO_MANIFEST_DIR") .map(PathBuf::from) .unwrap() - .join("test-fixtures/procfile") + .join("tests/fixtures/procfile") .canonicalize() .unwrap(); @@ -208,7 +227,7 @@ fn app_dir_absolute_path() { #[test] #[ignore = "integration test"] // The full panic message looks like this: -// `"App dir is not a valid directory: /.../libcnb-test/test-fixtures/non-existent-fixture"` +// `"App dir is not a valid directory: /.../libcnb-test/tests/fixtures/non-existent-fixture"` // It's intentionally an absolute path to make debugging failures easier when a relative path has been // passed (the common case). However, since the absolute path is system/environment dependent, we would // need to either construct the expected string dynamically in `should_panic` (but cannot due to @@ -216,10 +235,10 @@ fn app_dir_absolute_path() { // As such we test the most important part, the fact that the error message lists the non-existent // fixture directory path. We intentionally include the `libcnb-test/` crate directory prefix, since // that only appears in the absolute path, not the relative path passed to `BuildConfig::new`. -#[should_panic(expected = "libcnb-test/test-fixtures/non-existent-fixture")] +#[should_panic(expected = "libcnb-test/tests/fixtures/non-existent-fixture")] fn app_dir_invalid_path() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/non-existent-fixture") + BuildConfig::new("heroku/builder:22", "tests/fixtures/non-existent-fixture") .buildpacks(Vec::new()), |_| {}, ); @@ -228,12 +247,12 @@ fn app_dir_invalid_path() { #[test] #[ignore = "integration test"] // The full panic message looks like this: -// `"App dir is not a valid directory: /.../libcnb-test/test-fixtures/non-existent-fixture"` +// `"App dir is not a valid directory: /.../libcnb-test/tests/fixtures/non-existent-fixture"` // See above for why we only test this substring. -#[should_panic(expected = "libcnb-test/test-fixtures/non-existent-fixture")] +#[should_panic(expected = "libcnb-test/tests/fixtures/non-existent-fixture")] fn app_dir_invalid_path_checked_before_applying_preprocessor() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/non-existent-fixture") + BuildConfig::new("heroku/builder:22", "tests/fixtures/non-existent-fixture") .buildpacks(Vec::new()) .app_dir_preprocessor(|_| { unreachable!("The app dir should be validated before the preprocessor is run."); @@ -253,7 +272,7 @@ pack command failed with exit code 1! ERROR: image 'libcnbtest_")] fn download_sbom_files_failure() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/empty") + BuildConfig::new("heroku/builder:22", "tests/fixtures/empty") .buildpacks(Vec::new()) .expected_pack_result(PackResult::Failure), |context| { @@ -266,7 +285,7 @@ fn download_sbom_files_failure() { #[ignore = "integration test"] fn starting_containers() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { // Using the default entrypoint and command. @@ -345,7 +364,7 @@ docker command failed with exit code 127! docker: Error response from daemon:")] fn start_container_spawn_failure() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { context.start_container( @@ -369,7 +388,7 @@ docker command failed with exit code 1! Error response from daemon:")] fn shell_exec_when_container_has_crashed() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { context.start_container( @@ -401,7 +420,7 @@ some stdout ")] fn shell_exec_nonzero_exit_status() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { context.start_container(ContainerConfig::new(), |container| { @@ -428,7 +447,7 @@ some stdout ")] fn run_shell_command_nonzero_exit_status() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { context.run_shell_command("echo 'some stdout'; echo 'some stderr' >&2; exit 1"); @@ -440,7 +459,7 @@ fn run_shell_command_nonzero_exit_status() { #[ignore = "integration test"] fn logs_work_after_container_crashed() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { context.start_container( @@ -463,7 +482,7 @@ fn logs_work_after_container_crashed() { #[should_panic(expected = "Port `0' not valid")] fn expose_port_invalid_port() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { context.start_container(ContainerConfig::new().expose_port(0), |_| { @@ -480,7 +499,7 @@ fn expose_port_invalid_port() { )] fn address_for_port_when_port_not_exposed() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { context.start_container(ContainerConfig::new(), |container| { @@ -505,7 +524,7 @@ some stdout ")] fn address_for_port_when_container_crashed() { TestRunner::default().build( - BuildConfig::new("heroku/builder:22", "test-fixtures/procfile") + BuildConfig::new("heroku/builder:22", "tests/fixtures/procfile") .buildpacks([BuildpackReference::Other(String::from(PROCFILE_URL))]), |context| { context.start_container( @@ -522,3 +541,42 @@ fn address_for_port_when_container_crashed() { }, ); } + +#[test] +#[ignore = "integration test"] +fn basic_build_with_libcnb_reference_to_single_buildpack() { + TestRunner::default().build( + BuildConfig::new("heroku/builder:22", "tests/fixtures/empty").buildpacks([ + BuildpackReference::WorkspaceBuildpack(buildpack_id!("libcnb-test/a")), + ]), + |context| { + assert_empty!(context.pack_stderr); + assert_contains!( + context.pack_stdout, + indoc! {" + Buildpack A + "} + ); + }, + ); +} + +#[test] +#[ignore = "integration test"] +fn basic_build_with_libcnb_reference_to_composite_buildpack() { + TestRunner::default().build( + BuildConfig::new("heroku/builder:22", "tests/fixtures/empty").buildpacks([ + BuildpackReference::WorkspaceBuildpack(buildpack_id!("libcnb-test/composite")), + ]), + |context| { + assert_empty!(context.pack_stderr); + assert_contains!( + context.pack_stdout, + indoc! {" + Buildpack A + Buildpack B + "} + ); + }, + ); +} diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index bedaac4b..49785511 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -5,7 +5,7 @@ use crate::data::buildpack::StackId; use crate::data::layer::LayerName; use crate::data::store::Store; use crate::data::{ - buildpack::SingleBuildpackDescriptor, buildpack_plan::BuildpackPlan, launch::Launch, + buildpack::ComponentBuildpackDescriptor, buildpack_plan::BuildpackPlan, launch::Launch, }; use crate::layer::{HandleLayerErrorOrBuildpackError, Layer, LayerData}; use crate::sbom::Sbom; @@ -19,7 +19,7 @@ pub struct BuildContext { pub stack_id: StackId, pub platform: B::Platform, pub buildpack_plan: BuildpackPlan, - pub buildpack_descriptor: SingleBuildpackDescriptor, + pub buildpack_descriptor: ComponentBuildpackDescriptor, pub store: Option, } diff --git a/libcnb/src/detect.rs b/libcnb/src/detect.rs index 083dd3c4..dd347b9e 100644 --- a/libcnb/src/detect.rs +++ b/libcnb/src/detect.rs @@ -2,7 +2,7 @@ use crate::buildpack::Buildpack; use crate::data::buildpack::StackId; -use crate::{data::build_plan::BuildPlan, data::buildpack::SingleBuildpackDescriptor}; +use crate::{data::build_plan::BuildPlan, data::buildpack::ComponentBuildpackDescriptor}; use std::fmt::Debug; use std::path::PathBuf; @@ -12,7 +12,7 @@ pub struct DetectContext { pub buildpack_dir: PathBuf, pub stack_id: StackId, pub platform: B::Platform, - pub buildpack_descriptor: SingleBuildpackDescriptor, + pub buildpack_descriptor: ComponentBuildpackDescriptor, } /// Describes the result of the detect phase. diff --git a/libcnb/src/layer/public_interface.rs b/libcnb/src/layer/public_interface.rs index 2f835041..488fa879 100644 --- a/libcnb/src/layer/public_interface.rs +++ b/libcnb/src/layer/public_interface.rs @@ -187,7 +187,7 @@ impl LayerResultBuilder { metadata, env: None, exec_d_programs: HashMap::new(), - sboms: vec![], + sboms: Vec::new(), } } diff --git a/libcnb/src/layer/tests.rs b/libcnb/src/layer/tests.rs index b225c200..ad33d12f 100644 --- a/libcnb/src/layer/tests.rs +++ b/libcnb/src/layer/tests.rs @@ -21,7 +21,7 @@ use crate::layer::{ }; use crate::layer_env::{LayerEnv, ModificationBehavior, Scope}; use crate::{read_toml_file, Buildpack, Env, LIBCNB_SUPPORTED_BUILDPACK_API}; -use libcnb_data::buildpack::{BuildpackVersion, SingleBuildpackDescriptor, Stack}; +use libcnb_data::buildpack::{BuildpackVersion, ComponentBuildpackDescriptor, Stack}; use libcnb_data::buildpack_plan::BuildpackPlan; use libcnb_data::layer::LayerName; use libcnb_data::layer_content_metadata::LayerContentMetadata; @@ -903,8 +903,10 @@ fn build_context(temp_dir: &TempDir) -> BuildContext { buildpack_dir, stack_id: stack_id!("heroku-20"), platform: GenericPlatform::new(Env::new()), - buildpack_plan: BuildpackPlan { entries: vec![] }, - buildpack_descriptor: SingleBuildpackDescriptor { + buildpack_plan: BuildpackPlan { + entries: Vec::new(), + }, + buildpack_descriptor: ComponentBuildpackDescriptor { api: LIBCNB_SUPPORTED_BUILDPACK_API, buildpack: crate::data::buildpack::Buildpack { id: buildpack_id!("libcnb/test"), @@ -913,8 +915,8 @@ fn build_context(temp_dir: &TempDir) -> BuildContext { homepage: None, clear_env: true, description: None, - keywords: vec![], - licenses: vec![], + keywords: Vec::new(), + licenses: Vec::new(), sbom_formats: HashSet::new(), }, stacks: vec![Stack::Any], diff --git a/libcnb/src/layer_env.rs b/libcnb/src/layer_env.rs index f767292d..542163f5 100644 --- a/libcnb/src/layer_env.rs +++ b/libcnb/src/layer_env.rs @@ -284,7 +284,7 @@ impl LayerEnv { let include_path = layer_dir.as_ref().join("include"); let pkgconfig_path = layer_dir.as_ref().join("pkgconfig"); - let layer_path_specs = vec![ + let layer_path_specs = [ ("PATH", Scope::Build, &bin_path), ("LIBRARY_PATH", Scope::Build, &lib_path), ("LD_LIBRARY_PATH", Scope::Build, &lib_path), @@ -822,7 +822,7 @@ mod tests { #[test] fn modification_behavior_order() { - let tests = vec![ + let tests = [ ( ModificationBehavior::Append, ModificationBehavior::Default, diff --git a/libcnb/src/sbom.rs b/libcnb/src/sbom.rs index 57c26d5c..1ed9c5ed 100644 --- a/libcnb/src/sbom.rs +++ b/libcnb/src/sbom.rs @@ -45,7 +45,7 @@ impl TryFrom for Sbom { type Error = cyclonedx_bom::errors::JsonWriteError; fn try_from(cyclonedx_bom: cyclonedx_bom::models::bom::Bom) -> Result { - let mut data = vec![]; + let mut data = Vec::new(); cyclonedx_bom.output_as_json_v1_3(&mut data)?; diff --git a/libherokubuildpack/src/command.rs b/libherokubuildpack/src/command.rs index ac37e420..408ebcf9 100644 --- a/libherokubuildpack/src/command.rs +++ b/libherokubuildpack/src/command.rs @@ -86,8 +86,8 @@ impl CommandExt for process::Command { stdout_write: OW, stderr_write: EW, ) -> io::Result { - let mut stdout_buffer = vec![]; - let mut stderr_buffer = vec![]; + let mut stdout_buffer = Vec::new(); + let mut stderr_buffer = Vec::new(); self.spawn_and_write_streams( tee(&mut stdout_buffer, stdout_write), @@ -151,8 +151,8 @@ mod test { #[test] #[cfg(unix)] fn test_spawn_and_write_streams() { - let mut stdout_buf = vec![]; - let mut stderr_buf = vec![]; + let mut stdout_buf = Vec::new(); + let mut stderr_buf = Vec::new(); Command::new("echo") .args(["-n", "Hello World!"]) @@ -167,8 +167,8 @@ mod test { #[test] #[cfg(unix)] fn test_output_and_write_streams() { - let mut stdout_buf = vec![]; - let mut stderr_buf = vec![]; + let mut stdout_buf = Vec::new(); + let mut stderr_buf = Vec::new(); let output = Command::new("echo") .args(["-n", "Hello World!"]) diff --git a/libherokubuildpack/src/toml.rs b/libherokubuildpack/src/toml.rs index deebcbb0..d189abf4 100644 --- a/libherokubuildpack/src/toml.rs +++ b/libherokubuildpack/src/toml.rs @@ -131,7 +131,7 @@ mod test { hash_map.insert(String::from("foo"), String::from("bar")); assert_eq!( - toml_select_value::<&str, Vec<&str>>(vec![], &toml.into()), + toml_select_value::<&str, Vec<&str>>(Vec::new(), &toml.into()), Some(&toml::Value::from(hash_map)) ); } diff --git a/libherokubuildpack/src/write.rs b/libherokubuildpack/src/write.rs index 7ed0f0ac..57a957d1 100644 --- a/libherokubuildpack/src/write.rs +++ b/libherokubuildpack/src/write.rs @@ -16,7 +16,7 @@ pub fn mapped) -> Vec) + Sync + Send + 'static> MappedWrite { inner: w, marker_byte, - buffer: vec![], + buffer: Vec::new(), mapping_fn: Arc::new(f), } } @@ -122,8 +122,8 @@ mod test { #[test] fn test_tee_write() { - let mut a = vec![]; - let mut b = vec![]; + let mut a = Vec::new(); + let mut b = Vec::new(); let mut input = "foo bar baz".as_bytes(); std::io::copy(&mut input, &mut tee(&mut a, &mut b)).unwrap(); @@ -134,7 +134,7 @@ mod test { #[test] fn test_mapped_write() { - let mut output = vec![]; + let mut output = Vec::new(); let mut input = "foo\nbar\nbaz".as_bytes(); std::io::copy(