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>
....
Related
Thanks to this awesome article by Nate McMaster, I know how to package a .NET core console application as a Nuget package that automatically installs itself as a (pre, in this instance) build task.
To test if everything works, I simply had my custom tool write out a public C# class.
Here is the complete and runnable sample on Github.
However, the file that my custom tool adds isn't really part of the build (the first one that actually generates the file) and therefore the introduced class is not in the assembly after the first build (see Line 38 here). However, because the .NET core projects now automatically include all .cs files alongside the project, it builds the new class into the output on subsequent builds (see Line 57 here).
The generated files don't go away on clean, though and generally don't behave like something an MSBuild task would output. However, because the exec happens in a targets file, we ought to have access to all the machinery to make this happen. So my question is:
How do I correctly execute a custom build tool (console app) that needs to examine the project, its files and generate source code (preferably in obj/ as say <foo>.g.cs that gets compiled into the resulting assembly as part of a single build? Ideally, this generated file(s) shouldn't appear in the solution explorer, either.
Help!
When generating the intermediate file (CustomTool.g.cs) in the intermediate folder (you'll need to resolve it, see example in Refit library: https://github.com/reactiveui/refit/blob/5b4e14aaf8a1fcc27396b7c08171d100aba1b97d/Refit/targets/refit.targets#L11); you need to explicitly add it as a compile item.
Taking your example targets file (https://github.com/aniongithub/CustomTool/blob/master/CustomTool/RunCustomTool.targets#L13):
<Project>
<PropertyGroup>
<IntermediateOutputPath Condition="$(IntermediateOutputPath) == '' Or $(IntermediateOutputPath) == '*Undefined*'">$(MSBuildProjectDirectory)obj\$(Configuration)\</IntermediateOutputPath>
<!-- Command to invoke CustomTool -->
<CustomTool>dotnet "$(MSBuildThisFileDirectory)/netcoreapp2.2/CustomTool.dll"</CustomTool>
<!-- Other variables -->
<CustomVariable>"$(MSBuildProjectDir)"</CustomVariable>
</PropertyGroup>
<Target Name="CustomTool" BeforeTargets="CoreCompile" DependsOnTargets="PrepareForBuild">
<Exec Command="$(CustomTool) $(ProjectPath) $(IntermediateOutputPath)CustomTool.g.cs" />
<!-- add generated file as a compile item, otherwise it won't get picked up -->
<ItemGroup Condition="Exists('$(IntermediateOutputPath)\CustomTool.g.cs')">
<Compile Include="$(IntermediateOutputPath)\CustomTool.g.cs" />
</ItemGroup>
</Target>
</Project>
There is this big solution I'm working on, where I turned a lot of the projects into NuGet packages. The packages were created via a .nuproj file in a separate solution in VS.
Everything works fine, except for the following:
At bootstrap I load some catalogs for MEF to be able to import them, which worked perfectly when I worked with the original projects, but now the needed DLLs (which come from the a package) don't make it to the bin\Debug\Modules folder.
Is there a way to make NuGet copy its content to the Modules folder? (and not to the root path)
I tried using the different kinds of sub-folders inside the package with no success.
I found that the best solution for this matter is the following:
Take the files that need to be loaded and put them on the content folder. This can be done simply:
<ItemGroup>
<Content Include=" {here go the needed files} " />
</ItemGroup>
The content folder just holds the files, but it does not copy them to the output folder on the client project. In order to copy them to the desired output, a .targets file can be used, just like the following:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyToOutput" AfterTargets="Build">
<ItemGroup>
<FilesToCopy Include="$(MSBuildThisFileDirectory)..\content\**\*.*"/>
</ItemGroup>
<Copy
SourceFiles="#(FilesToCopy)"
DestinationFiles="#(FilesToCopy->'$(OutDir)/%(RecursiveDir)%(FileName)%(Extension)')"/>
</Target>
</Project>
Keep in mind that the targets file name and the ID of the NuGet have to be equal for the targets file to be added to the project.
You should be able to use a target of content/Modules. Anything in the content directory is copied in to the bin directory on build.
If you were trying to use the special "convention based" folders, like lib/net45, those are directories that cause Visual Studio to automatically create an assembly reference when the package is installed. You shouldn't use those for regular content files.
See the documentation for more details.
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 just started looking into msbuild, because I want to make my own build scripts. For now I am able to create build scripts that compiles only one project, but how do I handle dependencies?
For example what if I have two projects that gets build with these two msbuild scripts?
projectA.xml
projectB.xml
How do I tell msbuild that when I am executing projectB.xml that it should first execute projectA.xml?
I have googled alot on this, but it does not seem to get anything that a starter like me understands. I would be more than happy with a link to an article describing this, or maybe just a small code example.
The reason why I want this control is because of a library I am building. The library consists of several projects. A developer should be able to pull the source code for the library down and build only the libraries that he wants.
Actually I want to be able to build .net modules from the different projects. That is why I want to be able to run a customized msbuild script.
If you create a solution with the two projects you can target the .sln file with msbuild, rather than directly building the projects, it should take care of project dependencies :)
But that's if you're using standard .csproj projects...
Ok I looked at a project I'm working on, and it's like this:
<ItemGroup>
<ProjectReference Include="..\SomeFolder\SomeProject.csproj">
<Project>{1A94B405-2D01-4A09-90D5-A5B31180A03B}</Project>
<Name>SomeProjectNamespace</Name>
</ProjectReference>
</ItemGroup>
And here's an MSDN page about references. Scroll down till you find ProjectReference...
I setup my build scripts so that I have a few common targets that do not do anything, but use DependsOnTargets to setup project dependencies and run the build.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- ************************************************************************************************ -->
<!-- Targets that run the builds -->
<!-- ************************************************************************************************ -->
<Target Name="AutoBuild" DependsOnTargets="BuildProject1;BuildProject2;BuildInstallers">
<OnError ExecuteTargets="NotifyFailure" />
</Target>
<Target Name="FullCompile" DependsOnTargets="BuildProject1;BuildProject2">
<OnError ExecuteTargets="NotifyFailure" />
</Target>
<!-- Build Project 1 -->
<Target Name="BuildProject1">
<!-- Use MSBuild task and point it to build project1.csproj, project1.sln or whatever your projects is -->
</Target>
<!-- Build Project 2 -->
<Target Name="BuildProject2">
<!-- Use MSBuild task and point it to build project2.csproj, project2.sln or whatever your projects is -->
</Target>
<Target Name="BuildInstallers">
<!-- Whatever logic you have for building installers -->
</Target>
</Project>
In MSBuild issue #2887 a similar situation is discussed. The thread also reveals a link to official ProjectReference Protocol.
You dont need to build using the sln. If you use project references in your csproj then the dependency order is taken care of by MSBuild.
Try it. Automajically.
You do not need to sort the dependency order in your msbuild script.
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).