forked from drasi-project/drasi-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.rs
More file actions
226 lines (197 loc) · 8.1 KB
/
Copy pathbuild.rs
File metadata and controls
226 lines (197 loc) · 8.1 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
use std::process::Command;
/// Vendor OCI registry and default tag for pre-built native libraries.
const VENDOR_REGISTRY: &str = "ghcr.io";
const VENDOR_REPO_PREFIX: &str = "drasi-project/vendor";
const VENDOR_TAG: &str = "v1";
/// Targets that require vendored native libraries.
const VENDORED_TARGETS: &[&str] = &["x86_64-pc-windows-msvc"];
fn main() {
// Declare env vars and files this build script depends on so Cargo
// reruns it when they change.
println!("cargo:rerun-if-changed=Cargo.lock");
println!("cargo:rerun-if-env-changed=TARGET");
// Track UI dist directory so recompilation picks up new UI assets.
// Ensure the directory exists so rust-embed doesn't fail at compile time.
// This runs unconditionally because build.rs executes before runtime config
// is known — the `enableUi` setting is a runtime concern, not a build concern.
// An empty directory simply means no assets are embedded.
let ui_dist = std::path::Path::new("ui/dist");
println!("cargo:rerun-if-changed=ui/dist");
if !ui_dist.exists() {
if let Err(e) = std::fs::create_dir_all(ui_dist) {
println!(
"cargo:warning=Failed to create ui/dist directory: {e}. \
Embedded UI assets will not be available."
);
}
}
let rustc_version = Command::new("rustc")
.arg("--version")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.unwrap_or_else(|| "unknown".to_string());
println!(
"cargo:rustc-env=DRASI_RUSTC_VERSION={}",
rustc_version.trim()
);
// Auto-download vendored native libraries for targets that need them.
if let (Ok(target), Ok(manifest_dir)) =
(std::env::var("TARGET"), std::env::var("CARGO_MANIFEST_DIR"))
{
let vendor_dir = std::path::PathBuf::from(&manifest_dir)
.join("vendor")
.join(&target);
let vendor_lib_dir = vendor_dir.join("lib");
if VENDORED_TARGETS.contains(&target.as_str()) && !vendor_lib_dir.exists() {
eprintln!(
"cargo:warning=Vendored libs for {target} not found, downloading from OCI registry..."
);
match download_vendor(&target, &vendor_dir) {
Ok(()) => eprintln!("cargo:warning=Vendored libs for {target} downloaded ✓"),
Err(e) => {
panic!("Failed to download vendored libs for {target}: {e}");
}
}
}
if vendor_lib_dir.exists() {
println!(
"cargo:rustc-link-search=native={}",
vendor_lib_dir.display()
);
}
}
// Emit the plugin-sdk version by reading it from the SDK crate's Cargo.toml
let sdk_version = read_dep_version("drasi-plugin-sdk").unwrap_or_else(|| "unknown".into());
println!("cargo:rustc-env=DRASI_PLUGIN_SDK_VERSION={sdk_version}");
// Emit dependency versions for host version info (used by plugin auto-install)
let core_version = read_dep_version("drasi-core").unwrap_or_else(|| "unknown".into());
println!("cargo:rustc-env=DRASI_CORE_VERSION={core_version}");
let lib_version = read_dep_version("drasi-lib").unwrap_or_else(|| "unknown".into());
println!("cargo:rustc-env=DRASI_LIB_VERSION={lib_version}");
// Emit the target triple for runtime platform detection
if let Ok(target) = std::env::var("TARGET") {
println!("cargo:rustc-env=TARGET_TRIPLE={target}");
}
// Emit the Rust sysroot native lib directory so the server can add it to
// LD_LIBRARY_PATH at runtime when loading dylib plugins that depend on libstd.
if let Some(sysroot_lib) = rust_sysroot_native_lib_dir() {
println!("cargo:rustc-env=DRASI_RUST_LIB_DIR={sysroot_lib}");
}
}
fn read_dep_version(crate_name: &str) -> Option<String> {
// Parse the lock file to find the exact resolved version
let lock_contents = std::fs::read_to_string("Cargo.lock").ok()?;
let mut found = false;
for line in lock_contents.lines() {
if line.starts_with("name = ") && line.contains(crate_name) {
found = true;
continue;
}
if found && line.starts_with("version = ") {
return Some(
line.trim_start_matches("version = ")
.trim_matches('"')
.to_string(),
);
}
if found && line.trim().is_empty() {
break;
}
}
None
}
/// Returns the path to the Rust sysroot's native library directory for the
/// current target (e.g. `<sysroot>/lib/rustlib/<target>/lib`).
///
/// This directory contains `libstd-*.so` which dylib plugins depend on.
fn rust_sysroot_native_lib_dir() -> Option<String> {
let sysroot = Command::new("rustc")
.arg("--print")
.arg("sysroot")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())?;
let target = std::env::var("TARGET").ok()?;
let lib_dir = format!("{}/lib/rustlib/{}/lib", sysroot.trim(), target);
if std::path::Path::new(&lib_dir).exists() {
Some(lib_dir)
} else {
None
}
}
/// Download vendored native libraries from the OCI registry.
///
/// Fetches a tar.gz artifact from `ghcr.io/drasi-project/vendor/{target}:{tag}`
/// and extracts it to `vendor/{target}/`.
fn download_vendor(target: &str, vendor_dir: &std::path::Path) -> Result<(), String> {
use flate2::read::GzDecoder;
use sha2::{Digest, Sha256};
use tar::Archive;
let repo = format!("{VENDOR_REPO_PREFIX}/{target}");
let base_url = format!("https://{VENDOR_REGISTRY}/v2/{repo}");
// Get anonymous pull token (vendor packages are public)
let token_url = format!(
"https://{VENDOR_REGISTRY}/token?scope=repository:{repo}:pull&service={VENDOR_REGISTRY}"
);
let token_resp: serde_json::Value = ureq::get(&token_url)
.call()
.map_err(|e| format!("token request failed: {e}"))?
.body_mut()
.read_json()
.map_err(|e| format!("token parse failed: {e}"))?;
let token = token_resp
.get("token")
.and_then(|t| t.as_str())
.ok_or("no token in response")?;
// Fetch manifest
let manifest: serde_json::Value = ureq::get(&format!("{base_url}/manifests/{VENDOR_TAG}"))
.header("Authorization", &format!("Bearer {token}"))
.header("Accept", "application/vnd.oci.image.manifest.v1+json")
.call()
.map_err(|e| format!("manifest fetch failed: {e}"))?
.body_mut()
.read_json()
.map_err(|e| format!("manifest parse failed: {e}"))?;
let layer = manifest
.get("layers")
.and_then(|l| l.as_array())
.and_then(|a| a.first())
.ok_or("no layers in manifest")?;
let digest = layer
.get("digest")
.and_then(|d| d.as_str())
.ok_or("no digest in layer")?;
// Download blob (raise body limit — vendor tarballs are ~25MB+)
let blob = ureq::get(&format!("{base_url}/blobs/{digest}"))
.header("Authorization", &format!("Bearer {token}"))
.call()
.map_err(|e| format!("blob download failed: {e}"))?
.into_body()
.with_config()
.limit(100 * 1024 * 1024) // 100 MB
.read_to_vec()
.map_err(|e| format!("blob read failed: {e}"))?;
// Verify digest
let actual_digest = format!("sha256:{:x}", Sha256::digest(&blob));
if actual_digest != digest {
return Err(format!(
"digest mismatch: expected {digest}, got {actual_digest}"
));
}
// Extract tarball into parent of vendor_dir (tar contains the target name as root)
let parent = vendor_dir.parent().ok_or("vendor_dir has no parent")?;
std::fs::create_dir_all(parent).map_err(|e| format!("mkdir failed: {e}"))?;
let decoder = GzDecoder::new(&blob[..]);
let mut archive = Archive::new(decoder);
archive
.unpack(parent)
.map_err(|e| format!("extract failed: {e}"))?;
if !vendor_dir.join("lib").exists() {
return Err(format!(
"extraction succeeded but {}/lib not found",
vendor_dir.display()
));
}
Ok(())
}