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