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