Sharpmake.Generators/VisualStudio/Pyproj.cs (341 lines of code) (raw):
// Copyright (c) Ubisoft. All Rights Reserved.
// Licensed under the Apache 2.0 License. See LICENSE.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Win32;
namespace Sharpmake.Generators.VisualStudio
{
public partial class Pyproj : IProjectGenerator
{
internal class ItemGroups
{
internal CSproj.ItemGroups.ItemGroup<ProjectReference> ProjectReferences = new CSproj.ItemGroups.ItemGroup<ProjectReference>();
internal string Resolve(Resolver resolver)
{
var writer = new StringWriter();
writer.Write(ProjectReferences.Resolve(resolver));
return writer.ToString();
}
internal class ProjectReference : CSproj.ItemGroups.ItemGroupItem, CSproj.IResolvable
{
public Guid Project;
public string Name;
public string Private;
public bool? ReferenceOutputAssembly = null;
public string Resolve(Resolver resolver)
{
using (resolver.NewScopedParameter("include", Include))
using (resolver.NewScopedParameter("projectRefName", Name))
using (resolver.NewScopedParameter("projectGUID", Project.ToString("B").ToUpper()))
using (resolver.NewScopedParameter("private", Private))
using (resolver.NewScopedParameter("ReferenceOutputAssembly", ReferenceOutputAssembly))
{
var writer = new StringWriter();
writer.Write(Template.Project.ProjectReferenceBegin);
writer.Write(Template.Project.ProjectRefName);
writer.Write(Template.Project.ProjectGUID);
writer.Write(Template.Project.Private);
if (ReferenceOutputAssembly.HasValue)
writer.Write(Template.Project.ReferenceOutputAssembly);
writer.Write(Template.Project.ProjectReferenceEnd);
return resolver.Resolve(writer.ToString());
}
}
}
}
private PythonProject _project;
private List<Project.Configuration> _projectConfigurationList;
private Builder _builder;
public const string ProjectExtension = ".pyproj";
private void Write(string value, TextWriter writer, Resolver resolver)
{
writer.Write(resolver.Resolve(value));
}
public void Generate(Builder builder, Project project, List<Project.Configuration> configurations, string projectFile, List<string> generatedFiles, List<string> skipFiles)
{
_builder = builder;
FileInfo fileInfo = new FileInfo(projectFile);
string projectPath = fileInfo.Directory.FullName;
string projectFileName = fileInfo.Name;
bool updated;
if (!(project is PythonProject))
throw new ArgumentException("Project is not a PythonProject");
string projectFileResult = Generate((PythonProject)project, configurations, projectPath, projectFileName, out updated);
if (updated)
generatedFiles.Add(projectFileResult);
else
skipFiles.Add(projectFileResult);
_builder = null;
}
private string Generate(PythonProject project, List<Project.Configuration> unsortedConfigurations, string projectPath, string projectFile, out bool updated)
{
var itemGroups = new ItemGroups();
// Need to sort by name and platform
List<Project.Configuration> configurations = new List<Project.Configuration>();
configurations.AddRange(unsortedConfigurations.OrderBy(conf => conf.Name + conf.Platform));
string sourceRootPath = project.IsSourceFilesCaseSensitive ? Util.GetCapitalizedPath(project.SourceRootPath) : project.SourceRootPath;
Resolver resolver = new Resolver();
using (resolver.NewScopedParameter("guid", configurations.First().ProjectGuid))
using (resolver.NewScopedParameter("projectHome", Util.PathGetRelative(projectPath, sourceRootPath)))
using (resolver.NewScopedParameter("startupFile", project.StartupFile))
using (resolver.NewScopedParameter("searchPath", project.SearchPaths.JoinStrings(";")))
{
_project = project;
_projectConfigurationList = configurations;
DevEnvRange devEnvRange = new DevEnvRange(unsortedConfigurations);
bool needsPypatching = devEnvRange.MinDevEnv >= DevEnv.vs2017;
if (!needsPypatching && (devEnvRange.MinDevEnv != devEnvRange.MaxDevEnv))
{
Builder.Instance.LogWarningLine("There are mixed devEnvs for one project. VS2017 or higher Visual Studio solutions will require manual updates.");
}
MemoryStream memoryStream = new MemoryStream();
StreamWriter writer = new StreamWriter(memoryStream);
// xml begin header
Write(Template.Project.ProjectBegin, writer, resolver);
string defaultInterpreterRegisterKeyName = $@"Software\Microsoft\VisualStudio\{devEnvRange.MinDevEnv.GetVisualVersionString()}\PythonTools\Options\Interpreters";
var defaultInterpreter = GetRegistryCurrentUserSubKeyValue(defaultInterpreterRegisterKeyName, "DefaultInterpreter", "{00000000-0000-0000-0000-000000000000}");
var defaultInterpreterVersion = GetRegistryCurrentUserSubKeyValue(defaultInterpreterRegisterKeyName, "DefaultInterpreterVersion", "2.7");
string currentInterpreterId = defaultInterpreter;
string currentInterpreterVersion = defaultInterpreterVersion;
string ptvsTargetsFile = $@"$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets";
// environments
foreach (PythonEnvironment pyEnvironment in _project.Environments)
{
if (pyEnvironment.IsActivated)
{
string interpreterRegisterKeyName =
$@"Software\Microsoft\VisualStudio\{devEnvRange.MinDevEnv.GetVisualVersionString()}\PythonTools\Interpreters\{{{pyEnvironment.Guid}}}";
string interpreterDescription = GetRegistryCurrentUserSubKeyValue(interpreterRegisterKeyName, "Description", "");
if (interpreterDescription != string.Empty)
{
currentInterpreterId = $"{{{pyEnvironment.Guid}}}";
currentInterpreterVersion = GetRegistryCurrentUserSubKeyValue(interpreterRegisterKeyName, "Version", currentInterpreterVersion);
}
}
}
// virtual environments
foreach (PythonVirtualEnvironment virtualEnvironment in _project.VirtualEnvironments)
{
if (virtualEnvironment.IsDefault)
{
string baseInterpreterRegisterKeyName =
$@"Software\Microsoft\VisualStudio\{devEnvRange.MinDevEnv.GetVisualVersionString()}\PythonTools\Interpreters\{{{virtualEnvironment.BaseInterpreterGuid}}}";
string baseInterpreterDescription = GetRegistryCurrentUserSubKeyValue(baseInterpreterRegisterKeyName, "Description", "");
if (baseInterpreterDescription != string.Empty)
{
currentInterpreterId = $"{{{virtualEnvironment.Guid}}}";
currentInterpreterVersion = GetRegistryCurrentUserSubKeyValue(baseInterpreterRegisterKeyName, "Version", currentInterpreterVersion);
}
}
}
// Project description
if (needsPypatching)
{
currentInterpreterId = $"MSBuild|debug|$(MSBuildProjectFullPath)";
ptvsTargetsFile = FileGeneratorUtilities.RemoveLineTag;
}
using (resolver.NewScopedParameter("interpreterId", currentInterpreterId))
using (resolver.NewScopedParameter("interpreterVersion", currentInterpreterVersion))
using (resolver.NewScopedParameter("ptvsTargetsFile", ptvsTargetsFile))
{
Write(Template.Project.ProjectDescription, writer, resolver);
}
GenerateItems(writer, resolver);
string baseGuid = FileGeneratorUtilities.RemoveLineTag;
foreach (PythonVirtualEnvironment virtualEnvironment in _project.VirtualEnvironments)
{
baseGuid = needsPypatching ? baseGuid : virtualEnvironment.BaseInterpreterGuid.ToString();
string pyVersion = string.IsNullOrEmpty(virtualEnvironment.Version) ? currentInterpreterVersion : virtualEnvironment.Version;
Write(Template.Project.ProjectItemGroupBegin, writer, resolver);
using (resolver.NewScopedParameter("name", virtualEnvironment.Name))
using (resolver.NewScopedParameter("version", pyVersion))
using (resolver.NewScopedParameter("basePath", virtualEnvironment.Path))
using (resolver.NewScopedParameter("baseGuid", baseGuid))
using (resolver.NewScopedParameter("guid", virtualEnvironment.Guid))
{
Write(Template.Project.VirtualEnvironmentInterpreter, writer, resolver);
}
Write(Template.Project.ProjectItemGroupEnd, writer, resolver);
}
Write(Template.Project.ProjectItemGroupBegin, writer, resolver);
if (_project.Environments.Count > 0)
{
foreach (PythonEnvironment pyEnvironment in _project.Environments)
{
// Verify if the interpreter exists in the register.
string interpreterRegisterKeyName =
$@"Software\Microsoft\VisualStudio\{devEnvRange.MinDevEnv.GetVisualVersionString()}\PythonTools\Interpreters\{{{pyEnvironment.Guid}}}";
string interpreterDescription = GetRegistryCurrentUserSubKeyValue(interpreterRegisterKeyName, "Description", "");
if (interpreterDescription != string.Empty)
{
string interpreterVersion = GetRegistryCurrentUserSubKeyValue(interpreterRegisterKeyName, "Version", currentInterpreterVersion);
using (resolver.NewScopedParameter("guid", $"{{{pyEnvironment.Guid}}}"))
using (resolver.NewScopedParameter("version", interpreterVersion))
{
Write(Template.Project.InterpreterReference, writer, resolver);
}
}
}
}
else if (_project.VirtualEnvironments.Count == 0) // Set the default interpreter
{
using (resolver.NewScopedParameter("guid", currentInterpreterId))
using (resolver.NewScopedParameter("version", currentInterpreterVersion))
{
Write(Template.Project.InterpreterReference, writer, resolver);
}
}
Write(Template.Project.ProjectItemGroupEnd, writer, resolver);
// configuration general
foreach (Project.Configuration conf in _projectConfigurationList)
{
foreach (var dependencies in new[] { conf.ResolvedPublicDependencies, conf.DotNetPrivateDependencies.Select(x => x.Configuration) })
{
foreach (var dependency in dependencies)
{
string relativeToProjectFile = Util.PathGetRelative(sourceRootPath, dependency.ProjectFullFileNameWithExtension);
bool privateDependency = project.DependenciesCopyLocal.HasFlag(Project.DependenciesCopyLocalTypes.ProjectReferences);
conf.GetDependencySetting(dependency.Project.GetType());
itemGroups.ProjectReferences.Add(new ItemGroups.ProjectReference
{
Include = relativeToProjectFile,
Name = dependency.ProjectName,
Private = privateDependency ? "True" : "False",
Project = new Guid(dependency.ProjectGuid)
});
}
}
}
GenerateFolders(writer, resolver);
// Import native Python Tools project
if (needsPypatching)
{
Write(Template.Project.ImportPythonTools, writer, resolver);
}
writer.Write(itemGroups.Resolve(resolver));
Write(Template.Project.ProjectEnd, writer, resolver);
// Write the project file
writer.Flush();
// remove all line that contain RemoveLineTag
memoryStream = Util.RemoveLineTags(memoryStream, FileGeneratorUtilities.RemoveLineTag);
memoryStream.Seek(0, SeekOrigin.Begin);
FileInfo projectFileInfo = new FileInfo(projectPath + @"\" + projectFile + ProjectExtension);
updated = _builder.Context.WriteGeneratedFile(project.GetType(), projectFileInfo, memoryStream);
writer.Close();
_project = null;
return projectFileInfo.FullName;
}
}
private void GenerateItems(StreamWriter writer, Resolver resolver)
{
Strings projectFiles = _project.GetSourceFilesForConfigurations(_projectConfigurationList);
// Add source files
ProjectDirectory rootDirectory = new ProjectDirectory(null, _project.SourceRootPath);
foreach (string file in projectFiles)
{
string relativeFile = GetProperRelativePathToSourcePath(file);
relativeFile = relativeFile.Trim('.', '\\', '/');
string[] splitFile = relativeFile.Split('\\', '/');
ProjectDirectory directory = rootDirectory;
for (int i = 0; i < splitFile.Length - 1; ++i)
{
directory = directory.GetSubDirectory(splitFile[i]);
}
directory.AddFile(file);
}
Write(Template.Project.ProjectItemGroupBegin, writer, resolver);
foreach (ProjectDirectory subDirectory in rootDirectory.Directories)
WriteProjectDirectory(writer, subDirectory, " ", true);
foreach (string file in rootDirectory.Files)
WriteProjectFile(writer, file, " ");
Write(Template.Project.ProjectItemGroupEnd, writer, resolver);
}
private void GenerateFolders(StreamWriter writer, Resolver resolver)
{
Strings projectFiles = _project.GetSourceFilesForConfigurations(_projectConfigurationList);
// Add source files
ProjectDirectory rootDirectory = new ProjectDirectory(null, _project.SourceRootPath);
foreach (string file in projectFiles)
{
string relativeFile = GetProperRelativePathToSourcePath(file);
relativeFile = relativeFile.Trim('.', '\\', '/');
string[] splitFile = relativeFile.Split('\\', '/');
ProjectDirectory directory = rootDirectory;
for (int i = 0; i < splitFile.Length - 1; ++i)
{
directory = directory.GetSubDirectory(splitFile[i]);
}
directory.AddFile(file);
}
Write(Template.Project.ProjectItemGroupBegin, writer, resolver);
foreach (ProjectDirectory subDirectory in rootDirectory.Directories)
WriteProjectDirectory(writer, subDirectory, " ", false);
Write(Template.Project.ProjectItemGroupEnd, writer, resolver);
}
private void WriteProjectDirectory(StreamWriter writer, ProjectDirectory directory, string prefix, bool writeFiles)
{
if (writeFiles)
{
foreach (string filename in directory.Files)
{
WriteProjectFile(writer, filename, prefix);
}
}
else
{
writer.WriteLine(prefix + "<Folder Include=\"" + GetProperRelativePathToSourcePath(directory.Path) + "\" />");
}
foreach (ProjectDirectory subDirectory in directory.Directories)
{
WriteProjectDirectory(writer, subDirectory, prefix, writeFiles);
}
}
private void WriteProjectFile(StreamWriter writer, string filename, string prefix)
{
string fileTag = (Path.GetExtension(filename) == ".py") ? "Compile" : "Content";
writer.WriteLine(prefix + "<" + fileTag + " Include=\"" + GetProperRelativePathToSourcePath(filename) + "\" />");
}
private string GetProperRelativePathToSourcePath(string path)
{
return Util.PathGetRelative(_project.SourceRootPath, _project.IsSourceFilesCaseSensitive ? Util.GetCapitalizedPath(path) : path);
}
private static string GetRegistryCurrentUserSubKeyValue(string registrySubKey, string value, string fallbackValue)
{
string key = string.Empty;
if (OperatingSystem.IsWindows())
{
using (RegistryKey subKey = Registry.CurrentUser.OpenSubKey(registrySubKey))
key = (string)subKey?.GetValue(value);
}
if (string.IsNullOrEmpty(key))
key = fallbackValue;
return key;
}
private class ProjectDirectory
{
public string Name;
public string Path;
public List<string> Files = new List<string>();
public List<ProjectDirectory> Directories = new List<ProjectDirectory>();
public ProjectDirectory(string name, string path)
{
Name = name;
Path = path;
}
public ProjectDirectory GetSubDirectory(string name)
{
foreach (ProjectDirectory dir in Directories)
{
if (dir.Name == name)
return dir;
}
ProjectDirectory newDir = new ProjectDirectory(name, Path + "\\" + name);
Directories.Add(newDir);
return newDir;
}
public void AddFile(string filename)
{
Files.Add(filename);
}
}
}
}