Keep Bazel flags consistent

Whenever a Bazel configuration flag (i.e. --define, --copt) is
changed/added/removed, Bazel must reanalyze the entire build
graph (at least at the moment), which can be expensive.

In order to avoid doing so, we must keep all of the
analysis-cache-affecting flags used during the project generation
phase the same as the flags used during a build inside of Xcode.

There are a few keys points here:
- Make sure all of our Bazel invocations set the --override_repository
  flag and don't change its value between builds and generation
- We use different flags for Swift/non-Swift targets, so we have
  introduced a project-level which specifices which configuration
  flags to use during generation (Swift or non-Swift)
- User builds (from the command line) also follow the same
  restrictions; in order to make it easier for the user, we now have
  a script located inside the generated xcodeproj which allows
  building of a target with the same flags that Tulsi would use.

PiperOrigin-RevId: 204796256
diff --git a/src/Tulsi.xcodeproj/project.pbxproj b/src/Tulsi.xcodeproj/project.pbxproj
index 598a441..7af3111 100644
--- a/src/Tulsi.xcodeproj/project.pbxproj
+++ b/src/Tulsi.xcodeproj/project.pbxproj
@@ -108,8 +108,14 @@
 		5442049C2064156D00EBF343 /* install_genfiles.py in Resources */ = {isa = PBXBuildFile; fileRef = 5442049B2064156D00EBF343 /* install_genfiles.py */; };
 		546AE0AF1F75BE1D00FE9562 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546AE0AE1F75BE1D00FE9562 /* StringExtensions.swift */; };
 		546AE0B11F75C0C800FE9562 /* ShellEscapingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546AE0B01F75C0C800FE9562 /* ShellEscapingTests.swift */; };
+		54A7E04620CC596A00B3AF4C /* bazel_build_settings.py.template in Resources */ = {isa = PBXBuildFile; fileRef = 54A7E04520CC596A00B3AF4C /* bazel_build_settings.py.template */; };
+		54A7E04A20D04C5A00B3AF4C /* user_build.py in Resources */ = {isa = PBXBuildFile; fileRef = 54A7E04920D04C5A00B3AF4C /* user_build.py */; };
 		54BDD0181F4E0FD000AAC99A /* TulsiParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BDD0171F4E0FD000AAC99A /* TulsiParameter.swift */; };
+		54CA33F320C735C200E32515 /* BazelBuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA33F220C735C200E32515 /* BazelBuildSettings.swift */; };
+		54D17A5220D94C4B0028D377 /* PythonableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D17A5120D94C4B0028D377 /* PythonableTests.swift */; };
+		54D8453F20CB121D004F6CF2 /* BazelSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8453E20CB121D004F6CF2 /* BazelSettingsProvider.swift */; };
 		54EA05C81F62E3A700472AB6 /* RuleEntryMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EA05C71F62E3A700472AB6 /* RuleEntryMap.swift */; };
+		54EC201820D1A8270050AF12 /* TulsiApplicationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EC201720D1A8270050AF12 /* TulsiApplicationSupport.swift */; };
 		54EF320A1F3E0804009E9C7F /* bazel_build_events.py in Resources */ = {isa = PBXBuildFile; fileRef = 54EF32091F3E0804009E9C7F /* bazel_build_events.py */; };
 		774F6E9720A2400E00572B76 /* bazel_build_flags.py in Resources */ = {isa = PBXBuildFile; fileRef = 774F6E9620A2400E00572B76 /* bazel_build_flags.py */; };
 		8B0F78C81BE5BC7E00357561 /* ConfigEditorSourceFilterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B0F78C71BE5BC7E00357561 /* ConfigEditorSourceFilterViewController.swift */; };
@@ -288,8 +294,14 @@
 		5442049B2064156D00EBF343 /* install_genfiles.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = install_genfiles.py; sourceTree = "<group>"; };
 		546AE0AE1F75BE1D00FE9562 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = "<group>"; };
 		546AE0B01F75C0C800FE9562 /* ShellEscapingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellEscapingTests.swift; sourceTree = "<group>"; };
+		54A7E04520CC596A00B3AF4C /* bazel_build_settings.py.template */ = {isa = PBXFileReference; explicitFileType = text.script.python; path = bazel_build_settings.py.template; sourceTree = "<group>"; };
+		54A7E04920D04C5A00B3AF4C /* user_build.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = user_build.py; sourceTree = "<group>"; };
 		54BDD0171F4E0FD000AAC99A /* TulsiParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TulsiParameter.swift; sourceTree = "<group>"; };
+		54CA33F220C735C200E32515 /* BazelBuildSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelBuildSettings.swift; sourceTree = "<group>"; };
+		54D17A5120D94C4B0028D377 /* PythonableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PythonableTests.swift; sourceTree = "<group>"; };
+		54D8453E20CB121D004F6CF2 /* BazelSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelSettingsProvider.swift; sourceTree = "<group>"; };
 		54EA05C71F62E3A700472AB6 /* RuleEntryMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleEntryMap.swift; sourceTree = "<group>"; };
+		54EC201720D1A8270050AF12 /* TulsiApplicationSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TulsiApplicationSupport.swift; sourceTree = "<group>"; };
 		54EF32091F3E0804009E9C7F /* bazel_build_events.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = bazel_build_events.py; sourceTree = "<group>"; };
 		774F6E9620A2400E00572B76 /* bazel_build_flags.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; name = bazel_build_flags.py; path = TulsiGenerator/Scripts/bazel_build_flags.py; sourceTree = "<group>"; };
 		8B0F78C71BE5BC7E00357561 /* ConfigEditorSourceFilterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigEditorSourceFilterViewController.swift; sourceTree = "<group>"; };
@@ -409,11 +421,12 @@
 				3DE40AA41C17A2F60055E464 /* PBXObjectsTests.swift */,
 				8B8F55AC1BE3ECDC0095AF7F /* PBXProjSerializerTests.swift */,
 				3D4A123B1C1882F5006E592D /* PBXTargetGeneratorTests.swift */,
+				54D17A5120D94C4B0028D377 /* PythonableTests.swift */,
+				546AE0B01F75C0C800FE9562 /* ShellEscapingTests.swift */,
 				3DA65B611C693B570055448E /* TulsiGeneratorConfigTests.swift */,
 				3D51A80B1C52CB6C00FE90A6 /* TulsiOptionSetTests.swift */,
 				3DA65B651C693B7E0055448E /* TulsiProjectTests.swift */,
 				3DA65B3D1C6849140055448E /* XcodeProjectGeneratorTests.swift */,
-				546AE0B01F75C0C800FE9562 /* ShellEscapingTests.swift */,
 			);
 			path = TulsiGeneratorTests;
 			sourceTree = "<group>";
@@ -433,6 +446,7 @@
 				2DD7C6C31F6887DB00163B92 /* DeploymentTarget.swift */,
 				E1C0EBDA1F70982300FA2054 /* XcodeGeneratorInvalidPaths.swift */,
 				54EA05C71F62E3A700472AB6 /* RuleEntryMap.swift */,
+				54CA33F220C735C200E32515 /* BazelBuildSettings.swift */,
 			);
 			name = Models;
 			sourceTree = "<group>";
@@ -472,6 +486,8 @@
 				5429EA901F38F56200A78405 /* BazelXcodeProjectPatcher.swift */,
 				5416093B1F5854090016769C /* BazelBuildEvents.swift */,
 				E155E20B1FCE47D9002B16BB /* BazelBuildSettingsFeatures.swift */,
+				54D8453E20CB121D004F6CF2 /* BazelSettingsProvider.swift */,
+				54EC201720D1A8270050AF12 /* TulsiApplicationSupport.swift */,
 			);
 			name = Implementation;
 			sourceTree = "<group>";
@@ -491,6 +507,8 @@
 				E1C0186C2051B65C000580CC /* symbol_cache_schema.py */,
 				E1C018712051B66C000580CC /* update_symbol_cache.py */,
 				3DFB7C4A1C81F78000376760 /* command_line_splitter.sh */,
+				54A7E04520CC596A00B3AF4C /* bazel_build_settings.py.template */,
+				54A7E04920D04C5A00B3AF4C /* user_build.py */,
 			);
 			path = Scripts;
 			sourceTree = "<group>";
@@ -767,8 +785,10 @@
 				3D9485411C3193F00026CE41 /* Options.strings in Resources */,
 				3D5A1B581D1B3485006FC2A6 /* StubInfoPlist.plist in Resources */,
 				E135A781205880720082E4D0 /* apfs_clone_copy.py in Resources */,
+				54A7E04620CC596A00B3AF4C /* bazel_build_settings.py.template in Resources */,
 				3DAFE8AE1DF1F23D0055AF56 /* StubIOSAppExtensionInfoPlist.plist in Resources */,
 				3DCD08111D779576006ABE5B /* StubWatchOS2AppExtensionInfoPlist.plist in Resources */,
+				54A7E04A20D04C5A00B3AF4C /* user_build.py in Resources */,
 				3D903A7A1D775AE800B034E9 /* StubWatchOS2InfoPlist.plist in Resources */,
 				5442049C2064156D00EBF343 /* install_genfiles.py in Resources */,
 				E1C018722051B66C000580CC /* update_symbol_cache.py in Resources */,
@@ -846,6 +866,7 @@
 				3D99268C1C29F1100094E098 /* PBXUniformTypeIdentifiers.swift in Sources */,
 				3D1F2E261CF760420008CE83 /* BazelLocator.swift in Sources */,
 				3DAEE45E1C85128600BA1C67 /* BazelWorkspacePathInfoFetcher.swift in Sources */,
+				54EC201820D1A8270050AF12 /* TulsiApplicationSupport.swift in Sources */,
 				3DCFE5DE1C80B70700D7F31B /* BazelAspectInfoExtractor.swift in Sources */,
 				54EA05C81F62E3A700472AB6 /* RuleEntryMap.swift in Sources */,
 				3D51A8001C52C10A00FE90A6 /* TulsiOption.swift in Sources */,
@@ -864,6 +885,8 @@
 				5416093C1F5854090016769C /* BazelBuildEvents.swift in Sources */,
 				3D9926891C29F10B0094E098 /* BuildLabel.swift in Sources */,
 				3D9926971C29FB180094E098 /* TulsiProjectInfoExtractor.swift in Sources */,
+				54D8453F20CB121D004F6CF2 /* BazelSettingsProvider.swift in Sources */,
+				54CA33F320C735C200E32515 /* BazelBuildSettings.swift in Sources */,
 				5429EA911F38F56200A78405 /* BazelXcodeProjectPatcher.swift in Sources */,
 				546AE0AF1F75BE1D00FE9562 /* StringExtensions.swift in Sources */,
 				3DA65B461C68558A0055448E /* XcodeProjectGenerator.swift in Sources */,
@@ -889,6 +912,7 @@
 				3D9926841C29F0CC0094E098 /* PBXObjectsTests.swift in Sources */,
 				3DA65B621C693B570055448E /* TulsiGeneratorConfigTests.swift in Sources */,
 				3DA65B3E1C6849140055448E /* XcodeProjectGeneratorTests.swift in Sources */,
+				54D17A5220D94C4B0028D377 /* PythonableTests.swift in Sources */,
 				3DFB7C4F1C835EFB00376760 /* CommandLineSplitterTests.swift in Sources */,
 				3DA65B661C693B7E0055448E /* TulsiProjectTests.swift in Sources */,
 				3DA65B601C6930210055448E /* NSURLExtensionsTests.swift in Sources */,
diff --git a/src/Tulsi/TulsiGeneratorConfigDocument.swift b/src/Tulsi/TulsiGeneratorConfigDocument.swift
index 1effc05..78782f0 100644
--- a/src/Tulsi/TulsiGeneratorConfigDocument.swift
+++ b/src/Tulsi/TulsiGeneratorConfigDocument.swift
@@ -462,10 +462,12 @@
         let startupOptions = optionSet[.BazelBuildStartupOptionsDebug]
         let buildOptions = optionSet[.BazelBuildOptionsDebug]
         let projectGenBuildOptions = optionSet[.BazelBuildOptionsProjectGenerationOnly]
