-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbuild_builtin_examples.rs
More file actions
233 lines (193 loc) · 7.49 KB
/
build_builtin_examples.rs
File metadata and controls
233 lines (193 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
use std::ops::Deref;
use clap::builder::PossibleValuesParser;
use clap::builder::Str;
use clap::builder::ValueParser;
use clap::ValueHint;
use flate2::write::GzEncoder;
use flate2::Compression;
use tar::Archive;
use tar::Builder;
const GOURD_INIT_EXAMPLE_FOLDERS: &str = "src/resources/gourd_init_examples";
/// Creates tarballs for examples that should be included in the `gourd`
/// runtime.
///
/// Also returns the updated CLI command while including completions of the
/// example.
fn build_builtin_examples(out_folder: &Path, completions_command: Command) -> Result<Command> {
let _ = fs::create_dir(out_folder);
println!("cargo::rerun-if-changed={GOURD_INIT_EXAMPLE_FOLDERS}");
println!("cargo::rerun-if-changed=src/resources/build_builtin_examples.rs");
let mut possible_ids: Vec<Str> = vec![];
for e in PathBuf::from(GOURD_INIT_EXAMPLE_FOLDERS)
.read_dir()
.context(format!(
"Could not find the '{GOURD_INIT_EXAMPLE_FOLDERS}' directory."
))?
{
let path = e?.path();
if (path.is_dir()) {
println!("Generating example tarball for {path:?}");
let mut tar_path = PathBuf::from(out_folder);
let file_name = path
.file_name()
.context("Could not get the directory name")?;
tar_path.push(file_name);
tar_path.set_extension("tar.gz");
let id_str = Str::from(
&file_name
.to_str()
.context("Invalid characters in example subfolder name")?
.to_owned()
.replace([' ', '_'], "-"),
);
if (id_str.contains('.')) {
println!(
"cargo:warning=The '.' character is invalid for a folder name \
in \"resources/gourd_init_examples\": {id_str}."
);
continue;
}
if possible_ids.contains(&id_str) {
println!(
"cargo:warning=There are two subfolders matching the \"{id_str}\" example ID."
);
continue;
}
println!("The output file is {tar_path:?}");
generate_example_tarball(&path, &tar_path)?;
possible_ids.push(id_str);
}
}
Ok(
completions_command.mut_subcommand("init", |init_subcommand| {
init_subcommand.mut_arg("example", |example_arg| {
example_arg.value_parser(ValueParser::from(possible_ids))
})
}),
)
}
/// Creates a `gourd init` example at the specified path.
///
/// This function accepts a path to a subfolder containing a valid `gourd.toml`
/// and other experiment resources.
/// It compresses the folder contents into a `.tar.gz` archive (excluding the
/// folder itself), while also compiling `.rs` filesinto platform-native
/// binaries. The archive will be created at the provided 'tarball' path.
fn generate_example_tarball(subfolder_path: &Path, tarball_output_path: &Path) -> Result<()> {
if !subfolder_path.is_dir() {
bail!("The subfolder path {subfolder_path:?} is not a directory.");
}
if !tarball_output_path
.parent()
.expect("The tarball output path has no parent.")
.is_dir()
{
bail!("The tarball output path {tarball_output_path:?} is not a directory.");
}
let mut file = File::create(tarball_output_path)?;
let mut gz = GzEncoder::new(file, Compression::default());
let mut tar = tar::Builder::new(gz);
println!("Writing the folder contents to {tarball_output_path:?}");
append_files_to_tarball(&mut tar, PathBuf::from("."), subfolder_path)?;
println!("Finalizing the archive.");
tar.finish();
Ok(())
}
/// Appends experiment files to the given tarball builder.
///
/// This function recursively searches the provided directory, adding all
/// normal files and a `rustc`-compiled version of each `.rs` file to the tar
/// archive.
fn append_files_to_tarball(
tar: &mut Builder<GzEncoder<File>>,
path_in_subfolder: PathBuf,
subfolder_root: &Path,
) -> Result<()> {
let mut fs_path = subfolder_root.to_path_buf();
fs_path.push(&path_in_subfolder);
if fs_path.is_file() {
println!("Inclding file: {fs_path:?}");
if is_a_rust_file(&fs_path) {
compile_rust_file(&fs_path)
.context(format!("Could not compile a Rust example: {fs_path:?}"))?;
let compiled_fs_path = &fs_path.with_extension("");
let compiled_subfolder_path = &path_in_subfolder.with_extension("");
tar.append_path_with_name(compiled_fs_path, compiled_subfolder_path)
.context(format!(
"Could not add a compiled Rust file to the tarball: {compiled_fs_path:?}"
))?;
fs::remove_file(compiled_fs_path).context(format!(
"Could not remove the compiled file: {compiled_fs_path:?}"
));
} else {
tar.append_path_with_name(&fs_path, &path_in_subfolder)
.context(format!("Could not add a file to the tarball: {fs_path:?}"))?;
}
Ok(())
} else if fs_path.is_dir() {
for e in fs::read_dir(&fs_path)
.context(format!("Could not read the directory at {fs_path:?}"))?
{
let entry_name = e
.context(format!(
"Could not unwrap directory entry in entry {fs_path:?}"
))?
.file_name();
let mut new_path_in_subfolder = path_in_subfolder.clone();
new_path_in_subfolder.push(entry_name);
append_files_to_tarball(tar, new_path_in_subfolder, subfolder_root)?
}
Ok(())
} else {
Ok(())
}
}
/// Checks whether the path is a Rust file.
///
/// Returns true if the provided path links to a file,
/// and the file has the `.rs` extension.
fn is_a_rust_file(path: &Path) -> bool {
path.is_file() && path.extension().is_some_and(|ext| ext == "rs")
}
/// Returns the path of the file after it has been compiled with `rustc`.
fn compile_rust_file(path: &Path) -> Result<()> {
let canon_path =
canonicalize(path).context(format!("Could not canonicalize the path: {path:?}"))?;
let str_path = canon_path.to_str().ok_or_else(|| anyhow!(":("))?;
let compiled_path = canon_path.with_extension("");
let str_compiled_path = compiled_path.to_str().ok_or_else(|| anyhow!(":("))?;
// on linux, we statically link the examples to musl in order to prevent glibc
// compatibility errors.
let target_triple = format!("{}-unknown-linux-musl", std::env::consts::ARCH);
let compile_args = if std::env::consts::OS == "linux" {
std::process::Command::new("rustup")
.arg("target")
.arg("add")
.arg(&target_triple)
.spawn()?
.wait()?;
vec![
"--target",
&target_triple,
"-C",
"target-feature=+crt-static",
"-O",
str_path,
"-o",
str_compiled_path,
]
} else {
vec!["-O", str_path, "-o", str_compiled_path]
};
let output = run_command(
"rustc",
&compile_args,
Some(canon_path.parent().ok_or_else(|| anyhow!(":("))?.to_owned()),
)?;
if !compiled_path.is_file() {
Err(anyhow!("Rustc output: {output}")
.context(format!("No rust file generated at {compiled_path:?}")))
} else {
Ok(())
}
}