Is it possible to hook into the Roslyn build process during a Visual Studio/TFS build, and if yes, is it possible to get a hold of the Microsoft.CodeAnalysis.Solution/Microsoft.CodeAnalysis.Project instance being used by Roslyn during compilation?
The way I see it, Jon is pretty much right in his comment. What I would suggest is to create an MSBuild task, which is your wanted hook into the build process.
Create an MSBuild project file (you've probably seen them already, it's those files that have the .targets extension). It looks something like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="MyNamespace.MyReferenceValidationTask" AssemblyFile="MyPath\MyNamespace.dll"/>
<Target
BeforeTargets="BeforeCompile"
Name="ValidationTarget">
<MyNamespace.MyReferenceValidationTask
SolutionRoot="$(SolutionDir)" />
</Target>
</Project>
The attribute in the "MyNamespace.MyReferenceValidationTask" tag "SolutionRoot" is your property in your task. All the macros that are available in Visual Studio are also available here. (see this post here: https://stackoverflow.com/a/1453023/978594)
What you do inside of the task is entirely up to you. You can, for example, load your solution file with Roslyn and thus have all the projects and their references and do your desired validation.
Related
After creating a new .NET Standard class library project I'd like override the BeforeBuild msbuild task. Previously, I'd unload and edit the .csproj file and simply follow the instructions To modify your build process, add your task inside one of the targets below and uncomment it. and add my logic to the template there. The new .NET Standard project contains no such instructions.
Simply adding the task where it used to be has no effect. The reason is has no effect can be seen by inspecting the output of dotnet msbuild /pp run in the project directory. The task I added is redeclared later in the unified output which, I assume, overrides my task as no checks seem to be made to see if the task already exists.
The documentation I found here and here do not cover msbuild extensions of the project file.
The behaviour changed.
<Project Sdk="Microsoft.NET.Sdk">
...
</Project>
is expanded to
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
...
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>
If you want to override BeforeBuild, you will have to manually import the Sdk props and targets, but this is not recommended. The recommended solution is creating a target with BeforeTargets="Build".
EDIT: There's a discussion about BeforeBuild and AfterBuild on Sdk based projects here: https://github.com/Microsoft/msbuild/issues/1680
Working on a very large C# project with multiple subportions. One of these portions creates and places dlls in a specific location for consumption. As per a recent change we're now trying to place these dlls with every root build call made. (Use to be manually placed every milestone or so)
So I'm referencing the dirs.proj file that is compiling the subdirectory which creates and places the dlls. That all works fine. The problem is that for one reason or another other portions of the project start to look for these dlls before that part has finished compiling.
How can I ensure that this part of the project gets compiled and places the dlls before beginning to compile the rest of it? I have a very brief understanding of and but really don't know how to use them to do what I want.
Thanks for any and all help!
Read up on Build Targets and the MakeDir task.
You'll want to include the logic creating the new directory in a task that is scheduled to occur prior to the default Build task.
<Target BeforeTargets="Build" Name="CreateRequiredFolder" Condition="!EXIST('$(MyDirectory)')" >
<MakeDir Directories="$(MyDirectory)" />
</Target>
Note how we use BeforeTargets to schedule this before Build, and the condition on the target causes it to be skipped if the desired folder already exists.
Here is what I ended up doing to get it to work.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="......." InitialTargets="BuildWebshared">
<ItemGroup>
<WebsharedProjects Include="..\location\dirs.proj" />
</ItemGroup>
<Target Name="BuildWebshared">
<MSBuild
Projects="#(WebsharedProjects)"
Targets="Build">
</MSBuild>
</Target>
....
after applying the suggested changes from this answer my buildserver is unable to build the affected solutions.
The problem is that the .NET 4.0 and the .NET 4.5 projects are compiled after eachother.
As an example let's assume that I've 4 projects: a.csproj, b.csproj, c.csproj. All projects have a .NET 4.0 and a .NET 4.5 version, meaning that I've a.csproj, a_40.csproj, b.csproj, b_40.csproj, c.csproj and c_40.csproj. Some of those projects depend on another project inside the solution. So for example b*.csproj depends on a*.csproj. Also the output library of a.csproj and a_40.csproj have the same name: a.dll.
Now instead of going the sane way and compiling all 4.0 assemblies and then all 4.5 assemblies, TFS, as usual, chooses the insane way and builds everything in a completely randomized order while using the same output folder for all projects. Which of course fails.
I guess there are at least two solutions to that problem:
Instead of throwing everything in the same build folder, use one folder for each project or at least one per .NET version. (as Visual Studio does)
Force the TFS to build the projects in a specific sequence (all .NET 4.0 assemblies first for example).
The problem: I've no idea how to do that.
My question: Can someone show me how to make either of my two suggested solutions work or show me another solution to this problem?
Found the solution: https://stackoverflow.com/a/1027551/937093
By adding two build configurations to all affected solutions (one for building the .NET 4 projects and one for building the .NET 4.5 projects) and creating two builddefinitions in TFS I got the thing working the way I wanted it.
I don't use TFS as build machine (I prefer Jenkins or other CI tool), but all servers allows you to create your own MSBuild Script to do your builds. A .csproj IS a MSBuild script.
If you create your own script you can setup your preferred order. Anyway, using .sln usually solves the dependencies correctly.
If you want to dig deeper in MSBuild, take a look at the reference: http://msdn.microsoft.com/en-us/library/0k6kkbsd.aspx
A sample script would look like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TextToSay>This is a property</TextToSay>
</PropertyGroup>
<ItemGroup>
<Binaries Include="$(MSBuildProjectDirectory)\..\MSBuildTests\**\*.dll" />
</ItemGroup>
<UsingTask TaskName="GenerateDumbFiles" AssemblyFile="$(MSBuildProjectDirectory)\..\MSBuildTests\MSBuildTests.Tasks\bin\Debug\MSBuildTests.Tasks.dll" />
<Target Name="SaySomething">
<Message Text="$(TextToSay)" />
</Target>
<Target Name="Build">
<Delete Files="#(Binaries)" />
<MSBuild Projects="$(MSBuildProjectDirectory)\..\MSBuildTests\MSBuildTests.sln" />
<MakeDir Directories="$(MSBuildProjectDirectory)\..\dumbfiles" />
<GenerateDumbFiles Directory="$(MSBuildProjectDirectory)\..\dumbfiles" Prefix="DumbFile" Count="100" />
</Target>
<Target Name="TransformLog">
<XslTransformation XmlInputPaths="$(MSBuildProjectDirectory)\msbuild-output.xml" XslInputPath="$(MSBuildProjectDirectory)\msbuild.xsl" OutputPaths="$(MSBuildProjectDirectory)\log.html" />
</Target>
The MSBuild task is used to compile stuff in .NET.
I have a solution which contains a lot of C# projects, how can I change the configuration of all projects very quickly, like I want to change the output folder from bin to MyBin. I know C++ property sheet can do the similar thing but C# doesn't have property sheet.
You can use a common 'partial' project file to store common stuff.
Move all the stuff that you want to be changed simultaneously into a stand-alone .proj file, e.g. common.proj:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath>Debug</OutputPath>
<Platform>AnyCPU</Platform>
</PropertyGroup>
</Project>
Than use msbuild import declaration to 'include' common part into every project in your solution:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="common.proj" />
</Project>
MsBuild imports work more or less in the same manner as C++ includes: before a project is built all the import directives are replaced with the content of the imported file. Now you can change your common properties just in one file - common.proj
There's one important thing to mention: VS caches included project files, so you'd have to reload all the projects after applying a change to common.proj file (so I suggest building from command-line when you actively change commom.proj)
We use this approach to manage code-analysis settings (as they are supposed to be the same across all the projects in the solution).
You have to define new Build Configuration, which can be copied from Release or from Debug or constructed all manually. After you can customize for every project it's option in regard of that custom build you just created and you done. This is done, naturally, only once. After, whenever you choose that custom build all properties chosen for every single project in solution will be set with the properties you want.
Rightclick on ALL projects in the solution, one-by-one.
Click 'unload' on each
then click "edit XXX.csproj" again on all of them
You will now have many XMLs opened as text files.
Now use the Find&Replace (control-shift-H if you use default shortcuts), and set:
Find what: OutputPath>bin\Debug</OutputPath
replace with: MyBin\Debug
Look in: All open documents
and hit Replace All.
Then do the same with bin\Release and MyBin\Release.
Then save all XMLs, then right-click on every project and choose "reload".
You can do the same in any text editor or any command-line find&replace utility like sed. If you ave any, use them instead - it will save you from rightlicking/unloading all projects.
You can also exploit the fact that CSPROJs are just MSBuild files, so you can create a 'configuration' MSBuild script that will be included each of your C# projects, and which will define extra variables like 'common output path' etc. But while this certainly works (I've did it a few times), it will most probably screw up some PropertySheets in the VS UI in the most natural way, for example, if you use this to override/setup the OutputPaths, then the VS UI wil display empty or broken output paths and trying to use the VS UI to correct/change them will in turn overwrite your smart settings that read them from common config file. Obvious, isn't it.
EDIT: here it's quickly explained: Partial .csproj Files, however, please read my comments below too, just in case.
I've got an MSBuild script that is just about doing everything that I need it to do apart from my post-build step (see a previous question that I asked: MSBuild conditional Exec?).
What I'm looking to do is build many csproj files and optionally perform post-build steps if and only if the project was built. I don't want to perform my post-build step all the time or else the timestamp on my final output will be modified unnecessarily (and it makes the build process very time consuming for no reason).
In my MSBuild script I've got something like the following for each of my csproj files:
<Target Name="ProjectName">
<MSBuild Projects="PathToProject" Properties="Configuration=$(buildtype)" />
</Target>
Edit:
I think what I really want to do is detect when the CoreCompile task runs for each project. If there were some way to check for this in a condition?
Any ideas?
I'm new to MSBuild so maybe I'm on completely the wrong track!
Thanks,
Alan
You can also do it based on the configuration selected in your build process. For CI, you should always use "Release" or "Production" (you can define your own).
<Exec Condition="'$(ConfigurationName)'=='Release'" Command="your command goes here ..."/>
After much searching for a simple solution to this problem I didn't find one and ended up coming up with a solution of my own that works but may not be the best solution. However, I wanted to share it with anyone else that is having the same problem so that you can at least have a working solution and hopefully saving you a lot of head banging.
To recap, what I wanted to do was run a command line tool after my project was built but only if the assembly was updated (i.e. the timestamp changed). I didn't want to put this into the post-build section of every project because I only wanted the post-build to happen on our build server (not development machines).
I didn't find any way of doing this externally in my main .proj file and did end up altering the post-build section of each .csproj file. However, I prefixed it with an if condition something like this:
if '$(ExecuteCommand)' == 'true' command.exe
This means that the command will never be executed on the development machine but when I invoke the build from my .proj file I can set that flag to true like this:
<!-- Define common properties -->
<PropertyGroup>
<ExecuteCommand>true</ExecuteCommand>
</PropertyGroup>
<Target Name="YourTarget">
<!-- Build project -->
<MSBuild Projects="Path to project" Properties="ExecuteCommand=$(ExecuteCommand)" />
</Target>
As I said, I don't think it is the most graceful solution but it certainly works and will be sufficient for me for the time being. However, I'd still be interested to hear what the proper way of achieving this is so that I can improve my script.
Thanks,
Alan
If you can add the following to each of your projects:
<Target Name="DoStuffWithNewlyCompiledAssembly">
<Exec Command="command.exe" />
</Target>
... then you only need to add a property:
<Target Name="Name">
<MSBuild Projects="" Properties="TargetsTriggeredByCompilation=DoStuffWithNewlyCompiledAssembly" />
</Target>
This works because someone smart at Microsoft added the following line at the end of the CoreCompile target in Microsoft.[CSharp|VisualBasic][.Core].targets (the file name depends on the language and MSBuild/Visual Studio version).
<CallTarget Targets="$(TargetsTriggeredByCompilation)" Condition="'$(TargetsTriggeredByCompilation)' != ''"/>
So if you specify a target name in the TargetsTriggeredByCompilation property, your target will run if CoreCompile runs-- and your target will not run if CoreCompile is skipped (e.g. because the output assembly is already up-to-date with respect to the code).