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

--prefix list #11

Merged
merged 5 commits into from
Oct 29, 2024
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "conda-deny"
description = "A CLI tool to check your project's dependencies for license compliance."
version = "0.3.0"
version = "0.3.1"
edition = "2021"

[features]
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@

[![License][license-badge]](LICENSE)
[![CI Status][ci-badge]][ci]
![Binary Build](https://github.com/quantco/conda-deny/actions/workflows/build.yml/badge.svg)
[![Binary Build][binary-build-badge]][binary-build]
[![Conda Platform][conda-badge]][conda-url]
[![codecov](https://codecov.io/gh/Quantco/conda-deny/graph/badge.svg)](https://codecov.io/gh/Quantco/conda-deny)
[![Codecov][codecov]][codecov-url]

[license-badge]: https://img.shields.io/github/license/quantco/conda-deny?style=flat-square
[ci-badge]: https://img.shields.io/github/actions/workflow/status/quantco/conda-deny/ci.yml?style=flat-square&branch=main
[ci]: https://github.com/quantco/conda-deny/actions/

[ci-badge]: https://img.shields.io/github/actions/workflow/status/quantco/conda-deny/ci.yml?branch=main&style=flat-square&label=CI
[ci]: https://github.com/quantco/conda-deny/actions/workflows/ci.yml

[binary-build-badge]: https://img.shields.io/github/actions/workflow/status/quantco/conda-deny/build.yml?branch=main&style=flat-square&label=Binary%20Build
[binary-build]: https://github.com/quantco/conda-deny/actions/workflows/build.yml

[conda-badge]: https://img.shields.io/conda/vn/conda-forge/conda-deny?style=flat-square
[conda-url]: https://prefix.dev/channels/conda-forge/packages/conda-deny

[codecov]: https://img.shields.io/codecov/c/github/quantco/conda-deny/main?style=flat-square
[codecov-url]: https://codecov.io/gh/Quantco/conda-deny

</div>

## 🗂 Table of Contents
Expand Down
18 changes: 9 additions & 9 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ pub struct Cli {

#[arg(long, global = true)]
pub prefix: Option<Vec<String>>,

#[arg(short, long)]
pub lockfile: Option<Vec<String>>,

#[arg(short, long)]
pub platform: Option<Vec<String>>,

#[arg(short, long)]
pub environment: Option<Vec<String>>,
}

#[derive(clap::Subcommand, Debug)]
Expand All @@ -27,15 +36,6 @@ pub enum Commands {

#[arg(short, long, action = ArgAction::SetTrue)]
osi: bool,

#[arg(short, long)]
lockfile: Option<Vec<String>>,

#[arg(short, long)]
platform: Option<Vec<String>>,

#[arg(short, long)]
environment: Option<Vec<String>>,
},
List {},
}
Expand Down
79 changes: 34 additions & 45 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,66 +19,55 @@ use log::debug;
use crate::conda_deny_config::CondaDenyConfig;
use crate::license_info::LicenseInfos;

pub type CheckInput<'a> = (
pub type CliInput<'a> = (
&'a CondaDenyConfig,
Vec<String>,
Vec<String>,
Vec<String>,
Vec<String>,
&'a Vec<String>,
&'a Vec<String>,
&'a Vec<String>,
&'a Vec<String>,
bool,
);
pub type CheckOutput = (Vec<LicenseInfo>, Vec<LicenseInfo>);

pub fn list(conda_deny_config: &CondaDenyConfig) -> Result<()> {
let mut license_infos =
LicenseInfos::get_license_infos_from_config(conda_deny_config, vec![], vec![], vec![])
.with_context(|| "Getting license information from config file failed.")?;

license_infos.sort();
license_infos.dedup();

license_infos.list();
Ok(())
}

pub fn check_license_infos(check_input: CheckInput) -> Result<CheckOutput> {
let (conda_deny_config, cli_lockfiles, cli_platforms, cli_environment, conda_prefixes, osi) =
check_input;
pub fn fetch_license_infos(cli_input: CliInput) -> Result<LicenseInfos> {
let (conda_deny_config, cli_lockfiles, cli_platforms, cli_environments, conda_prefixes, _) =
cli_input;

if conda_prefixes.is_empty() {
let mut license_infos = LicenseInfos::get_license_infos_from_config(
LicenseInfos::get_license_infos_from_config(
conda_deny_config,
cli_lockfiles,
cli_platforms,
cli_environment,
cli_environments,
)
.with_context(|| "Getting license information from config file failed.")?;
.with_context(|| "Getting license information from config file failed.")
} else {
LicenseInfos::from_conda_prefixes(conda_prefixes)
.with_context(|| "Getting license information from conda prefixes failed.")
}
}

license_infos.sort();
license_infos.dedup();
pub fn list(cli_input: CliInput) -> Result<()> {
let license_infos =
fetch_license_infos(cli_input).with_context(|| "Fetching license information failed.")?;
license_infos.list();
Ok(())
}

if osi {
debug!("Checking licenses for OSI compliance");
Ok(license_infos.osi_check())
} else {
let license_whitelist = build_license_whitelist(conda_deny_config)
.with_context(|| "Building the license whitelist failed.")?;
debug!("Checking licenses against specified whitelist");
Ok(license_infos.check(&license_whitelist))
}
pub fn check_license_infos(cli_input: CliInput) -> Result<CheckOutput> {
let (conda_deny_config, _, _, _, _, osi) = cli_input;

let license_infos =
fetch_license_infos(cli_input).with_context(|| "Fetching license information failed.")?;

if osi {
debug!("Checking licenses for OSI compliance");
Ok(license_infos.osi_check())
} else {
let mut conda_prefixes_license_infos = LicenseInfos::from_conda_prefixes(&conda_prefixes)?;
conda_prefixes_license_infos.sort();
conda_prefixes_license_infos.dedup();
if osi {
debug!("Checking for OSI licenses");
Ok(conda_prefixes_license_infos.osi_check())
} else {
let license_whitelist = build_license_whitelist(conda_deny_config)
let license_whitelist = build_license_whitelist(conda_deny_config)
.with_context(|| "Building the license whitelist failed.")?;
debug!("Checking licenses against specified whitelist");
Ok(conda_prefixes_license_infos.check(&license_whitelist))
}
debug!("Checking licenses against specified whitelist");
Ok(license_infos.check(&license_whitelist))
}
}

Expand Down
25 changes: 15 additions & 10 deletions src/license_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl LicenseInfos {
environment_specs.clone(),
platforms.clone(),
)
.with_context(|| "Failed to ge package records for pixi.lock")?;
.with_context(|| "Failed to get package records for pixi.lock")?;

package_records.extend(package_records_for_lockfile);
} else {
Expand All @@ -176,6 +176,9 @@ impl LicenseInfos {
license_infos.push(license_info);
}

license_infos.sort();
license_infos.dedup();

Ok(LicenseInfos { license_infos })
}

Expand All @@ -197,6 +200,9 @@ impl LicenseInfos {
license_infos.extend(license_infos_for_meta.license_infos);
}

license_infos.sort();
license_infos.dedup();

Ok(LicenseInfos { license_infos })
}

Expand All @@ -211,17 +217,17 @@ impl LicenseInfos {

pub fn get_license_infos_from_config(
config: &CondaDenyConfig,
cli_lockfiles: Vec<String>,
cli_platforms: Vec<String>,
cli_environments: Vec<String>,
cli_lockfiles: &[String],
cli_platforms: &[String],
cli_environments: &[String],
) -> Result<LicenseInfos> {
let mut platforms = config.get_platform_spec().map_or(vec![], |p| p);
let mut lockfiles = config.get_lockfile_spec();
let mut environment_specs = config.get_environment_spec().map_or(vec![], |e| e);

platforms.extend(cli_platforms);
lockfiles.extend(cli_lockfiles);
environment_specs.extend(cli_environments);
platforms.extend(cli_platforms.to_owned());
lockfiles.extend(cli_lockfiles.to_owned());
environment_specs.extend(cli_environments.to_owned());

LicenseInfos::from_pixi_lockfiles(lockfiles, platforms, environment_specs)
}
Expand Down Expand Up @@ -463,8 +469,7 @@ mod tests {
"tests/test_pyproject_toml_files/"
);
let config = CondaDenyConfig::from_path(&test_file_path).expect("Failed to read config");
let license_infos =
LicenseInfos::get_license_infos_from_config(&config, vec![], vec![], vec![]);
assert_eq!(license_infos.unwrap().license_infos.len(), 534);
let license_infos = LicenseInfos::get_license_infos_from_config(&config, &[], &[], &[]);
assert_eq!(license_infos.unwrap().license_infos.len(), 396);
}
}
2 changes: 1 addition & 1 deletion src/license_whitelist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ pub fn build_license_whitelist(
}

