Flow project metadata to assembly

This commit is contained in:
Pavel Krymets 2015-12-14 10:05:38 -08:00
parent fb461c27bf
commit ab5b16c00c
8 changed files with 246 additions and 12 deletions

View file

@ -0,0 +1,72 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Resources;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.IO;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.Dotnet.Cli.Compiler.Common
{
public class AssemblyInfoFileGenerator
{
public static string Generate(AssemblyInfoOptions metadata, IEnumerable<string> sourceFiles)
{
var projectAttributes = new Dictionary<Type, string>()
{
[typeof(AssemblyTitleAttribute)] = EscapeCharacters(metadata.Title),
[typeof(AssemblyDescriptionAttribute)] = EscapeCharacters(metadata.Description),
[typeof(AssemblyCopyrightAttribute)] = EscapeCharacters(metadata.Copyright),
[typeof(AssemblyFileVersionAttribute)] = EscapeCharacters(metadata.AssemblyFileVersion?.ToString()),
[typeof(AssemblyVersionAttribute)] = EscapeCharacters(metadata.AssemblyVersion?.ToString()),
[typeof(AssemblyInformationalVersionAttribute)] = EscapeCharacters(metadata.InformationalVersion),
[typeof(AssemblyCultureAttribute)] = EscapeCharacters(metadata.Culture),
[typeof(NeutralResourcesLanguageAttribute)] = EscapeCharacters(metadata.NeutralLanguage)
};
var existingAttributes = new List<Type>();
foreach (var sourceFile in sourceFiles)
{
var tree = CSharpSyntaxTree.ParseText(File.ReadAllText(sourceFile));
var root = tree.GetRoot();
// assembly attributes can be only on first level
foreach (var attributeListSyntax in root.ChildNodes().OfType<AttributeListSyntax>())
{
if (attributeListSyntax.Target.Identifier.Kind() == SyntaxKind.AssemblyKeyword)
{
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
var projectAttribute = projectAttributes.FirstOrDefault(attribute => IsSameAttribute(attribute.Key, attributeSyntax));
if (projectAttribute.Key != null)
{
existingAttributes.Add(projectAttribute.Key);
}
}
}
}
}
return string.Join(Environment.NewLine, projectAttributes
.Where(projectAttribute => projectAttribute.Value != null && !existingAttributes.Contains(projectAttribute.Key))
.Select(projectAttribute => $"[assembly:{projectAttribute.Key.FullName}(\"{projectAttribute.Value}\")]"));
}
private static bool IsSameAttribute(Type attributeType, AttributeSyntax attributeSyntax)
{
var name = attributeSyntax.Name.ToString();
// This check is quite stupid but we can not do more without semantic model
return attributeType.FullName.StartsWith(name) || attributeType.Name.StartsWith(name);
}
private static string EscapeCharacters(string str)
{
return str != null ? SymbolDisplay.FormatLiteral(str, quote: false) : null;
}
}
}

View file

