1#![allow(clippy::needless_doctest_main)]
16#![doc = include_str!("../README.md")]
17#![deny(missing_docs)]
18#![deny(rustdoc::broken_intra_doc_links)]
19#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
20
21mod config;
22mod docker;
23
24use std::{
25 borrow::Cow,
26 collections::HashMap,
27 default::Default,
28 env,
29 ffi::OsStr,
30 fmt::Write as _,
31 fs::{self, File},
32 io::{BufRead, BufReader, Write},
33 path::{Path, PathBuf},
34 process::{Command, Stdio},
35 str::FromStr,
36};
37
38use anyhow::{anyhow, Context, Result};
39use cargo_metadata::{Message, MetadataCommand, Package};
40use config::GuestMetadata;
41use risc0_binfmt::{ProgramBinary, KERNEL_START_ADDR};
42use risc0_zkp::core::digest::Digest;
43use risc0_zkvm_platform::memory;
44use serde::Deserialize;
45
46use self::{config::GuestInfo, docker::build_guest_package_docker};
47
48pub use self::{
49 config::{
50 DockerOptions, DockerOptionsBuilder, DockerOptionsBuilderError, GuestOptions,
51 GuestOptionsBuilder, GuestOptionsBuilderError,
52 },
53 docker::{docker_build, BuildStatus, TARGET_DIR},
54};
55
56const RISC0_TARGET_TRIPLE: &str = "riscv32im-risc0-zkvm-elf";
57const DEFAULT_DOCKER_TAG: &str = "r0.1.88.0";
58
59#[derive(Debug, Deserialize)]
60struct Risc0Metadata {
61 methods: Vec<String>,
62}
63
64impl Risc0Metadata {
65 fn from_package(pkg: &Package) -> Option<Risc0Metadata> {
66 let obj = pkg.metadata.get("risc0").unwrap();
67 serde_json::from_value(obj.clone()).unwrap()
68 }
69}
70
71trait GuestBuilder: Sized {
72 fn build(guest_info: &GuestInfo, name: &str, elf_path: &str) -> Result<Self>;
73
74 fn codegen_consts(&self) -> String;
75
76 #[cfg(feature = "guest-list")]
77 fn codegen_list_entry(&self) -> String;
78}
79
80#[derive(Debug, Clone)]
82pub struct MinGuestListEntry {
83 pub name: Cow<'static, str>,
85
86 pub path: Cow<'static, str>,
88}
89
90impl GuestBuilder for MinGuestListEntry {
91 fn build(_guest_info: &GuestInfo, name: &str, elf_path: &str) -> Result<Self> {
92 Ok(Self {
93 name: Cow::Owned(name.to_owned()),
94 path: Cow::Owned(elf_path.to_owned()),
95 })
96 }
97
98 fn codegen_consts(&self) -> String {
99 if self.path.contains('#') {
103 panic!("method path cannot include #: {}", self.path);
104 }
105
106 let upper = self.name.to_uppercase().replace('-', "_");
107 let elf_path: &str = &self.path;
108
109 format!(r##"pub const {upper}_PATH: &str = r#"{elf_path}"#;"##)
110 }
111
112 #[cfg(feature = "guest-list")]
113 fn codegen_list_entry(&self) -> String {
114 let upper = self.name.to_uppercase().replace('-', "_");
115 format!(
116 r##"
117 MinGuestListEntry {{
118 name: std::borrow::Cow::Borrowed("{upper}"),
119 path: std::borrow::Cow::Borrowed({upper}_PATH),
120 }}"##
121 )
122 }
123}
124
125#[derive(Debug, Clone)]
127pub struct GuestListEntry {
128 pub name: Cow<'static, str>,
130
131 pub elf: Cow<'static, [u8]>,
133
134 pub image_id: Digest,
136
137 pub path: Cow<'static, str>,
139}
140
141fn r0vm_image_id(path: &str, flag: &str) -> Result<Digest> {
142 use hex::FromHex;
143 let output = Command::new("r0vm")
144 .env_remove("RUST_LOG")
145 .args(["--elf", path, flag])
146 .output()?;
147 if output.status.success() {
148 let stdout = String::from_utf8(output.stdout)?;
149 let digest = stdout.trim();
150 Ok(Digest::from_hex(digest).context("expecting a hex string")?)
151 } else {
152 let stderr = String::from_utf8(output.stderr)?;
153 Err(anyhow!("{stderr}"))
154 }
155}
156
157fn compute_image_id(elf: &[u8], elf_path: &str) -> Result<Digest> {
158 Ok(match r0vm_image_id(elf_path, "--id") {
159 Ok(image_id) => image_id,
160 Err(err) => {
161 tty_println("Falling back to slow ImageID computation. Updating to the latest r0vm will speed this up.");
162 tty_println(&format!(" error: {err}"));
163 risc0_binfmt::compute_image_id(elf)?
164 }
165 })
166}
167
168impl GuestBuilder for GuestListEntry {
169 fn build(guest_info: &GuestInfo, name: &str, elf_path: &str) -> Result<Self> {
172 let mut elf = vec![];
173 let mut elf_path = elf_path.to_owned();
174 let mut image_id = Digest::default();
175 let is_kernel = guest_info.metadata.kernel;
176
177 if !is_skip_build() {
178 if is_kernel {
179 elf = std::fs::read(&elf_path)?;
180 } else {
181 let user_elf = std::fs::read(&elf_path)?;
182 let kernel_elf = guest_info.options.kernel();
183 let binary = ProgramBinary::new(&user_elf, &kernel_elf);
184 elf = binary.encode();
185 let combined_path = PathBuf::from_str(&(elf_path + ".bin"))?;
186 std::fs::write(&combined_path, &elf)?;
187 elf_path = combined_path.to_str().unwrap().to_owned();
188 image_id = compute_image_id(&elf, &elf_path)?;
189 }
190 }
191
192 Ok(Self {
193 name: Cow::Owned(name.to_owned()),
194 elf: Cow::Owned(elf),
195 image_id,
196 path: Cow::Owned(elf_path),
197 })
198 }
199
200 fn codegen_consts(&self) -> String {
201 if self.path.contains('#') {
205 panic!("method path cannot include #: {}", self.path);
206 }
207
208 let upper = self.name.to_uppercase().replace('-', "_");
209
210 let image_id = self.image_id.as_words();
211 let elf = if is_skip_build() {
212 "&[]".to_string()
213 } else {
214 format!("include_bytes!({:?})", self.path)
215 };
216
217 let mut str = String::new();
218
219 writeln!(&mut str, "pub const {upper}_ELF: &[u8] = {elf};").unwrap();
220 writeln!(&mut str, "pub const {upper}_PATH: &str = {:?};", self.path).unwrap();
221 writeln!(&mut str, "pub const {upper}_ID: [u32; 8] = {image_id:?};").unwrap();
222
223 str
224 }
225
226 #[cfg(feature = "guest-list")]
227 fn codegen_list_entry(&self) -> String {
228 let upper = self.name.to_uppercase().replace('-', "_");
229 format!(
230 r##"
231 GuestListEntry {{
232 name: std::borrow::Cow::Borrowed("{upper}"),
233 elf: std::borrow::Cow::Borrowed({upper}_ELF),
234 image_id: {upper}_ID,
235 path: std::borrow::Cow::Borrowed({upper}_PATH),
236 }}"##
237 )
238 }
239}
240
241pub fn get_package(manifest_dir: impl AsRef<Path>) -> Package {
244 let manifest_dir =
246 fs::canonicalize(manifest_dir.as_ref()).expect("could not canonicalize manifest path");
247 let manifest_path = manifest_dir.join("Cargo.toml");
248 let manifest_meta = MetadataCommand::new()
249 .manifest_path(&manifest_path)
250 .no_deps()
251 .exec()
252 .expect("cargo metadata command failed");
253 let mut matching: Vec<Package> = manifest_meta
254 .packages
255 .into_iter()
256 .filter(|pkg| {
257 let std_path: &Path = pkg.manifest_path.as_ref();
258 std_path == manifest_path
259 })
260 .collect();
261 if matching.is_empty() {
262 eprintln!("ERROR: No package found in {manifest_dir:?}");
263 std::process::exit(-1);
264 }
265 if matching.len() > 1 {
266 eprintln!("ERROR: Multiple packages found in {manifest_dir:?}",);
267 std::process::exit(-1);
268 }
269 matching.pop().unwrap()
270}
271
272pub fn get_target_dir(manifest_path: impl AsRef<Path>) -> PathBuf {
275 MetadataCommand::new()
276 .manifest_path(manifest_path.as_ref())
277 .no_deps()
278 .exec()
279 .expect("cargo metadata command failed")
280 .target_directory
281 .into()
282}
283
284fn current_package() -> Package {
286 get_package(env::var("CARGO_MANIFEST_DIR").unwrap())
287}
288
289fn guest_packages(pkg: &Package) -> Vec<Package> {
292 let manifest_dir = pkg.manifest_path.parent().unwrap();
293 Risc0Metadata::from_package(pkg)
294 .unwrap()
295 .methods
296 .iter()
297 .map(|inner| get_package(manifest_dir.join(inner)))
298 .collect()
299}
300
301fn is_debug() -> bool {
302 get_env_var("RISC0_BUILD_DEBUG") == "1"
303}
304
305fn is_skip_build() -> bool {
306 !get_env_var("RISC0_SKIP_BUILD").is_empty()
307}
308
309fn get_env_var(name: &str) -> String {
310 let ret = env::var(name).unwrap_or_default();
311 if let Some(pkg) = env::var_os("CARGO_PKG_NAME") {
312 if pkg != "cargo-risczero" {
313 println!("cargo:rerun-if-env-changed={name}");
314 }
315 }
316 ret
317}
318
319fn guest_methods<G: GuestBuilder>(
321 pkg: &Package,
322 target_dir: impl AsRef<Path>,
323 guest_info: &GuestInfo,
324 profile: &str,
325) -> Vec<G> {
326 pkg.targets
327 .iter()
328 .filter(|target| target.is_bin())
329 .filter(|target| {
330 target
331 .required_features
332 .iter()
333 .all(|required_feature| guest_info.options.features.contains(required_feature))
334 })
335 .map(|target| {
336 G::build(
337 guest_info,
338 &target.name,
339 target_dir
340 .as_ref()
341 .join(RISC0_TARGET_TRIPLE)
342 .join(profile)
343 .join(&target.name)
344 .to_str()
345 .context("elf path contains invalid unicode")
346 .unwrap(),
347 )
348 .unwrap()
349 })
350 .collect()
351}
352
353fn sanitized_cmd(tool: &str) -> Command {
356 let mut cmd = Command::new(tool);
357 for (key, _val) in env::vars().filter(|x| x.0.starts_with("CARGO")) {
358 cmd.env_remove(key);
359 }
360 cmd.env_remove("RUSTUP_TOOLCHAIN");
361 cmd
362}
363
364fn cpp_toolchain() -> Option<PathBuf> {
365 let rzup = rzup::Rzup::new().unwrap();
366 let (version, path) = rzup
367 .get_default_version(&rzup::Component::CppToolchain)
368 .unwrap()?;
369 println!("Using C++ toolchain version {version}");
370 Some(path)
371}
372
373fn rust_toolchain() -> PathBuf {
374 let rzup = rzup::Rzup::new().unwrap();
375 let Some((version, path)) = rzup
376 .get_default_version(&rzup::Component::RustToolchain)
377 .unwrap()
378 else {
379 panic!("Risc Zero Rust toolchain not found. Try running `rzup install rust`");
380 };
381 println!("Using Rust toolchain version {version}");
382 path
383}
384
385#[stability::unstable]
388pub fn cargo_command(subcmd: &str, rustc_flags: &[String]) -> Command {
389 let mut guest_info = GuestInfo::default();
390 guest_info.metadata.rustc_flags = Some(rustc_flags.to_vec());
391 cargo_command_internal(subcmd, &guest_info)
392}
393
394pub(crate) fn cargo_command_internal(subcmd: &str, guest_info: &GuestInfo) -> Command {
395 let rustc = rust_toolchain().join("bin/rustc");
396 println!("Using rustc: {}", rustc.display());
397
398 let mut cmd = sanitized_cmd("cargo");
399 let mut args = vec![subcmd, "--target", RISC0_TARGET_TRIPLE];
400
401 if !get_env_var("RISC0_BUILD_LOCKED").is_empty() {
402 args.push("--locked");
403 }
404
405 let rust_src = get_env_var("RISC0_RUST_SRC");
406 if !rust_src.is_empty() {
407 args.push("-Z");
408 args.push("build-std=alloc,core,proc_macro,panic_abort,std");
409 args.push("-Z");
410 args.push("build-std-features=compiler-builtins-mem");
411 cmd.env("__CARGO_TESTS_ONLY_SRC_ROOT", rust_src);
412 }
413
414 let encoded_rust_flags = encode_rust_flags(&guest_info.metadata, false);
415
416 if !cpp_toolchain_override() {
417 if let Some(toolchain_path) = cpp_toolchain() {
418 cmd.env("CC", toolchain_path.join("bin/riscv32-unknown-elf-gcc"));
419 } else {
420 cmd.env(
425 "CC",
426 "/no_risc0_cpp_toolchain_installed_run_rzup_install_cpp",
427 );
428 }
429
430 cmd.env("CFLAGS_riscv32im_risc0_zkvm_elf", "-march=rv32im -nostdlib");
431
432 cmd.env("RISC0_FEATURE_bigint2", "");
435 }
436
437 cmd.env("RUSTC", rustc)
438 .env("CARGO_ENCODED_RUSTFLAGS", encoded_rust_flags)
439 .args(args);
440 cmd
441}
442
443fn get_rust_toolchain_version() -> semver::Version {
444 let rzup = rzup::Rzup::new().unwrap();
445 let Some((version, _)) = rzup
446 .get_default_version(&rzup::Component::RustToolchain)
447 .unwrap()
448 else {
449 panic!("Risc Zero Rust toolchain not found. Try running `rzup install rust`");
450 };
451 version
452}
453
454pub(crate) fn encode_rust_flags(guest_meta: &GuestMetadata, escape_special_chars: bool) -> String {
456 let lower_atomic = if get_rust_toolchain_version() > semver::Version::new(1, 81, 0) {
458 "passes=lower-atomic"
459 } else {
460 "passes=loweratomic"
461 };
462 let rustc_flags = guest_meta.rustc_flags.clone().unwrap_or_default();
463 let rustc_flags: Vec<_> = rustc_flags.iter().map(|s| s.as_str()).collect();
464 let text_addr = if guest_meta.kernel {
465 KERNEL_START_ADDR.0
466 } else {
467 memory::TEXT_START
468 };
469 [
470 rustc_flags.as_slice(),
472 &[
473 "-C",
475 lower_atomic,
476 "-C",
482 &format!("link-arg=-Ttext={text_addr:#010x}"),
483 "-C",
486 "link-arg=--fatal-warnings",
487 "-C",
488 "panic=abort",
489 "--cfg",
490 "getrandom_backend=\"custom\"",
491 ],
492 ]
493 .concat()
494 .iter()
495 .map(|x| {
496 if escape_special_chars {
497 x.escape_default().to_string()
498 } else {
499 x.to_string()
500 }
501 })
502 .collect::<Vec<String>>()
503 .join("\x1f")
504}
505
506fn cpp_toolchain_override() -> bool {
507 !get_env_var("CC_riscv32im_risc0_zkvm_elf").is_empty()
510 || !get_env_var("CFLAGS_riscv32im_risc0_zkvm_elf").is_empty()
511}
512
513pub fn build_rust_runtime() -> String {
517 build_rust_runtime_with_features(&[])
518}
519
520pub fn build_rust_runtime_with_features(features: &[&str]) -> String {
525 build_staticlib(
526 "risc0-zkvm-platform",
527 &[&["rust-runtime", "panic-handler", "entrypoint"], features].concat(),
528 )
529}
530
531fn build_staticlib(guest_pkg: &str, features: &[&str]) -> String {
533 let guest_dir = get_guest_dir("static-lib", guest_pkg);
534
535 let guest_info = GuestInfo::default();
536 let mut cmd = cargo_command_internal("rustc", &guest_info);
537
538 if !is_debug() {
539 cmd.arg("--release");
540 }
541
542 cmd.args([
544 "--package",
545 guest_pkg,
546 "--target-dir",
547 guest_dir.to_str().unwrap(),
548 "--lib",
549 "--message-format=json",
550 "--crate-type=staticlib",
551 ]);
552
553 for feature in features {
554 cmd.args(["--features", &(guest_pkg.to_owned() + "/" + feature)]);
555 }
556
557 eprintln!("Building staticlib: {cmd:?}");
558
559 let mut child = cmd.stdout(Stdio::piped()).spawn().unwrap();
562 let reader = std::io::BufReader::new(child.stdout.take().unwrap());
563 let mut libs = Vec::new();
564 for message in Message::parse_stream(reader) {
565 match message.unwrap() {
566 Message::CompilerArtifact(artifact) => {
567 for filename in artifact.filenames {
568 if let Some("a") = filename.extension() {
569 libs.push(filename.to_string());
570 }
571 }
572 }
573 Message::CompilerMessage(msg) => {
574 eprint!("{msg}");
575 }
576 _ => (),
577 }
578 }
579
580 let output = child.wait().expect("Couldn't get cargo's exit status");
581 if !output.success() {
582 panic!("Unable to build static library")
583 }
584
585 match libs.as_slice() {
586 [] => panic!("No static library was built"),
587 [lib] => lib.to_string(),
588 _ => panic!("Multiple static libraries found: {:?}", libs.as_slice()),
589 }
590}
591
592fn tty_println(msg: &str) {
597 let tty_file = env::var("RISC0_GUEST_LOGFILE").unwrap_or_else(|_| "/dev/tty".to_string());
598
599 let mut tty = fs::OpenOptions::new()
600 .read(true)
601 .write(true)
602 .create(true)
603 .truncate(false)
604 .open(tty_file)
605 .ok();
606
607 if let Some(tty) = &mut tty {
608 writeln!(tty, "{msg}").unwrap();
609 } else {
610 eprintln!("{msg}");
611 }
612}
613
614fn build_guest_package(pkg: &Package, target_dir: impl AsRef<Path>, guest_info: &GuestInfo) {
617 if is_skip_build() {
618 return;
619 }
620
621 let target_dir = target_dir.as_ref();
622 fs::create_dir_all(target_dir).unwrap();
623
624 let mut cmd = cargo_command_internal("build", guest_info);
625
626 let features_str = guest_info.options.features.join(",");
627 if !features_str.is_empty() {
628 cmd.args(["--features", &features_str]);
629 }
630
631 cmd.args([
632 "--manifest-path",
633 pkg.manifest_path.as_str(),
634 "--target-dir",
635 target_dir.to_str().unwrap(),
636 ]);
637
638 if !is_debug() {
639 cmd.args(["--release"]);
640 }
641
642 let mut child = cmd
643 .stderr(Stdio::piped())
644 .spawn()
645 .expect("cargo build failed");
646 let stderr = child.stderr.take().unwrap();
647
648 tty_println(&format!(
649 "{}: Starting build for {RISC0_TARGET_TRIPLE}",
650 pkg.name
651 ));
652
653 for line in BufReader::new(stderr).lines() {
654 tty_println(&format!("{}: {}", pkg.name, line.unwrap()));
655 }
656
657 let res = child.wait().expect("Guest 'cargo build' failed");
658 if !res.success() {
659 std::process::exit(res.code().unwrap());
660 }
661}
662
663fn get_out_dir() -> PathBuf {
664 if let Some(target_dir) = env::var_os("CARGO_TARGET_DIR").map(Into::<PathBuf>::into) {
667 if target_dir.is_absolute() {
668 return target_dir.join("riscv-guest");
669 }
670 }
671
672 let mut dir: PathBuf = env::var_os("OUT_DIR").unwrap().into();
673 loop {
674 if dir.join(".rustc_info.json").exists()
675 || dir.join("CACHEDIR.TAG").exists()
676 || dir.file_name() == Some(OsStr::new("target"))
677 && dir
678 .parent()
679 .is_some_and(|parent| parent.join("Cargo.toml").exists())
680 {
681 return dir.join("riscv-guest");
682 }
683 if dir.pop() {
684 continue;
685 }
686 panic!("Cannot find cargo target dir location")
687 }
688}
689
690fn get_guest_dir(host_pkg: impl AsRef<Path>, guest_pkg: impl AsRef<Path>) -> PathBuf {
691 get_out_dir().join(host_pkg).join(guest_pkg)
692}
693
694pub fn embed_methods_with_options(
698 guest_pkg_to_options: HashMap<&str, GuestOptions>,
699) -> Vec<GuestListEntry> {
700 do_embed_methods(guest_pkg_to_options)
701}
702
703pub fn embed_method_metadata_with_options(
712 guest_pkg_to_options: HashMap<&str, GuestOptions>,
713) -> Vec<MinGuestListEntry> {
714 do_embed_methods(guest_pkg_to_options)
715}
716
717struct GuestPackageWithOptions {
718 name: String,
719 pkg: Package,
720 opts: GuestOptions,
721 target_dir: PathBuf,
722}
723
724fn do_embed_methods<G: GuestBuilder>(mut guest_opts: HashMap<&str, GuestOptions>) -> Vec<G> {
728 let pkg = current_package();
730 let guest_packages = guest_packages(&pkg);
731
732 let mut pkg_opts = vec![];
733 for guest_pkg in guest_packages {
734 let guest_dir = get_guest_dir(&pkg.name, &guest_pkg.name);
735 let opts = guest_opts
736 .remove(guest_pkg.name.as_str())
737 .unwrap_or_default();
738 pkg_opts.push(GuestPackageWithOptions {
739 name: format!("{}.{}", pkg.name, guest_pkg.name),
740 pkg: guest_pkg,
741 opts,
742 target_dir: guest_dir,
743 });
744 }
745
746 if let Some(package) = guest_opts.keys().next() {
748 panic!("Error: guest options were provided for package {package:?} but the package was not built.");
749 }
750
751 build_methods(&pkg_opts)
752}
753
754fn build_methods<G: GuestBuilder>(guest_packages: &[GuestPackageWithOptions]) -> Vec<G> {
755 let out_dir_env = env::var_os("OUT_DIR").unwrap();
756 let out_dir = Path::new(&out_dir_env); let methods_path = out_dir.join("methods.rs");
759 let mut methods_file = File::create(&methods_path).unwrap();
760
761 #[cfg(feature = "guest-list")]
766 let mut guest_list_codegen = Vec::new();
767 #[cfg(feature = "guest-list")]
768 methods_file
769 .write_all(b"use risc0_build::GuestListEntry;\n")
770 .unwrap();
771
772 let profile = if is_debug() { "debug" } else { "release" };
773
774 let mut guest_list = vec![];
775 for guest in guest_packages {
776 println!("Building guest package: {}", guest.name);
777
778 let guest_info = GuestInfo {
779 options: guest.opts.clone(),
780 metadata: (&guest.pkg).into(),
781 };
782
783 let methods: Vec<G> = if guest.opts.use_docker.is_some() {
784 build_guest_package_docker(&guest.pkg, &guest.target_dir, &guest_info).unwrap();
785 guest_methods(&guest.pkg, &guest.target_dir, &guest_info, "docker")
786 } else {
787 build_guest_package(&guest.pkg, &guest.target_dir, &guest_info);
788 guest_methods(&guest.pkg, &guest.target_dir, &guest_info, profile)
789 };
790
791 for method in methods {
792 methods_file
793 .write_all(method.codegen_consts().as_bytes())
794 .unwrap();
795
796 #[cfg(feature = "guest-list")]
797 guest_list_codegen.push(method.codegen_list_entry());
798 guest_list.push(method);
799 }
800 }
801
802 #[cfg(feature = "guest-list")]
803 methods_file
804 .write_all(
805 format!(
806 "\npub const GUEST_LIST: &[{}] = &[{}];\n",
807 std::any::type_name::<G>(),
808 guest_list_codegen.join(",")
809 )
810 .as_bytes(),
811 )
812 .unwrap();
813
814 println!("cargo:rerun-if-changed={}", methods_path.display());
821 println!("cargo:rerun-if-env-changed=RISC0_GUEST_LOGFILE");
822
823 guest_list
824}
825
826pub fn embed_methods() -> Vec<GuestListEntry> {
845 embed_methods_with_options(HashMap::new())
846}
847
848pub fn build_package(
851 pkg: &Package,
852 target_dir: impl AsRef<Path>,
853 options: GuestOptions,
854) -> Result<Vec<GuestListEntry>> {
855 println!("Building guest package: {}", pkg.name);
856
857 let guest_info = GuestInfo {
858 options: options.clone(),
859 metadata: pkg.into(),
860 };
861
862 let profile = if is_debug() { "debug" } else { "release" };
863
864 if options.use_docker.is_some() {
865 build_guest_package_docker(pkg, target_dir.as_ref(), &guest_info)?;
866 Ok(guest_methods(pkg, &target_dir, &guest_info, "docker"))
867 } else {
868 build_guest_package(pkg, &target_dir, &guest_info);
869 Ok(guest_methods(pkg, &target_dir, &guest_info, profile))
870 }
871}
872
873#[cfg(test)]
874mod tests {
875 use super::*;
876
877 const RUSTC_FLAGS: &[&str] = &[
878 "--cfg",
879 "foo=\"bar\"",
880 "--cfg",
881 "foo='bar'",
882 "-C",
883 "link-args=--fatal-warnings",
884 ];
885
886 #[test]
887 fn encodes_rustc_flags() {
888 let guest_meta = GuestMetadata {
889 rustc_flags: Some(RUSTC_FLAGS.iter().map(ToString::to_string).collect()),
890 ..Default::default()
891 };
892 let encoded = encode_rust_flags(&guest_meta, false);
893 let expected = [
894 "--cfg",
895 "foo=\"bar\"",
896 "--cfg",
897 "foo='bar'",
898 "-C",
899 "link-args=--fatal-warnings",
900 ]
901 .join("\x1f");
902 assert!(encoded.contains(&expected));
903 }
904
905 #[test]
906 fn escapes_strings_when_encoding_when_requested() {
907 let guest_meta = GuestMetadata {
908 rustc_flags: Some(RUSTC_FLAGS.iter().map(ToString::to_string).collect()),
909 ..Default::default()
910 };
911 let encoded = encode_rust_flags(&guest_meta, true);
912 let expected = [
913 "--cfg",
914 "foo=\\\"bar\\\"",
915 "--cfg",
916 "foo=\\\'bar\\\'",
917 "-C",
918 "link-args=--fatal-warnings",
919 ]
920 .join("\x1f");
921 assert!(encoded.contains(&expected));
922 }
923}