msbuild: how to build and run a console application within the build and have it write output files to the output directory of another project? - msbuild

I have three projects, I will call them ProjectSource, ProjectProcessor and ProjectTarget
ProjectSource is a .NET Standard 2.0 library producing a DLL.
ProjectProcessor is a .NET Core 2.1 console library. ProjectProcessor has a project reference on ProjectSource. It loads the ProjectSource DLL and based on some types it finds in there generates some output files (lets just say they are documentation). The number of files it produces can change over time.
ProjectTarget is a .NET Framework 4.7.1 console application. It has a dependency on ProjectSource, but also needs to include the content output by ProjectProcessor.
All projects are our code so we could change them quite a bit.
My question is, what is the right way to get the content output files of ProjectProcessor written to the output directory of ProjectTarget?
Approach 1: One approach that did not work was to use project reference in ProjectTarget to ProjectProcessor. It produced a build error that a .NET Framework 4.7.1 console application referencing a .NET Core 2.1 console library. I wouldn't want all the project output copied anyway, just the generated documentation files. I did try a ProjectReference with ReferenceOutputAssembly="false" and OutputItemType="Content".
Approach 2: Another approach is to have a pre or post build step on ProjectTarget that calls the ProjectProcessor console application, passing in the
current target directory (of ProjectTarget). For example:
<PropertyGroup>
<PreBuildEvent>dotnet $(SolutionDir)projects\ProjectProcessor\$(OutDir)netcoreapp2.1\ProjectProcessor.dll $(TargetDir)</PreBuildEvent>
</PropertyGroup>
This works, with the unfortunat downside that it doesn't recognize when it needs to run (I think it'd always run?). If ProjectSource or
ProjectProcessor have code changes I'd like it to run again, but not otherwise. (based on MSBuild copy output from another project into the output of the current project)
Approach 3: Finally, googling led me to try adding a target like this:
<Target Name="GenerateDocumentationContent" BeforeTargets="AfterBuild">
<MSBuild Projects="..\ProjectProcessor\ProjectProcessor.csproj" BuildInParallel="false" Targets="Build">
<Output TaskParameter="TargetOutputs" ItemName="DocumentationOutputDir" />
</MSBuild>
<Exec Command="dotnet #(DocumentationOutputDir) $(TargetDir)"/>
</Target>
This builds the ProjectProcessor project as expected and calls the resulting console application with the right parameters. But it doesn't run in the case that ProjectTarget doesn't have changes and I want it to run if ProjectSource and ProjectProcessor have changes.

Related

MSBuild 16.9 .NET Core 3.1 - OutDir isn't searched for dependencies

