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