Improve handling of dSYM bundles

- Use `AppleBinaryInfo` to support `macos_command_line_application`
- Locate the dSYM bundle by the primary artifact path - they should be in the same folder (siblings). This allows us to avoid the reliance on `bazel-bin` which doesn't work depending on the configuration transition.
- Rename the bundle to be what Xcode expects it to be (vs. keeping the Bazel name)

PiperOrigin-RevId: 311542552
diff --git a/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects.bzl b/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects.bzl
index 3d0e35c..cf29804 100644
--- a/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects.bzl
+++ b/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects.bzl
@@ -20,6 +20,7 @@
 
 load(
     ":tulsi/tulsi_aspects_paths.bzl",
+    "AppleBinaryInfo",
     "AppleBundleInfo",
     "AppleTestInfo",
     "IosExtensionBundleInfo",
@@ -915,6 +916,17 @@
         ),
     ]
 
+def _bundle_dsym_path(apple_bundle):
+    """Compute the dSYM path for the bundle.
+
+    Due to b/110264170 dSYMs are not fully exposed via a provider. We instead
+    rely on the fact that `rules_apple` puts them next to the bundle just like
+    Xcode.
+    """
+    bin_path = apple_bundle.archive.dirname
+    dsym_name = apple_bundle.bundle_name + apple_bundle.bundle_extension + ".dSYM"
+    return bin_path + "/" + dsym_name
+
 def _collect_bundle_info(target):
     """Returns Apple bundle info for the given target, None if not a bundle."""
     if AppleBundleInfo in target:
@@ -922,6 +934,7 @@
         has_dsym = (apple_common.AppleDebugOutputs in target)
         return struct(
             archive_root = apple_bundle.archive_root,
+            dsym_path = _bundle_dsym_path(apple_bundle),
             bundle_name = apple_bundle.bundle_name,
             bundle_extension = apple_bundle.bundle_extension,
             has_dsym = has_dsym,
@@ -1038,6 +1051,7 @@
     embedded_bundles = depset(direct_embedded_bundles, transitive = transitive_embedded_bundles)
 
     artifact = None
+    dsym_path = None
     bundle_name = None
     archive_root = None
     infoplist = None
@@ -1045,35 +1059,41 @@
         bundle_info = target[AppleBundleInfo]
 
         artifact = bundle_info.archive.path
+        dsym_path = _bundle_dsym_path(bundle_info)
         archive_root = bundle_info.archive_root
         infoplist = bundle_info.infoplist
 
         bundle_name = bundle_info.bundle_name
-    elif (target_kind == "macos_command_line_application" or
-          target_kind == "cc_binary" or target_kind == "cc_test"):
-        # Special support for macos_command_line_application and cc_* targets
-        # which do not have an AppleBundleInfo provider.
-
-        # Both the dSYM binary and executable binary don't have an extension, so
-        # pick the first extension-less file not in a DWARF folder.
+    elif AppleBinaryInfo in target:
+        # Support for non-bundled binary targets such as
+        # `macos_command_line_application`. These still have dSYMs support and
+        # should be located next to the binary.
+        artifact = target[AppleBinaryInfo].binary.path
+        dsym_path = artifact + ".dSYM"
+    elif (target_kind == "cc_binary" or target_kind == "cc_test"):
+        # Special support for cc_* targets which do not have AppleBinaryInfo or
+        # AppleBundleInfo providers.
+        #
+        # At the moment these don't have support for dSYMs (b/124859331), but
+        # in case they do in the future we filter out the dSYM files.
         artifacts = [
-            x.path
+            x
             for x in target.files.to_list()
             if x.extension == "" and
                "Contents/Resources/DWARF" not in x.path
         ]
         if len(artifacts) > 0:
-            artifact = artifacts[0]
+            artifact = artifacts[0].path
     else:
         # Special support for *_library targets, which Tulsi allows building at
         # the top-level.
         artifacts = [
-            x.path
+            x
             for x in target.files.to_list()
             if x.extension == "a"
         ]
         if len(artifacts) > 0:
-            artifact = artifacts[0]
+            artifact = artifacts[0].path
 
     # Collect generated files for bazel_build.py to copy under Tulsi root.
     all_files_depsets = []
@@ -1109,15 +1129,12 @@
         transitive = transitive_generated_files,
     )
 
-    has_dsym = False
-    if hasattr(ctx.fragments, "objc"):
-        # Check the fragment directly, as macos_command_line_application does not
-        # propagate apple_common.AppleDebugOutputs.
-        has_dsym = ctx.fragments.objc.generate_dsym
+    has_dsym = apple_common.AppleDebugOutputs in target
 
     info = _struct_omitting_none(
         artifact = artifact,
         archive_root = archive_root,
+        dsym_path = dsym_path,
         generated_sources = [(x.path, x.short_path) for x in generated_files.to_list()],
         bundle_name = bundle_name,
         embedded_bundles = embedded_bundles.to_list(),
diff --git a/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects_paths.bzl b/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects_paths.bzl
index a79a7c5..14c7669 100644
--- a/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects_paths.bzl
+++ b/src/TulsiGenerator/Bazel/tulsi/tulsi_aspects_paths.bzl
@@ -19,6 +19,7 @@
 
 load(
     "@build_bazel_rules_apple//apple:providers.bzl",
+    _AppleBinaryInfo = "AppleBinaryInfo",
     _AppleBundleInfo = "AppleBundleInfo",
     _IosExtensionBundleInfo = "IosExtensionBundleInfo",
 )
@@ -32,6 +33,8 @@
 )
 
 # Re-export providers.
