Skip to content

Commit e0facda

Browse files
authored
Commands: move PluginDelegate to a separate file (#5882)
`SwiftPackageTool.swift` is quite big, it would be great to split it into multiple files for easier maintenance. Moved `final class PluginDelegate` declaration to a new `Utilities/PluginDelegate.swift` file.
1 parent d9d05c1 commit e0facda

File tree

3 files changed

+393
-328
lines changed

3 files changed

+393
-328
lines changed

Sources/Commands/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ add_library(Commands
2828
Utilities/DOTManifestSerializer.swift
2929
Utilities/GenerateLinuxMain.swift
3030
Utilities/MultiRootSupport.swift
31+
Utilities/PluginDelegate.swift
3132
Utilities/SymbolGraphExtract.swift
3233
Utilities/TestingSupport.swift)
3334
target_link_libraries(Commands PUBLIC

Sources/Commands/SwiftPackageTool.swift

-328
Original file line numberDiff line numberDiff line change
@@ -1133,323 +1133,6 @@ extension SwiftPackageTool {
11331133
}
11341134
}
11351135

1136-
final class PluginDelegate: PluginInvocationDelegate {
1137-
let swiftTool: SwiftTool
1138-
let plugin: PluginTarget
1139-
var lineBufferedOutput: Data
1140-
1141-
init(swiftTool: SwiftTool, plugin: PluginTarget) {
1142-
self.swiftTool = swiftTool
1143-
self.plugin = plugin
1144-
self.lineBufferedOutput = Data()
1145-
}
1146-
1147-
func pluginCompilationStarted(commandLine: [String], environment: EnvironmentVariables) {
1148-
}
1149-
1150-
func pluginCompilationEnded(result: PluginCompilationResult) {
1151-
}
1152-
1153-
func pluginCompilationWasSkipped(cachedResult: PluginCompilationResult) {
1154-
}
1155-
1156-
func pluginEmittedOutput(_ data: Data) {
1157-
lineBufferedOutput += data
1158-
while let newlineIdx = lineBufferedOutput.firstIndex(of: UInt8(ascii: "\n")) {
1159-
let lineData = lineBufferedOutput.prefix(upTo: newlineIdx)
1160-
print(String(decoding: lineData, as: UTF8.self))
1161-
lineBufferedOutput = lineBufferedOutput.suffix(from: newlineIdx.advanced(by: 1))
1162-
}
1163-
}
1164-
1165-
func pluginEmittedDiagnostic(_ diagnostic: Basics.Diagnostic) {
1166-
swiftTool.observabilityScope.emit(diagnostic)
1167-
}
1168-
1169-
func pluginRequestedBuildOperation(subset: PluginInvocationBuildSubset, parameters: PluginInvocationBuildParameters, completion: @escaping (Result<PluginInvocationBuildResult, Error>) -> Void) {
1170-
// Run the build in the background and call the completion handler when done.
1171-
DispatchQueue.sharedConcurrent.async {
1172-
completion(Result {
1173-
return try self.performBuildForPlugin(subset: subset, parameters: parameters)
1174-
})
1175-
}
1176-
}
1177-
1178-
private func performBuildForPlugin(subset: PluginInvocationBuildSubset, parameters: PluginInvocationBuildParameters) throws -> PluginInvocationBuildResult {
1179-
// Configure the build parameters.
1180-
var buildParameters = try self.swiftTool.buildParameters()
1181-
switch parameters.configuration {
1182-
case .debug:
1183-
buildParameters.configuration = .debug
1184-
case .release:
1185-
buildParameters.configuration = .release
1186-
}
1187-
buildParameters.flags.cCompilerFlags.append(contentsOf: parameters.otherCFlags)
1188-
buildParameters.flags.cxxCompilerFlags.append(contentsOf: parameters.otherCxxFlags)
1189-
buildParameters.flags.swiftCompilerFlags.append(contentsOf: parameters.otherSwiftcFlags)
1190-
buildParameters.flags.linkerFlags.append(contentsOf: parameters.otherLinkerFlags)
1191-
1192-
// Configure the verbosity of the output.
1193-
let logLevel: Diagnostic.Severity
1194-
switch parameters.logging {
1195-
case .concise:
1196-
logLevel = .warning
1197-
case .verbose:
1198-
logLevel = .info
1199-
case .debug:
1200-
logLevel = .debug
1201-
}
1202-
1203-
// Determine the subset of products and targets to build.
1204-
var explicitProduct: String? = .none
1205-
let buildSubset: BuildSubset
1206-
switch subset {
1207-
case .all(let includingTests):
1208-
buildSubset = includingTests ? .allIncludingTests : .allExcludingTests
1209-
case .product(let name):
1210-
buildSubset = .product(name)
1211-
explicitProduct = name
1212-
case .target(let name):
1213-
buildSubset = .target(name)
1214-
}
1215-
1216-
// Create a build operation. We have to disable the cache in order to get a build plan created.
1217-
let outputStream = BufferedOutputByteStream()
1218-
let buildSystem = try swiftTool.createBuildSystem(
1219-
explicitBuildSystem: .native,
1220-
explicitProduct: explicitProduct,
1221-
cacheBuildManifest: false,
1222-
customBuildParameters: buildParameters,
1223-
customOutputStream: outputStream,
1224-
customLogLevel: logLevel
1225-
)
1226-
1227-
// Run the build. This doesn't return until the build is complete.
1228-
let success = buildSystem.buildIgnoringError(subset: buildSubset)
1229-
1230-
// Create and return the build result record based on what the delegate collected and what's in the build plan.
1231-
let builtProducts = try buildSystem.buildPlan.buildProducts.filter {
1232-
switch subset {
1233-
case .all(let includingTests):
1234-
return includingTests ? true : $0.product.type != .test
1235-
case .product(let name):
1236-
return $0.product.name == name
1237-
case .target(let name):
1238-
return $0.product.name == name
1239-
}
1240-
}
1241-
let builtArtifacts: [PluginInvocationBuildResult.BuiltArtifact] = builtProducts.compactMap {
1242-
switch $0.product.type {
1243-
case .library(let kind):
1244-
return .init(path: $0.binaryPath.pathString, kind: (kind == .dynamic) ? .dynamicLibrary : .staticLibrary)
1245-
case .executable:
1246-
return .init(path: $0.binaryPath.pathString, kind: .executable)
1247-
default:
1248-
return nil
1249-
}
1250-
}
1251-
return PluginInvocationBuildResult(
1252-
succeeded: success,
1253-
logText: outputStream.bytes.cString,
1254-
builtArtifacts: builtArtifacts)
1255-
}
1256-
1257-
func pluginRequestedTestOperation(subset: PluginInvocationTestSubset, parameters: PluginInvocationTestParameters, completion: @escaping (Result<PluginInvocationTestResult, Error>) -> Void) {
1258-
// Run the test in the background and call the completion handler when done.
1259-
DispatchQueue.sharedConcurrent.async {
1260-
completion(Result {
1261-
return try self.performTestsForPlugin(subset: subset, parameters: parameters)
1262-
})
1263-
}
1264-
}
1265-
1266-
func performTestsForPlugin(subset: PluginInvocationTestSubset, parameters: PluginInvocationTestParameters) throws -> PluginInvocationTestResult {
1267-
// Build the tests. Ideally we should only build those that match the subset, but we don't have a way to know which ones they are until we've built them and can examine the binaries.
1268-
let toolchain = try swiftTool.getDestinationToolchain()
1269-
var buildParameters = try swiftTool.buildParameters()
1270-
buildParameters.enableTestability = true
1271-
buildParameters.enableCodeCoverage = parameters.enableCodeCoverage
1272-
let buildSystem = try swiftTool.createBuildSystem(customBuildParameters: buildParameters)
1273-
try buildSystem.build(subset: .allIncludingTests)
1274-
1275-
// Clean out the code coverage directory that may contain stale `profraw` files from a previous run of the code coverage tool.
1276-
if parameters.enableCodeCoverage {
1277-
try swiftTool.fileSystem.removeFileTree(buildParameters.codeCovPath)
1278-
}
1279-
1280-
// Construct the environment we'll pass down to the tests.
1281-
let testEnvironment = try TestingSupport.constructTestEnvironment(
1282-
toolchain: toolchain,
1283-
buildParameters: buildParameters,
1284-
sanitizers: swiftTool.options.build.sanitizers
1285-
)
1286-
1287-
// Iterate over the tests and run those that match the filter.
1288-
var testTargetResults: [PluginInvocationTestResult.TestTarget] = []
1289-
var numFailedTests = 0
1290-
for testProduct in buildSystem.builtTestProducts {
1291-
// Get the test suites in the bundle. Each is just a container for test cases.
1292-
let testSuites = try TestingSupport.getTestSuites(
1293-
fromTestAt: testProduct.bundlePath,
1294-
swiftTool: swiftTool,
1295-
enableCodeCoverage: parameters.enableCodeCoverage,
1296-
sanitizers: swiftTool.options.build.sanitizers
1297-
)
1298-
for testSuite in testSuites {
1299-
// Each test suite is just a container for test cases (confusingly called "tests", though they are test cases).
1300-
for testCase in testSuite.tests {
1301-
// Each test case corresponds to a combination of target and a XCTestCase, and is a collection of tests that can actually be run.
1302-
var testResults: [PluginInvocationTestResult.TestTarget.TestCase.Test] = []
1303-
for testName in testCase.tests {
1304-
// Check if we should filter out this test.
1305-
let testSpecifier = testCase.name + "/" + testName
1306-
if case .filtered(let regexes) = subset {
1307-
guard regexes.contains(where: { testSpecifier.range(of: $0, options: .regularExpression) != nil }) else {
1308-
continue
1309-
}
1310-
}
1311-
1312-
// Configure a test runner.
1313-
let testRunner = TestRunner(
1314-
bundlePaths: [testProduct.bundlePath],
1315-
xctestArg: testSpecifier,
1316-
cancellator: swiftTool.cancellator,
1317-
toolchain: toolchain,
1318-
testEnv: testEnvironment,
1319-
observabilityScope: swiftTool.observabilityScope)
1320-
1321-
// Run the test — for now we run the sequentially so we can capture accurate timing results.
1322-
let startTime = DispatchTime.now()
1323-
let success = testRunner.test(outputHandler: { _ in }) // this drops the tests output
1324-
let duration = Double(startTime.distance(to: .now()).milliseconds() ?? 0) / 1000.0
1325-
numFailedTests += success ? 0 : 1
1326-
testResults.append(.init(name: testName, result: success ? .succeeded : .failed, duration: duration))
1327-
}
1328-
1329-
// Don't add any results if we didn't run any tests.
1330-
if testResults.isEmpty { continue }
1331-
1332-
// Otherwise we either create a new create a new target result or add to the previous one, depending on whether the target name is the same.
1333-
let testTargetName = testCase.name.prefix(while: { $0 != "." })
1334-
if let lastTestTargetName = testTargetResults.last?.name, testTargetName == lastTestTargetName {
1335-
// Same as last one, just extend its list of cases. We know we have a last one at this point.
1336-
testTargetResults[testTargetResults.count-1].testCases.append(.init(name: testCase.name, tests: testResults))
1337-
}
1338-
else {
1339-
// Not the same, so start a new target result.
1340-
testTargetResults.append(.init(name: String(testTargetName), testCases: [.init(name: testCase.name, tests: testResults)]))
1341-
}
1342-
}
1343-
}
1344-
}
1345-
1346-
// Deal with code coverage, if enabled.
1347-
let codeCoverageDataFile: AbsolutePath?
1348-
if parameters.enableCodeCoverage {
1349-
// Use `llvm-prof` to merge all the `.profraw` files into a single `.profdata` file.
1350-
let mergedCovFile = buildParameters.codeCovDataFile
1351-
let codeCovFileNames = try swiftTool.fileSystem.getDirectoryContents(buildParameters.codeCovPath)
1352-
var llvmProfCommand = [try toolchain.getLLVMProf().pathString]
1353-
llvmProfCommand += ["merge", "-sparse"]
1354-
for fileName in codeCovFileNames where fileName.hasSuffix(".profraw") {
1355-
let filePath = buildParameters.codeCovPath.appending(component: fileName)
1356-
llvmProfCommand.append(filePath.pathString)
1357-
}
1358-
llvmProfCommand += ["-o", mergedCovFile.pathString]
1359-
try TSCBasic.Process.checkNonZeroExit(arguments: llvmProfCommand)
1360-
1361-
// Use `llvm-cov` to export the merged `.profdata` file contents in JSON form.
1362-
var llvmCovCommand = [try toolchain.getLLVMCov().pathString]
1363-
llvmCovCommand += ["export", "-instr-profile=\(mergedCovFile.pathString)"]
1364-
for product in buildSystem.builtTestProducts {
1365-
llvmCovCommand.append("-object")
1366-
llvmCovCommand.append(product.binaryPath.pathString)
1367-
}
1368-
// We get the output on stdout, and have to write it to a JSON ourselves.
1369-
let jsonOutput = try TSCBasic.Process.checkNonZeroExit(arguments: llvmCovCommand)
1370-
let jsonCovFile = buildParameters.codeCovDataFile.parentDirectory.appending(component: buildParameters.codeCovDataFile.basenameWithoutExt + ".json")
1371-
try swiftTool.fileSystem.writeFileContents(jsonCovFile, string: jsonOutput)
1372-
1373-
// Return the path of the exported code coverage data file.
1374-
codeCoverageDataFile = jsonCovFile
1375-
}
1376-
else {
1377-
codeCoverageDataFile = nil
1378-
}
1379-
1380-
// Return the results to the plugin. We only consider the test run a success if no test failed.
1381-
return PluginInvocationTestResult(
1382-
succeeded: (numFailedTests == 0),
1383-
testTargets: testTargetResults,
1384-
codeCoverageDataFile: codeCoverageDataFile?.pathString)
1385-
}
1386-
1387-
func pluginRequestedSymbolGraph(forTarget targetName: String, options: PluginInvocationSymbolGraphOptions, completion: @escaping (Result<PluginInvocationSymbolGraphResult, Error>) -> Void) {
1388-
// Extract the symbol graph in the background and call the completion handler when done.
1389-
DispatchQueue.sharedConcurrent.async {
1390-
completion(Result {
1391-
return try self.createSymbolGraphForPlugin(forTarget: targetName, options: options)
1392-
})
1393-
}
1394-
}
1395-
1396-
private func createSymbolGraphForPlugin(forTarget targetName: String, options: PluginInvocationSymbolGraphOptions) throws -> PluginInvocationSymbolGraphResult {
1397-
// Current implementation uses `SymbolGraphExtract()` but in the future we should emit the symbol graph while building.
1398-
1399-
// Create a build system for building the target., skipping the the cache because we need the build plan.
1400-
let buildSystem = try swiftTool.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false)
1401-
1402-
// Find the target in the build operation's package graph; it's an error if we don't find it.
1403-
let packageGraph = try buildSystem.getPackageGraph()
1404-
guard let target = packageGraph.allTargets.first(where: { $0.name == targetName }) else {
1405-
throw StringError("could not find a target named “\(targetName)")
1406-
}
1407-
1408-
// Build the target, if needed.
1409-
try buildSystem.build(subset: .target(target.name))
1410-
1411-
// Configure the symbol graph extractor.
1412-
var symbolGraphExtractor = try SymbolGraphExtract(
1413-
fileSystem: swiftTool.fileSystem,
1414-
tool: swiftTool.getDestinationToolchain().getSymbolGraphExtract()
1415-
)
1416-
symbolGraphExtractor.skipSynthesizedMembers = !options.includeSynthesized
1417-
switch options.minimumAccessLevel {
1418-
case .private:
1419-
symbolGraphExtractor.minimumAccessLevel = .private
1420-
case .fileprivate:
1421-
symbolGraphExtractor.minimumAccessLevel = .fileprivate
1422-
case .internal:
1423-
symbolGraphExtractor.minimumAccessLevel = .internal
1424-
case .public:
1425-
symbolGraphExtractor.minimumAccessLevel = .public
1426-
case .open:
1427-
symbolGraphExtractor.minimumAccessLevel = .open
1428-
}
1429-
symbolGraphExtractor.skipInheritedDocs = true
1430-
symbolGraphExtractor.includeSPISymbols = options.includeSPI
1431-
1432-
// Determine the output directory, and remove any old version if it already exists.
1433-
guard let package = packageGraph.package(for: target) else {
1434-
throw StringError("could not determine the package for target “\(target.name)")
1435-
}
1436-
let outputDir = try buildSystem.buildPlan.buildParameters.dataPath.appending(components: "extracted-symbols", package.identity.description, target.name)
1437-
try swiftTool.fileSystem.removeFileTree(outputDir)
1438-
1439-
// Run the symbol graph extractor on the target.
1440-
try symbolGraphExtractor.extractSymbolGraph(
1441-
target: target,
1442-
buildPlan: try buildSystem.buildPlan,
1443-
outputRedirection: .collect,
1444-
outputDirectory: outputDir,
1445-
verboseOutput: self.swiftTool.logLevel <= .info
1446-
)
1447-
1448-
// Return the results to the plugin.
1449-
return PluginInvocationSymbolGraphResult(directoryPath: outputDir.pathString)
1450-
}
1451-
}
1452-
14531136
extension PluginCommandIntent {
14541137
var invocationVerb: String {
14551138
switch self {
@@ -1882,14 +1565,3 @@ private extension Basics.Diagnostic {
18821565
.error("missing required argument \(argument)")
18831566
}
18841567
}
1885-
1886-
extension BuildSystem {
1887-
fileprivate func buildIgnoringError(subset: BuildSubset) -> Bool {
1888-
do {
1889-
try self.build(subset: subset)
1890-
return true
1891-
} catch {
1892-
return false
1893-
}
1894-
}
1895-
}

0 commit comments

Comments
 (0)