Repository: rust-lang/cargo (rust)

Issue:
`cargo:rerun-if-env-changed` doesn't work with `env` configuration
### Problem

When a `build.rs` script emits `cargo:rerun-if-env-changed`, it is not re-run when the value of the specified variable is changed via the `env` configuration.



### Steps

`build.rs`:
```rust
pub fn main() {
    println!("cargo:rerun-if-env-changed=FOO");
    if let Ok(foo) = std::env::var("FOO") {
        assert!(&foo != "bad");
    }
}
```

`.cargo/config.toml`:
```toml
[env]
FOO = "good"
```

1. Run `cargo build`, it succeeds.
2. Change `.cargo/config.toml` to this:
```toml
[env]
FOO = "bad"
```
3. Run `cargo build` again, and it uses the cached build and succeeds. It should run `build.rs` again, and it should fail.

# Workaround

4. Run `FOO=bar cargo build`, ignore the result.
5. Run `cargo build` again, now it runs `build.rs` with `FOO=bad` from the config and fails as expected.

### Possible Solution(s)

_No response_

### Notes

_No response_

### Version

```text
cargo 1.58.0 (7f08ace4f 2021-11-24)
release: 1.58.0
commit-hash: 7f08ace4f1305de7f3b1b0e2f765911957226bd4
commit-date: 2021-11-24
host: x86_64-unknown-linux-gnu
libgit2: 1.3.0 (sys:0.13.23 vendored)
libcurl: 7.80.0-DEV (sys:0.4.51+curl-7.80.0 vendored ssl:OpenSSL/1.1.1l)
os: Debian 11 (bullseye) [64-bit]
```


Pull request:
fix: env table config can't trigger rebuild with `rerun-if-env-changed`.
### What does this PR try to resolve?

As #10358 said, `When a build.rs script emits cargo:rerun-if-env-changed, it is not re-run when the value of the specified variable is changed via the env configuration.`  

Fixes #10358 

### How should we test and review this PR?
Add test bofore fixing to reflect the issue, the next commit fixed it.

### Additional information

The PR is a sucessor of https://github.com/rust-lang/cargo/pull/14058, so the previous dicussion can be refer to it.

