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