Skip to content

Commit d1be8db

Browse files
committed
Implement simple build command
1 parent 0fb8ce6 commit d1be8db

File tree

7 files changed

+202
-15
lines changed

7 files changed

+202
-15
lines changed

cargo-rustler/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ name = "cargo-rustler"
33
version = "0.1.0"
44
edition = "2021"
55

6+
metadata = { msrv = "1.77" }
7+
8+
69
[dependencies]
10+
cargo_metadata = "0.19.2"
711
clap = { version = "4.5", features = [ "derive" ] }
812
libloading = "0.8"
913
rustler = { version = "0.36.1", path = "../rustler" }
14+
tempfile = "3.19.1"

cargo-rustler/snippets/on_load.erl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-on_load(on_load/0).
2+
3+
on_load() ->
4+
PrivDir = case application:get_application(?MODULE) of
5+
{ok, App} ->
6+
code:priv_dir(App);
7+
undefined ->
8+
case code:which(?MODULE) of
9+
non_existing ->
10+
error(nif_module_not_found);
11+
BeamPath ->
12+
AbsDir = filename:dirname(filename:absname(BeamPath)),
13+
filename:flatten(filename:join([AbsDir, "..", "priv"]))
14+
end
15+
end,
16+
17+
Filename = filename:join([PrivDir, "native", ?NIF_NAME]),
18+
erlang:load_nif(Filename, ?NIF_LOAD_INFO).
19+

cargo-rustler/src/erl_build.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use std::fs::{self, File};
2+
use std::io::Write;
3+
use std::path::Path;
4+
use std::process::Command;
5+
6+
use tempfile::TempDir;
7+
8+
pub fn build(module_name: &str, module: &str, output: &Path) {
9+
let dir = TempDir::new().expect("Failed to create temp dir");
10+
let module_filename = dir.path().join(format!("{module_name}.erl"));
11+
let out_dir = output.join("ebin");
12+
fs::create_dir_all(&out_dir).expect("Failed to create output directory");
13+
14+
// println!("Writing module to: {module_filename:?}\n\n====\n{module}\n===");
15+
16+
{
17+
let mut f = File::create_new(&module_filename).expect("Failed to create temp file");
18+
f.write_all(module.as_bytes())
19+
.expect("Failed to write to temp file");
20+
}
21+
22+
let command = Command::new("erlc")
23+
.arg("-o")
24+
.arg(out_dir)
25+
.arg(&module_filename)
26+
.output()
27+
.expect("Failed to execute erlc");
28+
29+
if !command.status.success() {
30+
let stderr = String::from_utf8_lossy(&command.stdout);
31+
panic!(
32+
"Erlang compilation failed: {}",
33+
stderr.trim_end_matches('\n')
34+
);
35+
}
36+
}

cargo-rustler/src/main.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1+
mod erl_build;
12
#[cfg(unix)]
23
mod fake_symbols;
34
mod nif;
45
mod nif_elixir;
56
mod nif_erlang;
7+
mod rust_build;
68

79
use std::path::PathBuf;
810

911
use clap::{Parser, Subcommand};
12+
use rust_build::BuildArgs;
1013

1114
use crate::nif::NifLibrary;
1215

1316
#[derive(Parser)]
1417
#[command(version, about, long_about = None)]
1518
struct Cli {
1619
#[command(subcommand)]
17-
command: Option<Commands>,
20+
command: Commands,
1821
}
1922

2023
#[derive(clap::ValueEnum, Clone, Default, Debug)]
@@ -33,13 +36,15 @@ enum Commands {
3336
#[arg(short, long, default_value_t, value_enum)]
3437
format: OutputFormat,
3538
},
39+
40+
Build(BuildArgs),
3641
}
3742

