How to use getBuildEnvironment method of Generator class

Best Mockingbird code snippet using Generator.getBuildEnvironment

Run Mockingbird automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Generator.swift

Source: Generator.swift Github

copy
1import Foundation
2import MockingbirdCommon
3import MockingbirdGenerator
4import PathKit
5import XcodeProj
6import os.log
7
8class Generator {
9  struct Configuration: Encodable {
10    let projectPath: Path
11    let sourceRoot: Path
12    let inputTargetNames: [String]
13    let environmentProjectFilePath: Path?
14    let environmentSourceRoot: Path?
15    let environmentTargetName: String?
16    let outputPaths: [Path]
17    let outputDir: Path?
18    let supportPath: Path?
19    let header: [String]
20    let compilationCondition: String?
21    let pruningMethod: PruningMethod
22    let onlyMockProtocols: Bool
23    let disableSwiftlint: Bool
24    let disableCache: Bool
25    let disableRelaxedLinking: Bool
26  }
27  
28  enum Error: LocalizedError {
29    case mismatchedInputsAndOutputs(inputCount: Int, outputCount: Int)
30    case invalidOutputPath(path: Path)
31    case invalidInputTarget(name: String)
32    
33    var errorDescription: String? {
34      switch self {
35      case let .mismatchedInputsAndOutputs(inputCount, outputCount):
36        return "Mismatched number of input targets (\(inputCount)) and output file paths (\(outputCount))"
37      case let .invalidOutputPath(path):
38        return "Invalid output file path \(path)"
39      case let .invalidInputTarget(name):
40        return "The target '\(name)' does not exist in the project"
41      }
42    }
43  }
44  
45  enum Constants {
46    static let generatedFileNameSuffix = "Mocks.generated.swift"
47    static let xcodeCacheSubdirectory = "MockingbirdCache"
48    static let jsonCacheSubdirectory = ".mockingbird"
49  }
50  
51  let config: Configuration
52  let configHash: String
53  let cliVersion = "\(mockingbirdVersion)"
54  
55  let sourceTargetCacheDirectory: Path
56  let testTargetCacheDirectory: Path?
57  
58  init(_ config: Configuration) throws {
59    self.config = config
60    self.configHash = try config.toSha1Hash()
61    
62    // Set up directories for target metadata caching.
63    if config.projectPath.extension == "xcodeproj" {
64      self.sourceTargetCacheDirectory = config.projectPath + Constants.xcodeCacheSubdirectory
65      if let environmentProjectFilePath = config.environmentProjectFilePath {
66        let cacheDirectory = environmentProjectFilePath + Constants.xcodeCacheSubdirectory
67        self.testTargetCacheDirectory = cacheDirectory
68      } else {
69        self.testTargetCacheDirectory = nil
70      }
71    } else {
72      self.sourceTargetCacheDirectory = config.projectPath.parent()
73        + Constants.jsonCacheSubdirectory
74      self.testTargetCacheDirectory = self.sourceTargetCacheDirectory
75    }
76  }
77  
78  var parsedProjects = [Path: Project]()
79  func getProject(_ projectPath: Path) throws -> Project {
80    if let xcodeproj = parsedProjects[projectPath] { return xcodeproj }
81    var project: Project!
82    try time(.parseXcodeProject) {
83      if projectPath.extension == "xcodeproj" {
84        project = .xcode(try XcodeProj(path: projectPath))
85      } else {
86        logInfo("Inferring JSON project description from extension \((projectPath.extension ?? "").singleQuoted)")
87        project = .json(try JSONProject(path: projectPath))
88      }
89    }
90    parsedProjects[projectPath] = project
91    return project
92  }
93  
94  // Parsing Xcode projects can be slow, so lazily get implicit build environments.
95  func getBuildEnvironment() -> [String: Any] {
96    let project = try? getProject(config.projectPath)
97    switch project {
98    case .xcode(let xcodeproj): return xcodeproj.implicitBuildEnvironment
99    case .json, .none: return [:]
100    }
101  }
102  
103  var projectHashes: [Path: String] = [:]
104  func getProjectHash(_ projectPath: Path) -> String? {
105    if let projectHash = projectHashes[projectPath.absolute()] { return projectHash }
106    let filePath = projectPath.extension == "xcodeproj"
107      ? projectPath.glob("*.pbxproj").sorted().first
108      : projectPath
109    let projectHash = try? filePath?.read().hash()
110    self.projectHashes[projectPath.absolute()] = projectHash
111    return projectHash
112  }
113  
114  // Get cached source target metadata.
115  func getCachedSourceTarget(targetName: String) -> TargetType? {
116    guard !config.disableCache,
117      let projectHash = getProjectHash(config.projectPath),
118      let cachedTarget = findCachedSourceTarget(for: targetName,
119                                                cliVersion: cliVersion,
120                                                projectHash: projectHash,
121                                                configHash: configHash,
122                                                cacheDirectory: sourceTargetCacheDirectory,
123                                                sourceRoot: config.sourceRoot)
124      else { return nil }
125    return .sourceTarget(cachedTarget)
126  }
127  
128  // Get cached test target metadata.
129  func getCachedTestTarget(targetName: String) -> TargetType? {
130    guard config.pruningMethod != .disable,
131      let cacheDirectory = testTargetCacheDirectory,
132      let testProjectPath = config.environmentProjectFilePath,
133      let testSourceRoot = config.environmentSourceRoot,
134      let projectHash = getProjectHash(testProjectPath),
135      let cachedTarget = findCachedTestTarget(for: targetName,
136                                              projectHash: projectHash,
137                                              cliVersion: cliVersion,
138                                              cacheDirectory: cacheDirectory,
139                                              sourceRoot: testSourceRoot)
140      else { return nil }
141    return .testTarget(cachedTarget)
142  }
143  
144  func generate() throws {
145    if !config.outputPaths.isEmpty && config.inputTargetNames.count != config.outputPaths.count {
146      throw Error.mismatchedInputsAndOutputs(inputCount: config.inputTargetNames.count,
147                                             outputCount: config.outputPaths.count)
148    }
149    
150    if config.supportPath == nil {
151      logWarning("No supporting source files specified which can result in missing mocks")
152    }
153    
154    // Resolve target names to concrete Xcode project targets.
155    let targets = try config.inputTargetNames.compactMap({ targetName throws -> TargetType? in
156      return try Generator.resolveTarget(targetName: targetName,
157                                         projectPath: config.projectPath,
158                                         getCachedTarget: getCachedSourceTarget,
159                                         getProject: getProject)
160    })
161    
162    // Resolve unspecified output paths to the default mock file output destination.
163    let outputPaths: [Path] = try {
164      if !config.outputPaths.isEmpty {
165        return config.outputPaths
166      }
167      return try targets.map({ target throws -> Path in
168        let outputDir = config.outputDir ?? config.sourceRoot.mocksDirectory
169        try outputDir.mkpath()
170        return Generator.defaultOutputPath(for: target,
171                                           outputDir: outputDir,
172                                           environment: getBuildEnvironment)
173      })
174    }()
175    
176    let queue = OperationQueue.createForActiveProcessors()
177    
178    // Create operations to find used mock types in tests.
179    let pruningPipeline = config.pruningMethod == .disable ? nil :
180      PruningPipeline(config: config,
181                      getCachedTarget: getCachedTestTarget,
182                      getProject: getProject,
183                      environment: getBuildEnvironment)
184    if let pruningOperations = pruningPipeline?.operations {
185      queue.addOperations(pruningOperations, waitUntilFinished: false)
186    }
187    let findMockedTypesOperation = pruningPipeline?.findMockedTypesOperation
188    
189    // Create abstract generation pipelines from targets and output paths.
190    var pipelines = [Pipeline]()
191    for (target, outputPath) in zip(targets, outputPaths) {
192      guard !outputPath.isDirectory else {
193        throw Error.invalidOutputPath(path: outputPath)
194      }
195      try pipelines.append(Pipeline(inputTarget: target,
196                                    outputPath: outputPath,
197                                    config: config,
198                                    findMockedTypesOperation: findMockedTypesOperation,
199                                    environment: getBuildEnvironment))
200    }
201    pipelines.forEach({ queue.addOperations($0.operations, waitUntilFinished: false) })
202    
203    // Run the operations.
204    let operationsCopy = queue.operations.compactMap({ $0 as? BasicOperation })
205    queue.waitUntilAllOperationsAreFinished()
206    operationsCopy.compactMap({ $0.error }).forEach({ logError($0) })
207    
208    // Write intermediary module cache info into project cache directory.
209    if !config.disableCache {
210      try time(.cacheMocks) {
211        try cachePipelines(sourcePipelines: pipelines, pruningPipeline: pruningPipeline)
212      }
213    }
214  }
215  
216  func cachePipelines(sourcePipelines: [Pipeline], pruningPipeline: PruningPipeline?) throws {
217    guard let projectHash = getProjectHash(config.projectPath) else { return }
218    
219    // Cache source targets for generation.
220    try sourceTargetCacheDirectory.mkpath()
221    try sourcePipelines.forEach({
222      try $0.cache(projectHash: projectHash,
223                   cliVersion: cliVersion,
224                   configHash: configHash,
225                   sourceRoot: config.sourceRoot,
226                   cacheDirectory: sourceTargetCacheDirectory,
227                   environment: getBuildEnvironment)
228    })
229    
230    // Cache test target for thunk pruning.
231    if config.pruningMethod != .disable {
232      if let testTargetCacheDirectory = testTargetCacheDirectory,
233         let environmentSourceRoot = config.environmentSourceRoot,
234         let testProjectPath = config.environmentProjectFilePath,
235         let projectHash = getProjectHash(testProjectPath) {
236        try testTargetCacheDirectory.mkpath()
237        try pruningPipeline?.cache(projectHash: projectHash,
238                                   cliVersion: cliVersion,
239                                   sourceRoot: environmentSourceRoot,
240                                   cacheDirectory: testTargetCacheDirectory,
241                                   environment: getBuildEnvironment)
242      }
243    }
244  }
245  
246  static func defaultOutputPath(for sourceTarget: TargetType,
247                                testTarget: TargetType? = nil,
248                                outputDir: Path,
249                                environment: () -> [String: Any]) -> Path {
250    let moduleName = sourceTarget.resolveProductModuleName(environment: environment)
251    
252    let prefix: String
253    if let testTargetName = testTarget?.resolveProductModuleName(environment: environment),
254      testTargetName != moduleName {
255      prefix = testTargetName + "-"
256    } else {
257      prefix = "" // Probably installed on a source target instead of a test target.
258    }
259    
260    return outputDir + "\(prefix)\(moduleName)\(Constants.generatedFileNameSuffix)"
261  }
262  
263  static func resolveTarget(targetName: String,
264                            projectPath: Path,
265                            isValidTarget: (TargetType) -> Bool = { _ in true },
266                            getCachedTarget: (String) -> TargetType?,
267                            getProject: (Path) throws -> Project) throws -> TargetType {
268    // Check if the target is cached in the project.
269    if let cachedTarget = getCachedTarget(targetName) {
270      return cachedTarget
271    }
272    
273    // Need to parse the Xcode project for the full `PBXTarget` object.
274    let project = try getProject(projectPath)
275    let targets = project.targets(named: targetName).filter(isValidTarget)
276    
277    if targets.count > 1 {
278      logWarning("Found multiple targets named \(targetName.singleQuoted), using the first one")
279    }
280    
281    guard let target = targets.first else {
282      throw Error.invalidInputTarget(name: targetName)
283    }
284    return target
285  }
286}
287
288extension Path {
289  var mocksDirectory: Path {
290    return absolute() + Path("MockingbirdMocks")
291  }
292  
293  func targetLockFilePath(for targetName: String, testBundle: String?) -> Path {
294    var lockFileName: String
295    if let testBundle = testBundle {
296      lockFileName = "\(targetName)-\(testBundle).lock"
297    } else {
298      lockFileName = "\(targetName).lock"
299    }
300    return self + lockFileName
301  }
302}
303
304extension XcodeProj {
305  var implicitBuildEnvironment: [String: Any] {
306    var buildEnvironment = [String: Any]()
307
308    if let projectName = pbxproj.rootObject?.name {
309      buildEnvironment["PROJECT_NAME"] = projectName
310      buildEnvironment["PROJECT"] = projectName
311    }
312    
313    return buildEnvironment
314  }
315}
316
317extension PBXProductType {
318  var isTestBundle: Bool {
319    switch self {
320    case .unitTestBundle, .uiTestBundle, .ocUnitTestBundle: return true
321    default: return false
322    }
323  }
324  
325  var isSwiftUnitTestBundle: Bool {
326    switch self {
327    case .unitTestBundle: return true
328    default: return false
329    }
330  }
331}
332
Full Screen

