1#![allow(clippy::unwrap_used)]
5
6const TOOLCHAIN: &str = "1.90.0";
8
9use crate::manifest;
10use jam_codec::Encode;
11use jam_program_blob::{ConventionalMetadata, CoreVmProgramBlob, ProgramBlob};
12use polkavm_linker::ProgramParts;
13use std::{
14 borrow::Cow,
15 fmt::Display,
16 fs,
17 path::{Path, PathBuf},
18 process::Command,
19};
20
21pub enum BlobType {
23 Service,
25 Authorizer,
27 CoreVmGuest,
29}
30impl Display for BlobType {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 Self::Service => write!(f, "Service"),
34 Self::Authorizer => write!(f, "Authorizer"),
35 Self::CoreVmGuest => write!(f, "CoreVmGuest"),
36 }
37 }
38}
39
40impl BlobType {
41 pub fn dispatch_table(&self) -> Vec<Vec<u8>> {
43 match self {
44 Self::Service => vec![b"jade_refine".into(), b"jade_accumulate".into()],
45 Self::Authorizer => vec![b"jade_is_authorized".into()],
46 Self::CoreVmGuest => vec![b"main".into()],
47 }
48 }
49
50 fn output_file(&self, out_dir: &Path, crate_name: &str) -> PathBuf {
51 let suffix = match self {
52 Self::Service | Self::Authorizer => "jam",
53 Self::CoreVmGuest => "corevm",
54 };
55 out_dir.join(format!("{crate_name}.{suffix}"))
56 }
57}
58
59pub enum ProfileType {
61 Debug,
63 Release,
65 Other(&'static str),
67}
68impl ProfileType {
69 fn as_str(&self) -> &'static str {
70 match self {
71 ProfileType::Debug => "debug",
72 ProfileType::Release => "release",
73 ProfileType::Other(s) => s,
74 }
75 }
76 fn to_arg(&self) -> String {
77 match self {
78 ProfileType::Debug => "--debug".into(),
79 ProfileType::Release => "--release".into(),
80 ProfileType::Other(s) => format!("--profile={s}"),
81 }
82 }
83}
84
85pub fn build_pvm_blob(
89 crate_dir: &Path,
90 blob_type: BlobType,
91 out_dir: &Path,
92 install_rustc: bool,
93 profile: ProfileType,
94) -> (String, PathBuf) {
95 let (target_name, target_json_path) = (
96 "riscv64emac-unknown-none-polkavm",
97 polkavm_linker::target_json_64_path().unwrap(),
98 );
99
100 println!("🪤 PVM module type: {blob_type}");
101 println!("🎯 Target name: {target_name}");
102
103 let rustup_installed = if Command::new("rustup").output().is_ok() {
104 let output = Command::new("rustup")
105 .args(["component", "list", "--toolchain", TOOLCHAIN, "--installed"])
106 .output()
107 .unwrap_or_else(|_| {
108 panic!(
109 "Failed to execute `rustup component list --toolchain {TOOLCHAIN} --installed`.\n\
110 Please install `rustup` to continue.",
111 )
112 });
113
114 if !output.status.success()
115 || !output
116 .stdout
117 .split(|x| *x == b'\n')
118 .any(|x| x[..] == b"rust-src"[..])
119 {
120 if install_rustc {
121 println!("Installing rustc dependencies...");
122 let mut child = Command::new("rustup")
123 .args(["toolchain", "install", TOOLCHAIN, "-c", "rust-src"])
124 .stdout(std::process::Stdio::inherit())
125 .stderr(std::process::Stdio::inherit())
126 .spawn()
127 .unwrap_or_else(|_| {
128 panic!(
129 "Failed to execute `rustup toolchain install {TOOLCHAIN} -c rust-src`.\n\
130 Please install `rustup` to continue."
131 )
132 });
133 if !child
134 .wait()
135 .expect("Failed to execute rustup process")
136 .success()
137 {
138 panic!("Failed to install `rust-src` component of {TOOLCHAIN}.");
139 }
140 } else {
141 panic!("`rust-src` component of {TOOLCHAIN} is required to build the PVM binary.",);
142 }
143 }
144 println!("ℹ️ `rustup` and toolchain installed. Continuing build process...");
145
146 true
147 } else {
148 println!("ℹ️ `rustup` not installed, here be dragons. Continuing build process...");
149
150 false
151 };
152
153 let info = manifest::crate_info(crate_dir).expect("failed to get crate info");
154 println!("📦 Crate name: {}", info.name);
155 println!("🏷️ Build profile: {}", profile.as_str());
156
157 let mut child = Command::new("cargo");
158
159 child
160 .current_dir(crate_dir)
161 .env_clear()
162 .env("PATH", std::env::var("PATH").unwrap())
163 .env("RUSTFLAGS", "-C panic=abort")
164 .env("CARGO_TARGET_DIR", out_dir)
165 .env("RUSTC_BOOTSTRAP", "1");
167
168 if rustup_installed {
169 child.arg(format!("+{TOOLCHAIN}"));
170 }
171
172 child
173 .args(["rustc", "-Z", "build-std=core,alloc"])
174 .arg(profile.to_arg())
175 .arg("--target")
176 .arg(target_json_path)
177 .arg("--features")
178 .arg(if !matches!(blob_type, BlobType::CoreVmGuest) {
179 "tiny"
180 } else {
181 ""
182 })
183 .arg("--lib")
184 .arg("--crate-type=cdylib")
185 .env(
186 "RUSTFLAGS",
187 format!(
188 "{} -C link-arg=--strip-debug",
189 std::env::var("RUSTFLAGS").unwrap_or_default()
190 ),
191 );
192
193 let mut child = child.spawn().expect("Failed to execute cargo process");
194 let status = child.wait().expect("Failed to execute cargo process");
195
196 if !status.success() {
197 eprintln!("Failed to build RISC-V ELF due to cargo execution error");
198 std::process::exit(1);
199 }
200
201 println!("Converting RISC-V ELF to PVM blob...");
203 let mut config = polkavm_linker::Config::default();
204 config.set_strip(true);
205 config.set_dispatch_table(blob_type.dispatch_table());
206
207 let input_root = &out_dir.join(target_name).join(profile.as_str());
208 let input_path_bin = input_root.join(&info.name);
209 let input_path_cdylib = input_root.join(format!("{}.elf", info.name.replace("-", "_")));
210 let input_path = if input_path_cdylib.exists() {
211 if input_path_bin.exists() {
212 eprintln!(
213 "Both {} and {} exist; run 'cargo clean' to get rid of old artifacts!",
214 input_path_cdylib.display(),
215 input_path_bin.display()
216 );
217 std::process::exit(1);
218 }
219 input_path_cdylib
220 } else if input_path_bin.exists() {
221 input_path_bin
222 } else {
223 eprintln!(
224 "Failed to build: neither {} nor {} exist",
225 input_path_cdylib.display(),
226 input_path_bin.display()
227 );
228 std::process::exit(1);
229 };
230
231 let orig =
232 fs::read(&input_path).unwrap_or_else(|e| panic!("Failed to read {input_path:?} :{e:?}"));
233 let linked = polkavm_linker::program_from_elf(config, orig.as_ref())
234 .expect("Failed to link pvm program:");
235
236 let jam_out = out_dir.join("jam");
238 fs::create_dir_all(&jam_out).expect("Failed to create jam directory");
239 let output_path_pvm = jam_out.join(format!("{}.pvm", &info.name));
240 fs::write(output_path_pvm, &linked).expect("Error writing resulting binary");
241 let name = info.name.clone();
242 let metadata = ConventionalMetadata::Info(info).encode().into();
243 let output_file = blob_type.output_file(&jam_out, &name);
244 if !matches!(blob_type, BlobType::CoreVmGuest) {
245 let parts = polkavm_linker::ProgramParts::from_bytes(linked.into())
246 .expect("failed to deserialize linked PolkaVM program");
247 let blob = self::to_blob(&parts, metadata)
248 .to_vec()
249 .expect("error serializing the .jam blob");
250 fs::write(&output_file, blob).expect("error writing the .jam blob");
251 } else {
252 let blob = CoreVmProgramBlob {
253 metadata,
254 pvm_blob: linked.into(),
255 }
256 .to_vec()
257 .expect("error serializing the CoreVM blob");
258 fs::write(&output_file, blob).expect("error writing the CoreVM blob");
259 }
260
261 (name, output_file)
262}
263
264#[macro_export]
266macro_rules! pvm_binary {
267 ($name:literal) => {
268 include_bytes!(env!("PVM_BINARY"))
269 };
270}
271
272fn to_blob<'a>(parts: &'a ProgramParts, metadata: Cow<'a, [u8]>) -> ProgramBlob<'a> {
273 let mut ro_data = parts.ro_data.to_vec();
275 ro_data.resize(parts.ro_data_size as usize, 0);
276 let padding = (parts.rw_data_size as usize).next_multiple_of(4096)
278 - parts.rw_data.len().next_multiple_of(4096);
279 let rw_data_padding_pages = padding / 4096;
280 let rw_data_padding_pages = rw_data_padding_pages
281 .try_into()
282 .expect("The RW data section is too big");
283 ProgramBlob {
284 metadata,
285 ro_data: ro_data.into(),
286 rw_data: (&parts.rw_data[..]).into(),
287 code_blob: (&parts.code_and_jump_table[..]).into(),
288 rw_data_padding_pages,
289 stack_size: parts.stack_size,
290 }
291}