1use std::collections::HashMap;
7use std::ffi::OsString;
8use std::fmt;
9use std::io;
10use std::io::Read;
11use std::path::Path;
12use std::path::PathBuf;
13use std::str;
14use std::str::FromStr;
15use std::sync::Arc;
16
17use anyhow::bail;
18use cargo_util::ProcessBuilder;
19use cargo_util::Sha256;
20use cargo_util::paths;
21use serde::Serialize;
22
23use crate::CARGO_ENV;
24use crate::CargoResult;
25use crate::core::manifest::ManifestMetadata;
26
27const CURRENT_ENCODED_DEP_INFO_VERSION: u8 = 1;
29
30#[derive(Default)]
32pub struct RustcDepInfo {
33 pub files: HashMap<PathBuf, Option<(u64, Checksum)>>,
37 pub env: Vec<(String, Option<String>)>,
47}
48
49#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
52pub enum DepInfoPathType {
53 PackageRootRelative,
55 BuildRootRelative,
58}
59
60#[derive(Default, Debug, PartialEq, Eq)]
111pub struct EncodedDepInfo {
112 pub files: Vec<(DepInfoPathType, PathBuf, Option<(u64, String)>)>,
113 pub env: Vec<(String, Option<String>)>,
114}
115
116impl EncodedDepInfo {
117 pub fn parse(mut bytes: &[u8]) -> Option<EncodedDepInfo> {
118 let bytes = &mut bytes;
119 read_magic_marker(bytes)?;
120 let version = read_u8(bytes)?;
121 if version != CURRENT_ENCODED_DEP_INFO_VERSION {
122 return None;
123 }
124
125 let nfiles = read_usize(bytes)?;
126 let mut files = Vec::with_capacity(nfiles);
127 for _ in 0..nfiles {
128 let ty = match read_u8(bytes)? {
129 0 => DepInfoPathType::PackageRootRelative,
130 1 => DepInfoPathType::BuildRootRelative,
131 _ => return None,
132 };
133 let path_bytes = read_bytes(bytes)?;
134 let path = paths::bytes2path(path_bytes).ok()?;
135 let has_checksum = read_bool(bytes)?;
136 let checksum_info = has_checksum
137 .then(|| {
138 let file_len = read_u64(bytes);
139 let checksum_string = read_bytes(bytes)
140 .map(Vec::from)
141 .and_then(|v| String::from_utf8(v).ok());
142 file_len.zip(checksum_string)
143 })
144 .flatten();
145 files.push((ty, path, checksum_info));
146 }
147
148 let nenv = read_usize(bytes)?;
149 let mut env = Vec::with_capacity(nenv);
150 for _ in 0..nenv {
151 let key = str::from_utf8(read_bytes(bytes)?).ok()?.to_string();
152 let val = match read_u8(bytes)? {
153 0 => None,
154 1 => Some(str::from_utf8(read_bytes(bytes)?).ok()?.to_string()),
155 _ => return None,
156 };
157 env.push((key, val));
158 }
159 return Some(EncodedDepInfo { files, env });
160
161 fn read_magic_marker(bytes: &mut &[u8]) -> Option<()> {
163 let _size = read_usize(bytes)?;
164 let path_type = read_u8(bytes)?;
165 if path_type != u8::MAX {
166 None
168 } else {
169 Some(())
170 }
171 }
172
173 fn read_usize(bytes: &mut &[u8]) -> Option<usize> {
174 let ret = bytes.get(..4)?;
175 *bytes = &bytes[4..];
176 Some(u32::from_le_bytes(ret.try_into().unwrap()) as usize)
177 }
178
179 fn read_u64(bytes: &mut &[u8]) -> Option<u64> {
180 let ret = bytes.get(..8)?;
181 *bytes = &bytes[8..];
182 Some(u64::from_le_bytes(ret.try_into().unwrap()))
183 }
184
185 fn read_bool(bytes: &mut &[u8]) -> Option<bool> {
186 read_u8(bytes).map(|b| b != 0)
187 }
188
189 fn read_u8(bytes: &mut &[u8]) -> Option<u8> {
190 let ret = *bytes.get(0)?;
191 *bytes = &bytes[1..];
192 Some(ret)
193 }
194
195 fn read_bytes<'a>(bytes: &mut &'a [u8]) -> Option<&'a [u8]> {
196 let n = read_usize(bytes)? as usize;
197 let ret = bytes.get(..n)?;
198 *bytes = &bytes[n..];
199 Some(ret)
200 }
201 }
202
203 pub fn serialize(&self) -> CargoResult<Vec<u8>> {
204 let mut ret = Vec::new();
205 let dst = &mut ret;
206
207 write_magic_marker(dst);
208 dst.push(CURRENT_ENCODED_DEP_INFO_VERSION);
209
210 write_usize(dst, self.files.len());
211 for (ty, file, checksum_info) in self.files.iter() {
212 match ty {
213 DepInfoPathType::PackageRootRelative => dst.push(0),
214 DepInfoPathType::BuildRootRelative => dst.push(1),
215 }
216 write_bytes(dst, paths::path2bytes(file)?);
217 write_bool(dst, checksum_info.is_some());
218 if let Some((len, checksum)) = checksum_info {
219 write_u64(dst, *len);
220 write_bytes(dst, checksum);
221 }
222 }
223
224 write_usize(dst, self.env.len());
225 for (key, val) in self.env.iter() {
226 write_bytes(dst, key);
227 match val {
228 None => dst.push(0),
229 Some(val) => {
230 dst.push(1);
231 write_bytes(dst, val);
232 }
233 }
234 }
235 return Ok(ret);
236
237 fn write_magic_marker(dst: &mut Vec<u8>) {
241 write_usize(dst, 1);
242 dst.push(u8::MAX);
243 }
244
245 fn write_bytes(dst: &mut Vec<u8>, val: impl AsRef<[u8]>) {
246 let val = val.as_ref();
247 write_usize(dst, val.len());
248 dst.extend_from_slice(val);
249 }
250
251 fn write_usize(dst: &mut Vec<u8>, val: usize) {
252 dst.extend(&u32::to_le_bytes(val as u32));
253 }
254
255 fn write_u64(dst: &mut Vec<u8>, val: u64) {
256 dst.extend(&u64::to_le_bytes(val));
257 }
258
259 fn write_bool(dst: &mut Vec<u8>, val: bool) {
260 dst.push(u8::from(val));
261 }
262 }
263}
264
265pub fn translate_dep_info(
292 rustc_dep_info: &Path,
293 cargo_dep_info: &Path,
294 rustc_cwd: &Path,
295 pkg_root: &Path,
296 build_root: &Path,
297 rustc_cmd: &ProcessBuilder,
298 allow_package: bool,
299 env_config: &Arc<HashMap<String, OsString>>,
300) -> CargoResult<()> {
301 let depinfo = parse_rustc_dep_info(rustc_dep_info)?;
302
303 let build_root = crate::util::try_canonicalize(build_root)?;
304 let pkg_root = crate::util::try_canonicalize(pkg_root)?;
305 let mut on_disk_info = EncodedDepInfo::default();
306 on_disk_info.env = depinfo.env;
307
308 on_disk_info.env.retain(|(key, _)| {
339 ManifestMetadata::should_track(key)
340 || env_config.contains_key(key)
341 || !rustc_cmd.get_envs().contains_key(key)
342 || key == CARGO_ENV
343 });
344
345 let serialize_path = |file| {
346 let abs_file = rustc_cwd.join(file);
349 let canon_file =
353 crate::util::try_canonicalize(&abs_file).unwrap_or_else(|_| abs_file.clone());
354
355 let (ty, path) = if let Ok(stripped) = canon_file.strip_prefix(&build_root) {
356 (DepInfoPathType::BuildRootRelative, stripped)
357 } else if let Ok(stripped) = canon_file.strip_prefix(&pkg_root) {
358 if !allow_package {
359 return None;
360 }
361 (DepInfoPathType::PackageRootRelative, stripped)
362 } else {
363 (DepInfoPathType::BuildRootRelative, &*abs_file)
367 };
368 Some((ty, path.to_owned()))
369 };
370
371 for (file, checksum_info) in depinfo.files {
372 let Some((path_type, path)) = serialize_path(file) else {
373 continue;
374 };
375 on_disk_info.files.push((
376 path_type,
377 path,
378 checksum_info.map(|(len, checksum)| (len, checksum.to_string())),
379 ));
380 }
381 paths::write(cargo_dep_info, on_disk_info.serialize()?)?;
382 Ok(())
383}
384
385pub fn parse_rustc_dep_info(rustc_dep_info: &Path) -> CargoResult<RustcDepInfo> {
387 let contents = paths::read(rustc_dep_info)?;
388 let mut ret = RustcDepInfo::default();
389 let mut found_deps = false;
390
391 for line in contents.lines() {
392 if let Some(rest) = line.strip_prefix("# env-dep:") {
393 let mut parts = rest.splitn(2, '=');
394 let Some(env_var) = parts.next() else {
395 continue;
396 };
397 let env_val = match parts.next() {
398 Some(s) => Some(unescape_env(s)?),
399 None => None,
400 };
401 ret.env.push((unescape_env(env_var)?, env_val));
402 } else if let Some(pos) = line.find(": ") {
403 if found_deps {
404 continue;
405 }
406 found_deps = true;
407 let mut deps = line[pos + 2..].split_whitespace();
408
409 while let Some(s) = deps.next() {
410 let mut file = s.to_string();
411 while file.ends_with('\\') {
412 file.pop();
413 file.push(' ');
414 file.push_str(deps.next().ok_or_else(|| {
415 crate::util::internal("malformed dep-info format, trailing \\")
416 })?);
417 }
418 ret.files.entry(file.into()).or_default();
419 }
420 } else if let Some(rest) = line.strip_prefix("# checksum:") {
421 let mut parts = rest.splitn(3, ' ');
422 let Some(checksum) = parts.next().map(Checksum::from_str).transpose()? else {
423 continue;
424 };
425 let Some(Ok(file_len)) = parts
426 .next()
427 .and_then(|s| s.strip_prefix("file_len:").map(|s| s.parse::<u64>()))
428 else {
429 continue;
430 };
431 let Some(path) = parts.next().map(PathBuf::from) else {
432 continue;
433 };
434
435 ret.files.insert(path, Some((file_len, checksum)));
436 }
437 }
438 return Ok(ret);
439
440 fn unescape_env(s: &str) -> CargoResult<String> {
444 let mut ret = String::with_capacity(s.len());
445 let mut chars = s.chars();
446 while let Some(c) = chars.next() {
447 if c != '\\' {
448 ret.push(c);
449 continue;
450 }
451 match chars.next() {
452 Some('\\') => ret.push('\\'),
453 Some('n') => ret.push('\n'),
454 Some('r') => ret.push('\r'),
455 Some(c) => bail!("unknown escape character `{}`", c),
456 None => bail!("unterminated escape character"),
457 }
458 }
459 Ok(ret)
460 }
461}
462
463pub fn parse_dep_info(
475 pkg_root: &Path,
476 build_root: &Path,
477 dep_info: &Path,
478) -> CargoResult<Option<RustcDepInfo>> {
479 let Ok(data) = paths::read_bytes(dep_info) else {
480 return Ok(None);
481 };
482 let Some(info) = EncodedDepInfo::parse(&data) else {
483 tracing::warn!("failed to parse cargo's dep-info at {:?}", dep_info);
484 return Ok(None);
485 };
486 let mut ret = RustcDepInfo::default();
487 ret.env = info.env;
488 ret.files
489 .extend(info.files.into_iter().map(|(ty, path, checksum_info)| {
490 (
491 make_absolute_path(ty, pkg_root, build_root, path),
492 checksum_info.and_then(|(file_len, checksum)| {
493 Checksum::from_str(&checksum).ok().map(|c| (file_len, c))
494 }),
495 )
496 }));
497 Ok(Some(ret))
498}
499
500fn make_absolute_path(
501 ty: DepInfoPathType,
502 pkg_root: &Path,
503 build_root: &Path,
504 path: PathBuf,
505) -> PathBuf {
506 let relative_to = match ty {
507 DepInfoPathType::PackageRootRelative => pkg_root,
508 DepInfoPathType::BuildRootRelative => build_root,
510 };
511
512 if path.as_os_str().is_empty() {
513 return relative_to.to_path_buf();
516 }
517
518 relative_to.join(path)
519}
520
521#[derive(Copy, Clone, Debug, Eq, PartialEq)]
524pub enum ChecksumAlgo {
525 Sha256,
526 Blake3,
527}
528
529impl ChecksumAlgo {
530 fn hash_len(&self) -> usize {
531 match self {
532 ChecksumAlgo::Sha256 | ChecksumAlgo::Blake3 => 32,
533 }
534 }
535}
536
537impl FromStr for ChecksumAlgo {
538 type Err = InvalidChecksum;
539
540 fn from_str(s: &str) -> Result<Self, Self::Err> {
541 match s {
542 "sha256" => Ok(Self::Sha256),
543 "blake3" => Ok(Self::Blake3),
544 _ => Err(InvalidChecksum::InvalidChecksumAlgo),
545 }
546 }
547}
548
549impl fmt::Display for ChecksumAlgo {
550 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
551 f.write_str(match self {
552 ChecksumAlgo::Sha256 => "sha256",
553 ChecksumAlgo::Blake3 => "blake3",
554 })
555 }
556}
557
558#[derive(Copy, Clone, Debug, Eq, PartialEq)]
559pub struct Checksum {
560 algo: ChecksumAlgo,
561 value: [u8; 32],
563}
564
565impl Checksum {
566 pub fn new(algo: ChecksumAlgo, value: [u8; 32]) -> Self {
567 Self { algo, value }
568 }
569
570 pub fn compute(algo: ChecksumAlgo, contents: impl Read) -> Result<Self, io::Error> {
571 let mut buf = vec![0; 16 * 1024];
574 let mut ret = Self {
575 algo,
576 value: [0; 32],
577 };
578 let len = algo.hash_len();
579 let value = &mut ret.value[..len];
580
581 fn digest<T>(
582 mut hasher: T,
583 mut update: impl FnMut(&mut T, &[u8]),
584 finish: impl FnOnce(T, &mut [u8]),
585 mut contents: impl Read,
586 buf: &mut [u8],
587 value: &mut [u8],
588 ) -> Result<(), io::Error> {
589 loop {
590 let bytes_read = contents.read(buf)?;
591 if bytes_read == 0 {
592 break;
593 }
594 update(&mut hasher, &buf[0..bytes_read]);
595 }
596 finish(hasher, value);
597 Ok(())
598 }
599
600 match algo {
601 ChecksumAlgo::Sha256 => {
602 digest(
603 Sha256::new(),
604 |h, b| {
605 h.update(b);
606 },
607 |mut h, out| out.copy_from_slice(&h.finish()),
608 contents,
609 &mut buf,
610 value,
611 )?;
612 }
613 ChecksumAlgo::Blake3 => {
614 digest(
615 blake3::Hasher::new(),
616 |h, b| {
617 h.update(b);
618 },
619 |h, out| out.copy_from_slice(h.finalize().as_bytes()),
620 contents,
621 &mut buf,
622 value,
623 )?;
624 }
625 }
626 Ok(ret)
627 }
628
629 pub fn algo(&self) -> ChecksumAlgo {
630 self.algo
631 }
632
633 pub fn value(&self) -> &[u8; 32] {
634 &self.value
635 }
636}
637
638impl FromStr for Checksum {
639 type Err = InvalidChecksum;
640
641 fn from_str(s: &str) -> Result<Self, Self::Err> {
642 let mut parts = s.split('=');
643 let Some(algo) = parts.next().map(ChecksumAlgo::from_str).transpose()? else {
644 return Err(InvalidChecksum::InvalidFormat);
645 };
646 let Some(checksum) = parts.next() else {
647 return Err(InvalidChecksum::InvalidFormat);
648 };
649 let mut value = [0; 32];
650 if hex::decode_to_slice(checksum, &mut value[0..algo.hash_len()]).is_err() {
651 return Err(InvalidChecksum::InvalidChecksum(algo));
652 }
653 Ok(Self { algo, value })
654 }
655}
656
657impl fmt::Display for Checksum {
658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
659 let mut checksum = [0; 64];
660 let hash_len = self.algo.hash_len();
661 hex::encode_to_slice(&self.value[0..hash_len], &mut checksum[0..(hash_len * 2)])
662 .map_err(|_| fmt::Error)?;
663 write!(
664 f,
665 "{}={}",
666 self.algo,
667 str::from_utf8(&checksum[0..(hash_len * 2)]).unwrap_or_default()
668 )
669 }
670}
671
672impl Serialize for Checksum {
673 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
674 where
675 S: serde::Serializer,
676 {
677 serializer.serialize_str(&self.to_string())
678 }
679}
680
681#[derive(Debug, thiserror::Error)]
682pub enum InvalidChecksum {
683 #[error("algorithm portion incorrect, expected `sha256`, or `blake3`")]
684 InvalidChecksumAlgo,
685 #[error("expected {} hexadecimal digits in checksum portion", .0.hash_len() * 2)]
686 InvalidChecksum(ChecksumAlgo),
687 #[error("expected a string with format \"algorithm=hex_checksum\"")]
688 InvalidFormat,
689}
690
691#[cfg(test)]
692mod encoded_dep_info {
693 use super::*;
694
695 #[track_caller]
696 fn gen_test(checksum: bool) {
697 let checksum = checksum.then_some((768, "c01efc669f09508b55eced32d3c88702578a7c3e".into()));
698 let lib_rs = (
699 DepInfoPathType::BuildRootRelative,
700 PathBuf::from("src/lib.rs"),
701 checksum.clone(),
702 );
703
704 let depinfo = EncodedDepInfo {
705 files: vec![lib_rs.clone()],
706 env: Vec::new(),
707 };
708 let data = depinfo.serialize().unwrap();
709 assert_eq!(EncodedDepInfo::parse(&data).unwrap(), depinfo);
710
711 let mod_rs = (
712 DepInfoPathType::BuildRootRelative,
713 PathBuf::from("src/mod.rs"),
714 checksum.clone(),
715 );
716 let depinfo = EncodedDepInfo {
717 files: vec![lib_rs.clone(), mod_rs.clone()],
718 env: Vec::new(),
719 };
720 let data = depinfo.serialize().unwrap();
721 assert_eq!(EncodedDepInfo::parse(&data).unwrap(), depinfo);
722
723 let depinfo = EncodedDepInfo {
724 files: vec![lib_rs, mod_rs],
725 env: vec![
726 ("Gimli".into(), Some("Legolas".into())),
727 ("Beren".into(), Some("LĂșthien".into())),
728 ],
729 };
730 let data = depinfo.serialize().unwrap();
731 assert_eq!(EncodedDepInfo::parse(&data).unwrap(), depinfo);
732 }
733
734 #[test]
735 fn round_trip() {
736 gen_test(false);
737 }
738
739 #[test]
740 fn round_trip_with_checksums() {
741 gen_test(true);
742 }
743
744 #[test]
745 fn path_type_is_u8_max() {
746 #[rustfmt::skip]
747 let data = [
748 0x01, 0x00, 0x00, 0x00, 0xff, CURRENT_ENCODED_DEP_INFO_VERSION, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x72, 0x75, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, ];
757 assert_eq!(
759 EncodedDepInfo::parse(&data).unwrap(),
760 EncodedDepInfo {
761 files: vec![(DepInfoPathType::PackageRootRelative, "rust".into(), None)],
762 env: Vec::new(),
763 }
764 );
765 }
766
767 #[test]
768 fn parse_v0_fingerprint_dep_info() {
769 #[rustfmt::skip]
770 let data = [
771 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x72, 0x75, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, ];
777 assert!(EncodedDepInfo::parse(&data).is_none());
779 }
780}