+        let prioritizeSwiftOption = optionSet[.ProjectPrioritizesSwift]
         ruleEntryMap = try self.infoExtractor.ruleEntriesForLabels(selectedLabels,
                                                                    startupOptions: startupOptions,
                                                                    buildOptions: buildOptions,
-                                                                   projectGenBuildOptions: projectGenBuildOptions)
+                                                                   projectGenBuildOptions: projectGenBuildOptions,
+                                                                   prioritizeSwiftOption: prioritizeSwiftOption)
       } catch TulsiProjectInfoExtractor.ExtractorError.ruleEntriesFailed(let info) {
         LogMessage.postError("Label resolution failed: \(info)")
         return
@@ -788,7 +790,8 @@
     let ruleEntryMap = try infoExtractor.ruleEntriesForLabels(concreteBuildTargetLabels,
                                                               startupOptions: optionSet![.BazelBuildStartupOptionsDebug],
                                                               buildOptions: optionSet![.BazelBuildOptionsDebug],
-                                                              projectGenBuildOptions: optionSet![.BazelBuildOptionsProjectGenerationOnly])
+                                                              projectGenBuildOptions: optionSet![.BazelBuildOptionsProjectGenerationOnly],
+                                                              prioritizeSwiftOption: optionSet![.ProjectPrioritizesSwift])
     var unresolvedLabels = Set<BuildLabel>()
     var ruleInfos = [UIRuleInfo]()
     for label in concreteBuildTargetLabels {
diff --git a/src/TulsiGenerator/BazelAspectInfoExtractor.swift b/src/TulsiGenerator/BazelAspectInfoExtractor.swift
index bb459df..49f66c1 100644
--- a/src/TulsiGenerator/BazelAspectInfoExtractor.swift
+++ b/src/TulsiGenerator/BazelAspectInfoExtractor.swift
@@ -28,6 +28,8 @@
   var bazelURL: URL
   /// The location of the Bazel workspace to be examined.
   let workspaceRootURL: URL
+  /// Stores Tulsi-specific Bazel settings.
+  let bazelSettingsProvider: BazelSettingsProviderProtocol
 
   private let bundle: Bundle
   /// Absolute path to the workspace containing the Tulsi aspect bzl file.
@@ -42,9 +44,11 @@
 
   init(bazelURL: URL,
        workspaceRootURL: URL,
+       bazelSettingsProvider: BazelSettingsProviderProtocol,
        localizedMessageLogger: LocalizedMessageLogger) {
     self.bazelURL = bazelURL
     self.workspaceRootURL = workspaceRootURL
+    self.bazelSettingsProvider = bazelSettingsProvider
     self.localizedMessageLogger = localizedMessageLogger
 
     let buildEventsFileName = "build_events_\(getpid()).json"
@@ -62,7 +66,8 @@
   func extractRuleEntriesForLabels(_ targets: [BuildLabel],
                                    startupOptions: [String] = [],
                                    buildOptions: [String] = [],
-                                   projectGenerationOptions: [String] = []) throws -> RuleEntryMap {
+                                   projectGenerationOptions: [String] = [],
+                                   prioritizeSwift: Bool = false) throws -> RuleEntryMap {
     guard !targets.isEmpty else {
       return RuleEntryMap()
     }
@@ -70,7 +75,8 @@
     return try extractRuleEntriesUsingBEP(targets,
                                           startupOptions: startupOptions,
                                           buildOptions: buildOptions,
-                                          projectGenerationOptions: projectGenerationOptions)
+                                          projectGenerationOptions: projectGenerationOptions,
+                                          prioritizeSwift: prioritizeSwift)
   }
 
   // MARK: - Private methods
@@ -78,7 +84,8 @@
   private func extractRuleEntriesUsingBEP(_ targets: [BuildLabel],
                                           startupOptions: [String],
                                           buildOptions: [String],
-                                          projectGenerationOptions: [String]) throws -> RuleEntryMap {
+                                          projectGenerationOptions: [String],
+                                          prioritizeSwift: Bool) throws -> RuleEntryMap {
     localizedMessageLogger.infoMessage("Build Events JSON file at \"\(buildEventsFilePath)\"")
 
     let progressNotifier = ProgressNotifier(name: SourceFileExtraction,
@@ -97,6 +104,7 @@
                                                startupOptions: startupOptions,
                                                buildOptions: buildOptions,
                                                projectGenerationOptions: projectGenerationOptions,
+                                               prioritizeSwift: prioritizeSwift,
                                                progressNotifier: progressNotifier) {
                                                 (process: Process, debugInfo: String) -> Void in
        defer { semaphore.signal() }
@@ -153,6 +161,7 @@
                                             startupOptions: [String] = [],
                                             buildOptions: [String] = [],
                                             projectGenerationOptions: [String] = [],
+                                            prioritizeSwift: Bool,
                                             progressNotifier: ProgressNotifier? = nil,
                                             terminationHandler: @escaping CompletionHandler) -> Process? {
 
@@ -168,19 +177,23 @@
 
     let tulsiVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "UNKNOWN"
 
+    let tulsiFlags = bazelSettingsProvider.tulsiFlags(hasSwift: prioritizeSwift).getFlags()
     var arguments = startupOptions
+    arguments.append(contentsOf: tulsiFlags.startup)
+    arguments.append("build")
     arguments.append(contentsOf: [
-        "build",
-        // The aspect is run in debug mode to match the default Xcode build configuration.
-        // This does indeed affect Bazel analysis caching.
-        "--compilation_mode=dbg",
-        "--symlink_prefix=/",  // Generate artifacts without overwriting the normal build symlinks.
         // The following flags control Bazel console output and should not affect Bazel analysis
         // caching.
         "--announce_rc",  // Print the RC files used by this operation.
         "--show_result=0",  // Don't bother printing the build results.
         "--noshow_loading_progress",  // Don't show Bazel's loading progress.
         "--noshow_progress",  // Don't show Bazel's build progress.
+        "--symlink_prefix=/",  // Generate artifacts without overwriting the normal build symlinks.
+    ])
+    arguments.append(contentsOf: projectGenerationOptions)
+    arguments.append(contentsOf: buildOptions)
+    arguments.append(contentsOf: tulsiFlags.build)
+    arguments.append(contentsOf: [
         // The following flags are used by Tulsi to identify itself and read build information from
         // Bazel. They should not affect Bazel analysis caching.
         "--tool_tag=tulsi_v\(tulsiVersion):generator",  // Add a tag for tracking.
@@ -191,20 +204,10 @@
         "--noexpand_test_suites",
         // The following flags WILL affect Bazel analysis caching.
         // Keep this consistent with bazel_build.py.
-        "--nocheck_visibility",  // Don't do package visibility enforcement during aspect runs.
-        "--override_repository=tulsi=\(aspectWorkspacePath)",
         "--aspects",
         "@tulsi//tulsi:tulsi_aspects.bzl%\(aspect)",
         "--output_groups=tulsi-info,-_,-default",  // Build only the aspect artifacts.
     ])
-    // Extra flags added by bazel_build.py.
-    arguments.append(contentsOf: [
-        "--features=debug_prefix_map_pwd_is_dot",
-        "--define=apple.add_debugger_entitlement=1",
-        "--define=apple.propagate_embedded_extra_outputs=1",
-    ])
-    arguments.append(contentsOf: projectGenerationOptions)
-    arguments.append(contentsOf: buildOptions)
     arguments.append(contentsOf: targets)
 
     let process = TulsiProcessRunner.createProcess(bazelURL.path,
diff --git a/src/TulsiGenerator/BazelBuildSettings.swift b/src/TulsiGenerator/BazelBuildSettings.swift
new file mode 100644
index 0000000..4743124
--- /dev/null
+++ b/src/TulsiGenerator/BazelBuildSettings.swift
@@ -0,0 +1,230 @@
+// 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 Foundation
+
+protocol Pythonable {
+  func toPython(_ indentation: String) -> String
+}
+
+enum PythonSettings {
+  static let doubleIndent: String = "    "
+}
+
+extension Dictionary where Key == String, Value: Pythonable {
+  func toPython(_ indentation: String) -> String {
+    guard !isEmpty else { return "{}" }
+
+    var script = "{\n"
+    for (key, value) in self {
+      script += """
+\(indentation)\(PythonSettings.doubleIndent)'\(key)': \(value.toPython("")),
+
+"""
+    }
+    script += "\(indentation)}"
+    return script
+  }
+}
+
+extension Array where Element: Pythonable {
+  func toPython(_ indentation: String) -> String {
+    guard !isEmpty else { return "[]" }
+
+    var script = "[\n"
+    for value in self {
+      script += """
+\(indentation)\(PythonSettings.doubleIndent)\(value.toPython("")),
+
+"""
+    }
+    script += "\(indentation)]"
+    return script
+  }
+}
+
+extension Set where Element: Pythonable {
+  func toPython(_ indentation: String) -> String {
+    guard !isEmpty else { return "set()" }
+
+    var script = "set([\n"
+    for value in self {
+      script += """
+\(indentation)\(PythonSettings.doubleIndent)\(value.toPython("")),
+
+"""
+    }
+    script += "\(indentation)])"
+    return script
+  }
+}
+
+extension String: Pythonable {
+  func toPython(_ indentation: String) -> String {
+    guard self.contains("'") else { return "'\(self)'"}
+
+    let escapedStr = self.replacingOccurrences(of: "'", with: "\\'")
+    return "'\(escapedStr)'"
+  }
+}
+
+class BazelFlags: Equatable, Pythonable {
+  public let startup: [String]
+  public let build: [String]
+
+  public convenience init(startupStr: String, buildStr: String) {
+    self.init(startup: startupStr.components(separatedBy: " "),
+              build: buildStr.components(separatedBy: " "))
+  }
+
+  public init(startup: [String] = [], build: [String] = []) {
+    self.startup = startup.filter { !$0.isEmpty }
+    self.build = build.filter { !$0.isEmpty }
+  }
+
+  var isEmpty: Bool {
+    return startup.isEmpty && build.isEmpty
+  }
+
+  func toPython(_ indentation: String) -> String {
+    guard !self.isEmpty else { return "BazelFlags()" }
+
+    let nestedIndentation = "\(indentation)\(PythonSettings.doubleIndent)"
+    return """
+BazelFlags(
+\(nestedIndentation)startup = \(startup.toPython(nestedIndentation)),
+\(nestedIndentation)build = \(build.toPython(nestedIndentation)),
+\(indentation))
+"""
+  }
+
+  static func ==(lhs: BazelFlags, rhs: BazelFlags) -> Bool {
+    return lhs.startup == rhs.startup && lhs.build == rhs.build
+  }
+
+  static func +(lhs: BazelFlags, rhs: BazelFlags) -> BazelFlags {
+    return BazelFlags(startup: lhs.startup + rhs.startup,
+                      build: lhs.build + rhs.build)
+  }
+}
+
+class BazelFlagsSet: Equatable, Pythonable {
+  public let debug: BazelFlags
+  public let release: BazelFlags
+
+  public convenience init(startupFlags: [String] = [], buildFlags: [String] = []) {
+    self.init(common: BazelFlags(startup: startupFlags, build: buildFlags))
+  }
+
+  public init(debug: BazelFlags = BazelFlags(),
+              release: BazelFlags = BazelFlags(),
+              common: BazelFlags = BazelFlags()) {
+    self.debug = debug + common
+    self.release = release + common
+  }
+
+  var isEmpty: Bool {
+    return debug.isEmpty && release.isEmpty
+  }
+
+  func getFlags(forDebug: Bool = true) -> BazelFlags {
+    return forDebug ? debug : release
+  }
+
+  func toPython(_ indentation: String) -> String {
+    guard !isEmpty else { return "BazelFlagsSet()" }
+
+    let nestedIndentation = "\(indentation)\(PythonSettings.doubleIndent)"
+
+    // If debug == release we don't need to specify the same flags twice.
+    guard debug != release else {
+      return """
+BazelFlagsSet(
+\(nestedIndentation)flags = \(debug.toPython(nestedIndentation)),
+\(indentation))
+"""
+    }
+
+    return """
+BazelFlagsSet(
+\(nestedIndentation)debug = \(debug.toPython(nestedIndentation)),
+\(nestedIndentation)release = \(release.toPython(nestedIndentation)),
+\(indentation))
+"""
+  }
+
+  static func ==(lhs: BazelFlagsSet, rhs: BazelFlagsSet) -> Bool {
+    return lhs.debug == rhs.debug && lhs.release == rhs.release
+  }
+
+  static func +(lhs: BazelFlagsSet, rhs: BazelFlagsSet) -> BazelFlagsSet {
+    return BazelFlagsSet(debug: lhs.debug + rhs.debug,
+                         release: lhs.release + rhs.release)
+  }
+}
+
+class BazelBuildSettings: Pythonable {
+
+  public let bazel: String
+  public let bazelExecRoot: String
+
+  public let tulsiCacheAffectingFlagsSet: BazelFlagsSet
+  public let tulsiCacheSafeFlagSet: BazelFlagsSet
+
+  public let tulsiSwiftFlagSet: BazelFlagsSet
+  public let tulsiNonSwiftFlagSet: BazelFlagsSet
+
+  /// Set of targets which depend (in some fashion) on Swift.
+  public let swiftTargets: Set<String>
+
+  public let projDefaultFlagSet: BazelFlagsSet
+  public let projTargetFlagSets: [String: BazelFlagsSet]
+
+  public init(bazel: String,
+              bazelExecRoot: String,
+              swiftTargets: Set<String>,
+              tulsiCacheAffectingFlagsSet: BazelFlagsSet,
+              tulsiCacheSafeFlagSet: BazelFlagsSet,
+              tulsiSwiftFlagSet: BazelFlagsSet,
+              tulsiNonSwiftFlagSet: BazelFlagsSet,
+              projDefaultFlagSet: BazelFlagsSet,
+              projTargetFlagSets: [String: BazelFlagsSet]) {
+    self.bazel = bazel
+    self.bazelExecRoot = bazelExecRoot
+    self.swiftTargets = swiftTargets
+    self.tulsiCacheAffectingFlagsSet = tulsiCacheAffectingFlagsSet
+    self.tulsiCacheSafeFlagSet = tulsiCacheSafeFlagSet
+    self.tulsiSwiftFlagSet = tulsiSwiftFlagSet
+    self.tulsiNonSwiftFlagSet = tulsiNonSwiftFlagSet
+    self.projDefaultFlagSet = projDefaultFlagSet
+    self.projTargetFlagSets = projTargetFlagSets
+  }
+
+  public func toPython(_ indentation: String) -> String {
+    let nestedIndentation = "\(indentation)\(PythonSettings.doubleIndent)"
+    return """
+BazelBuildSettings(
+\(nestedIndentation)\(bazel.toPython(nestedIndentation)),
+\(nestedIndentation)\(bazelExecRoot.toPython(nestedIndentation)),
+\(nestedIndentation)\(swiftTargets.toPython(nestedIndentation)),
+\(nestedIndentation)\(tulsiCacheAffectingFlagsSet.toPython(nestedIndentation)),
+\(nestedIndentation)\(tulsiCacheSafeFlagSet.toPython(nestedIndentation)),
+\(nestedIndentation)\(tulsiSwiftFlagSet.toPython(nestedIndentation)),
+\(nestedIndentation)\(tulsiNonSwiftFlagSet.toPython(nestedIndentation)),
+\(nestedIndentation)\(projDefaultFlagSet.toPython(nestedIndentation)),
+\(nestedIndentation)\(projTargetFlagSets.toPython(nestedIndentation)),
+\(indentation))
+"""
+  }
+}
diff --git a/src/TulsiGenerator/BazelQueryInfoExtractor.swift b/src/TulsiGenerator/BazelQueryInfoExtractor.swift
index 15f8b1a..6ae4033 100644
--- a/src/TulsiGenerator/BazelQueryInfoExtractor.swift
+++ b/src/TulsiGenerator/BazelQueryInfoExtractor.swift
@@ -28,15 +28,21 @@
   var bazelURL: URL
   /// The location of the directory in which the workspace enclosing this BUILD file can be found.
   let workspaceRootURL: URL
+  /// Universal flags for all Bazel invocations.
+  private let bazelUniversalFlags: BazelFlags
 
   private let localizedMessageLogger: LocalizedMessageLogger
   private var queuedInfoMessages = [String]()
 
   private typealias CompletionHandler = (Process, Data, String?, String) -> Void
 
-  init(bazelURL: URL, workspaceRootURL: URL, localizedMessageLogger: LocalizedMessageLogger) {
+  init(bazelURL: URL,
+       workspaceRootURL: URL,
+       bazelUniversalFlags: BazelFlags,
+       localizedMessageLogger: LocalizedMessageLogger) {
     self.bazelURL = bazelURL
     self.workspaceRootURL = workspaceRootURL
+    self.bazelUniversalFlags = bazelUniversalFlags
     self.localizedMessageLogger = localizedMessageLogger
   }
 
@@ -143,14 +149,18 @@
 
     var arguments = [
         "--max_idle_secs=60",
-        "query",
+    ]
+    arguments.append(contentsOf: bazelUniversalFlags.startup)
+    arguments.append("query")
+    arguments.append(contentsOf: bazelUniversalFlags.build)
+    arguments.append(contentsOf: [
         "--announce_rc",  // Print the RC files used by this operation.
         "--noimplicit_deps",
         "--order_output=no",
         "--noshow_loading_progress",
         "--noshow_progress",
         query
-    ]
+    ])
     arguments.append(contentsOf: additionalArguments)
     if let kind = outputKind {
       arguments.append(contentsOf: ["--output", kind])
diff --git a/src/TulsiGenerator/BazelSettingsProvider.swift b/src/TulsiGenerator/BazelSettingsProvider.swift
new file mode 100644
index 0000000..c9524bd
--- /dev/null
+++ b/src/TulsiGenerator/BazelSettingsProvider.swift
@@ -0,0 +1,194 @@
+// 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 Foundation
+
+/// Defines an object that provides flags for Bazel invocations.
+protocol BazelSettingsProviderProtocol {
+  /// All general-Tulsi flags, varying based on whether the project has Swift or not.
+  func tulsiFlags(hasSwift: Bool) -> BazelFlagsSet
+
+  /// Cache-able Bazel flags based off TulsiOptions, used to generate BazelBuildSettings.
+  func optionsBasedFlags(_ options: TulsiOptionSet) -> BazelFlagsSet
+
+  /// Bazel build settings, used during Xcode/user Bazel builds.
+  func buildSettings(bazel: String,
+                     bazelExecRoot: String,
+                     options: TulsiOptionSet,
+                     buildRuleEntries: Set<RuleEntry>) -> BazelBuildSettings
+}
+
+class BazelSettingsProvider: BazelSettingsProviderProtocol {
+
+  /// Non-cacheable flags added by Tulsi for dbg (Debug) builds.
+  static let tulsiDebugFlags = BazelFlags(build: ["--compilation_mode=dbg"])
+
+  /// Non-cacheable flags added by Tulsi for opt (Release) builds.
+  static let tulsiReleaseFlags = BazelFlags(build: ["--compilation_mode=opt", "--strip=always"])
+
+  /// Non-cacheable flags added by Tulsi for all builds.
+  static let tulsiCommonNonCacheableFlags = BazelFlags(build:  [
+      "--define=apple.add_debugger_entitlement=1",
+      "--define=apple.propagate_embedded_extra_outputs=1",
+  ])
+
+  /// Cache-able flags added by Tulsi for builds.
+  static let tulsiCacheableFlags = BazelFlagsSet(buildFlags: ["--announce_rc"])
+  /// Non-cacheable flags added by Tulsi for builds.
+  static let tulsiNonCacheableFlags = BazelFlagsSet(debug: tulsiDebugFlags,
+                                                    release: tulsiReleaseFlags,
+                                                    common: tulsiCommonNonCacheableFlags)
+
+  /// Flags added by Tulsi for builds which contain Swift.
+  /// - Always generate dSYMs for projects with Swift dependencies, as dSYMs are still required to
+  ///   expr or print variables within Bazel-built Swift modules in LLDB.
+  static let tulsiSwiftFlags = BazelFlagsSet(buildFlags: ["--apple_generate_dsym"])
+
+  /// Flags added by Tulsi for builds which do not contain Swift.
+  /// - Enable dSYMs only for Release builds.
+  /// - Flag for remapping debug symbols.
+  static let tulsiNonSwiftFlags = BazelFlagsSet(
+      release: BazelFlags(build: ["--apple_generate_dsym"]),
+      // TODO: Somehow support the open-source version of this which varies based on the exec-root.
+      common: BazelFlags(build: ["--features=debug_prefix_map_pwd_is_dot"]))
+
+  let universalFlags: BazelFlags
+  let cacheableFlags: BazelFlagsSet
+  let nonCacheableFlags: BazelFlagsSet
+
+  let swiftFlags: BazelFlagsSet
+  let nonSwiftFlags: BazelFlagsSet
+
+  public convenience init(universalFlags: BazelFlags) {
+    self.init(universalFlags: universalFlags,
+              cacheableFlags: BazelSettingsProvider.tulsiCacheableFlags,
+              nonCacheableFlags: BazelSettingsProvider.tulsiNonCacheableFlags,
+              swiftFlags: BazelSettingsProvider.tulsiSwiftFlags,
+              nonSwiftFlags: BazelSettingsProvider.tulsiNonSwiftFlags)
+  }
+
+  public init(universalFlags: BazelFlags,
+              cacheableFlags: BazelFlagsSet,
+              nonCacheableFlags: BazelFlagsSet,
+              swiftFlags: BazelFlagsSet,
+              nonSwiftFlags: BazelFlagsSet) {
+    self.universalFlags = universalFlags
+    self.cacheableFlags = cacheableFlags
+    self.nonCacheableFlags = nonCacheableFlags
+    self.swiftFlags = swiftFlags
+    self.nonSwiftFlags = nonSwiftFlags
+  }
+
+  func tulsiFlags(hasSwift: Bool) -> BazelFlagsSet {
+    let languageFlags = hasSwift ? swiftFlags : nonSwiftFlags
+    return BazelFlagsSet(common: universalFlags) + cacheableFlags + nonCacheableFlags + languageFlags
+  }
+
+  func optionsBasedFlags(_ options: TulsiOptionSet) -> BazelFlagsSet {
+    var configBasedTulsiFlags = [String]()
+    if let continueBuildingAfterError = options[.BazelContinueBuildingAfterError].commonValueAsBool,
+      continueBuildingAfterError {
+      configBasedTulsiFlags.append("--keep_going")
+    }
+    return BazelFlagsSet(buildFlags: configBasedTulsiFlags)
+  }
+
+  func buildSettings(bazel: String,
+                     bazelExecRoot: String,
+                     options: TulsiOptionSet,
+                     buildRuleEntries: Set<RuleEntry>) -> BazelBuildSettings {
+    let projDefaultSettings = getProjDefaultSettings(options)
+    var targetSettings = [String: BazelFlagsSet]()
+
+    // Create a Set of all targets which have specialized Bazel settings.
+    var labels = Set<String>()
+    labels.formUnion(getTargets(options, .BazelBuildOptionsDebug))
+    labels.formUnion(getTargets(options, .BazelBuildOptionsRelease))
+    labels.formUnion(getTargets(options, .BazelBuildStartupOptionsDebug))
+    labels.formUnion(getTargets(options, .BazelBuildStartupOptionsRelease))
+
+    for lbl in labels {
+      guard let settings = getTargetSettings(options, lbl, defaultValue: projDefaultSettings) else {
+        continue
+      }
+      targetSettings[lbl] = settings
+    }
+
+    let swiftRuleEntries = buildRuleEntries.filter {
+        $0.attributes[.has_swift_dependency] as? Bool ?? false
+    }
+    let swiftTargets = Set(swiftRuleEntries.map { $0.label.value })
+
+    return BazelBuildSettings(bazel: bazel,
+                              bazelExecRoot: bazelExecRoot,
+                              swiftTargets: swiftTargets,
+                              tulsiCacheAffectingFlagsSet: BazelFlagsSet(common: universalFlags) + nonCacheableFlags,
+                              tulsiCacheSafeFlagSet: cacheableFlags + optionsBasedFlags(options),
+                              tulsiSwiftFlagSet: swiftFlags,
+                              tulsiNonSwiftFlagSet: nonSwiftFlags,
+                              projDefaultFlagSet: projDefaultSettings,
+                              projTargetFlagSets: targetSettings)
+  }
+
+
+  private func getValue(_ options: TulsiOptionSet, _ key: TulsiOptionKey, defaultValue: String)
+      -> String {
+    return options[key].commonValue ?? defaultValue
+  }
+
+  private func getTargets(_ options: TulsiOptionSet, _ key: TulsiOptionKey) -> [String] {
+    guard let targetValues = options[key].targetValues else { return [String]() }
+    return Array(targetValues.keys)
+  }
+
+  private func getTargetValue(_ options: TulsiOptionSet,
+                              _ key: TulsiOptionKey,
+                              _ target: String,
+                              defaultValue: String) -> String {
+    return options[key, target] ?? defaultValue
+  }
+
+  private func getProjDefaultSettings(_ options: TulsiOptionSet) -> BazelFlagsSet {
+    let debugStartup = getValue(options, .BazelBuildStartupOptionsDebug, defaultValue: "")
+    let debugBuild = getValue(options, .BazelBuildOptionsDebug, defaultValue: "")
+    let releaseStartup = getValue(options, .BazelBuildStartupOptionsRelease, defaultValue: "")
+    let releaseBuild = getValue(options, .BazelBuildOptionsRelease, defaultValue: "")
+
+    let debugFlags = BazelFlags(startupStr: debugStartup, buildStr: debugBuild)
+    let releaseFlags = BazelFlags(startupStr: releaseStartup, buildStr: releaseBuild)
+
+    return BazelFlagsSet(debug: debugFlags, release: releaseFlags)
+  }
+
+  private func getTargetSettings(_ options: TulsiOptionSet,
+                                 _ label: String,
+                                 defaultValue: BazelFlagsSet) -> BazelFlagsSet? {
+    let debugStartup = getTargetValue(options, .BazelBuildStartupOptionsDebug, label, defaultValue: "")
+    let debugBuild = getTargetValue(options, .BazelBuildOptionsDebug, label, defaultValue: "")
+    let releaseStartup = getTargetValue(options, .BazelBuildStartupOptionsRelease, label, defaultValue: "")
+    let releaseBuild = getTargetValue(options, .BazelBuildOptionsRelease, label, defaultValue: "")
+
+    let debugFlags = BazelFlags(startupStr: debugStartup, buildStr: debugBuild)
+    let releaseFlags = BazelFlags(startupStr: releaseStartup, buildStr: releaseBuild)
+
+    // Return nil if we have the same settings as the defaultValue.
+    guard debugFlags != defaultValue.debug
+      && releaseFlags != defaultValue.release else {
+        return nil
+    }
+    return BazelFlagsSet(debug: debugFlags, release: releaseFlags)
+  }
+
+}
+
diff --git a/src/TulsiGenerator/BazelWorkspaceInfoExtractor.swift b/src/TulsiGenerator/BazelWorkspaceInfoExtractor.swift
index bd9ac19..d7f35d7 100644
--- a/src/TulsiGenerator/BazelWorkspaceInfoExtractor.swift
+++ b/src/TulsiGenerator/BazelWorkspaceInfoExtractor.swift
@@ -36,6 +36,9 @@
     return workspacePathInfoFetcher.getExecutionRoot()
   }
 
+  /// Bazel settings provider for all invocations.
+  let bazelSettingsProvider: BazelSettingsProviderProtocol
+
   /// Fetcher object from which a workspace's path info may be obtained.
   private let workspacePathInfoFetcher: BazelWorkspacePathInfoFetcher
 
@@ -46,15 +49,30 @@
   private var ruleEntryCache = RuleEntryMap()
 
   init(bazelURL: URL, workspaceRootURL: URL, localizedMessageLogger: LocalizedMessageLogger) {
+    let universalFlags: BazelFlags
+    if let applicationSupport = ApplicationSupport() {
+      let tulsiVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "UNKNOWN"
+      let aspectPath = try! applicationSupport.copyTulsiAspectFiles(tulsiVersion: tulsiVersion)
+      universalFlags = BazelFlags(build: ["--override_repository=tulsi=\(aspectPath)"])
+    } else {
+      let bundle = Bundle(for: type(of: self))
+      let bazelWorkspace =
+        bundle.url(forResource: "WORKSPACE", withExtension: nil)!.deletingLastPathComponent()
+      universalFlags = BazelFlags(build: ["--override_repository=tulsi=\(bazelWorkspace.path)"])
+    }
 
+    bazelSettingsProvider = BazelSettingsProvider(universalFlags: universalFlags)
     workspacePathInfoFetcher = BazelWorkspacePathInfoFetcher(bazelURL: bazelURL,
                                                              workspaceRootURL: workspaceRootURL,
+                                                             bazelUniversalFlags: universalFlags,
                                                              localizedMessageLogger: localizedMessageLogger)
     aspectExtractor = BazelAspectInfoExtractor(bazelURL: bazelURL,
                                                workspaceRootURL: workspaceRootURL,
+                                               bazelSettingsProvider: bazelSettingsProvider,
                                                localizedMessageLogger: localizedMessageLogger)
     queryExtractor = BazelQueryInfoExtractor(bazelURL: bazelURL,
                                              workspaceRootURL: workspaceRootURL,
+                                             bazelUniversalFlags: universalFlags,
                                              localizedMessageLogger: localizedMessageLogger)
   }
 
@@ -67,7 +85,8 @@
   func ruleEntriesForLabels(_ labels: [BuildLabel],
                             startupOptions: TulsiOption,
                             buildOptions: TulsiOption,
-                            projectGenBuildOptions: TulsiOption) throws -> RuleEntryMap {
+                            projectGenBuildOptions: TulsiOption,
+                            prioritizeSwiftOption: TulsiOption) throws -> RuleEntryMap {
     func isLabelMissing(_ label: BuildLabel) -> Bool {
       return !ruleEntryCache.hasAnyRuleEntry(withBuildLabel: label)
     }
@@ -84,12 +103,15 @@
     let buildOptions = splitOptionString(buildOptions.commonValue)
     let projectGenerationOptions = splitOptionString(projectGenBuildOptions.commonValue)
 
+    let prioritizeSwift = prioritizeSwiftOption.commonValueAsBool ?? false
+
     do {
       let ruleEntryMap =
         try aspectExtractor.extractRuleEntriesForLabels(labels,
                                                         startupOptions: startupOptions,
                                                         buildOptions: buildOptions,
-                                                        projectGenerationOptions: projectGenerationOptions)
+                                                        projectGenerationOptions: projectGenerationOptions,
+                                                        prioritizeSwift: prioritizeSwift)
       ruleEntryCache = RuleEntryMap(ruleEntryMap)
     } catch BazelAspectInfoExtractor.ExtractorError.buildFailed {
       throw BazelWorkspaceInfoExtractorError.aspectExtractorFailed("Bazel aspects could not be built.")
diff --git a/src/TulsiGenerator/BazelWorkspaceInfoExtractorProtocol.swift b/src/TulsiGenerator/BazelWorkspaceInfoExtractorProtocol.swift
index 0b7925a..1d26444 100644
--- a/src/TulsiGenerator/BazelWorkspaceInfoExtractorProtocol.swift
+++ b/src/TulsiGenerator/BazelWorkspaceInfoExtractorProtocol.swift
@@ -29,7 +29,8 @@
   func ruleEntriesForLabels(_ labels: [BuildLabel],
                             startupOptions: TulsiOption,
                             buildOptions: TulsiOption,
-                            projectGenBuildOptions: TulsiOption) throws -> RuleEntryMap
+                            projectGenBuildOptions: TulsiOption,
+                            prioritizeSwiftOption: TulsiOption) throws -> RuleEntryMap
 
   /// Extracts labels for the files referenced by the build infrastructure for the given set of
   /// BUILD targets.
@@ -49,4 +50,7 @@
 
   /// Absolute path to the Bazel execution root.
   var bazelExecutionRoot: String {get}
+
+  /// Bazel flag provider for all invocations.
+  var bazelSettingsProvider: BazelSettingsProviderProtocol {get}
 }
diff --git a/src/TulsiGenerator/BazelWorkspacePathInfoFetcher.swift b/src/TulsiGenerator/BazelWorkspacePathInfoFetcher.swift
index 56d1288..ae51efe 100644
--- a/src/TulsiGenerator/BazelWorkspacePathInfoFetcher.swift
+++ b/src/TulsiGenerator/BazelWorkspacePathInfoFetcher.swift
@@ -25,13 +25,18 @@
   private let bazelURL: URL
   /// The location of the Bazel workspace to be examined.
   private let workspaceRootURL: URL
+  /// Universal flags for all Bazel invocations.
+  private let bazelUniversalFlags: BazelFlags
+
   private let localizedMessageLogger: LocalizedMessageLogger
   private let semaphore: DispatchSemaphore
   private var fetchCompleted = false
 
-  init(bazelURL: URL, workspaceRootURL: URL, localizedMessageLogger: LocalizedMessageLogger) {
+  init(bazelURL: URL, workspaceRootURL: URL, bazelUniversalFlags: BazelFlags,
+       localizedMessageLogger: LocalizedMessageLogger) {
     self.bazelURL = bazelURL
     self.workspaceRootURL = workspaceRootURL
+    self.bazelUniversalFlags = bazelUniversalFlags
     self.localizedMessageLogger = localizedMessageLogger
 
     semaphore = DispatchSemaphore(value: 0)
@@ -82,9 +87,13 @@
       fetchCompleted = true
       return
     }
+    var arguments = [String]()
+    arguments.append(contentsOf: bazelUniversalFlags.startup)
+    arguments.append("info")
+    arguments.append(contentsOf: bazelUniversalFlags.build)
 
     let process = TulsiProcessRunner.createProcess(bazelURL.path,
-                                                   arguments: ["info"],
+                                                   arguments: arguments,
                                                    messageLogger: localizedMessageLogger,
                                                    loggingIdentifier: "bazel_get_package_path" ) {
       completionInfo in
diff --git a/src/TulsiGenerator/PBXTargetGenerator.swift b/src/TulsiGenerator/PBXTargetGenerator.swift
index 38ec877..b3195bd 100644
--- a/src/TulsiGenerator/PBXTargetGenerator.swift
+++ b/src/TulsiGenerator/PBXTargetGenerator.swift
@@ -61,7 +61,7 @@
   /// Returns a new PBXGroup instance appropriate for use as a top level project group.
   static func mainGroupForOutputFolder(_ outputFolderURL: URL, workspaceRootURL: URL) -> PBXGroup
 
-  init(bazelURL: URL,
+  init(bazelPath: String,
        bazelBinPath: String,
        project: PBXProject,
        buildScriptPath: String,
@@ -171,8 +171,8 @@
   /// workspace content.
   static let BazelWorkspaceSymlinkVarName = "TULSI_BWRS"
 
-  /// Location of the bazel binary.
-  let bazelURL: URL
+  /// Path to the Bazel executable.
+  let bazelPath: String
 
   /// Location of the bazel bin symlink, relative to the workspace root.
   let bazelBinPath: String
@@ -426,7 +426,7 @@
     return options[.ImprovedImportAutocompletionFix].commonValueAsBool ?? true
   }
 
-  init(bazelURL: URL,
+  init(bazelPath: String,
        bazelBinPath: String,
        project: PBXProject,
        buildScriptPath: String,
@@ -437,7 +437,7 @@
        workspaceRootURL: URL,
        suppressCompilerDefines: Bool = false,
        redactWorkspaceSymlink: Bool = false) {
-    self.bazelURL = bazelURL
+    self.bazelPath = bazelPath
     self.bazelBinPath = bazelBinPath
     self.project = project
     self.buildScriptPath = buildScriptPath
@@ -697,7 +697,6 @@
   func generateBazelCleanTarget(_ scriptPath: String, workingDirectory: String = "") {
     assert(bazelCleanScriptTarget == nil, "generateBazelCleanTarget may only be called once")
 
-    let bazelPath = bazelURL.path
     bazelCleanScriptTarget = project.createLegacyTarget(PBXTargetGenerator.BazelCleanTarget,
                                                         deploymentTarget: nil,
                                                         buildToolPath: "\(scriptPath)",
@@ -781,11 +780,14 @@
   func generateBuildTargetsForRuleEntries(_ ruleEntries: Set<RuleEntry>,
                                           ruleEntryMap: RuleEntryMap) throws {
     let namedRuleEntries = generateUniqueNamesForRuleEntries(ruleEntries)
-    var testTargetLinkages = [(PBXNativeTarget, BuildLabel?, RuleEntry)]()
+
     let progressNotifier = ProgressNotifier(name: GeneratingBuildTargets,
                                             maxValue: namedRuleEntries.count)
+
+    var testTargetLinkages = [(PBXNativeTarget, BuildLabel?, RuleEntry)]()
     var watchAppTargets = [String: (PBXNativeTarget, RuleEntry)]()
     var watchExtensionsByEntry = [RuleEntry: PBXNativeTarget]()
+
     for (name, entry) in namedRuleEntries {
       progressNotifier.incrementValue()
       let target = try createBuildTargetForRuleEntry(entry,
@@ -1394,11 +1396,6 @@
         addPreprocessorDefine("DEBUG=1", toConfig: config)
       } else if configName == "Release" {
         addPreprocessorDefine("NDEBUG=1", toConfig: config)
-
-        if !indexerSettingsOnly {
-          // Enable dSYM generation for release builds.
-          config.buildSettings["TULSI_MUST_USE_DSYM"] = "YES"
-        }
       }
     }
   }
@@ -1477,7 +1474,8 @@
   /// Creates a PBXNativeTarget for the given rule entry, returning it.
   private func createBuildTargetForRuleEntry(_ entry: RuleEntry,
                                              named name: String,
-                                             ruleEntryMap: RuleEntryMap) throws -> (PBXNativeTarget) {
+                                             ruleEntryMap: RuleEntryMap)
+      throws -> (PBXNativeTarget) {
     guard let pbxTargetType = entry.pbxTargetType else {
       throw ProjectSerializationError.unsupportedTargetType(entry.type, entry.label.value)
     }
@@ -1579,10 +1577,10 @@
     return buildPhase
   }
 
-  private func createBuildPhaseForRuleEntry(_ entry: RuleEntry) -> PBXShellScriptBuildPhase? {
+  private func createBuildPhaseForRuleEntry(_ entry: RuleEntry)
+      -> PBXShellScriptBuildPhase? {
     let buildLabel = entry.label.value
-    let commandLine = buildScriptCommandlineForBuildLabels(buildLabel,
-                                                           withOptionsForTargetLabel: entry.label)
+    let commandLine = buildScriptCommandlineForBuildLabels(buildLabel)
     let workingDirectory = PBXTargetGenerator.workingDirectoryForPBXGroup(project.mainGroup)
     let changeDirectoryAction: String
     if workingDirectory.isEmpty {
@@ -1601,87 +1599,12 @@
   }
 
   /// Constructs a commandline string that will invoke the bazel build script to generate the given
-  /// buildLabels (a space-separated set of Bazel target labels) with user options set for the given
-  /// optionsTarget.
-  private func buildScriptCommandlineForBuildLabels(_ buildLabels: String,
-                                                    withOptionsForTargetLabel target: BuildLabel) -> String {
-    var commandLine = "\"\(buildScriptPath)\" " +
+  /// buildLabels (a space-separated set of Bazel target labels).
+  private func buildScriptCommandlineForBuildLabels(_ buildLabels: String) -> String {
+    return "\"\(buildScriptPath)\" " +
         "\(buildLabels) " +
-        "--bazel \"\(bazelURL.path)\" " +
+        "--bazel \"\(bazelPath)\" " +
         "--bazel_bin_path \"\(bazelBinPath)\" " +
         "--verbose "
-
-    func addPerConfigValuesForOptions(_ optionKeys: [TulsiOptionKey],
-                                      additionalFlags: String = "",
-                                      optionFlag: String) {
-      // Get the value for each config and test to see if they are all identical and may be
-      // collapsed.
-      var configValues = [TulsiOptionKey: String?]()
-      var firstValue: String? = nil
-      var valuesDiffer = false
-      for key in optionKeys {
-        let value = options[key, target.value]
-        if configValues.isEmpty {
-          firstValue = value
-        } else if value != firstValue {
-          valuesDiffer = true
-        }
-        configValues[key] = value
-      }
-
-      if !valuesDiffer {
-        // Return early if nothing was set.
-        guard let concreteValue = firstValue else { return }
-        commandLine += "\(optionFlag) \(concreteValue) "
-        if !additionalFlags.isEmpty {
-          commandLine += "\(additionalFlags) "
-        }
-        commandLine += "-- "
-
-        return
-      }
-
-      // Emit a filtered option (--optionName[configName]) for each config.
-      // Note that we sort the keys to make sure the ordering is stable even when adding/removing
-      // other keys.
-      for (optionKey, value) in configValues.sorted(by: { $0.0.rawValue < $1.0.rawValue }) {
-        guard let concreteValue = value else { continue }
-        let rawName = optionKey.rawValue
-        var configKey: String?
-        for key in PBXTargetGenerator.buildConfigNames {
-          if rawName.hasSuffix(key) {
-            configKey = key
-            break
-          }
-        }
-        if configKey == nil {
-          assertionFailure("Failed to map option key \(optionKey) to a build config.")
-          configKey = "Debug"
-        }
-        commandLine += "\(optionFlag)[\(configKey!)] \(concreteValue) "
-        commandLine += additionalFlags.isEmpty ? "-- " : "\(additionalFlags) -- "
-      }
-    }
-
-    let additionalFlags: String
-    if let shouldContinueBuildingAfterError = options[.BazelContinueBuildingAfterError].commonValueAsBool,
-        shouldContinueBuildingAfterError {
-      additionalFlags = "--keep_going"
-    } else {
-      additionalFlags = ""
-    }
-
-    addPerConfigValuesForOptions([.BazelBuildOptionsDebug,
-                                  .BazelBuildOptionsRelease,
-                                 ],
-                                 additionalFlags: additionalFlags,
-                                 optionFlag: "--bazel_options")
-
-    addPerConfigValuesForOptions([.BazelBuildStartupOptionsDebug,
-                                  .BazelBuildStartupOptionsRelease
-                                 ],
-                                 optionFlag: "--bazel_startup_options")
-
-    return commandLine
   }
 }
diff --git a/src/TulsiGenerator/Scripts/bazel_build.py b/src/TulsiGenerator/Scripts/bazel_build.py
index 1b77015..621207d 100755
--- a/src/TulsiGenerator/Scripts/bazel_build.py
+++ b/src/TulsiGenerator/Scripts/bazel_build.py
@@ -16,7 +16,6 @@
 """Bridge between Xcode and Bazel for the "build" action."""
 
 import atexit
-import collections
 import errno
 import fcntl
 import io
@@ -35,10 +34,10 @@
 from apfs_clone_copy import CopyOnWrite
 import bazel_build_events
 from bazel_build_flags import bazel_build_flags
+import bazel_build_settings
 import bazel_options
 from bootstrap_lldbinit import BootstrapLLDBInit
 from bootstrap_lldbinit import TULSI_LLDBINIT_FILE
-from execroot_path import BAZEL_EXECUTION_ROOT
 import tulsi_logging
 from update_symbol_cache import UpdateSymbolCache
 
@@ -191,37 +190,16 @@
 class _OptionsParser(object):
   """Handles parsing script options."""
 
-  # Key for options that should be applied to all build configurations.
-  ALL_CONFIGS = '__all__'
+  # List of all supported Xcode configurations.
+  KNOWN_CONFIGS = ['Debug', 'Release']
 
-  # The build configurations handled by this parser.
-  KNOWN_CONFIGS = ['Debug', 'Release', 'Fastbuild']
-
-  def __init__(self, sdk_version, platform_name, arch):
+  def __init__(self, build_settings, sdk_version, platform_name, arch):
     self.targets = []
-    self.startup_options = collections.defaultdict(list)
-    self.build_options = collections.defaultdict(
-        list,
-        {
-            _OptionsParser.ALL_CONFIGS: [
-                '--verbose_failures',
-                '--announce_rc',
-                '--bes_outerr_buffer_size=0',  # Don't buffer Bazel output.
-            ],
-
-            'Debug': [
-                '--compilation_mode=dbg',
-            ],
-
-            'Release': [
-                '--compilation_mode=opt',
-                '--strip=always',
-            ],
-
-            'Fastbuild': [
-                '--compilation_mode=fastbuild',
-            ],
-        })
+    self.build_settings = build_settings
+    self.common_build_options = [
+        '--verbose_failures',
+        '--bes_outerr_buffer_size=0',  # Don't buffer Bazel output.
+    ]
 
     self.sdk_version = sdk_version
     self.platform_name = platform_name
@@ -237,8 +215,7 @@
     else:
       self._WarnUnknownPlatform()
       config_platform = 'ios'
-    self.build_options[_OptionsParser.ALL_CONFIGS].extend(
-        bazel_build_flags(config_platform, arch))
+    self.common_build_options.extend(bazel_build_flags(config_platform, arch))
 
     self.verbose = 0
     self.bazel_bin_path = 'bazel-bin'
@@ -255,29 +232,10 @@
             Increments the verbosity of the script by one level. This argument
             may be provided multiple times to enable additional output levels.
 
-        --bazel_startup_options <option1> [<option2> ...] --
-            Provides one or more Bazel startup options.
-
-        --bazel_options <option1> [<option2> ...] --
-            Provides one or more Bazel build options.
-
         --bazel_bin_path <path>
             Path at which Bazel-generated artifacts may be retrieved.
       """ % sys.argv[0])
 
-    usage += '\n' + textwrap.fill(
-        'Note that the --bazel_startup_options and --bazel_options options may '
-        'include an optional configuration specifier in brackets to limit '
-        'their contents to a given build configuration. Options provided with '
-        'no configuration filter will apply to all configurations in addition '
-        'to any configuration-specific options.', 120)
-
-    usage += '\n' + textwrap.fill(
-        'E.g., --bazel_options common --  --bazel_options[Release] release -- '
-        'would result in "bazel build common release" in the "Release" '
-        'configuration and "bazel build common" in all other configurations.',
-        120)
-
     return usage
 
   def ParseOptions(self, args):
@@ -292,35 +250,24 @@
 
     return self._ParseVariableOptions(args[bazel_executable_index + 2:])
 
-  def GetStartupOptions(self, config):
-    """Returns the full set of startup options for the given config."""
-    return self._GetOptions(self.startup_options, config)
+  def GetBaseFlagsForTargets(self, config):
+    is_debug = config == 'Debug'
+    return self.build_settings.flags_for_target(
+        self.targets[0],
+        is_debug)
 
-  def GetBuildOptions(self, config):
+  def GetBazelOptions(self, config):
     """Returns the full set of build options for the given config."""
-    options = self._GetOptions(self.build_options, config)
+    bazel, start_up, build = self.GetBaseFlagsForTargets(config)
+    all_build = []
+    all_build.extend(self.common_build_options)
+    all_build.extend(build)
 
     version_string = self._GetXcodeVersionString()
     if version_string and self._NeedsXcodeVersionFlag(version_string):
-      self._AddDefaultOption(options, '--xcode_version', version_string)
-    return options
+      all_build.append('--xcode_version=%s' % version_string)
 
-  @staticmethod
-  def _AddDefaultOption(option_list, option, default_value):
-    matching_options = [opt for opt in option_list if opt.startswith(option)]
-    if matching_options:
-      return option_list
-
-    option_list.append('%s=%s' % (option, default_value))
-    return option_list
-
-  @staticmethod
-  def _GetOptions(option_set, config):
-    """Returns a flattened list from options_set for the given config."""
-    options = list(option_set[_OptionsParser.ALL_CONFIGS])
-    if config != _OptionsParser.ALL_CONFIGS:
-      options.extend(option_set[config])
-    return options
+    return bazel, start_up, all_build
 
   def _WarnUnknownPlatform(self):
     sys.stdout.write('Warning: unknown platform "%s" will be treated as '
@@ -336,37 +283,7 @@
       arg = args[0]
       args = args[1:]
 
-      if arg.startswith('--bazel_startup_options'):
-        config = self._ParseConfigFilter(arg)
-        args, items, terminated = self._ParseDoubleDashDelimitedItems(args)
-        if not terminated:
-          return ('Missing "--" terminator while parsing %s' % arg, 2)
-        duplicates = self._FindDuplicateOptions(self.startup_options,
-                                                config,
-                                                items)
-        if duplicates:
-          return (
-              '%s items conflict with common options: %s' % (
-                  arg, ','.join(duplicates)),
-              2)
-        self.startup_options[config].extend(items)
-
-      elif arg.startswith('--bazel_options'):
-        config = self._ParseConfigFilter(arg)
-        args, items, terminated = self._ParseDoubleDashDelimitedItems(args)
-        if not terminated:
-          return ('Missing "--" terminator while parsing %s' % arg, 2)
-        duplicates = self._FindDuplicateOptions(self.build_options,
-                                                config,
-                                                items)
-        if duplicates:
-          return (
-              '%s items conflict with common options: %s' % (
-                  arg, ','.join(duplicates)),
-              2)
-        self.build_options[config].extend(items)
-
-      elif arg == '--bazel_bin_path':
+      if arg == '--bazel_bin_path':
         if not args:
           return ('Missing required parameter for %s' % arg, 2)
         self.bazel_bin_path = args[0]
@@ -385,65 +302,6 @@
     return (None, 0)
 
   @staticmethod
-  def _ParseConfigFilter(arg):
-    match = re.search(r'\[([^\]]+)\]', arg)
-    if not match:
-      return _OptionsParser.ALL_CONFIGS
-    return match.group(1)
-
-  @staticmethod
-  def _ConsumeArgumentForParam(param, args):
-    if not args:
-      return (None, 'Missing required parameter for "%s" option' % param)
-    val = args[0]
-    return (args[1:], val)
-
-  @staticmethod
-  def _ParseDoubleDashDelimitedItems(args):
-    """Consumes options until -- is found."""
-    options = []
-    terminator_found = False
-
-    opts = args
-    while opts:
-      opt = opts[0]
-      opts = opts[1:]
-      if opt == '--':
-        terminator_found = True
-        break
-      options.append(opt)
-
-    return opts, options, terminator_found
-
-  @staticmethod
-  def _FindDuplicateOptions(options_dict, config, new_options):
-    """Returns a list of options appearing in both given option lists."""
-
-    allowed_duplicates = [
-        '--copt',
-        '--config',
-        '--define',
-        '--objccopt',
-    ]
-
-    def ExtractOptionNames(opts):
-      names = set()
-      for opt in opts:
-        split_opt = opt.split('=', 1)
-        if split_opt[0] not in allowed_duplicates:
-          names.add(split_opt[0])
-      return names
-
-    current_set = ExtractOptionNames(options_dict[config])
-    new_set = ExtractOptionNames(new_options)
-    conflicts = current_set.intersection(new_set)
-
-    if config != _OptionsParser.ALL_CONFIGS:
-      current_set = ExtractOptionNames(options_dict[_OptionsParser.ALL_CONFIGS])
-      conflicts = conflicts.union(current_set.intersection(new_set))
-    return conflicts
-
-  @staticmethod
   def _GetXcodeVersionString():
     """Returns Xcode version info from the environment as a string."""
     reported_version = os.environ['XCODE_VERSION_ACTUAL']
@@ -529,14 +387,6 @@
     else:
       self.normalized_prefix_map = True
 
-    if self.swift_dependency:
-      # Always generate dSYMs for projects with Swift dependencies, as dSYMs are
-      # still required to expr or print variables within Bazel-built Swift
-      # modules in LLDB.
-      self.generate_dsym = True
-    else:
-      self.generate_dsym = os.environ.get('TULSI_MUST_USE_DSYM', 'NO') == 'YES'
-
     self.update_symbol_cache = UpdateSymbolCache()
 
     # Target architecture.  Must be defined for correct setting of
@@ -622,7 +472,14 @@
       sys.stderr.write('Xcode action is %s, ignoring.' % self.xcode_action)
       return 0
 
-    parser = _OptionsParser(self.sdk_version,
+    build_settings = bazel_build_settings.BUILD_SETTINGS
+    if build_settings is None:
+      _PrintXcodeError('Unable to resolve build settings. '
+                       'Please report a Tulsi bug.')
+      return 1
+
+    parser = _OptionsParser(build_settings,
+                            self.sdk_version,
                             self.platform_name,
                             self.arch)
     timer = Timer('Parsing options', 'parsing_options').Start()
@@ -635,6 +492,7 @@
     self.verbose = parser.verbose
     self.bazel_bin_path = os.path.abspath(parser.bazel_bin_path)
     self.bazel_executable = parser.bazel_executable
+    self.bazel_exec_root = build_settings.bazelExecRoot
 
     # Until wrapped_clang is updated, use -fdebug-prefix-map to have debug
     # symbols match Xcode-visible sources.
@@ -650,7 +508,7 @@
       source_map = self._ExtractTargetSourceMap(False)
       if source_map:
         prefix_map = '--copt=-fdebug-prefix-map=%s=%s' % source_map
-        parser.build_options[_OptionsParser.ALL_CONFIGS].append(prefix_map)
+        parser.common_build_options.append(prefix_map)
 
     self.build_path = os.path.join(self.bazel_bin_path,
                                    os.environ.get('TULSI_BUILD_PATH', ''))
@@ -677,10 +535,10 @@
     post_bazel_timer = Timer('Total Tulsi Post-Bazel time', 'total_post_bazel')
     post_bazel_timer.Start()
 
-    if not os.path.exists(BAZEL_EXECUTION_ROOT):
+    if not os.path.exists(self.bazel_exec_root):
       _PrintXcodeError('No Bazel execution root was found at %s. Debugging '
                        'experience will be compromised. Please report a Tulsi '
-                       'bug.' % BAZEL_EXECUTION_ROOT)
+                       'bug.' % self.bazel_exec_root)
       return 404
 
     # This needs to run after `bazel build`, since it depends on the Bazel
@@ -760,8 +618,6 @@
 
   def _BuildBazelCommand(self, options):
     """Builds up a commandline string suitable for running Bazel."""
-    bazel_command = [options.bazel_executable]
-
     configuration = os.environ['CONFIGURATION']
     # Treat the special testrunner build config as a Debug compile.
     test_runner_config_prefix = '__TulsiTestRunner_'
@@ -777,14 +633,11 @@
       _PrintXcodeError('Unknown build configuration "%s"' % configuration)
       return (None, 1)
 
-    bazel_command.extend(options.GetStartupOptions(configuration))
+    bazel, start_up, build = options.GetBazelOptions(configuration)
+    bazel_command = [bazel]
+    bazel_command.extend(start_up)
     bazel_command.append('build')
-    bazel_command.extend(options.GetBuildOptions(configuration))
-
-    # Do not follow symlinks on __file__ in case this script is linked during
-    # development.
-    tulsi_package_dir = os.path.abspath(
-        os.path.join(os.path.dirname(__file__), '..', 'Bazel'))
+    bazel_command.extend(build)
 
     bazel_command.extend([
         # The following flags are used by Tulsi to identify itself and read
@@ -800,15 +653,6 @@
     else:
       bazel_command.append('--output_groups=tulsi-outputs,default')
 
-    bazel_command.extend([
-        # The following flags WILL affect Bazel analysis caching.
-        # Keep this consistent with BazelAspectInfoExtractor.swift.
-        '--nocheck_visibility',  # Don't do package visibility enforcement.
-        '--override_repository=tulsi=%s' % tulsi_package_dir])
-
-    if self.generate_dsym:
-      bazel_command.append('--apple_generate_dsym')
-
     # A normalized path for -fdebug-prefix-map exists for keeping all debug
     # information as built by Clang consistent for the sake of caching within
     # a distributed build system.
@@ -822,10 +666,6 @@
     if self.normalized_prefix_map:
       bazel_command.append('--features=debug_prefix_map_pwd_is_dot')
 
-    bazel_command.extend([
-        '--define=apple.add_debugger_entitlement=1',
-        '--define=apple.propagate_embedded_extra_outputs=1'])
-
     bazel_command.extend(options.targets)
 
     extra_options = bazel_options.BazelOptions(os.environ)
@@ -1169,7 +1009,7 @@
     path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                         'install_genfiles.py')
 
-    args = [path, BAZEL_EXECUTION_ROOT]
+    args = [path, self.bazel_exec_root]
     args.extend(outputs)
 
     self._PrintVerbose('Spawning subprocess install_genfiles.py to copy '
@@ -1749,44 +1589,6 @@
 
     return 0
 
-  def _ExtractBazelInfoExecrootPaths(self):
-    """Extracts the path to the execution root found in this WORKSPACE.
-
-    Returns:
-      None: if an error occurred.
-      str: a string representing the absolute path to the execution root found
-           for the current Bazel WORKSPACE.
-    """
-    if not self.bazel_executable:
-      _PrintXcodeWarning('Attempted to find the execution root, but the '
-                         'path to the Bazel executable was not provided.')
-      return None
-
-    timer = Timer('Finding Bazel execution root', 'bazel_execroot').Start()
-    returncode, output = self._RunSubprocess([
-        self.bazel_executable,
-        'info',
-        'execution_root',
-        '--noshow_loading_progress',
-        '--noshow_progress',
-    ])
-    timer.End()
-
-    if returncode:
-      _PrintXcodeWarning('%s returned %d while finding the execution root'
-                         % (self.bazel_executable, returncode))
-      return None
-
-    for line in output.splitlines():
-      # Filter out output that does not contain the /execroot path.
-      if '/execroot' not in line:
-        continue
-      # Return the path from the first /execroot found.
-      return line
-    _PrintXcodeWarning('%s did not return a recognized /execroot path.'
-                       % self.bazel_executable)
-    return None
-
   def _NormalizePath(self, path):
     """Returns paths with a common form, normalized with a trailing slash.
 
@@ -1820,30 +1622,10 @@
 
     # Add a redirection for the Bazel execution root, the path where sources
     # are referenced by Bazel.
-    sm_execroot = self._ExtractExecroot()
-    if sm_execroot:
-      if normalize:
-        sm_execroot = self._NormalizePath(sm_execroot)
-      return (sm_execroot, sm_destpath)
-
-    return None
-
-  def _ExtractExecroot(self):
-    """Finds the execution root from BAZEL_EXECUTION_ROOT or bazel info.
-
-    Returns:
-      None: if an error occurred.
-      str: the "execution root", the path to the "root" of all source files
-           compiled by Bazel as a string.
-    """
-    # If we have a cached execution root, check that it exists.
-    if os.path.exists(BAZEL_EXECUTION_ROOT):
-      # If so, use it.
-      execroot = BAZEL_EXECUTION_ROOT
-    else:
-      # Query Bazel directly for the execution root.
-      execroot = self._ExtractBazelInfoExecrootPaths()
-    return execroot
+    sm_execroot = self.bazel_exec_root
+    if normalize:
+      sm_execroot = self._NormalizePath(sm_execroot)
+    return (sm_execroot, sm_destpath)
 
   def _LinkTulsiWorkspace(self):
     """Links the Bazel Workspace to the Tulsi Workspace (`tulsi-workspace`)."""
@@ -1851,7 +1633,7 @@
     if os.path.islink(tulsi_workspace):
       os.unlink(tulsi_workspace)
 
-    os.symlink(BAZEL_EXECUTION_ROOT, tulsi_workspace)
+    os.symlink(self.bazel_exec_root, tulsi_workspace)
     if not os.path.exists(tulsi_workspace):
       _PrintXcodeError(
           'Linking Tulsi Workspace to %s failed.' % tulsi_workspace)
diff --git a/src/TulsiGenerator/Scripts/bazel_build_settings.py.template b/src/TulsiGenerator/Scripts/bazel_build_settings.py.template
new file mode 100644
index 0000000..1465f51
--- /dev/null
+++ b/src/TulsiGenerator/Scripts/bazel_build_settings.py.template
@@ -0,0 +1,110 @@
+# 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.
+#
+#
+# Generated by Tulsi to resolve flags during builds.
+
+
+def _StandardizeTargetLabel(label):
+  """Convert labels of form //dir/target to //dir/target:target."""
+  if label is None:
+    return label
+  tokens = label.rsplit('/', 1)
+  if len(tokens) <= 1:
+    return label
+
+  target_base = tokens[0]
+  target = tokens[1]
+
+  if '...' in target or ':' in target:
+    return label
+  return label + ':' + target
+
+
+class BazelFlags(object):
+  """Represents Bazel flags."""
+
+  def __init__(self, startup = [], build = []):
+    self.startup = startup
+    self.build = build
+
+
+class BazelFlagsSet(object):
+  """Represents a set of Bazel flags which can vary by compilation mode."""
+
+  def __init__(self, debug = None, release = None, flags = None):
+    if debug is None:
+      debug = flags or BazelFlags()
+    if release is None:
+      release = flags or BazelFlags()
+
+    self.debug = debug
+    self.release = release
+
+  def flags(self, is_debug):
+    """Returns the proper flags (either debug or release)."""
+    return self.debug if is_debug else self.release
+
+
+class BazelBuildSettings(object):
+  """Represents a Tulsi project's Bazel settings."""
+
+  def __init__(self, bazel, bazelExecRoot, swiftTargets,
+               cacheAffecting, cacheSafe,
+               swiftOnly, nonSwiftOnly,
+               projDefault, projTargetMap):
+    self.bazel = bazel
+    self.bazelExecRoot = bazelExecRoot
+    self.swiftTargets = swiftTargets
+    self.cacheAffecting = cacheAffecting
+    self.cacheSafe = cacheSafe
+    self.swiftOnly = swiftOnly
+    self.nonSwiftOnly = nonSwiftOnly
+    self.projDefault = projDefault
+    self.projTargetMap = projTargetMap
+
+  def flags_for_target(self, target, is_debug):
+    """Returns (bazel, startup flags, build flags) for the given target."""
+
+    target = _StandardizeTargetLabel(target)
+    target_flag_set = self.projTargetMap.get(target)
+    if not target_flag_set:
+      target_flag_set = self.projDefault
+
+    lang = self.swiftOnly if target in self.swiftTargets else self.nonSwiftOnly
+
+    cache_affecting = self.cacheAffecting.flags(is_debug)
+    cache_safe = self.cacheSafe.flags(is_debug)
+    target = target_flag_set.flags(is_debug)
+    lang = lang.flags(is_debug)
+
+    startupFlags = []
+    startupFlags.extend(target.startup)
+    startupFlags.extend(cache_safe.startup)
+    startupFlags.extend(cache_affecting.startup)
+    startupFlags.extend(lang.startup)
+
+    buildFlags = []
+    buildFlags.extend(target.build)
+    buildFlags.extend(cache_safe.build)
+    buildFlags.extend(cache_affecting.build)
+    buildFlags.extend(lang.build)
+
+    return (self.bazel, startupFlags, buildFlags)
+
+# Default value in case the template does not behave as expected.
+BUILD_SETTINGS = None
+
+# <template>
+
diff --git a/src/TulsiGenerator/Scripts/user_build.py b/src/TulsiGenerator/Scripts/user_build.py
new file mode 100755
index 0000000..cb0a817
--- /dev/null
+++ b/src/TulsiGenerator/Scripts/user_build.py
@@ -0,0 +1,92 @@
+#!/usr/bin/python
+# 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.
+
+"""Invokes Bazel builds for the given target using Tulsi specific flags."""
+
+
+import argparse
+import subprocess
+import sys
+from bazel_build_settings import BUILD_SETTINGS
+
+
+def _FatalError(msg, exit_code=1):
+  """Prints a fatal error message to stderr and exits."""
+  sys.stderr.write(msg)
+  sys.exit(exit_code)
+
+
+def _BuildSettingsTargetForTargets(targets):
+  """Returns the singular target to use when fetching build settings."""
+  return targets[0] if len(targets) == 1 else None
+
+
+def _CreateCommand(targets, build_settings, test, release,
+                   config, xcode_version):
+  """Creates a Bazel command for targets with the specified settings."""
+  target = _BuildSettingsTargetForTargets(targets)
+  bazel, startup, flags = build_settings.flags_for_target(target, not release)
+  bazel_action = 'test' if test else 'build'
+
+  command = [bazel]
+  command.extend(startup)
+  command.append(bazel_action)
+  command.extend(flags)
+  if config:
+    command.append('--config=%s' % config)
+  if xcode_version:
+    command.append('--xcode_version=%s' % xcode_version)
+  command.extend(targets)
+
+  return command
+
+
+def _InterruptSafeCall(cmd):
+  p = subprocess.Popen(cmd)
+  try:
+    return p.wait()
+  except KeyboardInterrupt:
+    return p.wait()
+
+
+def main():
+  if not BUILD_SETTINGS:
+    _FatalError('Unable to fetch build settings. Please report a Tulsi bug.')
+
+  parser = argparse.ArgumentParser(description='Invoke a Bazel build or test '
+                                               'with the same flags as Tulsi.')
+  parser.add_argument('--test', dest='test', action='store_true', default=False)
+  parser.add_argument('--release', dest='release', action='store_true',
+                      default=False)
+  parser.add_argument('--noprint_cmd', dest='print_cmd', action='store_false',
+                      default=True)
+  parser.add_argument('--norun', dest='run', action='store_false', default=True)
+  parser.add_argument('--config', help='Bazel --config flag.')
+  parser.add_argument('--xcode_version', help='Bazel --xcode_version flag.')
+  parser.add_argument('targets', nargs='+')
+
+  args = parser.parse_args()
+  command = _CreateCommand(args.targets, BUILD_SETTINGS, args.test,
+                           args.release, args.config, args.xcode_version)
+  if args.print_cmd:
+    print ' '.join(command)
+
+  if args.run:
+    return _InterruptSafeCall(command)
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/src/TulsiGenerator/StringExtensions.swift b/src/TulsiGenerator/StringExtensions.swift
index 375bfab..751c1c7 100644
--- a/src/TulsiGenerator/StringExtensions.swift
+++ b/src/TulsiGenerator/StringExtensions.swift
@@ -15,9 +15,13 @@
 import Foundation
 
 extension String {
-  // Escape the string for the shell by enclosing it in single quotes and replace single quotes
-  // with '\\'' which outputs '\''. See https://stackoverflow.com/a/1315213 for more information.
+  // Escape the string for the shell by enclosing it in single quotes if needed. When enclosing in
+  // single quotes, we must replace single quotes with '\\'' which outputs '\''.
+  // See https://stackoverflow.com/a/1315213 for more information.
   public var escapingForShell: String {
+    guard rangeOfCharacter(from: .whitespaces) != nil || contains("'") || contains("$") else {
+      return self
+    }
     let escapedString = replacingOccurrences(of: "'", with: "'\\''")
     return "'\(escapedString)'"
   }
diff --git a/src/TulsiGenerator/TulsiApplicationSupport.swift b/src/TulsiGenerator/TulsiApplicationSupport.swift
new file mode 100644
index 0000000..0bf9b82
--- /dev/null
+++ b/src/TulsiGenerator/TulsiApplicationSupport.swift
@@ -0,0 +1,96 @@
+// 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 Foundation
+
+class ApplicationSupport {
+
+  private let fileManager: FileManager
+  let tulsiFolder: URL
+
+  init?(fileManager: FileManager = .default) {
+    /// Fetching the appName this way will result in failure for our tests, which is intentional as
+    /// we don't want to install files to ~/Library/Application Support when testing.
+    guard let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String else { return nil }
+    guard let folder = fileManager.urls(for: .applicationSupportDirectory,
+                                        in: .userDomainMask).first else {
+      return nil
+    }
+
+    self.fileManager = fileManager
+    self.tulsiFolder = folder.appendingPathComponent(appName, isDirectory: true)
+  }
+
+  /// Copies Tulsi aspect files over to ~/Library/Application\ Support/Tulsi/<version>/Bazel and
+  /// returns the folder.
+  func copyTulsiAspectFiles(tulsiVersion: String) throws -> String {
+    let bundle = Bundle(for: type(of: self))
+    let bazelWorkspaceFile = bundle.url(forResource: "WORKSPACE", withExtension: nil)!
+    let tulsiFiles = bundle.urls(forResourcesWithExtension: nil, subdirectory: "tulsi")!
+
+    let bazelSubpath = (tulsiVersion as NSString).appendingPathComponent("Bazel")
+    let bazelPath = try installFiles([bazelWorkspaceFile], toSubpath: bazelSubpath)
+
+    let tulsiAspectsSubpath = (bazelSubpath as NSString).appendingPathComponent("tulsi")
+    try installFiles(tulsiFiles, toSubpath: tulsiAspectsSubpath)
+
+    return bazelPath.path
+  }
+
+  @discardableResult
+  private func installFiles(_ files: [URL],
+                            toSubpath subpath: String) throws -> URL {
+    let folder = tulsiFolder.appendingPathComponent(subpath, isDirectory: true)
+
+    try createDirectory(atURL: folder)
+
+    for sourceURL in files {
+      let filename = sourceURL.lastPathComponent
+
+      guard let targetURL = URL(string: filename, relativeTo: folder) else {
+        throw TulsiXcodeProjectGenerator.GeneratorError.serializationFailed(
+            "Unable to resolve URL for \(filename) in \(folder.path).")
+      }
+      do {
+        try copyFileIfNeeded(fromURL: sourceURL, toURL: targetURL)
+      }
+    }
+    return folder
+  }
+
+  private func copyFileIfNeeded(fromURL: URL, toURL: URL) throws {
+    do {
+      // Only over-write if needed.
+      if fileManager.fileExists(atPath: toURL.path) {
+        guard !fileManager.contentsEqual(atPath: fromURL.path, andPath: toURL.path) else {
+          return;
+        }
+        print("Overwriting \(toURL.path) as its contents changed.")
+        try fileManager.removeItem(at: toURL)
+      }
+      try fileManager.copyItem(at: fromURL, to: toURL)
+    }
+  }
+
+  private func createDirectory(atURL url: URL) throws {
+    var isDirectory: ObjCBool = false
+    let fileExists = fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory)
+
+    guard !fileExists || !isDirectory.boolValue else { return }
+
+    try fileManager.createDirectory(at: url,
+                                    withIntermediateDirectories: true,
+                                    attributes: nil)
+  }
+}
diff --git a/src/TulsiGenerator/TulsiOptionSet.swift b/src/TulsiGenerator/TulsiOptionSet.swift
index 665c0c4..9be92b9 100644
--- a/src/TulsiGenerator/TulsiOptionSet.swift
+++ b/src/TulsiGenerator/TulsiOptionSet.swift
@@ -51,7 +51,10 @@
       ImprovedImportAutocompletionFix,
 
       // Generate .runfiles directory, as referenced by TEST_SRCDIR in bazel tests.
-      GenerateRunfiles
+      GenerateRunfiles,
+
+      // Used by Tulsi to improve Bazel-caching of build flags.
+      ProjectPrioritizesSwift
 
   // Options for build invocations.
   case BazelBuildOptionsDebug,
@@ -295,6 +298,7 @@
     addBoolOption(.IncludeBuildSources, .Generic, false)
     addBoolOption(.ImprovedImportAutocompletionFix, .Generic, true)
     addBoolOption(.GenerateRunfiles, .Generic, false)
+    addBoolOption(.ProjectPrioritizesSwift, .Generic, false)
 
     addStringOption(.CommandlineArguments, [.TargetSpecializable, .SupportsInheritKeyword])
     addStringOption(.EnvironmentVariables, [.TargetSpecializable, .SupportsInheritKeyword])
diff --git a/src/TulsiGenerator/TulsiProjectInfoExtractor.swift b/src/TulsiGenerator/TulsiProjectInfoExtractor.swift
index 5ed4823..198f261 100644
--- a/src/TulsiGenerator/TulsiProjectInfoExtractor.swift
+++ b/src/TulsiGenerator/TulsiProjectInfoExtractor.swift
@@ -47,22 +47,26 @@
   public func ruleEntriesForInfos(_ infos: [RuleInfo],
                                   startupOptions: TulsiOption,
                                   buildOptions: TulsiOption,
-                                  projectGenBuildOptions: TulsiOption) throws -> RuleEntryMap {
+                                  projectGenBuildOptions: TulsiOption,
+                                  prioritizeSwiftOption: TulsiOption) throws -> RuleEntryMap {
     return try ruleEntriesForLabels(infos.map({ $0.label }),
                                     startupOptions: startupOptions,
                                     buildOptions: buildOptions,
-                                    projectGenBuildOptions: projectGenBuildOptions)
+                                    projectGenBuildOptions: projectGenBuildOptions,
+                                    prioritizeSwiftOption: prioritizeSwiftOption)
   }
 
   public func ruleEntriesForLabels(_ labels: [BuildLabel],
                                    startupOptions: TulsiOption,
                                    buildOptions: TulsiOption,
-                                   projectGenBuildOptions: TulsiOption) throws -> RuleEntryMap {
+                                   projectGenBuildOptions: TulsiOption,
+                                   prioritizeSwiftOption: TulsiOption) throws -> RuleEntryMap {
     do {
       return try workspaceInfoExtractor.ruleEntriesForLabels(labels,
                                                              startupOptions: startupOptions,
                                                              buildOptions: buildOptions,
-                                                             projectGenBuildOptions: projectGenBuildOptions)
+                                                             projectGenBuildOptions: projectGenBuildOptions,
+                                                             prioritizeSwiftOption: prioritizeSwiftOption)
     } catch BazelWorkspaceInfoExtractorError.aspectExtractorFailed(let info) {
       throw ExtractorError.ruleEntriesFailed(info)
     }
diff --git a/src/TulsiGenerator/TulsiXcodeProjectGenerator.swift b/src/TulsiGenerator/TulsiXcodeProjectGenerator.swift
index 7e254da..7616035 100644
--- a/src/TulsiGenerator/TulsiXcodeProjectGenerator.swift
+++ b/src/TulsiGenerator/TulsiXcodeProjectGenerator.swift
@@ -57,7 +57,8 @@
                             bundle.url(forResource: "bootstrap_lldbinit", withExtension: "py")!,
                             bundle.url(forResource: "symbol_cache_schema", withExtension: "py")!,
                             bundle.url(forResource: "update_symbol_cache", withExtension: "py")!,
-                            bundle.url(forResource: "install_genfiles", withExtension: "py")!],
+                            bundle.url(forResource: "install_genfiles", withExtension: "py")!,
+                            bundle.url(forResource: "user_build", withExtension: "py")!],
         iOSUIRunnerEntitlements: bundle.url(forResource: "iOSXCTRunner", withExtension: "entitlements")!,
         macOSUIRunnerEntitlements: bundle.url(forResource: "macOSXCTRunner", withExtension: "entitlements")!,
         stubInfoPlist: bundle.url(forResource: "StubInfoPlist", withExtension: "plist")!,
diff --git a/src/TulsiGenerator/XcodeProjectGenerator.swift b/src/TulsiGenerator/XcodeProjectGenerator.swift
index 9327796..a13fa8a 100644
--- a/src/TulsiGenerator/XcodeProjectGenerator.swift
+++ b/src/TulsiGenerator/XcodeProjectGenerator.swift
@@ -67,6 +67,7 @@
   static let ConfigDirectorySubpath = "\(TulsiArtifactDirectory)/Configs"
   static let ProjectResourcesDirectorySubpath = "\(TulsiArtifactDirectory)/Resources"
   private static let BuildScript = "bazel_build.py"
+  private static let SettingsScript = "bazel_build_settings.py"
   private static let CleanScript = "bazel_clean.sh"
   private static let ShellCommandsUtil = "bazel_cache_reader"
   private static let ShellCommandsCleanScript = "clean_symbol_cache"
@@ -114,6 +115,9 @@
   /// Exposed for testing. Do not modify user defaults.
   var suppressModifyingUserDefaults = false
 
+  /// Exposed for testing. Do not generate the build settings python file.
+  var suppressGeneratingBuildSettings = false
+
   var cachedDefaultSwiftVersion: String?
 
   init(workspaceRootURL: URL,
@@ -218,8 +222,7 @@
     try installXcodeSchemesForProjectInfo(projectInfo,
                                           projectURL: projectURL,
                                           projectBundleName: projectBundleName)
-    installTulsiScripts(projectURL)
-    installTulsiBazelPackage(projectURL)
+    installTulsiScriptsForProjectInfo(projectInfo, projectURL: projectURL)
     installGeneratorConfig(projectURL)
     installGeneratedProjectResources(projectURL)
     installStubExtensionPlistFiles(projectURL,
@@ -357,8 +360,7 @@
     let buildScriptPath = "${PROJECT_FILE_PATH}/\(XcodeProjectGenerator.ScriptDirectorySubpath)/\(XcodeProjectGenerator.BuildScript)"
     let cleanScriptPath = "${PROJECT_FILE_PATH}/\(XcodeProjectGenerator.ScriptDirectorySubpath)/\(XcodeProjectGenerator.CleanScript)"
 
-
-    let generator = pbxTargetGeneratorType.init(bazelURL: config.bazelURL,
+    let generator = pbxTargetGeneratorType.init(bazelPath: config.bazelURL.path,
                                                 bazelBinPath: workspaceInfoExtractor.bazelBinPath,
                                                 project: xcodeProject,
                                                 buildScriptPath: buildScriptPath,
@@ -505,7 +507,6 @@
       buildSettings["TULSI_PROJECT"] = config.projectName
       generator.generateTopLevelBuildConfigurations(buildSettings)
     }
-
     try profileAction("generating_build_targets") {
       try generator.generateBuildTargetsForRuleEntries(targetRules,
                                                        ruleEntryMap: ruleEntryMap)
@@ -580,7 +581,8 @@
       return try workspaceInfoExtractor.ruleEntriesForLabels(config.buildTargetLabels,
                                                              startupOptions: config.options[.BazelBuildStartupOptionsDebug],
                                                              buildOptions: config.options[.BazelBuildOptionsDebug],
-                                                             projectGenBuildOptions: config.options[.BazelBuildOptionsProjectGenerationOnly])
+                                                             projectGenBuildOptions: config.options[.BazelBuildOptionsProjectGenerationOnly],
+                                                             prioritizeSwiftOption: config.options[.ProjectPrioritizesSwift])
     } catch BazelWorkspaceInfoExtractorError.aspectExtractorFailed(let info) {
       throw ProjectGeneratorError.labelAspectFailure(info)
     }
@@ -941,31 +943,61 @@
     guard savePlist(schemeManagementDict, url: schemeManagementURL) else { return }
   }
 
-  /// Create a file that contains the execution root for the workspace of the generated project.
-  private func installCachedExecutionRoot(_ scriptDirectoryURL: URL) {
-    let executionRootFileURL = scriptDirectoryURL.appendingPathComponent(XcodeProjectGenerator.CachedExecutionRootFilename)
+  /// Create a file that contains Bazel build settings for the generated project.
+  private func generateBuildSettingsFile(_ buildRuleEntries: Set<RuleEntry>,
+                                         _ scriptDirectoryURL: URL) {
+    guard !suppressGeneratingBuildSettings else { return }
 
-    let execroot = workspaceInfoExtractor.bazelExecutionRoot.replacingOccurrences(of: "'",
-                                                                                  with: "")
+    let fileURL = scriptDirectoryURL.appendingPathComponent(XcodeProjectGenerator.SettingsScript)
 
-    // Entire script is one variable, directly referenced within bazel_build.py. If this is an empty
-    // string, the path will return False in an os.path.exists(...) call.
-    let script = "BAZEL_EXECUTION_ROOT = '\(execroot)'\n"
+    let bazelSettingsProvider = workspaceInfoExtractor.bazelSettingsProvider
+    let bazelExecRoot = workspaceInfoExtractor.bazelExecutionRoot
+    let bazelBuildSettings = bazelSettingsProvider.buildSettings(bazel: config.bazelURL.path,
+                                                                 bazelExecRoot: bazelExecRoot,
+                                                                 options: config.options,
+                                                                 buildRuleEntries: buildRuleEntries)
+
+    let bundle = Bundle(for: type(of: self))
+
+    guard let templateURL = bundle.url(forResource: XcodeProjectGenerator.SettingsScript,
+                                       withExtension: "template") else {
+      localizedMessageLogger.error("GeneratingBazelBuildSettingsFailed",
+                                   comment: "Error message for generating build settings failed. Internal error in %1$@.",
+                                   context: config.projectName,
+                                   values: "Resource not found: Unable to find the script to template.")
+      return
+    }
+
+    guard let scriptTemplateData = fileManager.contents(atPath: templateURL.path) else {
+      localizedMessageLogger.error("GeneratingBazelBuildSettingsFailed",
+                                   comment: "Error message for generating build settings failed. Internal error in %1$@.",
+                                   context: config.projectName,
+                                   values: "Resource not readable: Unable to read the script to template.")
+      return
+    }
+    guard let scriptTemplate = String.init(data: scriptTemplateData, encoding: .utf8) else {
+      localizedMessageLogger.error("GeneratingBazelBuildSettingsFailed",
+                                   comment: "Error message for generating build settings failed. Internal error in %1$@.",
+                                   context: config.projectName,
+                                   values: "Resource parsing failed: Unable to load as utf8.")
+      return
+    }
+    let script = scriptTemplate.replacingOccurrences(of: "# <template>",
+                                                     with: "BUILD_SETTINGS = \(bazelBuildSettings.toPython(""))")
 
     var errorInfo: String? = nil
     do {
-      try writeDataHandler(executionRootFileURL, script.data(using: .utf8)!)
+      try writeDataHandler(fileURL, script.data(using: .utf8)!)
     } catch let e as NSError {
       errorInfo = e.localizedDescription
     } catch {
       errorInfo = "Unexpected exception"
     }
     if let errorInfo = errorInfo {
-      // Return an error, as failing to create the file will leave us without a buildable project.
-      localizedMessageLogger.error("BazelExecutionRootCacheFailed",
-                                   comment: XcodeProjectGenerator.CachedExecutionRootFilename +
-                                            "could not be created. \(errorInfo)",
-                                   context: config.projectName)
+      localizedMessageLogger.error("GeneratingBazelBuildSettingsFailed",
+                                   comment: "Error message for generating build settings failed. Internal error in %1$@.",
+                                   context: config.projectName,
+                                   values: errorInfo)
       return
     }
   }
@@ -1103,8 +1135,8 @@
     }
   }
 
-  private func installTulsiScripts(_ projectURL: URL) {
-
+  private func installTulsiScriptsForProjectInfo(_ projectInfo: GeneratedProjectInfo,
+                                                 projectURL: URL) {
     let scriptDirectoryURL = projectURL.appendingPathComponent(XcodeProjectGenerator.ScriptDirectorySubpath,
                                                                     isDirectory: true)
     if createDirectory(scriptDirectoryURL) {
@@ -1119,30 +1151,7 @@
                    toDirectory: scriptDirectoryURL)
       installFiles(resourceURLs.extraBuildScripts.map { ($0, $0.lastPathComponent) },
                    toDirectory: scriptDirectoryURL)
-      installCachedExecutionRoot(scriptDirectoryURL)
-
-      localizedMessageLogger.logProfilingEnd(profilingToken)
-    }
-  }
-
-  private func installTulsiBazelPackage(_ projectURL: URL) {
-
-    let bazelWorkspaceURL = projectURL.appendingPathComponent(XcodeProjectGenerator.BazelDirectorySubpath,
-                                                              isDirectory: true)
-    let bazelPackageURL = bazelWorkspaceURL.appendingPathComponent(XcodeProjectGenerator.TulsiPackageName,
-                                                                   isDirectory: true)
-
-    if createDirectory(bazelPackageURL) {
-      let profilingToken = localizedMessageLogger.startProfiling("installing_package",
-                                                                 context: config.projectName)
-      let progressNotifier = ProgressNotifier(name: InstallingScripts, maxValue: 1)
-      defer { progressNotifier.incrementValue() }
-      localizedMessageLogger.infoMessage("Installing Bazel integration package")
-
-      installFiles([(resourceURLs.bazelWorkspaceFile, XcodeProjectGenerator.WorkspaceFile)],
-                   toDirectory: bazelWorkspaceURL)
-      installFiles(resourceURLs.tulsiPackageFiles.map { ($0, $0.lastPathComponent) },
-                   toDirectory: bazelPackageURL)
+      generateBuildSettingsFile(projectInfo.buildRuleEntries, scriptDirectoryURL)
 
       localizedMessageLogger.logProfilingEnd(profilingToken)
     }
@@ -1310,9 +1319,9 @@
         // Only over-write if needed.
         if fileManager.fileExists(atPath: targetURL.path) {
           guard !fileManager.contentsEqual(atPath: sourceURL.path, andPath: targetURL.path) else {
-            print("Not overwriting \(targetURL.path) as its contents haven't changed.")
             continue;
           }
+          print("Overwriting \(targetURL.path) as its contents changed.")
           try fileManager.removeItem(at: targetURL)
         }
         try fileManager.copyItem(at: sourceURL, to: targetURL)
diff --git a/src/TulsiGenerator/en.lproj/Localizable.strings b/src/TulsiGenerator/en.lproj/Localizable.strings
index 7be9732..e562137 100644
--- a/src/TulsiGenerator/en.lproj/Localizable.strings
+++ b/src/TulsiGenerator/en.lproj/Localizable.strings
@@ -17,9 +17,6 @@
 /* Message to show when the bazel bin symlink name was not able to be extracted from the workspace. */
 "BazelBinSymlinkNameNotFound" = "Was not able to extract the bazel bin symlink name from the workspace. This is a Tulsi or Bazel bug, please report.";
 
-/* Message to show when we are not able to generate the Python script containing an execution root. */
-"BazelExecutionRootCacheFailed" = "Could not generate a script that contains the execution root for the current Bazel workspace. This is a Tulsi bug, please report.";
-
 /* Error message for when a Bazel extractor did not complete successfully. Details are logged separately. */
 "BazelInfoExtractionFailed" = "Failed to retrieve information about the Bazel workspace. This usually means that the contents of a BUILD file are incorrect or flags are missing. Read the message log carefully for details.";
 
@@ -80,6 +77,9 @@
 /* Failed to find a watchOS application extension with the given name. The project will not be able to launch the watch app. */
 "FindingWatchExtensionFailed" = "Failed to find watchOS extension '%1$@'. The generated project will not able to run the watch app.";
 
+/* Message to show when we are not able to generate the Python script containing the project's Bazel build settings. */
+"GeneratingBazelBuildSettingsFailed" = "Could not generate a script that contains the build settings for the project. This is a Tulsi bug, please report. Additional error context: %1$@";
+
 /* Message to show when loading a .xccurrentversion file fails. */
 "LoadingXCCurrentVersionFailed" = "Failed to determine the current version for a versioned group %1$@. The generated project may malfunction. Error: %2$@";
 
diff --git a/src/TulsiGenerator/en.lproj/Options.strings b/src/TulsiGenerator/en.lproj/Options.strings
index e2eee14..2d2ecef 100644
--- a/src/TulsiGenerator/en.lproj/Options.strings
+++ b/src/TulsiGenerator/en.lproj/Options.strings
@@ -29,6 +29,9 @@
 
 "GenerateRunfiles" = "Generate the runfiles directory used as TEST_SRCDIR";
 
+"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.";
+
 "CommandlineArguments" = "Commandline arguments";
 "EnvironmentVariables" = "Environment variables";
 
diff --git a/src/TulsiGeneratorIntegrationTests/AspectTests.swift b/src/TulsiGeneratorIntegrationTests/AspectTests.swift
index c7477b6..c22c43b 100644
--- a/src/TulsiGeneratorIntegrationTests/AspectTests.swift
+++ b/src/TulsiGeneratorIntegrationTests/AspectTests.swift
@@ -23,8 +23,10 @@
 
   override func setUp() {
     super.setUp()
+    let bazelSettingsProvider = BazelSettingsProvider(universalFlags: bazelUniversalFlags)
     aspectInfoExtractor = BazelAspectInfoExtractor(bazelURL: bazelURL,
                                                    workspaceRootURL: workspaceRootURL!,
+                                                   bazelSettingsProvider: bazelSettingsProvider,
                                                    localizedMessageLogger: localizedMessageLogger)
   }
 
@@ -453,8 +455,10 @@
 
   override func setUp() {
     super.setUp()
+    let bazelSettingsProvider = BazelSettingsProvider(universalFlags: bazelUniversalFlags)
     aspectInfoExtractor = BazelAspectInfoExtractor(bazelURL: bazelURL,
                                                    workspaceRootURL: workspaceRootURL!,
+                                                   bazelSettingsProvider: bazelSettingsProvider,
                                                    localizedMessageLogger: localizedMessageLogger)
     installBUILDFile("TestSuiteRoot",
                      intoSubdirectory: testDir,
diff --git a/src/TulsiGeneratorIntegrationTests/BazelIntegrationTestCase.swift b/src/TulsiGeneratorIntegrationTests/BazelIntegrationTestCase.swift
index 2166628..92e69a4 100644
--- a/src/TulsiGeneratorIntegrationTests/BazelIntegrationTestCase.swift
+++ b/src/TulsiGeneratorIntegrationTests/BazelIntegrationTestCase.swift
@@ -24,6 +24,7 @@
   var bazelBuildOptions = [String]()
   var runfilesURL: URL! = nil
   var fakeBazelWorkspace: BazelFakeWorkspace! = nil
+  var bazelUniversalFlags = BazelFlags()
   var workspaceRootURL: URL! = nil
   var testUndeclaredOutputsDir: URL? = nil
   var workspaceInfoFetcher: BazelWorkspacePathInfoFetcher! = nil
@@ -91,10 +92,19 @@
       fatalError("Failed to find workspaceRootURL.")
     }
 
+    let bundle = Bundle(for: TulsiXcodeProjectGenerator.self)
+    let bazelWorkspace =
+      bundle.url(forResource: "WORKSPACE", withExtension: nil)!.deletingLastPathComponent()
+
+    bazelUniversalFlags = BazelFlags(build: [
+        "--override_repository=tulsi=\(bazelWorkspace.path)"
+    ])
+
     localizedMessageLogger = DirectLocalizedMessageLogger()
     localizedMessageLogger.startLogging()
     workspaceInfoFetcher = BazelWorkspacePathInfoFetcher(bazelURL: bazelURL,
                                                          workspaceRootURL: workspaceRootURL,
+                                                         bazelUniversalFlags: bazelUniversalFlags,
                                                          localizedMessageLogger: localizedMessageLogger)
   }
 
diff --git a/src/TulsiGeneratorIntegrationTests/QueryTests.swift b/src/TulsiGeneratorIntegrationTests/QueryTests.swift
index c8720d9..206d08f 100644
--- a/src/TulsiGeneratorIntegrationTests/QueryTests.swift
+++ b/src/TulsiGeneratorIntegrationTests/QueryTests.swift
@@ -25,6 +25,7 @@
     super.setUp()
     infoExtractor = BazelQueryInfoExtractor(bazelURL: bazelURL,
                                             workspaceRootURL: workspaceRootURL!,
+                                            bazelUniversalFlags: bazelUniversalFlags,
                                             localizedMessageLogger: localizedMessageLogger)
   }
 
@@ -164,6 +165,7 @@
     super.setUp()
     infoExtractor = BazelQueryInfoExtractor(bazelURL: bazelURL,
                                             workspaceRootURL: workspaceRootURL!,
+                                            bazelUniversalFlags: bazelUniversalFlags,
                                             localizedMessageLogger: localizedMessageLogger)
     installBUILDFile("ComplexSingle", intoSubdirectory: testDir)
   }
diff --git a/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/One/TestOne.BUILD b/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/One/TestOne.BUILD
index 57544a0..42abf0c 100644
--- a/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/One/TestOne.BUILD
+++ b/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/One/TestOne.BUILD
@@ -14,6 +14,10 @@
 
 # Simple mock test.
 
+package(
+    default_visibility = ["//TestSuite:__subpackages__"],
+)
+
 load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
 
 test_suite(
diff --git a/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/Three/TestThree.BUILD b/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/Three/TestThree.BUILD
index b9cfc72..c02940b 100644
--- a/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/Three/TestThree.BUILD
+++ b/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/Three/TestThree.BUILD
@@ -14,6 +14,10 @@
 
 # Simple mock test.
 
+package(
+    default_visibility = ["//TestSuite:__subpackages__"],
+)
+
 load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
 
 test_suite(
diff --git a/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/Two/TestTwo.BUILD b/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/Two/TestTwo.BUILD
index 611d045..b4f0e74 100644
--- a/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/Two/TestTwo.BUILD
+++ b/src/TulsiGeneratorIntegrationTests/Resources/TestSuite/Two/TestTwo.BUILD
@@ -14,6 +14,10 @@
 
 # Simple mock test.
 
+package(
+    default_visibility = ["//TestSuite:__subpackages__"],
+)
+
 load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
 
 objc_library(
diff --git a/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift b/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
index c0f8271..6e9e01b 100644
--- a/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
+++ b/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
@@ -15,9 +15,32 @@
 import Foundation
 @testable import TulsiGenerator
 
+class MockBazelSettingsProvider: BazelSettingsProviderProtocol {
+  func tulsiFlags(hasSwift: Bool) -> BazelFlagsSet {
+    return BazelFlagsSet()
+  }
+
+  func optionsBasedFlags(_ options: TulsiOptionSet) -> BazelFlagsSet {
+    return BazelFlagsSet()
+  }
+
+  func buildSettings(bazel: String, bazelExecRoot: String, options: TulsiOptionSet, buildRuleEntries: Set<RuleEntry>) -> BazelBuildSettings {
+    return BazelBuildSettings(bazel: bazel,
+                              bazelExecRoot: bazelExecRoot,
+                              swiftTargets: [],
+                              tulsiCacheAffectingFlagsSet: BazelFlagsSet(),
+                              tulsiCacheSafeFlagSet: BazelFlagsSet(),
+                              tulsiSwiftFlagSet: BazelFlagsSet(),
+                              tulsiNonSwiftFlagSet: BazelFlagsSet(),
+                              projDefaultFlagSet: BazelFlagsSet(),
+                              projTargetFlagSets: [:])
+  }
+}
 
 class MockWorkspaceInfoExtractor: BazelWorkspaceInfoExtractorProtocol {
 
+  let bazelSettingsProvider: BazelSettingsProviderProtocol = MockBazelSettingsProvider()
+
   var labelToRuleEntry = [BuildLabel: RuleEntry]()
   /// The set of labels passed to ruleEntriesForLabels that could not be found in the
   /// labelToRuleEntry dictionary.
@@ -34,7 +57,8 @@
   func ruleEntriesForLabels(_ labels: [BuildLabel],
                             startupOptions: TulsiOption,
                             buildOptions: TulsiOption,
-                            projectGenBuildOptions: TulsiOption) throws -> RuleEntryMap {
+                            projectGenBuildOptions: TulsiOption,
+                            prioritizeSwiftOption: TulsiOption) throws -> RuleEntryMap {
     invalidLabels.removeAll(keepingCapacity: true)
     let ret = RuleEntryMap()
     for label in labels {
diff --git a/src/TulsiGeneratorTests/PBXTargetGeneratorTests.swift b/src/TulsiGeneratorTests/PBXTargetGeneratorTests.swift
index e081a9b..2b2ae87 100644
--- a/src/TulsiGeneratorTests/PBXTargetGeneratorTests.swift
+++ b/src/TulsiGeneratorTests/PBXTargetGeneratorTests.swift
@@ -18,7 +18,7 @@
 // Note: Rather than test the serializer's output, we make use of the knowledge that
 // buildSerializerWithRuleEntries modifies a project directly.
 class PBXTargetGeneratorTests: XCTestCase {
-  let bazelURL = URL(fileURLWithPath: "__BAZEL_BINARY_")
+  let bazelPath = "__BAZEL_BINARY_"
   let workspaceRootURL = URL(fileURLWithPath: "/workspaceRootURL", isDirectory: true)
   let stubPlistPaths = StubInfoPlistPaths(resourcesDirectory: "${PROJECT_FILE_PATH}/.tulsi/Resources",
                                           defaultStub: "TestInfo.plist",
@@ -31,7 +31,7 @@
   override func setUp() {
     super.setUp()
     project = PBXProject(name: "TestProject")
-    targetGenerator = PBXTargetGenerator(bazelURL: bazelURL,
+    targetGenerator = PBXTargetGenerator(bazelPath: bazelPath,
                                          bazelBinPath: "bazel-bin",
                                          project: project,
                                          buildScriptPath: "",
@@ -100,7 +100,7 @@
 
 
 class PBXTargetGeneratorTestsWithFiles: XCTestCase {
-  let bazelURL = URL(fileURLWithPath: "__BAZEL_BINARY_")
+  let bazelPath = "__BAZEL_BINARY_"
   let workspaceRootURL = URL(fileURLWithPath: "/workspaceRootURL", isDirectory: true)
   let sdkRoot = "sdkRoot"
   let stubPlistPaths = StubInfoPlistPaths(resourcesDirectory:"${PROJECT_ROOT}/asd",
@@ -128,7 +128,7 @@
     pchFile = project.mainGroup.getOrCreateFileReferenceBySourceTree(.Group, path: "pch.pch")
     let options = TulsiOptionSet()
     messageLogger = MockLocalizedMessageLogger()
-    targetGenerator = PBXTargetGenerator(bazelURL: bazelURL,
+    targetGenerator = PBXTargetGenerator(bazelPath: bazelPath,
                                          bazelBinPath: "bazel-bin",
                                          project: project,
                                          buildScriptPath: "",
@@ -153,8 +153,8 @@
 
     XCTAssertEqual(target.buildToolPath, scriptPath)
 
-    // The script should launch the test scriptPath with bazelURL's path as the only argument.
-    let expectedScriptArguments = "\"\(bazelURL.path)\" \"bazel-bin\""
+    // The script should launch the test scriptPath with bazelPath's path as the only argument.
+    let expectedScriptArguments = "\"\(bazelPath)\" \"bazel-bin\""
     XCTAssertEqual(target.buildArgumentsString, expectedScriptArguments)
   }
 
@@ -358,7 +358,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule1BuildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -395,7 +395,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule2BuildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule2BuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -473,7 +473,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule1BuildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -515,7 +515,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule2BuildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule2BuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -587,7 +587,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule1BuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -628,7 +628,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule2BuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule2BuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -705,7 +705,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule1BuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -747,7 +747,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule2BuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule2BuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -824,7 +824,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule1BuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -865,7 +865,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule2BuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule2BuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -933,7 +933,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule1BuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -1003,7 +1003,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule1BuildTarget),
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget),
           SourcesBuildPhaseDefinition(files: testSources, mainGroup: project.mainGroup),
         ]
       )
@@ -1080,7 +1080,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL,
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath,
                                               buildTarget: rule1BuildTarget),
           ]
       )
@@ -1124,7 +1124,7 @@
           ],
           expectedBuildPhases: [
               SourcesBuildPhaseDefinition(files: testSources, mainGroup: project.mainGroup),
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL,
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath,
                                               buildTarget: testRuleBuildTarget)
           ]
       )
@@ -1212,7 +1212,7 @@
         ],
       expectedBuildPhases: [
         SourcesBuildPhaseDefinition(files: testSources, mainGroup: project.mainGroup),
-        BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL,
+        BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath,
                                              buildTarget: "\(testRulePackage):\(testRuleTargetName)")
       ]
     )
@@ -1300,7 +1300,7 @@
         ],
       expectedBuildPhases: [
         SourcesBuildPhaseDefinition(files: testSources, mainGroup: project.mainGroup),
-        BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL,
+        BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath,
                                              buildTarget: "\(testRulePackage):\(testRuleTargetName)"),
         SwiftDummyShellScriptBuildPhaseDefinition()
       ]
@@ -1380,7 +1380,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL,
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath,
                                           buildTarget: rule1BuildTarget),
           ]
       )
@@ -1423,7 +1423,7 @@
           ],
         expectedBuildPhases: [
           SourcesBuildPhaseDefinition(files: testSources, mainGroup: project.mainGroup),
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL,
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath,
                                           buildTarget: testRuleBuildTarget)
         ]
       )
@@ -1551,7 +1551,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL,
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath,
                                                    buildTarget: testRuleBuildTarget),
           ]
       )
