Merge remote-tracking branch 'origin/master' into signing
This commit is contained in:
commit
74ab15d04d
|
@ -7,6 +7,13 @@ update_configs:
|
|||
- 'tuist/core'
|
||||
default_labels:
|
||||
- 'dependencies'
|
||||
automerged_updates:
|
||||
- match:
|
||||
dependency_type: 'development'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_type: 'production'
|
||||
update_type: 'semver:minor'
|
||||
- package_manager: 'javascript'
|
||||
directory: '/website'
|
||||
update_schedule: 'weekly'
|
||||
|
@ -14,3 +21,10 @@ update_configs:
|
|||
- 'tuist/core'
|
||||
default_labels:
|
||||
- 'dependencies'
|
||||
automerged_updates:
|
||||
- match:
|
||||
dependency_type: 'development'
|
||||
update_type: 'semver:minor'
|
||||
- match:
|
||||
dependency_type: 'production'
|
||||
update_type: 'semver:minor'
|
||||
|
|
|
@ -45,12 +45,3 @@ jobs:
|
|||
uses: norio-nomura/action-swiftlint@3c67ce2e382be797d968883944140ffa0113f737
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
changelog:
|
||||
name: Changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Changelog Reminder
|
||||
uses: peterjgrainger/action-changelog-reminder@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -4,13 +4,31 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
|
||||
## Next
|
||||
|
||||
### Changed
|
||||
- Optimize `TargetNode`'s set operations https://github.com/tuist/tuist/pull/1095 by @kwridan
|
||||
- Optimize `BuildPhaseGenerator`'s method of detecting assets and localized files https://github.com/tuist/tuist/pull/1094 by @kwridan
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix `TargetAction` when `PROJECT_DIR` includes a space https://github.com/tuist/tuist/pull/1037 by @fortmarek
|
||||
- Fix code example compilation issues in "Project description helpers" documentation https://github.com/tuist/tuist/pull/1081 by @chojnac
|
||||
|
||||
### Added
|
||||
|
||||
- New `ProjectDescription` models for `scaffold` command https://github.com/tuist/tuist/pull/1082 by @fortmarek
|
||||
- Allow specifying Project Organization name via new `organizationName` parameter to `Project` initializer or via `Config` new GenerationOption. https://github.com/tuist/tuist/pull/1062 by @c0diq
|
||||
- `tuist lint` command https://github.com/tuist/tuist/pull/1043 by @pepibumur.
|
||||
- Add `--verbose` https://github.com/tuist/tuist/pull/1027 by @ollieatkinson.
|
||||
- `TuistInsights` target https://github.com/tuist/tuist/pull/1084 by @pepibumur.
|
||||
- Add `cloudURL` attribute to `Config` https://github.com/tuist/tuist/pull/1085 by @pepibumur.
|
||||
|
||||
### Changed
|
||||
|
||||
- Rename `TuistConfig.swift` to `Config.swift` https://github.com/tuist/tuist/pull/1083 by @pepibumur.
|
||||
- Generator update - leveraging intermediate descriptors https://github.com/tuist/tuist/pull/1007 by @kwridan
|
||||
- Note: `TuistGenerator.Generator` is now deprecated and will be removed in a future version of Tuist.
|
||||
|
||||
## 1.3.0
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@ GEM
|
|||
builder (3.2.3)
|
||||
byebug (11.1.1)
|
||||
claide (1.0.3)
|
||||
cocoapods (1.9.0)
|
||||
cocoapods (1.9.1)
|
||||
activesupport (>= 4.0.2, < 5)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.9.0)
|
||||
cocoapods-core (= 1.9.1)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.2.2, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
|
@ -37,7 +37,7 @@ GEM
|
|||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.4)
|
||||
xcodeproj (>= 1.14.0, < 2.0)
|
||||
cocoapods-core (1.9.0)
|
||||
cocoapods-core (1.9.1)
|
||||
activesupport (>= 4.0.2, < 6)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
|
|
|
@ -100,6 +100,15 @@
|
|||
"version": "0.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-log",
|
||||
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "74d7b91ceebc85daf387ebb206003f78813f71aa",
|
||||
"version": "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftPM",
|
||||
"repositoryURL": "https://github.com/apple/swift-package-manager",
|
||||
|
|
|
@ -4,7 +4,7 @@ import PackageDescription
|
|||
|
||||
let package = Package(
|
||||
name: "tuist",
|
||||
platforms: [.macOS(.v10_11)],
|
||||
platforms: [.macOS(.v10_12)],
|
||||
products: [
|
||||
.executable(name: "tuist", targets: ["tuist"]),
|
||||
.executable(name: "tuistenv", targets: ["tuistenv"]),
|
||||
|
@ -32,6 +32,7 @@ let package = Package(
|
|||
.package(url: "https://github.com/IBM-Swift/BlueSignals", .upToNextMajor(from: "1.0.21")),
|
||||
.package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "5.0.1")),
|
||||
.package(url: "https://github.com/rnine/Checksum.git", .upToNextMajor(from: "1.0.2")),
|
||||
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.2.0")),
|
||||
.package(url: "https://github.com/thii/xcbeautify.git", .upToNextMajor(from: "0.7.3")),
|
||||
.package(url: "https://github.com/krzyzanowskim/CryptoSwift", .upToNextMajor(from: "1.3.0")),
|
||||
],
|
||||
|
@ -54,7 +55,7 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
name: "TuistKit",
|
||||
dependencies: ["XcodeProj", "SPMUtility", "TuistSupport", "TuistGenerator", "TuistCache", "TuistAutomation", "ProjectDescription", "Signals", "RxSwift", "RxBlocking", "Checksum", "TuistLoader", "TuistSigning"]
|
||||
dependencies: ["XcodeProj", "SPMUtility", "TuistSupport", "TuistGenerator", "TuistCache", "TuistAutomation", "ProjectDescription", "Signals", "RxSwift", "RxBlocking", "Checksum", "TuistLoader", "TuistInsights", "TuistSigning"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TuistKitTests",
|
||||
|
@ -90,7 +91,7 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
name: "TuistSupport",
|
||||
dependencies: ["SPMUtility", "RxSwift", "RxRelay"]
|
||||
dependencies: ["SPMUtility", "RxSwift", "RxRelay", "Logging"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistSupportTesting",
|
||||
|
@ -118,7 +119,7 @@ let package = Package(
|
|||
),
|
||||
.testTarget(
|
||||
name: "TuistGeneratorIntegrationTests",
|
||||
dependencies: ["TuistGenerator", "TuistSupportTesting", "TuistCoreTesting"]
|
||||
dependencies: ["TuistGenerator", "TuistSupportTesting", "TuistCoreTesting", "TuistGeneratorTesting"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistCache",
|
||||
|
@ -148,6 +149,18 @@ let package = Package(
|
|||
name: "TuistAutomationIntegrationTests",
|
||||
dependencies: ["TuistAutomation", "TuistSupportTesting"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistInsights",
|
||||
dependencies: ["XcodeProj", "SPMUtility", "TuistCore", "TuistSupport", "XcbeautifyLib"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TuistInsightsTests",
|
||||
dependencies: ["TuistInsights", "TuistSupportTesting"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TuistInsightsIntegrationTests",
|
||||
dependencies: ["TuistInsights", "TuistSupportTesting"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistSigning",
|
||||
dependencies: ["TuistCore", "TuistSupport", "CryptoSwift"]
|
||||
|
|
|
@ -26,6 +26,7 @@ The example below shows how projects are defined with Tuist:
|
|||
import ProjectDescription
|
||||
|
||||
let project = Project(name: "App",
|
||||
organizationName: "tuist",
|
||||
targets: [
|
||||
Target(name: "App",
|
||||
platform: .iOS,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
|
||||
public struct AnalyzeAction: Equatable, Codable {
|
||||
public let configurationName: String
|
||||
|
||||
public init(configurationName: String) {
|
||||
self.configurationName = configurationName
|
||||
}
|
||||
|
||||
public init(config: PresetBuildConfiguration = .release) {
|
||||
self.init(configurationName: config.name)
|
||||
}
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
import Foundation
|
||||
|
||||
public typealias TuistConfig = Config
|
||||
|
||||
/// This model allows to configure Tuist.
|
||||
public struct TuistConfig: Codable, Equatable {
|
||||
public struct Config: Codable, Equatable {
|
||||
/// Contains options related to the project generation.
|
||||
///
|
||||
/// - xcodeProjectName(TemplateString): When passed, Tuist generates the project with the specific name on disk instead of using the project name.
|
||||
/// - organizationName(Strig): When passed, Tuist generates the project with the specific organization name.
|
||||
public enum GenerationOptions: Encodable, Decodable, Equatable {
|
||||
case xcodeProjectName(TemplateString)
|
||||
case organizationName(String)
|
||||
}
|
||||
|
||||
/// Generation options.
|
||||
|
@ -15,22 +19,28 @@ public struct TuistConfig: Codable, Equatable {
|
|||
/// List of Xcode versions that the project supports.
|
||||
public let compatibleXcodeVersions: CompatibleXcodeVersions
|
||||
|
||||
/// URL to the server that caching and insights will interact with.
|
||||
public let cloudURL: String?
|
||||
|
||||
/// Initializes the tuist cofiguration.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - compatibleXcodeVersions: .
|
||||
/// - generationOptions: List of Xcode versions that the project supports. An empty list means that
|
||||
/// - compatibleXcodeVersions: List of Xcode versions the project is compatible with.
|
||||
/// - cloudURL: URL to the server that caching and insights will interact with.
|
||||
/// - generationOptions: List of options to use when generating the project.
|
||||
public init(compatibleXcodeVersions: CompatibleXcodeVersions = .all,
|
||||
cloudURL: String? = nil,
|
||||
generationOptions: [GenerationOptions]) {
|
||||
self.generationOptions = generationOptions
|
||||
self.compatibleXcodeVersions = compatibleXcodeVersions
|
||||
self.generationOptions = generationOptions
|
||||
self.cloudURL = cloudURL
|
||||
dumpIfNeeded(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension TuistConfig.GenerationOptions {
|
||||
extension Config.GenerationOptions {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case xcodeProjectName
|
||||
case xcodeProjectName, organizationName
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
|
@ -42,6 +52,12 @@ extension TuistConfig.GenerationOptions {
|
|||
self = .xcodeProjectName(templateProjectName)
|
||||
return
|
||||
}
|
||||
if container.allKeys.contains(.organizationName), try container.decodeNil(forKey: .organizationName) == false {
|
||||
var associatedValues = try container.nestedUnkeyedContainer(forKey: .organizationName)
|
||||
let organizationName = try associatedValues.decode(String.self)
|
||||
self = .organizationName(organizationName)
|
||||
return
|
||||
}
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown enum case"))
|
||||
}
|
||||
|
||||
|
@ -52,6 +68,9 @@ extension TuistConfig.GenerationOptions {
|
|||
case let .xcodeProjectName(templateProjectName):
|
||||
var associatedValues = container.nestedUnkeyedContainer(forKey: .xcodeProjectName)
|
||||
try associatedValues.encode(templateProjectName)
|
||||
case let .organizationName(name):
|
||||
var associatedValues = container.nestedUnkeyedContainer(forKey: .organizationName)
|
||||
try associatedValues.encode(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,5 +84,9 @@ public func == (lhs: TuistConfig.GenerationOptions, rhs: TuistConfig.GenerationO
|
|||
switch (lhs, rhs) {
|
||||
case let (.xcodeProjectName(lhs), .xcodeProjectName(rhs)):
|
||||
return lhs.rawString == rhs.rawString
|
||||
case let (.organizationName(lhs), .organizationName(rhs)):
|
||||
return lhs == rhs
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -66,6 +66,8 @@ extension FileElement: ExpressibleByStringLiteral {
|
|||
}
|
||||
}
|
||||
|
||||
extension FileElement: ExpressibleByStringInterpolation {}
|
||||
|
||||
extension Array: ExpressibleByUnicodeScalarLiteral where Element == FileElement {
|
||||
public typealias UnicodeScalarLiteralType = String
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Foundation
|
||||
|
||||
public struct Path: Codable, ExpressibleByStringLiteral, Equatable {
|
||||
public struct Path: Codable, ExpressibleByStringLiteral, ExpressibleByStringInterpolation, Equatable {
|
||||
public enum PathType: String, Codable {
|
||||
case relativeToCurrentFile
|
||||
case relativeToManifest
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import Foundation
|
||||
|
||||
public struct ProfileAction: Equatable, Codable {
|
||||
public let configurationName: String
|
||||
public let executable: TargetReference?
|
||||
public let arguments: Arguments?
|
||||
|
||||
public init(configurationName: String,
|
||||
executable: TargetReference? = nil,
|
||||
arguments: Arguments? = nil) {
|
||||
self.configurationName = configurationName
|
||||
self.executable = executable
|
||||
self.arguments = arguments
|
||||
}
|
||||
|
||||
public init(config: PresetBuildConfiguration = .release,
|
||||
executable: TargetReference? = nil,
|
||||
arguments: Arguments? = nil) {
|
||||
self.init(configurationName: config.name,
|
||||
executable: executable,
|
||||
arguments: arguments)
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import Foundation
|
|||
|
||||
public struct Project: Codable, Equatable {
|
||||
public let name: String
|
||||
public let organizationName: String?
|
||||
public let packages: [Package]
|
||||
public let targets: [Target]
|
||||
public let schemes: [Scheme]
|
||||
|
@ -11,12 +12,14 @@ public struct Project: Codable, Equatable {
|
|||
public let additionalFiles: [FileElement]
|
||||
|
||||
public init(name: String,
|
||||
organizationName: String? = nil,
|
||||
packages: [Package] = [],
|
||||
settings: Settings? = nil,
|
||||
targets: [Target] = [],
|
||||
schemes: [Scheme] = [],
|
||||
additionalFiles: [FileElement] = []) {
|
||||
self.name = name
|
||||
self.organizationName = organizationName
|
||||
self.packages = packages
|
||||
self.targets = targets
|
||||
self.schemes = schemes
|
||||
|
|
|
@ -9,18 +9,24 @@ public struct Scheme: Equatable, Codable {
|
|||
public let testAction: TestAction?
|
||||
public let runAction: RunAction?
|
||||
public let archiveAction: ArchiveAction?
|
||||
public let profileAction: ProfileAction?
|
||||
public let analyzeAction: AnalyzeAction?
|
||||
|
||||
public init(name: String,
|
||||
shared: Bool = true,
|
||||
buildAction: BuildAction? = nil,
|
||||
testAction: TestAction? = nil,
|
||||
runAction: RunAction? = nil,
|
||||
archiveAction: ArchiveAction? = nil) {
|
||||
archiveAction: ArchiveAction? = nil,
|
||||
profileAction: ProfileAction? = nil,
|
||||
analyzeAction: AnalyzeAction? = nil) {
|
||||
self.name = name
|
||||
self.shared = shared
|
||||
self.buildAction = buildAction
|
||||
self.testAction = testAction
|
||||
self.runAction = runAction
|
||||
self.archiveAction = archiveAction
|
||||
self.profileAction = profileAction
|
||||
self.analyzeAction = analyzeAction
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// MARK: - FileList
|
||||
|
||||
/// A model to refer to source files that supports passing compiler flags.
|
||||
public struct SourceFileGlob: ExpressibleByStringLiteral, Codable, Equatable {
|
||||
public struct SourceFileGlob: ExpressibleByStringLiteral, ExpressibleByStringInterpolation, Codable, Equatable {
|
||||
/// Relative glob pattern.
|
||||
public let glob: Path
|
||||
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
import Foundation
|
||||
|
||||
/// Template manifest - used with `tuist scaffold`
|
||||
public struct Template: Codable, Equatable {
|
||||
/// Description of template
|
||||
public let description: String
|
||||
/// Attributes to be passed to template
|
||||
public let attributes: [Attribute]
|
||||
/// Files to generate
|
||||
public let files: [File]
|
||||
/// Directories to generate
|
||||
public let directories: [String]
|
||||
|
||||
public init(description: String,
|
||||
attributes: [Attribute] = [],
|
||||
files: [File] = [],
|
||||
directories: [String] = [],
|
||||
script _: String? = nil) {
|
||||
self.description = description
|
||||
self.attributes = attributes
|
||||
self.files = files
|
||||
self.directories = directories
|
||||
dumpIfNeeded(self)
|
||||
}
|
||||
|
||||
/// Enum containing information about how to generate file
|
||||
public enum Contents: Codable, Equatable {
|
||||
/// String Contents is defined in `Template.swift` and contains a simple `String`
|
||||
/// Can not contain any additional logic apart from plain `String` from `arguments`
|
||||
case string(String)
|
||||
/// File content is defined in a different file from `Template.swift`
|
||||
/// Can contain additional logic and anything that is defined in `ProjectDescriptionHelpers`
|
||||
case file(String)
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case value
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let value = try container.decode(String.self, forKey: .value)
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
if type == "string" {
|
||||
self = .string(value)
|
||||
} else if type == "file" {
|
||||
self = .file(value)
|
||||
} else {
|
||||
fatalError("Argument '\(type)' not supported")
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
switch self {
|
||||
case let .string(contents):
|
||||
try container.encode("string", forKey: .type)
|
||||
try container.encode(contents, forKey: .value)
|
||||
case let .file(path):
|
||||
try container.encode("file", forKey: .type)
|
||||
try container.encode(path, forKey: .value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// File description for generating
|
||||
public struct File: Codable, Equatable {
|
||||
public let path: String
|
||||
public let contents: Contents
|
||||
|
||||
public init(path: String, contents: Contents) {
|
||||
self.path = path
|
||||
self.contents = contents
|
||||
}
|
||||
}
|
||||
|
||||
/// Attribute to be passed to `tuist scaffold` for generating with `Template`
|
||||
public enum Attribute: Codable, Equatable {
|
||||
/// Required attribute with a given name
|
||||
case required(String)
|
||||
/// Optional attribute with a given name and a default value used when attribute not provided by user
|
||||
case optional(String, default: String)
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case name
|
||||
case `default`
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let name = try container.decode(String.self, forKey: .name)
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
if type == "required" {
|
||||
self = .required(name)
|
||||
} else if type == "optional" {
|
||||
let defaultValue = try container.decode(String.self, forKey: .default)
|
||||
self = .optional(name, default: defaultValue)
|
||||
} else {
|
||||
fatalError("Argument '\(type)' not supported")
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
switch self {
|
||||
case let .required(name):
|
||||
try container.encode("required", forKey: .type)
|
||||
try container.encode(name, forKey: .name)
|
||||
case let .optional(name, default: defaultValue):
|
||||
try container.encode("optional", forKey: .type)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(defaultValue, forKey: .default)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Template.File {
|
||||
/// - Parameters:
|
||||
/// - path: Path where to generate file
|
||||
/// - contents: String Contents
|
||||
/// - Returns: `Template.File` that is `.string`
|
||||
static func string(path: String, contents: String) -> Template.File {
|
||||
Template.File(path: path, contents: .string(contents))
|
||||
}
|
||||
|
||||
/// - Parameters:
|
||||
/// - path: Path where to generate file
|
||||
/// - templatePath: Path of file where the template is defined
|
||||
/// - Returns: `Template.File` that is `.file`
|
||||
static func file(path: String, templatePath: String) -> Template.File {
|
||||
Template.File(path: path, contents: .file(templatePath))
|
||||
}
|
||||
}
|
||||
|
||||
public extension String.StringInterpolation {
|
||||
mutating func appendInterpolation(_ value: Template.Attribute) {
|
||||
switch value {
|
||||
case let .required(name), let .optional(name, default: _):
|
||||
appendInterpolation("{{ \(name) }}")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import TuistSupport
|
||||
let logger = Logger(label: "io.tuist.cache")
|
|
@ -80,7 +80,7 @@ public final class XCFrameworkBuilder: XCFrameworkBuilding {
|
|||
let outputDirectory = try TemporaryDirectory(removeTreeOnDeinit: false)
|
||||
let temporaryPath = try TemporaryDirectory(removeTreeOnDeinit: false)
|
||||
|
||||
Printer.shared.print(section: "Building .xcframework for \(target.name)")
|
||||
logger.notice("Building .xcframework for \(target.name)", metadata: .section)
|
||||
|
||||
// Build for the device
|
||||
// Without the BUILD_LIBRARY_FOR_DISTRIBUTION argument xcodebuild doesn't generate the .swiftinterface file
|
||||
|
@ -95,7 +95,7 @@ public final class XCFrameworkBuilder: XCFrameworkBuilding {
|
|||
.buildSetting("SKIP_INSTALL", "NO"),
|
||||
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"))
|
||||
.do(onSubscribed: {
|
||||
Printer.shared.print(subsection: "Building \(target.name) for device")
|
||||
logger.notice("Building \(target.name) for device", metadata: .subsection)
|
||||
})
|
||||
|
||||
// Build for the simulator
|
||||
|
@ -113,7 +113,7 @@ public final class XCFrameworkBuilder: XCFrameworkBuilding {
|
|||
.buildSetting("SKIP_INSTALL", "NO"),
|
||||
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"))
|
||||
.do(onSubscribed: {
|
||||
Printer.shared.print(subsection: "Building \(target.name) for simulator")
|
||||
logger.notice("Building \(target.name) for simulator", metadata: .subsection)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ public final class XCFrameworkBuilder: XCFrameworkBuilding {
|
|||
let xcframeworkPath = outputDirectory.path.appending(component: "\(target.productName).xcframework")
|
||||
let xcframeworkObservable = xcodeBuildController.createXCFramework(frameworks: frameworkpaths, output: xcframeworkPath)
|
||||
.do(onSubscribed: {
|
||||
Printer.shared.print(subsection: "Exporting xcframework for \(target.platform.caseValue)")
|
||||
logger.notice("Exporting xcframework for \(target.platform.caseValue)", metadata: .subsection)
|
||||
})
|
||||
|
||||
return deviceArchiveObservable
|
||||
|
|
|
@ -4,7 +4,7 @@ import TuistSupport
|
|||
|
||||
public extension Observable where Element == SystemEvent<XcodeBuildOutput> {
|
||||
func printFormattedOutput() -> Observable<SystemEvent<XcodeBuildOutput>> {
|
||||
self.do(onNext: { event in
|
||||
`do`(onNext: { event in
|
||||
switch event {
|
||||
case let .standardError(error):
|
||||
let string = error.formatted ?? error.raw
|
||||
|
|
|
@ -11,12 +11,12 @@ public protocol GraphLoading: AnyObject {
|
|||
/// - Parameter path: Path to the directory that contains the workspace.
|
||||
func loadWorkspace(path: AbsolutePath) throws -> (Graph, Workspace)
|
||||
|
||||
/// Loads the TuistConfig.
|
||||
/// Loads the configuration.
|
||||
///
|
||||
/// - Parameter path: Directory from which look up and load the TuistConfig.
|
||||
/// - Returns: Loaded TuistConfig object.
|
||||
/// - Throws: An error if the TuistConfig.swift can't be parsed.
|
||||
func loadTuistConfig(path: AbsolutePath) throws -> TuistConfig
|
||||
/// - Parameter path: Directory from which look up and load the Config.
|
||||
/// - Returns: Loaded Config object.
|
||||
/// - Throws: An error if the Config.swift can't be parsed.
|
||||
func loadConfig(path: AbsolutePath) throws -> Config
|
||||
}
|
||||
|
||||
public class GraphLoader: GraphLoading {
|
||||
|
@ -76,15 +76,15 @@ public class GraphLoader: GraphLoading {
|
|||
return (graph, workspace)
|
||||
}
|
||||
|
||||
public func loadTuistConfig(path: AbsolutePath) throws -> TuistConfig {
|
||||
public func loadConfig(path: AbsolutePath) throws -> Config {
|
||||
let cache = GraphLoaderCache()
|
||||
|
||||
if let tuistConfig = cache.tuistConfig(path) {
|
||||
return tuistConfig
|
||||
if let config = cache.config(path) {
|
||||
return config
|
||||
} else {
|
||||
let tuistConfig = try modelLoader.loadTuistConfig(at: path)
|
||||
cache.add(tuistConfig: tuistConfig, path: path)
|
||||
return tuistConfig
|
||||
let config = try modelLoader.loadConfig(at: path)
|
||||
cache.add(config: config, path: path)
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ public class GraphLoaderCache: GraphLoaderCaching {
|
|||
|
||||
// MARK: - GraphLoaderCaching
|
||||
|
||||
var tuistConfigs: [AbsolutePath: TuistConfig] = [:]
|
||||
var configs: [AbsolutePath: Config] = [:]
|
||||
public var projects: [AbsolutePath: Project] = [:]
|
||||
public var packages: [AbsolutePath: [PackageNode]] = [:]
|
||||
public var precompiledNodes: [AbsolutePath: PrecompiledNode] = [:]
|
||||
|
@ -50,12 +50,12 @@ public class GraphLoaderCache: GraphLoaderCaching {
|
|||
packageNodes[package.path] = package
|
||||
}
|
||||
|
||||
public func tuistConfig(_ path: AbsolutePath) -> TuistConfig? {
|
||||
tuistConfigs[path]
|
||||
public func config(_ path: AbsolutePath) -> Config? {
|
||||
configs[path]
|
||||
}
|
||||
|
||||
public func add(tuistConfig: TuistConfig, path: AbsolutePath) {
|
||||
tuistConfigs[path] = tuistConfig
|
||||
public func add(config: Config, path: AbsolutePath) {
|
||||
configs[path] = config
|
||||
}
|
||||
|
||||
public func project(_ path: AbsolutePath) -> Project? {
|
||||
|
|
|
@ -48,7 +48,7 @@ public class TargetNode: GraphNode {
|
|||
return false
|
||||
}
|
||||
return path == otherTagetNode.path
|
||||
&& target == otherTagetNode.target
|
||||
&& name == otherTagetNode.name
|
||||
}
|
||||
|
||||
// MARK: - Encodable
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import TuistSupport
|
||||
let logger = Logger(label: "io.tuist.core")
|
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
|
||||
public struct AnalyzeAction: Equatable {
|
||||
// MARK: - Attributes
|
||||
|
||||
public let configurationName: String
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(configurationName: String) {
|
||||
self.configurationName = configurationName
|
||||
}
|
||||
}
|
|
@ -3,12 +3,13 @@ import Foundation
|
|||
import TuistSupport
|
||||
|
||||
/// This model allows to configure Tuist.
|
||||
public struct TuistConfig: Equatable, Hashable {
|
||||
public struct Config: Equatable, Hashable {
|
||||
/// Contains options related to the project generation.
|
||||
///
|
||||
/// - xcodeProjectName: Name used for the Xcode project
|
||||
public enum GenerationOption: Hashable, Equatable {
|
||||
case xcodeProjectName(String)
|
||||
case organizationName(String)
|
||||
}
|
||||
|
||||
/// Generation options.
|
||||
|
@ -17,20 +18,25 @@ public struct TuistConfig: Equatable, Hashable {
|
|||
/// List of Xcode versions the project or set of projects is compatible with.
|
||||
public let compatibleXcodeVersions: CompatibleXcodeVersions
|
||||
|
||||
/// URL to the server that caching and insights will interact with.
|
||||
public let cloudURL: URL?
|
||||
|
||||
/// Returns the default Tuist configuration.
|
||||
public static var `default`: TuistConfig {
|
||||
TuistConfig(compatibleXcodeVersions: .all,
|
||||
generationOptions: [])
|
||||
public static var `default`: Config {
|
||||
Config(compatibleXcodeVersions: .all, cloudURL: nil, generationOptions: [])
|
||||
}
|
||||
|
||||
/// Initializes the tuist cofiguration.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - compatibleXcodeVersions: List of Xcode versions the project or set of projects is compatible with.
|
||||
/// - cloudURL: URL to the server that caching and insights will interact with.
|
||||
/// - generationOptions: Generation options.
|
||||
public init(compatibleXcodeVersions: CompatibleXcodeVersions,
|
||||
cloudURL: URL?,
|
||||
generationOptions: [GenerationOption]) {
|
||||
self.compatibleXcodeVersions = compatibleXcodeVersions
|
||||
self.cloudURL = cloudURL
|
||||
self.generationOptions = generationOptions
|
||||
}
|
||||
|
||||
|
@ -38,11 +44,7 @@ public struct TuistConfig: Equatable, Hashable {
|
|||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(generationOptions)
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
public static func == (lhs: TuistConfig, rhs: TuistConfig) -> Bool {
|
||||
lhs.generationOptions == rhs.generationOptions
|
||||
hasher.combine(cloudURL)
|
||||
hasher.combine(compatibleXcodeVersions)
|
||||
}
|
||||
}
|
|
@ -42,13 +42,14 @@ public extension Array where Element == LintingIssue {
|
|||
let errorIssues = filter { $0.severity == .error }
|
||||
let warningIssues = filter { $0.severity == .warning }
|
||||
|
||||
warningIssues.forEach { issue in
|
||||
Printer.shared.print(warning: "\(issue.description)")
|
||||
for issue in warningIssues {
|
||||
logger.warning("\(issue.description)")
|
||||
}
|
||||
|
||||
errorIssues.forEach { issue in
|
||||
Printer.shared.print(errorMessage: "\(issue.description)")
|
||||
for issue in errorIssues {
|
||||
logger.error("\(issue.description)")
|
||||
}
|
||||
|
||||
if !errorIssues.isEmpty { throw LintingError() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import Foundation
|
||||
|
||||
public struct ProfileAction: Equatable {
|
||||
// MARK: - Attributes
|
||||
|
||||
public let configurationName: String
|
||||
public let executable: TargetReference?
|
||||
public let arguments: Arguments?
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(configurationName: String,
|
||||
executable: TargetReference? = nil,
|
||||
arguments: Arguments? = nil) {
|
||||
self.configurationName = configurationName
|
||||
self.executable = executable
|
||||
self.arguments = arguments
|
||||
}
|
||||
}
|
|
@ -11,6 +11,9 @@ public struct Project: Equatable, CustomStringConvertible {
|
|||
/// Project name.
|
||||
public let name: String
|
||||
|
||||
/// Organization name.
|
||||
public let organizationName: String?
|
||||
|
||||
/// Project file name.
|
||||
public let fileName: String
|
||||
|
||||
|
@ -39,6 +42,7 @@ public struct Project: Equatable, CustomStringConvertible {
|
|||
/// - Parameters:
|
||||
/// - path: Path to the folder that contains the project manifest.
|
||||
/// - name: Project name.
|
||||
/// - organizationName: Organization name.
|
||||
/// - settings: The settings to apply at the project level
|
||||
/// - filesGroup: The root group to place project files within
|
||||
/// - targets: The project targets
|
||||
|
@ -46,6 +50,7 @@ public struct Project: Equatable, CustomStringConvertible {
|
|||
/// *(Those won't be included in any build phases)*
|
||||
public init(path: AbsolutePath,
|
||||
name: String,
|
||||
organizationName: String? = nil,
|
||||
fileName: String? = nil,
|
||||
settings: Settings,
|
||||
filesGroup: ProjectGroup,
|
||||
|
@ -55,6 +60,7 @@ public struct Project: Equatable, CustomStringConvertible {
|
|||
additionalFiles: [FileElement] = []) {
|
||||
self.path = path
|
||||
self.name = name
|
||||
self.organizationName = organizationName
|
||||
self.fileName = fileName ?? name
|
||||
self.targets = targets
|
||||
self.packages = packages
|
||||
|
|
|
@ -10,6 +10,8 @@ public struct Scheme: Equatable {
|
|||
public let testAction: TestAction?
|
||||
public let runAction: RunAction?
|
||||
public let archiveAction: ArchiveAction?
|
||||
public let profileAction: ProfileAction?
|
||||
public let analyzeAction: AnalyzeAction?
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
|
@ -18,13 +20,17 @@ public struct Scheme: Equatable {
|
|||
buildAction: BuildAction? = nil,
|
||||
testAction: TestAction? = nil,
|
||||
runAction: RunAction? = nil,
|
||||
archiveAction: ArchiveAction? = nil) {
|
||||
archiveAction: ArchiveAction? = nil,
|
||||
profileAction: ProfileAction? = nil,
|
||||
analyzeAction: AnalyzeAction? = nil) {
|
||||
self.name = name
|
||||
self.shared = shared
|
||||
self.buildAction = buildAction
|
||||
self.testAction = testAction
|
||||
self.runAction = runAction
|
||||
self.archiveAction = archiveAction
|
||||
self.profileAction = profileAction
|
||||
self.analyzeAction = analyzeAction
|
||||
}
|
||||
|
||||
public func targetDependencies() -> [TargetReference] {
|
||||
|
@ -39,6 +45,7 @@ public struct Scheme: Equatable {
|
|||
runAction?.executable.map { [$0] },
|
||||
archiveAction?.preActions.compactMap(\.target),
|
||||
archiveAction?.postActions.compactMap(\.target),
|
||||
profileAction?.executable.map { [$0] },
|
||||
]
|
||||
|
||||
let targets = targetSources.compactMap { $0 }.flatMap { $0 }.uniqued()
|
||||
|
|
|
@ -25,10 +25,10 @@ public protocol GeneratorModelLoading {
|
|||
/// - Throws: Error encountered during the loading process (e.g. Missing workspace)
|
||||
func loadWorkspace(at path: AbsolutePath) throws -> Workspace
|
||||
|
||||
/// Load a TusitConfig model at the specified path
|
||||
/// Load a Config model at the specified path
|
||||
///
|
||||
/// - Parameter path: The absolute path for the tuistconfig model to load
|
||||
/// - Returns: The tuistconfig loaded from the specified path
|
||||
/// - Throws: Error encountered during the loading process (e.g. Missing tuistconfig)
|
||||
func loadTuistConfig(at path: AbsolutePath) throws -> TuistConfig
|
||||
/// - Parameter path: The absolute path for the Config model to load
|
||||
/// - Returns: The config loaded from the specified path
|
||||
/// - Throws: Error encountered during the loading process (e.g. Missing Config file)
|
||||
func loadConfig(at path: AbsolutePath) throws -> Config
|
||||
}
|
||||
|
|
|
@ -47,17 +47,17 @@ public protocol GraphLoaderCaching: AnyObject {
|
|||
/// - name: Name of the target.
|
||||
func targetNode(_ path: AbsolutePath, name: String) -> TargetNode?
|
||||
|
||||
// MARK: - TuistConfig
|
||||
// MARK: - Config
|
||||
|
||||
/// It returns a Tuist configuration if it exists at the given directory.
|
||||
/// - Parameter path: Path to the directory that contains the TuistConfig.
|
||||
func tuistConfig(_ path: AbsolutePath) -> TuistConfig?
|
||||
/// - Parameter path: Path to the directory that contains the Config.
|
||||
func config(_ path: AbsolutePath) -> Config?
|
||||
|
||||
/// Caches a TuistConfig representation.
|
||||
/// Caches a Config representation.
|
||||
/// - Parameters:
|
||||
/// - tuistConfig: Tuist configuration.
|
||||
/// - config: Tuist configuration.
|
||||
/// - path: Path to the directory that contains th
|
||||
func add(tuistConfig: TuistConfig, path: AbsolutePath)
|
||||
func add(config: Config, path: AbsolutePath)
|
||||
|
||||
// MARK: - CocoaPods
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ public final class MockGraphLoader: GraphLoading {
|
|||
return try loadWorkspaceStub?(path) ?? (Graph.test(), Workspace.test())
|
||||
}
|
||||
|
||||
public var loadTuistConfigStub: ((AbsolutePath) throws -> (TuistConfig))?
|
||||
public func loadTuistConfig(path: AbsolutePath) throws -> TuistConfig {
|
||||
try loadTuistConfigStub?(path) ?? TuistConfig.test()
|
||||
public var loadConfigStub: ((AbsolutePath) throws -> (Config))?
|
||||
public func loadConfig(path: AbsolutePath) throws -> Config {
|
||||
try loadConfigStub?(path) ?? Config.test()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ public final class MockGraphLoaderCache: GraphLoaderCaching {
|
|||
var precompiledNodeStub: ((AbsolutePath) -> PrecompiledNode?)?
|
||||
var addTargetNodeArgs: [TargetNode] = []
|
||||
var targetNodeStub: ((AbsolutePath, String) -> TargetNode?)?
|
||||
var tuistConfigStub: [AbsolutePath: TuistConfig] = [:]
|
||||
var addTuistConfigArgs: [(tuistConfig: TuistConfig, path: AbsolutePath)] = []
|
||||
var configStub: [AbsolutePath: Config] = [:]
|
||||
var addConfigArgs: [(config: Config, path: AbsolutePath)] = []
|
||||
public var cocoapodsNodes: [AbsolutePath: CocoaPodsNode] = [:]
|
||||
var cocoapodsStub: [AbsolutePath: CocoaPodsNode] = [:]
|
||||
var addCococaPodsArgs: [CocoaPodsNode] = []
|
||||
|
@ -42,12 +42,12 @@ public final class MockGraphLoaderCache: GraphLoaderCaching {
|
|||
addCococaPodsArgs.append(cocoapods)
|
||||
}
|
||||
|
||||
public func tuistConfig(_ path: AbsolutePath) -> TuistConfig? {
|
||||
tuistConfigStub[path]
|
||||
public func config(_ path: AbsolutePath) -> Config? {
|
||||
configStub[path]
|
||||
}
|
||||
|
||||
public func add(tuistConfig: TuistConfig, path: AbsolutePath) {
|
||||
addTuistConfigArgs.append((tuistConfig: tuistConfig, path: path))
|
||||
public func add(config: Config, path: AbsolutePath) {
|
||||
addConfigArgs.append((config: config, path: path))
|
||||
}
|
||||
|
||||
public func project(_ path: AbsolutePath) -> Project? {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
@testable import TuistCore
|
||||
|
||||
public extension AnalyzeAction {
|
||||
static func test(configurationName: String = "Beta Release") -> AnalyzeAction {
|
||||
AnalyzeAction(configurationName: configurationName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
@testable import TuistCore
|
||||
|
||||
public extension Config {
|
||||
static func test(compatibleXcodeVersions: CompatibleXcodeVersions = .all,
|
||||
cloudURL: URL? = nil,
|
||||
generationOptions: [GenerationOption] = []) -> Config {
|
||||
Config(compatibleXcodeVersions: compatibleXcodeVersions,
|
||||
cloudURL: cloudURL,
|
||||
generationOptions: generationOptions)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
@testable import TuistCore
|
||||
|
||||
public extension ProfileAction {
|
||||
static func test(configurationName: String = "Beta Release",
|
||||
executable: TargetReference? = TargetReference(projectPath: "/Project", name: "App"),
|
||||
arguments: Arguments? = Arguments.test()) -> ProfileAction {
|
||||
ProfileAction(configurationName: configurationName,
|
||||
executable: executable,
|
||||
arguments: arguments)
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import Foundation
|
|||
public extension Project {
|
||||
static func test(path: AbsolutePath = AbsolutePath("/Project"),
|
||||
name: String = "Project",
|
||||
organizationName: String? = nil,
|
||||
fileName: String? = nil,
|
||||
settings: Settings = Settings.test(),
|
||||
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||
|
@ -14,6 +15,7 @@ public extension Project {
|
|||
additionalFiles: [FileElement] = []) -> Project {
|
||||
Project(path: path,
|
||||
name: name,
|
||||
organizationName: organizationName,
|
||||
fileName: fileName,
|
||||
settings: settings,
|
||||
filesGroup: filesGroup,
|
||||
|
@ -25,6 +27,7 @@ public extension Project {
|
|||
|
||||
static func empty(path: AbsolutePath = AbsolutePath("/test/"),
|
||||
name: String = "Project",
|
||||
organizationName: String? = nil,
|
||||
settings: Settings = .default,
|
||||
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||
targets: [Target] = [],
|
||||
|
@ -33,6 +36,7 @@ public extension Project {
|
|||
additionalFiles: [FileElement] = []) -> Project {
|
||||
Project(path: path,
|
||||
name: name,
|
||||
organizationName: organizationName,
|
||||
settings: settings,
|
||||
filesGroup: filesGroup,
|
||||
targets: targets,
|
||||
|
|
|
@ -8,12 +8,16 @@ public extension Scheme {
|
|||
buildAction: BuildAction? = BuildAction.test(),
|
||||
testAction: TestAction? = TestAction.test(),
|
||||
runAction: RunAction? = RunAction.test(),
|
||||
archiveAction: ArchiveAction? = ArchiveAction.test()) -> Scheme {
|
||||
archiveAction: ArchiveAction? = ArchiveAction.test(),
|
||||
profileAction: ProfileAction? = ProfileAction.test(),
|
||||
analyzeAction: AnalyzeAction? = AnalyzeAction.test()) -> Scheme {
|
||||
Scheme(name: name,
|
||||
shared: shared,
|
||||
buildAction: buildAction,
|
||||
testAction: testAction,
|
||||
runAction: runAction,
|
||||
archiveAction: archiveAction)
|
||||
archiveAction: archiveAction,
|
||||
profileAction: profileAction,
|
||||
analyzeAction: analyzeAction)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
@testable import TuistCore
|
||||
|
||||
public extension TuistConfig {
|
||||
static func test(compatibleXcodeVersions: CompatibleXcodeVersions = .all,
|
||||
generationOptions: [GenerationOption] = []) -> TuistConfig {
|
||||
TuistConfig(compatibleXcodeVersions: compatibleXcodeVersions,
|
||||
generationOptions: generationOptions)
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ final class BundleCommand: Command {
|
|||
init(parser: ArgumentParser,
|
||||
versionsController: VersionsControlling,
|
||||
installer: Installing) {
|
||||
_ = parser.add(subparser: BundleCommand.command, overview: BundleCommand.overview)
|
||||
let subParser = parser.add(subparser: BundleCommand.command, overview: BundleCommand.overview)
|
||||
self.versionsController = versionsController
|
||||
self.installer = installer
|
||||
}
|
||||
|
@ -66,13 +66,13 @@ final class BundleCommand: Command {
|
|||
}
|
||||
|
||||
let version = try String(contentsOf: versionFilePath.url)
|
||||
Printer.shared.print(section: "Bundling the version \(version) in the directory \(binFolderPath.pathString)")
|
||||
logger.notice("Bundling the version \(version) in the directory \(binFolderPath.pathString)", metadata: .section)
|
||||
|
||||
let versionPath = versionsController.path(version: version)
|
||||
|
||||
// Installing
|
||||
if !FileHandler.shared.exists(versionPath) {
|
||||
Printer.shared.print("Version \(version) not available locally. Installing...")
|
||||
logger.notice("Version \(version) not available locally. Installing...")
|
||||
try installer.install(version: version, force: false)
|
||||
}
|
||||
|
||||
|
@ -82,6 +82,6 @@ final class BundleCommand: Command {
|
|||
}
|
||||
try FileHandler.shared.copy(from: versionPath, to: binFolderPath)
|
||||
|
||||
Printer.shared.print(success: "tuist bundled successfully at \(binFolderPath.pathString)")
|
||||
logger.notice("tuist bundled successfully at \(binFolderPath.pathString)", metadata: .success)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,6 @@ public final class CommandRegistry {
|
|||
// MARK: - Static
|
||||
|
||||
static func processArguments() -> [String] {
|
||||
Array(ProcessInfo.processInfo.arguments)
|
||||
CommandRunner.arguments()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,9 +63,9 @@ class CommandRunner: CommandRunning {
|
|||
|
||||
switch resolvedVersion {
|
||||
case let .bin(path):
|
||||
Printer.shared.print("Using bundled version at path \(path.pathString)")
|
||||
logger.notice("Using bundled version at path \(path.pathString)")
|
||||
case let .versionFile(path, value):
|
||||
Printer.shared.print("Using version \(value) defined at \(path.pathString)")
|
||||
logger.notice("Using version \(value) defined at \(path.pathString)")
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ class CommandRunner: CommandRunning {
|
|||
|
||||
func runVersion(_ version: String) throws {
|
||||
if !versionsController.versions().contains(where: { $0.description == version }) {
|
||||
Printer.shared.print("Version \(version) not found locally. Installing...")
|
||||
logger.notice("Version \(version) not found locally. Installing...")
|
||||
try installer.install(version: version, force: false)
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,13 @@ class CommandRunner: CommandRunning {
|
|||
}
|
||||
|
||||
func runAtPath(_ path: AbsolutePath) throws {
|
||||
var args = [path.appending(component: Constants.binName).pathString]
|
||||
var args: [String] = []
|
||||
|
||||
if CommandLine.arguments.contains("--verbose") {
|
||||
args.append("TUIST_VERBOSE=true")
|
||||
}
|
||||
|
||||
args.append(path.appending(component: Constants.binName).pathString)
|
||||
args.append(contentsOf: Array(arguments().dropFirst()))
|
||||
|
||||
var environment = ProcessInfo.processInfo.environment
|
||||
|
@ -125,6 +131,6 @@ class CommandRunner: CommandRunning {
|
|||
// MARK: - Static
|
||||
|
||||
static func arguments() -> [String] {
|
||||
Array(ProcessInfo.processInfo.arguments)
|
||||
Array(ProcessInfo.processInfo.arguments).filter { $0 != "--verbose" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ final class InstallCommand: Command {
|
|||
let version = result.get(versionArgument)!
|
||||
let versions = versionsController.versions().map { $0.description }
|
||||
if versions.contains(version) {
|
||||
Printer.shared.print(warning: "Version \(version) already installed, skipping")
|
||||
logger.warning("Version \(version) already installed, skipping")
|
||||
return
|
||||
}
|
||||
try installer.install(version: version, force: force)
|
||||
|
|
|
@ -46,19 +46,19 @@ class LocalCommand: Command {
|
|||
// MARK: - Fileprivate
|
||||
|
||||
private func printLocalVersions() throws {
|
||||
Printer.shared.print(section: "The following versions are available in the local environment:")
|
||||
logger.notice("The following versions are available in the local environment:", metadata: .section)
|
||||
let versions = versionController.semverVersions()
|
||||
let output = versions.sorted().reversed().map { "- \($0)" }.joined(separator: "\n")
|
||||
Printer.shared.print("\(output)")
|
||||
logger.notice("\(output)")
|
||||
}
|
||||
|
||||
private func createVersionFile(version: String) throws {
|
||||
let currentPath = FileHandler.shared.currentPath
|
||||
Printer.shared.print(section: "Generating \(Constants.versionFileName) file with version \(version)")
|
||||
logger.notice("Generating \(Constants.versionFileName) file with version \(version)", metadata: .section)
|
||||
let tuistVersionPath = currentPath.appending(component: Constants.versionFileName)
|
||||
try "\(version)".write(to: URL(fileURLWithPath: tuistVersionPath.pathString),
|
||||
atomically: true,
|
||||
encoding: .utf8)
|
||||
Printer.shared.print(success: "File generated at path \(tuistVersionPath.pathString)")
|
||||
logger.notice("File generated at path \(tuistVersionPath.pathString)", metadata: .success)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,9 +40,9 @@ final class UninstallCommand: Command {
|
|||
let versions = versionsController.versions().map { $0.description }
|
||||
if versions.contains(version) {
|
||||
try versionsController.uninstall(version: version)
|
||||
Printer.shared.print(success: "Version \(version) uninstalled")
|
||||
logger.notice("Version \(version) uninstalled", metadata: .success)
|
||||
} else {
|
||||
Printer.shared.print(warning: "Version \(version) cannot be uninstalled because it's not installed")
|
||||
logger.warning("Version \(version) cannot be uninstalled because it's not installed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ final class UpdateCommand: Command {
|
|||
/// - Throws: An error if the update process fails.
|
||||
func run(with result: ArgumentParser.Result) throws {
|
||||
let force = result.get(forceArgument) ?? false
|
||||
Printer.shared.print(section: "Checking for updates...")
|
||||
logger.notice("Checking for updates...", metadata: .section)
|
||||
try updater.update(force: force)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,6 @@ class VersionCommand: NSObject, Command {
|
|||
// MARK: - Command
|
||||
|
||||
func run(with _: ArgumentParser.Result) {
|
||||
Printer.shared.print("\(Constants.version)")
|
||||
logger.notice("\(Constants.version)")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ final class Installer: Installing {
|
|||
func install(version: String, temporaryDirectory: TemporaryDirectory, force: Bool = false) throws {
|
||||
// We ignore the Swift version and install from the soruce code
|
||||
if force {
|
||||
Printer.shared.print("Forcing the installation of \(version) from the source code")
|
||||
logger.notice("Forcing the installation of \(version) from the source code")
|
||||
try installFromSource(version: version,
|
||||
temporaryDirectory: temporaryDirectory)
|
||||
return
|
||||
|
@ -102,16 +102,17 @@ final class Installer: Installing {
|
|||
try versionsController.install(version: version, installation: { installationDirectory in
|
||||
|
||||
// Download bundle
|
||||
Printer.shared.print("Downloading version \(version)")
|
||||
logger.notice("Downloading version \(version)")
|
||||
|
||||
let downloadPath = temporaryDirectory.path.appending(component: Constants.bundleName)
|
||||
try System.shared.run("/usr/bin/curl", "-LSs", "--output", downloadPath.pathString, bundleURL.absoluteString)
|
||||
|
||||
// Unzip
|
||||
Printer.shared.print("Installing...")
|
||||
logger.notice("Installing...")
|
||||
try System.shared.run("/usr/bin/unzip", "-q", downloadPath.pathString, "-d", installationDirectory.pathString)
|
||||
|
||||
try createTuistVersionFile(version: version, path: installationDirectory)
|
||||
Printer.shared.print("Version \(version) installed")
|
||||
logger.notice("Version \(version) installed")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -123,7 +124,7 @@ final class Installer: Installing {
|
|||
let buildDirectory = temporaryDirectory.path.appending(RelativePath(".build/release/"))
|
||||
|
||||
// Cloning and building
|
||||
Printer.shared.print("Pulling source code")
|
||||
logger.notice("Pulling source code")
|
||||
_ = try System.shared.observable(["/usr/bin/env", "git", "clone", Constants.gitRepositoryURL, temporaryDirectory.path.pathString])
|
||||
.mapToString()
|
||||
.printStandardError()
|
||||
|
@ -143,7 +144,7 @@ final class Installer: Installing {
|
|||
}
|
||||
}
|
||||
|
||||
Printer.shared.print("Building using Swift (it might take a while)")
|
||||
logger.notice("Building using Swift (it might take a while)")
|
||||
let swiftPath = try System.shared
|
||||
.observable(["/usr/bin/xcrun", "-f", "swift"])
|
||||
.mapToString()
|
||||
|
@ -184,7 +185,7 @@ final class Installer: Installing {
|
|||
to: installationDirectory)
|
||||
|
||||
try createTuistVersionFile(version: version, path: installationDirectory)
|
||||
Printer.shared.print("Version \(version) installed")
|
||||
logger.notice("Version \(version) installed")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import TuistSupport
|
||||
let logger = Logger(label: "io.tuist.env")
|
|
@ -34,22 +34,22 @@ final class Updater: Updating {
|
|||
}
|
||||
|
||||
guard let highestRemoteVersion = try googleCloudStorageClient.latestVersion().toBlocking().first() else {
|
||||
Printer.shared.print("No remote versions found")
|
||||
logger.warning("No remote versions found")
|
||||
return
|
||||
}
|
||||
|
||||
if force {
|
||||
Printer.shared.print("Forcing the update of version \(highestRemoteVersion)")
|
||||
logger.notice("Forcing the update of version \(highestRemoteVersion)")
|
||||
try installer.install(version: highestRemoteVersion.description, force: true)
|
||||
} else if let highestLocalVersion = versionsController.semverVersions().sorted().last {
|
||||
if highestRemoteVersion <= highestLocalVersion {
|
||||
Printer.shared.print("There are no updates available")
|
||||
logger.notice("There are no updates available")
|
||||
} else {
|
||||
Printer.shared.print("Installing new version available \(highestRemoteVersion)")
|
||||
logger.notice("Installing new version available \(highestRemoteVersion)")
|
||||
try installer.install(version: highestRemoteVersion.description, force: false)
|
||||
}
|
||||
} else {
|
||||
Printer.shared.print("No local versions available. Installing the latest version \(highestRemoteVersion)")
|
||||
logger.notice("No local versions available. Installing the latest version \(highestRemoteVersion)")
|
||||
try installer.install(version: highestRemoteVersion.description, force: false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import Foundation
|
||||
|
||||
/// Command Descriptor
|
||||
///
|
||||
/// Describes a command that needs to be executed as part of
|
||||
/// generating a project or workspace.
|
||||
///
|
||||
/// - seealso: `SideEffectsDescriptor`
|
||||
public struct CommandDescriptor {
|
||||
public var command: [String]
|
||||
|
||||
/// Creates a command descriptor
|
||||
/// - Parameter command: The command and its arguments to perform
|
||||
public init(command: [String]) {
|
||||
self.command = command
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
|
||||
/// File Descriptor
|
||||
///
|
||||
/// Describes a file operation that needs to take place as
|
||||
/// part of generating a project or workspace.
|
||||
///
|
||||
/// - seealso: `SideEffectsDescriptor`
|
||||
public struct FileDescriptor {
|
||||
public enum State {
|
||||
case present
|
||||
case absent
|
||||
}
|
||||
|
||||
/// Path to the file
|
||||
public var path: AbsolutePath
|
||||
|
||||
/// The contents of the file
|
||||
public var contents: Data?
|
||||
|
||||
/// The desired state of the file (`.present` creates a fiile, `.absent` deletes a file)
|
||||
public var state: State
|
||||
|
||||
/// Creates a File Descriptor
|
||||
/// - Parameters:
|
||||
/// - path: Path to the file
|
||||
/// - contents: The contents of the file (Optional)
|
||||
/// - state: The desired state of the file (`.present` creates a fiile, `.absent` deletes a file)
|
||||
public init(path: AbsolutePath,
|
||||
contents: Data? = nil,
|
||||
state: FileDescriptor.State = .present) {
|
||||
self.path = path
|
||||
self.contents = contents
|
||||
self.state = state
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import XcodeProj
|
||||
|
||||
/// Project Descriptor
|
||||
///
|
||||
/// Contains the information needed to generate a project.
|
||||
///
|
||||
/// Can be used in conjunction with `XcodeProjWriter` to
|
||||
/// generate an `.xcodeproj` file.
|
||||
///
|
||||
/// - seealso: `XcodeProjWriter`
|
||||
public struct ProjectDescriptor {
|
||||
/// Path to the project
|
||||
public var path: AbsolutePath
|
||||
|
||||
/// Path to the xcodeproj file
|
||||
public var xcodeprojPath: AbsolutePath
|
||||
|
||||
/// The XcodeProj representation of this project
|
||||
public var xcodeProj: XcodeProj
|
||||
|
||||
/// The scheme descriptors of all the schemes within this project
|
||||
public var schemeDescriptors: [SchemeDescriptor]
|
||||
|
||||
/// The side effects required for generating this project
|
||||
public var sideEffectDescriptors: [SideEffectDescriptor]
|
||||
|
||||
public init(path: AbsolutePath,
|
||||
xcodeprojPath: AbsolutePath,
|
||||
xcodeProj: XcodeProj,
|
||||
schemeDescriptors: [SchemeDescriptor],
|
||||
sideEffectDescriptors: [SideEffectDescriptor]) {
|
||||
self.path = path
|
||||
self.xcodeprojPath = xcodeprojPath
|
||||
self.xcodeProj = xcodeProj
|
||||
self.schemeDescriptors = schemeDescriptors
|
||||
self.sideEffectDescriptors = sideEffectDescriptors
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import Foundation
|
||||
import XcodeProj
|
||||
|
||||
/// Scheme Descriptor
|
||||
///
|
||||
/// Contains the information needed to generate a scheme.
|
||||
///
|
||||
/// When part of a `ProjectDescriptor` or `WorkspaceDescriptor`, it
|
||||
/// can be used in conjunction with `XcodeProjWriter` to generate
|
||||
/// an `.xcscheme` file.
|
||||
///
|
||||
/// - seealso: `ProjectDescriptor`
|
||||
/// - seealso: `WorkspaceDescriptor`
|
||||
/// - seealso: `XcodeProjWriter`
|
||||
public struct SchemeDescriptor {
|
||||
/// The XCScheme scheme representation
|
||||
public var xcScheme: XCScheme
|
||||
|
||||
/// The Scheme type shared vs user scheme
|
||||
public var shared: Bool
|
||||
|
||||
public init(xcScheme: XCScheme, shared: Bool) {
|
||||
self.xcScheme = xcScheme
|
||||
self.shared = shared
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
|
||||
/// Side Effect Descriptor
|
||||
///
|
||||
/// Describes a side effect that needs to take place without performing it
|
||||
/// immediately within a component. This allows components to be side effect free,
|
||||
/// determenistic and much easier to test.
|
||||
///
|
||||
/// When part of a `ProjectDescriptor` or `WorkspaceDescriptor`, it
|
||||
/// can be used in conjunction with `XcodeProjWriter` to perform side effects.
|
||||
///
|
||||
/// - seealso: `ProjectDescriptor`
|
||||
/// - seealso: `WorkspaceDescriptor`
|
||||
/// - seealso: `XcodeProjWriter`
|
||||
public enum SideEffectDescriptor {
|
||||
/// Create / Remove a file
|
||||
case file(FileDescriptor)
|
||||
|
||||
/// Perform a command
|
||||
case command(CommandDescriptor)
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import XcodeProj
|
||||
|
||||
/// Workspace Descriptor
|
||||
///
|
||||
/// Contains the information needed to generate a workspace
|
||||
/// and all its projects.
|
||||
///
|
||||
/// Can be used in conjunction with `XcodeProjWriter` to
|
||||
/// generate an `.xcworkspace` file along with all its
|
||||
/// `.xcodeproj` files.
|
||||
///
|
||||
/// - seealso: `XcodeProjWriter`
|
||||
public struct WorkspaceDescriptor {
|
||||
/// Path to the workspace
|
||||
public var path: AbsolutePath
|
||||
|
||||
/// Path to the xcworkspace file
|
||||
public var xcworkspacePath: AbsolutePath
|
||||
|
||||
/// The XCWorkspace representation of the workspace
|
||||
public var xcworkspace: XCWorkspace
|
||||
|
||||
/// The project descriptors of all the projects within this workspace
|
||||
public var projectDescriptors: [ProjectDescriptor]
|
||||
|
||||
/// The scheme descriptors of all the schemes within this workspace
|
||||
public var schemeDescriptors: [SchemeDescriptor]
|
||||
|
||||
/// The side effects required for generating this workspace
|
||||
public var sideEffectDescriptors: [SideEffectDescriptor]
|
||||
|
||||
public init(path: AbsolutePath,
|
||||
xcworkspacePath: AbsolutePath,
|
||||
xcworkspace: XCWorkspace,
|
||||
projectDescriptors: [ProjectDescriptor],
|
||||
schemeDescriptors: [SchemeDescriptor],
|
||||
sideEffectDescriptors: [SideEffectDescriptor]) {
|
||||
self.path = path
|
||||
self.xcworkspacePath = xcworkspacePath
|
||||
self.xcworkspace = xcworkspace
|
||||
self.projectDescriptors = projectDescriptors
|
||||
self.schemeDescriptors = schemeDescriptors
|
||||
self.sideEffectDescriptors = sideEffectDescriptors
|
||||
}
|
||||
}
|
|
@ -206,22 +206,21 @@ final class BuildPhaseGenerator: BuildPhaseGenerating {
|
|||
var buildFilesCache = Set<AbsolutePath>()
|
||||
try files.sorted().forEach { buildFilePath in
|
||||
let pathString = buildFilePath.pathString
|
||||
let pathRange = NSRange(location: 0, length: pathString.count)
|
||||
let isLocalized = ProjectFileElements.localizedRegex.firstMatch(in: pathString, options: [], range: pathRange) != nil
|
||||
let isLocalized = pathString.contains(".lproj/")
|
||||
let isLproj = buildFilePath.extension == "lproj"
|
||||
let isAsset = ProjectFileElements.assetRegex.firstMatch(in: pathString, options: [], range: pathRange) != nil
|
||||
let isAssetWithinXCAssets = pathString.contains(".xcassets/")
|
||||
|
||||
/// Assets that are part of a .xcassets folder
|
||||
/// are not added individually. The whole folder is added
|
||||
/// instead as a group.
|
||||
if isAsset {
|
||||
if isAssetWithinXCAssets {
|
||||
return
|
||||
}
|
||||
|
||||
var element: (element: PBXFileElement, path: AbsolutePath)?
|
||||
|
||||
if isLocalized {
|
||||
let name = buildFilePath.components.last!
|
||||
let name = buildFilePath.basename
|
||||
let path = buildFilePath.parentDirectory.parentDirectory.appending(component: name)
|
||||
guard let group = fileElements.group(path: path) else {
|
||||
throw BuildPhaseGenerationError.missingFileReference(buildFilePath)
|
||||
|
|
|
@ -14,10 +14,11 @@ protocol DerivedFileGenerating {
|
|||
/// - 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: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, () throws -> Void)
|
||||
func generate(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, [SideEffectDescriptor])
|
||||
}
|
||||
|
||||
final class DerivedFileGenerator: DerivedFileGenerating {
|
||||
typealias ProjectTransformation = (project: Project, sideEffects: [SideEffectDescriptor])
|
||||
fileprivate static let derivedFolderName = "Derived"
|
||||
fileprivate static let infoPlistsFolderName = "InfoPlists"
|
||||
|
||||
|
@ -32,17 +33,10 @@ final class DerivedFileGenerator: DerivedFileGenerating {
|
|||
self.infoPlistContentProvider = infoPlistContentProvider
|
||||
}
|
||||
|
||||
func generate(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, () throws -> Void) {
|
||||
/// The files that are not necessary anymore should be deleted after we generate the project.
|
||||
/// Otherwise, Xcode will try to reload their references before the project generation.
|
||||
var toDelete: Set<AbsolutePath> = []
|
||||
let (project, infoPlistsToDelete) = try generateInfoPlists(graph: graph, project: project, sourceRootPath: sourceRootPath)
|
||||
func generate(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, [SideEffectDescriptor]) {
|
||||
let transformation = try generateInfoPlists(graph: graph, project: project, sourceRootPath: sourceRootPath)
|
||||
|
||||
toDelete.formUnion(infoPlistsToDelete)
|
||||
|
||||
return (project, {
|
||||
try toDelete.forEach { try FileHandler.shared.delete($0) }
|
||||
})
|
||||
return (transformation.project, transformation.sideEffects)
|
||||
}
|
||||
|
||||
/// Genreates the Info.plist files.
|
||||
|
@ -53,8 +47,9 @@ final class DerivedFileGenerator: DerivedFileGenerating {
|
|||
/// - 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: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, Set<AbsolutePath>) {
|
||||
let infoPlistsPath = DerivedFileGenerator.infoPlistsPath(sourceRootPath: sourceRootPath)
|
||||
func generateInfoPlists(graph: Graphing,
|
||||
project: Project,
|
||||
sourceRootPath: AbsolutePath) throws -> ProjectTransformation {
|
||||
let targetsWithGeneratableInfoPlists = project.targets.filter {
|
||||
if let infoPlist = $0.infoPlist, case InfoPlist.file = infoPlist {
|
||||
return false
|
||||
|
@ -70,44 +65,58 @@ final class DerivedFileGenerator: DerivedFileGenerating {
|
|||
}
|
||||
let toDelete = Set(existing).subtracting(new)
|
||||
|
||||
if !FileHandler.shared.exists(infoPlistsPath), !targetsWithGeneratableInfoPlists.isEmpty {
|
||||
try FileHandler.shared.createFolder(infoPlistsPath)
|
||||
let deletions = toDelete.map {
|
||||
SideEffectDescriptor.file(FileDescriptor(path: $0, state: .absent))
|
||||
}
|
||||
|
||||
// Generate the Info.plist
|
||||
let newTargets = try project.targets.map { (target) -> Target in
|
||||
guard targetsWithGeneratableInfoPlists.contains(target) else { return target }
|
||||
let transformation = try project.targets.map { (target) -> (Target, [SideEffectDescriptor]) in
|
||||
guard targetsWithGeneratableInfoPlists.contains(target),
|
||||
let infoPlist = target.infoPlist else {
|
||||
return (target, [])
|
||||
}
|
||||
|
||||
guard let infoPlist = target.infoPlist else { return target }
|
||||
|
||||
let dictionary: [String: Any]
|
||||
|
||||
if case let InfoPlist.dictionary(content) = infoPlist {
|
||||
dictionary = content.mapValues { $0.value }
|
||||
} else if case let InfoPlist.extendingDefault(extended) = infoPlist,
|
||||
let content = self.infoPlistContentProvider.content(graph: graph,
|
||||
project: project,
|
||||
target: target,
|
||||
extendedWith: extended) {
|
||||
dictionary = content
|
||||
} 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)
|
||||
if FileHandler.shared.exists(path) { try FileHandler.shared.delete(path) }
|
||||
|
||||
let data = try PropertyListSerialization.data(fromPropertyList: dictionary,
|
||||
format: .xml,
|
||||
options: 0)
|
||||
|
||||
try data.write(to: path.url)
|
||||
let sideEffet = 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))
|
||||
return (target.with(infoPlist: InfoPlist.file(path: path)), [sideEffet])
|
||||
}
|
||||
|
||||
return (project.with(targets: newTargets), toDelete)
|
||||
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: Graphing) -> [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.
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
/// Project Generation Configuration
|
||||
///
|
||||
/// Allow specifying additional generation options
|
||||
/// for an individual project.
|
||||
public struct ProjectGenerationConfig {
|
||||
/// The source root path of the generated Xcode project
|
||||
public var sourceRootPath: AbsolutePath?
|
||||
|
||||
/// The xcodeproj file path
|
||||
public var xcodeprojPath: AbsolutePath?
|
||||
|
||||
public init(sourceRootPath: AbsolutePath? = nil,
|
||||
xcodeprojPath: AbsolutePath? = nil) {
|
||||
self.sourceRootPath = sourceRootPath
|
||||
self.xcodeprojPath = xcodeprojPath
|
||||
}
|
||||
}
|
||||
|
||||
/// Descriptor Generator
|
||||
///
|
||||
/// This component genertes`XcodeProj` representations of a given graph model.
|
||||
/// No sideeffects take place as a result of this generation.
|
||||
///
|
||||
/// - Seealso: `GraphLoader`
|
||||
/// - Seealso: `GraphLinter`
|
||||
/// - Seealso: `XcodeProjWriter`
|
||||
///
|
||||
public protocol DescriptorGenerating {
|
||||
/// Generate an individual project descriptor
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - project: Project model
|
||||
/// - graph: Graph model
|
||||
///
|
||||
/// - Seealso: `GraphLoader`
|
||||
func generateProject(project: Project, graph: Graph) throws -> ProjectDescriptor
|
||||
|
||||
/// Generate an individual project descriptor with some additional configuration
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - project: Project model
|
||||
/// - graph: Graph model
|
||||
/// - config: The project generation configuration
|
||||
///
|
||||
/// - Seealso: `GraphLoader`
|
||||
func generateProject(project: Project, graph: Graph, config: ProjectGenerationConfig) throws -> ProjectDescriptor
|
||||
|
||||
/// Generate a workspace descriptor
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - project: Workspace model
|
||||
/// - graph: Graph model
|
||||
///
|
||||
/// - Seealso: `GraphLoader`
|
||||
func generateWorkspace(workspace: Workspace, graph: Graph) throws -> WorkspaceDescriptor
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
/// Default implementation of `DescriptorGenerating`
|
||||
public final class DescriptorGenerator: DescriptorGenerating {
|
||||
private let workspaceGenerator: WorkspaceGenerating
|
||||
private let projectGenerator: ProjectGenerating
|
||||
|
||||
public convenience init(defaultSettingsProvider: DefaultSettingsProviding = DefaultSettingsProvider()) {
|
||||
let configGenerator = ConfigGenerator(defaultSettingsProvider: defaultSettingsProvider)
|
||||
let targetGenerator = TargetGenerator(configGenerator: configGenerator)
|
||||
let schemesGenerator = SchemesGenerator()
|
||||
let workspaceStructureGenerator = WorkspaceStructureGenerator()
|
||||
let projectGenerator = ProjectGenerator(targetGenerator: targetGenerator,
|
||||
configGenerator: configGenerator,
|
||||
schemesGenerator: schemesGenerator)
|
||||
let workspaceGenerator = WorkspaceGenerator(projectGenerator: projectGenerator,
|
||||
workspaceStructureGenerator: workspaceStructureGenerator,
|
||||
schemesGenerator: schemesGenerator)
|
||||
self.init(workspaceGenerator: workspaceGenerator,
|
||||
projectGenerator: projectGenerator)
|
||||
}
|
||||
|
||||
init(workspaceGenerator: WorkspaceGenerating,
|
||||
projectGenerator: ProjectGenerating) {
|
||||
self.workspaceGenerator = workspaceGenerator
|
||||
self.projectGenerator = projectGenerator
|
||||
}
|
||||
|
||||
public func generateProject(project: Project, graph: Graph) throws -> ProjectDescriptor {
|
||||
try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: nil,
|
||||
xcodeprojPath: nil)
|
||||
}
|
||||
|
||||
public func generateProject(project: Project, graph: Graph, config: ProjectGenerationConfig) throws -> ProjectDescriptor {
|
||||
try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: config.sourceRootPath,
|
||||
xcodeprojPath: config.xcodeprojPath)
|
||||
}
|
||||
|
||||
public func generateWorkspace(workspace: Workspace, graph: Graph) throws -> WorkspaceDescriptor {
|
||||
try workspaceGenerator.generate(workspace: workspace,
|
||||
path: workspace.path,
|
||||
graph: graph)
|
||||
}
|
||||
}
|
|
@ -4,6 +4,11 @@ import TuistCore
|
|||
import TuistSupport
|
||||
|
||||
/// A component responsible for generating Xcode projects & workspaces
|
||||
@available(
|
||||
*,
|
||||
deprecated,
|
||||
message: "Generating is deprecated and will be removed in a future Tuist version. Please use `DescriptorGenerating` instead."
|
||||
)
|
||||
public protocol Generating {
|
||||
/// Generates an Xcode project at a given path. Only the specified project is generated (excluding its dependencies).
|
||||
///
|
||||
|
@ -55,11 +60,19 @@ public protocol Generating {
|
|||
///
|
||||
/// - seealso: Generating
|
||||
/// - seealso: GeneratorModelLoading
|
||||
@available(
|
||||
*,
|
||||
deprecated,
|
||||
message: "Generator is deprecated and will be removed in a future Tuist version. Please use `DescriptorGenerator` instead."
|
||||
)
|
||||
public class Generator: Generating {
|
||||
private let graphLoader: GraphLoading
|
||||
private let graphLinter: GraphLinting
|
||||
private let workspaceGenerator: WorkspaceGenerating
|
||||
private let projectGenerator: ProjectGenerating
|
||||
private let writer: XcodeProjWriting
|
||||
private let cocoapodsInteractor: CocoaPodsInteracting
|
||||
private let swiftPackageManagerInteractor: SwiftPackageManagerInteracting
|
||||
|
||||
/// Instance to lint the Tuist configuration against the system.
|
||||
private let environmentLinter: EnvironmentLinting
|
||||
|
@ -74,29 +87,40 @@ public class Generator: Generating {
|
|||
configGenerator: configGenerator)
|
||||
let environmentLinter = EnvironmentLinter()
|
||||
let workspaceStructureGenerator = WorkspaceStructureGenerator()
|
||||
let cocoapodsInteractor = CocoaPodsInteractor()
|
||||
let schemesGenerator = SchemesGenerator()
|
||||
let workspaceGenerator = WorkspaceGenerator(projectGenerator: projectGenerator,
|
||||
workspaceStructureGenerator: workspaceStructureGenerator,
|
||||
cocoapodsInteractor: cocoapodsInteractor,
|
||||
schemesGenerator: schemesGenerator)
|
||||
let writer = XcodeProjWriter()
|
||||
let cocoapodsInteractor: CocoaPodsInteracting = CocoaPodsInteractor()
|
||||
let swiftPackageManagerInteractor: SwiftPackageManagerInteracting = SwiftPackageManagerInteractor()
|
||||
|
||||
self.init(graphLoader: graphLoader,
|
||||
graphLinter: graphLinter,
|
||||
workspaceGenerator: workspaceGenerator,
|
||||
projectGenerator: projectGenerator,
|
||||
environmentLinter: environmentLinter)
|
||||
environmentLinter: environmentLinter,
|
||||
writer: writer,
|
||||
cocoapodsInteractor: cocoapodsInteractor,
|
||||
swiftPackageManagerInteractor: swiftPackageManagerInteractor)
|
||||
}
|
||||
|
||||
init(graphLoader: GraphLoading,
|
||||
graphLinter: GraphLinting,
|
||||
workspaceGenerator: WorkspaceGenerating,
|
||||
projectGenerator: ProjectGenerating,
|
||||
environmentLinter: EnvironmentLinting) {
|
||||
environmentLinter: EnvironmentLinting,
|
||||
writer: XcodeProjWriting,
|
||||
cocoapodsInteractor: CocoaPodsInteracting,
|
||||
swiftPackageManagerInteractor: SwiftPackageManagerInteracting) {
|
||||
self.graphLoader = graphLoader
|
||||
self.graphLinter = graphLinter
|
||||
self.workspaceGenerator = workspaceGenerator
|
||||
self.projectGenerator = projectGenerator
|
||||
self.environmentLinter = environmentLinter
|
||||
self.writer = writer
|
||||
self.cocoapodsInteractor = cocoapodsInteractor
|
||||
self.swiftPackageManagerInteractor = swiftPackageManagerInteractor
|
||||
}
|
||||
|
||||
public func generateProject(_ project: Project,
|
||||
|
@ -107,31 +131,35 @@ public class Generator: Generating {
|
|||
/// are relative to the directory that contains the manifest.
|
||||
let sourceRootPath = sourceRootPath ?? project.path
|
||||
|
||||
let generatedProject = try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: sourceRootPath,
|
||||
xcodeprojPath: xcodeprojPath)
|
||||
return generatedProject.path
|
||||
let descriptor = try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: sourceRootPath,
|
||||
xcodeprojPath: xcodeprojPath)
|
||||
|
||||
try writer.write(project: descriptor)
|
||||
return descriptor.xcodeprojPath
|
||||
}
|
||||
|
||||
public func generateProject(at path: AbsolutePath) throws -> (AbsolutePath, Graphing) {
|
||||
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
|
||||
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
|
||||
let config = try graphLoader.loadConfig(path: path)
|
||||
try environmentLinter.lint(config: config).printAndThrowIfNeeded()
|
||||
|
||||
let (graph, project) = try graphLoader.loadProject(path: path)
|
||||
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
||||
|
||||
let generatedProject = try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: path,
|
||||
xcodeprojPath: nil)
|
||||
return (generatedProject.path, graph)
|
||||
let descriptor = try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: path,
|
||||
xcodeprojPath: nil)
|
||||
|
||||
try writer.write(project: descriptor)
|
||||
return (descriptor.xcodeprojPath, graph)
|
||||
}
|
||||
|
||||
public func generateProjectWorkspace(at path: AbsolutePath,
|
||||
workspaceFiles: [AbsolutePath]) throws -> (AbsolutePath, Graphing) {
|
||||
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
|
||||
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
|
||||
let config = try graphLoader.loadConfig(path: path)
|
||||
try environmentLinter.lint(config: config).printAndThrowIfNeeded()
|
||||
|
||||
let (graph, project) = try graphLoader.loadProject(path: path)
|
||||
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
||||
|
@ -141,17 +169,20 @@ public class Generator: Generating {
|
|||
projects: graph.projectPaths,
|
||||
additionalFiles: workspaceFiles.map(FileElement.file))
|
||||
|
||||
let workspacePath = try workspaceGenerator.generate(workspace: workspace,
|
||||
path: path,
|
||||
graph: graph,
|
||||
tuistConfig: tuistConfig)
|
||||
return (workspacePath, graph)
|
||||
let descriptor = try workspaceGenerator.generate(workspace: workspace,
|
||||
path: path,
|
||||
graph: graph)
|
||||
try writer.write(workspace: descriptor)
|
||||
|
||||
try postGenerationActions(for: graph, workspaceName: descriptor.xcworkspacePath.basename)
|
||||
|
||||
return (descriptor.xcworkspacePath, graph)
|
||||
}
|
||||
|
||||
public func generateWorkspace(at path: AbsolutePath,
|
||||
workspaceFiles: [AbsolutePath]) throws -> (AbsolutePath, Graphing) {
|
||||
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
|
||||
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
|
||||
let config = try graphLoader.loadConfig(path: path)
|
||||
try environmentLinter.lint(config: config).printAndThrowIfNeeded()
|
||||
let (graph, workspace) = try graphLoader.loadWorkspace(path: path)
|
||||
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
||||
|
||||
|
@ -159,10 +190,18 @@ public class Generator: Generating {
|
|||
.merging(projects: graph.projectPaths)
|
||||
.adding(files: workspaceFiles)
|
||||
|
||||
let workspacePath = try workspaceGenerator.generate(workspace: updatedWorkspace,
|
||||
path: path,
|
||||
graph: graph,
|
||||
tuistConfig: tuistConfig)
|
||||
return (workspacePath, graph)
|
||||
let descriptor = try workspaceGenerator.generate(workspace: updatedWorkspace,
|
||||
path: path,
|
||||
graph: graph)
|
||||
try writer.write(workspace: descriptor)
|
||||
|
||||
try postGenerationActions(for: graph, workspaceName: descriptor.xcworkspacePath.basename)
|
||||
|
||||
return (descriptor.xcworkspacePath, graph)
|
||||
}
|
||||
|
||||
private func postGenerationActions(for graph: Graph, workspaceName: String) throws {
|
||||
try swiftPackageManagerInteractor.install(graph: graph, workspaceName: workspaceName)
|
||||
try cocoapodsInteractor.install(graph: graph)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,6 @@ class ProjectFileElements {
|
|||
// swiftlint:disable:next force_try
|
||||
static let localizedRegex = try! NSRegularExpression(pattern: "(.+\\.lproj)/.+",
|
||||
options: [])
|
||||
// swiftlint:disable:next force_try
|
||||
static let assetRegex = try! NSRegularExpression(pattern: ".+/.+\\.xcassets/.+",
|
||||
options: [])
|
||||
|
||||
// MARK: - Attributes
|
||||
|
||||
|
|
|
@ -29,13 +29,13 @@ protocol ProjectGenerating: AnyObject {
|
|||
/// - graph: Dependencies graph.
|
||||
/// - sourceRootPath: Directory where the files are relative to.
|
||||
/// - xcodeprojPath: Path to the Xcode project. When not given, the xcodeproj is generated at sourceRootPath.
|
||||
/// - Returns: Generated project descriptor
|
||||
func generate(project: Project,
|
||||
graph: Graphing,
|
||||
sourceRootPath: AbsolutePath?,
|
||||
xcodeprojPath: AbsolutePath?) throws -> GeneratedProject
|
||||
xcodeprojPath: AbsolutePath?) throws -> ProjectDescriptor
|
||||
}
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
final class ProjectGenerator: ProjectGenerating {
|
||||
// MARK: - Attributes
|
||||
|
||||
|
@ -72,11 +72,12 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
|
||||
// MARK: - ProjectGenerating
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
func generate(project: Project,
|
||||
graph: Graphing,
|
||||
sourceRootPath: AbsolutePath? = nil,
|
||||
xcodeprojPath: AbsolutePath? = nil) throws -> GeneratedProject {
|
||||
Printer.shared.print("Generating project \(project.name)")
|
||||
xcodeprojPath: AbsolutePath? = nil) throws -> ProjectDescriptor {
|
||||
logger.notice("Generating project \(project.name)")
|
||||
|
||||
// Getting the path.
|
||||
let sourceRootPath = sourceRootPath ?? project.path
|
||||
|
@ -84,22 +85,9 @@ 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")
|
||||
|
||||
// Project and workspace.
|
||||
return try generateProjectAndWorkspace(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: sourceRootPath,
|
||||
xcodeprojPath: xcodeprojPath)
|
||||
}
|
||||
|
||||
// MARK: - Fileprivate
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
private func generateProjectAndWorkspace(project: Project,
|
||||
graph: Graphing,
|
||||
sourceRootPath: AbsolutePath,
|
||||
xcodeprojPath: AbsolutePath) throws -> GeneratedProject {
|
||||
// Derived files
|
||||
let (project, deleteOldDerivedFiles) = try derivedFileGenerator.generate(graph: graph, project: project, sourceRootPath: sourceRootPath)
|
||||
// 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)
|
||||
|
@ -107,19 +95,13 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
let pbxproj = PBXProj(objectVersion: projectConstants.objectVersion,
|
||||
archiveVersion: projectConstants.archiveVersion,
|
||||
classes: [:])
|
||||
|
||||
let groups = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
xcodeprojPath: xcodeprojPath,
|
||||
sourceRootPath: sourceRootPath)
|
||||
|
||||
let groups = ProjectGroups.generate(project: project, pbxproj: pbxproj, xcodeprojPath: xcodeprojPath, sourceRootPath: sourceRootPath)
|
||||
let fileElements = ProjectFileElements()
|
||||
try fileElements.generateProjectFiles(project: project,
|
||||
graph: graph,
|
||||
groups: groups,
|
||||
pbxproj: pbxproj,
|
||||
sourceRootPath: sourceRootPath)
|
||||
|
||||
let configurationList = try configGenerator.generateProjectConfig(project: project, pbxproj: pbxproj, fileElements: fileElements)
|
||||
let pbxProject = try generatePbxproject(project: project,
|
||||
projectFileElements: fileElements,
|
||||
|
@ -141,16 +123,26 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
try generateSwiftPackageReferences(project: project,
|
||||
pbxproj: pbxproj,
|
||||
pbxProject: pbxProject)
|
||||
try deleteOldDerivedFiles()
|
||||
|
||||
return try write(xcodeprojPath: xcodeprojPath,
|
||||
nativeTargets: nativeTargets,
|
||||
workspace: workspace,
|
||||
pbxproj: pbxproj,
|
||||
project: project,
|
||||
graph: graph)
|
||||
let generatedProject = GeneratedProject(pbxproj: pbxproj,
|
||||
path: xcodeprojPath,
|
||||
targets: nativeTargets,
|
||||
name: xcodeprojPath.basename)
|
||||
|
||||
let schemes = try schemesGenerator.generateProjectSchemes(project: project,
|
||||
generatedProject: generatedProject,
|
||||
graph: graph)
|
||||
|
||||
let xcodeProj = XcodeProj(workspace: workspace, pbxproj: pbxproj)
|
||||
return ProjectDescriptor(path: project.path,
|
||||
xcodeprojPath: xcodeprojPath,
|
||||
xcodeProj: xcodeProj,
|
||||
schemeDescriptors: schemes,
|
||||
sideEffectDescriptors: sideEffects)
|
||||
}
|
||||
|
||||
// MARK: - Fileprivate
|
||||
|
||||
private func generatePbxproject(project: Project,
|
||||
projectFileElements: ProjectFileElements,
|
||||
configurationList: XCConfigurationList,
|
||||
|
@ -158,6 +150,7 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
pbxproj: PBXProj) throws -> PBXProject {
|
||||
let defaultRegions = ["en", "Base"]
|
||||
let knownRegions = Set(defaultRegions + projectFileElements.knownRegions).sorted()
|
||||
let attributes = project.organizationName.map { ["ORGANIZATIONNAME": $0] } ?? [:]
|
||||
let pbxProject = PBXProject(name: project.name,
|
||||
buildConfigurationList: configurationList,
|
||||
compatibilityVersion: Xcode.Default.compatibilityVersion,
|
||||
|
@ -169,7 +162,8 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
projectDirPath: "",
|
||||
projects: [],
|
||||
projectRoots: [],
|
||||
targets: [])
|
||||
targets: [],
|
||||
attributes: attributes)
|
||||
pbxproj.add(object: pbxProject)
|
||||
pbxproj.rootObject = pbxProject
|
||||
return pbxProject
|
||||
|
@ -264,84 +258,6 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
pbxProject.packages = packageReferences.sorted { $0.key < $1.key }.map { $1 }
|
||||
}
|
||||
|
||||
private func write(xcodeprojPath: AbsolutePath,
|
||||
nativeTargets: [String: PBXNativeTarget],
|
||||
workspace: XCWorkspace,
|
||||
pbxproj: PBXProj,
|
||||
project: Project,
|
||||
graph: Graphing) throws -> GeneratedProject {
|
||||
let fileHandler = FileHandler.shared
|
||||
func write(xcodeprojPath: AbsolutePath) throws -> GeneratedProject {
|
||||
let generatedProject = GeneratedProject(pbxproj: pbxproj,
|
||||
path: xcodeprojPath,
|
||||
targets: nativeTargets,
|
||||
name: xcodeprojPath.basename)
|
||||
try writeXcodeproj(workspace: workspace,
|
||||
pbxproj: pbxproj,
|
||||
xcodeprojPath: xcodeprojPath)
|
||||
|
||||
try writeSchemes(project: project,
|
||||
generatedProject: generatedProject,
|
||||
xcprojectPath: xcodeprojPath,
|
||||
graph: graph)
|
||||
|
||||
return generatedProject
|
||||
}
|
||||
|
||||
guard fileHandler.exists(xcodeprojPath) else {
|
||||
return try write(xcodeprojPath: xcodeprojPath)
|
||||
}
|
||||
|
||||
var generatedProject: GeneratedProject!
|
||||
try fileHandler.inTemporaryDirectory { temporaryPath in
|
||||
let temporaryPath = temporaryPath.appending(component: xcodeprojPath.basename)
|
||||
generatedProject = try write(xcodeprojPath: temporaryPath)
|
||||
|
||||
let pathsToReplace = self.pathsToReplace(xcodeProjPath: temporaryPath)
|
||||
try pathsToReplace.forEach {
|
||||
let relativeFile = $0.relative(to: temporaryPath)
|
||||
let writeToPath = xcodeprojPath.appending(relativeFile)
|
||||
try fileHandler.createFolder(writeToPath.parentDirectory)
|
||||
try fileHandler.replace(writeToPath, with: $0)
|
||||
}
|
||||
}
|
||||
|
||||
return generatedProject.at(path: xcodeprojPath)
|
||||
}
|
||||
|
||||
private func pathsToReplace(xcodeProjPath: AbsolutePath) -> [AbsolutePath] {
|
||||
var paths = [
|
||||
"project.pbxproj",
|
||||
"project.xcworkspace",
|
||||
"xcshareddata/xcschemes",
|
||||
]
|
||||
|
||||
if FileHandler.shared.exists(xcodeProjPath.appending(component: "xcuserdata")) {
|
||||
paths.append("xcuserdata/**/*.xcscheme")
|
||||
}
|
||||
|
||||
return paths.flatMap {
|
||||
FileHandler.shared.glob(xcodeProjPath, glob: $0)
|
||||
}
|
||||
}
|
||||
|
||||
private func writeXcodeproj(workspace: XCWorkspace,
|
||||
pbxproj: PBXProj,
|
||||
xcodeprojPath: AbsolutePath) throws {
|
||||
let xcodeproj = XcodeProj(workspace: workspace, pbxproj: pbxproj)
|
||||
try xcodeproj.write(path: xcodeprojPath.path)
|
||||
}
|
||||
|
||||
private func writeSchemes(project: Project,
|
||||
generatedProject: GeneratedProject,
|
||||
xcprojectPath: AbsolutePath,
|
||||
graph: Graphing) throws {
|
||||
try schemesGenerator.generateProjectSchemes(project: project,
|
||||
xcprojectPath: xcprojectPath,
|
||||
generatedProject: generatedProject,
|
||||
graph: graph)
|
||||
}
|
||||
|
||||
private func determineProjectConstants(graph: Graphing) throws -> ProjectConstants {
|
||||
if !graph.packages.isEmpty {
|
||||
return .xcode11
|
||||
|
|
|
@ -15,9 +15,8 @@ protocol SchemesGenerating {
|
|||
/// - graph: Tuist graph.
|
||||
/// - Throws: A FatalError if the generation of the schemes fails.
|
||||
func generateWorkspaceSchemes(workspace: Workspace,
|
||||
xcworkspacePath: AbsolutePath,
|
||||
generatedProjects: [AbsolutePath: GeneratedProject],
|
||||
graph: Graphing) throws
|
||||
graph: Graphing) throws -> [SchemeDescriptor]
|
||||
|
||||
/// Generates the schemes for the project targets.
|
||||
///
|
||||
|
@ -28,17 +27,8 @@ protocol SchemesGenerating {
|
|||
/// - graph: Tuist graph.
|
||||
/// - Throws: A FatalError if the generation of the schemes fails.
|
||||
func generateProjectSchemes(project: Project,
|
||||
xcprojectPath: AbsolutePath,
|
||||
generatedProject: GeneratedProject,
|
||||
graph: Graphing) throws
|
||||
|
||||
/// Wipes shared and user schemes at a workspace or project path. This is needed
|
||||
/// currently to support the workspace scheme generation case where a workspace that
|
||||
/// already exists on disk is being regenerated. Wiping the schemes directory prevents
|
||||
/// older custom schemes from persisting after regeneration.
|
||||
///
|
||||
/// - Parameter at: Path to the workspace or project.
|
||||
func wipeSchemes(at: AbsolutePath) throws
|
||||
graph: Graphing) throws -> [SchemeDescriptor]
|
||||
}
|
||||
|
||||
// swiftlint:disable:next type_body_length
|
||||
|
@ -49,42 +39,24 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
/// Default version for generated schemes.
|
||||
private static let defaultVersion = "1.3"
|
||||
|
||||
/// Generates the schemes for the workspace targets.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - workspace: Workspace model.
|
||||
/// - xcworkspacePath: Path to the workspace.
|
||||
/// - generatedProject: Generated Xcode project.
|
||||
/// - graph: Tuist graph.
|
||||
/// - Throws: A FatalError if the generation of the schemes fails.
|
||||
func generateWorkspaceSchemes(workspace: Workspace,
|
||||
xcworkspacePath: AbsolutePath,
|
||||
generatedProjects: [AbsolutePath: GeneratedProject],
|
||||
graph: Graphing) throws {
|
||||
try workspace.schemes.forEach { scheme in
|
||||
graph: Graphing) throws -> [SchemeDescriptor] {
|
||||
let schemes = try workspace.schemes.map { scheme in
|
||||
try generateScheme(scheme: scheme,
|
||||
xcPath: xcworkspacePath,
|
||||
path: workspace.path,
|
||||
graph: graph,
|
||||
generatedProjects: generatedProjects)
|
||||
}
|
||||
|
||||
return schemes
|
||||
}
|
||||
|
||||
/// Generate schemes for a project.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - project: Project manifest.
|
||||
/// - xcprojectPath: Path to project's .xcodeproj.
|
||||
/// - generatedProject: Generated Project
|
||||
/// - graph: Tuist graph.
|
||||
func generateProjectSchemes(project: Project,
|
||||
xcprojectPath: AbsolutePath,
|
||||
generatedProject: GeneratedProject,
|
||||
graph: Graphing) throws {
|
||||
/// Generate custom schemes from manifest
|
||||
try project.schemes.forEach { scheme in
|
||||
graph: Graphing) throws -> [SchemeDescriptor] {
|
||||
let customSchemes: [SchemeDescriptor] = try project.schemes.map { scheme in
|
||||
try generateScheme(scheme: scheme,
|
||||
xcPath: xcprojectPath,
|
||||
path: project.path,
|
||||
graph: graph,
|
||||
generatedProjects: [project.path: generatedProject])
|
||||
|
@ -94,14 +66,15 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
let buildConfiguration = defaultDebugBuildConfigurationName(in: project)
|
||||
let userDefinedSchemes = Set(project.schemes.map(\.name))
|
||||
let defaultSchemeTargets = project.targets.filter { !userDefinedSchemes.contains($0.name) }
|
||||
try defaultSchemeTargets.forEach { target in
|
||||
let defaultSchemes: [SchemeDescriptor] = try defaultSchemeTargets.map { target in
|
||||
let scheme = createDefaultScheme(target: target, project: project, buildConfiguration: buildConfiguration, graph: graph)
|
||||
try generateScheme(scheme: scheme,
|
||||
xcPath: xcprojectPath,
|
||||
path: project.path,
|
||||
graph: graph,
|
||||
generatedProjects: [project.path: generatedProject])
|
||||
return try generateScheme(scheme: scheme,
|
||||
path: project.path,
|
||||
graph: graph,
|
||||
generatedProjects: [project.path: generatedProject])
|
||||
}
|
||||
|
||||
return customSchemes + defaultSchemes
|
||||
}
|
||||
|
||||
/// Wipes shared and user schemes at a workspace or project path. This is needed
|
||||
|
@ -149,13 +122,9 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
/// - graph: Tuist graph.
|
||||
/// - generatedProjects: Project paths mapped to generated projects.
|
||||
private func generateScheme(scheme: Scheme,
|
||||
xcPath: AbsolutePath,
|
||||
path: AbsolutePath,
|
||||
graph: Graphing,
|
||||
generatedProjects: [AbsolutePath: GeneratedProject]) throws {
|
||||
let schemeDirectory = try createSchemesDirectory(path: xcPath, shared: scheme.shared)
|
||||
let schemePath = schemeDirectory.appending(component: "\(scheme.name).xcscheme")
|
||||
|
||||
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> SchemeDescriptor {
|
||||
let generatedBuildAction = try schemeBuildAction(scheme: scheme,
|
||||
graph: graph,
|
||||
rootPath: path,
|
||||
|
@ -181,17 +150,17 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
rootPath: path,
|
||||
generatedProjects: generatedProjects)
|
||||
|
||||
let scheme = XCScheme(name: scheme.name,
|
||||
lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion,
|
||||
version: SchemesGenerator.defaultVersion,
|
||||
buildAction: generatedBuildAction,
|
||||
testAction: generatedTestAction,
|
||||
launchAction: generatedLaunchAction,
|
||||
profileAction: generatedProfileAction,
|
||||
analyzeAction: generatedAnalyzeAction,
|
||||
archiveAction: generatedArchiveAction)
|
||||
let xcscheme = XCScheme(name: scheme.name,
|
||||
lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion,
|
||||
version: SchemesGenerator.defaultVersion,
|
||||
buildAction: generatedBuildAction,
|
||||
testAction: generatedTestAction,
|
||||
launchAction: generatedLaunchAction,
|
||||
profileAction: generatedProfileAction,
|
||||
analyzeAction: generatedAnalyzeAction,
|
||||
archiveAction: generatedArchiveAction)
|
||||
|
||||
try scheme.write(path: schemePath.path, override: true)
|
||||
return SchemeDescriptor(xcScheme: xcscheme, shared: scheme.shared)
|
||||
}
|
||||
|
||||
/// Generates the scheme build action.
|
||||
|
@ -382,9 +351,23 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
rootPath: AbsolutePath,
|
||||
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.ProfileAction? {
|
||||
guard var target = try defaultTargetReference(scheme: scheme) else { return nil }
|
||||
if let executable = scheme.runAction?.executable {
|
||||
var commandlineArguments: XCScheme.CommandLineArguments?
|
||||
var environments: [XCScheme.EnvironmentVariable]?
|
||||
|
||||
if let action = scheme.profileAction, let executable = action.executable {
|
||||
target = executable
|
||||
if let arguments = action.arguments {
|
||||
commandlineArguments = XCScheme.CommandLineArguments(arguments: commandlineArgruments(arguments.launch))
|
||||
environments = environmentVariables(arguments.environment)
|
||||
}
|
||||
} else if let action = scheme.runAction, let executable = action.executable {
|
||||
target = executable
|
||||
if let arguments = action.arguments {
|
||||
commandlineArguments = XCScheme.CommandLineArguments(arguments: commandlineArgruments(arguments.launch))
|
||||
environments = environmentVariables(arguments.environment)
|
||||
}
|
||||
}
|
||||
|
||||
guard let targetNode = try graph.target(path: target.projectPath, name: target.name) else { return nil }
|
||||
guard let buildableReference = try createBuildableReference(targetReference: target,
|
||||
graph: graph,
|
||||
|
@ -400,10 +383,12 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
macroExpansion = buildableReference
|
||||
}
|
||||
|
||||
let buildConfiguration = defaultReleaseBuildConfigurationName(in: targetNode.project)
|
||||
let buildConfiguration = scheme.profileAction?.configurationName ?? defaultReleaseBuildConfigurationName(in: targetNode.project)
|
||||
return XCScheme.ProfileAction(buildableProductRunnable: buildableProductRunnable,
|
||||
buildConfiguration: buildConfiguration,
|
||||
macroExpansion: macroExpansion)
|
||||
macroExpansion: macroExpansion,
|
||||
commandlineArguments: commandlineArguments,
|
||||
environmentVariables: environments)
|
||||
}
|
||||
|
||||
/// Returns the scheme analyze action.
|
||||
|
@ -421,7 +406,7 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
guard let target = try defaultTargetReference(scheme: scheme),
|
||||
let targetNode = try graph.target(path: target.projectPath, name: target.name) else { return nil }
|
||||
|
||||
let buildConfiguration = defaultDebugBuildConfigurationName(in: targetNode.project)
|
||||
let buildConfiguration = scheme.analyzeAction?.configurationName ?? defaultDebugBuildConfigurationName(in: targetNode.project)
|
||||
return XCScheme.AnalyzeAction(buildConfiguration: buildConfiguration)
|
||||
}
|
||||
|
||||
|
|
|
@ -28,14 +28,11 @@ protocol WorkspaceGenerating: AnyObject {
|
|||
/// - workspace: Workspace model.
|
||||
/// - path: Path to the directory where the generation command is executed from.
|
||||
/// - graph: In-memory representation of the graph.
|
||||
/// - tuistConfig: Tuist configuration.
|
||||
/// - Returns: Path to the generated workspace.
|
||||
/// - Returns: Generated workspace descriptor
|
||||
/// - Throws: An error if the generation fails.
|
||||
@discardableResult
|
||||
func generate(workspace: Workspace,
|
||||
path: AbsolutePath,
|
||||
graph: Graphing,
|
||||
tuistConfig: TuistConfig) throws -> AbsolutePath
|
||||
graph: Graphing) throws -> WorkspaceDescriptor
|
||||
}
|
||||
|
||||
final class WorkspaceGenerator: WorkspaceGenerating {
|
||||
|
@ -43,64 +40,55 @@ final class WorkspaceGenerator: WorkspaceGenerating {
|
|||
|
||||
private let projectGenerator: ProjectGenerating
|
||||
private let workspaceStructureGenerator: WorkspaceStructureGenerating
|
||||
private let cocoapodsInteractor: CocoaPodsInteracting
|
||||
private let schemesGenerator: SchemesGenerating
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
convenience init(defaultSettingsProvider: DefaultSettingsProviding = DefaultSettingsProvider(),
|
||||
cocoapodsInteractor: CocoaPodsInteracting = CocoaPodsInteractor()) {
|
||||
convenience init(defaultSettingsProvider: DefaultSettingsProviding = DefaultSettingsProvider()) {
|
||||
let configGenerator = ConfigGenerator(defaultSettingsProvider: defaultSettingsProvider)
|
||||
let targetGenerator = TargetGenerator(configGenerator: configGenerator)
|
||||
let projectGenerator = ProjectGenerator(targetGenerator: targetGenerator,
|
||||
configGenerator: configGenerator)
|
||||
self.init(projectGenerator: projectGenerator,
|
||||
workspaceStructureGenerator: WorkspaceStructureGenerator(),
|
||||
cocoapodsInteractor: cocoapodsInteractor,
|
||||
schemesGenerator: SchemesGenerator())
|
||||
}
|
||||
|
||||
init(projectGenerator: ProjectGenerating,
|
||||
workspaceStructureGenerator: WorkspaceStructureGenerating,
|
||||
cocoapodsInteractor: CocoaPodsInteracting,
|
||||
schemesGenerator: SchemesGenerating) {
|
||||
self.projectGenerator = projectGenerator
|
||||
self.workspaceStructureGenerator = workspaceStructureGenerator
|
||||
self.cocoapodsInteractor = cocoapodsInteractor
|
||||
self.schemesGenerator = schemesGenerator
|
||||
}
|
||||
|
||||
// MARK: - WorkspaceGenerating
|
||||
|
||||
/// Generates the given workspace.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - workspace: Workspace model.
|
||||
/// - path: Path to the directory where the generation command is executed from.
|
||||
/// - graph: In-memory representation of the graph.
|
||||
/// - tuistConfig: Tuist configuration.
|
||||
/// - Returns: Path to the generated workspace.
|
||||
/// - Throws: An error if the generation fails.
|
||||
@discardableResult
|
||||
func generate(workspace: Workspace,
|
||||
path: AbsolutePath,
|
||||
graph: Graphing,
|
||||
tuistConfig _: TuistConfig) throws -> AbsolutePath {
|
||||
func generate(workspace: Workspace, path: AbsolutePath, graph: Graphing) throws -> WorkspaceDescriptor {
|
||||
let workspaceName = "\(graph.name).xcworkspace"
|
||||
|
||||
Printer.shared.print(section: "Generating workspace \(workspaceName)")
|
||||
logger.notice("Generating workspace \(workspaceName)", metadata: .section)
|
||||
|
||||
/// Projects
|
||||
|
||||
var generatedProjects = [AbsolutePath: GeneratedProject]()
|
||||
try graph.projects.forEach { project in
|
||||
let generatedProject = try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: project.path,
|
||||
xcodeprojPath: nil)
|
||||
generatedProjects[project.path] = generatedProject
|
||||
let projects = try graph.projects.map { project in
|
||||
try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: project.path,
|
||||
xcodeprojPath: nil)
|
||||
}
|
||||
|
||||
let generatedProjects: [AbsolutePath: GeneratedProject] = Dictionary(uniqueKeysWithValues: projects.map { project in
|
||||
let pbxproj = project.xcodeProj.pbxproj
|
||||
let targets = pbxproj.nativeTargets.map {
|
||||
($0.name, $0)
|
||||
}
|
||||
return (project.path,
|
||||
GeneratedProject(pbxproj: pbxproj,
|
||||
path: project.xcodeprojPath,
|
||||
targets: Dictionary(targets, uniquingKeysWith: { $1 }),
|
||||
name: project.xcodeprojPath.basename))
|
||||
})
|
||||
|
||||
// Workspace structure
|
||||
let structure = workspaceStructureGenerator.generateStructure(path: path,
|
||||
workspace: workspace,
|
||||
|
@ -115,128 +103,18 @@ final class WorkspaceGenerator: WorkspaceGenerating {
|
|||
path: path)
|
||||
}
|
||||
|
||||
try write(workspace: workspace,
|
||||
xcworkspace: xcWorkspace,
|
||||
generatedProjects: generatedProjects,
|
||||
graph: graph,
|
||||
to: workspacePath)
|
||||
|
||||
// Schemes
|
||||
|
||||
try writeSchemes(workspace: workspace,
|
||||
xcworkspace: xcWorkspace,
|
||||
generatedProjects: generatedProjects,
|
||||
graph: graph,
|
||||
to: workspacePath)
|
||||
let schemes = try schemesGenerator.generateWorkspaceSchemes(workspace: workspace,
|
||||
generatedProjects: generatedProjects,
|
||||
graph: graph)
|
||||
|
||||
// SPM
|
||||
|
||||
try generatePackageDependencyManager(at: path,
|
||||
workspace: workspace,
|
||||
workspaceName: workspaceName,
|
||||
graph: graph)
|
||||
|
||||
// CocoaPods
|
||||
|
||||
try cocoapodsInteractor.install(graph: graph)
|
||||
|
||||
return workspacePath
|
||||
}
|
||||
|
||||
private func generatePackageDependencyManager(
|
||||
at path: AbsolutePath,
|
||||
workspace _: Workspace,
|
||||
workspaceName: String,
|
||||
graph: Graphing
|
||||
) throws {
|
||||
let packages = graph.packages
|
||||
|
||||
if packages.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let hasRemotePackage = packages.first(where: { node in
|
||||
switch node.package {
|
||||
case .remote: return true
|
||||
case .local: return false
|
||||
}
|
||||
}) != nil
|
||||
|
||||
let rootPackageResolvedPath = path.appending(component: ".package.resolved")
|
||||
let workspacePackageResolvedFolderPath = path.appending(RelativePath("\(workspaceName)/xcshareddata/swiftpm"))
|
||||
let workspacePackageResolvedPath = workspacePackageResolvedFolderPath.appending(component: "Package.resolved")
|
||||
|
||||
if hasRemotePackage, FileHandler.shared.exists(rootPackageResolvedPath) {
|
||||
try FileHandler.shared.createFolder(workspacePackageResolvedFolderPath)
|
||||
if FileHandler.shared.exists(workspacePackageResolvedPath) {
|
||||
try FileHandler.shared.delete(workspacePackageResolvedPath)
|
||||
}
|
||||
try FileHandler.shared.copy(from: rootPackageResolvedPath, to: workspacePackageResolvedPath)
|
||||
}
|
||||
|
||||
let workspacePath = path.appending(component: workspaceName)
|
||||
// -list parameter is a workaround to resolve package dependencies for given workspace without specifying scheme
|
||||
try System.shared.runAndPrint(["xcodebuild", "-resolvePackageDependencies", "-workspace", workspacePath.pathString, "-list"])
|
||||
|
||||
if hasRemotePackage {
|
||||
if FileHandler.shared.exists(rootPackageResolvedPath) {
|
||||
try FileHandler.shared.delete(rootPackageResolvedPath)
|
||||
}
|
||||
|
||||
try FileHandler.shared.linkFile(atPath: workspacePackageResolvedPath, toPath: rootPackageResolvedPath)
|
||||
}
|
||||
}
|
||||
|
||||
private func write(workspace _: Workspace,
|
||||
xcworkspace: XCWorkspace,
|
||||
generatedProjects _: [AbsolutePath: GeneratedProject],
|
||||
graph _: Graphing,
|
||||
to: AbsolutePath) throws {
|
||||
let workspaceDataFile = "contents.xcworkspacedata"
|
||||
let fileHandler = FileHandler.shared
|
||||
|
||||
// If the workspace doesn't exist we can write it because there isn't any
|
||||
// Xcode instance that might depend on it.
|
||||
if !fileHandler.exists(to.appending(component: workspaceDataFile)) {
|
||||
try xcworkspace.write(path: to.path)
|
||||
return
|
||||
}
|
||||
|
||||
// If the workspace exists, we want to reduce the likeliness of causing
|
||||
// Xcode not to be able to reload the workspace.
|
||||
// We only replace the current one if something has changed.
|
||||
try fileHandler.inTemporaryDirectory { temporaryPath in
|
||||
let temporaryPath = temporaryPath.appending(component: to.basename)
|
||||
try xcworkspace.write(path: temporaryPath.path)
|
||||
|
||||
let workspaceData: (AbsolutePath) throws -> Data = {
|
||||
let dataPath = $0.appending(component: workspaceDataFile)
|
||||
return try Data(contentsOf: dataPath.url)
|
||||
}
|
||||
|
||||
let currentData = try workspaceData(to)
|
||||
let currentWorkspaceData = try workspaceData(temporaryPath)
|
||||
|
||||
guard currentData != currentWorkspaceData else {
|
||||
return
|
||||
}
|
||||
|
||||
try fileHandler.createFolder(to)
|
||||
try fileHandler.replace(to.appending(component: workspaceDataFile),
|
||||
with: temporaryPath.appending(component: workspaceDataFile))
|
||||
}
|
||||
}
|
||||
|
||||
private func writeSchemes(workspace: Workspace,
|
||||
xcworkspace _: XCWorkspace,
|
||||
generatedProjects: [AbsolutePath: GeneratedProject],
|
||||
graph: Graphing,
|
||||
to path: AbsolutePath) throws {
|
||||
try schemesGenerator.wipeSchemes(at: path)
|
||||
try schemesGenerator.generateWorkspaceSchemes(workspace: workspace,
|
||||
xcworkspacePath: path,
|
||||
generatedProjects: generatedProjects,
|
||||
graph: graph)
|
||||
return WorkspaceDescriptor(path: path,
|
||||
xcworkspacePath: workspacePath,
|
||||
xcworkspace: xcWorkspace,
|
||||
projectDescriptors: projects,
|
||||
schemeDescriptors: schemes,
|
||||
sideEffectDescriptors: [])
|
||||
}
|
||||
|
||||
/// Create a XCWorkspaceDataElement.file from a path string.
|
||||
|
|
|
@ -7,14 +7,14 @@ public protocol EnvironmentLinting {
|
|||
///
|
||||
/// - Parameter config: Tuist configuration to be linted against the system.
|
||||
/// - Returns: A list of linting issues.
|
||||
func lint(config: TuistConfig) throws -> [LintingIssue]
|
||||
func lint(config: Config) throws -> [LintingIssue]
|
||||
}
|
||||
|
||||
public class EnvironmentLinter: EnvironmentLinting {
|
||||
/// Default constructor.
|
||||
public init() {}
|
||||
|
||||
public func lint(config: TuistConfig) throws -> [LintingIssue] {
|
||||
public func lint(config: Config) throws -> [LintingIssue] {
|
||||
var issues = [LintingIssue]()
|
||||
|
||||
issues.append(contentsOf: try lintXcodeVersion(config: config))
|
||||
|
@ -28,7 +28,7 @@ public class EnvironmentLinter: EnvironmentLinting {
|
|||
/// - Parameter config: Tuist configuration.
|
||||
/// - Returns: An array with a linting issue if the selected version is not compatible.
|
||||
/// - Throws: An error if there's an error obtaining the selected Xcode version.
|
||||
func lintXcodeVersion(config: TuistConfig) throws -> [LintingIssue] {
|
||||
func lintXcodeVersion(config: Config) throws -> [LintingIssue] {
|
||||
guard case let CompatibleXcodeVersions.list(compatibleVersions) = config.compatibleXcodeVersions else {
|
||||
return []
|
||||
}
|
||||
|
|
|
@ -17,8 +17,10 @@ public class GraphLinter: GraphLinting {
|
|||
// MARK: - Init
|
||||
|
||||
public convenience init() {
|
||||
self.init(projectLinter: ProjectLinter(),
|
||||
staticProductsLinter: StaticProductsGraphLinter())
|
||||
let projectLinter = ProjectLinter()
|
||||
let staticProductsLinter = StaticProductsGraphLinter()
|
||||
self.init(projectLinter: projectLinter,
|
||||
staticProductsLinter: staticProductsLinter)
|
||||
}
|
||||
|
||||
init(projectLinter: ProjectLinting,
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import TuistSupport
|
||||
let logger = Logger(label: "io.tuist.generator")
|
|
@ -28,7 +28,7 @@ enum CocoaPodsInteractorError: FatalError, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
protocol CocoaPodsInteracting {
|
||||
public protocol CocoaPodsInteracting {
|
||||
/// Runs 'pod install' for all the CocoaPods dependencies that have been indicated in the graph.
|
||||
///
|
||||
/// - Parameter graph: Project graph.
|
||||
|
@ -36,17 +36,19 @@ protocol CocoaPodsInteracting {
|
|||
func install(graph: Graphing) throws
|
||||
}
|
||||
|
||||
final class CocoaPodsInteractor: CocoaPodsInteracting {
|
||||
public final class CocoaPodsInteractor: CocoaPodsInteracting {
|
||||
public init() {}
|
||||
|
||||
/// Runs 'pod install' for all the CocoaPods dependencies that have been indicated in the graph.
|
||||
///
|
||||
/// - Parameter graph: Project graph.
|
||||
/// - Throws: An error if the installation of the pods fails.
|
||||
func install(graph: Graphing) throws {
|
||||
public func install(graph: Graphing) throws {
|
||||
do {
|
||||
try install(graph: graph, updatingRepo: false)
|
||||
} catch let error as CocoaPodsInteractorError {
|
||||
if case CocoaPodsInteractorError.outdatedRepository = error {
|
||||
Printer.shared.print(warning: "The local CocoaPods specs repository is outdated. Re-running 'pod install' updating the repository.")
|
||||
logger.warning("The local CocoaPods specs repository is outdated. Re-running 'pod install' updating the repository.")
|
||||
try self.install(graph: graph, updatingRepo: true)
|
||||
} else {
|
||||
throw error
|
||||
|
@ -81,7 +83,8 @@ final class CocoaPodsInteractor: CocoaPodsInteracting {
|
|||
|
||||
// The installation of Pods might fail if the local repository that contains the specs
|
||||
// is outdated.
|
||||
Printer.shared.print(section: "Installing CocoaPods dependencies defined in \(node.podfilePath)")
|
||||
logger.notice("Installing CocoaPods dependencies defined in \(node.podfilePath)", metadata: .section)
|
||||
|
||||
var mightNeedRepoUpdate: Bool = false
|
||||
let outputClosure: ([UInt8]) -> Void = { bytes in
|
||||
let content = String(data: Data(bytes), encoding: .utf8)
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
/// Swift Package Manager Interactor
|
||||
///
|
||||
/// This component is responsible for resolving
|
||||
/// any Swift package manager dependencies declared
|
||||
/// within projects in the graph.
|
||||
///
|
||||
public protocol SwiftPackageManagerInteracting {
|
||||
/// Installs Swift Package dependencies for a given graph and workspace
|
||||
///
|
||||
/// - The instllation process involves performing a Swift package dependency
|
||||
/// resolution to generated the `Package.resolved` file (via `xcodebuild`).
|
||||
/// - This file is then symlinked to the root path of the workspace.
|
||||
///
|
||||
/// - Note: this should be called post generation and writing projects
|
||||
/// and workspaces to disk.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The graph of projects
|
||||
/// - workspaceName: The name of the generated workspace (e.g. `MyWorkspace.xcworkspace`)
|
||||
func install(graph: Graphing, workspaceName: String) throws
|
||||
}
|
||||
|
||||
public class SwiftPackageManagerInteractor: SwiftPackageManagerInteracting {
|
||||
private let fileHandler: FileHandling
|
||||
public init(fileHandler: FileHandling = FileHandler.shared) {
|
||||
self.fileHandler = fileHandler
|
||||
}
|
||||
|
||||
public func install(graph: Graphing, workspaceName: String) throws {
|
||||
try generatePackageDependencyManager(at: graph.entryPath,
|
||||
workspaceName: workspaceName,
|
||||
graph: graph)
|
||||
}
|
||||
|
||||
private func generatePackageDependencyManager(
|
||||
at path: AbsolutePath,
|
||||
workspaceName: String,
|
||||
graph: Graphing
|
||||
) throws {
|
||||
let packages = graph.packages
|
||||
guard !packages.remotePackages.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
let rootPackageResolvedPath = path.appending(component: ".package.resolved")
|
||||
let workspacePackageResolvedFolderPath = path.appending(RelativePath("\(workspaceName)/xcshareddata/swiftpm"))
|
||||
let workspacePackageResolvedPath = workspacePackageResolvedFolderPath.appending(component: "Package.resolved")
|
||||
|
||||
if fileHandler.exists(rootPackageResolvedPath) {
|
||||
try fileHandler.createFolder(workspacePackageResolvedFolderPath)
|
||||
if fileHandler.exists(workspacePackageResolvedPath) {
|
||||
try fileHandler.delete(workspacePackageResolvedPath)
|
||||
}
|
||||
try fileHandler.copy(from: rootPackageResolvedPath, to: workspacePackageResolvedPath)
|
||||
}
|
||||
|
||||
let workspacePath = path.appending(component: workspaceName)
|
||||
// -list parameter is a workaround to resolve package dependencies for given workspace without specifying scheme
|
||||
try System.shared.runAndPrint(["xcodebuild", "-resolvePackageDependencies", "-workspace", workspacePath.pathString, "-list"])
|
||||
|
||||
if fileHandler.exists(rootPackageResolvedPath) {
|
||||
try fileHandler.delete(rootPackageResolvedPath)
|
||||
}
|
||||
|
||||
try fileHandler.linkFile(atPath: workspacePackageResolvedPath, toPath: rootPackageResolvedPath)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Array where Element == PackageNode {
|
||||
var remotePackages: [PackageNode] {
|
||||
compactMap { node in
|
||||
switch node.package {
|
||||
case .remote:
|
||||
return node
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistSupport
|
||||
import XcodeProj
|
||||
|
||||
public protocol XcodeProjWriting {
|
||||
func write(project: ProjectDescriptor) throws
|
||||
func write(workspace: WorkspaceDescriptor) throws
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public final class XcodeProjWriter: XcodeProjWriting {
|
||||
private let fileHandler: FileHandling
|
||||
private let system: Systeming
|
||||
|
||||
public init(fileHandler: FileHandling = FileHandler.shared,
|
||||
system: Systeming = System.shared) {
|
||||
self.fileHandler = fileHandler
|
||||
self.system = system
|
||||
}
|
||||
|
||||
public func write(project: ProjectDescriptor) throws {
|
||||
let project = enrichingXcodeProjWithSchemes(descriptor: project)
|
||||
try project.xcodeProj.write(path: project.xcodeprojPath.path)
|
||||
try project.schemeDescriptors.forEach { try write(scheme: $0, xccontainerPath: project.xcodeprojPath) }
|
||||
try project.sideEffectDescriptors.forEach(perform)
|
||||
}
|
||||
|
||||
public func write(workspace: WorkspaceDescriptor) throws {
|
||||
try workspace.projectDescriptors.forEach(write)
|
||||
try workspace.xcworkspace.write(path: workspace.xcworkspacePath.path, override: true)
|
||||
try workspace.schemeDescriptors.forEach { try write(scheme: $0, xccontainerPath: workspace.xcworkspacePath) }
|
||||
try workspace.sideEffectDescriptors.forEach(perform)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func enrichingXcodeProjWithSchemes(descriptor: ProjectDescriptor) -> ProjectDescriptor {
|
||||
let sharedSchemes = descriptor.schemeDescriptors.filter { $0.shared }
|
||||
let userSchemes = descriptor.schemeDescriptors.filter { !$0.shared }
|
||||
|
||||
let xcodeProj = descriptor.xcodeProj
|
||||
let sharedData = xcodeProj.sharedData ?? XCSharedData(schemes: [])
|
||||
|
||||
sharedData.schemes.append(contentsOf: sharedSchemes.map { $0.xcScheme })
|
||||
xcodeProj.sharedData = sharedData
|
||||
|
||||
return ProjectDescriptor(path: descriptor.path,
|
||||
xcodeprojPath: descriptor.xcodeprojPath,
|
||||
xcodeProj: descriptor.xcodeProj,
|
||||
schemeDescriptors: userSchemes,
|
||||
sideEffectDescriptors: descriptor.sideEffectDescriptors)
|
||||
}
|
||||
|
||||
private func write(scheme: SchemeDescriptor,
|
||||
xccontainerPath: AbsolutePath) throws {
|
||||
let schemeDirectory = self.schemeDirectory(path: xccontainerPath, shared: scheme.shared)
|
||||
let schemePath = schemeDirectory.appending(component: "\(scheme.xcScheme.name).xcscheme")
|
||||
try fileHandler.createFolder(schemeDirectory)
|
||||
try scheme.xcScheme.write(path: schemePath.path, override: true)
|
||||
}
|
||||
|
||||
private func perform(sideEffect: SideEffectDescriptor) throws {
|
||||
switch sideEffect {
|
||||
case let .file(file):
|
||||
try process(file: file)
|
||||
case let .command(command):
|
||||
try perform(command: command)
|
||||
}
|
||||
}
|
||||
|
||||
private func process(file: FileDescriptor) throws {
|
||||
switch file.state {
|
||||
case .present:
|
||||
try fileHandler.createFolder(file.path.parentDirectory)
|
||||
if let contents = file.contents {
|
||||
try contents.write(to: file.path.url)
|
||||
} else {
|
||||
try fileHandler.touch(file.path)
|
||||
}
|
||||
case .absent:
|
||||
try fileHandler.delete(file.path)
|
||||
}
|
||||
}
|
||||
|
||||
private func perform(command: CommandDescriptor) throws {
|
||||
try system.run(command.command)
|
||||
}
|
||||
|
||||
private func schemeDirectory(path: AbsolutePath, shared: Bool = true) -> AbsolutePath {
|
||||
if shared {
|
||||
return path.appending(RelativePath("xcshareddata/xcschemes"))
|
||||
} else {
|
||||
let username = NSUserName()
|
||||
return path.appending(RelativePath("xcuserdata/\(username).xcuserdatad/xcschemes"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
import Basic
|
||||
import Foundation
|
||||
import XcodeProj
|
||||
@testable import TuistGenerator
|
||||
|
||||
public extension ProjectDescriptor {
|
||||
static func test(path: AbsolutePath = AbsolutePath("/Test"),
|
||||
xcodeprojPath: AbsolutePath? = nil,
|
||||
schemes: [SchemeDescriptor] = [],
|
||||
sideEffects: [SideEffectDescriptor] = []) -> ProjectDescriptor {
|
||||
let mainGroup = PBXGroup()
|
||||
let configurationList = XCConfigurationList()
|
||||
let pbxProject = PBXProject(name: "Test",
|
||||
buildConfigurationList: configurationList,
|
||||
compatibilityVersion: "1",
|
||||
mainGroup: mainGroup)
|
||||
let pbxproj = PBXProj(objectVersion: 50,
|
||||
archiveVersion: 10)
|
||||
pbxproj.add(object: mainGroup)
|
||||
pbxproj.add(object: pbxProject)
|
||||
pbxproj.add(object: configurationList)
|
||||
pbxproj.rootObject = pbxProject
|
||||
let xcodeProj = XcodeProj(workspace: XCWorkspace(), pbxproj: pbxproj)
|
||||
return ProjectDescriptor(path: path,
|
||||
xcodeprojPath: xcodeprojPath ?? path.appending(component: "Test.xcodeproj"),
|
||||
xcodeProj: xcodeProj,
|
||||
schemeDescriptors: schemes,
|
||||
sideEffectDescriptors: sideEffects)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import Basic
|
||||
import Foundation
|
||||
import XcodeProj
|
||||
@testable import TuistGenerator
|
||||
|
||||
public extension SchemeDescriptor {
|
||||
static func test(name: String, shared: Bool) -> SchemeDescriptor {
|
||||
let scheme = XCScheme(name: name, lastUpgradeVersion: "1131", version: "1")
|
||||
return SchemeDescriptor(xcScheme: scheme, shared: shared)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
import Basic
|
||||
import Foundation
|
||||
import XcodeProj
|
||||
@testable import TuistGenerator
|
||||
|
||||
public extension WorkspaceDescriptor {
|
||||
static func test(path: AbsolutePath = AbsolutePath("/Test"),
|
||||
xcworkspacePath: AbsolutePath = AbsolutePath("/Test/Project.xcworkspace"),
|
||||
projects: [ProjectDescriptor] = [],
|
||||
schemes: [SchemeDescriptor] = [],
|
||||
sideEffects: [SideEffectDescriptor] = []) -> WorkspaceDescriptor {
|
||||
WorkspaceDescriptor(path: path,
|
||||
xcworkspacePath: xcworkspacePath,
|
||||
xcworkspace: XCWorkspace(),
|
||||
projectDescriptors: projects,
|
||||
schemeDescriptors: schemes,
|
||||
sideEffectDescriptors: sideEffects)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
import XcodeProj
|
||||
@testable import TuistGenerator
|
||||
|
||||
final class MockDescriptorGenerator: DescriptorGenerating {
|
||||
enum MockError: Error {
|
||||
case stubNotImplemented
|
||||
}
|
||||
|
||||
var generateProjectSub: ((Project, Graph) throws -> ProjectDescriptor)?
|
||||
func generateProject(project: Project, graph: Graph) throws -> ProjectDescriptor {
|
||||
guard let generateProjectSub = generateProjectSub else {
|
||||
throw MockError.stubNotImplemented
|
||||
}
|
||||
|
||||
return try generateProjectSub(project, graph)
|
||||
}
|
||||
|
||||
var generateProjectWithConfigStub: ((Project, Graph, ProjectGenerationConfig) throws -> ProjectDescriptor)?
|
||||
func generateProject(project: Project, graph: Graph, config: ProjectGenerationConfig) throws -> ProjectDescriptor {
|
||||
guard let generateProjectWithConfigStub = generateProjectWithConfigStub else {
|
||||
throw MockError.stubNotImplemented
|
||||
}
|
||||
|
||||
return try generateProjectWithConfigStub(project, graph, config)
|
||||
}
|
||||
|
||||
var generateWorkspaceStub: ((Workspace, Graph) throws -> WorkspaceDescriptor)?
|
||||
func generateWorkspace(workspace: Workspace, graph: Graph) throws -> WorkspaceDescriptor {
|
||||
guard let generateWorkspaceStub = generateWorkspaceStub else {
|
||||
throw MockError.stubNotImplemented
|
||||
}
|
||||
|
||||
return try generateWorkspaceStub(workspace, graph)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
import Foundation
|
||||
@testable import TuistGenerator
|
||||
|
||||
final class MockXcodeProjWriter: XcodeProjWriting {
|
||||
var writeProjectCalls: [ProjectDescriptor] = []
|
||||
func write(project: ProjectDescriptor) throws {
|
||||
writeProjectCalls.append(project)
|
||||
}
|
||||
|
||||
var writeworkspaceCalls: [WorkspaceDescriptor] = []
|
||||
func write(workspace: WorkspaceDescriptor) throws {
|
||||
writeworkspaceCalls.append(workspace)
|
||||
}
|
||||
}
|
|
@ -4,11 +4,11 @@ import TuistCore
|
|||
|
||||
public final class MockEnvironmentLinter: EnvironmentLinting {
|
||||
public var lintStub: [LintingIssue]?
|
||||
public var lintArgs: [TuistConfig] = []
|
||||
public var lintArgs: [Config] = []
|
||||
|
||||
public init() {}
|
||||
|
||||
public func lint(config: TuistConfig) throws -> [LintingIssue] {
|
||||
public func lint(config: Config) throws -> [LintingIssue] {
|
||||
lintArgs.append(config)
|
||||
return lintStub ?? []
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
import Foundation
|
|
@ -17,10 +17,7 @@ protocol CacheControlling {
|
|||
|
||||
final class CacheController: CacheControlling {
|
||||
/// Xcode project generator.
|
||||
private let generator: Generating
|
||||
|
||||
/// Manifest loader.
|
||||
private let manifestLoader: ManifestLoading
|
||||
private let generator: ProjectGenerating
|
||||
|
||||
/// Utility to build the xcframeworks.
|
||||
private let xcframeworkBuilder: XCFrameworkBuilding
|
||||
|
@ -31,28 +28,26 @@ final class CacheController: CacheControlling {
|
|||
/// Cache.
|
||||
private let cache: CacheStoraging
|
||||
|
||||
init(generator: Generating = Generator(),
|
||||
manifestLoader: ManifestLoading = ManifestLoader(),
|
||||
init(generator: ProjectGenerating = ProjectGenerator(),
|
||||
xcframeworkBuilder: XCFrameworkBuilding = XCFrameworkBuilder(xcodeBuildController: XcodeBuildController()),
|
||||
cache: CacheStoraging = Cache(),
|
||||
graphContentHasher: GraphContentHashing = GraphContentHasher()) {
|
||||
self.generator = generator
|
||||
self.manifestLoader = manifestLoader
|
||||
self.xcframeworkBuilder = xcframeworkBuilder
|
||||
self.cache = cache
|
||||
self.graphContentHasher = graphContentHasher
|
||||
}
|
||||
|
||||
func cache(path: AbsolutePath) throws {
|
||||
let (path, graph) = try generator.generateWorkspace(at: path, manifestLoader: manifestLoader)
|
||||
let (path, graph) = try generator.generateWithGraph(path: path, projectOnly: false)
|
||||
|
||||
Printer.shared.print(section: "Hashing cacheable frameworks")
|
||||
logger.notice("Hashing cacheable frameworks")
|
||||
let cacheableTargets = try self.cacheableTargets(graph: graph)
|
||||
|
||||
let completables = try cacheableTargets.map { try buildAndCacheXCFramework(path: path, target: $0.key, hash: $0.value) }
|
||||
_ = try Completable.zip(completables).toBlocking().last()
|
||||
|
||||
Printer.shared.print(success: "All cacheable frameworks have been cached successfully")
|
||||
logger.notice("All cacheable frameworks have been cached successfully", metadata: .success)
|
||||
}
|
||||
|
||||
/// Returns all the targets that are cacheable and their hashes.
|
||||
|
@ -61,7 +56,7 @@ final class CacheController: CacheControlling {
|
|||
try graphContentHasher.contentHashes(for: graph)
|
||||
.filter { target, hash in
|
||||
if let exists = try self.cache.exists(hash: hash).toBlocking().first(), exists {
|
||||
Printer.shared.print("The target \(.bold(.raw(target.name))) with hash \(.bold(.raw(hash))) is already in the cache. Skipping...")
|
||||
logger.pretty("The target \(.bold(.raw(target.name))) with hash \(.bold(.raw(hash))) is already in the cache. Skipping...")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -27,6 +27,6 @@ class BuildCommand: NSObject, RawCommand {
|
|||
}
|
||||
|
||||
func run(arguments _: [String]) throws {
|
||||
Printer.shared.print("Command not available yet")
|
||||
logger.notice("Command not available yet")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public final class CommandRegistry {
|
|||
}
|
||||
|
||||
public static func processArguments() -> [String] {
|
||||
Array(ProcessInfo.processInfo.arguments)
|
||||
Array(ProcessInfo.processInfo.arguments).filter { $0 != "--verbose" }
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
|
|
@ -44,6 +44,6 @@ class DumpCommand: NSObject, Command {
|
|||
}
|
||||
let project = try manifestLoader.loadProject(at: path)
|
||||
let json: JSON = try project.toJSON()
|
||||
Printer.shared.print("\(json.toString(prettyPrint: true))")
|
||||
logger.notice("\(json.toString(prettyPrint: true))")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,10 +52,10 @@ class EditCommand: NSObject, Command {
|
|||
try! FileHandler.shared.delete(EditCommand.temporaryDirectory.path)
|
||||
exit(0)
|
||||
}
|
||||
Printer.shared.print(success: "Opening Xcode to edit the project. Press \(.keystroke("CTRL + C")) once you are done editing")
|
||||
logger.pretty("Opening Xcode to edit the project. Press \(.keystroke("CTRL + C")) once you are done editing")
|
||||
try opener.open(path: xcodeprojPath, wait: true)
|
||||
} else {
|
||||
Printer.shared.print(success: "Xcode project generated at \(xcodeprojPath.pathString)")
|
||||
logger.notice("Xcode project generated at \(xcodeprojPath.pathString)", metadata: .success)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,7 @@ class FocusCommand: NSObject, Command {
|
|||
// MARK: - Attributes
|
||||
|
||||
/// Generator instance to generate the project workspace.
|
||||
private let generator: Generating
|
||||
|
||||
/// Manifest loader instance that can load project maifests from disk
|
||||
private let manifestLoader: ManifestLoading
|
||||
private let generator: ProjectGenerating
|
||||
|
||||
/// Opener instance to run open in the system.
|
||||
private let opener: Opening
|
||||
|
@ -32,14 +29,8 @@ class FocusCommand: NSObject, Command {
|
|||
///
|
||||
/// - Parameter parser: Argument parser that parses the CLI arguments.
|
||||
required convenience init(parser: ArgumentParser) {
|
||||
let manifestLoader = ManifestLoader()
|
||||
let manifestLinter = ManifestLinter()
|
||||
let modelLoader = GeneratorModelLoader(manifestLoader: manifestLoader,
|
||||
manifestLinter: manifestLinter)
|
||||
let generator = Generator(modelLoader: modelLoader)
|
||||
self.init(parser: parser,
|
||||
generator: generator,
|
||||
manifestLoader: manifestLoader,
|
||||
generator: ProjectGenerator(),
|
||||
opener: Opener())
|
||||
}
|
||||
|
||||
|
@ -48,24 +39,20 @@ class FocusCommand: NSObject, Command {
|
|||
/// - Parameters:
|
||||
/// - parser: Argument parser that parses the CLI arguments.
|
||||
/// - generator: Generator instance to generate the project workspace.
|
||||
/// - manifestLoader: Manifest loader instance that can load project maifests from disk
|
||||
/// - opener: Opener instance to run open in the system.
|
||||
init(parser: ArgumentParser,
|
||||
generator: Generating,
|
||||
manifestLoader: ManifestLoading,
|
||||
generator: ProjectGenerating,
|
||||
opener: Opening) {
|
||||
parser.add(subparser: FocusCommand.command, overview: FocusCommand.overview)
|
||||
self.generator = generator
|
||||
self.manifestLoader = manifestLoader
|
||||
self.opener = opener
|
||||
}
|
||||
|
||||
func run(with _: ArgumentParser.Result) throws {
|
||||
let path = FileHandler.shared.currentPath
|
||||
|
||||
let (workspacePath, _) = try generator.generate(at: path,
|
||||
manifestLoader: manifestLoader,
|
||||
projectOnly: false)
|
||||
let workspacePath = try generator.generate(path: path,
|
||||
projectOnly: false)
|
||||
|
||||
try opener.open(path: workspacePath)
|
||||
}
|
||||
|
|
|
@ -13,32 +13,25 @@ class GenerateCommand: NSObject, Command {
|
|||
|
||||
// MARK: - Attributes
|
||||
|
||||
private let generator: Generating
|
||||
private let manifestLoader: ManifestLoading
|
||||
private let clock: Clock
|
||||
private let generator: ProjectGenerating
|
||||
let pathArgument: OptionArgument<String>
|
||||
let projectOnlyArgument: OptionArgument<Bool>
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
required convenience init(parser: ArgumentParser) {
|
||||
let manifestLoader = ManifestLoader()
|
||||
let manifestLinter = ManifestLinter()
|
||||
let modelLoader = GeneratorModelLoader(manifestLoader: manifestLoader, manifestLinter: manifestLinter)
|
||||
let generator = Generator(modelLoader: modelLoader)
|
||||
let projectGenerator = ProjectGenerator()
|
||||
self.init(parser: parser,
|
||||
generator: generator,
|
||||
manifestLoader: manifestLoader,
|
||||
generator: projectGenerator,
|
||||
clock: WallClock())
|
||||
}
|
||||
|
||||
init(parser: ArgumentParser,
|
||||
generator: Generating,
|
||||
manifestLoader: ManifestLoading,
|
||||
generator: ProjectGenerating,
|
||||
clock: Clock) {
|
||||
let subParser = parser.add(subparser: GenerateCommand.command, overview: GenerateCommand.overview)
|
||||
self.generator = generator
|
||||
self.manifestLoader = manifestLoader
|
||||
self.clock = clock
|
||||
|
||||
pathArgument = subParser.add(option: "--path",
|
||||
|
@ -57,13 +50,12 @@ class GenerateCommand: NSObject, Command {
|
|||
let path = self.path(arguments: arguments)
|
||||
let projectOnly = arguments.get(projectOnlyArgument) ?? false
|
||||
|
||||
_ = try generator.generate(at: path,
|
||||
manifestLoader: manifestLoader,
|
||||
projectOnly: projectOnly)
|
||||
try generator.generate(path: path, projectOnly: projectOnly)
|
||||
|
||||
let time = String(format: "%.3f", timer.stop())
|
||||
Printer.shared.print(success: "Project generated.")
|
||||
Printer.shared.print("Total time taken: \(time)s")
|
||||
|
||||
logger.notice("Project generated.", metadata: .success)
|
||||
logger.notice("Total time taken: \(time)s")
|
||||
}
|
||||
|
||||
// MARK: - Fileprivate
|
||||
|
|
|
@ -45,11 +45,11 @@ class GraphCommand: NSObject, Command {
|
|||
|
||||
let path = FileHandler.shared.currentPath.appending(component: "graph.dot")
|
||||
if FileHandler.shared.exists(path) {
|
||||
Printer.shared.print("Deleting existing graph at \(path.pathString)")
|
||||
logger.notice("Deleting existing graph at \(path.pathString)")
|
||||
try FileHandler.shared.delete(path)
|
||||
}
|
||||
|
||||
try FileHandler.shared.write(graph, path: path, atomically: true)
|
||||
Printer.shared.print(success: "Graph exported to \(path.pathString)")
|
||||
logger.notice("Graph exported to \(path.pathString)", metadata: .success)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ class InitCommand: NSObject, Command {
|
|||
kind: String.self,
|
||||
usage: "The name of the project. If it's not passed (Default: Name of the directory).",
|
||||
completion: nil)
|
||||
|
||||
self.playgroundGenerator = playgroundGenerator
|
||||
}
|
||||
|
||||
|
@ -92,10 +93,10 @@ class InitCommand: NSObject, Command {
|
|||
try generateWorkspaceSwift(name: name, platform: platform, path: path)
|
||||
try generateSwiftFiles(name: name, platform: platform, path: path)
|
||||
try generatePlaygrounds(name: name, path: path, platform: platform)
|
||||
try generateTuistConfig(path: path)
|
||||
try generateConfig(path: path)
|
||||
try generateGitIgnore(path: path)
|
||||
|
||||
Printer.shared.print(success: "Project generated at path \(path.pathString).")
|
||||
logger.notice("Project generated at path \(path.pathString).", metadata: .success)
|
||||
}
|
||||
|
||||
// MARK: - Fileprivate
|
||||
|
@ -328,15 +329,19 @@ class InitCommand: NSObject, Command {
|
|||
try content.write(to: setupPath.url, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
private func generateTuistConfig(path: AbsolutePath) throws {
|
||||
private func generateConfig(path: AbsolutePath) throws {
|
||||
let content = """
|
||||
import ProjectDescription
|
||||
|
||||
let config = TuistConfig(generationOptions: [
|
||||
let config = Config(generationOptions: [
|
||||
])
|
||||
"""
|
||||
let setupPath = path.appending(component: Manifest.tuistConfig.fileName)
|
||||
try content.write(to: setupPath.url, atomically: true, encoding: .utf8)
|
||||
let configPath = path.appending(RelativePath("\(Constants.tuistDirectoryName)/\(Manifest.config.fileName)"))
|
||||
if !FileHandler.shared.exists(configPath.parentDirectory) {
|
||||
try FileHandler.shared.createFolder(configPath.parentDirectory)
|
||||
}
|
||||
|
||||
try content.write(to: configPath.url, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
|
|
|
@ -78,28 +78,28 @@ class LintCommand: NSObject, Command {
|
|||
let manifests = manifestLoading.manifests(at: path)
|
||||
var graph: Graphing!
|
||||
|
||||
Printer.shared.print(section: "Loading the dependency graph")
|
||||
logger.notice("Loading the dependency graph")
|
||||
if manifests.contains(.workspace) {
|
||||
Printer.shared.print("Loading workspace at \(path.pathString)")
|
||||
logger.notice("Loading workspace at \(path.pathString)")
|
||||
(graph, _) = try graphLoader.loadWorkspace(path: path)
|
||||
} else if manifests.contains(.project) {
|
||||
Printer.shared.print("Loading project at \(path.pathString)")
|
||||
logger.notice("Loading project at \(path.pathString)")
|
||||
(graph, _) = try graphLoader.loadProject(path: path)
|
||||
} else {
|
||||
throw LintCommandError.manifestNotFound(path)
|
||||
}
|
||||
|
||||
Printer.shared.print(section: "Running linters")
|
||||
let config = try graphLoader.loadTuistConfig(path: path)
|
||||
logger.notice("Running linters")
|
||||
let config = try graphLoader.loadConfig(path: path)
|
||||
|
||||
var issues: [LintingIssue] = []
|
||||
Printer.shared.print("Linting the environment")
|
||||
logger.notice("Linting the environment")
|
||||
issues.append(contentsOf: try environmentLinter.lint(config: config))
|
||||
Printer.shared.print("Linting the loaded dependency graph")
|
||||
logger.notice("Linting the loaded dependency graph")
|
||||
issues.append(contentsOf: graphLinter.lint(graph: graph))
|
||||
|
||||
if issues.isEmpty {
|
||||
Printer.shared.print(success: "No linting issues found")
|
||||
logger.notice("No linting issues found", metadata: .success)
|
||||
} else {
|
||||
try issues.printAndThrowIfNeeded()
|
||||
}
|
||||
|
|
|
@ -18,6 +18,6 @@ class VersionCommand: NSObject, Command {
|
|||
// MARK: - Command
|
||||
|
||||
func run(with _: ArgumentParser.Result) {
|
||||
Printer.shared.print("\(Constants.version)")
|
||||
logger.notice("\(Constants.version)")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
import TuistGenerator
|
||||
import TuistLoader
|
||||
|
||||
extension Generator {
|
||||
/// Initializes a generator instance with all the dependencies that are specific to Tuist.
|
||||
convenience init() {
|
||||
let manifestLoader = ManifestLoader()
|
||||
let manifestLinter = ManifestLinter()
|
||||
let modelLoader = GeneratorModelLoader(manifestLoader: manifestLoader, manifestLinter: manifestLinter)
|
||||
self.init(modelLoader: modelLoader)
|
||||
}
|
||||
}
|
||||
|
||||
extension Generating {
|
||||
func generate(at path: AbsolutePath,
|
||||
manifestLoader: ManifestLoading,
|
||||
projectOnly: Bool) throws -> (AbsolutePath, Graphing) {
|
||||
if projectOnly {
|
||||
return try generateProject(at: path)
|
||||
} else {
|
||||
return try generateWorkspace(at: path,
|
||||
manifestLoader: manifestLoader)
|
||||
}
|
||||
}
|
||||
|
||||
func generateWorkspace(at path: AbsolutePath,
|
||||
manifestLoader: ManifestLoading) throws -> (AbsolutePath, Graphing) {
|
||||
let manifests = manifestLoader.manifests(at: path)
|
||||
if manifests.contains(.workspace) {
|
||||
return try generateWorkspace(at: path, workspaceFiles: [])
|
||||
} else if manifests.contains(.project) {
|
||||
return try generateProjectWorkspace(at: path, workspaceFiles: [])
|
||||
} else {
|
||||
throw ManifestLoaderError.manifestNotFound(path)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import TuistSupport
|
||||
let logger = Logger(label: "io.tuist")
|
|
@ -33,7 +33,7 @@ protocol ProjectEditing: AnyObject {
|
|||
|
||||
final class ProjectEditor: ProjectEditing {
|
||||
/// Project generator.
|
||||
let generator: Generating
|
||||
let generator: DescriptorGenerating
|
||||
|
||||
/// Project editor mapper.
|
||||
let projectEditorMapper: ProjectEditorMapping
|
||||
|
@ -47,16 +47,21 @@ final class ProjectEditor: ProjectEditing {
|
|||
/// Utility to locate the helpers directory.
|
||||
let helpersDirectoryLocator: HelpersDirectoryLocating
|
||||
|
||||
init(generator: Generating = Generator(),
|
||||
/// Xcode Project writer
|
||||
private let writer: XcodeProjWriting
|
||||
|
||||
init(generator: DescriptorGenerating = DescriptorGenerator(),
|
||||
projectEditorMapper: ProjectEditorMapping = ProjectEditorMapper(),
|
||||
resourceLocator: ResourceLocating = ResourceLocator(),
|
||||
manifestFilesLocator: ManifestFilesLocating = ManifestFilesLocator(),
|
||||
helpersDirectoryLocator: HelpersDirectoryLocating = HelpersDirectoryLocator()) {
|
||||
helpersDirectoryLocator: HelpersDirectoryLocating = HelpersDirectoryLocator(),
|
||||
writer: XcodeProjWriting = XcodeProjWriter()) {
|
||||
self.generator = generator
|
||||
self.projectEditorMapper = projectEditorMapper
|
||||
self.resourceLocator = resourceLocator
|
||||
self.manifestFilesLocator = manifestFilesLocator
|
||||
self.helpersDirectoryLocator = helpersDirectoryLocator
|
||||
self.writer = writer
|
||||
}
|
||||
|
||||
func edit(at: AbsolutePath, in dstDirectory: AbsolutePath) throws -> AbsolutePath {
|
||||
|
@ -82,9 +87,13 @@ final class ProjectEditor: ProjectEditing {
|
|||
manifests: manifests.map { $0.1 },
|
||||
helpers: helpers,
|
||||
projectDescriptionPath: projectDesciptionPath)
|
||||
return try generator.generateProject(project,
|
||||
graph: graph,
|
||||
sourceRootPath: project.path,
|
||||
|
||||
let config = ProjectGenerationConfig(sourceRootPath: project.path,
|
||||
xcodeprojPath: xcodeprojPath)
|
||||
let descriptor = try generator.generateProject(project: project,
|
||||
graph: graph,
|
||||
config: config)
|
||||
try writer.write(project: descriptor)
|
||||
return descriptor.xcodeprojPath
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
import TuistGenerator
|
||||
import TuistLoader
|
||||
|
||||
protocol ProjectGenerating {
|
||||
@discardableResult
|
||||
func generate(path: AbsolutePath, projectOnly: Bool) throws -> AbsolutePath
|
||||
func generateWithGraph(path: AbsolutePath, projectOnly: Bool) throws -> (AbsolutePath, Graphing)
|
||||
}
|
||||
|
||||
class ProjectGenerator: ProjectGenerating {
|
||||
private let manifestLoader: ManifestLoading = ManifestLoader()
|
||||
private let manifestLinter: ManifestLinting = ManifestLinter()
|
||||
private let graphLinter: GraphLinting = GraphLinter()
|
||||
private let environmentLinter: EnvironmentLinting = EnvironmentLinter()
|
||||
private let generator: DescriptorGenerating = DescriptorGenerator()
|
||||
private let writer: XcodeProjWriting = XcodeProjWriter()
|
||||
private let cocoapodsInteractor: CocoaPodsInteracting = CocoaPodsInteractor()
|
||||
private let swiftPackageManagerInteractor: SwiftPackageManagerInteracting = SwiftPackageManagerInteractor()
|
||||
private let modelLoader: GeneratorModelLoading
|
||||
private let graphLoader: GraphLoading
|
||||
|
||||
init() {
|
||||
modelLoader = GeneratorModelLoader(manifestLoader: manifestLoader,
|
||||
manifestLinter: manifestLinter)
|
||||
graphLoader = GraphLoader(modelLoader: modelLoader)
|
||||
}
|
||||
|
||||
func generate(path: AbsolutePath, projectOnly: Bool) throws -> AbsolutePath {
|
||||
let (generatedPath, _) = try generateWithGraph(path: path, projectOnly: projectOnly)
|
||||
return generatedPath
|
||||
}
|
||||
|
||||
func generateWithGraph(path: AbsolutePath, projectOnly: Bool) throws -> (AbsolutePath, Graphing) {
|
||||
let manifests = manifestLoader.manifests(at: path)
|
||||
|
||||
if projectOnly {
|
||||
return try generateProject(path: path)
|
||||
} else if manifests.contains(.workspace) {
|
||||
return try generateWorkspace(path: path)
|
||||
} else if manifests.contains(.project) {
|
||||
return try generateProjectWorkspace(path: path)
|
||||
} else {
|
||||
throw ManifestLoaderError.manifestNotFound(path)
|
||||
}
|
||||
}
|
||||
|
||||
private func generateProject(path: AbsolutePath) throws -> (AbsolutePath, Graph) {
|
||||
// Load
|
||||
let (graph, project) = try graphLoader.loadProject(path: path)
|
||||
|
||||
// Lint
|
||||
try lint(graph: graph)
|
||||
|
||||
// Generate
|
||||
let projectDescriptor = try generator.generateProject(project: project, graph: graph)
|
||||
|
||||
// Write
|
||||
try writer.write(project: projectDescriptor)
|
||||
|
||||
// Post Generate Actions
|
||||
try postGenerationActions(for: graph, workspaceName: projectDescriptor.xcodeprojPath.basename)
|
||||
|
||||
return (projectDescriptor.xcodeprojPath, graph)
|
||||
}
|
||||
|
||||
private func generateWorkspace(path: AbsolutePath) throws -> (AbsolutePath, Graph) {
|
||||
// Load
|
||||
let (graph, workspace) = try graphLoader.loadWorkspace(path: path)
|
||||
|
||||
// Lint
|
||||
try lint(graph: graph)
|
||||
|
||||
// Generate
|
||||
let updatedWorkspace = workspace.merging(projects: graph.projectPaths)
|
||||
let workspaceDescriptor = try generator.generateWorkspace(workspace: updatedWorkspace,
|
||||
graph: graph)
|
||||
|
||||
// Write
|
||||
try writer.write(workspace: workspaceDescriptor)
|
||||
|
||||
// Post Generate Actions
|
||||
try postGenerationActions(for: graph, workspaceName: workspaceDescriptor.xcworkspacePath.basename)
|
||||
|
||||
return (workspaceDescriptor.xcworkspacePath, graph)
|
||||
}
|
||||
|
||||
private func generateProjectWorkspace(path: AbsolutePath) throws -> (AbsolutePath, Graph) {
|
||||
// Load
|
||||
let (graph, project) = try graphLoader.loadProject(path: path)
|
||||
|
||||
// Lint
|
||||
try lint(graph: graph)
|
||||
|
||||
// Generate
|
||||
let workspace = Workspace(path: path, name: project.name, projects: graph.projectPaths)
|
||||
let workspaceDescriptor = try generator.generateWorkspace(workspace: workspace, graph: graph)
|
||||
|
||||
// Write
|
||||
try writer.write(workspace: workspaceDescriptor)
|
||||
|
||||
// Post Generate Actions
|
||||
try postGenerationActions(for: graph, workspaceName: workspaceDescriptor.xcworkspacePath.basename)
|
||||
|
||||
return (workspaceDescriptor.xcworkspacePath, graph)
|
||||
}
|
||||
|
||||
private func lint(graph: Graphing) throws {
|
||||
let config = try graphLoader.loadConfig(path: graph.entryPath)
|
||||
|
||||
try environmentLinter.lint(config: config).printAndThrowIfNeeded()
|
||||
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
||||
}
|
||||
|
||||
private func postGenerationActions(for graph: Graph, workspaceName: String) throws {
|
||||
try swiftPackageManagerInteractor.install(graph: graph, workspaceName: workspaceName)
|
||||
try cocoapodsInteractor.install(graph: graph)
|
||||
}
|
||||
}
|
|
@ -7,11 +7,21 @@ import TuistSupport
|
|||
public class GeneratorModelLoader: GeneratorModelLoading {
|
||||
private let manifestLoader: ManifestLoading
|
||||
private let manifestLinter: ManifestLinting
|
||||
private let rootDirectoryLocator: RootDirectoryLocating
|
||||
|
||||
public init(manifestLoader: ManifestLoading,
|
||||
manifestLinter: ManifestLinting) {
|
||||
public convenience init(manifestLoader: ManifestLoading,
|
||||
manifestLinter: ManifestLinting) {
|
||||
self.init(manifestLoader: manifestLoader,
|
||||
manifestLinter: manifestLinter,
|
||||
rootDirectoryLocator: RootDirectoryLocator())
|
||||
}
|
||||
|
||||
init(manifestLoader: ManifestLoading,
|
||||
manifestLinter: ManifestLinting,
|
||||
rootDirectoryLocator: RootDirectoryLocating) {
|
||||
self.manifestLoader = manifestLoader
|
||||
self.manifestLinter = manifestLinter
|
||||
self.rootDirectoryLocator = rootDirectoryLocator
|
||||
}
|
||||
|
||||
/// Load a Project model at the specified path
|
||||
|
@ -22,11 +32,11 @@ public class GeneratorModelLoader: GeneratorModelLoading {
|
|||
/// - Throws: Error encountered during the loading process (e.g. Missing project)
|
||||
public func loadProject(at path: AbsolutePath) throws -> TuistCore.Project {
|
||||
let manifest = try manifestLoader.loadProject(at: path)
|
||||
let tuistConfig = try loadTuistConfig(at: path)
|
||||
let config = try loadConfig(at: path)
|
||||
let generatorPaths = GeneratorPaths(manifestDirectory: path)
|
||||
try manifestLinter.lint(project: manifest).printAndThrowIfNeeded()
|
||||
let project = try TuistCore.Project.from(manifest: manifest, generatorPaths: generatorPaths)
|
||||
return try enriched(model: project, with: tuistConfig)
|
||||
return try enriched(model: project, with: config)
|
||||
}
|
||||
|
||||
public func loadWorkspace(at path: AbsolutePath) throws -> TuistCore.Workspace {
|
||||
|
@ -39,32 +49,55 @@ public class GeneratorModelLoader: GeneratorModelLoading {
|
|||
return workspace
|
||||
}
|
||||
|
||||
public func loadTuistConfig(at path: AbsolutePath) throws -> TuistCore.TuistConfig {
|
||||
guard let tuistConfigPath = FileHandler.shared.locateDirectoryTraversingParents(from: path, path: Manifest.tuistConfig.fileName) else {
|
||||
return TuistCore.TuistConfig.default
|
||||
public func loadConfig(at path: AbsolutePath) throws -> TuistCore.Config {
|
||||
// If the Config.swift file exists in the root Tuist/ directory, we load it from there
|
||||
if let rootDirectoryPath = rootDirectoryLocator.locate(from: path) {
|
||||
let configPath = rootDirectoryPath.appending(RelativePath("\(Constants.tuistDirectoryName)/\(Manifest.config.fileName)"))
|
||||
|
||||
if FileHandler.shared.exists(configPath) {
|
||||
let manifest = try manifestLoader.loadConfig(at: configPath.parentDirectory)
|
||||
return try TuistCore.Config.from(manifest: manifest)
|
||||
}
|
||||
}
|
||||
|
||||
let manifest = try manifestLoader.loadTuistConfig(at: tuistConfigPath.parentDirectory)
|
||||
return try TuistCore.TuistConfig.from(manifest: manifest)
|
||||
// We first try to load the deprecated file. If it doesn't exist, we load the new file name.
|
||||
let fileNames = [Manifest.config]
|
||||
.flatMap { [$0.deprecatedFileName, $0.fileName] }
|
||||
.compactMap { $0 }
|
||||
|
||||
for fileName in fileNames {
|
||||
guard let configPath = FileHandler.shared.locateDirectoryTraversingParents(from: path, path: fileName) else {
|
||||
continue
|
||||
}
|
||||
let manifest = try manifestLoader.loadConfig(at: configPath.parentDirectory)
|
||||
return try TuistCore.Config.from(manifest: manifest)
|
||||
}
|
||||
|
||||
return TuistCore.Config.default
|
||||
}
|
||||
|
||||
private func enriched(model: TuistCore.Project,
|
||||
with config: TuistCore.TuistConfig) throws -> TuistCore.Project {
|
||||
private func enriched(model: TuistCore.Project, with config: TuistCore.Config) throws -> TuistCore.Project {
|
||||
var enrichedModel = model
|
||||
|
||||
// Xcode project file name
|
||||
let xcodeFileName = xcodeFileNameOverride(from: config, for: model)
|
||||
enrichedModel = enrichedModel.replacing(fileName: xcodeFileName)
|
||||
|
||||
// Xcode project organization name
|
||||
if let organizationName = organizationNameOverride(from: config) {
|
||||
enrichedModel = enrichedModel.replacing(organizationName: organizationName)
|
||||
}
|
||||
|
||||
return enrichedModel
|
||||
}
|
||||
|
||||
private func xcodeFileNameOverride(from config: TuistCore.TuistConfig,
|
||||
for model: TuistCore.Project) -> String? {
|
||||
private func xcodeFileNameOverride(from config: TuistCore.Config, for model: TuistCore.Project) -> String? {
|
||||
var xcodeFileName = config.generationOptions.compactMap { item -> String? in
|
||||
switch item {
|
||||
case let .xcodeProjectName(projectName):
|
||||
return projectName.description
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}.first
|
||||
|
||||
|
@ -74,4 +107,15 @@ public class GeneratorModelLoader: GeneratorModelLoading {
|
|||
|
||||
return xcodeFileName
|
||||
}
|
||||
|
||||
private func organizationNameOverride(from config: TuistCore.Config) -> String? {
|
||||
config.generationOptions.compactMap { item -> String? in
|
||||
switch item {
|
||||
case let .organizationName(name):
|
||||
return name
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}.first
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,12 +51,12 @@ public enum ManifestLoaderError: FatalError, Equatable {
|
|||
}
|
||||
|
||||
public protocol ManifestLoading {
|
||||
/// Loads the TuistConfig.swift in the given directory.
|
||||
/// Loads the Config.swift in the given directory.
|
||||
///
|
||||
/// - Parameter path: Path to the directory that contains the TuistConfig.swift file.
|
||||
/// - Returns: Loaded TuistConfig.swift file.
|
||||
/// - Parameter path: Path to the directory that contains the Config.swift file.
|
||||
/// - Returns: Loaded Config.swift file.
|
||||
/// - Throws: An error if the file has a syntax error.
|
||||
func loadTuistConfig(at path: AbsolutePath) throws -> ProjectDescription.TuistConfig
|
||||
func loadConfig(at path: AbsolutePath) throws -> ProjectDescription.Config
|
||||
|
||||
/// Loads the Project.swift in the given directory.
|
||||
/// - Parameter path: Path to the directory that contains the Project.swift.
|
||||
|
@ -104,8 +104,8 @@ public class ManifestLoader: ManifestLoading {
|
|||
Set(manifestFilesLocator.locate(at: path).map { $0.0 })
|
||||
}
|
||||
|
||||
public func loadTuistConfig(at path: AbsolutePath) throws -> ProjectDescription.TuistConfig {
|
||||
try loadManifest(.tuistConfig, at: path)
|
||||
public func loadConfig(at path: AbsolutePath) throws -> ProjectDescription.Config {
|
||||
try loadManifest(.config, at: path)
|
||||
}
|
||||
|
||||
public func loadProject(at path: AbsolutePath) throws -> ProjectDescription.Project {
|
||||
|
@ -134,12 +134,19 @@ public class ManifestLoader: ManifestLoading {
|
|||
// MARK: - Private
|
||||
|
||||
private func loadManifest<T: Decodable>(_ manifest: Manifest, at path: AbsolutePath) throws -> T {
|
||||
let manifestPath = path.appending(component: manifest.fileName)
|
||||
guard FileHandler.shared.exists(manifestPath) else {
|
||||
throw ManifestLoaderError.manifestNotFound(manifest, path)
|
||||
var fileNames = [manifest.fileName]
|
||||
if let deprecatedFileName = manifest.deprecatedFileName {
|
||||
fileNames.insert(deprecatedFileName, at: 0)
|
||||
}
|
||||
let data = try loadManifestData(at: manifestPath)
|
||||
return try decoder.decode(T.self, from: data)
|
||||
|
||||
for fileName in fileNames {
|
||||
let manifestPath = path.appending(component: fileName)
|
||||
if !FileHandler.shared.exists(manifestPath) { continue }
|
||||
let data = try loadManifestData(at: manifestPath)
|
||||
return try decoder.decode(T.self, from: data)
|
||||
}
|
||||
|
||||
throw ManifestLoaderError.manifestNotFound(manifest, path)
|
||||
}
|
||||
|
||||
private func loadManifestData(at path: AbsolutePath) throws -> Data {
|
||||
|
|
|
@ -50,7 +50,7 @@ public class SetupLoader: SetupLoading {
|
|||
.printAndThrowIfNeeded()
|
||||
try setup.forEach { command in
|
||||
if try !command.isMet(projectPath: path) {
|
||||
Printer.shared.print(subsection: "Configuring \(command.name)")
|
||||
logger.notice("Configuring \(command.name)", metadata: .subsection)
|
||||
try command.meet(projectPath: path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import TuistSupport
|
||||
let logger = Logger(label: "io.tuist.loader")
|
|
@ -0,0 +1,13 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import ProjectDescription
|
||||
import TuistCore
|
||||
|
||||
extension TuistCore.AnalyzeAction {
|
||||
static func from(manifest: ProjectDescription.AnalyzeAction,
|
||||
generatorPaths _: GeneratorPaths) throws -> TuistCore.AnalyzeAction {
|
||||
let configurationName = manifest.configurationName
|
||||
|
||||
return AnalyzeAction(configurationName: configurationName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import ProjectDescription
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
enum ConfigManifestMapperError: FatalError {
|
||||
/// Thrown when the cloud URL is invalid.
|
||||
case invalidCloudURL(String)
|
||||
|
||||
/// Error type.
|
||||
var type: ErrorType {
|
||||
switch self {
|
||||
case .invalidCloudURL: return .abort
|
||||
}
|
||||
}
|
||||
|
||||
/// Error description.
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .invalidCloudURL(url):
|
||||
return "The cloud URL '\(url)' is not a valid URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TuistCore.Config {
|
||||
/// Maps a ProjectDescription.Config instance into a TuistCore.Config model.
|
||||
/// - Parameters:
|
||||
/// - manifest: Manifest representation of Tuist config.
|
||||
/// - generatorPaths: Generator paths.
|
||||
static func from(manifest: ProjectDescription.Config) throws -> TuistCore.Config {
|
||||
let generationOptions = try manifest.generationOptions.map { try TuistCore.Config.GenerationOption.from(manifest: $0) }
|
||||
let compatibleXcodeVersions = TuistCore.CompatibleXcodeVersions.from(manifest: manifest.compatibleXcodeVersions)
|
||||
var cloudURL: URL?
|
||||
if let manifestCloudURL = manifest.cloudURL {
|
||||
if let manifestCloudURL = URL(string: manifestCloudURL) {
|
||||
cloudURL = manifestCloudURL
|
||||
} else {
|
||||
throw ConfigManifestMapperError.invalidCloudURL(manifestCloudURL)
|
||||
}
|
||||
}
|
||||
return TuistCore.Config(compatibleXcodeVersions: compatibleXcodeVersions, cloudURL: cloudURL, generationOptions: generationOptions)
|
||||
}
|
||||
}
|
||||
|
||||
extension TuistCore.Config.GenerationOption {
|
||||
/// Maps a ProjectDescription.Config.GenerationOptions instance into a TuistCore.Config.GenerationOptions model.
|
||||
/// - Parameters:
|
||||
/// - manifest: Manifest representation of Tuist config generation options
|
||||
/// - generatorPaths: Generator paths.
|
||||
static func from(manifest: ProjectDescription.Config.GenerationOptions) throws -> TuistCore.Config.GenerationOption {
|
||||
switch manifest {
|
||||
case let .xcodeProjectName(templateString):
|
||||
return .xcodeProjectName(templateString.description)
|
||||
case let .organizationName(name):
|
||||
return .organizationName(name)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,10 +19,10 @@ extension TuistCore.FileElement {
|
|||
|
||||
if files.isEmpty {
|
||||
if FileHandler.shared.isFolder(path) {
|
||||
Printer.shared.print(warning: "'\(path.pathString)' is a directory, try using: '\(path.pathString)/**' to list its files")
|
||||
logger.warning("'\(path.pathString)' is a directory, try using: '\(path.pathString)/**' to list its files")
|
||||
} else {
|
||||
// FIXME: This should be done in a linter.
|
||||
Printer.shared.print(warning: "No files found at: \(path.pathString)")
|
||||
logger.warning("No files found at: \(path.pathString)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,13 +32,13 @@ extension TuistCore.FileElement {
|
|||
func folderReferences(_ path: AbsolutePath) -> [AbsolutePath] {
|
||||
guard FileHandler.shared.exists(path) else {
|
||||
// FIXME: This should be done in a linter.
|
||||
Printer.shared.print(warning: "\(path.pathString) does not exist")
|
||||
logger.warning("\(path.pathString) does not exist")
|
||||
return []
|
||||
}
|
||||
|
||||
guard FileHandler.shared.isFolder(path) else {
|
||||
// FIXME: This should be done in a linter.
|
||||
Printer.shared.print(warning: "\(path.pathString) is not a directory - folder reference paths need to point to directories")
|
||||
logger.warning("\(path.pathString) is not a directory - folder reference paths need to point to directories")
|
||||
return []
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import ProjectDescription
|
||||
import TuistCore
|
||||
|
||||
extension TuistCore.ProfileAction {
|
||||
static func from(manifest: ProjectDescription.ProfileAction,
|
||||
generatorPaths: GeneratorPaths) throws -> TuistCore.ProfileAction {
|
||||
let configurationName = manifest.configurationName
|
||||
let arguments = manifest.arguments.map { TuistCore.Arguments.from(manifest: $0) }
|
||||
|
||||
var executableResolved: TuistCore.TargetReference?
|
||||
if let executable = manifest.executable {
|
||||
executableResolved = TargetReference(projectPath: try generatorPaths.resolveSchemeActionProjectPath(executable.projectPath),
|
||||
name: executable.targetName)
|
||||
}
|
||||
|
||||
return ProfileAction(configurationName: configurationName,
|
||||
executable: executableResolved,
|
||||
arguments: arguments)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue