Skip to content

Commit c475f79

Browse files
committed
[hermes] Add shadow copy creation
gherrit-pr-id: Gdc72294eb3c40c4afa61ea6e8f965ba310cd22ef
1 parent 3d5982e commit c475f79

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+6271
-7
lines changed

tools/Cargo.lock

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/hermes/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ serde = { version = "1.0.228", features = ["derive"] }
1818
serde_json = "1.0.149"
1919
syn = { version = "2.0.114", features = ["full", "visit", "extra-traits", "parsing"] }
2020
thiserror = "2.0.18"
21+
walkdir = "2.5.0"
2122

2223
[dev-dependencies]
2324
syn = { version = "2.0.114", features = ["printing", "full", "visit", "extra-traits", "parsing"] }

tools/hermes/src/main.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod errors;
22
mod parse;
33
mod resolve;
4+
mod shadow;
45
mod transform;
56
mod ui_test_shim;
67

@@ -27,11 +28,14 @@ fn main() {
2728
// TODO: Better error handling than `.unwrap()`.
2829
let roots = resolve::resolve_roots(&args.resolve).unwrap();
2930

31+
// TODO: What artifacts need to be updated (not just copied)? E.g., do we
32+
// need to update `Cargo.toml` to rewrite relative paths?
33+
3034
// TODO: From each root, parse and walk referenced modules.
3135
let mut has_errors = false;
32-
for (package, kind, path) in roots {
36+
for (package, kind, path) in roots.roots {
3337
let mut edits = Vec::new();
34-
let res = parse::read_file_and_visit_hermes_items(path.as_std_path(), |_src, res| {
38+
let res = parse::read_file_and_visit_hermes_items(&path, |_src, res| {
3539
if let Err(e) = res {
3640
has_errors = true;
3741
eprint!("{:?}", miette::Report::new(e));
@@ -52,4 +56,7 @@ fn main() {
5256
let mut source = source.into_bytes();
5357
transform::apply_edits(&mut source, &edits);
5458
}
59+
60+
// TODO: Create shadow skeleton.
61+
// shadow::create_shadow_skeleton(&roots.workspace, todo!(), todo!(), todo!()).unwrap();
5562
}

tools/hermes/src/resolve.rs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use std::env;
1+
use std::{env, path::PathBuf};
22

33
use anyhow::{anyhow, Context, Result};
44
use cargo_metadata::{
5-
camino::Utf8PathBuf, Metadata, MetadataCommand, Package, PackageName, Target, TargetKind,
5+
Metadata, MetadataCommand, Package, PackageName, Target, TargetKind,
66
};
77
use clap::Parser;
88

@@ -96,10 +96,15 @@ impl TryFrom<&TargetKind> for HermesTargetKind {
9696
}
9797
}
9898

99+
pub struct Roots {
100+
pub workspace: PathBuf,
101+
pub roots: Vec<(PackageName, HermesTargetKind, PathBuf)>,
102+
}
103+
99104
/// Resolves all verification roots.
100105
///
101106
/// Each entry represents a distinct compilation artifact to be verified.
102-
pub fn resolve_roots(args: &Args) -> Result<Vec<(PackageName, HermesTargetKind, Utf8PathBuf)>> {
107+
pub fn resolve_roots(args: &Args) -> Result<Roots> {
103108
let mut cmd = MetadataCommand::new();
104109

105110
if let Some(path) = &args.manifest.manifest_path {
@@ -111,10 +116,13 @@ pub fn resolve_roots(args: &Args) -> Result<Vec<(PackageName, HermesTargetKind,
111116
args.features.forward_metadata(&mut cmd);
112117

113118
let metadata = cmd.exec().context("Failed to run 'cargo metadata'")?;
119+
check_for_external_deps(&metadata)?;
114120

115121
let selected_packages = resolve_packages(&metadata, &args.workspace)?;
116122

117-
let mut roots = Vec::new();
123+
let mut roots =
124+
Roots { workspace: metadata.workspace_root.as_std_path().to_owned(), roots: Vec::new() };
125+
118126
for package in selected_packages {
119127
log::trace!("Scanning package: {}", package.name);
120128

@@ -126,7 +134,11 @@ pub fn resolve_roots(args: &Args) -> Result<Vec<(PackageName, HermesTargetKind,
126134
}
127135

128136
for (target, kind) in targets {
129-
roots.push((package.name.clone(), kind.clone(), target.src_path.clone()));
137+
roots.roots.push((
138+
package.name.clone(),
139+
kind.clone(),
140+
target.src_path.as_std_path().to_owned(),
141+
));
130142
}
131143
}
132144

@@ -254,3 +266,35 @@ fn resolve_targets<'a>(
254266

255267
Ok(selected_artifacts)
256268
}
269+
270+
// TODO: Eventually, we'll want to support external path dependencies by
271+
// rewriting the path in the `Cargo.toml` in the shadow copy.
272+
273+
/// Scans the package graph to ensure all local dependencies are contained
274+
/// within the workspace root. Returns an error if an external path dependency
275+
/// is found.
276+
pub fn check_for_external_deps(metadata: &Metadata) -> Result<()> {
277+
let workspace_root = metadata.workspace_root.as_std_path();
278+
279+
for pkg in &metadata.packages {
280+
// We only care about packages that are "local" (source is None).
281+
// If source is Some(...), it's from crates.io or git, which is fine
282+
// (handled by Cargo).
283+
if pkg.source.is_none() {
284+
let pkg_path = pkg.manifest_path.as_std_path();
285+
286+
// Check if the package lives outside the workspace tree
287+
if !pkg_path.starts_with(workspace_root) {
288+
anyhow::bail!(
289+
"Unsupported external dependency: '{}' at {:?}.\n\
290+
Hermes currently only supports verifying workspaces where all local \
291+
dependencies are contained within the workspace root.",
292+
pkg.name,
293+
pkg_path
294+
);
295+
}
296+
}
297+
}
298+
299+
Ok(())
300+
}

tools/hermes/src/shadow.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::{
2+
collections::HashSet,
3+
fs,
4+
path::{Path, PathBuf},
5+
};
6+
7+
use anyhow::{Context, Result};
8+
use walkdir::WalkDir;
9+
10+
// TODO: Call this with `skip_paths` set to all `.rs` files which we've found
11+
// via our traversal using syn.
12+
13+
/// Creates a "Shadow Skeleton" of the source project.
14+
pub fn create_shadow_skeleton(
15+
source_root: &Path,
16+
dest_root: &Path,
17+
target_dir: &Path,
18+
skip_paths: &HashSet<PathBuf>,
19+
) -> Result<()> {
20+
let walker = WalkDir::new(source_root)
21+
.follow_links(false) // Security: don't follow symlinks out of the root.
22+
.into_iter();
23+
24+
for entry in walker.filter_entry(|e| e.path() != target_dir && e.file_name() != ".git") {
25+
let entry = entry.context("Failed to read directory entry")?;
26+
let src_path = entry.path();
27+
28+
let relative_path =
29+
src_path.strip_prefix(source_root).context("File is not inside source root")?;
30+
let dest_path = dest_root.join(relative_path);
31+
32+
if entry.file_type().is_dir() {
33+
fs::create_dir_all(&dest_path)
34+
.with_context(|| format!("Failed to create shadow directory: {:?}", dest_path))?;
35+
continue;
36+
}
37+
38+
if entry.file_type().is_file() && !skip_paths.contains(src_path) {
39+
make_symlink(src_path, &dest_path)?;
40+
}
41+
}
42+
43+
Ok(())
44+
}
45+
46+
#[cfg(unix)]
47+
fn make_symlink(original: &Path, link: &Path) -> Result<()> {
48+
std::os::unix::fs::symlink(original, link)
49+
.with_context(|| format!("Failed to symlink {:?} -> {:?}", original, link))
50+
}
51+
52+
#[cfg(windows)]
53+
fn make_symlink(original: &Path, link: &Path) -> Result<()> {
54+
// Windows treats file and directory symlinks differently.
55+
// Since we only call this on files (checking is_file above),
56+
// we use symlink_file.
57+
std::os::windows::fs::symlink_file(original, link)
58+
.with_context(|| format!("Failed to symlink {:?} -> {:?}", original, link))
59+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"files":{".cargo_vcs_info.json":"6dd5cd03c25ddfc57cd9bd43fcd964b24fecb903f60fd1568f092121b22fee93","COPYING":"01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f","Cargo.lock":"fa40407b035c7abffe97d267d2ff95d22d83e5b916aca876bec49a56a9067c73","Cargo.toml":"991f8df8fa5a259801900a56908cf21a66c5cf7b238bc81ba9bdf348e233252e","Cargo.toml.orig":"d0b19f99d598bf88dd101f441f577d7a82455cf85d1f44aa0771e1d1f633da45","LICENSE-MIT":"cb3c929a05e6cbc9de9ab06a4c57eeb60ca8c724bef6c138c87d3a577e27aa14","README.md":"70c109d9c89b4479016142f2a4ad6963b6fe5793bcdd997add3d3af3d2baf36b","UNLICENSE":"7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c","examples/is_same_file.rs":"7b3eeb27a15051667d97615fc7a2339cbff5630df3bca6ac19ab81d5be22f329","examples/is_stderr.rs":"e1c5d1a0f36d7aa0020bb5b87c2f45c7176033f03c52cf395be55dd8debfc413","rustfmt.toml":"1ca600239a27401c4a43f363cf3f38183a212affc1f31bff3ae93234bbaec228","src/lib.rs":"b22c2f0b5cad2248f16f4f42add52b2dc0c627631f71ee67a8c38fe305048f85","src/unix.rs":"69abed9fade151247696c6d4a442ef299554f3722e23a2d08053598a52a27d62","src/unknown.rs":"bfde4e9ac88f500c0ccb69165383682ddd24bf7d7ddaf5859426e1fd4b2f9359","src/win.rs":"94f912cc3734f60608d0ee2b0c664afb65fc96e5b0b223a53565fb8998c03fa3"},"package":"93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"git": {
3+
"sha1": "5799cd323b8eefd17a089c950dac113f66c89c9e"
4+
}
5+
}

tools/vendor/same-file/COPYING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This project is dual-licensed under the Unlicense and MIT licenses.
2+
3+
You may use this code under the terms of either license.

tools/vendor/same-file/Cargo.lock

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/vendor/same-file/Cargo.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
2+
#
3+
# When uploading crates to the registry Cargo will automatically
4+
# "normalize" Cargo.toml files for maximal compatibility
5+
# with all versions of Cargo and also rewrite `path` dependencies
6+
# to registry (e.g., crates.io) dependencies
7+
#
8+
# If you believe there's an error in this file please file an
9+
# issue against the rust-lang/cargo repository. If you're
10+
# editing this file be aware that the upstream Cargo.toml
11+
# will likely look very different (and much more reasonable)
12+
13+
[package]
14+
edition = "2018"
15+
name = "same-file"
16+
version = "1.0.6"
17+
authors = ["Andrew Gallant <jamslam@gmail.com>"]
18+
exclude = ["/.github"]
19+
description = "A simple crate for determining whether two file paths point to the same file.\n"
20+
homepage = "https://github.com/BurntSushi/same-file"
21+
documentation = "https://docs.rs/same-file"
22+
readme = "README.md"
23+
keywords = ["same", "file", "equal", "inode"]
24+
license = "Unlicense/MIT"
25+
repository = "https://github.com/BurntSushi/same-file"
26+
[dev-dependencies.doc-comment]
27+
version = "0.3"
28+
[target."cfg(windows)".dependencies.winapi-util]
29+
version = "0.1.1"

0 commit comments

Comments
 (0)