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