Repository: Enselic/cargo-public-api (rust)

Issue:
Error if the library target name is different than the package name
## Bug

`cargo public-api` entirely doesn't work if used to check a library on a package with a different name.

Looking at the error, my hypothesis is that the app looks for a concrete rustdoc JSON named _exactly_ the same way as the package, but the name that rustdoc generates is actually the name of the library target.

## Reproduction

1. Create a new lib project

```console
$ cargo new --lib public-api-repro
    Created library `public-api-repro` package
```

2. Change the name of the library target by adding the following to the `Cargo.toml`:

```toml
[lib]
name = "other_name"
```

3. Try to use `cargo-public-api`:
```console
$ cargo public-api
 Documenting public-api-repro v0.1.0 (/home/v0ldek/public-api-repro)
    Finished dev [unoptimized + debuginfo] target(s) in 0.21s
Error: Failed to parse rustdoc JSON at "/home/v0ldek/public-api-repro/target/doc/public_api_repro.json".

This version of `cargo public-api` requires at least:

    nightly-2023-05-24

Ensure your nightly toolchain is up to date with:

    rustup install nightly --profile minimal

If that does not help, it might be `cargo public-api` that is out of date. Try
to install the latest version with

    cargo install cargo-public-api --locked

If the issue remains, please report at

    https://github.com/Enselic/cargo-public-api/issues

Caused by:
    No such file or directory (os error 2)
```

**Note**: after the last step, the `target/doc` directory does contain a file named `other_name.json`.

## Expected behaviour

`cargo-public-api` should recognise the library target name and look for the correct rustdoc file.

## Additional details

Rust version: newest stable, 1.71.0
Rust nightly version: 1.73.0-nightly, 2023-08-02
`cargo-public-api` version: 0.31.2

Pull request description:
Handle when `[package]` `name` differs from `[lib]` `name`
Closes #476

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 2009a4e..c95e920 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -57,6 +57,13 @@ exclude = [
     # To test that we pass --cap-lints when building rustdoc JSON
     "test-apis/lint_error",
 
+    # Has `[lib] name = "other_name"` in Cargo.toml so that package name and lib
+    # name are different.
+    "test-apis/other-lib-name",
+
+    # Contains no lib, only a bin.
+    "test-apis/no-lib",
+
     # Test for feature flags
     "test-apis/features",
 
diff --git a/rustdoc-json/src/builder.rs b/rustdoc-json/src/builder.rs
index a12d887..5f2e13d 100644
--- a/rustdoc-json/src/builder.rs
+++ b/rustdoc-json/src/builder.rs
@@ -115,7 +115,7 @@ fn cargo_rustdoc_command(options: &Builder) -> Command {
 }
 
 /// Returns `./target/doc/crate_name.json`. Also takes care of transforming
-/// `crate-name` to `crate_name`.
+/// `crate-name` to `crate_name`. Also handles `[lib] name = "foo"`.
 fn rustdoc_json_path_for_manifest_path(
     manifest_path: impl AsRef<Path>,
     package: Option<&str>,
@@ -165,10 +165,11 @@ fn target_directory(manifest_path: impl AsRef<Path>) -> Result<PathBuf, BuildErr
 /// `Cargo.toml` manifest path.
 fn package_name(manifest_path: impl AsRef<Path>) -> Result<String, BuildError> {
     let manifest = cargo_manifest::Manifest::from_path(manifest_path.as_ref())?;
-    Ok(manifest
+    let package_name = manifest
         .package
         .ok_or_else(|| BuildError::VirtualManifest(manifest_path.as_ref().to_owned()))?
-        .name)
+        .name;
+    Ok(manifest.lib.and_then(|p| p.name).unwrap_or(package_name))
 }
 
 /// Builds rustdoc JSON. There are many build options. Refer to the docs to


Test patch:
diff --git a/cargo-public-api/tests/cargo-public-api-bin-tests.rs b/cargo-public-api/tests/cargo-public-api-bin-tests.rs
index 8f102bf..7ace69f 100644
--- a/cargo-public-api/tests/cargo-public-api-bin-tests.rs
+++ b/cargo-public-api/tests/cargo-public-api-bin-tests.rs
@@ -104,6 +104,40 @@ fn list_public_items_with_lint_error() {
         .success();
 }
 
+/// Ensure we can handle when
+///
+/// ```
+/// [lib]
+/// name = "foo"
+/// ```
+///
+/// has a different name than
+///
+/// ```
+/// [package]
+/// name = "bar"
+/// ```
+///
+/// in `Cargo.toml`.
+#[test]
+fn list_public_items_with_other_lib_name() {
+    let mut cmd = TestCmd::new().with_separate_target_dir();
+    cmd.args(["--manifest-path", "../test-apis/other-lib-name/Cargo.toml"]);
+    cmd.assert()
+        .stdout_or_update("./expected-output/other_lib_name.txt")
+        .success();
+}
+
+#[test]
+fn list_public_items_with_no_lib() {
+    let mut cmd = TestCmd::new().with_separate_target_dir();
+    cmd.args(["--manifest-path", "../test-apis/no-lib/Cargo.toml"]);
+    cmd.assert()
+        .stderr(contains("error")) // Be aware of ANSI color escape codes!
+        .stderr(contains("no library targets found in package `no-lib`"))
+        .failure();
+}
+
 /// Test that `cargo-public-api` can be renamed to `cargo-public-api-v0.13.0`
 /// and still be invoked as `cargo public-api-v0.13.0` i.e. as a cargo
 /// subcommand.
diff --git a/cargo-public-api/tests/expected-output/other_lib_name.txt b/cargo-public-api/tests/expected-output/other_lib_name.txt
new file mode 100644
index 0000000..659c896
--- /dev/null
+++ b/cargo-public-api/tests/expected-output/other_lib_name.txt
@@ -0,0 +1,2 @@
+pub mod other_name
+pub fn other_name::lib_name_differs_from_package_name() -> usize
diff --git a/test-apis/no-lib/Cargo.toml b/test-apis/no-lib/Cargo.toml
new file mode 100644
index 0000000..f2210a3
--- /dev/null
+++ b/test-apis/no-lib/Cargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "no-lib"
+edition = "2021"
+version = "0.1.0"
diff --git a/test-apis/no-lib/src/main.rs b/test-apis/no-lib/src/main.rs
new file mode 100644
index 0000000..e7a11a9
--- /dev/null
+++ b/test-apis/no-lib/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+    println!("Hello, world!");
+}
diff --git a/test-apis/other-lib-name/Cargo.toml b/test-apis/other-lib-name/Cargo.toml
new file mode 100644
index 0000000..5d10dcb
--- /dev/null
+++ b/test-apis/other-lib-name/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+edition = "2021"
+name = "other-lib-name"
+version = "0.1.0"
+
+[lib]
+name = "other_name"
diff --git a/test-apis/other-lib-name/src/lib.rs b/test-apis/other-lib-name/src/lib.rs
new file mode 100644
index 0000000..f6f6301
--- /dev/null
+++ b/test-apis/other-lib-name/src/lib.rs
@@ -0,0 +1,3 @@
+pub fn lib_name_differs_from_package_name() -> usize {
+    42
+}