1mod build;
2mod dependency;
3mod deploy;
4mod partial;
5mod platform;
6mod rock_source;
7mod serde_util;
8mod test_spec;
9
10use std::{
11 collections::HashMap, convert::Infallible, fmt::Display, io, path::PathBuf, str::FromStr,
12};
13
14use ottavino::{Closure, Executor, Fuel};
15use ottavino_util::serde::from_value;
16use serde::{de::DeserializeOwned, Deserialize, Serialize};
17
18pub use build::*;
19pub use dependency::*;
20pub use deploy::*;
21pub use partial::*;
22pub use platform::*;
23pub use rock_source::*;
24use ssri::Integrity;
25pub use test_spec::*;
26use thiserror::Error;
27use url::Url;
28
29pub(crate) use serde_util::*;
30
31use crate::{
32 config::{LuaVersion, LuaVersionUnset},
33 hash::HasIntegrity,
34 package::{PackageName, PackageSpec, PackageVersion, PackageVersionReq},
35 project::{project_toml::ProjectTomlError, ProjectRoot},
36 rockspec::{lua_dependency::LuaDependencySpec, Rockspec},
37 ROCKSPEC_FUEL_LIMIT,
38};
39
40#[derive(Error, Debug)]
41pub enum LuaRockspecError {
42 #[error("manifest exceeds computational limit of {ROCKSPEC_FUEL_LIMIT} steps")]
43 FuelLimitExceeded,
44 #[error(
45 r#"could not parse rockspec ({cause}):
46
47 {content}"#
48 )]
49 ExecutionError {
50 #[source]
51 cause: ottavino::ExternError,
52 content: String,
53 },
54 #[error(
55 r#"could not find rockspec field '{field}':
56
57 {content}"#
58 )]
59 LuaKeyNotFound { field: String, content: String },
60 #[error(
61 r#"could not deserialize rockspec field '{field}':
62
63 {content}"#
64 )]
65 LuaKeyDeserializationFailure {
66 field: String,
67 content: String,
68 #[source]
69 cause: ottavino_util::serde::de::Error,
70 },
71 #[error("{}copy_directories cannot contain the rockspec name", ._0.as_ref().map(|p| format!("{p}: ")).unwrap_or_default())]
72 CopyDirectoriesContainRockspecName(Option<String>),
73 #[error(
74 r#"could not parse rockspec ({cause})
75
76 {content}"#
77 )]
78 LuaTable {
79 content: String,
80 #[source]
81 cause: LuaTableError,
82 },
83 #[error("cannot create Lua rockspec with off-spec dependency: {0}")]
84 OffSpecDependency(PackageName),
85 #[error("cannot create Lua rockspec with off-spec build dependency: {0}")]
86 OffSpecBuildDependency(PackageName),
87 #[error("cannot create Lua rockspec with off-spec test dependency: {0}")]
88 OffSpecTestDependency(PackageName),
89 #[error(transparent)]
90 ProjectToml(#[from] ProjectTomlError),
91}
92
93#[derive(Clone, Debug)]
94#[cfg_attr(test, derive(PartialEq))]
95pub struct LocalLuaRockspec {
96 rockspec_format: Option<RockspecFormat>,
98 package: PackageName,
100 version: PackageVersion,
102 description: RockDescription,
103 supported_platforms: PlatformSupport,
104 lua: PackageVersionReq,
106 dependencies: PerPlatform<Vec<LuaDependencySpec>>,
107 build_dependencies: PerPlatform<Vec<LuaDependencySpec>>,
108 external_dependencies: PerPlatform<HashMap<String, ExternalDependencySpec>>,
109 test_dependencies: PerPlatform<Vec<LuaDependencySpec>>,
110 build: PerPlatform<BuildSpec>,
111 source: PerPlatform<RemoteRockSource>,
112 test: PerPlatform<TestSpec>,
113 deploy: PerPlatform<DeploySpec>,
114 raw_content: String,
116}
117
118trait HasRockspecKey<'gc> {
119 fn get_rockspec_key<V: Deserialize<'gc>>(
120 &self,
121 ctx: ottavino::Context<'gc>,
122 key: String,
123 rockspec_content: &str,
124 ) -> Result<V, LuaRockspecError>;
125}
126
127impl<'gc> HasRockspecKey<'gc> for ottavino::Table<'gc> {
128 fn get_rockspec_key<V: Deserialize<'gc>>(
129 &self,
130 ctx: ottavino::Context<'gc>,
131 key: String,
132 rockspec_content: &str,
133 ) -> Result<V, LuaRockspecError> {
134 from_value(self.get_value(ctx, key.clone())).map_err(|cause| {
135 LuaRockspecError::LuaKeyDeserializationFailure {
136 field: key,
137 content: rockspec_content.to_string(),
138 cause,
139 }
140 })
141 }
142}
143
144impl LocalLuaRockspec {
145 pub fn new(
146 rockspec_content: &str,
147 project_root: ProjectRoot,
148 ) -> Result<Self, LuaRockspecError> {
149 let mut lua = ottavino::Lua::core();
150
151 let rockspec = lua
152 .try_enter(|ctx| {
153 let closure = Closure::load(ctx, None, rockspec_content.as_bytes())?;
154
155 let executor = Executor::start(ctx, closure.into(), ());
156
157 let output = executor.step(ctx, &mut Fuel::with(ROCKSPEC_FUEL_LIMIT))?;
158
159 if !output {
160 return Ok(Err(LuaRockspecError::FuelLimitExceeded));
161 }
162
163 let globals = ctx.globals();
164
165 let dependencies: PerPlatform<Vec<LuaDependencySpec>> =
166 globals.get_rockspec_key(ctx, "dependencies".into(), rockspec_content)?;
167
168 let lua_version_req = dependencies
169 .current_platform()
170 .iter()
171 .find(|dep| dep.name().to_string() == "lua")
172 .cloned()
173 .map(|dep| dep.version_req().clone())
174 .unwrap_or(PackageVersionReq::Any);
175
176 fn strip_lua(
177 dependencies: PerPlatform<Vec<LuaDependencySpec>>,
178 ) -> PerPlatform<Vec<LuaDependencySpec>> {
179 dependencies.map(|deps| {
180 deps.iter()
181 .filter(|dep| dep.name().to_string() != "lua")
182 .cloned()
183 .collect()
184 })
185 }
186
187 let build_dependencies: PerPlatform<Vec<LuaDependencySpec>> =
188 globals.get_rockspec_key(ctx, "build_dependencies".into(), rockspec_content)?;
189
190 let test_dependencies: PerPlatform<Vec<LuaDependencySpec>> =
191 globals.get_rockspec_key(ctx, "test_dependencies".into(), rockspec_content)?;
192
193 let source: PerPlatform<RemoteRockSource> = match globals.get_value(ctx, "source") {
194 ottavino::Value::Nil => {
195 PerPlatform::new(RockSourceSpec::File(project_root.to_path_buf()).into())
196 }
197 value => from_value(value).map_err(|cause| {
198 LuaRockspecError::LuaKeyDeserializationFailure {
199 field: "source".into(),
200 content: rockspec_content.to_string(),
201 cause,
202 }
203 })?,
204 };
205
206 let rockspec = LocalLuaRockspec {
207 rockspec_format: globals.get_rockspec_key(
208 ctx,
209 "rockspec_format".into(),
210 rockspec_content,
211 )?,
212 package: globals.get_rockspec_key(ctx, "package".into(), rockspec_content)?,
213 version: globals.get_rockspec_key(ctx, "version".into(), rockspec_content)?,
214 description: parse_lua_tbl_or_default(ctx, "description").map_err(|cause| {
215 LuaRockspecError::LuaTable {
216 content: rockspec_content.to_string(),
217 cause,
218 }
219 })?,
220 supported_platforms: parse_lua_tbl_or_default(ctx, "supported_platforms")
221 .map_err(|cause| LuaRockspecError::LuaTable {
222 content: rockspec_content.to_string(),
223 cause,
224 })?,
225 lua: lua_version_req,
226 dependencies: strip_lua(dependencies),
227 build_dependencies: strip_lua(build_dependencies),
228 test_dependencies: strip_lua(test_dependencies),
229 external_dependencies: globals.get_rockspec_key(
230 ctx,
231 "external_dependencies".into(),
232 rockspec_content,
233 )?,
234 build: globals.get_rockspec_key(ctx, "build".into(), rockspec_content)?,
235 test: globals.get_rockspec_key(ctx, "test".into(), rockspec_content)?,
236 deploy: globals.get_rockspec_key(ctx, "deploy".into(), rockspec_content)?,
237 raw_content: rockspec_content.into(),
238
239 source,
240 };
241
242 Ok(Ok(rockspec))
243 })
244 .map_err(|cause| LuaRockspecError::ExecutionError {
245 content: rockspec_content.to_string(),
246 cause,
247 })??;
248
249 let rockspec_file_name = format!("{}-{}.rockspec", rockspec.package(), rockspec.version());
250
251 if rockspec
252 .build()
253 .default
254 .copy_directories
255 .contains(&PathBuf::from(&rockspec_file_name))
256 {
257 return Err(LuaRockspecError::CopyDirectoriesContainRockspecName(None));
258 }
259
260 for (platform, build_override) in &rockspec.build().per_platform {
261 if build_override
262 .copy_directories
263 .contains(&PathBuf::from(&rockspec_file_name))
264 {
265 return Err(LuaRockspecError::CopyDirectoriesContainRockspecName(Some(
266 platform.to_string(),
267 )));
268 }
269 }
270 Ok(rockspec)
271 }
272}
273
274impl Rockspec for LocalLuaRockspec {
275 type Error = Infallible;
276
277 fn package(&self) -> &PackageName {
278 &self.package
279 }
280
281 fn version(&self) -> &PackageVersion {
282 &self.version
283 }
284
285 fn description(&self) -> &RockDescription {
286 &self.description
287 }
288
289 fn supported_platforms(&self) -> &PlatformSupport {
290 &self.supported_platforms
291 }
292
293 fn lua(&self) -> &PackageVersionReq {
294 &self.lua
295 }
296
297 fn dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
298 &self.dependencies
299 }
300
301 fn build_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
302 &self.build_dependencies
303 }
304
305 fn external_dependencies(&self) -> &PerPlatform<HashMap<String, ExternalDependencySpec>> {
306 &self.external_dependencies
307 }
308
309 fn test_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
310 &self.test_dependencies
311 }
312
313 fn build(&self) -> &PerPlatform<BuildSpec> {
314 &self.build
315 }
316
317 fn test(&self) -> &PerPlatform<TestSpec> {
318 &self.test
319 }
320
321 fn source(&self) -> &PerPlatform<RemoteRockSource> {
322 &self.source
323 }
324
325 fn deploy(&self) -> &PerPlatform<DeploySpec> {
326 &self.deploy
327 }
328
329 fn build_mut(&mut self) -> &mut PerPlatform<BuildSpec> {
330 &mut self.build
331 }
332
333 fn test_mut(&mut self) -> &mut PerPlatform<TestSpec> {
334 &mut self.test
335 }
336
337 fn source_mut(&mut self) -> &mut PerPlatform<RemoteRockSource> {
338 &mut self.source
339 }
340
341 fn deploy_mut(&mut self) -> &mut PerPlatform<DeploySpec> {
342 &mut self.deploy
343 }
344
345 fn format(&self) -> &Option<RockspecFormat> {
346 &self.rockspec_format
347 }
348
349 fn to_lua_remote_rockspec_string(&self) -> Result<String, Self::Error> {
350 Ok(self.raw_content.clone())
351 }
352}
353
354impl HasIntegrity for LocalLuaRockspec {
355 fn hash(&self) -> io::Result<Integrity> {
356 Ok(Integrity::from(&self.raw_content))
357 }
358}
359
360#[derive(Clone, Debug)]
361#[cfg_attr(test, derive(PartialEq))]
362pub struct RemoteLuaRockspec {
363 local: LocalLuaRockspec,
364 source: PerPlatform<RemoteRockSource>,
365}
366
367impl RemoteLuaRockspec {
368 pub fn new(rockspec_content: &str) -> Result<Self, LuaRockspecError> {
369 let mut lua = ottavino::Lua::core();
370
371 lua.try_enter(|ctx| {
372 let closure = Closure::load(ctx, None, rockspec_content.as_bytes())?;
373
374 let executor = Executor::start(ctx, closure.into(), ());
375
376 let output = executor.step(ctx, &mut Fuel::with(ROCKSPEC_FUEL_LIMIT))?;
377
378 if !output {
379 return Ok(Err(LuaRockspecError::FuelLimitExceeded));
380 }
381
382 let globals = ctx.globals();
383
384 let source = globals.get_rockspec_key(ctx, "source".into(), rockspec_content)?;
385
386 Ok(Ok(RemoteLuaRockspec {
387 local: LocalLuaRockspec::new(rockspec_content, ProjectRoot::new())?,
388 source,
389 }))
390 })
391 .map_err(|cause| LuaRockspecError::ExecutionError {
392 content: rockspec_content.to_string(),
393 cause,
394 })?
395 }
396
397 pub fn from_package_and_source_spec(
398 package_spec: PackageSpec,
399 source_spec: RockSourceSpec,
400 ) -> Self {
401 let version = package_spec.version().clone();
402 let rockspec_format = RockspecFormat::default();
403 let raw_content = format!(
404 r#"
405rockspec_format = "{}"
406package = "{}"
407version = "{}"
408{}
409build = {{
410 type = "source"
411}}"#,
412 &rockspec_format,
413 package_spec.name(),
414 &version,
415 &source_spec.display_lua(),
416 );
417
418 let source: RemoteRockSource = source_spec.into();
419
420 let local = LocalLuaRockspec {
421 rockspec_format: Some(rockspec_format),
422 package: package_spec.name().clone(),
423 version,
424 description: RockDescription::default(),
425 supported_platforms: PlatformSupport::default(),
426 lua: PackageVersionReq::Any,
427 dependencies: PerPlatform::default(),
428 build_dependencies: PerPlatform::default(),
429 external_dependencies: PerPlatform::default(),
430 test_dependencies: PerPlatform::default(),
431 build: PerPlatform::new(BuildSpec {
432 build_backend: Some(BuildBackendSpec::Source),
433 install: InstallSpec::default(),
434 copy_directories: Vec::new(),
435 patches: HashMap::new(),
436 }),
437 source: PerPlatform::new(source.clone()),
438 test: PerPlatform::default(),
439 deploy: PerPlatform::default(),
440 raw_content,
441 };
442 Self {
443 local,
444 source: PerPlatform::new(source),
445 }
446 }
447}
448
449impl Rockspec for RemoteLuaRockspec {
450 type Error = Infallible;
451
452 fn package(&self) -> &PackageName {
453 self.local.package()
454 }
455
456 fn version(&self) -> &PackageVersion {
457 self.local.version()
458 }
459
460 fn description(&self) -> &RockDescription {
461 self.local.description()
462 }
463
464 fn supported_platforms(&self) -> &PlatformSupport {
465 self.local.supported_platforms()
466 }
467
468 fn lua(&self) -> &PackageVersionReq {
469 self.local.lua()
470 }
471
472 fn dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
473 self.local.dependencies()
474 }
475
476 fn build_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
477 match self.format() {
478 Some(RockspecFormat::_1_0 | RockspecFormat::_2_0)
481 if self
482 .build()
483 .current_platform()
484 .build_backend
485 .as_ref()
486 .is_some_and(|build_backend| build_backend.can_use_build_dependencies()) =>
487 {
488 self.local.dependencies()
489 }
490 _ => self.local.build_dependencies(),
491 }
492 }
493
494 fn external_dependencies(&self) -> &PerPlatform<HashMap<String, ExternalDependencySpec>> {
495 self.local.external_dependencies()
496 }
497
498 fn test_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
499 self.local.test_dependencies()
500 }
501
502 fn build(&self) -> &PerPlatform<BuildSpec> {
503 self.local.build()
504 }
505
506 fn test(&self) -> &PerPlatform<TestSpec> {
507 self.local.test()
508 }
509
510 fn source(&self) -> &PerPlatform<RemoteRockSource> {
511 &self.source
512 }
513
514 fn deploy(&self) -> &PerPlatform<DeploySpec> {
515 self.local.deploy()
516 }
517
518 fn build_mut(&mut self) -> &mut PerPlatform<BuildSpec> {
519 self.local.build_mut()
520 }
521
522 fn test_mut(&mut self) -> &mut PerPlatform<TestSpec> {
523 self.local.test_mut()
524 }
525
526 fn source_mut(&mut self) -> &mut PerPlatform<RemoteRockSource> {
527 &mut self.source
528 }
529
530 fn deploy_mut(&mut self) -> &mut PerPlatform<DeploySpec> {
531 self.local.deploy_mut()
532 }
533
534 fn format(&self) -> &Option<RockspecFormat> {
535 self.local.format()
536 }
537
538 fn to_lua_remote_rockspec_string(&self) -> Result<String, Self::Error> {
539 Ok(self.local.raw_content.clone())
540 }
541}
542
543#[derive(Error, Debug)]
544pub enum LuaVersionError {
545 #[error(
546 r#"
547The lua version {0} is not supported by {1} version {2}.
548
549HINT: If Lux has auto-detected an incompatible Lua installation,
550 use `--lua-version` to specify the Lua version to use.
551 Valid versions are: '5.1', '5.2', '5.3', '5.4', '5.5', 'jit' and 'jit52'.
552"#
553 )]
554 LuaVersionUnsupported(LuaVersion, PackageName, PackageVersion),
555 #[error(transparent)]
556 LuaVersionUnset(#[from] LuaVersionUnset),
557}
558
559impl HasIntegrity for RemoteLuaRockspec {
560 fn hash(&self) -> io::Result<Integrity> {
561 Ok(Integrity::from(&self.local.raw_content))
562 }
563}
564
565#[derive(Clone, Deserialize, Debug, PartialEq, Default)]
566pub struct RockDescription {
567 pub summary: Option<String>,
569 pub detailed: Option<String>,
571 pub license: Option<String>,
573 #[serde(default, deserialize_with = "deserialize_url")]
575 pub homepage: Option<Url>,
576 pub issues_url: Option<String>,
578 pub maintainer: Option<String>,
580 #[serde(default)]
582 pub labels: Vec<String>,
583}
584
585fn deserialize_url<'de, D>(deserializer: D) -> Result<Option<Url>, D::Error>
586where
587 D: serde::Deserializer<'de>,
588{
589 let s = Option::<String>::deserialize(deserializer)?;
590 s.map(|s| Url::parse(&s).map_err(serde::de::Error::custom))
591 .transpose()
592}
593
594impl DisplayAsLuaKV for RockDescription {
595 fn display_lua(&self) -> DisplayLuaKV {
596 let mut description = Vec::new();
597
598 if let Some(summary) = &self.summary {
599 description.push(DisplayLuaKV {
600 key: "summary".to_string(),
601 value: DisplayLuaValue::String(summary.clone()),
602 })
603 }
604 if let Some(detailed) = &self.detailed {
605 description.push(DisplayLuaKV {
606 key: "detailed".to_string(),
607 value: DisplayLuaValue::String(detailed.clone()),
608 })
609 }
610 if let Some(license) = &self.license {
611 description.push(DisplayLuaKV {
612 key: "license".to_string(),
613 value: DisplayLuaValue::String(license.clone()),
614 })
615 }
616 if let Some(homepage) = &self.homepage {
617 description.push(DisplayLuaKV {
618 key: "homepage".to_string(),
619 value: DisplayLuaValue::String(homepage.to_string()),
620 })
621 }
622 if let Some(issues_url) = &self.issues_url {
623 description.push(DisplayLuaKV {
624 key: "issues_url".to_string(),
625 value: DisplayLuaValue::String(issues_url.clone()),
626 })
627 }
628 if let Some(maintainer) = &self.maintainer {
629 description.push(DisplayLuaKV {
630 key: "maintainer".to_string(),
631 value: DisplayLuaValue::String(maintainer.clone()),
632 })
633 }
634 if !self.labels.is_empty() {
635 description.push(DisplayLuaKV {
636 key: "labels".to_string(),
637 value: DisplayLuaValue::List(
638 self.labels
639 .iter()
640 .cloned()
641 .map(DisplayLuaValue::String)
642 .collect(),
643 ),
644 })
645 }
646
647 DisplayLuaKV {
648 key: "description".to_string(),
649 value: DisplayLuaValue::Table(description),
650 }
651 }
652}
653
654#[derive(Error, Debug)]
655#[error("invalid rockspec format: {0}")]
656pub struct InvalidRockspecFormat(String);
657
658#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
659pub enum RockspecFormat {
660 #[serde(rename = "1.0")]
661 _1_0,
662 #[serde(rename = "2.0")]
663 _2_0,
664 #[serde(rename = "3.0")]
665 #[default]
666 _3_0,
667}
668
669impl FromStr for RockspecFormat {
670 type Err = InvalidRockspecFormat;
671
672 fn from_str(s: &str) -> Result<Self, Self::Err> {
673 match s {
674 "1.0" => Ok(Self::_1_0),
675 "2.0" => Ok(Self::_2_0),
676 "3.0" => Ok(Self::_3_0),
677 txt => Err(InvalidRockspecFormat(txt.to_string())),
678 }
679 }
680}
681
682impl Display for RockspecFormat {
683 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684 match self {
685 Self::_1_0 => write!(f, "1.0"),
686 Self::_2_0 => write!(f, "2.0"),
687 Self::_3_0 => write!(f, "3.0"),
688 }
689 }
690}
691
692#[derive(Error, Debug)]
693pub enum LuaTableError {
694 #[error("could not parse '{variable}'. Expected list, but got {invalid_type}")]
695 ParseError {
696 variable: String,
697 invalid_type: String,
698 },
699 #[error(transparent)]
700 DeserializationError(#[from] ottavino_util::serde::de::Error),
701}
702
703fn parse_lua_tbl_or_default<T>(
704 ctx: ottavino::Context<'_>,
705 lua_var_name: &str,
706) -> Result<T, LuaTableError>
707where
708 T: Default,
709 T: DeserializeOwned,
710{
711 let ret = match ctx.globals().get_value(ctx, lua_var_name.to_string()) {
712 ottavino::Value::Nil => T::default(),
713 value @ ottavino::Value::Table(_) => from_value(value)?,
714 value => Err(LuaTableError::ParseError {
715 variable: lua_var_name.to_string(),
716 invalid_type: value.type_name().to_string(),
717 })?,
718 };
719 Ok(ret)
720}
721
722#[cfg(test)]
723mod tests {
724 use std::path::PathBuf;
725
726 use crate::git::GitSource;
727 use crate::lua_rockspec::PlatformIdentifier;
728 use crate::package::PackageSpec;
729
730 use super::*;
731
732 #[test]
733 pub fn parse_rockspec() {
734 let rockspec_content = "
735 rockspec_format = '1.0'\n
736 package = 'foo'\n
737 version = '1.0.0-1'\n
738 source = {\n
739 url = 'https://github.com/lumen-oss/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
740 }\n
741 "
742 .to_string();
743 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
744 assert_eq!(rockspec.local.rockspec_format, Some(RockspecFormat::_1_0));
745 assert_eq!(rockspec.local.package, "foo".into());
746 assert_eq!(rockspec.local.version, "1.0.0-1".parse().unwrap());
747 assert_eq!(rockspec.local.description, RockDescription::default());
748
749 let rockspec_content = "
750 package = 'bar'\n
751 version = '2.0.0-1'\n
752 description = {}\n
753 source = {\n
754 url = 'https://github.com/lumen-oss/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
755 }\n
756 "
757 .to_string();
758 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
759 assert_eq!(rockspec.local.rockspec_format, None);
760 assert_eq!(rockspec.local.package, "bar".into());
761 assert_eq!(rockspec.local.version, "2.0.0-1".parse().unwrap());
762 assert_eq!(rockspec.local.description, RockDescription::default());
763
764 let rockspec_content = "
765 package = 'rocks.nvim'\n
766 version = '3.0.0-1'\n
767 description = {\n
768 summary = 'some summary',
769 detailed = 'some detailed description',
770 license = 'MIT',
771 homepage = 'https://github.com/lumen-oss/rocks.nvim',
772 issues_url = 'https://github.com/lumen-oss/rocks.nvim/issues',
773 maintainer = 'Lumen Labs',
774 }\n
775 source = {\n
776 url = 'https://github.com/lumen-oss/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
777 }\n
778 "
779 .to_string();
780 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
781 assert_eq!(rockspec.local.rockspec_format, None);
782 assert_eq!(rockspec.local.package, "rocks.nvim".into());
783 assert_eq!(rockspec.local.version, "3.0.0-1".parse().unwrap());
784 let expected_description = RockDescription {
785 summary: Some("some summary".into()),
786 detailed: Some("some detailed description".into()),
787 license: Some("MIT".into()),
788 homepage: Some(Url::parse("https://github.com/lumen-oss/rocks.nvim").unwrap()),
789 issues_url: Some("https://github.com/lumen-oss/rocks.nvim/issues".into()),
790 maintainer: Some("Lumen Labs".into()),
791 labels: Vec::new(),
792 };
793 assert_eq!(rockspec.local.description, expected_description);
794
795 let rockspec_content = "
796 package = 'rocks.nvim'\n
797 version = '3.0.0-1'\n
798 description = {\n
799 summary = 'some summary',
800 detailed = 'some detailed description',
801 license = 'MIT',
802 homepage = 'https://github.com/lumen-oss/rocks.nvim',
803 issues_url = 'https://github.com/lumen-oss/rocks.nvim/issues',
804 maintainer = 'Lumen Labs',
805 labels = {},
806 }\n
807 external_dependencies = { FOO = { library = 'foo' } }\n
808 source = {\n
809 url = 'https://github.com/lumen-oss/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
810 }\n
811 "
812 .to_string();
813 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
814 assert_eq!(rockspec.local.rockspec_format, None);
815 assert_eq!(rockspec.local.package, "rocks.nvim".into());
816 assert_eq!(rockspec.local.version, "3.0.0-1".parse().unwrap());
817 let expected_description = RockDescription {
818 summary: Some("some summary".into()),
819 detailed: Some("some detailed description".into()),
820 license: Some("MIT".into()),
821 homepage: Some(Url::parse("https://github.com/lumen-oss/rocks.nvim").unwrap()),
822 issues_url: Some("https://github.com/lumen-oss/rocks.nvim/issues".into()),
823 maintainer: Some("Lumen Labs".into()),
824 labels: Vec::new(),
825 };
826 assert_eq!(rockspec.local.description, expected_description);
827 assert_eq!(
828 *rockspec
829 .local
830 .external_dependencies
831 .default
832 .get("FOO")
833 .unwrap(),
834 ExternalDependencySpec {
835 library: Some("foo".into()),
836 header: None
837 }
838 );
839
840 let rockspec_content = "
841 package = 'rocks.nvim'\n
842 version = '3.0.0-1'\n
843 description = {\n
844 summary = 'some summary',
845 detailed = 'some detailed description',
846 license = 'MIT',
847 homepage = 'https://github.com/lumen-oss/rocks.nvim',
848 issues_url = 'https://github.com/lumen-oss/rocks.nvim/issues',
849 maintainer = 'Lumen Labs',
850 labels = { 'package management', },
851 }\n
852 supported_platforms = { 'unix', '!windows' }\n
853 dependencies = { 'neorg ~> 6' }\n
854 build_dependencies = { 'foo' }\n
855 external_dependencies = { FOO = { header = 'foo.h' } }\n
856 test_dependencies = { 'busted >= 2.0.0' }\n
857 source = {\n
858 url = 'git+https://github.com/lumen-oss/rocks.nvim',\n
859 hash = 'sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=',\n
860 }\n
861 "
862 .to_string();
863 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
864 assert_eq!(rockspec.local.rockspec_format, None);
865 assert_eq!(rockspec.local.package, "rocks.nvim".into());
866 assert_eq!(rockspec.local.version, "3.0.0-1".parse().unwrap());
867 let expected_description = RockDescription {
868 summary: Some("some summary".into()),
869 detailed: Some("some detailed description".into()),
870 license: Some("MIT".into()),
871 homepage: Some(Url::parse("https://github.com/lumen-oss/rocks.nvim").unwrap()),
872 issues_url: Some("https://github.com/lumen-oss/rocks.nvim/issues".into()),
873 maintainer: Some("Lumen Labs".into()),
874 labels: vec!["package management".into()],
875 };
876 assert_eq!(rockspec.local.description, expected_description);
877 assert!(rockspec
878 .local
879 .supported_platforms
880 .is_supported(&PlatformIdentifier::Unix));
881 assert!(!rockspec
882 .local
883 .supported_platforms
884 .is_supported(&PlatformIdentifier::Windows));
885 let neorg = PackageSpec::parse("neorg".into(), "6.0.0".into()).unwrap();
886 assert!(rockspec
887 .local
888 .dependencies
889 .default
890 .into_iter()
891 .any(|dep| dep.matches(&neorg)));
892 let foo = PackageSpec::parse("foo".into(), "1.0.0".into()).unwrap();
893 assert!(rockspec
894 .local
895 .build_dependencies
896 .default
897 .into_iter()
898 .any(|dep| dep.matches(&foo)));
899 let busted = PackageSpec::parse("busted".into(), "2.2.0".into()).unwrap();
900 assert_eq!(
901 *rockspec
902 .local
903 .external_dependencies
904 .default
905 .get("FOO")
906 .unwrap(),
907 ExternalDependencySpec {
908 header: Some("foo.h".into()),
909 library: None
910 }
911 );
912 assert!(rockspec
913 .local
914 .test_dependencies
915 .default
916 .into_iter()
917 .any(|dep| dep.matches(&busted)));
918
919 let rockspec_content = "
920 rockspec_format = '1.0'\n
921 package = 'foo'\n
922 version = '1.0.0-1'\n
923 source = {\n
924 url = 'git+https://hub.com/owner/example-project/',\n
925 branch = 'bar',\n
926 }\n
927 "
928 .to_string();
929 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
930 assert_eq!(
931 rockspec.local.source.default.source_spec,
932 RockSourceSpec::Git(GitSource {
933 url: "https://hub.com/owner/example-project/".parse().unwrap(),
934 checkout_ref: Some("bar".into())
935 })
936 );
937 assert_eq!(rockspec.local.test, PerPlatform::default());
938 let rockspec_content = "
939 rockspec_format = '1.0'\n
940 package = 'foo'\n
941 version = '1.0.0-1'\n
942 source = {\n
943 url = 'git+https://hub.com/owner/example-project/',\n
944 tag = 'bar',\n
945 }\n
946 "
947 .to_string();
948 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
949 assert_eq!(
950 rockspec.local.source.default.source_spec,
951 RockSourceSpec::Git(GitSource {
952 url: "https://hub.com/owner/example-project/".parse().unwrap(),
953 checkout_ref: Some("bar".into())
954 })
955 );
956 let rockspec_content = "
957 rockspec_format = '1.0'\n
958 package = 'foo'\n
959 version = '1.0.0-1'\n
960 source = {\n
961 url = 'git+https://hub.com/owner/example-project/',\n
962 branch = 'bar',\n
963 tag = 'baz',\n
964 }\n
965 "
966 .to_string();
967 let _rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap_err();
968 let rockspec_content = "
969 rockspec_format = '1.0'\n
970 package = 'foo'\n
971 version = '1.0.0-1'\n
972 source = {\n
973 url = 'git+https://hub.com/owner/example-project/',\n
974 tag = 'bar',\n
975 file = 'foo.tar.gz',\n
976 }\n
977 build = {\n
978 install = {\n
979 conf = {['foo.bar'] = 'config/bar.toml'},\n
980 },\n
981 }\n
982 "
983 .to_string();
984 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
985 assert_eq!(
986 rockspec.local.source.default.archive_name,
987 Some("foo.tar.gz".into())
988 );
989 let foo_bar_path = rockspec
990 .local
991 .build
992 .default
993 .install
994 .conf
995 .get("foo.bar")
996 .unwrap();
997 assert_eq!(*foo_bar_path, PathBuf::from("config/bar.toml"));
998 let rockspec_content = "
999 rockspec_format = '1.0'\n
1000 package = 'foo'\n
1001 version = '1.0.0-1'\n
1002 source = {\n
1003 url = 'git+https://hub.com/example-project/foo.zip',\n
1004 }\n
1005 build = {\n
1006 install = {\n
1007 lua = {\n
1008 'foo.lua',\n
1009 ['foo.bar'] = 'src/bar.lua',\n
1010 },\n
1011 bin = {['foo.bar'] = 'bin/bar'},\n
1012 },\n
1013 }\n
1014 "
1015 .to_string();
1016 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1017 assert!(matches!(
1018 rockspec.local.build.default.build_backend,
1019 Some(BuildBackendSpec::Builtin { .. })
1020 ));
1021 let install_lua_spec = rockspec.local.build.default.install.lua;
1022 let foo_bar_path = install_lua_spec
1023 .get(&LuaModule::from_str("foo.bar").unwrap())
1024 .unwrap();
1025 assert_eq!(*foo_bar_path, PathBuf::from("src/bar.lua"));
1026 let foo_path = install_lua_spec
1027 .get(&LuaModule::from_str("foo").unwrap())
1028 .unwrap();
1029 assert_eq!(*foo_path, PathBuf::from("foo.lua"));
1030 let foo_bar_path = rockspec
1031 .local
1032 .build
1033 .default
1034 .install
1035 .bin
1036 .get("foo.bar")
1037 .unwrap();
1038 assert_eq!(*foo_bar_path, PathBuf::from("bin/bar"));
1039 let rockspec_content = "
1040 rockspec_format = '1.0'\n
1041 package = 'foo'\n
1042 version = '1.0.0-1'\n
1043 source = {\n
1044 url = 'git+https://hub.com/example-project/',\n
1045 }\n
1046 build = {\n
1047 copy_directories = { 'lua' },\n
1048 }\n
1049 "
1050 .to_string();
1051 let _rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap_err();
1052 let rockspec_content = "
1053 rockspec_format = '1.0'\n
1054 package = 'foo'\n
1055 version = '1.0.0-1'\n
1056 source = {\n
1057 url = 'git+https://hub.com/example-project/',\n
1058 }\n
1059 build = {\n
1060 copy_directories = { 'lib' },\n
1061 }\n
1062 "
1063 .to_string();
1064 let _rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap_err();
1065 let rockspec_content = "
1066 rockspec_format = '1.0'\n
1067 package = 'foo'\n
1068 version = '1.0.0-1'\n
1069 source = {\n
1070 url = 'git+https://hub.com/example-project/',\n
1071 }\n
1072 build = {\n
1073 copy_directories = { 'rock_manifest' },\n
1074 }\n
1075 "
1076 .to_string();
1077 let _rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap_err();
1078 let rockspec_content = "
1079 rockspec_format = '1.0'\n
1080 package = 'foo'\n
1081 version = '1.0.0-1'\n
1082 source = {\n
1083 url = 'git+https://hub.com/example-project/foo.zip',\n
1084 dir = 'baz',\n
1085 }\n
1086 build = {\n
1087 type = 'make',\n
1088 install = {\n
1089 lib = {['foo.so'] = 'lib/bar.so'},\n
1090 },\n
1091 copy_directories = {\n
1092 'plugin',\n
1093 'ftplugin',\n
1094 },\n
1095 patches = {\n
1096 ['lua51-support.diff'] = [[\n
1097 --- before.c\n
1098 +++ path/to/after.c\n
1099 ]],\n
1100 },\n
1101 }\n
1102 "
1103 .to_string();
1104 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1105 assert_eq!(rockspec.local.source.default.unpack_dir, Some("baz".into()));
1106 assert_eq!(
1107 rockspec.local.build.default.build_backend,
1108 Some(BuildBackendSpec::Make(MakeBuildSpec::default()))
1109 );
1110 let foo_bar_path = rockspec
1111 .local
1112 .build
1113 .default
1114 .install
1115 .lib
1116 .get("foo.so")
1117 .unwrap();
1118 assert_eq!(*foo_bar_path, PathBuf::from("lib/bar.so"));
1119 let copy_directories = rockspec.local.build.default.copy_directories;
1120 assert_eq!(
1121 copy_directories,
1122 vec![PathBuf::from("plugin"), PathBuf::from("ftplugin")]
1123 );
1124 let patches = rockspec.local.build.default.patches;
1125 let _patch = patches.get(&PathBuf::from("lua51-support.diff")).unwrap();
1126 let rockspec_content = "
1127 rockspec_format = '1.0'\n
1128 package = 'foo'\n
1129 version = '1.0.0-1'\n
1130 source = {\n
1131 url = 'git+https://hub.com/example-project/foo.zip',\n
1132 }\n
1133 build = {\n
1134 type = 'cmake',\n
1135 }\n
1136 "
1137 .to_string();
1138 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1139 assert_eq!(
1140 rockspec.local.build.default.build_backend,
1141 Some(BuildBackendSpec::CMake(CMakeBuildSpec::default()))
1142 );
1143 let rockspec_content = "
1144 rockspec_format = '1.0'\n
1145 package = 'foo'\n
1146 version = '1.0.0-1'\n
1147 source = {\n
1148 url = 'git+https://hub.com/example-project/foo.zip',\n
1149 }\n
1150 build = {\n
1151 type = 'command',\n
1152 build_command = 'foo',\n
1153 install_command = 'bar',\n
1154 }\n
1155 "
1156 .to_string();
1157 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1158 assert!(matches!(
1159 rockspec.local.build.default.build_backend,
1160 Some(BuildBackendSpec::Command(CommandBuildSpec { .. }))
1161 ));
1162 let rockspec_content = "
1163 rockspec_format = '1.0'\n
1164 package = 'foo'\n
1165 version = '1.0.0-1'\n
1166 source = {\n
1167 url = 'git+https://hub.com/example-project/foo.zip',\n
1168 }\n
1169 build = {\n
1170 type = 'command',\n
1171 install_command = 'foo',\n
1172 }\n
1173 "
1174 .to_string();
1175 RemoteLuaRockspec::new(&rockspec_content).unwrap();
1176 let rockspec_content = "
1177 rockspec_format = '1.0'\n
1178 package = 'foo'\n
1179 version = '1.0.0-1'\n
1180 source = {\n
1181 url = 'git+https://hub.com/example-project/foo.zip',\n
1182 }\n
1183 build = {\n
1184 type = 'command',\n
1185 build_command = 'foo',\n
1186 }\n
1187 "
1188 .to_string();
1189 RemoteLuaRockspec::new(&rockspec_content).unwrap();
1190 let rockspec_content = "
1192 package = 'rocks'\n
1193 version = '3.0.0-1'\n
1194 dependencies = {\n
1195 'neorg ~> 6',\n
1196 'toml-edit ~> 1',\n
1197 platforms = {\n
1198 windows = {\n
1199 'neorg = 5.0.0',\n
1200 'toml = 1.0.0',\n
1201 },\n
1202 unix = {\n
1203 'neorg = 5.0.0',\n
1204 },\n
1205 linux = {\n
1206 'toml = 1.0.0',\n
1207 },\n
1208 },\n
1209 }\n
1210 source = {\n
1211 url = 'git+https://github.com/lumen-oss/rocks.nvim',\n
1212 hash = 'sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=',\n
1213 }\n
1214 "
1215 .to_string();
1216 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1217 let neorg_override = PackageSpec::parse("neorg".into(), "5.0.0".into()).unwrap();
1218 let toml_edit = PackageSpec::parse("toml-edit".into(), "1.0.0".into()).unwrap();
1219 let toml = PackageSpec::parse("toml".into(), "1.0.0".into()).unwrap();
1220 assert_eq!(rockspec.local.dependencies.default.len(), 2);
1221 let per_platform = &rockspec.local.dependencies.per_platform;
1222 assert_eq!(
1223 per_platform
1224 .get(&PlatformIdentifier::Windows)
1225 .unwrap()
1226 .iter()
1227 .filter(|dep| dep.matches(&neorg_override)
1228 || dep.matches(&toml_edit)
1229 || dep.matches(&toml))
1230 .count(),
1231 3
1232 );
1233 assert_eq!(
1234 per_platform
1235 .get(&PlatformIdentifier::Unix)
1236 .unwrap()
1237 .iter()
1238 .filter(|dep| dep.matches(&neorg_override)
1239 || dep.matches(&toml_edit)
1240 || dep.matches(&toml))
1241 .count(),
1242 2
1243 );
1244 assert_eq!(
1245 per_platform
1246 .get(&PlatformIdentifier::Linux)
1247 .unwrap()
1248 .iter()
1249 .filter(|dep| dep.matches(&neorg_override)
1250 || dep.matches(&toml_edit)
1251 || dep.matches(&toml))
1252 .count(),
1253 3
1254 );
1255 let rockspec_content = "
1256 package = 'rocks'\n
1257 version = '3.0.0-1'\n
1258 external_dependencies = {\n
1259 FOO = { library = 'foo' },\n
1260 platforms = {\n
1261 windows = {\n
1262 FOO = { library = 'foo.dll' },\n
1263 },\n
1264 unix = {\n
1265 BAR = { header = 'bar.h' },\n
1266 },\n
1267 linux = {\n
1268 FOO = { library = 'foo.so' },\n
1269 },\n
1270 },\n
1271 }\n
1272 source = {\n
1273 url = 'https://github.com/lumen-oss/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
1274 }\n
1275 "
1276 .to_string();
1277 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1278 assert_eq!(
1279 *rockspec
1280 .local
1281 .external_dependencies
1282 .default
1283 .get("FOO")
1284 .unwrap(),
1285 ExternalDependencySpec {
1286 library: Some("foo".into()),
1287 header: None
1288 }
1289 );
1290 let per_platform = rockspec.local.external_dependencies.per_platform;
1291 assert_eq!(
1292 *per_platform
1293 .get(&PlatformIdentifier::Windows)
1294 .and_then(|it| it.get("FOO"))
1295 .unwrap(),
1296 ExternalDependencySpec {
1297 library: Some("foo.dll".into()),
1298 header: None
1299 }
1300 );
1301 assert_eq!(
1302 *per_platform
1303 .get(&PlatformIdentifier::Unix)
1304 .and_then(|it| it.get("FOO"))
1305 .unwrap(),
1306 ExternalDependencySpec {
1307 library: Some("foo".into()),
1308 header: None
1309 }
1310 );
1311 assert_eq!(
1312 *per_platform
1313 .get(&PlatformIdentifier::Unix)
1314 .and_then(|it| it.get("BAR"))
1315 .unwrap(),
1316 ExternalDependencySpec {
1317 header: Some("bar.h".into()),
1318 library: None
1319 }
1320 );
1321 assert_eq!(
1322 *per_platform
1323 .get(&PlatformIdentifier::Linux)
1324 .and_then(|it| it.get("BAR"))
1325 .unwrap(),
1326 ExternalDependencySpec {
1327 header: Some("bar.h".into()),
1328 library: None
1329 }
1330 );
1331 assert_eq!(
1332 *per_platform
1333 .get(&PlatformIdentifier::Linux)
1334 .and_then(|it| it.get("FOO"))
1335 .unwrap(),
1336 ExternalDependencySpec {
1337 library: Some("foo.so".into()),
1338 header: None
1339 }
1340 );
1341 let rockspec_content = "
1342 rockspec_format = '1.0'\n
1343 package = 'foo'\n
1344 version = '1.0.0-1'\n
1345 source = {\n
1346 url = 'git+https://hub.com/example-project/.git',\n
1347 branch = 'bar',\n
1348 platforms = {\n
1349 macosx = {\n
1350 branch = 'mac',\n
1351 },\n
1352 windows = {\n
1353 url = 'git+https://winhub.com/example-project/.git',\n
1354 branch = 'win',\n
1355 },\n
1356 },\n
1357 }\n
1358 "
1359 .to_string();
1360 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1361 assert_eq!(
1362 rockspec.local.source.default.source_spec,
1363 RockSourceSpec::Git(GitSource {
1364 url: "https://hub.com/example-project/.git".parse().unwrap(),
1365 checkout_ref: Some("bar".into())
1366 })
1367 );
1368 assert_eq!(
1369 rockspec
1370 .source
1371 .per_platform
1372 .get(&PlatformIdentifier::MacOSX)
1373 .map(|it| it.source_spec.clone())
1374 .unwrap(),
1375 RockSourceSpec::Git(GitSource {
1376 url: "https://hub.com/example-project/.git".parse().unwrap(),
1377 checkout_ref: Some("mac".into())
1378 })
1379 );
1380 assert_eq!(
1381 rockspec
1382 .source
1383 .per_platform
1384 .get(&PlatformIdentifier::Windows)
1385 .map(|it| it.source_spec.clone())
1386 .unwrap(),
1387 RockSourceSpec::Git(GitSource {
1388 url: "https://winhub.com/example-project/.git".parse().unwrap(),
1389 checkout_ref: Some("win".into())
1390 })
1391 );
1392 let rockspec_content = "
1393 rockspec_format = '1.0'\n
1394 package = 'foo'\n
1395 version = '1.0.0-1'\n
1396 source = { url = 'git+https://hub.com/example-project/foo.zip' }\n
1397 build = {\n
1398 type = 'make',\n
1399 install = {\n
1400 lib = {['foo.bar'] = 'lib/bar.so'},\n
1401 },\n
1402 copy_directories = { 'plugin' },\n
1403 platforms = {\n
1404 unix = {\n
1405 copy_directories = { 'ftplugin' },\n
1406 },\n
1407 linux = {\n
1408 copy_directories = { 'foo' },\n
1409 },\n
1410 },\n
1411 }\n
1412 "
1413 .to_string();
1414 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1415 let per_platform = rockspec.local.build.per_platform;
1416 let unix = per_platform.get(&PlatformIdentifier::Unix).unwrap();
1417 assert_eq!(
1418 unix.copy_directories,
1419 vec![PathBuf::from("plugin"), PathBuf::from("ftplugin")]
1420 );
1421 let linux = per_platform.get(&PlatformIdentifier::Linux).unwrap();
1422 assert_eq!(
1423 linux.copy_directories,
1424 vec![
1425 PathBuf::from("plugin"),
1426 PathBuf::from("foo"),
1427 PathBuf::from("ftplugin")
1428 ]
1429 );
1430 let rockspec_content = "
1431 package = 'foo'\n
1432 version = '1.0.0-1'\n
1433 source = { url = 'git+https://hub.com/example-project/foo.zip' }\n
1434 build = {\n
1435 type = 'builtin',\n
1436 modules = {\n
1437 cjson = {\n
1438 sources = { 'lua_cjson.c', 'strbuf.c', 'fpconv.c' },\n
1439 }\n
1440 },\n
1441 platforms = {\n
1442 win32 = { modules = { cjson = { defines = {\n
1443 'DISABLE_INVALID_NUMBERS', 'USE_INTERNAL_ISINF'\n
1444 } } } }\n
1445 },\n
1446 }\n
1447 "
1448 .to_string();
1449 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1450 let win32 = rockspec.local.build.get(&PlatformIdentifier::Windows);
1451 assert_eq!(
1452 win32.build_backend,
1453 Some(BuildBackendSpec::Builtin(BuiltinBuildSpec {
1454 modules: vec![(
1455 LuaModule::from_str("cjson").unwrap(),
1456 ModuleSpec::ModulePaths(ModulePaths {
1457 sources: vec!["lua_cjson.c".into(), "strbuf.c".into(), "fpconv.c".into()],
1458 libraries: Vec::default(),
1459 defines: vec![
1460 ("DISABLE_INVALID_NUMBERS".into(), None),
1461 ("USE_INTERNAL_ISINF".into(), None)
1462 ],
1463 incdirs: Vec::default(),
1464 libdirs: Vec::default(),
1465 })
1466 )]
1467 .into_iter()
1468 .collect()
1469 }))
1470 );
1471 let rockspec_content = "
1472 rockspec_format = '1.0'\n
1473 package = 'foo'\n
1474 version = '1.0.0-1'\n
1475 deploy = {\n
1476 wrap_bin_scripts = false,\n
1477 }\n
1478 source = { url = 'git+https://hub.com/example-project/foo.zip' }\n
1479 ";
1480 let rockspec = RemoteLuaRockspec::new(rockspec_content).unwrap();
1481 let deploy_spec = &rockspec.deploy().current_platform();
1482 assert!(!deploy_spec.wrap_bin_scripts);
1483 }
1484
1485 #[test]
1486 pub fn parse_scm_rockspec() {
1487 let rockspec_content = "
1488 package = 'foo'\n
1489 version = 'scm-1'\n
1490 source = {\n
1491 url = 'https://github.com/lumen-oss/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
1492 }\n
1493 "
1494 .to_string();
1495 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1496 assert_eq!(rockspec.local.package, "foo".into());
1497 assert_eq!(rockspec.local.version, "scm-1".parse().unwrap());
1498 }
1499
1500 #[test]
1501 pub fn regression_luasystem() {
1502 let rockspec_content =
1503 String::from_utf8(std::fs::read("resources/test/luasystem-0.4.4-1.rockspec").unwrap())
1504 .unwrap();
1505 let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1506 let build_spec = rockspec.local.build.current_platform();
1507 assert!(matches!(
1508 build_spec.build_backend,
1509 Some(BuildBackendSpec::Builtin { .. })
1510 ));
1511 if let Some(BuildBackendSpec::Builtin(BuiltinBuildSpec { modules })) =
1512 &build_spec.build_backend
1513 {
1514 assert_eq!(
1515 modules.get(&LuaModule::from_str("system.init").unwrap()),
1516 Some(&ModuleSpec::SourcePath("system/init.lua".into()))
1517 );
1518 assert_eq!(
1519 modules.get(&LuaModule::from_str("system.core").unwrap()),
1520 Some(&ModuleSpec::ModulePaths(ModulePaths {
1521 sources: vec![
1522 "src/core.c".into(),
1523 "src/compat.c".into(),
1524 "src/time.c".into(),
1525 "src/environment.c".into(),
1526 "src/random.c".into(),
1527 "src/term.c".into(),
1528 "src/bitflags.c".into(),
1529 "src/wcwidth.c".into(),
1530 ],
1531 defines: luasystem_expected_defines(),
1532 libraries: luasystem_expected_libraries(),
1533 incdirs: luasystem_expected_incdirs(),
1534 libdirs: luasystem_expected_libdirs(),
1535 }))
1536 );
1537 }
1538 if let Some(BuildBackendSpec::Builtin(BuiltinBuildSpec { modules })) = &rockspec
1539 .local
1540 .build
1541 .get(&PlatformIdentifier::Windows)
1542 .build_backend
1543 {
1544 if let ModuleSpec::ModulePaths(paths) = modules
1545 .get(&LuaModule::from_str("system.core").unwrap())
1546 .unwrap()
1547 {
1548 assert_eq!(paths.libraries, luasystem_expected_windows_libraries());
1549 };
1550 }
1551 if let Some(BuildBackendSpec::Builtin(BuiltinBuildSpec { modules })) = &rockspec
1552 .local
1553 .build
1554 .get(&PlatformIdentifier::Win32)
1555 .build_backend
1556 {
1557 if let ModuleSpec::ModulePaths(paths) = modules
1558 .get(&LuaModule::from_str("system.core").unwrap())
1559 .unwrap()
1560 {
1561 assert_eq!(paths.libraries, luasystem_expected_windows_libraries());
1562 };
1563 }
1564 }
1565
1566 fn luasystem_expected_defines() -> Vec<(String, Option<String>)> {
1567 if cfg!(target_os = "windows") {
1568 vec![
1569 ("WINVER".into(), Some("0x0600".into())),
1570 ("_WIN32_WINNT".into(), Some("0x0600".into())),
1571 ]
1572 } else {
1573 Vec::default()
1574 }
1575 }
1576
1577 fn luasystem_expected_windows_libraries() -> Vec<PathBuf> {
1578 vec!["advapi32".into(), "winmm".into()]
1579 }
1580 fn luasystem_expected_libraries() -> Vec<PathBuf> {
1581 if cfg!(any(target_os = "linux", target_os = "android")) {
1582 vec!["rt".into()]
1583 } else if cfg!(target_os = "windows") {
1584 luasystem_expected_windows_libraries()
1585 } else {
1586 Vec::default()
1587 }
1588 }
1589
1590 fn luasystem_expected_incdirs() -> Vec<PathBuf> {
1591 Vec::default()
1592 }
1593
1594 fn luasystem_expected_libdirs() -> Vec<PathBuf> {
1595 Vec::default()
1596 }
1597
1598 #[test]
1599 pub fn rust_mlua_rockspec() {
1600 let rockspec_content = "
1601 package = 'foo'\n
1602 version = 'scm-1'\n
1603 source = {\n
1604 url = 'https://github.com/lumen-oss/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
1605 }\n
1606 build = {
1607 type = 'rust-mlua',
1608 modules = {
1609 'foo',
1610 bar = 'baz',
1611 },
1612 target_path = 'path/to/cargo/target/directory',
1613 default_features = false,
1614 include = {
1615 'file.lua',
1616 ['path/to/another/file.lua'] = 'another-file.lua',
1617 },
1618 features = {'extra', 'features'},
1619 }
1620 ";
1621 let rockspec = RemoteLuaRockspec::new(rockspec_content).unwrap();
1622 let build_spec = rockspec.local.build.current_platform();
1623 if let Some(BuildBackendSpec::RustMlua(build_spec)) = build_spec.build_backend.to_owned() {
1624 assert_eq!(
1625 build_spec.modules.get("foo").unwrap(),
1626 &PathBuf::from(format!("libfoo.{}", std::env::consts::DLL_EXTENSION))
1627 );
1628 assert_eq!(
1629 build_spec.modules.get("bar").unwrap(),
1630 &PathBuf::from(format!("libbaz.{}", std::env::consts::DLL_EXTENSION))
1631 );
1632 assert_eq!(
1633 build_spec.include.get(&PathBuf::from("file.lua")).unwrap(),
1634 &PathBuf::from("file.lua")
1635 );
1636 assert_eq!(
1637 build_spec
1638 .include
1639 .get(&PathBuf::from("path/to/another/file.lua"))
1640 .unwrap(),
1641 &PathBuf::from("another-file.lua")
1642 );
1643 } else {
1644 panic!("Expected RustMlua build backend");
1645 }
1646 }
1647
1648 #[tokio::test]
1649 pub async fn regression_ltui() {
1650 let content =
1651 String::from_utf8(std::fs::read("resources/test/ltui-2.8-2.rockspec").unwrap())
1652 .unwrap();
1653 RemoteLuaRockspec::new(&content).unwrap();
1654 }
1655
1656 #[test]
1659 pub fn regression_off_spec_install_binaries() {
1660 let rockspec_content = r#"
1661 package = "WSAPI"
1662 version = "1.7-1"
1663
1664 source = {
1665 url = "git://github.com/keplerproject/wsapi",
1666 tag = "v1.7",
1667 }
1668
1669 build = {
1670 type = "builtin",
1671 modules = {
1672 ["wsapi"] = "src/wsapi.lua",
1673 },
1674 -- Offending Line
1675 install = { bin = { "src/launcher/wsapi.cgi" } }
1676 }
1677 "#;
1678
1679 let rockspec = RemoteLuaRockspec::new(rockspec_content).unwrap();
1680
1681 assert_eq!(
1682 rockspec.build().current_platform().install.bin,
1683 HashMap::from([("wsapi.cgi".into(), PathBuf::from("src/launcher/wsapi.cgi"))])
1684 );
1685 }
1686
1687 #[test]
1688 pub fn regression_external_dependencies() {
1689 let content =
1690 String::from_utf8(std::fs::read("resources/test/luaossl-20220711-0.rockspec").unwrap())
1691 .unwrap();
1692 let rockspec = RemoteLuaRockspec::new(&content).unwrap();
1693 if cfg!(target_family = "unix") {
1694 assert_eq!(
1695 rockspec
1696 .local
1697 .external_dependencies
1698 .current_platform()
1699 .get("OPENSSL")
1700 .unwrap(),
1701 &ExternalDependencySpec {
1702 library: Some("ssl".into()),
1703 header: Some("openssl/ssl.h".into()),
1704 }
1705 );
1706 }
1707 let per_platform = rockspec.local.external_dependencies.per_platform;
1708 assert_eq!(
1709 *per_platform
1710 .get(&PlatformIdentifier::Windows)
1711 .and_then(|it| it.get("OPENSSL"))
1712 .unwrap(),
1713 ExternalDependencySpec {
1714 library: Some("libeay32".into()),
1715 header: Some("openssl/ssl.h".into()),
1716 }
1717 );
1718 }
1719
1720 #[test]
1721 pub fn remote_lua_rockspec_from_package_and_source_spec() {
1722 let package_req = "foo@1.0.5".parse().unwrap();
1723 let source = GitSource {
1724 url: "https://hub.com/owner/example-project.git".parse().unwrap(),
1725 checkout_ref: Some("1.0.5".into()),
1726 };
1727 let source_spec = RockSourceSpec::Git(source);
1728 let rockspec =
1729 RemoteLuaRockspec::from_package_and_source_spec(package_req, source_spec.clone());
1730 let generated_rockspec_str = rockspec.local.raw_content;
1731 let rockspec2 = RemoteLuaRockspec::new(&generated_rockspec_str).unwrap();
1732 assert_eq!(rockspec2.local.package, "foo".into());
1733 assert_eq!(rockspec2.local.version, "1.0.5".parse().unwrap());
1734 assert_eq!(rockspec2.local.source, PerPlatform::new(source_spec.into()));
1735 }
1736
1737 #[test]
1738 pub fn regression_complex_source_field() {
1739 let rockspec_content = r#"
1740 package = "say"
1741 local rock_version = "1.4.1"
1742 local rock_release = "3"
1743 local namespace = "lunarmodules"
1744 local repository = package
1745
1746 version = ("%s-%s"):format(rock_version, rock_release)
1747
1748 source = {
1749 url = ("git+https://github.com/%s/%s.git"):format(namespace, repository),
1750 branch = rock_version == "scm" and "master" or nil,
1751 tag = rock_version ~= "scm" and "v"..rock_version or nil,
1752 }
1753
1754 description = {
1755 summary = "Lua string hashing/indexing library",
1756 }
1757
1758 dependencies = {
1759 "lua >= 5.1",
1760 }
1761
1762 build = {
1763 type = "builtin",
1764 }
1765 "#
1766 .to_string();
1767 RemoteLuaRockspec::new(&rockspec_content).unwrap();
1768 }
1769}