if final_license_whitelist.safe_licenses.is_empty() {
anyhow::bail!("Your license whitelist is empty.");
anyhow::bail!("Your license whitelist is empty.\nIf you want to use the OSI license whitelist, use the --osi flag.");
} else {
debug!("License whitelist built successfully.");
}
Expand Down
50 changes: 24 additions & 26 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,42 +48,39 @@ fn main() -> Result<()> {
}

let conda_prefixes = cli.prefix.unwrap_or_default();
let cli_lockfiles = cli.lockfile.unwrap_or_default();
let cli_platforms = cli.platform.unwrap_or_default();
let cli_environments = cli.environment.unwrap_or_default();

let cli_input = (
&config,
&cli_lockfiles,
&cli_platforms,
&cli_environments,
&conda_prefixes,
osi,
);

debug!("CLI input for platforms: {:?}", cli_platforms);
debug!("CLI input for environments: {:?}", cli_environments);
debug!("CLI input for conda prefixes: {:?}", conda_prefixes);
let mut locks_to_check = cli_lockfiles.clone();
locks_to_check.push("pixi.lock".to_string());
debug!("CLI input for pixi lockfiles: {:?}", locks_to_check);
debug!("CLI input for OSI compliance: {}", osi);

match cli.command {
Commands::Check {
include_safe,
osi,
lockfile,
platform,
environment,
osi: _,
} => {
let cli_lockfiles = lockfile.unwrap_or_default();
let cli_platforms = platform.unwrap_or_default();
let cli_environment = environment.unwrap_or_default();

debug!("Check command called.");
debug!("Checking platforms: {:?}", cli_platforms);
debug!("Checking environments: {:?}", cli_environment);
debug!("Checking conda prefixes: {:?}", conda_prefixes);
let mut locks_to_check = cli_lockfiles.clone();
locks_to_check.push("pixi.lock".to_string());
debug!("Checking pixi lockfiles: {:?}", locks_to_check);
debug!("Checking for OSI compliance: {}", osi);

if include_safe {
debug!("Including safe dependencies in output");
}

let check_input = (
&config,
cli_lockfiles,
cli_platforms,
cli_environment,
conda_prefixes,
osi,
);

let check_output = check_license_infos(check_input)?;
let check_output = check_license_infos(cli_input)?;

let (safe_dependencies, unsafe_dependencies) = check_output;

Expand All @@ -99,7 +96,8 @@ fn main() -> Result<()> {
}
Commands::List {} => {
debug!("List command called");
list(&config)
list(cli_input)?;
Ok(())
}
}
}
35 changes: 35 additions & 0 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use assert_cmd::prelude::*;
use core::str;
use std::path::Path;
use std::process::Command;

Expand Down Expand Up @@ -93,4 +94,38 @@ mod tests {
command.arg("check --osi").current_dir(test_dir);
command.assert().failure();
}

#[test]
fn test_prefix_list() {
// When --prefix is specified, only the license information for the conda-meta directory in the specified prefix should be listed
// License information from pixi.lock should not be listed

let test_dir = Path::new("tests/test_end_to_end/test_prefix_list");

let output = Command::cargo_bin("conda-deny")
.unwrap()
.arg("list")
.arg("--prefix")
.arg("../../../tests/test_conda_prefixes/test-env")
.current_dir(test_dir)
.output()
.expect("Failed to execute command");

assert!(
output.status.success(),
"Command did not execute successfully"
);

let stdout = str::from_utf8(&output.stdout).expect("Failed to convert output to string");

let line_count = stdout.lines().count();

let expected_line_count = 50;
assert_eq!(
line_count, expected_line_count,
"Unexpected number of output lines"
);

println!("Output has {} lines", line_count);
}
}
Loading