I'm trying to use MSBuild in a powershell script to build many projects and solutions in a full application suite. I set the parameter for OutDir to point to a single binaries directory and from an output perspective that works.
However the documentation states that OutDir is included in AssemblySearchPaths. But looking at the logs MSBuild is clearly stuck using the hintpath from the csproj file. I've tried setting AdditionalLibPaths as well with no success. This appears to be an issue with building from Visual Studio 2019 as well. My hintpaths point to a common debug directory. A release build still looks in the debug directory. This used to work in older versions of Studio in the .NET Framework days. It worked in older TFS XAML builds setting Output Location to "SingleFolder"
I've also played around with OutDir path ending various quantities of back slashes. I suspect that this old issue is fixed.
How can I get MSBuild to use an alternate directory for the dependencies?
https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-properties?view=vs-2019
EDIT:
As per the accepted answer, adding OutDir to the AssemblySearchPaths does the trick. For me, I've created a proj file that I've added to each .NET Core csproj files. My thought is that when this gets fixed I can remove the tweak in one place.
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AssemblySearchPaths>$(AssemblySearchPaths);$(OutDir)</AssemblySearchPaths>
</PropertyGroup>
My hintpaths point to a common debug directory. A release build still
looks in the debug directory.
The outdir is always the output folder which does not distinguish between Release and Debug. So you have to use <OutDir>C:\ttt\$(Configuration)\</OutDir> to distinguish between them.
Actually, the system msbuild properties are read earlier than the start of build task. You have to set the properties before the start of build process.
Simply modifying the system properties outdir in csproj will only take effect during the build process, but the system properties are still read before the build starts, also AssemblySearchPaths property read the previous outdir property. So you always take the default values before the modification.
You have to use Directory.Build.props file, it set the values earlier than msbuild start.
1) create a file called Directory.Build.props under your project folder.
2) add the outdir property like these into the file.
<Project>
<PropertyGroup>
<OutDir>C:\ttt\$(Configuration)\</OutDir>
</PropertyGroup>
</Project>
3) restart VS to enable it.
However, I note that it works well in non-sdk net framework projects but it does not list under new-sdk net core projects.
non-sdk net framework projects
new-sdk net core projects
Not sure it is an issue or the Team has forgotten it. Anyway, I have reported it to our DC Forum. You can vote it or add any comments if I did not described it in detail.
As a workaround, you could try to set the new value for AssemblySearchPaths property.
In order not to lose the original value of AssemblySearchPaths, you must add it to csporj file rather than Directory.Build.props file.
Just add these into csproj file:
<PropertyGroup>
<AssemblySearchPaths>$(AssemblySearchPaths);$(OutDir)</AssemblySearchPaths>
</PropertyGroup>
Update 1
I think it is an issue for net core projects.
What I said before is for VS IDE build. Now for MSBuild Command Line, it is another situation.
For non-sdk net framework projects
When I used msbuild xxx\xxx.csproj -p:outdir=c:\ttt -v:diagnostic, it shows this:
Well. It works perfect as we wished.
However, when we used the same command line for new-sdk net core projects, it does nothing. So I think it is quite an issue for net core projects.
And you should note that AdditionalLibPaths cannot be recognized by AssemblySearchPaths.
When I used this under :
msbuild xxx\xxx.csproj -p:AdditionalLibPaths=c:\ttt -v:diagnostic
And you should note that there is no property for AdditionalLibPaths under the list of AssemblySearchPaths property. And it also does not work for net core projects.
In short, it is quite an issue for net core projects no doubt. I also modify the DC ticket.
Now for new-sdk net core projects,
Since you used msbuild command line to set properties, so there is no need to use Directly.Build.props file. MSbuild command line property assignment is actually the same effect of the file.
Also, AssemblySearchPaths is not ready-only. You could modify it. And actually, all msbuild properties can be overwritten and that is a flexible feature of MSBuild.
In summary, you still have to use AssemblySearchPaths.
Solution
Since The Team has some problems with this detail in the net core project, we can use the flexibility of MSbuild to manually modify to get what we want:
1) abandon using Directly.Build.props file and also keep adding these on the net core csproj file:
<PropertyGroup>
<AssemblySearchPaths>$(AssemblySearchPaths);$(OutDir)</AssemblySearchPaths>
</PropertyGroup>
2) use the following command line for net core projects:
msbuild xxx\xxx.csproj -p:Outdir=c:\ttt -v:diagnostic

.NET Core RuntimeIdentifier vs TargetFramework

