Migrate info plist generator to a project mapper (#1469)
- The info plist generator is now a project mapper - This helps keeps project modifications and side effects consistent to some of the others introduced - Updated `Constants` to create a nested hierarchy for `DerivedDirectory` to allow grouping all related constants - Updated side effect descriptions such that they are all `CustomStringContvertible` to allow using them in verbose logs Test Plan: - Run `tuist generate` within `fixtures/ios_app_with_watchapp2` - Verify the Info.plist files continue to be generated to the `Derived/InfoPlists` directory
This commit is contained in:
parent
e8ecaf6871
commit
ac502072bc
|
@ -12,6 +12,8 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
### Changed
|
||||
|
||||
- Use `LD_RUNPATH_SEARCH_PATHS` instead of embedding dynamic frameworks for unit test targets [#1463](https://github.com/tuist/tuist/pull/1463) by [@fortmarek](https://github.com/fortmarek)
|
||||
- Migrate info plist generator to a project mapper [#1469](https://github.com/tuist/tuist/pull/1469) by [@kwridan](https://github.com/kwridan).
|
||||
|
||||
|
||||
## 1.11.0 - Volare
|
||||
|
||||
|
|
|
@ -19,3 +19,9 @@ public struct CommandDescriptor: Equatable {
|
|||
self.init(command: command)
|
||||
}
|
||||
}
|
||||
|
||||
extension CommandDescriptor: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"execute \(command.joined(separator: " "))"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
/// Directory Descriptor
|
||||
///
|
||||
/// Describes a folder operation that needs to take place as
|
||||
/// part of generating a project or workspace.
|
||||
///
|
||||
/// - seealso: `SideEffectsDescriptor`
|
||||
public struct DirectoryDescriptor: Equatable {
|
||||
public enum State {
|
||||
case present
|
||||
case absent
|
||||
}
|
||||
|
||||
/// Path to the directory
|
||||
public var path: AbsolutePath
|
||||
|
||||
/// The desired state of the directory (`.present` creates a fiile, `.absent` deletes a file)
|
||||
public var state: State
|
||||
|
||||
/// Creates a DirectoryDescriptor Descriptor
|
||||
/// - Parameters:
|
||||
/// - path: Path to the file
|
||||
/// - state: The desired state of the file (`.present` creates a fiile, `.absent` deletes a file)
|
||||
public init(path: AbsolutePath,
|
||||
state: DirectoryDescriptor.State = .present) {
|
||||
self.path = path
|
||||
self.state = state
|
||||
}
|
||||
}
|
||||
|
||||
extension DirectoryDescriptor: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch state {
|
||||
case .absent:
|
||||
return "delete directory \(path.pathString)"
|
||||
case .present:
|
||||
return "create directory \(path.pathString)"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,3 +35,14 @@ public struct FileDescriptor: Equatable {
|
|||
self.state = state
|
||||
}
|
||||
}
|
||||
|
||||
extension FileDescriptor: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch state {
|
||||
case .absent:
|
||||
return "delete file \(path.pathString)"
|
||||
case .present:
|
||||
return "create file \(path.pathString)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,22 @@ public enum SideEffectDescriptor: Equatable {
|
|||
/// Create / Remove a file
|
||||
case file(FileDescriptor)
|
||||
|
||||
/// Create / remove a directory
|
||||
case directory(DirectoryDescriptor)
|
||||
|
||||
/// Perform a command
|
||||
case command(CommandDescriptor)
|
||||
}
|
||||
|
||||
extension SideEffectDescriptor: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .file(fileDescriptor):
|
||||
return fileDescriptor.description
|
||||
case let .directory(directoryDescriptor):
|
||||
return directoryDescriptor.description
|
||||
case let .command(commandDescriptor):
|
||||
return commandDescriptor.description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,15 +43,24 @@ public enum InfoPlist: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
// Path to a user defined info.plist file (already exists on disk).
|
||||
case file(path: AbsolutePath)
|
||||
|
||||
// Path to a generated info.plist file (may not exist on disk at the time of project generation).
|
||||
case generatedFile(path: AbsolutePath)
|
||||
|
||||
// User defined dictionary of keys/values for an info.plist file.
|
||||
case dictionary([String: Value])
|
||||
|
||||
// User defined dictionary of keys/values for an info.plist file extending the default set of keys/values
|
||||
// for the target type.
|
||||
case extendingDefault(with: [String: Value])
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
public var path: AbsolutePath? {
|
||||
switch self {
|
||||
case let .file(path):
|
||||
case let .file(path), let .generatedFile(path: path):
|
||||
return path
|
||||
default:
|
||||
return nil
|
||||
|
|
|
@ -16,11 +16,14 @@ public final class SideEffectDescriptorExecutor: SideEffectDescriptorExecuting {
|
|||
|
||||
public func execute(sideEffects: [SideEffectDescriptor]) throws {
|
||||
for sideEffect in sideEffects {
|
||||
logger.debug("Side effect: \(sideEffect)")
|
||||
switch sideEffect {
|
||||
case let .command(commandDescriptor):
|
||||
try perform(command: commandDescriptor)
|
||||
case let .file(fileDescriptor):
|
||||
try process(file: fileDescriptor)
|
||||
case let .directory(directoryDescriptor):
|
||||
try process(directory: directoryDescriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +44,19 @@ public final class SideEffectDescriptorExecutor: SideEffectDescriptorExecuting {
|
|||
}
|
||||
}
|
||||
|
||||
private func process(directory: DirectoryDescriptor) throws {
|
||||
switch directory.state {
|
||||
case .present:
|
||||
if !FileHandler.shared.exists(directory.path) {
|
||||
try FileHandler.shared.createFolder(directory.path)
|
||||
}
|
||||
case .absent:
|
||||
if FileHandler.shared.exists(directory.path) {
|
||||
try FileHandler.shared.delete(directory.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func perform(command: CommandDescriptor) throws {
|
||||
try System.shared.run(command.command)
|
||||
}
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
import XcodeProj
|
||||
|
||||
protocol DerivedFileGenerating {
|
||||
/// Generates the derived files that are associated to the given project.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: Project whose derived files will be generated.
|
||||
/// - sourceRootPath: Path to the directory in which the Xcode project will be generated.
|
||||
/// - Throws: An error if the generation of the derived files errors.
|
||||
/// - Returns: A project that might have got mutated after the generation of derived files, and a
|
||||
/// function to be called after the project generation to delete the derived files that are not necessary anymore.
|
||||
func generate(graph: Graph, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, [SideEffectDescriptor])
|
||||
}
|
||||
|
||||
final class DerivedFileGenerator: DerivedFileGenerating {
|
||||
typealias ProjectTransformation = (project: Project, sideEffects: [SideEffectDescriptor])
|
||||
fileprivate static let infoPlistsFolderName = "InfoPlists"
|
||||
|
||||
/// Info.plist content provider.
|
||||
let infoPlistContentProvider: InfoPlistContentProviding
|
||||
|
||||
/// Initializes the generator with its attributes.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - infoPlistContentProvider: Info.plist content provider.
|
||||
init(infoPlistContentProvider: InfoPlistContentProviding = InfoPlistContentProvider()) {
|
||||
self.infoPlistContentProvider = infoPlistContentProvider
|
||||
}
|
||||
|
||||
func generate(graph: Graph, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, [SideEffectDescriptor]) {
|
||||
let transformation = try generateInfoPlists(graph: graph, project: project, sourceRootPath: sourceRootPath)
|
||||
|
||||
return (transformation.project, transformation.sideEffects)
|
||||
}
|
||||
|
||||
/// Genreates the Info.plist files.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: Project that contains the targets whose Info.plist files will be generated.
|
||||
/// - sourceRootPath: Path to the directory in which the project is getting generated.
|
||||
/// - Returns: A set with paths to the Info.plist files that are no longer necessary and therefore need to be removed.
|
||||
/// - Throws: An error if the encoding of the Info.plist content fails.
|
||||
func generateInfoPlists(graph: Graph,
|
||||
project: Project,
|
||||
sourceRootPath: AbsolutePath) throws -> ProjectTransformation {
|
||||
let targetsWithGeneratableInfoPlists = project.targets.filter {
|
||||
if let infoPlist = $0.infoPlist, case InfoPlist.file = infoPlist {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Getting the Info.plist files that need to be deleted
|
||||
let glob = "\(Constants.derivedFolderName)/\(DerivedFileGenerator.infoPlistsFolderName)/*.plist"
|
||||
let existing = FileHandler.shared.glob(sourceRootPath, glob: glob)
|
||||
let new: [AbsolutePath] = targetsWithGeneratableInfoPlists.map {
|
||||
DerivedFileGenerator.infoPlistPath(target: $0, sourceRootPath: sourceRootPath)
|
||||
}
|
||||
let toDelete = Set(existing).subtracting(new)
|
||||
|
||||
let deletions = toDelete.map {
|
||||
SideEffectDescriptor.file(FileDescriptor(path: $0, state: .absent))
|
||||
}
|
||||
|
||||
// Generate the Info.plist
|
||||
let transformation = try project.targets.map { (target) -> (Target, [SideEffectDescriptor]) in
|
||||
guard targetsWithGeneratableInfoPlists.contains(target),
|
||||
let infoPlist = target.infoPlist else {
|
||||
return (target, [])
|
||||
}
|
||||
|
||||
guard let dictionary = infoPlistDictionary(infoPlist: infoPlist,
|
||||
project: project,
|
||||
target: target,
|
||||
graph: graph) else {
|
||||
return (target, [])
|
||||
}
|
||||
|
||||
let path = DerivedFileGenerator.infoPlistPath(target: target, sourceRootPath: sourceRootPath)
|
||||
|
||||
let data = try PropertyListSerialization.data(fromPropertyList: dictionary,
|
||||
format: .xml,
|
||||
options: 0)
|
||||
|
||||
let sideEffect = SideEffectDescriptor.file(FileDescriptor(path: path, contents: data))
|
||||
|
||||
// Override the Info.plist value to point to te generated one
|
||||
return (target.with(infoPlist: InfoPlist.file(path: path)), [sideEffect])
|
||||
}
|
||||
|
||||
return (project: project.with(targets: transformation.map { $0.0 }),
|
||||
sideEffects: deletions + transformation.flatMap { $0.1 })
|
||||
}
|
||||
|
||||
private func infoPlistDictionary(infoPlist: InfoPlist,
|
||||
project: Project,
|
||||
target: Target,
|
||||
graph: Graph) -> [String: Any]? {
|
||||
switch infoPlist {
|
||||
case let .dictionary(content):
|
||||
return content.mapValues { $0.value }
|
||||
case let .extendingDefault(extended):
|
||||
if let content = infoPlistContentProvider.content(graph: graph,
|
||||
project: project,
|
||||
target: target,
|
||||
extendedWith: extended) {
|
||||
return content
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the path to the directory that contains all the derived files.
|
||||
///
|
||||
/// - Parameter sourceRootPath: Directory where the project will be generated.
|
||||
/// - Returns: Path to the directory that contains all the derived files.
|
||||
static func path(sourceRootPath: AbsolutePath) -> AbsolutePath {
|
||||
sourceRootPath
|
||||
.appending(component: Constants.derivedFolderName)
|
||||
}
|
||||
|
||||
/// Returns the path to the directory where all generated Info.plist files will be.
|
||||
///
|
||||
/// - Parameter sourceRootPath: Directory where the Xcode project gets genreated.
|
||||
/// - Returns: The path to the directory where all the Info.plist files will be generated.
|
||||
static func infoPlistsPath(sourceRootPath: AbsolutePath) -> AbsolutePath {
|
||||
path(sourceRootPath: sourceRootPath)
|
||||
.appending(component: DerivedFileGenerator.infoPlistsFolderName)
|
||||
}
|
||||
|
||||
/// Returns the path where the derived Info.plist is generated.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - target: The target the InfoPlist belongs to.
|
||||
/// - sourceRootPath: The directory where the Xcode project will be generated.
|
||||
/// - Returns: The path where the derived Info.plist is generated.
|
||||
static func infoPlistPath(target: Target, sourceRootPath: AbsolutePath) -> AbsolutePath {
|
||||
infoPlistsPath(sourceRootPath: sourceRootPath)
|
||||
.appending(component: "\(target.name).plist")
|
||||
}
|
||||
}
|
|
@ -8,12 +8,11 @@ protocol InfoPlistContentProviding {
|
|||
/// and product, and extends them with the values provided by the user.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: The project that hosts the target for which the Info.plist content will be returned
|
||||
/// - target: Target whose Info.plist content will be returned.
|
||||
/// - extendedWith: Values provided by the user to extend the default ones.
|
||||
/// - Returns: Content to generate the Info.plist file.
|
||||
func content(graph: Graph, project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]?
|
||||
func content(project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]?
|
||||
}
|
||||
|
||||
final class InfoPlistContentProvider: InfoPlistContentProviding {
|
||||
|
@ -22,12 +21,11 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
|
|||
/// and product, and extends them with the values provided by the user.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: The project that hosts the target for which the Info.plist content will be returned
|
||||
/// - target: Target whose Info.plist content will be returned.
|
||||
/// - extendedWith: Values provided by the user to extend the default ones.
|
||||
/// - Returns: Content to generate the Info.plist file.
|
||||
func content(graph: Graph, project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]? {
|
||||
func content(project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]? {
|
||||
if target.product == .staticLibrary || target.product == .dynamicLibrary {
|
||||
return nil
|
||||
}
|
||||
|
@ -57,16 +55,16 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
|
|||
|
||||
// watchOS app
|
||||
if target.product == .watch2App, target.platform == .watchOS {
|
||||
let host = graph.hostTargetNodeFor(path: project.path, name: target.name)
|
||||
let host = hostTarget(for: target, in: project)
|
||||
extend(&content, with: watchosApp(name: target.name,
|
||||
hostAppBundleId: host?.target.bundleId))
|
||||
hostAppBundleId: host?.bundleId))
|
||||
}
|
||||
|
||||
// watchOS app extension
|
||||
if target.product == .watch2Extension, target.platform == .watchOS {
|
||||
let host = graph.hostTargetNodeFor(path: project.path, name: target.name)
|
||||
let host = hostTarget(for: target, in: project)
|
||||
extend(&content, with: watchosAppExtension(name: target.name,
|
||||
hostAppBundleId: host?.target.bundleId))
|
||||
hostAppBundleId: host?.bundleId))
|
||||
}
|
||||
|
||||
extend(&content, with: extendedWith.unwrappingValues())
|
||||
|
@ -223,4 +221,10 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
|
|||
fileprivate func extend(_ base: inout [String: Any], with: [String: Any]) {
|
||||
with.forEach { base[$0.key] = $0.value }
|
||||
}
|
||||
|
||||
private func hostTarget(for target: Target, in project: Project) -> Target? {
|
||||
project.targets.first {
|
||||
$0.dependencies.contains(.target(name: target.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,9 +47,6 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
/// Generator for the project schemes.
|
||||
let schemesGenerator: SchemesGenerating
|
||||
|
||||
/// Generator for the project derived files.
|
||||
let derivedFileGenerator: DerivedFileGenerating
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
/// Initializes the project generator with its attributes.
|
||||
|
@ -58,15 +55,12 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
/// - targetGenerator: Generator for the project targets.
|
||||
/// - configGenerator: Generator for the project configuration.
|
||||
/// - schemesGenerator: Generator for the project schemes.
|
||||
/// - derivedFileGenerator: Generator for the project derived files.
|
||||
init(targetGenerator: TargetGenerating = TargetGenerator(),
|
||||
configGenerator: ConfigGenerating = ConfigGenerator(),
|
||||
schemesGenerator: SchemesGenerating = SchemesGenerator(),
|
||||
derivedFileGenerator: DerivedFileGenerating = DerivedFileGenerator()) {
|
||||
schemesGenerator: SchemesGenerating = SchemesGenerator()) {
|
||||
self.targetGenerator = targetGenerator
|
||||
self.configGenerator = configGenerator
|
||||
self.schemesGenerator = schemesGenerator
|
||||
self.derivedFileGenerator = derivedFileGenerator
|
||||
}
|
||||
|
||||
// MARK: - ProjectGenerating
|
||||
|
@ -84,10 +78,6 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
// If the xcodeproj path is not given, we generate it under the source root path.
|
||||
let xcodeprojPath = xcodeprojPath ?? sourceRootPath.appending(component: "\(project.fileName).xcodeproj")
|
||||
|
||||
// Derived files
|
||||
// TODO: experiment with moving this outside the project generator to avoid needing to mutate the project
|
||||
let (project, sideEffects) = try derivedFileGenerator.generate(graph: graph, project: project, sourceRootPath: sourceRootPath)
|
||||
|
||||
let workspaceData = XCWorkspaceData(children: [])
|
||||
let workspace = XCWorkspace(data: workspaceData)
|
||||
let projectConstants = try determineProjectConstants(graph: graph)
|
||||
|
@ -137,7 +127,7 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
xcodeprojPath: xcodeprojPath,
|
||||
xcodeProj: xcodeProj,
|
||||
schemeDescriptors: schemes,
|
||||
sideEffectDescriptors: sideEffects)
|
||||
sideEffectDescriptors: [])
|
||||
}
|
||||
|
||||
// MARK: - Fileprivate
|
||||
|
|
|
@ -138,7 +138,9 @@ class TargetLinter: TargetLinting {
|
|||
|
||||
private func lintInfoplistExists(target: Target) -> [LintingIssue] {
|
||||
var issues: [LintingIssue] = []
|
||||
if let infoPlist = target.infoPlist, let path = infoPlist.path, !FileHandler.shared.exists(path) {
|
||||
if let infoPlist = target.infoPlist,
|
||||
case let InfoPlist.file(path: path) = infoPlist,
|
||||
!FileHandler.shared.exists(path) {
|
||||
issues.append(LintingIssue(reason: "Info.plist file not found at path \(infoPlist.path!.pathString)", severity: .error))
|
||||
}
|
||||
return issues
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import Foundation
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
/// A project mapper that returns side effects to delete the derived directory.
|
||||
public final class DeleteDerivedDirectoryProjectMapper: ProjectMapping {
|
||||
private let derivedDirectoryName: String
|
||||
|
||||
public init(derivedDirectoryName: String = Constants.DerivedDirectory.name) {
|
||||
self.derivedDirectoryName = derivedDirectoryName
|
||||
}
|
||||
|
||||
// MARK: - ProjectMapping
|
||||
|
||||
public func map(project: Project) throws -> (Project, [SideEffectDescriptor]) {
|
||||
logger.debug("Determining the /Derived directories that should be deleted within \(project.path)")
|
||||
let derivedDirectoryPath = project.path.appending(component: derivedDirectoryName)
|
||||
let directoryDescriptor = DirectoryDescriptor(path: derivedDirectoryPath, state: .absent)
|
||||
|
||||
return (project, [
|
||||
.directory(directoryDescriptor),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
import XcodeProj
|
||||
|
||||
/// A project mapper that generates derived Info.plist files for targets that define it as a dictonary.
|
||||
public final class GenerateInfoPlistProjectMapper: ProjectMapping {
|
||||
private let infoPlistContentProvider: InfoPlistContentProviding
|
||||
private let derivedDirectoryName: String
|
||||
private let infoPlistsDirectoryName: String
|
||||
|
||||
public convenience init(derivedDirectoryName: String = Constants.DerivedDirectory.name,
|
||||
infoPlistsDirectoryName: String = Constants.DerivedDirectory.infoPlists) {
|
||||
self.init(infoPlistContentProvider: InfoPlistContentProvider(),
|
||||
derivedDirectoryName: derivedDirectoryName,
|
||||
infoPlistsDirectoryName: infoPlistsDirectoryName)
|
||||
}
|
||||
|
||||
init(infoPlistContentProvider: InfoPlistContentProviding,
|
||||
derivedDirectoryName: String,
|
||||
infoPlistsDirectoryName: String) {
|
||||
self.infoPlistContentProvider = infoPlistContentProvider
|
||||
self.derivedDirectoryName = derivedDirectoryName
|
||||
self.infoPlistsDirectoryName = infoPlistsDirectoryName
|
||||
}
|
||||
|
||||
// MARK: - ProjectMapping
|
||||
|
||||
public func map(project: Project) throws -> (Project, [SideEffectDescriptor]) {
|
||||
var results = (targets: [Target](), sideEffects: [SideEffectDescriptor]())
|
||||
results = try project.targets.reduce(into: results) { results, target in
|
||||
let (updatedTarget, sideEffects) = try map(target: target, project: project)
|
||||
results.targets.append(updatedTarget)
|
||||
results.sideEffects.append(contentsOf: sideEffects)
|
||||
}
|
||||
|
||||
return (project.with(targets: results.targets), results.sideEffects)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func map(target: Target, project: Project) throws -> (Target, [SideEffectDescriptor]) {
|
||||
// There's nothing to do
|
||||
guard let infoPlist = target.infoPlist else {
|
||||
return (target, [])
|
||||
}
|
||||
|
||||
// Get the Info.plist that needs to be generated
|
||||
guard let dictionary = infoPlistDictionary(infoPlist: infoPlist,
|
||||
project: project,
|
||||
target: target) else {
|
||||
return (target, [])
|
||||
}
|
||||
let data = try PropertyListSerialization.data(fromPropertyList: dictionary,
|
||||
format: .xml,
|
||||
options: 0)
|
||||
|
||||
let infoPlistPath = project.path
|
||||
.appending(component: derivedDirectoryName)
|
||||
.appending(component: infoPlistsDirectoryName)
|
||||
.appending(component: "\(target.name).plist")
|
||||
let sideEffect = SideEffectDescriptor.file(FileDescriptor(path: infoPlistPath, contents: data))
|
||||
|
||||
let newTarget = target.with(infoPlist: InfoPlist.generatedFile(path: infoPlistPath))
|
||||
|
||||
return (newTarget, [sideEffect])
|
||||
}
|
||||
|
||||
private func infoPlistDictionary(infoPlist: InfoPlist,
|
||||
project: Project,
|
||||
target: Target) -> [String: Any]? {
|
||||
switch infoPlist {
|
||||
case let .dictionary(content):
|
||||
return content.mapValues { $0.value }
|
||||
case let .extendingDefault(extended):
|
||||
if let content = infoPlistContentProvider.content(project: project,
|
||||
target: target,
|
||||
extendedWith: extended) {
|
||||
return content
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,11 @@ class ProjectMapperProvider: ProjectMapperProviding {
|
|||
mappers.append(AutogeneratedSchemesProjectMapper())
|
||||
}
|
||||
|
||||
// Info Plist
|
||||
mappers.append(DeleteDerivedDirectoryProjectMapper())
|
||||
mappers.append(GenerateInfoPlistProjectMapper())
|
||||
|
||||
// Signing
|
||||
mappers.append(SigningMapper())
|
||||
|
||||
return SequentialProjectMapper(mappers: mappers)
|
||||
|
|
|
@ -37,8 +37,8 @@ public class SigningMapper: ProjectMapping {
|
|||
try signingCipher.decryptSigning(at: path, keepFiles: true)
|
||||
defer { try? signingCipher.encryptSigning(at: path, keepFiles: false) }
|
||||
|
||||
let derivedDirectory = project.path.appending(component: Constants.derivedFolderName)
|
||||
let keychainPath = derivedDirectory.appending(component: Constants.signingKeychain)
|
||||
let derivedDirectory = project.path.appending(component: Constants.DerivedDirectory.name)
|
||||
let keychainPath = derivedDirectory.appending(component: Constants.DerivedDirectory.signingKeychain)
|
||||
|
||||
let (certificates, provisioningProfiles) = try signingMatcher.match(from: project.path)
|
||||
|
||||
|
|
|
@ -48,10 +48,10 @@ public final class SigningInteractor: SigningInteracting {
|
|||
let entryPath = graph.entryPath
|
||||
guard
|
||||
let signingDirectory = try signingFilesLocator.locateSigningDirectory(from: entryPath),
|
||||
let derivedDirectory = rootDirectoryLocator.locate(from: entryPath)?.appending(component: Constants.derivedFolderName)
|
||||
let derivedDirectory = rootDirectoryLocator.locate(from: entryPath)?.appending(component: Constants.DerivedDirectory.name)
|
||||
else { return }
|
||||
|
||||
let keychainPath = derivedDirectory.appending(component: Constants.signingKeychain)
|
||||
let keychainPath = derivedDirectory.appending(component: Constants.DerivedDirectory.signingKeychain)
|
||||
|
||||
let masterKey = try signingCipher.readMasterKey(at: signingDirectory)
|
||||
try FileHandler.shared.createFolder(derivedDirectory)
|
||||
|
|
|
@ -9,10 +9,10 @@ public struct Constants {
|
|||
public static let bundleName: String = "tuist.zip"
|
||||
public static let trueValues: [String] = ["1", "true", "TRUE", "yes", "YES"]
|
||||
public static let tuistDirectoryName: String = "Tuist"
|
||||
public static let derivedFolderName = "Derived"
|
||||
|
||||
public static let helpersDirectoryName: String = "ProjectDescriptionHelpers"
|
||||
public static let signingDirectoryName: String = "Signing"
|
||||
public static let signingKeychain = "signing.keychain"
|
||||
|
||||
public static let masterKey = "master.key"
|
||||
public static let encryptedExtension = "encrypted"
|
||||
public static let templatesDirectoryName: String = "Templates"
|
||||
|
@ -20,6 +20,12 @@ public struct Constants {
|
|||
public static let joinSlackURL: String = "https://slack.tuist.io/"
|
||||
public static let tuistGeneratedFileName = ".tuist-generated"
|
||||
|
||||
public struct DerivedDirectory {
|
||||
public static let name = "Derived"
|
||||
public static let infoPlists = "InfoPlists"
|
||||
public static let signingKeychain = "signing.keychain"
|
||||
}
|
||||
|
||||
public struct EnvironmentVariables {
|
||||
public static let colouredOutput = "TUIST_COLOURED_OUTPUT"
|
||||
public static let versionsDirectory = "TUIST_VERSIONS_DIRECTORY"
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistCoreTesting
|
||||
import TuistSupport
|
||||
import XcodeProj
|
||||
import XCTest
|
||||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class DerivedFileGeneratorTests: TuistUnitTestCase {
|
||||
var infoPlistContentProvider: MockInfoPlistContentProvider!
|
||||
var subject: DerivedFileGenerator!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
infoPlistContentProvider = MockInfoPlistContentProvider()
|
||||
subject = DerivedFileGenerator(infoPlistContentProvider: infoPlistContentProvider)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
infoPlistContentProvider = nil
|
||||
subject = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func test_generate_generatesTheInfoPlistFiles_whenDictionaryInfoPlist() throws {
|
||||
// Given
|
||||
let temporaryPath = try self.temporaryPath()
|
||||
let target = Target.test(name: "Target", infoPlist: InfoPlist.dictionary(["a": "b"]))
|
||||
let project = Project.test(name: "App", targets: [target])
|
||||
|
||||
// When
|
||||
let (_, sideEffects) = try subject.generate(graph: Graph.test(), project: project, sourceRootPath: temporaryPath)
|
||||
|
||||
// Then
|
||||
let file = try XCTUnwrap(sideEffects.files.first)
|
||||
let contents = try XCTUnwrap(file.contents)
|
||||
let content = try PropertyListSerialization.propertyList(from: contents, options: [], format: nil)
|
||||
XCTAssertTrue(NSDictionary(dictionary: (content as? [String: Any]) ?? [:])
|
||||
.isEqual(to: ["a": "b"]))
|
||||
}
|
||||
|
||||
func test_generate_generatesTheInfoPlistFiles_whenExtendingDefault() throws {
|
||||
// Given
|
||||
let temporaryPath = try self.temporaryPath()
|
||||
let target = Target.test(name: "Target", infoPlist: InfoPlist.extendingDefault(with: ["a": "b"]))
|
||||
let project = Project.test(name: "App", targets: [target])
|
||||
let infoPlistsPath = DerivedFileGenerator.infoPlistsPath(sourceRootPath: temporaryPath)
|
||||
let path = infoPlistsPath.appending(component: "Target.plist")
|
||||
infoPlistContentProvider.contentStub = ["test": "value"]
|
||||
|
||||
// When
|
||||
let (_, sideEffects) = try subject.generate(graph: Graph.test(), project: project, sourceRootPath: temporaryPath)
|
||||
|
||||
// Then
|
||||
let file = try XCTUnwrap(sideEffects.files.first)
|
||||
XCTAssertEqual(file.path, path)
|
||||
XCTAssertTrue(infoPlistContentProvider.contentArgs.first?.target == target)
|
||||
XCTAssertTrue(infoPlistContentProvider.contentArgs.first?.extendedWith["a"] == "b")
|
||||
|
||||
let writtenData = try XCTUnwrap(file.contents)
|
||||
let content = try PropertyListSerialization.propertyList(from: writtenData, options: [], format: nil)
|
||||
XCTAssertTrue(NSDictionary(dictionary: (content as? [String: Any]) ?? [:])
|
||||
.isEqual(to: ["test": "value"]))
|
||||
}
|
||||
|
||||
func test_generate_returnsABlockToDeleteUnnecessaryInfoPlistFiles() throws {
|
||||
// Given
|
||||
let temporaryPath = try self.temporaryPath()
|
||||
let target = Target.test(infoPlist: InfoPlist.dictionary(["a": "b"]))
|
||||
let project = Project.test(name: "App", targets: [target])
|
||||
|
||||
let infoPlistsPath = DerivedFileGenerator.infoPlistsPath(sourceRootPath: temporaryPath)
|
||||
try FileHandler.shared.createFolder(infoPlistsPath)
|
||||
let oldPlistPath = infoPlistsPath.appending(component: "Old.plist")
|
||||
try FileHandler.shared.touch(oldPlistPath)
|
||||
|
||||
// When
|
||||
let (_, sideEffects) = try subject.generate(graph: Graph.test(),
|
||||
project: project,
|
||||
sourceRootPath: temporaryPath)
|
||||
|
||||
// Then
|
||||
let file = try XCTUnwrap(sideEffects.deletions.first)
|
||||
XCTAssertEqual(file, oldPlistPath)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Array where Element == SideEffectDescriptor {
|
||||
var files: [FileDescriptor] {
|
||||
compactMap {
|
||||
switch $0 {
|
||||
case let .file(file):
|
||||
return file
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var deletions: [AbsolutePath] {
|
||||
compactMap {
|
||||
switch $0 {
|
||||
case let .file(file):
|
||||
switch file.state {
|
||||
case .absent:
|
||||
return file.path
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,8 +17,7 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .iOS, product: .app)
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
let got = subject.content(project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
|
@ -54,8 +53,7 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .macOS, product: .app)
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
let got = subject.content(project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
|
@ -83,8 +81,7 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .macOS, product: .framework)
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
let got = subject.content(project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
|
@ -108,8 +105,7 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .macOS, product: .staticLibrary)
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
let got = subject.content(project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
|
@ -122,8 +118,7 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .macOS, product: .dynamicLibrary)
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
let got = subject.content(project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
|
@ -136,8 +131,7 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .iOS, product: .bundle)
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
let got = subject.content(project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
|
@ -156,8 +150,7 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
|
||||
func test_contentPackageType() {
|
||||
func content(for target: Target) -> [String: Any]? {
|
||||
subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
subject.content(project: .empty(),
|
||||
target: target,
|
||||
extendedWith: [:])
|
||||
}
|
||||
|
@ -178,19 +171,17 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
product: .watch2App)
|
||||
let app = Target.test(platform: .iOS,
|
||||
product: .app,
|
||||
bundleId: "io.tuist.my.app.id")
|
||||
bundleId: "io.tuist.my.app.id",
|
||||
dependencies: [
|
||||
.target(name: watchApp.name),
|
||||
])
|
||||
let project = Project.test(targets: [
|
||||
app,
|
||||
watchApp,
|
||||
])
|
||||
let graph = Graph.create(project: project, dependencies: [
|
||||
(target: app, dependencies: [watchApp]),
|
||||
(target: watchApp, dependencies: []),
|
||||
])
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: graph,
|
||||
project: project,
|
||||
let got = subject.content(project: project,
|
||||
target: watchApp,
|
||||
extendedWith: [
|
||||
"ExtraAttribute": "Value",
|
||||
|
@ -225,19 +216,17 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
product: .watch2Extension)
|
||||
let watchApp = Target.test(platform: .watchOS,
|
||||
product: .watch2App,
|
||||
bundleId: "io.tuist.my.app.id.mywatchapp")
|
||||
bundleId: "io.tuist.my.app.id.mywatchapp",
|
||||
dependencies: [
|
||||
.target(name: watchAppExtension.name),
|
||||
])
|
||||
let project = Project.test(targets: [
|
||||
watchApp,
|
||||
watchAppExtension,
|
||||
])
|
||||
let graph = Graph.create(project: project, dependencies: [
|
||||
(target: watchApp, dependencies: [watchAppExtension]),
|
||||
(target: watchAppExtension, dependencies: []),
|
||||
])
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: graph,
|
||||
project: project,
|
||||
let got = subject.content(project: project,
|
||||
target: watchAppExtension,
|
||||
extendedWith: [
|
||||
"ExtraAttribute": "Value",
|
||||
|
|
|
@ -4,11 +4,11 @@ import TuistCoreTesting
|
|||
@testable import TuistGenerator
|
||||
|
||||
final class MockInfoPlistContentProvider: InfoPlistContentProviding {
|
||||
var contentArgs: [(graph: Graph, project: Project, target: Target, extendedWith: [String: InfoPlist.Value])] = []
|
||||
var contentArgs: [(project: Project, target: Target, extendedWith: [String: InfoPlist.Value])] = []
|
||||
var contentStub: [String: Any]?
|
||||
|
||||
func content(graph: Graph, project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]? {
|
||||
contentArgs.append((graph: graph, project: project, target: target, extendedWith: extendedWith))
|
||||
func content(project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]? {
|
||||
contentArgs.append((project: project, target: target, extendedWith: extendedWith))
|
||||
return contentStub ?? [:]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
public final class DeleteDerivedDirectoryProjectMapperTests: TuistUnitTestCase {
|
||||
var subject: DeleteDerivedDirectoryProjectMapper!
|
||||
|
||||
override public func setUp() {
|
||||
super.setUp()
|
||||
subject = DeleteDerivedDirectoryProjectMapper()
|
||||
}
|
||||
|
||||
override public func tearDown() {
|
||||
super.tearDown()
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_map_returns_sideEffectsToDeleteDerivedDirectories() throws {
|
||||
// Given
|
||||
let projectA = Project.test(path: "/projectA")
|
||||
|
||||
// When
|
||||
let (_, sideEffects) = try subject.map(project: projectA)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(sideEffects, [
|
||||
.directory(.init(path: projectA.path.appending(component: Constants.DerivedDirectory.name), state: .absent)),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
public final class GenerateInfoPlistProjectMapperTests: TuistUnitTestCase {
|
||||
var infoPlistContentProvider: MockInfoPlistContentProvider!
|
||||
var subject: GenerateInfoPlistProjectMapper!
|
||||
|
||||
override public func setUp() {
|
||||
super.setUp()
|
||||
infoPlistContentProvider = MockInfoPlistContentProvider()
|
||||
subject = GenerateInfoPlistProjectMapper(infoPlistContentProvider: infoPlistContentProvider,
|
||||
derivedDirectoryName: Constants.DerivedDirectory.name,
|
||||
infoPlistsDirectoryName: Constants.DerivedDirectory.infoPlists)
|
||||
}
|
||||
|
||||
override public func tearDown() {
|
||||
super.tearDown()
|
||||
infoPlistContentProvider = nil
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_map() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", infoPlist: .dictionary(["A": "A_VALUE"]))
|
||||
let targetB = Target.test(name: "B", infoPlist: .dictionary(["B": "B_VALUE"]))
|
||||
let project = Project.test(targets: [targetA, targetB])
|
||||
|
||||
// When
|
||||
let (mappedProject, sideEffects) = try subject.map(project: project)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(sideEffects.count, 2)
|
||||
XCTAssertEqual(mappedProject.targets.count, 2)
|
||||
|
||||
try XCTAssertSideEffectsCreateDerivedInfoPlist(named: "A.plist",
|
||||
content: ["A": "A_VALUE"],
|
||||
projectPath: project.path,
|
||||
sideEffects: sideEffects)
|
||||
try XCTAssertSideEffectsCreateDerivedInfoPlist(named: "B.plist",
|
||||
content: ["B": "B_VALUE"],
|
||||
projectPath: project.path,
|
||||
sideEffects: sideEffects)
|
||||
XCTAssertTargetExistsWithDerivedInfoPlist(named: "A.plist",
|
||||
project: mappedProject)
|
||||
XCTAssertTargetExistsWithDerivedInfoPlist(named: "B.plist",
|
||||
project: mappedProject)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func XCTAssertSideEffectsCreateDerivedInfoPlist(named: String,
|
||||
content: [String: String],
|
||||
projectPath: AbsolutePath,
|
||||
sideEffects: [SideEffectDescriptor],
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) throws {
|
||||
let data = try PropertyListSerialization.data(fromPropertyList: content,
|
||||
format: .xml,
|
||||
options: 0)
|
||||
|
||||
XCTAssertNotNil(sideEffects.first(where: { sideEffect in
|
||||
guard case let SideEffectDescriptor.file(file) = sideEffect else { return false }
|
||||
return file.path == projectPath
|
||||
.appending(component: Constants.DerivedDirectory.name)
|
||||
.appending(component: Constants.DerivedDirectory.infoPlists)
|
||||
.appending(component: named) && file.contents == data
|
||||
}), file: file, line: line)
|
||||
}
|
||||
|
||||
private func XCTAssertTargetExistsWithDerivedInfoPlist(named: String,
|
||||
project: Project,
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line) {
|
||||
XCTAssertNotNil(project.targets.first(where: { (target: Target) in
|
||||
target.infoPlist?.path == project.path
|
||||
.appending(component: Constants.DerivedDirectory.name)
|
||||
.appending(component: Constants.DerivedDirectory.infoPlists)
|
||||
.appending(component: named)
|
||||
}), file: file, line: line)
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ final class SecurityControllerIntegrationTests: TuistTestCase {
|
|||
|
||||
func test_import_certificate() throws {
|
||||
// Given
|
||||
let keychainPath = try temporaryPath().appending(component: Constants.signingKeychain)
|
||||
let keychainPath = try temporaryPath().appending(component: Constants.DerivedDirectory.signingKeychain)
|
||||
|
||||
let currentDirectory = AbsolutePath(#file.replacingOccurrences(of: "file://", with: "")).removingLastComponent()
|
||||
let publicKey = currentDirectory.appending(component: "Target.Debug.cer")
|
||||
|
@ -54,7 +54,7 @@ final class SecurityControllerIntegrationTests: TuistTestCase {
|
|||
|
||||
func test_import_certificate_when_exists() throws {
|
||||
// Given
|
||||
let keychainPath = try temporaryPath().appending(component: Constants.signingKeychain)
|
||||
let keychainPath = try temporaryPath().appending(component: Constants.DerivedDirectory.signingKeychain)
|
||||
|
||||
let currentDirectory = AbsolutePath(#file.replacingOccurrences(of: "file://", with: "")).removingLastComponent()
|
||||
let publicKey = currentDirectory.appending(component: "Target.Debug.cer")
|
||||
|
|
|
@ -64,7 +64,8 @@ final class SigningInteractorTests: TuistUnitTestCase {
|
|||
|
||||
let rootDirectory = try temporaryPath()
|
||||
rootDirectoryLocator.locateStub = rootDirectory
|
||||
let keychainDirectory = rootDirectory.appending(components: Constants.derivedFolderName, Constants.signingKeychain)
|
||||
let keychainDirectory = rootDirectory
|
||||
.appending(components: Constants.DerivedDirectory.name, Constants.DerivedDirectory.signingKeychain)
|
||||
|
||||
var receivedKeychainDirectory: AbsolutePath?
|
||||
var receivedMasterKey: String?
|
||||
|
@ -95,7 +96,8 @@ final class SigningInteractorTests: TuistUnitTestCase {
|
|||
|
||||
let rootDirectory = try temporaryPath()
|
||||
rootDirectoryLocator.locateStub = rootDirectory
|
||||
let keychainDirectory = rootDirectory.appending(components: Constants.derivedFolderName, Constants.signingKeychain)
|
||||
let keychainDirectory = rootDirectory
|
||||
.appending(components: Constants.DerivedDirectory.name, Constants.DerivedDirectory.signingKeychain)
|
||||
|
||||
var receivedKeychainDirectory: AbsolutePath?
|
||||
var receivedMasterKey: String?
|
||||
|
@ -125,7 +127,8 @@ final class SigningInteractorTests: TuistUnitTestCase {
|
|||
|
||||
let rootDirectory = try temporaryPath()
|
||||
rootDirectoryLocator.locateStub = rootDirectory
|
||||
let keychainDirectory = rootDirectory.appending(components: Constants.derivedFolderName, Constants.signingKeychain)
|
||||
let keychainDirectory = rootDirectory
|
||||
.appending(components: Constants.DerivedDirectory.name, Constants.DerivedDirectory.signingKeychain)
|
||||
|
||||
var receivedKeychainDirectory: AbsolutePath?
|
||||
var receivedMasterKey: String?
|
||||
|
|
|
@ -81,8 +81,8 @@ final class SigningMapperTests: TuistUnitTestCase {
|
|||
path: try temporaryPath(),
|
||||
targets: [target]
|
||||
)
|
||||
let derivedDirectory = project.path.appending(component: Constants.derivedFolderName)
|
||||
let keychainPath = derivedDirectory.appending(component: Constants.signingKeychain)
|
||||
let derivedDirectory = project.path.appending(component: Constants.DerivedDirectory.name)
|
||||
let keychainPath = derivedDirectory.appending(component: Constants.DerivedDirectory.signingKeychain)
|
||||
|
||||
let expectedConfigurations: [BuildConfiguration: Configuration] = [
|
||||
BuildConfiguration(
|
||||
|
|
Loading…
Reference in New Issue