internal void Link()

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;
            }