Can someone explain the purpose of this two in csproj file (VS2017):
<TargetFramework>netstandard1.6</TargetFramework>
<RuntimeIdentifier>win7</RuntimeIdentifier>
I just migrated from VS2015 and now can't publish my web api because it looks I should use only one target framework. In addition I can't specify multiple RIDs.
All these changed things make me frustrated. Nothing works from scratch, should overcome something over and over.
I just want developing my web-api on windows, run xUnit tests here and then deploy web-api to run on linux (ubuntu) server.
What I should put in both parameters in csproj ?
Links with good explanation is highly appreciated.
Update1
I have web api with referenced .net core libraries. Everything where migrated from VS2015. Now in root project I have
<TargetFrameworks>netcoreapp1.1;net461</TargetFrameworks>. When I publish via VS2017 I got error:
C:\Program
Files\dotnet\sdk\1.0.3\Sdks\Microsoft.NET.Sdk\buildCrossTargeting\Microsoft.NET.Sdk.targets(31,5):
error : The 'Publish' target is not supported without specifying a
target framework. The current project targets multiple frameworks,
please specify the framework for the published application.
But I have specified target framework in publish as netcoreapp1.1.
OK. Then I updated my csproj with <PropertyGroup Condition="$(TargetFramework)'=='netcoreapp1.1'">
<RuntimeIdentifier>ubuntu.16.10-x64</RuntimeIdentifier>
</PropertyGroup> as suggested below.
But now I even can't build app, get error:
5>C:\Program Files (x86)\Microsoft Visual
Studio\2017\Enterprise\MSBuild\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.targets(92,5):
error : Assets file '\obj\project.assets.json' doesn't
have a target for '.NETCoreApp,Version=v1.1/ubuntu.16.10-x64'. Ensure
you have restored this project for TargetFramework='netcoreapp1.1' and
RuntimeIdentifier='ubuntu.16.10-x64'.
I just want develop with VS2017 at windows 8.1/windows7 and deploy to ubuntu 16.10.
What I'm doing wrong ?
Update2
I have 8 projects in solution. 3 of them are xUnit tests. Thus we have 5 projects. 4 of these 5 are class libraries and 1 is my web-app.
All 4 class libraries have this:
<TargetFrameworks>netstandard1.6;net461</TargetFrameworks>
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
My web app:
<TargetFrameworks>netcoreapp1.1;net461</TargetFrameworks>
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
How to publish my web-app ?
The <TargetFramework> (or <TargetFrameworks> when you want have multiple targets, such as net451, one or multiple netstandard1.x etc). Per <TargetFramework> / <TargetFrameworks> entry one set of assemblies will be created and located inside bin\Debug\<targetframeworkid>).
This is useful, when you want to use a different library in .NET Core (because the library you used only works with full .NET Framework such as 4.5.1) or remove this feature from i.e. .NET Core because it's unsupported.
It is used for both, building and NuGet restore. i.e. you can't use a net451 only library in a .NET Core project (netstandard 1.1 for example - but you can use netstandard1.1 in a net451 project).
<RuntimeIdentifier> / <RuntimeIdentifiers> on the other side is used for NuGet mainly. It tells NuGet which packages you need. For example if you want to target Linux, Mac and Windows, certain assemblies require native libraries (such as encryption. On windows CryptoAPI will be used, but on Linux and Mac you need OpenSSL). This includes non-managed dlls and *.so (Linux) files.
i.e. <RuntimeIdentifiers>win7-x64;win7-x86;ubuntu.16.10-x64</RuntimeIdentifiers> will make nuget restore packages for win7 (both x64 and x86) versions and x64 only for ubuntu. This is required, because when you work on windows you need to download these native libraries too so you deploy/package them with dotnet publish.
Here's a little catch though: When you have a full .NET Framework reference in <TargetFramework> or <TargetFrameworks>, then you must specify a single <RuntimeIdentifier> (singular, not plural <RuntimeIdentifiers>), otherwise you will get an error.
For example:
<PropertyGroup>
<TargetFrameworks>netstandard1.0;net451</TargetFrameworks>
<RuntimeIdentifiers>win7-x64;win7-x86;ubuntu.16.10-x64</RuntimeIdentifiers>
</PropertyGroup>
<!-- This entry will only be used for the .NET Framework 4.5.1 output -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net451'">
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
</PropertyGroup>
RID is short for Runtime IDentifier. RIDs are used to identify target
operating systems where an application or asset (that is, assembly)
will run. They look like this: "ubuntu.14.04-x64", "win7-x64",
"osx.10.11-x64". For the packages with native dependencies, it will
designate on which platforms the package can be restored.
More in docs
First change to proper RID from win7 to win7-x64 or win7-x86. Next add other RID like ubuntu. For example:
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
<RuntimeIdentifier>win7-x64;ubuntu.16.10-x64</RuntimeIdentifier>
</PropertyGroup>
Target framework looking good. For more read docs

Parallel MSBUILD - critical sections?

