diff --git a/Plugins/PackageGenerator/PackageGenerator.swift b/Plugins/PackageGenerator/PackageGenerator.swift index 4fab5a3..4243ea2 100644 --- a/Plugins/PackageGenerator/PackageGenerator.swift +++ b/Plugins/PackageGenerator/PackageGenerator.swift @@ -93,22 +93,38 @@ struct PackageGenerator { print("\(parsedPackages.count) packages found") // Clean packages - parsedPackages = parsedPackages.map { parsedPackage in - var parsedPackage = parsedPackage - var localDependencies = parsedPackage.dependencies + var cleanedPackages: [ParsedPackage] = [] + for parsedPackage in parsedPackages { + var processedPackage = parsedPackage + var localDependencies = processedPackage.dependencies localDependencies.removeAll(where: config.exclusions.apple.contains(_:)) localDependencies.removeAll(where: config.exclusions.imports.contains(_:)) localDependencies.sort(by: <) - parsedPackage.dependencies = localDependencies + processedPackage.dependencies = localDependencies - parsedPackage.name = config.mappers.targets[parsedPackage.path, default: parsedPackage.name] - if config.exclusions.targets.contains(parsedPackage.name) == false { - parsedPackages.append(parsedPackage) + processedPackage.name = config.mappers.targets[processedPackage.path, default: processedPackage.name] + + // Apply target type overrides from configuration + if let targetTypes = config.targetTypes, + let targetTypeString = targetTypes[processedPackage.name], + let targetType = TargetType(rawValue: targetTypeString) { + processedPackage.targetType = targetType + } else if !processedPackage.isTest { + // Auto-detect executable targets by checking for main.swift file + let sourcePath = packageDirectory.appendingPathComponent(processedPackage.path) + let mainSwiftPath = sourcePath.appendingPathComponent("main.swift") + if FileManager.default.fileExists(atPath: mainSwiftPath.path) { + processedPackage.targetType = .executableTarget + } + } + + if config.exclusions.targets.contains(processedPackage.name) == false { + cleanedPackages.append(processedPackage) } else { - Diagnostics.emit(.warning, "❌ Rejecting: \(parsedPackage)") + Diagnostics.emit(.warning, "❌ Rejecting: \(processedPackage)") } - return parsedPackage } + parsedPackages = cleanedPackages // UpdateIsLeaf if config.leafInfo == true { @@ -120,6 +136,26 @@ struct PackageGenerator { updateIsUnsused(config, parsedPackages) } + if let targetTypes = config.targetTypes?.keys, targetTypes.isEmpty == false { + let tTypes = Set(targetTypes) + let parsedPackagesNames = parsedPackages.map(\.name) + let toRemove = tTypes.subtracting(parsedPackagesNames) + if toRemove.isEmpty == false { + for targetToRemove in toRemove { + Diagnostics.warning("🗑️ Please consider removing \"\(targetToRemove)\" from targetTypes configuration because this target is not found") + } + } + + // Validate target type values + if let targetTypeConfigs = config.targetTypes { + for (targetName, targetTypeString) in targetTypeConfigs { + if TargetType(rawValue: targetTypeString) == nil { + Diagnostics.warning("⚠️ Invalid target type '\(targetTypeString)' for target '\(targetName)'. Valid types are: target, testTarget, executableTarget, systemLibrary, binaryTarget") + } + } + } + } + if let targetNames = config.targetsParameters?.keys, targetNames.isEmpty == false { let tNames = Set(targetNames) let parsedPackagesNames = parsedPackages.map(\.name) @@ -338,13 +374,24 @@ struct PackageGenerator { var last: String = "" for parsedPackage in parsedPackages.sorted(by: \.name, order: <) { - if parsedPackage.isTest { continue } + // Skip test targets, system libraries, and binary targets for products + if parsedPackage.isTest || parsedPackage.targetType == .systemLibrary || parsedPackage.targetType == .binaryTarget { continue } if last.isEmpty == false { outputFileHandle.write("\(last),\n".data(using: .utf8)!) } let name = configuration.mappers.targets[parsedPackage.path, default: parsedPackage.name] let spaces = String(repeating: " ", count: configuration.spaces) - last = "\(spaces).library(name: \"" + name + "\", targets: [\"" + name + "\"])" + + // Generate appropriate product type + let productType: String + switch parsedPackage.targetType { + case .executableTarget: + productType = "executable" + default: + productType = "library" + } + + last = "\(spaces).\(productType)(name: \"" + name + "\", targets: [\"" + name + "\"])" } outputFileHandle.write("\(last)\n])\n".data(using: .utf8)!) } @@ -391,8 +438,23 @@ struct PackageGenerator { var isLeaf = "// [\(fakeTarget.dependencies.count)|\(fakeTarget.localDependencies)" + (fakeTarget.hasBiggestNumberOfDependencies ? "|🚛]" : "]") if configuration.leafInfo != true { isLeaf = "" } + // Determine the target type to generate + let targetTypeString: String + switch fakeTarget.targetType { + case .target: + targetTypeString = "target" + case .testTarget: + targetTypeString = "testTarget" + case .executableTarget: + targetTypeString = "executableTarget" + case .systemLibrary: + targetTypeString = "systemLibrary" + case .binaryTarget: + targetTypeString = "binaryTarget" + } + return """ - \(spaces).\(fakeTarget.isTest ? "testTarget" : "target")( + \(spaces).\(targetTypeString)( \(spaces)\(spaces)name: "\(name)",\(isLeaf)\(dependencies) \(spaces)\(spaces)path: "\(fakeTarget.path)"\(otherParameters) \(spaces)) diff --git a/Plugins/PackageGenerator/PackageGeneratorConfiguration.swift b/Plugins/PackageGenerator/PackageGeneratorConfiguration.swift index 4b6c644..f121923 100644 --- a/Plugins/PackageGenerator/PackageGeneratorConfiguration.swift +++ b/Plugins/PackageGenerator/PackageGeneratorConfiguration.swift @@ -16,6 +16,7 @@ struct PackageGeneratorConfiguration: Codable { var headerFileURL: String? var packageDirectories: [PackageInformation] var targetsParameters: [String: [String]]? + var targetTypes: [String: String]? var spaces: Int var unusedThreshold: Int? var pragmaMark: Bool @@ -34,6 +35,7 @@ struct PackageGeneratorConfiguration: Codable { unusedThreshold: Int? = nil, pragmaMark: Bool = false, targetsParameters: [String: [String]]? = nil, + targetTypes: [String: String]? = nil, generateExportedFiles: Bool = false ) { self.mappers = mappers @@ -48,6 +50,7 @@ struct PackageGeneratorConfiguration: Codable { self.unusedThreshold = unusedThreshold self.pragmaMark = pragmaMark self.targetsParameters = targetsParameters + self.targetTypes = targetTypes self.generateExportedFiles = generateExportedFiles } diff --git a/Plugins/PackageGenerator/ParsedPackage.swift b/Plugins/PackageGenerator/ParsedPackage.swift index d6217b9..ad5a628 100644 --- a/Plugins/PackageGenerator/ParsedPackage.swift +++ b/Plugins/PackageGenerator/ParsedPackage.swift @@ -1,8 +1,17 @@ import Foundation +public enum TargetType: String, Codable { + case target + case testTarget + case executableTarget + case systemLibrary + case binaryTarget +} + public struct ParsedPackage: Codable, CustomStringConvertible { public var name: String public var isTest: Bool + public var targetType: TargetType public var dependencies: [String] public var path: String public var fullPath: String @@ -15,12 +24,18 @@ public struct ParsedPackage: Codable, CustomStringConvertible { } public var description: String { - return "[\(dependencies.count)|\(localDependencies)] \(name) \(hasResources == false ? "" : "/ hasResources")" + return "[\(dependencies.count)|\(localDependencies)] \(name) (\(targetType.rawValue)) \(hasResources == false ? "" : "/ hasResources")" } - public init(name: String, isTest: Bool, dependencies: [String], path: String, fullPath: String, resources: String? = nil, localDependencies: Int = 0, hasBiggestNumberOfDependencies: Bool = false) { + public init(name: String, isTest: Bool, targetType: TargetType? = nil, dependencies: [String], path: String, fullPath: String, resources: String? = nil, localDependencies: Int = 0, hasBiggestNumberOfDependencies: Bool = false) { self.name = name self.isTest = isTest + // Auto-determine target type based on isTest if not explicitly provided + if let targetType = targetType { + self.targetType = targetType + } else { + self.targetType = isTest ? .testTarget : .target + } self.dependencies = dependencies self.path = path self.fullPath = fullPath diff --git a/README.md b/README.md index 864fd6b..9262c7d 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ This file contains these keys: - `exclusions.imports`: An array of string that represents all other SDK that should not be added as dependencies to a target - `exclusions.targets`: An array of string that represent all targets that should not be added in the generated `Package.swift` - `targetsParameters`: An dictionary that represent what custom parameter to add to a target +- `targetTypes`: A dictionary that specifies custom target types for specific targets. Valid values are: `target`, `testTarget`, `executableTarget`, `systemLibrary`, `binaryTarget` - `generateExportedFiles`: A bool that represents if the generator should create `exported.swift` files in each package with `@_exported import` statements for local dependencies ```json @@ -77,6 +78,11 @@ This file contains these keys: "Analytics": ["exclude: [\"__Snapshots__\"]", "resources: [.copy(\"Fonts/\")]"], "target2": ["resources: [.copy(\"Dictionaries/\")]"] }, + "targetTypes": { + "MyApp": "executableTarget", + "SystemWrapper": "systemLibrary", + "PrecompiledFramework": "binaryTarget" + }, "verbose": false, "pragmaMark": false, "spaces": 2,