Add arm64_32 to the list of supported watchOS CPUs and add an option to enable it from the Tulsi document.

PiperOrigin-RevId: 222404989
diff --git a/src/Tulsi/TulsiGeneratorConfigDocument.swift b/src/Tulsi/TulsiGeneratorConfigDocument.swift
index 4b70a4b..8d947cd 100644
--- a/src/Tulsi/TulsiGeneratorConfigDocument.swift
+++ b/src/Tulsi/TulsiGeneratorConfigDocument.swift
@@ -468,12 +468,14 @@
         let compilationModeOption = optionSet[.ProjectGenerationCompilationMode]
         let platformConfigOption = optionSet[.ProjectGenerationPlatformConfiguration]
         let prioritizeSwiftOption = optionSet[.ProjectPrioritizesSwift]
+        let useArm64_32 = optionSet[.UseArm64_32]
         ruleEntryMap = try self.infoExtractor.ruleEntriesForLabels(selectedLabels,
                                                                    startupOptions: startupOptions,
                                                                    buildOptions: buildOptions,
                                                                    compilationModeOption: compilationModeOption,
                                                                    platformConfigOption: platformConfigOption,
                                                                    prioritizeSwiftOption: prioritizeSwiftOption,
+                                                                   useArm64_32Option: useArm64_32,
                                                                    features: self.enabledFeatures(options: optionSet))
       } catch TulsiProjectInfoExtractor.ExtractorError.ruleEntriesFailed(let info) {
         LogMessage.postError("Label resolution failed: \(info)")
@@ -803,6 +805,7 @@
                                                               compilationModeOption: options[.ProjectGenerationCompilationMode],
                                                               platformConfigOption: options[.ProjectGenerationPlatformConfiguration],
                                                               prioritizeSwiftOption: options[.ProjectPrioritizesSwift],
+                                                              useArm64_32Option: options[.UseArm64_32],
                                                               features: enabledFeatures(options: options))
     var unresolvedLabels = Set<BuildLabel>()
     var ruleInfos = [UIRuleInfo]()
diff --git a/src/TulsiGenerator/BazelAspectInfoExtractor.swift b/src/TulsiGenerator/BazelAspectInfoExtractor.swift
index 0100caf..bff66f0 100644
--- a/src/TulsiGenerator/BazelAspectInfoExtractor.swift
+++ b/src/TulsiGenerator/BazelAspectInfoExtractor.swift
@@ -73,6 +73,7 @@
                                    compilationMode: String? = nil,
                                    platformConfig: String? = nil,
                                    prioritizeSwift: Bool? = nil,
+                                   useArm64_32: Bool? = nil,
                                    features: Set<BazelSettingFeature> = []) throws -> RuleEntryMap {
     guard !targets.isEmpty else {
       return RuleEntryMap()
@@ -84,6 +85,7 @@
                                           compilationMode: compilationMode,
                                           platformConfig: platformConfig,
                                           prioritizeSwift: prioritizeSwift,
+                                          useArm64_32: useArm64_32,
                                           features: features)
   }
 
@@ -95,6 +97,7 @@
                                           compilationMode: String?,
                                           platformConfig: String?,
                                           prioritizeSwift: Bool?,
+                                          useArm64_32: Bool?,
                                           features: Set<BazelSettingFeature>) throws -> RuleEntryMap {
     localizedMessageLogger.infoMessage("Build Events JSON file at \"\(buildEventsFilePath)\"")
 
@@ -116,6 +119,7 @@
                                                compilationMode: compilationMode,
                                                platformConfig: platformConfig,
                                                prioritizeSwift: prioritizeSwift,
+                                               useArm64_32: useArm64_32,
                                                features: features,
                                                progressNotifier: progressNotifier) {
                                                 (process: Process, debugInfo: String) -> Void in
@@ -175,6 +179,7 @@
                                             compilationMode: String?,
                                             platformConfig: String?,
                                             prioritizeSwift: Bool?,
+                                            useArm64_32: Bool?,
                                             features: Set<BazelSettingFeature>,
                                             progressNotifier: ProgressNotifier? = nil,
                                             terminationHandler: @escaping CompletionHandler) -> Process? {
@@ -202,6 +207,10 @@
       config = PlatformConfiguration.defaultConfiguration
     }
 
