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.85.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 = manifest_dir.as_ref();
245 let manifest_path = manifest_dir.join("Cargo.toml");
246 let manifest_meta = MetadataCommand::new()
247 .manifest_path(&manifest_path)
248 .no_deps()
249 .exec()
250 .expect("cargo metadata command failed");
251 let mut matching: Vec<Package> = manifest_meta
252 .packages
253 .into_iter()
254 .filter(|pkg| {
255 let std_path: &Path = pkg.manifest_path.as_ref();
256 std_path == manifest_path
257 })
258 .collect();
259 if matching.is_empty() {
260 eprintln!("ERROR: No package found in {manifest_dir:?}");
261 std::process::exit(-1);
262 }
263 if matching.len() > 1 {
264 eprintln!("ERROR: Multiple packages found in {manifest_dir:?}",);
265 std::process::exit(-1);
266 }
267 matching.pop().unwrap()
268}
269
270pub fn get_target_dir(manifest_path: impl AsRef<Path>) -> PathBuf {
273 MetadataCommand::new()
274 .manifest_path(manifest_path.as_ref())
275 .no_deps()
276 .exec()
277 .expect("cargo metadata command failed")
278 .target_directory
279 .into()
280}
281
282fn current_package() -> Package {
284 get_package(env::var("CARGO_MANIFEST_DIR").unwrap())
285}
286
287fn guest_packages(pkg: &Package) -> Vec<Package> {
290 let manifest_dir = pkg.manifest_path.parent().unwrap();
291 Risc0Metadata::from_package(pkg)
292 .unwrap()
293 .methods
294 .iter()
295 .map(|inner| get_package(manifest_dir.join(inner)))
296 .collect()
297}
298
299fn is_debug() -> bool {
300 get_env_var("RISC0_BUILD_DEBUG") == "1"
301}
302
303fn is_skip_build() -> bool {
304 !get_env_var("RISC0_SKIP_BUILD").is_empty()
305}
306
307fn get_env_var(name: &str) -> String {
308 let ret = env::var(name).unwrap_or_default();
309 if let Some(pkg) = env::var_os("CARGO_PKG_NAME") {
310 if pkg != "cargo-risczero" {
311 println!("cargo:rerun-if-env-changed={name}");
312 }
313 }
314 ret
315}
316
317fn guest_methods<G: GuestBuilder>(
319 pkg: &Package,
320 target_dir: impl AsRef<Path>,
321 guest_info: &GuestInfo,
322 profile: &str,
323) -> Vec<G> {
324 pkg.targets
325 .iter()
326 .filter(|target| target.is_bin())
327 .filter(|target| {
328 target
329 .required_features
330 .iter()
331 .all(|required_feature| guest_info.options.features.contains(required_feature))
332 })
333 .map(|target| {
334 G::build(
335 guest_info,
336 &target.name,
337 target_dir
338 .as_ref()
339 .join(RISC0_TARGET_TRIPLE)
340 .join(profile)
341 .join(&target.name)
342 .to_str()
343 .context("elf path contains invalid unicode")
344 .unwrap(),
345 )
346 .unwrap()
347 })
348 .collect()
349}
350
351fn sanitized_cmd(tool: &str) -> Command {
354 let mut cmd = Command::new(tool);
355 for (key, _val) in env::vars().filter(|x| x.0.starts_with("CARGO")) {
356 cmd.env_remove(key);
357 }
358 cmd.env_remove("RUSTUP_TOOLCHAIN");
359 cmd
360}
361
362fn cpp_toolchain() -> Option<PathBuf> {
363 let rzup = rzup::Rzup::new().unwrap();
364 let (version, path) = rzup
365 .get_default_version(&rzup::Component::CppToolchain)
366 .unwrap()?;
367 println!("Using C++ toolchain version {version}");
368 Some(path)
369}
370
371fn rust_toolchain() -> PathBuf {
372 let rzup = rzup::Rzup::new().unwrap();
373 let Some((version, path)) = rzup
374 .get_default_version(&rzup::Component::RustToolchain)
375 .unwrap()
376 else {
377 panic!("Risc Zero Rust toolchain not found. Try running `rzup install rust`");
378 };
379 println!("Using Rust toolchain version {version}");
380 path
381}
382
383#[stability::unstable]
386pub fn cargo_command(subcmd: &str, rustc_flags: &[String]) -> Command {
387 let mut guest_info = GuestInfo::default();
388 guest_info.metadata.rustc_flags = Some(rustc_flags.to_vec());
389 cargo_command_internal(subcmd, &guest_info)
390}
391
392pub(crate) fn cargo_command_internal(subcmd: &str, guest_info: &GuestInfo) -> Command {
393 let rustc = rust_toolchain().join("bin/rustc");
394 println!("Using rustc: {}", rustc.display());
395
396 let mut cmd = sanitized_cmd("cargo");
397 let mut args = vec![subcmd, "--target", RISC0_TARGET_TRIPLE];
398
399 if !get_env_var("RISC0_BUILD_LOCKED").is_empty() {
400 args.push("--locked");
401 }
402
403 let rust_src = get_env_var("RISC0_RUST_SRC");
404 if !rust_src.is_empty() {
405 args.push("-Z");
406 args.push("build-std=alloc,core,proc_macro,panic_abort,std");
407 args.push("-Z");
408 args.push("build-std-features=compiler-builtins-mem");
409 cmd.env("__CARGO_TESTS_ONLY_SRC_ROOT", rust_src);
410 }
411
412 let encoded_rust_flags = encode_rust_flags(&guest_info.metadata, false);
413
414 if !cpp_toolchain_override() {
415 if let Some(toolchain_path) = cpp_toolchain() {
416 cmd.env("CC", toolchain_path.join("bin/riscv32-unknown-elf-gcc"));
417 } else {
418 cmd.env(
423 "CC",
424 "/no_risc0_cpp_toolchain_installed_run_rzup_install_cpp",
425 );
426 }
427
428 cmd.env("CFLAGS_riscv32im_risc0_zkvm_elf", "-march=rv32im -nostdlib");
429
430 #[cfg(feature = "unstable")]
435 cmd.env("RISC0_FEATURE_bigint2", "");
436 }
437
438 cmd.env("RUSTC", rustc)
439 .env("CARGO_ENCODED_RUSTFLAGS", encoded_rust_flags)
440 .args(args);
441 cmd
442}
443
444fn get_rust_toolchain_version() -> semver::Version {
445 let rzup = rzup::Rzup::new().unwrap();
446 let Some((version, _)) = rzup
447 .get_default_version(&rzup::Component::RustToolchain)
448 .unwrap()
449 else {
450 panic!("Risc Zero Rust toolchain not found. Try running `rzup install rust`");
451 };
452 version
453}
454
455pub(crate) fn encode_rust_flags(guest_meta: &GuestMetadata, escape_special_chars: bool) -> String {
457 let lower_atomic = if get_rust_toolchain_version() > semver::Version::new(1, 81, 0) {
459 "passes=lower-atomic"
460 } else {
461 "passes=loweratomic"
462 };
463 let rustc_flags = guest_meta.rustc_flags.clone().unwrap_or_default();
464 let rustc_flags: Vec<_> = rustc_flags.iter().map(|s| s.as_str()).collect();
465 let text_addr = if guest_meta.kernel {
466 KERNEL_START_ADDR.0
467 } else {
468 memory::TEXT_START
469 };
470 [
471 rustc_flags.as_slice(),
473 &[
474 "-C",
476 lower_atomic,
477 "-C",
483 &format!("link-arg=-Ttext={text_addr:#010x}"),
484 "-C",
487 "link-arg=--fatal-warnings",
488 "-C",
489 "panic=abort",
490 "--cfg",
491 "getrandom_backend=\"custom\"",
492 ],
493 ]
494 .concat()
495 .iter()
496 .map(|x| {
497 if escape_special_chars {
498 x.escape_default().to_string()
499 } else {
500 x.to_string()
501 }
502 })
503 .collect::<Vec<String>>()
504 .join("\x1f")
505}
506
507fn cpp_toolchain_override() -> bool {
508 !get_env_var("CC_riscv32im_risc0_zkvm_elf").is_empty()
511 || !get_env_var("CFLAGS_riscv32im_risc0_zkvm_elf").is_empty()
512}
513
514pub fn build_rust_runtime() -> String {
518 build_rust_runtime_with_features(&[])
519}
520
521pub fn build_rust_runtime_with_features(features: &[&str]) -> String {
526 build_staticlib(
527 "risc0-zkvm-platform",
528 &[&["rust-runtime", "panic-handler", "entrypoint"], features].concat(),
529 )
530}
531
532fn build_staticlib(guest_pkg: &str, features: &[&str]) -> String {
534 let guest_dir = get_guest_dir("static-lib", guest_pkg);
535
536 let guest_info = GuestInfo::default();
537 let mut cmd = cargo_command_internal("rustc", &guest_info);
538
539 if !is_debug() {
540 cmd.arg("--release");
541 }
542
543 cmd.args([
545 "--package",
546 guest_pkg,
547 "--target-dir",
548 guest_dir.to_str().unwrap(),
549 "--lib",
550 "--message-format=json",
551 "--crate-type=staticlib",
552 ]);
553
554 for feature in features {
555 cmd.args(["--features", &(guest_pkg.to_owned() + "/" + feature)]);
556 }
557
558 eprintln!("Building staticlib: {:?}", cmd);
559
560 let mut child = cmd.stdout(Stdio::piped()).spawn().unwrap();
563 let reader = std::io::BufReader::new(child.stdout.take().unwrap());
564 let mut libs = Vec::new();
565 for message in Message::parse_stream(reader) {
566 match message.unwrap() {
567 Message::CompilerArtifact(artifact) => {
568 for filename in artifact.filenames {
569 if let Some("a") = filename.extension() {
570 libs.push(filename.to_string());
571 }
572 }
573 }
574 Message::CompilerMessage(msg) => {
575 eprint!("{}", msg);
576 }
577 _ => (),
578 }
579 }
580
581 let output = child.wait().expect("Couldn't get cargo's exit status");
582 if !output.success() {
583 panic!("Unable to build static library")
584 }
585
586 match libs.as_slice() {
587 [] => panic!("No static library was built"),
588 [lib] => lib.to_string(),
589 _ => panic!("Multiple static libraries found: {:?}", libs.as_slice()),
590 }
591}
592
593fn tty_println(msg: &str) {
598 let tty_file = env::var("RISC0_GUEST_LOGFILE").unwrap_or_else(|_| "/dev/tty".to_string());
599
600 let mut tty = fs::OpenOptions::new()
601 .read(true)
602 .write(true)
603 .create(true)
604 .truncate(false)
605 .open(tty_file)
606 .ok();
607
608 if let Some(tty) = &mut tty {
609 writeln!(tty, "{msg}").unwrap();
610 } else {
611 eprintln!("{msg}");
612 }
613}
614
615fn build_guest_package(pkg: &Package, target_dir: impl AsRef<Path>, guest_info: &GuestInfo) {
618 if is_skip_build() {
619 return;
620 }
621
622 let target_dir = target_dir.as_ref();
623 fs::create_dir_all(target_dir).unwrap();
624
625 let mut cmd = cargo_command_internal("build", guest_info);
626
627 let features_str = guest_info.options.features.join(",");
628 if !features_str.is_empty() {
629 cmd.args(["--features", &features_str]);
630 }
631
632 cmd.args([
633 "--manifest-path",
634 pkg.manifest_path.as_str(),
635 "--target-dir",
636 target_dir.to_str().unwrap(),
637 ]);
638
639 if !is_debug() {
640 cmd.args(["--release"]);
641 }
642
643 let mut child = cmd
644 .stderr(Stdio::piped())
645 .spawn()
646 .expect("cargo build failed");
647 let stderr = child.stderr.take().unwrap();
648
649 tty_println(&format!(
650 "{}: Starting build for {RISC0_TARGET_TRIPLE}",
651 pkg.name
652 ));
653
654 for line in BufReader::new(stderr).lines() {
655 tty_println(&format!("{}: {}", pkg.name, line.unwrap()));
656 }
657
658 let res = child.wait().expect("Guest 'cargo build' failed");
659 if !res.success() {
660 std::process::exit(res.code().unwrap());
661 }
662}
663
664fn get_out_dir() -> PathBuf {
665 if let Some(target_dir) = env::var_os("CARGO_TARGET_DIR").map(Into::<PathBuf>::into) {
668 if target_dir.is_absolute() {
669 return target_dir.join("riscv-guest");
670 }
671 }
672
673 let mut dir: PathBuf = env::var_os("OUT_DIR").unwrap().into();
674 loop {
675 if dir.join(".rustc_info.json").exists()
676 || dir.join("CACHEDIR.TAG").exists()
677 || dir.file_name() == Some(OsStr::new("target"))
678 && dir
679 .parent()
680 .is_some_and(|parent| parent.join("Cargo.toml").exists())
681 {
682 return dir.join("riscv-guest");
683 }
684 if dir.pop() {
685 continue;
686 }
687 panic!("Cannot find cargo target dir location")
688 }
689}
690
691fn get_guest_dir(host_pkg: impl AsRef<Path>, guest_pkg: impl AsRef<Path>) -> PathBuf {
692 get_out_dir().join(host_pkg).join(guest_pkg)
693}
694
695pub fn embed_methods_with_options(
699 guest_pkg_to_options: HashMap<&str, GuestOptions>,
700) -> Vec<GuestListEntry> {
701 do_embed_methods(guest_pkg_to_options)
702}
703
704pub fn embed_method_metadata_with_options(
713 guest_pkg_to_options: HashMap<&str, GuestOptions>,
714) -> Vec<MinGuestListEntry> {
715 do_embed_methods(guest_pkg_to_options)
716}
717
718struct GuestPackageWithOptions {
719 name: String,
720 pkg: Package,
721 opts: GuestOptions,
722 target_dir: PathBuf,
723}
724
725fn do_embed_methods<G: GuestBuilder>(mut guest_opts: HashMap<&str, GuestOptions>) -> Vec<G> {
729 let pkg = current_package();
731 let guest_packages = guest_packages(&pkg);
732
733 let mut pkg_opts = vec![];
734 for guest_pkg in guest_packages {
735 let guest_dir = get_guest_dir(&pkg.name, &guest_pkg.name);
736 let opts = guest_opts
737 .remove(guest_pkg.name.as_str())
738 .unwrap_or_default();
739 pkg_opts.push(GuestPackageWithOptions {
740 name: format!("{}.{}", pkg.name, guest_pkg.name),
741 pkg: guest_pkg,
742 opts,
743 target_dir: guest_dir,
744 });
745 }
746
747 if let Some(package) = guest_opts.keys().next() {
749 panic!("Error: guest options were provided for package {package:?} but the package was not built.");
750 }
751
752 build_methods(&pkg_opts)
753}
754
755fn build_methods<G: GuestBuilder>(guest_packages: &[GuestPackageWithOptions]) -> Vec<G> {
756 let out_dir_env = env::var_os("OUT_DIR").unwrap();
757 let out_dir = Path::new(&out_dir_env); let methods_path = out_dir.join("methods.rs");
760 let mut methods_file = File::create(&methods_path).unwrap();
761
762 #[cfg(feature = "guest-list")]
767 let mut guest_list_codegen = Vec::new();
768 #[cfg(feature = "guest-list")]
769 methods_file
770 .write_all(b"use risc0_build::GuestListEntry;\n")
771 .unwrap();
772
773 let profile = if is_debug() { "debug" } else { "release" };
774
775 let mut guest_list = vec![];
776 for guest in guest_packages {
777 println!("Building guest package: {}", guest.name);
778
779 let guest_info = GuestInfo {
780 options: guest.opts.clone(),
781 metadata: (&guest.pkg).into(),
782 };
783
784 let methods: Vec<G> = if guest.opts.use_docker.is_some() {
785 build_guest_package_docker(&guest.pkg, &guest.target_dir, &guest_info).unwrap();
786 guest_methods(&guest.pkg, &guest.target_dir, &guest_info, "docker")
787 } else {
788 build_guest_package(&guest.pkg, &guest.target_dir, &guest_info);
789 guest_methods(&guest.pkg, &guest.target_dir, &guest_info, profile)
790 };
791
792 for method in methods {
793 methods_file
794 .write_all(method.codegen_consts().as_bytes())
795 .unwrap();
796
797 #[cfg(feature = "guest-list")]
798 guest_list_codegen.push(method.codegen_list_entry());
799 guest_list.push(method);
800 }
801 }
802
803 #[cfg(feature = "guest-list")]
804 methods_file
805 .write_all(
806 format!(
807 "\npub const GUEST_LIST: &[{}] = &[{}];\n",
808 std::any::type_name::<G>(),
809 guest_list_codegen.join(",")
810 )
811 .as_bytes(),
812 )
813 .unwrap();
814
815 println!("cargo:rerun-if-changed={}", methods_path.display());
822 println!("cargo:rerun-if-env-changed=RISC0_GUEST_LOGFILE");
823
824 guest_list
825}
826
827pub fn embed_methods() -> Vec<GuestListEntry> {
846 embed_methods_with_options(HashMap::new())
847}
848
849pub fn build_package(
852 pkg: &Package,
853 target_dir: impl AsRef<Path>,
854 options: GuestOptions,
855) -> Result<Vec<GuestListEntry>> {
856 println!("Building guest package: {}", pkg.name);
857
858 let guest_info = GuestInfo {
859 options: options.clone(),
860 metadata: pkg.into(),
861 };
862
863 let profile = if is_debug() { "debug" } else { "release" };
864
865 if options.use_docker.is_some() {
866 build_guest_package_docker(pkg, target_dir.as_ref(), &guest_info)?;
867 Ok(guest_methods(pkg, &target_dir, &guest_info, "docker"))
868 } else {
869 build_guest_package(pkg, &target_dir, &guest_info);
870 Ok(guest_methods(pkg, &target_dir, &guest_info, profile))
871 }
872}
873
874#[cfg(test)]
875mod tests {
876 use super::*;
877
878 const RUSTC_FLAGS: &[&str] = &[
879 "--cfg",
880 "foo=\"bar\"",
881 "--cfg",
882 "foo='bar'",
883 "-C",
884 "link-args=--fatal-warnings",
885 ];
886
887 #[test]
888 fn encodes_rustc_flags() {
889 let guest_meta = GuestMetadata {
890 rustc_flags: Some(RUSTC_FLAGS.iter().map(ToString::to_string).collect()),
891 ..Default::default()
892 };
893 let encoded = encode_rust_flags(&guest_meta, false);
894 let expected = [
895 "--cfg",
896 "foo=\"bar\"",
897 "--cfg",
898 "foo='bar'",
899 "-C",
900 "link-args=--fatal-warnings",
901 ]
902 .join("\x1f");
903 assert!(encoded.contains(&expected));
904 }
905
906 #[test]
907 fn escapes_strings_when_encoding_when_requested() {
908 let guest_meta = GuestMetadata {
909 rustc_flags: Some(RUSTC_FLAGS.iter().map(ToString::to_string).collect()),
910 ..Default::default()
911 };
912 let encoded = encode_rust_flags(&guest_meta, true);
913 let expected = [
914 "--cfg",
915 "foo=\\\"bar\\\"",
916 "--cfg",
917 "foo=\\\'bar\\\'",
918 "-C",
919 "link-args=--fatal-warnings",
920 ]
921 .join("\x1f");
922 assert!(encoded.contains(&expected));
923 }
924}