in Sharpmake/Project.Configuration.cs [3512:3949]
internal void Link(Builder builder)
{
Trace.Assert(_linkState == LinkState.NotLinked);
_linkState = LinkState.Linking;
if (builder.DumpDependencyGraph && !Project.IsFastBuildAll)
{
DependencyTracker.Instance.AddDependency(DependencyType.Public, Project, this, UnResolvedPublicDependencies, _dependenciesSetting);
DependencyTracker.Instance.AddDependency(DependencyType.Private, Project, this, UnResolvedPrivateDependencies, _dependenciesSetting);
}
// Check if we need to add dependencies on libs that we compile (in the current solution)
bool explicitDependenciesGlobal = true;
if (IsFastBuild)
{
explicitDependenciesGlobal = Sharpmake.Options.GetObject<Options.Vc.Linker.UseLibraryDependencyInputs>(this) != Sharpmake.Options.Vc.Linker.UseLibraryDependencyInputs.Enable;
}
else
{
explicitDependenciesGlobal = Sharpmake.Options.GetObject<Options.Vc.Linker.LinkLibraryDependencies>(this) != Sharpmake.Options.Vc.Linker.LinkLibraryDependencies.Enable;
}
// create a tree of dependency from this configuration
DependencyNode rootNode = BuildDependencyNodeTree(builder, this);
HashSet<Configuration> resolvedPublicDependencies = new HashSet<Configuration>();
HashSet<Configuration> resolvedPrivateDependencies = new HashSet<Configuration>();
var resolvedDotNetPublicDependencies = new HashSet<DotNetDependency>();
var resolvedDotNetPrivateDependencies = new HashSet<DotNetDependency>();
// Keep track of all configurations that have been swapped to dll as we don't want to include them in the final solution
HashSet<Configuration> configurationsSwappedToDll = null;
// We also keep track of configurations that have been added explicitly without swapping, to make sure the project is still included in the solution
HashSet<Configuration> configurationsStillUsedAsNotSwappedToDll = null;
var visitedNodes = new Dictionary<DependencyNode, List<PropagationSettings>>();
var visitingNodes = new Stack<Tuple<DependencyNode, PropagationSettings>>();
visitingNodes.Push(
Tuple.Create(
rootNode,
new PropagationSettings(
DependencySetting.Default,
isImmediate: true,
hasPublicPathToRoot: true,
hasPublicPathToImmediate: true,
goesThroughDLL: false,
isDotnetReferenceSwappedWithOutputAssembly: false,
hasProjectReference: true)));
(IConfigurationTasks, Platform)? lastPlatformConfigurationTasks = null;
IConfigurationTasks GetConfigurationTasks(Platform platform)
{
if (lastPlatformConfigurationTasks.HasValue && lastPlatformConfigurationTasks.Value.Item2 == platform)
{
return lastPlatformConfigurationTasks.Value.Item1;
}
var tasks = PlatformRegistry.Get<IConfigurationTasks>(platform);
lastPlatformConfigurationTasks = (tasks, platform);
return tasks;
}
while (visitingNodes.Count > 0)
{
var visitedTuple = visitingNodes.Pop();
var visitedNode = visitedTuple.Item1;
var propagationSetting = visitedTuple.Item2;
bool nodeAlreadyVisited = visitedNodes.ContainsKey(visitedNode);
if (nodeAlreadyVisited && visitedNodes[visitedNode].Contains(propagationSetting))
continue;
if (!nodeAlreadyVisited)
visitedNodes[visitedNode] = new List<PropagationSettings>();
visitedNodes[visitedNode].Add(propagationSetting);
Configuration dependency = visitedNode._configuration;
bool isRoot = visitedNode == rootNode;
bool isImmediate = propagationSetting.IsImmediate;
bool hasPublicPathToRoot = propagationSetting.HasPublicPathToRoot;
bool hasPublicPathToImmediate = propagationSetting.HasPublicPathToImmediate;
bool goesThroughDLL = propagationSetting.GoesThroughDLL;
bool isDotnetReferenceSwappedWithOutputAssembly = propagationSetting.IsDotnetReferenceSwappedWithOutputAssembly || visitedNode._dependencySetting.HasFlag(DependencySetting.DependOnAssemblyOutput);
bool hasProjectReference = propagationSetting.HasProjectReference && !visitedNode._dependencySetting.HasFlag(DependencySetting.NoProjectReference);
foreach (var childNode in visitedNode._childNodes)
{
var childTuple = Tuple.Create(
childNode.Item1,
new PropagationSettings(
isRoot ? childNode.Item1._dependencySetting : (propagationSetting.DependencySetting & childNode.Item1._dependencySetting), // propagate the parent setting by masking it
isRoot, // only children of root are immediate
(isRoot || hasPublicPathToRoot) && childNode.Item2 == DependencyType.Public,
(isImmediate || hasPublicPathToImmediate) && childNode.Item2 == DependencyType.Public,
!isRoot && (goesThroughDLL || visitedNode._configuration.Output == OutputType.Dll),
isDotnetReferenceSwappedWithOutputAssembly || visitedNode._dependencySetting.HasFlag(DependencySetting.DependOnAssemblyOutput),
hasProjectReference && !visitedNode._dependencySetting.HasFlag(DependencySetting.NoProjectReference)
)
);
visitingNodes.Push(childTuple);
}
if (isRoot)
continue;
if (hasPublicPathToRoot)
{
resolvedPrivateDependencies.Remove(dependency);
resolvedPublicDependencies.Add(dependency);
}
else if (!resolvedPublicDependencies.Contains(dependency))
{
resolvedPrivateDependencies.Add(dependency);
}
bool isExport = dependency.Project.SharpmakeProjectType == ProjectTypeAttribute.Export;
bool compile = dependency.Project.SharpmakeProjectType == ProjectTypeAttribute.Generate ||
dependency.Project.SharpmakeProjectType == ProjectTypeAttribute.Compile;
var dependencySetting = propagationSetting.DependencySetting;
if (dependencySetting != DependencySetting.OnlyBuildOrder)
{
_resolvedEventPreBuildExe.AddRange(dependency.EventPreBuildExe);
_resolvedEventPostBuildExe.AddRange(dependency.EventPostBuildExe);
_resolvedEventCustomPreBuildExe.AddRange(dependency.EventCustomPreBuildExe);
_resolvedEventCustomPostBuildExe.AddRange(dependency.EventCustomPostBuildExe);
_resolvedTargetCopyFiles.AddRange(dependency.TargetCopyFiles);
_resolvedTargetDependsFiles.AddRange(dependency.TargetDependsFiles);
_resolvedExecDependsFiles.AddRange(dependency.EventPreBuildExe);
_resolvedExecDependsFiles.AddRange(dependency.EventPostBuildExe);
foreach (var keyValuePair in dependency.TargetCopyFilesToSubDirectory)
{
_resolvedTargetCopyFilesToSubDirectory.Add(keyValuePair);
}
}
else if (Output == OutputType.None && isExport == false)
{
GenericBuildDependencies.Add(dependency);
}
if (dependency.Output == OutputType.Lib
|| dependency.Output == OutputType.Dll
|| dependency.Output == OutputType.Utility
|| dependency.Output == OutputType.None)
{
bool wantIncludePaths = isImmediate || hasPublicPathToImmediate;
if (wantIncludePaths && dependencySetting.HasFlag(DependencySetting.IncludePaths))
{
DependenciesIncludePaths.AddRange(dependency.IncludePaths);
DependenciesIncludeSystemPaths.AddRange(dependency.IncludeSystemPaths);
_dependenciesResourceIncludePaths.AddRange(dependency.ResourceIncludePaths);
// Is there a case where we want the defines but *not* the include paths?
if (dependencySetting.HasFlag(DependencySetting.Defines))
Defines.AddRange(dependency.ExportDefines);
}
}
switch (dependency.Output)
{
case OutputType.None:
case OutputType.Lib:
{
bool dependencyOutputLib = dependency.Output == OutputType.Lib;
if (dependencyOutputLib && !goesThroughDLL &&
(Output == OutputType.Lib ||
dependency.ExportSymbolThroughProject == null ||
dependency.ExportSymbolThroughProject == Project.GetType())
)
{
if (explicitDependenciesGlobal || !compile)
GetConfigurationTasks(dependency.Platform).SetupStaticLibraryPaths(this, dependencySetting, dependency);
if (dependencySetting.HasFlag(DependencySetting.LibraryFiles) && hasProjectReference)
ConfigurationDependencies.Add(dependency);
if (dependencySetting == DependencySetting.OnlyBuildOrder && hasProjectReference)
BuildOrderDependencies.Add(dependency);
}
if (!goesThroughDLL)
{
if (dependencySetting.HasFlag(DependencySetting.LibraryPaths))
DependenciesOtherLibraryPaths.AddRange(dependency.LibraryPaths);
if (dependencySetting.HasFlag(DependencySetting.LibraryFiles))
DependenciesOtherLibraryFiles.AddRange(dependency.LibraryFiles);
if (dependencySetting.HasFlag(DependencySetting.ForceUsingAssembly))
DependenciesForceUsingFiles.AddRange(dependency.ForceUsingFiles);
}
if (dependency.Platform.Equals(Platform.mac) ||
dependency.Platform.Equals(Platform.ios) ||
dependency.Platform.Equals(Platform.tvos) ||
dependency.Platform.Equals(Platform.watchos) ||
dependency.Platform.Equals(Platform.maccatalyst)
)
{
if (dependency.XcodeSystemFrameworks.Count > 0)
XcodeDependenciesSystemFrameworks.AddRange(dependency.XcodeSystemFrameworks);
if (dependency.XcodeDeveloperFrameworks.Count > 0)
XcodeDependenciesDeveloperFrameworks.AddRange(dependency.XcodeDeveloperFrameworks);
if (dependency.XcodeUserFrameworks.Count > 0)
XcodeDependenciesUserFrameworks.AddRange(dependency.XcodeUserFrameworks);
if (dependency.XcodeEmbeddedFrameworks.Count > 0)
XcodeDependenciesEmbeddedFrameworks.AddRange(dependency.XcodeEmbeddedFrameworks);
if (dependency.XcodeSystemFrameworkPaths.Count > 0)
XcodeDependenciesSystemFrameworkPaths.AddRange(dependency.XcodeSystemFrameworkPaths);
if (dependency.XcodeFrameworkPaths.Count > 0)
XcodeDependenciesFrameworkPaths.AddRange(dependency.XcodeFrameworkPaths);
}
// If our no-output project is just a build-order dependency, update the build order accordingly
if (!dependencyOutputLib && isImmediate && dependencySetting == DependencySetting.OnlyBuildOrder && !isExport)
GenericBuildDependencies.Add(dependency);
}
break;
case OutputType.Dll:
case OutputType.AppleFramework:
case OutputType.AppleBundle:
{
var configTasks = GetConfigurationTasks(dependency.Platform);
if (dependency.ExportDllSymbols && (isImmediate || hasPublicPathToRoot || !goesThroughDLL))
{
if (explicitDependenciesGlobal || !compile || (IsFastBuild && Util.IsDotNet(dependency)))
configTasks.SetupDynamicLibraryPaths(this, dependencySetting, dependency);
if (dependencySetting.HasFlag(DependencySetting.LibraryFiles) && hasProjectReference)
ConfigurationDependencies.Add(dependency);
if (dependencySetting.HasFlag(DependencySetting.ForceUsingAssembly))
ForceUsingDependencies.Add(dependency);
if (dependencySetting == DependencySetting.OnlyBuildOrder && hasProjectReference)
BuildOrderDependencies.Add(dependency);
// check if that case is valid: dll with additional libs
if (isExport && !goesThroughDLL)
{
if (dependencySetting.HasFlag(DependencySetting.LibraryPaths))
DependenciesOtherLibraryPaths.AddRange(dependency.LibraryPaths);
if (dependencySetting.HasFlag(DependencySetting.LibraryFiles))
DependenciesOtherLibraryFiles.AddRange(dependency.LibraryFiles);
}
}
if (!dependency.ExportDllSymbols && (isImmediate || hasPublicPathToRoot || !goesThroughDLL))
{
if (dependencySetting.HasFlag(DependencySetting.LibraryFiles) ||
dependencySetting.HasFlag(DependencySetting.ForceUsingAssembly) ||
dependencySetting == DependencySetting.OnlyBuildOrder)
BuildOrderDependencies.Add(dependency);
}
if (dependencySetting.HasFlag(DependencySetting.AdditionalUsingDirectories) ||
dependencySetting.HasFlag(DependencySetting.ForceUsingAssembly))
AdditionalUsingDirectories.Add(dependency.TargetPath);
string dependencyDllFullPath = Path.Combine(dependency.TargetPath, dependency.TargetFileFullNameWithExtension);
if ((Output == OutputType.Exe || ExecuteTargetCopy)
&& dependency.AllowOutputDllCopy
&& dependencySetting.HasFlag(DependencySetting.LibraryFiles)
&& dependency.TargetPath != TargetPath)
{
// If using OnlyBuildOrder, ExecuteTargetCopy must be set to enable the copy.
if (dependencySetting != DependencySetting.OnlyBuildOrder || ExecuteTargetCopy)
{
_resolvedTargetCopyFiles.Add(dependencyDllFullPath);
// Add PDBs only if they exist and the dependency is not an [export] project
if ((!isExport || dependency.AllowExportProjectsToCopyPdbToDependentTargets) &&
Sharpmake.Options.GetObject<Options.Vc.Linker.GenerateDebugInformation>(dependency) != Sharpmake.Options.Vc.Linker.GenerateDebugInformation.Disable)
{
if (dependency.CopyLinkerPdbToDependentTargets)
_resolvedTargetCopyFiles.Add(dependency.LinkerPdbFilePath);
if (dependency.CopyCompilerPdbToDependentTargets)
_resolvedTargetCopyFiles.Add(dependency.CompilerPdbFilePath);
}
}
_resolvedEventPreBuildExe.AddRange(dependency.EventPreBuildExe);
_resolvedEventPostBuildExe.AddRange(dependency.EventPostBuildExe);
_resolvedEventCustomPreBuildExe.AddRange(dependency.EventCustomPreBuildExe);
_resolvedEventCustomPostBuildExe.AddRange(dependency.EventCustomPostBuildExe);
}
_resolvedTargetDependsFiles.Add(dependencyDllFullPath);
// If this is not a .Net project, no .Net dependencies are needed
if (Util.IsDotNet(this))
{
// If the dependency is not a .Net project, it will not function properly when referenced by a .Net compilation process
if (Util.IsDotNet(dependency))
{
if (hasPublicPathToRoot)
resolvedDotNetPublicDependencies.Add(new DotNetDependency(dependency));
else if (isImmediate)
resolvedDotNetPrivateDependencies.Add(new DotNetDependency(dependency));
}
// If the dependency is not a .Net project, it need anyway to be compiled before this one, so we add it as post dependency in the solution
else if (isImmediate && !isExport)
{
GenericBuildDependencies.Add(dependency);
}
}
}
break;
case OutputType.AppleApp:
case OutputType.Exe:
{
if (hasPublicPathToRoot)
resolvedDotNetPublicDependencies.Add(new DotNetDependency(dependency));
else if (isImmediate)
resolvedDotNetPrivateDependencies.Add(new DotNetDependency(dependency));
if (hasProjectReference)
{
if (dependencySetting == DependencySetting.OnlyBuildOrder)
BuildOrderDependencies.Add(dependency);
else
ConfigurationDependencies.Add(dependency);
}
}
break;
case OutputType.Utility:
{
// As visual studio do not handle reference being different between configuration,
// We use the "utility" output to mark a configuration as exclude from build.
if (!goesThroughDLL &&
(Output == OutputType.Lib ||
dependency.ExportSymbolThroughProject == null ||
dependency.ExportSymbolThroughProject == Project.GetType()) &&
dependencySetting.HasFlag(DependencySetting.LibraryFiles))
{
ConfigurationDependencies.Add(dependency);
}
}
break;
case OutputType.DotNetConsoleApp:
case OutputType.DotNetClassLibrary:
case OutputType.DotNetWindowsApp:
{
if (dependencySetting.HasFlag(DependencySetting.AdditionalUsingDirectories) ||
dependencySetting.HasFlag(DependencySetting.ForceUsingAssembly))
AdditionalUsingDirectories.Add(dependency.TargetPath);
bool? referenceOutputAssembly = ReferenceOutputAssembly;
if (isImmediate && dependencySetting == DependencySetting.OnlyBuildOrder)
referenceOutputAssembly = false;
if (dependencySetting.HasFlag(DependencySetting.ForceUsingAssembly))
ForceUsingDependencies.Add(dependency);
var dotNetDependency = new DotNetDependency(dependency)
{
ReferenceOutputAssembly = referenceOutputAssembly,
ReferenceSwappedWithOutputAssembly = isDotnetReferenceSwappedWithOutputAssembly
};
if (isDotnetReferenceSwappedWithOutputAssembly)
{
configurationsSwappedToDll ??= new HashSet<Configuration>();
configurationsSwappedToDll.Add(dotNetDependency.Configuration);
}
else
{
configurationsStillUsedAsNotSwappedToDll ??= new HashSet<Configuration>();
configurationsStillUsedAsNotSwappedToDll.Add(dotNetDependency.Configuration);
}
if (!resolvedDotNetPublicDependencies.Contains(dotNetDependency))
{
if (hasPublicPathToRoot)
{
resolvedDotNetPrivateDependencies.Remove(dotNetDependency);
resolvedDotNetPublicDependencies.Add(dotNetDependency);
}
else if ((isImmediate || hasPublicPathToImmediate))
{
resolvedDotNetPrivateDependencies.Add(dotNetDependency);
}
}
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
if (resolvedPublicDependencies.Overlaps(resolvedPrivateDependencies) || resolvedDotNetPublicDependencies.Overlaps(resolvedDotNetPrivateDependencies))
throw new InternalError("Something goes wrong in Project.Configuration.ResolveDependencies(): same dependency resolved in public and private lists");
// Will include to the project:
// - lib,dll: include paths
// - lib,dll: library paths and files
// - dll: copy dll to the output executable directory
_resolvedPublicDependencies = resolvedPublicDependencies.ToList();
// Will include to the project to act as a project bridge:
// - lib: add Library paths and files to be able to link the executable
// - dll: Copy dll to the output path
_resolvedPrivateDependencies = resolvedPrivateDependencies.ToList();
DotNetPublicDependencies = resolvedDotNetPublicDependencies.ToList();
DotNetPrivateDependencies = resolvedDotNetPrivateDependencies.ToList();
DotNetPublicDependencies.Sort(SortDotNetDependencyForLink);
DotNetPrivateDependencies.Sort(SortDotNetDependencyForLink);
if (configurationsSwappedToDll is not null)
{
ConfigurationsSwappedToDll = configurationsSwappedToDll;
// Remove configurations that have been explicitly used as not swapped to dll
if (configurationsStillUsedAsNotSwappedToDll is not null)
ConfigurationsSwappedToDll.ExceptWith(configurationsStillUsedAsNotSwappedToDll);
}
// sort base on DependenciesOrder
_resolvedPublicDependencies.Sort(SortConfigurationForLink);
_resolvedPrivateDependencies.Sort(SortConfigurationForLink);
_resolvedDependencies = new List<Configuration>();
_resolvedDependencies.AddRange(_resolvedPublicDependencies);
_resolvedDependencies.AddRange(_resolvedPrivateDependencies);
_linkState = LinkState.Linked;
}