@ -0,0 +1,143 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.DotNet.ProjectModel;
using System;
using System.Collections.Generic;
using System.CommandLine;
namespace Microsoft.Dotnet.Cli.Compiler.Common
{
public class AssemblyInfoOptions
{
private const string TitleOptionName = "title";
private const string DescriptionOptionName = "description";
private const string CopyrightOptionName = "copyright";
private const string AssemblyFileVersionOptionName = "file-version";
private const string AssemblyVersionOptionName = "version";
private const string InformationalVersionOptionName = "informational-version";
private const string CultureOptionName = "culture";
private const string NeutralCultureOptionName = "neutral-language";
public string Title { get; set; }
public string Description { get; set; }
public string Copyright { get; set; }
public string AssemblyFileVersion { get; set; }
public string AssemblyVersion { get; set; }
public string InformationalVersion { get; set; }
public string Culture { get; set; }
public string NeutralLanguage { get; set; }
public static AssemblyInfoOptions CreateForProject(Project project)
{
return new AssemblyInfoOptions()
{
AssemblyVersion = project.Version?.Version.ToString(),
AssemblyFileVersion = project.AssemblyFileVersion.ToString(),
InformationalVersion = project.Version.ToString(),
Copyright = project.Copyright,
Description = project.Description,
Title = project.Title,
NeutralLanguage = project.Language
};
}
public static AssemblyInfoOptions Parse(ArgumentSyntax syntax)
{
string version = null;
string informationalVersion = null;
string fileVersion = null;
string title = null;
string description = null;
string copyright = null;
string culture = null;
string neutralCulture = null;
syntax.DefineOption(AssemblyVersionOptionName, ref version, "Assembly version");
syntax.DefineOption(TitleOptionName, ref title, "Assembly title");
syntax.DefineOption(DescriptionOptionName, ref description, "Assembly description");
syntax.DefineOption(CopyrightOptionName, ref copyright, "Assembly copyright");
syntax.DefineOption(NeutralCultureOptionName, ref neutralCulture, "Assembly neutral culture");
syntax.DefineOption(CultureOptionName, ref culture, "Assembly culture");
syntax.DefineOption(InformationalVersionOptionName, ref informationalVersion, "Assembly informational version");
syntax.DefineOption(AssemblyFileVersionOptionName, ref fileVersion, "Assembly title");
return new AssemblyInfoOptions()
{
AssemblyFileVersion = fileVersion,
AssemblyVersion = version,
Copyright = copyright,
NeutralLanguage = neutralCulture,
Description = description,
InformationalVersion = informationalVersion,
Title = title
};
}
public static IEnumerable<string> SerializeToArgs(AssemblyInfoOptions assemblyInfoOptions)
{
var options = new List<string>();
if (!string.IsNullOrWhiteSpace(assemblyInfoOptions.Title))
{
options.Add(FormatOption(TitleOptionName, assemblyInfoOptions.Title));
}
if (!string.IsNullOrWhiteSpace(assemblyInfoOptions.Description))
{
options.Add(FormatOption(DescriptionOptionName, assemblyInfoOptions.Description));
}
if (!string.IsNullOrWhiteSpace(assemblyInfoOptions.Copyright))
{
options.Add(FormatOption(CopyrightOptionName, assemblyInfoOptions.Copyright));
}
if (!string.IsNullOrWhiteSpace(assemblyInfoOptions.AssemblyFileVersion))
{
options.Add(FormatOption(AssemblyFileVersionOptionName, assemblyInfoOptions.AssemblyFileVersion));
}
if (!string.IsNullOrWhiteSpace(assemblyInfoOptions.AssemblyVersion))
{
options.Add(FormatOption(AssemblyVersionOptionName, assemblyInfoOptions.AssemblyVersion));
}
if (!string.IsNullOrWhiteSpace(assemblyInfoOptions.InformationalVersion))
{
options.Add(FormatOption(InformationalVersionOptionName, assemblyInfoOptions.InformationalVersion));
}
if (!string.IsNullOrWhiteSpace(assemblyInfoOptions.Culture))
{
options.Add(FormatOption(CultureOptionName, assemblyInfoOptions.Culture));
}
if (!string.IsNullOrWhiteSpace(assemblyInfoOptions.NeutralLanguage))
{
options.Add(FormatOption(NeutralCultureOptionName, assemblyInfoOptions.NeutralLanguage));
}
return options;
}
private static string FormatOption(string optionName, string value)
{
return $"--{optionName}:{value}";
}
}
}

View file

