Skip to content

Commit 4431162

Browse files
committed
Add Snippet support with SwiftBuild
Snippets are treated as executable targets with the native build system. This change updates the PIF Builder to support snippet, giving Snippet support with the Swift Build build system. Depends on: swiftlang/swift-build#775 Fixes: #9040 issue: rdar://158630024 issue: rdar://147705448
1 parent dd002d4 commit 4431162

File tree

20 files changed

+1869
-513
lines changed

20 files changed

+1869
-513
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// The Swift Programming Language
2+
// https://docs.swift.org/swift-book
3+
4+
@main
5+
struct foo {
6+
static func main() {
7+
print("hello, snippets. File: \(#file)")
8+
}
9+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("hello, snippets. File: \(#file)")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("hello, snippets! File: \(#file)")

Sources/Basics/FileSystem/InMemoryFileSystem.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,24 @@ public final class InMemoryFileSystem: FileSystem {
226226
}
227227

228228
/// Virtualized current working directory.
229+
private var _currentWorkingDirectory: TSCBasic.AbsolutePath = try! .init(validating: "/")
230+
229231
public var currentWorkingDirectory: TSCBasic.AbsolutePath? {
230-
return try? .init(validating: "/")
232+
return _currentWorkingDirectory
231233
}
232234

233235
public func changeCurrentWorkingDirectory(to path: TSCBasic.AbsolutePath) throws {
234-
throw FileSystemError(.unsupported, path)
236+
return try lock.withLock {
237+
// Verify the path exists and is a directory
238+
guard let node = try getNode(path) else {
239+
throw FileSystemError(.noEntry, path)
240+
}
241+
242+
guard case .directory = node.contents else {
243+
throw FileSystemError(.notDirectory, path)
244+
}
245+
_currentWorkingDirectory = path
246+
}
235247
}
236248

237249
public var homeDirectory: TSCBasic.AbsolutePath {

Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,34 @@ import struct TSCBasic.ByteString
3434
@available(*, deprecated, renamed: "SwiftModuleBuildDescription")
3535
public typealias SwiftTargetBuildDescription = SwiftModuleBuildDescription
3636

37+
// looking into the file content to see if it is using the @main annotation
38+
// this is not bullet-proof since theoretically the file can contain the @main string for other reasons
39+
// but it is the closest to accurate we can do at this point
40+
package func containsAtMain(fileSystem: FileSystem, path: AbsolutePath) throws -> Bool {
41+
let content: String = try fileSystem.readFileContents(path)
42+
let lines = content.split(whereSeparator: { $0.isNewline }).map { $0.trimmingCharacters(in: .whitespaces) }
43+
44+
var multilineComment = false
45+
for line in lines {
46+
if line.hasPrefix("//") {
47+
continue
48+
}
49+
if line.hasPrefix("/*") {
50+
multilineComment = true
51+
}
52+
if line.hasSuffix("*/") {
53+
multilineComment = false
54+
}
55+
if multilineComment {
56+
continue
57+
}
58+
if line.hasPrefix("@main") {
59+
return true
60+
}
61+
}
62+
return false
63+
}
64+
3765
/// Build description for a Swift module.
3866
public final class SwiftModuleBuildDescription {
3967
/// The package this target belongs to.
@@ -216,40 +244,12 @@ public final class SwiftModuleBuildDescription {
216244
return false
217245
}
218246
// looking into the file content to see if it is using the @main annotation which requires parse-as-library
219-
return (try? self.containsAtMain(fileSystem: self.fileSystem, path: self.sources[0])) ?? false
247+
return (try? containsAtMain(fileSystem: self.fileSystem, path: self.sources[0])) ?? false
220248
default:
221249
return false
222250
}
223251
}
224252

225-
// looking into the file content to see if it is using the @main annotation
226-
// this is not bullet-proof since theoretically the file can contain the @main string for other reasons
227-
// but it is the closest to accurate we can do at this point
228-
func containsAtMain(fileSystem: FileSystem, path: AbsolutePath) throws -> Bool {
229-
let content: String = try self.fileSystem.readFileContents(path)
230-
let lines = content.split(whereSeparator: { $0.isNewline }).map { $0.trimmingCharacters(in: .whitespaces) }
231-
232-
var multilineComment = false
233-
for line in lines {
234-
if line.hasPrefix("//") {
235-
continue
236-
}
237-
if line.hasPrefix("/*") {
238-
multilineComment = true
239-
}
240-
if line.hasSuffix("*/") {
241-
multilineComment = false
242-
}
243-
if multilineComment {
244-
continue
245-
}
246-
if line.hasPrefix("@main") {
247-
return true
248-
}
249-
}
250-
return false
251-
}
252-
253253
/// The filesystem to operate on.
254254
let fileSystem: FileSystem
255255

Sources/Commands/PackageCommands/CompletionCommand.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,18 @@ extension SwiftPackageCommand {
7979
case .listExecutables:
8080
let graph = try await swiftCommandState.loadPackageGraph()
8181
let package = graph.rootPackages[graph.rootPackages.startIndex].underlying
82-
let executables = package.products.filter { $0.type == .executable }
82+
let executables = package.products.filter { $0.type == .executable }.sorted()
8383
for executable in executables {
8484
print(executable.name)
8585
}
8686
case .listSnippets:
8787
let graph = try await swiftCommandState.loadPackageGraph()
8888
let package = graph.rootPackages[graph.rootPackages.startIndex].underlying
89-
let executables = package.modules.filter { $0.type == .snippet }
89+
let executables = package.modules.filter { $0.type == .snippet }.sorted()
9090
for executable in executables {
9191
print(executable.name)
9292
}
9393
}
9494
}
9595
}
96-
}
96+
}

