Prioritize bundle names for unique target naming

https://github.com/bazelbuild/tulsi/commit/2f58e2c12ccd54eff78633d2f89311aefaf624e1 updated Tulsi to always use the target name over the bundle name fetched from Bazel. With this change, we use the bundle name if it's unique.

PiperOrigin-RevId: 305933047
diff --git a/src/TulsiGenerator/PBXTargetGenerator.swift b/src/TulsiGenerator/PBXTargetGenerator.swift
index 4924b45..6c755ce 100644
--- a/src/TulsiGenerator/PBXTargetGenerator.swift
+++ b/src/TulsiGenerator/PBXTargetGenerator.swift
@@ -1016,55 +1016,88 @@
     return potentialPrefix
   }
 
-  private func generateUniqueNamesForRuleEntries(_ ruleEntries: Set<RuleEntry>) -> [String: RuleEntry] {
-    // Build unique names for the target rules.
-    var rulesEntriesByTargetName = [String: [RuleEntry]]()
-    for entry: RuleEntry in ruleEntries {
-      let shortName = entry.label.targetName!
-      rulesEntriesByTargetName[shortName, default: []].append(entry)
-    }
+  /// Name the given `ruleEntries` using the `namer` function.
+  ///
+  /// `ruleEntries` must be mutually exclusive with the values in `named`. Intended use case:
+  /// call this first with an initial set and `namer`, and then subsequent calls should use the
+  /// results of the previous call (unnamed entries) with a different `namer`.
+  ///
+  /// Only unique names will be inserted into the `named` dictionary. If when naming a
+  /// `RuleEntry`, the name is already in the `named` dictionary, the previously named
+  /// `RuleEntry` will still be valid.
+  ///
+  /// Returns a `Set<RuleEntry>` representing the entries which still need to be named.
+  private func uniqueNames(for ruleEntries: Set<RuleEntry>,
+                           named: inout [String: RuleEntry],
+                           namer: (_ ruleEntry: RuleEntry) -> String?
+  ) -> Set<RuleEntry> {
+    var unnamed = Set<RuleEntry>()
 
-    var conflictingRuleEntries: [RuleEntry] = []
-    var conflictingFullNames: Set<String> = []
-    var namedRuleEntries = [String: RuleEntry]()
-
-    // Identify those which are OK and those which are in conflict.
-    for (name, entries) in rulesEntriesByTargetName {
-      guard entries.count > 1 else {
-        namedRuleEntries[name] = entries.first!
+    // Group the entries by name.
+    var ruleEntriesByName = [String: [RuleEntry]]()
+    for entry in ruleEntries {
+      guard let name = namer(entry) else {
+        unnamed.insert(entry)
         continue
       }
-
-      conflictingRuleEntries.append(contentsOf: entries)
-      conflictingFullNames.formUnion(entries.map {
-        $0.label.asFullPBXTargetName!
-      })
+      ruleEntriesByName[name, default: []].append(entry)
     }
 
+    for (name, entries) in ruleEntriesByName {
+      // Name already used or not unique.
+      guard entries.count == 1 && named.index(forKey: name) == nil else {
+        unnamed.formUnion(entries)
+        continue
+      }
+      named[name] = entries.first!
+    }
+    return unnamed
+  }
+
+  /// Generate unique names for the given rule entries, using the bundle name when it is
+  /// unique. Otherwise, falls back to a name based on the target label.
+  private func generateUniqueNamesForRuleEntries(_ ruleEntries: Set<RuleEntry>) -> [String: RuleEntry] {
+    var named = [String: RuleEntry]()
+    // Try to name using the bundle names first, then the target name.
+    var unnamed = self.uniqueNames(for: ruleEntries, named: &named) { $0.bundleName }
+    unnamed = self.uniqueNames(for: unnamed, named: &named) {
+      $0.label.targetName
+    }
+
+    // Continue only if we need to de-duplicate.
+    guard !unnamed.isEmpty else {
+      return named
+    }
+
+    // Special handling for the remaining unnamed entries - use their full target label.
+    let conflictingFullNames = Set(unnamed.map {
+      $0.label.asFullPBXTargetName!
+    })
+
     // Try to strip out a common prefix if we can find one.
     let commonPrefix = self.longestCommonPrefix(conflictingFullNames, separator: "-")
 
     guard !commonPrefix.isEmpty else {
-      for entry in conflictingRuleEntries {
+      for entry in unnamed {
         let fullName = entry.label.asFullPBXTargetName!
-        namedRuleEntries[fullName] = entry
+        named[fullName] = entry
       }
-      return namedRuleEntries
+      return named
     }
 
     // Found a common prefix, we can strip it as long as we don't cause a new duplicate.
     let charsToDrop = commonPrefix.count
-    for entry in conflictingRuleEntries {
+    for entry in unnamed {
       let fullName = entry.label.asFullPBXTargetName!
       let shortenedFullName = String(fullName.dropFirst(charsToDrop))
-      guard !shortenedFullName.isEmpty && namedRuleEntries.index(forKey: shortenedFullName) == nil else {
-        namedRuleEntries[fullName] = entry
+      guard !shortenedFullName.isEmpty && named.index(forKey: shortenedFullName) == nil else {
+        named[fullName] = entry
         continue
       }
-      namedRuleEntries[shortenedFullName] = entry
+      named[shortenedFullName] = entry
     }
 
-    return namedRuleEntries
+    return named
   }
 
   /// Adds the given file targets to a versioned group.
diff --git a/src/TulsiGeneratorIntegrationTests/Resources/GoldenProjects/TestSuiteExplicitXCTestsProject.xcodeproj/project.pbxproj b/src/TulsiGeneratorIntegrationTests/Resources/GoldenProjects/TestSuiteExplicitXCTestsProject.xcodeproj/project.pbxproj
index 54ef49c..ca7179b 100644
--- a/src/TulsiGeneratorIntegrationTests/Resources/GoldenProjects/TestSuiteExplicitXCTestsProject.xcodeproj/project.pbxproj
+++ b/src/TulsiGeneratorIntegrationTests/Resources/GoldenProjects/TestSuiteExplicitXCTestsProject.xcodeproj/project.pbxproj
@@ -284,8 +284,8 @@
 			buildConfigurationList = F4222DED8D0BF21000000000 /* Build configuration list for PBXNativeTarget "One-XCTest" */;
 			buildPhases = (
 				978262AB428C9DC600000000 /* ShellScript */,
-				978262ABF6DBF80000000000 /* ShellScript */,
-				04BFD5160000000000000000 /* Sources */,
+				978262ABF6DBF80000000001 /* ShellScript */,
+				04BFD5160000000000000001 /* Sources */,
 			);
 			buildRules = (
 			);
@@ -356,8 +356,8 @@
 			buildConfigurationList = F4222DEDF975288C00000000 /* Build configuration list for PBXNativeTarget "Three-XCTest" */;
 			buildPhases = (
 				978262ABE1E5C70200000000 /* ShellScript */,
-				978262ABF6DBF80000000001 /* ShellScript */,
-				04BFD5160000000000000001 /* Sources */,
+				978262ABF6DBF80000000000 /* ShellScript */,
+				04BFD5160000000000000000 /* Sources */,
 			);
 			buildRules = (
 			);
@@ -549,7 +549,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 0;
 			files = (
-				952C886DA25B0A0200000000 /* XCTest.m in One */,
+				952C886D96D67B6F00000000 /* XCTest.m in Three */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -557,7 +557,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 0;
 			files = (
-				952C886D96D67B6F00000000 /* XCTest.m in Three */,
+				952C886DA25B0A0200000000 /* XCTest.m in One */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -603,7 +603,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Stub Launch Image";
-				BAZEL_TARGET = "//TestSuite/One:XCTest";
+				BAZEL_TARGET = "//TestSuite/Three:XCTest";
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				FRAMEWORK_SEARCH_PATHS = "";
@@ -616,12 +616,12 @@
 				OTHER_LDFLAGS = "--version";
 				OTHER_SWIFT_FLAGS = "--version";
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.testapplicationTests;
-				PRODUCT_NAME = "One-XCTest";
+				PRODUCT_NAME = "Three-XCTest";
 				SDKROOT = iphoneos;
 				SWIFT_INSTALL_OBJC_HEADER = NO;
 				SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(PRODUCT_NAME).h";
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApplication.app/TestApplication";
-				TULSI_BUILD_PATH = TestSuite/One;
+				TULSI_BUILD_PATH = TestSuite/Three;
 				TULSI_TEST_RUNNER_ONLY = YES;
 				TULSI_XCODE_VERSION = 11.2.1.11B500;
 			};
@@ -656,7 +656,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Stub Launch Image";
-				BAZEL_TARGET = "//TestSuite/Three:XCTest";
+				BAZEL_TARGET = "//TestSuite/One:XCTest";
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				FRAMEWORK_SEARCH_PATHS = "";
@@ -669,12 +669,12 @@
 				OTHER_LDFLAGS = "--version";
 				OTHER_SWIFT_FLAGS = "--version";
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.testapplicationTests;
-				PRODUCT_NAME = "Three-XCTest";
+				PRODUCT_NAME = "One-XCTest";
 				SDKROOT = iphoneos;
 				SWIFT_INSTALL_OBJC_HEADER = NO;
 				SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(PRODUCT_NAME).h";
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApplication.app/TestApplication";
-				TULSI_BUILD_PATH = TestSuite/Three;
+				TULSI_BUILD_PATH = TestSuite/One;
 				TULSI_TEST_RUNNER_ONLY = YES;
 				TULSI_XCODE_VERSION = 11.2.1.11B500;
 			};
@@ -779,17 +779,17 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Stub Launch Image";
-				BAZEL_TARGET = "//TestSuite/One:XCTest";
+				BAZEL_TARGET = "//TestSuite/Three:XCTest";
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
 				INFOPLIST_FILE = "${PROJECT_FILE_PATH}/.tulsi/Resources/StubInfoPlist.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.testapplicationTests;
-				PRODUCT_NAME = "One-XCTest";
+				PRODUCT_NAME = "Three-XCTest";
 				SDKROOT = iphoneos;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApplication.app/TestApplication";
-				TULSI_BUILD_PATH = TestSuite/One;
+				TULSI_BUILD_PATH = TestSuite/Three;
 				TULSI_TEST_RUNNER_ONLY = YES;
 				TULSI_XCODE_VERSION = 11.2.1.11B500;
 			};
@@ -816,17 +816,17 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Stub Launch Image";
-				BAZEL_TARGET = "//TestSuite/Three:XCTest";
+				BAZEL_TARGET = "//TestSuite/One:XCTest";
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
 				INFOPLIST_FILE = "${PROJECT_FILE_PATH}/.tulsi/Resources/StubInfoPlist.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.testapplicationTests;
-				PRODUCT_NAME = "Three-XCTest";
+				PRODUCT_NAME = "One-XCTest";
 				SDKROOT = iphoneos;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApplication.app/TestApplication";
-				TULSI_BUILD_PATH = TestSuite/Three;
+				TULSI_BUILD_PATH = TestSuite/One;
 				TULSI_TEST_RUNNER_ONLY = YES;
 				TULSI_XCODE_VERSION = 11.2.1.11B500;
 			};
@@ -922,17 +922,17 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Stub Launch Image";
-				BAZEL_TARGET = "//TestSuite/One:XCTest";
+				BAZEL_TARGET = "//TestSuite/Three:XCTest";
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_PREPROCESSOR_DEFINITIONS = "NDEBUG=1";
 				INFOPLIST_FILE = "${PROJECT_FILE_PATH}/.tulsi/Resources/StubInfoPlist.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.testapplicationTests;
-				PRODUCT_NAME = "One-XCTest";
+				PRODUCT_NAME = "Three-XCTest";
 				SDKROOT = iphoneos;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApplication.app/TestApplication";
-				TULSI_BUILD_PATH = TestSuite/One;
+				TULSI_BUILD_PATH = TestSuite/Three;
 				TULSI_TEST_RUNNER_ONLY = YES;
 				TULSI_XCODE_VERSION = 11.2.1.11B500;
 			};
@@ -959,17 +959,17 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Stub Launch Image";
-				BAZEL_TARGET = "//TestSuite/Three:XCTest";
+				BAZEL_TARGET = "//TestSuite/One:XCTest";
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_PREPROCESSOR_DEFINITIONS = "NDEBUG=1";
 				INFOPLIST_FILE = "${PROJECT_FILE_PATH}/.tulsi/Resources/StubInfoPlist.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.testapplicationTests;
-				PRODUCT_NAME = "Three-XCTest";
+				PRODUCT_NAME = "One-XCTest";
 				SDKROOT = iphoneos;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApplication.app/TestApplication";
-				TULSI_BUILD_PATH = TestSuite/Three;
+				TULSI_BUILD_PATH = TestSuite/One;
 				TULSI_TEST_RUNNER_ONLY = YES;
 				TULSI_XCODE_VERSION = 11.2.1.11B500;
 			};
@@ -1065,7 +1065,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Stub Launch Image";
-				BAZEL_TARGET = "//TestSuite/One:XCTest";
+				BAZEL_TARGET = "//TestSuite/Three:XCTest";
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				FRAMEWORK_SEARCH_PATHS = "";
@@ -1078,12 +1078,12 @@
 				OTHER_LDFLAGS = "--version";
 				OTHER_SWIFT_FLAGS = "--version";
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.testapplicationTests;
-				PRODUCT_NAME = "One-XCTest";
+				PRODUCT_NAME = "Three-XCTest";
 				SDKROOT = iphoneos;
 				SWIFT_INSTALL_OBJC_HEADER = NO;
 				SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(PRODUCT_NAME).h";
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApplication.app/TestApplication";
-				TULSI_BUILD_PATH = TestSuite/One;
+				TULSI_BUILD_PATH = TestSuite/Three;
 				TULSI_TEST_RUNNER_ONLY = YES;
 				TULSI_XCODE_VERSION = 11.2.1.11B500;
 			};
@@ -1118,7 +1118,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Stub Launch Image";
-				BAZEL_TARGET = "//TestSuite/Three:XCTest";
+				BAZEL_TARGET = "//TestSuite/One:XCTest";
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				FRAMEWORK_SEARCH_PATHS = "";
@@ -1131,12 +1131,12 @@
 				OTHER_LDFLAGS = "--version";
 				OTHER_SWIFT_FLAGS = "--version";
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.testapplicationTests;
-				PRODUCT_NAME = "Three-XCTest";
+				PRODUCT_NAME = "One-XCTest";
 				SDKROOT = iphoneos;
 				SWIFT_INSTALL_OBJC_HEADER = NO;
 				SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(PRODUCT_NAME).h";
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApplication.app/TestApplication";
-				TULSI_BUILD_PATH = TestSuite/Three;
+				TULSI_BUILD_PATH = TestSuite/One;
 				TULSI_TEST_RUNNER_ONLY = YES;
 				TULSI_XCODE_VERSION = 11.2.1.11B500;
 			};
