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
30 changes: 30 additions & 0 deletions Sources/SWBCSupport/CLibclang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,18 @@ extern "C" {
*/
void (*clang_experimental_DependencyScannerServiceOptions_setCASOptions)(CXDependencyScannerServiceOptions Opts, CXCASOptions);

/**
* Set the working directory optimization option.
* The dependency scanner service option Opts will indicate to the scanner that
* the current working directory can or cannot be ignored when computing the
* pcms' context hashes. The scanner will then determine if it is safe to
* optimize each module and act accordingly.
*
* \param Value If it is non zero, the option is on. Otherwise the
* option is off.
*/
void (*clang_experimental_DependencyScannerServiceOptions_setCWDOptimization)(CXDependencyScannerServiceOptions Opts, int Value);

/**
* Create a \c CXDependencyScannerService object.
* Must be disposed with \c clang_experimental_DependencyScannerService_dispose_v0().
Expand Down Expand Up @@ -1141,6 +1153,12 @@ extern "C" {
*/
const char *(*clang_experimental_DepGraphModule_getCacheKey)(CXDepGraphModule);

/**
* \returns 1 if the scanner ignores the current working directory when
* computing the module's context hash. Otherwise returns 0.
*/
int (*clang_experimental_DepGraphModule_isCWDIgnored)(CXDepGraphModule);

/**
* \returns the number \c CXDepGraphTUCommand objects in the graph.
*/
Expand Down Expand Up @@ -1410,6 +1428,7 @@ struct LibclangWrapper {
LOOKUP_OPTIONAL(clang_experimental_DependencyScannerServiceOptions_setDependencyMode);
LOOKUP_OPTIONAL_CAS_API(clang_experimental_DependencyScannerServiceOptions_setCASDatabases);
LOOKUP_OPTIONAL(clang_experimental_DependencyScannerServiceOptions_setCASOptions);
LOOKUP_OPTIONAL(clang_experimental_DependencyScannerServiceOptions_setCWDOptimization);
LOOKUP_OPTIONAL(clang_experimental_DependencyScannerService_create_v1);
LOOKUP_OPTIONAL_DEPENDENCY_SCANNER_API(clang_experimental_DependencyScannerService_dispose_v0);
LOOKUP_OPTIONAL_DEPENDENCY_SCANNER_API(clang_experimental_DependencyScannerWorker_create_v0);
Expand All @@ -1429,6 +1448,7 @@ struct LibclangWrapper {
LOOKUP_OPTIONAL(clang_experimental_DepGraphModule_getModuleDeps);
LOOKUP_OPTIONAL(clang_experimental_DepGraphModule_getBuildArguments);
LOOKUP_OPTIONAL(clang_experimental_DepGraphModule_getCacheKey);
LOOKUP_OPTIONAL(clang_experimental_DepGraphModule_isCWDIgnored);
LOOKUP_OPTIONAL(clang_experimental_DepGraphModule_getIncludeTreeID);
LOOKUP_OPTIONAL(clang_experimental_DepGraph_getNumTUCommands);
LOOKUP_OPTIONAL(clang_experimental_DepGraph_getTUCommand);
Expand Down Expand Up @@ -1574,6 +1594,9 @@ struct LibclangScanner {
if (casOpts) {
lib->fns.clang_experimental_DependencyScannerServiceOptions_setCASOptions(opts, casOpts->casOpts);
}
if (lib->fns.clang_experimental_DependencyScannerServiceOptions_setCWDOptimization) {
lib->fns.clang_experimental_DependencyScannerServiceOptions_setCWDOptimization(opts, 1);
}
service = lib->fns.clang_experimental_DependencyScannerService_create_v1(opts);
assert(service && "unable to create service");
lib->fns.clang_experimental_DependencyScannerServiceOptions_dispose(opts);
Expand Down Expand Up @@ -1747,6 +1770,11 @@ extern "C" {
lib->wrapper->fns.clang_experimental_cas_isMaterialized;
}

bool libclang_has_current_working_directory_optimization(libclang_t lib) {
return lib->wrapper->fns.clang_experimental_DepGraphModule_isCWDIgnored &&
lib->wrapper->fns.clang_experimental_DependencyScannerServiceOptions_setCWDOptimization;
}

libclang_casoptions_t libclang_casoptions_create(libclang_t lib) {
auto opts = lib->wrapper->fns.clang_experimental_cas_Options_create();
return new libclang_casoptions_t_{{lib->wrapper, opts}};
Expand Down Expand Up @@ -2055,6 +2083,7 @@ extern "C" {
const char *includeTreeID = lib->fns.clang_experimental_DepGraphModule_getIncludeTreeID
? lib->fns.clang_experimental_DepGraphModule_getIncludeTreeID(depMod)
: nullptr;
bool isCWDIgnored = lib->fns.clang_experimental_DepGraphModule_isCWDIgnored ? lib->fns.clang_experimental_DepGraphModule_isCWDIgnored(depMod) : 0;
LibclangFunctions::CXCStringArray fileDeps = lib->fns.clang_experimental_DepGraphModule_getFileDeps(depMod);
LibclangFunctions::CXCStringArray moduleDeps = lib->fns.clang_experimental_DepGraphModule_getModuleDeps(depMod);
LibclangFunctions::CXCStringArray buildArguments = lib->fns.clang_experimental_DepGraphModule_getBuildArguments(depMod);
Expand All @@ -2063,6 +2092,7 @@ extern "C" {
modules[i].context_hash = contextHash;
modules[i].module_map_path = moduleMapPath;
modules[i].include_tree_id = includeTreeID;
modules[i].is_cwd_ignored = isCWDIgnored;
modules[i].cache_key = cacheKey;
modules[i].file_deps = copyStringSet(fileDeps);
modules[i].module_deps = copyStringSet(moduleDeps);
Expand Down
4 changes: 4 additions & 0 deletions Sources/SWBCSupport/CLibclang.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ typedef struct {
const char *module_map_path;
const char **file_deps;
const char *include_tree_id;
bool is_cwd_ignored;
const char **module_deps;
const char *cache_key;
const char **build_arguments;
Expand Down Expand Up @@ -122,6 +123,9 @@ bool libclang_has_cas_pruning_feature(libclang_t lib);
/// Whether the libclang has CAS up-to-date checking support.
bool libclang_has_cas_up_to_date_checks_feature(libclang_t lib);

/// Whether the libclang has current working directory optimization support.
bool libclang_has_current_working_directory_optimization(libclang_t lib);

/// Create the CAS options object.
libclang_casoptions_t libclang_casoptions_create(libclang_t lib);

Expand Down
5 changes: 5 additions & 0 deletions Sources/SWBCore/LibclangVendored/Libclang.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ public final class Libclang {
public var supportsCASUpToDateChecks: Bool {
libclang_has_cas_up_to_date_checks_feature(lib)
}

public var supportsCurrentWorkingDirectoryOptimization: Bool {
libclang_has_current_working_directory_optimization(lib)
}
}

enum DependencyScanningError: Error {
Expand Down Expand Up @@ -130,6 +134,7 @@ public final class DependencyScanner {
public var module_deps: some Sequence<String> { clang_module_dependency.module_deps.toLazyStringSequence() }
public var cache_key: String? { clang_module_dependency.cache_key.map { String(cString: $0) } }
public var build_arguments: some Sequence<String> { clang_module_dependency.build_arguments.toLazyStringSequence() }
public var is_cwd_ignored: Bool { clang_module_dependency.is_cwd_ignored }
}

public struct Command {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,8 @@ package final class ClangModuleDependencyGraph {
fileDependencies: fileDependencies,
includeTreeID: module.include_tree_id,
moduleDependencies: OrderedSet(moduleDeps),
// Cached builds do not rely on the process working directory, and different scanner working directories should not inhibit task deduplication
workingDirectory: module.cache_key != nil ? Path.root : workingDirectory,
// Cached builds do not rely on the process working directory, and different scanner working directories should not inhibit task deduplication. The same is true if the scanner reports the working directory can be ignored.
workingDirectory: module.cache_key != nil || module.is_cwd_ignored ? Path.root : workingDirectory,
command: DependencyInfo.CompileCommand(cacheKey: module.cache_key, arguments: commandLine),
transitiveIncludeTreeIDs: transitiveIncludeTreeIDs,
transitiveCompileCommandCacheKeys: transitiveCommandCacheKeys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
taskKey: .precompileClangModule(precompileModuleTaskKey),
taskID: state.dynamicTaskBaseID,
singleUse: true,
workingDirectory: dependencyInfo.workingDirectory,
workingDirectory: Path.root,
environment: task.environment,
forTarget: nil,
priority: .preferred,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ final public class PrecompileClangModuleTaskAction: TaskAction, BuildValueValida
taskKey: .precompileClangModule(taskKey),
taskID: taskID,
singleUse: false,
workingDirectory: dependencyInfo.workingDirectory,
workingDirectory: Path.root,
environment: task.environment,
forTarget: task.forTarget,
priority: .preferred,
Expand Down Expand Up @@ -188,7 +188,7 @@ final public class PrecompileClangModuleTaskAction: TaskAction, BuildValueValida
if try ClangCompileTaskAction.replayCachedCommand(
command,
casDBs: casDBs,
workingDirectory: task.workingDirectory,
workingDirectory: dependencyInfo.workingDirectory,
outputDelegate: outputDelegate,
enableDiagnosticRemarks: key.casOptions!.enableDiagnosticRemarks
) {
Expand All @@ -198,7 +198,7 @@ final public class PrecompileClangModuleTaskAction: TaskAction, BuildValueValida

let delegate = TaskProcessDelegate(outputDelegate: outputDelegate)
// The frontend invocations should be unaffected by the environment, pass an empty one.
try await spawn(commandLine: commandLine, environment: [:], workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate)
try await spawn(commandLine: commandLine, environment: [:], workingDirectory: dependencyInfo.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate)

let result = delegate.commandResult ?? .failed
if result == .succeeded {
Expand Down
7 changes: 7 additions & 0 deletions Sources/SWBTestSupport/SkippedTestSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,13 @@ extension Trait where Self == Testing.ConditionTrait {
}
}

package static var requireDependencyScannerWorkingDirectoryOptimization: Self {
enabled {
let libclang = try #require(try await ConditionTraitContext.shared.libclang)
return libclang.supportsCurrentWorkingDirectoryOptimization
}
}

package static var requireCompilationCaching: Self {
enabled("compilation caching is not supported") {
try await ConditionTraitContext.shared.supportsCompilationCaching
Expand Down
107 changes: 107 additions & 0 deletions Tests/SWBBuildSystemTests/ClangExplicitModulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,113 @@ fileprivate struct ClangExplicitModulesTests: CoreBasedTests {
}
}

@Test(.requireSDKs(.macOS), .requireDependencyScanner, .requireDependencyScannerWorkingDirectoryOptimization)
func sharingBetweenProjects() async throws {
try await withTemporaryDirectory { tmpDir in
let testWorkspace = TestWorkspace(
"Test",
sourceRoot: tmpDir.join("Test"),
projects: [
TestProject(
"aProject",
groupTree: TestGroup(
"Sources",
children: [
TestFile("mod_nested.h"),
TestFile("mod.h"),
TestFile("module.modulemap"),
TestFile("file_1.c"),
]),
buildConfigurations: [TestBuildConfiguration(
"Debug",
buildSettings: [
"PRODUCT_NAME": "$(TARGET_NAME)",
"CLANG_ENABLE_MODULES": "YES",
"_EXPERIMENTAL_CLANG_EXPLICIT_MODULES": "YES",
"CLANG_EXPLICIT_MODULES_OUTPUT_PATH": "\(tmpDir.join("clangmodules").str)",
])],
targets: [
TestStandardTarget(
"Library_1",
type: .staticLibrary,
buildPhases: [
TestSourcesBuildPhase(["file_1.c"]),
]),
]), TestProject(
"aProject2",
groupTree: TestGroup(
"Sources",
children: [
TestFile("file_2.c"),
]),
buildConfigurations: [TestBuildConfiguration(
"Debug",
buildSettings: [
"PRODUCT_NAME": "$(TARGET_NAME)",
"CLANG_ENABLE_MODULES": "YES",
"_EXPERIMENTAL_CLANG_EXPLICIT_MODULES": "YES",
"HEADER_SEARCH_PATHS": "$(inherited) \(tmpDir.join("Test/aProject").str)",
"CLANG_EXPLICIT_MODULES_OUTPUT_PATH": "\(tmpDir.join("clangmodules").str)",
])],
targets: [
TestStandardTarget(
"Library_2",
type: .staticLibrary,
buildPhases: [
TestSourcesBuildPhase(["file_2.c"]),
]),
])])

let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false)

try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/module.modulemap")) { stream in
stream <<<
"""
module mod { header "mod.h" }
"""
}

try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/mod.h")) { stream in
stream <<<
"""
void foo(void) {}
"""
}

try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/file_1.c")) { stream in
stream <<<
"""
#include "mod.h"
"""
}

try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject2/file_2.c")) { stream in
stream <<<
"""
#include "mod.h"
"""
}

let parameters = BuildParameters(configuration: "Debug")
let buildRequest = BuildRequest(parameters: parameters, buildTargets: tester.workspace.projects[0].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }) + tester.workspace.projects[1].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }), continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: false, useDryRun: false)

try await tester.checkBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) { results in

for targetName in ["Library_1", "Library_2"] {
results.checkTaskExists(.matchTargetName(targetName), .matchRuleType("ScanDependencies"))
results.checkTaskExists(.matchTargetName(targetName), .matchRuleType("CompileC"))
}

// We should optimize out the working directory and only build one variant of "mod"
results.checkTasks(.matchRulePattern(["PrecompileModule", .contains("mod-")])) { tasks in
#expect(tasks.count == 1)
}

results.checkNoDiagnostics()
}
}
}

@Test(.requireSDKs(.macOS))
func incrementalBuildBasics() async throws {
try await withTemporaryDirectory { tmpDir in
Expand Down