Skip to content

Commit 475fde5

Browse files
neonichumirza-garibovic
authored andcommitted
Introduce a new MODULE_DEPENDENCIES build setting
This is mostly declarative right now, but does inform implicit dependencies.
1 parent 6160b62 commit 475fde5

File tree

8 files changed

+171
-6
lines changed

8 files changed

+171
-6
lines changed

Sources/SWBBuildSystem/DependencyCycleFormatter.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ struct DependencyCycleFormatter {
379379
message = "Target '\(previousTargetName)' has an explicit dependency on Target '\(targetName)'"
380380
case let .implicitBuildPhaseLinkage(filename, _, buildPhase)?:
381381
message = "Target '\(previousTargetName)' has an implicit dependency on Target '\(targetName)' because '\(previousTargetName)' references the file '\(filename)' in the build phase '\(buildPhase)'"
382-
case let .implicitBuildSettingLinkage(settingName, options)?:
382+
case let .implicitBuildSetting(settingName, options)?:
383383
message = "Target '\(previousTargetName)' has an implicit dependency on Target '\(targetName)' because '\(previousTargetName)' defines the option '\(options.joined(separator: " "))' in the build setting '\(settingName)'"
384384
case let .impliedByTransitiveDependencyViaRemovedTargets(intermediateTargetName: intermediateTargetName):
385385
message = "Target '\(previousTargetName)' has a dependency on Target '\(targetName)' via its transitive dependency through '\(intermediateTargetName)'"
@@ -501,7 +501,7 @@ struct DependencyCycleFormatter {
501501
suffix = " via the “Target Dependencies“ build phase"
502502
case let .implicitBuildPhaseLinkage(filename, _, buildPhase)?:
503503
suffix = " because the scheme has implicit dependencies enabled and the Target '\(lastTargetsName)' references the file '\(filename)' in the build phase '\(buildPhase)'"
504-
case let .implicitBuildSettingLinkage(settingName, options)?:
504+
case let .implicitBuildSetting(settingName, options)?:
505505
suffix = " because the scheme has implicit dependencies enabled and the Target '\(lastTargetsName)' defines the options '\(options.joined(separator: " "))' in the build setting '\(settingName)'"
506506
case let .impliedByTransitiveDependencyViaRemovedTargets(intermediateTargetName: intermediateTargetName):
507507
suffix = " via its transitive dependency through '\(intermediateTargetName)'"

Sources/SWBCore/LinkageDependencyResolver.swift

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
public import SWBUtil
1414
import SWBMacro
15+
internal import Foundation
1516

1617
/// A completely resolved graph of configured targets for use in a build.
1718
public struct TargetLinkageGraph: TargetGraph {
@@ -79,11 +80,15 @@ actor LinkageDependencyResolver {
7980
/// Sets of targets mapped by product name stem.
8081
private let targetsByProductNameStem: [String: Set<StandardTarget>]
8182

83+
/// Sets of targets mapped by module name (computed using parameters from the build request).
84+
private let targetsByUnconfiguredModuleName: [String: Set<StandardTarget>]
85+
8286
internal let resolver: DependencyResolver
8387

8488
init(workspaceContext: WorkspaceContext, buildRequest: BuildRequest, buildRequestContext: BuildRequestContext, delegate: any TargetDependencyResolverDelegate) {
8589
var targetsByProductName = [String: Set<StandardTarget>]()
8690
var targetsByProductNameStem = [String: Set<StandardTarget>]()
91+
var targetsByUnconfiguredModuleName = [String: Set<StandardTarget>]()
8792
for case let target as StandardTarget in workspaceContext.workspace.allTargets {
8893
// FIXME: We are relying on the product reference name being constant here. This is currently true, given how our path resolver works, but it is possible to construct an Xcode project for which this doesn't work (Xcode doesn't, however, handle that situation very well). We should resolve this: <rdar://problem/29410050> Swift Build doesn't support product references with non-constant basenames
8994

@@ -95,11 +100,17 @@ actor LinkageDependencyResolver {
95100
if let stem = Path(productName).stem, stem != productName {
96101
targetsByProductNameStem[stem, default: []].insert(target)
97102
}
103+
104+
let moduleName = buildRequestContext.getCachedSettings(buildRequest.parameters, target: target).globalScope.evaluate(BuiltinMacros.PRODUCT_MODULE_NAME)
105+
if !moduleName.isEmpty {
106+
targetsByUnconfiguredModuleName[moduleName, default: []].insert(target)
107+
}
98108
}
99109

100110
// Remember the mappings we created.
101111
self.targetsByProductName = targetsByProductName
102112
self.targetsByProductNameStem = targetsByProductNameStem
113+
self.targetsByUnconfiguredModuleName = targetsByUnconfiguredModuleName
103114

104115
resolver = DependencyResolver(workspaceContext: workspaceContext, buildRequest: buildRequest, buildRequestContext: buildRequestContext, delegate: delegate)
105116
}
@@ -333,7 +344,7 @@ actor LinkageDependencyResolver {
333344

334345
// Skip this flag if its corresponding product name is the same as the product of one of our explicit dependencies. This effectively matches the flag to an explicit dependency.
335346
if !productNamesOfExplicitDependencies.contains(productName), let implicitDependency = await implicitDependency(forProductName: productName, from: configuredTarget, imposedParameters: imposedParameters, source: .frameworkLinkerFlag(flag: flag, frameworkName: stem, buildSetting: macro)) {
336-
await result.append(ResolvedTargetDependency(target: implicitDependency, reason: .implicitBuildSettingLinkage(settingName: macro.name, options: [flag, stem])))
347+
await result.append(ResolvedTargetDependency(target: implicitDependency, reason: .implicitBuildSetting(settingName: macro.name, options: [flag, stem])))
337348
return
338349
}
339350
} addLibrary: { macro, prefix, stem in
@@ -349,7 +360,7 @@ actor LinkageDependencyResolver {
349360
if productNamesOfExplicitDependencies.intersection(productNames).isEmpty {
350361
for productName in productNames {
351362
if let implicitDependency = await implicitDependency(forProductName: productName, from: configuredTarget, imposedParameters: imposedParameters, source: .libraryLinkerFlag(flag: prefix, libraryName: stem, buildSetting: macro)) {
352-
await result.append(ResolvedTargetDependency(target: implicitDependency, reason: .implicitBuildSettingLinkage(settingName: macro.name, options: ["\(prefix)\(stem)"])))
363+
await result.append(ResolvedTargetDependency(target: implicitDependency, reason: .implicitBuildSetting(settingName: macro.name, options: ["\(prefix)\(stem)"])))
353364
// We only match one.
354365
return
355366
}
@@ -360,6 +371,16 @@ actor LinkageDependencyResolver {
360371
}
361372
}
362373

374+
let moduleNamesOfExplicitDependencies = Set<String>(immediateDependencies.compactMap{
375+
buildRequestContext.getCachedSettings($0.parameters, target: $0.target).globalScope.evaluate(BuiltinMacros.PRODUCT_MODULE_NAME)
376+
})
377+
378+
for moduleDependencyName in configuredTargetSettings.moduleDependencies.map { $0.name } {
379+
if !moduleNamesOfExplicitDependencies.contains(moduleDependencyName), let implicitDependency = await implicitDependency(forModuleName: moduleDependencyName, from: configuredTarget, imposedParameters: imposedParameters, source: .moduleDependency(name: moduleDependencyName, buildSetting: BuiltinMacros.MODULE_DEPENDENCIES)) {
380+
await result.append(ResolvedTargetDependency(target: implicitDependency, reason: .implicitBuildSetting(settingName: BuiltinMacros.MODULE_DEPENDENCIES.name, options: [moduleDependencyName])))
381+
}
382+
}
383+
363384
return await result.value
364385
}
365386

@@ -444,6 +465,30 @@ actor LinkageDependencyResolver {
444465
return resolver.lookupConfiguredTarget(candidateDependencyTarget, parameters: candidateParameters, imposedParameters: effectiveImposedParameters)
445466
}
446467

468+
private func implicitDependency(forModuleName moduleName: String, from configuredTarget: ConfiguredTarget, imposedParameters: SpecializationParameters?, source: ImplicitDependencySource) async -> ConfiguredTarget? {
469+
let candidateConfiguredTargets = await (targetsByUnconfiguredModuleName[moduleName] ?? []).asyncMap { [self] candidateTarget -> ConfiguredTarget? in
470+
// Prefer overriding build parameters from the build request, if present.
471+
let buildParameters = resolver.buildParametersByTarget[candidateTarget] ?? configuredTarget.parameters
472+
473+
// Validate the module name using concrete parameters.
474+
let configuredModuleName = buildRequestContext.getCachedSettings(buildParameters, target: candidateTarget).globalScope.evaluate(BuiltinMacros.PRODUCT_MODULE_NAME)
475+
if configuredModuleName != moduleName {
476+
return nil
477+
}
478+
479+
// Get a configured target for this target, and use it as the implicit dependency.
480+
if let candidateConfiguredTarget = await implicitDependency(candidate: candidateTarget, parameters: buildParameters, isValidFor: configuredTarget, imposedParameters: imposedParameters, resolver: resolver) {
481+
return candidateConfiguredTarget
482+
}
483+
484+
return nil
485+
}.compactMap { $0 }.sorted()
486+
487+
emitAmbiguousImplicitDependencyWarningIfNeeded(for: configuredTarget, dependencies: candidateConfiguredTargets, from: source)
488+
489+
return candidateConfiguredTargets.first
490+
}
491+
447492
/// Search for an implicit dependency by full product name.
448493
nonisolated private func implicitDependency(forProductName productName: String, from configuredTarget: ConfiguredTarget, imposedParameters: SpecializationParameters?, source: ImplicitDependencySource) async -> ConfiguredTarget? {
449494
let candidateConfiguredTargets = await (targetsByProductName[productName] ?? []).asyncMap { [self] candidateTarget -> ConfiguredTarget? in
@@ -506,6 +551,9 @@ actor LinkageDependencyResolver {
506551
/// The dependency's product name matched the basename of a build file in the target's build phases.
507552
case productNameStem(_ stem: String, buildFile: BuildFile, buildPhase: BuildPhase)
508553

554+
/// The dependency's module name matched a declared module dependency of the client target.
555+
case moduleDependency(name: String, buildSetting: MacroDeclaration)
556+
509557
var valueForDisplay: String {
510558
switch self {
511559
case let .frameworkLinkerFlag(flag, frameworkName, _):
@@ -516,6 +564,8 @@ actor LinkageDependencyResolver {
516564
return "product reference '\(productName)'"
517565
case let .productNameStem(stem, _, _):
518566
return "product bundle executable reference '\(stem)'"
567+
case let .moduleDependency(name, _):
568+
return "module dependency \(name)"
519569
}
520570
}
521571
}
@@ -530,6 +580,8 @@ actor LinkageDependencyResolver {
530580
case let .productReference(_, buildFile, buildPhase),
531581
let .productNameStem(_, buildFile, buildPhase):
532582
location = .buildFile(buildFileGUID: buildFile.guid, buildPhaseGUID: buildPhase.guid, targetGUID: configuredTarget.target.guid)
583+
case let .moduleDependency(_, buildSetting):
584+
location = .buildSettings([buildSetting])
533585
}
534586

535587
delegate.emit(.overrideTarget(configuredTarget), SWBUtil.Diagnostic(behavior: .warning, location: location, data: DiagnosticData("Multiple targets match implicit dependency for \(source.valueForDisplay). Consider adding an explicit dependency on the intended target to resolve this ambiguity.", component: .targetIntegrity), childDiagnostics: candidateConfiguredTargets.map({ dependency -> Diagnostic in

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,7 @@ public final class BuiltinMacros {
865865
public static let MODULEMAP_PATH = BuiltinMacros.declareStringMacro("MODULEMAP_PATH")
866866
public static let MODULEMAP_PRIVATE_FILE = BuiltinMacros.declareStringMacro("MODULEMAP_PRIVATE_FILE")
867867
public static let MODULES_FOLDER_PATH = BuiltinMacros.declarePathMacro("MODULES_FOLDER_PATH")
868+
public static let MODULE_DEPENDENCIES = BuiltinMacros.declareStringListMacro("MODULE_DEPENDENCIES")
868869
public static let MODULE_VERIFIER_KIND = BuiltinMacros.declareEnumMacro("MODULE_VERIFIER_KIND") as EnumMacroDeclaration<ModuleVerifierKind>
869870
public static let MODULE_VERIFIER_LSV = BuiltinMacros.declareBooleanMacro("MODULE_VERIFIER_LSV")
870871
public static let MODULE_VERIFIER_SUPPORTED_LANGUAGES = BuiltinMacros.declareStringListMacro("MODULE_VERIFIER_SUPPORTED_LANGUAGES")
@@ -1944,6 +1945,7 @@ public final class BuiltinMacros {
19441945
MODULEMAP_PRIVATE_FILE,
19451946
MODULES_FOLDER_PATH,
19461947
MODULE_CACHE_DIR,
1948+
MODULE_DEPENDENCIES,
19471949
MODULE_NAME,
19481950
MODULE_START,
19491951
MODULE_STOP,

Sources/SWBCore/Settings/Settings.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5331,3 +5331,23 @@ extension MacroEvaluationScope {
53315331
}
53325332
}
53335333
}
5334+
5335+
extension Settings {
5336+
public struct ModuleDependencyInfo {
5337+
let name: String
5338+
let isPublic: Bool
5339+
}
5340+
5341+
public var moduleDependencies: [ModuleDependencyInfo] {
5342+
self.globalScope.evaluate(BuiltinMacros.MODULE_DEPENDENCIES).compactMap {
5343+
let components = $0.components(separatedBy: " ")
5344+
guard let name = components.last else {
5345+
return nil
5346+
}
5347+
return ModuleDependencyInfo(
5348+
name: name,
5349+
isPublic: components.count > 1 && components.first == "public"
5350+
)
5351+
}
5352+
}
5353+
}

Sources/SWBCore/Specs/CoreBuildSystem.xcspec

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,16 @@ When `GENERATE_INFOPLIST_FILE` is enabled, sets the value of the [CFBundleIdenti
15971597
sdk,
15981598
);
15991599
},
1600+
{
1601+
Name = "MODULE_DEPENDENCIES";
1602+
Type = StringList;
1603+
Category = BuildOptions;
1604+
DefaultValue = "";
1605+
ConditionFlavors = (
1606+
arch,
1607+
sdk,
1608+
);
1609+
},
16001610
{
16011611
Name = "GENERATE_PRELINK_OBJECT_FILE";
16021612
Type = Boolean;

Sources/SWBCore/Specs/en.lproj/CoreBuildSystem.strings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ Generally you should not specify an order file in Debug or Development configura
397397
"[OTHER_LDFLAGS]-name" = "Other Linker Flags";
398398
"[OTHER_LDFLAGS]-description" = "Options defined in this setting are passed to invocations of the linker.";
399399

400+
"[MODULE_DEPENDENCIES]-name" = "Module Dependencies";
401+
"[MODULE_DEPENDENCIES]-description" = "Other modules this target depends on.";
402+
400403
"[OTHER_LIBTOOLFLAGS]-name" = "Other Librarian Flags";
401404
"[OTHER_LIBTOOLFLAGS]-description" = "Options defined in this setting are passed to all invocations of the archive librarian, which is used to generate static libraries.";
402405

Sources/SWBCore/TargetDependencyResolver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public enum TargetDependencyReason: Sendable {
2525
/// - parameter buildPhase: The name of the build phase used to find this linkage. This is used for diagnostics.
2626
case implicitBuildPhaseLinkage(filename: String, buildableItem: BuildFile.BuildableItem, buildPhase: String)
2727
/// The upstream target has an implicit dependency on the target due to options being passed via a build setting.
28-
case implicitBuildSettingLinkage(settingName: String, options: [String])
28+
case implicitBuildSetting(settingName: String, options: [String])
2929
/// The upstream target has a transitive dependency on the target via target(s) which were removed from the build graph.
3030
case impliedByTransitiveDependencyViaRemovedTargets(intermediateTargetName: String)
3131
}
@@ -213,7 +213,7 @@ public struct TargetBuildGraph: TargetGraph, Sendable {
213213
dependencyString = "Explicit dependency on \(dependencyDescription)"
214214
case .implicitBuildPhaseLinkage(filename: let filename, buildableItem: _, buildPhase: let buildPhase):
215215
dependencyString = "Implicit dependency on \(dependencyDescription) via file '\(filename)' in build phase '\(buildPhase)'"
216-
case .implicitBuildSettingLinkage(settingName: let settingName, options: let options):
216+
case .implicitBuildSetting(settingName: let settingName, options: let options):
217217
dependencyString = "Implicit dependency on \(dependencyDescription) via options '\(options.joined(separator: " "))' in build setting '\(settingName)'"
218218
case .impliedByTransitiveDependencyViaRemovedTargets(let intermediateTargetName):
219219
dependencyString = "Dependency on \(dependencyDescription) via transitive dependency through '\(intermediateTargetName)'"

0 commit comments

Comments
 (0)