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