Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore file support when finding buildpacks #673

Merged
merged 4 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `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))

## [0.14.0] - 2023-08-18

Expand Down
5 changes: 2 additions & 3 deletions libcnb-cargo/src/package/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions libcnb-cargo/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,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, "meta-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, "meta-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());
Expand Down
1 change: 1 addition & 0 deletions libcnb-package/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
7 changes: 3 additions & 4 deletions libcnb-package/src/buildpack_dependency_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Graph<BuildpackDependencyGraphNode, ()>, BuildBuildpackDependencyGraphError> {
find_buildpack_dirs(cargo_workspace_root, ignore)
find_buildpack_dirs(cargo_workspace_root)
.map_err(BuildBuildpackDependencyGraphError::FindBuildpackDirectories)
.and_then(|buildpack_directories| {
buildpack_directories
Expand Down Expand Up @@ -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}")]
Expand Down
48 changes: 16 additions & 32 deletions libcnb-package/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,39 +106,23 @@ fn create_file_symlink<P: AsRef<Path>, Q: AsRef<Path>>(
///
/// # 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<Vec<PathBuf>> {
fn find_buildpack_dirs_recursive(
path: &Path,
ignore: &[&Path],
accumulator: &mut Vec<PathBuf>,
) -> 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<Vec<PathBuf>, ignore::Error> {
ignore::Walk::new(start_dir)
.collect::<Result<Vec<_>, _>>()
.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<PathBuf> = 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
Expand Down