Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Fixtures/Miscellaneous/ParseAsLibrary/Package.swift
Original file line number Diff line number Diff line change
@@ -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"),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@main struct Entry {
static func main() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
print(42)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@main struct Entry {
static func main() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
print(42)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@main struct Entry {
static func main() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
func foo() {}
1 change: 0 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,6 @@ let package = Package(
.target(
name: "SwiftBuildSupport",
dependencies: [
"Build",
"SPMBuildCore",
"PackageGraph",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions Sources/SPMBuildCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions Sources/SPMBuildCore/MainAttrDetection.swift
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ import struct PackageGraph.ResolvedProduct

import func PackageLoading.pkgConfigArgs

import SPMBuildCore

import enum SwiftBuild.ProjectModel

// MARK: - PIF GUID Helpers
Expand Down Expand Up @@ -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<PackageGraph.ResolvedModule> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions Tests/CommandsTests/BuildCommandTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Basics
import func Build.containsAtMain
import SPMBuildCore
import Testing

struct ContainsAtMainReturnsExpectedValueTestData: CustomStringConvertible {
Expand All @@ -14,7 +14,7 @@ struct ContainsAtMainReturnsExpectedValueTestData: CustomStringConvertible {
}

@Suite
struct BuildDescriptionTests {
struct MainAttrDetectionTests {
@Test(
.tags(
.TestSize.small,
Expand Down