rez_next_package/
package.rs

1//! Package implementation
2
3#[cfg(feature = "python-bindings")]
4use pyo3::prelude::*;
5use rez_next_common::RezCoreError;
6use rez_next_version::Version;
7
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Simple package requirement for basic functionality
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct PackageRequirement {
14    /// Package name
15    pub name: String,
16    /// Version requirement (optional)
17    pub version_spec: Option<String>,
18    /// Whether this is a weak requirement
19    pub weak: bool,
20}
21
22impl PackageRequirement {
23    /// Create a new package requirement
24    pub fn new(name: String) -> Self {
25        Self {
26            name,
27            version_spec: None,
28            weak: false,
29        }
30    }
31
32    /// Create a package requirement with version specification
33    pub fn with_version(name: String, version_spec: String) -> Self {
34        Self {
35            name,
36            version_spec: Some(version_spec),
37            weak: false,
38        }
39    }
40
41    /// Parse a requirement string like "python-3.9" or "maya>=2023"
42    pub fn parse(requirement_str: &str) -> Result<Self, RezCoreError> {
43        // Simple parsing - can be enhanced later
44        if let Some(dash_pos) = requirement_str.rfind('-') {
45            let name = requirement_str[..dash_pos].to_string();
46            let version = requirement_str[dash_pos + 1..].to_string();
47            Ok(Self::with_version(name, version))
48        } else {
49            Ok(Self::new(requirement_str.to_string()))
50        }
51    }
52
53    /// Get the package name
54    pub fn name(&self) -> &str {
55        &self.name
56    }
57
58    /// Get the version specification
59    pub fn version_spec(&self) -> Option<&str> {
60        self.version_spec.as_deref()
61    }
62
63    /// Convert to string representation
64    pub fn to_string(&self) -> String {
65        if let Some(ref version) = self.version_spec {
66            format!("{}-{}", self.name, version)
67        } else {
68            self.name.clone()
69        }
70    }
71
72    /// Get requirement string (for compatibility)
73    pub fn requirement_string(&self) -> String {
74        self.to_string()
75    }
76
77    /// Check if this requirement is satisfied by a version (simplified)
78    pub fn satisfied_by(&self, version: &Version) -> bool {
79        // Simplified implementation - can be enhanced later
80        if let Some(ref version_spec) = self.version_spec {
81            // For now, just check if the version string matches
82            version.as_str() == version_spec
83        } else {
84            // No version constraint, always satisfied
85            true
86        }
87    }
88}
89
90/// High-performance package representation compatible with rez
91#[cfg_attr(feature = "python-bindings", pyclass)]
92#[derive(Debug)]
93pub struct Package {
94    /// Package name
95    #[cfg(feature = "python-bindings")]
96    #[pyo3(get)]
97    pub name: String,
98    /// Package name (non-Python version)
99    #[cfg(not(feature = "python-bindings"))]
100    pub name: String,
101
102    /// Package version
103    #[cfg(feature = "python-bindings")]
104    #[pyo3(get)]
105    pub version: Option<Version>,
106    /// Package version (non-Python version)
107    #[cfg(not(feature = "python-bindings"))]
108    pub version: Option<Version>,
109
110    /// Package description
111    #[cfg(feature = "python-bindings")]
112    #[pyo3(get)]
113    pub description: Option<String>,
114    /// Package description (non-Python version)
115    #[cfg(not(feature = "python-bindings"))]
116    pub description: Option<String>,
117
118    /// Package authors
119    #[cfg(feature = "python-bindings")]
120    #[pyo3(get)]
121    pub authors: Vec<String>,
122    /// Package authors (non-Python version)
123    #[cfg(not(feature = "python-bindings"))]
124    pub authors: Vec<String>,
125
126    /// Package requirements
127    pub requires: Vec<String>,
128
129    /// Build requirements
130    pub build_requires: Vec<String>,
131
132    /// Private build requirements
133    pub private_build_requires: Vec<String>,
134
135    /// Package variants
136    pub variants: Vec<Vec<String>>,
137
138    /// Package tools
139    pub tools: Vec<String>,
140
141    /// Package commands
142    pub commands: Option<String>,
143
144    /// Build command for custom builds
145    pub build_command: Option<String>,
146
147    /// Build system type
148    pub build_system: Option<String>,
149
150    /// Pre commands (executed before main commands)
151    pub pre_commands: Option<String>,
152
153    /// Post commands (executed after main commands)
154    pub post_commands: Option<String>,
155
156    /// Pre test commands (executed before tests)
157    pub pre_test_commands: Option<String>,
158
159    /// Pre build commands (executed before build)
160    pub pre_build_commands: Option<String>,
161
162    /// Package tests
163    pub tests: HashMap<String, String>,
164
165    /// Required rez version
166    pub requires_rez_version: Option<String>,
167
168    /// Package UUID
169    #[cfg(feature = "python-bindings")]
170    #[pyo3(get)]
171    pub uuid: Option<String>,
172    /// Package UUID (non-Python version)
173    #[cfg(not(feature = "python-bindings"))]
174    pub uuid: Option<String>,
175
176    /// Package config
177    #[cfg(feature = "python-bindings")]
178    pub config: HashMap<String, PyObject>,
179
180    /// Package config (non-Python version)
181    #[cfg(not(feature = "python-bindings"))]
182    pub config: HashMap<String, String>,
183
184    /// Package help
185    pub help: Option<String>,
186
187    /// Package relocatable flag
188    pub relocatable: Option<bool>,
189
190    /// Package cachable flag
191    pub cachable: Option<bool>,
192
193    /// Package timestamp
194    pub timestamp: Option<i64>,
195
196    /// Package revision
197    pub revision: Option<String>,
198
199    /// Package changelog
200    pub changelog: Option<String>,
201
202    /// Package release message
203    pub release_message: Option<String>,
204
205    /// Previous version
206    pub previous_version: Option<Version>,
207
208    /// Previous revision
209    pub previous_revision: Option<String>,
210
211    /// VCS type
212    pub vcs: Option<String>,
213
214    /// Package format version
215    pub format_version: Option<i32>,
216
217    /// Package base
218    pub base: Option<String>,
219
220    /// Package has plugins
221    pub has_plugins: Option<bool>,
222
223    /// Plugin for packages
224    pub plugin_for: Vec<String>,
225
226    /// Package hashed variants
227    pub hashed_variants: Option<bool>,
228
229    /// Package preprocess function
230    pub preprocess: Option<String>,
231}
232
233#[cfg(feature = "python-bindings")]
234impl Clone for Package {
235    fn clone(&self) -> Self {
236        Python::with_gil(|py| {
237            let cloned_config: HashMap<String, PyObject> = self
238                .config
239                .iter()
240                .map(|(k, v)| (k.clone(), v.clone_ref(py)))
241                .collect();
242
243            Self {
244                name: self.name.clone(),
245                version: self.version.clone(),
246                description: self.description.clone(),
247                authors: self.authors.clone(),
248                requires: self.requires.clone(),
249                build_requires: self.build_requires.clone(),
250                private_build_requires: self.private_build_requires.clone(),
251                variants: self.variants.clone(),
252                tools: self.tools.clone(),
253                commands: self.commands.clone(),
254                build_command: self.build_command.clone(),
255                build_system: self.build_system.clone(),
256                pre_commands: self.pre_commands.clone(),
257                post_commands: self.post_commands.clone(),
258                pre_test_commands: self.pre_test_commands.clone(),
259                pre_build_commands: self.pre_build_commands.clone(),
260                tests: self.tests.clone(),
261                requires_rez_version: self.requires_rez_version.clone(),
262                uuid: self.uuid.clone(),
263                config: cloned_config,
264                help: self.help.clone(),
265                relocatable: self.relocatable,
266                cachable: self.cachable,
267                timestamp: self.timestamp,
268                revision: self.revision.clone(),
269                changelog: self.changelog.clone(),
270                release_message: self.release_message.clone(),
271                previous_version: self.previous_version.clone(),
272                previous_revision: self.previous_revision.clone(),
273                vcs: self.vcs.clone(),
274                format_version: self.format_version,
275                base: self.base.clone(),
276                has_plugins: self.has_plugins,
277                plugin_for: self.plugin_for.clone(),
278                hashed_variants: self.hashed_variants,
279                preprocess: self.preprocess.clone(),
280            }
281        })
282    }
283}
284
285#[cfg(not(feature = "python-bindings"))]
286impl Clone for Package {
287    fn clone(&self) -> Self {
288        Self {
289            name: self.name.clone(),
290            version: self.version.clone(),
291            description: self.description.clone(),
292            authors: self.authors.clone(),
293            requires: self.requires.clone(),
294            build_requires: self.build_requires.clone(),
295            private_build_requires: self.private_build_requires.clone(),
296            variants: self.variants.clone(),
297            tools: self.tools.clone(),
298            commands: self.commands.clone(),
299            build_command: self.build_command.clone(),
300            build_system: self.build_system.clone(),
301            pre_commands: self.pre_commands.clone(),
302            post_commands: self.post_commands.clone(),
303            pre_test_commands: self.pre_test_commands.clone(),
304            pre_build_commands: self.pre_build_commands.clone(),
305            tests: self.tests.clone(),
306            requires_rez_version: self.requires_rez_version.clone(),
307            uuid: self.uuid.clone(),
308            config: self.config.clone(),
309            help: self.help.clone(),
310            relocatable: self.relocatable,
311            cachable: self.cachable,
312            timestamp: self.timestamp,
313            revision: self.revision.clone(),
314            changelog: self.changelog.clone(),
315            release_message: self.release_message.clone(),
316            previous_version: self.previous_version.clone(),
317            previous_revision: self.previous_revision.clone(),
318            vcs: self.vcs.clone(),
319            format_version: self.format_version,
320            base: self.base.clone(),
321            has_plugins: self.has_plugins,
322            plugin_for: self.plugin_for.clone(),
323            hashed_variants: self.hashed_variants,
324            preprocess: self.preprocess.clone(),
325        }
326    }
327}
328
329impl Serialize for Package {
330    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
331    where
332        S: serde::Serializer,
333    {
334        use serde::ser::SerializeStruct;
335        let mut state = serializer.serialize_struct("Package", 24)?;
336        state.serialize_field("name", &self.name)?;
337        state.serialize_field("version", &self.version)?;
338        state.serialize_field("description", &self.description)?;
339        state.serialize_field("authors", &self.authors)?;
340        state.serialize_field("requires", &self.requires)?;
341        state.serialize_field("build_requires", &self.build_requires)?;
342        state.serialize_field("private_build_requires", &self.private_build_requires)?;
343        state.serialize_field("variants", &self.variants)?;
344        state.serialize_field("tools", &self.tools)?;
345        state.serialize_field("commands", &self.commands)?;
346        state.serialize_field("build_command", &self.build_command)?;
347        state.serialize_field("build_system", &self.build_system)?;
348        state.serialize_field("pre_commands", &self.pre_commands)?;
349        state.serialize_field("post_commands", &self.post_commands)?;
350        state.serialize_field("pre_test_commands", &self.pre_test_commands)?;
351        state.serialize_field("pre_build_commands", &self.pre_build_commands)?;
352        state.serialize_field("tests", &self.tests)?;
353        state.serialize_field("requires_rez_version", &self.requires_rez_version)?;
354        state.serialize_field("uuid", &self.uuid)?;
355        // Skip config field as PyObject cannot be serialized
356        state.serialize_field("help", &self.help)?;
357        state.serialize_field("relocatable", &self.relocatable)?;
358        state.serialize_field("cachable", &self.cachable)?;
359        state.serialize_field("timestamp", &self.timestamp)?;
360        state.serialize_field("revision", &self.revision)?;
361        state.serialize_field("changelog", &self.changelog)?;
362        state.serialize_field("release_message", &self.release_message)?;
363        state.serialize_field("previous_version", &self.previous_version)?;
364        state.serialize_field("previous_revision", &self.previous_revision)?;
365        state.serialize_field("vcs", &self.vcs)?;
366        state.serialize_field("format_version", &self.format_version)?;
367        state.serialize_field("base", &self.base)?;
368        state.serialize_field("has_plugins", &self.has_plugins)?;
369        state.serialize_field("plugin_for", &self.plugin_for)?;
370        state.serialize_field("hashed_variants", &self.hashed_variants)?;
371        state.serialize_field("preprocess", &self.preprocess)?;
372        state.end()
373    }
374}
375
376impl<'de> Deserialize<'de> for Package {
377    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
378    where
379        D: serde::Deserializer<'de>,
380    {
381        use serde::de::{self, MapAccess, Visitor};
382        use std::fmt;
383
384        #[derive(Deserialize)]
385        #[serde(field_identifier, rename_all = "snake_case")]
386        enum Field {
387            Name,
388            Version,
389            Description,
390            Authors,
391            Requires,
392            BuildRequires,
393            PrivateBuildRequires,
394            Variants,
395            Tools,
396            Commands,
397            BuildCommand,
398            BuildSystem,
399            PreCommands,
400            PostCommands,
401            PreTestCommands,
402            PreBuildCommands,
403            Tests,
404            RequiresRezVersion,
405            Uuid,
406            Help,
407            Relocatable,
408            Cachable,
409            Timestamp,
410            Revision,
411            Changelog,
412            ReleaseMessage,
413            PreviousVersion,
414            PreviousRevision,
415            Vcs,
416            FormatVersion,
417            Base,
418            HasPlugins,
419            PluginFor,
420            HashedVariants,
421            Preprocess,
422        }
423
424        struct PackageVisitor;
425
426        impl<'de> Visitor<'de> for PackageVisitor {
427            type Value = Package;
428
429            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
430                formatter.write_str("struct Package")
431            }
432
433            fn visit_map<V>(self, mut map: V) -> Result<Package, V::Error>
434            where
435                V: MapAccess<'de>,
436            {
437                let mut name = None;
438                let mut version = None;
439                let mut description = None;
440                let mut authors = None;
441                let mut requires = None;
442                let mut build_requires = None;
443                let mut private_build_requires = None;
444                let mut variants = None;
445                let mut tools = None;
446                let mut commands = None;
447                let mut build_command = None;
448                let mut build_system = None;
449                let mut pre_commands = None;
450                let mut post_commands = None;
451                let mut pre_test_commands = None;
452                let mut pre_build_commands = None;
453                let mut tests = None;
454                let mut requires_rez_version = None;
455                let mut uuid = None;
456                let mut help = None;
457                let mut relocatable = None;
458                let mut cachable = None;
459                let mut timestamp = None;
460                let mut revision = None;
461                let mut changelog = None;
462                let mut release_message = None;
463                let mut previous_version = None;
464                let mut previous_revision = None;
465                let mut vcs = None;
466                let mut format_version = None;
467                let mut base = None;
468                let mut has_plugins = None;
469                let mut plugin_for = None;
470                let mut hashed_variants = None;
471                let mut preprocess = None;
472
473                while let Some(key) = map.next_key()? {
474                    match key {
475                        Field::Name => {
476                            if name.is_some() {
477                                return Err(de::Error::duplicate_field("name"));
478                            }
479                            name = Some(map.next_value()?);
480                        }
481                        Field::Version => {
482                            if version.is_some() {
483                                return Err(de::Error::duplicate_field("version"));
484                            }
485                            version = Some(map.next_value()?);
486                        }
487                        Field::Description => {
488                            if description.is_some() {
489                                return Err(de::Error::duplicate_field("description"));
490                            }
491                            description = Some(map.next_value()?);
492                        }
493                        Field::Authors => {
494                            if authors.is_some() {
495                                return Err(de::Error::duplicate_field("authors"));
496                            }
497                            authors = Some(map.next_value()?);
498                        }
499                        Field::Requires => {
500                            if requires.is_some() {
501                                return Err(de::Error::duplicate_field("requires"));
502                            }
503                            requires = Some(map.next_value()?);
504                        }
505                        Field::BuildRequires => {
506                            if build_requires.is_some() {
507                                return Err(de::Error::duplicate_field("build_requires"));
508                            }
509                            build_requires = Some(map.next_value()?);
510                        }
511                        Field::PrivateBuildRequires => {
512                            if private_build_requires.is_some() {
513                                return Err(de::Error::duplicate_field("private_build_requires"));
514                            }
515                            private_build_requires = Some(map.next_value()?);
516                        }
517                        Field::Variants => {
518                            if variants.is_some() {
519                                return Err(de::Error::duplicate_field("variants"));
520                            }
521                            variants = Some(map.next_value()?);
522                        }
523                        Field::Tools => {
524                            if tools.is_some() {
525                                return Err(de::Error::duplicate_field("tools"));
526                            }
527                            tools = Some(map.next_value()?);
528                        }
529                        Field::Commands => {
530                            if commands.is_some() {
531                                return Err(de::Error::duplicate_field("commands"));
532                            }
533                            commands = Some(map.next_value()?);
534                        }
535                        Field::BuildCommand => {
536                            if build_command.is_some() {
537                                return Err(de::Error::duplicate_field("build_command"));
538                            }
539                            build_command = Some(map.next_value()?);
540                        }
541                        Field::BuildSystem => {
542                            if build_system.is_some() {
543                                return Err(de::Error::duplicate_field("build_system"));
544                            }
545                            build_system = Some(map.next_value()?);
546                        }
547                        Field::PreCommands => {
548                            if pre_commands.is_some() {
549                                return Err(de::Error::duplicate_field("pre_commands"));
550                            }
551                            pre_commands = Some(map.next_value()?);
552                        }
553                        Field::PostCommands => {
554                            if post_commands.is_some() {
555                                return Err(de::Error::duplicate_field("post_commands"));
556                            }
557                            post_commands = Some(map.next_value()?);
558                        }
559                        Field::PreTestCommands => {
560                            if pre_test_commands.is_some() {
561                                return Err(de::Error::duplicate_field("pre_test_commands"));
562                            }
563                            pre_test_commands = Some(map.next_value()?);
564                        }
565                        Field::PreBuildCommands => {
566                            if pre_build_commands.is_some() {
567                                return Err(de::Error::duplicate_field("pre_build_commands"));
568                            }
569                            pre_build_commands = Some(map.next_value()?);
570                        }
571                        Field::Tests => {
572                            if tests.is_some() {
573                                return Err(de::Error::duplicate_field("tests"));
574                            }
575                            tests = Some(map.next_value()?);
576                        }
577                        Field::RequiresRezVersion => {
578                            if requires_rez_version.is_some() {
579                                return Err(de::Error::duplicate_field("requires_rez_version"));
580                            }
581                            requires_rez_version = Some(map.next_value()?);
582                        }
583                        Field::Uuid => {
584                            if uuid.is_some() {
585                                return Err(de::Error::duplicate_field("uuid"));
586                            }
587                            uuid = Some(map.next_value()?);
588                        }
589                        Field::Help => {
590                            if help.is_some() {
591                                return Err(de::Error::duplicate_field("help"));
592                            }
593                            help = Some(map.next_value()?);
594                        }
595                        Field::Relocatable => {
596                            if relocatable.is_some() {
597                                return Err(de::Error::duplicate_field("relocatable"));
598                            }
599                            relocatable = Some(map.next_value()?);
600                        }
601                        Field::Cachable => {
602                            if cachable.is_some() {
603                                return Err(de::Error::duplicate_field("cachable"));
604                            }
605                            cachable = Some(map.next_value()?);
606                        }
607                        Field::Timestamp => {
608                            if timestamp.is_some() {
609                                return Err(de::Error::duplicate_field("timestamp"));
610                            }
611                            timestamp = Some(map.next_value()?);
612                        }
613                        Field::Revision => {
614                            if revision.is_some() {
615                                return Err(de::Error::duplicate_field("revision"));
616                            }
617                            revision = Some(map.next_value()?);
618                        }
619                        Field::Changelog => {
620                            if changelog.is_some() {
621                                return Err(de::Error::duplicate_field("changelog"));
622                            }
623                            changelog = Some(map.next_value()?);
624                        }
625                        Field::ReleaseMessage => {
626                            if release_message.is_some() {
627                                return Err(de::Error::duplicate_field("release_message"));
628                            }
629                            release_message = Some(map.next_value()?);
630                        }
631                        Field::PreviousVersion => {
632                            if previous_version.is_some() {
633                                return Err(de::Error::duplicate_field("previous_version"));
634                            }
635                            previous_version = Some(map.next_value()?);
636                        }
637                        Field::PreviousRevision => {
638                            if previous_revision.is_some() {
639                                return Err(de::Error::duplicate_field("previous_revision"));
640                            }
641                            previous_revision = Some(map.next_value()?);
642                        }
643                        Field::Vcs => {
644                            if vcs.is_some() {
645                                return Err(de::Error::duplicate_field("vcs"));
646                            }
647                            vcs = Some(map.next_value()?);
648                        }
649                        Field::FormatVersion => {
650                            if format_version.is_some() {
651                                return Err(de::Error::duplicate_field("format_version"));
652                            }
653                            format_version = Some(map.next_value()?);
654                        }
655                        Field::Base => {
656                            if base.is_some() {
657                                return Err(de::Error::duplicate_field("base"));
658                            }
659                            base = Some(map.next_value()?);
660                        }
661                        Field::HasPlugins => {
662                            if has_plugins.is_some() {
663                                return Err(de::Error::duplicate_field("has_plugins"));
664                            }
665                            has_plugins = Some(map.next_value()?);
666                        }
667                        Field::PluginFor => {
668                            if plugin_for.is_some() {
669                                return Err(de::Error::duplicate_field("plugin_for"));
670                            }
671                            plugin_for = Some(map.next_value()?);
672                        }
673                        Field::HashedVariants => {
674                            if hashed_variants.is_some() {
675                                return Err(de::Error::duplicate_field("hashed_variants"));
676                            }
677                            hashed_variants = Some(map.next_value()?);
678                        }
679                        Field::Preprocess => {
680                            if preprocess.is_some() {
681                                return Err(de::Error::duplicate_field("preprocess"));
682                            }
683                            preprocess = Some(map.next_value()?);
684                        }
685                    }
686                }
687
688                let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
689                Ok(Package {
690                    name,
691                    version: version.unwrap_or(None),
692                    description: description.unwrap_or(None),
693                    authors: authors.unwrap_or_default(),
694                    requires: requires.unwrap_or_default(),
695                    build_requires: build_requires.unwrap_or_default(),
696                    private_build_requires: private_build_requires.unwrap_or_default(),
697                    variants: variants.unwrap_or_default(),
698                    tools: tools.unwrap_or_default(),
699                    commands: commands.unwrap_or(None),
700                    build_command: build_command.unwrap_or(None),
701                    build_system: build_system.unwrap_or(None),
702                    pre_commands: pre_commands.unwrap_or(None),
703                    post_commands: post_commands.unwrap_or(None),
704                    pre_test_commands: pre_test_commands.unwrap_or(None),
705                    pre_build_commands: pre_build_commands.unwrap_or(None),
706                    tests: tests.unwrap_or_default(),
707                    requires_rez_version: requires_rez_version.unwrap_or(None),
708                    uuid: uuid.unwrap_or(None),
709                    config: HashMap::new(), // Cannot deserialize PyObject
710                    help: help.unwrap_or(None),
711                    relocatable: relocatable.unwrap_or(None),
712                    cachable: cachable.unwrap_or(None),
713                    timestamp: timestamp.unwrap_or(None),
714                    revision: revision.unwrap_or(None),
715                    changelog: changelog.unwrap_or(None),
716                    release_message: release_message.unwrap_or(None),
717                    previous_version: previous_version.unwrap_or(None),
718                    previous_revision: previous_revision.unwrap_or(None),
719                    vcs: vcs.unwrap_or(None),
720                    format_version: format_version.unwrap_or(None),
721                    base: base.unwrap_or(None),
722                    has_plugins: has_plugins.unwrap_or(None),
723                    plugin_for: plugin_for.unwrap_or_default(),
724                    hashed_variants: hashed_variants.unwrap_or(None),
725                    preprocess: preprocess.unwrap_or(None),
726                })
727            }
728        }
729
730        const FIELDS: &'static [&'static str] = &[
731            "name",
732            "version",
733            "description",
734            "authors",
735            "requires",
736            "build_requires",
737            "private_build_requires",
738            "variants",
739            "tools",
740            "commands",
741            "build_command",
742            "build_system",
743            "pre_commands",
744            "post_commands",
745            "pre_test_commands",
746            "pre_build_commands",
747            "tests",
748            "requires_rez_version",
749            "uuid",
750            "help",
751            "relocatable",
752            "cachable",
753            "timestamp",
754            "revision",
755            "changelog",
756            "release_message",
757            "previous_version",
758            "previous_revision",
759            "vcs",
760            "format_version",
761            "base",
762            "has_plugins",
763            "plugin_for",
764            "hashed_variants",
765            "preprocess",
766        ];
767        deserializer.deserialize_struct("Package", FIELDS, PackageVisitor)
768    }
769}
770
771#[cfg(feature = "python-bindings")]
772#[pymethods]
773impl Package {
774    #[new]
775    pub fn new(name: String) -> Self {
776        Self {
777            name,
778            version: None,
779            description: None,
780            authors: Vec::new(),
781            requires: Vec::new(),
782            build_requires: Vec::new(),
783            private_build_requires: Vec::new(),
784            variants: Vec::new(),
785            tools: Vec::new(),
786            commands: None,
787            build_command: None,
788            build_system: None,
789            pre_commands: None,
790            post_commands: None,
791            pre_test_commands: None,
792            pre_build_commands: None,
793            tests: HashMap::new(),
794            requires_rez_version: None,
795            uuid: None,
796            config: HashMap::new(),
797            help: None,
798            relocatable: None,
799            cachable: None,
800            timestamp: None,
801            revision: None,
802            changelog: None,
803            release_message: None,
804            previous_version: None,
805            previous_revision: None,
806            vcs: None,
807            format_version: None,
808            base: None,
809            has_plugins: None,
810            plugin_for: Vec::new(),
811            hashed_variants: None,
812            preprocess: None,
813        }
814    }
815
816    /// Get the qualified name of the package (name-version)
817    #[getter]
818    pub fn qualified_name(&self) -> String {
819        match &self.version {
820            Some(version) => format!("{}-{}", self.name, version.as_str()),
821            None => self.name.clone(),
822        }
823    }
824
825    /// Get the package as an exact requirement string
826    pub fn as_exact_requirement(&self) -> String {
827        match &self.version {
828            Some(version) => format!("{}=={}", self.name, version.as_str()),
829            None => self.name.clone(),
830        }
831    }
832
833    /// Check if this is a package (always true for Package)
834    #[getter]
835    pub fn is_package(&self) -> bool {
836        true
837    }
838
839    /// Check if this is a variant (always false for Package)
840    #[getter]
841    pub fn is_variant(&self) -> bool {
842        false
843    }
844
845    /// Get the number of variants
846    #[getter]
847    pub fn num_variants(&self) -> usize {
848        self.variants.len()
849    }
850
851    /// Set the package version
852    pub fn set_version(&mut self, version: Version) {
853        self.version = Some(version);
854    }
855
856    /// Set the package description
857    pub fn set_description(&mut self, description: String) {
858        self.description = Some(description);
859    }
860
861    /// Add an author
862    pub fn add_author(&mut self, author: String) {
863        self.authors.push(author);
864    }
865
866    /// Add a requirement
867    pub fn add_requirement(&mut self, requirement: String) {
868        self.requires.push(requirement);
869    }
870
871    /// Add a build requirement
872    pub fn add_build_requirement(&mut self, requirement: String) {
873        self.build_requires.push(requirement);
874    }
875
876    /// Add a private build requirement
877    pub fn add_private_build_requirement(&mut self, requirement: String) {
878        self.private_build_requires.push(requirement);
879    }
880
881    /// Add a variant
882    pub fn add_variant(&mut self, variant: Vec<String>) {
883        self.variants.push(variant);
884    }
885
886    /// Add a tool
887    pub fn add_tool(&mut self, tool: String) {
888        self.tools.push(tool);
889    }
890
891    /// Set commands
892    pub fn set_commands(&mut self, commands: String) {
893        self.commands = Some(commands);
894    }
895
896    /// Get string representation
897    fn __str__(&self) -> String {
898        self.qualified_name()
899    }
900
901    /// Get representation
902    fn __repr__(&self) -> String {
903        format!("Package('{}')", self.qualified_name())
904    }
905
906    /// Create a new package (static method)
907    #[staticmethod]
908    pub fn new_static(name: String) -> Self {
909        Self::new(name)
910    }
911}
912
913#[cfg(not(feature = "python-bindings"))]
914impl Package {
915    pub fn new(name: String) -> Self {
916        Self {
917            name,
918            version: None,
919            description: None,
920            authors: Vec::new(),
921            requires: Vec::new(),
922            build_requires: Vec::new(),
923            private_build_requires: Vec::new(),
924            variants: Vec::new(),
925            tools: Vec::new(),
926            commands: None,
927            build_command: None,
928            build_system: None,
929            pre_commands: None,
930            post_commands: None,
931            pre_test_commands: None,
932            pre_build_commands: None,
933            tests: HashMap::new(),
934            requires_rez_version: None,
935            uuid: None,
936            config: HashMap::new(),
937            help: None,
938            relocatable: None,
939            cachable: None,
940            timestamp: None,
941            revision: None,
942            changelog: None,
943            release_message: None,
944            previous_version: None,
945            previous_revision: None,
946            vcs: None,
947            format_version: None,
948            base: None,
949            has_plugins: None,
950            plugin_for: Vec::new(),
951            hashed_variants: None,
952            preprocess: None,
953        }
954    }
955
956    /// Get the qualified name of the package (name-version)
957    pub fn qualified_name(&self) -> String {
958        match &self.version {
959            Some(version) => format!("{}-{}", self.name, version.as_str()),
960            None => self.name.clone(),
961        }
962    }
963
964    /// Get the package as an exact requirement string
965    pub fn as_exact_requirement(&self) -> String {
966        match &self.version {
967            Some(version) => format!("{}=={}", self.name, version.as_str()),
968            None => self.name.clone(),
969        }
970    }
971
972    /// Check if this is a package (always true for Package)
973    pub fn is_package(&self) -> bool {
974        true
975    }
976
977    /// Check if this is a variant (always false for Package)
978    pub fn is_variant(&self) -> bool {
979        false
980    }
981
982    /// Get the number of variants
983    pub fn num_variants(&self) -> usize {
984        self.variants.len()
985    }
986
987    /// Set the package version
988    pub fn set_version(&mut self, version: Version) {
989        self.version = Some(version);
990    }
991
992    /// Set the package description
993    pub fn set_description(&mut self, description: String) {
994        self.description = Some(description);
995    }
996
997    /// Add an author
998    pub fn add_author(&mut self, author: String) {
999        self.authors.push(author);
1000    }
1001
1002    /// Add a requirement
1003    pub fn add_requirement(&mut self, requirement: String) {
1004        self.requires.push(requirement);
1005    }
1006
1007    /// Add a build requirement
1008    pub fn add_build_requirement(&mut self, requirement: String) {
1009        self.build_requires.push(requirement);
1010    }
1011
1012    /// Add a private build requirement
1013    pub fn add_private_build_requirement(&mut self, requirement: String) {
1014        self.private_build_requires.push(requirement);
1015    }
1016
1017    /// Add a variant
1018    pub fn add_variant(&mut self, variant: Vec<String>) {
1019        self.variants.push(variant);
1020    }
1021
1022    /// Add a tool
1023    pub fn add_tool(&mut self, tool: String) {
1024        self.tools.push(tool);
1025    }
1026
1027    /// Set commands
1028    pub fn set_commands(&mut self, commands: String) {
1029        self.commands = Some(commands);
1030    }
1031
1032    /// Validate the package definition
1033    pub fn validate(&self) -> Result<(), RezCoreError> {
1034        // Check required fields
1035        if self.name.is_empty() {
1036            return Err(RezCoreError::PackageParse(
1037                "Package name cannot be empty".to_string(),
1038            ));
1039        }
1040
1041        // Validate name format (alphanumeric, underscore, hyphen)
1042        if !self
1043            .name
1044            .chars()
1045            .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
1046        {
1047            return Err(RezCoreError::PackageParse(format!(
1048                "Invalid package name '{}': only alphanumeric, underscore, and hyphen allowed",
1049                self.name
1050            )));
1051        }
1052
1053        // Validate version if present
1054        if let Some(ref version) = self.version {
1055            // Version validation is handled by the Version type itself
1056            if version.as_str().is_empty() {
1057                return Err(RezCoreError::PackageParse(
1058                    "Package version cannot be empty".to_string(),
1059                ));
1060            }
1061        }
1062
1063        // Validate requirements format
1064        for req in &self.requires {
1065            if req.is_empty() {
1066                return Err(RezCoreError::PackageParse(
1067                    "Requirement cannot be empty".to_string(),
1068                ));
1069            }
1070        }
1071
1072        for req in &self.build_requires {
1073            if req.is_empty() {
1074                return Err(RezCoreError::PackageParse(
1075                    "Build requirement cannot be empty".to_string(),
1076                ));
1077            }
1078        }
1079
1080        for req in &self.private_build_requires {
1081            if req.is_empty() {
1082                return Err(RezCoreError::PackageParse(
1083                    "Private build requirement cannot be empty".to_string(),
1084                ));
1085            }
1086        }
1087
1088        // Validate variants
1089        for variant in &self.variants {
1090            for req in variant {
1091                if req.is_empty() {
1092                    return Err(RezCoreError::PackageParse(
1093                        "Variant requirement cannot be empty".to_string(),
1094                    ));
1095                }
1096            }
1097        }
1098
1099        Ok(())
1100    }
1101}
1102
1103#[cfg(feature = "python-bindings")]
1104impl Package {
1105    /// Create a package from a dictionary/map
1106    pub fn from_dict(data: HashMap<String, PyObject>) -> Result<Self, RezCoreError> {
1107        Python::with_gil(|py| {
1108            let name = data
1109                .get("name")
1110                .ok_or_else(|| RezCoreError::PackageParse("Missing 'name' field".to_string()))?
1111                .extract::<String>(py)
1112                .map_err(|e| RezCoreError::PackageParse(format!("Invalid 'name' field: {}", e)))?;
1113
1114            let mut package = Package::new(name);
1115
1116            // Set version if present
1117            if let Some(version_obj) = data.get("version") {
1118                if let Ok(version_str) = version_obj.extract::<String>(py) {
1119                    let version = Version::parse(&version_str).map_err(|e| {
1120                        RezCoreError::PackageParse(format!("Invalid version: {}", e))
1121                    })?;
1122                    package.version = Some(version);
1123                }
1124            }
1125
1126            // Set description if present
1127            if let Some(desc_obj) = data.get("description") {
1128                if let Ok(desc) = desc_obj.extract::<String>(py) {
1129                    package.description = Some(desc);
1130                }
1131            }
1132
1133            // Set authors if present
1134            if let Some(authors_obj) = data.get("authors") {
1135                if let Ok(authors) = authors_obj.extract::<Vec<String>>(py) {
1136                    package.authors = authors;
1137                }
1138            }
1139
1140            // Set requires if present
1141            if let Some(requires_obj) = data.get("requires") {
1142                if let Ok(requires) = requires_obj.extract::<Vec<String>>(py) {
1143                    package.requires = requires;
1144                }
1145            }
1146
1147            // Set build_requires if present
1148            if let Some(build_requires_obj) = data.get("build_requires") {
1149                if let Ok(build_requires) = build_requires_obj.extract::<Vec<String>>(py) {
1150                    package.build_requires = build_requires;
1151                }
1152            }
1153
1154            // Set variants if present
1155            if let Some(variants_obj) = data.get("variants") {
1156                if let Ok(variants) = variants_obj.extract::<Vec<Vec<String>>>(py) {
1157                    package.variants = variants;
1158                }
1159            }
1160
1161            // Set tools if present
1162            if let Some(tools_obj) = data.get("tools") {
1163                if let Ok(tools) = tools_obj.extract::<Vec<String>>(py) {
1164                    package.tools = tools;
1165                }
1166            }
1167
1168            Ok(package)
1169        })
1170    }
1171
1172    /// Validate the package definition
1173    pub fn validate(&self) -> Result<(), RezCoreError> {
1174        // Check required fields
1175        if self.name.is_empty() {
1176            return Err(RezCoreError::PackageParse(
1177                "Package name cannot be empty".to_string(),
1178            ));
1179        }
1180
1181        // Validate name format (alphanumeric, underscore, hyphen)
1182        if !self
1183            .name
1184            .chars()
1185            .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
1186        {
1187            return Err(RezCoreError::PackageParse(format!(
1188                "Invalid package name '{}': only alphanumeric, underscore, and hyphen allowed",
1189                self.name
1190            )));
1191        }
1192
1193        // Validate version if present
1194        if let Some(ref version) = self.version {
1195            // Version validation is handled by the Version type itself
1196            if version.as_str().is_empty() {
1197                return Err(RezCoreError::PackageParse(
1198                    "Package version cannot be empty".to_string(),
1199                ));
1200            }
1201        }
1202
1203        // Validate requirements format
1204        for req in &self.requires {
1205            if req.is_empty() {
1206                return Err(RezCoreError::PackageParse(
1207                    "Requirement cannot be empty".to_string(),
1208                ));
1209            }
1210        }
1211
1212        for req in &self.build_requires {
1213            if req.is_empty() {
1214                return Err(RezCoreError::PackageParse(
1215                    "Build requirement cannot be empty".to_string(),
1216                ));
1217            }
1218        }
1219
1220        for req in &self.private_build_requires {
1221            if req.is_empty() {
1222                return Err(RezCoreError::PackageParse(
1223                    "Private build requirement cannot be empty".to_string(),
1224                ));
1225            }
1226        }
1227
1228        // Validate variants
1229        for variant in &self.variants {
1230            for req in variant {
1231                if req.is_empty() {
1232                    return Err(RezCoreError::PackageParse(
1233                        "Variant requirement cannot be empty".to_string(),
1234                    ));
1235                }
1236            }
1237        }
1238
1239        Ok(())
1240    }
1241}