1#![allow(clippy::unwrap_used)]
5
6const TOOLCHAIN: &str = "nightly-2024-11-01";
8
9use jam_program_blob::{ConventionalMetadata, CrateInfo, ProgramBlob};
10use scale::Encode;
11use std::{
12 fmt::Display,
13 fs,
14 path::{Path, PathBuf},
15 process::Command,
16 sync::OnceLock,
17};
18
19pub enum BlobType {
20 Service,
21 Authorizer,
22 CoreVm,
23}
24impl Display for BlobType {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 Self::Service => write!(f, "Service"),
28 Self::Authorizer => write!(f, "Authorizer"),
29 Self::CoreVm => write!(f, "CoreVm"),
30 }
31 }
32}
33
34impl BlobType {
35 pub fn dispatch_table(&self) -> Vec<Vec<u8>> {
36 match self {
37 Self::Service =>
38 vec![b"refine_ext".into(), b"accumulate_ext".into(), b"on_transfer_ext".into()],
39 Self::Authorizer => vec![b"is_authorized_ext".into()],
40 Self::CoreVm => vec![b"main".into()],
41 }
42 }
43}
44
45pub enum ProfileType {
46 Debug,
47 Release,
48 Other(&'static str),
49}
50impl ProfileType {
51 fn as_str(&self) -> &'static str {
52 match self {
53 ProfileType::Debug => "debug",
54 ProfileType::Release => "release",
55 ProfileType::Other(s) => s,
56 }
57 }
58 fn to_arg(&self) -> String {
59 match self {
60 ProfileType::Debug => "--debug".into(),
61 ProfileType::Release => "--release".into(),
62 ProfileType::Other(s) => format!("--profile={s}"),
63 }
64 }
65}
66
67fn build_pvm_blob_in_build_script(crate_dir: &Path, blob_type: BlobType) {
68 let out_dir: PathBuf = std::env::var("OUT_DIR").expect("No OUT_DIR").into();
69 println!("cargo:rerun-if-env-changed=SKIP_PVM_BUILDS");
70 if std::env::var_os("SKIP_PVM_BUILDS").is_some() {
71 let output_file = out_dir.join(format!("{}.jam", &get_crate_info(crate_dir).name));
72 fs::write(&output_file, []).expect("error creating dummy .jam blob");
73 } else {
74 println!("cargo:rerun-if-changed={}", crate_dir.to_str().unwrap());
75 build_pvm_blob(crate_dir, blob_type, &out_dir, false, ProfileType::Release);
76 }
77}
78
79pub fn build_service(crate_dir: &Path) {
85 build_pvm_blob_in_build_script(crate_dir, BlobType::Service);
86}
87
88pub fn build_authorizer(crate_dir: &Path) {
94 build_pvm_blob_in_build_script(crate_dir, BlobType::Authorizer);
95}
96
97pub fn build_core_vm(crate_dir: &Path) {
103 build_pvm_blob_in_build_script(crate_dir, BlobType::CoreVm);
104}
105
106fn get_crate_info(crate_dir: &Path) -> CrateInfo {
107 let manifest = Command::new("cargo")
108 .current_dir(crate_dir)
109 .arg("read-manifest")
110 .output()
111 .unwrap()
112 .stdout;
113 let man = serde_json::from_slice::<serde_json::Value>(&manifest).unwrap();
114 let name = man.get("name").unwrap().as_str().unwrap().to_string();
115 let version = man.get("version").unwrap().as_str().unwrap().to_string();
116 let license = man.get("license").unwrap().as_str().unwrap().to_string();
117 let authors = man
118 .get("authors")
119 .unwrap()
120 .as_array()
121 .unwrap()
122 .iter()
123 .map(|x| x.as_str().unwrap().to_owned())
124 .collect::<Vec<String>>();
125 CrateInfo { name, version, license, authors }
126}
127
128pub fn build_pvm_blob(
132 crate_dir: &Path,
133 blob_type: BlobType,
134 out_dir: &Path,
135 install_rustc: bool,
136 profile: ProfileType,
137) -> (String, PathBuf) {
138 let (target_name, target_json_path) =
139 ("riscv64emac-unknown-none-polkavm", polkavm_linker::target_json_64_path().unwrap());
140
141 println!("🪤 PVM module type: {}", blob_type);
142 println!("🎯 Target name: {}", target_name);
143
144 let rustup_installed = if Command::new("rustup").output().is_ok() {
145 let output = Command::new("rustup")
146 .args(["component", "list", "--toolchain", TOOLCHAIN, "--installed"])
147 .output()
148 .unwrap_or_else(|_| {
149 panic!(
150 "Failed to execute `rustup component list --toolchain {TOOLCHAIN} --installed`.\n\
151 Please install `rustup` to continue.",
152 )
153 });
154
155 if !output.status.success() ||
156 !output.stdout.split(|x| *x == b'\n').any(|x| x[..] == b"rust-src"[..])
157 {
158 if install_rustc {
159 println!("Installing rustc dependencies...");
160 let mut child = Command::new("rustup")
161 .args(["toolchain", "install", TOOLCHAIN, "-c", "rust-src"])
162 .stdout(std::process::Stdio::inherit())
163 .stderr(std::process::Stdio::inherit())
164 .spawn()
165 .unwrap_or_else(|_| {
166 panic!(
167 "Failed to execute `rustup toolchain install {TOOLCHAIN} -c rust-src`.\n\
168 Please install `rustup` to continue."
169 )
170 });
171 if !child.wait().expect("Failed to execute rustup process").success() {
172 panic!("Failed to install `rust-src` component of {TOOLCHAIN}.");
173 }
174 } else {
175 panic!("`rust-src` component of {TOOLCHAIN} is required to build the PVM binary.",);
176 }
177 }
178
179 true
180 } else {
181 println!("ℹ️ `rustup` not installed, here be dragons. Continuing build process...");
182
183 false
184 };
185
186 let info = get_crate_info(crate_dir);
187 println!("📦 Crate name: {}", info.name);
188 println!("🏷️ Build profile: {}", profile.as_str());
189
190 let mut child = Command::new("cargo");
191
192 child
193 .current_dir(crate_dir)
194 .env_clear()
195 .env("PATH", std::env::var("PATH").unwrap())
196 .env("RUSTFLAGS", "-C panic=abort")
197 .env("CARGO_TARGET_DIR", out_dir)
198 .env("RUSTC_BOOTSTRAP", "1");
200
201 if rustup_installed {
202 child.arg(format!("+{TOOLCHAIN}"));
203 }
204
205 child
206 .args(["build", "-Z", "build-std=core,alloc"])
207 .arg(profile.to_arg())
208 .arg("--target")
209 .arg(target_json_path)
210 .arg("--features")
211 .arg(if cfg!(feature = "tiny") { "tiny" } else { "" });
212
213 if let Some(client) = get_job_server_client() {
216 client.configure(&mut child);
217 }
218
219 let mut child = child.spawn().expect("Failed to execute cargo process");
220 let status = child.wait().expect("Failed to execute cargo process");
221
222 if !status.success() {
223 eprintln!("Failed to build RISC-V ELF due to cargo execution error");
224 std::process::exit(1);
225 }
226
227 println!("Converting RISC-V ELF to PVM blob...");
229 let mut config = polkavm_linker::Config::default();
230 config.set_strip(true);
231 config.set_dispatch_table(blob_type.dispatch_table());
232 let input_path = &out_dir.join(target_name).join(profile.as_str()).join(&info.name);
233 let orig =
234 fs::read(input_path).unwrap_or_else(|e| panic!("Failed to read {:?} :{:?}", input_path, e));
235 let linked = polkavm_linker::program_from_elf(config, orig.as_ref())
236 .expect("Failed to link polkavm program:");
237
238 let output_path_polkavm = &out_dir.join(format!("{}.polkavm", &info.name));
240 fs::write(output_path_polkavm, &linked).expect("Error writing resulting binary");
241
242 let parts = polkavm_linker::ProgramParts::from_bytes(linked.into())
243 .expect("failed to deserialize linked PolkaVM program");
244
245 let rw_data_padding = parts.rw_data_size as usize - parts.rw_data.len();
246 let rw_data_padding_pages = rw_data_padding / 4096;
247
248 let mut ro_data = parts.ro_data.to_vec();
249 let mut rw_data = parts.rw_data.to_vec();
250
251 ro_data.resize(parts.ro_data_size as usize, 0);
252 rw_data.resize(ro_data.len() + parts.rw_data_size as usize - rw_data_padding_pages * 4096, 0);
253
254 let name = info.name.clone();
255
256 let rw_data_padding_pages: u16 =
257 rw_data_padding_pages.try_into().expect("the RW data section is too big");
258 let blob_jam = ProgramBlob {
259 metadata: ConventionalMetadata::Info(info).encode().into(),
260 ro_data: ro_data.into(),
261 rw_data: (&parts.rw_data[..]).into(),
262 code_blob: (&parts.code_and_jump_table[..]).into(),
263 rw_data_padding_pages,
264 stack_size: parts.stack_size,
265 };
266
267 let output_file = out_dir.join(format!("{}.jam", &name));
268 fs::write(&output_file, blob_jam.to_vec().expect("error serializing the .jam blob"))
269 .expect("error writing the .jam blob");
270
271 (name, output_file)
272}
273
274fn get_job_server_client() -> Option<&'static jobserver::Client> {
275 static CLIENT: OnceLock<Option<jobserver::Client>> = OnceLock::new();
276 CLIENT.get_or_init(|| unsafe { jobserver::Client::from_env() }).as_ref()
277}
278
279#[macro_export]
280macro_rules! pvm_binary {
281 ($name:literal) => {
282 include_bytes!(concat!(env!("OUT_DIR"), "/", $name, ".jam"))
283 };
284}