+    if let useArm64_32 = useArm64_32 {
+      PlatformConfiguration.useArm64_32 = useArm64_32
+    }
+
     let tulsiFlags = bazelSettingsProvider.tulsiFlags(hasSwift: hasSwift,
                                                       options: nil,
                                                       features: features).getFlags(forDebug: isDbg)
diff --git a/src/TulsiGenerator/BazelBuildSettingsFeatures.swift b/src/TulsiGenerator/BazelBuildSettingsFeatures.swift
index 53385bb..0f813a7 100644
--- a/src/TulsiGenerator/BazelBuildSettingsFeatures.swift
+++ b/src/TulsiGenerator/BazelBuildSettingsFeatures.swift
@@ -21,23 +21,19 @@
 ///  - iOS also sets the --watchos_cpus flag (as it can contain a watchOS app embedded)
 extension PlatformConfiguration {
   public var bazelFlags: [String] {
-    let cpuStr = cpu.rawValue
     var flags = ["--apple_platform_type=\(platform.bazelPlatform)"]
 
     switch platform {
-    case .ios:
-      fallthrough
-    case .macos:
-      flags.append("--cpu=\(platform.bazelCPUPlatform)_\(cpuStr)")
+    case .ios, .macos:
+      flags.append("--cpu=\(platform.bazelCPUPlatform)_\(cpu.rawValue)")
     case .tvos:
-      fallthrough
+      flags.append("--\(platform.bazelCPUPlatform)_cpus=\(cpu.rawValue)")
     case .watchos:
-      flags.append("--\(platform.bazelCPUPlatform)_cpus=\(cpuStr)")
+      flags.append("--\(platform.bazelCPUPlatform)_cpus=\(cpu.watchCPU.rawValue)")
     }
 
     if case .ios = platform {
-      let watchCPU: CPU = cpu.isARM ? .armv7k : .i386
-      flags.append("--\(PlatformType.watchos.bazelCPUPlatform)_cpus=\(watchCPU.rawValue)")
+      flags.append("--\(PlatformType.watchos.bazelCPUPlatform)_cpus=\(cpu.watchCPU.rawValue)")
     }
 
     return flags
diff --git a/src/TulsiGenerator/BazelSettingsProvider.swift b/src/TulsiGenerator/BazelSettingsProvider.swift
index ecd78bc..cbe7c27 100644
--- a/src/TulsiGenerator/BazelSettingsProvider.swift
+++ b/src/TulsiGenerator/BazelSettingsProvider.swift
@@ -253,6 +253,10 @@
       defaultConfig = PlatformConfiguration.defaultConfiguration
     }
 
+    if let useArm64_32 = options[.UseArm64_32].commonValueAsBool {
+      PlatformConfiguration.useArm64_32 = useArm64_32
+    }
+
     return BazelBuildSettings(bazel: bazel,
                               bazelExecRoot: bazelExecRoot,
                               defaultPlatformConfigIdentifier: defaultConfig.identifier,
diff --git a/src/TulsiGenerator/BazelWorkspaceInfoExtractor.swift b/src/TulsiGenerator/BazelWorkspaceInfoExtractor.swift
index d746eef..4aa6e57 100644
--- a/src/TulsiGenerator/BazelWorkspaceInfoExtractor.swift
+++ b/src/TulsiGenerator/BazelWorkspaceInfoExtractor.swift
@@ -100,6 +100,7 @@
                             compilationModeOption: TulsiOption,
                             platformConfigOption: TulsiOption,
                             prioritizeSwiftOption: TulsiOption,
+                            useArm64_32Option: TulsiOption,
                             features: Set<BazelSettingFeature>) throws -> RuleEntryMap {
     func isLabelMissing(_ label: BuildLabel) -> Bool {
       return !ruleEntryCache.hasAnyRuleEntry(withBuildLabel: label)
@@ -118,6 +119,7 @@
     let compilationMode = compilationModeOption.commonValue
     let platformConfig = platformConfigOption.commonValue
     let prioritizeSwift = prioritizeSwiftOption.commonValueAsBool
+    let useArm64_32Option = useArm64_32Option.commonValueAsBool
 
     do {
       let ruleEntryMap =
@@ -127,6 +129,7 @@
                                                         compilationMode: compilationMode,
                                                         platformConfig: platformConfig,
                                                         prioritizeSwift: prioritizeSwift,
+                                                        useArm64_32: useArm64_32Option,
                                                         features: features)
       ruleEntryCache = RuleEntryMap(ruleEntryMap)
     } catch BazelAspectInfoExtractor.ExtractorError.buildFailed {
diff --git a/src/TulsiGenerator/BazelWorkspaceInfoExtractorProtocol.swift b/src/TulsiGenerator/BazelWorkspaceInfoExtractorProtocol.swift
index 1c789f6..18cd837 100644
--- a/src/TulsiGenerator/BazelWorkspaceInfoExtractorProtocol.swift
+++ b/src/TulsiGenerator/BazelWorkspaceInfoExtractorProtocol.swift
@@ -32,6 +32,7 @@
                             compilationModeOption: TulsiOption,
                             platformConfigOption: TulsiOption,
                             prioritizeSwiftOption: TulsiOption,
+                            useArm64_32Option: TulsiOption,
                             features: Set<BazelSettingFeature>) throws -> RuleEntryMap
 
   /// Extracts labels for the files referenced by the build infrastructure for the given set of
diff --git a/src/TulsiGenerator/DeploymentTarget.swift b/src/TulsiGenerator/DeploymentTarget.swift
index 5720762..01951b9 100644
--- a/src/TulsiGenerator/DeploymentTarget.swift
+++ b/src/TulsiGenerator/DeploymentTarget.swift
@@ -21,8 +21,9 @@
   case armv7
   case armv7k
   case arm64
+  case arm64_32
 
-  public static let allCases: [CPU] = [.i386, .x86_64, .armv7, .armv7k, .arm64]
+  public static let allCases: [CPU] = [.i386, .x86_64, .armv7, .armv7k, .arm64, .arm64_32]
 
   var isARM: Bool {
     switch self {
@@ -31,11 +32,13 @@
     case .armv7: return true
     case .armv7k: return true
     case .arm64: return true
+    case .arm64_32: return true
     }
   }
 
   var watchCPU: CPU {
-    return isARM ? .i386 : .armv7k
+    let armCPU = PlatformConfiguration.useArm64_32 ? CPU.arm64_32 : .armv7k
+    return isARM ? armCPU : .i386
   }
 }
 
@@ -44,6 +47,7 @@
 
   public let platform: PlatformType
   public let cpu: CPU
+  public static var useArm64_32 = false
 
   /// Default to iOS 64-bit simulator.
   public static let defaultConfiguration = PlatformConfiguration(platform: .ios, cpu: .x86_64)
@@ -98,7 +102,7 @@
     case .ios: return [.i386, .x86_64, .armv7, .arm64]
     case .macos: return  [.x86_64]
     case .tvos: return [.x86_64, .arm64]
-    case .watchos: return [.i386, .armv7k]
+    case .watchos: return [.i386, .armv7k, .arm64_32]
     }
   }
 
