forked from bottlerocket-os/bottlerocket
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuild.rs
153 lines (133 loc) · 6.84 KB
/
build.rs
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
// src/variant/current is a link to the API model we actually want to build; this build.rs creates
// that symlink based on the VARIANT environment variable, which either comes from the build
// system or the user, if doing a local `cargo build`.
//
// See README.md to understand the symlink setup.
use filetime::{set_symlink_file_times, FileTime};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::env;
use std::fs::{self, File};
use std::io::{self, Write};
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::process;
/// The name of the environment variable that tells us the current variant; we need to rebuild if
/// this changes.
const VARIANT_ENV: &str = "VARIANT";
/// We create a link from 'current' to the variant selected by the environment variable above.
const VARIANT_LINK: &str = "src/variant/current";
/// We create a link for the 'variant' module's mod.rs; this can't be checked into the repo because
/// the `src/variant` directory is a cache mount created by Docker before building a package.
/// This isn't variant-specific, so we can have a fixed link target. The file has top-level
/// definitions that apply to all models, and defines a 'current' submodule (that Rust will be able
/// to find through the 'current' link mentioned above) and re-exports everything in 'current' so
/// that consumers of the model don't have to care what the current variant is.
const MOD_LINK: &str = "src/variant/mod.rs";
const MOD_LINK_TARGET: &str = "../variant_mod.rs";
fn main() {
// Tell cargo when we have to rerun; we always want variant links to be correct, especially
// after changing the variant we're building for.
println!("cargo:rerun-if-env-changed={}", VARIANT_ENV);
println!("cargo:rerun-if-changed={}", VARIANT_LINK);
println!("cargo:rerun-if-changed={}", MOD_LINK);
generate_readme();
link_current_variant();
}
fn link_current_variant() {
// The VARIANT variable is originally BUILDSYS_VARIANT, set in the top-level Makefile.toml,
// and is passed through as VARIANT by the top-level Dockerfile. It represents which OS
// variant we're building, and therefore which API model to use.
let variant = env::var(VARIANT_ENV).unwrap_or_else(|_| {
eprintln!("For local builds, you must set the {} environment variable so we know which API model to build against. Valid values are the directories in variants/, for example \"aws-ecs-1\".", VARIANT_ENV);
process::exit(1);
});
// Point to the source for the requested variant
let variant_target = format!("../{}", variant);
// Make sure requested variant exists
let variant_path = format!("src/{}", variant);
if !Path::new(&variant_path).exists() {
eprintln!("The environment variable {} should refer to a directory under sources/models/src with an API model, but it's set to '{}' which doesn't exist", VARIANT_ENV, variant);
process::exit(1);
}
// Create the symlink for the following `cargo build` to use for its source code
symlink_safe(&variant_target, VARIANT_LINK).unwrap_or_else(|e| {
eprintln!("Failed to create symlink at '{}' pointing to '{}' - we need this to support different API models for different variants. Error: {}", VARIANT_LINK, variant_target, e);
process::exit(1);
});
// Also create the link for mod.rs so Rust can import source from the "current" link
// created above.
symlink_safe(MOD_LINK_TARGET, MOD_LINK).unwrap_or_else(|e| {
eprintln!("Failed to create symlink at '{}' pointing to '{}' - we need this to build a Rust module structure through the `current` link. Error: {}", MOD_LINK, MOD_LINK_TARGET, e);
process::exit(1);
});
// Set the mtime of the links to a fixed time, the epoch. This is because cargo decides
// whether to rerun build.rs based on the "rerun-if-changed" statements printed above and the
// mtime of the files they reference. If the mtime of the file doesn't match the mtime of the
// "output" file in the build directory (which contains the output of the rerun-if prints) then
// it rebuilds. Those times won't match because we don't control when they happen, meaning
// we'd rebuild every time. Setting to a consistent time means we only rebuild when the other
// rerun-if statements apply, the important one being the variant changing.
//
// Note that we still use rerun-if-changed for these links in case someone changes them outside
// of this build.rs. If they really want to get around our system, they'd also need to set the
// mtime to epoch, and then hopefully they know what they're doing.
for link in &[VARIANT_LINK, MOD_LINK] {
// Do our best, but if we fail, rebuilding isn't the end of the world.
// Note: set_symlink_file_times is the only method that operates on the symlink rather than
// its target, and it also updates atime, which we don't care about but isn't harmful.
if let Err(e) = set_symlink_file_times(link, FileTime::zero(), FileTime::zero()) {
eprintln!(
"Warning: unable to set mtime on {}; crate may rebuild unnecessarily: {}",
link, e
);
}
}
}
fn generate_readme() {
// Check for environment variable "SKIP_README". If it is set,
// skip README generation
if env::var_os("SKIP_README").is_some() {
return;
}
let mut lib = File::open("src/lib.rs").unwrap();
let mut template = File::open("README.tpl").unwrap();
let content = cargo_readme::generate_readme(
&PathBuf::from("."), // root
&mut lib, // source
Some(&mut template), // template
// The "add x" arguments don't apply when using a template.
true, // add title
false, // add badges
false, // add license
true, // indent headings
)
.unwrap();
let mut readme = File::create("README.md").unwrap();
readme.write_all(content.as_bytes()).unwrap();
}
// Creates the requested symlink through an atomic swap, so it doesn't matter if the link path
// already exists or not; like --force but fewer worries about reentrancy and retries.
fn symlink_safe<P1, P2>(target: P1, link: P2) -> io::Result<()>
where
P1: AsRef<Path>,
P2: AsRef<Path>,
{
// Create the link at a temporary path.
let temp_link = link.as_ref().with_file_name(format!(".{}", rando()));
symlink(&target, &temp_link)?;
// Swap the temporary link into the real location
if let Err(e) = fs::rename(&temp_link, &link) {
// If we couldn't, for whatever reason, clean up the temporary path and return the error.
let _ = fs::remove_file(&temp_link);
return Err(e);
}
Ok(())
}
// Generates a random ID, affectionately known as a 'rando'.
fn rando() -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(16)
.map(char::from)
.collect()
}