MSBuild targets for nuget project called from another project - c#

I am a beginner with MSBuild. I have a project (A) that depends on another project (B) and it consumes it either from a NuGet server either from a local workspace (I use NuGetReferenceSwitcher for this purpose).
Shortly, here are my requirements:
When consumed from NuGet server, project B has to support both Debug and Release builds. In this way, if we switch to Debug in Visual Studio we would be able to debug also project B when called from A. Right now, the NuGet also gives the Release version.
When switching to local workspace, it should still build properly.
Here is how my local files are located:
\workspace\projectA\projectA.sln
\workspace\projectB\projectB.csproj
The solution I tried is the following:
Inside projectB I created a targets file (located in build\projectB.targets) with the following contents:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Reference Include="lib1">
<HintPath>"$(MSBuildProjectDirectory)\bin\$platform$\Debug\lib1.dll"</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<Reference Include="lib1">
<HintPath>"$(MSBuildProjectDirectory)\bin\$platform$\Release\lib1.dll"</HintPath>
</Reference>
</ItemGroup>
</Project>
projectB.csproj contains this:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="packages\some_package" Condition="Exists('packages\some_package)" />
projectB.nuspec contains:
<file src="build\**" target="build\net462" />
projectA.csproj (that depends on projectB) contains:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\projectB.*\build\projectB.targets" Condition="Exists('..\packages\projectB.*\build\projectB.targets')" />
<Import Project="..\..\..\..\projectB\projectB.csproj" Condition="!Exists('..\packages\projectB.*\build\projectB.targets')" />
Here are my questions:
With this configuration, when using local workspace for projectB, the compilation of projectA fails because of the relative path of "some_package" which is not find from projectA (that also uses relative path for projectB as you see in point 4). How else could I do reference projectB from projectA so it doesn't fail the compiling?
Why is this code not working to switch from release to debug when using nuget?
Thank you,

Why is this code not working to switch from release to debug when using nuget?
Quite simply, NuGet doesn't have a concept of "Debug" or "Release".
However, there is a way to create debug symbols for NuGet packages and then configure Visual Studio to step through the code of the installed packages.

Related

MSBuild not able to build Nuget package because it's storing binaries in the wrong folder

I have a Visual Studio solution which has several projects. One of them, which is called Extensions is supposed to build a Nuget package. Problem is that if I use MSBuild to build the solution (by executing msbuild from the command prompt), I get the following error for the Nuget package build task:
"C:\git\repo\dirs.proj" (default target) (1:7) ->
"C:\git\repo\sources\dirs.proj" (default target) (2:5) ->
"C:\git\repo\sources\dev\Sdk\CompanyName.ProductName.Extensions.csproj" (default target) (7:6) ->
(GenerateNuspec target) ->
C:\Program Files\dotnet\sdk\5.0.408\Sdks\NuGet.Build.Tasks.Pack\build\NuGet.Build.Tasks.Pack.targets(221,5): error : Could not find a part of the path 'C:\git\ess\target\distrib\Debug\Amd64\CompanyName.ProductName.Extensions'. [C:\git\ess\sources\dev\Sdk\CompanyName.ProductName.Extensions.csproj]
Here is the CSPROJ file I have for this project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>CompanyName.ProductName.Extensions</AssemblyName>
<RootNamespace>CompanyName.ProductName.Extensions</RootNamespace>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeCoreAssets</TargetsForTfmSpecificBuildOutput>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
<NuspecFile>CompanyName.ProductName.Extensions.nuspec</NuspecFile>
<PackageOutputPath>$(DistribRoot)\$(Configuration)\$(Platform)\$(MSBuildProjectName)</PackageOutputPath>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<SkipAssemblyComVisible>true</SkipAssemblyComVisible>
<IncludeBuildOutput>false</IncludeBuildOutput>
<OutputPath>$(DistribRoot)\$(Configuration)\$(Platform)\$(MSBuildProjectName)</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<NuspecBasePath>$(OutputPath)</NuspecBasePath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Core\CompanyName.ProductName.Core.csproj" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<Target DependsOnTargets="ResolveReferences" Name="IncludeCoreAssets">
<ItemGroup>
<BuildOutputInPackage Include="#(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
</ItemGroup>
</Target>
</Project>
I personally think the issue is that for some reason when MSbuild is trying to create the Nuget package, it's trying to find the Extensions project DLLs in this path: \target\distrib\Debug\Amd64\CompanyName.ProductName.Extensions\, even though it actually built and stored the said binaries in this path earlier: \target\distrib\CompanyName.ProductName.Extensions\. <--- This is something I checked manually myself by examining the 'target' folder after running msbuild.
Visual Studio however doesn't have this issue. When I build this solution within Visual Studio, it stores the binaries for this Extensions project in \target\distrib\Debug\AnyCPU\CompanyName.ProductName.Extensions\ folder, which I think matches the pattern defined in the CSPROJ file:
<OutputPath>$(DistribRoot)\$(Configuration)\$(Platform)\$(MSBuildProjectName)</OutputPath>
Visual Studio also stored the Nuget package in this folder, which is also correct as per the <PackageOutputPath> spec mentioned in the CSPROJ file.
So can someone suggest why MSbuild is storing the built DLLs of this project in \target\distrib\CompanyName.ProductName.Extensions\, but is trying to find them in \target\distrib\Debug\Amd64\CompanyName.ProductName.Extensions\ ?
I think the issue is that for some reason, MSbuild isn't adhering to the <OutputPath> spec mentioned above when it's storing the built DLLs for this project.

