diff --git a/NuGet.config b/NuGet.config index 5500f6d50..52bf41419 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,4 +1,4 @@ - + diff --git a/misc/AssemblyInfo/global.json b/misc/AssemblyInfo/global.json new file mode 100644 index 000000000..4df3dbdac --- /dev/null +++ b/misc/AssemblyInfo/global.json @@ -0,0 +1,5 @@ +{ + "projects": [ + "src" + ] +} \ No newline at end of file diff --git a/misc/AssemblyInfo/src/Test/project.json b/misc/AssemblyInfo/src/Test/project.json new file mode 100644 index 000000000..1bf05e9d3 --- /dev/null +++ b/misc/AssemblyInfo/src/Test/project.json @@ -0,0 +1,18 @@ +{ + "title": "Hello title", + "description": "Hello description", + "language": "en", + "copyright": "Hello copyright", + "version": "1.2.3-*", + "commands": { + "HelloWorld": "HelloWorld" + }, + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + "System.Console": "4.0.0-*" + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Dnx.Compilation.Abstractions/CompilationProjectContext.cs b/src/Microsoft.Dnx.Compilation.Abstractions/CompilationProjectContext.cs index 6f299ebb7..bb947ce56 100644 --- a/src/Microsoft.Dnx.Compilation.Abstractions/CompilationProjectContext.cs +++ b/src/Microsoft.Dnx.Compilation.Abstractions/CompilationProjectContext.cs @@ -11,6 +11,9 @@ public class CompilationProjectContext public CompilationTarget Target { get; } public string ProjectDirectory { get; } public string ProjectFilePath { get; } + public string Title { get; } + public string Description { get; } + public string Copyright { get; } public string Version { get; } public Version AssemblyFileVersion { get; } public CompilationFiles Files { get; } @@ -24,6 +27,9 @@ public CompilationProjectContext( CompilationTarget target, string projectDirectory, string projectFilePath, + string title, + string description, + string copyright, string version, Version assemblyFileVersion, bool embedInteropTypes, @@ -34,6 +40,9 @@ public CompilationProjectContext( ProjectDirectory = projectDirectory; ProjectFilePath = projectFilePath; Files = files; + Title = title; + Description = description; + Copyright = copyright; Version = version; AssemblyFileVersion = assemblyFileVersion; EmbedInteropTypes = embedInteropTypes; diff --git a/src/Microsoft.Dnx.Compilation.CSharp/RoslynCompiler.cs b/src/Microsoft.Dnx.Compilation.CSharp/RoslynCompiler.cs index 1f23eb720..580461636 100644 --- a/src/Microsoft.Dnx.Compilation.CSharp/RoslynCompiler.cs +++ b/src/Microsoft.Dnx.Compilation.CSharp/RoslynCompiler.cs @@ -127,7 +127,7 @@ public CompilationContext CompileProject( references, compilationSettings.CompilationOptions); - compilation = ApplyVersionInfo(compilation, projectContext, parseOptions); + compilation = ApplyProjectInfo(compilation, projectContext, parseOptions); var compilationContext = new CompilationContext( compilation, @@ -251,58 +251,30 @@ private CompilationModules GetCompileModules(CompilationTarget target) }); } - private static CSharpCompilation ApplyVersionInfo(CSharpCompilation compilation, CompilationProjectContext project, + private static CSharpCompilation ApplyProjectInfo(CSharpCompilation compilation, CompilationProjectContext project, CSharpParseOptions parseOptions) { - const string assemblyFileVersionName = "System.Reflection.AssemblyFileVersionAttribute"; - const string assemblyVersionName = "System.Reflection.AssemblyVersionAttribute"; - const string assemblyInformationalVersion = "System.Reflection.AssemblyInformationalVersionAttribute"; - - var assemblyAttributes = compilation.Assembly.GetAttributes(); - - var foundAssemblyFileVersion = false; - var foundAssemblyVersion = false; - var foundAssemblyInformationalVersion = false; - - foreach (var assembly in assemblyAttributes) - { - string attributeName = assembly.AttributeClass.ToString(); - - if (string.Equals(attributeName, assemblyFileVersionName, StringComparison.Ordinal)) - { - foundAssemblyFileVersion = true; - } - else if (string.Equals(attributeName, assemblyVersionName, StringComparison.Ordinal)) - { - foundAssemblyVersion = true; - } - else if (string.Equals(attributeName, assemblyInformationalVersion, StringComparison.Ordinal)) - { - foundAssemblyInformationalVersion = true; - } - } - - var versionAttributes = new StringBuilder(); - if (!foundAssemblyFileVersion) - { - versionAttributes.AppendLine($"[assembly:{assemblyFileVersionName}(\"{project.AssemblyFileVersion}\")]"); - } - - if (!foundAssemblyVersion) + var projectAttributes = new Dictionary(StringComparer.Ordinal) { - versionAttributes.AppendLine($"[assembly:{assemblyVersionName}(\"{RemovePrereleaseTag(project.Version)}\")]"); - } + [typeof(System.Reflection.AssemblyTitleAttribute).FullName] = project.Title, + [typeof(System.Reflection.AssemblyDescriptionAttribute).FullName] = project.Description, + [typeof(System.Reflection.AssemblyCopyrightAttribute).FullName] = project.Copyright, + [typeof(System.Reflection.AssemblyFileVersionAttribute).FullName] = project.AssemblyFileVersion.ToString(), + [typeof(System.Reflection.AssemblyVersionAttribute).FullName] = RemovePrereleaseTag(project.Version), + [typeof(System.Reflection.AssemblyInformationalVersionAttribute).FullName] = project.Version + }; - if (!foundAssemblyInformationalVersion) - { - versionAttributes.AppendLine($"[assembly:{assemblyInformationalVersion}(\"{project.Version}\")]"); - } + var assemblyAttributes = compilation.Assembly.GetAttributes() + .Select(assemblyAttribute => assemblyAttribute.AttributeClass.ToString()); + var newAttributes = string.Join(Environment.NewLine, projectAttributes + .Where(projectAttribute => projectAttribute.Value != null && !assemblyAttributes.Contains(projectAttribute.Key)) + .Select(projectAttribute => $"[assembly:{projectAttribute.Key}(\"{projectAttribute.Value}\")]")); - if (versionAttributes.Length != 0) + if (!string.IsNullOrWhiteSpace(newAttributes)) { compilation = compilation.AddSyntaxTrees(new[] { - CSharpSyntaxTree.ParseText(versionAttributes.ToString(), parseOptions) + CSharpSyntaxTree.ParseText(newAttributes, parseOptions) }); } @@ -361,7 +333,7 @@ private IList GetSyntaxTrees(CompilationProjectContext project, foreach (var d in dirs) { - ctx.Monitor(new FileWriteTimeCacheDependency(d)); + ctx?.Monitor(new FileWriteTimeCacheDependency(d)); } return trees; diff --git a/src/Microsoft.Dnx.Compilation/ProjectExtensions.cs b/src/Microsoft.Dnx.Compilation/ProjectExtensions.cs index 1f30c73eb..1fc4467d9 100644 --- a/src/Microsoft.Dnx.Compilation/ProjectExtensions.cs +++ b/src/Microsoft.Dnx.Compilation/ProjectExtensions.cs @@ -11,6 +11,9 @@ public static CompilationProjectContext ToCompilationContext(this Project self, new CompilationTarget(self.Name, frameworkName, configuration, aspect), self.ProjectDirectory, self.ProjectFilePath, + self.Title, + self.Description, + self.Copyright, self.Version.ToString(), self.AssemblyFileVersion, self.EmbedInteropTypes, diff --git a/src/Microsoft.Dnx.Compilation/Properties/AssemblyInfo.cs b/src/Microsoft.Dnx.Compilation/Properties/AssemblyInfo.cs index 7c654bb28..815dbe8d4 100644 --- a/src/Microsoft.Dnx.Compilation/Properties/AssemblyInfo.cs +++ b/src/Microsoft.Dnx.Compilation/Properties/AssemblyInfo.cs @@ -6,5 +6,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.Dnx.Compilation.Tests")] +[assembly: InternalsVisibleTo("Microsoft.Dnx.Compilation.CSharp.Tests")] [assembly: AssemblyMetadata("Serviceable", "True")] [assembly: NeutralResourcesLanguage("en-US")] \ No newline at end of file diff --git a/test/Microsoft.Dnx.Compilation.CSharp.Tests/CompilationContextFacts.cs b/test/Microsoft.Dnx.Compilation.CSharp.Tests/CompilationContextFacts.cs index 419f84377..ecbaf084a 100644 --- a/test/Microsoft.Dnx.Compilation.CSharp.Tests/CompilationContextFacts.cs +++ b/test/Microsoft.Dnx.Compilation.CSharp.Tests/CompilationContextFacts.cs @@ -58,6 +58,9 @@ private static CompilationProjectContext CreateMockProject() new CompilationTarget("MockProject", new FrameworkName(".NETFramework, Version=8.0"), "fakeConfiguration", null), "c:\\wonderland", "c:\\wonderland\\project.json", + "Title", + "Description", + "Copyright", "0.0.1-rc-fake", new Version(0, 0, 1), embedInteropTypes: false, diff --git a/test/Microsoft.Dnx.Compilation.CSharp.Tests/RoslynCompilerTest.cs b/test/Microsoft.Dnx.Compilation.CSharp.Tests/RoslynCompilerTest.cs new file mode 100644 index 000000000..3bf42b362 --- /dev/null +++ b/test/Microsoft.Dnx.Compilation.CSharp.Tests/RoslynCompilerTest.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Versioning; +using Microsoft.CodeAnalysis; +using Microsoft.Dnx.Compilation.Caching; +using Microsoft.Dnx.Runtime; +#if DNX451 +using Moq; +#endif +using Xunit; + +namespace Microsoft.Dnx.Compilation.CSharp.Tests +{ + public class RoslynCompilerTest + { +#if DNX451 + [Fact] + public void FlowsProjectPropertiesIntoAssembly() + { + const string testName = "Test name"; + const string testTitle = "Test title"; + const string testDescription = "Test description"; + const string testCopyright = "Test copyright"; + const string testAssemblyFileVersion = "1.2.3.4"; + const string testVersion = "1.2.3-rc1"; + const string testFrameworkName = "DNX,Version=v4.5.1"; + + // Arrange + var compilationProjectContext = new CompilationProjectContext( + new CompilationTarget(testName, new FrameworkName(testFrameworkName), string.Empty, string.Empty), + string.Empty, + string.Empty, + testTitle, + testDescription, + testCopyright, + testVersion, + new Version(testAssemblyFileVersion), + false, + new CompilationFiles( + new List { }, + new List { }), + new Mock().Object); + var compiler = new RoslynCompiler( + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object); + var metadataReference = new Mock(); + metadataReference + .Setup(reference => reference.MetadataReference) + .Returns(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)); + + // Act + var compilationContext = compiler.CompileProject( + compilationProjectContext, + new List { metadataReference.Object }, + new List { }, + () => new List { }); + + // Assert + var expectedAttributes = new Dictionary + { + [typeof(AssemblyTitleAttribute).FullName] = testTitle, + [typeof(AssemblyDescriptionAttribute).FullName] = testDescription, + [typeof(AssemblyCopyrightAttribute).FullName] = testCopyright, + [typeof(AssemblyFileVersionAttribute).FullName] = testAssemblyFileVersion, + [typeof(AssemblyVersionAttribute).FullName] = testVersion.Substring(0, testVersion.IndexOf('-')), + [typeof(AssemblyInformationalVersionAttribute).FullName] = testVersion, + }; + var compilationAttributes = compilationContext.Compilation.Assembly.GetAttributes(); + + Assert.All(compilationAttributes, compilationAttribute => expectedAttributes[compilationAttribute.AttributeClass.ToString()].Equals( + compilationAttribute.ConstructorArguments.First().Value)); + } +#endif + } +} diff --git a/test/Microsoft.Dnx.Compilation.CSharp.Tests/project.json b/test/Microsoft.Dnx.Compilation.CSharp.Tests/project.json index cc8aa40a5..5a9cbd0c0 100644 --- a/test/Microsoft.Dnx.Compilation.CSharp.Tests/project.json +++ b/test/Microsoft.Dnx.Compilation.CSharp.Tests/project.json @@ -5,7 +5,11 @@ "xunit.runner.aspnet": "2.0.0-aspnet-*" }, "frameworks": { - "dnx451": { }, + "dnx451": { + "dependencies": { + "Moq": "4.2.1312.1622" + } + }, "dnxcore50": { } }, "commands": { diff --git a/test/Microsoft.Dnx.Tooling.FunctionalTests/DnuPackTests.cs b/test/Microsoft.Dnx.Tooling.FunctionalTests/DnuPackTests.cs index 23ff68101..addd3397e 100644 --- a/test/Microsoft.Dnx.Tooling.FunctionalTests/DnuPackTests.cs +++ b/test/Microsoft.Dnx.Tooling.FunctionalTests/DnuPackTests.cs @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using System.Runtime.Versioning; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using Microsoft.AspNet.Testing.xunit; using Microsoft.Dnx.Testing.Framework; using Newtonsoft.Json.Linq; @@ -108,5 +111,78 @@ public void DnuPack_ResourcesNoArgs(DnxSdk sdk) TestUtils.CleanUpTestDir(sdk); } + + [Theory, TraceTest] + [MemberData(nameof(DnxSdks))] + public void ProjectPropertiesFlowIntoAssembly(DnxSdk sdk) + { + // Arrange + var solution = TestUtils.GetSolution(sdk, "AssemblyInfo"); + var project = solution.GetProject("Test"); + + sdk.Dnu.Restore(solution.RootPath).EnsureSuccess(); + + // Act + var result = sdk.Dnu.Pack(project.ProjectDirectory); + + // Assert + Assert.Equal(0, result.ExitCode); + + var assemblyPath = result.GetAssemblyPath(sdk.TargetFramework); + + using (var stream = File.OpenRead(assemblyPath)) + { + using (var peReader = new PEReader(stream)) + { + var metadataReader = peReader.GetMetadataReader(); + var assemblyDefinition = metadataReader.GetAssemblyDefinition(); + var attributes = assemblyDefinition.GetCustomAttributes() + .Select(handle => metadataReader.GetCustomAttribute(handle)) + .ToDictionary(attribute => GetAttributeName(attribute, metadataReader), attribute => GetAttributeArgument(attribute, metadataReader)); + + Assert.Equal(project.Title, attributes[typeof(AssemblyTitleAttribute).Name]); + Assert.Equal(project.Description, attributes[typeof(AssemblyDescriptionAttribute).Name]); + Assert.Equal(project.Copyright, attributes[typeof(AssemblyCopyrightAttribute).Name]); + Assert.Equal(project.AssemblyFileVersion.ToString(), attributes[typeof(AssemblyFileVersionAttribute).Name]); + Assert.Equal(project.Version.ToString(), attributes[typeof(AssemblyInformationalVersionAttribute).Name]); + Assert.Equal(project.Version.Version, assemblyDefinition.Version); + } + } + + TestUtils.CleanUpTestDir(sdk); + } + + private string GetAttributeName(CustomAttribute attribute, MetadataReader metadataReader) + { + var container = metadataReader.GetMemberReference((MemberReferenceHandle)attribute.Constructor).Parent; + var name = metadataReader.GetTypeReference((TypeReferenceHandle)container).Name; + return metadataReader.GetString(name); + } + + private string GetAttributeArgument(CustomAttribute attribute, MetadataReader metadataReader) + { + var signature = metadataReader.GetMemberReference((MemberReferenceHandle)attribute.Constructor).Signature; + var signatureReader = metadataReader.GetBlobReader(signature); + var valueReader = metadataReader.GetBlobReader(attribute.Value); + + valueReader.ReadUInt16(); // Skip prolog + signatureReader.ReadSignatureHeader(); // Skip header + + int parameterCount; + signatureReader.TryReadCompressedInteger(out parameterCount); + + signatureReader.ReadSignatureTypeCode(); // Skip return type + + for (int i = 0; i < parameterCount; i++) + { + var signatureTypeCode = signatureReader.ReadSignatureTypeCode(); + if (signatureTypeCode == SignatureTypeCode.String) + { + return valueReader.ReadSerializedString(); + } + } + + return string.Empty; + } } } diff --git a/test/Microsoft.Dnx.Tooling.FunctionalTests/project.json b/test/Microsoft.Dnx.Tooling.FunctionalTests/project.json index d286186ae..f383a408b 100644 --- a/test/Microsoft.Dnx.Tooling.FunctionalTests/project.json +++ b/test/Microsoft.Dnx.Tooling.FunctionalTests/project.json @@ -1,18 +1,19 @@ -{ - "dependencies": { - "Microsoft.AspNet.Testing": "1.0.0-*", - "Microsoft.Dnx.CommonTestUtils": "1.0.0-*", - "Microsoft.Dnx.Runtime.Sources": "1.0.0-*", - "Microsoft.Dnx.Testing.Framework": "1.0.0-*", - "Microsoft.Dnx.Tooling": "1.0.0-*", - "xunit.runner.aspnet": "2.0.0-aspnet-*" - }, - - "frameworks": { - "dnx451": { } - }, - - "commands": { - "test": "xunit.runner.aspnet" - } -} +{ + "dependencies": { + "System.Reflection.Metadata": "1.0.21-*", + "Microsoft.AspNet.Testing": "1.0.0-*", + "Microsoft.Dnx.CommonTestUtils": "1.0.0-*", + "Microsoft.Dnx.Runtime.Sources": "1.0.0-*", + "Microsoft.Dnx.Testing.Framework": "1.0.0-*", + "Microsoft.Dnx.Tooling": "1.0.0-*", + "xunit.runner.aspnet": "2.0.0-aspnet-*" + }, + + "frameworks": { + "dnx451": { } + }, + + "commands": { + "test": "xunit.runner.aspnet" + } +}