1
0
Fork 0

Preserve user-defined test targets across Package.swift regeneration

This commit is contained in:
Brendan Szymanski 2026-07-21 20:08:39 -04:00
parent f305df0eff
commit b13f7976cc
4 changed files with 114 additions and 2 deletions

View file

@ -256,7 +256,12 @@ extension CodeGenerator {
\(products) ],
targets: [
\(cTargets)
\(swiftTargets)\(smokeTarget) ]
\(swiftTargets)\(smokeTarget)
// === USER TARGETS preserved across generations ===
// BEGIN_USER_TARGETS
// END_USER_TARGETS
]
)
"""

View file

@ -24,4 +24,39 @@ public struct CodeGenerator {
}
return files
}
// MARK: - User target preservation
/// Preserves user-defined targets between `// BEGIN_USER_TARGETS` and
/// `// END_USER_TARGETS` markers when regenerating Package.swift.
///
/// If the existing file has content between the markers, it is spliced
/// into the freshly generated output. If either file lacks the markers,
/// the generated output is returned unchanged.
///
/// - Parameters:
/// - generated: The freshly generated Package.swift content.
/// - existing: The existing Package.swift on disk (if any).
/// - Returns: The generated content with user targets spliced in.
public static func preserveUserTargets(generated: String, existing: String) -> String {
let beginMarker = "// BEGIN_USER_TARGETS"
let endMarker = "// END_USER_TARGETS"
guard let genStart = generated.range(of: beginMarker),
let genEnd = generated.range(of: endMarker, range: genStart.upperBound..<generated.endIndex) else {
return generated
}
// Default to keeping the generated gap (blank line and indent)
var userContent = String(generated[genStart.upperBound..<genEnd.lowerBound])
if let exStart = existing.range(of: beginMarker),
let exEnd = existing.range(of: endMarker, range: exStart.upperBound..<existing.endIndex) {
userContent = String(existing[exStart.upperBound..<exEnd.lowerBound])
}
var result = generated
result.replaceSubrange(genStart.upperBound..<genEnd.lowerBound, with: userContent)
return result
}
}

View file

@ -78,8 +78,16 @@ struct GeneratorCLI {
}
// Write scaffolding (Package.swift, C module maps, umbrella headers)
let scaffolding = CodeGenerator.generateMonorepoScaffolding(
var scaffolding = CodeGenerator.generateMonorepoScaffolding(
analysis: analysis, includeSmokeTarget: args.includeSmokeTarget)
// Preserve user-defined targets in Package.swift across generations
let existingPkgPath = outputRoot.appendingPathComponent("Package.swift")
let existingPkg = (try? String(contentsOf: existingPkgPath, encoding: .utf8)) ?? ""
if let pkgContent = scaffolding["Package.swift"] {
scaffolding["Package.swift"] = CodeGenerator.preserveUserTargets(
generated: pkgContent, existing: existingPkg)
}
for (relativePath, content) in scaffolding.sorted(by: { $0.key < $1.key }) {
let fileURL = outputRoot.appendingPathComponent(relativePath)
try FileManager.default.createDirectory(at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true)

View file

@ -96,3 +96,67 @@ func testPackageScaffoldingAdw() {
#expect(scaffolding["Sources/CAdw/module.modulemap"]?.contains("link \"adwaita-1\"") == true)
#expect(scaffolding["Sources/CAdw/CAdw.h"]?.contains("#include <adwaita.h>") == true)
}
// MARK: - User target preservation
@Test("generateMonorepoScaffolding includes user targets marker block")
func testMonorepoPackageHasUserTargetMarkers() {
// Build minimal MultiPackageAnalysis with one module
let repo = Repository(
namespaces: [Namespace(name: "Gtk", version: "4.0",
cSharedLibrary: "libgtk-4.so.1", cIdentifierPrefix: "Gtk")]
)
let analysis = MultiPackageAnalysis(
repositories: ["Gtk": repo],
directDependencies: ["Gtk": []],
transitiveDependencies: ["Gtk": []],
implicitImports: ["Gtk": []],
packageConfigs: [:]
)
let scaffolding = CodeGenerator.generateMonorepoScaffolding(analysis: analysis)
let pkg = scaffolding["Package.swift"] ?? ""
#expect(pkg.contains("// BEGIN_USER_TARGETS"))
#expect(pkg.contains("// END_USER_TARGETS"))
}
@Test("preserveUserTargets splices user block from existing into generated")
func testPreserveUserTargets() {
let existing = """
targets: [
.systemLibrary(name: "CGLib"),
// BEGIN_USER_TARGETS
.testTarget(name: "MyTests", dependencies: ["GLib"]),
// END_USER_TARGETS
]
"""
let generated = """
targets: [
.systemLibrary(name: "CGLib"),
// BEGIN_USER_TARGETS
// END_USER_TARGETS
]
"""
let result = CodeGenerator.preserveUserTargets(generated: generated, existing: existing)
#expect(result.contains("MyTests"))
#expect(result != generated, "Result should differ from generated since existing has content")
#expect(result.contains("// BEGIN_USER_TARGETS"))
#expect(result.contains("// END_USER_TARGETS"))
#expect(result.contains(".testTarget(name:"))
}
@Test("preserveUserTargets returns generated unchanged when existing has no markers")
func testPreserveUserTargetsFirstGeneration() {
let generated = """
targets: [
.systemLibrary(name: "CGLib"),
// BEGIN_USER_TARGETS
// END_USER_TARGETS
]
"""
let result = CodeGenerator.preserveUserTargets(generated: generated, existing: "")
#expect(result == generated)
}