Repository: PyO3/maturin (language: python)

Issue:
Issue with README building sdist 
### Bug Description

Since maturin v1.7.0 it seems the default behavior taking a readme for the sdist build has changed and it break my workflow.

Issue: even though I have set the correct path to the readme in `pyproject.toml` maturin keep taking the README.md at the root of my workspace.

NB: for maturin v1.5.x and v1.6.0 everything works fine

Output:
```
🔗 Found pyo3 bindings
🐍 Found CPython 3.12 at /home/quentin/Workspace/Rust/poppy/python/.env/bin/python
📡 Using build options features from pyproject.toml
💥 maturin failed
  Caused by: Failed to build source distribution
  Caused by: File poppy_py-0.1.8/README.md was already added from /home/user/Rust/poppy/README.md, can't added it from /home/user/Rust/poppy/python/README.md
 ```
 
 

### Your maturin version (`maturin --version`)

1.7.0

### Your Python version (`python -V`)

3.12.4

### Your pip version (`pip -V`)

24.1.2

### What bindings you're using

pyo3

### Does `cargo build` work?

- [X] Yes, it works

### If on windows, have you checked that you aren't accidentally using unix path (those with the forward slash `/`)?

- [ ] Yes

### Steps to Reproduce

git clone https://github.com/hashlookup/poppy.git
cd poppy
maturin sdist -m python/Cargo.toml -o /tmp/sdist


