diff --git a/.github/workflows/XcodeGraph.yml b/.github/workflows/XcodeGraph.yml index be439d79..4d2070db 100644 --- a/.github/workflows/XcodeGraph.yml +++ b/.github/workflows/XcodeGraph.yml @@ -2,16 +2,16 @@ name: XcodeGraph on: push: branches: - - '**' + - "**" tags-ignore: - - '**' + - "**" paths: - - '**/*.swift' - - '.github/workflows/*.yml' + - "**/*.swift" + - ".github/workflows/*.yml" pull_request: paths: - - '**/*.swift' - - '.github/workflows/*.yml' + - "**/*.swift" + - ".github/workflows/*.yml" concurrency: group: XcodeGraph-${{ github.workflow }}-${{ github.ref }} @@ -19,7 +19,7 @@ concurrency: env: MISE_EXPERIMENTAL: 1 - TUIST_CONFIG_CLOUD_TOKEN: ${{ secrets.TUIST_CONFIG_CLOUD_TOKEN }} + TUIST_CONFIG_TOKEN: ${{ secrets.TUIST_CONFIG_CLOUD_TOKEN }} jobs: spm_build: @@ -28,45 +28,45 @@ jobs: matrix: os: - ubuntu-22.04 - - macOS-13 + - macos-14 swift-version: - - '5.9' + - "5.9" swift-compat-ver: - - '5' + - "5" runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: SwiftyLab/setup-swift@latest - with: - swift-version: ${{ matrix.swift-version }} - # DEBUG mode - - name: Build with debug mode. - id: debug_build - run: swift build --configuration debug -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} - continue-on-error: true - - name: Retry build with debug mode if necessary - if: steps.debug_build.outcome == 'failure' - run: | - swift build --configuration debug -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} - # RELEASE mode - - name: Build with release mode. - id: release_build - run: swift build --configuration release -Xswiftc -enable-testing -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} - continue-on-error: true - - name: Retry build with release mode if necessary - if: steps.release_build.outcome == 'failure' - run: | - swift build --configuration release -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} + - uses: actions/checkout@v4 + - uses: SwiftyLab/setup-swift@latest + with: + swift-version: ${{ matrix.swift-version }} + # DEBUG mode + - name: Build with debug mode. + id: debug_build + run: swift build --configuration debug -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} + continue-on-error: true + - name: Retry build with debug mode if necessary + if: steps.debug_build.outcome == 'failure' + run: | + swift build --configuration debug -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} + # RELEASE mode + - name: Build with release mode. + id: release_build + run: swift build --configuration release -Xswiftc -enable-testing -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} + continue-on-error: true + - name: Retry build with release mode if necessary + if: steps.release_build.outcome == 'failure' + run: | + swift build --configuration release -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} tuist_build: name: Tuist Build strategy: matrix: os: - - macOS-13 + - macos-14 swift-version: - - '5.9' + - "5.9" swift-compat-ver: - - '5' + - "5" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -74,6 +74,8 @@ jobs: with: swift-version: ${{ matrix.swift-version }} - uses: jdx/mise-action@v2 + with: + version: 2024.11.8 - name: Install dependencies run: tuist install - name: Build @@ -83,11 +85,11 @@ jobs: strategy: matrix: os: - - macOS-13 + - macos-14 swift-version: - - '5.9' + - "5.9" swift-compat-ver: - - '5' + - "5" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -95,5 +97,7 @@ jobs: with: swift-version: ${{ matrix.swift-version }} - uses: jdx/mise-action@v2 + with: + version: 2024.11.8 - name: Lint - run: mise run lint \ No newline at end of file + run: mise run lint diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml index 6117f145..ded627e3 100644 --- a/.github/workflows/cache.yml +++ b/.github/workflows/cache.yml @@ -4,12 +4,12 @@ on: branches: - main paths: - - '**/*.swift' - - '.github/workflows/*.yml' + - "**/*.swift" + - ".github/workflows/*.yml" pull_request: paths: - - '**/*.swift' - - '.github/workflows/*.yml' + - "**/*.swift" + - ".github/workflows/*.yml" concurrency: group: Cache-${{ github.workflow }}-${{ github.ref }} @@ -17,19 +17,19 @@ concurrency: env: MISE_EXPERIMENTAL: 1 - TUIST_CONFIG_CLOUD_TOKEN: ${{ secrets.TUIST_CONFIG_CLOUD_TOKEN }} - + TUIST_CONFIG_TOKEN: ${{ secrets.TUIST_CONFIG_CLOUD_TOKEN }} + jobs: warm: name: Warm strategy: matrix: os: - - macOS-13 + - macOS-14 swift-version: - - '5.9' + - "5.9" swift-compat-ver: - - '5' + - "5" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -37,7 +37,9 @@ jobs: with: swift-version: ${{ matrix.swift-version }} - uses: jdx/mise-action@v2 + with: + version: 2024.11.8 - name: Install dependencies run: tuist install - name: Build - run: tuist cache \ No newline at end of file + run: tuist cache diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e3a9ec0..baa09515 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,7 @@ jobs: - uses: jdx/mise-action@v2 with: experimental: true + version: 2024.11.8 - name: Check if there are releasable changes id: is-releasable run: | diff --git a/.mise.toml b/.mise.toml index 1efb1547..a00fc97f 100644 --- a/.mise.toml +++ b/.mise.toml @@ -2,4 +2,4 @@ tuist = "4.34.0" swiftlint = "0.54.0" swiftformat = "0.53.3" -"git-cliff" = "2.4.0" \ No newline at end of file +"git-cliff" = "2.6.1" diff --git a/Sources/XcodeGraph/Graph/GraphTarget.swift b/Sources/XcodeGraph/Graph/GraphTarget.swift index 71d25639..78f81765 100644 --- a/Sources/XcodeGraph/Graph/GraphTarget.swift +++ b/Sources/XcodeGraph/Graph/GraphTarget.swift @@ -37,7 +37,7 @@ public struct GraphTarget: Equatable, Hashable, Comparable, CustomDebugStringCon public static func test( path: AbsolutePath = .root, target: Target = .test(), - project: Project = .test() + project: Project = .test(type: .local) ) -> GraphTarget { GraphTarget( path: path, diff --git a/Sources/XcodeGraph/Models/Project.swift b/Sources/XcodeGraph/Models/Project.swift index 9ee70f60..fa00ffd9 100644 --- a/Sources/XcodeGraph/Models/Project.swift +++ b/Sources/XcodeGraph/Models/Project.swift @@ -1,6 +1,22 @@ import Foundation import Path +public enum ProjectType: Hashable, Equatable, Codable, CustomStringConvertible, Sendable { + /// A project is a local project managed by the user. + case local + /// A project is external (e.g. represents a Swift Package project). In those cases, + /// a hash can be provided, from example from the resolved ref of the represented package, + /// to skip the file-system-based hashing. + case external(hash: String? = nil) + + public var description: String { + switch self { + case .local: "local project" + case let .external(hash): "external project" + } + } +} + public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible, Codable, Sendable { // MARK: - Attributes @@ -59,8 +75,12 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug public var lastUpgradeCheck: Version? /// Indicates whether the project is imported through `Package.swift`. + @available(*, deprecated, renamed: "type", message: "Use project type instead") public var isExternal: Bool + /// It represents the type of project. + public var type: ProjectType? + // MARK: - Init /// Initializes the project with its attributes. @@ -85,6 +105,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug /// - resourceSynthesizers: `ResourceSynthesizers` that will be applied on individual target's resources /// - lastUpgradeCheck: The version in which a check happened related to recommended settings after updating Xcode. /// - isExternal: Indicates whether the project is imported through `Package.swift`. + @available(*, deprecated, message: "Use the constructor that takes type: insead of isExternal:") public init( path: AbsolutePath, sourceRootPath: AbsolutePath, @@ -127,6 +148,74 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug self.isExternal = isExternal } + /// Initializes the project with its attributes. + /// + /// - Parameters: + /// - path: Path to the folder that contains the project manifest. + /// - sourceRootPath: Path to the directory where the Xcode project will be generated. + /// - xcodeProjPath: Path to the Xcode project that will be generated. + /// - name: Project name. + /// - organizationName: Organization name. + /// - defaultKnownRegions: Default known regions. + /// - developmentRegion: Development region. + /// - options: Additional project options. + /// - settings: The settings to apply at the project level + /// - filesGroup: The root group to place project files within + /// - targets: The project targets + /// *(Those won't be included in any build phases)* + /// - packages: Project swift packages. + /// - schemes: Project schemes. + /// - ideTemplateMacros: IDE template macros that represent content of IDETemplateMacros.plist. + /// - additionalFiles: The additional files to include in the project + /// - resourceSynthesizers: `ResourceSynthesizers` that will be applied on individual target's resources + /// - lastUpgradeCheck: The version in which a check happened related to recommended settings after updating Xcode. + /// - type: The type of project, either local or external. This attribute supersedes `isExternal` + public init( + path: AbsolutePath, + sourceRootPath: AbsolutePath, + xcodeProjPath: AbsolutePath, + name: String, + organizationName: String?, + classPrefix: String?, + defaultKnownRegions: [String]?, + developmentRegion: String?, + options: Options, + settings: Settings, + filesGroup: ProjectGroup, + targets: [Target], + packages: [Package], + schemes: [Scheme], + ideTemplateMacros: IDETemplateMacros?, + additionalFiles: [FileElement], + resourceSynthesizers: [ResourceSynthesizer], + lastUpgradeCheck: Version?, + type: ProjectType + ) { + self.path = path + self.sourceRootPath = sourceRootPath + self.xcodeProjPath = xcodeProjPath + self.name = name + self.organizationName = organizationName + self.classPrefix = classPrefix + self.defaultKnownRegions = defaultKnownRegions + self.developmentRegion = developmentRegion + self.options = options + self.targets = Dictionary(uniqueKeysWithValues: targets.map { ($0.name, $0) }) + self.packages = packages + self.schemes = schemes + self.settings = settings + self.filesGroup = filesGroup + self.ideTemplateMacros = ideTemplateMacros + self.additionalFiles = additionalFiles + self.resourceSynthesizers = resourceSynthesizers + self.lastUpgradeCheck = lastUpgradeCheck + isExternal = switch type { + case .external: true + case .local: false + } + self.type = type + } + // MARK: - CustomStringConvertible public var description: String { @@ -157,6 +246,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug #if DEBUG extension Project { + @available(*, deprecated, message: "Use test that takes a project type instead of isExternal attribute.") public static func test( path: AbsolutePath = try! AbsolutePath(validating: "/Project"), // swiftlint:disable:this force_try sourceRootPath: AbsolutePath = try! AbsolutePath(validating: "/Project"), // swiftlint:disable:this force_try @@ -202,6 +292,52 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug ) } + public static func test( + path: AbsolutePath = try! AbsolutePath(validating: "/Project"), // swiftlint:disable:this force_try + sourceRootPath: AbsolutePath = try! AbsolutePath(validating: "/Project"), // swiftlint:disable:this force_try + // swiftlint:disable:next force_try + xcodeProjPath: AbsolutePath = try! AbsolutePath(validating: "/Project/Project.xcodeproj"), + name: String = "Project", + organizationName: String? = nil, + classPrefix: String? = nil, + defaultKnownRegions: [String]? = nil, + developmentRegion: String? = nil, + options: Options = .test(automaticSchemesOptions: .disabled), + settings: Settings = Settings.test(), + filesGroup: ProjectGroup = .group(name: "Project"), + targets: [Target] = [Target.test()], + packages: [Package] = [], + schemes: [Scheme] = [], + ideTemplateMacros: IDETemplateMacros? = nil, + additionalFiles: [FileElement] = [], + resourceSynthesizers: [ResourceSynthesizer] = [], + lastUpgradeCheck: Version? = nil, + type: ProjectType + ) -> Project { + Project( + path: path, + sourceRootPath: sourceRootPath, + xcodeProjPath: xcodeProjPath, + name: name, + organizationName: organizationName, + classPrefix: classPrefix, + defaultKnownRegions: defaultKnownRegions, + developmentRegion: developmentRegion, + options: options, + settings: settings, + filesGroup: filesGroup, + targets: targets, + packages: packages, + schemes: schemes, + ideTemplateMacros: ideTemplateMacros, + additionalFiles: additionalFiles, + resourceSynthesizers: resourceSynthesizers, + lastUpgradeCheck: lastUpgradeCheck, + type: type + ) + } + + @available(*, deprecated, message: "Use empty that takes a project type instead of isExternal attribute.") public static func empty( path: AbsolutePath = try! AbsolutePath(validating: "/test/"), // swiftlint:disable:this force_try sourceRootPath: AbsolutePath = try! AbsolutePath(validating: "/test/"), // swiftlint:disable:this force_try @@ -246,6 +382,51 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug isExternal: isExternal ) } + + public static func empty( + path: AbsolutePath = try! AbsolutePath(validating: "/test/"), // swiftlint:disable:this force_try + sourceRootPath: AbsolutePath = try! AbsolutePath(validating: "/test/"), // swiftlint:disable:this force_try + // swiftlint:disable:next force_try + xcodeProjPath: AbsolutePath = try! AbsolutePath(validating: "/test/text.xcodeproj"), + name: String = "Project", + organizationName: String? = nil, + classPrefix: String? = nil, + defaultKnownRegions: [String]? = nil, + developmentRegion: String? = nil, + options: Options = .test(automaticSchemesOptions: .disabled), + settings: Settings = .default, + filesGroup: ProjectGroup = .group(name: "Project"), + targets: [Target] = [], + packages: [Package] = [], + schemes: [Scheme] = [], + ideTemplateMacros: IDETemplateMacros? = nil, + additionalFiles: [FileElement] = [], + resourceSynthesizers: [ResourceSynthesizer] = [], + lastUpgradeCheck: Version? = nil, + type: ProjectType + ) -> Project { + Project( + path: path, + sourceRootPath: sourceRootPath, + xcodeProjPath: xcodeProjPath, + name: name, + organizationName: organizationName, + classPrefix: classPrefix, + defaultKnownRegions: defaultKnownRegions, + developmentRegion: developmentRegion, + options: options, + settings: settings, + filesGroup: filesGroup, + targets: targets, + packages: packages, + schemes: schemes, + ideTemplateMacros: ideTemplateMacros, + additionalFiles: additionalFiles, + resourceSynthesizers: resourceSynthesizers, + lastUpgradeCheck: lastUpgradeCheck, + type: type + ) + } } extension Project.Options {