@@ -1279,10 +1279,10 @@
 		F4222DED8D0BF21000000000 /* Build configuration list for PBXNativeTarget "One-XCTest" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
-				0207AA2838C3D90E00000000 /* Debug */,
-				0207AA28616216BF00000000 /* Release */,
-				0207AA28F23A778400000000 /* __TulsiTestRunner_Debug */,
-				0207AA281FC531E700000000 /* __TulsiTestRunner_Release */,
+				0207AA2838C3D90E00000002 /* Debug */,
+				0207AA28616216BF00000002 /* Release */,
+				0207AA28F23A778400000002 /* __TulsiTestRunner_Debug */,
+				0207AA281FC531E700000002 /* __TulsiTestRunner_Release */,
 			);
 			defaultConfigurationIsVisible = 0;
 		};
@@ -1307,10 +1307,10 @@
 		F4222DEDF975288C00000000 /* Build configuration list for PBXNativeTarget "Three-XCTest" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
-				0207AA2838C3D90E00000002 /* Debug */,
-				0207AA28616216BF00000002 /* Release */,
-				0207AA28F23A778400000002 /* __TulsiTestRunner_Debug */,
-				0207AA281FC531E700000002 /* __TulsiTestRunner_Release */,
+				0207AA2838C3D90E00000000 /* Debug */,
+				0207AA28616216BF00000000 /* Release */,
+				0207AA28F23A778400000000 /* __TulsiTestRunner_Debug */,
+				0207AA281FC531E700000000 /* __TulsiTestRunner_Release */,
 			);
 			defaultConfigurationIsVisible = 0;
 		};
