Ensure runfiles.{,root_}symlinks makes it into tarballs

Right now we only add files. This is problematic when creating a tarball
of an sh_binary(). Starting with Bazel 9, sh_binary() is no longer
provided by Bazel itself. In that case rules_shell will declare its own
copy of runfiles.bash, putting a symlink at the old location in the
runfiles directory.
diff --git a/pkg/install.bzl b/pkg/install.bzl
index ee3d195..02787ee 100644
--- a/pkg/install.bzl
+++ b/pkg/install.bzl
@@ -75,7 +75,10 @@
 
     my_runfiles = ctx.runfiles(
         files = [manifest_file],
-        transitive_files = depset(transitive = mapping_context.file_deps),
+        transitive_files = depset(
+            direct = mapping_context.file_deps_direct,
+            transitive = mapping_context.file_deps_transitive,
+        ),
     )
 
     return [
diff --git a/pkg/private/pkg_files.bzl b/pkg/private/pkg_files.bzl
index 1f1c902..d766534 100644
--- a/pkg/private/pkg_files.bzl
+++ b/pkg/private/pkg_files.bzl
@@ -73,7 +73,8 @@
     doc = """Fields passed to process_* methods.""",
     fields = {
         "content_map": "in/out The content_map we are building up",
-        "file_deps": "in/out list of file Depsets represented in the map",
+        "file_deps_direct": "in/out list of files represented in the map",
+        "file_deps_transitive": "in/out list of file Depsets represented in the map",
         "label": "ctx.label",
 
         # Behaviors
@@ -119,7 +120,8 @@
 
     return _MappingContext(
         content_map = dict(),
-        file_deps = list(),
+        file_deps_direct = list(),
+        file_deps_transitive = list(),
         label = label,
         allow_duplicates_with_different_content = allow_duplicates_with_different_content,
         strip_prefix = strip_prefix,
@@ -265,7 +267,7 @@
     # Gather the files for every srcs entry here, even if it is not from
     # a pkg_* rule.
     if DefaultInfo in src:
-        mapping_context.file_deps.append(src[DefaultInfo].files)
+        mapping_context.file_deps_transitive.append(src[DefaultInfo].files)
     found_info = False
     if PackageFilesInfo in src:
         _process_pkg_files(
@@ -431,18 +433,16 @@
 
     if include_runfiles:
         runfiles = src[DefaultInfo].default_runfiles
-        if runfiles and runfiles.files:
-            mapping_context.file_deps.append(runfiles.files)
-
+        if runfiles:
             # Computing the runfiles root is subtle. It should be based off of
             # the executable, but that is not always obvious. When in doubt,
             # the first file of DefaultInfo.files should be the right target.
             base_file = the_executable or all_files[0]
             base_file_path = dest_path(base_file, data_path, data_path_without_prefix)
-            base_path = base_file_path + ".runfiles/" + workspace_name
+            base_root_path = base_file_path + ".runfiles"
+            base_path = base_root_path + "/" + workspace_name
 
-            for rf in runfiles.files.to_list():
-                d_path = mapping_context.path_mapper(base_path + "/" + rf.short_path)
+            def add_mapped_file(d_path, rf):
                 fmode = "0755" if rf == the_executable else mapping_context.default_mode
                 _check_dest(mapping_context.content_map, d_path, rf, src.label, mapping_context.allow_duplicates_with_different_content)
                 if hasattr(rf, "is_symlink") and rf.is_symlink:  # File.is_symlink is Bazel 8+
@@ -463,11 +463,27 @@
                     gid = mapping_context.default_gid,
                 )
 
+            if runfiles.files:
+                mapping_context.file_deps_transitive.append(runfiles.files)
+                for rf in runfiles.files.to_list():
+                    d_path = mapping_context.path_mapper(base_path + "/" + rf.short_path)
+                    add_mapped_file(d_path, rf)
+            if runfiles.symlinks:
+                for se in runfiles.symlinks.to_list():
+                    mapping_context.file_deps_direct.append(se.target_file)
+                    d_path = mapping_context.path_mapper(base_path + "/" + se.path)
+                    add_mapped_file(d_path, se.target_file)
+            if runfiles.root_symlinks:
+                for se in runfiles.root_symlinks.to_list():
+                    mapping_context.file_deps_direct.append(se.target_file)
+                    d_path = mapping_context.path_mapper(base_root_path + "/" + se.path)
+                    add_mapped_file(d_path, se.target_file)
+
             # if repo_mapping manifest exists (for e.g. with --enable_bzlmod),
             # create _repo_mapping under runfiles directory
             repo_mapping_manifest = get_repo_mapping_manifest(src)
             if repo_mapping_manifest:
-                mapping_context.file_deps.append(depset([repo_mapping_manifest]))
+                mapping_context.file_deps_direct.append(repo_mapping_manifest)
 
                 # TODO: This should really be a symlink into .runfiles/_repo_mapping
                 # that also respects remap_paths. For now this is duplicated with the
diff --git a/pkg/private/tar/tar.bzl b/pkg/private/tar/tar.bzl
index ae86834..d3114de 100644
--- a/pkg/private/tar/tar.bzl
+++ b/pkg/private/tar/tar.bzl
@@ -141,7 +141,7 @@
         target_files = target[DefaultInfo].files.to_list()
         if len(target_files) != 1:
             fail("Each input must describe exactly one file.", attr = "files")
-        mapping_context.file_deps.append(depset([target_files[0]]))
+        mapping_context.file_deps_direct.append(target_files[0])
         add_single_file(
             mapping_context,
             f_dest_path,
@@ -188,8 +188,8 @@
         args.add("--preserve_mtime")
 
     inputs = depset(
-        direct = ctx.files.deps + files,
-        transitive = mapping_context.file_deps,
+        direct = mapping_context.file_deps_direct + ctx.files.deps + files,
+        transitive = mapping_context.file_deps_transitive,
     )
 
     ctx.actions.run(
diff --git a/pkg/private/zip/zip.bzl b/pkg/private/zip/zip.bzl
index 5531bc2..6810c5e 100644
--- a/pkg/private/zip/zip.bzl
+++ b/pkg/private/zip/zip.bzl
@@ -63,7 +63,10 @@
     args.set_param_file_format("multiline")
     args.use_param_file("@%s")
 
-    all_inputs = depset(direct = inputs, transitive = mapping_context.file_deps)
+    all_inputs = depset(
+        direct = mapping_context.file_deps_direct + inputs,
+        transitive = mapping_context.file_deps_transitive,
+    )
 
     ctx.actions.run(
         mnemonic = "PackageZip",
diff --git a/tests/tar/BUILD b/tests/tar/BUILD
index fe45dfa..e109d61 100644
--- a/tests/tar/BUILD
+++ b/tests/tar/BUILD
@@ -15,6 +15,7 @@
 """Tests for pkg_tar."""
 
 load("@rules_python//python:defs.bzl", "py_binary", "py_test")
+load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
 
 # buildifier: disable=bzl-visibility
 load("//pkg:mappings.bzl", "pkg_attributes", "pkg_files", "pkg_mkdirs", "pkg_mklink")
@@ -852,3 +853,32 @@
     True,
     False,
 ]]
+
+sh_binary(
+    name = "program_with_rules_shell_runfiles",
+    srcs = ["program_with_rules_shell_runfiles.sh"],
+    deps = ["@rules_shell//shell/runfiles"],
+)
+
+pkg_tar(
+    name = "program_with_rules_shell_runfiles_tar",
+    srcs = [":program_with_rules_shell_runfiles"],
+    include_runfiles = True,
+)
+
+# When Bazel does not provide an integrated version of sh_binary(),
+# rules_shell will provide a local copy of the runfiles library. In that
+# case it uses runfiles.root_symlinks to ensure that runfiles.bash is
+# available at its traditional location.
+verify_archive_test(
+    name = "program_with_rules_shell_runfiles_test",
+    must_contain = select({
+        "@platforms//os:windows": [
+            "program_with_rules_shell_runfiles.exe.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash",
+        ],
+        "//conditions:default": [
+            "program_with_rules_shell_runfiles.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash",
+        ],
+    }),
+    target = ":program_with_rules_shell_runfiles_tar",
+)
diff --git a/tests/tar/program_with_rules_shell_runfiles.sh b/tests/tar/program_with_rules_shell_runfiles.sh
new file mode 100755
index 0000000..7e548ad
--- /dev/null
+++ b/tests/tar/program_with_rules_shell_runfiles.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo Hello