├── .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 |
--------------------------------------------------------------------------------