Skip to content

Detect package references containing dll's that were not used during compilation #234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/Internal.AspNetCore.BuildTools.Tasks/FindUnusedReferences.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

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

namespace Microsoft.AspNetCore.BuildTools
{
#if SDK
public class Sdk_FindUnusedReferences : Task
#elif BuildTools
public class FindUnusedReferences : Task
#else
#error This must be built either for an SDK or for BuildTools
#endif
{
/// <summary>
/// IntermediateAssembly from CoreCompile
/// </summary>
[Required]
public string Assembly { get; set; }

/// <summary>
/// ReferencePath from CoreCompile
/// </summary>
[Required]
public ITaskItem[] References { get; set; }

/// <summary>
/// FileDefinitions from RunResolvePackageDependencies
/// </summary>
[Required]
public ITaskItem[] Packages { get; set; }

/// <summary>
/// PackageDependencies from RunResolvePackageDependencies
/// </summary>
[Required]
public ITaskItem[] Files { get; set; }

[Output]
public ITaskItem[] UnusedReferences { get; set; }

public override bool Execute()
{
var references = new HashSet<string>(References.Select(item => item.ItemSpec), StringComparer.OrdinalIgnoreCase);
var referenceFiles = Files.Where(file => references.Contains(file.GetMetadata("ResolvedPath")))
.ToDictionary(item => Path.GetFileNameWithoutExtension(item.GetMetadata("ResolvedPath")), StringComparer.OrdinalIgnoreCase);

var directReferences = new HashSet<string>(
Packages.Where(p => string.IsNullOrEmpty(p.GetMetadata("ParentPackage"))).Select(i => i.ItemSpec),
StringComparer.OrdinalIgnoreCase);

using (var fileStream = File.OpenRead(Assembly))
using (var reader = new PEReader(fileStream))
{
var metadataReader = reader.GetMetadataReader();
foreach (AssemblyReferenceHandle assemblyReferenceHandle in metadataReader.AssemblyReferences)
{
var assemblyReference = metadataReader.GetAssemblyReference(assemblyReferenceHandle);
var name = metadataReader.GetString(assemblyReference.Name);
if (referenceFiles.TryGetValue(name, out var fileItem))
{
var packageName = fileItem.GetMetadata("PackageName") + "/" + fileItem.GetMetadata("PackageVersion");
directReferences.Remove(packageName);
referenceFiles.Remove(name);
}

}
}

UnusedReferences = referenceFiles.Values.Where(f => directReferences.Any(r => f.ItemSpec.StartsWith(r))).ToArray();
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,26 @@
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MsBuildPackageVersions)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MsBuildPackageVersions)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(CommandLineUtilsVersion)" PrivateAssets="All" />
<PackageReference Include="System.Reflection.Metadata" Version="1.4.2" PrivateAssets="All" />
<PackageReference Include="System.Collections.Immutable" Version="1.2.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.6' ">
<PackageReference Include="System.Xml.XmlDocument" Version="4.0.1" PrivateAssets="All" />
<PackageReference Include="System.Xml.XPath.XmlDocument" Version="4.0.1" PrivateAssets="All" />
</ItemGroup>

<Target Name="PackTaskDependencies" BeforeTargets="GenerateNuspec">
<!--
The include needs to happen after output has been copied to build output folder
but before NuGet generates a nuspec. See https://github.com/NuGet/Home/issues/4704.
-->
<ItemGroup>
<_PackageFiles Include="bin\$(Configuration)\*\System.Reflection.Metadata.dll;bin\$(Configuration)\*\System.Collections.Immutable.dll">
<PackagePath>tools\%(RecursiveDir)</PackagePath>
<Visible>false</Visible>
<BuildAction>Content</BuildAction>
</_PackageFiles>
</ItemGroup>
</Target>
</Project>
3 changes: 2 additions & 1 deletion src/Internal.AspNetCore.BuildTools.Tasks/build/Tasks.tasks
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project>
<Project>

<PropertyGroup>
<_BuildToolsAssemblyTfm Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard1.6</_BuildToolsAssemblyTfm>
Expand All @@ -17,6 +17,7 @@
<UsingTask TaskName="Microsoft.AspNetCore.BuildTools.$(_BuildTasksPrefix)WaitForDebugger" AssemblyFile="$(_BuildToolsAssembly)" />
<UsingTask TaskName="Microsoft.AspNetCore.BuildTools.$(_BuildTasksPrefix)ZipArchive" AssemblyFile="$(_BuildToolsAssembly)" />
<UsingTask TaskName="Microsoft.AspNetCore.BuildTools.$(_BuildTasksPrefix)UnzipArchive" AssemblyFile="$(_BuildToolsAssembly)" />
<UsingTask TaskName="Microsoft.AspNetCore.BuildTools.$(_BuildTasksPrefix)FindUnusedReferences" AssemblyFile="$(_BuildToolsAssembly)" />

<!-- Note: use 'XmlPoke' where possible. We plan to remove this. See https://github.com/aspnet/BuildTools/issues/180 -->
<UsingTask TaskName="Microsoft.AspNetCore.BuildTools.$(_BuildTasksPrefix)XmlPoke2" AssemblyFile="$(_BuildToolsAssembly)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,27 @@
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MsBuildPackageVersions)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MsBuildPackageVersions)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(CommandLineUtilsVersion)" PrivateAssets="All" />
<PackageReference Include="System.Reflection.Metadata" Version="1.4.2" PrivateAssets="All" />
<PackageReference Include="System.Collections.Immutable" Version="1.2.0" PrivateAssets="All" />