How to create a nuget package with both release and debug dll's using nuget package explorer?

I'm using the Nuget Package Explorer to create some nuget packages. I've managed to do so just building a project in Release mode in VS and adding both the dll and pdb files to the package.
So far so good, but when I add the package to another project and try to step into the code while debugging, it will step over it instead.
I understand that I need to build and add the Debug dll and pdb to my package if I want to step into the code while debugging. I'm not sure though how to add these to the package I've already create, which already contains the Release dll and pdb file, which are named the same.
Any thoughts?
My thoughts are, NuGet packaging is a lot about conventions.
There is no problem in packaging same namespaces and same names for different platforms (as in lib/net40/mydll.dll, lib/net35/mydll.dll etc in the same package), as NuGet will filter registered dependencies by platform.
Building several versions for the same platform seems unconventional, this discussion is biased towards making a package per build. That doesn't mean you can't do it, but you should first ask yourself if you should.
That said, if your debug and release builds are very different (conditional compiling etc) this might useful though. But how will end-users choose Release or Debug when installing your package?
An idea could be, one version per build configuration. Both can be installed into the project. To do that, either add a targets file to your package or build a powershell install script (unsupported since Nuget v3) that adds conditional references directly in the target project file, if you want something less basic than whatever MsBuild can do for you.
Example of the first tactic: Create a .target file (in your package, create a build folder and then create build\YourLib.targets with the following contents):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Reference Include="YourLib">
<HintPath>..\packages\YourLib.1.0.0\lib\Debug\YourLib.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<Reference Include="YourLib">
<HintPath>..\packages\YourLib.1.0.0\lib\Release\YourLib.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
Providing you created debug and release folders (platform folder is optional), the build output will effectively change depending on configuration - provided packet consumers have conventional configuration names, but you could always extend the condition logic a bit with $(Configuration).Contains etc or just put that in the package readme
Inspired by #Tewr I've found a cumbersome but a working solution.
Create a nuget with the following file structure:
lib\net\$(Configuration)\YourLib.1.0.0.dll <---- put here some dummy file named YourLib.1.0.0.dll
tools\release\YourLib.1.0.0.dll <--- put here the release version
tools\debug\YourLib.1.0.0.dll <--- put here the debug version
build\YourLib.targets
The targets file content:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyReferences" BeforeTargets="Build" Condition="Exists('..\packages\YourLib.1.0.0\lib\net\%24(Configuration)')">
<Exec Command="mkdir ..\packages\YourLib.1.0.0\lib\net\Release" />
<Exec Command="mkdir ..\packages\YourLib.1.0.0\lib\net\Debug" />
<Exec Command='copy "..\packages\YourLib.1.0.0\tools\Release\YourLib.1.0.0.dll" "..\packages\YourLib.1.0.0\lib\net\Release"' />
<Exec Command='copy "..\packages\YourLib.1.0.0\tools\Debug\YourLib.1.0.0.dll" "..\packages\YourLib.1.0.0\lib\net\Debug"' />
<Exec Command='rmdir /S /Q "..\packages\YourLib.1.0.0\lib\net\%24(Configuration)"' />
</Target>
The dlls in lib folder will be automatically added as references creating the following in the project file:
<Reference Include="YourLib>
<HintPath>..\packages\YourLib.1.0.0\lib\net\$(Configuration)\YourLib.1.0.0.dll</HintPath>
<Private>True</Private>
</Reference>
Once you build the project for the first time, the target will copy the release and debug version from tools\release and tools\debug folders to lib\net\release and lib\net\debug folders. In the end, it will delete the lib\net\$(Configuration) folder
Enjoy (or not - I personally don't like the solution).
Thanks #Tewr In new nuget format and sdk style csproj format, we can use some constant as $(MSBuildThisFileDirectory) to get the current file path.
The code that uses the version will increase maintenance difficulty. The sdk style csproj format use the new package format that will do not output the package file to package folder.
We can add the targets file to build folder and use $(MSBuildThisFileDirectory) to get the file path.
<ItemGroup Condition="'$(Configuration)' == 'DEBUG'">
<Reference Include="YourLib">
<HintPath>$(MSBuildThisFileDirectory)..\lib\debug\YourLib.dll</HintPath>
</Reference>
</ItemGroup>
See the file

Visual Studio/MSBuild copy referenced class library's app.config as *.dll.config to bin folder of current project

I have a class library that is referenced by many other web application projects. It has many settings in its app.config that I wish to use in all of the referencing web projects. When the class library is built, it copies the app.config to its own bin folder as <assembly.name>.dll.config.
How do I ensure that <assembly.name>.dll.config is copied to the bin folder of each of my referencing web application projects?
Visual Studio / MSBuild does not seem to do this by default.
Changing Copy to Output Directory or Build Action (in any combination) does not work.
SlowCheetah VS extension appears to do what I want as an unintended side-effect of its config transform process, but I want to do this without any 3rd-party extensions.
As an aside: It's impractical to manually put all of this config in each of the web application projects. I can read the file by looking for <assembly.name>.dll.config in the bin folder, and extract the settings from there. I can already do this, so that's not an issue - I just need to ensure the file is going to be there for reading
This can be achieved without 3rd-party tools by adding the following to the class library's project file, assembly.name.csproj:
// Find this <Import> element for Microsoft.CSharp.targets
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
// Add this <ItemGroup> element immediately after
<ItemGroup>
<Content Include="app.config">
<Link>$(TargetName).dll.config</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
This causes the app.config to be copied to whichever project is referencing it as <assembly.name>.dll.config. It's good because you only need to configure the one .csproj file and the effect cascades out to all referencing projects.
Visual Studio 2015 and earlier
For Visual Studio 2015, I am using the solution of #theyetiman (see this answer):
<ItemGroup>
<None Include="app.config" />
</ItemGroup>
<ItemGroup>
<None Include="app.config">
<Link>$(TargetFileName).config</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
(The import of Microsoft.CSharp.targets was already present.)
Visual Studio 2017
During my experiments, Visual Studio 2017 seems to handle configuration files as expected out of the box. No manual modification of the project file is needed.

Windows Store App DLL 32bit and 64bit

I've written a small Media Foundation Transform and added the C++ DLL to my C# Windows Store App project.
The 32bit version of the DLL running the x86 configuration works just fine but x64 doesn't work (it throws an Exception with the following message:
"MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED : HRESULT - 0x800700C1")
If I add the 64bit version it's the same just the other way around x64 works and x86 doesn't.
Is there any way I can set it up so that it uses the x86 version of the DLL for the x86 configuration and the x64 version for the x64 configuration?
Took me some time but I figured out how to do it using NuGet.
First I added a location on my PC as package source as explained here.
In VS2013 CE you do this by opening the NuGet Package Manager Settings (Tools > NuGet Package Manager > Package Manager Settings) and under Package Sources you add a location on your computer.
To create the NuGet I combined several HowTo's since one alone didn't work.
The main one was this.
I built the dlls for my required platforms and created the following folder structure
Since it might be a little hard to see ProjectName.props and ProjectName.targets are located in the netcore451 folder.
The .dll, .pri and .winmd in lib are the x86 version. According to the HowTo it's redundant and you can just ignore these files but without them VS might not work correctly in design mode.
In the folder that contains build and lib I created a file ProjectName.nuspec
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/01/nuspec.xsd">
<metadata minClientVersion="2.5">
<id>ProjectName</id>
<version>1.0.0</version>
<authors>Stefan Fabian</authors>
<owners>Stefan Fabian</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Description</description>
<releaseNotes>First release.</releaseNotes>
<copyright>Copyright 2015</copyright>
<references>
<group targetFramework=".NETCore4.5.1">
<reference file="ProjectName.winmd" />
</group>
</references>
</metadata>
</package>
ProjectName.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>
ProjectName.targets
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="PlatformCheck" BeforeTargets="InjectReference"
Condition=" ( ('$(Platform)' != 'x86') AND ('$(Platform)' != 'AMD64') AND ('$(Platform)' != 'Win32') AND ('$(Platform)' != 'ARM') AND ('$(Platform)' != 'x64') )">
<Error Text="$(MSBuildThisFileName) does not work correctly on '$(Platform)'
platform. You need to specify platform (x86 / x64 or ARM)." />
</Target>
<Target Name="InjectReference" BeforeTargets="ResolveAssemblyReferences">
<ItemGroup Condition="'$(Platform)' == 'x86' or '$(Platform)' == 'Win32'">
<Reference Include="ProjectName">
<HintPath>$(MSBuildThisFileDirectory)x86\ProjectName.winmd</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'x64' or '$(Platform)' == 'AMD64'">
<Reference Include="ProjectName">
<HintPath>$(MSBuildThisFileDirectory)x64\ProjectName.winmd</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'ARM'">
<Reference Include="ProjectName">
<HintPath>$(MSBuildThisFileDirectory)ARM\ProjectName.winmd</HintPath>
</Reference>
</ItemGroup>
</Target>
</Project>
I'm not sure if it's necessary to check for x86 and Win32 but it works for me.
Disclaimer
This was the first time ever I created a NuGet package and the code above might suck.

