Provide `.dSYM` package in `OutputGroupInfo` for cdylib and lib on darwin (#1072)

diff --git a/docs/defs.md b/docs/defs.md
index caa7ed3..251054c 100644
--- a/docs/defs.md
+++ b/docs/defs.md
@@ -159,6 +159,10 @@
 Hello world
 ```
 
+On Windows, a PDB file containing debugging information is available under
+the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
+is available under the key `dsym_folder` in `OutputGroupInfo`.
+
 **ATTRIBUTES**
 
 
diff --git a/docs/flatten.md b/docs/flatten.md
index 7dbaaf1..ddbe153 100644
--- a/docs/flatten.md
+++ b/docs/flatten.md
@@ -312,6 +312,10 @@
 Hello world
 ```
 
+On Windows, a PDB file containing debugging information is available under
+the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
+is available under the key `dsym_folder` in `OutputGroupInfo`.
+
 **ATTRIBUTES**
 
 
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index b40de71..d5e98e9 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -202,6 +202,10 @@
     This rule provides CcInfo, so it can be used everywhere Bazel
     expects rules_cc.
 
+    On Windows, a PDB file containing debugging information is available under
+    the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
+    is available under the key `dsym_folder` in `OutputGroupInfo`.
+
     Args:
         ctx (ctx): The rule's context object
 
@@ -1009,6 +1013,10 @@
         INFO: Running command line: bazel-bin/examples/rust/hello_world/hello_world
         Hello world
         ```
+
+        On Windows, a PDB file containing debugging information is available under
+        the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
+        is available under the key `dsym_folder` in `OutputGroupInfo`.
 """),
 )
 
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 7a91746..beb72b0 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -783,26 +783,34 @@
 
     outputs = [crate_info.output]
 
+    # For a cdylib that might be added as a dependency to a cc_* target on Windows, it is important to include the
+    # interface library that rustc generates in the output files.
     interface_library = None
-    pdb_file = None
-    if toolchain.os == "windows":
-        # For a cdylib that might be added as a dependency to a cc_* target on Windows, it is important to include the
-        # interface library that rustc generates in the output files.
-        if crate_info.type == "cdylib":
-            # Rustc generates the import library with a `.dll.lib` extension rather than the usual `.lib` one that msvc
-            # expects (see https://github.com/rust-lang/rust/pull/29520 for more context).
-            interface_library = ctx.actions.declare_file(crate_info.output.basename + ".lib")
-            outputs.append(interface_library)
+    if toolchain.os == "windows" and crate_info.type == "cdylib":
+        # Rustc generates the import library with a `.dll.lib` extension rather than the usual `.lib` one that msvc
+        # expects (see https://github.com/rust-lang/rust/pull/29520 for more context).
+        interface_library = ctx.actions.declare_file(crate_info.output.basename + ".lib")
+        outputs.append(interface_library)
 
-        # Rustc always generates a pdb file so provide it in an output group for crate types that benefit from having
-        # debug information in a separate file. Note that test targets do really need a pdb we skip them.
-        if crate_info.type in ("cdylib", "bin") and not crate_info.is_test:
+    # The action might generate extra output that we don't want to include in the `DefaultInfo` files.
+    action_outputs = list(outputs)
+
+    # Rustc generates a pdb file (on Windows) or a dsym folder (on macos) so provide it in an output group for crate
+    # types that benefit from having debug information in a separate file.
+    pdb_file = None
+    dsym_folder = None
+    if crate_info.type in ("cdylib", "bin") and not crate_info.is_test:
+        if toolchain.os == "windows":
             pdb_file = ctx.actions.declare_file(crate_info.output.basename[:-len(crate_info.output.extension)] + "pdb")
+            action_outputs.append(pdb_file)
+        elif toolchain.os == "darwin":
+            dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM")
+            action_outputs.append(dsym_folder)
 
     ctx.actions.run(
         executable = ctx.executable._process_wrapper,
         inputs = compile_inputs,
-        outputs = outputs + [pdb_file] if pdb_file else outputs,
+        outputs = action_outputs,
         env = env,
         arguments = args.all,
         mnemonic = "Rustc",
@@ -837,6 +845,8 @@
         providers += establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library)
     if pdb_file:
         providers.append(OutputGroupInfo(pdb_file = depset([pdb_file])))
+    if dsym_folder:
+        providers.append(OutputGroupInfo(dsym_folder = depset([dsym_folder])))
 
     return providers
 
diff --git a/test/unit/debug_info/BUILD.bazel b/test/unit/debug_info/BUILD.bazel
new file mode 100644
index 0000000..a577c0a
--- /dev/null
+++ b/test/unit/debug_info/BUILD.bazel
@@ -0,0 +1,4 @@
+load(":debug_info_analysis_test.bzl", "debug_info_analysis_test_suite")
+
+############################ UNIT TESTS #############################
+debug_info_analysis_test_suite(name = "debug_info_analysis_test_suite")
diff --git a/test/unit/debug_info/debug_info_analysis_test.bzl b/test/unit/debug_info/debug_info_analysis_test.bzl
new file mode 100644
index 0000000..9dd5ec1
--- /dev/null
+++ b/test/unit/debug_info/debug_info_analysis_test.bzl
@@ -0,0 +1,104 @@
+"""Analysis tests for debug info in cdylib and bin targets."""
+
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+load("//rust:defs.bzl", "rust_binary", "rust_shared_library")
+
+def _pdb_file_test_impl(ctx):
+    env = analysistest.begin(ctx)
+    target = analysistest.target_under_test(env)
+
+    files = target[DefaultInfo].files.to_list()
+    asserts.equals(env, len(files), 1)
+    file = files[0]
+    asserts.equals(env, file.extension, "pdb")
+
+    return analysistest.end(env)
+
+pdb_file_test = analysistest.make(_pdb_file_test_impl)
+
+def _dsym_folder_test_impl(ctx):
+    env = analysistest.begin(ctx)
+    target = analysistest.target_under_test(env)
+
+    files = target[DefaultInfo].files.to_list()
+    asserts.equals(env, len(files), 1)
+    file = files[0]
+    asserts.equals(env, file.extension, "dSYM")
+
+    return analysistest.end(env)
+
+dsym_folder_test = analysistest.make(_dsym_folder_test_impl)
+
+def debug_info_analysis_test_suite(name):
+    """Analysis tests for debug info in cdylib and bin targets.
+
+    Args:
+        name: the test suite name
+    """
+    rust_shared_library(
+        name = "mylib",
+        srcs = ["lib.rs"],
+    )
+
+    native.filegroup(
+        name = "mylib.pdb",
+        srcs = [":mylib"],
+        output_group = "pdb_file",
+    )
+
+    pdb_file_test(
+        name = "lib_pdb_test",
+        target_under_test = ":mylib.pdb",
+        target_compatible_with = ["@platforms//os:windows"],
+    )
+
+    native.filegroup(
+        name = "mylib.dSYM",
+        srcs = [":mylib"],
+        output_group = "dsym_folder",
+    )
+
+    dsym_folder_test(
+        name = "lib_dsym_test",
+        target_under_test = ":mylib.dSYM",
+        target_compatible_with = ["@platforms//os:macos"],
+    )
+
+    rust_binary(
+        name = "myrustbin",
+        srcs = ["main.rs"],
+    )
+
+    native.filegroup(
+        name = "mybin.pdb",
+        srcs = [":myrustbin"],
+        output_group = "pdb_file",
+    )
+
+    pdb_file_test(
+        name = "bin_pdb_test",
+        target_under_test = ":mybin.pdb",
+        target_compatible_with = ["@platforms//os:windows"],
+    )
+
+    native.filegroup(
+        name = "mybin.dSYM",
+        srcs = [":myrustbin"],
+        output_group = "dsym_folder",
+    )
+
+    dsym_folder_test(
+        name = "bin_dsym_test",
+        target_under_test = ":mybin.dSYM",
+        target_compatible_with = ["@platforms//os:macos"],
+    )
+
+    native.test_suite(
+        name = name,
+        tests = [
+            ":lib_pdb_test",
+            ":lib_dsym_test",
+            ":bin_pdb_test",
+            ":bin_dsym_test",
+        ],
+    )
diff --git a/test/unit/debug_info/lib.rs b/test/unit/debug_info/lib.rs
new file mode 100644
index 0000000..1f19b6d
--- /dev/null
+++ b/test/unit/debug_info/lib.rs
@@ -0,0 +1,4 @@
+#[no_mangle]
+pub extern "C" fn hello() {
+    println!("Hello");
+}
diff --git a/test/unit/debug_info/main.rs b/test/unit/debug_info/main.rs
new file mode 100644
index 0000000..6d85883
--- /dev/null
+++ b/test/unit/debug_info/main.rs
@@ -0,0 +1,3 @@
+pub fn main() {
+    println!("Hello");
+}
diff --git a/test/unit/win_interface_library/win_interface_library_analysis_test.bzl b/test/unit/win_interface_library/win_interface_library_analysis_test.bzl
index eced433..fef00f2 100644
--- a/test/unit/win_interface_library/win_interface_library_analysis_test.bzl
+++ b/test/unit/win_interface_library/win_interface_library_analysis_test.bzl
@@ -36,12 +36,6 @@
         target_compatible_with = ["@platforms//os:windows"],
     )
 
-    native.filegroup(
-        name = "mylib.pdb",
-        srcs = [":mylib"],
-        output_group = "pdb_file",
-    )
-
     cc_binary(
         name = "mybin",
         srcs = ["bin.cc"],
@@ -55,12 +49,6 @@
         target_compatible_with = ["@platforms//os:windows"],
     )
 
-    native.filegroup(
-        name = "myrustbin.pdb",
-        srcs = [":myrustbin"],
-        output_group = "pdb_file",
-    )
-
     win_interface_library_test(
         name = "win_interface_library_test",
         target_under_test = ":mylib",