Skip to content

Commit

Permalink
Merge branch 'sharpmake-swap-to-dll-dev' into 'main'
Browse files Browse the repository at this point in the history
Added CSharpDependenciesSwappedWithDLL Unit Test

See merge request Sharpmake/sharpmake!503
  • Loading branch information
baudronp committed Mar 4, 2024
2 parents 7e0f234 + 42e6874 commit 2f1f157
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 34 deletions.
73 changes: 44 additions & 29 deletions Sharpmake.Generators/VisualStudio/Csproj.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ internal class ItemGroupItem : IComparable<ItemGroupItem>, IEquatable<ItemGroupI
public string Include;

// This property is used to decide if this object is a Link
// If LinkFolder is null, this item is ín the project folder and is not a link
// If LinkFolder is null, this item is in the project folder and is not a link
// If LinkFolder is empty, this item is in the project's SourceRootPath or RootPath folder
// which are outside of the Project folder and is a link.
// If LinkedFolder is a file path, it's a link.
Expand Down Expand Up @@ -1453,38 +1453,53 @@ List<string> skipFiles
if (!Util.IsDotNet(dependencyConfiguration))
continue;

string dependencyExtension = Util.GetProjectFileExtension(dependencyConfiguration);
string projectFullFileNameWithExtension = Util.GetCapitalizedPath(dependencyConfiguration.ProjectFullFileName + dependencyExtension);
string relativeToProjectFile = Util.PathGetRelative(_projectPathCapitalized,
projectFullFileNameWithExtension);
if (dependency.ReferenceSwappedWithOutputAssembly)
{
var dotNetFramework = conf.Target.GetFragment<DotNetFramework>();
string dllPath = Path.Combine(dependency.Configuration.TargetPath, $"{dependency.Configuration.AssemblyName}{dependency.Configuration.DllFullExtension}");
var referencesByPath = new ItemGroups.Reference
{
Include = Path.GetFileNameWithoutExtension(dllPath),
SpecificVersion = false,
HintPath = Util.PathGetRelative(_projectPathCapitalized, dllPath),
Private = project.DependenciesCopyLocal.HasFlag(Project.DependenciesCopyLocalTypes.ExternalReferences),
};
itemGroups.AddReference(dotNetFramework, referencesByPath);
}
else
{
string dependencyExtension = Util.GetProjectFileExtension(dependencyConfiguration);
string projectFullFileNameWithExtension = Util.GetCapitalizedPath(dependencyConfiguration.ProjectFullFileName + dependencyExtension);
string relativeToProjectFile = Util.PathGetRelative(_projectPathCapitalized, projectFullFileNameWithExtension);

// If dependency project is marked as [Compile], read the GUID from the project file
if (dependencyConfiguration.Project.SharpmakeProjectType == Project.ProjectTypeAttribute.Compile && dependencyConfiguration.ProjectGuid == null)
dependencyConfiguration.ProjectGuid = ReadGuidFromProjectFile(dependencyConfiguration);
// If dependency project is marked as [Compile], read the GUID from the project file
if (dependencyConfiguration.Project.SharpmakeProjectType == Project.ProjectTypeAttribute.Compile && dependencyConfiguration.ProjectGuid == null)
dependencyConfiguration.ProjectGuid = ReadGuidFromProjectFile(dependencyConfiguration);

// FIXME : MsBuild does not seem to properly detect ReferenceOutputAssembly setting.
// It may try to recompile the project if the output file of the dependency is missing.
// To counter this, the CopyLocal field is forced to false for build-only dependencies.
bool isPrivate = project.DependenciesCopyLocal.HasFlag(Project.DependenciesCopyLocalTypes.ProjectReferences) && dependency.ReferenceOutputAssembly != false;
// FIXME : MsBuild does not seem to properly detect ReferenceOutputAssembly setting.
// It may try to recompile the project if the output file of the dependency is missing.
// To counter this, the CopyLocal field is forced to false for build-only dependencies.
bool isPrivate = project.DependenciesCopyLocal.HasFlag(Project.DependenciesCopyLocalTypes.ProjectReferences) && dependency.ReferenceOutputAssembly != false;

string includeOutputGroupsInVsix = null;
if (isPrivate && project.ProjectTypeGuids == CSharpProjectType.Vsix)
{
// Includes debug symbols of private (i.e. copy local) referenced projects in the VSIX.
// This WILL override default values of <IncludeOutputGroupsInVSIX> and <IncludeOutputGroupsInVSIXLocalOnly> from Microsoft.VsSDK.targets,
// so if the VSIXs stop working, this may be the cause...
includeOutputGroupsInVsix = "DebugSymbolsProjectOutputGroup;BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems;SatelliteDllsProjectOutputGroup";
}
string includeOutputGroupsInVsix = null;
if (isPrivate && project.ProjectTypeGuids == CSharpProjectType.Vsix)
{
// Includes debug symbols of private (i.e. copy local) referenced projects in the VSIX.
// This WILL override default values of <IncludeOutputGroupsInVSIX> and <IncludeOutputGroupsInVSIXLocalOnly> from Microsoft.VsSDK.targets,
// so if the VSIXs stop working, this may be the cause...
includeOutputGroupsInVsix = "DebugSymbolsProjectOutputGroup;BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems;SatelliteDllsProjectOutputGroup";
}

itemGroups.ProjectReferences.Add(new ItemGroups.ProjectReference
{
Include = relativeToProjectFile,
Name = dependencyConfiguration.ProjectName,
Private = isPrivate,
Project = new Guid(dependencyConfiguration.ProjectGuid),
ReferenceOutputAssembly = dependency.ReferenceOutputAssembly,
IncludeOutputGroupsInVSIX = includeOutputGroupsInVsix,
});
itemGroups.ProjectReferences.Add(new ItemGroups.ProjectReference
{
Include = relativeToProjectFile,
Name = dependencyConfiguration.ProjectName,
Private = isPrivate,
Project = new Guid(dependencyConfiguration.ProjectGuid),
ReferenceOutputAssembly = dependency.ReferenceOutputAssembly,
IncludeOutputGroupsInVSIX = includeOutputGroupsInVsix,
});
}
}
}