How can I force MSBuild to respect project dependencies in my solution when my project uses a Csc task?

Background:
I am currently using VS2010.
There is a known issue with MSBuild where project dependencies in a solution work when building from the IDE, but do not work when the solution is built through MSBuild.
(MSBuild is run via Cruise Control .NET like so:)
<cb:define buildArgs="/noconsolelogger /p:Configuration=$(configuration);Platform=$(platform) /maxcpucount /v:minimal /nologo" />
<!-- ... -->
<cb:define name="buildSolution">
<msbuild>
<executable>$(msbuildExe)</executable>
<workingDirectory>$(localWorkingDirectory)</workingDirectory>
<projectFile>$(solutionfile)</projectFile>
<buildArgs>$(buildArgs)</buildArgs>
<targets>build</targets>
<timeout>7200</timeout>
<logger>C:\Program Files (x86)\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger>
</msbuild>
</cb:define>
Thus, I solve this issue by using a non-linking project reference:
<!-- Inside ProjectB.csproj -->
<ProjectReference Include="..\ProjectA\ProjectA.vcxproj">
<LinkLibraryDependencies>false</LinkLibraryDependencies>
<Project>{GUID}</Project>
<Name>ProjectA</Name>
</ProjectReference>
In this case, ProjectA is a SWIG project that generates CS source files to be compiled into ProjectB. I accomplish this like so:
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\ProjectA\generated\cs\*.cs" />
</ItemGroup>
However, although this works fine when building through the IDE, it includes none of these files when building through MSBuild. I was able to get it to work by adding the following:
<ItemGroup>
<CSFile Include="..\ProjectA\generated\cs\*.cs" />
</ItemGroup>
<Target Name="Build" DependsOnTargets="BeforeBuild">
<Csc Sources="#(CSFile)" References="#(Reference)" OutputAssembly="$(OutputPath)$(AssemblyName).dll" TargetType="dll" />
</Target>
Actual Issue:
It seems like MSBuild is no longer respecting the project reference, so it will usually try to build ProjectB before ProjectA.
It appears that the issue was not build order so much as when the wildcards are evaluated.
In my example, the CSFile tag is evaluated before the generated .cs files from ProjectA exist, so even though ProjectA happens first, they are not included in the build.
To circumvent this, I have to manually call ProjectA from ProjectB through MSBuild, read the file list at runtime, and manually append it to the Compile collection.
At the end of the ProjectB.csproj file, I've inserted:
<Target Name="BeforeBuild">
<!-- Force project references to build before this, because otherwise it doesn't... -->
<MSBuild Projects="#(ProjectReference)"
Targets="Build"
BuildInParallel="false"
Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration);%(_MSBuildProjectReferenceExistent.SetPlatform)"
ContinueOnError="false" />
<CreateItem Include="..\ProjectA\generated\$(Platform)\$(Configuration)\cs\*.cs">
<Output ItemName="Compile" TaskParameter="Include" />
</CreateItem>
<CreateItem Include="..\ProjectA\generated\$(Platform)\$(Configuration)\cs\*.cs">
<Output ItemName="GeneratedCS" TaskParameter="Include" />
</CreateItem>
<Message Text="Generated .cs files...%0d #(GeneratedCS->'%(Filename)%(Extension)','%0d ')" Importance="high" />
</Target>
This will add all generated .cs files to the project, as well as print all their names to the console.
Looks like SWIG projects can't be compiled by MSBuild at all, but you might be able to compile your solution with SWIG using Visual Studio itself. Note that you'll have to have Visual Studio + SWIG on your Cruise Control build server, and somehow get Cruise Control to call devenv.exe with the correct Devenv Command Line Switches.
Background: In Visual Studio, most project files format function as both:
Project files that Visual Studio understands, possibly with a Visual Studio plugin like SWIG
MSBuild file that MSBuild understands.
This includes csproj, vcxproj, and many others. However, some project types like setup projects (vdproj) only support (1) above - and SWIG appear to be like that as well.

Categories