From 1babba4d62e00aca39c93345c8c5ee367640b6c2 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 7 Oct 2024 14:20:29 -0700 Subject: [PATCH 1/2] Support building only tests using SwiftPM This allows us to build everything required for the executable using CMake and only the test targets using SwiftPM, enabling us to run tests on Windows from build.ps1 with minimal overhead. --- Package.swift | 263 ++++++++++++++++++++++++++------------------------ 1 file changed, 137 insertions(+), 126 deletions(-) diff --git a/Package.swift b/Package.swift index 1f853d81f..f9f828c7f 100644 --- a/Package.swift +++ b/Package.swift @@ -14,123 +14,143 @@ import Foundation import PackageDescription +var products: [Product] = [ + .executable( + name: "swift-format", + targets: ["swift-format"] + ), + .library( + name: "SwiftFormat", + targets: ["SwiftFormat"] + ), + .plugin( + name: "FormatPlugin", + targets: ["Format Source Code"] + ), + .plugin( + name: "LintPlugin", + targets: ["Lint Source Code"] + ), +] + +var targets: [Target] = [ + .target( + name: "_SwiftFormatInstructionCounter", + exclude: ["CMakeLists.txt"] + ), + + .target( + name: "SwiftFormat", + dependencies: [ + .product(name: "Markdown", package: "swift-markdown"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + .product(name: "SwiftOperators", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), + ], + exclude: ["CMakeLists.txt"] + ), + .target( + name: "_SwiftFormatTestSupport", + dependencies: [ + "SwiftFormat", + .product(name: "SwiftOperators", package: "swift-syntax"), + ] + ), + .plugin( + name: "Format Source Code", + capability: .command( + intent: .sourceCodeFormatting(), + permissions: [ + .writeToPackageDirectory(reason: "This command formats the Swift source files") + ] + ), + dependencies: [ + .target(name: "swift-format") + ], + path: "Plugins/FormatPlugin" + ), + .plugin( + name: "Lint Source Code", + capability: .command( + intent: .custom( + verb: "lint-source-code", + description: "Lint source code for a specified target." + ) + ), + dependencies: [ + .target(name: "swift-format") + ], + path: "Plugins/LintPlugin" + ), + .executableTarget( + name: "generate-swift-format", + dependencies: [ + "SwiftFormat" + ] + ), + .executableTarget( + name: "swift-format", + dependencies: [ + "_SwiftFormatInstructionCounter", + "SwiftFormat", + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + ], + exclude: ["CMakeLists.txt"], + linkerSettings: swiftformatLinkSettings + ), + + .testTarget( + name: "SwiftFormatPerformanceTests", + dependencies: [ + "SwiftFormat", + "_SwiftFormatTestSupport", + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + ] + ), + .testTarget( + name: "SwiftFormatTests", + dependencies: [ + "SwiftFormat", + "_SwiftFormatTestSupport", + .product(name: "Markdown", package: "swift-markdown"), + .product(name: "SwiftOperators", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + ] + ), +] + +if buildOnlyTests { + products = [] + targets = targets.compactMap { target in + guard target.isTest || target.name == "_SwiftFormatTestSupport" else { + return nil + } + target.dependencies = target.dependencies.filter { dependency in + if case .byNameItem(name: "_SwiftFormatTestSupport", _) = dependency { + return true + } + return false + } + return target + } +} + let package = Package( name: "swift-format", platforms: [ .macOS("12.0"), .iOS("13.0"), ], - products: [ - .executable( - name: "swift-format", - targets: ["swift-format"] - ), - .library( - name: "SwiftFormat", - targets: ["SwiftFormat"] - ), - .plugin( - name: "FormatPlugin", - targets: ["Format Source Code"] - ), - .plugin( - name: "LintPlugin", - targets: ["Lint Source Code"] - ), - ], + products: products, dependencies: dependencies, - targets: [ - .target( - name: "_SwiftFormatInstructionCounter", - exclude: ["CMakeLists.txt"] - ), - - .target( - name: "SwiftFormat", - dependencies: omittingExternalDependenciesIfNecessary([ - .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - .product(name: "SwiftOperators", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), - ]), - exclude: ["CMakeLists.txt"] - ), - .target( - name: "_SwiftFormatTestSupport", - dependencies: omittingExternalDependenciesIfNecessary([ - "SwiftFormat", - .product(name: "SwiftOperators", package: "swift-syntax"), - ]) - ), - .plugin( - name: "Format Source Code", - capability: .command( - intent: .sourceCodeFormatting(), - permissions: [ - .writeToPackageDirectory(reason: "This command formats the Swift source files") - ] - ), - dependencies: [ - .target(name: "swift-format") - ], - path: "Plugins/FormatPlugin" - ), - .plugin( - name: "Lint Source Code", - capability: .command( - intent: .custom( - verb: "lint-source-code", - description: "Lint source code for a specified target." - ) - ), - dependencies: [ - .target(name: "swift-format") - ], - path: "Plugins/LintPlugin" - ), - .executableTarget( - name: "generate-swift-format", - dependencies: [ - "SwiftFormat" - ] - ), - .executableTarget( - name: "swift-format", - dependencies: omittingExternalDependenciesIfNecessary([ - "_SwiftFormatInstructionCounter", - "SwiftFormat", - .product(name: "ArgumentParser", package: "swift-argument-parser"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - ]), - exclude: ["CMakeLists.txt"], - linkerSettings: swiftformatLinkSettings - ), - - .testTarget( - name: "SwiftFormatPerformanceTests", - dependencies: omittingExternalDependenciesIfNecessary([ - "SwiftFormat", - "_SwiftFormatTestSupport", - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - ]) - ), - .testTarget( - name: "SwiftFormatTests", - dependencies: omittingExternalDependenciesIfNecessary([ - "SwiftFormat", - "_SwiftFormatTestSupport", - .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftOperators", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - ]) - ), - ] + targets: targets ) // MARK: - Parse build arguments @@ -147,26 +167,17 @@ var installAction: Bool { hasEnvironmentVariable("SWIFTFORMAT_CI_INSTALL") } /// remote dependency. var useLocalDependencies: Bool { hasEnvironmentVariable("SWIFTCI_USE_LOCAL_DEPS") } -var omitExternalDependencies: Bool { hasEnvironmentVariable("SWIFTFORMAT_OMIT_EXTERNAL_DEPENDENCIES") } - -func omittingExternalDependenciesIfNecessary( - _ dependencies: [Target.Dependency] -) -> [Target.Dependency] { - guard omitExternalDependencies else { - return dependencies - } - return dependencies.filter { dependency in - if case .productItem(_, let package, _, _) = dependency { - return package == nil - } - return true - } -} +/// Build only tests targets and test support modules. +/// +/// This is used to test swift-format on Windows, where the modules required for the `swift-format` executable are +/// built using CMake. When using this setting, the caller is responsible for passing the required search paths to +/// the `swift test` invocation so that all pre-built modules can be found. +var buildOnlyTests: Bool { hasEnvironmentVariable("SWIFTFORMAT_BUILD_ONLY_TESTS") } // MARK: - Dependencies var dependencies: [Package.Dependency] { - if omitExternalDependencies { + if buildOnlyTests { return [] } else if useLocalDependencies { return [ From 79cef55b88bdbbd39efcc1b2796c076c34fa535f Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 13 Oct 2024 22:07:41 -0700 Subject: [PATCH 2/2] Allow building against a single dynamic swift-syntax library When `SWIFTSYNTAX_BUILD_DYNAMIC_LIBRARY` is specified, change swift-syntax dependencies to depend on a single `_SwiftSyntaxDynamic` product instead of separate products for each module. This allows us to build SourceKit-LSP on Windows using SwiftPM without exceeding the maximum symbol limit and thus run SourceKit-LSP tests on Windows. See https://github.com/swiftlang/sourcekit-lsp/pull/1754 and https://github.com/swiftlang/swift-syntax/pull/2879. --- Package.swift | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Package.swift b/Package.swift index f9f828c7f..251a6cae1 100644 --- a/Package.swift +++ b/Package.swift @@ -42,21 +42,21 @@ var targets: [Target] = [ .target( name: "SwiftFormat", dependencies: [ - .product(name: "Markdown", package: "swift-markdown"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - .product(name: "SwiftOperators", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), - ], + .product(name: "Markdown", package: "swift-markdown") + ] + + swiftSyntaxDependencies([ + "SwiftOperators", "SwiftParser", "SwiftParserDiagnostics", "SwiftSyntax", "SwiftSyntaxBuilder", + ]), exclude: ["CMakeLists.txt"] ), .target( name: "_SwiftFormatTestSupport", dependencies: [ - "SwiftFormat", - .product(name: "SwiftOperators", package: "swift-syntax"), + "SwiftFormat" ] + + swiftSyntaxDependencies([ + "SwiftOperators", "SwiftParser", "SwiftParserDiagnostics", "SwiftSyntax", "SwiftSyntaxBuilder", + ]) ), .plugin( name: "Format Source Code", @@ -98,7 +98,7 @@ var targets: [Target] = [ .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ], + ] + swiftSyntaxDependencies(["SwiftParser", "SwiftSyntax"]), exclude: ["CMakeLists.txt"], linkerSettings: swiftformatLinkSettings ), @@ -110,7 +110,7 @@ var targets: [Target] = [ "_SwiftFormatTestSupport", .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ] + ] + swiftSyntaxDependencies(["SwiftParser", "SwiftSyntax"]) ), .testTarget( name: "SwiftFormatTests", @@ -153,6 +153,14 @@ let package = Package( targets: targets ) +func swiftSyntaxDependencies(_ names: [String]) -> [Target.Dependency] { + if buildDynamicSwiftSyntaxLibrary { + return [.product(name: "_SwiftSyntaxDynamic", package: "swift-syntax")] + } else { + return names.map { .product(name: $0, package: "swift-syntax") } + } +} + // MARK: - Parse build arguments func hasEnvironmentVariable(_ name: String) -> Bool { @@ -174,6 +182,15 @@ var useLocalDependencies: Bool { hasEnvironmentVariable("SWIFTCI_USE_LOCAL_DEPS" /// the `swift test` invocation so that all pre-built modules can be found. var buildOnlyTests: Bool { hasEnvironmentVariable("SWIFTFORMAT_BUILD_ONLY_TESTS") } +/// Whether swift-syntax is being built as a single dynamic library instead of as a separate library per module. +/// +/// This means that the swift-syntax symbols don't need to be statically linked, which alles us to stay below the +/// maximum number of exported symbols on Windows, in turn allowing us to build sourcekit-lsp using SwiftPM on Windows +/// and run its tests. +var buildDynamicSwiftSyntaxLibrary: Bool { + hasEnvironmentVariable("SWIFTSYNTAX_BUILD_DYNAMIC_LIBRARY") +} + // MARK: - Dependencies var dependencies: [Package.Dependency] {