├── .gitignore ├── License.md ├── README.md └── Solutions ├── BreakingChange ├── BreakingChange.csproj ├── Class1.cs └── Properties │ └── AssemblyInfo.cs ├── BreakingChange2 ├── BreakingChange2.csproj ├── Class1.cs └── Properties │ └── AssemblyInfo.cs ├── Endjin.Assembly.ChangeDetection.Specs ├── App.config ├── Endjin.Assembly.ChangeDetection.Specs.csproj ├── Features │ ├── ChangeRules.feature │ ├── ChangeRules.feature.cs │ ├── DifferenceDetection.feature │ ├── DifferenceDetection.feature.cs │ ├── SemanticVersioning.feature │ └── SemanticVersioning.feature.cs ├── Properties │ └── AssemblyInfo.cs ├── Steps │ ├── ChangeRulesSteps.cs │ ├── DifferenceDetectionSteps.cs │ └── SemanticVersioningSteps.cs └── packages.config ├── Endjin.Assembly.ChangeDetection.sln ├── Endjin.Assembly.ChangeDetection ├── Diff │ ├── AssemblyDiffCollection.cs │ ├── AssemblyDiffer.cs │ ├── BreakingChangeSearcher.cs │ ├── DiffCollection.cs │ ├── DiffOperation.cs │ ├── DiffPrinter.cs │ ├── DiffResult.cs │ └── TypeDiff.cs ├── DiffAssemblies.cs ├── Endjin.Assembly.ChangeDetection.csproj ├── Infrastructure │ ├── BlockingQueue.cs │ ├── BlockingQueueAggregator.cs │ ├── ClrContext.cs │ ├── DirectorySearcherAsync.cs │ ├── ExceptionHelper.cs │ ├── FileEnumerator.cs │ ├── FileNameComparer.cs │ ├── FileQuery.cs │ ├── InternalError.cs │ ├── LastException.cs │ ├── LazyFormat.cs │ ├── Level.cs │ ├── ListExtensions.cs │ ├── MessageTypes.cs │ ├── NullTraceListener.cs │ ├── PtrConverter.cs │ ├── SafeFindHandle.cs │ ├── StaticModule.cs │ ├── TraceCfgParser.cs │ ├── TraceFilter.cs │ ├── TraceFilterMatchAll.cs │ ├── TraceFilterMatchNone.cs │ ├── Tracer.cs │ ├── TracerConfig.cs │ ├── TypeHashes.cs │ ├── WIN32_FIND_DATA.cs │ ├── WorkItemDispatcher.cs │ ├── WorkItemDispatcherData.cs │ └── WorkItemOptions.cs ├── Introspection │ ├── AssemblyLoader.cs │ ├── FieldPrintOptions.cs │ ├── FilterFunctions.cs │ ├── FilterMode.cs │ ├── ISymChkExecutor.cs │ ├── MethodPrintOption.cs │ ├── PdbDownLoader.cs │ ├── PdbInformationReader.cs │ ├── SymChkExecutor.cs │ ├── TypeExtensions.cs │ └── TypeMapper.cs ├── ListDiffer.cs ├── Properties │ └── AssemblyInfo.cs ├── Query │ ├── BaseQuery.cs │ ├── EventComparer.cs │ ├── EventQuery.cs │ ├── FieldComparer.cs │ ├── FieldQuery.cs │ ├── GenericTypeMapper.cs │ ├── Matcher.cs │ ├── MethodComparer.cs │ ├── MethodQuery.cs │ ├── QueryAggregator.cs │ ├── TypeNameComparer.cs │ ├── TypeQuery.cs │ ├── TypeQueryExtensions.cs │ ├── TypeQueryFactory.cs │ ├── TypeQueryMode.cs │ └── UsageQueries │ │ ├── MatchContext.cs │ │ ├── QueryResult.cs │ │ ├── UsageQueryAggregator.cs │ │ ├── UsageVisitor.cs │ │ ├── WhoAccessesField.cs │ │ ├── WhoDerivesFromType.cs │ │ ├── WhoHasFieldOfType.cs │ │ ├── WhoImplementsInterface.cs │ │ ├── WhoInstantiatesType.cs │ │ ├── WhoReferencesAssembly.cs │ │ ├── WhoUsesEvents.cs │ │ ├── WhoUsesMethod.cs │ │ ├── WhoUsesStringConstant.cs │ │ └── WhoUsesType.cs ├── Rules │ ├── BreakingChangeRule.cs │ └── IRule.cs ├── SemVer │ ├── AnalysisResult.cs │ └── SemanticVersionAnalyzer.cs └── packages.config ├── Endjin.SemanticVersioning.TeamCity ├── App.config ├── AssemblyAttributeExtensions.cs ├── AttributeConverters │ ├── AssemblyCompanyAttributeConverter.cs │ ├── AssemblyConfigurationAttributeConverter.cs │ ├── AssemblyCopyrightAttributeConverter.cs │ ├── AssemblyDescriptionAttributeConverter.cs │ ├── AssemblyProductAttributeConverter.cs │ ├── AssemblyTitleAttributeConverter.cs │ ├── AssemblyTrademarkAttributeConverter.cs │ ├── ComVisibleAttributeConverter.cs │ ├── CompilationRelaxationsAttributeConverter.cs │ ├── DebuggableAttributeConverter.cs │ ├── GuidAttributeConverter.cs │ ├── RuntimeCompatibilityAttributeConverter.cs │ └── TargetFrameworkAttributeConverter.cs ├── CodeGenerator.cs ├── CommandLineProcessor.cs ├── CommandOptions.cs ├── Endjin.SemanticVersioning.TeamCity.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── NonBreakingAdditiveChange ├── Class1.cs ├── NonBreakingAdditiveChange.csproj └── Properties │ └── AssemblyInfo.cs ├── NonBreakingAdditiveChange2 ├── Class1.cs ├── NonBreakingAdditiveChange2.csproj └── Properties │ └── AssemblyInfo.cs ├── NuGet.config ├── Original ├── Class1.cs ├── Original.csproj └── Properties │ └── AssemblyInfo.cs └── Original2 ├── Class1.cs ├── Original2.csproj └── Properties └── AssemblyInfo.cs /.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 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 endjin limited 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | All licensing enquiries should be directed in the first instance to licensing@endjin.com -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Endjin.Assembly.ChangeDetection 2 | 3 | This is an experiment to see if it is possibly to build a tool to: 4 | 5 | - automatically detect breaking changes in a .NET assembly 6 | - determine the what the next valid SemanticVersion should be 7 | - use ILMerge to re-write the assembly with the new suggested Semantic Version number. 8 | 9 | For the purpose of automating the generation & versioning of NuGet packages. 10 | 11 | ## Breaking Changes rules: ## 12 | 13 | A breaking change is defined as the modification of any public type (either changing of a signature or the removal of a public type from the assembly). The addition of new types to an assembly is not deemed a breaking change. 14 | 15 | ## Thoughts ## 16 | 17 | Next steps, I would like to turn this tool into a TeamCity Meta-Runner, where the new Semantic Version number is echoed up, via a Service Message, to set the new version number for the branch. 18 | 19 | Ideally I would like the concepts laid out here to become a 1st class feature in TeamCity; to provide much better support for Semantic Versioning, Assembly breaking changes and the ability for the CI server to control the version number of the assemblies. 20 | 21 | 22 | ##Notes## 23 | 24 | To get this running locally, you may need install SpecFlow as Visual Studio Extension. 25 | 26 | ##Comments## 27 | 28 | Please ping me via @[HowardvRooijen](http://twitter.com/HowardvRooijen) 29 | 30 | ## Acknowledgements: ## 31 | 32 | Code from the CodePlex project [APIChange](https://apichange.codeplex.com/) by [Alois Kraus](http://geekswithblogs.net/akraus1/archive/2010/06/03/140207.aspx) have been used. 33 | As have modification made by [@GrahamTheCoder](http://twitter.com/grahamthecoder) who upgraded Mono.Cecil to a newer version 34 | 35 | -------------------------------------------------------------------------------- /Solutions/BreakingChange/BreakingChange.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {282B8835-B9D0-4F6E-B33D-73E9D8BD0D0D} 8 | Library 9 | Properties 10 | Original 11 | Original 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /Solutions/BreakingChange/Class1.cs: -------------------------------------------------------------------------------- 1 | namespace Original 2 | { 3 | public class Class1 4 | { 5 | public void MyTestMethod(string input) 6 | { 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Solutions/BreakingChange/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("BreakingChange")] 9 | [assembly: AssemblyDescription("Assembly with Breaking Change")] 10 | [assembly: AssemblyConfiguration("Release")] 11 | [assembly: AssemblyCompany("endjin")] 12 | [assembly: AssemblyProduct("BreakingChange")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("endjin")] 15 | [assembly: AssemblyCulture("en-gb")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("09b11047-db62-4282-836f-a1ce052bcc20")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Solutions/BreakingChange2/BreakingChange2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {20FE08AE-4A16-439F-AF5F-490AB49FEBFD} 8 | Library 9 | Properties 10 | Original2 11 | Original2 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 54 | -------------------------------------------------------------------------------- /Solutions/BreakingChange2/Class1.cs: -------------------------------------------------------------------------------- 1 | namespace Original2 2 | { 3 | public class Class1 4 | { 5 | public void MyTestMethod(string input) 6 | { 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Solutions/BreakingChange2/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("BreakingChange2")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("BreakingChange2")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("20fe08ae-4a16-439f-af5f-490ab49febfd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/Features/ChangeRules.feature: -------------------------------------------------------------------------------- 1 | Feature: Change Rules 2 | In order to establish what the semantic version of the build should be 3 | As the build process 4 | I want to decide if the current assembly has breaking changes from the previous version 5 | 6 | Scenario: New Assembly has a non breaking additive change does not violate any rules 7 | Given the previous assembly is called "TestData\Original\Original.dll" 8 | Given the new assembly is called "TestData\NonBreakingAdditiveChange\Original.dll" 9 | When I compare the two assemblies and validate the rules 10 | Then I should be told that the rule has not been violated 11 | 12 | Scenario: New Assembly has a breaking change due to a public api being modified does violate rules 13 | Given the previous assembly is called "TestData\Original\Original.dll" 14 | Given the new assembly is called "TestData\BreakingChange\Original.dll" 15 | When I compare the two assemblies and validate the rules 16 | Then I should be told that the rule has been violated -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/Features/DifferenceDetection.feature: -------------------------------------------------------------------------------- 1 | Feature: Difference Detection 2 | In order to establish what the semantic version of the build should be 3 | As the build process 4 | I want to decide if the current assembly has breaking changes from the previous version 5 | 6 | Scenario: New Assembly has a non breaking additive change 7 | Given the previous assembly is called "TestData\Original\Original.dll" 8 | Given the new assembly is called "TestData\NonBreakingAdditiveChange\Original.dll" 9 | When I compare the two assemblies 10 | Then I should be told there is 1 change 11 | And I should be told that the change is 1 method has been added 12 | And I should be told that the change is 0 method has been removed 13 | 14 | Scenario: New Assembly has a breaking change due to a public api being modified 15 | Given the previous assembly is called "TestData\Original\Original.dll" 16 | Given the new assembly is called "TestData\BreakingChange\Original.dll" 17 | When I compare the two assemblies 18 | Then I should be told there is 1 change 19 | And I should be told that the change is 1 method has been added 20 | And I should be told that the change is 1 method has been removed 21 | 22 | Scenario: Two new assemblies have non breaking additive changes 23 | Given the previous assemblies are "TestData\Original\*.dll;TestData\Original2\*.dll" 24 | Given the new assemblies are "TestData\NonBreakingAdditiveChange\*.dll;TestData\NonBreakingAdditiveChange2\*.dll" 25 | When I compare the two sets of assemblies 26 | Then I should be told there are 2 changes 27 | And I should be told that the changes include 2 methods have been added 28 | And I should be told that the changes include 0 methods have been removed 29 | 30 | Scenario: Two new assemblies both have a breaking change due to a public api being modified 31 | Given the previous assemblies are "TestData\Original\Original.dll;TestData\Original2\Original2.dll" 32 | Given the new assemblies are "TestData\BreakingChange\Original.dll;TestData\BreakingChange2\Original2.dll" 33 | When I compare the two sets of assemblies 34 | Then I should be told there are 2 changes 35 | And I should be told that the changes include 2 methods have been added 36 | And I should be told that the changes include 2 methods have been removed -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/Features/SemanticVersioning.feature: -------------------------------------------------------------------------------- 1 | Feature: SemanticVersioning 2 | In order to establish what the semantic version of the build should be 3 | As the build process 4 | I want to decide what the next public build number should be by comparing the current build with the previous build 5 | 6 | Scenario: New Assembly has a non breaking additive change 7 | Given the previous assembly is called "TestData\Original\Original.dll" 8 | Given the new assembly is called "TestData\NonBreakingAdditiveChange\Original.dll" 9 | Given the proposed version number is 1.0.1 10 | When I compare the two assemblies and validate the rules and the version number 11 | Then I should be told that the rule has not been violated 12 | And I should be told that the version number is 1.0.1 13 | 14 | Scenario: New Assembly has a breaking change due to a public api being modified does violate rules 15 | Given the previous assembly is called "TestData\Original\Original.dll" 16 | Given the new assembly is called "TestData\BreakingChange\Original.dll" 17 | Given the proposed version number is 1.0.1 18 | When I compare the two assemblies and validate the rules and the version number 19 | Then I should be told that the rule has been violated 20 | And I should be told that the version number is 2.0.0 -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Endjin.Assembly.ChangeDetection.Specs")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Endjin.Assembly.ChangeDetection.Specs")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a93aa5c3-e033-4463-b9b1-0a0776a81a61")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/Steps/ChangeRulesSteps.cs: -------------------------------------------------------------------------------- 1 | using Endjin.Assembly.ChangeDetection.Infrastructure; 2 | using Endjin.Assembly.ChangeDetection.Rules; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Specs.Steps 5 | { 6 | #region Using Directives 7 | 8 | using System.Collections.Generic; 9 | using Should; 10 | 11 | using TechTalk.SpecFlow; 12 | 13 | #endregion 14 | 15 | [Binding] 16 | public class ChangeRulesSteps 17 | { 18 | [When(@"I compare the two assemblies and validate the rules")] 19 | public void WhenICompareTheTwoAssembliesAndValidateTheRules() 20 | { 21 | var differ = new DiffAssemblies(); 22 | 23 | var previous = new FileQuery(ScenarioContext.Current.Get("PreviousAssembly")); 24 | var newAssembly = new FileQuery(ScenarioContext.Current.Get("NewAssembly")); 25 | 26 | var differences = differ.Execute(new List { previous }, new List { newAssembly }); 27 | var rule = new BreakingChangeRule(); 28 | 29 | var result = rule.Detect(differences); 30 | 31 | ScenarioContext.Current.Set(result, "Results"); 32 | } 33 | 34 | [Then(@"I should be told that the rule has been violated")] 35 | public void ThenIShouldBeToldThatTheRuleHasBeenViolated() 36 | { 37 | var results = ScenarioContext.Current.Get("Results"); 38 | 39 | results.ShouldBeTrue(); 40 | } 41 | 42 | [Then(@"I should be told that the rule has not been violated")] 43 | public void ThenIShouldBeToldThatTheRuleHasNotBeenViolated() 44 | { 45 | var results = ScenarioContext.Current.Get("Results"); 46 | 47 | results.ShouldBeFalse(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/Steps/DifferenceDetectionSteps.cs: -------------------------------------------------------------------------------- 1 | using Endjin.Assembly.ChangeDetection.Diff; 2 | using Endjin.Assembly.ChangeDetection.Infrastructure; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Specs.Steps 5 | { 6 | #region Using Directives 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using Should; 12 | 13 | using TechTalk.SpecFlow; 14 | 15 | #endregion 16 | 17 | [Binding] 18 | public class DifferenceDetectionSteps 19 | { 20 | [Given(@"the previous assembly is called ""(.*)""")] 21 | public void GivenThePreviousAssemblyIsCalled(string previousAssembly) 22 | { 23 | ScenarioContext.Current.Set(previousAssembly, "PreviousAssembly"); 24 | } 25 | 26 | [Given(@"the new assembly is called ""(.*)""")] 27 | public void GivenTheNewAssemblyIsCalled(string newAssembly) 28 | { 29 | ScenarioContext.Current.Set(newAssembly.ResolveBaseDirectory(), "NewAssembly"); 30 | } 31 | 32 | [Given(@"the previous assemblies are ""(.*)""")] 33 | public void GivenThePreviousAssembliesAre(string query) 34 | { 35 | ScenarioContext.Current.Set(query, "PreviousAssembliesQuery"); 36 | } 37 | 38 | [Given(@"the new assemblies are ""(.*)""")] 39 | public void GivenTheNewAssembliesAre(string query) 40 | { 41 | ScenarioContext.Current.Set(query, "NewAssembliesQuery"); 42 | } 43 | 44 | [When(@"I compare the two sets of assemblies")] 45 | public void WhenICompareTheTwoSetsOfAssemblies() 46 | { 47 | var differ = new DiffAssemblies(); 48 | 49 | var previousAssemblies = FileQuery.ParseQueryList(ScenarioContext.Current.Get("PreviousAssembliesQuery")); 50 | var newAssemblies = FileQuery.ParseQueryList(ScenarioContext.Current.Get("NewAssembliesQuery")); 51 | 52 | var differences = differ.Execute(previousAssemblies, newAssemblies); 53 | 54 | ScenarioContext.Current.Set(differences, "Results"); 55 | } 56 | 57 | [When(@"I compare the two assemblies")] 58 | public void WhenICompareTheTwoAssemblies() 59 | { 60 | var differ = new DiffAssemblies(); 61 | 62 | var previous = new FileQuery(ScenarioContext.Current.Get("PreviousAssembly")); 63 | var newAssembly = new FileQuery(ScenarioContext.Current.Get("NewAssembly")); 64 | 65 | var differences = differ.Execute(new List { previous }, new List { newAssembly }); 66 | 67 | ScenarioContext.Current.Set(differences, "Results"); 68 | } 69 | 70 | [Then(@"I should be told there is (.*) change")] 71 | [Then(@"I should be told there are (.*) changes")] 72 | public void ThenIShouldBeToldThereAreChanges(int numberOfChanges) 73 | { 74 | var results = ScenarioContext.Current.Get("Results"); 75 | 76 | results.ChangedTypes.Count.ShouldEqual(numberOfChanges); 77 | } 78 | 79 | [Then(@"I should be told that the change is (.*) method has been added")] 80 | [Then(@"I should be told that the change is (.*) methods have been added")] 81 | public void ThenIShouldBeToldThatTheChangeIsMethodHasBeenAdded(int numberAdded) 82 | { 83 | var results = ScenarioContext.Current.Get("Results"); 84 | results.ChangedTypes.First().Methods.AddedCount.ShouldEqual(numberAdded); 85 | } 86 | 87 | [Then(@"I should be told that the change is (.*) method has been removed")] 88 | [Then(@"I should be told that the change is (.*) methods have been removed")] 89 | public void ThenIShouldBeToldThatTheChangeIsMethodHasBeenRemoved(int numberRemoved) 90 | { 91 | var results = ScenarioContext.Current.Get("Results"); 92 | results.ChangedTypes.First().Methods.RemovedCount.ShouldEqual(numberRemoved); 93 | } 94 | 95 | [Then(@"I should be told that the changes include (.*) method has been added")] 96 | [Then(@"I should be told that the changes include (.*) methods have been added")] 97 | public void ThenIShouldBeToldThatTheChangesIncludeMethodsHaveBeenAdded(int numberAdded) 98 | { 99 | var results = ScenarioContext.Current.Get("Results"); 100 | results.ChangedTypes.Sum(diff => diff.Methods.AddedCount).ShouldEqual(numberAdded); 101 | } 102 | 103 | [Then(@"I should be told that the changes include (.*) method has been removed")] 104 | [Then(@"I should be told that the changes include (.*) methods have been removed")] 105 | public void ThenIShouldBeToldThatTheChangeIncludeMethodHasBeenRemoved(int numberRemoved) 106 | { 107 | var results = ScenarioContext.Current.Get("Results"); 108 | results.ChangedTypes.Sum(diff => diff.Methods.RemovedCount).ShouldEqual(numberRemoved); 109 | } 110 | 111 | } 112 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/Steps/SemanticVersioningSteps.cs: -------------------------------------------------------------------------------- 1 | using Endjin.Assembly.ChangeDetection.SemVer; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Specs.Steps 4 | { 5 | #region Using Directives 6 | 7 | using System.Collections.Generic; 8 | using Should; 9 | 10 | using TechTalk.SpecFlow; 11 | 12 | #endregion 13 | 14 | [Binding] 15 | public class SemanticVersioningSteps 16 | { 17 | [Given(@"the proposed version number is (.*)")] 18 | public void GivenTheProposedVersionNumberIs(string proposedVersionNumberVersion) 19 | { 20 | ScenarioContext.Current.Set(proposedVersionNumberVersion, "ProposedVersionNumber"); 21 | } 22 | 23 | [When(@"I compare the two assemblies and validate the rules and the version number")] 24 | public void WhenICompareTheTwoAssembliesAndValidateTheRulesAndTheVersionNumber() 25 | { 26 | var previous = ScenarioContext.Current.Get("PreviousAssembly"); 27 | var current = ScenarioContext.Current.Get("NewAssembly"); 28 | var proposedVersionNumber = ScenarioContext.Current.Get("ProposedVersionNumber"); 29 | 30 | var semanticVersionAnalyzer = new SemanticVersionAnalyzer(); 31 | 32 | var result = semanticVersionAnalyzer.Analyze(previous, current, proposedVersionNumber); 33 | 34 | ScenarioContext.Current.Set(result, "AnalysisResult"); 35 | ScenarioContext.Current.Set(result.BreakingChangesDetected, "Results"); 36 | } 37 | 38 | [Then(@"I should be told that the version number is (.*)")] 39 | public void ThenIShouldBeToldThatTheVersionNumberIs(string versionNumber) 40 | { 41 | var decidedVersionNumber = ScenarioContext.Current.Get("AnalysisResult"); 42 | 43 | decidedVersionNumber.VersionNumber.ShouldEqual(versionNumber); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection.Specs/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Diff/AssemblyDiffCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using Mono.Cecil; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Diff 6 | { 7 | [DebuggerDisplay("Add {AddedRemovedTypes.AddedCount} Remove {AddedRemovedTypes.RemovedCount} Changed {ChangedTypes.Count}")] 8 | public class AssemblyDiffCollection 9 | { 10 | public DiffCollection AddedRemovedTypes; 11 | 12 | public List ChangedTypes; 13 | 14 | public AssemblyDiffCollection() 15 | { 16 | this.AddedRemovedTypes = new DiffCollection(); 17 | this.ChangedTypes = new List(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Diff/AssemblyDiffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Endjin.Assembly.ChangeDetection.Query; 5 | using Mono.Cecil; 6 | 7 | namespace Endjin.Assembly.ChangeDetection.Diff 8 | { 9 | #region Using Directives 10 | 11 | 12 | 13 | #endregion 14 | 15 | public class AssemblyDiffer 16 | { 17 | private readonly AssemblyDiffCollection myDiff = new AssemblyDiffCollection(); 18 | 19 | private readonly AssemblyDefinition myV1; 20 | 21 | private readonly AssemblyDefinition myV2; 22 | 23 | public AssemblyDiffer(AssemblyDefinition v1, AssemblyDefinition v2) 24 | { 25 | if (v1 == null) 26 | { 27 | throw new ArgumentNullException("v1"); 28 | } 29 | if (v2 == null) 30 | { 31 | throw new ArgumentNullException("v2"); 32 | } 33 | 34 | this.myV1 = v1; 35 | this.myV2 = v2; 36 | } 37 | 38 | /// 39 | /// Initializes a new instance of the class. 40 | /// 41 | /// The assembly file v1. 42 | /// The assembly file v2. 43 | public AssemblyDiffer(string assemblyFileV1, string assemblyFileV2) 44 | { 45 | if (string.IsNullOrEmpty(assemblyFileV1)) 46 | { 47 | throw new ArgumentNullException("assemblyFileV1"); 48 | } 49 | if (string.IsNullOrEmpty(assemblyFileV2)) 50 | { 51 | throw new ArgumentNullException("assemblyFileV2"); 52 | } 53 | 54 | this.myV1 = AssemblyLoader.LoadCecilAssembly(assemblyFileV1); 55 | if (this.myV1 == null) 56 | { 57 | throw new ArgumentException(string.Format("Could not load assemblyV1 {0}", assemblyFileV1)); 58 | } 59 | 60 | this.myV2 = AssemblyLoader.LoadCecilAssembly(assemblyFileV2); 61 | if (this.myV2 == null) 62 | { 63 | throw new ArgumentException(string.Format("Could not load assemblyV2 {0}", assemblyFileV2)); 64 | } 65 | } 66 | 67 | private void OnAddedType(TypeDefinition type) 68 | { 69 | var diff = new DiffResult(type, new DiffOperation(true)); 70 | this.myDiff.AddedRemovedTypes.Add(diff); 71 | } 72 | 73 | private void OnRemovedType(TypeDefinition type) 74 | { 75 | var diff = new DiffResult(type, new DiffOperation(false)); 76 | this.myDiff.AddedRemovedTypes.Add(diff); 77 | } 78 | 79 | public AssemblyDiffCollection GenerateTypeDiff(QueryAggregator queries) 80 | { 81 | if (queries == null || queries.TypeQueries.Count == 0) 82 | { 83 | throw new ArgumentNullException("queries is null or contains no queries"); 84 | } 85 | 86 | var typesV1 = queries.ExeuteAndAggregateTypeQueries(this.myV1); 87 | var typesV2 = queries.ExeuteAndAggregateTypeQueries(this.myV2); 88 | 89 | var differ = new ListDiffer(this.ShallowTypeComapare); 90 | 91 | differ.Diff(typesV1, typesV2, this.OnAddedType, this.OnRemovedType); 92 | 93 | this.DiffTypes(typesV1, typesV2, queries); 94 | 95 | return this.myDiff; 96 | } 97 | 98 | private bool ShallowTypeComapare(TypeDefinition v1, TypeDefinition v2) 99 | { 100 | return v1.FullName == v2.FullName; 101 | } 102 | 103 | private void DiffTypes(List typesV1, List typesV2, QueryAggregator queries) 104 | { 105 | TypeDefinition typeV2; 106 | foreach (var typeV1 in typesV1) 107 | { 108 | typeV2 = this.GetTypeByDefinition(typeV1, typesV2); 109 | if (typeV2 != null) 110 | { 111 | var diffed = TypeDiff.GenerateDiff(typeV1, typeV2, queries); 112 | if (TypeDiff.None != diffed) 113 | { 114 | this.myDiff.ChangedTypes.Add(diffed); 115 | } 116 | } 117 | } 118 | } 119 | 120 | private TypeDefinition GetTypeByDefinition(TypeDefinition search, List types) 121 | { 122 | foreach (var type in types) 123 | { 124 | if (type.IsEqual(search)) 125 | { 126 | return type; 127 | } 128 | } 129 | 130 | return null; 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Diff/DiffCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Diff 5 | { 6 | public class DiffCollection : List> 7 | { 8 | public int AddedCount 9 | { 10 | get 11 | { 12 | var added = 0; 13 | foreach (var obj in this) 14 | { 15 | if (obj.Operation.IsAdded) 16 | { 17 | added++; 18 | } 19 | } 20 | 21 | return added; 22 | } 23 | } 24 | 25 | public int RemovedCount 26 | { 27 | get 28 | { 29 | var removed = 0; 30 | foreach (var obj in this) 31 | { 32 | if (obj.Operation.IsRemoved) 33 | { 34 | removed++; 35 | } 36 | } 37 | 38 | return removed; 39 | } 40 | } 41 | 42 | public IEnumerable> Added 43 | { 44 | get 45 | { 46 | foreach (var obj in this) 47 | { 48 | if (obj.Operation.IsAdded) 49 | { 50 | yield return obj; 51 | } 52 | } 53 | } 54 | } 55 | 56 | public IEnumerable> Removed 57 | { 58 | get 59 | { 60 | foreach (var obj in this) 61 | { 62 | if (obj.Operation.IsRemoved) 63 | { 64 | yield return obj; 65 | } 66 | } 67 | } 68 | } 69 | 70 | public List RemovedList 71 | { 72 | get 73 | { 74 | return (from type in this.Removed select type.ObjectV1).ToList(); 75 | } 76 | } 77 | 78 | public List AddedList 79 | { 80 | get 81 | { 82 | return (from type in this.Added select type.ObjectV1).ToList(); 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Diff/DiffOperation.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.Assembly.ChangeDetection.Diff 2 | { 3 | public class DiffOperation 4 | { 5 | public DiffOperation(bool isAdded) 6 | { 7 | this.IsAdded = isAdded; 8 | } 9 | 10 | public bool IsAdded { get; private set; } 11 | 12 | public bool IsRemoved 13 | { 14 | get 15 | { 16 | return !this.IsAdded; 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Diff/DiffPrinter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Mono.Cecil; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Diff 7 | { 8 | public class DiffPrinter 9 | { 10 | private readonly TextWriter Out; 11 | 12 | /// 13 | /// Print diffs to console 14 | /// 15 | public DiffPrinter() 16 | { 17 | this.Out = Console.Out; 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The output stream to print the change diff. 24 | public DiffPrinter(TextWriter outputStream) 25 | { 26 | if (outputStream == null) 27 | { 28 | throw new ArgumentNullException("outputStream"); 29 | } 30 | 31 | this.Out = outputStream; 32 | } 33 | 34 | internal void Print(AssemblyDiffCollection diff) 35 | { 36 | this.PrintAddedRemovedTypes(diff.AddedRemovedTypes); 37 | 38 | if (diff.ChangedTypes.Count > 0) 39 | { 40 | foreach (var typeChange in diff.ChangedTypes) 41 | { 42 | this.PrintTypeChanges(typeChange); 43 | } 44 | } 45 | } 46 | 47 | private void PrintTypeChanges(TypeDiff typeChange) 48 | { 49 | this.Out.WriteLine("\t" + typeChange.TypeV1.Print()); 50 | if (typeChange.HasChangedBaseType) 51 | { 52 | this.Out.WriteLine("\t\tBase type changed: {0} -> {1}", typeChange.TypeV1.IsNotNull(() => typeChange.TypeV1.BaseType.IsNotNull(() => typeChange.TypeV1.BaseType.FullName)), typeChange.TypeV2.IsNotNull(() => typeChange.TypeV2.BaseType.IsNotNull(() => typeChange.TypeV2.BaseType.FullName))); 53 | } 54 | 55 | if (typeChange.Interfaces.Count > 0) 56 | { 57 | foreach (var addedItf in typeChange.Interfaces.Added) 58 | { 59 | this.Out.WriteLine("\t\t+ interface: {0}", addedItf.ObjectV1.FullName); 60 | } 61 | foreach (var removedItd in typeChange.Interfaces.Removed) 62 | { 63 | this.Out.WriteLine("\t\t- interface: {0}", removedItd.ObjectV1.FullName); 64 | } 65 | } 66 | 67 | foreach (var addedEvent in typeChange.Events.Added) 68 | { 69 | this.Out.WriteLine("\t\t+ {0}", addedEvent.ObjectV1.Print()); 70 | } 71 | 72 | foreach (var remEvent in typeChange.Events.Removed) 73 | { 74 | this.Out.WriteLine("\t\t- {0}", remEvent.ObjectV1.Print()); 75 | } 76 | 77 | foreach (var addedField in typeChange.Fields.Added) 78 | { 79 | this.Out.WriteLine("\t\t+ {0}", addedField.ObjectV1.Print(FieldPrintOptions.All)); 80 | } 81 | 82 | foreach (var remField in typeChange.Fields.Removed) 83 | { 84 | this.Out.WriteLine("\t\t- {0}", remField.ObjectV1.Print(FieldPrintOptions.All)); 85 | } 86 | 87 | foreach (var addedMethod in typeChange.Methods.Added) 88 | { 89 | this.Out.WriteLine("\t\t+ {0}", addedMethod.ObjectV1.Print(MethodPrintOption.Full)); 90 | } 91 | 92 | foreach (var remMethod in typeChange.Methods.Removed) 93 | { 94 | this.Out.WriteLine("\t\t- {0}", remMethod.ObjectV1.Print(MethodPrintOption.Full)); 95 | } 96 | } 97 | 98 | private void PrintAddedRemovedTypes(DiffCollection diffCollection) 99 | { 100 | if (diffCollection.RemovedCount > 0) 101 | { 102 | this.Out.WriteLine("\tRemoved {0} public type/s", diffCollection.RemovedCount); 103 | foreach (var remType in diffCollection.Removed) 104 | { 105 | this.Out.WriteLine("\t\t- {0}", remType.ObjectV1.Print()); 106 | } 107 | } 108 | 109 | if (diffCollection.AddedCount > 0) 110 | { 111 | this.Out.WriteLine("\tAdded {0} public type/s", diffCollection.AddedCount); 112 | foreach (var addedType in diffCollection.Added) 113 | { 114 | this.Out.WriteLine("\t\t+ {0}", addedType.ObjectV1.Print()); 115 | } 116 | } 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Diff/DiffResult.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.Assembly.ChangeDetection.Diff 2 | { 3 | public class DiffResult 4 | { 5 | public DiffResult(T v1, DiffOperation diffType) 6 | { 7 | this.ObjectV1 = v1; 8 | this.Operation = diffType; 9 | } 10 | 11 | public DiffOperation Operation { get; private set; } 12 | 13 | public T ObjectV1 { get; private set; } 14 | 15 | public override string ToString() 16 | { 17 | return string.Format("{0}, {1}", this.ObjectV1, this.Operation.IsAdded ? "added" : "removed"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/DiffAssemblies.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Endjin.Assembly.ChangeDetection.Diff; 5 | using Endjin.Assembly.ChangeDetection.Infrastructure; 6 | using Endjin.Assembly.ChangeDetection.Introspection; 7 | using Endjin.Assembly.ChangeDetection.Query; 8 | 9 | namespace Endjin.Assembly.ChangeDetection 10 | { 11 | public class DiffAssemblies 12 | { 13 | private static readonly TypeHashes MyType = new TypeHashes(typeof(DiffAssemblies)); 14 | 15 | //protected CommandData myParsedArgs; 16 | 17 | protected void Validate() 18 | { 19 | /*base.Detect(); 20 | 21 | ValidateFileQuery(myParsedArgs.NewFiles, 22 | "-new is missing.", 23 | "Invalid directory in -new {0} query.", 24 | "The -new query {0} did not match any files."); 25 | 26 | ValidateFileQuery(myParsedArgs.OldFiles, 27 | "-old is missing.", 28 | "Not existing directory in -old {0} query.", 29 | "The -old query {0} did not match any files."); 30 | 31 | if (myParsedArgs.OutputToExcel) 32 | { 33 | AddErrorMessage("Excel output is not supported by this comand"); 34 | SetInvalid(); 35 | }*/ 36 | } 37 | 38 | public AssemblyDiffCollection Execute(List oldFiles, List newFiles) 39 | { 40 | using (var t = new Tracer(MyType, "Execute")) 41 | { 42 | //var removedTypes = 0; 43 | //var changedTypes = 0; 44 | 45 | var removedFiles = oldFiles.GetNotExistingFilesInOtherQuery(newFiles); 46 | 47 | if (removedFiles.Count > 0) 48 | { 49 | foreach (var str in removedFiles) 50 | { 51 | Console.WriteLine("\t{0}", Path.GetFileName(str)); 52 | } 53 | } 54 | 55 | var oldFilesQuery = new HashSet(oldFiles.GetFiles(), new FileNameComparer()); 56 | var newFilesQuery = new HashSet(newFiles.GetFiles(), new FileNameComparer()); 57 | 58 | // Get files which are present in one set and the other 59 | oldFilesQuery.IntersectWith(newFilesQuery); 60 | //DiffPrinter printer = new DiffPrinter(Out); 61 | 62 | var result = new AssemblyDiffCollection(); 63 | 64 | foreach (var fileName1 in oldFilesQuery) 65 | { 66 | if (fileName1.EndsWith(".XmlSerializers.dll", StringComparison.OrdinalIgnoreCase)) 67 | { 68 | t.Info("Ignore xml serializer dll {0}", fileName1); 69 | continue; 70 | } 71 | 72 | var fileName2 = newFiles.GetMatchingFileByName(fileName1); 73 | 74 | var assemblyV1 = AssemblyLoader.LoadCecilAssembly(fileName1); 75 | var assemblyV2 = AssemblyLoader.LoadCecilAssembly(fileName2); 76 | 77 | if (assemblyV1 != null && assemblyV2 != null) 78 | { 79 | var differ = new AssemblyDiffer(assemblyV1, assemblyV2); 80 | var differences = differ.GenerateTypeDiff(QueryAggregator.PublicApiQueries); 81 | result.AddedRemovedTypes.AddRange(differences.AddedRemovedTypes); 82 | result.ChangedTypes.AddRange(differences.ChangedTypes); 83 | 84 | //removedTypes += differences.AddedRemovedTypes.RemovedCount; 85 | //changedTypes += differences.ChangedTypes.Count; 86 | 87 | /*if (diff.AddedRemovedTypes.Count > 0 || diff.ChangedTypes.Count > 0) 88 | { 89 | // Out.WriteLine("{0} has {1} changes", Path.GetFileName(fileName1), diff.AddedRemovedTypes.Count + diff.ChangedTypes.Sum(type => type.Events.Count + type.Fields.Count + type.Interfaces.Count + type.Methods.Count)); 90 | 91 | // printer.Print(diff); 92 | // Out.WriteLine("From {0} assemblies were {1} types removed and {2} changed.", myParsedArgs.Queries1.GetFiles().Count(), removedTypes, changedTypes); 93 | * 94 | }*/ 95 | } 96 | } 97 | 98 | return result; 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/BlockingQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 7 | { 8 | /// 9 | /// A blocking queue which supports end markers to signal that no more work is left by inserting 10 | /// a null reference. This constrains the queue to reference types only. 11 | /// 12 | /// 13 | public class BlockingQueue : IEnumerable, IEnumerable, IDisposable 14 | where T : class 15 | { 16 | private readonly ManualResetEvent myEmptyEvent = new ManualResetEvent(false); 17 | 18 | /// 19 | /// The queue used to store the elements 20 | /// 21 | private readonly Queue myQueue = new Queue(); 22 | 23 | private bool myAllItemsProcessed; 24 | 25 | #region IDisposable Members 26 | 27 | /// 28 | /// Closes the EmptyEvent WaitHandle. 29 | /// 30 | public void Dispose() 31 | { 32 | this.myEmptyEvent.Close(); 33 | } 34 | 35 | #endregion 36 | 37 | /// 38 | /// Returns an IEnumerator of Type T for this queue 39 | /// 40 | /// 41 | IEnumerator IEnumerable.GetEnumerator() 42 | { 43 | while (true) 44 | { 45 | var item = this.Dequeue(); 46 | if (item == null) 47 | { 48 | break; 49 | } 50 | yield return item; 51 | } 52 | } 53 | 54 | /// 55 | /// Returns a untyped IEnumerator for this queue 56 | /// 57 | /// 58 | IEnumerator IEnumerable.GetEnumerator() 59 | { 60 | return ((IEnumerable)this).GetEnumerator(); 61 | } 62 | 63 | /// 64 | /// Deques an element from the queue and returns it. 65 | /// If the queue is empty the thread will block. If the queue is stopped it will immedieately 66 | /// return with null. 67 | /// 68 | /// An object of type T 69 | public T Dequeue() 70 | { 71 | if (this.myAllItemsProcessed) 72 | { 73 | return null; 74 | } 75 | 76 | lock (this.myQueue) 77 | { 78 | while (this.myQueue.Count == 0) 79 | { 80 | if (!Monitor.Wait(this.myQueue, 45)) 81 | { 82 | // dispatch any work which is not done yet 83 | if (this.myQueue.Count > 0) 84 | { 85 | continue; 86 | } 87 | } 88 | 89 | // finito 90 | if (this.myAllItemsProcessed) 91 | { 92 | return null; 93 | } 94 | } 95 | 96 | var result = this.myQueue.Dequeue(); 97 | if (result == null) 98 | { 99 | this.myAllItemsProcessed = true; 100 | this.myEmptyEvent.Set(); 101 | } 102 | return result; 103 | } 104 | } 105 | 106 | /// 107 | /// Releases the waiters by enqueuing a null reference which causes all waiters to be released. 108 | /// The will then get a null reference as queued element to signal that they should terminate. 109 | /// 110 | public void ReleaseWaiters() 111 | { 112 | this.Enqueue(null); 113 | } 114 | 115 | /// 116 | /// Waits the until empty. This does not mean that all items are already process. Only that 117 | /// the queue contains no more pending work. 118 | /// 119 | public void WaitUntilEmpty() 120 | { 121 | this.myEmptyEvent.WaitOne(); 122 | } 123 | 124 | /// 125 | /// Adds an element of type T to the queue. 126 | /// The consumer thread is notified (if waiting) 127 | /// 128 | /// An object of type T 129 | public void Enqueue(T data_in) 130 | { 131 | lock (this.myQueue) 132 | { 133 | this.myQueue.Enqueue(data_in); 134 | Monitor.PulseAll(this.myQueue); 135 | } 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/BlockingQueueAggregator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 5 | { 6 | /// 7 | /// Aggregate one or a list of queues for processing with WorkItemDispatcher. The queues 8 | /// are processed in the order of addition. Only when the first queue is empty the next 9 | /// queue is used until no more queues are left. 10 | /// 11 | /// 12 | public class BlockingQueueAggregator 13 | where T : class 14 | { 15 | private readonly Queue> myQueues = new Queue>(); 16 | 17 | private BlockingQueue myCurrent; 18 | 19 | public BlockingQueueAggregator(BlockingQueue queue) 20 | { 21 | if (queue == null) 22 | { 23 | throw new ArgumentNullException("queue"); 24 | } 25 | 26 | this.myQueues.Enqueue(queue); 27 | } 28 | 29 | public BlockingQueueAggregator(IEnumerable> queues) 30 | { 31 | if (queues == null) 32 | { 33 | throw new ArgumentNullException("queues"); 34 | } 35 | 36 | foreach (var queue in queues) 37 | { 38 | this.myQueues.Enqueue(queue); 39 | } 40 | } 41 | 42 | internal T Dequeue() 43 | { 44 | T lret = null; 45 | 46 | lock (this.myQueues) 47 | { 48 | TryNextQueue: 49 | if (this.myCurrent == null && this.myQueues.Count > 0) 50 | { 51 | this.myCurrent = this.myQueues.Dequeue(); 52 | } 53 | 54 | // No more queues available 55 | if (this.myCurrent == null) 56 | { 57 | return lret; 58 | } 59 | 60 | // get next element or null if no more elements are in the queue 61 | lret = this.myCurrent.Dequeue(); 62 | if (lret == null) 63 | { 64 | this.myCurrent = null; 65 | goto TryNextQueue; 66 | } 67 | } 68 | 69 | return lret; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/ClrContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 4 | { 5 | /// 6 | /// There are currently 4 combinations possible but I want to 7 | /// stay extensible since some .NET Framework patch might need 8 | /// additional treatement. 9 | /// 10 | [Flags] 11 | internal enum ClrContext 12 | { 13 | None, 14 | 15 | Is32Bit = 1, 16 | 17 | Is64Bit = 2, 18 | 19 | IsNet2 = 4, 20 | 21 | IsNet4 = 8 22 | } 23 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/FileNameComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 6 | { 7 | internal class FileNameComparer : IEqualityComparer 8 | { 9 | #region IEqualityComparer Members 10 | 11 | public bool Equals(string x, string y) 12 | { 13 | return string.Compare(Path.GetFileName(x), Path.GetFileName(y), StringComparison.OrdinalIgnoreCase) == 0; 14 | } 15 | 16 | public int GetHashCode(string obj) 17 | { 18 | return Path.GetFileName(obj).ToLower().GetHashCode(); 19 | } 20 | 21 | #endregion 22 | } 23 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/InternalError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 5 | { 6 | internal static class InternalError 7 | { 8 | private static readonly string Help = "Tracing can be enabled via the environment variable " + TracerConfig.TraceEnvVarName + Environment.NewLine + "Format: ; ; ; ..." + Environment.NewLine + " Can be: Default, File, Debugoutput or Console." + Environment.NewLine + " Default Write traces to configured trace listeners with the source name " + TracerConfig.TraceEnvVarName + " read from App.config" + Environment.NewLine + " File [FileName] Write traces to given file name. The AppDomain name and process id are prepended to the file name to make it unique. If none given a trace file where the executable is located is created" + Environment.NewLine + " DebugOutput Write to windows kernel via OutputDebugString which can be best viewed with dbgview from SysInternals" + Environment.NewLine + " Console Write to stdout" + Environment.NewLine + " <[!]TypeFilter> It is basically the full qualified type name (case insensitive)." + Environment.NewLine + " If the TypeFilter begins with a ! character it is treated as exclusion filter." + Environment.NewLine + " Example: ApiChange.Infrastructure.AggregateException or * or ApiChange.* partial name matches like Api* are NOT supported" + Environment.NewLine + " " + Environment.NewLine + " Enable a specific trace level and/or severity filter" + Environment.NewLine + " Several filters can be combined with the + sign." + Environment.NewLine + " Allowed trace Levels are: Level*, Level1, Level2, ... Level5, LevelDispose. Shortcuts are l*, l1, ... l5, ldispose." + Environment.NewLine + " Severity Filters are: All, InOut, Info, I, Information, Warning, Warn, W, Error, E, Exception, Ex" + Environment.NewLine + " Example: Level1+InOut+Info" + Environment.NewLine + " When no severity and or trace level is specified all levels/severities are enabled." + Environment.NewLine + " Enable full tracing (all severities and all levels) to debugoutput for all types except the ones which reside in the ApiChange.Infrastructure namespace" + Environment.NewLine + " " + TracerConfig.TraceEnvVarName + "=debugoutput; ApiChange.* all;!ApiChange.Infrastructure.* all" + Environment.NewLine + " Enable file traces with Level1 for all types except the ones beneath the ApiChange.Infrastructure namespace" + Environment.NewLine + " " + TracerConfig.TraceEnvVarName + "=file; * Level1;!ApiChange.Infrastructure.* all" + Environment.NewLine + " Trace all exceptions in the method where it is first encountered" + Environment.NewLine + " " + TracerConfig.TraceEnvVarName + "=file c:\\temp\\exceptions.txt; * Exception"; 9 | 10 | private static readonly DefaultTraceListener myOutDevice = new DefaultTraceListener(); 11 | 12 | internal static void Print(string message) 13 | { 14 | myOutDevice.WriteLine(message); 15 | Console.WriteLine(message); 16 | } 17 | 18 | internal static void Print(string fmt, params object[] args) 19 | { 20 | Print(string.Format(fmt, args)); 21 | } 22 | 23 | internal static void Print(Exception ex, string fmt, params object[] args) 24 | { 25 | Print(string.Format("{0}{1}{2}", string.Format(fmt, args), Environment.NewLine, ex)); 26 | } 27 | 28 | internal static void PrintHelp() 29 | { 30 | Print(Help); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/LastException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 7 | { 8 | internal class LastException 9 | { 10 | private static FieldInfo myThreadPointerFieldInfo; 11 | 12 | private static int myThreadOffset = -1; 13 | 14 | private readonly PtrConverter myConverter = new PtrConverter(); 15 | 16 | public LastException() 17 | { 18 | if (myThreadPointerFieldInfo == null) 19 | { 20 | // Dont read the don´t or you will get scared. 21 | // I prefer to read this more like: If you dont know the rules you will 22 | // get biten by the next .NET Framework update 23 | myThreadPointerFieldInfo = typeof(Thread).GetField("DONT_USE_InternalThread", BindingFlags.Instance | BindingFlags.NonPublic); 24 | } 25 | 26 | if (myThreadOffset == -1) 27 | { 28 | var context = ClrContext.None; 29 | context |= (IntPtr.Size == 8) ? ClrContext.Is64Bit : ClrContext.Is32Bit; 30 | context |= (Environment.Version.Major == 2) ? ClrContext.IsNet2 : ClrContext.None; 31 | context |= (Environment.Version.Major == 4) ? ClrContext.IsNet4 : ClrContext.None; 32 | 33 | switch (context) 34 | { 35 | case ClrContext.Is32Bit | ClrContext.IsNet2: 36 | myThreadOffset = 0x180; 37 | break; 38 | case ClrContext.Is32Bit | ClrContext.IsNet4: 39 | myThreadOffset = 0x188; 40 | break; 41 | case ClrContext.Is64Bit | ClrContext.IsNet2: 42 | myThreadOffset = 0x240; 43 | break; 44 | case ClrContext.Is64Bit | ClrContext.IsNet4: 45 | myThreadOffset = 0x250; 46 | break; 47 | 48 | default: // ups who did install .NET 5? 49 | myThreadOffset = -1; 50 | break; 51 | } 52 | } 53 | } 54 | 55 | /// 56 | /// Get from the current thread the last thrown exception object. 57 | /// 58 | /// null when none exists or the exception instance. 59 | public Exception GetLastException() 60 | { 61 | Exception lret = null; 62 | if (myThreadPointerFieldInfo != null) 63 | { 64 | var pInternalThread = (IntPtr)myThreadPointerFieldInfo.GetValue(Thread.CurrentThread); 65 | if (pInternalThread != IntPtr.Zero && myThreadOffset != -1) 66 | { 67 | var ppEx = Marshal.ReadIntPtr(pInternalThread, myThreadOffset); 68 | if (ppEx != IntPtr.Zero) 69 | { 70 | var pEx = Marshal.ReadIntPtr(ppEx); 71 | if (pEx != IntPtr.Zero) 72 | { 73 | lret = this.myConverter.ConvertFromIntPtr(pEx); 74 | } 75 | } 76 | } 77 | } 78 | return lret; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/LazyFormat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 4 | { 5 | /// 6 | /// Support lazy formatting of strings. This is useful to delay the cration of 7 | /// trace messages until the trace is enabled. 8 | /// 9 | public class LazyFormat 10 | { 11 | private readonly Func myFormatMethod; 12 | 13 | /// 14 | /// Supply the method which will generate a string with captured variables of your enclosing method. 15 | /// 16 | /// 17 | public LazyFormat(Func formatMethod) 18 | { 19 | if (formatMethod == null) 20 | { 21 | throw new ArgumentNullException("formatMethod"); 22 | } 23 | this.myFormatMethod = formatMethod; 24 | } 25 | 26 | /// 27 | /// Return the string generated by the formatMethod. 28 | /// 29 | /// 30 | /// A that represents the current . 31 | /// 32 | public override string ToString() 33 | { 34 | return this.myFormatMethod(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/Level.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 4 | { 5 | /// 6 | /// Trace levels where 1 is the one with only high level traces whereas 5 is the level with 7 | /// highest details. Trace levels can be combined together so you can look for all high level messages only 8 | /// and all errors at all levels. 9 | /// 10 | [Flags] 11 | public enum Level 12 | { 13 | None = 0, 14 | 15 | L1 = 1, 16 | 17 | L2 = 2, 18 | 19 | L3 = 4, 20 | 21 | L4 = 8, 22 | 23 | L5 = 16, 24 | 25 | Dispose = 32, 26 | 27 | All = L1 | L2 | L3 | L4 | L5 | Dispose 28 | } 29 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 6 | { 7 | public static class ListExtensions 8 | { 9 | public static string GetSearchDirs(this List queries) 10 | { 11 | if (queries == null) 12 | { 13 | throw new ArgumentNullException("queries was null."); 14 | } 15 | 16 | var ret = ""; 17 | foreach (var q in queries) 18 | { 19 | ret += q.SearchDir + ";"; 20 | } 21 | 22 | return ret.TrimEnd(';'); 23 | } 24 | 25 | public static string GetQueries(this List queries) 26 | { 27 | if (queries == null) 28 | { 29 | throw new ArgumentNullException("queries was null."); 30 | } 31 | 32 | var lret = ""; 33 | foreach (var q in queries) 34 | { 35 | lret += q.Query + " "; 36 | } 37 | return lret.Trim(); 38 | } 39 | 40 | public static IEnumerable GetFiles(this List queries) 41 | { 42 | if (queries == null) 43 | { 44 | throw new ArgumentNullException("queries was null."); 45 | } 46 | 47 | foreach (var q in queries) 48 | { 49 | q.BeginSearch(); 50 | } 51 | 52 | foreach (var q in queries) 53 | { 54 | foreach (var file in q.EnumerateFiles) 55 | { 56 | yield return file; 57 | } 58 | } 59 | } 60 | 61 | public static bool HasMatches(this List queries) 62 | { 63 | if (queries == null) 64 | { 65 | throw new ArgumentNullException("queries was null."); 66 | } 67 | 68 | var lret = false; 69 | foreach (var q in queries) 70 | { 71 | lret = q.HasMatches; 72 | if (lret) 73 | { 74 | break; 75 | } 76 | } 77 | 78 | return lret; 79 | } 80 | 81 | public static string GetMatchingFileByName(this List queries, string fileName) 82 | { 83 | if (queries == null) 84 | { 85 | throw new ArgumentNullException("queries was null."); 86 | } 87 | 88 | if (string.IsNullOrEmpty(fileName)) 89 | { 90 | throw new ArgumentException("fileName to filter for was null or empty"); 91 | } 92 | 93 | string match = null; 94 | foreach (var q in queries) 95 | { 96 | match = q.GetMatchingFileByName(fileName); 97 | if (match != null) 98 | { 99 | break; 100 | } 101 | } 102 | 103 | return match; 104 | } 105 | 106 | public static List GetNotExistingFilesInOtherQuery(this List queries, List otherQueries) 107 | { 108 | if (queries == null) 109 | { 110 | throw new ArgumentNullException("queries"); 111 | } 112 | 113 | if (otherQueries == null) 114 | { 115 | throw new ArgumentNullException("otherQueries"); 116 | } 117 | 118 | var query1 = new HashSet(queries.GetFiles(), new FileNameComparer()); 119 | var query2 = new HashSet(otherQueries.GetFiles(), new FileNameComparer()); 120 | 121 | var removedFiles = new HashSet(query1, new FileNameComparer()); 122 | removedFiles.ExceptWith(query2); 123 | 124 | return removedFiles.ToList(); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/MessageTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 4 | { 5 | /// 6 | /// Severity of trace messages 7 | /// 8 | [Flags] 9 | public enum MessageTypes 10 | { 11 | None = 0, 12 | 13 | Info = 1, 14 | 15 | Instrument = 2, 16 | 17 | Warning = 4, 18 | 19 | Error = 8, 20 | 21 | InOut = 16, 22 | 23 | Exception = 32, 24 | 25 | All = InOut | Info | Instrument | Warning | Error | Exception 26 | } 27 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/NullTraceListener.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 4 | { 5 | internal class NullTraceListener : TraceListener 6 | { 7 | public override void Write(string message) 8 | { 9 | } 10 | 11 | public override void WriteLine(object o) 12 | { 13 | } 14 | 15 | public override void WriteLine(string message) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/PtrConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection.Emit; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 5 | { 6 | /// 7 | /// This class can convert any pointer to a managed object into a true object reference back. 8 | /// 9 | /// 10 | public class PtrConverter 11 | { 12 | private static Void2ObjectConverter myConverter; 13 | 14 | // The type initializer is run every time the converter is instantiated with a different 15 | // generic argument. 16 | static PtrConverter() 17 | { 18 | GenerateDynamicMethod(); 19 | } 20 | 21 | private static void GenerateDynamicMethod() 22 | { 23 | if (myConverter == null) 24 | { 25 | var method = new DynamicMethod("ConvertPtrToObjReference", typeof(T), new[] { typeof(IntPtr) }, StaticModule.UnsafeModule); 26 | var gen = method.GetILGenerator(); 27 | // Load first argument 28 | gen.Emit(OpCodes.Ldarg_0); 29 | // return it directly. The Clr will take care of the cast! 30 | // this construct is unverifiable so we need to plug this into an assembly with 31 | // IL Verification disabled 32 | gen.Emit(OpCodes.Ret); 33 | myConverter = (Void2ObjectConverter)method.CreateDelegate(typeof(Void2ObjectConverter)); 34 | } 35 | } 36 | 37 | /// 38 | /// Convert a pointer to a managed object back to the original object reference 39 | /// 40 | /// Pointer to managed object 41 | /// Object reference 42 | /// 43 | /// When the pointer does not point to valid CLR object. This can happen when the GC decides to move object references 44 | /// to new memory locations. 45 | /// Beware this possibility exists all the time (although the probability should be very low)! 46 | /// 47 | public T ConvertFromIntPtr(IntPtr pObj) 48 | { 49 | return myConverter(pObj); 50 | } 51 | 52 | private delegate U Void2ObjectConverter(IntPtr pManagedObject); 53 | } 54 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/SafeFindHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ConstrainedExecution; 3 | using System.Runtime.InteropServices; 4 | using System.Security.Permissions; 5 | using Microsoft.Win32.SafeHandles; 6 | 7 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 8 | { 9 | /// 10 | /// Wraps a FindFirstFile handle. 11 | /// 12 | internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] 18 | internal SafeFindHandle() : base(true) 19 | { 20 | } 21 | 22 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 23 | [DllImport("kernel32.dll")] 24 | private static extern bool FindClose(IntPtr handle); 25 | 26 | /// 27 | /// When overridden in a derived class, executes the code required to free the handle. 28 | /// 29 | /// 30 | /// true if the handle is released successfully; otherwise, in the 31 | /// event of a catastrophic failure, false. In this case, it 32 | /// generates a releaseHandleFailed MDA Managed Debugging Assistant. 33 | /// 34 | protected override bool ReleaseHandle() 35 | { 36 | return FindClose(this.handle); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/StaticModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Reflection.Emit; 4 | using System.Security.Permissions; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 7 | { 8 | internal class StaticModule 9 | { 10 | private const string ModuleAssemblyName = "AloisDynamicCaster"; 11 | 12 | private static Module myUnsafeModule; 13 | 14 | public static Module UnsafeModule 15 | { 16 | get 17 | { 18 | if (myUnsafeModule == null) 19 | { 20 | var assemblyName = new AssemblyName(ModuleAssemblyName); 21 | var aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); 22 | var mBuilder = aBuilder.DefineDynamicModule(ModuleAssemblyName); 23 | // set SkipVerification=true on our assembly to prevent VerificationExceptions which warn 24 | // about unsafe things but we want to do unsafe things after all. 25 | var secAttrib = typeof(SecurityPermissionAttribute); 26 | var secCtor = secAttrib.GetConstructor(new[] { typeof(SecurityAction) }); 27 | var attribBuilder = new CustomAttributeBuilder(secCtor, new object[] { SecurityAction.Assert }, new[] { secAttrib.GetProperty("SkipVerification", BindingFlags.Instance | BindingFlags.Public) }, new object[] { true }); 28 | 29 | aBuilder.SetCustomAttribute(attribBuilder); 30 | var tb = mBuilder.DefineType("MyDynamicType", TypeAttributes.Public); 31 | myUnsafeModule = tb.CreateType().Module; 32 | } 33 | 34 | return myUnsafeModule; 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/TraceFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 5 | { 6 | internal class TraceFilter 7 | { 8 | private const int MATCHANY = -1; // Hash value that marks a * 9 | 10 | private readonly int[] myFilterHashes; 11 | 12 | private readonly Level myLevelFilter; 13 | 14 | private readonly MessageTypes myMsgTypeFilter = MessageTypes.None; 15 | 16 | private string myFilter; 17 | 18 | internal TraceFilter Next; 19 | 20 | protected TraceFilter() 21 | { 22 | } 23 | 24 | public TraceFilter(string typeFilter, MessageTypes msgTypeFilter, Level levelFilter, TraceFilter next) 25 | { 26 | if (string.IsNullOrEmpty(typeFilter)) 27 | { 28 | throw new ArgumentException("typeFilter was null or empty"); 29 | } 30 | 31 | this.myFilter = typeFilter; 32 | this.Next = next; 33 | this.myMsgTypeFilter = msgTypeFilter; 34 | this.myLevelFilter = levelFilter; 35 | 36 | var parts = typeFilter.Trim().ToLower().Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); 37 | this.myFilterHashes = new int[parts.Length]; 38 | Debug.Assert(parts.Length > 0, "Type filter parts should be > 0"); 39 | for (var i = 0; i < parts.Length; i++) 40 | { 41 | if (parts[i] == "*") 42 | { 43 | this.myFilterHashes[i] = MATCHANY; 44 | } 45 | else 46 | { 47 | this.myFilterHashes[i] = parts[i].GetHashCode(); 48 | } 49 | } 50 | } 51 | 52 | public virtual bool IsMatch(TypeHashes type, MessageTypes msgTypeFilter, Level level) 53 | { 54 | var lret = ((level & this.myLevelFilter) != Level.None); 55 | 56 | if (lret) 57 | { 58 | var areSameSize = (this.myFilterHashes.Length == type.myTypeHashes.Length); 59 | 60 | for (var i = 0; i < this.myFilterHashes.Length; i++) 61 | { 62 | if (this.myFilterHashes[i] == MATCHANY) 63 | { 64 | break; 65 | } 66 | 67 | if (i < type.myTypeHashes.Length) 68 | { 69 | // The current filter does not match exit 70 | // otherwise we compare the next round. 71 | if (this.myFilterHashes[i] != type.myTypeHashes[i]) 72 | { 73 | lret = false; 74 | break; 75 | } 76 | 77 | // We are still here when the last arry item matches 78 | // This is a full match 79 | if (i == this.myFilterHashes.Length - 1 && areSameSize) 80 | { 81 | break; 82 | } 83 | } 84 | else // the filter string is longer than the domain. That can never match 85 | { 86 | lret = false; 87 | break; 88 | } 89 | } 90 | } 91 | 92 | if (lret) 93 | { 94 | lret = (msgTypeFilter & this.myMsgTypeFilter) != MessageTypes.None; 95 | } 96 | 97 | // If no match try next filter 98 | if (this.Next != null && lret == false) 99 | { 100 | lret = this.Next.IsMatch(type, msgTypeFilter, level); 101 | } 102 | 103 | return lret; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/TraceFilterMatchAll.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 2 | { 3 | internal class TraceFilterMatchAll : TraceFilter 4 | { 5 | public override bool IsMatch(TypeHashes type, MessageTypes msgTypeFilter, Level level) 6 | { 7 | return true; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/TraceFilterMatchNone.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 2 | { 3 | class TraceFilterMatchNone : TraceFilter 4 | { 5 | public TraceFilterMatchNone() 6 | { 7 | } 8 | 9 | public override bool IsMatch(TypeHashes type, MessageTypes msgTypeFilter, Level level) 10 | { 11 | return false; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/TypeHashes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 5 | { 6 | /// 7 | /// Create a static instance of each class where you want to use tracing. 8 | /// It does basically encapsulate the typename and enables fast trace filters. 9 | /// 10 | public class TypeHashes 11 | { 12 | private static readonly char[] mySep = { '.' }; 13 | 14 | internal int[] myTypeHashes; 15 | 16 | /// 17 | /// Initializes a new instance of the TypeHandle class. 18 | /// 19 | /// Name of the type. 20 | public TypeHashes(string typeName) 21 | { 22 | if (string.IsNullOrEmpty(typeName)) 23 | { 24 | throw new ArgumentException("typeName"); 25 | } 26 | 27 | this.FullQualifiedTypeName = typeName; 28 | 29 | // Generate from the full qualified type name substring for each part between the . characters. 30 | // Each substring is then hashed so we can later compare not strings but integer arrays which are super 31 | // fast! Since this is done only once for a type we can afford doing a little more work here and spare 32 | // huge amount of comparison time later. 33 | // If by a rare incident the hash values would collide with another named type we would have enabled 34 | // tracing by accident for one more type than intended. 35 | var hashes = new List(); 36 | foreach (var substr in this.FullQualifiedTypeName.ToLower().Split(mySep)) 37 | { 38 | hashes.Add(substr.GetHashCode()); 39 | } 40 | this.myTypeHashes = hashes.ToArray(); 41 | } 42 | 43 | /// 44 | /// Create a TypeHandle which is used by the Tracer class. 45 | /// 46 | /// Type of your enclosing class. 47 | public TypeHashes(Type t) : this(CheckInput(t)) 48 | { 49 | } 50 | 51 | internal string FullQualifiedTypeName { get; private set; } 52 | 53 | private static string CheckInput(Type t) 54 | { 55 | if (t == null) 56 | { 57 | throw new ArgumentNullException("Type"); 58 | } 59 | 60 | return string.Join(".", t.Namespace, t.Name); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/WIN32_FIND_DATA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 6 | { 7 | /// 8 | /// Contains information about the file that is found 9 | /// by the FindFirstFile or FindNextFile functions. 10 | /// 11 | [Serializable] 12 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 13 | [BestFitMapping(false)] 14 | internal class WIN32_FIND_DATA 15 | { 16 | public FileAttributes dwFileAttributes; 17 | 18 | public uint ftCreationTime_dwLowDateTime; 19 | 20 | public uint ftCreationTime_dwHighDateTime; 21 | 22 | public uint ftLastAccessTime_dwLowDateTime; 23 | 24 | public uint ftLastAccessTime_dwHighDateTime; 25 | 26 | public uint ftLastWriteTime_dwLowDateTime; 27 | 28 | public uint ftLastWriteTime_dwHighDateTime; 29 | 30 | public uint nFileSizeHigh; 31 | 32 | public uint nFileSizeLow; 33 | 34 | public int dwReserved0; 35 | 36 | public int dwReserved1; 37 | 38 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] 39 | public string cFileName; 40 | 41 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] 42 | public string cAlternateFileName; 43 | 44 | /// 45 | /// Returns a that represents the current . 46 | /// 47 | /// 48 | /// A that represents the current . 49 | /// 50 | public override string ToString() 51 | { 52 | return "File name=" + this.cFileName; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/WorkItemDispatcherData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 4 | { 5 | /// 6 | /// Configures the WorkItemDispatcher. 7 | /// 8 | /// 9 | public class WorkItemDispatcherData 10 | where T : class 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public WorkItemDispatcherData() 16 | { 17 | this.Width = 1; 18 | } 19 | 20 | /// 21 | /// Dispatcher Name 22 | /// 23 | public string Name { get; set; } 24 | 25 | /// 26 | /// Number of concurrent threads running which are fetching work from the InputDataList and processing it. 27 | /// 28 | public int Width { get; set; } 29 | 30 | /// 31 | /// Delegate which is called when all work has been processed 32 | /// 33 | public Action OnCompleted { get; set; } 34 | 35 | /// 36 | /// Delegate which is called to process the actual work 37 | /// 38 | public Action Processor { get; set; } 39 | 40 | /// 41 | /// Error Handling options 42 | /// 43 | public WorkItemOptions Options { get; set; } 44 | 45 | /// 46 | /// Adds a queue to the InputDataList. The queue can be full or be filled later when work is available. 47 | /// 48 | public BlockingQueue InputData 49 | { 50 | set 51 | { 52 | this.InputDataList = new BlockingQueueAggregator(value); 53 | } 54 | } 55 | 56 | /// 57 | /// List of blocking queues which are processed by Width threads in via the Processor delegate in the order they were 58 | /// entered. 59 | /// 60 | public BlockingQueueAggregator InputDataList { set; get; } 61 | } 62 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Infrastructure/WorkItemOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Infrastructure 4 | { 5 | [Flags] 6 | public enum WorkItemOptions 7 | { 8 | /// 9 | /// Use standard behaviour 10 | /// 11 | Default = 0, 12 | 13 | /// 14 | /// When an exception occurs all worker threads are cancelled and the last worker thread exception is 15 | /// rethrown. 16 | /// 17 | ExitOnFirstEror = 1, 18 | 19 | /// 20 | /// Collect all exceptions from worker threads and throw an AggregateException when Dispose is called. 21 | /// 22 | AggregateExceptions = 2 23 | } 24 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Introspection/AssemblyLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Endjin.Assembly.ChangeDetection.Infrastructure; 4 | using Mono.Cecil; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Introspection 7 | { 8 | public class AssemblyLoader 9 | { 10 | private static readonly TypeHashes myType = new TypeHashes(typeof(AssemblyLoader)); 11 | 12 | private static bool IsManagedCppAssembly(AssemblyDefinition assembly) 13 | { 14 | foreach (ModuleDefinition mod in assembly.Modules) 15 | { 16 | foreach (AssemblyNameReference assemblyRef in mod.AssemblyReferences) 17 | { 18 | if (assemblyRef.Name == "Microsoft.VisualC") 19 | { 20 | // Managed C++ targets are not supported by Mono Cecil skip all targets 21 | // which reference the C-Runtime 22 | return true; 23 | } 24 | } 25 | } 26 | 27 | return false; 28 | } 29 | 30 | public static AssemblyDefinition LoadCecilAssembly(string fileName, bool immediateLoad = false, bool? readSymbols = null) 31 | { 32 | using (var t = new Tracer(Level.L5, myType, "LoadCecilAssembly")) 33 | { 34 | var pdbPath = Path.ChangeExtension(fileName, "pdb"); 35 | var tryReadSymbols = readSymbols ?? File.Exists(pdbPath); 36 | var fileInfo = new FileInfo(fileName); 37 | if (fileInfo.Length == 0) 38 | { 39 | t.Info("File {0} has zero byte length", fileName); 40 | return null; 41 | } 42 | 43 | try 44 | { 45 | var readingMode = immediateLoad ? ReadingMode.Immediate : ReadingMode.Deferred; 46 | var assemblyResolver = new DefaultAssemblyResolver(); 47 | assemblyResolver.AddSearchDirectory(fileInfo.Directory.FullName); 48 | var readerParameters = new ReaderParameters { ReadSymbols = tryReadSymbols, ReadingMode = readingMode, AssemblyResolver = assemblyResolver }; 49 | var assemblyDef = AssemblyDefinition.ReadAssembly(fileName, readerParameters); 50 | 51 | // Managed C++ assemblies are not supported by Mono Cecil 52 | if (IsManagedCppAssembly(assemblyDef)) 53 | { 54 | t.Info("File {0} is a managed C++ assembly", fileName); 55 | return null; 56 | } 57 | 58 | return assemblyDef; 59 | } 60 | catch (BadImageFormatException) // Ignore invalid images 61 | { 62 | } 63 | catch (IndexOutOfRangeException) 64 | { 65 | t.Info("File {0} is a managed C++ assembly", fileName); 66 | } 67 | catch (NullReferenceException) // ignore managed c++ targets 68 | { 69 | t.Info("File {0} is a managed C++ assembly", fileName); 70 | } 71 | catch (ArgumentOutOfRangeException) 72 | { 73 | t.Info("File {0} is a managed C++ assembly", fileName); 74 | } 75 | catch (Exception ex) 76 | { 77 | t.Error(Level.L1, "Could not read assembly {0}: {1}", fileName, ex); 78 | } 79 | 80 | return null; 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Introspection/FieldPrintOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Introspection 4 | { 5 | [Flags] 6 | public enum FieldPrintOptions 7 | { 8 | Visibility = 1, 9 | 10 | Modifiers = 2, 11 | 12 | SimpleType = 4, 13 | 14 | Value = 8, 15 | 16 | All = Visibility | Modifiers | SimpleType | Value 17 | } 18 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Introspection/FilterMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Introspection 4 | { 5 | [Flags] 6 | public enum FilterMode 7 | { 8 | Private = 1, 9 | 10 | Public = 2, 11 | 12 | Internal = 4, 13 | 14 | Protected = 8, 15 | 16 | NotInternalProtected = 16, 17 | 18 | All = Private | Public | Internal | Protected 19 | } 20 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Introspection/ISymChkExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Introspection 4 | { 5 | internal interface ISymChkExecutor 6 | { 7 | List FailedPdbs { get; set; } 8 | 9 | int SucceededPdbCount { get; } 10 | 11 | bool DownLoadPdb(string fullBinaryName, string symbolServer, string downloadDir); 12 | } 13 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Introspection/MethodPrintOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Introspection 4 | { 5 | [Flags] 6 | public enum MethodPrintOption 7 | { 8 | ShortNames = 1, 9 | 10 | Visiblity = 2, 11 | 12 | Modifier = 4, 13 | 14 | ReturnType = 8, 15 | 16 | Parameters = 16, 17 | 18 | ParamNames = 32, 19 | 20 | Full = ShortNames | Visiblity | Modifier | ReturnType | Parameters | ParamNames 21 | } 22 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Introspection/SymChkExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Text.RegularExpressions; 7 | using Endjin.Assembly.ChangeDetection.Infrastructure; 8 | 9 | namespace Endjin.Assembly.ChangeDetection.Introspection 10 | { 11 | internal class SymChkExecutor : ISymChkExecutor 12 | { 13 | private static readonly TypeHashes myType = new TypeHashes(typeof(SymChkExecutor)); 14 | 15 | internal static bool bCanStartSymChk = true; 16 | 17 | private static readonly Regex symPassedFileCountParser = new Regex(@"SYMCHK: PASSED \+ IGNORED files = (?\d+) *", RegexOptions.IgnoreCase); 18 | 19 | private static readonly Regex symFailedFileParser = new Regex(@"SYMCHK: (?.*?) +FAILED", RegexOptions.IgnoreCase); 20 | 21 | internal string SymChkExeName = "symchk.exe"; 22 | 23 | public SymChkExecutor() 24 | { 25 | this.FailedPdbs = new List(); 26 | } 27 | 28 | public int SucceededPdbCount { get; private set; } 29 | 30 | public List FailedPdbs { get; set; } 31 | 32 | public bool DownLoadPdb(string fullbinaryName, string symbolServer, string downloadDir) 33 | { 34 | using (var t = new Tracer(myType, "DownLoadPdb")) 35 | { 36 | var lret = bCanStartSymChk; 37 | 38 | if (lret) 39 | { 40 | var startInfo = new ProcessStartInfo(this.SymChkExeName, this.BuildCmdLine(fullbinaryName, symbolServer, downloadDir)); 41 | 42 | startInfo.RedirectStandardOutput = true; 43 | startInfo.RedirectStandardError = true; 44 | startInfo.UseShellExecute = false; 45 | startInfo.CreateNoWindow = true; 46 | 47 | Process proc = null; 48 | try 49 | { 50 | proc = Process.Start(startInfo); 51 | proc.OutputDataReceived += this.proc_OutputDataReceived; 52 | proc.ErrorDataReceived += this.proc_OutputDataReceived; 53 | proc.BeginErrorReadLine(); 54 | proc.BeginOutputReadLine(); 55 | 56 | proc.WaitForExit(); 57 | } 58 | catch (Win32Exception ex) 59 | { 60 | bCanStartSymChk = false; 61 | t.Error(ex, "Could not start symchk.exe to download pdb files"); 62 | lret = false; 63 | } 64 | finally 65 | { 66 | if (proc != null) 67 | { 68 | proc.OutputDataReceived -= this.proc_OutputDataReceived; 69 | proc.ErrorDataReceived -= this.proc_OutputDataReceived; 70 | proc.Dispose(); 71 | } 72 | } 73 | } 74 | 75 | if (this.FailedPdbs.Count > 0) 76 | { 77 | lret = false; 78 | } 79 | 80 | return lret; 81 | } 82 | } 83 | 84 | internal string BuildCmdLine(string binaryFileName, string symbolServer, string downloadDir) 85 | { 86 | var lret = string.Format("\"{0}\" /su \"{1}\" /oc \"{2}\"", binaryFileName, symbolServer, downloadDir ?? Path.GetDirectoryName(binaryFileName)); 87 | 88 | Tracer.Info(Level.L1, myType, "BuildCmdLine", "Symcheck command is {0} {1}", this.SymChkExeName, lret); 89 | 90 | return lret; 91 | } 92 | 93 | private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e) 94 | { 95 | if (e.Data != null) 96 | { 97 | var line = e.Data; 98 | 99 | var m = symPassedFileCountParser.Match(line); 100 | if (m.Success) 101 | { 102 | lock (this) 103 | { 104 | this.SucceededPdbCount += int.Parse(m.Groups["succeeded"].Value, CultureInfo.InvariantCulture); 105 | } 106 | } 107 | 108 | m = symFailedFileParser.Match(line); 109 | if (m.Success) 110 | { 111 | lock (this) 112 | { 113 | this.FailedPdbs.Add(m.Groups["filename"].Value); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Introspection/TypeMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Introspection 4 | { 5 | public class TypeMapper 6 | { 7 | private static readonly Dictionary mySimpleType2FullType = new Dictionary 8 | { 9 | { "bool", "System.Boolean" }, 10 | { "byte", "System.Byte" }, 11 | { "sbyte", "System.SByte" }, 12 | { "char", "System.Char" }, 13 | { "decimal", "System.Decimal" }, 14 | { "double", "System.Double" }, 15 | { "float", "System.Single" }, 16 | { "int", "System.Int32" }, 17 | { "uint", "System.UInt32" }, 18 | { "long", "System.Int64" }, 19 | { "ulong", "System.UInt64" }, 20 | { "object", "System.Object" }, 21 | { "short", "System.Int16" }, 22 | { "ushort", "System.UInt16" }, 23 | { "string", "System.String" }, 24 | { "", "System.Void" }, 25 | { "void", "System.Void" }, 26 | 27 | // for system reflection compat support this shorthand notation as well 28 | { "Bool", "System.Boolean" }, 29 | { "Byte", "System.Byte" }, 30 | { "SByte", "System.SByte" }, 31 | { "Char", "System.Char" }, 32 | { "Decimal", "System.Decimal" }, 33 | { "Double", "System.Double" }, 34 | { "Single", "System.Single" }, 35 | { "Int32", "System.Int32" }, 36 | { "UInt32", "System.UInt32" }, 37 | { "Int64", "System.Int64" }, 38 | { "UInt64", "System.UInt64" }, 39 | { "Object", "System.Object" }, 40 | { "Int16", "System.Int16" }, 41 | { "UInt16", "System.UInt16" }, 42 | { "String", "System.String" }, 43 | { "Void", "System.Void" } 44 | }; 45 | 46 | private static readonly Dictionary myFullType2SimpleType = new Dictionary 47 | { 48 | { "System.Boolean", "bool" }, 49 | { "System.Byte", "byte" }, 50 | { "System.SByte", "sbyte" }, 51 | { "System.Char", "char" }, 52 | { "System.Decimal", "decimal" }, 53 | { "System.Double", "double" }, 54 | { "System.Single", "float" }, 55 | { "System.Int32", "int" }, 56 | { "System.UInt32", "uint" }, 57 | { "System.Int64", "long" }, 58 | { "System.UInt64", "ulong" }, 59 | { "System.Object", "object" }, 60 | { "System.Int16", "short" }, 61 | { "System.UInt16", "ushort" }, 62 | { "System.String", "string" }, 63 | { "System.Void", "void" }, 64 | // Generic parameters have not full qualfied type names 65 | { "Boolean", "bool" }, 66 | { "Byte", "byte" }, 67 | { "SByte", "sbyte" }, 68 | { "Char", "char" }, 69 | { "Decimal", "decimal" }, 70 | { "Double", "double" }, 71 | { "Single", "float" }, 72 | { "Int32", "int" }, 73 | { "UInt32", "uint" }, 74 | { "Int64", "long" }, 75 | { "UInt64", "ulong" }, 76 | { "Object", "object" }, 77 | { "Int16", "short" }, 78 | { "UInt16", "ushort" }, 79 | { "String", "string" }, 80 | { "Void", "void" } 81 | }; 82 | 83 | /// 84 | /// Map a short type e.g int to the full system type System.Int32 85 | /// 86 | /// The short type. 87 | /// the expanded system type if possible 88 | public static string ShortToFull(string shortType) 89 | { 90 | var lret = shortType; 91 | string fullType = null; 92 | if (mySimpleType2FullType.TryGetValue(shortType, out fullType)) 93 | { 94 | lret = fullType; 95 | } 96 | return lret; 97 | } 98 | 99 | public static string FullToShort(string fullType) 100 | { 101 | var lret = fullType; 102 | string shortType = null; 103 | if (myFullType2SimpleType.TryGetValue(fullType, out shortType)) 104 | { 105 | lret = shortType; 106 | } 107 | 108 | return lret; 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/ListDiffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | 4 | namespace Endjin.Assembly.ChangeDetection 5 | { 6 | /// 7 | /// Compares two lists and creates two diff lists with added and removed elements 8 | /// 9 | /// 10 | public class ListDiffer 11 | { 12 | private readonly Func myIsEqual; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The comparer function to check for equality in the collections to be compared. 18 | public ListDiffer(Func comparer) 19 | { 20 | this.myIsEqual = comparer; 21 | } 22 | 23 | /// 24 | /// Compare two lists A and B and add the new elements in B to added and the elements elements 25 | /// which occur only in A and not in B to the removed collection. 26 | /// 27 | /// The list v1. 28 | /// The list v2. 29 | /// New added elements in version 2 30 | /// Removed elements in version 2 31 | public void Diff(IEnumerable listV1, IEnumerable listV2, Action added, Action removed) 32 | { 33 | foreach (T ai in listV1) 34 | { 35 | var bIsInList = false; 36 | foreach (T bi in listV2) 37 | { 38 | if (this.myIsEqual(ai, bi)) 39 | { 40 | bIsInList = true; 41 | break; 42 | } 43 | } 44 | 45 | if (!bIsInList) 46 | { 47 | removed(ai); 48 | } 49 | } 50 | 51 | foreach (T bi in listV2) 52 | { 53 | var bIsInList = false; 54 | foreach (T ai in listV1) 55 | { 56 | if (this.myIsEqual(bi, ai)) 57 | { 58 | bIsInList = true; 59 | break; 60 | } 61 | } 62 | 63 | if (!bIsInList) 64 | { 65 | added(bi); 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("AssemblyDifferences")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AssemblyDifferences")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("37a1521e-1b37-4c78-97e4-65524154b1fd")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/BaseQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Query 5 | { 6 | public class BaseQuery 7 | { 8 | // Common Regular expression part shared by the different queries 9 | private const string CommonModifiers = "!?static +|!?public +|!?protected +internal +|!?protected +|!?internal +|!?private +"; 10 | 11 | private static Regex myEventQueryParser; 12 | 13 | private static Regex myFieldQueryParser; 14 | 15 | private static Regex myMethodDefParser; 16 | 17 | protected internal bool? myIsInternal; 18 | 19 | protected internal bool? myIsPrivate; 20 | 21 | protected internal bool? myIsProtected; 22 | 23 | protected internal bool? myIsProtectedInernal; 24 | 25 | protected internal bool? myIsPublic; 26 | 27 | protected internal bool? myIsStatic; 28 | 29 | protected BaseQuery(string query) 30 | { 31 | if (string.IsNullOrEmpty(query)) 32 | { 33 | throw new ArgumentNullException("query string was empty"); 34 | } 35 | } 36 | 37 | internal static Regex EventQueryParser 38 | { 39 | get 40 | { 41 | if (myEventQueryParser == null) 42 | { 43 | myEventQueryParser = new Regex("^ *(?!?virtual +|event +|" + CommonModifiers + ")*" + @" *(?[^ ]+(<.*>)?) +(?[^ ]+) *$"); 44 | } 45 | 46 | return myEventQueryParser; 47 | } 48 | } 49 | 50 | internal static Regex FieldQueryParser 51 | { 52 | get 53 | { 54 | if (myFieldQueryParser == null) 55 | { 56 | myFieldQueryParser = new Regex(" *(?!?nocompilergenerated +|!?const +|!?readonly +|" + CommonModifiers + ")*" + @" *(?[^ ]+(<.*>)?) +(?[^ ]+) *$"); 57 | } 58 | 59 | return myFieldQueryParser; 60 | } 61 | } 62 | 63 | internal static Regex MethodDefParser 64 | { 65 | get 66 | { 67 | if (myMethodDefParser == null) 68 | { 69 | myMethodDefParser = new Regex(@" *(?!?virtual +|" + CommonModifiers + ")*" + @"(?.*<.*>( *\[\])?|[^ (\)]*( *\[\])?) +(?.+)\( *(?.*?) *\) *"); 70 | } 71 | 72 | return myMethodDefParser; 73 | } 74 | } 75 | 76 | protected internal Regex Parser { get; set; } 77 | 78 | protected internal string NameFilter { get; set; } 79 | 80 | protected internal virtual bool IsMatch(Match m, string key) 81 | { 82 | return m.Groups[key].Success; 83 | } 84 | 85 | protected internal virtual bool? Captures(Match m, string value) 86 | { 87 | var notValue = "!" + value; 88 | foreach (Capture capture in m.Groups["modifiers"].Captures) 89 | { 90 | if (value == capture.Value.TrimEnd()) 91 | { 92 | return true; 93 | } 94 | if (notValue == capture.Value.TrimEnd()) 95 | { 96 | return false; 97 | } 98 | } 99 | 100 | return null; 101 | } 102 | 103 | protected string Value(Match m, string groupName) 104 | { 105 | return m.Groups[groupName].Value; 106 | } 107 | 108 | protected virtual void SetModifierFilter(Match m) 109 | { 110 | this.myIsProtected = this.Captures(m, "protected"); 111 | this.myIsInternal = this.Captures(m, "internal"); 112 | this.myIsProtectedInernal = this.Captures(m, "protected internal"); 113 | this.myIsPublic = this.Captures(m, "public"); 114 | this.myIsPrivate = this.Captures(m, "private"); 115 | this.myIsStatic = this.Captures(m, "static"); 116 | } 117 | 118 | protected virtual bool MatchName(string name) 119 | { 120 | if (string.IsNullOrEmpty(this.NameFilter) || this.NameFilter == "*") 121 | { 122 | return true; 123 | } 124 | 125 | return Matcher.MatchWithWildcards(this.NameFilter, name, StringComparison.OrdinalIgnoreCase); 126 | } 127 | 128 | private int CountChars(char searchChar, string str) 129 | { 130 | var ret = 0; 131 | foreach (var c in str) 132 | { 133 | if (c == searchChar) 134 | { 135 | ret++; 136 | } 137 | } 138 | 139 | return ret; 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/EventComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Endjin.Assembly.ChangeDetection.Introspection; 3 | using Mono.Cecil; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Query 6 | { 7 | internal class EventComparer : IEqualityComparer 8 | { 9 | #region IEqualityComparer Members 10 | 11 | public bool Equals(EventDefinition x, EventDefinition y) 12 | { 13 | return x.AddMethod.IsEqual(y.AddMethod); 14 | } 15 | 16 | public int GetHashCode(EventDefinition obj) 17 | { 18 | return obj.AddMethod.Name.GetHashCode(); 19 | } 20 | 21 | #endregion 22 | } 23 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/EventQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using Mono.Cecil; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Query 7 | { 8 | public class EventQuery : MethodQuery 9 | { 10 | private static readonly EventQuery myPublicEvents = new EventQuery("public * *"); 11 | 12 | private static readonly EventQuery myProtectedEvents = new EventQuery("protected * *"); 13 | 14 | private static readonly EventQuery myInternalEvents = new EventQuery("internal * *"); 15 | 16 | private static readonly EventQuery myAllEvents = new EventQuery("* *"); 17 | 18 | public EventQuery() : this("*") 19 | { 20 | } 21 | 22 | public EventQuery(string query) : base("*") 23 | { 24 | if (string.IsNullOrEmpty(query)) 25 | { 26 | throw new ArgumentNullException("query string was empty"); 27 | } 28 | 29 | if (query == "*") 30 | { 31 | return; 32 | } 33 | 34 | // Get cached regex 35 | this.Parser = EventQueryParser; 36 | 37 | var match = this.Parser.Match(query); 38 | if (!match.Success) 39 | { 40 | throw new ArgumentException(string.Format("The event query string {0} was not a valid query.", query)); 41 | } 42 | 43 | this.SetModifierFilter(match); 44 | this.EventTypeFilter = GenericTypeMapper.ConvertClrTypeNames(this.Value(match, "eventType")); 45 | this.EventTypeFilter = this.PrependStarBeforeGenericTypes(this.EventTypeFilter); 46 | 47 | if (!this.EventTypeFilter.StartsWith("*")) 48 | { 49 | this.EventTypeFilter = "*" + this.EventTypeFilter; 50 | } 51 | 52 | if (this.EventTypeFilter == "*") 53 | { 54 | this.EventTypeFilter = null; 55 | } 56 | 57 | this.NameFilter = this.Value(match, "eventName"); 58 | } 59 | 60 | private string EventTypeFilter { get; set; } 61 | 62 | public static EventQuery PublicEvents 63 | { 64 | get 65 | { 66 | return myPublicEvents; 67 | } 68 | } 69 | 70 | public static EventQuery ProtectedEvents 71 | { 72 | get 73 | { 74 | return myProtectedEvents; 75 | } 76 | } 77 | 78 | public static EventQuery InternalEvents 79 | { 80 | get 81 | { 82 | return myInternalEvents; 83 | } 84 | } 85 | 86 | public static EventQuery AllEvents 87 | { 88 | get 89 | { 90 | return myAllEvents; 91 | } 92 | } 93 | 94 | private string PrependStarBeforeGenericTypes(string eventTypeFilter) 95 | { 96 | return eventTypeFilter.Replace("<", "<*").Replace("**", "*"); 97 | } 98 | 99 | [EditorBrowsable(EditorBrowsableState.Never)] 100 | public override List GetMethods(TypeDefinition type) 101 | { 102 | throw new NotSupportedException("The event query does not support a method match. Use GetMatchingEvents to get the query result"); 103 | } 104 | 105 | public List GetMatchingEvents(TypeDefinition type) 106 | { 107 | if (type == null) 108 | { 109 | throw new ArgumentException("The type instance to analyze was null. Can`t do that."); 110 | } 111 | 112 | var events = new List(); 113 | 114 | foreach (EventDefinition ev in type.Events) 115 | { 116 | if (this.IsMatchingEvent(ev)) 117 | { 118 | events.Add(ev); 119 | } 120 | } 121 | 122 | return events; 123 | } 124 | 125 | private bool IsMatchingEvent(EventDefinition ev) 126 | { 127 | var lret = true; 128 | 129 | lret = this.MatchMethodModifiers(ev.AddMethod); 130 | if (lret) 131 | { 132 | lret = this.MatchName(ev.Name); 133 | } 134 | 135 | if (lret) 136 | { 137 | lret = this.MatchEventType(ev.EventType); 138 | } 139 | 140 | return lret; 141 | } 142 | 143 | private bool MatchEventType(TypeReference evType) 144 | { 145 | if (string.IsNullOrEmpty(this.EventTypeFilter) || this.EventTypeFilter == "*") 146 | { 147 | return true; 148 | } 149 | 150 | return Matcher.MatchWithWildcards(this.EventTypeFilter, evType.FullName, StringComparison.OrdinalIgnoreCase); 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/FieldComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Endjin.Assembly.ChangeDetection.Introspection; 3 | using Mono.Cecil; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Query 6 | { 7 | internal class FieldComparer : IEqualityComparer 8 | { 9 | #region IEqualityComparer Members 10 | 11 | public bool Equals(FieldDefinition x, FieldDefinition y) 12 | { 13 | return x.IsEqual(y); 14 | } 15 | 16 | public int GetHashCode(FieldDefinition obj) 17 | { 18 | return obj.Name.GetHashCode(); 19 | } 20 | 21 | #endregion 22 | } 23 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/Matcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Query 6 | { 7 | /// 8 | /// Partial String matcher class which supports wildcards 9 | /// 10 | internal static class Matcher 11 | { 12 | private const string EscapedStar = "magic_star"; 13 | 14 | private static readonly char[] myNsTrimChars = { ' ', '*', '\t' }; 15 | 16 | // Cache filter string regular expressions for later reuse 17 | internal static Dictionary myFilter2Regex = new Dictionary(); 18 | 19 | /// 20 | /// Check if a given test string does match the pattern specified by the filterString. Besides 21 | /// normal string comparisons for the patterns *xxx, xxx*, *xxx* which are mapped to String.EndsWith, 22 | /// String.StartsWith and String.Contains are regular expressions used if the pattern is more complex 23 | /// like *xx*bbb. 24 | /// 25 | /// 26 | /// Filter string. A filter string of null or * will match any testString. If the teststring is 27 | /// null it will never match anything. 28 | /// 29 | /// String to check 30 | /// String Comparision mode 31 | /// true if the teststring does match, false otherwise. 32 | public static bool MatchWithWildcards(string filterString, string testString, StringComparison compMode) 33 | { 34 | if (filterString == null || filterString == "*") 35 | { 36 | return true; 37 | } 38 | 39 | if (testString == null) 40 | { 41 | return false; 42 | } 43 | 44 | if (IsRegexMatchNecessary(filterString)) 45 | { 46 | return IsMatch(filterString, testString, compMode); 47 | } 48 | 49 | var bMatchEnd = false; 50 | if (filterString.StartsWith("*", compMode)) 51 | { 52 | bMatchEnd = true; 53 | } 54 | 55 | var bMatchStart = false; 56 | if (filterString.EndsWith("*", compMode)) 57 | { 58 | bMatchStart = true; 59 | } 60 | 61 | var filterSubstring = filterString.Trim(myNsTrimChars); 62 | 63 | if (bMatchStart && bMatchEnd) 64 | { 65 | if (compMode == StringComparison.OrdinalIgnoreCase || compMode == StringComparison.InvariantCultureIgnoreCase) 66 | { 67 | return testString.ToLower().Contains(filterSubstring.ToLower()); 68 | } 69 | return testString.Contains(filterSubstring); 70 | } 71 | 72 | if (bMatchStart) 73 | { 74 | return testString.StartsWith(filterSubstring, compMode); 75 | } 76 | 77 | if (bMatchEnd) 78 | { 79 | return testString.EndsWith(filterSubstring, compMode); 80 | } 81 | 82 | return string.Compare(testString, filterSubstring, compMode) == 0; 83 | } 84 | 85 | // Check if * occurs inside filter string and not only at start or end 86 | internal static bool IsRegexMatchNecessary(string filter) 87 | { 88 | var start = Math.Min(1, Math.Max(filter.Length - 1, 0)); 89 | var len = Math.Max(filter.Length - 2, 0); 90 | return filter.IndexOf("*", start, len) != -1; 91 | } 92 | 93 | internal static bool IsMatch(string filter, string testString, StringComparison compMode) 94 | { 95 | var filterRex = GenerateRegexFromFilter(filter, compMode); 96 | return filterRex.IsMatch(testString); 97 | } 98 | 99 | internal static Regex GenerateRegexFromFilter(string filter, StringComparison mode) 100 | { 101 | Regex lret = null; 102 | 103 | if (!myFilter2Regex.TryGetValue(filter, out lret)) 104 | { 105 | var rex = Regex.Escape(filter.Replace("*", EscapedStar)); 106 | rex = "^" + rex + "$"; 107 | lret = new Regex(rex.Replace(EscapedStar, ".*?"), (mode == StringComparison.CurrentCultureIgnoreCase || mode == StringComparison.InvariantCultureIgnoreCase || mode == StringComparison.OrdinalIgnoreCase) ? RegexOptions.IgnoreCase : RegexOptions.None); 108 | myFilter2Regex[filter] = lret; 109 | } 110 | 111 | return lret; 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/MethodComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Endjin.Assembly.ChangeDetection.Introspection; 3 | using Mono.Cecil; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Query 6 | { 7 | internal class MethodComparer : IEqualityComparer 8 | { 9 | #region IEqualityComparer Members 10 | 11 | public bool Equals(MethodDefinition x, MethodDefinition y) 12 | { 13 | return x.IsEqual(y); 14 | } 15 | 16 | public int GetHashCode(MethodDefinition obj) 17 | { 18 | return obj.Name.GetHashCode(); 19 | } 20 | 21 | #endregion 22 | } 23 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/QueryAggregator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Mono.Cecil; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Query 6 | { 7 | public class QueryAggregator 8 | { 9 | public List EventQueries = new List(); 10 | 11 | public List FieldQueries = new List(); 12 | 13 | public List MethodQueries = new List(); 14 | 15 | public List TypeQueries = new List(); 16 | 17 | /// 18 | /// Contains also internal types, fields and methods since the InteralsVisibleToAttribute 19 | /// can open visibility 20 | /// 21 | public static QueryAggregator PublicApiQueries 22 | { 23 | get 24 | { 25 | var agg = new QueryAggregator(); 26 | 27 | agg.TypeQueries.Add(new TypeQuery(TypeQueryMode.ApiRelevant)); 28 | 29 | agg.MethodQueries.Add(MethodQuery.PublicMethods); 30 | agg.MethodQueries.Add(MethodQuery.ProtectedMethods); 31 | 32 | agg.FieldQueries.Add(FieldQuery.PublicFields); 33 | agg.FieldQueries.Add(FieldQuery.ProtectedFields); 34 | 35 | agg.EventQueries.Add(EventQuery.PublicEvents); 36 | agg.EventQueries.Add(EventQuery.ProtectedEvents); 37 | 38 | return agg; 39 | } 40 | } 41 | 42 | public static QueryAggregator AllExternallyVisibleApis 43 | { 44 | get 45 | { 46 | var agg = PublicApiQueries; 47 | agg.TypeQueries.Add(new TypeQuery(TypeQueryMode.Internal)); 48 | agg.MethodQueries.Add(MethodQuery.InternalMethods); 49 | agg.FieldQueries.Add(FieldQuery.InteralFields); 50 | agg.EventQueries.Add(EventQuery.InternalEvents); 51 | return agg; 52 | } 53 | } 54 | 55 | public List ExeuteAndAggregateTypeQueries(AssemblyDefinition assembly) 56 | { 57 | var result = new List(); 58 | foreach (var query in this.TypeQueries) 59 | { 60 | result.AddRange(query.GetTypes(assembly)); 61 | } 62 | 63 | var distinctResults = result; 64 | if (this.TypeQueries.Count > 1) 65 | { 66 | distinctResults = result.Distinct(new TypeNameComparer()).ToList(); 67 | } 68 | 69 | return distinctResults; 70 | } 71 | 72 | public List ExecuteAndAggregateMethodQueries(TypeDefinition type) 73 | { 74 | var methods = new List(); 75 | foreach (var query in this.MethodQueries) 76 | { 77 | methods.AddRange(query.GetMethods(type)); 78 | } 79 | 80 | var distinctResults = methods; 81 | if (this.MethodQueries.Count > 1) 82 | { 83 | distinctResults = methods.Distinct(new MethodComparer()).ToList(); 84 | } 85 | 86 | return distinctResults; 87 | } 88 | 89 | public List ExecuteAndAggregateFieldQueries(TypeDefinition type) 90 | { 91 | var fields = new List(); 92 | foreach (var query in this.FieldQueries) 93 | { 94 | fields.AddRange(query.GetMatchingFields(type)); 95 | } 96 | 97 | var distinctResults = fields; 98 | if (this.FieldQueries.Count > 1) 99 | { 100 | distinctResults = fields.Distinct(new FieldComparer()).ToList(); 101 | } 102 | 103 | return distinctResults; 104 | } 105 | 106 | public List ExecuteAndAggregateEventQueries(TypeDefinition type) 107 | { 108 | var ret = new List(); 109 | 110 | foreach (var query in this.EventQueries) 111 | { 112 | ret.AddRange(query.GetMatchingEvents(type)); 113 | } 114 | 115 | var distinctEvents = ret; 116 | if (this.EventQueries.Count > 1) 117 | { 118 | distinctEvents = ret.Distinct(new EventComparer()).ToList(); 119 | } 120 | 121 | return distinctEvents; 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/TypeNameComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Mono.Cecil; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Query 5 | { 6 | internal class TypeNameComparer : IEqualityComparer 7 | { 8 | #region IEqualityComparer Members 9 | 10 | public bool Equals(TypeDefinition x, TypeDefinition y) 11 | { 12 | return x.FullName == y.FullName; 13 | } 14 | 15 | public int GetHashCode(TypeDefinition obj) 16 | { 17 | return obj.Name.GetHashCode(); 18 | } 19 | 20 | #endregion 21 | } 22 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/TypeQueryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Mono.Cecil; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Query 5 | { 6 | public static class TypeQueryExtensions 7 | { 8 | public static IEnumerable GetMatchingTypes(this List list, AssemblyDefinition assembly) 9 | { 10 | foreach (var query in list) 11 | { 12 | var matchingTypes = query.GetTypes(assembly); 13 | foreach (var matchingType in matchingTypes) 14 | { 15 | yield return matchingType; 16 | } 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/TypeQueryFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Query 6 | { 7 | /// 8 | /// Parser for a list of type queries separated by ;. A type query can 9 | /// 10 | internal class TypeQueryFactory 11 | { 12 | private static readonly Regex myQueryParser = new Regex("^ *(?api +|nocompiler +|public +|internal +|class +|struct +|interface +|enum +)* *(?[^ ]+) *$"); 13 | 14 | /// 15 | /// Parse a list of type queries separated by ; and return the resulting type query list 16 | /// 17 | /// 18 | /// 19 | public List GetQueries(string queries) 20 | { 21 | return this.GetQueries(queries, TypeQueryMode.None); 22 | } 23 | 24 | public List GetQueries(string typeQueries, TypeQueryMode additionalFlags) 25 | { 26 | if (typeQueries == null) 27 | { 28 | throw new ArgumentNullException("typeQueries"); 29 | } 30 | 31 | var trimedQuery = typeQueries.Trim(); 32 | if (trimedQuery == "") 33 | { 34 | throw new ArgumentException("typeQueries was an empty string"); 35 | } 36 | 37 | var queries = trimedQuery.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); 38 | 39 | var ret = new List(); 40 | 41 | foreach (var query in queries) 42 | { 43 | var m = myQueryParser.Match(query); 44 | if (!m.Success) 45 | { 46 | throw new ArgumentException(string.Format("The type query \"{0}\" is not of the form [public|internal|class|interface|struct|enum|nocompiler|api] typename", query)); 47 | } 48 | 49 | var mode = this.GetQueryMode(m); 50 | var nameSpaceTypeName = this.SplitNameSpaceAndType(m.Groups["typeName"].Value); 51 | var typeQuery = new TypeQuery(mode, nameSpaceTypeName.Key, nameSpaceTypeName.Value); 52 | if (typeQuery.SearchMode == TypeQueryMode.None) 53 | { 54 | typeQuery.SearchMode |= additionalFlags; 55 | } 56 | ret.Add(typeQuery); 57 | } 58 | 59 | return ret; 60 | } 61 | 62 | internal KeyValuePair SplitNameSpaceAndType(string fullQualifiedTypeName) 63 | { 64 | if (string.IsNullOrEmpty(fullQualifiedTypeName)) 65 | { 66 | throw new ArgumentNullException("fullQualifiedTypeName"); 67 | } 68 | 69 | var parts = fullQualifiedTypeName.Trim().Split('.'); 70 | if (parts.Length > 1) 71 | { 72 | return new KeyValuePair(string.Join(".", parts, 0, parts.Length - 1), parts[parts.Length - 1]); 73 | } 74 | return new KeyValuePair(null, parts[0]); 75 | } 76 | 77 | private TypeQueryMode GetQueryMode(Match m) 78 | { 79 | var mode = TypeQueryMode.None; 80 | 81 | if (this.Captures(m, "public")) 82 | { 83 | mode |= TypeQueryMode.Public; 84 | } 85 | if (this.Captures(m, "internal")) 86 | { 87 | mode |= TypeQueryMode.Internal; 88 | } 89 | if (this.Captures(m, "class")) 90 | { 91 | mode |= TypeQueryMode.Class; 92 | } 93 | if (this.Captures(m, "interface")) 94 | { 95 | mode |= TypeQueryMode.Interface; 96 | } 97 | if (this.Captures(m, "struct")) 98 | { 99 | mode |= TypeQueryMode.ValueType; 100 | } 101 | if (this.Captures(m, "enum")) 102 | { 103 | mode |= TypeQueryMode.Enum; 104 | } 105 | if (this.Captures(m, "nocompiler")) 106 | { 107 | mode |= TypeQueryMode.NotCompilerGenerated; 108 | } 109 | if (this.Captures(m, "api")) 110 | { 111 | mode |= TypeQueryMode.ApiRelevant; 112 | } 113 | 114 | return mode; 115 | } 116 | 117 | protected internal virtual bool Captures(Match m, string value) 118 | { 119 | foreach (Capture capture in m.Groups["modifiers"].Captures) 120 | { 121 | if (value == capture.Value.TrimEnd()) 122 | { 123 | return true; 124 | } 125 | } 126 | 127 | return false; 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/TypeQueryMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Query 4 | { 5 | [Flags] 6 | public enum TypeQueryMode 7 | { 8 | None = 0, 9 | 10 | Public = 1, 11 | 12 | Internal = 2, 13 | 14 | NotCompilerGenerated = 4, 15 | 16 | Interface = 8, 17 | 18 | Class = 16, 19 | 20 | ValueType = 32, 21 | 22 | Enum = 64, 23 | 24 | ApiRelevant = Public | NotCompilerGenerated | Interface | Class | ValueType | Enum, 25 | 26 | All = Public | Internal | Interface | Class | ValueType | Enum 27 | } 28 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/MatchContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 4 | { 5 | public class MatchContext : Dictionary 6 | { 7 | public const string MatchReason = "Match Reason"; 8 | 9 | public const string MatchItem = "Match Item"; 10 | 11 | public MatchContext(string matchReason, string matchItem) 12 | { 13 | this[MatchReason] = matchReason; 14 | this[MatchItem] = matchItem; 15 | } 16 | 17 | public MatchContext() 18 | { 19 | } 20 | 21 | public string Reason 22 | { 23 | get 24 | { 25 | object lret = ""; 26 | if (!this.TryGetValue(MatchReason, out lret)) 27 | { 28 | lret = ""; 29 | } 30 | 31 | return lret.ToString(); 32 | } 33 | } 34 | 35 | public string Item 36 | { 37 | get 38 | { 39 | object lret; 40 | if (!this.TryGetValue(MatchItem, out lret)) 41 | { 42 | lret = ""; 43 | } 44 | 45 | return lret.ToString(); 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/QueryResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Mono.Cecil; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 5 | { 6 | public class QueryResult where T : MemberReference 7 | { 8 | private MatchContext myAnnotations; 9 | 10 | public QueryResult(T match, string fileName, int lineNumber) 11 | { 12 | if (match == null) 13 | { 14 | throw new ArgumentNullException("result"); 15 | } 16 | 17 | this.Match = match; 18 | this.SourceFileName = fileName; 19 | this.LineNumber = lineNumber; 20 | } 21 | 22 | public QueryResult(T match, string fileName, int lineNumber, MatchContext context) : this(match, fileName, lineNumber) 23 | { 24 | if (context == null) 25 | { 26 | throw new ArgumentNullException("match context was null"); 27 | } 28 | 29 | foreach (var kvp in context) 30 | { 31 | this.Annotations[kvp.Key] = kvp.Value; 32 | } 33 | } 34 | 35 | public T Match { get; private set; } 36 | 37 | public string SourceFileName { get; private set; } 38 | 39 | public int LineNumber { get; private set; } 40 | 41 | public MatchContext Annotations 42 | { 43 | get 44 | { 45 | if (this.myAnnotations == null) 46 | { 47 | this.myAnnotations = new MatchContext(); 48 | } 49 | 50 | return this.myAnnotations; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/UsageVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Mono.Cecil; 5 | using Mono.Cecil.Cil; 6 | using Mono.Collections.Generic; 7 | 8 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 9 | { 10 | public class UsageVisitor 11 | { 12 | public UsageVisitor(UsageQueryAggregator aggregator) 13 | { 14 | if (aggregator == null) 15 | { 16 | throw new ArgumentNullException("aggregator"); 17 | } 18 | 19 | this.Aggregator = aggregator; 20 | 21 | // subscribe ourself to the aggregator when the query is constructed 22 | this.Aggregator.AddQuery(this); 23 | } 24 | 25 | protected UsageQueryAggregator Aggregator { get; private set; } 26 | 27 | public virtual void VisitType(TypeDefinition type) 28 | { 29 | } 30 | 31 | public virtual void VisitField(FieldDefinition field) 32 | { 33 | } 34 | 35 | public virtual void VisitMethod(MethodDefinition method) 36 | { 37 | } 38 | 39 | public virtual void VisitLocals(Collection locals, MethodDefinition declaringMethod) 40 | { 41 | } 42 | 43 | public virtual void VisitAssemblyReference(AssemblyNameReference assemblyRef, AssemblyDefinition current) 44 | { 45 | } 46 | 47 | public virtual void VisitMethodBody(MethodBody body) 48 | { 49 | } 50 | 51 | protected static T ThrowIfNull(string name, T arg) 52 | { 53 | if (arg == null) 54 | { 55 | throw new ArgumentNullException(name); 56 | } 57 | 58 | return arg; 59 | } 60 | 61 | protected bool IsMatching(HashSet typeNameHash, List searchTypes, TypeReference currentType, out TypeDefinition foundType) 62 | { 63 | // check if type itself does match 64 | if (typeNameHash.Contains(currentType.Name)) 65 | { 66 | foreach (var searchType in searchTypes) 67 | { 68 | if (currentType.IsEqual(searchType, false)) 69 | { 70 | foundType = searchType; 71 | return true; 72 | } 73 | } 74 | } 75 | 76 | // check if type is a type with generic type parameters 77 | var genArg = currentType as GenericInstanceType; 78 | if (genArg != null) 79 | { 80 | foreach (TypeReference generic in genArg.GenericArguments) 81 | { 82 | if (this.IsMatching(typeNameHash, searchTypes, generic, out foundType)) 83 | { 84 | return true; 85 | } 86 | } 87 | } 88 | 89 | foundType = null; 90 | return false; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoAccessesField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Mono.Cecil; 5 | using Mono.Cecil.Cil; 6 | 7 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 8 | { 9 | public class WhoAccessesField : UsageVisitor 10 | { 11 | private readonly HashSet myDeclaringTypeNamesToSearch = new HashSet(); 12 | 13 | private readonly List mySearchFields; 14 | 15 | public WhoAccessesField(UsageQueryAggregator aggreagator, FieldDefinition field) : this(aggreagator, new List 16 | { 17 | ThrowIfNull("field", field) 18 | }) 19 | { 20 | } 21 | 22 | public WhoAccessesField(UsageQueryAggregator aggregator, List fields) : base(aggregator) 23 | { 24 | if (fields == null) 25 | { 26 | throw new ArgumentException("The field list was null."); 27 | } 28 | 29 | this.mySearchFields = fields; 30 | 31 | foreach (var field in fields) 32 | { 33 | if (field.HasConstant) 34 | { 35 | throw new ArgumentException(string.Format("The field {0} is constant. Its value is compiled directly into the users of this constant which makes is impossible to search for users of it.", field.Print(FieldPrintOptions.All))); 36 | } 37 | this.myDeclaringTypeNamesToSearch.Add(field.DeclaringType.Name); 38 | this.Aggregator.AddVisitScope(field.DeclaringType.Module.Assembly.Name.Name); 39 | } 40 | } 41 | 42 | private void CheckFieldReferenceAndAddIfMatch(Instruction instr, MethodDefinition method, string operation) 43 | { 44 | var field = (FieldReference)instr.Operand; 45 | 46 | if (this.myDeclaringTypeNamesToSearch.Contains(field.DeclaringType.Name)) 47 | { 48 | foreach (var searchField in this.mySearchFields) 49 | { 50 | if (field.DeclaringType.IsEqual(searchField.DeclaringType, false) && field.Name == searchField.Name && field.FieldType.IsEqual(searchField.FieldType)) 51 | { 52 | var context = new MatchContext(operation, string.Format("{0} {1}", searchField.DeclaringType.FullName, searchField.Name)); 53 | this.Aggregator.AddMatch(instr, method, false, context); 54 | } 55 | } 56 | } 57 | } 58 | 59 | public override void VisitMethod(MethodDefinition method) 60 | { 61 | if (!method.HasBody) 62 | { 63 | return; 64 | } 65 | 66 | foreach (Instruction instr in method.Body.Instructions) 67 | { 68 | switch (instr.OpCode.Code) 69 | { 70 | case Code.Ldfld: // Load instance field value 71 | this.CheckFieldReferenceAndAddIfMatch(instr, method, "Read"); 72 | break; 73 | case Code.Ldflda: // Load instance field address 74 | this.CheckFieldReferenceAndAddIfMatch(instr, method, "Load Address"); 75 | break; 76 | case Code.Ldsflda: // Load static field address 77 | this.CheckFieldReferenceAndAddIfMatch(instr, method, "Load Address"); 78 | break; 79 | case Code.Ldsfld: // Load static field value 80 | this.CheckFieldReferenceAndAddIfMatch(instr, method, "Read"); 81 | break; 82 | case Code.Stfld: // Store field 83 | this.CheckFieldReferenceAndAddIfMatch(instr, method, "Assign"); 84 | break; 85 | case Code.Stsfld: // Store static field 86 | this.CheckFieldReferenceAndAddIfMatch(instr, method, "Assign"); 87 | break; 88 | 89 | default: 90 | break; 91 | } 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoDerivesFromType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Mono.Cecil; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 7 | { 8 | public class WhoDerivesFromType : UsageVisitor 9 | { 10 | private readonly List mySearchBaseTypes; 11 | 12 | public WhoDerivesFromType(UsageQueryAggregator aggregator, TypeDefinition typeDef) : this(aggregator, new List 13 | { 14 | ThrowIfNull("typeDef", typeDef) 15 | }) 16 | { 17 | } 18 | 19 | public WhoDerivesFromType(UsageQueryAggregator aggregator, List typeDefs) : base(aggregator) 20 | { 21 | if (typeDefs == null) 22 | { 23 | throw new ArgumentException("The type list to query for was null."); 24 | } 25 | 26 | foreach (var type in typeDefs) 27 | { 28 | this.Aggregator.AddVisitScope(type.Module.Assembly.Name.Name); 29 | } 30 | 31 | this.mySearchBaseTypes = typeDefs; 32 | } 33 | 34 | public override void VisitType(TypeDefinition type) 35 | { 36 | if (type.BaseType == null) 37 | { 38 | return; 39 | } 40 | 41 | foreach (var searchType in this.mySearchBaseTypes) 42 | { 43 | if (type.BaseType.IsEqual(searchType, false)) 44 | { 45 | var context = new MatchContext("Derives from", searchType.Print()); 46 | this.Aggregator.AddMatch(type, context); 47 | break; 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoHasFieldOfType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Mono.Cecil; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 7 | { 8 | public class WhoHasFieldOfType : UsageVisitor 9 | { 10 | private readonly HashSet mySearchTypeNames = new HashSet(); 11 | 12 | private readonly List mySearchTypes; 13 | 14 | public WhoHasFieldOfType(UsageQueryAggregator aggregator, TypeDefinition fieldType) : this(aggregator, new List 15 | { 16 | ThrowIfNull("fieldType", fieldType) 17 | }) 18 | { 19 | } 20 | 21 | public WhoHasFieldOfType(UsageQueryAggregator aggregator, List fieldTypes) : base(aggregator) 22 | { 23 | if (fieldTypes == null) 24 | { 25 | throw new ArgumentNullException("fieldTypes"); 26 | } 27 | 28 | this.mySearchTypes = fieldTypes; 29 | 30 | foreach (var fieldType in fieldTypes) 31 | { 32 | this.mySearchTypeNames.Add(fieldType.Name); 33 | this.Aggregator.AddVisitScope(fieldType.Module.Assembly.Name.Name); 34 | } 35 | } 36 | 37 | public override void VisitField(FieldDefinition field) 38 | { 39 | TypeDefinition matchingType = null; 40 | if (this.IsMatching(this.mySearchTypeNames, this.mySearchTypes, field.FieldType, out matchingType)) 41 | { 42 | var context = new MatchContext("Has Field Of Type", matchingType.Print()); 43 | this.Aggregator.AddMatch(field, context); 44 | return; 45 | } 46 | 47 | var genType = field.FieldType as GenericInstanceType; 48 | if (genType != null) 49 | { 50 | foreach (TypeReference generic in genType.GenericArguments) 51 | { 52 | if (this.IsMatching(this.mySearchTypeNames, this.mySearchTypes, generic, out matchingType)) 53 | { 54 | var context = new MatchContext("Has Field Of Type", matchingType.Print()); 55 | this.Aggregator.AddMatch(field, context); 56 | return; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoImplementsInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Mono.Cecil; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 7 | { 8 | internal class WhoImplementsInterface : UsageVisitor 9 | { 10 | private readonly Dictionary myInterfaceNames = new Dictionary(); 11 | 12 | public WhoImplementsInterface(UsageQueryAggregator aggregator, TypeDefinition itf) : this(aggregator, new List 13 | { 14 | ThrowIfNull("itf", itf) 15 | }) 16 | { 17 | } 18 | 19 | public WhoImplementsInterface(UsageQueryAggregator aggreator, List interfaces) : base(aggreator) 20 | { 21 | if (interfaces == null || interfaces.Count == 0) 22 | { 23 | throw new ArgumentException("The interfaces collection was null."); 24 | } 25 | 26 | foreach (var type in interfaces) 27 | { 28 | if (!type.IsInterface) 29 | { 30 | throw new ArgumentException(string.Format("The type {0} is not an interface", type.Print())); 31 | } 32 | 33 | this.Aggregator.AddVisitScope(type.Module.Assembly.Name.Name); 34 | if (!this.myInterfaceNames.ContainsKey(type.Name)) 35 | { 36 | this.myInterfaceNames.Add(type.Name, type); 37 | } 38 | } 39 | } 40 | 41 | private bool IsMatchingInterface(TypeReference itf, out TypeDefinition searchItf) 42 | { 43 | if (this.myInterfaceNames.TryGetValue(itf.Name, out searchItf)) 44 | { 45 | if (itf.IsEqual(searchItf, false)) 46 | { 47 | return true; 48 | } 49 | } 50 | 51 | return false; 52 | } 53 | 54 | public override void VisitType(TypeDefinition type) 55 | { 56 | if (type.Interfaces == null) 57 | { 58 | return; 59 | } 60 | 61 | TypeDefinition searchItf = null; 62 | foreach (TypeReference itf in type.Interfaces) 63 | { 64 | if (this.IsMatchingInterface(itf, out searchItf)) 65 | { 66 | var context = new MatchContext(); 67 | context[MatchContext.MatchReason] = "Implements interface"; 68 | context[MatchContext.MatchItem] = searchItf.Print(); 69 | this.Aggregator.AddMatch(type, context); 70 | } 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoInstantiatesType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Mono.Cecil; 5 | using Mono.Cecil.Cil; 6 | 7 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 8 | { 9 | public class WhoInstantiatesType : UsageVisitor 10 | { 11 | private const string ConstructorCalledContext = "Constructor called"; 12 | 13 | private readonly TypeDefinition myType; 14 | 15 | public WhoInstantiatesType(UsageQueryAggregator aggregator, TypeDefinition type) : base(aggregator) 16 | { 17 | if (type == null) 18 | { 19 | throw new ArgumentNullException("type was null"); 20 | } 21 | 22 | this.myType = type; 23 | this.Aggregator.AddVisitScope(type.Module.Assembly.Name.Name); 24 | } 25 | 26 | public override void VisitMethodBody(Mono.Cecil.Cil.MethodBody body) 27 | { 28 | var methodDefinitions = myType.Methods.Where(x => x.IsConstructor).ToList(); 29 | foreach (Instruction instr in body.Instructions) 30 | { 31 | if (instr.OpCode.Code == Code.Newobj) 32 | { 33 | foreach (MethodReference method in methodDefinitions) 34 | { 35 | if (method.IsEqual((MethodReference)instr.Operand, false)) 36 | { 37 | MatchContext context = new MatchContext(ConstructorCalledContext, myType.Print()); 38 | Aggregator.AddMatch(instr, body.Method, true, context); 39 | } 40 | } 41 | } 42 | else if (instr.OpCode.Code == Code.Initobj) 43 | { 44 | TypeReference typeRef = (TypeReference)instr.Operand; 45 | if (typeRef.IsEqual(myType)) 46 | { 47 | MatchContext context = new MatchContext("Struct Constructor() called", myType.Print()); 48 | Aggregator.AddMatch(instr, body.Method, true, context); 49 | } 50 | } 51 | else if (instr.OpCode.Code == Code.Call) 52 | { 53 | // Value types are instantiated by directly calling the corresponding ctor 54 | MethodReference methodRef = (MethodReference)instr.Operand; 55 | if (methodRef.Name == ".ctor") 56 | { 57 | if (methodRef.DeclaringType.IsEqual(myType)) 58 | { 59 | MatchContext context = new MatchContext(ConstructorCalledContext, myType.Print()); 60 | Aggregator.AddMatch(instr, body.Method, false, context); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoReferencesAssembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Mono.Cecil; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 6 | { 7 | public class WhoReferencesAssembly : UsageVisitor 8 | { 9 | private readonly string myAssembly; 10 | 11 | public WhoReferencesAssembly(UsageQueryAggregator aggregator, string fileName) : base(aggregator) 12 | { 13 | if (string.IsNullOrEmpty(fileName)) 14 | { 15 | throw new ArgumentException("File name was null or empty."); 16 | } 17 | 18 | this.myAssembly = Path.GetFileNameWithoutExtension(fileName); 19 | aggregator.AddVisitScope(fileName); 20 | } 21 | 22 | public override void VisitAssemblyReference(AssemblyNameReference assemblyRef, AssemblyDefinition current) 23 | { 24 | if (string.Compare(assemblyRef.Name, this.myAssembly, true) == 0) 25 | { 26 | this.Aggregator.AddMatch(current); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoUsesEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Endjin.Assembly.ChangeDetection.Introspection; 4 | using Mono.Cecil; 5 | using Mono.Cecil.Cil; 6 | 7 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 8 | { 9 | public class WhoUsesEvents : UsageVisitor 10 | { 11 | public const string DefiningAssemblyKey = "Assembly"; 12 | 13 | public const string AddEventReason = "AddEvent"; 14 | 15 | public const string RemoveEventReason = "RemoveEvent"; 16 | 17 | private readonly HashSet myEventNames = new HashSet(); 18 | 19 | private readonly List myEvents; 20 | 21 | public WhoUsesEvents(UsageQueryAggregator aggregator, EventDefinition ev) : this(aggregator, new List 22 | { 23 | ThrowIfNull("ev", ev) 24 | }) 25 | { 26 | } 27 | 28 | public WhoUsesEvents(UsageQueryAggregator aggreagator, List events) : base(aggreagator) 29 | { 30 | if (events == null) 31 | { 32 | throw new ArgumentException("The events list was null."); 33 | } 34 | 35 | this.myEvents = events; 36 | 37 | foreach (var ev in this.myEvents) 38 | { 39 | this.Aggregator.AddVisitScope(ev.AddMethod.DeclaringType.Module.Assembly.Name.Name); 40 | this.myEventNames.Add(ev.AddMethod.Name); 41 | this.myEventNames.Add(ev.RemoveMethod.Name); 42 | } 43 | } 44 | 45 | private string GetPrettyEventName(EventDefinition ev) 46 | { 47 | return string.Format("{0}.{1}", ev.DeclaringType.FullName, ev.Name); 48 | } 49 | 50 | private MatchContext DoesMatch(MethodReference method) 51 | { 52 | MatchContext context = null; 53 | 54 | if (!this.myEventNames.Contains(method.Name)) 55 | { 56 | return context; 57 | } 58 | 59 | foreach (var searchEvent in this.myEvents) 60 | { 61 | if (method.IsEqual(searchEvent.AddMethod, false)) 62 | { 63 | context = new MatchContext(AddEventReason, this.GetPrettyEventName(searchEvent)); 64 | context[DefiningAssemblyKey] = searchEvent.DeclaringType.Module.FullyQualifiedName; 65 | break; 66 | } 67 | 68 | if (method.IsEqual(searchEvent.RemoveMethod, false)) 69 | { 70 | context = new MatchContext(RemoveEventReason, this.GetPrettyEventName(searchEvent)); 71 | context[DefiningAssemblyKey] = searchEvent.DeclaringType.Module.FullyQualifiedName; 72 | break; 73 | } 74 | } 75 | 76 | return context; 77 | } 78 | 79 | public override void VisitMethod(MethodDefinition method) 80 | { 81 | if (method.Body == null) 82 | { 83 | return; 84 | } 85 | 86 | MatchContext context = null; 87 | foreach (Instruction ins in method.Body.Instructions) 88 | { 89 | if (Code.Callvirt == ins.OpCode.Code) // normal instance call 90 | { 91 | context = this.DoesMatch((MethodReference)ins.Operand); 92 | if (context != null) 93 | { 94 | this.Aggregator.AddMatch(ins, method, false, context); 95 | } 96 | } 97 | 98 | if (Code.Call == ins.OpCode.Code) // static function call 99 | { 100 | context = this.DoesMatch((MethodReference)ins.Operand); 101 | if (context != null) 102 | { 103 | this.Aggregator.AddMatch(ins, method, false, context); 104 | } 105 | } 106 | 107 | if (Code.Ldftn == ins.OpCode.Code) // Delegate assignment 108 | { 109 | context = this.DoesMatch((MethodReference)ins.Operand); 110 | if (context != null) 111 | { 112 | this.Aggregator.AddMatch(ins, method, false, context); 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoUsesMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Endjin.Assembly.ChangeDetection.Infrastructure; 4 | using Endjin.Assembly.ChangeDetection.Introspection; 5 | using Mono.Cecil; 6 | using Mono.Cecil.Cil; 7 | 8 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 9 | { 10 | public class WhoUsesMethod : UsageVisitor 11 | { 12 | private const MethodPrintOption myMethodFormat = MethodPrintOption.ReturnType | MethodPrintOption.ShortNames | MethodPrintOption.Parameters; 13 | 14 | private static TypeHashes myType = new TypeHashes(typeof(WhoUsesMethod)); 15 | 16 | // Do fast compares with Hashsets instead of string comparisons 17 | private readonly Dictionary> myMethodNames = new Dictionary>(); 18 | 19 | public WhoUsesMethod(UsageQueryAggregator aggregator, List methods) : base(aggregator) 20 | { 21 | if (methods == null) 22 | { 23 | throw new ArgumentNullException("The method list to query for was null."); 24 | } 25 | 26 | foreach (var method in methods) 27 | { 28 | this.Aggregator.AddVisitScope(method.DeclaringType.Module.Assembly.Name.Name); 29 | List typeMethods = null; 30 | if (!this.myMethodNames.TryGetValue(method.DeclaringType.Name, out typeMethods)) 31 | { 32 | typeMethods = new List(); 33 | this.myMethodNames[method.DeclaringType.Name] = typeMethods; 34 | } 35 | this.myMethodNames[method.DeclaringType.Name].Add(method); 36 | } 37 | } 38 | 39 | private bool IsMatchingMethod(MethodReference methodReference, out MethodDefinition matchingMethod) 40 | { 41 | var lret = false; 42 | matchingMethod = null; 43 | 44 | var declaringType = ""; 45 | if (methodReference != null && methodReference.DeclaringType != null && methodReference.DeclaringType.GetElementType() != null) 46 | { 47 | declaringType = methodReference.DeclaringType.GetElementType().Name; 48 | } 49 | 50 | List typeMethods = null; 51 | if (this.myMethodNames.TryGetValue(declaringType, out typeMethods)) 52 | { 53 | foreach (var searchMethod in typeMethods) 54 | { 55 | if (methodReference.IsEqual(searchMethod, false)) 56 | { 57 | lret = true; 58 | matchingMethod = searchMethod; 59 | break; 60 | } 61 | } 62 | } 63 | 64 | return lret; 65 | } 66 | 67 | public override void VisitMethod(MethodDefinition method) 68 | { 69 | if (method.Body == null) 70 | { 71 | return; 72 | } 73 | 74 | MethodDefinition matchingMethod = null; 75 | foreach (Instruction ins in method.Body.Instructions) 76 | { 77 | if (Code.Callvirt == ins.OpCode.Code) // normal instance call 78 | { 79 | if (this.IsMatchingMethod((MethodReference)ins.Operand, out matchingMethod)) 80 | { 81 | var context = new MatchContext("Called method", matchingMethod.Print(myMethodFormat)); 82 | context["Type"] = matchingMethod.DeclaringType.FullName; 83 | this.Aggregator.AddMatch(ins, method, false, context); 84 | } 85 | } 86 | 87 | if (Code.Call == ins.OpCode.Code) // static function call 88 | { 89 | if (this.IsMatchingMethod((MethodReference)ins.Operand, out matchingMethod)) 90 | { 91 | var context = new MatchContext("Called method", matchingMethod.Print(myMethodFormat)); 92 | context["Type"] = matchingMethod.DeclaringType.FullName; 93 | this.Aggregator.AddMatch(ins, method, false, context); 94 | } 95 | } 96 | 97 | if (Code.Ldftn == ins.OpCode.Code) // Load Function Pointer for delegate call 98 | { 99 | if (this.IsMatchingMethod((MethodReference)ins.Operand, out matchingMethod)) 100 | { 101 | var context = new MatchContext("Called method", matchingMethod.Print(myMethodFormat)); 102 | context["Type"] = matchingMethod.DeclaringType.FullName; 103 | this.Aggregator.AddMatch(ins, method, false, context); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Query/UsageQueries/WhoUsesStringConstant.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | 5 | namespace Endjin.Assembly.ChangeDetection.Query.UsageQueries 6 | { 7 | /// 8 | /// Search for the ldstr opcode which loads a string constant and check if the string constant matches 9 | /// the search string. 10 | /// 11 | public class WhoUsesStringConstant : UsageVisitor 12 | { 13 | private readonly bool mybExatchMatch; 14 | 15 | private readonly StringComparison myComparisonMode; 16 | 17 | private readonly string mySearchString; 18 | 19 | public WhoUsesStringConstant(UsageQueryAggregator aggregator, string searchString, bool bExactMatch, StringComparison compMode) : base(aggregator) 20 | { 21 | if (string.IsNullOrEmpty(searchString)) 22 | { 23 | throw new ArgumentException("The search string was null or empty"); 24 | } 25 | 26 | this.mySearchString = searchString; 27 | this.mybExatchMatch = bExactMatch; 28 | this.myComparisonMode = compMode; 29 | } 30 | 31 | public WhoUsesStringConstant(UsageQueryAggregator aggregator, string searchString) : this(aggregator, searchString, false, StringComparison.OrdinalIgnoreCase) 32 | { 33 | } 34 | 35 | public WhoUsesStringConstant(UsageQueryAggregator aggregator, string searchString, bool bExactMatch) : this(aggregator, searchString, bExactMatch, StringComparison.OrdinalIgnoreCase) 36 | { 37 | } 38 | 39 | public override void VisitMethodBody(MethodBody body) 40 | { 41 | base.VisitMethodBody(body); 42 | foreach (Instruction instruction in body.Instructions) 43 | { 44 | if (instruction.OpCode.Code == Code.Ldstr) 45 | { 46 | var str = (string)instruction.Operand; 47 | if (this.IsMatch(str)) 48 | { 49 | this.AddMatch(instruction, body, str); 50 | } 51 | } 52 | } 53 | } 54 | 55 | private bool IsMatch(string value) 56 | { 57 | var lret = false; 58 | 59 | if (this.mybExatchMatch) 60 | { 61 | if (string.Compare(value, this.mySearchString, this.myComparisonMode) == 0) 62 | { 63 | lret = true; 64 | } 65 | } 66 | else 67 | { 68 | if (value.IndexOf(this.mySearchString, this.myComparisonMode) != -1) 69 | { 70 | lret = true; 71 | } 72 | } 73 | 74 | return lret; 75 | } 76 | 77 | public override void VisitField(FieldDefinition field) 78 | { 79 | base.VisitField(field); 80 | 81 | if (field.HasConstant) 82 | { 83 | var stringValue = field.Constant as string; 84 | if (stringValue != null && this.IsMatch(stringValue)) 85 | { 86 | var context = new MatchContext("String Match", this.mySearchString); 87 | context["String"] = stringValue; 88 | this.Aggregator.AddMatch(field, context); 89 | } 90 | } 91 | } 92 | 93 | private void AddMatch(Instruction ins, MethodBody body, string matchedString) 94 | { 95 | var context = new MatchContext("String Match", this.mySearchString); 96 | context["String"] = matchedString; 97 | this.Aggregator.AddMatch(ins, body.Method, false, context); 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Rules/BreakingChangeRule.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Endjin.Assembly.ChangeDetection.Diff; 3 | 4 | namespace Endjin.Assembly.ChangeDetection.Rules 5 | { 6 | #region Using Directives 7 | 8 | 9 | 10 | #endregion 11 | 12 | public class BreakingChangeRule : IRule 13 | { 14 | public bool Detect(AssemblyDiffCollection assemblyDiffCollection) 15 | { 16 | if (assemblyDiffCollection.AddedRemovedTypes.RemovedCount > 0) 17 | { 18 | return true; 19 | } 20 | 21 | if (assemblyDiffCollection.ChangedTypes.Count > 0) 22 | { 23 | foreach (var typeChange in assemblyDiffCollection.ChangedTypes) 24 | { 25 | if (typeChange.HasChangedBaseType) 26 | { 27 | return true; 28 | } 29 | 30 | if (typeChange.Interfaces.Count > 0) 31 | { 32 | if (typeChange.Interfaces.Removed.Any()) 33 | { 34 | return true; 35 | } 36 | } 37 | 38 | if (typeChange.Events.Removed.Any()) 39 | { 40 | return true; 41 | } 42 | 43 | if (typeChange.Fields.Removed.Any()) 44 | { 45 | return true; 46 | } 47 | 48 | if (typeChange.Methods.Removed.Any()) 49 | { 50 | return true; 51 | } 52 | } 53 | } 54 | 55 | return false; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/Rules/IRule.cs: -------------------------------------------------------------------------------- 1 | using Endjin.Assembly.ChangeDetection.Diff; 2 | 3 | namespace Endjin.Assembly.ChangeDetection.Rules 4 | { 5 | public interface IRule 6 | { 7 | bool Detect(AssemblyDiffCollection assemblyDiffCollection); 8 | } 9 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/SemVer/AnalysisResult.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.Assembly.ChangeDetection.SemVer 2 | { 3 | public class AnalysisResult 4 | { 5 | public string VersionNumber { get; set; } 6 | 7 | public bool BreakingChangesDetected { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/SemVer/SemanticVersionAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Endjin.Assembly.ChangeDetection.Infrastructure; 3 | using Endjin.Assembly.ChangeDetection.Rules; 4 | using Semver; 5 | 6 | namespace Endjin.Assembly.ChangeDetection.SemVer 7 | { 8 | #region Using Directives 9 | 10 | 11 | 12 | #endregion 13 | 14 | public class SemanticVersionAnalyzer 15 | { 16 | public AnalysisResult Analyze(string previousAssembly, string currentAssembly, string proposedVersionNumber) 17 | { 18 | var differ = new DiffAssemblies(); 19 | 20 | var previous = new FileQuery(previousAssembly); 21 | var current = new FileQuery(currentAssembly); 22 | 23 | var differences = differ.Execute(new List 24 | { 25 | previous 26 | }, 27 | new List 28 | { 29 | current 30 | }); 31 | 32 | var rule = new BreakingChangeRule(); 33 | 34 | var breakingChange = rule.Detect(differences); 35 | 36 | if (breakingChange) 37 | { 38 | var semVer = SemVersion.Parse(proposedVersionNumber); 39 | 40 | var decidedVersionNumber = semVer.Change(semVer.Major + 1, 0, 0); 41 | 42 | proposedVersionNumber = decidedVersionNumber.ToString(); 43 | } 44 | 45 | return new AnalysisResult 46 | { 47 | BreakingChangesDetected = breakingChange, 48 | VersionNumber = proposedVersionNumber 49 | }; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Solutions/Endjin.Assembly.ChangeDetection/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AssemblyAttributeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity 2 | { 3 | #region Using Directives 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | 10 | #endregion 11 | 12 | public static class AssemblyAttributeExtensions 13 | { 14 | public static T GetAssemblyAttribute(this Assembly assembly) where T : Attribute 15 | { 16 | object[] attributes = assembly.GetCustomAttributes(typeof(T), false); 17 | 18 | if (attributes.Length == 0) 19 | { 20 | return null; 21 | } 22 | 23 | return attributes.OfType().SingleOrDefault(); 24 | } 25 | 26 | public static IEnumerable GetAssemblyAttributes(this Assembly assembly) 27 | { 28 | var attribues = assembly.GetCustomAttributes(false); 29 | 30 | return from attribute in attribues where attribute.GetType() != typeof(AssemblyConfigurationAttribute) && 31 | attribute.GetType() != typeof(AssemblyFileVersionAttribute) && 32 | attribute.GetType() != typeof(AssemblyInformationalVersionAttribute) && 33 | attribute.GetType() != typeof(AssemblyVersionAttribute) select attribute as Attribute; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/AssemblyCompanyAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | using System.CodeDom; 4 | using System.Reflection; 5 | 6 | public class AssemblyCompanyAttributeConverter 7 | { 8 | public CodeAttributeDeclaration Convert(AssemblyCompanyAttribute attribute) 9 | { 10 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Company))); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/AssemblyConfigurationAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Reflection; 7 | 8 | #endregion 9 | 10 | public class AssemblyConfigurationAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(AssemblyConfigurationAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Configuration))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/AssemblyCopyrightAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Reflection; 7 | 8 | #endregion 9 | 10 | public class AssemblyCopyrightAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(AssemblyCopyrightAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Copyright))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/AssemblyDescriptionAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Reflection; 7 | 8 | #endregion 9 | 10 | public class AssemblyDescriptionAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(AssemblyDescriptionAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Description))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/AssemblyProductAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Reflection; 7 | 8 | #endregion 9 | 10 | public class AssemblyProductAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(AssemblyProductAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Product))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/AssemblyTitleAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Reflection; 7 | 8 | #endregion 9 | 10 | public class AssemblyTitleAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(AssemblyTitleAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Title))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/AssemblyTrademarkAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Reflection; 7 | 8 | #endregion 9 | 10 | public class AssemblyTrademarkAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(AssemblyTrademarkAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Trademark))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/ComVisibleAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Runtime.InteropServices; 7 | 8 | #endregion 9 | 10 | public class ComVisibleAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(ComVisibleAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Value))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/CompilationRelaxationsAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Runtime.CompilerServices; 7 | 8 | #endregion 9 | 10 | public class CompilationRelaxationsAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(CompilationRelaxationsAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.CompilationRelaxations))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/DebuggableAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Diagnostics; 7 | 8 | #endregion 9 | 10 | public class DebuggableAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(DebuggableAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.DebuggingFlags))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/GuidAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Runtime.InteropServices; 7 | 8 | #endregion 9 | 10 | public class GuidAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(GuidAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.Value))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/RuntimeCompatibilityAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Runtime.CompilerServices; 7 | 8 | #endregion 9 | 10 | public class RuntimeCompatibilityAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(RuntimeCompatibilityAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType())); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/AttributeConverters/TargetFrameworkAttributeConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity.AttributeConverters 2 | { 3 | #region Using Directives 4 | 5 | using System.CodeDom; 6 | using System.Runtime.Versioning; 7 | 8 | #endregion 9 | 10 | public class TargetFrameworkAttributeConverter 11 | { 12 | public CodeAttributeDeclaration Convert(TargetFrameworkAttribute attribute) 13 | { 14 | return new CodeAttributeDeclaration(new CodeTypeReference(attribute.GetType()), new CodeAttributeArgument(new CodePrimitiveExpression(attribute.FrameworkName))); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/CommandLineProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity 2 | { 3 | #region Using Directives 4 | 5 | using System; 6 | 7 | using global::CommandLine; 8 | 9 | #endregion 10 | 11 | public class CommandLineProcessor 12 | { 13 | public CommandOptions Process(string[] args) 14 | { 15 | var options = new CommandOptions(); 16 | var parser = new Parser(); 17 | 18 | if (!parser.ParseArguments(args, options)) 19 | { 20 | throw new InvalidOperationException(options.GetUsage()); 21 | } 22 | 23 | return options; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/CommandOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Endjin.SemanticVersioning.TeamCity 2 | { 3 | #region Using Directives 4 | 5 | using System; 6 | 7 | using CommandLine; 8 | using CommandLine.Text; 9 | 10 | #endregion 11 | 12 | public class CommandOptions 13 | { 14 | [Option('p', "previous assembly", HelpText = "Help Text")] 15 | public string PreviousAssembly { get; set; } 16 | 17 | [Option('c', "current assembly", HelpText = "Help Text")] 18 | public string CurrentAssembly { get; set; } 19 | 20 | [Option('v', "proposed version number", HelpText = "Help Text")] 21 | public string ProposedVersionNumber { get; set; } 22 | 23 | [Option('o', "output file path", HelpText = "Help Text")] 24 | public string OutputFile { get; set; } 25 | 26 | [HelpOption(HelpText = "Display this help text.")] 27 | public string GetUsage() 28 | { 29 | var help = new HelpText(new HeadingInfo("Semantic Versioning", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString())) 30 | { 31 | AdditionalNewLineAfterOption = false, 32 | MaximumDisplayWidth = Console.WindowWidth, 33 | Copyright = new CopyrightInfo("Endjin", DateTime.Now.Year) 34 | }; 35 | 36 | help.AddPreOptionsLine("Usage:"); 37 | help.AddPreOptionsLine(@" Endjin.SemanticVersioning.TeamCity.exe -o \foo.final.dll -p \foo.dll -c \foo.dll -v '2.0.0'"); 38 | help.AddOptions(this); 39 | 40 | return help; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/Endjin.SemanticVersioning.TeamCity.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {3206A0CB-2F90-4D0A-AC79-3F6E39990F8E} 8 | Exe 9 | Properties 10 | Endjin.SemanticVersioning.TeamCity 11 | Endjin.SemanticVersioning.TeamCity 12 | v4.5.1 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\..\Packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll 38 | True 39 | 40 | 41 | ..\..\Packages\ilmerge.2.14.1208\tools\ILMerge.exe 42 | False 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {a7a8d13c-9909-470b-9c83-eb9795dfb0ac} 77 | Endjin.Assembly.ChangeDetection 78 | 79 | 80 | 81 | 88 | -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/Program.cs: -------------------------------------------------------------------------------- 1 | using Endjin.Assembly.ChangeDetection.SemVer; 2 | 3 | namespace Endjin.SemanticVersioning.TeamCity 4 | { 5 | #region Using Directives 6 | 7 | using System; 8 | using ILMerging; 9 | 10 | #endregion 11 | 12 | public class Program 13 | { 14 | private static void Main(string[] args) 15 | { 16 | try 17 | { 18 | var commandLineProcessor = new CommandLineProcessor(); 19 | var options = commandLineProcessor.Process(args); 20 | 21 | var semanticVersionAnalyzer = new SemanticVersionAnalyzer(); 22 | var result = semanticVersionAnalyzer.Analyze(options.PreviousAssembly, options.CurrentAssembly, options.ProposedVersionNumber); 23 | 24 | if (result.BreakingChangesDetected) 25 | { 26 | var dynamicAssembly = new CodeGenerator().GenerateVersionDetailsDynamicAssembly(options.CurrentAssembly, result.VersionNumber, result.VersionNumber, result.VersionNumber); 27 | 28 | var ilMerge = new ILMerge 29 | { 30 | TargetKind = ILMerge.Kind.Dll, 31 | AttributeFile = dynamicAssembly, 32 | OutputFile = options.OutputFile 33 | }; 34 | 35 | ilMerge.SetInputAssemblies(new[] { options.CurrentAssembly } ); 36 | ilMerge.Merge(); 37 | 38 | Console.WriteLine("##teamcity[buildNumber '" + result.VersionNumber + "']"); 39 | } 40 | } 41 | catch (Exception exception) 42 | { 43 | var aggregateException = exception as AggregateException; 44 | if (aggregateException != null) 45 | { 46 | foreach (var e in aggregateException.InnerExceptions) 47 | { 48 | Console.WriteLine(e.Message); 49 | } 50 | } 51 | else 52 | { 53 | Console.WriteLine(exception.Message); 54 | } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Endjin.SemanticVersioning.TeamCity")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Endjin.SemanticVersioning.TeamCity")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("beedb238-b678-4984-9ab1-ed248bf66fd9")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Solutions/Endjin.SemanticVersioning.TeamCity/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Solutions/NonBreakingAdditiveChange/Class1.cs: -------------------------------------------------------------------------------- 1 | namespace Original 2 | { 3 | public class Class1 4 | { 5 | public void MyTestMethod() 6 | { 7 | } 8 | 9 | public void MyNonBreakingAddativeChangeMethod() 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Solutions/NonBreakingAdditiveChange/NonBreakingAdditiveChange.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {85164719-6220-4167-8C52-E0406CDE120F} 8 | Library 9 | Properties 10 | Original 11 | Original 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /Solutions/NonBreakingAdditiveChange/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("NonBreakingAdditiveChange")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NonBreakingAdditiveChange")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d1c18535-016c-4434-94d7-557eb3e0b531")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Solutions/NonBreakingAdditiveChange2/Class1.cs: -------------------------------------------------------------------------------- 1 | namespace Original2 2 | { 3 | public class Class1 4 | { 5 | public void MyTestMethod() 6 | { 7 | } 8 | 9 | public void MyNonBreakingAddativeChangeMethod() 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Solutions/NonBreakingAdditiveChange2/NonBreakingAdditiveChange2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0934CB7D-F546-48BF-A45B-77B66E5FE28F} 8 | Library 9 | Properties 10 | Original2 11 | Original2 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /Solutions/NonBreakingAdditiveChange2/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("NonBreakingAdditiveChange")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NonBreakingAdditiveChange")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d1c18535-016c-4434-94d7-557eb3e0b531")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Solutions/NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Solutions/Original/Class1.cs: -------------------------------------------------------------------------------- 1 | namespace Original 2 | { 3 | public class Class1 4 | { 5 | public void MyTestMethod() 6 | { 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Solutions/Original/Original.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D78B0500-809E-471B-933A-C48509B9C57C} 8 | Library 9 | Properties 10 | Original 11 | Original 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /Solutions/Original/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Original")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Original")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e1d7cff6-af04-4e71-ba06-39a80c0fd276")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Solutions/Original2/Class1.cs: -------------------------------------------------------------------------------- 1 | namespace Original2 2 | { 3 | public class Class1 4 | { 5 | public void MyTestMethod() 6 | { 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Solutions/Original2/Original2.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 9780b210-8fea-4c55-90a6-8c2a908979f1 8 | Library 9 | Properties 10 | Original2 11 | Original2 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Solutions/Original2/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Original2")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Original2")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9780b210-8fea-4c55-90a6-8c2a908979f1")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | --------------------------------------------------------------------------------