diff --git a/src/TulsiGeneratorIntegrationTests/Resources/GoldenProjects/TestSuiteExplicitXCTestsProject.xcodeproj/xcshareddata/xcschemes/TestApplication.xcscheme b/src/TulsiGeneratorIntegrationTests/Resources/GoldenProjects/TestSuiteExplicitXCTestsProject.xcodeproj/xcshareddata/xcschemes/TestApplication.xcscheme
index af2bf88..fe30c25 100644
--- a/src/TulsiGeneratorIntegrationTests/Resources/GoldenProjects/TestSuiteExplicitXCTestsProject.xcodeproj/xcshareddata/xcschemes/TestApplication.xcscheme
+++ b/src/TulsiGeneratorIntegrationTests/Resources/GoldenProjects/TestSuiteExplicitXCTestsProject.xcodeproj/xcshareddata/xcschemes/TestApplication.xcscheme
@@ -10,10 +10,10 @@
     <TestAction shouldUseLaunchSchemeArgsEnv="YES" selectedLauncherIdentifier="Xcode.DebuggerFoundation.Launcher.LLDB" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" buildConfiguration="__TulsiTestRunner_Debug">
         <Testables>
             <TestableReference skipped="NO">
-                <BuildableReference BuildableName="One-XCTest.xctest" BlueprintName="One-XCTest" BlueprintIdentifier="7E9AFE6269ABCA1200000000" ReferencedContainer="container:TestSuiteExplicitXCTestsProject.xcodeproj" BuildableIdentifier="primary"></BuildableReference>
+                <BuildableReference BuildableName="Three-XCTest.xctest" BlueprintName="Three-XCTest" BlueprintIdentifier="7E9AFE62F7F03B1E00000000" ReferencedContainer="container:TestSuiteExplicitXCTestsProject.xcodeproj" BuildableIdentifier="primary"></BuildableReference>
             </TestableReference>
             <TestableReference skipped="NO">