diff --git a/src/TulsiGenerator/TulsiOptionSet.swift b/src/TulsiGenerator/TulsiOptionSet.swift
index 68b4012..5062787 100644
--- a/src/TulsiGenerator/TulsiOptionSet.swift
+++ b/src/TulsiGenerator/TulsiOptionSet.swift
@@ -66,7 +66,11 @@
       PreBuildPhaseRunScript,
 
       // Custom build phase run script that runs after bazel build.
-      PostBuildPhaseRunScript
+      PostBuildPhaseRunScript,
+
+      // When building an iOS app with a companion watchOS app, the default architecture for the
+      // watchOS app will be armv7k. This option overrides the default and uses arm64_32 instead.
+      UseArm64_32
 
   // Options for build invocations.
   case BazelBuildOptionsDebug,
@@ -310,6 +314,7 @@
     addBoolOption(.ImprovedImportAutocompletionFix, .Generic, true)
     addBoolOption(.GenerateRunfiles, .Generic, false)
     addBoolOption(.ProjectPrioritizesSwift, .Generic, false)
+    addBoolOption(.UseArm64_32, .Generic, false)
 
     let defaultIdentifier = PlatformConfiguration.defaultConfiguration.identifier
     let platformCPUIdentifiers = PlatformConfiguration.allValidConfigurations.map { $0.identifier }
