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