Expand Down
36 changes: 36 additions & 0 deletions Sharpmake.UnitTests/CSharpDependencyPropagationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,29 @@ public void CSharpDependenciesOnlyBuildOrder()
}
}
}

[Test]
public void CSharpDependenciesSwappedWithDLL()
{
var project = GetProject<CSharpTestProjects.CSharpProjectDependencySwappedToDLL>();
foreach (var conf in project.Configurations)
{
// Dep 1 (swapped): CSharpInheritOnePublicDependencyProject -> CSharpOnePublicDependencyProject -> CSharpNoDependencyProject1
// Dep 2 (not swapped): CSharpProjectB
Assert.That(conf.DotNetPublicDependencies.Count, Is.EqualTo(4));
Assert.That(conf.DotNetPrivateDependencies.Count, Is.EqualTo(0));

foreach (var dependency in conf.DotNetPublicDependencies)
{
// All of them should be Swapped except CsharpProjectB (because it's transitive)
if (dependency.Configuration.Project.FullClassName == typeof(CSharpTestProjects.CSharpProjectB).FullName)
Assert.False(dependency.ReferenceSwappedWithOutputAssembly);
else
Assert.True(dependency.ReferenceSwappedWithOutputAssembly);
}
}

}
}


Expand Down Expand Up @@ -506,5 +529,18 @@ public void ConfigureAll(Configuration conf, Target target)
conf.AddPublicDependency<CSharpOnlyBuildOrderDependency>(target);
}
}

[Sharpmake.Generate]
public class CSharpProjectDependencySwappedToDLL : CSharpUnitTestCommonProject
{
public CSharpProjectDependencySwappedToDLL() { }

[Configure()]
public void ConfigureAll(Configuration conf, Target target)
{
conf.AddPublicDependency<CSharpInheritOnePublicDependencyProject>(target, DependencySetting.DependOnAssemblyOutput);
conf.AddPublicDependency<CSharpProjectB>(target);
}
}
}
}
1 change: 1 addition & 0 deletions Sharpmake/DotNetDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class DotNetDependency
{
public Project.Configuration Configuration { get; }
public bool? ReferenceOutputAssembly { get; set; }
public bool ReferenceSwappedWithOutputAssembly { get; set; } = false;

public DotNetDependency(Project.Configuration configuration)
{
Expand Down
51 changes: 46 additions & 5 deletions Sharpmake/Project.Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ public enum DependencySetting
AdditionalUsingDirectories = 1 << 5,
ForceUsingAssembly = 1 << 6,

/// <summary>
/// The dependent project will reference the target assembly file instead of using a project reference.
/// Valid only for C# projects. Note that these assemblies are expected to be found in the project's output
/// directory and thus must be built otherwise.
/// </summary>
DependOnAssemblyOutput = 1 << 7,

/// <summary>
/// Specifies that the dependent project inherits the dependency's library files, library
/// paths, include paths and defined symbols.
Expand Down Expand Up @@ -1643,6 +1650,8 @@ public string FastBuildUnityPath
public int MinFilesPerJumboFile = 2;
public int MinJumboFiles = 1;

internal HashSet<Configuration> ConfigurationsSwappedToDll { get; set; }

// container for executable
/// <summary>
/// Represents a build step that invokes an executable on the file system.
Expand Down Expand Up @@ -3269,13 +3278,14 @@ public enum StartActionSetting

internal class PropagationSettings
{
internal PropagationSettings(DependencySetting inDependencySetting, bool inIsImmediate, bool inHasPublicPathToRoot, bool inHasPublicPathToImmediate, bool inGoesThroughDLL)
internal PropagationSettings(DependencySetting inDependencySetting, bool inIsImmediate, bool inHasPublicPathToRoot, bool inHasPublicPathToImmediate, bool inGoesThroughDLL, bool isDotnetReferenceSwappedWithOutputAssembly)
{
_dependencySetting = inDependencySetting;
_isImmediate = inIsImmediate;
_hasPublicPathToRoot = inHasPublicPathToRoot;
_hasPublicPathToImmediate = inHasPublicPathToImmediate;
_goesThroughDLL = inGoesThroughDLL;
_isDotnetReferenceSwappedWithOutputAssembly = isDotnetReferenceSwappedWithOutputAssembly;
}

public override bool Equals(object obj)
Expand All @@ -3293,7 +3303,8 @@ public override bool Equals(object obj)
_isImmediate == other._isImmediate &&
_hasPublicPathToRoot == other._hasPublicPathToRoot &&
_hasPublicPathToImmediate == other._hasPublicPathToImmediate &&
_goesThroughDLL == other._goesThroughDLL;
_goesThroughDLL == other._goesThroughDLL &&
_isDotnetReferenceSwappedWithOutputAssembly == other._isDotnetReferenceSwappedWithOutputAssembly;
}

public override int GetHashCode()
Expand All @@ -3306,6 +3317,7 @@ public override int GetHashCode()
hash = hash * 23 + _hasPublicPathToRoot.GetHashCode();
hash = hash * 23 + _hasPublicPathToImmediate.GetHashCode();
hash = hash * 23 + _goesThroughDLL.GetHashCode();
hash = hash * 23 + _isDotnetReferenceSwappedWithOutputAssembly.GetHashCode();
return hash;
}
}
Expand All @@ -3315,6 +3327,7 @@ public override int GetHashCode()
internal readonly bool _hasPublicPathToRoot;
internal readonly bool _hasPublicPathToImmediate;
internal readonly bool _goesThroughDLL;
internal readonly bool _isDotnetReferenceSwappedWithOutputAssembly;
}

