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