Skip to content

Add prepare for index experimental build argument #7574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ Package.resolved
.docc-build
.vscode
Utilities/InstalledSwiftPMConfiguration/config.json
.devcontainer
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,17 @@ public final class SwiftTargetBuildDescription {
args += ["-emit-module-interface-path", self.parseableModuleInterfaceOutputPath.pathString]
}

if self.defaultBuildParameters.prepareForIndexing {
args += [
"-Xfrontend", "-enable-library-evolution",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is -enable-library-evolution needed here now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was waiting for an update toolchain. There's one there now. Should be ready to remove.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind updating the diff accordingly before merging?

Copy link
Member Author

@dschaefer2 dschaefer2 May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. That will be the last piece. Once I can make sure it works 😀

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the only thing blocking the PR from being merged, I’d prefer to get the PR merged with -enable-library-evolution still set because it could unblock me to test preparation in sourcekit-lsp. I know that I’ll get spurious warnings because of library evolution but I can ignore those for now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. As mentioned above, I'm not even sure if the change that was made even fixes it. I have one more fix Ben B mentioned about hiding the flag and then I'll kick off the tests.

"-Xfrontend", "-experimental-skip-all-function-bodies",
"-Xfrontend", "-experimental-lazy-typecheck",
"-Xfrontend", "-experimental-skip-non-exportable-decls",
"-Xfrontend", "-experimental-allow-module-with-compiler-errors",
"-Xfrontend", "-empty-abi-descriptor"
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also try out -experimental-lazy-typecheck and -experimental-skip-non-exportable-decls (sorry, meant to send these to you earlier). They're newer than the others, but should hopefully skip a reasonable amount of typechecking.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. Currently just trying to get all the xplatform tests passing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, those two chopped my previous prepare time in half for sourcekit-lsp which happened in 30 seconds versus 95 for the debug build.

I had to enable-library-evolution for those to work, but that should be fine?

Copy link
Contributor

@bnbarham bnbarham May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, enabling library evolution would result in different diagnostics (eg. need to add @unknown case for switching over non-frozen enums). @tshortli is library evolution a hard requirement for these?

Wow, those two chopped my previous prepare time in half for sourcekit-lsp which happened in 30 seconds versus 95 for the debug build.

Just to be clear, 95s is the full debug build and a prepare used to be 60s without these and is now 30s?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, 95s is the full debug build and a prepare used to be 60s without these and is now 30s?

Correct. For sourcekit-lsp. I've been bouncing around. I need to do a more complete benchmark on a few different projects to get a better picture.

Copy link
Contributor

@bnbarham bnbarham May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although that restriction is about making sure the emitted module is suitable for use during compilation of downstream dependents

Yeah, sounds like we could probably gate the library evolution requirement check on -experimental-skip-all-function-bodies since we don't produce a swiftmodule that would be usable for compilation in that case anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've lifted the restriction so that -enable-library-evolution is no longer required when indexing:
swiftlang/swift#73905
swiftlang/swift#73905

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks @tshortli!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still getting

<unknown>:0: warning: ignoring -experimental-skip-non-exportable-decls (requires -enable-library-evolution)
<unknown>:0: warning: ignoring -experimental-lazy-typecheck (requires -enable-library-evolution)

in the latest 6.0 toolchain from May 26. Not sure this change got in after that build?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would have been just after:
swiftlang/swift#73906

}

args += self.defaultBuildParameters.toolchain.extraFlags.swiftCompilerFlags
// User arguments (from -Xswiftc) should follow generated arguments to allow user overrides
args += self.defaultBuildParameters.flags.swiftCompilerFlags
Expand Down
21 changes: 16 additions & 5 deletions Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ extension LLBuildManifestBuilder {
let inputs = try self.computeSwiftCompileCmdInputs(target)

// Outputs.
let objectNodes = try target.objects.map(Node.file)
let objectNodes = target.defaultBuildParameters.prepareForIndexing ? [] : try target.objects.map(Node.file)
let moduleNode = Node.file(target.moduleOutputPath)
let cmdOutputs = objectNodes + [moduleNode]

Expand Down Expand Up @@ -397,7 +397,8 @@ extension LLBuildManifestBuilder {
fileList: target.sourcesFileListPath,
isLibrary: isLibrary,
wholeModuleOptimization: target.defaultBuildParameters.configuration == .release,
outputFileMapPath: try target.writeOutputFileMap() // FIXME: Eliminate side effect.
outputFileMapPath: try target.writeOutputFileMap(), // FIXME: Eliminate side effect.
prepareForIndexing: target.defaultBuildParameters.prepareForIndexing
)
}

Expand All @@ -417,6 +418,8 @@ extension LLBuildManifestBuilder {
inputs.append(resourcesNode)
}

let prepareForIndexing = target.defaultBuildParameters.prepareForIndexing

func addStaticTargetInputs(_ target: ResolvedModule) throws {
// Ignore C Modules.
if target.underlying is SystemLibraryTarget { return }
Expand All @@ -428,7 +431,7 @@ extension LLBuildManifestBuilder {
if target.underlying is ProvidedLibraryTarget { return }

// Depend on the binary for executable targets.
if target.type == .executable {
if target.type == .executable && !prepareForIndexing {
// FIXME: Optimize.
let product = try plan.graph.allProducts.first {
try $0.type == .executable && $0.executableTarget.id == target.id
Expand All @@ -446,8 +449,16 @@ extension LLBuildManifestBuilder {
case .swift(let target)?:
inputs.append(file: target.moduleOutputPath)
case .clang(let target)?:
for object in try target.objects {
inputs.append(file: object)
if prepareForIndexing {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on why this is necessary? This is handling a clang target, supposedly preparation doesn't apply here at all and these object files are produced by other clang targets?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the normal case this adds a dependency on the object files from the clang modules to force a rebuild when they change. In preparation, there are no object files generated so this was a haphazard attempt to propagate the dependencies to their sources. This should actually be the headers. Let me give this one a bit more thought.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After giving this more thought, I need to keep the clang commands around so I can map the swiftmodule dependencies on C source files so that the modules get re-prepared when the C files change. I'll just replace them with phony commands so they don't actually get built.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking at the clang commands, I realized that the header dependencies are coming from the generated .d files. Since I'm not running the compiler they won't be there. So my original idea of adding the dependencies on the headers instead of the object files seems to be working.

// In preparation, we're only building swiftmodules
// propagate the dependency to the header files in this target
for header in target.clangTarget.headers {
inputs.append(file: header)
}
} else {
for object in try target.objects {
inputs.append(file: object)
}
}
case nil:
throw InternalError("unexpected: target \(target) not in target map \(self.plan.targetMap)")
Expand Down
35 changes: 35 additions & 0 deletions Sources/Build/BuildManifest/LLBuildManifestBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,41 @@ public class LLBuildManifestBuilder {
return self.manifest
}

package func generatePrepareManifest(at path: AbsolutePath) throws -> LLBuildManifest {
self.swiftGetVersionFiles.removeAll()

self.manifest.createTarget(TargetKind.main.targetName)
self.manifest.createTarget(TargetKind.test.targetName)
self.manifest.defaultTarget = TargetKind.main.targetName

addPackageStructureCommand()

for (_, description) in self.plan.targetMap {
switch description {
case .swift(let desc):
try self.createSwiftCompileCommand(desc)
case .clang(let desc):
// Need the clang targets for tools
if desc.target.buildTriple == .tools {
try self.createClangCompileCommand(desc)
}
}
}

for (_, description) in self.plan.productMap {
// Need to generate macro products
switch description.product.type {
case .macro, .plugin:
try self.createProductCommand(description)
default:
break
}
}

try LLBuildManifestWriter.write(self.manifest, at: path, fileSystem: self.fileSystem)
return self.manifest
}

func addNode(_ node: Node, toTarget targetKind: TargetKind) {
self.manifest.addNode(node, toTarget: targetKind.targetName)
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/Build/BuildOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,9 @@ extension BuildDescription {
) throws -> (BuildDescription, LLBuildManifest) {
// Generate the llbuild manifest.
let llbuild = LLBuildManifestBuilder(plan, disableSandboxForPluginCommands: disableSandboxForPluginCommands, fileSystem: fileSystem, observabilityScope: observabilityScope)
let buildManifest = try llbuild.generateManifest(at: plan.destinationBuildParameters.llbuildManifest)
let buildManifest = plan.destinationBuildParameters.prepareForIndexing
? try llbuild.generatePrepareManifest(at: plan.destinationBuildParameters.llbuildManifest)
: try llbuild.generateManifest(at: plan.destinationBuildParameters.llbuildManifest)

let swiftCommands = llbuild.manifest.getCmdToolMap(kind: SwiftCompilerTool.self)
let swiftFrontendCommands = llbuild.manifest.getCmdToolMap(kind: SwiftFrontendTool.self)
Expand Down
3 changes: 3 additions & 0 deletions Sources/CoreCommands/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,9 @@ public struct BuildOptions: ParsableArguments {
@Flag(help: "Enable or disable indexing-while-building feature")
public var indexStoreMode: StoreMode = .autoIndexStore

@Flag(name: .customLong("experimental-prepare-for-indexing"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a good idea to have this hidden (noticed in swiftlang/sourcekit-lsp#1373)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. This option isn't really useful to humans :).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

var prepareForIndexing: Bool = false

/// Whether to enable generation of `.swiftinterface`s alongside `.swiftmodule`s.
@Flag(name: .customLong("enable-parseable-module-interfaces"))
public var shouldEnableParseableModuleInterfaces: Bool = false
Expand Down
6 changes: 4 additions & 2 deletions Sources/CoreCommands/SwiftCommandState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ public final class SwiftCommandState {
when building on macOS.
"""

private func _buildParams(toolchain: UserToolchain) throws -> BuildParameters {
private func _buildParams(toolchain: UserToolchain, prepareForIndexing: Bool? = nil) throws -> BuildParameters {
let triple = toolchain.targetTriple

let dataPath = self.scratchDirectory.appending(
Expand All @@ -748,6 +748,7 @@ public final class SwiftCommandState {
sanitizers: options.build.enabledSanitizers,
indexStoreMode: options.build.indexStoreMode.buildParameter,
isXcodeBuildSystemEnabled: options.build.buildSystem == .xcode,
prepareForIndexing: prepareForIndexing ?? options.build.prepareForIndexing,
debuggingParameters: .init(
debugInfoFormat: options.build.debugInfoFormat.buildParameter,
triple: triple,
Expand Down Expand Up @@ -796,7 +797,8 @@ public final class SwiftCommandState {

private lazy var _toolsBuildParameters: Result<BuildParameters, Swift.Error> = {
Result(catching: {
try _buildParams(toolchain: self.getHostToolchain())
// Ensure prepare for indexing is disable for tools
try _buildParams(toolchain: self.getHostToolchain(), prepareForIndexing: false)
})
}()

Expand Down
6 changes: 4 additions & 2 deletions Sources/LLBuildManifest/LLBuildManifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ public struct LLBuildManifest {
fileList: AbsolutePath,
isLibrary: Bool,
wholeModuleOptimization: Bool,
outputFileMapPath: AbsolutePath
outputFileMapPath: AbsolutePath,
prepareForIndexing: Bool
) {
assert(commands[name] == nil, "already had a command named '\(name)'")
let tool = SwiftCompilerTool(
Expand All @@ -386,7 +387,8 @@ public struct LLBuildManifest {
fileList: fileList,
isLibrary: isLibrary,
wholeModuleOptimization: wholeModuleOptimization,
outputFileMapPath: outputFileMapPath
outputFileMapPath: outputFileMapPath,
prepareForIndexing: prepareForIndexing
)
commands[name] = Command(name: name, tool: tool)
}
Expand Down
10 changes: 8 additions & 2 deletions Sources/LLBuildManifest/Tools.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ public struct SwiftCompilerTool: ToolProtocol {
public var isLibrary: Bool
public var wholeModuleOptimization: Bool
public var outputFileMapPath: AbsolutePath
public var prepareForIndexing: Bool

init(
inputs: [Node],
Expand All @@ -287,7 +288,8 @@ public struct SwiftCompilerTool: ToolProtocol {
fileList: AbsolutePath,
isLibrary: Bool,
wholeModuleOptimization: Bool,
outputFileMapPath: AbsolutePath
outputFileMapPath: AbsolutePath,
prepareForIndexing: Bool
) {
self.inputs = inputs
self.outputs = outputs
Expand All @@ -304,6 +306,7 @@ public struct SwiftCompilerTool: ToolProtocol {
self.isLibrary = isLibrary
self.wholeModuleOptimization = wholeModuleOptimization
self.outputFileMapPath = outputFileMapPath
self.prepareForIndexing = prepareForIndexing
}

var description: String {
Expand Down Expand Up @@ -334,7 +337,10 @@ public struct SwiftCompilerTool: ToolProtocol {
} else {
arguments += ["-incremental"]
}
arguments += ["-c", "@\(self.fileList.pathString)"]
if !prepareForIndexing {
arguments += ["-c"]
}
arguments += ["@\(self.fileList.pathString)"]
arguments += ["-I", importPath.pathString]
arguments += otherArguments
return arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ extension BuildParameters {

/// The debugging strategy according to the current build parameters.
public var debuggingStrategy: DebuggingStrategy? {
guard configuration == .debug else {
guard configuration == .debug, !prepareForIndexing else {
return nil
}

Expand Down
5 changes: 5 additions & 0 deletions Sources/SPMBuildCore/BuildParameters/BuildParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ public struct BuildParameters: Encodable {

public var shouldSkipBuilding: Bool

/// Do minimal build to prepare for indexing
public var prepareForIndexing: Bool

/// Build parameters related to debugging.
public var debuggingParameters: Debugging

Expand Down Expand Up @@ -131,6 +134,7 @@ public struct BuildParameters: Encodable {
indexStoreMode: IndexStoreMode = .auto,
isXcodeBuildSystemEnabled: Bool = false,
shouldSkipBuilding: Bool = false,
prepareForIndexing: Bool = false,
debuggingParameters: Debugging? = nil,
driverParameters: Driver = .init(),
linkingParameters: Linking = .init(),
Expand Down Expand Up @@ -185,6 +189,7 @@ public struct BuildParameters: Encodable {
self.indexStoreMode = indexStoreMode
self.isXcodeBuildSystemEnabled = isXcodeBuildSystemEnabled
self.shouldSkipBuilding = shouldSkipBuilding
self.prepareForIndexing = prepareForIndexing
self.driverParameters = driverParameters
self.linkingParameters = linkingParameters
self.outputParameters = outputParameters
Expand Down
4 changes: 3 additions & 1 deletion Sources/SPMTestSupport/MockBuildTestHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ public func mockBuildParameters(
useExplicitModuleBuild: Bool = false,
linkerDeadStrip: Bool = true,
linkTimeOptimizationMode: BuildParameters.LinkTimeOptimizationMode? = nil,
omitFramePointers: Bool? = nil
omitFramePointers: Bool? = nil,
prepareForIndexing: Bool = false
) -> BuildParameters {
try! BuildParameters(
dataPath: buildPath ?? AbsolutePath("/path/to/build").appending(triple.tripleString),
Expand All @@ -98,6 +99,7 @@ public func mockBuildParameters(
pkgConfigDirectories: [],
workers: 3,
indexStoreMode: indexStoreMode,
prepareForIndexing: prepareForIndexing,
debuggingParameters: .init(
triple: triple,
shouldEnableDebuggingEntitlement: config == .debug,
Expand Down
62 changes: 62 additions & 0 deletions Tests/BuildTests/PrepareForIndexTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (C) 2024 Apple Inc. All rights reserved.
//
// This document is the property of Apple Inc.
// It is considered confidential and proprietary.
//
// This document may not be reproduced or transmitted in any form,
// in whole or in part, without the express written permission of
// Apple Inc.

import Build
import Foundation
import LLBuildManifest
@_spi(SwiftPMInternal)
import SPMTestSupport
import TSCBasic
import XCTest

class PrepareForIndexTests: XCTestCase {
func testPrepare() throws {
let (graph, fs, scope) = try macrosPackageGraph()

let plan = try BuildPlan(
destinationBuildParameters: mockBuildParameters(prepareForIndexing: true),
toolsBuildParameters: mockBuildParameters(prepareForIndexing: false),
graph: graph,
fileSystem: fs,
observabilityScope: scope
)

let builder = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: scope)
let manifest = try builder.generatePrepareManifest(at: "/manifest")

// Make sure we're building the swift modules
let outputs = manifest.commands.flatMap(\.value.tool.outputs).map(\.name)
XCTAssertTrue(outputs.contains(where: { $0.hasSuffix(".swiftmodule")}))

// Ensure swiftmodules built with correct arguments
let coreCommands = manifest.commands.values.filter({
$0.tool.outputs.contains(where: {
$0.name.hasSuffix("debug/Core.build/Core.swiftmodule")
})
})
XCTAssertEqual(coreCommands.count, 1)
let coreSwiftc = try XCTUnwrap(coreCommands.first?.tool as? SwiftCompilerTool)
XCTAssertTrue(coreSwiftc.otherArguments.contains("-experimental-skip-all-function-bodies"))

// Ensure tools are built normally
let toolCommands = manifest.commands.values.filter({
$0.tool.outputs.contains(where: {
$0.name.hasSuffix("debug/Modules-tool/SwiftSyntax.swiftmodule")
})
})
XCTAssertEqual(toolCommands.count, 1)
let toolSwiftc = try XCTUnwrap(toolCommands.first?.tool as? SwiftCompilerTool)
XCTAssertFalse(toolSwiftc.otherArguments.contains("-experimental-skip-all-function-bodies"))

// Make sure only object files for tools are built
XCTAssertTrue(outputs.filter({ $0.hasSuffix(".o") }).allSatisfy({ $0.contains("-tool.build/")}),
"outputs:\n\t\(outputs.filter({ $0.hasSuffix(".o") }).joined(separator: "\n\t"))"
)
}
}