internal void Link(Builder builder)
Expand Down Expand Up @@ -3347,10 +3360,16 @@ internal void Link(Builder builder)

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, true, true, true, false)));
visitingNodes.Push(Tuple.Create(rootNode, new PropagationSettings(DependencySetting.Default, inIsImmediate: true, inHasPublicPathToRoot: true, inHasPublicPathToImmediate: true, inGoesThroughDLL: false, isDotnetReferenceSwappedWithOutputAssembly: false)));

(IConfigurationTasks, Platform)? lastPlatformConfigurationTasks = null;

Expand Down Expand Up @@ -3390,6 +3409,7 @@ IConfigurationTasks GetConfigurationTasks(Platform platform)
bool hasPublicPathToRoot = propagationSetting._hasPublicPathToRoot;
bool hasPublicPathToImmediate = propagationSetting._hasPublicPathToImmediate;
bool goesThroughDLL = propagationSetting._goesThroughDLL;
bool isDotnetReferenceSwappedWithOutputAssembly = propagationSetting._isDotnetReferenceSwappedWithOutputAssembly || visitedNode._dependencySetting.HasFlag(DependencySetting.DependOnAssemblyOutput);

foreach (var childNode in visitedNode._childNodes)
{
Expand All @@ -3400,7 +3420,8 @@ IConfigurationTasks GetConfigurationTasks(Platform platform)
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)
!isRoot && (goesThroughDLL || visitedNode._configuration.Output == OutputType.Dll),
isDotnetReferenceSwappedWithOutputAssembly || visitedNode._dependencySetting.HasFlag(DependencySetting.DependOnAssemblyOutput)
)
);

Expand Down Expand Up @@ -3659,8 +3680,20 @@ IConfigurationTasks GetConfigurationTasks(Platform platform)

var dotNetDependency = new DotNetDependency(dependency)
{
ReferenceOutputAssembly = referenceOutputAssembly
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))
{
Expand Down Expand Up @@ -3697,7 +3730,15 @@ IConfigurationTasks GetConfigurationTasks(Platform platform)

DotNetPublicDependencies = resolvedDotNetPublicDependencies.ToList();
DotNetPrivateDependencies = resolvedDotNetPrivateDependencies.ToList();
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);
Expand Down
6 changes: 6 additions & 0 deletions Sharpmake/Solution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,14 @@ internal void Link(Builder builder)
if (!configurationProject.Configuration.IsFastBuild && configurationProject.Configuration.ResolvedDependencies.Any(d => d.IsFastBuild))
unlinkedList.Add(configurationProject.Configuration);
unlinkedList.AddRange(dependenciesConfiguration.Where(c => !c.IsFastBuild && c.ResolvedDependencies.Any(d => d.IsFastBuild)));

foreach (Project.Configuration dependencyConfiguration in dependenciesConfiguration)
{
// Skip configuration that only have swapped-to-dll dependencies
var projectsSwappedToDll = configurationProject.Configuration.ConfigurationsSwappedToDll;
if (projectsSwappedToDll is not null && projectsSwappedToDll.Contains(dependencyConfiguration))
continue;

Project dependencyProject = dependencyConfiguration.Project;
if (dependencyProject.SharpmakeProjectType == Project.ProjectTypeAttribute.Export)
continue;
Expand Down

0 comments on commit 2f1f157

Please sign in to comment.