</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.6' ">
<PackageReference Include="System.Xml.XmlDocument" Version="4.0.1" PrivateAssets="All" />
<PackageReference Include="System.Xml.XPath.XmlDocument" Version="4.0.1" PrivateAssets="All" />
</ItemGroup>

<Target Name="PackTaskDependencies" BeforeTargets="GenerateNuspec">
<!--
The include needs to happen after output has been copied to build output folder
but before NuGet generates a nuspec. See https://github.com/NuGet/Home/issues/4704.
-->
<ItemGroup>
<_PackageFiles Include="bin\$(Configuration)\*\System.Reflection.Metadata.dll;bin\$(Configuration)\*\System.Collections.Immutable.dll">
<PackagePath>tools\%(RecursiveDir)</PackagePath>
<Visible>false</Visible>
<BuildAction>Content</BuildAction>
</_PackageFiles>
</ItemGroup>
</Target>
</Project>
15 changes: 15 additions & 0 deletions src/Internal.AspNetCore.Sdk/build/FindUnusedReferences.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!--
WARNING: These targets are intended for building Microsoft's ASP.NET Core repos and are not intended
for use outside of Microsoft.
-->
<Project>
<Target Name="FindUnusedReferences"
AfterTargets="CoreCompile"
DependsOnTargets="RunResolvePackageDependencies"
Condition=" '$(EnableFindUnusedReferences)' == 'true' ">
<Sdk_FindUnusedReferences Assembly="@(IntermediateAssembly)" References="@(ReferencePath)" Packages="@(PackageDependencies)" Files="@(FileDefinitions)" >
<Output TaskParameter="UnusedReferences" ItemName="UnusedReferences" />
</Sdk_FindUnusedReferences>
<Warning Condition="'@(UnusedReferences)' != ''" Text="Unused reference in $(MSBuildProjectFile)/$(TargetFramework) %(UnusedReferences.Identity) from package %(UnusedReferences.PackageName)" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to make this a verifier rule? We really don't have a good way to suppress individual warnings here and I rather not spend time every compilation doing extra work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, I need list of things that got passed to compiler and it's not easy to infer from assets.json anymore :(.

</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ for use outside of Microsoft.
<Project>
<Import Project="$(MSBuildThisFileDirectory)..\targets\Common.targets" />
<Import Project="$(MSBuildThisFileDirectory)ApiCheck.targets" />
<Import Project="$(MSBuildThisFileDirectory)FindUnusedReferences.targets" />
<Import Project="$(MSBuildThisFileDirectory)GenerateAssemblyInfo.targets" />
<Import Project="$(MSBuildThisFileDirectory)Git.targets" />

Expand Down