├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Directory.Build.props ├── LICENSE ├── README.md ├── assembly-rewriter.sln ├── build.bat ├── build.sh ├── build ├── keys │ ├── keypair.snk │ └── public.snk └── scripts │ ├── CommandLine.fs │ ├── Paths.fs │ ├── Program.fs │ ├── Targets.fs │ └── scripts.fsproj ├── dotnet-tools.json ├── global.json ├── nuget-icon.png └── src └── assembly-rewriter ├── AssemblyResolver.cs ├── AssemblyRewriter.cs ├── AssemblyToRewrite.cs ├── Options.cs ├── Program.cs ├── RepackConsoleLogger.cs └── assembly-rewriter.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*.cs] 4 | trim_trailing_whitespace=true 5 | insert_final_newline=true 6 | 7 | [*] 8 | indent_style = tab 9 | indent_size = 4 10 | 11 | [*.cshtml] 12 | indent_style = tab 13 | indent_size = 4 14 | 15 | [*.{fs,fsx,yml}] 16 | indent_style = space 17 | indent_size = 4 18 | 19 | [*.{md,markdown,json,js,csproj,fsproj,targets,targets,props}] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | # Dotnet code style settings: 24 | [*.{cs,vb}] 25 | 26 | # --- 27 | # naming conventions https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions 28 | # currently not supported in Rider/Resharper so not using these for now 29 | # --- 30 | 31 | # --- 32 | # language conventions https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language-conventions 33 | 34 | # Sort using and Import directives with System.* appearing first 35 | dotnet_sort_system_directives_first = true 36 | 37 | dotnet_style_qualification_for_field = false:error 38 | dotnet_style_qualification_for_property = false:error 39 | dotnet_style_qualification_for_method = false:error 40 | dotnet_style_qualification_for_event = false:error 41 | 42 | # Use language keywords instead of framework type names for type references 43 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 44 | dotnet_style_predefined_type_for_member_access = true:error 45 | 46 | # Suggest more modern language features when available 47 | dotnet_style_object_initializer = true:error 48 | dotnet_style_collection_initializer = true:error 49 | dotnet_style_explicit_tuple_names = true:error 50 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:error 51 | dotnet_style_prefer_inferred_tuple_names = true:error 52 | dotnet_style_coalesce_expression = true:error 53 | dotnet_style_null_propagation = true:error 54 | 55 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 56 | dotnet_style_readonly_field = true:error 57 | 58 | # CSharp code style settings: 59 | [*.cs] 60 | # Prefer "var" everywhere 61 | csharp_style_var_for_built_in_types = true:error 62 | csharp_style_var_when_type_is_apparent = true:error 63 | csharp_style_var_elsewhere = true:error 64 | 65 | csharp_style_expression_bodied_methods = true:error 66 | csharp_style_expression_bodied_constructors = true:error 67 | csharp_style_expression_bodied_operators = true:error 68 | csharp_style_expression_bodied_properties = true:error 69 | csharp_style_expression_bodied_indexers = true:error 70 | csharp_style_expression_bodied_accessors = true:error 71 | 72 | # Suggest more modern language features when available 73 | csharp_style_pattern_matching_over_is_with_cast_check = true:error 74 | csharp_style_pattern_matching_over_as_with_null_check = true:error 75 | csharp_style_inlined_variable_declaration = true:error 76 | csharp_style_deconstructed_variable_declaration = true:error 77 | csharp_style_pattern_local_over_anonymous_function = true:error 78 | csharp_style_throw_expression = true:error 79 | csharp_style_conditional_delegate_call = true:error 80 | 81 | csharp_prefer_braces = false:warning 82 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:error 83 | 84 | # --- 85 | # formatting conventions https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions 86 | 87 | # Newline settings (Allman yo!) 88 | csharp_new_line_before_open_brace = all:error 89 | csharp_new_line_before_else = true:error 90 | csharp_new_line_before_catch = true:error 91 | csharp_new_line_before_finally = true:error 92 | csharp_new_line_before_members_in_object_initializers = true 93 | # just a suggestion do to our JSON tests that use anonymous types to 94 | # represent json quite a bit (makes copy paste easier). 95 | csharp_new_line_before_members_in_anonymous_types = true:suggestion 96 | csharp_new_line_between_query_expression_clauses = true:error 97 | 98 | # Indent 99 | csharp_indent_case_contents = true:error 100 | csharp_indent_switch_labels = true:error 101 | csharp_space_after_cast = false:error 102 | csharp_space_after_keywords_in_control_flow_statements = true:error 103 | csharp_space_between_method_declaration_parameter_list_parentheses = false:error 104 | csharp_space_between_method_call_parameter_list_parentheses = false:error 105 | 106 | #Wrap 107 | csharp_preserve_single_line_statements = false:error 108 | csharp_preserve_single_line_blocks = true:error 109 | 110 | # Resharper 111 | resharper_csharp_braces_for_lock=required_for_complex 112 | resharper_csharp_braces_for_using=required_for_complex 113 | resharper_csharp_braces_for_while=required_for_complex 114 | resharper_csharp_braces_for_foreach=required_for_complex 115 | resharper_csharp_braces_for_for=required_for_complex 116 | resharper_csharp_braces_for_fixed=required_for_complex 117 | resharper_csharp_braces_for_ifelse=required_for_complex 118 | 119 | resharper_csharp_accessor_owner_body=expression_body 120 | 121 | 122 | resharper_redundant_case_label_highlighting=do_not_show 123 | 124 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Always be deploying 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - 'README.md' 7 | - '.editorconfig' 8 | push: 9 | paths-ignore: 10 | - 'README.md' 11 | - '.editorconfig' 12 | branches: 13 | - master 14 | tags: 15 | - "*.*.*" 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | with: 23 | fetch-depth: 1 24 | - run: | 25 | git fetch --prune --unshallow --tags 26 | echo exit code $? 27 | git tag --list 28 | - uses: actions/setup-dotnet@v1 29 | with: 30 | dotnet-version: | 31 | 5.0.x 32 | 6.0.x 33 | - uses: actions/setup-dotnet@v1 34 | with: 35 | dotnet-version: '6.0.302' 36 | source-url: https://nuget.pkg.github.com/nullean/index.json 37 | env: 38 | NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 39 | 40 | - run: ./build.sh build -s true 41 | name: Build 42 | - run: ./build.sh generatepackages -s true 43 | name: Generate local nuget packages 44 | - run: ./build.sh validatepackages -s true 45 | name: "validate *.npkg files that were created" 46 | - run: ./build.sh generateapichanges -s true 47 | name: "Inspect public API changes" 48 | 49 | - name: publish to github package repository 50 | if: github.event_name == 'push' && startswith(github.ref, 'refs/heads') 51 | shell: bash 52 | timeout-minutes: 10 53 | continue-on-error: true 54 | run: | 55 | until dotnet nuget push build/output/*.nupkg -k ${{secrets.GITHUB_TOKEN}} --skip-duplicate --no-symbols; do echo "Retrying"; sleep 1; done; 56 | 57 | - run: ./build.sh generatereleasenotes -s true 58 | name: Generate release notes for tag 59 | if: github.event_name == 'push' && startswith(github.ref, 'refs/tags') 60 | - run: ./build.sh createreleaseongithub -s true --token ${{secrets.GITHUB_TOKEN}} 61 | if: github.event_name == 'push' && startswith(github.ref, 'refs/tags') 62 | name: Create or update release for tag on github 63 | 64 | - run: dotnet nuget push build/output/*.nupkg -k ${{secrets.NUGET_ORG_API_KEY}} -s https://api.nuget.org/v3/index.json --skip-duplicate --no-symbols 65 | name: release to nuget.org 66 | if: github.event_name == 'push' && startswith(github.ref, 'refs/tags') 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.userprefs 2 | *.local.xml 3 | *.sln.docstates 4 | *.obj 5 | *.swp 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.tss 12 | *.vspscc 13 | *_i.c 14 | *_p.c 15 | *.ncb 16 | *.suo 17 | *.tlb 18 | *.tlh 19 | *.bak 20 | *.cache 21 | *.ilk 22 | *.log 23 | *.nupkg 24 | *.ncrunchsolution 25 | [Bb]in 26 | [Dd]ebug/ 27 | test-results 28 | test-results/* 29 | *.lib 30 | *.sbr 31 | *.DotSettings.user 32 | obj/ 33 | _ReSharper*/ 34 | _NCrunch*/ 35 | [Tt]est[Rr]esult* 36 | 37 | .fake/* 38 | .fake 39 | packages/* 40 | paket.exe 41 | paket-files/*.cached 42 | 43 | BenchmarkDotNet.Artifacts 44 | build/* 45 | !build/tools 46 | !build/keys 47 | build/tools/* 48 | !build/tools/sn 49 | !build/tools/sn/* 50 | !build/tools/ilmerge 51 | !build/*.fsx 52 | !build/*.fsx 53 | !build/*.ps1 54 | !build/*.nuspec 55 | !build/*.png 56 | !build/*.targets 57 | !build/scripts 58 | 59 | 60 | /dep/Newtonsoft.Json.4.0.2 61 | !docs/build 62 | docs/node_modules 63 | doc/Help 64 | 65 | /src/Nest.Tests.Unit/*.ncrunchproject 66 | *.ncrunchproject 67 | Cache 68 | YamlCache 69 | tests.yaml 70 | 71 | *.DS_Store 72 | *.sln.ide 73 | 74 | launchSettings.json 75 | # https://github.com/elastic/elasticsearch-net/pull/1822#issuecomment-183722698 76 | *.project.lock.json 77 | project.lock.json 78 | .vs 79 | .vs/* 80 | 81 | .idea/ 82 | *.sln.iml 83 | /src/.vs/restore.dg 84 | # temporary location for doc generation 85 | docs-temp 86 | *.binlog 87 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | canary 5 | 0.2 6 | 7 | 8 | 9 | all 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nullean - A collection of .NET tools 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # assembly-rewriter 2 | 3 | Rewrites .NET assemblies with [Mono.Cecil](https://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/), to allow two different versions of the same assembly to be referenced within an application. 4 | 5 | It assumes that the assembly DLL name is the top level namespace and rewrites 6 | 7 | 1. the top level namespace for all types within the assembly 8 | 2. assemblies in the order of dependencies first 9 | 3. IL `ldstr` op codes if they start with the namespace 10 | 4. compiler generated backing fields 11 | 12 | This small program was written to allow different versions [Elasticsearch .NET clients](https://github.com/elastic/elasticsearch-net) to be rewritten for benchmark comparisons. 13 | Your mileage may vary rewriting other assemblies :) 14 | 15 | ## Installation 16 | 17 | 18 | Distributed as a .NET tool so install using the following 19 | 20 | ``` 21 | dotnet tool install assembly-rewriter 22 | ``` 23 | 24 | ## Run 25 | 26 | ```bat 27 | dotnet assembly-rewriter 28 | ``` 29 | 30 | You can omit `dotnet` if you install this as a global tool 31 | 32 | 33 | ## Examples 34 | 35 | Rewrite [NEST, the Elasticsearch .NET high level client](https://github.com/elastic/elasticsearch-net), version 6.2.0 36 | 37 | ```c# 38 | assembly-rewriter -i C:/Nest.dll -o C:/Nest620.dll 39 | ``` 40 | 41 | Now, `Nest620.dll` and another version of `Nest.dll` can be referenced in the same project. 42 | 43 | There's _a small issue here_ however; both versions of NEST rely on `Elasticsearch.Net.dll`, so we should also rewrite 44 | this dependency at the same time, and update the references to Elasticsearch.Net within NEST to reference the new rewritten assembly 45 | 46 | ```c# 47 | assembly-rewriter -i C:/Nest.dll -o C:/Nest620.dll -i C:/Elasticsearch.Net.dll -o C:/Elasticsearch.Net620.dll 48 | ``` 49 | 50 | Great! Now we can reference both in the same project. 51 | 52 | If there are other direct dependencies that may version clash, these can be passed as well 53 | 54 | ```c# 55 | assembly-rewriter -i C:/Nest.dll -o C:/Nest620.dll -i C:/Elasticsearch.Net.dll -o C:/Elasticsearch.Net620.dll -i C:/Newtonsoft.Json.dll -o C:/Newtonsoft.Json620.dll 56 | ``` 57 | 58 | ## Rewrite validation 59 | 60 | You can check to see if everything expected has been rewritten using [IL Disassembler](https://docs.microsoft.com/en-us/dotnet/framework/tools/ildasm-exe-il-disassembler) 61 | 62 | ```powershell 63 | ildasm .dll /OUT=.il /NOBAR 64 | Select-String -Path .il -Pattern '\.' -AllMatches | ft LineNumber,Line 65 | ``` 66 | 67 | ## License 68 | 69 | [Apache 2.0](License.txt) 70 | -------------------------------------------------------------------------------- /assembly-rewriter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "assembly-rewriter", "src\assembly-rewriter\assembly-rewriter.csproj", "{879F7668-3136-4A46-8B62-17085A764CBA}" 4 | EndProject 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{61F7EFA3-2D67-47B5-9327-3AE03AC1AC0D}" 6 | ProjectSection(SolutionItems) = preProject 7 | build.sh = build.sh 8 | build.bat = build.bat 9 | nuget-icon.png = nuget-icon.png 10 | README.md = README.md 11 | LICENSE = LICENSE 12 | global.json = global.json 13 | Directory.Build.props = Directory.Build.props 14 | .github\workflows\ci.yml = .github\workflows\ci.yml 15 | dotnet-tools.json = dotnet-tools.json 16 | EndProjectSection 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{32D9729D-0ACB-4F0F-A3D8-2194FAE4A8C8}" 19 | EndProject 20 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "scripts", "build\scripts\scripts.fsproj", "{827FD131-B575-4C75-94BB-31141C359273}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {879F7668-3136-4A46-8B62-17085A764CBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {879F7668-3136-4A46-8B62-17085A764CBA}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {879F7668-3136-4A46-8B62-17085A764CBA}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {879F7668-3136-4A46-8B62-17085A764CBA}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {827FD131-B575-4C75-94BB-31141C359273}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {827FD131-B575-4C75-94BB-31141C359273}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {827FD131-B575-4C75-94BB-31141C359273}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {827FD131-B575-4C75-94BB-31141C359273}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(NestedProjects) = preSolution 38 | {879F7668-3136-4A46-8B62-17085A764CBA} = {32D9729D-0ACB-4F0F-A3D8-2194FAE4A8C8} 39 | {827FD131-B575-4C75-94BB-31141C359273} = {61F7EFA3-2D67-47B5-9327-3AE03AC1AC0D} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | dotnet run --project build/scripts -- %* -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | dotnet run --project build/scripts -- "$@" 4 | -------------------------------------------------------------------------------- /build/keys/keypair.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullean/assembly-rewriter/4c22ffa34daaf9e695cc691c7dfc19ca565c082f/build/keys/keypair.snk -------------------------------------------------------------------------------- /build/keys/public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullean/assembly-rewriter/4c22ffa34daaf9e695cc691c7dfc19ca565c082f/build/keys/public.snk -------------------------------------------------------------------------------- /build/scripts/CommandLine.fs: -------------------------------------------------------------------------------- 1 | module CommandLine 2 | 3 | open Argu 4 | open Microsoft.FSharp.Reflection 5 | 6 | type Arguments = 7 | | [] Clean 8 | | [] Build 9 | 10 | | [] PristineCheck 11 | | [] GeneratePackages 12 | | [] ValidatePackages 13 | | [] GenerateReleaseNotes 14 | | [] GenerateApiChanges 15 | | [] Release 16 | 17 | | [] CreateReleaseOnGithub 18 | | [] Publish 19 | 20 | | [] SingleTarget of bool 21 | | [] Token of string 22 | with 23 | interface IArgParserTemplate with 24 | member this.Usage = 25 | match this with 26 | | Clean _ -> "clean known output locations" 27 | | Build _ -> "Run build and tests" 28 | | Release _ -> "runs build, and create an validates the packages shy of publishing them" 29 | | Publish _ -> "Runs the full release" 30 | 31 | | SingleTarget _ -> "Runs the provided sub command without running their dependencies" 32 | | Token _ -> "Token to be used to authenticate with github" 33 | 34 | | PristineCheck 35 | | GeneratePackages 36 | | ValidatePackages 37 | | GenerateReleaseNotes 38 | | GenerateApiChanges 39 | | CreateReleaseOnGithub 40 | -> "Undocumented, dependent target" 41 | member this.Name = 42 | match FSharpValue.GetUnionFields(this, typeof) with 43 | | case, _ -> case.Name.ToLowerInvariant() 44 | -------------------------------------------------------------------------------- /build/scripts/Paths.fs: -------------------------------------------------------------------------------- 1 | module Paths 2 | 3 | open System 4 | open System.IO 5 | 6 | let ToolName = "assembly-rewriter" 7 | let Repository = sprintf "nullean/%s" ToolName 8 | 9 | let Root = 10 | let mutable dir = DirectoryInfo(".") 11 | while dir.GetFiles("*.sln").Length = 0 do dir <- dir.Parent 12 | Environment.CurrentDirectory <- dir.FullName 13 | dir 14 | 15 | let RootRelative path = Path.GetRelativePath(Root.FullName, path) 16 | 17 | let Output = DirectoryInfo(Path.Combine(Root.FullName, "build", "output")) 18 | 19 | let ToolProject = DirectoryInfo(Path.Combine(Root.FullName, "src", ToolName)) 20 | -------------------------------------------------------------------------------- /build/scripts/Program.fs: -------------------------------------------------------------------------------- 1 | module Program 2 | 3 | open Argu 4 | open Bullseye 5 | open ProcNet 6 | open CommandLine 7 | 8 | [] 9 | let main argv = 10 | let parser = ArgumentParser.Create(programName = "./build.sh") 11 | let parsed = 12 | try 13 | let parsed = parser.ParseCommandLine(inputs = argv, raiseOnUsage = true) 14 | let arguments = parsed.GetSubCommand() 15 | Some (parsed, arguments) 16 | with e -> 17 | printfn "%s" e.Message 18 | None 19 | 20 | match parsed with 21 | | None -> 2 22 | | Some (parsed, arguments) -> 23 | 24 | let target = arguments.Name 25 | 26 | Targets.Setup parsed arguments 27 | let swallowTypes = [typeof; typeof] 28 | 29 | Targets.RunTargetsAndExit 30 | ([target], (fun e -> swallowTypes |> List.contains (e.GetType()) ), ":") 31 | 0 32 | 33 | -------------------------------------------------------------------------------- /build/scripts/Targets.fs: -------------------------------------------------------------------------------- 1 | module Targets 2 | 3 | open Argu 4 | open System 5 | open System.IO 6 | open Bullseye 7 | open CommandLine 8 | open Fake.Tools.Git 9 | open ProcNet 10 | 11 | 12 | let exec binary args = 13 | let r = Proc.Exec (binary, args |> List.map (fun a -> sprintf "\"%s\"" a) |> List.toArray) 14 | match r.HasValue with | true -> r.Value | false -> failwithf "invocation of `%s` timed out" binary 15 | 16 | let private restoreTools = lazy(exec "dotnet" ["tool"; "restore"]) 17 | let private currentVersion = 18 | lazy( 19 | restoreTools.Value |> ignore 20 | let r = Proc.Start("dotnet", "minver", "--default-pre-release-phase", "canary") 21 | let o = r.ConsoleOut |> Seq.find (fun l -> not(l.Line.StartsWith("MinVer:"))) 22 | o.Line 23 | ) 24 | 25 | let private clean (arguments:ParseResults) = 26 | if (Paths.Output.Exists) then Paths.Output.Delete (true) 27 | exec "dotnet" ["clean"] |> ignore 28 | 29 | let private build (arguments:ParseResults) = exec "dotnet" ["build"; "-c"; "Release"] |> ignore 30 | 31 | let private pristineCheck (arguments:ParseResults) = 32 | match Information.isCleanWorkingCopy "." with 33 | | true -> printfn "The checkout folder does not have pending changes, proceeding" 34 | | _ -> failwithf "The checkout folder has pending changes, aborting" 35 | 36 | let private generatePackages (arguments:ParseResults) = 37 | let output = Paths.RootRelative Paths.Output.FullName 38 | exec "dotnet" ["pack"; "-c"; "Release"; "-o"; output] |> ignore 39 | 40 | let private validatePackages (arguments:ParseResults) = 41 | let nugetPackage = 42 | let p = Paths.Output.GetFiles("*.nupkg") |> Seq.sortByDescending(fun f -> f.CreationTimeUtc) |> Seq.head 43 | Paths.RootRelative p.FullName 44 | exec "dotnet" ["nupkg-validator"; nugetPackage; "-v"; currentVersion.Value; "-a"; Paths.ToolName; "-k"; "96c599bbe3e70f5d"] |> ignore 45 | 46 | let private generateApiChanges (arguments:ParseResults) = 47 | let output = Paths.RootRelative <| Paths.Output.FullName 48 | let currentVersion = currentVersion.Value 49 | let args = 50 | [ 51 | "assembly-differ" 52 | (sprintf "previous-nuget|%s|%s|netcoreapp3.1" Paths.ToolName currentVersion); 53 | (sprintf "directory|src/%s/bin/Release/netcoreapp3.1" Paths.ToolName); 54 | "--target"; Paths.ToolName; "-f"; "github-comment"; "--output"; output 55 | ] 56 | 57 | exec "dotnet" args |> ignore 58 | 59 | let private generateReleaseNotes (arguments:ParseResults) = 60 | let currentVersion = currentVersion.Value 61 | let output = 62 | Paths.RootRelative <| Path.Combine(Paths.Output.FullName, sprintf "release-notes-%s.md" currentVersion) 63 | let tokenArgs = 64 | match arguments.TryGetResult Token with 65 | | None -> [] 66 | | Some token -> ["--token"; token;] 67 | let releaseNotesArgs = 68 | (Paths.Repository.Split("/") |> Seq.toList) 69 | @ ["--version"; currentVersion 70 | "--label"; "enhancement"; "New Features" 71 | "--label"; "bug"; "Bug Fixes" 72 | "--label"; "documentation"; "Docs Improvements" 73 | ] @ tokenArgs 74 | @ ["--output"; output] 75 | 76 | exec "dotnet" (["release-notes"] @ releaseNotesArgs) |> ignore 77 | 78 | let private createReleaseOnGithub (arguments:ParseResults) = 79 | let currentVersion = currentVersion.Value 80 | let tokenArgs = 81 | match arguments.TryGetResult Token with 82 | | None -> [] 83 | | Some token -> ["--token"; token;] 84 | let releaseNotes = Paths.RootRelative <| Path.Combine(Paths.Output.FullName, sprintf "release-notes-%s.md" currentVersion) 85 | let breakingChanges = Paths.RootRelative <| Path.Combine(Paths.Output.FullName, "github-breaking-changes-comments.md") 86 | let releaseArgs = 87 | (Paths.Repository.Split("/") |> Seq.toList) 88 | @ ["create-release" 89 | "--version"; currentVersion 90 | "--body"; releaseNotes; 91 | "--body"; breakingChanges; 92 | ] @ tokenArgs 93 | 94 | exec "dotnet" (["release-notes"] @ releaseArgs) |> ignore 95 | 96 | let private release (arguments:ParseResults) = printfn "release" 97 | 98 | let private publish (arguments:ParseResults) = printfn "publish" 99 | 100 | let Setup (parsed:ParseResults) (subCommand:Arguments) = 101 | let step (name:string) action = Targets.Target(name, new Action(fun _ -> action(parsed))) 102 | 103 | let cmd (name:string) commandsBefore steps action = 104 | let singleTarget = (parsed.TryGetResult SingleTarget |> Option.defaultValue false) 105 | let deps = 106 | match (singleTarget, commandsBefore) with 107 | | (true, _) -> [] 108 | | (_, Some d) -> d 109 | | _ -> [] 110 | let steps = steps |> Option.defaultValue [] 111 | Targets.Target(name, deps @ steps, Action(action)) 112 | 113 | step Clean.Name clean 114 | cmd Build.Name None (Some [Clean.Name]) <| fun _ -> build parsed 115 | 116 | step PristineCheck.Name pristineCheck 117 | step GeneratePackages.Name generatePackages 118 | step ValidatePackages.Name validatePackages 119 | step GenerateReleaseNotes.Name generateReleaseNotes 120 | step GenerateApiChanges.Name generateApiChanges 121 | cmd Release.Name 122 | (Some [PristineCheck.Name; Build.Name;]) 123 | (Some [GeneratePackages.Name; ValidatePackages.Name; GenerateReleaseNotes.Name; GenerateApiChanges.Name]) 124 | <| fun _ -> release parsed 125 | 126 | step CreateReleaseOnGithub.Name createReleaseOnGithub 127 | cmd Publish.Name 128 | (Some [Release.Name]) 129 | (Some [CreateReleaseOnGithub.Name; ]) 130 | <| fun _ -> publish parsed 131 | -------------------------------------------------------------------------------- /build/scripts/scripts.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "minver-cli": { 6 | "version": "4.3.0", 7 | "commands": [ 8 | "minver" 9 | ] 10 | }, 11 | "release-notes": { 12 | "version": "0.5.2", 13 | "commands": [ 14 | "release-notes" 15 | ] 16 | }, 17 | "nupkg-validator": { 18 | "version": "0.5.0", 19 | "commands": [ 20 | "nupkg-validator" 21 | ] 22 | }, 23 | "assembly-differ": { 24 | "version": "0.14.0", 25 | "commands": [ 26 | "assembly-differ" 27 | ] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.302", 4 | "rollForward": "latestFeature", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /nuget-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullean/assembly-rewriter/4c22ffa34daaf9e695cc691c7dfc19ca565c082f/nuget-icon.png -------------------------------------------------------------------------------- /src/assembly-rewriter/AssemblyResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Mono.Cecil; 6 | 7 | namespace AssemblyRewriter 8 | { 9 | internal class AssemblyResolver : DefaultAssemblyResolver 10 | { 11 | private readonly IEnumerable _directories; 12 | 13 | public AssemblyResolver(IEnumerable directories) => 14 | _directories = new HashSet(directories.Select(Path.GetFullPath), StringComparer.OrdinalIgnoreCase); 15 | 16 | public override AssemblyDefinition Resolve(AssemblyNameReference name) 17 | { 18 | try 19 | { 20 | return base.Resolve(name); 21 | } 22 | catch 23 | { 24 | foreach (var directory in _directories) 25 | { 26 | var filePath = Path.Combine(directory, name.Name + ".dll"); 27 | if (File.Exists(filePath)) 28 | return AssemblyDefinition.ReadAssembly(filePath); 29 | } 30 | 31 | throw; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/assembly-rewriter/AssemblyRewriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text.RegularExpressions; 7 | using Mono.Cecil; 8 | using Mono.Cecil.Cil; 9 | 10 | namespace AssemblyRewriter 11 | { 12 | public class AssemblyRewriter 13 | { 14 | private readonly bool _verbose; 15 | private readonly string _keyFile; 16 | private Dictionary _renames = new Dictionary(); 17 | 18 | public AssemblyRewriter(Options options) 19 | { 20 | _verbose = options.Verbose; 21 | // if a keyfile has been passed but no merge argument, use it to sign the rewritten assembly 22 | _keyFile = !string.IsNullOrEmpty(options.KeyFile) && !options.Merge ? options.KeyFile : null; 23 | } 24 | 25 | public void Rewrite( 26 | IEnumerable inputPaths, 27 | IEnumerable outputPaths, 28 | IEnumerable additionalResolveDirectories 29 | ) 30 | { 31 | var assemblies = inputPaths.Zip(outputPaths, 32 | (inputPath, outputPath) => new AssemblyToRewrite(inputPath, outputPath)).ToList(); 33 | 34 | _renames = assemblies.ToDictionary(k => k.InputName, v => v.OutputName); 35 | 36 | var resolveDirs = assemblies.Select(a => a.InputDirectory) 37 | .Concat(assemblies.Select(a => a.OutputDirectory)) 38 | .Concat(additionalResolveDirectories) 39 | .Distinct(); 40 | 41 | var resolver = new AssemblyResolver(resolveDirs); 42 | var readerParameters = new ReaderParameters {AssemblyResolver = resolver, ReadWrite = true}; 43 | 44 | foreach (var assembly in assemblies) RewriteAssembly(assembly, assemblies, readerParameters); 45 | } 46 | 47 | private string RenameTypeName(string typeName, Func replace = null) 48 | { 49 | replace ??= (t, o, n) => t.Replace(o + ".", n + "."); 50 | foreach (var kv in _renames) 51 | { 52 | //safeguard against multiple renames 53 | if (typeName.StartsWith($"{kv.Value}.") || 54 | typeName.Contains($"<{kv.Value}.") || 55 | typeName.Contains($",{kv.Value}.")) 56 | continue; 57 | 58 | var n = replace(typeName, kv.Key, kv.Value); 59 | if (typeName != n) return n; 60 | } 61 | 62 | return typeName; 63 | } 64 | 65 | private bool IsRewritableType(string typeName) => 66 | _renames.Keys.Any(r => typeName.Contains($"{r}.")); 67 | 68 | private bool IsRewritableType(Func act) => 69 | _renames.Any(kv => act(kv.Key, kv.Value)); 70 | 71 | private void RewriteAttributes(string assembly, IEnumerable attributes) 72 | { 73 | foreach (var attribute in attributes) 74 | { 75 | RewriteTypeReference(assembly, attribute.AttributeType); 76 | RewriteMemberReference(assembly, attribute.Constructor); 77 | 78 | if (attribute.HasConstructorArguments) 79 | { 80 | foreach (var constructorArgument in attribute.ConstructorArguments) 81 | { 82 | var genericInstanceType = constructorArgument.Value as GenericInstanceType; 83 | var valueTypeReference = constructorArgument.Value as TypeReference; 84 | var valueTypeDefinition = constructorArgument.Value as TypeDefinition; 85 | RewriteTypeReference(assembly, constructorArgument.Type); 86 | if (valueTypeReference != null) RewriteTypeReference(assembly, valueTypeReference); 87 | if (genericInstanceType != null) RewriteTypeReference(assembly, genericInstanceType); 88 | if (valueTypeDefinition == null) 89 | RewriteTypeReference(assembly, valueTypeDefinition); 90 | 91 | if (constructorArgument.Type.Name == nameof(Type)) 92 | { 93 | // intentional no-op, but required for Cecil 94 | // to update the ctor arguments 95 | } 96 | } 97 | } 98 | 99 | if (attribute.HasProperties) 100 | foreach (var property in attribute.Properties) 101 | RewriteTypeReference(assembly, property.Argument.Type); 102 | 103 | if (attribute.HasFields) 104 | foreach (var field in attribute.Fields) 105 | RewriteTypeReference(assembly, field.Argument.Type); 106 | } 107 | } 108 | 109 | private void RewriteMemberReference(string assembly, MemberReference memberReference) 110 | { 111 | if (!IsRewritableType(memberReference.Name)) return; 112 | 113 | if (memberReference.DeclaringType != null) 114 | RewriteTypeReference(assembly, memberReference.DeclaringType); 115 | 116 | var name = RenameTypeName(memberReference.Name); 117 | Write(assembly, memberReference.GetType().Name, $"{memberReference.Name} to {name}"); 118 | memberReference.Name = name; 119 | } 120 | 121 | private void RewriteGenericParameter(string assembly, GenericParameter genericParameter) 122 | { 123 | var name = RenameTypeName(genericParameter.Name); 124 | Write(assembly, nameof(GenericParameter), $"{genericParameter.Name} to {name}"); 125 | genericParameter.Name = name; 126 | 127 | foreach (var genericParameterConstraint in genericParameter.Constraints) 128 | { 129 | var constraintTypeName = genericParameterConstraint.ConstraintType.Name; 130 | if (!IsRewritableType(constraintTypeName)) continue; 131 | 132 | name = RenameTypeName(constraintTypeName); 133 | Write(assembly, nameof(GenericParameterConstraint), $"{constraintTypeName} to {name}"); 134 | genericParameterConstraint.ConstraintType.Name = name; 135 | } 136 | 137 | foreach (var nestedGenericParameter in genericParameter.GenericParameters) 138 | RewriteGenericParameter(assembly, nestedGenericParameter); 139 | } 140 | 141 | private void RewriteAssembly(AssemblyToRewrite assemblyToRewrite, List assembliesToRewrite, 142 | ReaderParameters readerParameters) 143 | { 144 | if (assemblyToRewrite.Rewritten) return; 145 | 146 | string tempOutputPath = null; 147 | string currentName; 148 | using (var assembly = AssemblyDefinition.ReadAssembly(assemblyToRewrite.InputPath, readerParameters)) 149 | { 150 | currentName = assembly.Name.Name; 151 | var newName = assemblyToRewrite.OutputName; 152 | 153 | Write(currentName, nameof(AssemblyDefinition), 154 | $"rewriting {currentName} from {assemblyToRewrite.InputPath}"); 155 | 156 | foreach (var moduleDefinition in assembly.Modules) 157 | { 158 | foreach (var assemblyReference in moduleDefinition.AssemblyReferences) 159 | { 160 | Write(currentName, nameof(AssemblyDefinition), 161 | $"{assembly.Name} references {assemblyReference.Name}"); 162 | 163 | var assemblyReferenceToRewrite = 164 | assembliesToRewrite.FirstOrDefault(a => a.InputName == assemblyReference.Name); 165 | 166 | if (assemblyReferenceToRewrite != null) 167 | { 168 | if (!assemblyReferenceToRewrite.Rewritten) 169 | { 170 | Write(currentName, nameof(AssemblyNameReference), 171 | $"{assemblyReference.Name} will be rewritten first"); 172 | RewriteAssembly(assemblyReferenceToRewrite, assembliesToRewrite, readerParameters); 173 | } 174 | else 175 | Write(currentName, nameof(AssemblyNameReference), 176 | $"{assemblyReference.Name} already rewritten"); 177 | 178 | foreach (var innerModuleDefinition in assembly.Modules) 179 | { 180 | RewriteTypeReferences(currentName, innerModuleDefinition.GetTypeReferences()); 181 | RewriteTypes(currentName, innerModuleDefinition.Types); 182 | } 183 | 184 | assemblyReference.Name = assemblyReferenceToRewrite.OutputName; 185 | } 186 | } 187 | 188 | RewriteTypes(currentName, moduleDefinition.Types); 189 | moduleDefinition.Name = RenameTypeName(moduleDefinition.Name); 190 | } 191 | 192 | RewriteAssemblyTitleAttribute(assembly, currentName, newName); 193 | assembly.Name.Name = newName; 194 | var writerParameters = new WriterParameters(); 195 | 196 | if (!string.IsNullOrEmpty(_keyFile) && File.Exists(_keyFile)) 197 | { 198 | Write(currentName, nameof(AssemblyDefinition), 199 | $"signing {newName} with keyfile {_keyFile}"); 200 | var fileBytes = File.ReadAllBytes(_keyFile); 201 | writerParameters.StrongNameKeyBlob = fileBytes; 202 | assembly.Name.Attributes |= AssemblyAttributes.PublicKey; 203 | assembly.MainModule.Attributes |= ModuleAttributes.StrongNameSigned; 204 | } 205 | 206 | if (assemblyToRewrite.OutputPath == assemblyToRewrite.InputPath) 207 | { 208 | tempOutputPath = assemblyToRewrite.OutputPath + ".temp"; 209 | assembly.Write(tempOutputPath, writerParameters); 210 | assemblyToRewrite.Rewritten = true; 211 | Write(currentName, nameof(AssemblyDefinition), 212 | $"finished rewriting {currentName} into {tempOutputPath}"); 213 | } 214 | else 215 | { 216 | assembly.Write(assemblyToRewrite.OutputPath, writerParameters); 217 | assemblyToRewrite.Rewritten = true; 218 | Write(currentName, nameof(AssemblyDefinition), 219 | $"finished rewriting {currentName} into {assemblyToRewrite.OutputPath}"); 220 | } 221 | } 222 | 223 | if (!string.IsNullOrWhiteSpace(tempOutputPath)) 224 | { 225 | File.Delete(assemblyToRewrite.OutputPath); 226 | File.Move(tempOutputPath, assemblyToRewrite.OutputPath); 227 | Write(currentName, nameof(AssemblyDefinition), 228 | $"Rename {tempOutputPath} back to {assemblyToRewrite.OutputPath}"); 229 | } 230 | } 231 | 232 | private void RewriteTypeReferences(string assembly, IEnumerable typeReferences) 233 | { 234 | foreach (var typeReference in typeReferences) 235 | RewriteTypeReference(assembly, typeReference); 236 | } 237 | 238 | private void RewriteTypeReference(string assembly, TypeReference typeReference) 239 | { 240 | //var oReference = typeReference; 241 | // var doNotRewrite = IsRewritableType((o, n) => 242 | // (!oReference.Namespace.StartsWith(o) || oReference.Namespace.StartsWith(n)) && 243 | // (oReference.Namespace != string.Empty || !oReference.Name.StartsWith($"<{o}-")) 244 | // ); 245 | // 246 | // if (doNotRewrite) return; 247 | if (typeReference == null) return; 248 | 249 | if (typeReference is TypeSpecification) typeReference = typeReference.GetElementType(); 250 | 251 | if (typeReference == null) return; 252 | 253 | if (typeReference.Namespace != string.Empty && IsRewritableType((o, n) => 254 | (typeReference.Namespace == o || typeReference.Namespace.StartsWith($"{o}.")) && 255 | !typeReference.Namespace.StartsWith(n))) 256 | { 257 | var name = RenameTypeName(typeReference.Namespace, (t, o, n) => t.Replace(o, n)); 258 | var newFullName = RenameTypeName(typeReference.FullName, (t, o, n) => t.Replace(o + ".", n + ".")); 259 | Write(assembly, nameof(TypeReference), $"{typeReference.FullName} to {newFullName}"); 260 | typeReference.Namespace = name; 261 | } 262 | 263 | if (IsRewritableType((o, n) => typeReference.Name.StartsWith($"<{o}-"))) 264 | { 265 | var name = RenameTypeName(typeReference.Name, (t, o, n) => t.Replace($"<{o}-", $"<{n}-")); 266 | var newFullName = RenameTypeName(typeReference.FullName, (t, o, n) => t.Replace($"<{o}-", $"<{n}-")); 267 | Write(assembly, nameof(TypeReference), $"{typeReference.FullName} to {newFullName}"); 268 | typeReference.Name = name; 269 | } 270 | 271 | if (typeReference.HasGenericParameters) 272 | foreach (var genericParameter in typeReference.GenericParameters) 273 | RewriteGenericParameter(assembly, genericParameter); 274 | 275 | if (typeReference.DeclaringType != null) 276 | RewriteTypeReference(assembly, typeReference.DeclaringType); 277 | } 278 | 279 | private void RewriteAssemblyTitleAttribute(AssemblyDefinition assembly, string currentName, string newName) 280 | { 281 | foreach (var attribute in assembly.CustomAttributes) 282 | { 283 | if (attribute.AttributeType.Name != nameof(AssemblyTitleAttribute)) continue; 284 | 285 | var currentAssemblyName = (string) attribute.ConstructorArguments[0].Value; 286 | var newAssemblyName = Regex.Replace(currentAssemblyName, Regex.Escape(currentName), newName); 287 | 288 | // give the assembly a new title, even when the top level namespace is not part of it 289 | if (newAssemblyName == currentAssemblyName) 290 | newAssemblyName += $" ({newName})"; 291 | 292 | Write(assembly.Name.Name, nameof(AssemblyTitleAttribute), 293 | $"{currentAssemblyName} to {newAssemblyName}"); 294 | attribute.ConstructorArguments[0] = 295 | new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, newAssemblyName); 296 | } 297 | } 298 | 299 | private void RewriteTypes(string assembly, IEnumerable typeDefinitions) 300 | { 301 | foreach (var typeDefinition in typeDefinitions) 302 | { 303 | if (typeDefinition.HasNestedTypes) 304 | RewriteTypes(assembly, typeDefinition.NestedTypes); 305 | 306 | if (IsRewritableType((o, n) => 307 | (typeDefinition.Namespace == o || typeDefinition.Namespace.StartsWith($"{o}.")) && 308 | !typeDefinition.Namespace.StartsWith(n))) 309 | { 310 | var name = RenameTypeName(typeDefinition.Namespace, (t, o, n) => t.Replace(o, n)); 311 | Write(assembly, nameof(TypeDefinition), 312 | $"{typeDefinition.FullName} to {name}.{typeDefinition.Name}"); 313 | typeDefinition.Namespace = name; 314 | } 315 | 316 | RewriteAttributes(assembly, typeDefinition.CustomAttributes); 317 | 318 | foreach (var methodDefinition in typeDefinition.Methods) 319 | RewriteMethodDefinition(assembly, methodDefinition); 320 | 321 | foreach (var propertyDefinition in typeDefinition.Properties) 322 | { 323 | RewriteAttributes(assembly, propertyDefinition.CustomAttributes); 324 | RewriteTypeReference(assembly, propertyDefinition.PropertyType); 325 | RewriteMemberReference(assembly, propertyDefinition); 326 | 327 | if (propertyDefinition.GetMethod != null) 328 | RewriteMethodDefinition(assembly, propertyDefinition.GetMethod); 329 | if (propertyDefinition.SetMethod != null) 330 | RewriteMethodDefinition(assembly, propertyDefinition.SetMethod); 331 | if (propertyDefinition.HasOtherMethods) 332 | foreach (var otherMethod in propertyDefinition.OtherMethods) 333 | RewriteMethodDefinition(assembly, otherMethod); 334 | 335 | // generic properties or explicitly implemented interface properties 336 | if (IsRewritableType(propertyDefinition.Name)) 337 | { 338 | var name = RenameTypeName(propertyDefinition.Name); 339 | Write(assembly, nameof(PropertyDefinition), $"{propertyDefinition.Name} to {name}"); 340 | propertyDefinition.Name = name; 341 | } 342 | } 343 | 344 | foreach (var fieldDefinition in typeDefinition.Fields) 345 | { 346 | // compiler generated backing field 347 | if (IsRewritableType((fieldDefinition.Name))) 348 | { 349 | var name = RenameTypeName(fieldDefinition.Name); 350 | Write(assembly, nameof(PropertyDefinition), $"{fieldDefinition.Name} to {name}"); 351 | fieldDefinition.Name = name; 352 | } 353 | 354 | RewriteAttributes(assembly, fieldDefinition.CustomAttributes); 355 | RewriteTypeReference(assembly, fieldDefinition.FieldType); 356 | RewriteMemberReference(assembly, fieldDefinition); 357 | } 358 | 359 | foreach (var interfaceImplementation in typeDefinition.Interfaces) 360 | { 361 | RewriteAttributes(assembly, interfaceImplementation.CustomAttributes); 362 | RewriteTypeReference(assembly, interfaceImplementation.InterfaceType); 363 | RewriteMemberReference(assembly, interfaceImplementation.InterfaceType); 364 | } 365 | 366 | foreach (var eventDefinition in typeDefinition.Events) 367 | { 368 | RewriteAttributes(assembly, eventDefinition.CustomAttributes); 369 | RewriteTypeReference(assembly, eventDefinition.EventType); 370 | RewriteMemberReference(assembly, eventDefinition.EventType); 371 | } 372 | 373 | foreach (var genericParameter in typeDefinition.GenericParameters) 374 | { 375 | RewriteAttributes(assembly, genericParameter.CustomAttributes); 376 | RewriteTypeReference(assembly, genericParameter); 377 | RewriteGenericParameter(assembly, genericParameter); 378 | } 379 | } 380 | } 381 | 382 | private void RewriteMethodDefinition(string assembly, MethodDefinition methodDefinition) 383 | { 384 | RewriteAttributes(assembly, methodDefinition.CustomAttributes); 385 | RewriteMemberReference(assembly, methodDefinition); 386 | 387 | if (IsRewritableType((o, n) => methodDefinition.Name.StartsWith(o + "."))) 388 | { 389 | var name = RenameTypeName(methodDefinition.Name); 390 | Write(assembly, nameof(MethodDefinition), $"{methodDefinition.Name} to {name}"); 391 | methodDefinition.Name = name; 392 | } 393 | 394 | foreach (var methodDefinitionOverride in methodDefinition.Overrides) 395 | { 396 | // explicit interface implementation of generic interface 397 | if (IsRewritableType(methodDefinition.Name)) 398 | { 399 | var name = RenameTypeName(methodDefinition.Name); 400 | Write(assembly, nameof(MethodDefinition), $"{methodDefinition.Name} to {name}"); 401 | methodDefinition.Name = name; 402 | } 403 | 404 | foreach (var genericParameter in methodDefinitionOverride.GenericParameters) 405 | { 406 | RewriteAttributes(assembly, genericParameter.CustomAttributes); 407 | RewriteGenericParameter(assembly, genericParameter); 408 | } 409 | 410 | RewriteMemberReference(assembly, methodDefinitionOverride); 411 | } 412 | 413 | foreach (var genericParameter in methodDefinition.GenericParameters) 414 | { 415 | RewriteAttributes(assembly, genericParameter.CustomAttributes); 416 | RewriteGenericParameter(assembly, genericParameter); 417 | } 418 | 419 | foreach (var parameterDefinition in methodDefinition.Parameters) 420 | { 421 | RewriteAttributes(assembly, parameterDefinition.CustomAttributes); 422 | RewriteTypeReference(assembly, parameterDefinition.ParameterType); 423 | } 424 | 425 | RewriteTypeReference(assembly, methodDefinition.ReturnType); 426 | RewriteMethodBody(assembly, methodDefinition); 427 | } 428 | 429 | private void RewriteMethodBody(string assembly, MethodDefinition methodDefinition) 430 | { 431 | if (!methodDefinition.HasBody) return; 432 | 433 | for (var index = 0; index < methodDefinition.Body.Instructions.Count; index++) 434 | { 435 | var instruction = methodDefinition.Body.Instructions[index]; 436 | 437 | // Strings that reference the namespace 438 | if (instruction.OpCode.Code == Code.Ldstr) 439 | { 440 | var operandString = (string) instruction.Operand; 441 | if (IsRewritableType((o, n) => operandString.StartsWith($"{o}."))) 442 | { 443 | var name = RenameTypeName(operandString); 444 | Write(assembly, nameof(Instruction), $"{instruction.OpCode.Code}. {name}"); 445 | instruction.Operand = operandString; 446 | } 447 | } 448 | // loading or storing compiler generated backing fields 449 | else if (instruction.OpCode.Code == Code.Ldfld || instruction.OpCode.Code == Code.Stfld) 450 | { 451 | var fieldReference = (FieldReference) instruction.Operand; 452 | 453 | // rename the compiler backing field name 454 | if (IsRewritableType((o, n) => fieldReference.Name.StartsWith($"<{o}."))) 455 | { 456 | var name = RenameTypeName(fieldReference.Name, (t, o, n) => t.Replace($"<{o}.", $"<{n}.")); 457 | Write(assembly, nameof(Instruction), $"{instruction.OpCode.Code}. {name}"); 458 | fieldReference.Name = name; 459 | } 460 | 461 | RewriteMemberReference(assembly, fieldReference); 462 | RewriteTypeReference(assembly, fieldReference.FieldType); 463 | } 464 | // method calls 465 | else if (instruction.OpCode.Code == Code.Call) 466 | { 467 | var methodReference = (MethodReference) instruction.Operand; 468 | RewriteMemberReference(assembly, methodReference); 469 | RewriteTypeReference(assembly, methodReference.ReturnType); 470 | 471 | if (methodReference.IsGenericInstance) 472 | { 473 | var genericInstance = (GenericInstanceMethod) methodReference; 474 | RewriteTypeReferences(assembly, genericInstance.GenericArguments); 475 | } 476 | } 477 | } 478 | } 479 | 480 | private void Write(string assembly, string operation, string message) 481 | { 482 | void Write() 483 | { 484 | Console.ForegroundColor = ConsoleColor.DarkGray; 485 | Console.Write($"[{DateTime.Now:yyyy-MM-ddTHH:mm:ss.ffzzz}]["); 486 | Console.ForegroundColor = ConsoleColor.Cyan; 487 | Console.Write(assembly.PadRight(18)); 488 | Console.ForegroundColor = ConsoleColor.DarkGray; 489 | Console.Write("]["); 490 | Console.ForegroundColor = ConsoleColor.Green; 491 | Console.Write($"{operation.PadRight(23)}"); 492 | Console.ForegroundColor = ConsoleColor.DarkGray; 493 | Console.Write("] "); 494 | Console.ForegroundColor = ConsoleColor.White; 495 | Console.WriteLine($"{message}"); 496 | Console.ResetColor(); 497 | } 498 | 499 | switch (operation) 500 | { 501 | case nameof(AssemblyDefinition): 502 | case nameof(AssemblyNameReference): 503 | case nameof(AssemblyTitleAttribute): 504 | case nameof(Rewrite): 505 | Write(); 506 | break; 507 | default: 508 | if (_verbose) 509 | Write(); 510 | break; 511 | } 512 | } 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /src/assembly-rewriter/AssemblyToRewrite.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace AssemblyRewriter 4 | { 5 | internal class AssemblyToRewrite 6 | { 7 | private string _inputDirectory; 8 | private string _inputName; 9 | private string _outputDirectory; 10 | private string _outputName; 11 | 12 | public AssemblyToRewrite(string inputPath, string outputPath) 13 | { 14 | InputPath = Path.GetFullPath(inputPath); 15 | OutputPath = Path.GetFullPath(outputPath); 16 | } 17 | 18 | public string InputPath { get; } 19 | 20 | public string InputDirectory => _inputDirectory ??= Path.GetDirectoryName(InputPath); 21 | 22 | public string InputName => _inputName ??= Path.GetFileNameWithoutExtension(InputPath); 23 | 24 | public string OutputDirectory => _outputDirectory ??= Path.GetDirectoryName(OutputPath); 25 | 26 | public string OutputName => _outputName ??= Path.GetFileNameWithoutExtension(OutputPath); 27 | 28 | public string OutputPath { get; } 29 | 30 | public bool Rewritten { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/assembly-rewriter/Options.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CommandLine; 3 | 4 | namespace AssemblyRewriter 5 | { 6 | public class Options 7 | { 8 | [Option('i', "in", Min = 1, Required = true, HelpText = "input path for assembly to rewrite. Use multiple flags for multiple input paths")] 9 | public IEnumerable InputPaths { get; set; } 10 | 11 | [Option('o', "out", Min = 1, Required = true, HelpText = "output path for rewritten assembly. Use multiple flags for multiple output paths")] 12 | public IEnumerable OutputPaths { get; set; } 13 | 14 | [Option('r', "resolvedir", HelpText = "Additional assembly resolve directories. Use multiple flags for multiple resolve directories")] 15 | public IEnumerable ResolveDirectories { get; set; } 16 | 17 | [Option('k', "keyfile", HelpText = "Sign rewritten assembly with this key file. When merge option is specified, the merged assembly will be signed.")] 18 | public string KeyFile { get; set; } 19 | 20 | [Option('m', "merge", Default = false, HelpText = "Merge all rewritten assemblies into a single assembly using the first output path as target")] 21 | public bool Merge { get; set; } 22 | 23 | [Option('v', "verbose", Default = false, HelpText = "verbose output")] 24 | public bool Verbose { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/assembly-rewriter/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using CommandLine; 5 | using CommandLine.Text; 6 | using ILRepacking; 7 | 8 | namespace AssemblyRewriter 9 | { 10 | internal static class Program 11 | { 12 | private static int Main(string[] args) 13 | { 14 | using var parser = new Parser(settings => 15 | { 16 | settings.HelpWriter = null; 17 | settings.IgnoreUnknownArguments = false; 18 | settings.AllowMultiInstance = true; 19 | }); 20 | 21 | var result = parser.ParseArguments(args); 22 | 23 | return result switch 24 | { 25 | Parsed parsed => Run(parsed.Value), 26 | NotParsed notParsed => HandleError(notParsed), 27 | _ => 1 28 | }; 29 | } 30 | 31 | private static int Run(Options options) 32 | { 33 | if (options.InputPaths.Count() != options.OutputPaths.Count()) 34 | { 35 | Console.ForegroundColor = ConsoleColor.Red; 36 | Console.WriteLine("Number of input paths must equal number of output paths"); 37 | Console.ResetColor(); 38 | return 1; 39 | } 40 | 41 | try 42 | { 43 | var rewriter = new AssemblyRewriter(options); 44 | rewriter.Rewrite(options.InputPaths, options.OutputPaths, options.ResolveDirectories); 45 | } 46 | catch (Exception e) 47 | { 48 | Console.ForegroundColor = ConsoleColor.Red; 49 | Console.WriteLine(e); 50 | return 1; 51 | } 52 | if (!options.Merge) return 0; 53 | try 54 | { 55 | var repackOptions = new RepackOptions 56 | { 57 | Internalize = true, 58 | Closed = true, 59 | KeepOtherVersionReferences = false, 60 | TargetKind = ILRepack.Kind.SameAsPrimaryAssembly, 61 | InputAssemblies = options.OutputPaths.ToArray(), 62 | LineIndexation = true, 63 | OutputFile = options.OutputPaths.First(), 64 | KeyFile = options.KeyFile, 65 | SearchDirectories = options.OutputPaths.Select(p=> new DirectoryInfo(p).FullName).Distinct(), 66 | }; 67 | 68 | var pack = new ILRepack(repackOptions, new RepackConsoleLogger()); 69 | pack.Repack(); 70 | } 71 | catch (Exception e) 72 | { 73 | Console.ForegroundColor = ConsoleColor.Red; 74 | Console.WriteLine(e); 75 | return 2; 76 | } 77 | return 0; 78 | } 79 | 80 | private static int HandleError(NotParsed notParsed) 81 | { 82 | var helpText = HelpText.AutoBuild(notParsed, h => 83 | { 84 | h.AdditionalNewLineAfterOption = false; 85 | h.Heading = "AssemblyRewriter" + 86 | Environment.NewLine + 87 | "----------------" + 88 | Environment.NewLine + 89 | "Rewrites assemblies and namespaces"; 90 | h.AddPostOptionsLine("Each input path must have a corresponding output path"); 91 | return HelpText.DefaultParsingErrorsHandler(notParsed, h); 92 | }, e => e); 93 | 94 | if (notParsed.Errors.IsHelp() || notParsed.Errors.IsVersion()) 95 | { 96 | Console.ForegroundColor = ConsoleColor.Green; 97 | Console.WriteLine(helpText); 98 | Console.ResetColor(); 99 | return 0; 100 | } 101 | 102 | Console.ForegroundColor = ConsoleColor.Red; 103 | Console.WriteLine(helpText); 104 | Console.ResetColor(); 105 | return 1; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/assembly-rewriter/RepackConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ILRepacking; 3 | 4 | namespace AssemblyRewriter 5 | { 6 | internal class RepackConsoleLogger : ILogger 7 | { 8 | private void Write(string level, string msg) 9 | { 10 | Console.ForegroundColor = ConsoleColor.DarkGray; 11 | Console.Write($"[{DateTime.Now:yyyy-MM-ddTHH:mm:ss.ffzzz}]["); 12 | Console.ForegroundColor = ConsoleColor.Cyan; 13 | Console.Write($"Repack"); 14 | Console.ForegroundColor = ConsoleColor.DarkGray; 15 | Console.Write($"]["); 16 | Console.ForegroundColor = LevelToConsoleColor(level); 17 | Console.Write($"{level.PadRight(7)}"); 18 | Console.ForegroundColor = ConsoleColor.DarkGray; 19 | Console.Write($"]"); 20 | Console.ForegroundColor = ConsoleColor.White; 21 | Console.WriteLine(msg); 22 | Console.ResetColor(); 23 | } 24 | 25 | private ConsoleColor LevelToConsoleColor(string level) => 26 | level switch 27 | { 28 | nameof(Error) => ConsoleColor.Red, 29 | nameof(Warn) => ConsoleColor.Yellow, 30 | nameof(Info) => ConsoleColor.Blue, 31 | nameof(Verbose) => ConsoleColor.Gray, 32 | _ => ConsoleColor.Gray 33 | }; 34 | 35 | public void Log(object str) => Write(nameof(Log), str.ToString()); 36 | 37 | public void Error(string msg) => Write(nameof(Error), msg); 38 | 39 | public void Warn(string msg) => Write(nameof(Warn), msg); 40 | 41 | public void Info(string msg) =>Write(nameof(Info), msg); 42 | 43 | public void Verbose(string msg) 44 | { 45 | if (!ShouldLogVerbose) return; 46 | Write(nameof(Verbose), msg); 47 | } 48 | 49 | public void DuplicateIgnored(string ignoredType, object ignoredObject) => 50 | Write(nameof(Warn), $"ignoredType:{ignoredType} ignoredObject:{ignoredObject}"); 51 | 52 | public bool ShouldLogVerbose { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/assembly-rewriter/assembly-rewriter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp3.0;netcoreapp3.1;net5.0;net6.0 5 | assembly-rewriter 6 | AssemblyRewriter 7 | true 8 | assembly-rewriter 9 | 10 | true 11 | ..\..\build\keys\keypair.snk 12 | 13 | nuget-icon.png 14 | MIT 15 | https://github.com/nullean/assembly-rewriter 16 | https://github.com/nullean/assembly-rewriter 17 | https://github.com/nullean/assembly-rewriter/releases 18 | 19 | assembly-rewriter: a dotnet tool to rewrite assembly namespaces 20 | Diff assemblies and nuget packages 21 | latest 22 | 23 | 24 | 25 | 26 | 27 | nuget-icon.png 28 | True 29 | nuget-icon.png 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | --------------------------------------------------------------------------------