blob: 4aa6e57934731498d7cdf6cd63f83f143f3eb756 [file] [log] [blame]
// Copyright 2016 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
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
// Concrete extractor that utilizes Bazel query ( and aspects to
// extract information from a workspace.
final class BazelWorkspaceInfoExtractor: BazelWorkspaceInfoExtractorProtocol {
var bazelURL: URL {
get { return queryExtractor.bazelURL as URL }
set {
queryExtractor.bazelURL = newValue
aspectExtractor.bazelURL = newValue
/// Returns the workspace relative path to the bazel bin symlink. Note that this may block.
var bazelBinPath: String {
return workspacePathInfoFetcher.getBazelBinPath()
/// Returns the absolute path to the execution root of this Bazel workspace. This may block.
var bazelExecutionRoot: String {
return workspacePathInfoFetcher.getExecutionRoot()
/// Bazel settings provider for all invocations.
let bazelSettingsProvider: BazelSettingsProviderProtocol
/// Bazel workspace root URL.
let workspaceRootURL: URL
/// Fetcher object from which a workspace's path info may be obtained.
private let workspacePathInfoFetcher: BazelWorkspacePathInfoFetcher
private let aspectExtractor: BazelAspectInfoExtractor
private let queryExtractor: BazelQueryInfoExtractor
// Cache of all RuleEntry instances loaded for the associated project.
private var ruleEntryCache = RuleEntryMap()
init(bazelURL: URL, workspaceRootURL: URL, localizedMessageLogger: LocalizedMessageLogger) {
let universalFlags: BazelFlags
// Install to ~/Library/Application Support when not running inside a test.
if let applicationSupport = ApplicationSupport() {
let tulsiVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "UNKNOWN"
let aspectPath = try! applicationSupport.copyTulsiAspectFiles(tulsiVersion: tulsiVersion)
universalFlags = BazelFlags(
// TODO(tulsi-team): See if we can avoid using --override_repository.
build: ["--override_repository=tulsi=\(aspectPath)"]
} else { // Running inside a test, just refer to the files directly from TulsiGenerator.
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)
let executionRootURL = URL(fileURLWithPath: workspacePathInfoFetcher.getExecutionRoot(),
isDirectory: false)
aspectExtractor = BazelAspectInfoExtractor(bazelURL: bazelURL,
workspaceRootURL: workspaceRootURL,
executionRootURL: executionRootURL,
bazelSettingsProvider: bazelSettingsProvider,
localizedMessageLogger: localizedMessageLogger)
queryExtractor = BazelQueryInfoExtractor(bazelURL: bazelURL,
workspaceRootURL: workspaceRootURL,
bazelUniversalFlags: universalFlags,
localizedMessageLogger: localizedMessageLogger)
self.workspaceRootURL = workspaceRootURL
// MARK: - BazelWorkspaceInfoExtractorProtocol
func extractRuleInfoFromProject(_ project: TulsiProject) -> [RuleInfo] {
return queryExtractor.extractTargetRulesFromPackages(project.bazelPackages)
func ruleEntriesForLabels(_ labels: [BuildLabel],
startupOptions: TulsiOption,
buildOptions: TulsiOption,
compilationModeOption: TulsiOption,
platformConfigOption: TulsiOption,
prioritizeSwiftOption: TulsiOption,
useArm64_32Option: TulsiOption,
features: Set<BazelSettingFeature>) throws -> RuleEntryMap {
func isLabelMissing(_ label: BuildLabel) -> Bool {
return !ruleEntryCache.hasAnyRuleEntry(withBuildLabel: label)
let missingLabels = labels.filter(isLabelMissing)
if missingLabels.isEmpty { return ruleEntryCache }
let commandLineSplitter = CommandLineSplitter()
func splitOptionString(_ options: String?) -> [String] {
guard let options = options else { return [] }
return commandLineSplitter.splitCommandLine(options) ?? []
let startupOptions = splitOptionString(startupOptions.commonValue)
let buildOptions = splitOptionString(buildOptions.commonValue)
let compilationMode = compilationModeOption.commonValue
let platformConfig = platformConfigOption.commonValue
let prioritizeSwift = prioritizeSwiftOption.commonValueAsBool
let useArm64_32Option = useArm64_32Option.commonValueAsBool
do {
let ruleEntryMap =
try aspectExtractor.extractRuleEntriesForLabels(labels,
startupOptions: startupOptions,
buildOptions: buildOptions,
compilationMode: compilationMode,
platformConfig: platformConfig,
prioritizeSwift: prioritizeSwift,
useArm64_32: useArm64_32Option,
features: features)
ruleEntryCache = RuleEntryMap(ruleEntryMap)
} catch BazelAspectInfoExtractor.ExtractorError.buildFailed {
throw BazelWorkspaceInfoExtractorError.aspectExtractorFailed("Bazel aspects could not be built.")
return ruleEntryCache
func extractBuildfiles<T: Collection>(_ forTargets: T) -> Set<BuildLabel> where T.Iterator.Element == BuildLabel {
return queryExtractor.extractBuildfiles(forTargets)
func logQueuedInfoMessages() {
func hasQueuedInfoMessages() -> Bool {
return aspectExtractor.hasQueuedInfoMessages || queryExtractor.hasQueuedInfoMessages