@@ -1614,7 +1614,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule1BuildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule1BuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -1651,7 +1651,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: rule2BuildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: rule2BuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -1715,7 +1715,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: buildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: buildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -1778,7 +1778,7 @@
           ),
           ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: buildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: buildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -1870,7 +1870,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: appBuildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: appBuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -1909,7 +1909,7 @@
               ),
           ],
           expectedBuildPhases: [
-              BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: watchAppBuildTarget)
+              BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: watchAppBuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -1948,7 +1948,7 @@
               ),
           ],
           expectedBuildPhases: [
-            BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: watchExtBuildTarget)
+            BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: watchExtBuildTarget)
           ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -2037,7 +2037,7 @@
           ),
         ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: appBuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: appBuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -2076,7 +2076,7 @@
           ),
         ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: macAppExtBuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: macAppExtBuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -2115,7 +2115,7 @@
           ),
         ],
         expectedBuildPhases: [
-          BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: macCLIAppBuildTarget)
+          BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: macCLIAppBuildTarget)
         ]
       )
       assertTarget(expectedTarget, inTargets: targets)
@@ -2601,7 +2601,7 @@
             ),
         ],
         expectedBuildPhases: [
-            BazelShellScriptBuildPhaseDefinition(bazelURL: bazelURL, buildTarget: target)
+            BazelShellScriptBuildPhaseDefinition(bazelPath: bazelPath, buildTarget: target)
         ]
     )
     assertTarget(expectedTarget, inTargets: targets)
