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