diff --git a/src/TulsiGenerator/TulsiProjectInfoExtractor.swift b/src/TulsiGenerator/TulsiProjectInfoExtractor.swift
index e0b90a6..c29139e 100644
--- a/src/TulsiGenerator/TulsiProjectInfoExtractor.swift
+++ b/src/TulsiGenerator/TulsiProjectInfoExtractor.swift
@@ -58,6 +58,7 @@
                                   compilationModeOption: TulsiOption,
                                   platformConfigOption: TulsiOption,
                                   prioritizeSwiftOption: TulsiOption,
+                                  useArm64_32Option: TulsiOption,
                                   features: Set<BazelSettingFeature>) throws -> RuleEntryMap {
     return try ruleEntriesForLabels(infos.map({ $0.label }),
                                     startupOptions: startupOptions,
@@ -65,6 +66,7 @@
                                     compilationModeOption: compilationModeOption,
                                     platformConfigOption: platformConfigOption,
                                     prioritizeSwiftOption: prioritizeSwiftOption,
+                                    useArm64_32Option: useArm64_32Option,
                                     features: features)
   }
 
@@ -74,6 +76,7 @@
                                    compilationModeOption: TulsiOption,
                                    platformConfigOption: TulsiOption,
                                    prioritizeSwiftOption: TulsiOption,
