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