+AppleBinaryInfo = _AppleBinaryInfo
+
 AppleBundleInfo = _AppleBundleInfo
 
 AppleTestInfo = _AppleTestInfo
diff --git a/src/TulsiGenerator/Scripts/bazel_build.py b/src/TulsiGenerator/Scripts/bazel_build.py
index 8263732..2499972 100755
--- a/src/TulsiGenerator/Scripts/bazel_build.py
+++ b/src/TulsiGenerator/Scripts/bazel_build.py
@@ -397,7 +397,6 @@
   def __init__(self, build_settings):
     self.build_settings = build_settings
     self.verbose = 0
-    self.build_path = None
     self.bazel_bin_path = None
     self.codesign_attributes = {}
 
@@ -526,9 +525,6 @@
     self.direct_debug_prefix_map = 'DirectDebugPrefixMap' in features
     self.normalized_prefix_map = 'DebugPathNormalization' in features
 
-    self.build_path = os.path.join(self.bazel_bin_path,
-                                   os.environ.get('TULSI_BUILD_PATH', ''))
-
     # Path to the Build Events JSON file uses pid and is removed if the
     # build is successful.
     filename = '%d_%s' % (os.getpid(), BazelBuildBridge.BUILD_EVENTS_FILE)
@@ -971,7 +967,7 @@
           self._RsyncBundle(full_name, bundle_path, output_path)
         else:
           _PrintXcodeWarning('Could not find bundle %s in main bundle. ' %
-                             (bundle_name + bundle_extension) +
+                             (full_name) +
                              'Device-level Instruments debugging will be '
                              'disabled for this bundle. Please report a '
                              'Tulsi bug and attach a full Xcode build log.')
@@ -1204,7 +1200,8 @@
   def _InstallDSYMBundles(self, output_dir, outputs_data):
     """Copies any generated dSYM bundles to the given directory."""
     # Indicates that our aspect reports a dSYM was generated for this build.
-    has_dsym = outputs_data[0]['has_dsym']
+    primary_output_data = outputs_data[0]
+    has_dsym = primary_output_data['has_dsym']
 
     if not has_dsym:
       return 0, None
@@ -1215,9 +1212,20 @@
     # Declares the Xcode-generated name of our main target's dSYM.
     # This environment variable is always set, for any possible Xcode output
     # that could generate a dSYM bundle.
-    target_dsym = os.environ.get('DWARF_DSYM_FILE_NAME')
-    if target_dsym:
-      dsym_to_process = set([(self.build_path, target_dsym)])
+    #
+    # Note that this may differ from the Bazel name as Tulsi may modify the
+    # Xcode `BUNDLE_NAME`, so we need to make sure we use Bazel as the source
+    # of truth for Bazel's dSYM name, but copy it over to where Xcode expects.
+    xcode_target_dsym = os.environ.get('DWARF_DSYM_FILE_NAME')
+    dsym_to_process = set()
+    if xcode_target_dsym:
+      dsym_path = primary_output_data.get('dsym_path')
+      if dsym_path:
+        dsym_to_process.add((dsym_path, xcode_target_dsym))
+      else:
+        _PrintXcodeWarning(
+            'Unable to resolve dSYM paths for main bundle %s'
+            % primary_output_data)
 
     # Collect additional dSYM bundles generated by the dependencies of this
     # build such as extensions or frameworks.
@@ -1226,28 +1234,26 @@
       for bundle_info in data.get('embedded_bundles', []):
         if not bundle_info['has_dsym']:
           continue
-        # Uses the parent of archive_root to find dSYM bundles associated with
-        # app/extension/df bundles. Currently hinges on implementation of the
-        # build rules.
-        dsym_path = os.path.dirname(bundle_info['archive_root'])
-        bundle_full_name = (bundle_info['bundle_name'] +
-                            bundle_info['bundle_extension'])
-        dsym_filename = '%s.dSYM' % bundle_full_name
-        child_dsyms.add((dsym_path, dsym_filename))
+        dsym_path = bundle_info.get('dsym_path')
+        if dsym_path:
+          child_dsyms.add((dsym_path, os.path.basename(dsym_path)))
+        else:
+          _PrintXcodeWarning(
+              'Unable to resolve dSYM paths for embedded bundle %s'
+              % bundle_info)
     dsym_to_process.update(child_dsyms)
 
     dsyms_found = []
-    for dsym_path, dsym_filename in dsym_to_process:
-      input_dsym_full_path = os.path.join(dsym_path, dsym_filename)
-      output_full_path = os.path.join(output_dir, dsym_filename)
+    for input_dsym_full_path, xcode_dsym_name in dsym_to_process:
+      output_full_path = os.path.join(output_dir, xcode_dsym_name)
       exit_code, path = self._InstallBundle(input_dsym_full_path,
                                             output_full_path)
       if exit_code:
-        _PrintXcodeWarning('Failed to install dSYM "%s" (%s)'
-                           % (dsym_filename, exit_code))
+        _PrintXcodeWarning('Failed to install dSYM to "%s" (%s)'
+                           % (input_dsym_full_path, exit_code))
       elif path is None:
-        _PrintXcodeWarning('Could not find a dSYM bundle named "%s"'
-                           % dsym_filename)
+        _PrintXcodeWarning('Did not find a dSYM bundle at %s'
+                           % input_dsym_full_path)
       else:
         dsyms_found.append(path)