Patch:
diff --git a/crates/cargo-util/src/paths.rs b/crates/cargo-util/src/paths.rs
index cc73fbad8..5d7e3c5a6 100644
--- a/crates/cargo-util/src/paths.rs
+++ b/crates/cargo-util/src/paths.rs
@@ -710,9 +710,9 @@ pub fn set_file_time_no_err<P: AsRef<Path>>(path: P, time: FileTime) {
 /// This canonicalizes both paths before stripping. This is useful if the
 /// paths are obtained in different ways, and one or the other may or may not
 /// have been normalized in some way.
-pub fn strip_prefix_canonical(
-    path: impl AsRef<Path>,
-    base: impl AsRef<Path>,
+pub fn strip_prefix_canonical<P: AsRef<Path>>(
+    path: P,
+    base: P,
 ) -> Result<PathBuf, std::path::StripPrefixError> {
     // Not all filesystems support canonicalize. Just ignore if it doesn't work.
     let safe_canonicalize = |path: &Path| match path.canonicalize() {
diff --git a/src/cargo/core/compiler/fingerprint/mod.rs b/src/cargo/core/compiler/fingerprint/mod.rs
index 623e4c59b..b35a592ab 100644
--- a/src/cargo/core/compiler/fingerprint/mod.rs
+++ b/src/cargo/core/compiler/fingerprint/mod.rs
@@ -374,6 +374,7 @@ mod dirty_reason;
 
 use std::collections::hash_map::{Entry, HashMap};
 use std::env;
+use std::ffi::OsString;
 use std::fs;
 use std::fs::File;
 use std::hash::{self, Hash, Hasher};
@@ -509,7 +510,7 @@ pub fn prepare_target(
         // thunk we can invoke on a foreign thread to calculate this.
         let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
         let metadata = build_runner.get_run_build_script_metadata(unit);
-        let (gen_local, _overridden) = build_script_local_fingerprints(build_runner, unit);
+        let (gen_local, _overridden) = build_script_local_fingerprints(build_runner, unit)?;
         let output_path = build_runner.build_explicit_deps[unit]
             .build_script_output
             .clone();
@@ -801,15 +802,24 @@ pub enum StaleItem {
 
 impl LocalFingerprint {
     /// Read the environment variable of the given env `key`, and creates a new
-    /// [`LocalFingerprint::RerunIfEnvChanged`] for it.
+    /// [`LocalFingerprint::RerunIfEnvChanged`] for it. The `env_config` is used firstly
+    /// to check if the env var is set in the config system as some envs need to be overridden.
+    /// If not, it will fallback to `std::env::var`.
     ///
-    // TODO: This is allowed at this moment. Should figure out if it makes
-    // sense if permitting to read env from the config system.
+    // TODO: `std::env::var` is allowed at this moment. Should figure out
+    // if it makes sense if permitting to read env from the env snapshot.
     #[allow(clippy::disallowed_methods)]
-    fn from_env<K: AsRef<str>>(key: K) -> LocalFingerprint {
+    fn from_env<K: AsRef<str>>(
+        key: K,
+        env_config: &Arc<HashMap<String, OsString>>,
+    ) -> LocalFingerprint {
         let key = key.as_ref();
         let var = key.to_owned();
-        let val = env::var(key).ok();
+        let val = if let Some(val) = env_config.get(key) {
+            val.to_str().map(ToOwned::to_owned)
+        } else {
+            env::var(key).ok()
+        };
         LocalFingerprint::RerunIfEnvChanged { var, val }
     }
 
@@ -1577,7 +1587,7 @@ fn calculate_run_custom_build(
     // the build script this means we'll be watching files and env vars.
     // Otherwise if we haven't previously executed it we'll just start watching
     // the whole crate.
-    let (gen_local, overridden) = build_script_local_fingerprints(build_runner, unit);
+    let (gen_local, overridden) = build_script_local_fingerprints(build_runner, unit)?;
     let deps = &build_runner.build_explicit_deps[unit];
     let local = (gen_local)(
         deps,
@@ -1671,7 +1681,7 @@ See https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-change
 fn build_script_local_fingerprints(
     build_runner: &mut BuildRunner<'_, '_>,
     unit: &Unit,
-) -> (
+) -> CargoResult<(
     Box<
         dyn FnOnce(
                 &BuildDeps,
@@ -1680,20 +1690,20 @@ fn build_script_local_fingerprints(
             + Send,
     >,
     bool,
-) {
+)> {
     assert!(unit.mode.is_run_custom_build());
     // First up, if this build script is entirely overridden, then we just
     // return the hash of what we overrode it with. This is the easy case!
     if let Some(fingerprint) = build_script_override_fingerprint(build_runner, unit) {
         debug!("override local fingerprints deps {}", unit.pkg);
-        return (
+        return Ok((
             Box::new(
                 move |_: &BuildDeps, _: Option<&dyn Fn() -> CargoResult<String>>| {
                     Ok(Some(vec![fingerprint]))
                 },
             ),
             true, // this is an overridden build script
-        );
+        ));
     }
 
     // ... Otherwise this is a "real" build script and we need to return a real
@@ -1705,6 +1715,7 @@ fn build_script_local_fingerprints(
     // obvious.
     let pkg_root = unit.pkg.root().to_path_buf();
     let target_dir = target_root(build_runner);
+    let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?);
     let calculate =
         move |deps: &BuildDeps, pkg_fingerprint: Option<&dyn Fn() -> CargoResult<String>>| {
             if deps.rerun_if_changed.is_empty() && deps.rerun_if_env_changed.is_empty() {
@@ -1734,11 +1745,16 @@ fn build_script_local_fingerprints(
             // Ok so now we're in "new mode" where we can have files listed as
             // dependencies as well as env vars listed as dependencies. Process
             // them all here.
-            Ok(Some(local_fingerprints_deps(deps, &target_dir, &pkg_root)))
+            Ok(Some(local_fingerprints_deps(
+                deps,
+                &target_dir,
+                &pkg_root,
+                &env_config,
+            )))
         };
 
     // Note that `false` == "not overridden"
-    (Box::new(calculate), false)
+    Ok((Box::new(calculate), false))
 }
 
 /// Create a [`LocalFingerprint`] for an overridden build script.
@@ -1769,6 +1785,7 @@ fn local_fingerprints_deps(
     deps: &BuildDeps,
     target_root: &Path,
     pkg_root: &Path,
+    env_config: &Arc<HashMap<String, OsString>>,
 ) -> Vec<LocalFingerprint> {
     debug!("new local fingerprints deps {:?}", pkg_root);
     let mut local = Vec::new();
@@ -1793,7 +1810,7 @@ fn local_fingerprints_deps(
     local.extend(
         deps.rerun_if_env_changed
             .iter()
-            .map(LocalFingerprint::from_env),
+            .map(|s| LocalFingerprint::from_env(s, env_config)),
     );
 
     local
diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs
index 6c920eec4..07ba919c9 100644
--- a/src/cargo/ops/cargo_package/mod.rs
+++ b/src/cargo/ops/cargo_package/mod.rs
@@ -16,7 +16,6 @@ use crate::core::Workspace;
 use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId};
 use crate::ops::lockfile::LOCKFILE_NAME;
 use crate::ops::registry::{infer_registry, RegistryOrIndex};
-use crate::sources::path::PathEntry;
 use crate::sources::registry::index::{IndexPackage, RegistryDependency};
 use crate::sources::{PathSource, CRATES_IO_REGISTRY};
 use crate::util::cache_lock::CacheLockMode;
@@ -397,7 +396,7 @@ fn prepare_archive(
 fn build_ar_list(
     ws: &Workspace<'_>,
     pkg: &Package,
-    src_files: Vec<PathEntry>,
+    src_files: Vec<PathBuf>,
     vcs_info: Option<vcs::VcsInfo>,
 ) -> CargoResult<Vec<ArchiveFile>> {
     let mut result = HashMap::new();
@@ -421,7 +420,7 @@ fn build_ar_list(
                     .push(ArchiveFile {
                         rel_path: rel_path.to_owned(),
                         rel_str: rel_str.to_owned(),
-                        contents: FileContents::OnDisk(src_file.to_path_buf()),
+                        contents: FileContents::OnDisk(src_file.clone()),
                     });
             }
         }
diff --git a/src/cargo/ops/cargo_package/vcs.rs b/src/cargo/ops/cargo_package/vcs.rs
index 9f2f57284..44adfd85a 100644
--- a/src/cargo/ops/cargo_package/vcs.rs
+++ b/src/cargo/ops/cargo_package/vcs.rs
@@ -1,6 +1,6 @@
 //! Helpers to gather the VCS information for `cargo package`.
 
-use std::collections::HashSet;
+use std::path::Path;
 use std::path::PathBuf;
 
 use anyhow::Context as _;
@@ -9,7 +9,6 @@ use serde::Serialize;
 use tracing::debug;
 
 use crate::core::Package;
-use crate::sources::PathEntry;
 use crate::CargoResult;
 use crate::GlobalContext;
 
@@ -42,7 +41,7 @@ pub struct GitVcsInfo {
 #[tracing::instrument(skip_all)]
 pub fn check_repo_state(
     p: &Package,
-    src_files: &[PathEntry],
+    src_files: &[PathBuf],
     gctx: &GlobalContext,
     opts: &PackageOpts<'_>,
 ) -> CargoResult<Option<VcsInfo>> {
@@ -92,8 +91,6 @@ pub fn check_repo_state(
         return Ok(None);
     }
 
-    warn_symlink_checked_out_as_plain_text_file(gctx, src_files, &repo)?;
-
     debug!(
         "found (git) Cargo.toml at `{}` in workdir `{}`",
         path.display(),
@@ -113,57 +110,11 @@ pub fn check_repo_state(
     return Ok(Some(VcsInfo { git, path_in_vcs }));
 }
 
-/// Warns if any symlinks were checked out as plain text files.
-///
-/// Git config [`core.symlinks`] defaults to true when unset.
-/// In git-for-windows (and git as well),
-/// the config should be set to false explicitly when the repo was created,
-/// if symlink support wasn't detected [^1].
-///
-/// We assume the config was always set at creation time and never changed.
-/// So, if it is true, we don't bother users with any warning.
-///
-/// [^1]: <https://github.com/git-for-windows/git/blob/f1241afcc7956918d5da33ef74abd9cbba369247/setup.c#L2394-L2403>
-///
-/// [`core.symlinks`]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresymlinks
-fn warn_symlink_checked_out_as_plain_text_file(
-    gctx: &GlobalContext,
-    src_files: &[PathEntry],
-    repo: &git2::Repository,
-) -> CargoResult<()> {
-    if repo
-        .config()
-        .and_then(|c| c.get_bool("core.symlinks"))
-        .unwrap_or(true)
-    {
-        return Ok(());
-    }
-
-    if src_files.iter().any(|f| f.maybe_plain_text_symlink()) {
-        let mut shell = gctx.shell();
-        shell.warn(format_args!(
-            "found symbolic links that may be checked out as regular files for git repo at `{}`\n\
-            This might cause the `.crate` file to include incorrect or incomplete files",
-            repo.workdir().unwrap().display(),
-        ))?;
-        let extra_note = if cfg!(windows) {
-            "\nAnd on Windows, enable the Developer Mode to support symlinks"
-        } else {
-            ""
-        };
-        shell.note(format_args!(
-            "to avoid this, set the Git config `core.symlinks` to `true`{extra_note}",
-        ))?;
-    }
-
-    Ok(())
-}
-
 /// The real git status check starts from here.
 fn git(
     pkg: &Package,
     gctx: &GlobalContext,
-    src_files: &[PathEntry],
+    src_files: &[PathBuf],
     repo: &git2::Repository,
     opts: &PackageOpts<'_>,
 ) -> CargoResult<Option<GitVcsInfo>> {
@@ -185,8 +136,7 @@ fn git(
     let mut dirty_src_files: Vec<_> = src_files
         .iter()
         .filter(|src_file| dirty_files.iter().any(|path| src_file.starts_with(path)))
-        .map(|p| p.as_ref())
-        .chain(dirty_files_outside_pkg_root(pkg, repo, src_files)?.iter())
+        .chain(dirty_metadata_paths(pkg, repo)?.iter())
         .map(|path| {
             pathdiff::diff_paths(path, cwd)
                 .as_ref()
@@ -219,47 +169,39 @@ fn git(
     }
 }
 
-/// Checks whether "included" source files outside package root have been modified.
-///
-/// This currently looks at
-///
-/// * `package.readme` and `package.license-file` pointing to paths outside package root
-/// * symlinks targets reside outside package root
+/// Checks whether files at paths specified in `package.readme` and
+/// `package.license-file` have been modified.
 ///
 /// This is required because those paths may link to a file outside the
 /// current package root, but still under the git workdir, affecting the
 /// final packaged `.crate` file.
-fn dirty_files_outside_pkg_root(
-    pkg: &Package,
-    repo: &git2::Repository,
-    src_files: &[PathEntry],
-) -> CargoResult<HashSet<PathBuf>> {
-    let pkg_root = pkg.root();
+fn dirty_metadata_paths(pkg: &Package, repo: &git2::Repository) -> CargoResult<Vec<PathBuf>> {
+    let mut dirty_files = Vec::new();
     let workdir = repo.workdir().unwrap();
-
+    let root = pkg.root();
     let meta = pkg.manifest().metadata();
-    let metadata_paths: Vec<_> = [&meta.license_file, &meta.readme]
-        .into_iter()
-        .filter_map(|p| p.as_deref())
-        .map(|path| paths::normalize_path(&pkg_root.join(path)))
-        .collect();
-
-    let mut dirty_symlinks = HashSet::new();
-    for rel_path in src_files
-        .iter()
-        .filter(|p| p.is_symlink_or_under_symlink())
-        .map(|p| p.as_ref())
-        .chain(metadata_paths.iter())
-        // If inside package root. Don't bother checking git status.
-        .filter(|p| paths::strip_prefix_canonical(p, pkg_root).is_err())
-        // Handle files outside package root but under git workdir,
-        .filter_map(|p| paths::strip_prefix_canonical(p, workdir).ok())
-    {
-        if repo.status_file(&rel_path)? != git2::Status::CURRENT {
-            dirty_symlinks.insert(workdir.join(rel_path));
+    for path in [&meta.license_file, &meta.readme] {
+        let Some(path) = path.as_deref().map(Path::new) else {
+            continue;
+        };
+        let abs_path = paths::normalize_path(&root.join(path));
+        if paths::strip_prefix_canonical(abs_path.as_path(), root).is_ok() {
+            // Inside package root. Don't bother checking git status.
+            continue;
+        }
+        if let Ok(rel_path) = paths::strip_prefix_canonical(abs_path.as_path(), workdir) {
+            // Outside package root but under git workdir,
+            if repo.status_file(&rel_path)? != git2::Status::CURRENT {
+                dirty_files.push(if abs_path.is_symlink() {
+                    // For symlinks, shows paths to symlink sources
+                    workdir.join(rel_path)
+                } else {
+                    abs_path
+                });
+            }
         }
     }
-    Ok(dirty_symlinks)
+    Ok(dirty_files)
 }
 
 /// Helper to collect dirty statuses for a single repo.
diff --git a/src/cargo/ops/vendor.rs b/src/cargo/ops/vendor.rs
index c6c76f624..35d0e0c94 100644
--- a/src/cargo/ops/vendor.rs
+++ b/src/cargo/ops/vendor.rs
@@ -2,7 +2,6 @@ use crate::core::shell::Verbosity;
 use crate::core::{GitReference, Package, Workspace};
 use crate::ops;
 use crate::sources::path::PathSource;
-use crate::sources::PathEntry;
 use crate::sources::CRATES_IO_REGISTRY;
 use crate::util::cache_lock::CacheLockMode;
 use crate::util::{try_canonicalize, CargoResult, GlobalContext};
@@ -316,14 +315,13 @@ fn sync(
 fn cp_sources(
     pkg: &Package,
     src: &Path,
-    paths: &[PathEntry],
+    paths: &[PathBuf],
     dst: &Path,
     cksums: &mut BTreeMap<String, String>,
     tmp_buf: &mut [u8],
     gctx: &GlobalContext,
 ) -> CargoResult<()> {
     for p in paths {
-        let p = p.as_ref();
         let relative = p.strip_prefix(&src).unwrap();
 
         match relative.to_str() {
diff --git a/src/cargo/sources/mod.rs b/src/cargo/sources/mod.rs
index dfbf79c76..9c98cc49e 100644
--- a/src/cargo/sources/mod.rs
+++ b/src/cargo/sources/mod.rs
@@ -29,7 +29,6 @@
 pub use self::config::SourceConfigMap;
 pub use self::directory::DirectorySource;
 pub use self::git::GitSource;
-pub use self::path::PathEntry;
 pub use self::path::PathSource;
 pub use self::path::RecursivePathSource;
 pub use self::registry::{
diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs
index c1e81528f..ee2e6fea4 100644
--- a/src/cargo/sources/path.rs
+++ b/src/cargo/sources/path.rs
@@ -94,7 +94,7 @@ impl<'gctx> PathSource<'gctx> {
     /// use other methods like `.gitignore`, `package.include`, or
     /// `package.exclude` to filter the list of files.
     #[tracing::instrument(skip_all)]
-    pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathEntry>> {
+    pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathBuf>> {
         list_files(pkg, self.gctx)
     }
 
@@ -278,7 +278,7 @@ impl<'gctx> RecursivePathSource<'gctx> {
     /// are relevant for building this package, but it also contains logic to
     /// use other methods like `.gitignore`, `package.include`, or
     /// `package.exclude` to filter the list of files.
-    pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathEntry>> {
+    pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathBuf>> {
         list_files(pkg, self.gctx)
     }
 
@@ -404,116 +404,6 @@ impl<'gctx> Source for RecursivePathSource<'gctx> {
     }
 }
 
-/// Type that abstracts over [`gix::dir::entry::Kind`] and [`fs::FileType`].
-#[derive(Debug, Clone, Copy)]
-enum FileType {
-    File { maybe_symlink: bool },
-    Dir,
-    Symlink,
-    Other,
-}
-
-impl From<fs::FileType> for FileType {
-    fn from(value: fs::FileType) -> Self {
-        if value.is_file() {
-            FileType::File {
-                maybe_symlink: false,
-            }
-        } else if value.is_dir() {
-            FileType::Dir
-        } else if value.is_symlink() {
-            FileType::Symlink
-        } else {
-            FileType::Other
-        }
-    }
-}
-
-impl From<gix::dir::entry::Kind> for FileType {
-    fn from(value: gix::dir::entry::Kind) -> Self {
-        use gix::dir::entry::Kind;
-        match value {
-            Kind::Untrackable => FileType::Other,
-            Kind::File => FileType::File {
-                maybe_symlink: false,
-            },
-            Kind::Symlink => FileType::Symlink,
-            Kind::Directory | Kind::Repository => FileType::Dir,
-        }
-    }
-}
-
-/// [`PathBuf`] with extra metadata.
-#[derive(Clone, Debug)]
-pub struct PathEntry {
-    path: PathBuf,
-    ty: FileType,
-    /// Whether this path was visited when traversing a symlink directory.
-    under_symlink_dir: bool,
-}
-
-impl PathEntry {
-    pub fn into_path_buf(self) -> PathBuf {
-        self.path
-    }
-
-    /// Similar to [`std::path::Path::is_file`]
-    /// but doesn't follow the symbolic link nor make any system call
-    pub fn is_file(&self) -> bool {
-        matches!(self.ty, FileType::File { .. })
-    }
-
-    /// Similar to [`std::path::Path::is_dir`]
-    /// but doesn't follow the symbolic link nor make any system call
-    pub fn is_dir(&self) -> bool {
-        matches!(self.ty, FileType::Dir)
-    }
-
-    /// Similar to [`std::path::Path::is_symlink`]
-    /// but doesn't follow the symbolic link nor make any system call
-    ///
-    /// If the path is not a symlink but under a symlink parent directory,
-    /// this will return false.
-    /// See [`PathEntry::is_symlink_or_under_symlink`] for an alternative.
-    pub fn is_symlink(&self) -> bool {
-        matches!(self.ty, FileType::Symlink)
-    }
-
-    /// Whether a path is a symlink or a path under a symlink directory.
-    ///
-    /// Use [`PathEntry::is_symlink`] to get the exact file type of the path only.
-    pub fn is_symlink_or_under_symlink(&self) -> bool {
-        self.is_symlink() || self.under_symlink_dir
-    }
-
-    /// Whether this path might be a plain text symlink.
-    ///
-    /// Git may check out symlinks as plain text files that contain the link texts,
-    /// when either `core.symlinks` is `false`, or on Windows.
-    pub fn maybe_plain_text_symlink(&self) -> bool {
-        matches!(
-            self.ty,
-            FileType::File {
-                maybe_symlink: true
-            }
-        )
-    }
-}
-
-impl std::ops::Deref for PathEntry {
-    type Target = Path;
-
-    fn deref(&self) -> &Self::Target {
-        self.path.as_path()
-    }
-}
-
-impl AsRef<PathBuf> for PathEntry {
-    fn as_ref(&self) -> &PathBuf {
-        &self.path
-    }
-}
-
 fn first_package<'p>(
     pkg_id: PackageId,
     pkgs: &'p Vec<Package>,
@@ -556,7 +446,7 @@ fn first_package<'p>(
 /// are relevant for building this package, but it also contains logic to
 /// use other methods like `.gitignore`, `package.include`, or
 /// `package.exclude` to filter the list of files.
-pub fn list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathEntry>> {
+pub fn list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathBuf>> {
     _list_files(pkg, gctx).with_context(|| {
         format!(
             "failed to determine list of files in {}",
@@ -566,7 +456,7 @@ pub fn list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathEn
 }
 
 /// See [`PathSource::list_files`].
-fn _list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathEntry>> {
+fn _list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathBuf>> {
     let root = pkg.root();
     let no_include_option = pkg.manifest().include().is_empty();
     let git_repo = if no_include_option {
@@ -690,7 +580,7 @@ fn list_files_gix(
     repo: &gix::Repository,
     filter: &dyn Fn(&Path, bool) -> bool,
     gctx: &GlobalContext,
-) -> CargoResult<Vec<PathEntry>> {
+) -> CargoResult<Vec<PathBuf>> {
     debug!("list_files_gix {}", pkg.package_id());
     let options = repo
         .dirwalk_options()?
@@ -729,7 +619,7 @@ fn list_files_gix(
         vec![include, exclude]
     };
 
-    let mut files = Vec::<PathEntry>::new();
+    let mut files = Vec::<PathBuf>::new();
     let mut subpackages_found = Vec::new();
     for item in repo
         .dirwalk_iter(index.clone(), pathspec, Default::default(), options)?
@@ -743,24 +633,7 @@ fn list_files_gix(
                         && item.entry.rela_path == "Cargo.lock")
             })
         })
-        .map(|res| {
-            res.map(|item| {
-                // Assumption: if a file tracked as a symlink in Git index, and
-                // the actual file type on disk is file, then it might be a
-                // plain text file symlink.
-                // There are exceptions like the file has changed from a symlink
-                // to a real text file, but hasn't been committed to Git index.
-                // Exceptions may be rare so we're okay with this now.
-                let maybe_plain_text_symlink = item.entry.index_kind
-                    == Some(gix::dir::entry::Kind::Symlink)
-                    && item.entry.disk_kind == Some(gix::dir::entry::Kind::File);
-                (
-                    item.entry.rela_path,
-                    item.entry.disk_kind,
-                    maybe_plain_text_symlink,
-                )
-            })
-        })
+        .map(|res| res.map(|item| (item.entry.rela_path, item.entry.disk_kind)))
         .chain(
             // Append entries that might be tracked in `<pkg_root>/target/`.
             index
@@ -778,13 +651,12 @@ fn list_files_gix(
                         // This traversal is not part of a `status()`, and tracking things in `target/`
                         // is rare.
                         None,
-                        false,
                     )
                 })
                 .map(Ok),
         )
     {
-        let (rela_path, kind, maybe_plain_text_symlink) = item?;
+        let (rela_path, kind) = item?;
         let file_path = root.join(gix::path::from_bstr(rela_path));
         if file_path.file_name().and_then(|name| name.to_str()) == Some("Cargo.toml") {
             // Keep track of all sub-packages found and also strip out all
@@ -829,20 +701,7 @@ fn list_files_gix(
         } else if (filter)(&file_path, is_dir) {
             assert!(!is_dir);
             trace!("  found {}", file_path.display());
-            let ty = match kind.map(Into::into) {
-                Some(FileType::File { .. }) => FileType::File {
-                    maybe_symlink: maybe_plain_text_symlink,
-                },
-                Some(ty) => ty,
-                None => FileType::Other,
-            };
-            files.push(PathEntry {
-                path: file_path,
-                ty,
-                // Git index doesn't include files from symlink diretory,
-                // symlink dirs are handled in `list_files_walk`.
-                under_symlink_dir: false,
-            });
+            files.push(file_path);
         }
     }
 
@@ -856,17 +715,13 @@ fn list_files_gix(
 /// is not tracked under a Git repository.
 fn list_files_walk(
     path: &Path,
-    ret: &mut Vec<PathEntry>,
+    ret: &mut Vec<PathBuf>,
     is_root: bool,
     filter: &dyn Fn(&Path, bool) -> bool,
     gctx: &GlobalContext,
 ) -> CargoResult<()> {
     let walkdir = WalkDir::new(path)
         .follow_links(true)
-        // While this is the default, set it explicitly.
-        // We need walkdir to visit the directory tree in depth-first order,
-        // so we can ensure a path visited later be under a certain directory.
-        .contents_first(false)
         .into_iter()
         .filter_entry(|entry| {
             let path = entry.path();
@@ -896,40 +751,12 @@ fn list_files_walk(
 
             true
         });
-
-    let mut current_symlink_dir = None;
     for entry in walkdir {
         match entry {
             Ok(entry) => {
                 let file_type = entry.file_type();
-
-                match current_symlink_dir.as_ref() {
-                    Some(dir) if entry.path().starts_with(dir) => {
-                        // Still walk under the same parent symlink dir, so keep it
-                    }
-                    Some(_) | None => {
-                        // Not under any parent symlink dir, update the current one.
-                        current_symlink_dir = if file_type.is_dir() && entry.path_is_symlink() {
-                            Some(entry.path().to_path_buf())
-                        } else {
-                            None
-                        };
-                    }
-                }
-
                 if file_type.is_file() || file_type.is_symlink() {
-                    // We follow_links(true) here so check if entry was created from a symlink
-                    let ty = if entry.path_is_symlink() {
-                        FileType::Symlink
-                    } else {
-                        file_type.into()
-                    };
-                    ret.push(PathEntry {
-                        path: entry.into_path(),
-                        ty,
-                        // This rely on contents_first(false), which walks in depth-first order
-                        under_symlink_dir: current_symlink_dir.is_some(),
-                    });
+                    ret.push(entry.into_path());
                 }
             }
             Err(err) if err.loop_ancestor().is_some() => {
@@ -943,11 +770,7 @@ fn list_files_walk(
                 // Otherwise, simply recover from it.
                 // Don't worry about error skipping here, the callers would
                 // still hit the IO error if they do access it thereafter.
-                Some(path) => ret.push(PathEntry {
-                    path: path.to_path_buf(),
-                    ty: FileType::Other,
-                    under_symlink_dir: false,
-                }),
+                Some(path) => ret.push(path.to_path_buf()),
                 None => return Err(err.into()),
             },
         }
@@ -978,7 +801,7 @@ fn last_modified_file(
         let mtime = paths::mtime(&file).unwrap_or_else(|_| FileTime::zero());
         if mtime > max {
             max = mtime;
-            max_path = file.into_path_buf();
+            max_path = file;
         }
     }
     trace!("last modified file {}: {}", path.display(), max);


Test patch:
diff --git a/crates/cargo-test-support/containers/sshd/Dockerfile b/crates/cargo-test-support/containers/sshd/Dockerfile
index 4e178d09e..de491fea8 100644
--- a/crates/cargo-test-support/containers/sshd/Dockerfile
+++ b/crates/cargo-test-support/containers/sshd/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.21
+FROM alpine:3.20
 
 RUN apk add --no-cache openssh git
 RUN ssh-keygen -A
diff --git a/tests/testsuite/build_script_env.rs b/tests/testsuite/build_script_env.rs
index 2c6b984b0..db48bcd83 100644
--- a/tests/testsuite/build_script_env.rs
+++ b/tests/testsuite/build_script_env.rs
@@ -383,3 +383,72 @@ fn rustc_cfg_with_and_without_value() {
     );
     check.run();
 }
+
+#[cargo_test]
+fn rerun_if_env_is_exsited_config() {
+    let p = project()
+        .file("src/main.rs", "fn main() {}")
+        .file(
+            "build.rs",
+            r#"
+            fn main() {
+                println!("cargo::rerun-if-env-changed=FOO");
+            }
+        "#,
+        )
+        .file(
+            ".cargo/config.toml",
+            r#"
+            [env]
+            FOO = "foo"
+            "#,
+        )
+        .build();
+
+    p.cargo("check")
+        .with_stderr_data(str![[r#"
+[COMPILING] foo v0.0.1 ([ROOT]/foo)
+[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
+
+"#]])
+        .run();
+
+    p.cargo(r#"check --config 'env.FOO="bar"'"#)
+        .with_stderr_data(str![[r#"
+[COMPILING] foo v0.0.1 ([ROOT]/foo)
+[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
+
+"#]])
+        .run();
+}
+
+#[cargo_test]
+fn rerun_if_env_newly_added_in_config() {
+    let p = project()
+        .file("src/main.rs", "fn main() {}")
+        .file(
+            "build.rs",
+            r#"
+            fn main() {
+                println!("cargo::rerun-if-env-changed=FOO");
+            }
+        "#,
+        )
+        .build();
+
+    p.cargo("check")
+        .with_stderr_data(str![[r#"
+[COMPILING] foo v0.0.1 ([ROOT]/foo)
+[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
+
+"#]])
+        .run();
+
+    p.cargo(r#"check --config 'env.FOO="foo"'"#)
+        .with_stderr_data(str![[r#"
+[COMPILING] foo v0.0.1 ([ROOT]/foo)
+[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
+
+"#]])
+        .run();
+}
diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs
index ae1c5d710..b418513ea 100644
--- a/tests/testsuite/package.rs
+++ b/tests/testsuite/package.rs
@@ -1339,12 +1339,10 @@ fn dirty_file_outside_pkg_root_considered_dirty() {
                 license-file = "../LICENSE"
             "#,
         )
-        .file("original-dir/file", "before")
         .symlink("lib.rs", "isengard/src/lib.rs")
         .symlink("README.md", "isengard/README.md")
         .file(&main_outside_pkg_root, "fn main() {}")
         .symlink(&main_outside_pkg_root, "isengard/src/main.rs")
-        .symlink_dir("original-dir", "isengard/symlink-dir")
     });
     git::commit(&repo);
 
@@ -1354,7 +1352,6 @@ fn dirty_file_outside_pkg_root_considered_dirty() {
     // * Changes in files outside package root that source files symlink to
     p.change_file("README.md", "after");
     p.change_file("lib.rs", "pub fn after() {}");
-    p.change_file("original-dir/file", "after");
     // * Changes in files outside pkg root that `license-file`/`readme` point to
     p.change_file("LICENSE", "after");
     // * When workspace inheritance is involved and changed
@@ -1378,12 +1375,10 @@ fn dirty_file_outside_pkg_root_considered_dirty() {
     p.cargo("package --workspace --no-verify")
         .with_status(101)
         .with_stderr_data(str![[r#"
-[ERROR] 4 files in the working directory contain changes that were not yet committed into git:
+[ERROR] 2 files in the working directory contain changes that were not yet committed into git:
 
 LICENSE
 README.md
-lib.rs
-original-dir/file
 
 to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag
 
@@ -1393,7 +1388,7 @@ to proceed despite this and include the uncommitted changes, pass the `--allow-d
     p.cargo("package --workspace --no-verify --allow-dirty")
         .with_stderr_data(str![[r#"
 [PACKAGING] isengard v0.0.0 ([ROOT]/foo/isengard)
-[PACKAGED] 9 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
+[PACKAGED] 8 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
 
 "#]])
         .run();
@@ -1416,7 +1411,6 @@ edition = "2021"
             "Cargo.toml.orig",
             "src/lib.rs",
             "src/main.rs",
-            "symlink-dir/file",
             "Cargo.lock",
             "LICENSE",
             "README.md",
@@ -1424,7 +1418,6 @@ edition = "2021"
         [
             ("src/lib.rs", str!["pub fn after() {}"]),
             ("src/main.rs", str![r#"fn main() { eprintln!("after"); }"#]),
-            ("symlink-dir/file", str!["after"]),
             ("README.md", str!["after"]),
             ("LICENSE", str!["after"]),
             ("Cargo.toml", cargo_toml),
@@ -7032,89 +7025,3 @@ src/main.rs
 "#]])
         .run();
 }
-
-#[cargo_test]
-fn git_core_symlinks_false() {
-    if !symlink_supported() {
-        return;
-    }
-
-    let git_project = git::new("bar", |p| {
-        p.file(
-            "Cargo.toml",
-            r#"
-                [package]
-                name = "bar"
-                description = "bar"
-                license = "MIT"
-                edition = "2021"
-                documentation = "foo"
-            "#,
-        )
-        .file("src/lib.rs", "//! This is a module")
-        .symlink("src/lib.rs", "symlink-lib.rs")
-        .symlink_dir("src", "symlink-dir")
-    });
-
-    let url = git_project.root().to_url().to_string();
-
-    let p = project().build();
-    let root = p.root();
-    // Remove the default project layout,
-    // so we can git-fetch from git_project under the same directory
-    fs::remove_dir_all(&root).unwrap();
-    fs::create_dir_all(&root).unwrap();
-    let repo = git::init(&root);
-
-    let mut cfg = repo.config().unwrap();
-    cfg.set_bool("core.symlinks", false).unwrap();
-
-    // let's fetch from git_project so it respects our core.symlinks=false config.
-    repo.remote_anonymous(&url)
-        .unwrap()
-        .fetch(&["HEAD"], None, None)
-        .unwrap();
-    let rev = repo
-        .find_reference("FETCH_HEAD")
-        .unwrap()
-        .peel_to_commit()
-        .unwrap();
-    repo.reset(rev.as_object(), git2::ResetType::Hard, None)
-        .unwrap();
-
-    p.cargo("package --allow-dirty")
-        .with_stderr_data(str![[r#"
-[WARNING] found symbolic links that may be checked out as regular files for git repo at `[ROOT]/foo/`
-This might cause the `.crate` file to include incorrect or incomplete files
-[NOTE] to avoid this, set the Git config `core.symlinks` to `true`
-...
-[PACKAGING] bar v0.0.0 ([ROOT]/foo)
-[PACKAGED] 7 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
-[VERIFYING] bar v0.0.0 ([ROOT]/foo)
-[COMPILING] bar v0.0.0 ([ROOT]/foo/target/package/bar-0.0.0)
-[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
-
-"#]])
-        .run();
-
-    let f = File::open(&p.root().join("target/package/bar-0.0.0.crate")).unwrap();
-    validate_crate_contents(
-        f,
-        "bar-0.0.0.crate",
-        &[
-            "Cargo.lock",
-            "Cargo.toml",
-            "Cargo.toml.orig",
-            "src/lib.rs",
-            // We're missing symlink-dir/lib.rs in the `.crate` file.
-            "symlink-dir",
-            "symlink-lib.rs",
-            ".cargo_vcs_info.json",
-        ],
-        [
-            // And their contents are incorrect.
-            ("symlink-dir", str!["[ROOT]/bar/src"]),
-            ("symlink-lib.rs", str!["[ROOT]/bar/src/lib.rs"]),
-        ],
-    );
-}