1#[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#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct PackageRequirement {
14 pub name: String,
16 pub version_spec: Option<String>,
18 pub weak: bool,
20}
21
22impl PackageRequirement {
23 pub fn new(name: String) -> Self {
25 Self {
26 name,
27 version_spec: None,
28 weak: false,
29 }
30 }
31
32 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 pub fn parse(requirement_str: &str) -> Result<Self, RezCoreError> {
43 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 pub fn name(&self) -> &str {
55 &self.name
56 }
57
58 pub fn version_spec(&self) -> Option<&str> {
60 self.version_spec.as_deref()
61 }
62
63 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 pub fn requirement_string(&self) -> String {
74 self.to_string()
75 }
76
77 pub fn satisfied_by(&self, version: &Version) -> bool {
79 if let Some(ref version_spec) = self.version_spec {
81 version.as_str() == version_spec
83 } else {
84 true
86 }
87 }
88}
89
90#[cfg_attr(feature = "python-bindings", pyclass)]
92#[derive(Debug)]
93pub struct Package {
94 #[cfg(feature = "python-bindings")]
96 #[pyo3(get)]
97 pub name: String,
98 #[cfg(not(feature = "python-bindings"))]
100 pub name: String,
101
102 #[cfg(feature = "python-bindings")]
104 #[pyo3(get)]
105 pub version: Option<Version>,
106 #[cfg(not(feature = "python-bindings"))]
108 pub version: Option<Version>,
109
110 #[cfg(feature = "python-bindings")]
112 #[pyo3(get)]
113 pub description: Option<String>,
114 #[cfg(not(feature = "python-bindings"))]
116 pub description: Option<String>,
117
118 #[cfg(feature = "python-bindings")]
120 #[pyo3(get)]
121 pub authors: Vec<String>,
122 #[cfg(not(feature = "python-bindings"))]
124 pub authors: Vec<String>,
125
126 pub requires: Vec<String>,
128
129 pub build_requires: Vec<String>,
131
132 pub private_build_requires: Vec<String>,
134
135 pub variants: Vec<Vec<String>>,
137
138 pub tools: Vec<String>,
140
141 pub commands: Option<String>,
143
144 pub build_command: Option<String>,
146
147 pub build_system: Option<String>,
149
150 pub pre_commands: Option<String>,
152
153 pub post_commands: Option<String>,
155
156 pub pre_test_commands: Option<String>,
158
159 pub pre_build_commands: Option<String>,
161
162 pub tests: HashMap<String, String>,
164
165 pub requires_rez_version: Option<String>,
167
168 #[cfg(feature = "python-bindings")]
170 #[pyo3(get)]
171 pub uuid: Option<String>,
172 #[cfg(not(feature = "python-bindings"))]
174 pub uuid: Option<String>,
175
176 #[cfg(feature = "python-bindings")]
178 pub config: HashMap<String, PyObject>,
179
180 #[cfg(not(feature = "python-bindings"))]
182 pub config: HashMap<String, String>,
183
184 pub help: Option<String>,
186
187 pub relocatable: Option<bool>,
189
190 pub cachable: Option<bool>,
192
193 pub timestamp: Option<i64>,
195
196 pub revision: Option<String>,
198
199 pub changelog: Option<String>,
201
202 pub release_message: Option<String>,
204
205 pub previous_version: Option<Version>,
207
208 pub previous_revision: Option<String>,
210
211 pub vcs: Option<String>,
213
214 pub format_version: Option<i32>,
216
217 pub base: Option<String>,
219
220 pub has_plugins: Option<bool>,
222
223 pub plugin_for: Vec<String>,
225
226 pub hashed_variants: Option<bool>,
228
229 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 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(), 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 #[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 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 #[getter]
835 pub fn is_package(&self) -> bool {
836 true
837 }
838
839 #[getter]
841 pub fn is_variant(&self) -> bool {
842 false
843 }
844
845 #[getter]
847 pub fn num_variants(&self) -> usize {
848 self.variants.len()
849 }
850
851 pub fn set_version(&mut self, version: Version) {
853 self.version = Some(version);
854 }
855
856 pub fn set_description(&mut self, description: String) {
858 self.description = Some(description);
859 }
860
861 pub fn add_author(&mut self, author: String) {
863 self.authors.push(author);
864 }
865
866 pub fn add_requirement(&mut self, requirement: String) {
868 self.requires.push(requirement);
869 }
870
871 pub fn add_build_requirement(&mut self, requirement: String) {
873 self.build_requires.push(requirement);
874 }
875
876 pub fn add_private_build_requirement(&mut self, requirement: String) {
878 self.private_build_requires.push(requirement);
879 }
880
881 pub fn add_variant(&mut self, variant: Vec<String>) {
883 self.variants.push(variant);
884 }
885
886 pub fn add_tool(&mut self, tool: String) {
888 self.tools.push(tool);
889 }
890
891 pub fn set_commands(&mut self, commands: String) {
893 self.commands = Some(commands);
894 }
895
896 fn __str__(&self) -> String {
898 self.qualified_name()
899 }
900
901 fn __repr__(&self) -> String {
903 format!("Package('{}')", self.qualified_name())
904 }
905
906 #[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 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 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 pub fn is_package(&self) -> bool {
974 true
975 }
976
977 pub fn is_variant(&self) -> bool {
979 false
980 }
981
982 pub fn num_variants(&self) -> usize {
984 self.variants.len()
985 }
986
987 pub fn set_version(&mut self, version: Version) {
989 self.version = Some(version);
990 }
991
992 pub fn set_description(&mut self, description: String) {
994 self.description = Some(description);
995 }
996
997 pub fn add_author(&mut self, author: String) {
999 self.authors.push(author);
1000 }
1001
1002 pub fn add_requirement(&mut self, requirement: String) {
1004 self.requires.push(requirement);
1005 }
1006
1007 pub fn add_build_requirement(&mut self, requirement: String) {
1009 self.build_requires.push(requirement);
1010 }
1011
1012 pub fn add_private_build_requirement(&mut self, requirement: String) {
1014 self.private_build_requires.push(requirement);
1015 }
1016
1017 pub fn add_variant(&mut self, variant: Vec<String>) {
1019 self.variants.push(variant);
1020 }
1021
1022 pub fn add_tool(&mut self, tool: String) {
1024 self.tools.push(tool);
1025 }
1026
1027 pub fn set_commands(&mut self, commands: String) {
1029 self.commands = Some(commands);
1030 }
1031
1032 pub fn validate(&self) -> Result<(), RezCoreError> {
1034 if self.name.is_empty() {
1036 return Err(RezCoreError::PackageParse(
1037 "Package name cannot be empty".to_string(),
1038 ));
1039 }
1040
1041 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 if let Some(ref version) = self.version {
1055 if version.as_str().is_empty() {
1057 return Err(RezCoreError::PackageParse(
1058 "Package version cannot be empty".to_string(),
1059 ));
1060 }
1061 }
1062
1063 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 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 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 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 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 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 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 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 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 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 pub fn validate(&self) -> Result<(), RezCoreError> {
1174 if self.name.is_empty() {
1176 return Err(RezCoreError::PackageParse(
1177 "Package name cannot be empty".to_string(),
1178 ));
1179 }
1180
1181 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 if let Some(ref version) = self.version {
1195 if version.as_str().is_empty() {
1197 return Err(RezCoreError::PackageParse(
1198 "Package version cannot be empty".to_string(),
1199 ));
1200 }
1201 }
1202
1203 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 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}