PostSharp + Code Analysis = Abomination
(If you just want the targets files, click here.)
Are you using PostSharp for your .NET development? Do you get frustrated when code analysis within VSTS reports warnings like these:

Now PostSharp is a superb AOP tool for .NET, but it is annoying to see these issues in code analysis again and again; what would be great is if we can edit the build process to make Code Analysis (CA) run before PostSharp processes the assembly. That way, code analysis errors on PostSharp-generated code is ignored, and everyone is happy. ![]()
It can take a while to parse through all the targets in Microsoft.Common.targets, Microsoft.CodeAnalysis.targets, and PostSharp-1.0.targets, but here’s the general order of what’s happening with a fresh PostSharp installation.
- …
- Compile
- …
- CoreCompile (Delegated to a specific language’s targets file; for example, C# runs csc.exe at this point.)
- …
- PostSharpInspectConstants
- PostSharpInspectReferences
- PostSharp (Where the actual post-compilation is done)
- …
- PrepareForRun (After build, but before running the application)
- CopyFilesToOutputDirectory
- RunCodeAnalysis
- …
So there are obviously a couple of steps between the PostSharp processing and CA. When building the project, the CoreCompile target will place the newly built assembly in the obj\$(Configuration)\ folder. Afterwards, when PostSharp runs, it compiles a new assembly with PDBs in the obj\$(Configuration)\PostSharp\ folder. Finally, that is copied to the obj\$(Configuration)\ folder, which overwrites what the CoreCompile task created.
CA waits until the files are copied to the $(OutDir) directory (which is by default bin\$(Configuration)\) before executing. Thus it would be great if we could now change the order of things to do the following when PostSharp is enabled:
- …
- Compile
- …
- CoreCompile (Delegated to a specific language’s targets file; for example, C# runs csc.exe at this point.)
- …
- RunCodeAnalysis
- PostSharpInspectConstants
- PostSharpInspectReferences
- PostSharp
- …
- PrepareForRun
- CopyFilesToOutputDirectory
- …
So the next question is how do we do that?
The first thing is we need to find the targets files for both CA and PostSharp. The former is located at %PROGRAMFILES%\MSBuild\Microsoft\VisualStudio\v9.0\CodeAnalysis\Microsoft.CodeAnalysis.targets, and the latter is located at %PROGRAMFILES%\MSBuild\PostSharp\PostSharp-1.0.targets. At the end of the PostSharp targets file, you can see that it inserts itself into the chain of compilation targets I described above.
<PropertyGroup>
<CompileDependsOn>
$(CompileDependsOn);
PostSharpInspectConstants;
PostSharpInspectReferences;
PostSharp
</CompileDependsOn>
<BuildDependsOn>
$(BuildDependsOn);
PostSharpVerify
</BuildDependsOn>
</PropertyGroup>
Now perhaps we can trick code analysis into running before PostSharp just by re-ordering the dependencies, like this:
<PropertyGroup>
<CompileDependsOn>
$(CompileDependsOn);
RunCodeAnalysis;
PostSharpInspectConstants;
PostSharpInspectReferences;
PostSharp
</CompileDependsOn>
<BuildDependsOn>
$(BuildDependsOn);
PostSharpVerify
</BuildDependsOn>
</PropertyGroup>
So we’ve said to run code analysis before the PostSharp processing. Unfortunately, this doesn’t work, as MSBuild reports the following error:

If we navigate to the Microsoft.CodeAnalysis.targets file, we can see that the RunCodeAnalysis target depends on the Compile target. But because we also overrode the CompileDependsOn property to include RunCodeAnalysis, we have two targets that are dependent on each other to run.
The next trick is to change what CodeAnalysis is dependent on. To do this, we need to change its semantics; instead of performing analysis on the $(OutDir)\$(TargetName)$(TargetExt) file (e.g. bin\Debug\Target.dll), we need to perform analysis on the obj\$(Configuration)\$(TargetName)$(TargetExt) file (e.g. obj\Debug\Target.dll). So instead of being dependent on Compile, the target is dependent on the step that generates the obj\Debug\Target.dll file: CoreCompile.
<Target
Name=“RunCodeAnalysis“
Condition=“‘$(RunCodeAnalysis)’==’true’ or ‘$(RunCodeAnalysisOnce)’==’true’“
Inputs=“$(CodeAnalysisInputAssemblyForTask)“
Outputs=“$(CodeAnalysisLogFileForTask);$(CodeAnalysisSucceededFile)“
DependsOnTargets=“CoreCompile;SetCodeAnalysisProperties“
>
We’re almost done. Unfortunately, now CodeAnalysis runs at the correct point, but it doesn’t run on the correct file. Remember we want it to perform analysis on the obj\$(Configuration)\ assembly before PostSharp has overwritten it. So how does Code Analysis know where to retrieve the assembly? Well, if we open the Microsoft.CodeAnalysis.targets file again and have a look at the properties, we can see:
<CodeAnalysisInputAssemblyForTask Condition=“‘$(CodeAnalysisInputAssembly)’!=”“>$(CodeAnalysisInputAssembly)</CodeAnalysisInputAssemblyForTask>
<CodeAnalysisInputAssemblyForTask Condition=“‘$(CodeAnalysisInputAssembly)’==”“>$(OutDir)$(TargetName)$(TargetExt)</CodeAnalysisInputAssemblyForTask>
<CodeAnalysisLogFileForTask Condition=“‘$(CodeAnalysisLogFile)’!=”“>$(CodeAnalysisLogFile)</CodeAnalysisLogFileForTask>
<CodeAnalysisLogFileForTask Condition=“‘$(CodeAnalysisLogFile)’==”“>$(CodeAnalysisInputAssemblyForTask).CodeAnalysisLog.xml</CodeAnalysisLogFileForTask>
So what this tells us is that the CodeAnalysisInputAssemblyForTask and CodeAnalysisLogFileForTask files can be overridden by a project file or another targets file by specifying either property in a <PropertyGroup> tag, like this:
<PropertyGroup>
<CodeAnalysisInputAssemblyForTask>obj\$(Configuration)\$(TargetName)$(TargetExt)</CodeAnalysisInputAssemblyForTask>
<CodeAnalysisLogFileForTask>$(CodeAnalysisInputAssemblyForTask).CodeAnalysisLog.xml</CodeAnalysisLogFileForTask>
</PropertyGroup>
When placed in a project file, this will instruct Code Analysis to analyze the assembly that we want and to produce the log file in the correct directory. This is a bit of a hassle when you have to change every single project, and there may be some cases where you want to keep the default behavior of Code Analysis (i.e. point at the bin\Debug folder when PostSharp is not run). There is one last tweak we can make to automate this change across all projects. Open up the PostSharp targets file and scroll down to the PropertyGroup that defines the CompileDependsOn property. Overwrite that single property group with the following:
<!– Introduces PostSharp in the chain of compilation targets –>
<PropertyGroup Condition=“‘$(RunCodeAnalysis)’==’true’ or ‘$(RunCodeAnalysisOnce)’==’true’“>
<CompileDependsOn>
$(CompileDependsOn);
RunCodeAnalysis;
PostSharpInspectConstants;
PostSharpInspectReferences;
PostSharp
</CompileDependsOn>
<CodeAnalysisInputAssemblyForTask>obj\$(Configuration)\$(TargetName)$(TargetExt)</CodeAnalysisInputAssemblyForTask>
<CodeAnalysisLogFileForTask>$(CodeAnalysisInputAssemblyForTask).CodeAnalysisLog.xml</CodeAnalysisLogFileForTask>
</PropertyGroup>
<PropertyGroup Condition=“‘$(RunCodeAnalysis)’!=’true’ and ‘$(RunCodeAnalysisOnce)’!=’true’“>
<CompileDependsOn>
$(CompileDependsOn);
PostSharpInspectConstants;
PostSharpInspectReferences;
PostSharp
</CompileDependsOn>
</PropertyGroup>
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
PostSharpVerify
</BuildDependsOn>
</PropertyGroup>
This final edit makes the targets a little more robust in that we (1) schedule Code Analysis to run before PostSharp only when CA is enabled and we (2) edit the CA Input Assembly property to target the desired location.
Here are the final targets files (Be sure to rename them to .targets instead of .xml):
PostSharp-1.0 Targets - %PROGRAMFILES%\MSBuild\PostSharp
Code Analysis VSTS 9.0 Targets - %PROGRAMFILES%\MSBuild\Microsoft\VisualStudio\v9.0\CodeAnalysis
Syndication
February 24th, 2008 at 9:29 am
[...] release candidate of PostSharp, an Aspect-Oriented Programming solution for .NET. In my previous post on PostSharp, I discussed a way to work around some pesky Code Analysis errors that PostSharp [...]