My organization has some huge builds that run on build servers, building lots and and lots of MSBUILD projects that are linked with ProjectReferences. We need to be able to build projects and configurations in parallel with msbuild /m.
My problem is that I have a project that is referenced from a large number of other projects, but the project itself is not reentrant. If more that two or more nodes try to build that project in parallel, it will fail.
How can I wrap this one Project, or it's Target, inside a critical section?
What I really need to do is something like this:
<Target>
<EnterCriticalSection ID=$(ProjectGuid) />
<Exec something />
<LeaveCriticalSection ID=$(ProjectGuid) />
</Target>
The idea being that if multiple MSBUILD nodes tried to build this project in parallel, only one of the nodes could do the execution, and the rest of the nodes would have to wait (or go do something else).
I suppose I could write custom MSBUILD tasks to do this, but isn't there some way of doing this built into the MSBUILD system?
=== EDIT 4/5/13. To clarify, the project is building a 3rd-party library with the build script provided for it. Completely rewriting their build script to make it reentrant -- by insuring that each build used a different set of folders for intermediate files, etc. -- is possible in theory, but not a practical solution. For one thing, all that work would would have to be redone on each new release of that library.
=== EDIT 4/6/13. On further reflection, I don't think it's even theoretically possible to insure a project is reentrant. Let me explain:
Suppose project XYZ is set up to use different temporary directories depending on platform and configuration in the usual way:
XYZ.proj:
<PropertyGroup>
<MyWorkingDir>tmp.$(Platform).$(Configuration)</MyWorkingDir>
</PropertyGroup>
Now suppose some other project GraphicsWindow references project XYZ, either through ProjectReferences or MSBuild tasks. And suppose the GraphicsWindow project can be built to use various graphics APIs. I.e., there's an OpenGL version, a DirectX 9 version, a DirectX 10, version...
So somewhere there's a .proj or .targets file containing a target to build all four versions:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectToBuild Include="GraphicsWindow.proj">
<Properties>GraphicsApi=OpenGL</Properties>
</ProjectToBuild>
<ProjectToBuild Include="GraphicsWindow.proj">
<Properties>GraphicsApi=D3D9</Properties>
</ProjectToBuild>
<ProjectToBuild Include="GraphicsWindow.proj">
<Properties>GraphicsApi=D3D10</Properties>
</ProjectToBuild>
<ProjectToBuild Include="GraphicsWindow.proj">
<Properties>GraphicsApi=D3D11</Properties>
</ProjectToBuild>
</ItemGroup>
<Target Name="All">
<MSBuild Projects="#(ProjectToBuild)" BuildInParallel="true" />
</Target>
</Project>
or the equivalent using batching.
Now MSBuild will build the XYZ project 4 times with the same Platform|Configuration combination and the same working directory.
This will work fine as long as you build without the /m option and MSBuild runs a single thread. Depending on how the XYZ project is written, the 2nd, 3rd, and 4th builds might do nothing because the outputs are up-to-date, or it might do some redundant work, but the final result will be correct and the build will succeed.
But as soon as you start using parallel MSBuild, this build is broken! There is now a race condition where multiple threads can enter the XYZ project's target(s) concurrently, and start building using the same working directories, which will fail.
Irrespective of how you execute MSBuild, with multi-proc option /m or without, it is guaranteed to execute a project once for every configuration requested by the build. Here is a quote from MSDN:
When the Microsoft Build Engine encounters a project-to-project (P2P) reference while it is using parallel builds to build a project, it builds the reference only one time. If two projects have the same P2P reference, the reference is not rebuilt for each project. Instead, the build engine returns the same P2P reference to both projects that depend on it. Future requests in the session for the same target are provided the same P2P reference.
If you see the same project built more than once, it means it was referenced in two (or more) different configurations. By configuration here I mean a set of parameters that was passed to the project, e.g. project platform (x86, x64, AnyCPU, etc.,) flavor(debug/retail), localization language, any other parameters you might use.
More often than not, this is problem with mixture of project platforms. For example, you have project A built for x64, project B built for AnyCPU, and both A and B reference C. Now C has to be built twice -- for x64 and AnyCPU. If C correctly handles both platforms by cleanly separating outputs into separate directories, there is no problem. However if C treats x64 and AnyCPU as the same, it will fail randomly in multi-proc build.
Start by inspecting your solution configuration dialog. Make sure all projects have consistent set of platform/configuration parameters. If you have a need to build same project in different configuraions, make sure it puts output into separate locations.

How to aggregate outputs from a dynamically generated set of projects into a single folder