Discussion / hints:
same error here
I think what we need to do is vendor the readmes and license file of workspace crates inside these crates, as oppose to using the top level workspace location they are pointing too.
Same error on `jiter` - see [this build](https://github.com/pydantic/jiter/actions/runs/10133443797/job/28274675992).
We are also running into this in delta-rs since maturin 1.7

https://github.com/delta-io/delta-rs/actions/runs/10395762774/job/28799000654

Fix patch:
diff --git a/Changelog.md b/Changelog.md
index e4de1ed97..2d746a2f7 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,6 +2,9 @@
 
 ## [Unreleased]
 
+* Forward `cargo package --list` warnings in [#2186](https://github.com/PyO3/maturin/pull/2186)
+* In source distributions, we move the readmes of path dependencies into the respective crate to avoid collision between different readmes in [#2184](https://github.com/PyO3/maturin/pull/2184)
+
 ## [1.7.0] - 2024-07-07
 
 * Initial iOS support in [#2101](https://github.com/PyO3/maturin/pull/2102)
diff --git a/guide/src/contributing.md b/guide/src/contributing.md
index c3c4ef449..4a1786076 100644
--- a/guide/src/contributing.md
+++ b/guide/src/contributing.md
@@ -63,7 +63,7 @@ Ready to contribute? Here's how to setup maturin for local development.
 7. Commit your changes and push your branch to GitHub:
    ```bash
    $ git add .
-   $ git Commit
+   $ git commit
    $ git push origin branch-name
    ```
 8. Submit a pull request through the [GitHub website](https://github.com/PyO3/maturin/pulls).
diff --git a/src/source_distribution.rs b/src/source_distribution.rs
index 81b599355..2b255ea13 100644
--- a/src/source_distribution.rs
+++ b/src/source_distribution.rs
@@ -9,10 +9,12 @@ use ignore::overrides::Override;
 use normpath::PathExt as _;
 use path_slash::PathExt as _;
 use std::collections::HashMap;
+use std::ffi::OsStr;
 use std::io::Write;
 use std::path::{Path, PathBuf};
 use std::process::Command;
 use std::str;
+use toml_edit::DocumentMut;
 use tracing::debug;
 
 /// Path dependency information.
@@ -47,13 +49,14 @@ fn parse_toml_file(path: &Path, kind: &str) -> Result<toml_edit::DocumentMut> {
 /// We only want to add path dependencies that are actually used
 /// to reduce the size of the source distribution.
 fn rewrite_cargo_toml(
-    manifest_path: impl AsRef<Path>,
+    document: &mut DocumentMut,
+    manifest_path: &Path,
     known_path_deps: &HashMap<String, PathDependency>,
-) -> Result<String> {
-    let manifest_path = manifest_path.as_ref();
-    debug!("Rewriting Cargo.toml at {}", manifest_path.display());
-    let mut document = parse_toml_file(manifest_path, "Cargo.toml")?;
-
+) -> Result<()> {
+    debug!(
+        "Rewriting Cargo.toml `workspace.members` at {}",
+        manifest_path.display()
+    );
     // Update workspace members
     if let Some(workspace) = document.get_mut("workspace").and_then(|x| x.as_table_mut()) {
         if let Some(members) = workspace.get_mut("members").and_then(|x| x.as_array()) {
@@ -102,7 +105,34 @@ fn rewrite_cargo_toml(
             }
         }
     }
-    Ok(document.to_string())
+    Ok(())
+}
+
+/// Rewrite `Cargo.toml` to find the readme in the same directory.
+///
+/// `package.readme` may point to any point above the package, so when we move the directory, but
+/// keep the readme position, we could get different readme files at the same archive location.
+/// Putting the readme in the same directory as the `Cargo.toml` prevents this.
+fn rewrite_cargo_toml_readme(
+    document: &mut DocumentMut,
+    manifest_path: &Path,
+    readme_name: Option<&str>,
+) -> Result<()> {
+    debug!(
+        "Rewriting Cargo.toml `package.readme` at {}",
+        manifest_path.display()
+    );
+
+    if let Some(readme_name) = readme_name {
+        let project = document.get_mut("package").with_context(|| {
+            format!(
+                "Missing `[package]` table in Cargo.toml with readme at {}",
+                manifest_path.display()
+            )
+        })?;
+        project["readme"] = toml_edit::value(readme_name);
+    }
+    Ok(())
 }
 
 /// When `pyproject.toml` is inside the Cargo workspace root,
@@ -150,6 +180,7 @@ fn add_crate_to_source_distribution(
     writer: &mut SDistWriter,
     manifest_path: impl AsRef<Path>,
     prefix: impl AsRef<Path>,
+    readme: Option<&Path>,
     known_path_deps: &HashMap<String, PathDependency>,
     root_crate: bool,
     skip_cargo_toml: bool,
@@ -223,15 +254,33 @@ fn add_crate_to_source_distribution(
 
     let cargo_toml_path = prefix.join(manifest_path.file_name().unwrap());
 
+    let readme_name = readme
+        .as_ref()
+        .map(|readme| {
+            readme
+                .file_name()
+                .and_then(OsStr::to_str)
+                .with_context(|| format!("Missing readme filename for {}", manifest_path.display()))
+        })
+        .transpose()?;
+
     if root_crate {
-        let rewritten_cargo_toml = rewrite_cargo_toml(manifest_path, known_path_deps)?;
+        let mut document = parse_toml_file(manifest_path, "Cargo.toml")?;
+        rewrite_cargo_toml_readme(&mut document, manifest_path, readme_name)?;
+        rewrite_cargo_toml(&mut document, manifest_path, known_path_deps)?;
         writer.add_bytes(
             cargo_toml_path,
             Some(manifest_path),
-            rewritten_cargo_toml.as_bytes(),
+            document.to_string().as_bytes(),
         )?;
     } else if !skip_cargo_toml {
-        writer.add_file(cargo_toml_path, manifest_path)?;
+        let mut document = parse_toml_file(manifest_path, "Cargo.toml")?;
+        rewrite_cargo_toml_readme(&mut document, manifest_path, readme_name)?;
+        writer.add_bytes(
+            cargo_toml_path,
+            Some(manifest_path),
+            document.to_string().as_bytes(),
+        )?;
     }
 
     for (target, source) in target_source {
@@ -440,6 +489,7 @@ fn add_cargo_package_files_to_sdist(
         writer,
         manifest_path,
         root_dir.join(relative_main_crate_manifest_dir),
+        None,
         &known_path_deps,
         true,
         false,
@@ -451,8 +501,9 @@ fn add_cargo_package_files_to_sdist(
             .normalize()
             .with_context(|| format!("failed to normalize readme path `{}`", readme.display()))?
             .into_path_buf();
-        let relative_readme = abs_readme.strip_prefix(&sdist_root).unwrap();
-        writer.add_file(root_dir.join(relative_readme), &abs_readme)?;
+        // Add readme next to Cargo.toml so we don't get collisions between crates using readmes
+        // higher up the file tree.
+        writer.add_file(root_dir.join(readme.file_name().unwrap()), &abs_readme)?;
     }
 
     // Add Cargo.lock file and workspace Cargo.toml
@@ -496,11 +547,17 @@ fn add_cargo_package_files_to_sdist(
                     readme: None,
                 },
             );
-            let workspace_cargo_toml = rewrite_cargo_toml(&workspace_manifest_path, &deps_to_keep)?;
+            let mut document =
+                parse_toml_file(workspace_manifest_path.as_std_path(), "Cargo.toml")?;
+            rewrite_cargo_toml(
+                &mut document,
+                workspace_manifest_path.as_std_path(),
+                &deps_to_keep,
+            )?;
             writer.add_bytes(
                 root_dir.join(relative_workspace_cargo_toml),
                 Some(workspace_manifest_path.as_std_path()),
-                workspace_cargo_toml.as_bytes(),
+                document.to_string().as_bytes(),
             )?;
         }
     } else if cargo_lock_required {
@@ -593,6 +650,7 @@ fn add_path_dep(
         writer,
         &path_dep.manifest_path,
         root_dir.join(relative_path_dep_manifest_dir),
+        path_dep.readme.as_deref(),
         known_path_deps,
         false,
         skip_cargo_toml,
@@ -604,15 +662,21 @@ fn add_path_dep(
             path_dep.manifest_path.display()
         )
     })?;
-    // Handle possible relative readme field in Cargo.toml
+    // Include readme
     if let Some(readme) = path_dep.readme.as_ref() {
         let readme = path_dep_manifest_dir.join(readme);
         let abs_readme = readme
             .normalize()
             .with_context(|| format!("failed to normalize readme path `{}`", readme.display()))?
             .into_path_buf();
-        let relative_readme = abs_readme.strip_prefix(sdist_root).unwrap();
-        writer.add_file(root_dir.join(relative_readme), &abs_readme)?;
+        // Add readme next to Cargo.toml so we don't get collisions between crates using readmes
+        // higher up the file tree. See also [`rewrite_cargo_toml_readme`].
+        writer.add_file(
+            root_dir
+                .join(relative_path_dep_manifest_dir)
+                .join(readme.file_name().unwrap()),
+            &abs_readme,
+        )?;
     }
     // Handle different workspace manifest
     if path_dep.workspace_root != workspace_root {


Test patch:
diff --git a/test-crates/readme-duplication/Cargo.lock b/test-crates/readme-duplication/Cargo.lock
new file mode 100644
index 000000000..89890ba69
--- /dev/null
+++ b/test-crates/readme-duplication/Cargo.lock
@@ -0,0 +1,176 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "indoc"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "portable-atomic"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "pyo3"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433"
+dependencies = [
+ "cfg-if",
+ "indoc",
+ "libc",
+ "memoffset",
+ "once_cell",
+ "portable-atomic",
+ "pyo3-build-config",
+ "pyo3-ffi",
+ "pyo3-macros",
+ "unindent",
+]
+
+[[package]]
+name = "pyo3-build-config"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8"
+dependencies = [
+ "once_cell",
+ "target-lexicon",
+]
+
+[[package]]
+name = "pyo3-ffi"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6"
+dependencies = [
+ "libc",
+ "pyo3-build-config",
+]
+
+[[package]]
+name = "pyo3-macros"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206"
+dependencies = [
+ "proc-macro2",
+ "pyo3-macros-backend",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pyo3-macros-backend"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "pyo3-build-config",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "readme-rs"
+version = "0.2.0"
+
+[[package]]
+name = "readme_py"
+version = "0.2.0"
+dependencies = [
+ "pyo3",
+ "readme-rs",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unindent"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
diff --git a/test-crates/readme-duplication/Cargo.toml b/test-crates/readme-duplication/Cargo.toml
new file mode 100644
index 000000000..2db8d5933
--- /dev/null
+++ b/test-crates/readme-duplication/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+resolver = "2"
+members = ["readme-py", "readme-rs"]
+
+[workspace.package]
+readme = "README.md"
diff --git a/test-crates/readme-duplication/README.md b/test-crates/readme-duplication/README.md
new file mode 100644
index 000000000..3eb343b88
--- /dev/null
+++ b/test-crates/readme-duplication/README.md
@@ -0,0 +1,1 @@
+Readme duplication test case - Readme 1
diff --git a/test-crates/readme-duplication/check_installed/check_installed.py b/test-crates/readme-duplication/check_installed/check_installed.py
new file mode 100755
index 000000000..dc384d60c
--- /dev/null
+++ b/test-crates/readme-duplication/check_installed/check_installed.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+import readme
+
+assert readme.value == 1
+
+print("SUCCESS")
diff --git a/test-crates/readme-duplication/readme-py/Cargo.toml b/test-crates/readme-duplication/readme-py/Cargo.toml
new file mode 100644
index 000000000..42e6f9f1c
--- /dev/null
+++ b/test-crates/readme-duplication/readme-py/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "readme_py"
+version = "0.2.0"
+edition = "2021"
+readme = "README.md"
+publish = false
+
+[lib]
+name = "readme"
+crate-type = ["cdylib"]
+
+[dependencies]
+readme-rs = { path = "../readme-rs" }
+pyo3 = "0.22.2"
diff --git a/test-crates/readme-duplication/readme-py/README.md b/test-crates/readme-duplication/readme-py/README.md
new file mode 100644
index 000000000..bb5ac5974
--- /dev/null
+++ b/test-crates/readme-duplication/readme-py/README.md
@@ -0,0 +1,1 @@
+Readme duplication test case - Readme 2
diff --git a/test-crates/readme-duplication/readme-py/pyproject.toml b/test-crates/readme-duplication/readme-py/pyproject.toml
new file mode 100644
index 000000000..9021407a1
--- /dev/null
+++ b/test-crates/readme-duplication/readme-py/pyproject.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["maturin==1.6.0,<2.0"]
+build-backend = "maturin"
+
+[project]
+name = "readme-py"
+version = "0.2.0"
+
+[tool.maturin]
+features = ["pyo3/extension-module"]
diff --git a/test-crates/readme-duplication/readme-py/src/lib.rs b/test-crates/readme-duplication/readme-py/src/lib.rs
new file mode 100644
index 000000000..08e8ad92c
--- /dev/null
+++ b/test-crates/readme-duplication/readme-py/src/lib.rs
@@ -0,0 +1,8 @@
+use pyo3::{pymodule, Bound, PyResult};
+use pyo3::types::{PyModule, PyModuleMethods};
+
+#[pymodule]
+fn readme(m: &Bound<PyModule>) -> PyResult<()> {
+    m.add("value", 1)?;
+    Ok(())
+}
diff --git a/test-crates/readme-duplication/readme-rs/Cargo.lock b/test-crates/readme-duplication/readme-rs/Cargo.lock
new file mode 100644
index 000000000..f0785eaa4
--- /dev/null
+++ b/test-crates/readme-duplication/readme-rs/Cargo.lock
@@ -0,0 +1,393 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ahash"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "getrandom"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "jemalloc-sys"
+version = "0.5.4+5.3.0-patched"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6c1946e1cea1788cbfde01c993b52a10e2da07f4bac608228d1bed20bfebf2"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "jemallocator"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0de374a9f8e63150e6f5e8a60cc14c668226d7a347d8aee1a45766e3c4dd3bc"
+dependencies = [
+ "jemalloc-sys",
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "poppy"
+version = "0.1.0"
+dependencies = [
+ "ahash",
+ "anyhow",
+ "clap",
+ "jemallocator",
+ "rand",
+ "thiserror",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+
+[[package]]
+name = "syn"
+version = "2.0.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/test-crates/readme-duplication/readme-rs/Cargo.toml b/test-crates/readme-duplication/readme-rs/Cargo.toml
new file mode 100644
index 000000000..dd7c3949a
--- /dev/null
+++ b/test-crates/readme-duplication/readme-rs/Cargo.toml
@@ -0,0 +1,5 @@
+[package]
+name = "readme-rs"
+version = "0.2.0"
+edition = "2021"
+readme.workspace = true
diff --git a/test-crates/readme-duplication/readme-rs/src/lib.rs b/test-crates/readme-duplication/readme-rs/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/run.rs b/tests/run.rs
index eca9416da..eb685b8e8 100644
--- a/tests/run.rs
+++ b/tests/run.rs
@@ -457,6 +457,17 @@ fn integration_with_data() {
     ));
 }
 
+#[test]
+fn integration_readme_duplication() {
+    handle_result(integration::test_integration(
+        "test-crates/readme-duplication/readme-py",
+        None,
+        "integration-readme-duplication",
+        false,
+        None,
+    ));
+}
+
 #[test]
 // Sourced from https://pypi.org/project/wasmtime/11.0.0/#files
 // update with wasmtime updates