+                                   useArm64_32Option: TulsiOption,
                                    features: Set<BazelSettingFeature>) throws -> RuleEntryMap {
     do {
       return try workspaceInfoExtractor.ruleEntriesForLabels(labels,
@@ -82,6 +85,7 @@
                                                              compilationModeOption: compilationModeOption,
                                                              platformConfigOption: platformConfigOption,
                                                              prioritizeSwiftOption: prioritizeSwiftOption,
+                                                             useArm64_32Option: useArm64_32Option,
                                                              features: features)
     } catch BazelWorkspaceInfoExtractorError.aspectExtractorFailed(let info) {
       throw ExtractorError.ruleEntriesFailed(info)
diff --git a/src/TulsiGenerator/XcodeProjectGenerator.swift b/src/TulsiGenerator/XcodeProjectGenerator.swift
index 4cd9dac..80ee81d 100644
--- a/src/TulsiGenerator/XcodeProjectGenerator.swift
+++ b/src/TulsiGenerator/XcodeProjectGenerator.swift
@@ -583,6 +583,7 @@
                                                              compilationModeOption: config.options[.ProjectGenerationCompilationMode],
                                                              platformConfigOption: config.options[.ProjectGenerationPlatformConfiguration],
                                                              prioritizeSwiftOption: config.options[.ProjectPrioritizesSwift],
+                                                             useArm64_32Option: config.options[.UseArm64_32],
                                                              features: features)
     } catch BazelWorkspaceInfoExtractorError.aspectExtractorFailed(let info) {
       throw ProjectGeneratorError.labelAspectFailure(info)
diff --git a/src/TulsiGenerator/en.lproj/Options.strings b/src/TulsiGenerator/en.lproj/Options.strings
index 022475f..5bc051c 100644
--- a/src/TulsiGenerator/en.lproj/Options.strings
+++ b/src/TulsiGenerator/en.lproj/Options.strings
@@ -32,6 +32,8 @@
 
 "GenerateRunfiles" = "Generate the runfiles directory used as TEST_SRCDIR";
 
+"UseArm64_32" = "Building for Apple Watch Series 4";
+"UseArm64_32_DESC" = "Enable this option only if you are building to a Series 4 device in Xcode 10 or greater. The default when building for a iOS device with a watch app is the 32bit armv7k architecture.";
 "ProjectPrioritizesSwift" = "Prioritize for Swift development instead of (Obj-)C(++)";
 "ProjectPrioritizesSwift_DESC" = "Tulsi uses this to try to improve Bazel caching; set this if you anticipate that most of your builds inside of the generated xcodeproj depend on Swift in some way. Setting this incorrectly won't break your builds but it will potentially make them slower.";
 "CLANG_CXX_LANGUAGE_STANDARD" = "C++ language standard";
diff --git a/src/TulsiGeneratorTests/BazelSettingsProviderTests.swift b/src/TulsiGeneratorTests/BazelSettingsProviderTests.swift
new file mode 100644
index 0000000..c0182ff
--- /dev/null
+++ b/src/TulsiGeneratorTests/BazelSettingsProviderTests.swift
@@ -0,0 +1,71 @@
+// Copyright 2018 The Tulsi Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import XCTest
+@testable import TulsiGenerator
+
+class BazelSettingsProviderTests: XCTestCase {
+  let bazel = "/path/to/bazel"
+  let bazelExecRoot = "__MOCK_EXEC_ROOT__"
+  let features = Set<BazelSettingFeature>()
+  let buildRuleEntries = Set<RuleEntry>()
+  let bazelSettingsProvider = BazelSettingsProvider(universalFlags: BazelFlags())
+
+
+  func testBazelBuildSettingsProviderWithoutArm64_32Flag() {
+    let options = TulsiOptionSet()
+    let settings = bazelSettingsProvider.buildSettings(bazel:bazel,
+                                                       bazelExecRoot: bazelExecRoot,
+                                                       options: options,
+                                                       features: features,
+                                                       buildRuleEntries: buildRuleEntries)
+
+    let arm64_32Flag = "--watchos_cpus=arm64_32"
+    // Check that the arm64_32 flag is not set anywhere it shouldn't be by default.
+    for (identifier, flags) in settings.platformConfigurationFlags {
+      if identifier != "watchos_arm64_32" {
+        XCTAssert(!flags.contains(arm64_32Flag),
+                  "arm64_32 flag was unexpectedly set for \(identifier) by default. \(flags)")
+      }
+    }
+  }
+
+  func testBazelBuildSettingsProviderWithArm64_32Flag() {
+    let options = TulsiOptionSet()
+    // Manually enable the Tulsi option to force use arm64_32.
+    options.options[.UseArm64_32]?.projectValue = "YES"
+
+    let settings = bazelSettingsProvider.buildSettings(bazel:bazel,
+                                                        bazelExecRoot: bazelExecRoot,
+                                                        options: options,
+                                                        features: Set<BazelSettingFeature>(),
+                                                        buildRuleEntries: Set<RuleEntry>())
+
+    let arm64_32Flag = "--watchos_cpus=arm64_32"
+    // The flags corresponding to these identifiers will contain '--watchos_cpus=armv7k' which
+    // must be overidden.
+    let identifiersToOverride = Set(["ios_armv7", "ios_arm64", "watchos_armv7k"])
+
+    // Test that the arm64_32 flag is set in the proper locations.
+    for (identifier, flags) in settings.platformConfigurationFlags {
+      if identifier == "watchos_arm64_32" || identifiersToOverride.contains(identifier) {
+        XCTAssert(flags.contains(arm64_32Flag),
+                  "arm64_32 flag was not set for \(identifier) by the UseArm64_32 option. \(flags)")
+      } else {
+        XCTAssert(!flags.contains(arm64_32Flag),
+                  "arm64_32 flag was unexpectedly set for \(identifier) by the UseArm64_32 option. \(flags)")
+      }
+    }
+  }
+}
diff --git a/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift b/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
index 5f61302..c98b6f7 100644
--- a/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
+++ b/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
@@ -68,6 +68,7 @@
                             compilationModeOption: TulsiOption,
                             platformConfigOption: TulsiOption,
                             prioritizeSwiftOption: TulsiOption,
+                            useArm64_32Option: TulsiOption,
                             features: Set<BazelSettingFeature>) throws -> RuleEntryMap {
     invalidLabels.removeAll(keepingCapacity: true)
     let ret = RuleEntryMap()