tools for building gleam projects with nix
1// SPDX-FileCopyrightText: 2025 Ruby Iris Juric <ruby@srxl.me>
2//
3// SPDX-License-Identifier: 0BSD
4
5use std::{fs, path::Path};
6
7use miette::{Context, IntoDiagnostic, Result, miette};
8
9use crate::gleam_toml::{self, GleamToml};
10
11pub struct AppSpec {
12 pub name: String,
13 pub contents: String,
14}
15
16fn get_applications(otp_dependencies: &[String], gleam_toml: &GleamToml) -> Vec<String> {
17 let extra_applications = gleam_toml
18 .erlang
19 .as_ref()
20 .and_then(|e| e.extra_applications.clone())
21 .unwrap_or_default();
22 let mut applications = [otp_dependencies, &extra_applications].concat();
23 applications.sort();
24 applications.dedup();
25
26 applications
27}
28
29fn get_modules(out_dir: &str) -> Result<Vec<String>> {
30 std::fs::read_dir(out_dir)
31 .into_diagnostic()?
32 .collect::<Result<Vec<_>, _>>()
33 .into_diagnostic()
34 .wrap_err(format!("While getting files in \"{out_dir}\":"))?
35 .iter()
36 .filter(|path| {
37 path.path()
38 .extension()
39 .map(|ext| ext == "beam")
40 .unwrap_or(false)
41 })
42 .map(|path| {
43 path.path()
44 .file_stem()
45 .and_then(|p| p.to_str())
46 .map(|p| p.to_string())
47 .ok_or(miette!("Non UTF-8 filename in directory"))
48 })
49 .collect::<Result<Vec<_>, _>>()
50}
51
52fn generate_app_spec(otp_dependencies: &[String], out_dir: &str) -> Result<AppSpec> {
53 let gleam_toml = gleam_toml::parse("gleam.toml")?;
54
55 let applications = get_applications(otp_dependencies, &gleam_toml).join(",");
56 let modules = get_modules(out_dir)?.join(",");
57 let start_module = gleam_toml
58 .erlang
59 .and_then(|e| e.application_start_module)
60 .map(|module| format!("{{mod, {{'{module}', []}}}},\n"))
61 .unwrap_or_default();
62
63 Ok(AppSpec {
64 name: format!("{}.app", gleam_toml.name),
65 contents: format!(
66 r#"{{application, {0}, [
67 {{vsn, "{1}"}},
68 {start_module}{{applications, [{applications}]}},
69 {{description, "{2}"}},
70 {{modules, [{modules}]}},
71 {{registered, []}}
72]}}."#,
73 gleam_toml.name,
74 gleam_toml.version,
75 gleam_toml.description.unwrap_or_default()
76 ),
77 })
78}
79
80pub fn generate_app_file(args: &[String]) -> Result<()> {
81 let (out_path, otp_dependencies) = args
82 .split_first()
83 .ok_or(miette!("No output path specified"))?;
84
85 let app_spec = generate_app_spec(otp_dependencies, &out_path)?;
86
87 fs::write(Path::new(&out_path).join(&app_spec.name), app_spec.contents).into_diagnostic()?;
88 println!("Successfully generated {}", app_spec.name);
89
90 Ok(())
91}