diff --git a/Package.swift b/Package.swift index 647491d..8be0904 100644 --- a/Package.swift +++ b/Package.swift @@ -16,6 +16,7 @@ let package = Package( ], products: [ .executable(name: "swift-package-list", targets: ["swift-package-list"]), + .plugin(name: "SwiftPackageListPlugin", targets: ["SwiftPackageListPlugin"]), .plugin(name: "SwiftPackageListJSONPlugin", targets: ["SwiftPackageListJSONPlugin"]), .plugin(name: "SwiftPackageListPropertyListPlugin", targets: ["SwiftPackageListPropertyListPlugin"]), .plugin(name: "SwiftPackageListSettingsBundlePlugin", targets: ["SwiftPackageListSettingsBundlePlugin"]), @@ -34,6 +35,11 @@ let package = Package( .product(name: "ArgumentParser", package: "swift-argument-parser"), ] ), + .plugin( + name: "SwiftPackageListPlugin", + capability: .buildTool(), + dependencies: [.target(name: "swift-package-list")] + ), .plugin( name: "SwiftPackageListJSONPlugin", capability: .buildTool(), diff --git a/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin+BuildToolPlugin.swift b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin+BuildToolPlugin.swift new file mode 100644 index 0000000..fdbb767 --- /dev/null +++ b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin+BuildToolPlugin.swift @@ -0,0 +1,28 @@ +// +// SwiftPackageListPlugin+BuildToolPlugin.swift +// SwiftPackageListPlugin +// +// Created by Felix Herrmann on 03.02.24. +// + +import PackagePlugin + +extension SwiftPackageListPlugin: BuildToolPlugin { + func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { + let executable = try context.tool(named: "swift-package-list").path + + let configurationPath = context.package.directory.appending(Configuration.fileName) + let configuration = try Configuration(path: configurationPath) + let targetConfiguration = configuration?.targets?[target.name] ?? configuration?.project + + let relativeProjectPath = configuration?.projectPath ?? "Package.swift" + let projectPath = context.package.directory.appending(relativeProjectPath) + + return try createBuildCommands( + executable: executable, + targetConfiguration: targetConfiguration, + projectPath: projectPath, + pluginWorkDirectory: context.pluginWorkDirectory + ) + } +} diff --git a/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin+XcodeBuildToolPlugin.swift b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin+XcodeBuildToolPlugin.swift new file mode 100644 index 0000000..d10b16d --- /dev/null +++ b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin+XcodeBuildToolPlugin.swift @@ -0,0 +1,31 @@ +// +// SwiftPackageListPlugin+XcodeBuildToolPlugin.swift +// SwiftPackageListPlugin +// +// Created by Felix Herrmann on 03.02.24. +// + +#if canImport(XcodeProjectPlugin) +import PackagePlugin +import XcodeProjectPlugin + +extension SwiftPackageListPlugin: XcodeBuildToolPlugin { + func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] { + let executable = try context.tool(named: "swift-package-list").path + + let configurationPath = context.xcodeProject.directory.appending(Configuration.fileName) + let configuration = try Configuration(path: configurationPath) + let targetConfiguration = configuration?.targets?[target.displayName] ?? configuration?.project + + let relativeProjectPath = configuration?.projectPath ?? "\(context.xcodeProject.displayName).xcodeproj" + let projectPath = context.xcodeProject.directory.appending(relativeProjectPath) + + return try createBuildCommands( + executable: executable, + targetConfiguration: targetConfiguration, + projectPath: projectPath, + pluginWorkDirectory: context.pluginWorkDirectory + ) + } +} +#endif diff --git a/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.Configuration.swift b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.Configuration.swift new file mode 100644 index 0000000..085deda --- /dev/null +++ b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.Configuration.swift @@ -0,0 +1,76 @@ +// +// SwiftPackageListPlugin.Configuration.swift +// SwiftPackageListPlugin +// +// Created by Felix Herrmann on 03.02.24. +// + +import Foundation +import PackagePlugin + +extension SwiftPackageListPlugin { + struct Configuration: Decodable { + let projectPath: String? + let project: TargetConfiguration? + let targets: [String: TargetConfiguration]? + } +} + +extension SwiftPackageListPlugin.Configuration { + struct TargetConfiguration: Decodable { + let outputType: OutputType? + let requiresLicense: Bool? + } +} + +extension SwiftPackageListPlugin.Configuration { + enum OutputType: String, Decodable { + case stdout + case json + case plist + case settingsBundle = "settings-bundle" + case pdf + } +} + +extension SwiftPackageListPlugin.Configuration.OutputType { + var fileName: String? { + switch self { + case .stdout: + return nil + case .json: + return "package-list.json" + case .plist: + return "package-list.plist" + case .settingsBundle: + return "Settings.bundle" + case .pdf: + return "Acknowledgements.pdf" + } + } +} + +extension SwiftPackageListPlugin.Configuration { + static let fileName = "swift-package-list-config.json" +} + +extension SwiftPackageListPlugin.Configuration { + init?(path: Path) throws { + guard FileManager.default.fileExists(atPath: path.string) else { return nil } + let url = URL(filePath: path.string) + + let data: Data + do { + data = try Data(contentsOf: url) + } catch { + throw SwiftPackageListPlugin.Error.configurationUnavailable(path: path, underlyingError: error) + } + + let decoder = JSONDecoder() + do { + self = try decoder.decode(SwiftPackageListPlugin.Configuration.self, from: data) + } catch { + throw SwiftPackageListPlugin.Error.configurationInvalid(path: path, underlyingError: error) + } + } +} diff --git a/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.Error.swift b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.Error.swift new file mode 100644 index 0000000..fbd4442 --- /dev/null +++ b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.Error.swift @@ -0,0 +1,29 @@ +// +// SwiftPackageListPlugin.Error.swift +// SwiftPackageListPlugin +// +// Created by Felix Herrmann on 03.02.24. +// + +import PackagePlugin + +extension SwiftPackageListPlugin { + enum Error: Swift.Error { + case configurationUnavailable(path: Path, underlyingError: Swift.Error) + case configurationInvalid(path: Path, underlyingError: Swift.Error) + case sourcePackagesNotFound(pluginWorkDirectory: Path) + } +} + +extension SwiftPackageListPlugin.Error: CustomDebugStringConvertible { + var debugDescription: String { + switch self { + case .configurationUnavailable(path: let path, underlyingError: let error): + return "The configuration at \(path.string) is unavailable: \(error)" + case .configurationInvalid(path: let path, underlyingError: let error): + return "The configuration at \(path.string) has an invalid format: \(error)" + case .sourcePackagesNotFound(pluginWorkDirectory: let directory): + return "SourcePackages directory not found in \(directory.string)" + } + } +} diff --git a/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.swift b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.swift new file mode 100644 index 0000000..9c5319a --- /dev/null +++ b/Plugins/SwiftPackageListPlugin/SwiftPackageListPlugin.swift @@ -0,0 +1,56 @@ +// +// SwiftPackageListPlugin.swift +// SwiftPackageListPlugin +// +// Created by Felix Herrmann on 03.02.24. +// + +import PackagePlugin + +@main +struct SwiftPackageListPlugin: Plugin { + func createBuildCommands( + executable: Path, + targetConfiguration: Configuration.TargetConfiguration?, + projectPath: Path, + pluginWorkDirectory: Path + ) throws -> [Command] { + let sourcePackagesPath = try sourcePackagesDirectory(pluginWorkDirectory: pluginWorkDirectory) + let outputType = targetConfiguration?.outputType ?? .json + let outputPath = pluginWorkDirectory + let requiresLicense = targetConfiguration?.requiresLicense ?? true + + let outputFiles: [Path] + if let fileName = outputType.fileName { + outputFiles = [outputPath.appending(fileName)] + } else { + outputFiles = [] + } + + return [ + .buildCommand( + displayName: "SwiftPackageListPlugin", + executable: executable, + arguments: [ + projectPath, + "--custom-source-packages-path", sourcePackagesPath, + "--output-type", outputType.rawValue, + "--output-path", outputPath, + requiresLicense ? "--requires-license" : "", + ], + outputFiles: outputFiles + ) + ] + } + + private func sourcePackagesDirectory(pluginWorkDirectory: Path) throws -> Path { + var path = pluginWorkDirectory + while path.lastComponent != "SourcePackages" { + guard path.string != "/" else { + throw SwiftPackageListPlugin.Error.sourcePackagesNotFound(pluginWorkDirectory: pluginWorkDirectory) + } + path = path.removingLastComponent() + } + return path + } +}