Skip to main content

cargo_steel_lib/
lib.rs

1use std::{error::Error, path::PathBuf, process::Command};
2
3use cargo_metadata::{Message, MetadataCommand, Package, TargetKind};
4use std::process::Stdio;
5
6fn package_contains_dependency_on_steel(packages: &[Package]) -> Option<&Package> {
7    packages.iter().find(|x| x.name.as_ref() == "steel-core")
8}
9
10/*
11TODO:
12- Desired output directory / do not copy to native automatically
13- Specify target architecture
14*/
15
16pub fn steel_home() -> Option<PathBuf> {
17    std::env::var("STEEL_HOME")
18        .ok()
19        .map(PathBuf::from)
20        .or_else(|| {
21            let home = env_home::env_home_dir().map(|x| x.join(".steel"));
22
23            if let Some(home) = home {
24                if home.exists() {
25                    return Some(home);
26                }
27
28                #[cfg(target_os = "windows")]
29                {
30                    if let Err(e) = std::fs::create_dir(&home) {
31                        eprintln!("Unable to create steel home directory {:?}: {}", home, e)
32                    }
33
34                    return Some(home);
35                }
36            }
37            #[cfg(not(target_os = "windows"))]
38            {
39                let bd = xdg::BaseDirectories::new();
40                let home = bd.data_home;
41
42                home.map(|mut x: PathBuf| {
43                    x.push("steel");
44
45                    // Just go ahead and initialize the directory, even though
46                    // this is probably not the best place to do this. This almost
47                    // assuredly could be lifted out of this check since failing here
48                    // could cause some annoyance.
49                    if !x.exists() {
50                        if let Err(e) = std::fs::create_dir(&x) {
51                            eprintln!("Unable to create steel home directory {:?}: {}", x, e)
52                        }
53                    }
54
55                    x
56                })
57            }
58
59            #[cfg(target_os = "windows")]
60            None
61        })
62}
63
64pub fn run(args: Vec<String>, env_vars: Vec<(String, String)>) -> Result<bool, Box<dyn Error>> {
65    let mut steel_home = steel_home().expect("Unable to find STEEL_HOME");
66
67    steel_home.push("native");
68
69    if !steel_home.exists() {
70        std::fs::create_dir(&steel_home)?;
71    }
72
73    // --manifest-path
74    let mut metadata_command = MetadataCommand::new();
75
76    for pair in args.chunks(2) {
77        match &pair {
78            &[arg, path] if arg == "--manifest-path" => {
79                metadata_command.manifest_path(path);
80            }
81            _ => {}
82        }
83    }
84
85    let metadata = metadata_command.exec()?;
86
87    let package = match metadata.root_package() {
88        Some(p) => p,
89        None => return Err("cargo steel-lib must be run from within a crate".into()),
90    };
91
92    println!("Attempting to install: {:#?}", package.name);
93
94    if package_contains_dependency_on_steel(&metadata.packages).is_none() {
95        return Err(
96            "Cannot install package as a steel dylib - does not contain a dependency on steel!"
97                .into(),
98        );
99    }
100
101    let mut command = Command::new("cargo")
102        .args([
103            "build",
104            "--release",
105            "--message-format=json-render-diagnostics",
106        ])
107        .args(args)
108        .envs(env_vars)
109        .stdout(Stdio::piped())
110        .spawn()
111        .unwrap();
112
113    let reader = std::io::BufReader::new(command.stdout.take().unwrap());
114
115    let artifacts = cargo_metadata::Message::parse_stream(reader).filter_map(|x| {
116        if let Ok(Message::CompilerArtifact(artifact)) = x {
117            Some(artifact)
118        } else {
119            None
120        }
121    });
122
123    for last in artifacts {
124        if last.target.kind.contains(&TargetKind::CDyLib) {
125            for file in last.filenames {
126                if file.extension() == Some(std::env::consts::DLL_EXTENSION) {
127                    println!("Found a cdylib!");
128                    let prefix = if cfg!(target_os = "windows") {
129                        "lib"
130                    } else {
131                        ""
132                    };
133                    // Replace all hyphens with lowercase in order to match the naming
134                    // conventions
135                    let filename =
136                        format!("{}{}", prefix, file.file_name().unwrap()).replace("-", "_");
137
138                    // If on windows, prefix with "lib" to match unix
139                    steel_home.push(filename);
140
141                    println!("Copying {} to {}", file, &steel_home.to_str().unwrap());
142
143                    if steel_home.exists() {
144                        std::fs::remove_file(&steel_home)
145                            .expect("Unable to delete the existing dylib");
146                    }
147
148                    std::fs::copy(file, &steel_home).unwrap();
149
150                    steel_home.pop();
151                    break;
152                }
153            }
154        }
155    }
156
157    println!("Done!");
158
159    let exit_status = command.wait().expect("Couldn't get cargo's exit status");
160
161    Ok(exit_status.success())
162}