├── .gitignore ├── LICENSE ├── README.md ├── ReferenceAssemblyGenerator.BuildTargets ├── ReferenceAssemblyGenerator.BuildTargets.nuspec └── build │ └── ReferenceAssemblyGenerator.BuildTargets.targets └── ReferenceAssemblyGenerator.CLI ├── Program.cs ├── ProgramOptions.cs ├── ReferenceAssemblyGenerator.CLI.csproj └── ReferenceAssemblyGenerator.sln /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studo 2015 cache/options directory 25 | .vs/ 26 | 27 | # MSTest test Results 28 | [Tt]est[Rr]esult*/ 29 | [Bb]uild[Ll]og.* 30 | 31 | *.nupkg 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | *_i.c 43 | *_p.c 44 | *_i.h 45 | *.ilk 46 | *.meta 47 | *.obj 48 | *.pch 49 | *.pdb 50 | *.pgc 51 | *.pgd 52 | *.rsp 53 | *.sbr 54 | *.tlb 55 | *.tli 56 | *.tlh 57 | *.tmp 58 | *.tmp_proj 59 | *.log 60 | *.vspscc 61 | *.vssscc 62 | .builds 63 | *.pidb 64 | *.svclog 65 | *.scc 66 | 67 | # Chutzpah Test files 68 | _Chutzpah* 69 | 70 | # Visual C++ cache files 71 | ipch/ 72 | *.aps 73 | *.ncb 74 | *.opensdf 75 | *.sdf 76 | *.cachefile 77 | 78 | # Visual Studio profiler 79 | *.psess 80 | *.vsp 81 | *.vspx 82 | 83 | # TFS 2012 Local Workspace 84 | $tf/ 85 | 86 | # Guidance Automation Toolkit 87 | *.gpState 88 | 89 | # ReSharper is a .NET coding add-in 90 | _ReSharper*/ 91 | *.[Rr]e[Ss]harper 92 | *.DotSettings.user 93 | 94 | # JustCode is a .NET coding addin-in 95 | .JustCode 96 | 97 | # TeamCity is a build add-in 98 | _TeamCity* 99 | 100 | # DotCover is a Code Coverage Tool 101 | *.dotCover 102 | 103 | # NCrunch 104 | _NCrunch_* 105 | .*crunch*.local.xml 106 | 107 | # MightyMoose 108 | *.mm.* 109 | AutoTest.Net/ 110 | 111 | # Web workbench (sass) 112 | .sass-cache/ 113 | 114 | # Installshield output folder 115 | [Ee]xpress/ 116 | 117 | # DocProject is a documentation generator add-in 118 | DocProject/buildhelp/ 119 | DocProject/Help/*.HxT 120 | DocProject/Help/*.HxC 121 | DocProject/Help/*.hhc 122 | DocProject/Help/*.hhk 123 | DocProject/Help/*.hhp 124 | DocProject/Help/Html2 125 | DocProject/Help/html 126 | 127 | # Click-Once directory 128 | publish/ 129 | 130 | # Publish Web Output 131 | *.[Pp]ublish.xml 132 | *.azurePubxml 133 | # TODO: Comment the next line if you want to checkin your web deploy settings 134 | # but database connection strings (with potential passwords) will be unencrypted 135 | *.pubxml 136 | *.publishproj 137 | 138 | # NuGet Packages 139 | *.nupkg 140 | # The packages folder can be ignored because of Package Restore 141 | **/packages/* 142 | # except build/, which is used as an MSBuild target. 143 | !**/packages/build/ 144 | # Uncomment if necessary however generally it will be regenerated when needed 145 | #!**/packages/repositories.config 146 | 147 | # Windows Azure Build Output 148 | csx/ 149 | *.build.csdef 150 | 151 | # Windows Store app package directory 152 | AppPackages/ 153 | 154 | # Others 155 | *.[Cc]ache 156 | ClientBin/ 157 | [Ss]tyle[Cc]op.* 158 | ~$* 159 | *~ 160 | *.dbmdl 161 | *.dbproj.schemaview 162 | *.pfx 163 | *.publishsettings 164 | node_modules/ 165 | bower_components/ 166 | 167 | # RIA/Silverlight projects 168 | Generated_Code/ 169 | 170 | # Backup & report files from converting an old project file 171 | # to a newer Visual Studio version. Backup files are not needed, 172 | # because we have git ;-) 173 | _UpgradeReport_Files/ 174 | Backup*/ 175 | UpgradeLog*.XML 176 | UpgradeLog*.htm 177 | 178 | # SQL Server files 179 | *.mdf 180 | *.ldf 181 | 182 | # Business Intelligence projects 183 | *.rdl.data 184 | *.bim.layout 185 | *.bim_*.settings 186 | 187 | # Microsoft Fakes 188 | FakesAssemblies/ 189 | 190 | # Node.js Tools for Visual Studio 191 | .ntvs_analysis.dat 192 | 193 | # Visual Studio 6 build log 194 | *.plg 195 | 196 | # Visual Studio 6 workspace options file 197 | *.opt 198 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Enes Sadık Özbek 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 | # Reference Assembly Generator 2 | A dotnet tool to generate reference assemblies. 3 | 4 | ### What is a reference assembly? 5 | [Reference assemblies](https://github.com/dotnet/roslyn/blob/master/docs/features/refout.md) are assemblies which only contain metadata but no executable IL code. 6 | 7 | ### Why would you need this? 8 | Sometimes you do not want to ship executable code but only metadata for developers to interact with your dll. 9 | This can be especially useful if other developers are developing addons/plugins/integrations to your proprietary product. 10 | You can then just provide your reference assembly to them. They will not need access to your product. 11 | 12 | ### Usage 13 | #### CLI 14 | `dotnet tool install ReferenceAssemblyGenerator.CLI <-g|--global>` 15 | `dotnet generatereference -- [--keep-non-public] [--force] [--use-ret] [--output ] ` 16 | 17 | #### NuGet 18 | First install the CLI tool globally: `dotnet tool install ReferenceAssemblyGenerator.CLI --global`. 19 | 20 | After that install `ReferenceAssemblyGenerator.BuildTargets` to your project and set `` to true in your .csproj. 21 | You can also set ``, `` and ``. 22 | 23 | By default, `` will be equal to the output file. 24 | 25 | ### License 26 | [MIT](https://github.com/ImperialPlugins/ReferenceAssemblyGenerator/blob/master/LICENSE) 27 | 28 | 29 | ### ReferenceAssemblyGenerator vs. Rosyln (/refout and /refonly) 30 | The C# and Visual Basic .NET compiler (Rosyln) contains the similar options [/refout and /refonly](https://github.com/dotnet/roslyn/blob/master/docs/features/refout.md). 31 | 32 | * Unlike Rosyln, ReferenceAssemblyGenerator removes non-public types and members too. It also removes all non-public attributes. 33 | * Rosyln only supports `throw null` as dummy instructions while ReferenceAssemblyGenerator also supports just `ret` instead. 34 | * Rosyln can only generate reference assemblies if you have the full source code. ReferenceAssemblyGenerator does not need source code, only the .dll or .exe. 35 | -------------------------------------------------------------------------------- /ReferenceAssemblyGenerator.BuildTargets/ReferenceAssemblyGenerator.BuildTargets.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReferenceAssemblyGenerator.BuildTargets 5 | 1.0.2 6 | Build targets for ReferenceAssemblyGenerator 7 | Enes Sadık Özbek <esozbek.me> 8 | true 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ReferenceAssemblyGenerator.BuildTargets/build/ReferenceAssemblyGenerator.BuildTargets.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(AssemblyName).dll 6 | 7 | 8 | $(AssemblyName).exe 9 | 10 | 11 | $(InputFile) 12 | 13 | 14 | generatereference -- $(InputFile) --force --output $(ReferenceOutputPath) 15 | 16 | 17 | $(ReferenceGeneratorCommand) --use-ret 18 | 19 | 20 | $(ReferenceGeneratorCommand) --keep-non-public 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ReferenceAssemblyGenerator.CLI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using CommandLine; 6 | using dnlib.DotNet; 7 | using dnlib.DotNet.Emit; 8 | 9 | namespace ReferenceAssemblyGenerator.CLI 10 | { 11 | public class Program 12 | { 13 | public static int Main(string[] args) 14 | { 15 | var result = Parser.Default.ParseArguments(args) 16 | .WithParsed(RunWithOptions); 17 | 18 | return result.Tag == ParserResultType.Parsed ? 0 : 1; 19 | } 20 | 21 | private static readonly List s_RemovedTypes = new List(); 22 | private static ProgramOptions s_ProgamOptions; 23 | 24 | private static void RunWithOptions(ProgramOptions opts) 25 | { 26 | s_ProgamOptions = opts; 27 | 28 | if (!File.Exists(opts.AssemblyPath)) 29 | { 30 | throw new FileNotFoundException("Assembly file was not found", opts.AssemblyPath); 31 | } 32 | 33 | if (string.IsNullOrEmpty(opts.OutputFile)) 34 | { 35 | string fileName = Path.GetFileNameWithoutExtension(opts.AssemblyPath); 36 | string extension = Path.GetExtension(opts.AssemblyPath); 37 | 38 | opts.OutputFile = opts.AssemblyPath.Replace(fileName + extension, fileName + "-reference" + extension); 39 | } 40 | 41 | if (File.Exists(opts.OutputFile) && !opts.Force) 42 | { 43 | throw new Exception("Output file exists already. Use --force to override it."); 44 | } 45 | 46 | byte[] assemblyData = File.ReadAllBytes(opts.AssemblyPath); 47 | 48 | using (MemoryStream inputStream = new MemoryStream(assemblyData)) 49 | using (MemoryStream outputStream = new MemoryStream(assemblyData)) 50 | { 51 | ModuleDefMD module = ModuleDefMD.Load(inputStream); 52 | 53 | RemoveNonPublicTypes(module.Types); 54 | RemoveNonPublicMembers(module.Types); 55 | 56 | module.IsILOnly = true; 57 | module.VTableFixups = null; 58 | module.IsStrongNameSigned = false; 59 | module.Assembly.PublicKey = null; 60 | module.Assembly.HasPublicKey = false; 61 | ClearCustomAttributes(module.CustomAttributes); 62 | 63 | if (File.Exists(opts.OutputFile)) 64 | { 65 | File.Delete(opts.OutputFile); 66 | } 67 | 68 | module.Write(outputStream); 69 | outputStream.Seek(0, SeekOrigin.Begin); 70 | 71 | using (var fileStream = File.Create(opts.OutputFile)) 72 | { 73 | outputStream.CopyTo(fileStream); 74 | } 75 | } 76 | } 77 | 78 | private static void RemoveNonPublicMembers(ICollection types) 79 | { 80 | foreach (var type in types) 81 | { 82 | ClearCustomAttributes(type.CustomAttributes); 83 | 84 | foreach (var method in type.Methods.ToList()) 85 | { 86 | ClearCustomAttributes(method.CustomAttributes); 87 | 88 | if (ShouldRemoveMethod(method)) 89 | { 90 | type.Methods.Remove(method); 91 | } 92 | else 93 | { 94 | PurgeMethodBody(method); 95 | } 96 | } 97 | 98 | foreach (var field in type.Fields.ToList()) 99 | { 100 | ClearCustomAttributes(field.CustomAttributes); 101 | 102 | if (s_RemovedTypes.Any(d => d.FullName.Equals(field.FieldType.FullName, StringComparison.OrdinalIgnoreCase)) || (!field.IsPublic && !field.IsFamily && !s_ProgamOptions.KeepNonPublic)) 103 | { 104 | type.Fields.Remove(field); 105 | } 106 | } 107 | 108 | foreach (var property in type.Properties.ToList()) 109 | { 110 | ClearCustomAttributes(property.CustomAttributes); 111 | 112 | var getMethod = property.GetMethod; 113 | var setMethod = property.SetMethod; 114 | 115 | if (getMethod != null || setMethod != null) 116 | { 117 | var propertyType = getMethod != null ? getMethod.ReturnType : setMethod.Parameters[0].Type; 118 | bool shouldRemoveProperty = 119 | s_RemovedTypes.Any(d => d.FullName.Equals(propertyType.FullName)); 120 | 121 | if (getMethod != null) 122 | { 123 | ClearCustomAttributes(getMethod.CustomAttributes); 124 | 125 | if (shouldRemoveProperty) 126 | { 127 | getMethod = property.GetMethod = null; 128 | } 129 | else 130 | { 131 | PurgeMethodBody(getMethod); 132 | } 133 | } 134 | 135 | if (setMethod != null) 136 | { 137 | ClearCustomAttributes(setMethod.CustomAttributes); 138 | 139 | if (shouldRemoveProperty) 140 | { 141 | setMethod = property.SetMethod = null; 142 | } 143 | else 144 | { 145 | PurgeMethodBody(setMethod); 146 | } 147 | } 148 | } 149 | 150 | if (getMethod == null && setMethod == null) 151 | { 152 | type.Properties.Remove(property); 153 | } 154 | } 155 | 156 | foreach (var @event in type.Events) 157 | { 158 | ClearCustomAttributes(@event.CustomAttributes); 159 | 160 | var addMethod = @event.AddMethod; 161 | var invokeMethod = @event.InvokeMethod; 162 | var removeMethod = @event.RemoveMethod; 163 | var otherMethods = @event.OtherMethods; 164 | 165 | if (addMethod != null) 166 | { 167 | ClearCustomAttributes(addMethod.CustomAttributes); 168 | 169 | if (ShouldRemoveMethod(addMethod)) 170 | { 171 | addMethod = @event.AddMethod = null; 172 | } 173 | else 174 | { 175 | PurgeMethodBody(addMethod); 176 | } 177 | } 178 | 179 | if (invokeMethod != null) 180 | { 181 | ClearCustomAttributes(invokeMethod.CustomAttributes); 182 | 183 | if (ShouldRemoveMethod(invokeMethod)) 184 | { 185 | invokeMethod = @event.InvokeMethod = null; 186 | } 187 | else 188 | { 189 | PurgeMethodBody(invokeMethod); 190 | } 191 | } 192 | 193 | if (removeMethod != null) 194 | { 195 | ClearCustomAttributes(removeMethod.CustomAttributes); 196 | 197 | if (ShouldRemoveMethod(removeMethod)) 198 | { 199 | removeMethod = @event.RemoveMethod = null; 200 | } 201 | else 202 | { 203 | PurgeMethodBody(removeMethod); 204 | } 205 | } 206 | 207 | if (otherMethods != null) 208 | { 209 | foreach (var otherMethod in otherMethods.ToList()) 210 | { 211 | ClearCustomAttributes(otherMethod.CustomAttributes); 212 | 213 | if (ShouldRemoveMethod(otherMethod)) 214 | { 215 | @event.OtherMethods.Remove(otherMethod); 216 | } 217 | else 218 | { 219 | PurgeMethodBody(otherMethod); 220 | } 221 | } 222 | } 223 | 224 | if (@event.OtherMethods.Count == 0) 225 | { 226 | otherMethods = null; 227 | } 228 | 229 | if (addMethod == null 230 | && invokeMethod == null 231 | && removeMethod == null 232 | && otherMethods == null) 233 | { 234 | type.Events.Remove(@event); 235 | } 236 | } 237 | 238 | RemoveNonPublicMembers(type.NestedTypes); 239 | } 240 | } 241 | 242 | private static void RemoveNonPublicTypes(ICollection types) 243 | { 244 | foreach (var type in types.ToList()) 245 | { 246 | type.CustomAttributes.Clear(); 247 | 248 | if ((type.IsNotPublic || type.IsGlobalModuleType) && !s_ProgamOptions.KeepNonPublic) 249 | { 250 | type.Fields.Clear(); 251 | type.Properties.Clear(); 252 | type.Events.Clear(); 253 | type.Methods.Clear(); 254 | type.NestedTypes.Clear(); 255 | 256 | if (!type.IsGlobalModuleType) 257 | { 258 | types.Remove(type); 259 | } 260 | 261 | s_RemovedTypes.Add(type); 262 | continue; 263 | } 264 | 265 | RemoveNonPublicTypes(type.NestedTypes); 266 | } 267 | } 268 | 269 | private static void ClearCustomAttributes(CustomAttributeCollection collection) 270 | { 271 | foreach (var type in s_RemovedTypes) 272 | { 273 | collection.RemoveAll(type.FullName); 274 | } 275 | } 276 | 277 | private static bool ShouldRemoveMethod(MethodDef method) 278 | { 279 | if (s_RemovedTypes.Any(d => method.Parameters.Any(e => e.ParamDef?.FullName?.Equals(d.FullName, StringComparison.OrdinalIgnoreCase) ?? false))) 280 | { 281 | return true; 282 | } 283 | 284 | if (method.IsSpecialName && (method.Name.Equals(".ctor") || method.Name.Equals(".cctor"))) 285 | { 286 | return false; 287 | } 288 | 289 | if (method.IsFamily) 290 | { 291 | return false; 292 | } 293 | 294 | return !method.IsPublic && !s_ProgamOptions.KeepNonPublic; 295 | } 296 | 297 | private static void PurgeMethodBody(MethodDef method) 298 | { 299 | if (!method.IsIL || method.Body == null) 300 | { 301 | Console.WriteLine($"Skipped method: {method.FullName} (NO IL BODY)"); 302 | return; 303 | } 304 | 305 | method.Body = new CilBody(); 306 | 307 | if (!s_ProgamOptions.UseRet) 308 | { 309 | // This is what Roslyn does with /refout and /refonly 310 | method.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull)); 311 | method.Body.Instructions.Add(Instruction.Create(OpCodes.Throw)); 312 | } 313 | else 314 | { 315 | method.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); 316 | } 317 | 318 | method.Body.UpdateInstructionOffsets(); 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /ReferenceAssemblyGenerator.CLI/ProgramOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace ReferenceAssemblyGenerator.CLI 4 | { 5 | public class ProgramOptions 6 | { 7 | [Option('o', "output", Required = false, HelpText = "Sets the output file")] 8 | public string OutputFile { get; set; } 9 | 10 | [Option('f', "force", Required = false, HelpText = "Overrides output file if it exists")] 11 | public bool Force { get; set; } 12 | 13 | [Option("keep-non-public", Required = false, HelpText = "Sets if non-public metadata should be kept")] 14 | public bool KeepNonPublic { get; set; } 15 | 16 | [Option("use-ret", Required = false, HelpText = "Uses empty returns instead of throw null")] 17 | public bool UseRet { get; set; } 18 | 19 | [Value(0, MetaName = "assemblyPath", Required = true, HelpText = "Path to assembly to generate reference assembly for.")] 20 | public string AssemblyPath { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /ReferenceAssemblyGenerator.CLI/ReferenceAssemblyGenerator.CLI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | true 7 | generatereference 8 | ./nupkg 9 | 1.0.6 10 | Copyright (C) Enes Sadik Oezbek 11 | true 12 | Enes Sadık Özbek <esozbek.me> 13 | 14 | Reference assembly generator for .NET modules 15 | http://github.com/Trojaner/ReferenceAssemblyGenerator 16 | http://github.com/Trojaner/ReferenceAssemblyGenerator 17 | reference assembly, dnlib 18 | ReferenceAssemblyGenerator.CLI 19 | ReferenceAssemblyGenerator CLI 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ReferenceAssemblyGenerator.CLI/ReferenceAssemblyGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29209.62 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReferenceAssemblyGenerator", "ReferenceAssemblyGenerator.CLI.csproj", "{3200A207-6EC0-4DE6-BF04-58BBD997E9E2}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {3200A207-6EC0-4DE6-BF04-58BBD997E9E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {3200A207-6EC0-4DE6-BF04-58BBD997E9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {3200A207-6EC0-4DE6-BF04-58BBD997E9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {3200A207-6EC0-4DE6-BF04-58BBD997E9E2}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {8CC88E2C-AEFA-4D30-B2A4-890A59C94A90} 24 | EndGlobalSection 25 | EndGlobal 26 | --------------------------------------------------------------------------------