3843
fn main() {
3944
let cli = Cli::parse();
4045

4146
match &cli.command {
42-
Some(Commands::Nif { path, format }) => {
47+
Commands::Nif { path, format } => {
4348
let lib = NifLibrary::load(path).unwrap();
4449

4550
match format {
@@ -57,8 +62,20 @@ fn main() {
5762
}
5863
}
5964
}
60-
None => {
61-
panic!("No command given")
65+
Commands::Build(args) => {
66+
println!("Building NIFs in {:?}", args.path);
67+
let paths = rust_build::build(args);
68+
69+
println!("Got NIFs: {paths:?}");
70+
71+
for path in paths {
72+
let lib = NifLibrary::load(&path).unwrap();
73+
let erlang = nif_erlang::LibAsErlang(lib);
74+
let module = format!("{erlang}");
75+
let module_name = erlang.0.name;
76+
77+
erl_build::build(&module_name, &module, &args.out);
78+
}
6279
}
6380
}
6481
}

cargo-rustler/src/nif.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
66
pub struct Nif {
77
pub name: String,
88
pub arity: usize,
9-
pub flags: usize,
9+
pub _flags: usize,
1010
}
1111

1212
pub struct NifLibrary {
@@ -52,7 +52,7 @@ impl NifLibrary {
5252
Some(Nif {
5353
name: CStr::from_ptr(f.name).to_str().ok()?.to_string(),
5454
arity: f.arity as usize,
55-
flags: f.flags as usize,
55+
_flags: f.flags as usize,
5656
})
5757
})
5858
.collect();

cargo-rustler/src/nif_erlang.rs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,54 @@ pub struct LibAsErlang(pub NifLibrary);
55

66
impl fmt::Display for LibAsErlang {
77
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
8-
write!(f, "-module({}).\n\n", string_to_erlang_atom(&self.0.name))?;
8+
let nif_lib = &self.0;
9+
10+
let module_name = string_to_erlang_atom(&nif_lib.name);
11+
12+
let nifs: Vec<_> = nif_lib
13+
.nifs
14+
.iter()
15+
.enumerate()
16+
.map(|(n, nif)| (n, string_to_erlang_atom(&nif.name), nif.arity))
17+
.collect();
18+
19+
let nif_name = nif_lib
20+
.path
21+
.with_extension("")
22+
.file_name()
23+
.unwrap()
24+
.to_string_lossy()
25+
.to_string();
26+
27+
writeln!(f, "-module({module_name}).\n")?;
928
writeln!(f, "-export([")?;
10-
let count = self.0.nifs.len();
11-
for (n, nif) in self.0.nifs.iter().enumerate() {
12-
write!(f, " {}/{}", string_to_erlang_atom(&nif.name), nif.arity)?;
13-
if n == count - 1 {
29+
let count = nifs.len();
30+
for (n, name, arity) in &nifs {
31+
write!(f, " {name}/{arity}")?;
32+
if *n != count - 1 {
33+
write!(f, ",")?;
34+
}
35+
writeln!(f)?;
36+
}
37+
write!(f, "]).\n\n")?;
38+
39+
writeln!(f, "-nifs([")?;
40+
for (n, name, arity) in &nifs {
41+
write!(f, " {name}/{arity}")?;
42+
if *n != count - 1 {
1443
write!(f, ",")?;
1544
}
1645
writeln!(f)?;
1746
}
1847
write!(f, "]).\n\n")?;
1948

20-
// TODO: On Load function
49+
writeln!(f, "-define(NIF_LOAD_INFO, 0).")?;
50+
writeln!(f, "-define(NIF_NAME, \"{}\").", &nif_name)?;
51+
write!(f, "{}", include_str!("../snippets/on_load.erl"))?;
2152

22-
for nif in &self.0.nifs {
23-
write!(f, "{}(", string_to_erlang_atom(&nif.name))?;
24-
for i in 0..nif.arity {
53+
for (_, name, arity) in &nifs {
54+
write!(f, "{name}(")?;
55+
for i in 0..*arity {
2556
if i > 0 {
2657
write!(f, ", ")?;
2758
}

cargo-rustler/src/rust_build.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use std::fs;
2+
use std::io::BufReader;
3+
use std::path::PathBuf;
4+
use std::process::{Command, Stdio};
5+
6+
use cargo_metadata::Message;
7+
use clap::Args;
8+
9+
#[derive(Args)]
10+
pub struct BuildArgs {
11+
#[arg(default_value = ".")]
12+
pub path: PathBuf,
13+
14+
#[arg(default_value = "out")]
15+
pub out: PathBuf,
16+
}
17+
18+
pub fn build(args: &BuildArgs) -> Vec<PathBuf> {
19+
let path = args.path.clone();
20+
21+
// Run `cargo build` in the specified directory
22+
let mut command = Command::new("cargo")
23+
.arg("build")
24+
.arg("--release")
25+
.arg("--message-format=json")
26+
.current_dir(&path)
27+
.stdout(Stdio::piped())
28+
.spawn()
29+
.expect("Failed to execute cargo build");
30+
31+
let reader = BufReader::new(command.stdout.take().unwrap());
32+
33+
let mut artifacts = vec![];
34+
35+
for message in Message::parse_stream(reader) {
36+
if let Message::CompilerArtifact(artifact) = message.unwrap() {
37+
// Check if the artifact is a library
38+
if artifact.target.is_dylib() || artifact.target.is_cdylib() {
39+
artifacts.push(artifact);
40+
}
41+
}
42+
}
43+
44+
dbg!(&artifacts);
45+
46+
let output = command.wait().expect("Couldn't get cargo's exit status");
47+
48+
if !output.success() {
49+
panic!("Cargo build failed with status: {}", output);
50+
}
51+
52+
artifacts
53+
.iter()
54+
.map(|artifact| {
55+
// Ensure the output directory exists
56+
let out_dir = args.out.join("priv/native/");
57+
fs::create_dir_all(&out_dir).expect("Failed to create output directory");
58+
59+
let name = &artifact.target.name;
60+
let filename = &artifact.filenames[0];
61+
let mut ext = filename.extension().unwrap();
62+
// Erlang expects .so on macOS
63+
if ext == "dylib" {
64+
ext = "so";
65+
}
66+
67+
// Stripping the "lib" prefix simplifies the load_nif call as it can be the same on all platforms
68+
let output_name = format!("{}.{}", name, ext);
69+
let destination = out_dir.join(&output_name);
70+
71+
println!("Copying artifact from {:?} to {:?}", filename, destination,);
72+
73+
// Copy the artifact to the output directory
74+
fs::copy(filename, &destination).expect("Failed to copy artifact");
75+
76+
destination
77+
})
78+
.collect()
79+
}

0 commit comments

Comments
 (0)