-                <BuildableReference BuildableName="Three-XCTest.xctest" BlueprintName="Three-XCTest" BlueprintIdentifier="7E9AFE62F7F03B1E00000000" ReferencedContainer="container:TestSuiteExplicitXCTestsProject.xcodeproj" BuildableIdentifier="primary"></BuildableReference>
+                <BuildableReference BuildableName="One-XCTest.xctest" BlueprintName="One-XCTest" BlueprintIdentifier="7E9AFE6269ABCA1200000000" ReferencedContainer="container:TestSuiteExplicitXCTestsProject.xcodeproj" BuildableIdentifier="primary"></BuildableReference>
             </TestableReference>
             <TestableReference skipped="NO">
                 <BuildableReference BuildableName="Two-XCTest.xctest" BlueprintName="Two-XCTest" BlueprintIdentifier="7E9AFE6296A78E1400000000" ReferencedContainer="container:TestSuiteExplicitXCTestsProject.xcodeproj" BuildableIdentifier="primary"></BuildableReference>
diff --git a/src/TulsiGeneratorTests/PBXTargetGeneratorTests.swift b/src/TulsiGeneratorTests/PBXTargetGeneratorTests.swift
index 0748e57..57aac99 100644
--- a/src/TulsiGeneratorTests/PBXTargetGeneratorTests.swift
+++ b/src/TulsiGeneratorTests/PBXTargetGeneratorTests.swift
@@ -1814,12 +1814,12 @@
         "BAZEL_TARGET": buildTarget,
         "DEBUG_INFORMATION_FORMAT": "dwarf",
         "INFOPLIST_FILE": stubPlistPaths.defaultStub,