@@ -2646,13 +2646,9 @@
     return newSettings
   }
 
-  private func releaseBuildSettingsFromSettings(_ settings: [String: String],
-                                                indexerSettingsOnly: Bool = false) -> [String: String] {
+  private func releaseBuildSettingsFromSettings(_ settings: [String: String]) -> [String: String] {
     var newSettings = settings
     newSettings["GCC_PREPROCESSOR_DEFINITIONS"] = "NDEBUG=1"
-    if !indexerSettingsOnly {
-      newSettings["TULSI_MUST_USE_DSYM"] = "YES"
-    }
     return newSettings
   }
 
@@ -2837,11 +2833,11 @@
   }
 
   private class BazelShellScriptBuildPhaseDefinition: BuildPhaseDefinition {
-    let bazelURL: URL
+    let bazelPath: String
     let buildTarget: String
 
-    init(bazelURL: URL, buildTarget: String) {
-      self.bazelURL = bazelURL
+    init(bazelPath: String, buildTarget: String) {
+      self.bazelPath = bazelPath
       self.buildTarget = buildTarget
       super.init(isa: "PBXShellScriptBuildPhase", files: [], mnemonic: "BazelBuild")
     }
@@ -2855,8 +2851,8 @@
 
       let script = scriptBuildPhase.shellScript
 
-      XCTAssert(script.contains(bazelURL.path),
-                "Build script does not contain \(bazelURL.path)",
+      XCTAssert(script.contains(bazelPath),
+                "Build script does not contain \(bazelPath)",
                 line: line)
       XCTAssert(script.contains(buildTarget),
                 "Build script does not contain build target \(buildTarget)",
@@ -2952,8 +2948,7 @@
             ),
             BuildConfigurationDefinition(
               name: "Release",
-              expectedBuildSettings: releaseBuildSettingsFromSettings(expectedBuildSettings,
-                                                                      indexerSettingsOnly: true)
+              expectedBuildSettings: releaseBuildSettingsFromSettings(expectedBuildSettings)
             ),
         ],
         expectedBuildPhases: expectedBuildPhases
diff --git a/src/TulsiGeneratorTests/PythonableTests.swift b/src/TulsiGeneratorTests/PythonableTests.swift
new file mode 100644
index 0000000..02680d7
--- /dev/null
+++ b/src/TulsiGeneratorTests/PythonableTests.swift
@@ -0,0 +1,94 @@
+// 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 PythonableTests: XCTestCase {
+
+  func testStringSimple() {
+    XCTAssertEqual("foobar".toPython(""), "'foobar'")
+    XCTAssertEqual("foobar".toPython("  "), "'foobar'")
+    XCTAssertEqual("this is a string".toPython(""), "'this is a string'")
+  }
+
+  func testStringEscapesSingleQuotes() {
+    XCTAssertEqual("foo'bar".toPython(""), "'foo\\'bar'")
+    XCTAssertEqual("foo'bar".toPython("  "), "'foo\\'bar'")
+    XCTAssertEqual("this''string".toPython(""), "'this\\'\\'string'")
+  }
+
+  func testArrayEmpty() {
+    XCTAssertEqual([String]().toPython(""), "[]")
+  }
+
+  func testArrayOfStrings() {
+    let arr = [
+      "Hello",
+      "Goodbye",
+      "'Escape'"
+    ]
+    XCTAssertEqual(arr.toPython(""), """
+[
+    'Hello',
+    'Goodbye',
+    '\\'Escape\\'',
+]
+""")
+    XCTAssertEqual(arr.toPython("  "), """
+[
+      'Hello',
+      'Goodbye',
+      '\\'Escape\\'',
+  ]
+""")
+  }
+
+  func testSetEmpty() {
+    XCTAssertEqual(Set<String>().toPython(""), "set()")
+  }
+
+  func testStringSet() {
+    let set: Set<String> = ["Hello"]
+    XCTAssertEqual(set.toPython(""), """
+set([
+    'Hello',
+])
+""")
+    XCTAssertEqual(set.toPython("  "), """
+set([
+      'Hello',
+  ])
+""")
+  }
+
+  func testDictionaryEmpty() {
+    XCTAssertEqual([String: String]().toPython(""), "{}")
+  }
+
+  func testStringDictionary() {
+    let dict = ["Type": "A"]
+    XCTAssertEqual(dict.toPython(""), """
+{
+    'Type': 'A',
+}
+""")
+    XCTAssertEqual(dict.toPython(" "), """
+{
+     'Type': 'A',
+ }
+""")
+  }
+}
+
diff --git a/src/TulsiGeneratorTests/ShellEscapingTests.swift b/src/TulsiGeneratorTests/ShellEscapingTests.swift
index 70fbdf5..a07bb38 100644
--- a/src/TulsiGeneratorTests/ShellEscapingTests.swift
+++ b/src/TulsiGeneratorTests/ShellEscapingTests.swift
@@ -18,7 +18,7 @@
 class ShellEscapingTests: XCTestCase {
 
   func testSurroundedBySingleQuotes() {
-    XCTAssertEqual("foobar".escapingForShell, "'foobar'")
+    XCTAssertEqual("foobar".escapingForShell, "foobar")
     XCTAssertEqual("this is a string".escapingForShell, "'this is a string'")
     XCTAssertEqual("$PWD".escapingForShell, "'$PWD'")
   }
diff --git a/src/TulsiGeneratorTests/XcodeProjectGeneratorTests.swift b/src/TulsiGeneratorTests/XcodeProjectGeneratorTests.swift
index 69f46ce..2cb4874 100644
--- a/src/TulsiGeneratorTests/XcodeProjectGeneratorTests.swift
+++ b/src/TulsiGeneratorTests/XcodeProjectGeneratorTests.swift
@@ -365,6 +365,7 @@
                                       pbxTargetGeneratorType: MockPBXTargetGenerator.self)
     generator.redactWorkspaceSymlink = true
     generator.suppressModifyingUserDefaults = true
+    generator.suppressGeneratingBuildSettings = true
     generator.writeDataHandler = { (url, _) in
       self.writtenFiles.insert(url.path)
     }
@@ -462,7 +463,7 @@
                     parent: nil)
   }
 
-  required init(bazelURL: URL,
+  required init(bazelPath: String,
                 bazelBinPath: String,
                 project: PBXProject,
                 buildScriptPath: String,