Sources/PackageModel/Module/Module.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ extension Module: Hashable {
292292
}
293293
}
294294

295+
extension Module: Comparable {
296+
public static func < (lhs: Module, rhs: Module) -> Bool {
297+
return lhs.name < rhs.name
298+
}
299+
}
300+
295301
extension Module: CustomStringConvertible {
296302
public var description: String {
297303
return "<\(Swift.type(of: self)): \(name)>"

Sources/PackageModel/Product.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ extension Product: Hashable {
6868
}
6969
}
7070

71+
extension Product: Comparable {
72+
public static func < (lhs: Product, rhs: Product) -> Bool {
73+
lhs.name < rhs.name
74+
}
75+
}
76+
7177
/// The type of product.
7278
public enum ProductType: Equatable, Hashable, Sendable {
7379

Sources/SwiftBuildSupport/PackagePIFBuilder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,13 +451,13 @@ public final class PackagePIFBuilder {
451451
try projectBuilder.makeLibraryProduct(product, type: libraryType)
452452
}
453453

454-
case .executable, .test:
454+
case .executable, .test, .snippet:
455455
try projectBuilder.makeMainModuleProduct(product)
456456

457457
case .plugin:
458458
try projectBuilder.makePluginProduct(product)
459459

460-
case .snippet, .macro:
460+
case .macro:
461461
break // TODO: Double-check what's going on here as we skip snippet modules too (rdar://147705448)
462462
}
463463
}

Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import TSCUtility
1717
import struct Basics.AbsolutePath
1818
import class Basics.ObservabilitySystem
1919
import struct Basics.SourceControlURL
20+
import func Build.containsAtMain
2021

2122
import class PackageModel.BinaryModule
2223
import class PackageModel.Manifest
@@ -55,7 +56,7 @@ extension PackagePIFProjectBuilder {
5556
let synthesizedResourceGeneratingPluginInvocationResults: [PackagePIFBuilder.BuildToolPluginInvocationResult] =
5657
[]
5758

58-
if product.type == .executable {
59+
if [.executable, .snippet].contains(product.type) {
5960
if let customPIFProductType = pifBuilder.delegate.customProductType(forExecutable: product.underlying) {
6061
pifProductType = customPIFProductType
6162
moduleOrProductType = PackagePIFBuilder.ModuleOrProductType(from: customPIFProductType)
@@ -138,6 +139,17 @@ extension PackagePIFProjectBuilder {
138139
settings[.INSTALL_PATH] = "/usr/local/bin"
139140
settings[.LD_RUNPATH_SEARCH_PATHS] = ["$(inherited)", "@executable_path/../lib"]
140141
}
142+
} else if mainModule.type == .snippet {
143+
let hasMainModule: Bool
144+
if let mainModule = product.mainModule {
145+
// Check if any source file in the main module contains @main
146+
hasMainModule = mainModule.sources.paths.contains { (sourcePath: AbsolutePath) in
147+
(try? containsAtMain(fileSystem: pifBuilder.fileSystem, path: sourcePath)) ?? false
148+
}
149+
} else {
150+
hasMainModule = false
151+
}
152+
settings[.SWIFT_DISABLE_PARSE_AS_LIBRARY] = hasMainModule ? "NO" : "YES"
141153
}
142154

143155
let mainTargetDeploymentTargets = mainModule.deploymentTargets(using: pifBuilder.delegate)

0 commit comments

Comments
 (0)