-        "PRODUCT_NAME": targetName,
+        "PRODUCT_NAME": bundleName,
         "SDKROOT": "iphoneos",
         "TULSI_BUILD_PATH": buildPath,
       ]
       let expectedTarget = TargetDefinition(
-        name: targetName,
+        name: bundleName,
         buildConfigurations: [
           BuildConfigurationDefinition(
             name: "Debug",
@@ -1844,7 +1844,202 @@
       )
       assertTarget(expectedTarget, inTargets: targets)
     }
+  }
 
+  func testGenerateTargetsForRuleEntriesWithTheSameBundleName() {
+    let bundleName = "test"
+    let rule1TargetName = "test1"
+    let rule1BuildPath = "test/test1"
+    let rule1BuildTarget = "\(rule1BuildPath):\(rule1TargetName)"
+    let rule2TargetName = "test2"
+    let rule2BuildPath = "test/test2"
+    let rule2BuildTarget = "\(rule2BuildPath):\(rule2TargetName)"
+    let rules = Set([
+      makeTestRuleEntry(rule1BuildTarget, type: "ios_application", bundleName: bundleName, productType: .Application),
+      makeTestRuleEntry(rule2BuildTarget, type: "ios_application", bundleName: bundleName, productType: .Application),
+    ])
+
+    do {
+      _ = try targetGenerator.generateBuildTargetsForRuleEntries(rules, ruleEntryMap: RuleEntryMap())
+    } catch let e as NSError {
+      XCTFail("Failed to generate build targets with error \(e.localizedDescription)")
+    }
+
+    let topLevelConfigs = project.buildConfigurationList.buildConfigurations
+    XCTAssertEqual(topLevelConfigs.count, 0)
+
+    let targets = project.targetByName
+    XCTAssertEqual(targets.count, 2)
+
+    do {
+      let expectedBuildSettings = [
+        "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "Stub Launch Image",
+        "BAZEL_TARGET": "test/test1:\(rule1TargetName)",
+        "DEBUG_INFORMATION_FORMAT": "dwarf",
+        "INFOPLIST_FILE": stubPlistPaths.defaultStub,
+        "PRODUCT_NAME": rule1TargetName,
+        "SDKROOT": "iphoneos",
+        "TULSI_BUILD_PATH": rule1BuildPath,
+      ]
+      let expectedTarget = TargetDefinition(
+        name: rule1TargetName,
+        buildConfigurations: [
+          BuildConfigurationDefinition(
+            name: "Debug",
+            expectedBuildSettings: debugBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "Release",
+            expectedBuildSettings: releaseBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "__TulsiTestRunner_Debug",
+            expectedBuildSettings: debugTestRunnerBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "__TulsiTestRunner_Release",
+            expectedBuildSettings: releaseTestRunnerBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+        ],
+        expectedBuildPhases: [
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget),
+        ]
+      )
+      assertTarget(expectedTarget, inTargets: targets)
+    }
+    do {
+      let expectedBuildSettings = [
+        "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "Stub Launch Image",
+        "BAZEL_TARGET": "test/test2:\(rule2TargetName)",
+        "DEBUG_INFORMATION_FORMAT": "dwarf",
+        "INFOPLIST_FILE": stubPlistPaths.defaultStub,
+        "PRODUCT_NAME": rule2TargetName,
+        "SDKROOT": "iphoneos",
+        "TULSI_BUILD_PATH": rule2BuildPath,
+      ]
+      let expectedTarget = TargetDefinition(
+        name: rule2TargetName,
+        buildConfigurations: [
+          BuildConfigurationDefinition(
+            name: "Debug",
+            expectedBuildSettings: debugBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "Release",
+            expectedBuildSettings: releaseBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "__TulsiTestRunner_Debug",
+            expectedBuildSettings: debugTestRunnerBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "__TulsiTestRunner_Release",
+            expectedBuildSettings: releaseTestRunnerBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+        ],
+        expectedBuildPhases: [
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule2BuildTarget),
+        ]
+      )
+      assertTarget(expectedTarget, inTargets: targets)
+    }
+  }
+
+  func testGenerateTargetsForRuleEntriesWithSamePotentialName() {
+    let targetAndBundleName = "test"
+    let rule1BuildPath = "test/test1"
+    let rule1BuildTarget = "\(rule1BuildPath):\(targetAndBundleName)"
+    let rule2BuildPath = "test/test2"
+    let rule2BuildTarget = "\(rule2BuildPath):\(targetAndBundleName)"
+    let rules = Set([
+      makeTestRuleEntry(rule1BuildTarget, type: "ios_application", bundleName: targetAndBundleName, productType: .Application),
+      makeTestRuleEntry(rule2BuildTarget, type: "ios_application", productType: .Application),
+    ])
+
+    do {
+      _ = try targetGenerator.generateBuildTargetsForRuleEntries(rules, ruleEntryMap: RuleEntryMap())
+    } catch let e as NSError {
+      XCTFail("Failed to generate build targets with error \(e.localizedDescription)")
+    }
+
+    let topLevelConfigs = project.buildConfigurationList.buildConfigurations
+    XCTAssertEqual(topLevelConfigs.count, 0)
+
+    let targets = project.targetByName
+    XCTAssertEqual(targets.count, 2)
+
+    do {
+      let expectedBuildSettings = [
+        "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "Stub Launch Image",
+        "BAZEL_TARGET": "test/test1:\(targetAndBundleName)",
+        "DEBUG_INFORMATION_FORMAT": "dwarf",
+        "INFOPLIST_FILE": stubPlistPaths.defaultStub,
+        "PRODUCT_NAME": targetAndBundleName,
+        "SDKROOT": "iphoneos",
+        "TULSI_BUILD_PATH": rule1BuildPath,
+      ]
+      let expectedTarget = TargetDefinition(
+        name: targetAndBundleName,
+        buildConfigurations: [
+          BuildConfigurationDefinition(
+            name: "Debug",
+            expectedBuildSettings: debugBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "Release",
+            expectedBuildSettings: releaseBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "__TulsiTestRunner_Debug",
+            expectedBuildSettings: debugTestRunnerBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "__TulsiTestRunner_Release",
+            expectedBuildSettings: releaseTestRunnerBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+        ],
+        expectedBuildPhases: [
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget),
+        ]
+      )
+      assertTarget(expectedTarget, inTargets: targets)
+    }
+    do {
+      let expectedBuildSettings = [
+        "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "Stub Launch Image",
+        "BAZEL_TARGET": "test/test2:\(targetAndBundleName)",
+        "DEBUG_INFORMATION_FORMAT": "dwarf",
+        "INFOPLIST_FILE": stubPlistPaths.defaultStub,
+        "PRODUCT_NAME": "test-test2-test",
+        "SDKROOT": "iphoneos",
+        "TULSI_BUILD_PATH": rule2BuildPath,
+      ]
+      let expectedTarget = TargetDefinition(
+        name: "test-test2-test",
+        buildConfigurations: [
+          BuildConfigurationDefinition(
+            name: "Debug",
+            expectedBuildSettings: debugBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "Release",
+            expectedBuildSettings: releaseBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "__TulsiTestRunner_Debug",
+            expectedBuildSettings: debugTestRunnerBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+          BuildConfigurationDefinition(
+            name: "__TulsiTestRunner_Release",
+            expectedBuildSettings: releaseTestRunnerBuildSettingsFromSettings(expectedBuildSettings)
+          ),
+        ],
+        expectedBuildPhases: [
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule2BuildTarget),
+        ]
+      )
+      assertTarget(expectedTarget, inTargets: targets)
+    }
   }
 
   func testGenerateWatchOSTarget() {