| // 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 |
| // |
| // 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 |
| |
| /// Handles fetching of interesting paths for a Bazel workspace. |
| class BazelWorkspacePathInfoFetcher { |
| /// The Bazel execution_root as defined by the target workspace. |
| private var executionRoot: String? = nil |
| /// The bazel bin symlink name as defined by the target workspace. |
| private var bazelBinSymlinkName: String? = nil |
| |
| /// The location of the bazel binary. |
| 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, bazelUniversalFlags: BazelFlags, |
| localizedMessageLogger: LocalizedMessageLogger) { |
| self.bazelURL = bazelURL |
| self.workspaceRootURL = workspaceRootURL |
| self.bazelUniversalFlags = bazelUniversalFlags |
| self.localizedMessageLogger = localizedMessageLogger |
| |
| semaphore = DispatchSemaphore(value: 0) |
| fetchWorkspaceInfo() |
| } |
| |
| /// Returns the execution_root for this fetcher's workspace, blocking until it is available. |
| func getExecutionRoot() -> String { |
| if !fetchCompleted { waitForCompletion() } |
| |
| guard let executionRoot = executionRoot else { |
| localizedMessageLogger.error("ExecutionRootNotFound", |
| comment: "Execution root should have been extracted from the workspace.") |
| return "" |
| } |
| return executionRoot |
| } |
| |
| /// Returns the bazel bin path for this workspace, blocking until the fetch is completed. |
| func getBazelBinPath() -> String { |
| if !fetchCompleted { waitForCompletion() } |
| |
| guard let bazelBinSymlinkName = bazelBinSymlinkName else { |
| localizedMessageLogger.error("BazelBinSymlinkNameNotFound", |
| comment: "Bazel bin symlink should have been extracted from the workspace.") |
| return "" |
| } |
| |
| return bazelBinSymlinkName |
| } |
| |
| // MARK: - Private methods |
| |
| // Waits for the workspace fetcher to signal the |
| private func waitForCompletion() { |
| _ = semaphore.wait(timeout: DispatchTime.distantFuture) |
| semaphore.signal() |
| } |
| |
| // Fetches Bazel path info from the registered workspace URL. |
| private func fetchWorkspaceInfo() { |
| let profilingStart = localizedMessageLogger.startProfiling("get_package_path", |
| message: "Fetching bazel path info") |
| guard FileManager.default.fileExists(atPath: bazelURL.path) else { |
| localizedMessageLogger.error("BazelBinaryNotFound", |
| comment: "Error to show when the bazel binary cannot be found at the previously saved location %1$@.", |
| values: bazelURL as NSURL) |
| 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: arguments, |
| messageLogger: localizedMessageLogger, |
| loggingIdentifier: "bazel_get_package_path" ) { |
| completionInfo in |
| defer { |
| self.localizedMessageLogger.logProfilingEnd(profilingStart) |
| self.fetchCompleted = true |
| self.semaphore.signal() |
| } |
| if completionInfo.process.terminationStatus == 0 { |
| if let stdout = NSString(data: completionInfo.stdout, encoding: String.Encoding.utf8.rawValue) { |
| self.extractWorkspaceInfo(stdout) |
| return |
| } |
| } |
| |
| let stderr = NSString(data: completionInfo.stderr, encoding: String.Encoding.utf8.rawValue) |
| let debugInfoFormatString = NSLocalizedString("DebugInfoForBazelCommand", |
| bundle: Bundle(for: type(of: self)), |
| comment: "Provides general information about a Bazel failure; a more detailed error may be reported elsewhere. The Bazel command is %1$@, exit code is %2$d, stderr %3$@.") |
| let debugInfo = String(format: debugInfoFormatString, |
| completionInfo.commandlineString, |
| completionInfo.terminationStatus, |
| stderr ?? "<No STDERR>") |
| self.localizedMessageLogger.infoMessage(debugInfo) |
| self.localizedMessageLogger.error("BazelWorkspaceInfoQueryFailed", |
| comment: "Extracting path info from bazel failed. The exit code is %1$d.", |
| details: stderr as String?, |
| values: completionInfo.process.terminationStatus) |
| } |
| process.currentDirectoryPath = workspaceRootURL.path |
| process.launch() |
| } |
| |
| private func extractWorkspaceInfo(_ output: NSString) { |
| let lines = output.components(separatedBy: CharacterSet.newlines) |
| for line in lines { |
| let components = line.components(separatedBy: ": ") |
| guard let key = components.first, !key.isEmpty else { continue } |
| let valueComponents = components.dropFirst() |
| let value = valueComponents.joined(separator: ": ") |
| |
| if key.hasSuffix("-bin") { |
| if (bazelBinSymlinkName != nil) { |
| self.localizedMessageLogger.warning("MultipleBazelWorkspaceSymlinkNames", |
| comment: "Error to show when more than one workspace key has a suffix of '-bin'.", |
| details: "More than one key in the workspace ends in '-bin'. Only the first key will be used.") |
| continue |
| } |
| bazelBinSymlinkName = key |
| } |
| |
| if key == "execution_root" { |
| executionRoot = value |
| } |
| } |
| } |
| } |