Skip to content

Commit

Permalink
Add msbuild task to test assembly informational version
Browse files Browse the repository at this point in the history
  • Loading branch information
safern committed Dec 11, 2020
1 parent 7980eef commit 78517da
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,20 @@
<NETCoreAppMaximumVersion>$(MajorVersion).$(MinorVersion)</NETCoreAppMaximumVersion>
</PropertyGroup>

<UsingTask TaskName="ValidateAssemblyInformationalVersion" AssemblyFile="$(InstallerTasksAssemblyPath)"/>
<Target Name="ValidateAssemblyInfoVersion"
AfterTargets="CopyFilesToOutputDirectory"
Condition="'$(ShouldValidateAssemblyInfoVersion)' == 'true'">

<PropertyGroup>
<_expectedVersion>$(ProductVersion)-$(VersionSuffix)</_expectedVersion>
<_expectedVersion Condition="'$(DotNetFinalVersionKind)' == 'release'">$(ProductVersion)</_expectedVersion>
</PropertyGroup>

<ValidateAssemblyInformationalVersion
AssemblyPath="$(TargetPath)"
ProductVersion="$(_expectedVersion)" />

</Target>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<EnsureRuntimePackageDependencies>false</EnsureRuntimePackageDependencies>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>

<!-- Validate assembly informational version -->
<ShouldValidateAssemblyInfoVersion>true</ShouldValidateAssemblyInfoVersion>

<!-- Ensure a portable PDB is emitted for the project. A PDB is needed for crossgen. -->
<DebugType>Portable</DebugType>
<DebugSymbols>true</DebugSymbols>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
<EnsureRuntimePackageDependencies>false</EnsureRuntimePackageDependencies>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>

<!-- Validate assembly informational version -->
<ShouldValidateAssemblyInfoVersion>true</ShouldValidateAssemblyInfoVersion>

<!-- Ensure a portable PDB is emitted for the project. A PDB is needed for crossgen. -->
<DebugType>Portable</DebugType>
<DebugSymbols>true</DebugSymbols>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.DotNet.Build.Tasks
{
/// <summary>
/// Validates that the AssemblyInformationalVersion matches the product version
/// </summary>
public class ValidateAssemblyInformationalVersion : Task
{
[Required]
public string AssemblyPath { get; set; }

[Required]
public string ProductVersion { get; set; }

public override bool Execute()
{
using FileStream stream = File.OpenRead(AssemblyPath);
MetadataReader metadataReader = new PEReader(stream).GetMetadataReader();
AssemblyDefinition assemblyDefinition = metadataReader.GetAssemblyDefinition();

CustomAttribute customAttribute = default;
bool foundAttribute = false;
foreach (CustomAttributeHandle customAttributeHandle in assemblyDefinition.GetCustomAttributes())
{
if (customAttributeHandle.IsAssemblyInformationalVersionAttribute(metadataReader, out customAttribute))
{
foundAttribute = true;
break;
}
}

string versionString = null;
if (foundAttribute)
{
CustomAttributeValue<string> attributeValue = customAttribute.DecodeValue(new CustomAttributeTypeProvider());
if (attributeValue.FixedArguments.Length > 0)
{
versionString = (string)attributeValue.FixedArguments[0].Value;
if (versionString != null)
{
int plusIndex = versionString.IndexOf('+');
if (plusIndex != -1)
{
versionString = versionString.Substring(0, plusIndex);
}
}
}
}

if (string.IsNullOrEmpty(versionString))
{
Log.LogError($"Couldn't find a valid version on AssemblyInformationalVersionAttribute in: {AssemblyPath}");
return false;
}

if (!versionString.Equals(ProductVersion, StringComparison.Ordinal))
{
Log.LogError($"AssemblyInformationalVersion {versionString} doesn't match expected version {ProductVersion} in: {AssemblyPath}");
return false;
}

Log.LogMessage(MessageImportance.Normal, $"AssemblyInformationalVersion {versionString} matched expected version {ProductVersion} in: {AssemblyPath}");

return true;
}
}

internal class CustomAttributeTypeProvider : ICustomAttributeTypeProvider<string>
{
public string GetSystemType()
{
return "[System.Runtime]System.Type";
}

public bool IsSystemType(string type) => false;

public string GetTypeFromSerializedName(string name)
{
return name;
}

public string GetPrimitiveType(PrimitiveTypeCode typeCode)
{
return typeCode switch
{
PrimitiveTypeCode.String => "string",
_ => throw new ArgumentOutOfRangeException(nameof(typeCode)),
};
}

public string GetSZArrayType(string elementType) => null;
public PrimitiveTypeCode GetUnderlyingEnumType(string type) => default;
public string GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind = 0) => null;
public string GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind = 0) => null;
}

internal static class MetadataReaderExtensions
{
private static bool Equals(this StringHandle handle, string other, MetadataReader reader) =>
reader.GetString(handle).Equals(other, StringComparison.Ordinal);

private static bool TypeMatchesNameAndNamespace(this EntityHandle handle, string @namespace, string name, MetadataReader reader)
{
switch (handle.Kind)
{
case HandleKind.TypeDefinition:
TypeDefinition td = reader.GetTypeDefinition((TypeDefinitionHandle)handle);
return !td.Namespace.IsNil && td.Namespace.Equals(@namespace, reader) && td.Name.Equals(name, reader);
case HandleKind.TypeReference:
TypeReference tr = reader.GetTypeReference((TypeReferenceHandle)handle);
return !tr.Namespace.IsNil && tr.Namespace.Equals(@namespace, reader) && tr.Name.Equals(name, reader);
default:
return false;
}
}

public static bool IsAssemblyInformationalVersionAttribute(this CustomAttributeHandle attributeHandle, MetadataReader reader, out CustomAttribute attribute)
{
const string @namespace = "System.Reflection";
const string name = "AssemblyInformationalVersionAttribute";
attribute = reader.GetCustomAttribute(attributeHandle);
EntityHandle ctorHandle = attribute.Constructor;
switch (ctorHandle.Kind)
{
case HandleKind.MemberReference:
return reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent.TypeMatchesNameAndNamespace(@namespace, name, reader);
case HandleKind.MethodDefinition:
EntityHandle handle = reader.GetMethodDefinition((MethodDefinitionHandle)ctorHandle).GetDeclaringType();
return handle.TypeMatchesNameAndNamespace(@namespace, name, reader);
default:
return false;
}
}
}
}

0 comments on commit 78517da

Please sign in to comment.