I need to collect into a single folder all test assemblies, with their dependencies, and configuration files. The process should preserve the directory structure from the output of each test project. We have a solution that requires manually attaching test projects to a master project, but our solution has far too many projects for this to be maintainable. These should be located automatically based on naming convention (x.UnitTest.csproj, y.IntegrationTest.csproj).
For background, we are working with a build system that passes artifacts (binaries, etc) between agents. We are compiling on one agent, and testing on other agents. The massive duplication of assemblies between test projects is slowing the build process down.
What I have done:
1) I have a csproj that references most of the test projects. This gets binaries and dependencies into one folder.
2) I am able to identify all files to copy using this
<CreateItem
Include="%(ProjectReference.RootDir)%(ProjectReference.Directory)$(OutDir)*.config">
<Output TaskParameter="Include" ItemName="TestConfigurationFiles"/>
</CreateItem>
<Copy
SourceFiles="#(TestConfigurationFiles)"
DestinationFolder="$(OutDir)">
</Copy>
I've attempted most obvious things, such as
MsBuild task: RebaseOutputs attribute, overriding the OutDir property. I can provide the msbuild task with a dynamically generated set of outputs, but can only build them in their default folder.
Hooking into the TargetOutputs of
msbuild task gives only the primary
output assembly (without
dependencies).
I experimented with "Copy Always" for
configuration files. This puts them
in the output directory of the
dependent project as "app.config" not
"dllname.config", and not in the
final project.
Solutions that could make this better might include
Provide an example of adding to the projectreference item array dynamically, before compilation.
Use msbuild TargetOutputs to create a list of all files in the folder (instead of just the primary output) and copy to a destination folder.
Today I'm using msbuild 3.5. Ideally the solution would work with msbuild 3.5. We are transitioning to .NET 4 / MsBuild 4 soon, so, if must be done in .Net 4, that is fine.
Have you considered flattening the folder structure when you export your artifacts?
Something like:
src/*.UnitTest*/bin/**/*.* -> /testlibs
src/*.IntegrationTest*/bin/**/*.* -> /testlibs

MSBuild doesn't pick up references of the referenced project

I bumped into a strange situation with MSBuild just now. There's a solution which has three projects: LibX, LibY and Exe. Exe references LibX. LibX in its turn references LibY, has some content files, and also references to a third-party library (several pre-built assemblies installed in both GAC and local lib folder). The third-party library is marked as "Copy Local" ("private") and appears in the output of the LibX project, as the LibY's output and LibX's content files do. Now, Exe project's output has LibX project output, content files of the LibX project, LibY project output (coming from LibX), but NO third-party library's assemblies.
Now I worked this around by referencing the third-party library directly in Exe project, but I don't feel this is a "right" solution.
Anyone had this problem before?
There is a difference in behavior when building with MSBuild (i.e. command line, TFS Build and other tools) compared to building with Visual Studio. The secondary references are not included in the references variable sent into MSBuild compile tasks.
There are several extension points provided by MSBuild to change how references are to be resolved. I have successfully used AfterResolveReference to fix this issue for some of my projects - I have posted more info about the background on my blog.
The workaround is to add the following code into you vbproj or csproj files
<Target Name="AfterResolveReferences">
<!-- Redefine referencepath to add dependencyies-->
<ItemGroup>
<ReferencePath Include="#(ReferenceDependencyPaths)">
</ReferencePath>
</ItemGroup>
</Target>
Microsoft has stated that this is a won't fix on Connect
You can actually go into the Microsoft.CSharp.targets or Microsoft.VisualBasic.targets file (located in the framework directory, usually C:\Windows\Microsoft.NET\Framework\v3.5) and modify the csc or vbc task parameters to include additional reference dependencies. In the file (VB targets, line 166; C# targets, line 164) change:\
References="#(ReferencePath)"
to
References="#(ReferencePath);#(ReferenceDependencyPaths)"
This might cause other issues depending on how complicated things are and it may play tricks with the Visual Studio inproc compiler, but it's the only way to do it in MSBuild that I've found.
josant's answer almost worked for me; I kept getting an error in Visual Studio when I tried that:
A problem occurred while trying to set the "References" parameter for the IDE's in-process compiler. Error HRESULT E_FAIL has been returned from a call to a COM component
The solution to my problem was to put a condition on the ItemGroup, like this:
<Target Name="AfterResolveReferences">
<!-- Redefine referencepath to add dependencies-->
<ItemGroup Condition=" '$(BuildingInsideVisualStudio)' != 'true' ">
<ReferencePath Include="#(ReferenceDependencyPaths)"></ReferencePath>
</ItemGroup>
</Target>
That caused Visual Studio to ignore the reference change completely, and the build works fine locally and on the build server.
Yes, I've had that problem, too. Though I'd love to say otherwise, I believe you must include all transitive dependencies as references in your build file.
I've combined Alex Yakunin's solution with one that will also copy native dll's.
The AfterResolveReferences method fails if you've got a directed graph not a tree with a "trying to deploy different copies of the dll" error. (cf. How to configure msbuild/MSVC to deploy dependent files of dependent assemblies)