PBXTarget+Target.swift

Source: PBXTarget+Target.swift Github

copy
1import Foundation
2import PathKit
3import XcodeProj
4
5extension PBXTarget: Target {
6  /// Get the build configuration for testing, which is almost always `debug`.
7  // TODO: Allow overriding for special cases via CLI argument.
8  var testingBuildConfiguration: XCBuildConfiguration? {
9    guard let inferredBuildConfiguration = buildConfigurationList?.buildConfigurations
10      .first(where: { $0.name.lowercased() == "debug" }) else {
11        logWarning("No debug build configuration found for target \(name.singleQuoted)")
12        return buildConfigurationList?.buildConfigurations.first
13    }
14    return inferredBuildConfiguration
15  }
16  
17  public func resolveProductModuleName(environment: () -> [String: Any]) -> String {
18    guard
19      let configuration = testingBuildConfiguration,
20      let moduleName = try? PBXTarget.resolve(
21        BuildSetting("$(PRODUCT_MODULE_NAME:default=$(PRODUCT_NAME:default=$(TARGET_NAME)))"),
22        from: getBuildEnvironment(configuration: configuration, environment: environment())
23      )
24    else {
25      let fallbackModuleName = name.escapingForModuleName()
26      logWarning("Unable to resolve product module name for target \(name.singleQuoted), falling back to \(fallbackModuleName.singleQuoted)")
27      return fallbackModuleName
28    }
29    
30    let escapedModuleName = moduleName.escapingForModuleName()
31    log("Resolved product module name \(escapedModuleName.singleQuoted) for target \(name.singleQuoted)")
32    return escapedModuleName
33  }
34
35  public func findSourceFilePaths(sourceRoot: Path) -> [Path] {
36    guard let phase = buildPhases.first(where: { $0.buildPhase == .sources }) else { return [] }
37    return phase.files?
38      .compactMap({ try? $0.file?.fullPath(sourceRoot: sourceRoot) })
39      .filter({ $0.extension == "swift" })
40      .map({ $0.absolute() }) ?? []
41  }
42  
43  /// Certain environment build settings are synthesized by Xcode and don't exist in the project
44  /// file such as `TARGET_NAME`. Since the generator usually runs as part of a test target bundle
45  /// (or even entirely outside of an Xcode build pipeline), we need to do some inference here.
46  func getBuildEnvironment(configuration: XCBuildConfiguration,
47                           environment: [String: Any]) -> [String: Any] {
48    let keepOld: (Any, Any) -> Any = { old, _ in old }
49    
50    // Explicit build settings defined in the Xcode project file.
51    var buildEnvironment: [String: Any] = configuration.buildSettings
52    
53    // Implicit settings derived from the target info.
54    buildEnvironment.merge([
55      "TARGET_NAME": name,
56      "TARGETNAME": name,
57    ], uniquingKeysWith: keepOld)
58    
59    // Implicit settings from external sources, e.g. `PROJECT_NAME`.
60    buildEnvironment.merge(environment, uniquingKeysWith: keepOld)
61    
62    return buildEnvironment
63  }
64  
65  enum ResolutionFailure: Error {
66    static let cycleFlagValue = "__MKB_CYCLE_FLAG__"
67    case evaluationCycle
68  }
69  
70  /// Recursively resolves build settings.
71  static func resolve(_ buildSetting: BuildSetting,
72                      from environment: [String: Any]) throws -> String {
73    return try buildSetting.components.map({ component -> String in
74      switch component {
75      case .literal(let literal):
76        guard literal.value != ResolutionFailure.cycleFlagValue else {
77          throw ResolutionFailure.evaluationCycle
78        }
79        return literal.value
80      case .expression(let expression):
81        // Recursive `resolve` calls pass `attributedEnvironment` to break evaluation cycles.
82        var attributedEnvironment = environment
83        attributedEnvironment[expression.name] = ResolutionFailure.cycleFlagValue
84        
85        if let buildSettingValue = environment[expression.name] as? String,
86          !buildSettingValue.isEmpty {
87          return try resolve(BuildSetting(buildSettingValue), from: attributedEnvironment)
88        }
89        
90        // Empty build settings can have default values as of Xcode 11.4
91        // https://developer.apple.com/documentation/xcode_release_notes/xcode_11_4_release_notes
92        if let expressionOperator = expression.expressionOperator,
93          expressionOperator.name == "default",
94          let buildSetting = expressionOperator.value {
95          return try resolve(buildSetting, from: attributedEnvironment)
96        }
97        
98        let targetName = environment["TARGET_NAME"] as? String ?? ""
99        let description = "$(\(expression))"
100        logWarning("The build setting expression \(description.singleQuoted) evaluates to an empty string when resolving the product module name for \(targetName.singleQuoted)")
101        return ""
102      }
103      }).joined()
104  }
105}
106
107extension PBXTargetDependency: TargetDependency {}
108
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Trigger getBuildEnvironment code on LambdaTest Cloud Grid

Execute automation tests with getBuildEnvironment on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)