1use crate::git::shorthand::RemoteGitUrlShorthand;
4use crate::git::GitSource;
5use crate::hash::HasIntegrity;
6use crate::lockfile::OptState;
7use crate::lockfile::PinnedState;
8use crate::lua_rockspec::DeploySpec;
9use crate::lua_rockspec::LocalLuaRockspec;
10use crate::lua_rockspec::LocalRockSource;
11use crate::lua_rockspec::LuaRockspecError;
12use crate::lua_rockspec::RemoteLuaRockspec;
13use crate::lua_rockspec::RockSourceSpec;
14use crate::lua_version::LuaVersion;
15use crate::operations::RunCommand;
16use crate::package::PackageNameList;
17use crate::package::SpecRev;
18use crate::rockspec::lua_dependency::LuaDependencySpec;
19use std::io;
20use std::{collections::HashMap, path::PathBuf};
21
22use itertools::Itertools;
23use nonempty::NonEmpty;
24use serde::de;
25use serde::{Deserialize, Deserializer};
26use ssri::Integrity;
27use thiserror::Error;
28
29use crate::{
30 config::Config,
31 lua_rockspec::{
32 BuildSpec, BuildSpecInternal, BuildSpecInternalError, DisplayAsLuaKV, ExternalDependencies,
33 ExternalDependencySpec, LuaVersionError, PartialLuaRockspec, PerPlatform,
34 PlatformIdentifier, PlatformSupport, PlatformValidationError, RemoteRockSource,
35 RockDescription, RockSourceError, RockspecFormat, TestSpec, TestSpecDecodeError,
36 TestSpecInternal,
37 },
38 package::{
39 BuildDependencies, Dependencies, PackageName, PackageReq, PackageVersion, PackageVersionReq,
40 },
41 rockspec::{LuaVersionCompatibility, Rockspec},
42};
43
44use super::gen::GenerateSourceError;
45use super::gen::RockSourceTemplate;
46use super::r#gen::GenerateVersionError;
47use super::r#gen::PackageVersionTemplate;
48use super::ProjectRoot;
49
50pub const PROJECT_TOML: &str = "lux.toml";
51
52#[derive(Deserialize)]
53#[serde(untagged)]
54#[allow(clippy::large_enum_variant)] enum DependencyEntry {
56 Simple(PackageVersionReq),
57 Detailed(DependencyTableEntry),
58}
59
60#[derive(Debug, Deserialize)]
61struct DependencyTableEntry {
62 version: PackageVersionReq,
63 #[serde(default)]
64 opt: Option<bool>,
65 #[serde(default)]
66 pin: Option<bool>,
67 #[serde(default)]
68 git: Option<RemoteGitUrlShorthand>,
69 #[serde(default)]
70 path: Option<PathBuf>,
71 #[serde(default)]
72 rev: Option<String>,
73}
74
75fn parse_map_to_dependency_vec_opt<'de, D>(
76 deserializer: D,
77) -> Result<Option<Vec<LuaDependencySpec>>, D::Error>
78where
79 D: Deserializer<'de>,
80{
81 let packages: Option<HashMap<PackageName, DependencyEntry>> =
82 Option::deserialize(deserializer)?;
83
84 match packages {
85 None => Ok(None),
86 Some(packages) => Ok(Some(
87 packages
88 .into_iter()
89 .map(|(name, spec)| match spec {
90 DependencyEntry::Simple(version_req) => {
91 Ok(PackageReq { name, version_req }.into())
92 }
93 DependencyEntry::Detailed(entry) => {
94 let source = match (entry.git, entry.rev, entry.path) {
95 (None, None, None) => Ok(None),
96 (None, Some(_), None) => Err(de::Error::custom(format!(
97 "dependency {} specifies a 'rev', but missing a 'git' field",
98 &name
99 ))),
100 (Some(git), Some(rev), None) => Ok(Some(RockSourceSpec::Git(GitSource {
101 url: git.into(),
102 checkout_ref: Some(rev),
103 }))),
104 (Some(git), None, None) => Ok(Some(RockSourceSpec::Git(GitSource {
105 url: git.into(),
106 checkout_ref: Some(
107 entry
108 .version
109 .clone()
110 .to_string()
111 .trim_start_matches("=")
112 .to_string(),
113 ),
114 }))),
115 (None, None, Some(path)) => Ok(Some(RockSourceSpec::File(path))),
116 (_, _, Some(_)) => Err(de::Error::custom(format!(
117 "dependency '{}' specifies a 'path', which cannot be combined with 'git' or 'rev'",
118 &name
119 ))),
120 }?;
121 Ok(LuaDependencySpec {
122 package_req: PackageReq {
123 name,
124 version_req: entry.version,
125 },
126 opt: OptState::from(entry.opt.unwrap_or(false)),
127 pin: PinnedState::from(entry.pin.unwrap_or(false)),
128 source,
129 })
130 }
131 })
132 .try_collect()?,
133 )),
134 }
135}
136
137#[derive(Debug, Error)]
138pub enum ProjectTomlError {
139 #[error("error generating rockspec source:\n{0}")]
140 GenerateSource(#[from] GenerateSourceError),
141 #[error("error generating rockspec version:\n{0}")]
142 GenerateVersion(#[from] GenerateVersionError),
143}
144
145#[derive(Debug, Error)]
146pub enum LocalProjectTomlValidationError {
147 #[error("no lua version provided")]
148 NoLuaVersion,
149 #[error("could not decode the test spec:\n:{0}")]
150 TestSpecError(#[from] TestSpecDecodeError),
151 #[error("could not decode the build spec:\n:{0}")]
152 BuildSpecInternal(#[from] BuildSpecInternalError),
153 #[error(transparent)]
154 PlatformValidation(#[from] PlatformValidationError),
155 #[error("{}copy_directories cannot contain a rockspec name", ._0.as_ref().map(|p| format!("{p}: ")).unwrap_or_default())]
156 CopyDirectoriesContainRockspecName(Option<String>),
157 #[error("could not decode the source spec:\n:{0}")]
158 RockSource(#[from] RockSourceError),
159 #[error("duplicate dependencies: {0}")]
160 DuplicateDependencies(PackageNameList),
161 #[error("duplicate test dependencies: {0}")]
162 DuplicateTestDependencies(PackageNameList),
163 #[error("duplicate build dependencies: {0}")]
164 DuplicateBuildDependencies(PackageNameList),
165 #[error(
166 r#"dependencies field cannot contain 'lua'.
167 Please provide the version in the top-level 'lua' field
168
169 Example:
170
171 ```toml
172 package = "my-package"
173 lua = ">=5.1"
174
175 [dependencies]
176 # do not add Lua here!
177 ```
178 "#
179 )]
180 DependenciesContainLua,
181 #[error("error generating rockspec source:\n{0}")]
182 GenerateSource(#[from] GenerateSourceError),
183 #[error("error generating rockspec version:\n{0}")]
184 GenerateVersion(#[from] GenerateVersionError),
185}
186
187#[derive(Debug, Error)]
188pub enum RemoteProjectTomlValidationError {
189 #[error("error generating rockspec source:\n{0}")]
190 GenerateSource(#[from] GenerateSourceError),
191 #[error("error generating rockspec version:\n{0}")]
192 GenerateVersion(#[from] GenerateVersionError),
193 #[error(transparent)]
194 LocalProjectTomlValidationError(#[from] LocalProjectTomlValidationError),
195}
196
197#[derive(Clone, Debug, Deserialize)]
201pub struct PartialProjectToml {
202 pub(crate) package: PackageName,
203 #[serde(default, rename = "version")]
204 pub(crate) version_template: PackageVersionTemplate,
205 #[serde(default)]
206 pub(crate) build: BuildSpecInternal,
207 pub(crate) rockspec_format: Option<RockspecFormat>,
208 #[serde(default)]
209 pub(crate) run: Option<RunSpec>,
210 #[serde(default)]
211 pub(crate) lua: Option<PackageVersionReq>,
212 #[serde(default)]
213 pub(crate) description: Option<RockDescription>,
214 #[serde(default)]
215 pub(crate) supported_platforms: Option<HashMap<PlatformIdentifier, bool>>,
216 #[serde(default, deserialize_with = "parse_map_to_dependency_vec_opt")]
217 pub(crate) dependencies: Option<Vec<LuaDependencySpec>>,
218 #[serde(default, deserialize_with = "parse_map_to_dependency_vec_opt")]
219 pub(crate) build_dependencies: Option<Vec<LuaDependencySpec>>,
220 #[serde(default)]
221 pub(crate) external_dependencies: Option<HashMap<String, ExternalDependencySpec>>,
222 #[serde(default, deserialize_with = "parse_map_to_dependency_vec_opt")]
223 pub(crate) test_dependencies: Option<Vec<LuaDependencySpec>>,
224 #[serde(default, rename = "source")]
225 pub(crate) source_template: RockSourceTemplate,
226 #[serde(default)]
227 pub(crate) test: Option<TestSpecInternal>,
228 #[serde(default)]
229 pub(crate) deploy: Option<DeploySpec>,
230
231 #[serde(skip, default = "ProjectRoot::new")]
233 pub(crate) project_root: ProjectRoot,
234}
235
236impl HasIntegrity for PartialProjectToml {
237 fn hash(&self) -> io::Result<Integrity> {
238 let toml_file = self.project_root.join(PROJECT_TOML);
239 let content = std::fs::read_to_string(&toml_file)?;
240 Ok(Integrity::from(&content))
241 }
242}
243
244impl PartialProjectToml {
245 pub(crate) fn new(str: &str, project_root: ProjectRoot) -> Result<Self, toml::de::Error> {
246 Ok(Self {
247 project_root,
248 ..toml::from_str(str)?
249 })
250 }
251
252 pub fn into_local(&self) -> Result<LocalProjectToml, LocalProjectTomlValidationError> {
255 let project_toml = self.clone();
256
257 if project_toml
259 .dependencies
260 .as_ref()
261 .is_some_and(|deps| deps.iter().any(|dep| dep.name() == &"lua".into()))
262 {
263 return Err(LocalProjectTomlValidationError::DependenciesContainLua);
264 }
265
266 let get_duplicates = |dependencies: &Option<Vec<LuaDependencySpec>>| {
267 dependencies
268 .iter()
269 .flat_map(|deps| {
270 deps.iter()
271 .map(|dep| dep.package_req().name())
272 .duplicates()
273 .cloned()
274 })
275 .collect_vec()
276 };
277 let duplicate_dependencies = get_duplicates(&self.dependencies);
278 if !duplicate_dependencies.is_empty() {
279 return Err(LocalProjectTomlValidationError::DuplicateDependencies(
280 PackageNameList::new(duplicate_dependencies),
281 ));
282 }
283 let duplicate_test_dependencies = get_duplicates(&self.test_dependencies);
284 if !duplicate_test_dependencies.is_empty() {
285 return Err(LocalProjectTomlValidationError::DuplicateTestDependencies(
286 PackageNameList::new(duplicate_test_dependencies),
287 ));
288 }
289 let duplicate_build_dependencies = get_duplicates(&self.build_dependencies);
290 if !duplicate_build_dependencies.is_empty() {
291 return Err(LocalProjectTomlValidationError::DuplicateBuildDependencies(
292 PackageNameList::new(duplicate_build_dependencies),
293 ));
294 }
295
296 let validated = LocalProjectToml {
297 internal: project_toml.clone(),
298
299 package: project_toml.package,
300 version: project_toml
301 .version_template
302 .try_generate(&self.project_root, None)
303 .unwrap_or(PackageVersion::default_dev_version()),
304 lua: project_toml
305 .lua
306 .ok_or(LocalProjectTomlValidationError::NoLuaVersion)?,
307 description: project_toml.description.unwrap_or_default(),
308 run: project_toml.run.map(PerPlatform::new),
309 supported_platforms: PlatformSupport::parse(
310 &project_toml
311 .supported_platforms
312 .unwrap_or_default()
313 .into_iter()
314 .map(|(platform, supported)| {
315 if supported {
316 format!("{platform}")
317 } else {
318 format!("!{platform}")
319 }
320 })
321 .collect_vec(),
322 )?,
323 dependencies: PerPlatform::new(
326 project_toml
327 .dependencies
328 .unwrap_or_default()
329 .into_iter()
330 .map(|dep| self.resolve_lua_dependency_spec(dep))
331 .collect_vec(),
332 ),
333 test_dependencies: PerPlatform::new(
334 project_toml
335 .test_dependencies
336 .unwrap_or_default()
337 .into_iter()
338 .map(|dep| self.resolve_lua_dependency_spec(dep))
339 .collect_vec(),
340 ),
341 build_dependencies: PerPlatform::new(
342 project_toml
343 .build_dependencies
344 .unwrap_or_default()
345 .into_iter()
346 .map(|dep| self.resolve_lua_dependency_spec(dep))
347 .collect_vec(),
348 ),
349 external_dependencies: PerPlatform::new(
350 project_toml.external_dependencies.unwrap_or_default(),
351 ),
352 test: PerPlatform::new(TestSpec::try_from(
353 project_toml.test.clone().unwrap_or_default(),
354 )?),
355 build: PerPlatform::new(BuildSpec::from_internal_spec(project_toml.build.clone())?),
356 deploy: PerPlatform::new(project_toml.deploy.clone().unwrap_or_default()),
357 rockspec_format: project_toml.rockspec_format.clone(),
358
359 source: PerPlatform::new(RemoteRockSource {
360 local: LocalRockSource::default(),
361 source_spec: RockSourceSpec::File(self.project_root.to_path_buf()),
362 }),
363 };
364
365 let rockspec_file_name = format!("{}-{}.rockspec", validated.package, validated.version);
366
367 if validated
368 .build
369 .default
370 .copy_directories
371 .contains(&PathBuf::from(&rockspec_file_name))
372 {
373 return Err(LocalProjectTomlValidationError::CopyDirectoriesContainRockspecName(None));
374 }
375
376 for (platform, build_override) in &validated.build.per_platform {
377 if build_override
378 .copy_directories
379 .contains(&PathBuf::from(&rockspec_file_name))
380 {
381 return Err(
382 LocalProjectTomlValidationError::CopyDirectoriesContainRockspecName(Some(
383 platform.to_string(),
384 )),
385 );
386 }
387 }
388
389 Ok(validated)
390 }
391
392 pub fn into_remote(
396 &self,
397 specrev: Option<SpecRev>,
398 ) -> Result<RemoteProjectToml, RemoteProjectTomlValidationError> {
399 let version = self
400 .version_template
401 .try_generate(&self.project_root, specrev)?;
402 let source =
403 self.source_template
404 .try_generate(&self.project_root, &self.package, &version)?;
405 let source = PerPlatform::new(RemoteRockSource::try_from(source).map_err(|err| {
406 RemoteProjectTomlValidationError::LocalProjectTomlValidationError(
407 LocalProjectTomlValidationError::RockSource(err),
408 )
409 })?);
410 let mut local = self.into_local()?;
411 local.version = version;
412
413 let validated = RemoteProjectToml { source, local };
414
415 Ok(validated)
416 }
417
418 pub fn package(&self) -> &PackageName {
421 &self.package
422 }
423
424 pub fn version(&self) -> Result<PackageVersion, GenerateVersionError> {
426 self.version_template.try_generate(&self.project_root, None)
427 }
428
429 pub fn merge(self, other: PartialLuaRockspec) -> Self {
432 PartialProjectToml {
433 package: other.package.unwrap_or(self.package),
434 version_template: self.version_template,
435 lua: other
436 .dependencies
437 .as_ref()
438 .and_then(|deps| {
439 deps.iter()
440 .find(|dep| dep.name() == &"lua".into())
441 .and_then(|dep| {
442 if dep.version_req().is_any() {
443 None
444 } else {
445 Some(dep.version_req().clone())
446 }
447 })
448 })
449 .or(self.lua),
450 build: other.build.unwrap_or(self.build),
451 run: self.run,
452 description: other.description.or(self.description),
453 supported_platforms: other
454 .supported_platforms
455 .map(|platform_support| platform_support.platforms().clone())
456 .or(self.supported_platforms),
457 dependencies: other
458 .dependencies
459 .map(|deps| {
460 deps.into_iter()
461 .filter(|dep| dep.name() != &"lua".into())
462 .collect()
463 })
464 .or(self.dependencies),
465 build_dependencies: other.build_dependencies.or(self.build_dependencies),
466 test_dependencies: other.test_dependencies.or(self.test_dependencies),
467 external_dependencies: other.external_dependencies.or(self.external_dependencies),
468 source_template: self.source_template,
469 test: other.test.or(self.test),
470 deploy: other.deploy.or(self.deploy),
471 rockspec_format: other.rockspec_format.or(self.rockspec_format),
472
473 project_root: self.project_root,
475 }
476 }
477
478 fn resolve_lua_dependency_spec(&self, dep: LuaDependencySpec) -> LuaDependencySpec {
479 match &dep.source {
480 Some(RockSourceSpec::File(path)) if path.is_dir() => dep,
481 Some(RockSourceSpec::File(path)) => LuaDependencySpec {
482 source: Some(RockSourceSpec::File(self.project_root.join(path))),
483 ..dep
484 },
485 _ => dep,
486 }
487 }
488}
489
490impl LuaVersionCompatibility for PartialProjectToml {
494 fn validate_lua_version(&self, version: &LuaVersion) -> Result<(), LuaVersionError> {
495 if self.supports_lua_version(version) {
496 Ok(())
497 } else {
498 Err(LuaVersionError::LuaVersionUnsupported(
499 version.clone(),
500 self.package().to_owned(),
501 self.version_template
502 .try_generate(&self.project_root, None)
503 .unwrap_or(PackageVersion::default_dev_version()),
504 ))
505 }
506 }
507
508 fn validate_lua_version_from_config(&self, config: &Config) -> Result<(), LuaVersionError> {
509 let _ = self.lua_version_matches(config)?;
510 Ok(())
511 }
512
513 fn lua_version_matches(&self, config: &Config) -> Result<LuaVersion, LuaVersionError> {
514 let version = LuaVersion::from(config)?.clone();
515 if self.supports_lua_version(&version) {
516 Ok(version)
517 } else {
518 Err(LuaVersionError::LuaVersionUnsupported(
519 version,
520 self.package.clone(),
521 self.version_template
522 .try_generate(&self.project_root, None)
523 .unwrap_or(PackageVersion::default_dev_version()),
524 ))
525 }
526 }
527
528 fn supports_lua_version(&self, lua_version: &LuaVersion) -> bool {
529 self.lua
530 .as_ref()
531 .is_none_or(|lua| lua.matches(&lua_version.as_version()))
532 }
533
534 fn lua_version(&self) -> Option<LuaVersion> {
535 for (possibility, version) in [
536 ("5.5.0", LuaVersion::Lua55),
537 ("5.4.0", LuaVersion::Lua54),
538 ("5.3.0", LuaVersion::Lua53),
539 ("5.2.0", LuaVersion::Lua52),
540 ("5.1.0", LuaVersion::Lua51),
541 ] {
542 let possibility = unsafe { possibility.parse().unwrap_unchecked() };
543 if self
544 .lua
545 .as_ref()
546 .is_none_or(|lua| lua.matches(&possibility))
547 {
548 return Some(version);
549 }
550 }
551 None
552 }
553}
554
555#[derive(Debug, Clone, Deserialize)]
557pub struct RunSpec {
558 pub(crate) command: Option<RunCommand>,
560 pub(crate) args: Option<NonEmpty<String>>,
562}
563
564#[derive(Debug)]
568pub struct LocalProjectToml {
569 package: PackageName,
570 version: PackageVersion,
571 lua: PackageVersionReq,
572 rockspec_format: Option<RockspecFormat>,
573 run: Option<PerPlatform<RunSpec>>,
574 description: RockDescription,
575 supported_platforms: PlatformSupport,
576 dependencies: PerPlatform<Vec<LuaDependencySpec>>,
577 build_dependencies: PerPlatform<Vec<LuaDependencySpec>>,
578 external_dependencies: PerPlatform<HashMap<String, ExternalDependencySpec>>,
579 test_dependencies: PerPlatform<Vec<LuaDependencySpec>>,
580 test: PerPlatform<TestSpec>,
581 build: PerPlatform<BuildSpec>,
582 deploy: PerPlatform<DeploySpec>,
583
584 internal: PartialProjectToml,
586
587 source: PerPlatform<RemoteRockSource>,
589}
590
591impl LocalProjectToml {
592 pub fn run(&self) -> Option<&PerPlatform<RunSpec>> {
593 self.run.as_ref()
594 }
595
596 pub fn to_lua_rockspec(&self) -> Result<LocalLuaRockspec, LuaRockspecError> {
599 if let Some(dep) = self
600 .dependencies()
601 .per_platform
602 .values()
603 .filter_map(|deps| deps.iter().find(|dep| dep.source().is_some()))
604 .collect_vec()
605 .first()
606 {
607 return Err(LuaRockspecError::OffSpecDependency(dep.name().clone()));
608 }
609 if let Some(dep) = self
610 .build_dependencies()
611 .per_platform
612 .values()
613 .filter_map(|deps| deps.iter().find(|dep| dep.source().is_some()))
614 .collect_vec()
615 .first()
616 {
617 return Err(LuaRockspecError::OffSpecBuildDependency(dep.name().clone()));
618 }
619 if let Some(dep) = self
620 .test_dependencies()
621 .per_platform
622 .values()
623 .filter_map(|deps| deps.iter().find(|dep| dep.source().is_some()))
624 .collect_vec()
625 .first()
626 {
627 return Err(LuaRockspecError::OffSpecTestDependency(dep.name().clone()));
628 }
629 LocalLuaRockspec::new(
630 &self.to_lua_remote_rockspec_string()?,
631 self.internal.project_root.clone(),
632 )
633 }
634}
635
636impl Rockspec for LocalProjectToml {
637 type Error = ProjectTomlError;
638
639 fn package(&self) -> &PackageName {
640 &self.package
641 }
642
643 fn version(&self) -> &PackageVersion {
644 &self.version
645 }
646
647 fn description(&self) -> &RockDescription {
648 &self.description
649 }
650
651 fn supported_platforms(&self) -> &PlatformSupport {
652 &self.supported_platforms
653 }
654
655 fn lua(&self) -> &PackageVersionReq {
656 &self.lua
657 }
658
659 fn dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
660 &self.dependencies
661 }
662
663 fn build_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
664 &self.build_dependencies
665 }
666
667 fn external_dependencies(&self) -> &PerPlatform<HashMap<String, ExternalDependencySpec>> {
668 &self.external_dependencies
669 }
670
671 fn test_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
672 &self.test_dependencies
673 }
674
675 fn build(&self) -> &PerPlatform<BuildSpec> {
676 &self.build
677 }
678
679 fn test(&self) -> &PerPlatform<TestSpec> {
680 &self.test
681 }
682
683 fn build_mut(&mut self) -> &mut PerPlatform<BuildSpec> {
684 &mut self.build
685 }
686
687 fn test_mut(&mut self) -> &mut PerPlatform<TestSpec> {
688 &mut self.test
689 }
690
691 fn format(&self) -> &Option<RockspecFormat> {
692 &self.rockspec_format
693 }
694
695 fn source(&self) -> &PerPlatform<RemoteRockSource> {
696 &self.source
697 }
698
699 fn source_mut(&mut self) -> &mut PerPlatform<RemoteRockSource> {
700 &mut self.source
701 }
702
703 fn deploy(&self) -> &PerPlatform<DeploySpec> {
704 &self.deploy
705 }
706
707 fn deploy_mut(&mut self) -> &mut PerPlatform<DeploySpec> {
708 &mut self.deploy
709 }
710
711 fn to_lua_remote_rockspec_string(&self) -> Result<String, Self::Error> {
712 let project_root = &self.internal.project_root;
713 let version = self
714 .internal
715 .version_template
716 .try_generate(project_root, None)?;
717 let starter = format!(
718 r#"
719rockspec_format = "{}"
720package = "{}"
721version = "{}""#,
722 self.rockspec_format
723 .as_ref()
724 .unwrap_or(&RockspecFormat::default()),
725 self.package,
726 &version
727 );
728
729 let mut template = Vec::new();
730
731 if self.description != RockDescription::default() {
732 template.push(self.description.display_lua());
733 }
734
735 if self.supported_platforms != PlatformSupport::default() {
736 template.push(self.supported_platforms.display_lua());
737 }
738
739 {
740 let mut dependencies = self.internal.dependencies.clone().unwrap_or_default();
741 dependencies.insert(
742 0,
743 PackageReq {
744 name: "lua".into(),
745 version_req: self.lua.clone(),
746 }
747 .into(),
748 );
749 template.push(Dependencies(&dependencies).display_lua());
750 }
751
752 let mut build_dependencies = self
753 .internal
754 .build_dependencies
755 .as_ref()
756 .cloned()
757 .unwrap_or_default();
758
759 let build_backend_dependency = self
760 .internal
761 .build
762 .build_type
763 .as_ref()
764 .and_then(|build_type| build_type.luarocks_build_backend());
765
766 if let Some(build_backend_dependency) = build_backend_dependency {
767 build_dependencies.push(build_backend_dependency);
768 }
769
770 if !build_dependencies.is_empty() {
771 template.push(BuildDependencies(&build_dependencies).display_lua());
772 }
773
774 match self.internal.external_dependencies {
775 Some(ref external_dependencies) if !external_dependencies.is_empty() => {
776 template.push(ExternalDependencies(external_dependencies).display_lua());
777 }
778 _ => {}
779 }
780
781 let source =
782 self.internal
783 .source_template
784 .try_generate(project_root, &self.package, &version)?;
785 template.push(source.display_lua());
786
787 template.push(self.internal.build.display_lua());
788
789 let unformatted_code = std::iter::once(starter)
790 .chain(template.into_iter().map(|kv| kv.to_string()))
791 .join("\n\n");
792 let result = match stylua_lib::format_code(
793 &unformatted_code,
794 stylua_lib::Config::default(),
795 None,
796 stylua_lib::OutputVerification::Full,
797 ) {
798 Ok(formatted_code) => formatted_code,
799 Err(_) => unformatted_code,
800 };
801 Ok(result)
802 }
803}
804
805#[derive(Error, Debug)]
806#[error(transparent)]
807pub enum ProjectTomlIntegrityError {
808 LuaRockspecError(#[from] LuaRockspecError),
809 IoError(#[from] io::Error),
810}
811
812impl HasIntegrity for LocalProjectToml {
813 fn hash(&self) -> io::Result<Integrity> {
814 match self.to_lua_rockspec() {
815 Ok(lua_rockspec) => lua_rockspec.hash(),
816 Err(_) => self.internal.hash(),
817 }
818 }
819}
820
821#[derive(Debug)]
822pub struct RemoteProjectToml {
823 local: LocalProjectToml,
824 source: PerPlatform<RemoteRockSource>,
825}
826
827impl RemoteProjectToml {
828 pub fn to_lua_rockspec(&self) -> Result<RemoteLuaRockspec, LuaRockspecError> {
829 RemoteLuaRockspec::new(&self.to_lua_remote_rockspec_string()?)
830 }
831}
832
833impl Rockspec for RemoteProjectToml {
834 type Error = ProjectTomlError;
835
836 fn package(&self) -> &PackageName {
837 self.local.package()
838 }
839
840 fn version(&self) -> &PackageVersion {
841 self.local.version()
842 }
843
844 fn description(&self) -> &RockDescription {
845 self.local.description()
846 }
847
848 fn supported_platforms(&self) -> &PlatformSupport {
849 self.local.supported_platforms()
850 }
851
852 fn lua(&self) -> &PackageVersionReq {
853 self.local.lua()
854 }
855
856 fn dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
857 self.local.dependencies()
858 }
859
860 fn build_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
861 self.local.build_dependencies()
862 }
863
864 fn external_dependencies(&self) -> &PerPlatform<HashMap<String, ExternalDependencySpec>> {
865 self.local.external_dependencies()
866 }
867
868 fn test_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
869 self.local.test_dependencies()
870 }
871
872 fn build(&self) -> &PerPlatform<BuildSpec> {
873 self.local.build()
874 }
875
876 fn test(&self) -> &PerPlatform<TestSpec> {
877 self.local.test()
878 }
879
880 fn build_mut(&mut self) -> &mut PerPlatform<BuildSpec> {
881 self.local.build_mut()
882 }
883
884 fn test_mut(&mut self) -> &mut PerPlatform<TestSpec> {
885 self.local.test_mut()
886 }
887
888 fn format(&self) -> &Option<RockspecFormat> {
889 self.local.format()
890 }
891
892 fn source(&self) -> &PerPlatform<RemoteRockSource> {
893 &self.source
894 }
895
896 fn source_mut(&mut self) -> &mut PerPlatform<RemoteRockSource> {
897 &mut self.source
898 }
899
900 fn deploy(&self) -> &PerPlatform<DeploySpec> {
901 self.local.deploy()
902 }
903
904 fn deploy_mut(&mut self) -> &mut PerPlatform<DeploySpec> {
905 self.local.deploy_mut()
906 }
907
908 fn to_lua_remote_rockspec_string(&self) -> Result<String, Self::Error> {
909 let project_root = &self.local.internal.project_root;
910 let starter = format!(
911 r#"
912rockspec_format = "{}"
913package = "{}"
914version = "{}""#,
915 self.local
916 .rockspec_format
917 .as_ref()
918 .unwrap_or(&RockspecFormat::default()),
919 self.local.package,
920 self.version()
921 );
922
923 let mut template = Vec::new();
924
925 if self.local.description != RockDescription::default() {
926 template.push(self.local.description.display_lua());
927 }
928
929 if self.local.supported_platforms != PlatformSupport::default() {
930 template.push(self.local.supported_platforms.display_lua());
931 }
932
933 {
934 let mut dependencies = self.local.internal.dependencies.clone().unwrap_or_default();
935 dependencies.insert(
936 0,
937 PackageReq {
938 name: "lua".into(),
939 version_req: self.local.lua.clone(),
940 }
941 .into(),
942 );
943 template.push(Dependencies(&dependencies).display_lua());
944 }
945
946 let mut build_dependencies = self
947 .local
948 .internal
949 .build_dependencies
950 .as_ref()
951 .cloned()
952 .unwrap_or_default();
953
954 let build_backend_dependency = self
955 .local
956 .internal
957 .build
958 .build_type
959 .as_ref()
960 .and_then(|build_type| build_type.luarocks_build_backend());
961
962 if let Some(build_backend_dependency) = build_backend_dependency {
963 build_dependencies.push(build_backend_dependency);
964 }
965
966 if !build_dependencies.is_empty() {
967 template.push(BuildDependencies(&build_dependencies).display_lua());
968 }
969
970 match self.local.internal.external_dependencies {
971 Some(ref external_dependencies) if !external_dependencies.is_empty() => {
972 template.push(ExternalDependencies(external_dependencies).display_lua());
973 }
974 _ => {}
975 }
976
977 let source = self.local.internal.source_template.try_generate(
978 project_root,
979 &self.local.internal.package,
980 self.version(),
981 )?;
982 template.push(source.display_lua());
983
984 if let Some(ref deploy) = self.local.internal.deploy {
985 template.push(deploy.display_lua());
986 }
987
988 template.push(self.local.internal.build.display_lua());
989
990 let unformatted_code = std::iter::once(starter)
991 .chain(template.into_iter().map(|kv| kv.to_string()))
992 .join("\n\n");
993 let result = match stylua_lib::format_code(
994 &unformatted_code,
995 stylua_lib::Config::default(),
996 None,
997 stylua_lib::OutputVerification::Full,
998 ) {
999 Ok(formatted_code) => formatted_code,
1000 Err(_) => unformatted_code,
1001 };
1002 Ok(result)
1003 }
1004}
1005
1006impl HasIntegrity for RemoteProjectToml {
1007 fn hash(&self) -> io::Result<Integrity> {
1008 self.to_lua_rockspec()
1009 .map_err(|err| {
1010 io::Error::other(format!(
1011 "unable to convert remote project to Lua rockspec:\n{}",
1012 err
1013 ))
1014 })?
1015 .hash()
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use std::path::PathBuf;
1022
1023 use assert_fs::prelude::{PathChild, PathCopy, PathCreateDir};
1024 use git2::{Repository, RepositoryInitOptions};
1025 use url::Url;
1026
1027 use crate::{
1028 git::{url::RemoteGitUrl, GitSource},
1029 lua_rockspec::{PartialLuaRockspec, PerPlatform, RemoteLuaRockspec, RockSourceSpec},
1030 project::{Project, ProjectRoot},
1031 rockspec::{lua_dependency::LuaDependencySpec, Rockspec},
1032 };
1033
1034 use super::PartialProjectToml;
1035
1036 #[test]
1037 fn project_toml_parsing() {
1038 let project_toml = r#"
1039 package = "my-package"
1040 version = "1.0.0"
1041 lua = "5.3"
1042
1043 rockspec_format = "1.0"
1044
1045 [source]
1046 url = "https://example.com"
1047
1048 [dependencies]
1049 foo = "1.0"
1050 bar = ">=2.0"
1051
1052 [run]
1053 args = ["--foo", "--bar"]
1054
1055 [build]
1056 type = "builtin"
1057 "#;
1058
1059 let project = PartialProjectToml::new(project_toml, ProjectRoot::default()).unwrap();
1060 let _ = project.into_remote(None).unwrap();
1061
1062 let project_toml = r#"
1063 package = "my-package"
1064 version = "1.0.0"
1065 lua = "5.1"
1066
1067 [description]
1068 summary = "A summary"
1069 detailed = "A detailed description"
1070 license = "MIT"
1071 homepage = "https://example.com"
1072 issues_url = "https://example.com/issues"
1073 maintainer = "John Doe"
1074 labels = ["label1", "label2"]
1075
1076 [supported_platforms]
1077 linux = true
1078 windows = false
1079
1080 [dependencies]
1081 foo = "1.0"
1082 bar = ">=2.0"
1083
1084 [build_dependencies]
1085 baz = "1.0"
1086
1087 [external_dependencies.foo]
1088 header = "foo.h"
1089
1090 [external_dependencies.bar]
1091 library = "libbar.so"
1092
1093 [test_dependencies]
1094 busted = "69.420"
1095
1096 [source]
1097 url = "https://example.com"
1098 hash = "sha256-di00mD8txN7rjaVpvxzNbnQsAh6H16zUtJZapH7U4HU="
1099 file = "my-package-1.0.0.tar.gz"
1100 dir = "my-package-1.0.0"
1101
1102 [test]
1103 type = "command"
1104 script = "test.lua"
1105 flags = [ "foo", "bar" ]
1106
1107 [run]
1108 command = "my-command"
1109 args = ["--foo", "--bar"]
1110
1111 [build]
1112 type = "builtin"
1113 "#;
1114
1115 let project = PartialProjectToml::new(project_toml, ProjectRoot::default()).unwrap();
1116 let _ = project.into_remote(None).unwrap();
1117 }
1118
1119 #[test]
1120 fn compare_project_toml_with_rockspec() {
1121 let project_toml = r#"
1122 package = "my-package"
1123 version = "1.0.0"
1124 lua = "5.1"
1125
1126 # For testing, specify a custom rockspec format
1127 # (defaults to 3.0)
1128 rockspec_format = "1.0"
1129
1130 [description]
1131 summary = "A summary"
1132 detailed = "A detailed description"
1133 license = "MIT"
1134 homepage = "https://example.com"
1135 issues_url = "https://example.com/issues"
1136 maintainer = "John Doe"
1137 labels = ["label1", "label2"]
1138
1139 [supported_platforms]
1140 linux = true
1141 windows = false
1142
1143 [dependencies]
1144 foo = "1.0"
1145 bar = ">=2.0"
1146
1147 [build_dependencies]
1148 baz = "1.0"
1149
1150 [external_dependencies.foo]
1151 header = "foo.h"
1152
1153 [external_dependencies.bar]
1154 library = "libbar.so"
1155
1156 [test_dependencies]
1157 busted = "1.0"
1158
1159 [source]
1160 url = "https://example.com"
1161 file = "my-package-1.0.0.tar.gz"
1162 dir = "my-package-1.0.0"
1163
1164 [test]
1165 type = "command"
1166 script = "test.lua"
1167 flags = [ "foo", "bar" ]
1168
1169 [run]
1170 command = "my-command"
1171 args = ["--foo", "--bar"]
1172
1173 [deploy]
1174 wrap_bin_scripts = false
1175
1176 [build]
1177 type = "builtin"
1178
1179 [build.install.lua]
1180 "foo.bar" = "src/bar.lua"
1181
1182 [build.install.lib]
1183 "foo.baz" = "src/baz.c"
1184
1185 [build.install.bin]
1186 "bla" = "src/bla"
1187
1188 [build.install.conf]
1189 "cfg.conf" = "resources/config.conf"
1190 "#;
1191
1192 let expected_rockspec = r#"
1193 rockspec_format = "1.0"
1194 package = "my-package"
1195 version = "1.0.0"
1196
1197 source = {
1198 url = "https://example.com",
1199 file = "my-package-1.0.0.tar.gz",
1200 dir = "my-package-1.0.0",
1201 }
1202
1203 description = {
1204 summary = "A summary",
1205 detailed = "A detailed description",
1206 license = "MIT",
1207 homepage = "https://example.com",
1208 issues_url = "https://example.com/issues",
1209 maintainer = "John Doe",
1210 labels = {"label1", "label2"},
1211 }
1212
1213 supported_platforms = {"linux", "!windows"}
1214
1215 dependencies = {
1216 "lua ==5.1",
1217 "foo ==1.0",
1218 "bar >=2.0",
1219 }
1220
1221 build_dependencies = {
1222 "baz ==1.0",
1223 }
1224
1225 external_dependencies = {
1226 foo = { header = "foo.h" },
1227 bar = { library = "libbar.so" },
1228 }
1229
1230 source = {
1231 url = "https://example.com",
1232 hash = "sha256-di00mD8txN7rjaVpvxzNbnQsAh6H16zUtJZapH7U4HU=",
1233 file = "my-package-1.0.0.tar.gz",
1234 dir = "my-package-1.0.0",
1235 }
1236
1237 test = {
1238 type = "command",
1239 script = "test.lua",
1240 flags = {"foo", "bar"},
1241 }
1242
1243 deploy = {
1244 wrap_bin_scripts = false,
1245 }
1246
1247 build = {
1248 type = "builtin",
1249 install = {
1250 lua = {
1251 ["foo.bar"] = "src/bar.lua",
1252 },
1253 lib = {
1254 ["foo.baz"] = "src/baz.c",
1255 },
1256 bin = {
1257 bla = "src/bla",
1258 },
1259 conf = {
1260 ["cfg.conf"] = "resources/config.conf",
1261 },
1262 },
1263 }
1264 "#;
1265
1266 let expected_rockspec = RemoteLuaRockspec::new(expected_rockspec).unwrap();
1267
1268 let project_toml = PartialProjectToml::new(project_toml, ProjectRoot::default()).unwrap();
1269 let rockspec = project_toml
1270 .into_remote(None)
1271 .unwrap()
1272 .to_lua_rockspec()
1273 .unwrap();
1274
1275 let sorted_package_reqs = |v: &PerPlatform<Vec<LuaDependencySpec>>| {
1276 let mut v = v.current_platform().clone();
1277 v.sort_by(|a, b| a.name().cmp(b.name()));
1278 v
1279 };
1280
1281 assert_eq!(rockspec.package(), expected_rockspec.package());
1282 assert_eq!(rockspec.version(), expected_rockspec.version());
1283 assert_eq!(rockspec.description(), expected_rockspec.description());
1284 assert_eq!(
1285 rockspec.supported_platforms(),
1286 expected_rockspec.supported_platforms()
1287 );
1288 assert_eq!(
1289 sorted_package_reqs(rockspec.dependencies()),
1290 sorted_package_reqs(expected_rockspec.dependencies())
1291 );
1292 assert_eq!(
1293 sorted_package_reqs(rockspec.build_dependencies()),
1294 sorted_package_reqs(expected_rockspec.build_dependencies())
1295 );
1296 assert_eq!(
1297 rockspec.external_dependencies(),
1298 expected_rockspec.external_dependencies()
1299 );
1300 assert_eq!(rockspec.source(), expected_rockspec.source());
1301 assert_eq!(rockspec.build(), expected_rockspec.build());
1302 assert_eq!(rockspec.format(), expected_rockspec.format());
1303 }
1304
1305 #[test]
1306 fn merge_project_toml_with_partial_rockspec() {
1307 let project_toml = r#"
1308 package = "my-package"
1309 version = "1.0.0"
1310 lua = "5.1"
1311
1312 # For testing, specify a custom rockspec format
1313 # (defaults to 3.0)
1314 rockspec_format = "1.0"
1315
1316 [description]
1317 summary = "A summary"
1318 detailed = "A detailed description"
1319 license = "MIT"
1320 homepage = "https://example.com"
1321 issues_url = "https://example.com/issues"
1322 maintainer = "John Doe"
1323 labels = ["label1", "label2"]
1324
1325 [supported_platforms]
1326 linux = true
1327 windows = false
1328
1329 [dependencies]
1330 foo = "1.0"
1331 bar = ">=2.0"
1332
1333 [build_dependencies]
1334 baz = "1.0"
1335
1336 [external_dependencies.foo]
1337 header = "foo.h"
1338
1339 [external_dependencies.bar]
1340 library = "libbar.so"
1341
1342 [test_dependencies]
1343 busted = "1.0"
1344
1345 [source]
1346 url = "https://example.com"
1347 file = "my-package-1.0.0.tar.gz"
1348 dir = "my-package-1.0.0"
1349
1350 [test]
1351 type = "command"
1352 script = "test.lua"
1353 flags = [ "foo", "bar" ]
1354
1355 [run]
1356 command = "my-command"
1357 args = [ "--foo", "--bar" ]
1358
1359 [build]
1360 type = "builtin"
1361 "#;
1362
1363 let mergable_rockspec_content = r#"
1364 rockspec_format = "1.0"
1365 package = "my-package-overwritten"
1366
1367 description = {
1368 summary = "A summary overwritten",
1369 detailed = "A detailed description overwritten",
1370 license = "GPL-2.0",
1371 homepage = "https://example.com/overwritten",
1372 issues_url = "https://example.com/issues/overwritten",
1373 maintainer = "John Doe Overwritten",
1374 labels = {"over", "written"},
1375 }
1376
1377 -- Inverted supported platforms
1378 supported_platforms = {"!linux", "windows"}
1379
1380 dependencies = {
1381 "lua 5.1",
1382 "foo >1.0",
1383 "bar <=2.0",
1384 }
1385
1386 build_dependencies = {
1387 "baz >1.0",
1388 }
1389
1390 external_dependencies = {
1391 foo = { header = "overwritten.h" },
1392 bar = { library = "overwritten.so" },
1393 }
1394
1395 test = {
1396 type = "command",
1397 script = "overwritten.lua",
1398 flags = {"over", "written"},
1399 }
1400
1401 build = {
1402 type = "builtin",
1403 }
1404 "#;
1405
1406 let remote_rockspec_content = format!(
1407 r#"{}
1408 version = "1.0.0"
1409 source = {{
1410 url = "https://example.com",
1411 file = "my-package-1.0.0.tar.gz",
1412 dir = "my-package-1.0.0",
1413 }}
1414 "#,
1415 &mergable_rockspec_content
1416 );
1417
1418 let project_toml = PartialProjectToml::new(project_toml, ProjectRoot::default()).unwrap();
1419 let partial_rockspec = PartialLuaRockspec::new(mergable_rockspec_content).unwrap();
1420 let expected_rockspec = RemoteLuaRockspec::new(&remote_rockspec_content).unwrap();
1421
1422 let merged = project_toml
1423 .merge(partial_rockspec)
1424 .into_remote(None)
1425 .unwrap();
1426
1427 let sorted_package_reqs = |v: &PerPlatform<Vec<LuaDependencySpec>>| {
1428 let mut v = v.current_platform().clone();
1429 v.sort_by(|a, b| a.name().cmp(b.name()));
1430 v
1431 };
1432
1433 assert_eq!(merged.package(), expected_rockspec.package());
1434 assert_eq!(merged.version(), expected_rockspec.version());
1435 assert_eq!(merged.description(), expected_rockspec.description());
1436 assert_eq!(
1437 merged.supported_platforms(),
1438 expected_rockspec.supported_platforms()
1439 );
1440 assert_eq!(
1441 sorted_package_reqs(merged.dependencies()),
1442 sorted_package_reqs(expected_rockspec.dependencies())
1443 );
1444 assert_eq!(
1445 sorted_package_reqs(merged.build_dependencies()),
1446 sorted_package_reqs(expected_rockspec.build_dependencies())
1447 );
1448 assert_eq!(
1449 merged.external_dependencies(),
1450 expected_rockspec.external_dependencies()
1451 );
1452 assert_eq!(merged.source(), expected_rockspec.source());
1453 assert_eq!(merged.build(), expected_rockspec.build());
1454 assert_eq!(merged.format(), expected_rockspec.format());
1455 assert!(merged.local.run().is_some());
1457 }
1458
1459 #[test]
1460 fn project_toml_with_lua_in_dependencies() {
1461 let project_toml = r#"
1462 package = "my-package"
1463 version = "1.0.0"
1464 # lua = ">5.1"
1465
1466 [dependencies]
1467 lua = "5.1" # disallowed
1468
1469 [build]
1470 type = "builtin"
1471 "#;
1472
1473 PartialProjectToml::new(project_toml, ProjectRoot::default())
1474 .unwrap()
1475 .into_local()
1476 .unwrap_err();
1477 }
1478
1479 #[test]
1480 fn project_toml_with_invalid_run_command() {
1481 for command in ["lua", "lua5.1", "lua5.2", "lua5.3", "lua5.4", "luajit"] {
1482 let project_toml = format!(
1483 r#"
1484 package = "my-package"
1485 version = "1.0.0"
1486 lua = "5.1"
1487
1488 [build]
1489 type = "builtin"
1490
1491 [run]
1492 command = "{command}"
1493 "#,
1494 );
1495
1496 PartialProjectToml::new(&project_toml, ProjectRoot::default()).unwrap_err();
1497 }
1498 }
1499
1500 #[test]
1501 fn generate_non_deterministic_git_source() {
1502 let rockspec_content = r#"
1503 package = "test-package"
1504 version = "1.0.0"
1505 lua = ">=5.1"
1506
1507 [source]
1508 url = "git+https://exaple.com/repo.git"
1509
1510 [build]
1511 type = "builtin"
1512 "#;
1513
1514 PartialProjectToml::new(rockspec_content, ProjectRoot::default())
1515 .unwrap()
1516 .into_remote(None)
1517 .unwrap_err();
1518 }
1519
1520 #[test]
1521 fn generate_deterministic_git_source() {
1522 let rockspec_content = r#"
1523 package = "test-package"
1524 version = "1.0.0"
1525 lua = ">=5.1"
1526
1527 [source]
1528 url = "git+https://exaple.com/owner/repo.git"
1529 tag = "v0.1.0"
1530
1531 [build]
1532 type = "builtin"
1533 "#;
1534
1535 PartialProjectToml::new(rockspec_content, ProjectRoot::default())
1536 .unwrap()
1537 .into_remote(None)
1538 .unwrap();
1539 }
1540
1541 fn init_sample_project_repo(temp_dir: &assert_fs::TempDir) -> Repository {
1542 let sample_project: PathBuf = "resources/test/sample-projects/source-template/".into();
1543 temp_dir.copy_from(&sample_project, &["**"]).unwrap();
1544 let repo = Repository::init(temp_dir).unwrap();
1545 let mut opts = RepositoryInitOptions::new();
1546 opts.initial_head("main");
1547 {
1548 let mut config = repo.config().unwrap();
1549 config.set_str("user.name", "name").unwrap();
1550 config.set_str("user.email", "email").unwrap();
1551 let mut index = repo.index().unwrap();
1552 let id = index.write_tree().unwrap();
1553
1554 let tree = repo.find_tree(id).unwrap();
1555 let sig = repo.signature().unwrap();
1556 repo.commit(Some("HEAD"), &sig, &sig, "initial\n\nbody", &tree, &[])
1557 .unwrap();
1558 }
1559 repo
1560 }
1561
1562 fn create_tag(repo: &Repository, name: &str) {
1563 let sig = repo.signature().unwrap();
1564 let id = repo.head().unwrap().target().unwrap();
1565 let obj = repo.find_object(id, None).unwrap();
1566 repo.tag(name, &obj, &sig, "msg", true).unwrap();
1567 }
1568
1569 #[test]
1570 fn test_git_project_generate_dev_source() {
1571 let project_root = assert_fs::TempDir::new().unwrap();
1572 init_sample_project_repo(&project_root);
1573 let project = Project::from_exact(&project_root).unwrap().unwrap();
1574 let remote_project_toml = project.toml().into_remote(None).unwrap();
1575 let source = remote_project_toml.source.current_platform();
1576 let source_spec = &source.source_spec;
1577 assert!(matches!(source_spec, &RockSourceSpec::Git { .. }));
1578 if let RockSourceSpec::Git(GitSource { url, checkout_ref }) = source_spec {
1579 let expected_url: RemoteGitUrl =
1580 "https://github.com/lumen-oss/lux.git".parse().unwrap();
1581 assert_eq!(url, &expected_url);
1582 assert!(checkout_ref.is_some());
1583 }
1584 assert_eq!(source.unpack_dir, Some("lux-dev".into()));
1585 }
1586
1587 #[test]
1588 fn test_git_project_generate_non_semver_tag_source() {
1589 let project_root = assert_fs::TempDir::new().unwrap();
1590 let repo = init_sample_project_repo(&project_root);
1591 let tag_name = "bla";
1592 create_tag(&repo, tag_name);
1593 let project = Project::from_exact(&project_root).unwrap().unwrap();
1594 let remote_project_toml = project.toml().into_remote(None).unwrap();
1595 let source = remote_project_toml.source.current_platform();
1596 let source_spec = &source.source_spec;
1597 assert!(matches!(source_spec, &RockSourceSpec::Git { .. }));
1598 if let RockSourceSpec::Git(GitSource { url, checkout_ref }) = source_spec {
1599 let expected_url: RemoteGitUrl =
1600 "https://github.com/lumen-oss/lux.git".parse().unwrap();
1601 assert_eq!(url, &expected_url);
1602 assert_eq!(checkout_ref, &Some(tag_name.to_string()));
1603 }
1604 assert_eq!(source.unpack_dir, Some("lux-dev".into()));
1605 }
1606
1607 #[test]
1608 fn test_git_project_generate_release_source_tag_with_v_prefix() {
1609 let project_root = assert_fs::TempDir::new().unwrap();
1610 let repo = init_sample_project_repo(&project_root);
1611 let tag_name = "v1.0.0";
1612 create_tag(&repo, "bla");
1613 create_tag(&repo, tag_name);
1614 let project = Project::from_exact(&project_root).unwrap().unwrap();
1615 let remote_project_toml = project.toml().into_remote(None).unwrap();
1616 let source = remote_project_toml.source.current_platform();
1617 let source_spec = &source.source_spec;
1618 assert!(matches!(source_spec, &RockSourceSpec::Url { .. }));
1619 if let RockSourceSpec::Url(url) = source_spec {
1620 let expected_url: Url = "https://github.com/lumen-oss/lux/archive/refs/tags/v1.0.0.zip"
1621 .parse()
1622 .unwrap();
1623 assert_eq!(url, &expected_url);
1624 }
1625 assert_eq!(source.unpack_dir, Some("lux-1.0.0".into()));
1626 }
1627
1628 #[test]
1629 fn test_git_project_generate_release_source_tag_without_v_prefix() {
1630 let project_root = assert_fs::TempDir::new().unwrap();
1631 let repo = init_sample_project_repo(&project_root);
1632 create_tag(&repo, "bla");
1633 let tag_name = "1.0.0";
1634 create_tag(&repo, tag_name);
1635 let project = Project::from_exact(&project_root).unwrap().unwrap();
1636 let remote_project_toml = project.toml().into_remote(None).unwrap();
1637 let source = remote_project_toml.source.current_platform();
1638 let source_spec = &source.source_spec;
1639 assert!(matches!(source_spec, &RockSourceSpec::Url { .. }));
1640 if let RockSourceSpec::Url(url) = source_spec {
1641 let expected_url: Url = "https://github.com/lumen-oss/lux/archive/refs/tags/1.0.0.zip"
1642 .parse()
1643 .unwrap();
1644 assert_eq!(url, &expected_url);
1645 }
1646 assert_eq!(source.unpack_dir, Some("lux-1.0.0".into()));
1647 }
1648
1649 #[test]
1650 fn test_git_project_in_subdirectory() {
1651 let temp_dir = assert_fs::TempDir::new().unwrap();
1652 let sample_project: PathBuf = "resources/test/sample-projects/source-template/".into();
1653 let project_dir = temp_dir.child("lux");
1654 project_dir.create_dir_all().unwrap();
1655 project_dir.copy_from(&sample_project, &["**"]).unwrap();
1656 let repo = Repository::init(&temp_dir).unwrap();
1657 let mut opts = RepositoryInitOptions::new();
1658 opts.initial_head("main");
1659 {
1660 let mut config = repo.config().unwrap();
1661 config.set_str("user.name", "name").unwrap();
1662 config.set_str("user.email", "email").unwrap();
1663 let mut index = repo.index().unwrap();
1664 let id = index.write_tree().unwrap();
1665
1666 let tree = repo.find_tree(id).unwrap();
1667 let sig = repo.signature().unwrap();
1668 repo.commit(Some("HEAD"), &sig, &sig, "initial\n\nbody", &tree, &[])
1669 .unwrap();
1670 }
1671 create_tag(&repo, "bla");
1672 let tag_name = "1.0.0";
1673 create_tag(&repo, tag_name);
1674 let project = Project::from_exact(&project_dir).unwrap().unwrap();
1675 let remote_project_toml = project.toml().into_remote(None).unwrap();
1676 let source = remote_project_toml.source.current_platform();
1677 let source_spec = &source.source_spec;
1678 assert!(matches!(source_spec, &RockSourceSpec::Url { .. }));
1679 if let RockSourceSpec::Url(url) = source_spec {
1680 let expected_url: Url = "https://github.com/lumen-oss/lux/archive/refs/tags/1.0.0.zip"
1681 .parse()
1682 .unwrap();
1683 assert_eq!(url, &expected_url);
1684 }
1685 assert_eq!(source.unpack_dir, Some("lux-1.0.0".into()));
1686 }
1687}