diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f8d5d0e92..78d853d98 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -85,7 +85,7 @@ jobs: with: toolchain: 1.73.0 targets: ${{ matrix.target }} - + - name: Install musl-gcc if: matrix.target == 'x86_64-unknown-linux-musl' run: sudo apt install musl-tools diff --git a/examples/rich/recipe.yaml b/examples/rich/recipe.yaml index 6c1056f4a..0e1417375 100644 --- a/examples/rich/recipe.yaml +++ b/examples/rich/recipe.yaml @@ -29,13 +29,15 @@ requirements: - python 3.10 - typing_extensions >=4.0.0,<5.0.0 -test: - imports: - - rich - commands: +tests: + - python: + imports: + - rich + - script: - pip check - requires: - - pip + requirements: + run: + - pip about: homepage: https://github.com/Textualize/rich diff --git a/examples/xtensor/recipe.yaml b/examples/xtensor/recipe.yaml index 39abf32e5..0dfdef1a8 100644 --- a/examples/xtensor/recipe.yaml +++ b/examples/xtensor/recipe.yaml @@ -40,19 +40,19 @@ requirements: run_constrained: - xsimd >=8.0.3,<10 -test: - commands: - - if: unix or emscripten - then: - - test -d ${PREFIX}/include/xtensor - - test -f ${PREFIX}/include/xtensor/xarray.hpp - - test -f ${PREFIX}/share/cmake/xtensor/xtensorConfig.cmake - - test -f ${PREFIX}/share/cmake/xtensor/xtensorConfigVersion.cmake - - if: win - then: - - if not exist %LIBRARY_PREFIX%\include\xtensor\xarray.hpp (exit 1) - - if not exist %LIBRARY_PREFIX%\share\cmake\xtensor\xtensorConfig.cmake (exit 1) - - if not exist %LIBRARY_PREFIX%\share\cmake\xtensor\xtensorConfigVersion.cmake (exit 1) +tests: + - script: + - if: unix or emscripten + then: + - test -d ${PREFIX}/include/xtensor + - test -f ${PREFIX}/include/xtensor/xarray.hpp + - test -f ${PREFIX}/share/cmake/xtensor/xtensorConfig.cmake + - test -f ${PREFIX}/share/cmake/xtensor/xtensorConfigVersion.cmake + - if: win + then: + - if not exist %LIBRARY_PREFIX%\include\xtensor\xarray.hpp (exit 1) + - if not exist %LIBRARY_PREFIX%\share\cmake\xtensor\xtensorConfig.cmake (exit 1) + - if not exist %LIBRARY_PREFIX%\share\cmake\xtensor\xtensorConfigVersion.cmake (exit 1) about: homepage: https://github.com/xtensor-stack/xtensor diff --git a/rust-tests/src/lib.rs b/rust-tests/src/lib.rs index e786bde56..6480343c9 100644 --- a/rust-tests/src/lib.rs +++ b/rust-tests/src/lib.rs @@ -147,6 +147,7 @@ mod tests { } unreachable!("above is an infinite loop") } + fn rattler() -> RattlerBuild { if let Ok(path) = std::env::var("RATTLER_BUILD_PATH") { if let Some(ret) = RattlerBuild::with_binary(path) { @@ -155,9 +156,11 @@ mod tests { } RattlerBuild::with_cargo(".").unwrap() } + fn recipes() -> PathBuf { test_data_dir().join("recipes") } + fn test_data_dir() -> PathBuf { PathBuf::from(shx("cargo locate-project --workspace -q --message-format=plain").unwrap()) .parent() @@ -426,10 +429,13 @@ mod tests { let license = pkg.join("info/licenses/LICENSE.rst"); assert!(license.exists()); - assert!(pkg.join("info/test/run_test.sh").exists()); - assert!(pkg.join("info/test/run_test.bat").exists()); - assert!(pkg.join("info/test/run_test.py").exists()); - assert!(pkg.join("info/test/test_time_dependencies.json").exists()); + assert!(pkg.join("info/tests/1/run_test.sh").exists()); + assert!(pkg.join("info/tests/1/run_test.bat").exists()); + assert!(pkg + .join("info/tests/1/test_time_dependencies.json") + .exists()); + + assert!(pkg.join("info/tests/0/run_test.py").exists()); // make sure that the entry point does not exist assert!(!pkg.join("python-scripts/flask").exists()); diff --git a/src/build.rs b/src/build.rs index 71ba42347..8f47bc241 100644 --- a/src/build.rs +++ b/src/build.rs @@ -18,12 +18,12 @@ use rattler_shell::shell; use crate::env_vars::write_env_script; use crate::metadata::{Directories, Output}; +use crate::package_test::TestConfiguration; use crate::packaging::{package_conda, record_files}; -use crate::recipe::parser::ScriptContent; +use crate::recipe::parser::{ScriptContent, TestType}; use crate::render::resolved_dependencies::{install_environments, resolve_dependencies}; use crate::source::fetch_sources; -use crate::test::TestConfiguration; -use crate::{index, test, tool_configuration}; +use crate::{index, package_test, tool_configuration}; const BASH_PREAMBLE: &str = r#" ## Start of bash preamble @@ -303,14 +303,18 @@ pub async fn run_build( ) .into_diagnostic()?; - if let Some(package_content) = output.recipe.test().package_content() { - test::run_package_content_tests( - package_content, - paths_json, - &output.build_configuration.target_platform, - ) - .await - .into_diagnostic()?; + // We run all the package content tests + for test in output.recipe.tests() { + // TODO we could also run each of the (potentially multiple) test scripts and collect the errors + if let TestType::PackageContents(package_contents) = test { + package_test::run_package_content_tests( + package_contents, + &paths_json, + &output.build_configuration.target_platform, + ) + .await + .into_diagnostic()?; + } } if !tool_configuration.no_clean { @@ -333,13 +337,14 @@ pub async fn run_build( } else { tracing::info!("Running tests"); - test::run_test( + package_test::run_test( &result, &TestConfiguration { test_prefix: test_dir.clone(), target_platform: Some(output.build_configuration.target_platform), keep_test_prefix: tool_configuration.no_clean, channels, + tool_configuration: tool_configuration.clone(), }, ) .await diff --git a/src/lib.rs b/src/lib.rs index b96c1107b..ac554e988 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,14 @@ -#![deny(missing_docs)] +// #![deny(missing_docs)] //! The library pieces of rattler-build pub mod build; pub mod metadata; +pub mod package_test; pub mod recipe; pub mod render; pub mod selectors; pub mod source; -pub mod test; pub mod tool_configuration; pub mod used_variables; pub mod variant_config; diff --git a/src/main.rs b/src/main.rs index f343d5fec..15bdbceca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,9 +26,9 @@ use rattler_build::{ build::run_build, hash::HashInfo, metadata::{BuildConfiguration, Directories, PackageIdentifier}, + package_test::{self, TestConfiguration}, recipe::{parser::Recipe, ParsingError}, selectors::SelectorConfig, - test::{self, TestConfiguration}, tool_configuration, variant_config::{ParseErrors, VariantConfig}, }; @@ -136,6 +136,10 @@ struct BuildOpts { #[derive(Parser)] struct TestOpts { + /// Channels to use when testing + #[arg(short = 'c', long)] + channel: Option>, + /// The package file to test #[arg(short, long)] package_file: PathBuf, @@ -187,10 +191,19 @@ async fn run_test_from_args(args: TestOpts) -> miette::Result<()> { test_prefix, target_platform: Some(Platform::current()), keep_test_prefix: false, - channels: vec!["conda-forge".to_string(), "./output".to_string()], + channels: args + .channel + .unwrap_or_else(|| vec!["conda-forge".to_string()]), + tool_configuration: tool_configuration::Configuration { + client: AuthenticatedClient::default(), + multi_progress_indicator: MultiProgress::new(), + // duplicate from `keep_test_prefix`? + no_clean: false, + ..Default::default() + }, }; - test::run_test(&package_file, &test_options) + package_test::run_test(&package_file, &test_options) .await .into_diagnostic()?; diff --git a/src/test.rs b/src/package_test.rs similarity index 74% rename from src/test.rs rename to src/package_test.rs index 38d364672..d6adfade0 100644 --- a/src/test.rs +++ b/src/package_test.rs @@ -7,27 +7,28 @@ //! * `imports` - import a list of modules and check if they can be imported //! * `files` - check if a list of files exist +use fs_err as fs; use std::{ - fs::{self}, - io::{Read, Write}, + io::Write, path::{Path, PathBuf}, str::FromStr, }; use dunce::canonicalize; -use indicatif::MultiProgress; use rattler::package_cache::CacheKey; use rattler_conda_types::{ - package::{ArchiveIdentifier, ArchiveType, PathsJson}, + package::{ArchiveIdentifier, PathsJson}, MatchSpec, Platform, }; -use rattler_networking::AuthenticatedClient; use rattler_shell::{ activation::{ActivationError, ActivationVariables, Activator}, shell::{Shell, ShellEnum, ShellScript}, }; -use crate::{env_vars, index, render::solver::create_environment, tool_configuration}; +use crate::{ + env_vars, index, recipe::parser::CommandsTestRequirements, render::solver::create_environment, + tool_configuration, +}; #[allow(missing_docs)] #[derive(thiserror::Error, Debug)] @@ -57,7 +58,7 @@ pub enum TestError { TestEnvironmentSetup(#[from] anyhow::Error), #[error("Failed to setup test environment: {0}")] - TestEnvironementActivation(#[from] ActivationError), + TestEnvironmentActivation(#[from] ActivationError), #[error("Failed to parse JSON from test files: {0}")] TestJSONParseError(#[from] serde_json::Error), @@ -181,10 +182,10 @@ impl Tests { } } -async fn tests_from_folder(pkg: &Path) -> Result<(PathBuf, Vec), TestError> { +async fn legacy_tests_from_folder(pkg: &Path) -> Result<(PathBuf, Vec), TestError> { let mut tests = Vec::new(); - let test_folder = pkg.join("info").join("test"); + let test_folder = pkg.join("info/test"); if !test_folder.exists() { return Ok((test_folder, tests)); @@ -212,52 +213,8 @@ async fn tests_from_folder(pkg: &Path) -> Result<(PathBuf, Vec), TestErro Ok((test_folder, tests)) } -fn file_from_tar_bz2(archive_path: &Path, find_path: &Path) -> Result { - let reader = std::fs::File::open(archive_path)?; - let mut archive = rattler_package_streaming::read::stream_tar_bz2(reader); - - for entry in archive.entries()? { - let mut entry = entry?; - let path = entry.path()?; - if path == find_path { - let mut contents = String::new(); - entry.read_to_string(&mut contents)?; - return Ok(contents); - } - } - Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("{:?} not found in {:?}", find_path, archive_path), - )) -} - -fn file_from_conda(archive_path: &Path, find_path: &Path) -> Result { - let reader = std::fs::File::open(archive_path)?; - - let mut archive = if find_path.starts_with("info") { - rattler_package_streaming::seek::stream_conda_info(reader) - .expect("Could not open conda file") - } else { - todo!("Not implemented yet"); - }; - - for entry in archive.entries()? { - let mut entry = entry?; - let path = entry.path()?; - if path == find_path { - let mut contents = String::new(); - entry.read_to_string(&mut contents)?; - return Ok(contents); - } - } - Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("{:?} not found in {:?}", find_path, archive_path), - )) -} - /// The configuration for a test -#[derive(Default, Debug)] +#[derive(Default)] pub struct TestConfiguration { /// The test prefix directory (will be created) pub test_prefix: PathBuf, @@ -268,6 +225,8 @@ pub struct TestConfiguration { /// The channels to use for the test – do not forget to add the local build outputs channel /// if desired pub channels: Vec, + /// The tool configuration + pub tool_configuration: tool_configuration::Configuration, } /// Run a test for a single package @@ -308,31 +267,6 @@ pub async fn run_test(package_file: &Path, config: &TestConfiguration) -> Result ), )?; - let archive_type = - ArchiveType::try_from(package_file).ok_or(TestError::ArchiveTypeNotSupported)?; - let test_dep_json = PathBuf::from("info/test/test_time_dependencies.json"); - let test_dependencies = match archive_type { - ArchiveType::TarBz2 => file_from_tar_bz2(package_file, &test_dep_json), - ArchiveType::Conda => file_from_conda(package_file, &test_dep_json), - }; - - let mut dependencies: Vec = match test_dependencies { - Ok(contents) => { - let test_deps: Vec = serde_json::from_str(&contents)?; - test_deps - .iter() - .map(|s| MatchSpec::from_str(s)) - .collect::, _>>()? - } - Err(error) => { - if error.kind() == std::io::ErrorKind::NotFound { - Vec::new() - } else { - return Err(TestError::TestFailed); - } - } - }; - // index the temporary channel index::index(tmp_repo.path(), Some(&target_platform))?; @@ -345,58 +279,204 @@ pub async fn run_test(package_file: &Path, config: &TestConfiguration) -> Result let package_folder = cache_dir.join("pkgs").join(cache_key.to_string()); if package_folder.exists() { - tracing::info!("Removing previously cached package {:?}", package_folder); - fs::remove_dir_all(package_folder)?; + tracing::info!("Removing previously cached package {:?}", &package_folder); + fs::remove_dir_all(&package_folder)?; + } + + let prefix = canonicalize(&config.test_prefix)?; + + let platform = if target_platform != Platform::NoArch { + target_platform + } else { + Platform::current() + }; + + let mut channels = config.channels.clone(); + channels.insert(0, tmp_repo.path().to_string_lossy().to_string()); + + tracing::info!("Collecting tests from {:?}", package_folder); + + rattler_package_streaming::fs::extract(package_file, &package_folder).map_err(|e| { + tracing::error!("Failed to extract package: {:?}", e); + TestError::TestFailed + })?; + + // extract package in place + if package_folder.join("info/test").exists() { + let test_dep_json = PathBuf::from("info/test/test_time_dependencies.json"); + let test_dependencies: Vec = if package_folder.join(&test_dep_json).exists() { + serde_json::from_str(&std::fs::read_to_string( + package_folder.join(&test_dep_json), + )?)? + } else { + Vec::new() + }; + + let mut dependencies: Vec = test_dependencies + .iter() + .map(|s| MatchSpec::from_str(s)) + .collect::, _>>()?; + + tracing::info!("Creating test environment in {:?}", prefix); + let match_spec = MatchSpec::from_str( + format!("{}={}={}", pkg.name, pkg.version, pkg.build_string).as_str(), + ) + .map_err(|e| TestError::MatchSpecParse(e.to_string()))?; + dependencies.push(match_spec); + + create_environment( + &dependencies, + &platform, + &prefix, + &channels, + &config.tool_configuration, + ) + .await + .map_err(TestError::TestEnvironmentSetup)?; + + // These are the legacy tests + let (test_folder, tests) = legacy_tests_from_folder(&package_folder).await?; + + for test in tests { + test.run(&prefix, &test_folder)?; + } + + tracing::info!( + "{} all tests passed!", + console::style(console::Emoji("✔", "")).green() + ); } + if package_folder.join("info/tests").exists() { + // These are the new style tests + let test_folder = package_folder.join("info/tests"); + let mut read_dir = tokio::fs::read_dir(&test_folder).await?; + + // for each enumerated test, we load and run it + while let Some(entry) = read_dir.next_entry().await? { + println!("test {:?}", entry.path()); + run_individual_test(&pkg, &entry.path(), &prefix, &channels, config).await?; + } + } + + fs::remove_dir_all(prefix)?; + + Ok(()) +} + +async fn run_python_test( + pkg: &ArchiveIdentifier, + _path: &Path, + prefix: &Path, + channels: &[String], + config: &TestConfiguration, +) -> Result<(), TestError> { + // create environment with the test dependencies let match_spec = MatchSpec::from_str(format!("{}={}={}", pkg.name, pkg.version, pkg.build_string).as_str()) - .map_err(|e| TestError::MatchSpecParse(e.to_string()))?; - dependencies.push(match_spec); + .unwrap(); - let prefix = canonicalize(&config.test_prefix)?; + let platform = Platform::current(); - let global_configuration = tool_configuration::Configuration { - client: AuthenticatedClient::default(), - multi_progress_indicator: MultiProgress::new(), - no_clean: config.keep_test_prefix, - ..Default::default() - }; + create_environment( + &[match_spec], + &platform, + prefix, + channels, + &config.tool_configuration, + ) + .await + .map_err(TestError::TestEnvironmentSetup)?; - tracing::info!("Creating test environment in {:?}", prefix); + Ok(()) +} - let platform = if target_platform != Platform::NoArch { - target_platform +async fn run_shell_test( + pkg: &ArchiveIdentifier, + path: &Path, + prefix: &Path, + channels: &[String], + config: &TestConfiguration, +) -> Result<(), TestError> { + // read the test dependencies + let deps = if path.join("test_time_dependencies.json").exists() { + let test_dep_json = path.join("test_time_dependencies.json"); + serde_json::from_str(&fs::read_to_string(test_dep_json)?)? } else { - Platform::current() + CommandsTestRequirements::default() }; + if !deps.build.is_empty() { + todo!("build dependencies not implemented yet"); + } + + let mut dependencies = deps + .run + .iter() + .map(|s| MatchSpec::from_str(s)) + .collect::, _>>()?; + + // create environment with the test dependencies + dependencies.push(MatchSpec::from_str( + format!("{}={}={}", pkg.name, pkg.version, pkg.build_string).as_str(), + )?); + + let platform = Platform::current(); + create_environment( &dependencies, &platform, - &prefix, - &config.channels, - &global_configuration, + prefix, + channels, + &config.tool_configuration, ) .await .map_err(TestError::TestEnvironmentSetup)?; - let cache_key = CacheKey::from(pkg); - let dir = cache_dir.join("pkgs").join(cache_key.to_string()); + let default_shell = ShellEnum::default(); - tracing::info!("Collecting tests from {:?}", dir); - let (test_folder, tests) = tests_from_folder(&dir).await?; + let test_file_path = if platform.is_windows() { + path.join("run_test.bat") + } else { + path.join("run_test.sh") + }; + + let contents = fs::read_to_string(&test_file_path)?; + let is_path_ext = |ext: &str| { + test_file_path + .extension() + .map(|s| s.eq(ext)) + .unwrap_or_default() + }; - for test in tests { - test.run(&prefix, &test_folder)?; + if Platform::current().is_windows() && is_path_ext("bat") { + tracing::info!("Testing commands:"); + run_in_environment(default_shell, contents, path, prefix)?; + } else if Platform::current().is_unix() && is_path_ext("sh") { + tracing::info!("Testing commands:"); + run_in_environment(default_shell, contents, path, prefix)?; } - tracing::info!( - "{} all tests passed!", - console::style(console::Emoji("✔", "")).green() - ); + Ok(()) +} - fs::remove_dir_all(prefix)?; +async fn run_individual_test( + pkg: &ArchiveIdentifier, + path: &Path, + prefix: &Path, + channels: &[String], + config: &TestConfiguration, +) -> Result<(), TestError> { + // detect which of the test files we have + if path.join("run_test.py").exists() { + // run python test + run_python_test(pkg, path, prefix, channels, config).await?; + } else if path.join("run_test.sh").exists() || path.join("run_test.bat").exists() { + // run shell test + run_shell_test(pkg, path, prefix, channels, config).await?; + } else { + // no test found + } Ok(()) } @@ -411,8 +491,8 @@ pub async fn run_test(package_file: &Path, config: &TestConfiguration) -> Result /// * `Ok(())` if the test was successful /// * `Err(TestError::TestFailed)` if the test failed pub async fn run_package_content_tests( - package_content: &crate::recipe::parser::PackageContent, - paths_json: PathsJson, + package_content: &crate::recipe::parser::PackageContents, + paths_json: &PathsJson, target_platform: &Platform, ) -> Result<(), TestError> { // files globset diff --git a/src/packaging.rs b/src/packaging.rs index 799c33814..17f400c89 100644 --- a/src/packaging.rs +++ b/src/packaging.rs @@ -27,6 +27,7 @@ use rattler_package_streaming::write::{ use crate::macos; use crate::metadata::Output; +use crate::recipe::parser::{DownstreamTest, PythonTest, TestType}; use crate::{linux, post}; #[derive(Debug, thiserror::Error)] @@ -589,7 +590,7 @@ fn copy_license_files( .use_gitignore(false) .run()?; - let copied_files_recipe_dir = copy_dir.copied_pathes(); + let copied_files_recipe_dir = copy_dir.copied_paths(); let any_include_matched_recipe_dir = copy_dir.any_include_glob_matched(); let copy_dir = crate::source::copy_dir::CopyDir::new( @@ -600,7 +601,7 @@ fn copy_license_files( .use_gitignore(false) .run()?; - let copied_files_work_dir = copy_dir.copied_pathes(); + let copied_files_work_dir = copy_dir.copied_paths(); let any_include_matched_work_dir = copy_dir.any_include_glob_matched(); let copied_files = copied_files_recipe_dir @@ -658,103 +659,131 @@ fn filter_pyc(path: &Path, new_files: &HashSet) -> bool { fn write_test_files(output: &Output, tmp_dir_path: &Path) -> Result, PackagingError> { let mut test_files = Vec::new(); - let test = output.recipe.test(); - if !test.is_empty() { - let test_folder = tmp_dir_path.join("info/test/"); - fs::create_dir_all(&test_folder)?; - - if !test.imports().is_empty() { - let test_file = test_folder.join("run_test.py"); - let mut file = File::create(&test_file)?; - for el in test.imports() { - writeln!(file, "import {}\n", el)?; + + for (idx, test) in output.recipe.tests().iter().enumerate() { + if let Some(files) = match test { + TestType::Python(python_test) => { + Some(serialize_python_test(python_test, idx, tmp_dir_path)?) } - test_files.push(test_file); + TestType::Command(command_test) => Some(serialize_command_test( + command_test, + idx, + output, + tmp_dir_path, + )?), + TestType::Downstream(downstream_test) => Some(serialize_downstream_test( + downstream_test, + idx, + tmp_dir_path, + )?), + TestType::PackageContents(_) => None, + } { + test_files.extend(files); } + } - if !test.commands().is_empty() { - let mut command_files = vec![]; - let target_platform = &output.build_configuration.target_platform; - if target_platform.is_windows() || target_platform == &Platform::NoArch { - command_files.push(test_folder.join("run_test.bat")); - } - if target_platform.is_unix() || target_platform == &Platform::NoArch { - command_files.push(test_folder.join("run_test.sh")); - } + Ok(test_files) +} - for cf in command_files { - let mut file = File::create(&cf)?; - for el in test.commands() { - writeln!(file, "{}\n", el)?; - } - test_files.push(cf); - } - } +fn serialize_downstream_test( + downstream_test: &DownstreamTest, + idx: usize, + tmp_dir_path: &Path, +) -> Result, PackagingError> { + let folder = tmp_dir_path.join(format!("info/tests/{}", idx)); + fs::create_dir_all(&folder)?; - if !test.requires().is_empty() { - let test_dependencies = test.requires(); - let test_file = test_folder.join("test_time_dependencies.json"); - let mut file = File::create(&test_file)?; - file.write_all(serde_json::to_string(test_dependencies)?.as_bytes())?; - test_files.push(test_file); - } + let path = folder.join("downstream_test.json"); + let mut file = File::create(&path)?; + file.write_all(serde_json::to_string(downstream_test)?.as_bytes())?; - if !test.files().is_empty() { - let globs = test.files(); - let include_globs = globs - .iter() - .filter(|glob| !glob.trim_start().starts_with('~')) - .map(AsRef::as_ref) - .collect::>(); - - let exclude_globs = globs - .iter() - .filter(|glob| glob.trim_start().starts_with('~')) - .map(AsRef::as_ref) - .collect::>(); - - let copy_dir = crate::source::copy_dir::CopyDir::new( - &output.build_configuration.directories.recipe_dir, - &test_folder, - ) - .with_include_globs(include_globs) - .with_exclude_globs(exclude_globs) - .use_gitignore(true) - .run()?; - - test_files.extend(copy_dir.copied_pathes().iter().cloned()); - } + Ok(vec![path]) +} + +fn serialize_command_test( + command_test: &crate::recipe::parser::CommandsTest, + idx: usize, + output: &Output, + tmp_dir_path: &Path, +) -> Result, PackagingError> { + let mut command_files = vec![]; + let mut test_files = vec![]; + + let test_folder = tmp_dir_path.join(format!("info/tests/{}", idx)); + fs::create_dir_all(&test_folder)?; + + let target_platform = &output.build_configuration.target_platform; + if target_platform.is_windows() || target_platform == &Platform::NoArch { + command_files.push(test_folder.join("run_test.bat")); + } + + if target_platform.is_unix() || target_platform == &Platform::NoArch { + command_files.push(test_folder.join("run_test.sh")); + } - if !test.source_files().is_empty() { - let globs = test.source_files(); - let include_globs = globs - .iter() - .filter(|glob| !glob.trim_start().starts_with('~')) - .map(AsRef::as_ref) - .collect::>(); - - let exclude_globs = globs - .iter() - .filter(|glob| glob.trim_start().starts_with('~')) - .map(AsRef::as_ref) - .collect::>(); - - let copy_dir = crate::source::copy_dir::CopyDir::new( - &output.build_configuration.directories.work_dir, - &test_folder, - ) - .with_include_globs(include_globs) - .with_exclude_globs(exclude_globs) - .use_gitignore(true) - .run()?; - - test_files.extend(copy_dir.copied_pathes().iter().cloned()); + for cf in command_files { + let mut file = File::create(&cf)?; + for el in &command_test.script { + writeln!(file, "{}\n", el)?; } + test_files.push(cf); + } + + if !command_test.requirements.is_empty() { + let test_dependencies = &command_test.requirements; + let test_file = test_folder.join("test_time_dependencies.json"); + let mut file = File::create(&test_file)?; + file.write_all(serde_json::to_string(&test_dependencies)?.as_bytes())?; + test_files.push(test_file); + } + + if !command_test.files.recipe.is_empty() { + let globs = &command_test.files.recipe; + let copy_dir = crate::source::copy_dir::CopyDir::new( + &output.build_configuration.directories.recipe_dir, + &test_folder, + ) + .with_parse_globs(globs.iter().map(AsRef::as_ref)) + .use_gitignore(true) + .run()?; + + test_files.extend(copy_dir.copied_paths().iter().cloned()); + } + + if !command_test.files.source.is_empty() { + let globs = &command_test.files.source; + let copy_dir = crate::source::copy_dir::CopyDir::new( + &output.build_configuration.directories.work_dir, + &test_folder, + ) + .with_parse_globs(globs.iter().map(AsRef::as_ref)) + .use_gitignore(true) + .run()?; + + test_files.extend(copy_dir.copied_paths().iter().cloned()); } Ok(test_files) } +fn serialize_python_test( + python_test: &PythonTest, + idx: usize, + tmp_dir_path: &Path, +) -> Result, PackagingError> { + let folder = tmp_dir_path.join(format!("info/tests/{}", idx)); + fs::create_dir_all(&folder)?; + + let path = folder.join("run_test.py"); + let mut file = File::create(&path)?; + + for el in &python_test.imports { + writeln!(file, "import {}", el)?; + } + + Ok(vec![path]) +} + fn write_recipe_folder( output: &Output, tmp_dir_path: &Path, @@ -764,7 +793,7 @@ fn write_recipe_folder( let copy_result = crate::source::copy_dir::CopyDir::new(recipe_dir, &recipe_folder).run()?; - let mut files = Vec::from(copy_result.copied_pathes()); + let mut files = Vec::from(copy_result.copied_paths()); // write the variant config to the appropriate file let variant_config_file = recipe_folder.join("variant_config.yaml"); let mut variant_config = File::create(&variant_config_file)?; diff --git a/src/recipe/parser.rs b/src/recipe/parser.rs index b1d807999..b7d207ebd 100644 --- a/src/recipe/parser.rs +++ b/src/recipe/parser.rs @@ -35,7 +35,10 @@ pub use self::{ }, script::{Script, ScriptContent}, source::{Checksum, GitSource, GitUrl, PathSource, Source, UrlSource}, - test::{PackageContent, Test}, + test::{ + CommandsTest, CommandsTestFiles, CommandsTestRequirements, DownstreamTest, PackageContents, + PythonTest, TestType, + }, }; use super::custom_yaml::Node; @@ -48,8 +51,8 @@ pub struct Recipe { source: Vec, build: Build, requirements: Requirements, - #[serde(default, skip_serializing_if = "Test::is_default")] - test: Test, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + tests: Vec, #[serde(default, skip_serializing_if = "About::is_default")] about: About, } @@ -178,7 +181,7 @@ impl Recipe { let mut build = Build::default(); let mut source = Vec::new(); let mut requirements = Requirements::default(); - let mut test = Test::default(); + let mut tests = Vec::default(); let mut about = About::default(); rendered_node @@ -198,7 +201,7 @@ impl Recipe { "source" => source = value.try_convert(key_str)?, "build" => build = value.try_convert(key_str)?, "requirements" => requirements = value.try_convert(key_str)?, - "test" => test = value.try_convert(key_str)?, + "tests" => tests = value.try_convert(key_str)?, "about" => about = value.try_convert(key_str)?, "context" => {} "extra" => {} @@ -231,7 +234,7 @@ impl Recipe { build, source, requirements, - test, + tests, about, }; @@ -259,8 +262,8 @@ impl Recipe { } /// Get the test information. - pub const fn test(&self) -> &Test { - &self.test + pub const fn tests(&self) -> &Vec { + &self.tests } /// Get the about information. @@ -272,6 +275,7 @@ impl Recipe { #[cfg(test)] mod tests { use insta::assert_yaml_snapshot; + use rattler_conda_types::Platform; use crate::{assert_miette_snapshot, variant_config::ParseErrors}; @@ -280,12 +284,24 @@ mod tests { #[test] fn it_works() { let recipe = include_str!("../../examples/xtensor/recipe.yaml"); - let recipe = Recipe::from_yaml(recipe, SelectorConfig::default()); - assert!(recipe.is_ok()); - #[cfg(target_family = "unix")] - insta::assert_debug_snapshot!(recipe.unwrap()); - #[cfg(target_family = "windows")] - insta::assert_debug_snapshot!("recipe_windows", recipe.unwrap()); + + let selector_config_win = SelectorConfig { + target_platform: Platform::Win64, + ..SelectorConfig::default() + }; + + let selector_config_unix = SelectorConfig { + target_platform: Platform::Linux64, + ..SelectorConfig::default() + }; + + let unix_recipe = Recipe::from_yaml(recipe, selector_config_unix); + let win_recipe = Recipe::from_yaml(recipe, selector_config_win); + assert!(unix_recipe.is_ok()); + assert!(win_recipe.is_ok()); + + insta::assert_debug_snapshot!("unix_recipe", unix_recipe.unwrap()); + insta::assert_debug_snapshot!("recipe_windows", win_recipe.unwrap()); } #[test] diff --git a/src/recipe/parser/snapshots/rattler_build__recipe__parser__test__test__parsing.snap b/src/recipe/parser/snapshots/rattler_build__recipe__parser__test__test__parsing.snap new file mode 100644 index 000000000..5ddd1c960 --- /dev/null +++ b/src/recipe/parser/snapshots/rattler_build__recipe__parser__test__test__parsing.snap @@ -0,0 +1,9 @@ +--- +source: src/recipe/parser/test.rs +expression: "serde_yaml::to_string(&tests).unwrap()" +--- +- test_type: python + imports: + - import os + - import sys + diff --git a/src/recipe/parser/test.rs b/src/recipe/parser/test.rs index 05a18e397..2aa11e6b4 100644 --- a/src/recipe/parser/test.rs +++ b/src/recipe/parser/test.rs @@ -1,48 +1,82 @@ +//! Test parser module. + use serde::{Deserialize, Serialize}; use crate::{ _partialerror, recipe::{ - custom_yaml::{HasSpan, RenderedMappingNode, RenderedNode, TryConvertNode}, + custom_yaml::{ + HasSpan, RenderedMappingNode, RenderedNode, RenderedSequenceNode, TryConvertNode, + }, error::{ErrorKind, PartialParsingError}, }, }; use super::FlattenErrors; -/// Define tests in your recipe that are executed after successfully building the package. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] -pub struct Test { - /// Try importing a python module as a sanity check - #[serde(default, skip_serializing_if = "Vec::is_empty")] - imports: Vec, - /// Run a list of given commands +pub struct CommandsTestRequirements { #[serde(default, skip_serializing_if = "Vec::is_empty")] - commands: Vec, - /// Extra requirements to be installed at test time + pub run: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] - requires: Vec, - /// Extra files to be copied to the test environment from the source dir (can be globs) + pub build: Vec, +} +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct CommandsTestFiles { + // TODO parse as globs #[serde(default, skip_serializing_if = "Vec::is_empty")] - source_files: Vec, - /// Extra files to be copied to the test environment from the build dir (can be globs) + pub source: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] - files: Vec, - /// All new test section - #[serde(skip_serializing_if = "Option::is_none")] - package_contents: Option, + pub recipe: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct CommandsTest { + pub script: Vec, + #[serde(default, skip_serializing_if = "CommandsTestRequirements::is_empty")] + pub requirements: CommandsTestRequirements, + #[serde(default, skip_serializing_if = "CommandsTestFiles::is_empty")] + pub files: CommandsTestFiles, } -impl Test { - /// Returns true if the test has its default configuration. - pub fn is_default(&self) -> bool { - self == &Self::default() +impl CommandsTestRequirements { + pub fn is_empty(&self) -> bool { + self.run.is_empty() && self.build.is_empty() + } +} + +impl CommandsTestFiles { + pub fn is_empty(&self) -> bool { + self.source.is_empty() && self.recipe.is_empty() } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PythonTest { + pub imports: Vec, +} +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct DownstreamTest { + pub downstream: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// The test type enum +#[serde(rename_all = "snake_case", tag = "test_type")] +pub enum TestType { + /// A Python test. + Python(PythonTest), + /// A test that executes multiple commands in a freshly created environment + Command(CommandsTest), + /// A test that runs the tests of a downstream package + Downstream(DownstreamTest), + /// A test that checks the contents of the package + PackageContents(PackageContents), +} + #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] /// PackageContent -pub struct PackageContent { +pub struct PackageContents { /// file paths, direct and/or globs #[serde(default, skip_serializing_if = "Vec::is_empty")] files: Vec, @@ -61,7 +95,7 @@ pub struct PackageContent { includes: Vec, } -impl PackageContent { +impl PackageContents { /// Get the package files. pub fn files(&self) -> &[String] { &self.files @@ -88,126 +122,331 @@ impl PackageContent { } } -impl TryConvertNode for RenderedNode { - fn try_convert(&self, name: &str) -> Result> { +impl TryConvertNode> for RenderedNode { + fn try_convert(&self, name: &str) -> Result, Vec> { + match self { + RenderedNode::Sequence(seq) => seq.try_convert(name), + RenderedNode::Scalar(_) | RenderedNode::Mapping(_) => Err(vec![_partialerror!( + *self.span(), + ErrorKind::ExpectedSequence, + )])?, + RenderedNode::Null(_) => Ok(vec![]), + } + } +} + +impl TryConvertNode> for RenderedSequenceNode { + fn try_convert(&self, name: &str) -> Result, Vec> { + let mut tests = vec![]; + for value in self.iter() { + let test = value.try_convert(name)?; + tests.push(test); + } + Ok(tests) + } +} + +impl TryConvertNode for RenderedNode { + fn try_convert(&self, name: &str) -> Result> { match self { RenderedNode::Mapping(map) => map.try_convert(name), RenderedNode::Sequence(_) | RenderedNode::Scalar(_) => Err(vec![_partialerror!( *self.span(), ErrorKind::ExpectedMapping, )])?, - RenderedNode::Null(_) => Ok(PackageContent::default()), + RenderedNode::Null(_) => Ok(TestType::PackageContents(PackageContents::default())), } } } -impl TryConvertNode for RenderedMappingNode { - fn try_convert(&self, name: &str) -> Result> { - let mut files = vec![]; - let mut site_packages = vec![]; - let mut libs = vec![]; - let mut bins = vec![]; - let mut includes = vec![]; +pub fn as_mapping( + value: &RenderedNode, + name: &str, +) -> Result> { + value.as_mapping().cloned().ok_or_else(|| { + vec![_partialerror!( + *value.span(), + ErrorKind::ExpectedMapping, + help = format!("expected fields for {name} to be a map") + )] + }) +} + +impl TryConvertNode for RenderedMappingNode { + fn try_convert(&self, name: &str) -> Result> { + let mut test = TestType::PackageContents(PackageContents::default()); + self.iter().map(|(key, value)| { let key_str = key.as_str(); + match key_str { - "files" => files = value.try_convert(key_str)?, - "site_packages" => site_packages = value.try_convert(key_str)?, - "libs" => libs = value.try_convert(key_str)?, - "bins" => bins = value.try_convert(key_str)?, - "includes" => includes = value.try_convert(key_str)?, + "python" => { + let imports = as_mapping(value, key_str)?.try_convert(key_str)?; + test = TestType::Python(imports); + } + "script" | "requirements" | "files" => { + let commands = self.try_convert(key_str)?; + test = TestType::Command(commands); + } + "downstream" => { + let downstream = self.try_convert(key_str)?; + test = TestType::Downstream(downstream); + } + "package_contents" => { + let package_contents = as_mapping(value, key_str)?.try_convert(key_str)?; + test = TestType::PackageContents(package_contents); + } invalid => Err(vec![_partialerror!( *key.span(), ErrorKind::InvalidField(invalid.to_string().into()), - help = format!("expected fields for {name} is one of `files`, `site_packages`, `libs`, `bins`, `includes`") + help = format!("expected fields for {name} is one of `python`, `command`, `downstream`, `package_contents`") )])? } Ok(()) }).flatten_errors()?; - Ok(PackageContent { - files, - site_packages, - bins, - libs, - includes, - }) + Ok(test) } } -impl Test { - /// Get package content. - pub fn package_content(&self) -> Option<&PackageContent> { - self.package_contents.as_ref() - } +/////////////////////////// +/// Python Test /// +/////////////////////////// - /// Get the imports. - pub fn imports(&self) -> &[String] { - self.imports.as_slice() - } +impl TryConvertNode for RenderedMappingNode { + fn try_convert(&self, name: &str) -> Result> { + let mut imports = vec![]; + + self.iter() + .map(|(key, value)| { + let key_str = key.as_str(); + match key_str { + "imports" => imports = value.try_convert(key_str)?, + invalid => Err(vec![_partialerror!( + *key.span(), + ErrorKind::InvalidField(invalid.to_string().into()), + help = format!("expected fields for {name} is one of `imports`") + )])?, + } + Ok(()) + }) + .flatten_errors()?; - /// Get the commands. - pub fn commands(&self) -> &[String] { - self.commands.as_slice() + if imports.is_empty() { + Err(vec![_partialerror!( + *self.span(), + ErrorKind::MissingField("imports".into()), + help = "expected field `imports` in python test to be a list of imports" + )])?; + } + + Ok(PythonTest { imports }) } +} + +/////////////////////////// +/// Downstream Test /// +/////////////////////////// + +impl TryConvertNode for RenderedMappingNode { + fn try_convert(&self, name: &str) -> Result> { + let mut downstream = String::new(); + + self.iter() + .map(|(key, value)| { + let key_str = key.as_str(); + match key_str { + "downstream" => downstream = value.try_convert(key_str)?, + invalid => Err(vec![_partialerror!( + *key.span(), + ErrorKind::InvalidField(invalid.to_string().into()), + help = format!("expected fields for {name} is one of `downstream`") + )])?, + } + Ok(()) + }) + .flatten_errors()?; - /// Get the requires. - pub fn requires(&self) -> &[String] { - self.requires.as_slice() + Ok(DownstreamTest { downstream }) } +} + +/////////////////////////// +/// Commands Test /// +/////////////////////////// + +impl TryConvertNode for RenderedMappingNode { + fn try_convert( + &self, + name: &str, + ) -> Result> { + let mut run = vec![]; + let mut build = vec![]; - /// Get the source files. - pub fn source_files(&self) -> &[String] { - self.source_files.as_slice() + self.iter() + .map(|(key, value)| { + let key_str = key.as_str(); + match key_str { + "run" => run = value.try_convert(key_str)?, + "build" => build = value.try_convert(key_str)?, + invalid => Err(vec![_partialerror!( + *key.span(), + ErrorKind::InvalidField(invalid.to_string().into()), + help = format!("expected fields for {name} is one of `run`, `build`") + )])?, + } + Ok(()) + }) + .flatten_errors()?; + + Ok(CommandsTestRequirements { run, build }) } +} - /// Get the files. - pub fn files(&self) -> &[String] { - self.files.as_slice() +impl TryConvertNode for RenderedMappingNode { + fn try_convert(&self, name: &str) -> Result> { + let mut source = vec![]; + let mut recipe = vec![]; + + self.iter() + .map(|(key, value)| { + let key_str = key.as_str(); + match key_str { + "source" => source = value.try_convert(key_str)?, + "recipe" => recipe = value.try_convert(key_str)?, + invalid => Err(vec![_partialerror!( + *key.span(), + ErrorKind::InvalidField(invalid.to_string().into()), + help = format!("expected fields for {name} is one of `source`, `build`") + )])?, + } + Ok(()) + }) + .flatten_errors()?; + + Ok(CommandsTestFiles { source, recipe }) } +} - /// Check if there is not test commands to be run - pub fn is_empty(&self) -> bool { - self.commands.is_empty() +impl TryConvertNode for RenderedMappingNode { + fn try_convert(&self, name: &str) -> Result> { + let mut script = vec![]; + let mut requirements = CommandsTestRequirements::default(); + let mut files = CommandsTestFiles::default(); + + self.iter() + .map(|(key, value)| { + let key_str = key.as_str(); + match key_str { + "script" => script = value.try_convert(key_str)?, + "requirements" => { + requirements = as_mapping(value, key_str)?.try_convert(key_str)? + } + "files" => files = as_mapping(value, key_str)?.try_convert(key_str)?, + invalid => Err(vec![_partialerror!( + *key.span(), + ErrorKind::InvalidField(invalid.to_string().into()), + help = format!( + "expected fields for {name} is one of `script`, `requirements`, `files`" + ) + )])?, + } + Ok(()) + }) + .flatten_errors()?; + + if script.is_empty() { + Err(vec![_partialerror!( + *self.span(), + ErrorKind::MissingField("script".into()), + help = "expected field `script` to be a list of commands" + )])?; + } + + Ok(CommandsTest { + script, + requirements, + files, + }) } } -impl TryConvertNode for RenderedNode { - fn try_convert(&self, name: &str) -> Result> { +/////////////////////////// +/// Package Contents /// +/////////////////////////// + +impl TryConvertNode for RenderedNode { + fn try_convert(&self, name: &str) -> Result> { match self { RenderedNode::Mapping(map) => map.try_convert(name), - RenderedNode::Scalar(_) => Err(vec![_partialerror!( + RenderedNode::Sequence(_) | RenderedNode::Scalar(_) => Err(vec![_partialerror!( *self.span(), ErrorKind::ExpectedMapping, )])?, - RenderedNode::Null(_) => Ok(Test::default()), - RenderedNode::Sequence(_) => todo!("Not implemented yet: sequence on Test"), + RenderedNode::Null(_) => Ok(PackageContents::default()), } } } -impl TryConvertNode for RenderedMappingNode { - fn try_convert(&self, name: &str) -> Result> { - let mut test = Test::default(); +impl TryConvertNode for RenderedMappingNode { + fn try_convert(&self, name: &str) -> Result> { + let mut files = vec![]; + let mut site_packages = vec![]; + let mut libs = vec![]; + let mut bins = vec![]; + let mut includes = vec![]; self.iter().map(|(key, value)| { let key_str = key.as_str(); match key_str { - "package_contents" => test.package_contents = value.try_convert(key_str)?, - "imports" => test.imports = value.try_convert(key_str)?, - "commands" => test.commands = value.try_convert(key_str)?, - "requires" => test.requires = value.try_convert(key_str)?, - "source_files" => test.source_files = value.try_convert(key_str)?, - "files" => test.files = value.try_convert(key_str)?, + "files" => files = value.try_convert(key_str)?, + "site_packages" => site_packages = value.try_convert(key_str)?, + "lib" => libs = value.try_convert(key_str)?, + "bin" => bins = value.try_convert(key_str)?, + "include" => includes = value.try_convert(key_str)?, invalid => Err(vec![_partialerror!( *key.span(), ErrorKind::InvalidField(invalid.to_string().into()), - help = format!("expected fields for {name} is one of `imports`, `commands`, `requires`, `source_files`, `files`") + help = format!("expected fields for {name} is one of `files`, `site_packages`, `libs`, `bins`, `includes`") )])? - }; + } Ok(()) }).flatten_errors()?; - Ok(test) + Ok(PackageContents { + files, + site_packages, + bins, + libs, + includes, + }) + } +} + +#[cfg(test)] +mod test { + use super::TestType; + use insta::assert_snapshot; + + use crate::recipe::custom_yaml::{RenderedNode, TryConvertNode}; + + #[test] + fn test_parsing() { + let test_section = r#" + tests: + - python: + imports: + - import os + - import sys + "#; + + // parse the YAML + let yaml_root = RenderedNode::parse_yaml(0, test_section) + .map_err(|err| vec![err]) + .unwrap(); + let tests_node = yaml_root.as_mapping().unwrap().get("tests").unwrap(); + let tests: Vec = tests_node.try_convert("tests").unwrap(); + + assert_snapshot!(serde_yaml::to_string(&tests).unwrap()); } } diff --git a/src/recipe/snapshots/rattler_build__recipe__parser__tests__recipe_windows.snap b/src/recipe/snapshots/rattler_build__recipe__parser__tests__recipe_windows.snap index 5db3ab5bb..74b103bbc 100644 --- a/src/recipe/snapshots/rattler_build__recipe__parser__tests__recipe_windows.snap +++ b/src/recipe/snapshots/rattler_build__recipe__parser__tests__recipe_windows.snap @@ -1,6 +1,6 @@ --- source: src/recipe/parser.rs -expression: recipe.unwrap() +expression: win_recipe.unwrap() --- Recipe { package: Package { @@ -254,18 +254,25 @@ Recipe { from_package: {}, }, }, - test: Test { - imports: [], - commands: [ - "if not exist %LIBRARY_PREFIX%\\include\\xtensor\\xarray.hpp (exit 1)", - "if not exist %LIBRARY_PREFIX%\\share\\cmake\\xtensor\\xtensorConfig.cmake (exit 1)", - "if not exist %LIBRARY_PREFIX%\\share\\cmake\\xtensor\\xtensorConfigVersion.cmake (exit 1)", - ], - requires: [], - source_files: [], - files: [], - package_contents: None, - }, + tests: [ + Command( + CommandsTest { + script: [ + "if not exist %LIBRARY_PREFIX%\\include\\xtensor\\xarray.hpp (exit 1)", + "if not exist %LIBRARY_PREFIX%\\share\\cmake\\xtensor\\xtensorConfig.cmake (exit 1)", + "if not exist %LIBRARY_PREFIX%\\share\\cmake\\xtensor\\xtensorConfigVersion.cmake (exit 1)", + ], + requirements: CommandsTestRequirements { + run: [], + build: [], + }, + files: CommandsTestFiles { + source: [], + recipe: [], + }, + }, + ), + ], about: About { homepage: Some( Url { diff --git a/src/recipe/snapshots/rattler_build__recipe__parser__tests__it_works.snap b/src/recipe/snapshots/rattler_build__recipe__parser__tests__unix_recipe.snap similarity index 93% rename from src/recipe/snapshots/rattler_build__recipe__parser__tests__it_works.snap rename to src/recipe/snapshots/rattler_build__recipe__parser__tests__unix_recipe.snap index 4095685a0..c1e931a44 100644 --- a/src/recipe/snapshots/rattler_build__recipe__parser__tests__it_works.snap +++ b/src/recipe/snapshots/rattler_build__recipe__parser__tests__unix_recipe.snap @@ -1,6 +1,6 @@ --- source: src/recipe/parser.rs -expression: recipe.unwrap() +expression: unix_recipe.unwrap() --- Recipe { package: Package { @@ -273,19 +273,26 @@ Recipe { from_package: {}, }, }, - test: Test { - imports: [], - commands: [ - "test -d ${PREFIX}/include/xtensor", - "test -f ${PREFIX}/include/xtensor/xarray.hpp", - "test -f ${PREFIX}/share/cmake/xtensor/xtensorConfig.cmake", - "test -f ${PREFIX}/share/cmake/xtensor/xtensorConfigVersion.cmake", - ], - requires: [], - source_files: [], - files: [], - package_contents: None, - }, + tests: [ + Command( + CommandsTest { + script: [ + "test -d ${PREFIX}/include/xtensor", + "test -f ${PREFIX}/include/xtensor/xarray.hpp", + "test -f ${PREFIX}/share/cmake/xtensor/xtensorConfig.cmake", + "test -f ${PREFIX}/share/cmake/xtensor/xtensorConfigVersion.cmake", + ], + requirements: CommandsTestRequirements { + run: [], + build: [], + }, + files: CommandsTestFiles { + source: [], + recipe: [], + }, + }, + ), + ], about: About { homepage: Some( Url { diff --git a/src/render/solver.rs b/src/render/solver.rs index 851de3d1e..31329e2eb 100644 --- a/src/render/solver.rs +++ b/src/render/solver.rs @@ -102,7 +102,7 @@ pub async fn create_environment( // Each channel contains multiple subdirectories. Users can specify the subdirectories they want // to use when specifying their channels. If the user didn't specify the default subdirectories // we use defaults based on the current platform. - let platforms = vec![Platform::NoArch, *target_platform]; + let platforms = &[Platform::NoArch, *target_platform]; let channel_urls = channels .iter() .flat_map(|channel| { diff --git a/src/snapshots/rattler_build__metadata__test__read_full_recipe.snap b/src/snapshots/rattler_build__metadata__test__read_full_recipe.snap index 8d9a098d0..ac963f469 100644 --- a/src/snapshots/rattler_build__metadata__test__read_full_recipe.snap +++ b/src/snapshots/rattler_build__metadata__test__read_full_recipe.snap @@ -25,13 +25,16 @@ recipe: - "pygments >=2.13.0,<3.0.0" - python ==3.10 - "typing_extensions >=4.0.0,<5.0.0" - test: - imports: - - rich - commands: - - pip check - requires: - - pip + tests: + - test_type: python + imports: + - rich + - test_type: command + script: + - pip check + requirements: + run: + - pip about: homepage: "https://github.com/Textualize/rich" repository: "https://github.com/Textualize/rich" @@ -52,13 +55,13 @@ build_configuration: hash_input: "{\"target_platform\": \"noarch\"}" hash_prefix: py directories: - host_prefix: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1700573439/host_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold - build_prefix: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1700573439/build_env - work_dir: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1700573439/work - build_dir: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1700573439 + host_prefix: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1702492812/host_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold + build_prefix: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1702492812/build_env + work_dir: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1702492812/work + build_dir: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1702492812 channels: - conda-forge - timestamp: "2023-11-21T13:30:39.246259Z" + timestamp: "2023-12-13T18:40:12.845800Z" subpackages: rich: name: rich @@ -225,39 +228,6 @@ finalized_dependencies: fn: tzdata-2023c-h71feb2d_0.conda url: "https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda" channel: "https://conda.anaconda.org/conda-forge/" - - build: hf2abe2d_0 - build_number: 0 - depends: - - libsqlite 3.44.0 h091b4b1_0 - - "libzlib >=1.2.13,<1.3.0a0" - - "ncurses >=6.4,<7.0a0" - - "readline >=8.2,<9.0a0" - license: Unlicense - md5: 0080e3f5d7d13d3b1e244ed24642ca9e - name: sqlite - sha256: 8263043d2a5762a5bbbb4ceee28382d97e70182fff8d45371b65fedda0b709ee - size: 800748 - subdir: osx-arm64 - timestamp: 1698855055771 - version: 3.44.0 - fn: sqlite-3.44.0-hf2abe2d_0.conda - url: "https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.44.0-hf2abe2d_0.conda" - channel: "https://conda.anaconda.org/conda-forge/" - - build: h091b4b1_0 - build_number: 0 - depends: - - "libzlib >=1.2.13,<1.3.0a0" - license: Unlicense - md5: 28eb31a5b4e704353ed575758e2fcf1d - name: libsqlite - sha256: 38e98953b572e2871f2b318fa7fe8d9997b0927970916c2d09402273b60ff832 - size: 815079 - subdir: osx-arm64 - timestamp: 1698855024189 - version: 3.44.0 - fn: libsqlite-3.44.0-h091b4b1_0.conda - url: "https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.44.0-h091b4b1_0.conda" - channel: "https://conda.anaconda.org/conda-forge/" - build: pyhd8ed1ab_0 build_number: 0 depends: @@ -275,23 +245,56 @@ finalized_dependencies: fn: poetry-core-1.8.1-pyhd8ed1ab_0.conda url: "https://conda.anaconda.org/conda-forge/noarch/poetry-core-1.8.1-pyhd8ed1ab_0.conda" channel: "https://conda.anaconda.org/conda-forge/" - - build: h0d3ecfb_0 + - build: hf2abe2d_0 + build_number: 0 + depends: + - libsqlite 3.44.2 h091b4b1_0 + - "libzlib >=1.2.13,<1.3.0a0" + - "ncurses >=6.4,<7.0a0" + - "readline >=8.2,<9.0a0" + license: Unlicense + md5: c98aa8eb8f02260610c5bb981027ba5d + name: sqlite + sha256: b034405d93e7153f777d52c18fe26120356c568e4ca85626712d633d939a8923 + size: 803166 + subdir: osx-arm64 + timestamp: 1700863604745 + version: 3.44.2 + fn: sqlite-3.44.2-hf2abe2d_0.conda + url: "https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.44.2-hf2abe2d_0.conda" + channel: "https://conda.anaconda.org/conda-forge/" + - build: h091b4b1_0 build_number: 0 + depends: + - "libzlib >=1.2.13,<1.3.0a0" + license: Unlicense + md5: d7e1af696cfadec251a0abdd7b79ed77 + name: libsqlite + sha256: f0dc2fe69eddb4bab72ff6bb0da51d689294f466ee1b01e80ced1e7878a21aa5 + size: 815254 + subdir: osx-arm64 + timestamp: 1700863572318 + version: 3.44.2 + fn: libsqlite-3.44.2-h091b4b1_0.conda + url: "https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.44.2-h091b4b1_0.conda" + channel: "https://conda.anaconda.org/conda-forge/" + - build: h0d3ecfb_1 + build_number: 1 constrains: - pyopenssl >=22.1 depends: - ca-certificates license: Apache-2.0 license_family: Apache - md5: 5a89552fececf4cd99628318ccbb67a3 + md5: 47d16d26100f19ca495882882b7bc93b name: openssl - sha256: 3c715b1d4940c7ad6065935db18924b85a54048dde066f963cfc250340639457 - size: 2147225 + sha256: a53e1c6c058b621fd1d13cca6f9cccd534d2b3f4b4ac789fe26f7902031d6c41 + size: 2856233 subdir: osx-arm64 - timestamp: 1698164947105 - version: 3.1.4 - fn: openssl-3.1.4-h0d3ecfb_0.conda - url: "https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.1.4-h0d3ecfb_0.conda" + timestamp: 1701162541844 + version: 3.2.0 + fn: openssl-3.2.0-h0d3ecfb_1.conda + url: "https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.2.0-h0d3ecfb_1.conda" channel: "https://conda.anaconda.org/conda-forge/" - build: hf0a4a13_0 build_number: 0 @@ -332,16 +335,16 @@ finalized_dependencies: - python >=3.7 license: MIT license_family: MIT - md5: 3fc026b9c87d091c4b34a6c997324ae8 + md5: 1cdea58981c5cbc17b51973bcaddcea7 name: wheel noarch: python - sha256: 84c3b57fba778add2bd47b7cc70e86f746d2c55549ffd2ccb6f3d6bf7c94d21d - size: 57901 + sha256: 80be0ccc815ce22f80c141013302839b0ed938a2edb50b846cf48d8a8c1cfa01 + size: 57553 subdir: noarch - timestamp: 1698670970223 - version: 0.41.3 - fn: wheel-0.41.3-pyhd8ed1ab_0.conda - url: "https://conda.anaconda.org/conda-forge/noarch/wheel-0.41.3-pyhd8ed1ab_0.conda" + timestamp: 1701013309664 + version: 0.42.0 + fn: wheel-0.42.0-pyhd8ed1ab_0.conda + url: "https://conda.anaconda.org/conda-forge/noarch/wheel-0.42.0-pyhd8ed1ab_0.conda" channel: "https://conda.anaconda.org/conda-forge/" - build: pyhd8ed1ab_0 build_number: 0 diff --git a/src/source/copy_dir.rs b/src/source/copy_dir.rs index 703a4718b..3258a5cb9 100644 --- a/src/source/copy_dir.rs +++ b/src/source/copy_dir.rs @@ -75,6 +75,7 @@ impl<'a> CopyDir<'a> { self } + #[allow(unused)] pub fn with_include_globs(mut self, includes: I) -> Self where I: IntoIterator, @@ -89,6 +90,7 @@ impl<'a> CopyDir<'a> { self } + #[allow(unused)] pub fn with_exclude_globs(mut self, excludes: I) -> Self where I: IntoIterator, @@ -146,7 +148,7 @@ impl<'a> CopyDir<'a> { let folders = Arc::new(folders.into_iter().map(PathBuf::from).collect::>()); let mut result = CopyDirResult { - copied_pathes: Vec::with_capacity(0), // do not allocate as we overwrite this anyways + copied_paths: Vec::with_capacity(0), // do not allocate as we overwrite this anyways include_globs: make_glob_match_map(globs)?, exclude_globs: make_glob_match_map(self.exclude_globs)?, }; @@ -262,20 +264,20 @@ impl<'a> CopyDir<'a> { .filter_map(|res| res.transpose()) .collect::, SourceError>>()?; - result.copied_pathes = copied_pathes; + result.copied_paths = copied_pathes; Ok(result) } } pub(crate) struct CopyDirResult<'a> { - copied_pathes: Vec, + copied_paths: Vec, include_globs: HashMap, Match>, exclude_globs: HashMap, Match>, } impl<'a> CopyDirResult<'a> { - pub fn copied_pathes(&self) -> &[PathBuf] { - &self.copied_pathes + pub fn copied_paths(&self) -> &[PathBuf] { + &self.copied_paths } pub fn include_globs(&self) -> &HashMap, Match> { @@ -402,8 +404,8 @@ mod test { .run() .unwrap(); - assert_eq!(copy_dir.copied_pathes().len(), 1); - assert_eq!(copy_dir.copied_pathes()[0], dest_dir_2.join("test.txt")); + assert_eq!(copy_dir.copied_paths().len(), 1); + assert_eq!(copy_dir.copied_paths()[0], dest_dir_2.join("test.txt")); let dest_dir_3 = tmp_dir_path.as_path().join("test_copy_dir_dest_3"); // ignore all txt files @@ -413,13 +415,13 @@ mod test { .run() .unwrap(); - assert_eq!(copy_dir.copied_pathes().len(), 2); + assert_eq!(copy_dir.copied_paths().len(), 2); let expected = [ dest_dir_3.join("test_dir/test.md"), dest_dir_3.join("test_dir/test_dir2"), ]; let expected = expected.iter().collect::>(); - let result = copy_dir.copied_pathes().iter().collect::>(); + let result = copy_dir.copied_paths().iter().collect::>(); assert_eq!(result, expected); } @@ -439,7 +441,7 @@ mod test { .use_gitignore(false) .run() .unwrap(); - assert_eq!(copy_dir.copied_pathes().len(), 2); + assert_eq!(copy_dir.copied_paths().len(), 2); fs_extra::dir::create_all(&dest_dir, true).unwrap(); let copy_dir = super::CopyDir::new(tmp_dir.path(), dest_dir.path()) @@ -448,9 +450,9 @@ mod test { .use_gitignore(false) .run() .unwrap(); - assert_eq!(copy_dir.copied_pathes().len(), 1); + assert_eq!(copy_dir.copied_paths().len(), 1); assert_eq!( - copy_dir.copied_pathes()[0], + copy_dir.copied_paths()[0], dest_dir.path().join("test_copy_dir/test_1.txt") ); @@ -460,9 +462,9 @@ mod test { .use_gitignore(false) .run() .unwrap(); - assert_eq!(copy_dir.copied_pathes().len(), 1); + assert_eq!(copy_dir.copied_paths().len(), 1); assert_eq!( - copy_dir.copied_pathes()[0], + copy_dir.copied_paths()[0], dest_dir.path().join("test_copy_dir/test_1.txt") ); } @@ -498,7 +500,7 @@ mod test { .use_gitignore(false) .run() .unwrap(); - assert_eq!(copy_dir.copied_pathes().len(), 3); + assert_eq!(copy_dir.copied_paths().len(), 3); let broken_symlink_dest = dest_dir.path().join("broken_symlink"); assert_eq!( diff --git a/test-data/recipes/flask/recipe.yaml b/test-data/recipes/flask/recipe.yaml index ecc7776e6..6017bbd54 100644 --- a/test-data/recipes/flask/recipe.yaml +++ b/test-data/recipes/flask/recipe.yaml @@ -35,15 +35,17 @@ requirements: - importlib-metadata >=3.6.0 - blinker >=1.6.2 -test: - requires: - - pip - imports: - - flask - - flask.json - commands: - - flask --help - - pip check +tests: + - python: + imports: + - flask + - flask.json + - requirements: + run: + - pip + script: + - flask --help + - pip check about: homepage: https://palletsprojects.com/p/flask diff --git a/test-data/recipes/package-content-tests/llama-recipe.yaml b/test-data/recipes/package-content-tests/llama-recipe.yaml index 910fa8996..2386ee5f8 100644 --- a/test-data/recipes/package-content-tests/llama-recipe.yaml +++ b/test-data/recipes/package-content-tests/llama-recipe.yaml @@ -26,11 +26,11 @@ requirements: - if: win then: ninja -test: - package_contents: - bins: - - main - - quantize +tests: + - package_contents: + bin: + - main + - quantize about: homepage: https://github.com/ggerganov/llama.cpp diff --git a/test-data/recipes/package-content-tests/recipe-test-fail.yaml b/test-data/recipes/package-content-tests/recipe-test-fail.yaml index c0eb83657..1d98d9d3a 100644 --- a/test-data/recipes/package-content-tests/recipe-test-fail.yaml +++ b/test-data/recipes/package-content-tests/recipe-test-fail.yaml @@ -10,8 +10,8 @@ build: else: - echo "Hello World" > %PREFIX%\test-execution.txt -test: - package_contents: - files: - - "**/*.txt" - - "**/*.php" +tests: + - package_contents: + files: + - "**/*.txt" + - "**/*.php" diff --git a/test-data/recipes/package-content-tests/recipe-test-succeed.yaml b/test-data/recipes/package-content-tests/recipe-test-succeed.yaml index 235719048..c20a958c8 100644 --- a/test-data/recipes/package-content-tests/recipe-test-succeed.yaml +++ b/test-data/recipes/package-content-tests/recipe-test-succeed.yaml @@ -12,9 +12,9 @@ build: - echo "Hello World" > %PREFIX%\test-execution.txt - mkdir %PREFIX%\Library\bin && echo "Hello World" > %PREFIX%\Library\bin\rust.exe -test: - package_contents: - files: - - "**/*.txt" - bins: - - rust +tests: + - package_contents: + files: + - "**/*.txt" + bin: + - rust diff --git a/test-data/recipes/package-content-tests/rich-recipe.yaml b/test-data/recipes/package-content-tests/rich-recipe.yaml index 7eb10de28..e7641d60f 100644 --- a/test-data/recipes/package-content-tests/rich-recipe.yaml +++ b/test-data/recipes/package-content-tests/rich-recipe.yaml @@ -29,16 +29,18 @@ requirements: - python 3.10 - typing_extensions >=4.0.0,<5.0.0 -test: - imports: - - rich - commands: +tests: + - python: + imports: + - rich + - script: - pip check - requires: - - pip - package_contents: - site_packages: - - rich + requirements: + run: + - pip + - package_contents: + site_packages: + - rich about: homepage: https://github.com/Textualize/rich diff --git a/test-data/recipes/tar-source/recipe.yaml b/test-data/recipes/tar-source/recipe.yaml index a301dda4c..f3519e368 100644 --- a/test-data/recipes/tar-source/recipe.yaml +++ b/test-data/recipes/tar-source/recipe.yaml @@ -18,4 +18,4 @@ build: then: - test -f jna-5.8.0.jar else: - - if not exist jna-5.8.0.jar exit 1 \ No newline at end of file + - if not exist jna-5.8.0.jar exit 1 diff --git a/test-data/recipes/test-execution/recipe-test-fail.yaml b/test-data/recipes/test-execution/recipe-test-fail.yaml index 2ef4005a1..46a1928dd 100644 --- a/test-data/recipes/test-execution/recipe-test-fail.yaml +++ b/test-data/recipes/test-execution/recipe-test-fail.yaml @@ -10,8 +10,8 @@ build: else: - echo "Hello World" > %PREFIX%\test-execution.txt -test: - commands: +tests: + - script: - if: unix then: - test -f $PREFIX/fail.txt diff --git a/test-data/recipes/test-execution/recipe-test-succeed.yaml b/test-data/recipes/test-execution/recipe-test-succeed.yaml index b791b83c6..99b0523a6 100644 --- a/test-data/recipes/test-execution/recipe-test-succeed.yaml +++ b/test-data/recipes/test-execution/recipe-test-succeed.yaml @@ -10,8 +10,8 @@ build: else: - echo "Hello World" > %PREFIX%\test-execution.txt -test: - commands: +tests: + - script: - if: unix then: - test -f $PREFIX/test-execution.txt diff --git a/test-data/recipes/test-sources/recipe.yaml b/test-data/recipes/test-sources/recipe.yaml index 0172f54b7..d3a611927 100644 --- a/test-data/recipes/test-sources/recipe.yaml +++ b/test-data/recipes/test-sources/recipe.yaml @@ -25,16 +25,17 @@ build: - test -f ./am-i-renamed.txt - test -f ./test.avi -test: - source_files: - - test.avi - files: - - test-file.txt - - test-folder/ - commands: - - echo "test" - - test -f ./test.avi - - test -f ./test-file.txt - - test -d ./test-folder - - test -f ./test-folder/test-file-2.txt - - test -f ./test-folder/test-file-3.txt +tests: + - script: + - echo "test" + - test -f ./test.avi + - test -f ./test-file.txt + - test -d ./test-folder + - test -f ./test-folder/test-file-2.txt + - test -f ./test-folder/test-file-3.txt + files: + source: + - test.avi + recipe: + - test-file.txt + - test-folder/ diff --git a/test-data/rendered_recipes/rich_recipe.yaml b/test-data/rendered_recipes/rich_recipe.yaml index 57cec86c6..b6e2cf198 100644 --- a/test-data/rendered_recipes/rich_recipe.yaml +++ b/test-data/rendered_recipes/rich_recipe.yaml @@ -21,13 +21,16 @@ recipe: - pygments >=2.13.0,<3.0.0 - python ==3.10 - typing_extensions >=4.0.0,<5.0.0 - test: + tests: + - test_type: python imports: - rich - commands: + - test_type: command + script: - pip check - requires: - - pip + requirements: + run: + - pip about: homepage: https://github.com/Textualize/rich repository: https://github.com/Textualize/rich @@ -53,13 +56,13 @@ build_configuration: hash_input: '{"target_platform": "noarch"}' hash_prefix: py directories: - host_prefix: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1700573439/host_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold - build_prefix: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1700573439/build_env - work_dir: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1700573439/work - build_dir: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1700573439 + host_prefix: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1702492812/host_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold + build_prefix: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1702492812/build_env + work_dir: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1702492812/work + build_dir: /Users/wolfv/Programs/rattler-build/output/bld/rattler-build_rich_1702492812 channels: - conda-forge - timestamp: 2023-11-21T13:30:39.246259Z + timestamp: 2023-12-13T18:40:12.845800Z subpackages: rich: name: rich @@ -226,39 +229,6 @@ finalized_dependencies: fn: tzdata-2023c-h71feb2d_0.conda url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda channel: https://conda.anaconda.org/conda-forge/ - - build: hf2abe2d_0 - build_number: 0 - depends: - - libsqlite 3.44.0 h091b4b1_0 - - libzlib >=1.2.13,<1.3.0a0 - - ncurses >=6.4,<7.0a0 - - readline >=8.2,<9.0a0 - license: Unlicense - md5: 0080e3f5d7d13d3b1e244ed24642ca9e - name: sqlite - sha256: 8263043d2a5762a5bbbb4ceee28382d97e70182fff8d45371b65fedda0b709ee - size: 800748 - subdir: osx-arm64 - timestamp: 1698855055771 - version: 3.44.0 - fn: sqlite-3.44.0-hf2abe2d_0.conda - url: https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.44.0-hf2abe2d_0.conda - channel: https://conda.anaconda.org/conda-forge/ - - build: h091b4b1_0 - build_number: 0 - depends: - - libzlib >=1.2.13,<1.3.0a0 - license: Unlicense - md5: 28eb31a5b4e704353ed575758e2fcf1d - name: libsqlite - sha256: 38e98953b572e2871f2b318fa7fe8d9997b0927970916c2d09402273b60ff832 - size: 815079 - subdir: osx-arm64 - timestamp: 1698855024189 - version: 3.44.0 - fn: libsqlite-3.44.0-h091b4b1_0.conda - url: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.44.0-h091b4b1_0.conda - channel: https://conda.anaconda.org/conda-forge/ - build: pyhd8ed1ab_0 build_number: 0 depends: @@ -276,23 +246,56 @@ finalized_dependencies: fn: poetry-core-1.8.1-pyhd8ed1ab_0.conda url: https://conda.anaconda.org/conda-forge/noarch/poetry-core-1.8.1-pyhd8ed1ab_0.conda channel: https://conda.anaconda.org/conda-forge/ - - build: h0d3ecfb_0 + - build: hf2abe2d_0 build_number: 0 + depends: + - libsqlite 3.44.2 h091b4b1_0 + - libzlib >=1.2.13,<1.3.0a0 + - ncurses >=6.4,<7.0a0 + - readline >=8.2,<9.0a0 + license: Unlicense + md5: c98aa8eb8f02260610c5bb981027ba5d + name: sqlite + sha256: b034405d93e7153f777d52c18fe26120356c568e4ca85626712d633d939a8923 + size: 803166 + subdir: osx-arm64 + timestamp: 1700863604745 + version: 3.44.2 + fn: sqlite-3.44.2-hf2abe2d_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.44.2-hf2abe2d_0.conda + channel: https://conda.anaconda.org/conda-forge/ + - build: h091b4b1_0 + build_number: 0 + depends: + - libzlib >=1.2.13,<1.3.0a0 + license: Unlicense + md5: d7e1af696cfadec251a0abdd7b79ed77 + name: libsqlite + sha256: f0dc2fe69eddb4bab72ff6bb0da51d689294f466ee1b01e80ced1e7878a21aa5 + size: 815254 + subdir: osx-arm64 + timestamp: 1700863572318 + version: 3.44.2 + fn: libsqlite-3.44.2-h091b4b1_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.44.2-h091b4b1_0.conda + channel: https://conda.anaconda.org/conda-forge/ + - build: h0d3ecfb_1 + build_number: 1 constrains: - pyopenssl >=22.1 depends: - ca-certificates license: Apache-2.0 license_family: Apache - md5: 5a89552fececf4cd99628318ccbb67a3 + md5: 47d16d26100f19ca495882882b7bc93b name: openssl - sha256: 3c715b1d4940c7ad6065935db18924b85a54048dde066f963cfc250340639457 - size: 2147225 + sha256: a53e1c6c058b621fd1d13cca6f9cccd534d2b3f4b4ac789fe26f7902031d6c41 + size: 2856233 subdir: osx-arm64 - timestamp: 1698164947105 - version: 3.1.4 - fn: openssl-3.1.4-h0d3ecfb_0.conda - url: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.1.4-h0d3ecfb_0.conda + timestamp: 1701162541844 + version: 3.2.0 + fn: openssl-3.2.0-h0d3ecfb_1.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.2.0-h0d3ecfb_1.conda channel: https://conda.anaconda.org/conda-forge/ - build: hf0a4a13_0 build_number: 0 @@ -333,16 +336,16 @@ finalized_dependencies: - python >=3.7 license: MIT license_family: MIT - md5: 3fc026b9c87d091c4b34a6c997324ae8 + md5: 1cdea58981c5cbc17b51973bcaddcea7 name: wheel noarch: python - sha256: 84c3b57fba778add2bd47b7cc70e86f746d2c55549ffd2ccb6f3d6bf7c94d21d - size: 57901 + sha256: 80be0ccc815ce22f80c141013302839b0ed938a2edb50b846cf48d8a8c1cfa01 + size: 57553 subdir: noarch - timestamp: 1698670970223 - version: 0.41.3 - fn: wheel-0.41.3-pyhd8ed1ab_0.conda - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.41.3-pyhd8ed1ab_0.conda + timestamp: 1701013309664 + version: 0.42.0 + fn: wheel-0.42.0-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.42.0-pyhd8ed1ab_0.conda channel: https://conda.anaconda.org/conda-forge/ - build: pyhd8ed1ab_0 build_number: 0