diff --git a/Fixtures/Miscellaneous/ParseAsLibrary/Package.swift b/Fixtures/Miscellaneous/ParseAsLibrary/Package.swift new file mode 100644 index 00000000000..e7ff15dddf6 --- /dev/null +++ b/Fixtures/Miscellaneous/ParseAsLibrary/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version: 6.0 + +import PackageDescription + +let package = Package( + name: "ParseAsLibrary", + products: [], + targets: [ + .executableTarget(name: "ExecutableTargetOneFileNamedMainMainAttr"), + .executableTarget(name: "ExecutableTargetOneFileNamedMainNoMainAttr"), + .executableTarget(name: "ExecutableTargetOneFileNotNamedMainMainAttr"), + .executableTarget(name: "ExecutableTargetOneFileNotNamedMainNoMainAttr"), + .executableTarget(name: "ExecutableTargetTwoFiles"), + ] +) diff --git a/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNamedMainMainAttr/main.swift b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNamedMainMainAttr/main.swift new file mode 100644 index 00000000000..15b65931c58 --- /dev/null +++ b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNamedMainMainAttr/main.swift @@ -0,0 +1,3 @@ +@main struct Entry { + static func main() {} +} diff --git a/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNamedMainNoMainAttr/main.swift b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNamedMainNoMainAttr/main.swift new file mode 100644 index 00000000000..517b47df53c --- /dev/null +++ b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNamedMainNoMainAttr/main.swift @@ -0,0 +1 @@ +print(42) diff --git a/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNotNamedMainMainAttr/othername.swift b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNotNamedMainMainAttr/othername.swift new file mode 100644 index 00000000000..15b65931c58 --- /dev/null +++ b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNotNamedMainMainAttr/othername.swift @@ -0,0 +1,3 @@ +@main struct Entry { + static func main() {} +} diff --git a/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNotNamedMainNoMainAttr/othername.swift b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNotNamedMainNoMainAttr/othername.swift new file mode 100644 index 00000000000..517b47df53c --- /dev/null +++ b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetOneFileNotNamedMainNoMainAttr/othername.swift @@ -0,0 +1 @@ +print(42) diff --git a/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetTwoFiles/one.swift b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetTwoFiles/one.swift new file mode 100644 index 00000000000..15b65931c58 --- /dev/null +++ b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetTwoFiles/one.swift @@ -0,0 +1,3 @@ +@main struct Entry { + static func main() {} +} diff --git a/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetTwoFiles/two.swift b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetTwoFiles/two.swift new file mode 100644 index 00000000000..d29f0705072 --- /dev/null +++ b/Fixtures/Miscellaneous/ParseAsLibrary/Sources/ExecutableTargetTwoFiles/two.swift @@ -0,0 +1 @@ +func foo() {} diff --git a/Package.swift b/Package.swift index c8f536d5948..f39bb5399ff 100644 --- a/Package.swift +++ b/Package.swift @@ -534,7 +534,6 @@ let package = Package( .target( name: "SwiftBuildSupport", dependencies: [ - "Build", "SPMBuildCore", "PackageGraph", ], diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index 1d2bdb407a6..b6b6c20fffa 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -34,34 +34,6 @@ import struct TSCBasic.ByteString @available(*, deprecated, renamed: "SwiftModuleBuildDescription") public typealias SwiftTargetBuildDescription = SwiftModuleBuildDescription -// looking into the file content to see if it is using the @main annotation -// this is not bullet-proof since theoretically the file can contain the @main string for other reasons -// but it is the closest to accurate we can do at this point -package func containsAtMain(fileSystem: FileSystem, path: AbsolutePath) throws -> Bool { - let content: String = try fileSystem.readFileContents(path) - let lines = content.split(whereSeparator: { $0.isNewline }).map { $0.trimmingCharacters(in: .whitespaces) } - - var multilineComment = false - for line in lines { - if line.hasPrefix("//") { - continue - } - if line.hasPrefix("/*") { - multilineComment = true - } - if line.hasSuffix("*/") { - multilineComment = false - } - if multilineComment { - continue - } - if line.hasPrefix("@main") { - return true - } - } - return false -} - /// Build description for a Swift module. public final class SwiftModuleBuildDescription { /// The package this target belongs to. diff --git a/Sources/SPMBuildCore/CMakeLists.txt b/Sources/SPMBuildCore/CMakeLists.txt index ccea44f655f..365e4f0fcfd 100644 --- a/Sources/SPMBuildCore/CMakeLists.txt +++ b/Sources/SPMBuildCore/CMakeLists.txt @@ -26,6 +26,7 @@ add_library(SPMBuildCore Plugins/PluginMessages.swift Plugins/PluginScriptRunner.swift CommandPluginResult.swift + MainAttrDetection.swift ResolvedPackage+Extensions.swift Triple+Extensions.swift XCFrameworkMetadata.swift diff --git a/Sources/SPMBuildCore/MainAttrDetection.swift b/Sources/SPMBuildCore/MainAttrDetection.swift new file mode 100644 index 00000000000..b4f3e0fcfb0 --- /dev/null +++ b/Sources/SPMBuildCore/MainAttrDetection.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import Foundation + +// looking into the file content to see if it is using the @main annotation +// this is not bullet-proof since theoretically the file can contain the @main string for other reasons +// but it is the closest to accurate we can do at this point +package func containsAtMain(fileSystem: FileSystem, path: AbsolutePath) throws -> Bool { + let content: String = try fileSystem.readFileContents(path) + let lines = content.split(whereSeparator: { $0.isNewline }).map { $0.trimmingCharacters(in: .whitespaces) } + + var multilineComment = false + for line in lines { + if line.hasPrefix("//") { + continue + } + if line.hasPrefix("/*") { + multilineComment = true + } + if line.hasSuffix("*/") { + multilineComment = false + } + if multilineComment { + continue + } + if line.hasPrefix("@main") { + return true + } + } + return false +} diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift index fcd0453308b..2d40bd05edd 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift @@ -55,6 +55,8 @@ import struct PackageGraph.ResolvedProduct import func PackageLoading.pkgConfigArgs +import SPMBuildCore + import enum SwiftBuild.ProjectModel // MARK: - PIF GUID Helpers @@ -769,6 +771,27 @@ extension PackageGraph.ResolvedModule { func recursivelyTraverseDependencies(with block: (ResolvedModule.Dependency) -> Void) { [self].recursivelyTraverseDependencies(with: block) } + + func addParseAsLibrarySettings(to settings: inout BuildSettings, toolsVersion: ToolsVersion, fileSystem: FileSystem) { + if toolsVersion > .v5_5 && [.executable, .snippet, .macro].contains(self.type) { + let usesAtMainAttr = self.sources.paths.contains { sourcePath in + (try? containsAtMain(fileSystem: fileSystem, path: sourcePath)) ?? false + } + if usesAtMainAttr { + // Always pass -parse-as-library if @main is used + settings[.SWIFT_LIBRARIES_ONLY] = "YES" + settings[.SWIFT_DISABLE_PARSE_AS_LIBRARY] = "NO" + } else { + // Never pass -parse-as-library if @main isn't used, fall back to compiler heuristics + settings[.SWIFT_LIBRARIES_ONLY] = "NO" + settings[.SWIFT_DISABLE_PARSE_AS_LIBRARY] = "YES" + } + } else if [.library, .test].contains(self.type) { + // Always pass -parse-as-library for libraries and tests + settings[.SWIFT_LIBRARIES_ONLY] = "YES" + settings[.SWIFT_DISABLE_PARSE_AS_LIBRARY] = "NO" + } + } } extension Collection { diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index dc926d66668..a78f902b0e8 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -616,6 +616,8 @@ extension PackagePIFProjectBuilder { settings[.SKIP_BUILDING_DOCUMENTATION] = "YES" } + sourceModule.addParseAsLibrarySettings(to: &settings, toolsVersion: package.manifest.toolsVersion, fileSystem: pifBuilder.fileSystem) + // Handle the target's dependencies (but only link against them if needed). let shouldLinkProduct = (desiredModuleType == .dynamicLibrary) || (desiredModuleType == .macro) sourceModule.recursivelyTraverseDependencies { dependency in diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index 69f1d6d8e35..8a5ae4034ec 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -17,7 +17,6 @@ import TSCUtility import struct Basics.AbsolutePath import class Basics.ObservabilitySystem import struct Basics.SourceControlURL -import func Build.containsAtMain import class PackageModel.BinaryModule import class PackageModel.Manifest @@ -139,19 +138,10 @@ extension PackagePIFProjectBuilder { settings[.INSTALL_PATH] = "/usr/local/bin" settings[.LD_RUNPATH_SEARCH_PATHS] = ["$(inherited)", "@executable_path/../lib"] } - } else if mainModule.type == .snippet { - let hasMainModule: Bool - if let mainModule = product.mainModule { - // Check if any source file in the main module contains @main - hasMainModule = mainModule.sources.paths.contains { (sourcePath: AbsolutePath) in - (try? containsAtMain(fileSystem: pifBuilder.fileSystem, path: sourcePath)) ?? false - } - } else { - hasMainModule = false - } - settings[.SWIFT_DISABLE_PARSE_AS_LIBRARY] = hasMainModule ? "NO" : "YES" } + mainModule.addParseAsLibrarySettings(to: &settings, toolsVersion: package.manifest.toolsVersion, fileSystem: pifBuilder.fileSystem) + let mainTargetDeploymentTargets = mainModule.deploymentTargets(using: pifBuilder.delegate) settings[.MACOSX_DEPLOYMENT_TARGET] = mainTargetDeploymentTargets[.macOS] ?? nil diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 6a6a1c6547f..8f3dd2948e5 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -1278,6 +1278,22 @@ struct BuildCommandTestCases { } } + @Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild]) + func parseAsLibraryCriteria(buildSystem: BuildSystemProvider.Kind) async throws { + try await withKnownIssue { + try await fixture(name: "Miscellaneous/ParseAsLibrary") { fixturePath in + _ = try await executeSwiftBuild( + fixturePath, + buildSystem: buildSystem, + throwIfCommandFails: true + ) + } + } when: { + ProcessInfo.hostOperatingSystem == .windows && + buildSystem == .swiftbuild + } + } + @Test( arguments: SupportedBuildSystemOnPlatform, ) diff --git a/Tests/BuildTests/BuildDescriptionTests.swift b/Tests/SPMBuildCoreTests/MainAttrDetectionTests.swift similarity index 99% rename from Tests/BuildTests/BuildDescriptionTests.swift rename to Tests/SPMBuildCoreTests/MainAttrDetectionTests.swift index 27f74d2c790..c7e3cdba562 100644 --- a/Tests/BuildTests/BuildDescriptionTests.swift +++ b/Tests/SPMBuildCoreTests/MainAttrDetectionTests.swift @@ -1,5 +1,5 @@ import Basics -import func Build.containsAtMain +import SPMBuildCore import Testing struct ContainsAtMainReturnsExpectedValueTestData: CustomStringConvertible { @@ -14,7 +14,7 @@ struct ContainsAtMainReturnsExpectedValueTestData: CustomStringConvertible { } @Suite -struct BuildDescriptionTests { +struct MainAttrDetectionTests { @Test( .tags( .TestSize.small,