Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 74 additions & 12 deletions Plugins/PackageGenerator/PackageGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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)!)
}
Expand Down Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions Plugins/PackageGenerator/PackageGeneratorConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -48,6 +50,7 @@ struct PackageGeneratorConfiguration: Codable {
self.unusedThreshold = unusedThreshold
self.pragmaMark = pragmaMark
self.targetsParameters = targetsParameters
self.targetTypes = targetTypes
self.generateExportedFiles = generateExportedFiles
}

Expand Down
19 changes: 17 additions & 2 deletions Plugins/PackageGenerator/ParsedPackage.swift
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down