@ -4,7 +4,9 @@
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23608",
"System.Linq": "4.0.1-rc2-23608",
"System.Reflection": "4.0.10-rc2-23608",
"System.CommandLine": "0.1.0-*",
"Microsoft.CodeAnalysis.CSharp": "1.1.1",
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
"Microsoft.DotNet.Cli.Utils": {
"type": "build",

View file

@ -13,6 +13,7 @@ using System.Text;
using Microsoft.DotNet.Cli.Compiler.Common;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.Dotnet.Cli.Compiler.Common;
namespace Microsoft.DotNet.Tools.Compiler.Csc
{
@ -25,6 +26,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Csc
DebugHelper.HandleDebugSwitch(ref args);
CommonCompilerOptions commonOptions = null;
AssemblyInfoOptions assemblyInfoOptions = null;
string tempOutDir = null;
IReadOnlyList<string> references = Array.Empty<string>();
IReadOnlyList<string> resources = Array.Empty<string>();
@ -37,6 +39,8 @@ namespace Microsoft.DotNet.Tools.Compiler.Csc
{
commonOptions = CommonCompilerOptionsExtensions.Parse(syntax);
assemblyInfoOptions = AssemblyInfoOptions.Parse(syntax);
syntax.DefineOption("temp-output", ref tempOutDir, "Compilation temporary directory");
syntax.DefineOption("out", ref outputName, "Name of the output assembly");
@ -46,7 +50,6 @@ namespace Microsoft.DotNet.Tools.Compiler.Csc
syntax.DefineOptionList("resource", ref resources, "Resources to embed");
syntax.DefineParameterList("source-files", ref sources, "Compilation sources");
if (tempOutDir == null)
{
syntax.ReportError("Option '--temp-output' is required");
@ -63,6 +66,11 @@ namespace Microsoft.DotNet.Tools.Compiler.Csc
var allArgs = new List<string>(translated);
allArgs.AddRange(GetDefaultOptions());
// Generate assembly info
var assemblyInfo = Path.Combine(tempOutDir, $"dotnet-compile.assemblyinfo.cs");
File.WriteAllText(assemblyInfo, AssemblyInfoFileGenerator.Generate(assemblyInfoOptions, sources));
allArgs.Add($"\"{assemblyInfo}\"");
if (outputName != null)
{
allArgs.Add($"-out:\"{outputName}\"");

View file

@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Dnx.Runtime.Common.CommandLine;
using Microsoft.Dotnet.Cli.Compiler.Common;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Compiler.Common;
using Microsoft.DotNet.Tools.Common;
@ -309,6 +310,9 @@ namespace Microsoft.DotNet.Tools.Compiler
// Add compilation options to the args
compilerArgs.AddRange(compilationOptions.SerializeToArgs());
// Add metadata options
compilerArgs.AddRange(AssemblyInfoOptions.SerializeToArgs(AssemblyInfoOptions.CreateForProject(context.ProjectFile)));
foreach (var dependency in dependencies)
{
var projectDependency = dependency.Library as ProjectDescription;
@ -332,7 +336,6 @@ namespace Microsoft.DotNet.Tools.Compiler
{
return false;
}
// Add project source files
var sourceFiles = context.ProjectFile.Files.SourceFiles;
compilerArgs.AddRange(sourceFiles);
@ -660,7 +663,7 @@ namespace Microsoft.DotNet.Tools.Compiler
// {file}.resx -> {file}.resources
var resourcesFile = Path.Combine(intermediateOutputPath, name);
var result = Command.Create("dotnet-resgen", $"\"{fileName}\" -o \"{resourcesFile}\"")
var result = Command.Create("dotnet-resgen", $"\"{fileName}\" -o \"{resourcesFile}\" -v \"{project.Version.Version}\"")
.ForwardStdErr()
.ForwardStdOut()
.Execute();
@ -725,6 +728,7 @@ namespace Microsoft.DotNet.Tools.Compiler
arguments.AddRange(references.Select(r => $"-r \"{r.ResolvedPath}\""));
arguments.Add($"-o \"{resourceOuputFile}\"");
arguments.Add($"-c {culture}");
arguments.Add($"-v {project.Version.Version}");
foreach (var resourceFile in resourceFileGroup)
{

View file

@ -6,6 +6,7 @@ using System.Linq;
using Microsoft.Dnx.Runtime.Common.CommandLine;
using Microsoft.DotNet.Cli.Utils;
using System;
using Microsoft.Dotnet.Cli.Compiler.Common;
namespace Microsoft.DotNet.Tools.Resgen
{
@ -23,6 +24,7 @@ namespace Microsoft.DotNet.Tools.Resgen
var ouputFile = app.Option("-o", "Output file name", CommandOptionType.SingleValue);
var culture = app.Option("-c", "Ouput assembly culture", CommandOptionType.SingleValue);
var version = app.Option("-v", "Ouput assembly version", CommandOptionType.SingleValue);
var references = app.Option("-r", "Compilation references", CommandOptionType.MultipleValue);
var inputFiles = app.Argument("<input>", "Input files", true);
@ -42,10 +44,14 @@ namespace Microsoft.DotNet.Tools.Resgen
case ResourceFileType.Dll:
using (var outputStream = outputResourceFile.File.Create())
{
var metadata = new AssemblyInfoOptions();
metadata.Culture = culture.Value();
metadata.AssemblyVersion = version.Value();
ResourceAssemblyGenerator.Generate(intputResourceFiles,
outputStream,
metadata,
Path.GetFileNameWithoutExtension(outputResourceFile.File.Name),
culture.Value(),
references.Values.ToArray()
);
}

View file

@ -7,13 +7,14 @@ using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Dotnet.Cli.Compiler.Common;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.Resgen
{
internal class ResourceAssemblyGenerator
{
public static void Generate(ResourceSource[] sourceFiles, Stream outputStream, string asemblyName, string culture, string[] references)
public static void Generate(ResourceSource[] sourceFiles, Stream outputStream, AssemblyInfoOptions metadata, string assemblyName, string[] references)
{
if (sourceFiles == null)
{
@ -51,17 +52,14 @@ namespace Microsoft.DotNet.Tools.Resgen
}
var compilationOptions = new CSharpCompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(asemblyName,
var compilation = CSharpCompilation.Create(assemblyName,
references: references.Select(reference => MetadataReference.CreateFromFile(reference)),
options: compilationOptions);
if (!string.IsNullOrEmpty(culture))
compilation = compilation.AddSyntaxTrees(new[]
{
compilation = compilation.AddSyntaxTrees(new[]
{
CSharpSyntaxTree.ParseText($"[assembly:System.Reflection.AssemblyCultureAttribute(\"{culture}\")]")
});
}
CSharpSyntaxTree.ParseText(AssemblyInfoFileGenerator.Generate(metadata, Enumerable.Empty<string>()))
});
var result = compilation.Emit(outputStream, manifestResources: resourceDescriptions);
if (!result.Success)

View file

@ -11,6 +11,7 @@
"System.Resources.ReaderWriter": "4.0.0-rc2-23608",
"Microsoft.CodeAnalysis.CSharp": "1.1.1",
"Microsoft.DotNet.Compiler.Common": "1.0.0-*",
"Microsoft.DotNet.Cli.Utils": {
"type": "build",
"version": "1.0.0-*"