in Sharpmake.Generators/VisualStudio/Sln.cs [249:680]
private string Generate(
Solution solution,
IReadOnlyList<Solution.Configuration> solutionConfigurations,
string solutionPath,
string solutionFile,
bool addMasterBff,
out bool updated
)
{
// reset current solution state
_rootSolutionFolders.Clear();
_solutionFolders.Clear();
FileInfo solutionFileInfo = new FileInfo(Util.GetCapitalizedPath(solutionPath + Path.DirectorySeparatorChar + solutionFile + SolutionExtension));
string solutionGuid = Util.BuildGuid(solutionFileInfo.FullName, solution.SharpmakeCsPath);
DevEnv devEnv = solutionConfigurations[0].Target.GetFragment<DevEnv>();
List<Solution.ResolvedProject> solutionProjects = ResolveSolutionProjects(solution, solutionConfigurations);
if (solutionProjects.Count == 0)
{
updated = solutionFileInfo.Exists;
if (updated)
Util.TryDeleteFile(solutionFileInfo.FullName);
return solutionFileInfo.FullName;
}
List<Solution.ResolvedProject> resolvedPathReferences = ResolveReferencesByPath(solutionProjects, solutionConfigurations[0].ProjectReferencesByPath);
var guidlist = solutionProjects.Select(p => p.UserData["Guid"]);
resolvedPathReferences = resolvedPathReferences.Where(r => !guidlist.Contains(r.UserData["Guid"])).ToList();
var fileGenerator = new FileGenerator();
// write solution header
switch (devEnv)
{
case DevEnv.vs2015:
fileGenerator.Write(Template.Solution.HeaderBeginVs2015);
break;
case DevEnv.vs2017:
fileGenerator.Write(Template.Solution.HeaderBeginVs2017);
break;
case DevEnv.vs2019:
fileGenerator.Write(Template.Solution.HeaderBeginVs2019);
break;
case DevEnv.vs2022:
fileGenerator.Write(Template.Solution.HeaderBeginVs2022);
break;
default:
throw new Error($"Unsupported DevEnv {devEnv} for solution {solution.Name}");
}
SolutionFolder masterBffFolder = null;
if (addMasterBff)
{
masterBffFolder = GetSolutionFolder(solution.FastBuildMasterBffSolutionFolder);
if (masterBffFolder == null)
throw new Error("FastBuildMasterBffSolutionFolder needs to be set in solution " + solutionFile);
}
// Write all needed folders before the projects to make sure the proper startup project is selected.
// Ensure folders are always in the same order to avoid random shuffles
_solutionFolders.Sort((a, b) =>
{
int nameComparison = string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase);
if (nameComparison != 0)
return nameComparison;
return a.Guid.CompareTo(b.Guid);
});
foreach (SolutionFolder folder in _solutionFolders)
{
using (fileGenerator.Declare("folderName", folder.Name))
using (fileGenerator.Declare("folderGuid", folder.Guid.ToString().ToUpper()))
{
fileGenerator.Write(Template.Solution.ProjectFolder);
if (masterBffFolder == folder)
{
var bffFilesPaths = new SortedSet<string>(new FileSystemStringComparer());
foreach (var conf in solutionConfigurations)
{
string masterBffFilePath = conf.MasterBffFilePath + FastBuildSettings.FastBuildConfigFileExtension;
bffFilesPaths.Add(Util.PathGetRelative(solutionPath, masterBffFilePath));
bffFilesPaths.Add(Util.PathGetRelative(solutionPath, FastBuild.MasterBff.GetGlobalBffConfigFileName(masterBffFilePath)));
}
// This always needs to be created so make sure it's there.
bffFilesPaths.Add(solutionFile + FastBuildSettings.FastBuildConfigFileExtension);
fileGenerator.Write(Template.Solution.SolutionItemBegin);
{
foreach (var path in bffFilesPaths)
{
using (fileGenerator.Declare("solutionItemPath", path))
fileGenerator.Write(Template.Solution.SolutionItem);
}
}
fileGenerator.Write(Template.Solution.ProjectSectionEnd);
}
fileGenerator.Write(Template.Solution.ProjectEnd);
}
}
Solution.ResolvedProject fastBuildAllProjectForSolutionDependency = null;
if (solution.FastBuildAllSlnDependencyFromExe)
{
var fastBuildAllProjects = solutionProjects.Where(p => p.Project.IsFastBuildAll).ToArray();
if (fastBuildAllProjects.Length > 1)
throw new Error("More than one FastBuildAll project");
if (fastBuildAllProjects.Length == 1)
fastBuildAllProjectForSolutionDependency = fastBuildAllProjects[0];
}
using (fileGenerator.Declare("solution", solution))
using (fileGenerator.Declare("solutionGuid", solutionGuid))
{
foreach (Solution.ResolvedProject resolvedProject in solutionProjects.Concat(resolvedPathReferences).Distinct(new Solution.ResolvedProjectGuidComparer()))
{
FileInfo projectFileInfo = new FileInfo(resolvedProject.ProjectFile);
using (fileGenerator.Declare("project", resolvedProject.Project))
using (fileGenerator.Declare("projectName", resolvedProject.ProjectName))
using (fileGenerator.Declare("projectFile", Util.PathGetRelative(solutionFileInfo.Directory.FullName, projectFileInfo.FullName)))
using (fileGenerator.Declare("projectGuid", resolvedProject.UserData["Guid"]))
using (fileGenerator.Declare("projectTypeGuid", resolvedProject.UserData["TypeGuid"]))
{
fileGenerator.Write(Template.Solution.ProjectBegin);
Strings buildDepsGuids = new Strings(resolvedProject.Configurations.SelectMany(
c => c.GenericBuildDependencies
.Where(dep => !dep.IsFastBuild)
.Select(p => p.ProjectGuid ?? ReadGuidFromProjectFile(p.ProjectFullFileNameWithExtension)
)
));
if (fastBuildAllProjectForSolutionDependency != null)
{
bool writeDependencyToFastBuildAll = (resolvedProject.Configurations.Any(conf => conf.IsFastBuild && conf.Output == Project.Configuration.OutputType.Exe)) ||
solution.ProjectsDependingOnFastBuildAllForThisSolution.Contains(resolvedProject.Project);
if (writeDependencyToFastBuildAll)
buildDepsGuids.Add(fastBuildAllProjectForSolutionDependency.UserData["Guid"] as string);
}
if (buildDepsGuids.Any())
{
fileGenerator.Write(Template.Solution.ProjectDependencyBegin);
foreach (string guid in buildDepsGuids.SortedValues)
{
using (fileGenerator.Declare("projectDependencyGuid", guid))
fileGenerator.Write(Template.Solution.ProjectDependency);
}
fileGenerator.Write(Template.Solution.ProjectSectionEnd);
}
fileGenerator.Write(Template.Solution.ProjectEnd);
}
}
}
// Write extra solution items
// TODO: What happens if we define an existing folder?
foreach (var items in solution.ExtraItems)
{
var folder = GetSolutionFolder(items.Key);
using (fileGenerator.Declare("folderName", folder.Name))
using (fileGenerator.Declare("folderGuid", folder.Guid))
using (fileGenerator.Declare("solution", solution))
{
fileGenerator.Write(Template.Solution.ProjectFolder);
{
fileGenerator.Write(Template.Solution.SolutionItemBegin);
foreach (string file in items.Value)
{
using (fileGenerator.Declare("solutionItemPath", Util.PathGetRelative(solutionPath, file)))
fileGenerator.Write(Template.Solution.SolutionItem);
}
fileGenerator.Write(Template.Solution.ProjectSectionEnd);
}
fileGenerator.Write(Template.Solution.ProjectEnd);
}
}
fileGenerator.Write(Template.Solution.GlobalBegin);
// write solution configurations
string visualStudioExe = GetVisualStudioIdePath(devEnv) + Util.WindowsSeparator + "devenv.com";
var configurationSectionNames = new List<string>();
bool containsMultiDotNetFramework = solutionConfigurations.All(sc => sc.Target.HaveFragment<DotNetFramework>()) &&
solutionConfigurations.Select(sc => sc.Target.GetFragment<DotNetFramework>()).Distinct().Count() > 1;
var multiDotNetFrameworkConfigurationNames = new HashSet<string>();
fileGenerator.Write(Template.Solution.GlobalSectionSolutionConfigurationBegin);
foreach (Solution.Configuration solutionConfiguration in solutionConfigurations)
{
string configurationName;
string category;
if (solution.MergePlatformConfiguration)
{
configurationName = solutionConfiguration.PlatformName + "-" + solutionConfiguration.Name;
category = "All Platforms";
}
else
{
configurationName = solutionConfiguration.Name;
category = solutionConfiguration.PlatformName;
}
if (containsMultiDotNetFramework)
{
if (multiDotNetFrameworkConfigurationNames.Contains(configurationName))
continue;
multiDotNetFrameworkConfigurationNames.Add(configurationName);
}
using (fileGenerator.Declare("configurationName", configurationName))
using (fileGenerator.Declare("category", category))
{
configurationSectionNames.Add(fileGenerator.Resolver.Resolve(Template.Solution.GlobalSectionSolutionConfiguration));
}
// set the compile command line
if (File.Exists(visualStudioExe))
{
solutionConfiguration.CompileCommandLine = string.Format(@"""{0}"" ""{1}"" /build ""{2}|{3}""",
visualStudioExe, solutionFileInfo.FullName, configurationName, category);
}
}
configurationSectionNames.Sort();
VerifySectionNamesDuplicates(solutionFileInfo.FullName, solutionConfigurations, configurationSectionNames);
foreach (string configurationSectionName in configurationSectionNames)
fileGenerator.Write(configurationSectionName);
fileGenerator.Write(Template.Solution.GlobalSectionSolutionConfigurationEnd);
// write all project target and match then to a solution target
fileGenerator.Write(Template.Solution.GlobalSectionProjectConfigurationBegin);
var solutionConfigurationFastBuildBuilt = new Dictionary<Solution.Configuration, List<string>>();
foreach (Solution.ResolvedProject solutionProject in solutionProjects)
{
if (containsMultiDotNetFramework)
multiDotNetFrameworkConfigurationNames.Clear();
foreach (Solution.Configuration solutionConfiguration in solutionConfigurations)
{
ITarget solutionTarget = solutionConfiguration.Target;
ITarget projectTarget = null;
Solution.Configuration.IncludedProjectInfo includedProject = solutionConfiguration.GetProject(solutionProject.Project.GetType());
bool perfectMatch = includedProject != null && solutionProject.Configurations.Contains(includedProject.Configuration);
if (perfectMatch)
{
projectTarget = includedProject.Target;
}
else
{
// try to find the target in the project that is the closest match from the solution one
int maxEqualFragments = 0;
int[] solutionTargetValues = solutionTarget.GetFragmentsValue();
Platform previousPlatform = Platform._reserved1;
foreach (var conf in solutionProject.Configurations)
{
Platform currentTargetPlatform = conf.Target.GetPlatform();
int[] candidateTargetValues = conf.Target.GetFragmentsValue();
if (solutionTargetValues.Length != candidateTargetValues.Length)
continue;
int equalFragments = 0;
for (int i = 0; i < solutionTargetValues.Length; ++i)
{
if ((solutionTargetValues[i] & candidateTargetValues[i]) != 0)
equalFragments++;
}
if ((equalFragments == maxEqualFragments && currentTargetPlatform < previousPlatform) || equalFragments > maxEqualFragments)
{
projectTarget = conf.Target;
maxEqualFragments = equalFragments;
previousPlatform = currentTargetPlatform;
}
}
// last resort: if we didn't find a good enough match, fallback to TargetDefault
if (projectTarget == null)
projectTarget = solutionProject.TargetDefault;
}
Project.Configuration projectConf = solutionProject.Project.GetConfiguration(projectTarget);
if (includedProject != null && includedProject.Configuration.IsFastBuild)
solutionConfigurationFastBuildBuilt.GetValueOrAdd(solutionConfiguration, new List<string>());
Platform projectPlatform = projectTarget.GetPlatform();
string configurationName;
string category;
if (solution.MergePlatformConfiguration)
{
configurationName = solutionConfiguration.PlatformName + "-" + solutionConfiguration.Name;
category = "All Platforms";
}
else
{
configurationName = solutionConfiguration.Name;
category = solutionConfiguration.PlatformName;
}
if (containsMultiDotNetFramework && includedProject?.Project is CSharpProject)
{
if (multiDotNetFrameworkConfigurationNames.Contains(configurationName))
continue;
multiDotNetFrameworkConfigurationNames.Add(configurationName);
}
using (fileGenerator.Declare("solutionConf", solutionConfiguration))
using (fileGenerator.Declare("projectGuid", solutionProject.UserData["Guid"]))
using (fileGenerator.Declare("projectConf", projectConf))
using (fileGenerator.Declare("projectPlatform", Util.GetToolchainPlatformString(projectPlatform, solutionProject.Project, solutionConfiguration.Target, true)))
using (fileGenerator.Declare("category", category))
using (fileGenerator.Declare("configurationName", configurationName))
{
bool build = false;
bool forceDeploy = false;
if (solution is PythonSolution)
{
// nothing is built in python solutions
}
else if (perfectMatch)
{
build = includedProject.ToBuild == Solution.Configuration.IncludedProjectInfo.Build.Yes;
forceDeploy = includedProject.Project.DeployProjectType == Project.DeployType.AlwaysDeploy || includedProject.Configuration.DeployProjectType == Project.DeployType.AlwaysDeploy;
// for fastbuild, only build the projects that cannot be built through dependency chain
if (!projectConf.IsFastBuild)
build |= includedProject.ToBuild == Solution.Configuration.IncludedProjectInfo.Build.YesThroughDependency;
else
{
if (build)
solutionConfigurationFastBuildBuilt[solutionConfiguration].Add(projectConf.Project.Name + " " + projectConf.Name);
}
}
fileGenerator.Write(Template.Solution.GlobalSectionProjectConfigurationActive);
bool buildDeploy = false;
if (build)
{
buildDeploy = includedProject.Project.DeployProjectType == Project.DeployType.OnlyIfBuild || includedProject.Configuration.DeployProjectType == Project.DeployType.OnlyIfBuild;
fileGenerator.Write(Template.Solution.GlobalSectionProjectConfigurationBuild);
}
if (forceDeploy || buildDeploy)
{
fileGenerator.Write(Template.Solution.GlobalSectionProjectConfigurationDeploy);
}
}
}
}
foreach (var fb in solutionConfigurationFastBuildBuilt)
{
var solutionConfiguration = fb.Key;
if (fb.Value.Count == 0)
Builder.Instance.LogErrorLine($"{solutionFile} - {solutionConfiguration.Name}|{solutionConfiguration.PlatformName} - has no FastBuild projects to build.");
else if (solution.GenerateFastBuildAllProject && fb.Value.Count > 1)
Builder.Instance.LogErrorLine($"{solutionFile} - {solutionConfiguration.Name}|{solutionConfiguration.PlatformName} - has more than one FastBuild project to build ({string.Join(";", fb.Value)}).");
}
fileGenerator.Write(Template.Solution.GlobalSectionProjectConfigurationEnd);
fileGenerator.Write(Template.Solution.SolutionProperties);
// Write nested folders
if (_solutionFolders.Count != 0)
{
fileGenerator.Write(Template.Solution.NestedProjectBegin);
foreach (SolutionFolder folder in _solutionFolders)
{
if (folder.Parent != null)
{
using (fileGenerator.Declare("nestedChildGuid", folder.Guid.ToString().ToUpper()))
using (fileGenerator.Declare("nestedParentGuid", folder.Parent.Guid.ToString().ToUpper()))
{
fileGenerator.Write(Template.Solution.NestedProjectItem);
}
}
}
foreach (Solution.ResolvedProject resolvedProject in solutionProjects.Concat(resolvedPathReferences))
{
SolutionFolder folder = resolvedProject.UserData["Folder"] as SolutionFolder;
if (folder != null)
{
using (fileGenerator.Declare("nestedChildGuid", resolvedProject.UserData["Guid"].ToString().ToUpper()))
using (fileGenerator.Declare("nestedParentGuid", folder.Guid.ToString().ToUpper()))
{
fileGenerator.Write(Template.Solution.NestedProjectItem);
}
}
}
fileGenerator.Write(Template.Solution.NestedProjectEnd);
}
fileGenerator.Write(Template.Solution.GlobalEnd);
// Write the solution file
updated = _builder.Context.WriteGeneratedFile(solution.GetType(), solutionFileInfo, fileGenerator);
solution.PostGenerationCallback?.Invoke(solutionPath, solutionFile, SolutionExtension);
return solutionFileInfo.FullName;
}