Skip to main content

rez_next_package/
lib.rs

1//! # Rez Core Package
2//!
3//! Package system implementation for Rez Core.
4//!
5//! This crate provides:
6//! - Package definition and metadata
7//! - Package variants and requirements
8//! - Package serialization/deserialization
9//! - Package validation
10//! - Package management operations
11
12pub mod developer_package; // Developer package support (rez.developer_package)
13pub mod filter;
14pub mod package;
15pub mod package_cache;
16pub mod package_copy; // Package copy operations (rez.package_copy)
17pub mod package_move; // Package move operations (rez.package_move)
18pub mod package_order; // Package ordering strategies
19pub mod package_py_utils; // Utility functions for package.py files
20pub mod package_remove; // Package remove operations (rez.package_remove)
21pub mod python_ast_parser;
22pub mod serialization; // Always available for CLI usage
23
24pub use developer_package::*; // Developer package support
25pub use filter::*;
26pub use package::*;
27pub use package_cache::*;
28pub use package_copy::*; // Package copy operations
29pub use package_move::*; // Package move operations
30pub use package_order::*; // Package ordering strategies
31pub use package_py_utils::*;
32pub use package_remove::*; // Package remove operations
33pub use python_ast_parser::*;
34pub use serialization::{PackageFormat, PackageSerializer};
35
36// Always export requirement types for CLI usage
37pub mod requirement;
38pub use requirement::{Requirement, VersionConstraint};
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use rez_next_version::Version;
44    use tempfile::TempDir;
45
46    #[test]
47    fn test_package_creation() {
48        let mut package = Package::new("test_package".to_string());
49        package.version = Some(Version::parse("1.0.0").unwrap());
50        package.description = Some("Test package description".to_string());
51        package.authors = vec!["Test Author".to_string()];
52
53        assert_eq!(package.name, "test_package");
54        assert!(package.version.is_some());
55        assert!(package.description.is_some());
56        assert_eq!(package.authors.len(), 1);
57    }
58
59    // ── Phase 88: Package variants tests ─────────────────────────────────────
60
61    #[test]
62    fn test_package_variants_empty() {
63        let pkg = Package::new("mypkg".to_string());
64        assert!(
65            pkg.variants.is_empty(),
66            "New package should have no variants"
67        );
68    }
69
70    #[test]
71    fn test_package_variants_single() {
72        let mut pkg = Package::new("mypkg".to_string());
73        pkg.variants.push(vec!["python-3.9".to_string()]);
74        assert_eq!(pkg.variants.len(), 1);
75        assert_eq!(pkg.variants[0], vec!["python-3.9"]);
76    }
77
78    #[test]
79    fn test_package_variants_multiple() {
80        let mut pkg = Package::new("mypkg".to_string());
81        pkg.variants
82            .push(vec!["python-3.9".to_string(), "platform-linux".to_string()]);
83        pkg.variants.push(vec![
84            "python-3.10".to_string(),
85            "platform-linux".to_string(),
86        ]);
87        pkg.variants.push(vec![
88            "python-3.11".to_string(),
89            "platform-windows".to_string(),
90        ]);
91        assert_eq!(pkg.variants.len(), 3);
92    }
93
94    #[test]
95    fn test_package_variants_parse_from_python() {
96        let content = r#"
97name = 'mypkg'
98version = '1.0.0'
99variants = [
100    ['python-3.9'],
101    ['python-3.10'],
102]
103"#;
104        let tmp = TempDir::new().unwrap();
105        let path = tmp.path().join("package.py");
106        std::fs::write(&path, content).unwrap();
107
108        let pkg = serialization::PackageSerializer::load_from_file(&path).unwrap();
109        assert_eq!(pkg.name, "mypkg");
110        assert_eq!(pkg.variants.len(), 2);
111        assert_eq!(pkg.variants[0], vec!["python-3.9"]);
112        assert_eq!(pkg.variants[1], vec!["python-3.10"]);
113    }
114
115    #[test]
116    fn test_package_requires_parsed() {
117        let content = r#"
118name = 'withreqs'
119version = '2.0.0'
120requires = ['python-3', 'maya-2023']
121"#;
122        let tmp = TempDir::new().unwrap();
123        let path = tmp.path().join("package.py");
124        std::fs::write(&path, content).unwrap();
125
126        let pkg = serialization::PackageSerializer::load_from_file(&path).unwrap();
127        assert!(
128            !pkg.requires.is_empty(),
129            "requires should be parsed: {:?}",
130            pkg.requires
131        );
132        assert!(
133            pkg.requires.iter().any(|r| r.contains("python")),
134            "python requirement should be present: {:?}",
135            pkg.requires
136        );
137    }
138
139    #[test]
140    fn test_package_tools_field() {
141        let mut pkg = Package::new("toolpkg".to_string());
142        pkg.tools = vec!["mytool".to_string(), "another-tool".to_string()];
143        assert_eq!(pkg.tools.len(), 2);
144        assert!(pkg.tools.contains(&"mytool".to_string()));
145    }
146
147    #[test]
148    fn test_package_commands_field() {
149        let mut pkg = Package::new("cmdpkg".to_string());
150        pkg.commands = Some("env.setenv('MY_VAR', '1')".to_string());
151        assert!(pkg.commands.is_some());
152    }
153
154    #[test]
155    fn test_package_requirement_satisfied_by() {
156        use super::package::PackageRequirement;
157        use rez_next_version::Version;
158
159        // In rez version semantics: "3.9" > "3.9.0" (shorter = greater)
160        // So >=3.9 means "greater than or equal to 3.9 (the epoch)"
161        // 3.11 > 3.10 > 3.9 > 3.9.0 > 3.8
162        let req_ge = PackageRequirement::with_version("python".to_string(), ">=3.9".to_string());
163        // 3.11 satisfies >=3.9 (3.11 > 3.9)
164        assert!(req_ge.satisfied_by(&Version::parse("3.11").unwrap()));
165        // 3.8 does not satisfy >=3.9
166        assert!(!req_ge.satisfied_by(&Version::parse("3.8").unwrap()));
167        // 3.9 exactly satisfies >=3.9
168        assert!(req_ge.satisfied_by(&Version::parse("3.9").unwrap()));
169    }
170
171    #[test]
172    fn test_package_requirement_ne_constraint() {
173        use super::package::PackageRequirement;
174        use rez_next_version::Version;
175
176        let req_ne = PackageRequirement::with_version("lib".to_string(), "!=1.5.0".to_string());
177        assert!(req_ne.satisfied_by(&Version::parse("1.4.0").unwrap()));
178        assert!(req_ne.satisfied_by(&Version::parse("1.6.0").unwrap()));
179        assert!(!req_ne.satisfied_by(&Version::parse("1.5.0").unwrap()));
180    }
181
182    #[test]
183    fn test_package_variant_requirements_structure() {
184        // Each variant is a list of requirements that the variant needs
185        let mut pkg = Package::new("maya_tools".to_string());
186        pkg.version = Some(Version::parse("1.0.0").unwrap());
187        // Variant 0: requires python-3.9 AND maya-2023
188        pkg.variants
189            .push(vec!["python-3.9".to_string(), "maya-2023".to_string()]);
190        // Variant 1: requires python-3.10 AND maya-2024
191        pkg.variants
192            .push(vec!["python-3.10".to_string(), "maya-2024".to_string()]);
193
194        assert_eq!(pkg.variants.len(), 2);
195        assert!(pkg.variants[0].iter().any(|r| r.contains("python-3.9")));
196        assert!(pkg.variants[1].iter().any(|r| r.contains("maya-2024")));
197    }
198}