From facc1e466eadefa8aecc772839571c71e59bb018 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 5 Sep 2018 10:03:54 +0100 Subject: [PATCH] Pre-trim Mono BCL (#1382) * Add "ilwipe" build command * Perform BCL wipe as part of build * Simplify by converting ilwipe command to process entire directories * Clean up the build --- Blazor.sln | 14 +- ...rosoft.AspNetCore.Blazor.Browser.JS.csproj | 4 +- .../ReferenceFromSource.props | 7 +- .../targets/Blazor.MonoRuntime.props | 9 -- .../targets/Blazor.MonoRuntime.targets | 6 +- .../BuildToolsExe.props | 5 + .../Cli/Commands/ILWipeCommand.cs | 110 ++++++++++++++++ .../Cli/Program.cs | 3 +- .../Core/ILWipe/AssemblyItem.cs | 80 ++++++++++++ .../Core/ILWipe/MethodWipedExceptionMethod.cs | 74 +++++++++++ .../Core/ILWipe/SpecList.cs | 33 +++++ .../Core/ILWipe/SpecListEntry.cs | 121 ++++++++++++++++++ .../Core/ILWipe/WipeAssembly.cs | 49 +++++++ ...rosoft.AspNetCore.Blazor.BuildTools.csproj | 7 +- .../ReferenceFromSource.props | 11 -- 15 files changed, 498 insertions(+), 35 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Blazor.BuildTools/BuildToolsExe.props create mode 100644 src/Microsoft.AspNetCore.Blazor.BuildTools/Cli/Commands/ILWipeCommand.cs create mode 100644 src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/AssemblyItem.cs create mode 100644 src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/MethodWipedExceptionMethod.cs create mode 100644 src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/SpecList.cs create mode 100644 src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/SpecListEntry.cs create mode 100644 src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/WipeAssembly.cs delete mode 100644 src/Microsoft.AspNetCore.Blazor.BuildTools/ReferenceFromSource.props diff --git a/Blazor.sln b/Blazor.sln index 1d4ce2ecff..2e6a44c313 100644 --- a/Blazor.sln +++ b/Blazor.sln @@ -8,9 +8,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B867E038-B3CE-43E3-9292-61568C46CDEB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mono", "mono", "{7B5CAAB1-A3EB-44F7-87E3-A13ED89FC17D}" - ProjectSection(SolutionItems) = preProject - src\mono\mono.targets = src\mono\mono.targets - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanity", "samples\MonoSanity\MonoSanity.csproj", "{7C53BB6B-5906-4753-B507-C9FCC2F7E5B7}" EndProject @@ -125,6 +122,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{F380 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.Test", "modules\jsinterop\test\Microsoft.JSInterop.Test\Microsoft.JSInterop.Test.csproj", "{ECF02708-4CA4-44B3-B23F-274F4B417FA5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mono", "src\mono\mono.csproj", "{DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -456,6 +455,14 @@ Global {ECF02708-4CA4-44B3-B23F-274F4B417FA5}.Release|Any CPU.Build.0 = Release|Any CPU {ECF02708-4CA4-44B3-B23F-274F4B417FA5}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {ECF02708-4CA4-44B3-B23F-274F4B417FA5}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}.Release|Any CPU.Build.0 = Release|Any CPU + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -512,6 +519,7 @@ Global {4A5D7F9D-9CED-44C1-983E-054D8E99A292} = {1386F99B-3862-40C2-B24D-796C07DC7921} {1386F99B-3862-40C2-B24D-796C07DC7921} = {F380B6B6-9486-42BC-9B24-C388F8BF13A3} {ECF02708-4CA4-44B3-B23F-274F4B417FA5} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E} + {DEEAE26E-D00C-4A3C-BE12-7A51A63CE55E} = {7B5CAAB1-A3EB-44F7-87E3-A13ED89FC17D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3} diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/Microsoft.AspNetCore.Blazor.Browser.JS.csproj b/src/Microsoft.AspNetCore.Blazor.Browser.JS/Microsoft.AspNetCore.Blazor.Browser.JS.csproj index 89d89ff11f..8f689bc3ed 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/Microsoft.AspNetCore.Blazor.Browser.JS.csproj +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/Microsoft.AspNetCore.Blazor.Browser.JS.csproj @@ -5,18 +5,20 @@ true Latest ${DefaultItemExcludes};node_modules\** + false true + - + diff --git a/src/Microsoft.AspNetCore.Blazor.Build/ReferenceFromSource.props b/src/Microsoft.AspNetCore.Blazor.Build/ReferenceFromSource.props index ef30bab8fc..c56ef232e7 100644 --- a/src/Microsoft.AspNetCore.Blazor.Build/ReferenceFromSource.props +++ b/src/Microsoft.AspNetCore.Blazor.Build/ReferenceFromSource.props @@ -21,9 +21,6 @@ - - - @@ -37,9 +34,11 @@ - + + diff --git a/src/Microsoft.AspNetCore.Blazor.Build/targets/Blazor.MonoRuntime.props b/src/Microsoft.AspNetCore.Blazor.Build/targets/Blazor.MonoRuntime.props index 07dcf437b4..47932fe42f 100644 --- a/src/Microsoft.AspNetCore.Blazor.Build/targets/Blazor.MonoRuntime.props +++ b/src/Microsoft.AspNetCore.Blazor.Build/targets/Blazor.MonoRuntime.props @@ -25,13 +25,4 @@ $(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName) - - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Blazor.Build/targets/Blazor.MonoRuntime.targets b/src/Microsoft.AspNetCore.Blazor.Build/targets/Blazor.MonoRuntime.targets index 3fec481cf8..95fd6c10b4 100644 --- a/src/Microsoft.AspNetCore.Blazor.Build/targets/Blazor.MonoRuntime.targets +++ b/src/Microsoft.AspNetCore.Blazor.Build/targets/Blazor.MonoRuntime.targets @@ -157,6 +157,9 @@ + + + $(TargetDir)$(BaseBlazorRuntimeAsmjsOutputPath)%(FileName)%(Extension) AsmJs @@ -424,9 +427,10 @@ 4) Add the file we just created to the list of file writes, to support incremental builds. --> + <_MonoBaseClassLibraryFolder Include="$(MonoBaseClassLibraryPath);$(MonoBaseClassLibraryFacadesPath)" /> <_BlazorAssembliesToLink Include="@(_BlazorDependencyInput->'-a "%(Identity)"')" /> <_BlazorAssembliesToLink Include="@(IntermediateAssembly->'-a "%(FullPath)"')" /> - <_BlazorFolderLookupPaths Include="@(MonoBaseClassLibraryFolder->'-d "%(Identity)"')" /> + <_BlazorFolderLookupPaths Include="@(_MonoBaseClassLibraryFolder->'-d "%(Identity)"')" /> <_BlazorAssemblyDescriptorFiles Include="@(BlazorLinkerDescriptor->'-x "%(FullPath)"')" Condition="'@(BlazorLinkerDescriptor)' != ''" /> diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/BuildToolsExe.props b/src/Microsoft.AspNetCore.Blazor.BuildTools/BuildToolsExe.props new file mode 100644 index 0000000000..414ce1387d --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/BuildToolsExe.props @@ -0,0 +1,5 @@ + + + dotnet "$(ArtifactsBinDir)Microsoft.AspNetCore.Blazor.BuildTools/netcoreapp2.1/Microsoft.AspNetCore.Blazor.BuildTools.dll" + + diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/Cli/Commands/ILWipeCommand.cs b/src/Microsoft.AspNetCore.Blazor.BuildTools/Cli/Commands/ILWipeCommand.cs new file mode 100644 index 0000000000..e2eccef17d --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/Cli/Commands/ILWipeCommand.cs @@ -0,0 +1,110 @@ +// 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.IO; +using System.Linq; +using Microsoft.AspNetCore.Blazor.BuildTools.Core.ILWipe; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.AspNetCore.Blazor.BuildTools.Cli.Commands +{ + static class ILWipeCommand + { + public static void Command(CommandLineApplication command) + { + command.Description = "Wipes code from .NET assemblies."; + command.HelpOption("-?|-h|--help"); + + var inputDirOption = command.Option( + "-i|--input", + "The directory containing assemblies from which code should be wiped.", + CommandOptionType.SingleValue); + + var specFileOption = command.Option( + "-s|--spec", + "The directory containing spec files describing which members to wipe from the assemblies.", + CommandOptionType.SingleValue); + + var verboseOption = command.Option( + "-v|--verbose", + "If set, logs additional information to the console.", + CommandOptionType.NoValue); + + var listOption = command.Option( + "-l|--list", + "If set, just writes lists the assembly contents to disk.", + CommandOptionType.NoValue); + + var outputOption = command.Option( + "-o|--output", + "The directory to which the wiped assembly files should be written.", + CommandOptionType.SingleValue); + + command.OnExecute(() => + { + var inputDir = GetRequiredOptionValue(inputDirOption); + var outputDir = GetRequiredOptionValue(outputOption); + var specDir = GetRequiredOptionValue(specFileOption); + + var specFiles = Directory.EnumerateFiles( + specDir, "*.txt", + new EnumerationOptions { RecurseSubdirectories = true }); + + foreach (var specFilePath in specFiles) + { + var specFileRelativePath = Path.GetRelativePath(specDir, specFilePath); + var assemblyRelativePath = Path.ChangeExtension(specFileRelativePath, ".dll"); + var inputAssemblyPath = Path.Combine(inputDir, assemblyRelativePath); + var outputAssemblyPath = Path.Combine(outputDir, assemblyRelativePath); + var inputAssemblySize = new FileInfo(inputAssemblyPath).Length; + + if (listOption.HasValue()) + { + var outputList = AssemblyItem + .ListContents(inputAssemblyPath) + .Select(item => item.ToString()); + File.WriteAllLines( + Path.ChangeExtension(outputAssemblyPath, ".txt"), + outputList); + } + else + { + WipeAssembly.Exec( + inputAssemblyPath, + outputAssemblyPath, + specFilePath, + verboseOption.HasValue()); + + Console.WriteLine( + $"{assemblyRelativePath.PadRight(40)} " + + $"{FormatFileSize(inputAssemblySize)} ==> " + + $"{FormatFileSize(outputAssemblyPath)}"); + } + } + + return 0; + }); + } + + private static string FormatFileSize(string path) + { + return FormatFileSize(new FileInfo(path).Length); + } + + private static string FormatFileSize(long length) + { + return string.Format("{0:0.000} MB", ((double)length) / (1024*1024)); + } + + private static string GetRequiredOptionValue(CommandOption option) + { + if (!option.HasValue()) + { + throw new InvalidOperationException($"Missing value for required option '{option.LongName}'."); + } + + return option.Value(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/Cli/Program.cs b/src/Microsoft.AspNetCore.Blazor.BuildTools/Cli/Program.cs index cfd630096d..661701ac16 100644 --- a/src/Microsoft.AspNetCore.Blazor.BuildTools/Cli/Program.cs +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/Cli/Program.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Blazor.BuildTools.Cli.Commands; @@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Blazor.BuildTools app.HelpOption("-?|-h|--help"); app.Command("checknodejs", CheckNodeJsInstalledCommand.Command); + app.Command("ilwipe", ILWipeCommand.Command); if (args.Length > 0) { diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/AssemblyItem.cs b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/AssemblyItem.cs new file mode 100644 index 0000000000..efdaa5f8fd --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/AssemblyItem.cs @@ -0,0 +1,80 @@ +// 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 Mono.Cecil; +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil.Cil; + +namespace Microsoft.AspNetCore.Blazor.BuildTools.Core.ILWipe +{ + class AssemblyItem + { + public static IEnumerable ListContents(string assemblyPath) + { + var moduleDefinition = ModuleDefinition.ReadModule(assemblyPath); + return ListContents(moduleDefinition); + } + + public static IEnumerable ListContents(ModuleDefinition moduleDefinition) + { + return moduleDefinition.Types + .SelectMany(GetNestedTypesRecursive) + .SelectMany(type => type.Methods) + .Select(method => new AssemblyItem(method)) + .OrderBy(item => item.ToString(), StringComparer.Ordinal); + } + + public MethodDefinition Method { get; } + + public AssemblyItem(MethodDefinition method) + { + Method = method ?? throw new ArgumentNullException(nameof(method)); + } + + public void WipeFromAssembly(MethodDefinition createMethodWipedException) + { + if (!Method.HasBody) + { + return; // Nothing to do + } + + // We don't want to actually remove the method definition from the assembly, because + // then you'd have an assembly that was invalid (it could contain calls to the method + // that no longer exists). Instead, remove all the instructions from its body, and + // replace it with "throw CreateMethodWipedException()". Then: + // [1] The method body is very short, while still definitely being valid (still OK for + // it to have any return type) + // [2] We've removed its references to other methods/types, so they are more likely + // to be actually removed fully by a subsequent IL linker pass + // [3] If the method is actually invoked at runtime, the stack trace will make clear + // which method is being excessively wiped + var il = Method.Body.GetILProcessor(); + il.Body.Instructions.Clear(); + il.Body.Variables.Clear(); + il.Body.ExceptionHandlers.Clear(); + il.Append(il.Create(OpCodes.Call, createMethodWipedException)); + il.Append(il.Create(OpCodes.Throw)); + } + + public override string ToString() + { + var result = Method.ToString(); + return result.Substring(result.IndexOf(' ') + 1); + } + + public int CodeSize + => Method.Body?.CodeSize ?? 0; + + private static IEnumerable GetNestedTypesRecursive(TypeDefinition type) + { + yield return type; + + foreach (var descendant in type.NestedTypes.SelectMany(GetNestedTypesRecursive)) + { + yield return descendant; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/MethodWipedExceptionMethod.cs b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/MethodWipedExceptionMethod.cs new file mode 100644 index 0000000000..0634e77032 --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/MethodWipedExceptionMethod.cs @@ -0,0 +1,74 @@ +// 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 Mono.Cecil; +using Mono.Cecil.Cil; +using System; + +namespace Microsoft.AspNetCore.Blazor.BuildTools.Core.ILWipe +{ + static class MethodWipedExceptionMethod + { + public static MethodDefinition AddToAssembly(ModuleDefinition moduleDefinition) + { + // Adds the following method to the assembly: + // namespace ILWipe + // { + // internal static class ILWipeHelpers + // { + // public static Exception CreateMethodWipedException() + // { + // return new NotImplementedException("Cannot call method because it was wiped. See stack trace for details."); + // } + // } + // } + var ilWipeHelpersType = new TypeDefinition("ILWipe", "ILWipeHelpers", + TypeAttributes.NotPublic | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit); + moduleDefinition.Types.Add(ilWipeHelpersType); + + var methodAttributes = + MethodAttributes.Public | + MethodAttributes.HideBySig | + MethodAttributes.Static; + var createMethodWipedExceptionMethod = new MethodDefinition( + "CreateMethodWipedException", + methodAttributes, + ImportEquivalentTypeFromMscorlib(moduleDefinition, typeof(Exception))); + ilWipeHelpersType.Methods.Add(createMethodWipedExceptionMethod); + + var notImplExceptionType = ImportEquivalentTypeFromMscorlib(moduleDefinition, typeof(NotImplementedException)); + var notImplExceptionCtor = new MethodReference(".ctor", moduleDefinition.TypeSystem.Void, notImplExceptionType); + notImplExceptionCtor.Parameters.Add(new ParameterDefinition(moduleDefinition.TypeSystem.String)); + + var il = createMethodWipedExceptionMethod.Body.GetILProcessor(); + il.Append(il.Create(OpCodes.Ldstr, "Cannot invoke method because it was wiped. See stack trace for details.")); + il.Append(il.Create(OpCodes.Newobj, notImplExceptionCtor)); + il.Append(il.Create(OpCodes.Ret)); + + return createMethodWipedExceptionMethod; + } + + static TypeReference ImportEquivalentTypeFromMscorlib(ModuleDefinition module, Type type) + { + // We have to do this instead of module.ImportReference(type), because the latter + // would try to reference it in System.Private.CoreLib because this tool itself + // compiles to target netcoreapp rather than netstandard + IMetadataScope mscorlibScope; + if (module.TryGetTypeReference(typeof(object).FullName, out var objectRef)) + { + mscorlibScope = objectRef.Scope; + } + else if (module.Name == "mscorlib.dll") + { + mscorlibScope = module; + } + else + { + throw new InvalidOperationException($"Could not resolve System.Object type within '{module.FileName}'."); + } + + var typeRef = new TypeReference(type.Namespace, type.Name, module, mscorlibScope, type.IsValueType); + return module.ImportReference(typeRef); + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/SpecList.cs b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/SpecList.cs new file mode 100644 index 0000000000..f49e5e1ba7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/SpecList.cs @@ -0,0 +1,33 @@ +// 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.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Blazor.BuildTools.Core.ILWipe +{ + class SpecList + { + private List _itemSpecs; + + public SpecList(string[] fromSpecLines) + { + var linesToUse = fromSpecLines.Where( + line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#')); + _itemSpecs = new List( + linesToUse.Select(line => new SpecListEntry(line))); + } + + public bool IsEmpty + { + get => _itemSpecs.Count == 0; + } + + public bool Match(AssemblyItem item) + { + // If this needs to be faster, consider implementing some kind of matching tree. + var lastMatchingSpec = _itemSpecs.LastOrDefault(spec => spec.IsMatch(item)); + return lastMatchingSpec == null ? false : !lastMatchingSpec.Negated; + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/SpecListEntry.cs b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/SpecListEntry.cs new file mode 100644 index 0000000000..98da3b7f41 --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/SpecListEntry.cs @@ -0,0 +1,121 @@ +// 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; + +namespace Microsoft.AspNetCore.Blazor.BuildTools.Core.ILWipe +{ + class SpecListEntry + { + public bool Negated { get; } + public string TypeName { get; } + public string MethodName { get; } + public string Args { get; } + + public SpecListEntry(string parseSpecLine) + { + parseSpecLine = parseSpecLine.Trim(); + + if (parseSpecLine.StartsWith('!')) + { + Negated = true; + parseSpecLine = parseSpecLine.Substring(1); + } + + var colonsPos = parseSpecLine.IndexOf("::"); + if (colonsPos < 0) + { + TypeName = parseSpecLine; + } + else + { + TypeName = parseSpecLine.Substring(0, colonsPos); + parseSpecLine = parseSpecLine.Substring(colonsPos + 2); + + var bracketPos = parseSpecLine.IndexOf('('); + if (bracketPos < 0) + { + MethodName = parseSpecLine; + } + else + { + MethodName = parseSpecLine.Substring(0, bracketPos); + Args = parseSpecLine.Substring(bracketPos + 1, parseSpecLine.Length - bracketPos - 2); + } + } + } + + public bool IsMatch(AssemblyItem item) + { + return MatchesType(item) + && MatchesMethod(item) + && MatchesArgs(item); + } + + private bool MatchesArgs(AssemblyItem item) + { + if (Args == null) + { + return true; + } + else + { + var methodString = item.Method.ToString(); + var bracketPos = methodString.IndexOf('('); + var argsString = methodString.Substring(bracketPos + 1, methodString.Length - bracketPos - 2); + return string.Equals(argsString, Args, StringComparison.Ordinal); + } + } + + private bool MatchesMethod(AssemblyItem item) + { + if (MethodName == null) + { + return true; + } + else if (MethodName.EndsWith('*')) + { + return item.Method.Name.StartsWith( + MethodName.Substring(0, MethodName.Length - 1), + StringComparison.Ordinal); + } + else + { + return string.Equals(item.Method.Name, MethodName, StringComparison.Ordinal); + } + } + + private bool MatchesType(AssemblyItem item) + { + var declaringTypeFullName = item.Method.DeclaringType.FullName; + if (TypeName.EndsWith('*')) + { + // Wildcard match + return declaringTypeFullName.StartsWith( + TypeName.Substring(0, TypeName.Length - 1), + StringComparison.Ordinal); + } + else + { + // Exact match + if (string.Equals( + item.Method.DeclaringType.FullName, + TypeName, + StringComparison.Ordinal)) + { + return true; + } + + // If we're matching all members of the type, include nested types + if (MethodName == null && declaringTypeFullName.StartsWith( + $"{TypeName}/", + StringComparison.Ordinal)) + { + return true; + } + + return false; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/WipeAssembly.cs b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/WipeAssembly.cs new file mode 100644 index 0000000000..3adf8f601a --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/Core/ILWipe/WipeAssembly.cs @@ -0,0 +1,49 @@ +// 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.IO; +using Mono.Cecil; +using System.Linq; + +namespace Microsoft.AspNetCore.Blazor.BuildTools.Core.ILWipe +{ + static class WipeAssembly + { + public static void Exec(string inputPath, string outputPath, string specFilePath, bool logVerbose) + { + if (string.IsNullOrEmpty(outputPath)) + { + outputPath = Path.ChangeExtension(inputPath, ".wiped" + Path.GetExtension(inputPath)); + } + + var specLines = File.ReadAllLines(specFilePath); + var wipeSpecList = new SpecList(specLines); + var moduleDefinition = ModuleDefinition.ReadModule(inputPath); + + if (!wipeSpecList.IsEmpty) + { + var createMethodWipedException = MethodWipedExceptionMethod.AddToAssembly(moduleDefinition); + + var contents = AssemblyItem.ListContents(moduleDefinition).ToList(); + foreach (var contentItem in contents) + { + var shouldWipe = wipeSpecList.Match(contentItem) + && contentItem.Method != createMethodWipedException; + + if (logVerbose) + { + Console.WriteLine($"{(shouldWipe ? "Wiping" : "Retaining")}: {contentItem}"); + } + + if (shouldWipe) + { + contentItem.WipeFromAssembly(createMethodWipedException); + } + } + } + + moduleDefinition.Write(outputPath); + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/Microsoft.AspNetCore.Blazor.BuildTools.csproj b/src/Microsoft.AspNetCore.Blazor.BuildTools/Microsoft.AspNetCore.Blazor.BuildTools.csproj index 30c0b4f127..dfc69d032e 100644 --- a/src/Microsoft.AspNetCore.Blazor.BuildTools/Microsoft.AspNetCore.Blazor.BuildTools.csproj +++ b/src/Microsoft.AspNetCore.Blazor.BuildTools/Microsoft.AspNetCore.Blazor.BuildTools.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -6,12 +6,9 @@ false - - - - + diff --git a/src/Microsoft.AspNetCore.Blazor.BuildTools/ReferenceFromSource.props b/src/Microsoft.AspNetCore.Blazor.BuildTools/ReferenceFromSource.props deleted file mode 100644 index 15d03741d6..0000000000 --- a/src/Microsoft.AspNetCore.Blazor.BuildTools/ReferenceFromSource.props +++ /dev/null @@ -1,11 +0,0 @@ - - - dotnet "$(ArtifactsBinDir)Microsoft.AspNetCore.Blazor.BuildTools/netcoreapp2.1/Microsoft.AspNetCore.Blazor.BuildTools.dll" - - - - - - -