From 51290a59d80436a00bf5f820da9bd33654c6ed15 Mon Sep 17 00:00:00 2001 From: Jimmy Arts Date: Fri, 28 Jul 2023 22:39:03 +0200 Subject: [PATCH] Add SPM config support (#1184) * Work on adding SPM support in configuration files * Change configuration setup, setup watch paths * Fix custom path in Package and relate excluded to right directory * Update docs and add CHANGELOG.md entry --- CHANGELOG.md | 4 ++ Package.resolved | 71 +++++++++++++++++++++++++++ Package.swift | 8 +-- Sourcery/Configuration.swift | 66 ++++++++++++++++++++++++- Sourcery/Sourcery.swift | 12 ++++- SourceryTests/ConfigurationSpec.swift | 2 +- guides/Usage.md | 16 ++++++ 7 files changed, 172 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08cff2472..eef07fa86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Sourcery CHANGELOG +## 2.1.0 +## Changes +- Added support for Swift Package Manager config ([#1184](https://github.com/krzysztofzablocki/Sourcery/pull/1184)) + ## 2.0.3 ## Internal Changes - Modifications to included files of Swift Templates are now detected by hashing instead of using the modification date when invalidating the cache ([#1161](https://github.com/krzysztofzablocki/Sourcery/pull/1161)) diff --git a/Package.resolved b/Package.resolved index 80478c944..37ac61114 100644 --- a/Package.resolved +++ b/Package.resolved @@ -117,6 +117,59 @@ "version" : "2.10.1" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "e394bf350e38cb100b6bc4172834770ede1b7232", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", + "version" : "1.0.4" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "75ec60b8b4cc0f085c3ac414f3dca5625fa3588e", + "version" : "2.2.4" + } + }, + { + "identity" : "swift-driver", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-driver.git", + "state" : { + "branch" : "release/5.8", + "revision" : "7cfe0c0b6e6297efe88a3ce34e6138ee7eda969e" + } + }, + { + "identity" : "swift-llbuild", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-llbuild.git", + "state" : { + "branch" : "release/5.8", + "revision" : "168f9dc3798def1ecdd7d40049f6e1841bf761d0" + } + }, + { + "identity" : "swift-package-manager", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-package-manager", + "state" : { + "revision" : "fa3db13e0bd00e33c187c63c80673b3ac7c82f55" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", @@ -126,6 +179,24 @@ "version" : "508.0.0" } }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "836bc4557b74fe6d2660218d56e3ce96aff76574", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-tools-support-core", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-tools-support-core.git", + "state" : { + "branch" : "release/5.8", + "revision" : "ac4871e01ef338cb95b5d28328cab0ec1dfae935" + } + }, { "identity" : "xcodeproj", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 6739e016d..b21c2955e 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import Foundation let package = Package( name: "Sourcery", platforms: [ - .macOS(.v10_15), + .macOS(.v12), ], products: [ // SPM won't generate .swiftmodule for a target directly used by a product, @@ -29,7 +29,8 @@ let package = Package( .package(url: "https://github.com/tuist/XcodeProj.git", exact: "8.3.1"), .package(url: "https://github.com/apple/swift-syntax.git", from: "508.0.0"), .package(url: "https://github.com/Quick/Quick.git", from: "3.0.0"), - .package(url: "https://github.com/Quick/Nimble.git", from: "9.0.0") + .package(url: "https://github.com/Quick/Nimble.git", from: "9.0.0"), + .package(url: "https://github.com/apple/swift-package-manager", revision: "fa3db13e0bd00e33c187c63c80673b3ac7c82f55"), ], targets: [ .executableTarget( @@ -55,7 +56,8 @@ let package = Package( "StencilSwiftKit", .product(name: "SwiftSyntax", package: "swift-syntax"), "XcodeProj", - "TryCatch" + "TryCatch", + .product(name: "SwiftPM-auto", package: "swift-package-manager"), ], path: "Sourcery", exclude: [ diff --git a/Sourcery/Configuration.swift b/Sourcery/Configuration.swift index 9c0b3b9e6..efbe10106 100644 --- a/Sourcery/Configuration.swift +++ b/Sourcery/Configuration.swift @@ -4,6 +4,10 @@ import PathKit import Yams import SourceryRuntime import QuartzCore +import Basics +import TSCBasic +import Workspace +import PackageModel public struct Project { public let file: XcodeProj @@ -150,9 +154,64 @@ extension Path { } } +public struct Package { + public let root: Path + public let targets: [Target] + + public struct Target { + let name: String + let root: Path + let excludes: [Path] + } + + public init(dict: [String: Any], relativePath: Path) throws { + guard let packageRootPath = dict["path"] as? String else { + throw Configuration.Error.invalidSources(message: "Package file directory path is not provided. Expected string.") + } + let path = Path(packageRootPath, relativeTo: relativePath) + + let packagePath = try AbsolutePath(validating: path.string) + let observability = ObservabilitySystem { Log.verbose("\($0): \($1)") } + let workspace = try Workspace(forRootPackage: packagePath) + + var manifestResult: Result? + let semaphore = DispatchSemaphore(value: 0) + workspace.loadRootManifest(at: packagePath, observabilityScope: observability.topScope, completion: { result in + manifestResult = result + semaphore.signal() + }) + semaphore.wait() + + guard let manifest = try manifestResult?.get() else{ + throw Configuration.Error.invalidSources(message: "Unable to load manifest") + } + self.root = path + let targetNames: [String] + if let targets = dict["target"] as? [String] { + targetNames = targets + } else if let target = dict["target"] as? String { + targetNames = [target] + } else { + throw Configuration.Error.invalidSources(message: "'target' key is missing. Expected object or array of objects.") + } + let sourcesPath = Path("Sources", relativeTo: path) + self.targets = manifest.targets.compactMap({ target in + guard targetNames.contains(target.name) else { + return nil + } + let rootPath = target.path.map { Path($0, relativeTo: path) } ?? Path(target.name, relativeTo: sourcesPath) + let excludePaths = target.exclude.map { path in + Path(path, relativeTo: rootPath) + } + return Target(name: target.name, root: rootPath, excludes: excludePaths) + }) + } +} + public enum Source { case projects([Project]) case sources(Paths) + case packages([Package]) public init(dict: [String: Any], relativePath: Path) throws { if let projects = (dict["project"] as? [[String: Any]]) ?? (dict["project"] as? [String: Any]).map({ [$0] }) { @@ -164,8 +223,11 @@ public enum Source { } catch { throw Configuration.Error.invalidSources(message: "\(error)") } + } else if let packages = (dict["package"] as? [[String: Any]]) ?? (dict["package"] as? [String: Any]).map({ [$0] }) { + guard !packages.isEmpty else { throw Configuration.Error.invalidSources(message: "No packages provided.") } + self = try .packages(packages.map({ try Package(dict: $0, relativePath: relativePath) })) } else { - throw Configuration.Error.invalidSources(message: "'sources' or 'project' key are missing.") + throw Configuration.Error.invalidSources(message: "'sources', 'project' or 'package' key are missing.") } } @@ -175,6 +237,8 @@ public enum Source { return paths.allPaths.isEmpty case let .projects(projects): return projects.isEmpty + case let .packages(packages): + return packages.isEmpty } } } diff --git a/Sourcery/Sourcery.swift b/Sourcery/Sourcery.swift index a1a68bc44..1632ae640 100644 --- a/Sourcery/Sourcery.swift +++ b/Sourcery/Sourcery.swift @@ -77,8 +77,11 @@ public class Sourcery { case let .sources(paths): watchPaths = paths case let .projects(projects): - watchPaths = Paths(include: projects.map({ $0.root }), - exclude: projects.flatMap({ $0.exclude })) + watchPaths = Paths(include: projects.map(\.root), + exclude: projects.flatMap(\.exclude)) + case let .packages(packages): + watchPaths = Paths(include: packages.flatMap({ $0.targets.map(\.root) }), + exclude: packages.flatMap({ $0.targets.flatMap(\.excludes) })) } let process: (Source) throws -> ParsingResult = { source in @@ -106,6 +109,11 @@ public class Sourcery { } } result = try self.parse(from: paths, forceParse: forceParse, parseDocumentation: parseDocumentation, modules: modules, requiresFileParserCopy: hasSwiftTemplates) + case let .packages(packages): + let paths: [Path] = packages.flatMap({ $0.targets.map(\.root) }) + let excludePaths: [Path] = packages.flatMap({ $0.targets.flatMap(\.excludes) }) + let modules = packages.flatMap({ $0.targets.map(\.name) }) + result = try self.parse(from: paths, exclude: excludePaths, forceParse: forceParse, parseDocumentation: parseDocumentation, modules: modules, requiresFileParserCopy: hasSwiftTemplates) } try self.generate(source: source, templatePaths: templatesPaths, output: output, parsingResult: &result, forceParse: forceParse, baseIndentation: baseIndentation) diff --git a/SourceryTests/ConfigurationSpec.swift b/SourceryTests/ConfigurationSpec.swift index e8114c3b0..0d1b8e5ab 100644 --- a/SourceryTests/ConfigurationSpec.swift +++ b/SourceryTests/ConfigurationSpec.swift @@ -117,7 +117,7 @@ class ConfigurationSpec: QuickSpec { it("throws error on missing sources") { let config: [String: Any] = ["templates": ["."], "output": "."] - expect(configError(config)).to(equal("Invalid sources. 'sources' or 'project' key are missing.")) + expect(configError(config)).to(equal("Invalid sources. 'sources', 'project' or 'package' key are missing.")) } it("throws error on invalid sources format") { diff --git a/guides/Usage.md b/guides/Usage.md index 66e01ad7c..473890546 100644 --- a/guides/Usage.md +++ b/guides/Usage.md @@ -100,6 +100,22 @@ project: - ``` +You can also provide a Swift Package which will be scanned. Source files will be scanned based on the package's `path` and `exclude` options. + +```yaml +package: + path: + target: +``` +Multiple targets: +```yaml +package: + path: + target: + - + - +``` + #### Excluding sources or templates You can specify paths to sources files that should be scanned using `include` key and paths that should be excluded using `exclude` key. These can be directory or file paths.