Merge remote-tracking branch 'origin/master' into signing
This commit is contained in:
commit
74ab15d04d
|
@ -7,6 +7,13 @@ update_configs:
|
||||||
- 'tuist/core'
|
- 'tuist/core'
|
||||||
default_labels:
|
default_labels:
|
||||||
- 'dependencies'
|
- 'dependencies'
|
||||||
|
automerged_updates:
|
||||||
|
- match:
|
||||||
|
dependency_type: 'development'
|
||||||
|
update_type: 'semver:minor'
|
||||||
|
- match:
|
||||||
|
dependency_type: 'production'
|
||||||
|
update_type: 'semver:minor'
|
||||||
- package_manager: 'javascript'
|
- package_manager: 'javascript'
|
||||||
directory: '/website'
|
directory: '/website'
|
||||||
update_schedule: 'weekly'
|
update_schedule: 'weekly'
|
||||||
|
@ -14,3 +21,10 @@ update_configs:
|
||||||
- 'tuist/core'
|
- 'tuist/core'
|
||||||
default_labels:
|
default_labels:
|
||||||
- 'dependencies'
|
- '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
|
uses: norio-nomura/action-swiftlint@3c67ce2e382be797d968883944140ffa0113f737
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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
|
## 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
|
### Fixed
|
||||||
|
|
||||||
- Fix `TargetAction` when `PROJECT_DIR` includes a space https://github.com/tuist/tuist/pull/1037 by @fortmarek
|
- 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
|
### 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.
|
- `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
|
## 1.3.0
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,10 @@ GEM
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
byebug (11.1.1)
|
byebug (11.1.1)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
cocoapods (1.9.0)
|
cocoapods (1.9.1)
|
||||||
activesupport (>= 4.0.2, < 5)
|
activesupport (>= 4.0.2, < 5)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
cocoapods-core (= 1.9.0)
|
cocoapods-core (= 1.9.1)
|
||||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||||
cocoapods-downloader (>= 1.2.2, < 2.0)
|
cocoapods-downloader (>= 1.2.2, < 2.0)
|
||||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||||
|
@ -37,7 +37,7 @@ GEM
|
||||||
nap (~> 1.0)
|
nap (~> 1.0)
|
||||||
ruby-macho (~> 1.4)
|
ruby-macho (~> 1.4)
|
||||||
xcodeproj (>= 1.14.0, < 2.0)
|
xcodeproj (>= 1.14.0, < 2.0)
|
||||||
cocoapods-core (1.9.0)
|
cocoapods-core (1.9.1)
|
||||||
activesupport (>= 4.0.2, < 6)
|
activesupport (>= 4.0.2, < 6)
|
||||||
algoliasearch (~> 1.0)
|
algoliasearch (~> 1.0)
|
||||||
concurrent-ruby (~> 1.1)
|
concurrent-ruby (~> 1.1)
|
||||||
|
|
|
@ -100,6 +100,15 @@
|
||||||
"version": "0.2.0"
|
"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",
|
"package": "SwiftPM",
|
||||||
"repositoryURL": "https://github.com/apple/swift-package-manager",
|
"repositoryURL": "https://github.com/apple/swift-package-manager",
|
||||||
|
|
|
@ -4,7 +4,7 @@ import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "tuist",
|
name: "tuist",
|
||||||
platforms: [.macOS(.v10_11)],
|
platforms: [.macOS(.v10_12)],
|
||||||
products: [
|
products: [
|
||||||
.executable(name: "tuist", targets: ["tuist"]),
|
.executable(name: "tuist", targets: ["tuist"]),
|
||||||
.executable(name: "tuistenv", targets: ["tuistenv"]),
|
.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/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/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/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/thii/xcbeautify.git", .upToNextMajor(from: "0.7.3")),
|
||||||
.package(url: "https://github.com/krzyzanowskim/CryptoSwift", .upToNextMajor(from: "1.3.0")),
|
.package(url: "https://github.com/krzyzanowskim/CryptoSwift", .upToNextMajor(from: "1.3.0")),
|
||||||
],
|
],
|
||||||
|
@ -54,7 +55,7 @@ let package = Package(
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "TuistKit",
|
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(
|
.testTarget(
|
||||||
name: "TuistKitTests",
|
name: "TuistKitTests",
|
||||||
|
@ -90,7 +91,7 @@ let package = Package(
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "TuistSupport",
|
name: "TuistSupport",
|
||||||
dependencies: ["SPMUtility", "RxSwift", "RxRelay"]
|
dependencies: ["SPMUtility", "RxSwift", "RxRelay", "Logging"]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "TuistSupportTesting",
|
name: "TuistSupportTesting",
|
||||||
|
@ -118,7 +119,7 @@ let package = Package(
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "TuistGeneratorIntegrationTests",
|
name: "TuistGeneratorIntegrationTests",
|
||||||
dependencies: ["TuistGenerator", "TuistSupportTesting", "TuistCoreTesting"]
|
dependencies: ["TuistGenerator", "TuistSupportTesting", "TuistCoreTesting", "TuistGeneratorTesting"]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "TuistCache",
|
name: "TuistCache",
|
||||||
|
@ -148,6 +149,18 @@ let package = Package(
|
||||||
name: "TuistAutomationIntegrationTests",
|
name: "TuistAutomationIntegrationTests",
|
||||||
dependencies: ["TuistAutomation", "TuistSupportTesting"]
|
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(
|
.target(
|
||||||
name: "TuistSigning",
|
name: "TuistSigning",
|
||||||
dependencies: ["TuistCore", "TuistSupport", "CryptoSwift"]
|
dependencies: ["TuistCore", "TuistSupport", "CryptoSwift"]
|
||||||
|
|
|
@ -26,6 +26,7 @@ The example below shows how projects are defined with Tuist:
|
||||||
import ProjectDescription
|
import ProjectDescription
|
||||||
|
|
||||||
let project = Project(name: "App",
|
let project = Project(name: "App",
|
||||||
|
organizationName: "tuist",
|
||||||
targets: [
|
targets: [
|
||||||
Target(name: "App",
|
Target(name: "App",
|
||||||
platform: .iOS,
|
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
|
import Foundation
|
||||||
|
|
||||||
|
public typealias TuistConfig = Config
|
||||||
|
|
||||||
/// This model allows to configure Tuist.
|
/// This model allows to configure Tuist.
|
||||||
public struct TuistConfig: Codable, Equatable {
|
public struct Config: Codable, Equatable {
|
||||||
/// Contains options related to the project generation.
|
/// 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.
|
/// - 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 {
|
public enum GenerationOptions: Encodable, Decodable, Equatable {
|
||||||
case xcodeProjectName(TemplateString)
|
case xcodeProjectName(TemplateString)
|
||||||
|
case organizationName(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generation options.
|
/// Generation options.
|
||||||
|
@ -15,22 +19,28 @@ public struct TuistConfig: Codable, Equatable {
|
||||||
/// List of Xcode versions that the project supports.
|
/// List of Xcode versions that the project supports.
|
||||||
public let compatibleXcodeVersions: CompatibleXcodeVersions
|
public let compatibleXcodeVersions: CompatibleXcodeVersions
|
||||||
|
|
||||||
|
/// URL to the server that caching and insights will interact with.
|
||||||
|
public let cloudURL: String?
|
||||||
|
|
||||||
/// Initializes the tuist cofiguration.
|
/// Initializes the tuist cofiguration.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - compatibleXcodeVersions: .
|
/// - compatibleXcodeVersions: List of Xcode versions the project is compatible with.
|
||||||
/// - generationOptions: List of Xcode versions that the project supports. An empty list means that
|
/// - 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,
|
public init(compatibleXcodeVersions: CompatibleXcodeVersions = .all,
|
||||||
|
cloudURL: String? = nil,
|
||||||
generationOptions: [GenerationOptions]) {
|
generationOptions: [GenerationOptions]) {
|
||||||
self.generationOptions = generationOptions
|
|
||||||
self.compatibleXcodeVersions = compatibleXcodeVersions
|
self.compatibleXcodeVersions = compatibleXcodeVersions
|
||||||
|
self.generationOptions = generationOptions
|
||||||
|
self.cloudURL = cloudURL
|
||||||
dumpIfNeeded(self)
|
dumpIfNeeded(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TuistConfig.GenerationOptions {
|
extension Config.GenerationOptions {
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case xcodeProjectName
|
case xcodeProjectName, organizationName
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
|
@ -42,6 +52,12 @@ extension TuistConfig.GenerationOptions {
|
||||||
self = .xcodeProjectName(templateProjectName)
|
self = .xcodeProjectName(templateProjectName)
|
||||||
return
|
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"))
|
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown enum case"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +68,9 @@ extension TuistConfig.GenerationOptions {
|
||||||
case let .xcodeProjectName(templateProjectName):
|
case let .xcodeProjectName(templateProjectName):
|
||||||
var associatedValues = container.nestedUnkeyedContainer(forKey: .xcodeProjectName)
|
var associatedValues = container.nestedUnkeyedContainer(forKey: .xcodeProjectName)
|
||||||
try associatedValues.encode(templateProjectName)
|
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) {
|
switch (lhs, rhs) {
|
||||||
case let (.xcodeProjectName(lhs), .xcodeProjectName(rhs)):
|
case let (.xcodeProjectName(lhs), .xcodeProjectName(rhs)):
|
||||||
return lhs.rawString == rhs.rawString
|
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 {
|
extension Array: ExpressibleByUnicodeScalarLiteral where Element == FileElement {
|
||||||
public typealias UnicodeScalarLiteralType = String
|
public typealias UnicodeScalarLiteralType = String
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Path: Codable, ExpressibleByStringLiteral, Equatable {
|
public struct Path: Codable, ExpressibleByStringLiteral, ExpressibleByStringInterpolation, Equatable {
|
||||||
public enum PathType: String, Codable {
|
public enum PathType: String, Codable {
|
||||||
case relativeToCurrentFile
|
case relativeToCurrentFile
|
||||||
case relativeToManifest
|
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 struct Project: Codable, Equatable {
|
||||||
public let name: String
|
public let name: String
|
||||||
|
public let organizationName: String?
|
||||||
public let packages: [Package]
|
public let packages: [Package]
|
||||||
public let targets: [Target]
|
public let targets: [Target]
|
||||||
public let schemes: [Scheme]
|
public let schemes: [Scheme]
|
||||||
|
@ -11,12 +12,14 @@ public struct Project: Codable, Equatable {
|
||||||
public let additionalFiles: [FileElement]
|
public let additionalFiles: [FileElement]
|
||||||
|
|
||||||
public init(name: String,
|
public init(name: String,
|
||||||
|
organizationName: String? = nil,
|
||||||
packages: [Package] = [],
|
packages: [Package] = [],
|
||||||
settings: Settings? = nil,
|
settings: Settings? = nil,
|
||||||
targets: [Target] = [],
|
targets: [Target] = [],
|
||||||
schemes: [Scheme] = [],
|
schemes: [Scheme] = [],
|
||||||
additionalFiles: [FileElement] = []) {
|
additionalFiles: [FileElement] = []) {
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.organizationName = organizationName
|
||||||
self.packages = packages
|
self.packages = packages
|
||||||
self.targets = targets
|
self.targets = targets
|
||||||
self.schemes = schemes
|
self.schemes = schemes
|
||||||
|
|
|
@ -9,18 +9,24 @@ public struct Scheme: Equatable, Codable {
|
||||||
public let testAction: TestAction?
|
public let testAction: TestAction?
|
||||||
public let runAction: RunAction?
|
public let runAction: RunAction?
|
||||||
public let archiveAction: ArchiveAction?
|
public let archiveAction: ArchiveAction?
|
||||||
|
public let profileAction: ProfileAction?
|
||||||
|
public let analyzeAction: AnalyzeAction?
|
||||||
|
|
||||||
public init(name: String,
|
public init(name: String,
|
||||||
shared: Bool = true,
|
shared: Bool = true,
|
||||||
buildAction: BuildAction? = nil,
|
buildAction: BuildAction? = nil,
|
||||||
testAction: TestAction? = nil,
|
testAction: TestAction? = nil,
|
||||||
runAction: RunAction? = nil,
|
runAction: RunAction? = nil,
|
||||||
archiveAction: ArchiveAction? = nil) {
|
archiveAction: ArchiveAction? = nil,
|
||||||
|
profileAction: ProfileAction? = nil,
|
||||||
|
analyzeAction: AnalyzeAction? = nil) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.shared = shared
|
self.shared = shared
|
||||||
self.buildAction = buildAction
|
self.buildAction = buildAction
|
||||||
self.testAction = testAction
|
self.testAction = testAction
|
||||||
self.runAction = runAction
|
self.runAction = runAction
|
||||||
self.archiveAction = archiveAction
|
self.archiveAction = archiveAction
|
||||||
|
self.profileAction = profileAction
|
||||||
|
self.analyzeAction = analyzeAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// MARK: - FileList
|
// MARK: - FileList
|
||||||
|
|
||||||
/// A model to refer to source files that supports passing compiler flags.
|
/// 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.
|
/// Relative glob pattern.
|
||||||
public let glob: Path
|
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 outputDirectory = try TemporaryDirectory(removeTreeOnDeinit: false)
|
||||||
let temporaryPath = 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
|
// Build for the device
|
||||||
// Without the BUILD_LIBRARY_FOR_DISTRIBUTION argument xcodebuild doesn't generate the .swiftinterface file
|
// 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("SKIP_INSTALL", "NO"),
|
||||||
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"))
|
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"))
|
||||||
.do(onSubscribed: {
|
.do(onSubscribed: {
|
||||||
Printer.shared.print(subsection: "Building \(target.name) for device")
|
logger.notice("Building \(target.name) for device", metadata: .subsection)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Build for the simulator
|
// Build for the simulator
|
||||||
|
@ -113,7 +113,7 @@ public final class XCFrameworkBuilder: XCFrameworkBuilding {
|
||||||
.buildSetting("SKIP_INSTALL", "NO"),
|
.buildSetting("SKIP_INSTALL", "NO"),
|
||||||
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"))
|
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"))
|
||||||
.do(onSubscribed: {
|
.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 xcframeworkPath = outputDirectory.path.appending(component: "\(target.productName).xcframework")
|
||||||
let xcframeworkObservable = xcodeBuildController.createXCFramework(frameworks: frameworkpaths, output: xcframeworkPath)
|
let xcframeworkObservable = xcodeBuildController.createXCFramework(frameworks: frameworkpaths, output: xcframeworkPath)
|
||||||
.do(onSubscribed: {
|
.do(onSubscribed: {
|
||||||
Printer.shared.print(subsection: "Exporting xcframework for \(target.platform.caseValue)")
|
logger.notice("Exporting xcframework for \(target.platform.caseValue)", metadata: .subsection)
|
||||||
})
|
})
|
||||||
|
|
||||||
return deviceArchiveObservable
|
return deviceArchiveObservable
|
||||||
|
|
|
@ -4,7 +4,7 @@ import TuistSupport
|
||||||
|
|
||||||
public extension Observable where Element == SystemEvent<XcodeBuildOutput> {
|
public extension Observable where Element == SystemEvent<XcodeBuildOutput> {
|
||||||
func printFormattedOutput() -> Observable<SystemEvent<XcodeBuildOutput>> {
|
func printFormattedOutput() -> Observable<SystemEvent<XcodeBuildOutput>> {
|
||||||
self.do(onNext: { event in
|
`do`(onNext: { event in
|
||||||
switch event {
|
switch event {
|
||||||
case let .standardError(error):
|
case let .standardError(error):
|
||||||
let string = error.formatted ?? error.raw
|
let string = error.formatted ?? error.raw
|
||||||
|
|
|
@ -11,12 +11,12 @@ public protocol GraphLoading: AnyObject {
|
||||||
/// - Parameter path: Path to the directory that contains the workspace.
|
/// - Parameter path: Path to the directory that contains the workspace.
|
||||||
func loadWorkspace(path: AbsolutePath) throws -> (Graph, 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.
|
/// - Parameter path: Directory from which look up and load the Config.
|
||||||
/// - Returns: Loaded TuistConfig object.
|
/// - Returns: Loaded Config object.
|
||||||
/// - Throws: An error if the TuistConfig.swift can't be parsed.
|
/// - Throws: An error if the Config.swift can't be parsed.
|
||||||
func loadTuistConfig(path: AbsolutePath) throws -> TuistConfig
|
func loadConfig(path: AbsolutePath) throws -> Config
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GraphLoader: GraphLoading {
|
public class GraphLoader: GraphLoading {
|
||||||
|
@ -76,15 +76,15 @@ public class GraphLoader: GraphLoading {
|
||||||
return (graph, workspace)
|
return (graph, workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadTuistConfig(path: AbsolutePath) throws -> TuistConfig {
|
public func loadConfig(path: AbsolutePath) throws -> Config {
|
||||||
let cache = GraphLoaderCache()
|
let cache = GraphLoaderCache()
|
||||||
|
|
||||||
if let tuistConfig = cache.tuistConfig(path) {
|
if let config = cache.config(path) {
|
||||||
return tuistConfig
|
return config
|
||||||
} else {
|
} else {
|
||||||
let tuistConfig = try modelLoader.loadTuistConfig(at: path)
|
let config = try modelLoader.loadConfig(at: path)
|
||||||
cache.add(tuistConfig: tuistConfig, path: path)
|
cache.add(config: config, path: path)
|
||||||
return tuistConfig
|
return config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ public class GraphLoaderCache: GraphLoaderCaching {
|
||||||
|
|
||||||
// MARK: - GraphLoaderCaching
|
// MARK: - GraphLoaderCaching
|
||||||
|
|
||||||
var tuistConfigs: [AbsolutePath: TuistConfig] = [:]
|
var configs: [AbsolutePath: Config] = [:]
|
||||||
public var projects: [AbsolutePath: Project] = [:]
|
public var projects: [AbsolutePath: Project] = [:]
|
||||||
public var packages: [AbsolutePath: [PackageNode]] = [:]
|
public var packages: [AbsolutePath: [PackageNode]] = [:]
|
||||||
public var precompiledNodes: [AbsolutePath: PrecompiledNode] = [:]
|
public var precompiledNodes: [AbsolutePath: PrecompiledNode] = [:]
|
||||||
|
@ -50,12 +50,12 @@ public class GraphLoaderCache: GraphLoaderCaching {
|
||||||
packageNodes[package.path] = package
|
packageNodes[package.path] = package
|
||||||
}
|
}
|
||||||
|
|
||||||
public func tuistConfig(_ path: AbsolutePath) -> TuistConfig? {
|
public func config(_ path: AbsolutePath) -> Config? {
|
||||||
tuistConfigs[path]
|
configs[path]
|
||||||
}
|
}
|
||||||
|
|
||||||
public func add(tuistConfig: TuistConfig, path: AbsolutePath) {
|
public func add(config: Config, path: AbsolutePath) {
|
||||||
tuistConfigs[path] = tuistConfig
|
configs[path] = config
|
||||||
}
|
}
|
||||||
|
|
||||||
public func project(_ path: AbsolutePath) -> Project? {
|
public func project(_ path: AbsolutePath) -> Project? {
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class TargetNode: GraphNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return path == otherTagetNode.path
|
return path == otherTagetNode.path
|
||||||
&& target == otherTagetNode.target
|
&& name == otherTagetNode.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Encodable
|
// 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
|
import TuistSupport
|
||||||
|
|
||||||
/// This model allows to configure Tuist.
|
/// This model allows to configure Tuist.
|
||||||
public struct TuistConfig: Equatable, Hashable {
|
public struct Config: Equatable, Hashable {
|
||||||
/// Contains options related to the project generation.
|
/// Contains options related to the project generation.
|
||||||
///
|
///
|
||||||
/// - xcodeProjectName: Name used for the Xcode project
|
/// - xcodeProjectName: Name used for the Xcode project
|
||||||
public enum GenerationOption: Hashable, Equatable {
|
public enum GenerationOption: Hashable, Equatable {
|
||||||
case xcodeProjectName(String)
|
case xcodeProjectName(String)
|
||||||
|
case organizationName(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generation options.
|
/// Generation options.
|
||||||
|
@ -17,20 +18,25 @@ public struct TuistConfig: Equatable, Hashable {
|
||||||
/// List of Xcode versions the project or set of projects is compatible with.
|
/// List of Xcode versions the project or set of projects is compatible with.
|
||||||
public let compatibleXcodeVersions: CompatibleXcodeVersions
|
public let compatibleXcodeVersions: CompatibleXcodeVersions
|
||||||
|
|
||||||
|
/// URL to the server that caching and insights will interact with.
|
||||||
|
public let cloudURL: URL?
|
||||||
|
|
||||||
/// Returns the default Tuist configuration.
|
/// Returns the default Tuist configuration.
|
||||||
public static var `default`: TuistConfig {
|
public static var `default`: Config {
|
||||||
TuistConfig(compatibleXcodeVersions: .all,
|
Config(compatibleXcodeVersions: .all, cloudURL: nil, generationOptions: [])
|
||||||
generationOptions: [])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the tuist cofiguration.
|
/// Initializes the tuist cofiguration.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - compatibleXcodeVersions: List of Xcode versions the project or set of projects is compatible with.
|
/// - 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.
|
/// - generationOptions: Generation options.
|
||||||
public init(compatibleXcodeVersions: CompatibleXcodeVersions,
|
public init(compatibleXcodeVersions: CompatibleXcodeVersions,
|
||||||
|
cloudURL: URL?,
|
||||||
generationOptions: [GenerationOption]) {
|
generationOptions: [GenerationOption]) {
|
||||||
self.compatibleXcodeVersions = compatibleXcodeVersions
|
self.compatibleXcodeVersions = compatibleXcodeVersions
|
||||||
|
self.cloudURL = cloudURL
|
||||||
self.generationOptions = generationOptions
|
self.generationOptions = generationOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,11 +44,7 @@ public struct TuistConfig: Equatable, Hashable {
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(generationOptions)
|
hasher.combine(generationOptions)
|
||||||
}
|
hasher.combine(cloudURL)
|
||||||
|
hasher.combine(compatibleXcodeVersions)
|
||||||
// MARK: - Equatable
|
|
||||||
|
|
||||||
public static func == (lhs: TuistConfig, rhs: TuistConfig) -> Bool {
|
|
||||||
lhs.generationOptions == rhs.generationOptions
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -42,13 +42,14 @@ public extension Array where Element == LintingIssue {
|
||||||
let errorIssues = filter { $0.severity == .error }
|
let errorIssues = filter { $0.severity == .error }
|
||||||
let warningIssues = filter { $0.severity == .warning }
|
let warningIssues = filter { $0.severity == .warning }
|
||||||
|
|
||||||
warningIssues.forEach { issue in
|
for issue in warningIssues {
|
||||||
Printer.shared.print(warning: "\(issue.description)")
|
logger.warning("\(issue.description)")
|
||||||
}
|
}
|
||||||
|
|
||||||
errorIssues.forEach { issue in
|
for issue in errorIssues {
|
||||||
Printer.shared.print(errorMessage: "\(issue.description)")
|
logger.error("\(issue.description)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errorIssues.isEmpty { throw LintingError() }
|
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.
|
/// Project name.
|
||||||
public let name: String
|
public let name: String
|
||||||
|
|
||||||
|
/// Organization name.
|
||||||
|
public let organizationName: String?
|
||||||
|
|
||||||
/// Project file name.
|
/// Project file name.
|
||||||
public let fileName: String
|
public let fileName: String
|
||||||
|
|
||||||
|
@ -39,6 +42,7 @@ public struct Project: Equatable, CustomStringConvertible {
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - path: Path to the folder that contains the project manifest.
|
/// - path: Path to the folder that contains the project manifest.
|
||||||
/// - name: Project name.
|
/// - name: Project name.
|
||||||
|
/// - organizationName: Organization name.
|
||||||
/// - settings: The settings to apply at the project level
|
/// - settings: The settings to apply at the project level
|
||||||
/// - filesGroup: The root group to place project files within
|
/// - filesGroup: The root group to place project files within
|
||||||
/// - targets: The project targets
|
/// - targets: The project targets
|
||||||
|
@ -46,6 +50,7 @@ public struct Project: Equatable, CustomStringConvertible {
|
||||||
/// *(Those won't be included in any build phases)*
|
/// *(Those won't be included in any build phases)*
|
||||||
public init(path: AbsolutePath,
|
public init(path: AbsolutePath,
|
||||||
name: String,
|
name: String,
|
||||||
|
organizationName: String? = nil,
|
||||||
fileName: String? = nil,
|
fileName: String? = nil,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
filesGroup: ProjectGroup,
|
filesGroup: ProjectGroup,
|
||||||
|
@ -55,6 +60,7 @@ public struct Project: Equatable, CustomStringConvertible {
|
||||||
additionalFiles: [FileElement] = []) {
|
additionalFiles: [FileElement] = []) {
|
||||||
self.path = path
|
self.path = path
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.organizationName = organizationName
|
||||||
self.fileName = fileName ?? name
|
self.fileName = fileName ?? name
|
||||||
self.targets = targets
|
self.targets = targets
|
||||||
self.packages = packages
|
self.packages = packages
|
||||||
|
|
|
@ -10,6 +10,8 @@ public struct Scheme: Equatable {
|
||||||
public let testAction: TestAction?
|
public let testAction: TestAction?
|
||||||
public let runAction: RunAction?
|
public let runAction: RunAction?
|
||||||
public let archiveAction: ArchiveAction?
|
public let archiveAction: ArchiveAction?
|
||||||
|
public let profileAction: ProfileAction?
|
||||||
|
public let analyzeAction: AnalyzeAction?
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
|
@ -18,13 +20,17 @@ public struct Scheme: Equatable {
|
||||||
buildAction: BuildAction? = nil,
|
buildAction: BuildAction? = nil,
|
||||||
testAction: TestAction? = nil,
|
testAction: TestAction? = nil,
|
||||||
runAction: RunAction? = nil,
|
runAction: RunAction? = nil,
|
||||||
archiveAction: ArchiveAction? = nil) {
|
archiveAction: ArchiveAction? = nil,
|
||||||
|
profileAction: ProfileAction? = nil,
|
||||||
|
analyzeAction: AnalyzeAction? = nil) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.shared = shared
|
self.shared = shared
|
||||||
self.buildAction = buildAction
|
self.buildAction = buildAction
|
||||||
self.testAction = testAction
|
self.testAction = testAction
|
||||||
self.runAction = runAction
|
self.runAction = runAction
|
||||||
self.archiveAction = archiveAction
|
self.archiveAction = archiveAction
|
||||||
|
self.profileAction = profileAction
|
||||||
|
self.analyzeAction = analyzeAction
|
||||||
}
|
}
|
||||||
|
|
||||||
public func targetDependencies() -> [TargetReference] {
|
public func targetDependencies() -> [TargetReference] {
|
||||||
|
@ -39,6 +45,7 @@ public struct Scheme: Equatable {
|
||||||
runAction?.executable.map { [$0] },
|
runAction?.executable.map { [$0] },
|
||||||
archiveAction?.preActions.compactMap(\.target),
|
archiveAction?.preActions.compactMap(\.target),
|
||||||
archiveAction?.postActions.compactMap(\.target),
|
archiveAction?.postActions.compactMap(\.target),
|
||||||
|
profileAction?.executable.map { [$0] },
|
||||||
]
|
]
|
||||||
|
|
||||||
let targets = targetSources.compactMap { $0 }.flatMap { $0 }.uniqued()
|
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)
|
/// - Throws: Error encountered during the loading process (e.g. Missing workspace)
|
||||||
func loadWorkspace(at path: AbsolutePath) throws -> 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
|
/// - Parameter path: The absolute path for the Config model to load
|
||||||
/// - Returns: The tuistconfig loaded from the specified path
|
/// - Returns: The config loaded from the specified path
|
||||||
/// - Throws: Error encountered during the loading process (e.g. Missing tuistconfig)
|
/// - Throws: Error encountered during the loading process (e.g. Missing Config file)
|
||||||
func loadTuistConfig(at path: AbsolutePath) throws -> TuistConfig
|
func loadConfig(at path: AbsolutePath) throws -> Config
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,17 +47,17 @@ public protocol GraphLoaderCaching: AnyObject {
|
||||||
/// - name: Name of the target.
|
/// - name: Name of the target.
|
||||||
func targetNode(_ path: AbsolutePath, name: String) -> TargetNode?
|
func targetNode(_ path: AbsolutePath, name: String) -> TargetNode?
|
||||||
|
|
||||||
// MARK: - TuistConfig
|
// MARK: - Config
|
||||||
|
|
||||||
/// It returns a Tuist configuration if it exists at the given directory.
|
/// It returns a Tuist configuration if it exists at the given directory.
|
||||||
/// - Parameter path: Path to the directory that contains the TuistConfig.
|
/// - Parameter path: Path to the directory that contains the Config.
|
||||||
func tuistConfig(_ path: AbsolutePath) -> TuistConfig?
|
func config(_ path: AbsolutePath) -> Config?
|
||||||
|
|
||||||
/// Caches a TuistConfig representation.
|
/// Caches a Config representation.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - tuistConfig: Tuist configuration.
|
/// - config: Tuist configuration.
|
||||||
/// - path: Path to the directory that contains th
|
/// - path: Path to the directory that contains th
|
||||||
func add(tuistConfig: TuistConfig, path: AbsolutePath)
|
func add(config: Config, path: AbsolutePath)
|
||||||
|
|
||||||
// MARK: - CocoaPods
|
// MARK: - CocoaPods
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ public final class MockGraphLoader: GraphLoading {
|
||||||
return try loadWorkspaceStub?(path) ?? (Graph.test(), Workspace.test())
|
return try loadWorkspaceStub?(path) ?? (Graph.test(), Workspace.test())
|
||||||
}
|
}
|
||||||
|
|
||||||
public var loadTuistConfigStub: ((AbsolutePath) throws -> (TuistConfig))?
|
public var loadConfigStub: ((AbsolutePath) throws -> (Config))?
|
||||||
public func loadTuistConfig(path: AbsolutePath) throws -> TuistConfig {
|
public func loadConfig(path: AbsolutePath) throws -> Config {
|
||||||
try loadTuistConfigStub?(path) ?? TuistConfig.test()
|
try loadConfigStub?(path) ?? Config.test()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ public final class MockGraphLoaderCache: GraphLoaderCaching {
|
||||||
var precompiledNodeStub: ((AbsolutePath) -> PrecompiledNode?)?
|
var precompiledNodeStub: ((AbsolutePath) -> PrecompiledNode?)?
|
||||||
var addTargetNodeArgs: [TargetNode] = []
|
var addTargetNodeArgs: [TargetNode] = []
|
||||||
var targetNodeStub: ((AbsolutePath, String) -> TargetNode?)?
|
var targetNodeStub: ((AbsolutePath, String) -> TargetNode?)?
|
||||||
var tuistConfigStub: [AbsolutePath: TuistConfig] = [:]
|
var configStub: [AbsolutePath: Config] = [:]
|
||||||
var addTuistConfigArgs: [(tuistConfig: TuistConfig, path: AbsolutePath)] = []
|
var addConfigArgs: [(config: Config, path: AbsolutePath)] = []
|
||||||
public var cocoapodsNodes: [AbsolutePath: CocoaPodsNode] = [:]
|
public var cocoapodsNodes: [AbsolutePath: CocoaPodsNode] = [:]
|
||||||
var cocoapodsStub: [AbsolutePath: CocoaPodsNode] = [:]
|
var cocoapodsStub: [AbsolutePath: CocoaPodsNode] = [:]
|
||||||
var addCococaPodsArgs: [CocoaPodsNode] = []
|
var addCococaPodsArgs: [CocoaPodsNode] = []
|
||||||
|
@ -42,12 +42,12 @@ public final class MockGraphLoaderCache: GraphLoaderCaching {
|
||||||
addCococaPodsArgs.append(cocoapods)
|
addCococaPodsArgs.append(cocoapods)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func tuistConfig(_ path: AbsolutePath) -> TuistConfig? {
|
public func config(_ path: AbsolutePath) -> Config? {
|
||||||
tuistConfigStub[path]
|
configStub[path]
|
||||||
}
|
}
|
||||||
|
|
||||||
public func add(tuistConfig: TuistConfig, path: AbsolutePath) {
|
public func add(config: Config, path: AbsolutePath) {
|
||||||
addTuistConfigArgs.append((tuistConfig: tuistConfig, path: path))
|
addConfigArgs.append((config: config, path: path))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func project(_ path: AbsolutePath) -> Project? {
|
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 {
|
public extension Project {
|
||||||
static func test(path: AbsolutePath = AbsolutePath("/Project"),
|
static func test(path: AbsolutePath = AbsolutePath("/Project"),
|
||||||
name: String = "Project",
|
name: String = "Project",
|
||||||
|
organizationName: String? = nil,
|
||||||
fileName: String? = nil,
|
fileName: String? = nil,
|
||||||
settings: Settings = Settings.test(),
|
settings: Settings = Settings.test(),
|
||||||
filesGroup: ProjectGroup = .group(name: "Project"),
|
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||||
|
@ -14,6 +15,7 @@ public extension Project {
|
||||||
additionalFiles: [FileElement] = []) -> Project {
|
additionalFiles: [FileElement] = []) -> Project {
|
||||||
Project(path: path,
|
Project(path: path,
|
||||||
name: name,
|
name: name,
|
||||||
|
organizationName: organizationName,
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
filesGroup: filesGroup,
|
filesGroup: filesGroup,
|
||||||
|
@ -25,6 +27,7 @@ public extension Project {
|
||||||
|
|
||||||
static func empty(path: AbsolutePath = AbsolutePath("/test/"),
|
static func empty(path: AbsolutePath = AbsolutePath("/test/"),
|
||||||
name: String = "Project",
|
name: String = "Project",
|
||||||
|
organizationName: String? = nil,
|
||||||
settings: Settings = .default,
|
settings: Settings = .default,
|
||||||
filesGroup: ProjectGroup = .group(name: "Project"),
|
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||||
targets: [Target] = [],
|
targets: [Target] = [],
|
||||||
|
@ -33,6 +36,7 @@ public extension Project {
|
||||||
additionalFiles: [FileElement] = []) -> Project {
|
additionalFiles: [FileElement] = []) -> Project {
|
||||||
Project(path: path,
|
Project(path: path,
|
||||||
name: name,
|
name: name,
|
||||||
|
organizationName: organizationName,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
filesGroup: filesGroup,
|
filesGroup: filesGroup,
|
||||||
targets: targets,
|
targets: targets,
|
||||||
|
|
|
@ -8,12 +8,16 @@ public extension Scheme {
|
||||||
buildAction: BuildAction? = BuildAction.test(),
|
buildAction: BuildAction? = BuildAction.test(),
|
||||||
testAction: TestAction? = TestAction.test(),
|
testAction: TestAction? = TestAction.test(),
|
||||||
runAction: RunAction? = RunAction.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,
|
Scheme(name: name,
|
||||||
shared: shared,
|
shared: shared,
|
||||||
buildAction: buildAction,
|
buildAction: buildAction,
|
||||||
testAction: testAction,
|
testAction: testAction,
|
||||||
runAction: runAction,
|
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,
|
init(parser: ArgumentParser,
|
||||||
versionsController: VersionsControlling,
|
versionsController: VersionsControlling,
|
||||||
installer: Installing) {
|
installer: Installing) {
|
||||||
_ = parser.add(subparser: BundleCommand.command, overview: BundleCommand.overview)
|
let subParser = parser.add(subparser: BundleCommand.command, overview: BundleCommand.overview)
|
||||||
self.versionsController = versionsController
|
self.versionsController = versionsController
|
||||||
self.installer = installer
|
self.installer = installer
|
||||||
}
|
}
|
||||||
|
@ -66,13 +66,13 @@ final class BundleCommand: Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = try String(contentsOf: versionFilePath.url)
|
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)
|
let versionPath = versionsController.path(version: version)
|
||||||
|
|
||||||
// Installing
|
// Installing
|
||||||
if !FileHandler.shared.exists(versionPath) {
|
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)
|
try installer.install(version: version, force: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +82,6 @@ final class BundleCommand: Command {
|
||||||
}
|
}
|
||||||
try FileHandler.shared.copy(from: versionPath, to: binFolderPath)
|
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
|
// MARK: - Static
|
||||||
|
|
||||||
static func processArguments() -> [String] {
|
static func processArguments() -> [String] {
|
||||||
Array(ProcessInfo.processInfo.arguments)
|
CommandRunner.arguments()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,9 +63,9 @@ class CommandRunner: CommandRunning {
|
||||||
|
|
||||||
switch resolvedVersion {
|
switch resolvedVersion {
|
||||||
case let .bin(path):
|
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):
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ class CommandRunner: CommandRunning {
|
||||||
|
|
||||||
func runVersion(_ version: String) throws {
|
func runVersion(_ version: String) throws {
|
||||||
if !versionsController.versions().contains(where: { $0.description == version }) {
|
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)
|
try installer.install(version: version, force: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,13 @@ class CommandRunner: CommandRunning {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAtPath(_ path: AbsolutePath) throws {
|
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()))
|
args.append(contentsOf: Array(arguments().dropFirst()))
|
||||||
|
|
||||||
var environment = ProcessInfo.processInfo.environment
|
var environment = ProcessInfo.processInfo.environment
|
||||||
|
@ -125,6 +131,6 @@ class CommandRunner: CommandRunning {
|
||||||
// MARK: - Static
|
// MARK: - Static
|
||||||
|
|
||||||
static func arguments() -> [String] {
|
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 version = result.get(versionArgument)!
|
||||||
let versions = versionsController.versions().map { $0.description }
|
let versions = versionsController.versions().map { $0.description }
|
||||||
if versions.contains(version) {
|
if versions.contains(version) {
|
||||||
Printer.shared.print(warning: "Version \(version) already installed, skipping")
|
logger.warning("Version \(version) already installed, skipping")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try installer.install(version: version, force: force)
|
try installer.install(version: version, force: force)
|
||||||
|
|
|
@ -46,19 +46,19 @@ class LocalCommand: Command {
|
||||||
// MARK: - Fileprivate
|
// MARK: - Fileprivate
|
||||||
|
|
||||||
private func printLocalVersions() throws {
|
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 versions = versionController.semverVersions()
|
||||||
let output = versions.sorted().reversed().map { "- \($0)" }.joined(separator: "\n")
|
let output = versions.sorted().reversed().map { "- \($0)" }.joined(separator: "\n")
|
||||||
Printer.shared.print("\(output)")
|
logger.notice("\(output)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createVersionFile(version: String) throws {
|
private func createVersionFile(version: String) throws {
|
||||||
let currentPath = FileHandler.shared.currentPath
|
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)
|
let tuistVersionPath = currentPath.appending(component: Constants.versionFileName)
|
||||||
try "\(version)".write(to: URL(fileURLWithPath: tuistVersionPath.pathString),
|
try "\(version)".write(to: URL(fileURLWithPath: tuistVersionPath.pathString),
|
||||||
atomically: true,
|
atomically: true,
|
||||||
encoding: .utf8)
|
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 }
|
let versions = versionsController.versions().map { $0.description }
|
||||||
if versions.contains(version) {
|
if versions.contains(version) {
|
||||||
try versionsController.uninstall(version: version)
|
try versionsController.uninstall(version: version)
|
||||||
Printer.shared.print(success: "Version \(version) uninstalled")
|
logger.notice("Version \(version) uninstalled", metadata: .success)
|
||||||
} else {
|
} 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.
|
/// - Throws: An error if the update process fails.
|
||||||
func run(with result: ArgumentParser.Result) throws {
|
func run(with result: ArgumentParser.Result) throws {
|
||||||
let force = result.get(forceArgument) ?? false
|
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)
|
try updater.update(force: force)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,6 @@ class VersionCommand: NSObject, Command {
|
||||||
// MARK: - Command
|
// MARK: - Command
|
||||||
|
|
||||||
func run(with _: ArgumentParser.Result) {
|
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 {
|
func install(version: String, temporaryDirectory: TemporaryDirectory, force: Bool = false) throws {
|
||||||
// We ignore the Swift version and install from the soruce code
|
// We ignore the Swift version and install from the soruce code
|
||||||
if force {
|
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,
|
try installFromSource(version: version,
|
||||||
temporaryDirectory: temporaryDirectory)
|
temporaryDirectory: temporaryDirectory)
|
||||||
return
|
return
|
||||||
|
@ -102,16 +102,17 @@ final class Installer: Installing {
|
||||||
try versionsController.install(version: version, installation: { installationDirectory in
|
try versionsController.install(version: version, installation: { installationDirectory in
|
||||||
|
|
||||||
// Download bundle
|
// Download bundle
|
||||||
Printer.shared.print("Downloading version \(version)")
|
logger.notice("Downloading version \(version)")
|
||||||
|
|
||||||
let downloadPath = temporaryDirectory.path.appending(component: Constants.bundleName)
|
let downloadPath = temporaryDirectory.path.appending(component: Constants.bundleName)
|
||||||
try System.shared.run("/usr/bin/curl", "-LSs", "--output", downloadPath.pathString, bundleURL.absoluteString)
|
try System.shared.run("/usr/bin/curl", "-LSs", "--output", downloadPath.pathString, bundleURL.absoluteString)
|
||||||
|
|
||||||
// Unzip
|
// Unzip
|
||||||
Printer.shared.print("Installing...")
|
logger.notice("Installing...")
|
||||||
try System.shared.run("/usr/bin/unzip", "-q", downloadPath.pathString, "-d", installationDirectory.pathString)
|
try System.shared.run("/usr/bin/unzip", "-q", downloadPath.pathString, "-d", installationDirectory.pathString)
|
||||||
|
|
||||||
try createTuistVersionFile(version: version, path: installationDirectory)
|
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/"))
|
let buildDirectory = temporaryDirectory.path.appending(RelativePath(".build/release/"))
|
||||||
|
|
||||||
// Cloning and building
|
// 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])
|
_ = try System.shared.observable(["/usr/bin/env", "git", "clone", Constants.gitRepositoryURL, temporaryDirectory.path.pathString])
|
||||||
.mapToString()
|
.mapToString()
|
||||||
.printStandardError()
|
.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
|
let swiftPath = try System.shared
|
||||||
.observable(["/usr/bin/xcrun", "-f", "swift"])
|
.observable(["/usr/bin/xcrun", "-f", "swift"])
|
||||||
.mapToString()
|
.mapToString()
|
||||||
|
@ -184,7 +185,7 @@ final class Installer: Installing {
|
||||||
to: installationDirectory)
|
to: installationDirectory)
|
||||||
|
|
||||||
try createTuistVersionFile(version: version, path: 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 {
|
guard let highestRemoteVersion = try googleCloudStorageClient.latestVersion().toBlocking().first() else {
|
||||||
Printer.shared.print("No remote versions found")
|
logger.warning("No remote versions found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if force {
|
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)
|
try installer.install(version: highestRemoteVersion.description, force: true)
|
||||||
} else if let highestLocalVersion = versionsController.semverVersions().sorted().last {
|
} else if let highestLocalVersion = versionsController.semverVersions().sorted().last {
|
||||||
if highestRemoteVersion <= highestLocalVersion {
|
if highestRemoteVersion <= highestLocalVersion {
|
||||||
Printer.shared.print("There are no updates available")
|
logger.notice("There are no updates available")
|
||||||
} else {
|
} else {
|
||||||
Printer.shared.print("Installing new version available \(highestRemoteVersion)")
|
logger.notice("Installing new version available \(highestRemoteVersion)")
|
||||||
try installer.install(version: highestRemoteVersion.description, force: false)
|
try installer.install(version: highestRemoteVersion.description, force: false)
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
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>()
|
var buildFilesCache = Set<AbsolutePath>()
|
||||||
try files.sorted().forEach { buildFilePath in
|
try files.sorted().forEach { buildFilePath in
|
||||||
let pathString = buildFilePath.pathString
|
let pathString = buildFilePath.pathString
|
||||||
let pathRange = NSRange(location: 0, length: pathString.count)
|
let isLocalized = pathString.contains(".lproj/")
|
||||||
let isLocalized = ProjectFileElements.localizedRegex.firstMatch(in: pathString, options: [], range: pathRange) != nil
|
|
||||||
let isLproj = buildFilePath.extension == "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
|
/// Assets that are part of a .xcassets folder
|
||||||
/// are not added individually. The whole folder is added
|
/// are not added individually. The whole folder is added
|
||||||
/// instead as a group.
|
/// instead as a group.
|
||||||
if isAsset {
|
if isAssetWithinXCAssets {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var element: (element: PBXFileElement, path: AbsolutePath)?
|
var element: (element: PBXFileElement, path: AbsolutePath)?
|
||||||
|
|
||||||
if isLocalized {
|
if isLocalized {
|
||||||
let name = buildFilePath.components.last!
|
let name = buildFilePath.basename
|
||||||
let path = buildFilePath.parentDirectory.parentDirectory.appending(component: name)
|
let path = buildFilePath.parentDirectory.parentDirectory.appending(component: name)
|
||||||
guard let group = fileElements.group(path: path) else {
|
guard let group = fileElements.group(path: path) else {
|
||||||
throw BuildPhaseGenerationError.missingFileReference(buildFilePath)
|
throw BuildPhaseGenerationError.missingFileReference(buildFilePath)
|
||||||
|
|
|
@ -14,10 +14,11 @@ protocol DerivedFileGenerating {
|
||||||
/// - Throws: An error if the generation of the derived files errors.
|
/// - 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
|
/// - 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.
|
/// 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 {
|
final class DerivedFileGenerator: DerivedFileGenerating {
|
||||||
|
typealias ProjectTransformation = (project: Project, sideEffects: [SideEffectDescriptor])
|
||||||
fileprivate static let derivedFolderName = "Derived"
|
fileprivate static let derivedFolderName = "Derived"
|
||||||
fileprivate static let infoPlistsFolderName = "InfoPlists"
|
fileprivate static let infoPlistsFolderName = "InfoPlists"
|
||||||
|
|
||||||
|
@ -32,17 +33,10 @@ final class DerivedFileGenerator: DerivedFileGenerating {
|
||||||
self.infoPlistContentProvider = infoPlistContentProvider
|
self.infoPlistContentProvider = infoPlistContentProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func generate(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, () throws -> Void) {
|
func generate(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, [SideEffectDescriptor]) {
|
||||||
/// The files that are not necessary anymore should be deleted after we generate the project.
|
let transformation = try generateInfoPlists(graph: graph, project: project, sourceRootPath: sourceRootPath)
|
||||||
/// 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)
|
|
||||||
|
|
||||||
toDelete.formUnion(infoPlistsToDelete)
|
return (transformation.project, transformation.sideEffects)
|
||||||
|
|
||||||
return (project, {
|
|
||||||
try toDelete.forEach { try FileHandler.shared.delete($0) }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Genreates the Info.plist files.
|
/// 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.
|
/// - 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.
|
/// - 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.
|
/// - Throws: An error if the encoding of the Info.plist content fails.
|
||||||
func generateInfoPlists(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> (Project, Set<AbsolutePath>) {
|
func generateInfoPlists(graph: Graphing,
|
||||||
let infoPlistsPath = DerivedFileGenerator.infoPlistsPath(sourceRootPath: sourceRootPath)
|
project: Project,
|
||||||
|
sourceRootPath: AbsolutePath) throws -> ProjectTransformation {
|
||||||
let targetsWithGeneratableInfoPlists = project.targets.filter {
|
let targetsWithGeneratableInfoPlists = project.targets.filter {
|
||||||
if let infoPlist = $0.infoPlist, case InfoPlist.file = infoPlist {
|
if let infoPlist = $0.infoPlist, case InfoPlist.file = infoPlist {
|
||||||
return false
|
return false
|
||||||
|
@ -70,44 +65,58 @@ final class DerivedFileGenerator: DerivedFileGenerating {
|
||||||
}
|
}
|
||||||
let toDelete = Set(existing).subtracting(new)
|
let toDelete = Set(existing).subtracting(new)
|
||||||
|
|
||||||
if !FileHandler.shared.exists(infoPlistsPath), !targetsWithGeneratableInfoPlists.isEmpty {
|
let deletions = toDelete.map {
|
||||||
try FileHandler.shared.createFolder(infoPlistsPath)
|
SideEffectDescriptor.file(FileDescriptor(path: $0, state: .absent))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the Info.plist
|
// Generate the Info.plist
|
||||||
let newTargets = try project.targets.map { (target) -> Target in
|
let transformation = try project.targets.map { (target) -> (Target, [SideEffectDescriptor]) in
|
||||||
guard targetsWithGeneratableInfoPlists.contains(target) else { return target }
|
guard targetsWithGeneratableInfoPlists.contains(target),
|
||||||
|
let infoPlist = target.infoPlist else {
|
||||||
|
return (target, [])
|
||||||
|
}
|
||||||
|
|
||||||
guard let infoPlist = target.infoPlist else { return target }
|
guard let dictionary = infoPlistDictionary(infoPlist: infoPlist,
|
||||||
|
project: project,
|
||||||
let dictionary: [String: Any]
|
target: target,
|
||||||
|
graph: graph) else {
|
||||||
if case let InfoPlist.dictionary(content) = infoPlist {
|
return (target, [])
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = DerivedFileGenerator.infoPlistPath(target: target, sourceRootPath: sourceRootPath)
|
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,
|
let data = try PropertyListSerialization.data(fromPropertyList: dictionary,
|
||||||
format: .xml,
|
format: .xml,
|
||||||
options: 0)
|
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
|
// 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.
|
/// 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
|
import TuistSupport
|
||||||
|
|
||||||
/// A component responsible for generating Xcode projects & workspaces
|
/// 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 {
|
public protocol Generating {
|
||||||
/// Generates an Xcode project at a given path. Only the specified project is generated (excluding its dependencies).
|
/// 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: Generating
|
||||||
/// - seealso: GeneratorModelLoading
|
/// - 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 {
|
public class Generator: Generating {
|
||||||
private let graphLoader: GraphLoading
|
private let graphLoader: GraphLoading
|
||||||
private let graphLinter: GraphLinting
|
private let graphLinter: GraphLinting
|
||||||
private let workspaceGenerator: WorkspaceGenerating
|
private let workspaceGenerator: WorkspaceGenerating
|
||||||
private let projectGenerator: ProjectGenerating
|
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.
|
/// Instance to lint the Tuist configuration against the system.
|
||||||
private let environmentLinter: EnvironmentLinting
|
private let environmentLinter: EnvironmentLinting
|
||||||
|
@ -74,29 +87,40 @@ public class Generator: Generating {
|
||||||
configGenerator: configGenerator)
|
configGenerator: configGenerator)
|
||||||
let environmentLinter = EnvironmentLinter()
|
let environmentLinter = EnvironmentLinter()
|
||||||
let workspaceStructureGenerator = WorkspaceStructureGenerator()
|
let workspaceStructureGenerator = WorkspaceStructureGenerator()
|
||||||
let cocoapodsInteractor = CocoaPodsInteractor()
|
|
||||||
let schemesGenerator = SchemesGenerator()
|
let schemesGenerator = SchemesGenerator()
|
||||||
let workspaceGenerator = WorkspaceGenerator(projectGenerator: projectGenerator,
|
let workspaceGenerator = WorkspaceGenerator(projectGenerator: projectGenerator,
|
||||||
workspaceStructureGenerator: workspaceStructureGenerator,
|
workspaceStructureGenerator: workspaceStructureGenerator,
|
||||||
cocoapodsInteractor: cocoapodsInteractor,
|
|
||||||
schemesGenerator: schemesGenerator)
|
schemesGenerator: schemesGenerator)
|
||||||
|
let writer = XcodeProjWriter()
|
||||||
|
let cocoapodsInteractor: CocoaPodsInteracting = CocoaPodsInteractor()
|
||||||
|
let swiftPackageManagerInteractor: SwiftPackageManagerInteracting = SwiftPackageManagerInteractor()
|
||||||
|
|
||||||
self.init(graphLoader: graphLoader,
|
self.init(graphLoader: graphLoader,
|
||||||
graphLinter: graphLinter,
|
graphLinter: graphLinter,
|
||||||
workspaceGenerator: workspaceGenerator,
|
workspaceGenerator: workspaceGenerator,
|
||||||
projectGenerator: projectGenerator,
|
projectGenerator: projectGenerator,
|
||||||
environmentLinter: environmentLinter)
|
environmentLinter: environmentLinter,
|
||||||
|
writer: writer,
|
||||||
|
cocoapodsInteractor: cocoapodsInteractor,
|
||||||
|
swiftPackageManagerInteractor: swiftPackageManagerInteractor)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(graphLoader: GraphLoading,
|
init(graphLoader: GraphLoading,
|
||||||
graphLinter: GraphLinting,
|
graphLinter: GraphLinting,
|
||||||
workspaceGenerator: WorkspaceGenerating,
|
workspaceGenerator: WorkspaceGenerating,
|
||||||
projectGenerator: ProjectGenerating,
|
projectGenerator: ProjectGenerating,
|
||||||
environmentLinter: EnvironmentLinting) {
|
environmentLinter: EnvironmentLinting,
|
||||||
|
writer: XcodeProjWriting,
|
||||||
|
cocoapodsInteractor: CocoaPodsInteracting,
|
||||||
|
swiftPackageManagerInteractor: SwiftPackageManagerInteracting) {
|
||||||
self.graphLoader = graphLoader
|
self.graphLoader = graphLoader
|
||||||
self.graphLinter = graphLinter
|
self.graphLinter = graphLinter
|
||||||
self.workspaceGenerator = workspaceGenerator
|
self.workspaceGenerator = workspaceGenerator
|
||||||
self.projectGenerator = projectGenerator
|
self.projectGenerator = projectGenerator
|
||||||
self.environmentLinter = environmentLinter
|
self.environmentLinter = environmentLinter
|
||||||
|
self.writer = writer
|
||||||
|
self.cocoapodsInteractor = cocoapodsInteractor
|
||||||
|
self.swiftPackageManagerInteractor = swiftPackageManagerInteractor
|
||||||
}
|
}
|
||||||
|
|
||||||
public func generateProject(_ project: Project,
|
public func generateProject(_ project: Project,
|
||||||
|
@ -107,31 +131,35 @@ public class Generator: Generating {
|
||||||
/// are relative to the directory that contains the manifest.
|
/// are relative to the directory that contains the manifest.
|
||||||
let sourceRootPath = sourceRootPath ?? project.path
|
let sourceRootPath = sourceRootPath ?? project.path
|
||||||
|
|
||||||
let generatedProject = try projectGenerator.generate(project: project,
|
let descriptor = try projectGenerator.generate(project: project,
|
||||||
graph: graph,
|
graph: graph,
|
||||||
sourceRootPath: sourceRootPath,
|
sourceRootPath: sourceRootPath,
|
||||||
xcodeprojPath: xcodeprojPath)
|
xcodeprojPath: xcodeprojPath)
|
||||||
return generatedProject.path
|
|
||||||
|
try writer.write(project: descriptor)
|
||||||
|
return descriptor.xcodeprojPath
|
||||||
}
|
}
|
||||||
|
|
||||||
public func generateProject(at path: AbsolutePath) throws -> (AbsolutePath, Graphing) {
|
public func generateProject(at path: AbsolutePath) throws -> (AbsolutePath, Graphing) {
|
||||||
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
|
let config = try graphLoader.loadConfig(path: path)
|
||||||
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
|
try environmentLinter.lint(config: config).printAndThrowIfNeeded()
|
||||||
|
|
||||||
let (graph, project) = try graphLoader.loadProject(path: path)
|
let (graph, project) = try graphLoader.loadProject(path: path)
|
||||||
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
||||||
|
|
||||||
let generatedProject = try projectGenerator.generate(project: project,
|
let descriptor = try projectGenerator.generate(project: project,
|
||||||
graph: graph,
|
graph: graph,
|
||||||
sourceRootPath: path,
|
sourceRootPath: path,
|
||||||
xcodeprojPath: nil)
|
xcodeprojPath: nil)
|
||||||
return (generatedProject.path, graph)
|
|
||||||
|
try writer.write(project: descriptor)
|
||||||
|
return (descriptor.xcodeprojPath, graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func generateProjectWorkspace(at path: AbsolutePath,
|
public func generateProjectWorkspace(at path: AbsolutePath,
|
||||||
workspaceFiles: [AbsolutePath]) throws -> (AbsolutePath, Graphing) {
|
workspaceFiles: [AbsolutePath]) throws -> (AbsolutePath, Graphing) {
|
||||||
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
|
let config = try graphLoader.loadConfig(path: path)
|
||||||
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
|
try environmentLinter.lint(config: config).printAndThrowIfNeeded()
|
||||||
|
|
||||||
let (graph, project) = try graphLoader.loadProject(path: path)
|
let (graph, project) = try graphLoader.loadProject(path: path)
|
||||||
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
||||||
|
@ -141,17 +169,20 @@ public class Generator: Generating {
|
||||||
projects: graph.projectPaths,
|
projects: graph.projectPaths,
|
||||||
additionalFiles: workspaceFiles.map(FileElement.file))
|
additionalFiles: workspaceFiles.map(FileElement.file))
|
||||||
|
|
||||||
let workspacePath = try workspaceGenerator.generate(workspace: workspace,
|
let descriptor = try workspaceGenerator.generate(workspace: workspace,
|
||||||
path: path,
|
path: path,
|
||||||
graph: graph,
|
graph: graph)
|
||||||
tuistConfig: tuistConfig)
|
try writer.write(workspace: descriptor)
|
||||||
return (workspacePath, graph)
|
|
||||||
|
try postGenerationActions(for: graph, workspaceName: descriptor.xcworkspacePath.basename)
|
||||||
|
|
||||||
|
return (descriptor.xcworkspacePath, graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func generateWorkspace(at path: AbsolutePath,
|
public func generateWorkspace(at path: AbsolutePath,
|
||||||
workspaceFiles: [AbsolutePath]) throws -> (AbsolutePath, Graphing) {
|
workspaceFiles: [AbsolutePath]) throws -> (AbsolutePath, Graphing) {
|
||||||
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
|
let config = try graphLoader.loadConfig(path: path)
|
||||||
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
|
try environmentLinter.lint(config: config).printAndThrowIfNeeded()
|
||||||
let (graph, workspace) = try graphLoader.loadWorkspace(path: path)
|
let (graph, workspace) = try graphLoader.loadWorkspace(path: path)
|
||||||
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
|
||||||
|
|
||||||
|
@ -159,10 +190,18 @@ public class Generator: Generating {
|
||||||
.merging(projects: graph.projectPaths)
|
.merging(projects: graph.projectPaths)
|
||||||
.adding(files: workspaceFiles)
|
.adding(files: workspaceFiles)
|
||||||
|
|
||||||
let workspacePath = try workspaceGenerator.generate(workspace: updatedWorkspace,
|
let descriptor = try workspaceGenerator.generate(workspace: updatedWorkspace,
|
||||||
path: path,
|
path: path,
|
||||||
graph: graph,
|
graph: graph)
|
||||||
tuistConfig: tuistConfig)
|
try writer.write(workspace: descriptor)
|
||||||
return (workspacePath, graph)
|
|
||||||
|
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
|
// swiftlint:disable:next force_try
|
||||||
static let localizedRegex = try! NSRegularExpression(pattern: "(.+\\.lproj)/.+",
|
static let localizedRegex = try! NSRegularExpression(pattern: "(.+\\.lproj)/.+",
|
||||||
options: [])
|
options: [])
|
||||||
// swiftlint:disable:next force_try
|
|
||||||
static let assetRegex = try! NSRegularExpression(pattern: ".+/.+\\.xcassets/.+",
|
|
||||||
options: [])
|
|
||||||
|
|
||||||
// MARK: - Attributes
|
// MARK: - Attributes
|
||||||
|
|
||||||
|
|
|
@ -29,13 +29,13 @@ protocol ProjectGenerating: AnyObject {
|
||||||
/// - graph: Dependencies graph.
|
/// - graph: Dependencies graph.
|
||||||
/// - sourceRootPath: Directory where the files are relative to.
|
/// - sourceRootPath: Directory where the files are relative to.
|
||||||
/// - xcodeprojPath: Path to the Xcode project. When not given, the xcodeproj is generated at sourceRootPath.
|
/// - xcodeprojPath: Path to the Xcode project. When not given, the xcodeproj is generated at sourceRootPath.
|
||||||
|
/// - Returns: Generated project descriptor
|
||||||
func generate(project: Project,
|
func generate(project: Project,
|
||||||
graph: Graphing,
|
graph: Graphing,
|
||||||
sourceRootPath: AbsolutePath?,
|
sourceRootPath: AbsolutePath?,
|
||||||
xcodeprojPath: AbsolutePath?) throws -> GeneratedProject
|
xcodeprojPath: AbsolutePath?) throws -> ProjectDescriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable type_body_length
|
|
||||||
final class ProjectGenerator: ProjectGenerating {
|
final class ProjectGenerator: ProjectGenerating {
|
||||||
// MARK: - Attributes
|
// MARK: - Attributes
|
||||||
|
|
||||||
|
@ -72,11 +72,12 @@ final class ProjectGenerator: ProjectGenerating {
|
||||||
|
|
||||||
// MARK: - ProjectGenerating
|
// MARK: - ProjectGenerating
|
||||||
|
|
||||||
|
// swiftlint:disable:next function_body_length
|
||||||
func generate(project: Project,
|
func generate(project: Project,
|
||||||
graph: Graphing,
|
graph: Graphing,
|
||||||
sourceRootPath: AbsolutePath? = nil,
|
sourceRootPath: AbsolutePath? = nil,
|
||||||
xcodeprojPath: AbsolutePath? = nil) throws -> GeneratedProject {
|
xcodeprojPath: AbsolutePath? = nil) throws -> ProjectDescriptor {
|
||||||
Printer.shared.print("Generating project \(project.name)")
|
logger.notice("Generating project \(project.name)")
|
||||||
|
|
||||||
// Getting the path.
|
// Getting the path.
|
||||||
let sourceRootPath = sourceRootPath ?? project.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.
|
// If the xcodeproj path is not given, we generate it under the source root path.
|
||||||
let xcodeprojPath = xcodeprojPath ?? sourceRootPath.appending(component: "\(project.fileName).xcodeproj")
|
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
|
// 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 workspaceData = XCWorkspaceData(children: [])
|
||||||
let workspace = XCWorkspace(data: workspaceData)
|
let workspace = XCWorkspace(data: workspaceData)
|
||||||
|
@ -107,19 +95,13 @@ final class ProjectGenerator: ProjectGenerating {
|
||||||
let pbxproj = PBXProj(objectVersion: projectConstants.objectVersion,
|
let pbxproj = PBXProj(objectVersion: projectConstants.objectVersion,
|
||||||
archiveVersion: projectConstants.archiveVersion,
|
archiveVersion: projectConstants.archiveVersion,
|
||||||
classes: [:])
|
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()
|
let fileElements = ProjectFileElements()
|
||||||
try fileElements.generateProjectFiles(project: project,
|
try fileElements.generateProjectFiles(project: project,
|
||||||
graph: graph,
|
graph: graph,
|
||||||
groups: groups,
|
groups: groups,
|
||||||
pbxproj: pbxproj,
|
pbxproj: pbxproj,
|
||||||
sourceRootPath: sourceRootPath)
|
sourceRootPath: sourceRootPath)
|
||||||
|
|
||||||
let configurationList = try configGenerator.generateProjectConfig(project: project, pbxproj: pbxproj, fileElements: fileElements)
|
let configurationList = try configGenerator.generateProjectConfig(project: project, pbxproj: pbxproj, fileElements: fileElements)
|
||||||
let pbxProject = try generatePbxproject(project: project,
|
let pbxProject = try generatePbxproject(project: project,
|
||||||
projectFileElements: fileElements,
|
projectFileElements: fileElements,
|
||||||
|
@ -141,16 +123,26 @@ final class ProjectGenerator: ProjectGenerating {
|
||||||
try generateSwiftPackageReferences(project: project,
|
try generateSwiftPackageReferences(project: project,
|
||||||
pbxproj: pbxproj,
|
pbxproj: pbxproj,
|
||||||
pbxProject: pbxProject)
|
pbxProject: pbxProject)
|
||||||
try deleteOldDerivedFiles()
|
|
||||||
|
|
||||||
return try write(xcodeprojPath: xcodeprojPath,
|
let generatedProject = GeneratedProject(pbxproj: pbxproj,
|
||||||
nativeTargets: nativeTargets,
|
path: xcodeprojPath,
|
||||||
workspace: workspace,
|
targets: nativeTargets,
|
||||||
pbxproj: pbxproj,
|
name: xcodeprojPath.basename)
|
||||||
project: project,
|
|
||||||
graph: graph)
|
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,
|
private func generatePbxproject(project: Project,
|
||||||
projectFileElements: ProjectFileElements,
|
projectFileElements: ProjectFileElements,
|
||||||
configurationList: XCConfigurationList,
|
configurationList: XCConfigurationList,
|
||||||
|
@ -158,6 +150,7 @@ final class ProjectGenerator: ProjectGenerating {
|
||||||
pbxproj: PBXProj) throws -> PBXProject {
|
pbxproj: PBXProj) throws -> PBXProject {
|
||||||
let defaultRegions = ["en", "Base"]
|
let defaultRegions = ["en", "Base"]
|
||||||
let knownRegions = Set(defaultRegions + projectFileElements.knownRegions).sorted()
|
let knownRegions = Set(defaultRegions + projectFileElements.knownRegions).sorted()
|
||||||
|
let attributes = project.organizationName.map { ["ORGANIZATIONNAME": $0] } ?? [:]
|
||||||
let pbxProject = PBXProject(name: project.name,
|
let pbxProject = PBXProject(name: project.name,
|
||||||
buildConfigurationList: configurationList,
|
buildConfigurationList: configurationList,
|
||||||
compatibilityVersion: Xcode.Default.compatibilityVersion,
|
compatibilityVersion: Xcode.Default.compatibilityVersion,
|
||||||
|
@ -169,7 +162,8 @@ final class ProjectGenerator: ProjectGenerating {
|
||||||
projectDirPath: "",
|
projectDirPath: "",
|
||||||
projects: [],
|
projects: [],
|
||||||
projectRoots: [],
|
projectRoots: [],
|
||||||
targets: [])
|
targets: [],
|
||||||
|
attributes: attributes)
|
||||||
pbxproj.add(object: pbxProject)
|
pbxproj.add(object: pbxProject)
|
||||||
pbxproj.rootObject = pbxProject
|
pbxproj.rootObject = pbxProject
|
||||||
return pbxProject
|
return pbxProject
|
||||||
|
@ -264,84 +258,6 @@ final class ProjectGenerator: ProjectGenerating {
|
||||||
pbxProject.packages = packageReferences.sorted { $0.key < $1.key }.map { $1 }
|
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 {
|
private func determineProjectConstants(graph: Graphing) throws -> ProjectConstants {
|
||||||
if !graph.packages.isEmpty {
|
if !graph.packages.isEmpty {
|
||||||
return .xcode11
|
return .xcode11
|
||||||
|
|
|
@ -15,9 +15,8 @@ protocol SchemesGenerating {
|
||||||
/// - graph: Tuist graph.
|
/// - graph: Tuist graph.
|
||||||
/// - Throws: A FatalError if the generation of the schemes fails.
|
/// - Throws: A FatalError if the generation of the schemes fails.
|
||||||
func generateWorkspaceSchemes(workspace: Workspace,
|
func generateWorkspaceSchemes(workspace: Workspace,
|
||||||
xcworkspacePath: AbsolutePath,
|
|
||||||
generatedProjects: [AbsolutePath: GeneratedProject],
|
generatedProjects: [AbsolutePath: GeneratedProject],
|
||||||
graph: Graphing) throws
|
graph: Graphing) throws -> [SchemeDescriptor]
|
||||||
|
|
||||||
/// Generates the schemes for the project targets.
|
/// Generates the schemes for the project targets.
|
||||||
///
|
///
|
||||||
|
@ -28,17 +27,8 @@ protocol SchemesGenerating {
|
||||||
/// - graph: Tuist graph.
|
/// - graph: Tuist graph.
|
||||||
/// - Throws: A FatalError if the generation of the schemes fails.
|
/// - Throws: A FatalError if the generation of the schemes fails.
|
||||||
func generateProjectSchemes(project: Project,
|
func generateProjectSchemes(project: Project,
|
||||||
xcprojectPath: AbsolutePath,
|
|
||||||
generatedProject: GeneratedProject,
|
generatedProject: GeneratedProject,
|
||||||
graph: Graphing) throws
|
graph: Graphing) throws -> [SchemeDescriptor]
|
||||||
|
|
||||||
/// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next type_body_length
|
// swiftlint:disable:next type_body_length
|
||||||
|
@ -49,42 +39,24 @@ final class SchemesGenerator: SchemesGenerating {
|
||||||
/// Default version for generated schemes.
|
/// Default version for generated schemes.
|
||||||
private static let defaultVersion = "1.3"
|
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,
|
func generateWorkspaceSchemes(workspace: Workspace,
|
||||||
xcworkspacePath: AbsolutePath,
|
|
||||||
generatedProjects: [AbsolutePath: GeneratedProject],
|
generatedProjects: [AbsolutePath: GeneratedProject],
|
||||||
graph: Graphing) throws {
|
graph: Graphing) throws -> [SchemeDescriptor] {
|
||||||
try workspace.schemes.forEach { scheme in
|
let schemes = try workspace.schemes.map { scheme in
|
||||||
try generateScheme(scheme: scheme,
|
try generateScheme(scheme: scheme,
|
||||||
xcPath: xcworkspacePath,
|
|
||||||
path: workspace.path,
|
path: workspace.path,
|
||||||
graph: graph,
|
graph: graph,
|
||||||
generatedProjects: generatedProjects)
|
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,
|
func generateProjectSchemes(project: Project,
|
||||||
xcprojectPath: AbsolutePath,
|
|
||||||
generatedProject: GeneratedProject,
|
generatedProject: GeneratedProject,
|
||||||
graph: Graphing) throws {
|
graph: Graphing) throws -> [SchemeDescriptor] {
|
||||||
/// Generate custom schemes from manifest
|
let customSchemes: [SchemeDescriptor] = try project.schemes.map { scheme in
|
||||||
try project.schemes.forEach { scheme in
|
|
||||||
try generateScheme(scheme: scheme,
|
try generateScheme(scheme: scheme,
|
||||||
xcPath: xcprojectPath,
|
|
||||||
path: project.path,
|
path: project.path,
|
||||||
graph: graph,
|
graph: graph,
|
||||||
generatedProjects: [project.path: generatedProject])
|
generatedProjects: [project.path: generatedProject])
|
||||||
|
@ -94,14 +66,15 @@ final class SchemesGenerator: SchemesGenerating {
|
||||||
let buildConfiguration = defaultDebugBuildConfigurationName(in: project)
|
let buildConfiguration = defaultDebugBuildConfigurationName(in: project)
|
||||||
let userDefinedSchemes = Set(project.schemes.map(\.name))
|
let userDefinedSchemes = Set(project.schemes.map(\.name))
|
||||||
let defaultSchemeTargets = project.targets.filter { !userDefinedSchemes.contains($0.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)
|
let scheme = createDefaultScheme(target: target, project: project, buildConfiguration: buildConfiguration, graph: graph)
|
||||||
try generateScheme(scheme: scheme,
|
return try generateScheme(scheme: scheme,
|
||||||
xcPath: xcprojectPath,
|
path: project.path,
|
||||||
path: project.path,
|
graph: graph,
|
||||||
graph: graph,
|
generatedProjects: [project.path: generatedProject])
|
||||||
generatedProjects: [project.path: generatedProject])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return customSchemes + defaultSchemes
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wipes shared and user schemes at a workspace or project path. This is needed
|
/// 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.
|
/// - graph: Tuist graph.
|
||||||
/// - generatedProjects: Project paths mapped to generated projects.
|
/// - generatedProjects: Project paths mapped to generated projects.
|
||||||
private func generateScheme(scheme: Scheme,
|
private func generateScheme(scheme: Scheme,
|
||||||
xcPath: AbsolutePath,
|
|
||||||
path: AbsolutePath,
|
path: AbsolutePath,
|
||||||
graph: Graphing,
|
graph: Graphing,
|
||||||
generatedProjects: [AbsolutePath: GeneratedProject]) throws {
|
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> SchemeDescriptor {
|
||||||
let schemeDirectory = try createSchemesDirectory(path: xcPath, shared: scheme.shared)
|
|
||||||
let schemePath = schemeDirectory.appending(component: "\(scheme.name).xcscheme")
|
|
||||||
|
|
||||||
let generatedBuildAction = try schemeBuildAction(scheme: scheme,
|
let generatedBuildAction = try schemeBuildAction(scheme: scheme,
|
||||||
graph: graph,
|
graph: graph,
|
||||||
rootPath: path,
|
rootPath: path,
|
||||||
|
@ -181,17 +150,17 @@ final class SchemesGenerator: SchemesGenerating {
|
||||||
rootPath: path,
|
rootPath: path,
|
||||||
generatedProjects: generatedProjects)
|
generatedProjects: generatedProjects)
|
||||||
|
|
||||||
let scheme = XCScheme(name: scheme.name,
|
let xcscheme = XCScheme(name: scheme.name,
|
||||||
lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion,
|
lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion,
|
||||||
version: SchemesGenerator.defaultVersion,
|
version: SchemesGenerator.defaultVersion,
|
||||||
buildAction: generatedBuildAction,
|
buildAction: generatedBuildAction,
|
||||||
testAction: generatedTestAction,
|
testAction: generatedTestAction,
|
||||||
launchAction: generatedLaunchAction,
|
launchAction: generatedLaunchAction,
|
||||||
profileAction: generatedProfileAction,
|
profileAction: generatedProfileAction,
|
||||||
analyzeAction: generatedAnalyzeAction,
|
analyzeAction: generatedAnalyzeAction,
|
||||||
archiveAction: generatedArchiveAction)
|
archiveAction: generatedArchiveAction)
|
||||||
|
|
||||||
try scheme.write(path: schemePath.path, override: true)
|
return SchemeDescriptor(xcScheme: xcscheme, shared: scheme.shared)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates the scheme build action.
|
/// Generates the scheme build action.
|
||||||
|
@ -382,9 +351,23 @@ final class SchemesGenerator: SchemesGenerating {
|
||||||
rootPath: AbsolutePath,
|
rootPath: AbsolutePath,
|
||||||
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.ProfileAction? {
|
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.ProfileAction? {
|
||||||
guard var target = try defaultTargetReference(scheme: scheme) else { return nil }
|
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
|
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 targetNode = try graph.target(path: target.projectPath, name: target.name) else { return nil }
|
||||||
guard let buildableReference = try createBuildableReference(targetReference: target,
|
guard let buildableReference = try createBuildableReference(targetReference: target,
|
||||||
graph: graph,
|
graph: graph,
|
||||||
|
@ -400,10 +383,12 @@ final class SchemesGenerator: SchemesGenerating {
|
||||||
macroExpansion = buildableReference
|
macroExpansion = buildableReference
|
||||||
}
|
}
|
||||||
|
|
||||||
let buildConfiguration = defaultReleaseBuildConfigurationName(in: targetNode.project)
|
let buildConfiguration = scheme.profileAction?.configurationName ?? defaultReleaseBuildConfigurationName(in: targetNode.project)
|
||||||
return XCScheme.ProfileAction(buildableProductRunnable: buildableProductRunnable,
|
return XCScheme.ProfileAction(buildableProductRunnable: buildableProductRunnable,
|
||||||
buildConfiguration: buildConfiguration,
|
buildConfiguration: buildConfiguration,
|
||||||
macroExpansion: macroExpansion)
|
macroExpansion: macroExpansion,
|
||||||
|
commandlineArguments: commandlineArguments,
|
||||||
|
environmentVariables: environments)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the scheme analyze action.
|
/// Returns the scheme analyze action.
|
||||||
|
@ -421,7 +406,7 @@ final class SchemesGenerator: SchemesGenerating {
|
||||||
guard let target = try defaultTargetReference(scheme: scheme),
|
guard let target = try defaultTargetReference(scheme: scheme),
|
||||||
let targetNode = try graph.target(path: target.projectPath, name: target.name) else { return nil }
|
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)
|
return XCScheme.AnalyzeAction(buildConfiguration: buildConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,14 +28,11 @@ protocol WorkspaceGenerating: AnyObject {
|
||||||
/// - workspace: Workspace model.
|
/// - workspace: Workspace model.
|
||||||
/// - path: Path to the directory where the generation command is executed from.
|
/// - path: Path to the directory where the generation command is executed from.
|
||||||
/// - graph: In-memory representation of the graph.
|
/// - graph: In-memory representation of the graph.
|
||||||
/// - tuistConfig: Tuist configuration.
|
/// - Returns: Generated workspace descriptor
|
||||||
/// - Returns: Path to the generated workspace.
|
|
||||||
/// - Throws: An error if the generation fails.
|
/// - Throws: An error if the generation fails.
|
||||||
@discardableResult
|
|
||||||
func generate(workspace: Workspace,
|
func generate(workspace: Workspace,
|
||||||
path: AbsolutePath,
|
path: AbsolutePath,
|
||||||
graph: Graphing,
|
graph: Graphing) throws -> WorkspaceDescriptor
|
||||||
tuistConfig: TuistConfig) throws -> AbsolutePath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class WorkspaceGenerator: WorkspaceGenerating {
|
final class WorkspaceGenerator: WorkspaceGenerating {
|
||||||
|
@ -43,64 +40,55 @@ final class WorkspaceGenerator: WorkspaceGenerating {
|
||||||
|
|
||||||
private let projectGenerator: ProjectGenerating
|
private let projectGenerator: ProjectGenerating
|
||||||
private let workspaceStructureGenerator: WorkspaceStructureGenerating
|
private let workspaceStructureGenerator: WorkspaceStructureGenerating
|
||||||
private let cocoapodsInteractor: CocoaPodsInteracting
|
|
||||||
private let schemesGenerator: SchemesGenerating
|
private let schemesGenerator: SchemesGenerating
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
convenience init(defaultSettingsProvider: DefaultSettingsProviding = DefaultSettingsProvider(),
|
convenience init(defaultSettingsProvider: DefaultSettingsProviding = DefaultSettingsProvider()) {
|
||||||
cocoapodsInteractor: CocoaPodsInteracting = CocoaPodsInteractor()) {
|
|
||||||
let configGenerator = ConfigGenerator(defaultSettingsProvider: defaultSettingsProvider)
|
let configGenerator = ConfigGenerator(defaultSettingsProvider: defaultSettingsProvider)
|
||||||
let targetGenerator = TargetGenerator(configGenerator: configGenerator)
|
let targetGenerator = TargetGenerator(configGenerator: configGenerator)
|
||||||
let projectGenerator = ProjectGenerator(targetGenerator: targetGenerator,
|
let projectGenerator = ProjectGenerator(targetGenerator: targetGenerator,
|
||||||
configGenerator: configGenerator)
|
configGenerator: configGenerator)
|
||||||
self.init(projectGenerator: projectGenerator,
|
self.init(projectGenerator: projectGenerator,
|
||||||
workspaceStructureGenerator: WorkspaceStructureGenerator(),
|
workspaceStructureGenerator: WorkspaceStructureGenerator(),
|
||||||
cocoapodsInteractor: cocoapodsInteractor,
|
|
||||||
schemesGenerator: SchemesGenerator())
|
schemesGenerator: SchemesGenerator())
|
||||||
}
|
}
|
||||||
|
|
||||||
init(projectGenerator: ProjectGenerating,
|
init(projectGenerator: ProjectGenerating,
|
||||||
workspaceStructureGenerator: WorkspaceStructureGenerating,
|
workspaceStructureGenerator: WorkspaceStructureGenerating,
|
||||||
cocoapodsInteractor: CocoaPodsInteracting,
|
|
||||||
schemesGenerator: SchemesGenerating) {
|
schemesGenerator: SchemesGenerating) {
|
||||||
self.projectGenerator = projectGenerator
|
self.projectGenerator = projectGenerator
|
||||||
self.workspaceStructureGenerator = workspaceStructureGenerator
|
self.workspaceStructureGenerator = workspaceStructureGenerator
|
||||||
self.cocoapodsInteractor = cocoapodsInteractor
|
|
||||||
self.schemesGenerator = schemesGenerator
|
self.schemesGenerator = schemesGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - WorkspaceGenerating
|
// MARK: - WorkspaceGenerating
|
||||||
|
|
||||||
/// Generates the given workspace.
|
func generate(workspace: Workspace, path: AbsolutePath, graph: Graphing) throws -> WorkspaceDescriptor {
|
||||||
///
|
|
||||||
/// - 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 {
|
|
||||||
let workspaceName = "\(graph.name).xcworkspace"
|
let workspaceName = "\(graph.name).xcworkspace"
|
||||||
|
|
||||||
Printer.shared.print(section: "Generating workspace \(workspaceName)")
|
logger.notice("Generating workspace \(workspaceName)", metadata: .section)
|
||||||
|
|
||||||
/// Projects
|
/// Projects
|
||||||
|
let projects = try graph.projects.map { project in
|
||||||
var generatedProjects = [AbsolutePath: GeneratedProject]()
|
try projectGenerator.generate(project: project,
|
||||||
try graph.projects.forEach { project in
|
graph: graph,
|
||||||
let generatedProject = try projectGenerator.generate(project: project,
|
sourceRootPath: project.path,
|
||||||
graph: graph,
|
xcodeprojPath: nil)
|
||||||
sourceRootPath: project.path,
|
|
||||||
xcodeprojPath: nil)
|
|
||||||
generatedProjects[project.path] = generatedProject
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Workspace structure
|
||||||
let structure = workspaceStructureGenerator.generateStructure(path: path,
|
let structure = workspaceStructureGenerator.generateStructure(path: path,
|
||||||
workspace: workspace,
|
workspace: workspace,
|
||||||
|
@ -115,128 +103,18 @@ final class WorkspaceGenerator: WorkspaceGenerating {
|
||||||
path: path)
|
path: path)
|
||||||
}
|
}
|
||||||
|
|
||||||
try write(workspace: workspace,
|
|
||||||
xcworkspace: xcWorkspace,
|
|
||||||
generatedProjects: generatedProjects,
|
|
||||||
graph: graph,
|
|
||||||
to: workspacePath)
|
|
||||||
|
|
||||||
// Schemes
|
// Schemes
|
||||||
|
|
||||||
try writeSchemes(workspace: workspace,
|
let schemes = try schemesGenerator.generateWorkspaceSchemes(workspace: workspace,
|
||||||
xcworkspace: xcWorkspace,
|
generatedProjects: generatedProjects,
|
||||||
generatedProjects: generatedProjects,
|
graph: graph)
|
||||||
graph: graph,
|
|
||||||
to: workspacePath)
|
|
||||||
|
|
||||||
// SPM
|
return WorkspaceDescriptor(path: path,
|
||||||
|
xcworkspacePath: workspacePath,
|
||||||
try generatePackageDependencyManager(at: path,
|
xcworkspace: xcWorkspace,
|
||||||
workspace: workspace,
|
projectDescriptors: projects,
|
||||||
workspaceName: workspaceName,
|
schemeDescriptors: schemes,
|
||||||
graph: graph)
|
sideEffectDescriptors: [])
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a XCWorkspaceDataElement.file from a path string.
|
/// 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.
|
/// - Parameter config: Tuist configuration to be linted against the system.
|
||||||
/// - Returns: A list of linting issues.
|
/// - Returns: A list of linting issues.
|
||||||
func lint(config: TuistConfig) throws -> [LintingIssue]
|
func lint(config: Config) throws -> [LintingIssue]
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EnvironmentLinter: EnvironmentLinting {
|
public class EnvironmentLinter: EnvironmentLinting {
|
||||||
/// Default constructor.
|
/// Default constructor.
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public func lint(config: TuistConfig) throws -> [LintingIssue] {
|
public func lint(config: Config) throws -> [LintingIssue] {
|
||||||
var issues = [LintingIssue]()
|
var issues = [LintingIssue]()
|
||||||
|
|
||||||
issues.append(contentsOf: try lintXcodeVersion(config: config))
|
issues.append(contentsOf: try lintXcodeVersion(config: config))
|
||||||
|
@ -28,7 +28,7 @@ public class EnvironmentLinter: EnvironmentLinting {
|
||||||
/// - Parameter config: Tuist configuration.
|
/// - Parameter config: Tuist configuration.
|
||||||
/// - Returns: An array with a linting issue if the selected version is not compatible.
|
/// - 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.
|
/// - 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 {
|
guard case let CompatibleXcodeVersions.list(compatibleVersions) = config.compatibleXcodeVersions else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,10 @@ public class GraphLinter: GraphLinting {
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
public convenience init() {
|
public convenience init() {
|
||||||
self.init(projectLinter: ProjectLinter(),
|
let projectLinter = ProjectLinter()
|
||||||
staticProductsLinter: StaticProductsGraphLinter())
|
let staticProductsLinter = StaticProductsGraphLinter()
|
||||||
|
self.init(projectLinter: projectLinter,
|
||||||
|
staticProductsLinter: staticProductsLinter)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(projectLinter: ProjectLinting,
|
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.
|
/// Runs 'pod install' for all the CocoaPods dependencies that have been indicated in the graph.
|
||||||
///
|
///
|
||||||
/// - Parameter graph: Project graph.
|
/// - Parameter graph: Project graph.
|
||||||
|
@ -36,17 +36,19 @@ protocol CocoaPodsInteracting {
|
||||||
func install(graph: Graphing) throws
|
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.
|
/// Runs 'pod install' for all the CocoaPods dependencies that have been indicated in the graph.
|
||||||
///
|
///
|
||||||
/// - Parameter graph: Project graph.
|
/// - Parameter graph: Project graph.
|
||||||
/// - Throws: An error if the installation of the pods fails.
|
/// - Throws: An error if the installation of the pods fails.
|
||||||
func install(graph: Graphing) throws {
|
public func install(graph: Graphing) throws {
|
||||||
do {
|
do {
|
||||||
try install(graph: graph, updatingRepo: false)
|
try install(graph: graph, updatingRepo: false)
|
||||||
} catch let error as CocoaPodsInteractorError {
|
} catch let error as CocoaPodsInteractorError {
|
||||||
if case CocoaPodsInteractorError.outdatedRepository = error {
|
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)
|
try self.install(graph: graph, updatingRepo: true)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
@ -81,7 +83,8 @@ final class CocoaPodsInteractor: CocoaPodsInteracting {
|
||||||
|
|
||||||
// The installation of Pods might fail if the local repository that contains the specs
|
// The installation of Pods might fail if the local repository that contains the specs
|
||||||
// is outdated.
|
// 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
|
var mightNeedRepoUpdate: Bool = false
|
||||||
let outputClosure: ([UInt8]) -> Void = { bytes in
|
let outputClosure: ([UInt8]) -> Void = { bytes in
|
||||||
let content = String(data: Data(bytes), encoding: .utf8)
|
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 final class MockEnvironmentLinter: EnvironmentLinting {
|
||||||
public var lintStub: [LintingIssue]?
|
public var lintStub: [LintingIssue]?
|
||||||
public var lintArgs: [TuistConfig] = []
|
public var lintArgs: [Config] = []
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public func lint(config: TuistConfig) throws -> [LintingIssue] {
|
public func lint(config: Config) throws -> [LintingIssue] {
|
||||||
lintArgs.append(config)
|
lintArgs.append(config)
|
||||||
return lintStub ?? []
|
return lintStub ?? []
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
import Foundation
|
|
@ -17,10 +17,7 @@ protocol CacheControlling {
|
||||||
|
|
||||||
final class CacheController: CacheControlling {
|
final class CacheController: CacheControlling {
|
||||||
/// Xcode project generator.
|
/// Xcode project generator.
|
||||||
private let generator: Generating
|
private let generator: ProjectGenerating
|
||||||
|
|
||||||
/// Manifest loader.
|
|
||||||
private let manifestLoader: ManifestLoading
|
|
||||||
|
|
||||||
/// Utility to build the xcframeworks.
|
/// Utility to build the xcframeworks.
|
||||||
private let xcframeworkBuilder: XCFrameworkBuilding
|
private let xcframeworkBuilder: XCFrameworkBuilding
|
||||||
|
@ -31,28 +28,26 @@ final class CacheController: CacheControlling {
|
||||||
/// Cache.
|
/// Cache.
|
||||||
private let cache: CacheStoraging
|
private let cache: CacheStoraging
|
||||||
|
|
||||||
init(generator: Generating = Generator(),
|
init(generator: ProjectGenerating = ProjectGenerator(),
|
||||||
manifestLoader: ManifestLoading = ManifestLoader(),
|
|
||||||
xcframeworkBuilder: XCFrameworkBuilding = XCFrameworkBuilder(xcodeBuildController: XcodeBuildController()),
|
xcframeworkBuilder: XCFrameworkBuilding = XCFrameworkBuilder(xcodeBuildController: XcodeBuildController()),
|
||||||
cache: CacheStoraging = Cache(),
|
cache: CacheStoraging = Cache(),
|
||||||
graphContentHasher: GraphContentHashing = GraphContentHasher()) {
|
graphContentHasher: GraphContentHashing = GraphContentHasher()) {
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
self.manifestLoader = manifestLoader
|
|
||||||
self.xcframeworkBuilder = xcframeworkBuilder
|
self.xcframeworkBuilder = xcframeworkBuilder
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
self.graphContentHasher = graphContentHasher
|
self.graphContentHasher = graphContentHasher
|
||||||
}
|
}
|
||||||
|
|
||||||
func cache(path: AbsolutePath) throws {
|
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 cacheableTargets = try self.cacheableTargets(graph: graph)
|
||||||
|
|
||||||
let completables = try cacheableTargets.map { try buildAndCacheXCFramework(path: path, target: $0.key, hash: $0.value) }
|
let completables = try cacheableTargets.map { try buildAndCacheXCFramework(path: path, target: $0.key, hash: $0.value) }
|
||||||
_ = try Completable.zip(completables).toBlocking().last()
|
_ = 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.
|
/// Returns all the targets that are cacheable and their hashes.
|
||||||
|
@ -61,7 +56,7 @@ final class CacheController: CacheControlling {
|
||||||
try graphContentHasher.contentHashes(for: graph)
|
try graphContentHasher.contentHashes(for: graph)
|
||||||
.filter { target, hash in
|
.filter { target, hash in
|
||||||
if let exists = try self.cache.exists(hash: hash).toBlocking().first(), exists {
|
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 false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -27,6 +27,6 @@ class BuildCommand: NSObject, RawCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(arguments _: [String]) throws {
|
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] {
|
public static func processArguments() -> [String] {
|
||||||
Array(ProcessInfo.processInfo.arguments)
|
Array(ProcessInfo.processInfo.arguments).filter { $0 != "--verbose" }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Internal
|
// MARK: - Internal
|
||||||
|
|
|
@ -44,6 +44,6 @@ class DumpCommand: NSObject, Command {
|
||||||
}
|
}
|
||||||
let project = try manifestLoader.loadProject(at: path)
|
let project = try manifestLoader.loadProject(at: path)
|
||||||
let json: JSON = try project.toJSON()
|
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)
|
try! FileHandler.shared.delete(EditCommand.temporaryDirectory.path)
|
||||||
exit(0)
|
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)
|
try opener.open(path: xcodeprojPath, wait: true)
|
||||||
} else {
|
} 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
|
// MARK: - Attributes
|
||||||
|
|
||||||
/// Generator instance to generate the project workspace.
|
/// Generator instance to generate the project workspace.
|
||||||
private let generator: Generating
|
private let generator: ProjectGenerating
|
||||||
|
|
||||||
/// Manifest loader instance that can load project maifests from disk
|
|
||||||
private let manifestLoader: ManifestLoading
|
|
||||||
|
|
||||||
/// Opener instance to run open in the system.
|
/// Opener instance to run open in the system.
|
||||||
private let opener: Opening
|
private let opener: Opening
|
||||||
|
@ -32,14 +29,8 @@ class FocusCommand: NSObject, Command {
|
||||||
///
|
///
|
||||||
/// - Parameter parser: Argument parser that parses the CLI arguments.
|
/// - Parameter parser: Argument parser that parses the CLI arguments.
|
||||||
required convenience init(parser: ArgumentParser) {
|
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,
|
self.init(parser: parser,
|
||||||
generator: generator,
|
generator: ProjectGenerator(),
|
||||||
manifestLoader: manifestLoader,
|
|
||||||
opener: Opener())
|
opener: Opener())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,24 +39,20 @@ class FocusCommand: NSObject, Command {
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - parser: Argument parser that parses the CLI arguments.
|
/// - parser: Argument parser that parses the CLI arguments.
|
||||||
/// - generator: Generator instance to generate the project workspace.
|
/// - 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.
|
/// - opener: Opener instance to run open in the system.
|
||||||
init(parser: ArgumentParser,
|
init(parser: ArgumentParser,
|
||||||
generator: Generating,
|
generator: ProjectGenerating,
|
||||||
manifestLoader: ManifestLoading,
|
|
||||||
opener: Opening) {
|
opener: Opening) {
|
||||||
parser.add(subparser: FocusCommand.command, overview: FocusCommand.overview)
|
parser.add(subparser: FocusCommand.command, overview: FocusCommand.overview)
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
self.manifestLoader = manifestLoader
|
|
||||||
self.opener = opener
|
self.opener = opener
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(with _: ArgumentParser.Result) throws {
|
func run(with _: ArgumentParser.Result) throws {
|
||||||
let path = FileHandler.shared.currentPath
|
let path = FileHandler.shared.currentPath
|
||||||
|
|
||||||
let (workspacePath, _) = try generator.generate(at: path,
|
let workspacePath = try generator.generate(path: path,
|
||||||
manifestLoader: manifestLoader,
|
projectOnly: false)
|
||||||
projectOnly: false)
|
|
||||||
|
|
||||||
try opener.open(path: workspacePath)
|
try opener.open(path: workspacePath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,32 +13,25 @@ class GenerateCommand: NSObject, Command {
|
||||||
|
|
||||||
// MARK: - Attributes
|
// MARK: - Attributes
|
||||||
|
|
||||||
private let generator: Generating
|
|
||||||
private let manifestLoader: ManifestLoading
|
|
||||||
private let clock: Clock
|
private let clock: Clock
|
||||||
|
private let generator: ProjectGenerating
|
||||||
let pathArgument: OptionArgument<String>
|
let pathArgument: OptionArgument<String>
|
||||||
let projectOnlyArgument: OptionArgument<Bool>
|
let projectOnlyArgument: OptionArgument<Bool>
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
required convenience init(parser: ArgumentParser) {
|
required convenience init(parser: ArgumentParser) {
|
||||||
let manifestLoader = ManifestLoader()
|
let projectGenerator = ProjectGenerator()
|
||||||
let manifestLinter = ManifestLinter()
|
|
||||||
let modelLoader = GeneratorModelLoader(manifestLoader: manifestLoader, manifestLinter: manifestLinter)
|
|
||||||
let generator = Generator(modelLoader: modelLoader)
|
|
||||||
self.init(parser: parser,
|
self.init(parser: parser,
|
||||||
generator: generator,
|
generator: projectGenerator,
|
||||||
manifestLoader: manifestLoader,
|
|
||||||
clock: WallClock())
|
clock: WallClock())
|
||||||
}
|
}
|
||||||
|
|
||||||
init(parser: ArgumentParser,
|
init(parser: ArgumentParser,
|
||||||
generator: Generating,
|
generator: ProjectGenerating,
|
||||||
manifestLoader: ManifestLoading,
|
|
||||||
clock: Clock) {
|
clock: Clock) {
|
||||||
let subParser = parser.add(subparser: GenerateCommand.command, overview: GenerateCommand.overview)
|
let subParser = parser.add(subparser: GenerateCommand.command, overview: GenerateCommand.overview)
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
self.manifestLoader = manifestLoader
|
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
|
|
||||||
pathArgument = subParser.add(option: "--path",
|
pathArgument = subParser.add(option: "--path",
|
||||||
|
@ -57,13 +50,12 @@ class GenerateCommand: NSObject, Command {
|
||||||
let path = self.path(arguments: arguments)
|
let path = self.path(arguments: arguments)
|
||||||
let projectOnly = arguments.get(projectOnlyArgument) ?? false
|
let projectOnly = arguments.get(projectOnlyArgument) ?? false
|
||||||
|
|
||||||
_ = try generator.generate(at: path,
|
try generator.generate(path: path, projectOnly: projectOnly)
|
||||||
manifestLoader: manifestLoader,
|
|
||||||
projectOnly: projectOnly)
|
|
||||||
|
|
||||||
let time = String(format: "%.3f", timer.stop())
|
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
|
// MARK: - Fileprivate
|
||||||
|
|
|
@ -45,11 +45,11 @@ class GraphCommand: NSObject, Command {
|
||||||
|
|
||||||
let path = FileHandler.shared.currentPath.appending(component: "graph.dot")
|
let path = FileHandler.shared.currentPath.appending(component: "graph.dot")
|
||||||
if FileHandler.shared.exists(path) {
|
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.delete(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
try FileHandler.shared.write(graph, path: path, atomically: true)
|
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,
|
kind: String.self,
|
||||||
usage: "The name of the project. If it's not passed (Default: Name of the directory).",
|
usage: "The name of the project. If it's not passed (Default: Name of the directory).",
|
||||||
completion: nil)
|
completion: nil)
|
||||||
|
|
||||||
self.playgroundGenerator = playgroundGenerator
|
self.playgroundGenerator = playgroundGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,10 +93,10 @@ class InitCommand: NSObject, Command {
|
||||||
try generateWorkspaceSwift(name: name, platform: platform, path: path)
|
try generateWorkspaceSwift(name: name, platform: platform, path: path)
|
||||||
try generateSwiftFiles(name: name, platform: platform, path: path)
|
try generateSwiftFiles(name: name, platform: platform, path: path)
|
||||||
try generatePlaygrounds(name: name, path: path, platform: platform)
|
try generatePlaygrounds(name: name, path: path, platform: platform)
|
||||||
try generateTuistConfig(path: path)
|
try generateConfig(path: path)
|
||||||
try generateGitIgnore(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
|
// MARK: - Fileprivate
|
||||||
|
@ -328,15 +329,19 @@ class InitCommand: NSObject, Command {
|
||||||
try content.write(to: setupPath.url, atomically: true, encoding: .utf8)
|
try content.write(to: setupPath.url, atomically: true, encoding: .utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateTuistConfig(path: AbsolutePath) throws {
|
private func generateConfig(path: AbsolutePath) throws {
|
||||||
let content = """
|
let content = """
|
||||||
import ProjectDescription
|
import ProjectDescription
|
||||||
|
|
||||||
let config = TuistConfig(generationOptions: [
|
let config = Config(generationOptions: [
|
||||||
])
|
])
|
||||||
"""
|
"""
|
||||||
let setupPath = path.appending(component: Manifest.tuistConfig.fileName)
|
let configPath = path.appending(RelativePath("\(Constants.tuistDirectoryName)/\(Manifest.config.fileName)"))
|
||||||
try content.write(to: setupPath.url, atomically: true, encoding: .utf8)
|
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
|
// swiftlint:disable:next function_body_length
|
||||||
|
|
|
@ -78,28 +78,28 @@ class LintCommand: NSObject, Command {
|
||||||
let manifests = manifestLoading.manifests(at: path)
|
let manifests = manifestLoading.manifests(at: path)
|
||||||
var graph: Graphing!
|
var graph: Graphing!
|
||||||
|
|
||||||
Printer.shared.print(section: "Loading the dependency graph")
|
logger.notice("Loading the dependency graph")
|
||||||
if manifests.contains(.workspace) {
|
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)
|
(graph, _) = try graphLoader.loadWorkspace(path: path)
|
||||||
} else if manifests.contains(.project) {
|
} 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)
|
(graph, _) = try graphLoader.loadProject(path: path)
|
||||||
} else {
|
} else {
|
||||||
throw LintCommandError.manifestNotFound(path)
|
throw LintCommandError.manifestNotFound(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
Printer.shared.print(section: "Running linters")
|
logger.notice("Running linters")
|
||||||
let config = try graphLoader.loadTuistConfig(path: path)
|
let config = try graphLoader.loadConfig(path: path)
|
||||||
|
|
||||||
var issues: [LintingIssue] = []
|
var issues: [LintingIssue] = []
|
||||||
Printer.shared.print("Linting the environment")
|
logger.notice("Linting the environment")
|
||||||
issues.append(contentsOf: try environmentLinter.lint(config: config))
|
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))
|
issues.append(contentsOf: graphLinter.lint(graph: graph))
|
||||||
|
|
||||||
if issues.isEmpty {
|
if issues.isEmpty {
|
||||||
Printer.shared.print(success: "No linting issues found")
|
logger.notice("No linting issues found", metadata: .success)
|
||||||
} else {
|
} else {
|
||||||
try issues.printAndThrowIfNeeded()
|
try issues.printAndThrowIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,6 @@ class VersionCommand: NSObject, Command {
|
||||||
// MARK: - Command
|
// MARK: - Command
|
||||||
|
|
||||||
func run(with _: ArgumentParser.Result) {
|
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 {
|
final class ProjectEditor: ProjectEditing {
|
||||||
/// Project generator.
|
/// Project generator.
|
||||||
let generator: Generating
|
let generator: DescriptorGenerating
|
||||||
|
|
||||||
/// Project editor mapper.
|
/// Project editor mapper.
|
||||||
let projectEditorMapper: ProjectEditorMapping
|
let projectEditorMapper: ProjectEditorMapping
|
||||||
|
@ -47,16 +47,21 @@ final class ProjectEditor: ProjectEditing {
|
||||||
/// Utility to locate the helpers directory.
|
/// Utility to locate the helpers directory.
|
||||||
let helpersDirectoryLocator: HelpersDirectoryLocating
|
let helpersDirectoryLocator: HelpersDirectoryLocating
|
||||||
|
|
||||||
init(generator: Generating = Generator(),
|
/// Xcode Project writer
|
||||||
|
private let writer: XcodeProjWriting
|
||||||
|
|
||||||
|
init(generator: DescriptorGenerating = DescriptorGenerator(),
|
||||||
projectEditorMapper: ProjectEditorMapping = ProjectEditorMapper(),
|
projectEditorMapper: ProjectEditorMapping = ProjectEditorMapper(),
|
||||||
resourceLocator: ResourceLocating = ResourceLocator(),
|
resourceLocator: ResourceLocating = ResourceLocator(),
|
||||||
manifestFilesLocator: ManifestFilesLocating = ManifestFilesLocator(),
|
manifestFilesLocator: ManifestFilesLocating = ManifestFilesLocator(),
|
||||||
helpersDirectoryLocator: HelpersDirectoryLocating = HelpersDirectoryLocator()) {
|
helpersDirectoryLocator: HelpersDirectoryLocating = HelpersDirectoryLocator(),
|
||||||
|
writer: XcodeProjWriting = XcodeProjWriter()) {
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
self.projectEditorMapper = projectEditorMapper
|
self.projectEditorMapper = projectEditorMapper
|
||||||
self.resourceLocator = resourceLocator
|
self.resourceLocator = resourceLocator
|
||||||
self.manifestFilesLocator = manifestFilesLocator
|
self.manifestFilesLocator = manifestFilesLocator
|
||||||
self.helpersDirectoryLocator = helpersDirectoryLocator
|
self.helpersDirectoryLocator = helpersDirectoryLocator
|
||||||
|
self.writer = writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func edit(at: AbsolutePath, in dstDirectory: AbsolutePath) throws -> AbsolutePath {
|
func edit(at: AbsolutePath, in dstDirectory: AbsolutePath) throws -> AbsolutePath {
|
||||||
|
@ -82,9 +87,13 @@ final class ProjectEditor: ProjectEditing {
|
||||||
manifests: manifests.map { $0.1 },
|
manifests: manifests.map { $0.1 },
|
||||||
helpers: helpers,
|
helpers: helpers,
|
||||||
projectDescriptionPath: projectDesciptionPath)
|
projectDescriptionPath: projectDesciptionPath)
|
||||||
return try generator.generateProject(project,
|
|
||||||
graph: graph,
|
let config = ProjectGenerationConfig(sourceRootPath: project.path,
|
||||||
sourceRootPath: project.path,
|
|
||||||
xcodeprojPath: xcodeprojPath)
|
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 {
|
public class GeneratorModelLoader: GeneratorModelLoading {
|
||||||
private let manifestLoader: ManifestLoading
|
private let manifestLoader: ManifestLoading
|
||||||
private let manifestLinter: ManifestLinting
|
private let manifestLinter: ManifestLinting
|
||||||
|
private let rootDirectoryLocator: RootDirectoryLocating
|
||||||
|
|
||||||
public init(manifestLoader: ManifestLoading,
|
public convenience init(manifestLoader: ManifestLoading,
|
||||||
manifestLinter: ManifestLinting) {
|
manifestLinter: ManifestLinting) {
|
||||||
|
self.init(manifestLoader: manifestLoader,
|
||||||
|
manifestLinter: manifestLinter,
|
||||||
|
rootDirectoryLocator: RootDirectoryLocator())
|
||||||
|
}
|
||||||
|
|
||||||
|
init(manifestLoader: ManifestLoading,
|
||||||
|
manifestLinter: ManifestLinting,
|
||||||
|
rootDirectoryLocator: RootDirectoryLocating) {
|
||||||
self.manifestLoader = manifestLoader
|
self.manifestLoader = manifestLoader
|
||||||
self.manifestLinter = manifestLinter
|
self.manifestLinter = manifestLinter
|
||||||
|
self.rootDirectoryLocator = rootDirectoryLocator
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a Project model at the specified path
|
/// 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)
|
/// - Throws: Error encountered during the loading process (e.g. Missing project)
|
||||||
public func loadProject(at path: AbsolutePath) throws -> TuistCore.Project {
|
public func loadProject(at path: AbsolutePath) throws -> TuistCore.Project {
|
||||||
let manifest = try manifestLoader.loadProject(at: path)
|
let manifest = try manifestLoader.loadProject(at: path)
|
||||||
let tuistConfig = try loadTuistConfig(at: path)
|
let config = try loadConfig(at: path)
|
||||||
let generatorPaths = GeneratorPaths(manifestDirectory: path)
|
let generatorPaths = GeneratorPaths(manifestDirectory: path)
|
||||||
try manifestLinter.lint(project: manifest).printAndThrowIfNeeded()
|
try manifestLinter.lint(project: manifest).printAndThrowIfNeeded()
|
||||||
let project = try TuistCore.Project.from(manifest: manifest, generatorPaths: generatorPaths)
|
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 {
|
public func loadWorkspace(at path: AbsolutePath) throws -> TuistCore.Workspace {
|
||||||
|
@ -39,32 +49,55 @@ public class GeneratorModelLoader: GeneratorModelLoading {
|
||||||
return workspace
|
return workspace
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadTuistConfig(at path: AbsolutePath) throws -> TuistCore.TuistConfig {
|
public func loadConfig(at path: AbsolutePath) throws -> TuistCore.Config {
|
||||||
guard let tuistConfigPath = FileHandler.shared.locateDirectoryTraversingParents(from: path, path: Manifest.tuistConfig.fileName) else {
|
// If the Config.swift file exists in the root Tuist/ directory, we load it from there
|
||||||
return TuistCore.TuistConfig.default
|
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)
|
// We first try to load the deprecated file. If it doesn't exist, we load the new file name.
|
||||||
return try TuistCore.TuistConfig.from(manifest: manifest)
|
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,
|
private func enriched(model: TuistCore.Project, with config: TuistCore.Config) throws -> TuistCore.Project {
|
||||||
with config: TuistCore.TuistConfig) throws -> TuistCore.Project {
|
|
||||||
var enrichedModel = model
|
var enrichedModel = model
|
||||||
|
|
||||||
// Xcode project file name
|
// Xcode project file name
|
||||||
let xcodeFileName = xcodeFileNameOverride(from: config, for: model)
|
let xcodeFileName = xcodeFileNameOverride(from: config, for: model)
|
||||||
enrichedModel = enrichedModel.replacing(fileName: xcodeFileName)
|
enrichedModel = enrichedModel.replacing(fileName: xcodeFileName)
|
||||||
|
|
||||||
|
// Xcode project organization name
|
||||||
|
if let organizationName = organizationNameOverride(from: config) {
|
||||||
|
enrichedModel = enrichedModel.replacing(organizationName: organizationName)
|
||||||
|
}
|
||||||
|
|
||||||
return enrichedModel
|
return enrichedModel
|
||||||
}
|
}
|
||||||
|
|
||||||
private func xcodeFileNameOverride(from config: TuistCore.TuistConfig,
|
private func xcodeFileNameOverride(from config: TuistCore.Config, for model: TuistCore.Project) -> String? {
|
||||||
for model: TuistCore.Project) -> String? {
|
|
||||||
var xcodeFileName = config.generationOptions.compactMap { item -> String? in
|
var xcodeFileName = config.generationOptions.compactMap { item -> String? in
|
||||||
switch item {
|
switch item {
|
||||||
case let .xcodeProjectName(projectName):
|
case let .xcodeProjectName(projectName):
|
||||||
return projectName.description
|
return projectName.description
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}.first
|
}.first
|
||||||
|
|
||||||
|
@ -74,4 +107,15 @@ public class GeneratorModelLoader: GeneratorModelLoading {
|
||||||
|
|
||||||
return xcodeFileName
|
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 {
|
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.
|
/// - Parameter path: Path to the directory that contains the Config.swift file.
|
||||||
/// - Returns: Loaded TuistConfig.swift file.
|
/// - Returns: Loaded Config.swift file.
|
||||||
/// - Throws: An error if the file has a syntax error.
|
/// - 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.
|
/// Loads the Project.swift in the given directory.
|
||||||
/// - Parameter path: Path to the directory that contains the Project.swift.
|
/// - 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 })
|
Set(manifestFilesLocator.locate(at: path).map { $0.0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadTuistConfig(at path: AbsolutePath) throws -> ProjectDescription.TuistConfig {
|
public func loadConfig(at path: AbsolutePath) throws -> ProjectDescription.Config {
|
||||||
try loadManifest(.tuistConfig, at: path)
|
try loadManifest(.config, at: path)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadProject(at path: AbsolutePath) throws -> ProjectDescription.Project {
|
public func loadProject(at path: AbsolutePath) throws -> ProjectDescription.Project {
|
||||||
|
@ -134,12 +134,19 @@ public class ManifestLoader: ManifestLoading {
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func loadManifest<T: Decodable>(_ manifest: Manifest, at path: AbsolutePath) throws -> T {
|
private func loadManifest<T: Decodable>(_ manifest: Manifest, at path: AbsolutePath) throws -> T {
|
||||||
let manifestPath = path.appending(component: manifest.fileName)
|
var fileNames = [manifest.fileName]
|
||||||
guard FileHandler.shared.exists(manifestPath) else {
|
if let deprecatedFileName = manifest.deprecatedFileName {
|
||||||
throw ManifestLoaderError.manifestNotFound(manifest, path)
|
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 {
|
private func loadManifestData(at path: AbsolutePath) throws -> Data {
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class SetupLoader: SetupLoading {
|
||||||
.printAndThrowIfNeeded()
|
.printAndThrowIfNeeded()
|
||||||
try setup.forEach { command in
|
try setup.forEach { command in
|
||||||
if try !command.isMet(projectPath: path) {
|
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)
|
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 files.isEmpty {
|
||||||
if FileHandler.shared.isFolder(path) {
|
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 {
|
} else {
|
||||||
// FIXME: This should be done in a linter.
|
// 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] {
|
func folderReferences(_ path: AbsolutePath) -> [AbsolutePath] {
|
||||||
guard FileHandler.shared.exists(path) else {
|
guard FileHandler.shared.exists(path) else {
|
||||||
// FIXME: This should be done in a linter.
|
// 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 []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
guard FileHandler.shared.isFolder(path) else {
|
guard FileHandler.shared.isFolder(path) else {
|
||||||
// FIXME: This should be done in a linter.
|
// 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 []
|
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