using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.XPath;
using ServiceClientGenerator.Generators.ProjectFiles;
namespace ServiceClientGenerator
{
///
/// Class used to emit the set of per-platform project files for a service.
/// Existing project files are retained, only missing files are generated.
///
public class ProjectFileCreator
{
///
/// On conclusion, the set of project files that were generated for the service.
///
public Dictionary CreatedProjectFiles { get; set; }
private GeneratorOptions Options;
public ProjectFileCreator(GeneratorOptions options)
{
Options = options;
}
public void ExecuteCore(string coreFilesRoot, IEnumerable projectFileConfigurations)
{
CreatedProjectFiles = new Dictionary();
foreach (var projectFileConfiguration in projectFileConfigurations)
{
var projectType = projectFileConfiguration.Name;
var assemblyName = "AWSSDK.Core";
var projectFilename = string.Concat(assemblyName, ".", projectType, ".csproj");
bool newProject = false;
if (projectFileConfiguration.Template.Equals("VS2017ProjectFile", StringComparison.InvariantCultureIgnoreCase))
{
// for vs2017 projects, skip csproject generation
}
else
{
string projectGuid;
if (File.Exists(Utils.PathCombineAlt(coreFilesRoot, projectFilename)))
{
Console.WriteLine("...updating existing project file {0}", projectFilename);
var projectPath = Utils.PathCombineAlt(coreFilesRoot, projectFilename);
projectGuid = Utils.GetProjectGuid(projectPath);
}
else
{
newProject = true;
projectGuid = Utils.NewProjectGuid;
Console.WriteLine("...creating project file {0}", projectFilename);
}
var projectProperties = new Project();
projectProperties.Name = projectFileConfiguration.Name;
projectProperties.ProjectGuid = projectGuid;
projectProperties.RootNamespace = "Amazon";
projectProperties.AssemblyName = assemblyName;
projectProperties.SourceDirectories = GetCoreProjectSourceFolders(projectFileConfiguration, coreFilesRoot);
projectProperties.IndividualFileIncludes = new List
{
"endpoints.json",
};
projectProperties.KeyFilePath = Utils.PathCombineAlt("..", "..", "awssdk.dll.snk");
projectProperties.SupressWarnings = "419,1570,1591";
projectProperties.NugetPackagesLocation = Utils.PathCombineAlt("..", "..", "packages");
projectProperties.FxcopAnalyzerRuleSetFilePath = Utils.PathCombineAlt("..", "..", "AWSDotNetSDK.ruleset");
projectProperties.FxcopAnalyzerRuleSetFilePathForBuild = Utils.PathCombineAlt("..", "..", "AWSDotNetSDKForBuild.ruleset");
projectProperties.TargetFrameworks = projectFileConfiguration.TargetFrameworkVersions;
projectProperties.DefineConstants = projectFileConfiguration.CompilationConstants;
projectProperties.BinSubfolder = projectFileConfiguration.BinSubFolder;
projectProperties.PackageReferences = projectFileConfiguration.PackageReferences;
projectProperties.CustomRoslynAnalyzersDllDirectory = Utils.PathCombineAlt("..", "..", "..", "buildtools", "CustomRoslynAnalyzers.dll");
var projectConfigurationData = new ProjectConfigurationData { ProjectGuid = projectGuid };
var projectName = Path.GetFileNameWithoutExtension(projectFilename);
if (newProject)
CreatedProjectFiles[projectName] = projectConfigurationData;
var projectReferences = new List();
projectProperties.ProjectReferences = projectReferences.OrderBy(x => x.Name).ToList();
GenerateProjectFile(projectFileConfiguration, projectConfigurationData, projectProperties, coreFilesRoot, projectFilename);
}
}
}
///
/// Creates the platform-specific project files for the given service configuration
///
/// The folder under which all of the source files for the service will exist
///
///
public void Execute(string serviceFilesRoot, ServiceConfiguration serviceConfiguration, IEnumerable projectFileConfigurations)
{
CreatedProjectFiles = new Dictionary();
var assemblyName = "AWSSDK." + serviceConfiguration.Namespace.Split('.')[1];
foreach (var projectFileConfiguration in projectFileConfigurations)
{
var projectType = projectFileConfiguration.Name;
if (projectType.Equals("NetStandard", StringComparison.InvariantCultureIgnoreCase) && !serviceConfiguration.NetStandardSupport)
continue;
if (projectFileConfiguration.Template.Equals("VS2017ProjectFile", StringComparison.InvariantCultureIgnoreCase))
{
var projectReferenceList = new List();
foreach (var dependency in serviceConfiguration.ServiceDependencies.Keys)
{
if (string.Equals(dependency, "Core", StringComparison.InvariantCultureIgnoreCase))
continue;
projectReferenceList.Add(new ProjectReference
{
IncludePath = Utils.PathCombineAlt("..", "..", "Services", dependency, $"AWSSDK.{dependency}.{projectType}.csproj")
});
}
projectReferenceList.Add(new ProjectReference
{
IncludePath = serviceConfiguration.IsTestService
? Utils.PathCombineAlt("..", "..", "..", "src", "Core", $"AWSSDK.Core.{projectType}.csproj")
: Utils.PathCombineAlt("..", "..", "Core", $"AWSSDK.Core.{projectType}.csproj")
}); ;
projectFileConfiguration.ProjectReferences = projectReferenceList;
GenerateVS2017ProjectFile(serviceFilesRoot, serviceConfiguration, projectFileConfiguration);
continue;
}
var projectFilename = string.Concat(assemblyName, ".", projectType, ".csproj");
bool newProject = false;
string projectGuid;
if (File.Exists(Utils.PathCombineAlt(serviceFilesRoot, projectFilename)))
{
Console.WriteLine("...updating existing project file {0}", projectFilename);
var projectPath = Utils.PathCombineAlt(serviceFilesRoot, projectFilename);
projectGuid = Utils.GetProjectGuid(projectPath);
}
else
{
newProject = true;
projectGuid = Utils.NewProjectGuid;
Console.WriteLine("...creating project file {0}", projectFilename);
}
var projectProperties = new Project();
projectProperties.Name = projectFileConfiguration.Name;
projectProperties.ProjectGuid = projectGuid;
projectProperties.RootNamespace = serviceConfiguration.Namespace;
projectProperties.AssemblyName = assemblyName;
projectProperties.SourceDirectories = GetProjectSourceFolders(projectFileConfiguration, serviceFilesRoot);
projectProperties.NugetPackagesLocation = Utils.PathCombineAlt("..", "..", "..", "packages");
projectProperties.FxcopAnalyzerRuleSetFilePath = Utils.PathCombineAlt("..", "..", "..", "AWSDotNetSDK.ruleset");
projectProperties.FxcopAnalyzerRuleSetFilePathForBuild = Utils.PathCombineAlt("..", "..", "..", "AWSDotNetSDKForBuild.ruleset");
projectProperties.TargetFrameworks = projectFileConfiguration.TargetFrameworkVersions;
projectProperties.DefineConstants = projectFileConfiguration.CompilationConstants;
projectProperties.BinSubfolder = projectFileConfiguration.BinSubFolder;
projectProperties.PackageReferences = projectFileConfiguration.PackageReferences;
projectProperties.CustomRoslynAnalyzersDllDirectory = Utils.PathCombineAlt("..", "..", "..", "..", "buildtools", "CustomRoslynAnalyzers.dll");
var projectConfigurationData = new ProjectConfigurationData { ProjectGuid = projectGuid };
var projectName = Path.GetFileNameWithoutExtension(projectFilename);
if (newProject)
CreatedProjectFiles[projectName] = projectConfigurationData;
var projectReferences = new List();
if (serviceConfiguration.ServiceDependencies != null)
{
foreach (var dependency in serviceConfiguration.ServiceDependencies)
{
var dependencyProjectName = "AWSSDK." + dependency.Key + "." + projectType;
string dependencyProject;
if (string.Equals(dependency.Key, "Core", StringComparison.InvariantCultureIgnoreCase))
{
dependencyProject = Utils.PathCombineAlt("..", "..", dependency.Key, dependencyProjectName, ".csproj");
}
else
{
dependencyProject = Utils.PathCombineAlt("..", dependency.Key, dependencyProjectName, ".csproj");
}
projectReferences.Add(new ProjectReference
{
IncludePath = dependencyProject,
ProjectGuid = Utils.GetProjectGuid(Utils.PathCombineAlt(serviceFilesRoot, dependencyProject)),
Name = dependencyProjectName
});
}
}
projectProperties.ProjectReferences = projectReferences.OrderBy(x => x.Name).ToList();
if (serviceConfiguration.ReferenceDependencies != null &&
serviceConfiguration.ReferenceDependencies.ContainsKey(projectFileConfiguration.Name))
{
projectProperties.ReferenceDependencies = serviceConfiguration.ReferenceDependencies[projectFileConfiguration.Name];
}
GenerateProjectFile(projectFileConfiguration, projectConfigurationData, projectProperties, serviceFilesRoot, projectFilename);
}
}
///
/// Invokes the T4 generator to emit a platform-specific project file.
///
///
///
///
///
private void GenerateProjectFile(ProjectFileConfiguration projectFileConfiguration,
ProjectConfigurationData projectConfiguration,
Project projectProperties,
string serviceFilesRoot,
string projectFilename)
{
var projectName = Path.GetFileNameWithoutExtension(projectFilename);
string generatedContent = null;
try
{
var projectTemplateType = Type.GetType(
"ServiceClientGenerator.Generators.ProjectFiles." +
projectFileConfiguration.Template);
dynamic generator = Activator.CreateInstance(projectTemplateType);
generator.Project = projectProperties;
generatedContent = generator.TransformText();
}
catch (Exception)
{
throw new ArgumentException("Project template name "
+ projectFileConfiguration.Template + " is not recognized");
}
GeneratorDriver.WriteFile(serviceFilesRoot, string.Empty, projectFilename, generatedContent);
projectConfiguration.ConfigurationPlatforms = projectFileConfiguration.Configurations;
}
private void GenerateVS2017ProjectFile(string serviceFilesRoot, ServiceConfiguration serviceConfiguration, ProjectFileConfiguration projectFileConfiguration)
{
var assemblyName = "AWSSDK." + serviceConfiguration.Namespace.Split('.')[1];
var projectType = projectFileConfiguration.Name;
var projectProperties = new Project();
projectProperties.AssemblyName = assemblyName;
projectProperties.ProjectReferences = projectFileConfiguration.ProjectReferences;
projectProperties.TargetFrameworks = projectFileConfiguration.TargetFrameworkVersions;
projectProperties.DefineConstants = projectFileConfiguration.CompilationConstants;
projectProperties.CompileRemoveList = projectFileConfiguration.PlatformExcludeFolders.ToList();
if (serviceConfiguration.IsTestService)
{
var toExclude = projectProperties.CompileRemoveList as List;
toExclude.Add("UnitTests");
}
projectProperties.FrameworkPathOverride = projectFileConfiguration.FrameworkPathOverride;
projectProperties.ReferenceDependencies = projectFileConfiguration.DllReferences;
projectProperties.SupressWarnings = projectFileConfiguration.NoWarn;
projectProperties.SignBinaries = true;
projectProperties.PackageReferences = projectFileConfiguration.PackageReferences;
projectProperties.FxcopAnalyzerRuleSetFilePath = Utils.PathCombineAlt("..", "..", "..", "AWSDotNetSDK.ruleset");
projectProperties.FxcopAnalyzerRuleSetFilePathForBuild = Utils.PathCombineAlt("..", "..", "..", "AWSDotNetSDKForBuild.ruleset");
projectProperties.CustomRoslynAnalyzersDllDirectory = Utils.PathCombineAlt("..", "..", "..", "..", "buildtools", "CustomRoslynAnalyzers.dll");
List dependencies;
List references = new List();
if ( serviceConfiguration.NugetDependencies != null &&
serviceConfiguration.NugetDependencies.TryGetValue(projectFileConfiguration.Name, out dependencies))
{
foreach(var dependency in dependencies)
{
references.Add(new PackageReference
{
Include = dependency.Name,
Version = dependency.Version,
});
}
projectProperties.PackageReferences = references;
}
var projectJsonTemplate = new VS2017ProjectFile();
projectJsonTemplate.Project = projectProperties;
projectJsonTemplate.ServiceConfiguration = serviceConfiguration;
var content = projectJsonTemplate.TransformText();
GeneratorDriver.WriteFile(serviceFilesRoot, string.Empty, string.Format("{0}.{1}.csproj", assemblyName, projectType), content);
}
///
/// Returns the collection of subfolders containing source code that need to be
/// included in the project file. This is comprised the standard platform folders
/// under Generated, plus any custom folders we find that are not otherwise handled
/// (eg Properties).
///
///
/// The .Net project type we are generating. This governs the platform-specific
/// subfolders that get included in the project.
///
/// The root output folder for the service code
///
private IList GetCoreProjectSourceFolders(ProjectFileConfiguration projectFileConfiguration, string coreRootFolder)
{
var exclusionList = new List
{
"Properties",
"bin",
"obj",
".vs"
};
// Start with the standard folders for core
var sourceCodeFolders = new List
{
"."
};
var childDirectories = Directory.GetDirectories(coreRootFolder, "*", SearchOption.AllDirectories).OrderBy(d => d);
foreach (var childDirectory in childDirectories)
{
var childDirectoryAlt = Utils.ConvertPathAlt(childDirectory);
var folder = childDirectoryAlt.Substring(coreRootFolder.Length).TrimStart('/');
if (exclusionList.Any(e => folder.Equals(e, StringComparison.InvariantCulture) ||
folder.StartsWith(e + "/", StringComparison.InvariantCulture)))
continue;
if (projectFileConfiguration.IsPlatformCodeFolder(folder))
{
if (projectFileConfiguration.IsValidPlatformPathForProject(folder))
sourceCodeFolders.Add(folder);
}
else
{
sourceCodeFolders.Add(folder);
}
}
var foldersThatExist = new List();
foreach (var folder in sourceCodeFolders)
{
if (Directory.Exists(Utils.PathCombineAlt(coreRootFolder, folder)))
foldersThatExist.Add(folder);
}
return foldersThatExist;
}
///
/// Returns the collection of subfolders containing source code that need to be
/// included in the project file. This is comprised the standard platform folders
/// under Generated, plus any custom folders we find that are not otherwise handled
/// (eg Properties).
///
///
/// The .Net project type we are generating. This governs the platform-specific
/// subfolders that get included in the project.
///
/// The root output folder for the service code
///
private IList GetProjectSourceFolders(ProjectFileConfiguration projectFileConfiguration, string serviceRootFolder)
{
// Start with the standard generated code folders for the platform
var sourceCodeFolders = new List
{
"Generated",
Utils.PathCombineAlt("Generated", "Internal"),
Utils.PathCombineAlt("Generated", "Model"),
Utils.PathCombineAlt("Generated", "Model", "Internal"),
Utils.PathCombineAlt("Generated", "Model", "Internal", "MarshallTransformations")
};
var platformSubFolders = projectFileConfiguration.PlatformCodeFolders;
sourceCodeFolders.AddRange(platformSubFolders.Select(folder => Utils.PathCombineAlt(@"Generated", folder)));
// Augment the returned folders with any custom subfolders already in existence. If the custom folder
// ends with a recognised platform, only add it to the set if it matches the platform being generated
if (Directory.Exists(serviceRootFolder))
{
var subFolders = Directory.GetDirectories(serviceRootFolder, "*", SearchOption.AllDirectories).OrderBy(d => d);
if (subFolders.Any())
{
foreach (var folder in subFolders)
{
var serviceRelativeFolder = Utils.ConvertPathAlt(folder.Substring(serviceRootFolder.Length));
if (!serviceRelativeFolder.StartsWith(@"/Custom", StringComparison.OrdinalIgnoreCase))
continue;
if (projectFileConfiguration.IsPlatformCodeFolder(serviceRelativeFolder))
{
if (projectFileConfiguration.IsValidPlatformPathForProject(serviceRelativeFolder))
sourceCodeFolders.Add(serviceRelativeFolder.TrimStart('/'));
}
else
sourceCodeFolders.Add(serviceRelativeFolder.TrimStart('/'));
}
}
}
var foldersThatExist = new List();
foreach (var folder in sourceCodeFolders)
{
var folderAlt = Utils.ConvertPathAlt(folder);
if (Directory.Exists(Utils.PathCombineAlt(serviceRootFolder, folderAlt)))
foldersThatExist.Add(folderAlt);
}
// sort so we get a predictable layout
foldersThatExist.Sort(StringComparer.OrdinalIgnoreCase);
return foldersThatExist;
}
public class ProjectReference : IComparable
{
public string IncludePath { get; set; }
public string ProjectGuid { get; set; }
public string Name { get; set; }
int IComparable.CompareTo(ProjectReference that)
{
return string.Compare(this.Name, that.Name);
}
}
public class ProjectConfigurationData
{
public string ProjectGuid { get; set; }
public IEnumerable ConfigurationPlatforms { get; set; }
}
public class PackageReference
{
public string Include { get; set; }
public string Version { get; set; }
public string PrivateAssets { get; set; } = "none";
public string IncludeAssets { get; set; }
public bool IsAnalyzer { get; set; }
public bool HasPrivateAssets => PrivateAssets != "" && PrivateAssets != "none";
public static PackageReference ParseJson(Json.LitJson.JsonData data)
{
return new PackageReference
{
Include = data.SafeGetString("include"),
Version = data.SafeGetString("version"),
PrivateAssets = data.SafeGetString("privateAssets"),
IncludeAssets = data.SafeGetString("includeAssets"),
IsAnalyzer = data.SafeGet("analyzer") != null ? (bool) data.SafeGet("analyzer") : false
};
}
}
}
}