From b3c88534de05a41f06eeff2c1c0402e9b5e6e7c4 Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Fri, 20 Dec 2024 15:49:23 -0800 Subject: [PATCH] Add target variant ABI digester file Now teaching the compiler to emit the ABI JSON file for the target variant module. --- Sources/SwiftDriver/Driver/Driver.swift | 55 ++++++++++----- .../SwiftDriver/Jobs/APIDigesterJobs.swift | 2 +- Sources/SwiftDriver/Jobs/EmitModuleJob.swift | 2 +- .../SwiftDriver/Jobs/FrontendJobHelpers.swift | 2 +- Sources/SwiftDriver/Jobs/MergeModuleJob.swift | 2 +- Sources/SwiftOptions/Options.swift | 2 + Tests/SwiftDriverTests/SwiftDriverTests.swift | 70 +++++++++++++++++++ 7 files changed, 112 insertions(+), 23 deletions(-) diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index 57e2f0185..b8382dbb7 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -398,6 +398,9 @@ public struct Driver { /// Path to the emitted API descriptor file. let apiDescriptorFilePath: VirtualPath.Handle? + + /// Path to the emitted ABI descriptor file. + let abiDescriptorFilePath: TypedVirtualPath? } private static func computeModuleOutputPaths( @@ -411,12 +414,14 @@ public struct Driver { outputFileMap: OutputFileMap?, projectDirectory: VirtualPath.Handle?, apiDescriptorDirectory: VirtualPath?, + supportedFrontendFeatures: Set, target: FrontendTargetInfo.Target, isVariant: Bool) throws -> SupplementalModuleTargetOutputPaths { struct SupplementalPathOptions { let moduleDocPath: Option let sourceInfoPath: Option let apiDescriptorPath: Option + let abiDescriptorPath: Option let moduleInterfacePath: Option let privateInterfacePath: Option let packageInterfacePath: Option @@ -425,6 +430,7 @@ public struct Driver { moduleDocPath: .emitModuleDocPath, sourceInfoPath: .emitModuleSourceInfoPath, apiDescriptorPath: .emitApiDescriptorPath, + abiDescriptorPath: .emitAbiDescriptorPath, moduleInterfacePath: .emitModuleInterfacePath, privateInterfacePath: .emitPrivateModuleInterfacePath, packageInterfacePath: .emitPackageModuleInterfacePath) @@ -433,6 +439,7 @@ public struct Driver { moduleDocPath: .emitVariantModuleDocPath, sourceInfoPath: .emitVariantModuleSourceInfoPath, apiDescriptorPath: .emitVariantApiDescriptorPath, + abiDescriptorPath: .emitVariantAbiDescriptorPath, moduleInterfacePath: .emitVariantModuleInterfacePath, privateInterfacePath: .emitVariantPrivateModuleInterfacePath, packageInterfacePath: .emitVariantPackageModuleInterfacePath) @@ -459,6 +466,31 @@ public struct Driver { moduleName: moduleOutputInfo.name, projectDirectory: projectDirectory) + // --------------------- + // ABI Descriptor Path + func computeABIDescriptorFilePath(target: FrontendTargetInfo.Target, + features: Set) -> TypedVirtualPath? { + guard features.contains(KnownCompilerFeature.emit_abi_descriptor.rawValue) else { + return nil + } + // Emit the descriptor only on platforms where Library Evolution is + // supported + guard target.triple.isDarwin || parsedOptions.hasArgument(.enableLibraryEvolution) else { + return nil + } + guard let moduleOutput = moduleOutputInfo.output else { + return nil + } + + guard let path = try? VirtualPath.lookup(moduleOutput.outputPath) + .replacingExtension(with: .jsonABIBaseline) else { + return nil + } + return TypedVirtualPath(file: path.intern(), type: .jsonABIBaseline) + } + let abiDescriptorFilePath = computeABIDescriptorFilePath(target: target, + features: supportedFrontendFeatures) + // --------------------- // API Descriptor Path let apiDescriptorFilePath: VirtualPath.Handle? @@ -545,7 +577,8 @@ public struct Driver { swiftPrivateInterfacePath: swiftPrivateInterfacePath, swiftPackageInterfacePath: swiftPackageInterfacePath, moduleSourceInfoPath: moduleSourceInfoPath, - apiDescriptorFilePath: apiDescriptorFilePath) + apiDescriptorFilePath: apiDescriptorFilePath, + abiDescriptorFilePath: abiDescriptorFilePath) } /// Structure storing paths to supplemental outputs for the target module @@ -622,24 +655,6 @@ public struct Driver { .appending(component: "Frameworks") } () - lazy var abiDescriptorPath: TypedVirtualPath? = { - guard isFeatureSupported(.emit_abi_descriptor) else { - return nil - } - // Emit the descriptor only on platforms where Library Evolution is supported, - // or opted-into explicitly. - guard targetTriple.isDarwin || parsedOptions.hasArgument(.enableLibraryEvolution) else { - return nil - } - guard let moduleOutput = moduleOutputInfo.output else { - return nil - } - guard let path = try? VirtualPath.lookup(moduleOutput.outputPath).replacingExtension(with: .jsonABIBaseline) else { - return nil - } - return TypedVirtualPath(file: path.intern(), type: .jsonABIBaseline) - }() - public static func isOptionFound(_ opt: String, allOpts: Set) -> Bool { var current = opt while(true) { @@ -1221,6 +1236,7 @@ public struct Driver { outputFileMap: self.outputFileMap, projectDirectory: projectDirectory, apiDescriptorDirectory: apiDescriptorDirectory, + supportedFrontendFeatures: self.supportedFrontendFeatures, target: frontendTargetInfo.target, isVariant: false) @@ -1237,6 +1253,7 @@ public struct Driver { outputFileMap: self.outputFileMap, projectDirectory: projectDirectory, apiDescriptorDirectory: apiDescriptorDirectory, + supportedFrontendFeatures: self.supportedFrontendFeatures, target: targetVariant, isVariant: true) } else { diff --git a/Sources/SwiftDriver/Jobs/APIDigesterJobs.swift b/Sources/SwiftDriver/Jobs/APIDigesterJobs.swift index b09c9072c..84dd11846 100644 --- a/Sources/SwiftDriver/Jobs/APIDigesterJobs.swift +++ b/Sources/SwiftDriver/Jobs/APIDigesterJobs.swift @@ -72,7 +72,7 @@ extension Driver { case .api: return nil case .abi: - return abiDescriptorPath + return moduleOutputPaths.abiDescriptorFilePath } } guard let currentABI = getDescriptorPath(for: mode) else { diff --git a/Sources/SwiftDriver/Jobs/EmitModuleJob.swift b/Sources/SwiftDriver/Jobs/EmitModuleJob.swift index 1cc990474..0a8fde6db 100644 --- a/Sources/SwiftDriver/Jobs/EmitModuleJob.swift +++ b/Sources/SwiftDriver/Jobs/EmitModuleJob.swift @@ -125,7 +125,7 @@ extension Driver { let outputPath = VirtualPath.lookup(moduleOutputPath) commandLine.appendFlag(.o) commandLine.appendPath(outputPath) - if let abiPath = abiDescriptorPath { + if let abiPath = moduleOutputPaths.abiDescriptorFilePath { commandLine.appendFlag(.emitAbiDescriptorPath) commandLine.appendPath(abiPath.file) outputs.append(abiPath) diff --git a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift index bb3b60e83..a2b42c989 100644 --- a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift +++ b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift @@ -692,7 +692,7 @@ extension Driver { input: nil, flag: "-emit-tbd-path") - if let abiDescriptorPath = abiDescriptorPath { + if let abiDescriptorPath = moduleOutputPaths.abiDescriptorFilePath { try addOutputOfType(outputType: .jsonABIBaseline, finalOutputPath: abiDescriptorPath.fileHandle, input: nil, diff --git a/Sources/SwiftDriver/Jobs/MergeModuleJob.swift b/Sources/SwiftDriver/Jobs/MergeModuleJob.swift index dd6717dbf..6b9d89e4b 100644 --- a/Sources/SwiftDriver/Jobs/MergeModuleJob.swift +++ b/Sources/SwiftDriver/Jobs/MergeModuleJob.swift @@ -75,7 +75,7 @@ extension Driver { commandLine.appendFlag(.o) commandLine.appendPath(outputPath) - if let abiPath = abiDescriptorPath { + if let abiPath = moduleOutputPaths.abiDescriptorFilePath { commandLine.appendFlag(.emitAbiDescriptorPath) commandLine.appendPath(abiPath.file) outputs.append(abiPath) diff --git a/Sources/SwiftOptions/Options.swift b/Sources/SwiftOptions/Options.swift index 29ceef11d..4d6f126bf 100644 --- a/Sources/SwiftOptions/Options.swift +++ b/Sources/SwiftOptions/Options.swift @@ -384,6 +384,7 @@ extension Option { public static let emitTbdPathEQ: Option = Option("-emit-tbd-path=", .joined, alias: Option.emitTbdPath, attributes: [.frontend, .noInteractive, .argumentIsPath, .supplementaryOutput, .cacheInvariant]) public static let emitTbdPath: Option = Option("-emit-tbd-path", .separate, attributes: [.frontend, .noInteractive, .argumentIsPath, .supplementaryOutput, .cacheInvariant], metaVar: "", helpText: "Emit the TBD file to ") public static let emitTbd: Option = Option("-emit-tbd", .flag, attributes: [.frontend, .noInteractive, .supplementaryOutput], helpText: "Emit a TBD file") + public static let emitVariantAbiDescriptorPath: Option = Option("-emit-variant-abi-descriptor-path", .separate, attributes: [.frontend, .noDriver, .cacheInvariant], metaVar: "", helpText: "Output the ABI descriptor of current target variant module to ") public static let emitVariantApiDescriptorPath: Option = Option("-emit-variant-api-descriptor-path", .separate, attributes: [.frontend, .noInteractive, .argumentIsPath, .supplementaryOutput, .cacheInvariant], metaVar: "", helpText: "Output a JSON file describing the target variant module's API to ") public static let emitVariantModuleDocPath: Option = Option("-emit-variant-module-doc-path", .separate, attributes: [.frontend, .noDriver, .cacheInvariant], metaVar: "", helpText: "Output module documentation file for the target variant to ") public static let emitVariantModuleInterfacePath: Option = Option("-emit-variant-module-interface-path", .separate, attributes: [.frontend, .noInteractive, .argumentIsPath, .supplementaryOutput, .cacheInvariant], metaVar: "", helpText: "Output module interface file for the target variant to ") @@ -1301,6 +1302,7 @@ extension Option { Option.emitTbdPathEQ, Option.emitTbdPath, Option.emitTbd, + Option.emitVariantAbiDescriptorPath, Option.emitVariantApiDescriptorPath, Option.emitVariantModuleDocPath, Option.emitVariantModuleInterfacePath, diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index fdcc1328f..34e30c14f 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -4090,13 +4090,83 @@ final class SwiftDriverTests: XCTestCase { XCTAssert(targetModuleJob.commandLine.contains(.path(.relative(try .init(validating: "foo.swiftmodule/target.swiftdoc"))))) XCTAssert(targetModuleJob.commandLine.contains(.path(.relative(try .init(validating: "foo.swiftmodule/target.swiftsourceinfo"))))) + XCTAssert(targetModuleJob.commandLine.contains(.path(.relative(try .init(validating: "foo.swiftmodule/target.abi.json"))))) XCTAssertTrue(targetModuleJob.commandLine.contains(subsequence: [.flag("-o"), .path(.relative(try .init(validating: "foo.swiftmodule/target.swiftmodule")))])) XCTAssert(variantModuleJob.commandLine.contains(.path(.relative(try .init(validating: "foo.swiftmodule/variant.swiftdoc"))))) XCTAssert(variantModuleJob.commandLine.contains(.path(.relative(try .init(validating: "foo.swiftmodule/variant.swiftsourceinfo"))))) + XCTAssert(variantModuleJob.commandLine.contains(.path(.relative(try .init(validating: "foo.swiftmodule/variant.abi.json"))))) XCTAssertTrue(variantModuleJob.commandLine.contains(subsequence: [.flag("-o"), .path(.relative(try .init(validating: "foo.swiftmodule/variant.swiftmodule")))])) } + do { + // explicitly emit variant supplemental outputs + var driver = try Driver(args: ["swiftc", + "-target", "x86_64-apple-macosx10.14", + "-target-variant", "x86_64-apple-ios13.1-macabi", + "-enable-library-evolution", + "-package-name", "Susan", + "-emit-module", + "-emit-module-path", "target.swiftmodule", + "-emit-variant-module-path", "variant.swiftmodule", + "-Xfrontend", "-emit-module-doc-path", "-Xfrontend", "target.swiftdoc", + "-Xfrontend", "-emit-variant-module-doc-path", "variant.swiftdoc", + "-emit-module-source-info-path", "target.sourceinfo", + "-emit-variant-module-source-info-path", "variant.sourceinfo", + "-emit-package-module-interface-path", "target.package.swiftinterface", + "-emit-variant-package-module-interface-path", "variant.package.swiftinterface", + "-emit-private-module-interface-path", "target.private.swiftinterface", + "-emit-variant-private-module-interface-path", "variant.private.swiftinterface", + "-emit-module-interface-path", "target.swiftinterface", + "-emit-variant-module-interface-path", "variant.swiftinterface", + "foo.swift"]) + + let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs() + // emit module, emit module, compile foo.swift, + // verify target.swiftinterface, + // verify target.private.swiftinterface, + // verify target.package.swiftinterface, + XCTAssertEqual(plannedJobs.count, 6) + + let targetModuleJob: Job = plannedJobs[0] + let variantModuleJob = plannedJobs[1] + + XCTAssertEqual(targetModuleJob.outputs.filter { $0.type == .swiftModule }.last!.file, + try toPath("target.swiftmodule")) + XCTAssertEqual(variantModuleJob.outputs.filter { $0.type == .swiftModule }.last!.file, + try toPath("variant.swiftmodule")) + + XCTAssertEqual(targetModuleJob.outputs.filter { $0.type == .swiftDocumentation }.last!.file, + try toPath("target.swiftdoc")) + XCTAssertEqual(variantModuleJob.outputs.filter { $0.type == .swiftDocumentation }.last!.file, + try toPath("variant.swiftdoc")) + + XCTAssertEqual(targetModuleJob.outputs.filter { $0.type == .swiftSourceInfoFile }.last!.file, + try toPath("target.sourceinfo")) + XCTAssertEqual(variantModuleJob.outputs.filter { $0.type == .swiftSourceInfoFile }.last!.file, + try toPath("variant.sourceinfo")) + + XCTAssertEqual(targetModuleJob.outputs.filter { $0.type == .swiftInterface}.last!.file, + try toPath("target.swiftinterface")) + XCTAssertEqual(variantModuleJob.outputs.filter { $0.type == .swiftInterface}.last!.file, + try toPath("variant.swiftinterface")) + + XCTAssertEqual(targetModuleJob.outputs.filter { $0.type == .privateSwiftInterface}.last!.file, + try toPath("target.private.swiftinterface")) + XCTAssertEqual(variantModuleJob.outputs.filter { $0.type == .privateSwiftInterface}.last!.file, + try toPath("variant.private.swiftinterface")) + + XCTAssertEqual(targetModuleJob.outputs.filter { $0.type == .packageSwiftInterface}.last!.file, + try toPath("target.package.swiftinterface")) + XCTAssertEqual(variantModuleJob.outputs.filter { $0.type == .packageSwiftInterface}.last!.file, + try toPath("variant.package.swiftinterface")) + + XCTAssertEqual(targetModuleJob.outputs.filter { $0.type == .jsonABIBaseline }.last!.file, + try toPath("target.abi.json")) + XCTAssertEqual(variantModuleJob.outputs.filter { $0.type == .jsonABIBaseline}.last!.file, + try toPath("variant.abi.json")) + } + #if os(macOS) do { try withTemporaryDirectory { path in