I have a project with some nuget packages referenced.
In output folders (bin\Debug or bin\Release), all referenced libraries lie next to the executable.
How to specify output folder for libraries?
I want all nuget libraries in bin\Release\Libs and executable in bin\Release.
I woke up early this morning and decided to have a go at doing it myself. Turned out to be pretty quick, but that may be because of my (unfortunate) experience with looking into MSBuild files. Writing this post took me far longer than writing the target.
From your question, I assume you're using a traditional project, since SDK style projects only create the project's assembly in the bin directory. However, I much prefer SDK style projects because use can quickly and easily use the dotnet cli to create test projects and the csproj is much more easily editable. So, I'll give you my steps to find my solution for SDK style projects, and you need to follow along to do something similar with a traditional project.
So, we want to change where a files are being copied, which means we need to modify some items. Everything in MSBuild runs in a target, so we'll need to know when to run our custom target, what items to modify and probably what metadata of those items to modify. I created a new project, added some NuGet references then ran dotnet msbuild -t:publish -bl and opened the msbuild.binlog file.
What metadata to change
Searching for the name of a dll that came from a nuget package, I find a message saying copied from ... to ..., so I click on it to go to the entry, and follow the tree back to the task, which I see is the built-in Copy task. The target path to the task is Publish -> _PublishBuildAlternative -> ComputeAndCopyFilesToPublisDirectory -> CopyFilesToPublishDIrectory -> _CopyResolvedFilesToPublishAlways. Double clicking the copy task I see
<Copy SourceFiles = "#(_ResolvedFileToPublishAlways)"
DestinationFiles="#(_ResolvedFileToPublishAlways->'$(PublishDir)%(RelativePath)')"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForPublishFilesIfPossible)"
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForPublishFilesIfPossible)">
So, I can guess I need to modify the RelativePath metadata of an _ResolvedFileToPublishAlways item.
What item to change
Side note: MSBuild doesn't have public/private modifies, so instead a convention is generally used. Anything starting with an underscore should be considered to be an implementation detail that could change between releases, so it's better to use things that do not start with an underscore, and the teams who maintain the targets file should try harder not to break compatibility.
So, since _ResolvedFileToPublishAlways starts with an underscore, let's find out where it was created. Searching for it takes me to a target where the binlog tells me it was added, in a target called _ComputeResolvedFilesToPublishTypes, and its definition is
<Target Name="_ComputeResolvedFilesToPublishTypes">
<ItemGroup>
<_ResolvedFileToPublishPreserveNewest Include="#(ResolvedFileToPublish)"
Condition="'%(ResolvedFileToPublish.CopyToPublishDirectory)'=='PreserveNewest'" />
<_ResolvedFileToPublishAlways Include="#(ResolvedFileToPublish)"
Condition="'%(ResolvedFileToPublish.CopyToPublishDirectory)'=='Always'" />
</ItemGroup>
</Target>
So, I can see that it's simply copying ResolvedFileToPublish items to new item names. Looking for where those items are created, it's in a target named ComputeFilesToPublish, and expanding the tree to see all the items created and their metadata, I'm going to guess the items I want to modify all have AssetType = runtime, which is perfect for a condition we're going to need to use.
When to run our target
Ideally I would run just before CopyFilesToPublishDirectory, however looking at its definition I see
<Target Name="CopyFilesToPublishDirectory"
DependsOnTargets="_CopyResolvedFilesToPublishPreserveNewest;
_CopyResolvedFilesToPublishAlways" />
The problem is that when MSBuild executes a target it runs in this order:
Any targets listed in DependsOnTargets
Any target that lists the current target as BeforeTargets
The current target
Any targets that lists the current target as AfterTargets
So, while I want to run BeforeTargets='CopyFilesToPublishDirectory', the DependsOnTargets will run before my target, so I can't do that. So I'll choose to run AfterTargets="ComputeFilesToPublish". There are other targets that run in between those, and one sounds like that it might add ResolvedFileToPublish items, but with my current project the target doesn't run because of conditions, so my custom target might not be generic enough to work for all projects.
Writing our custom target
So now we know when our target will run, which items it will modify and how we will modify their metadata.
<Target Name="RedirectRuntimeFilesToBinDirectory" AfterTargets="ComputeFilesToPublish">
<ItemGroup>
<ResolvedFileToPublish Condition=" '%(ResolvedFileToPublish.AssetType)' == 'runtime' ">
<RelativePath>lib\%(RelativePath)</RelativePath>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
Unfortunately the binlog doesn't show the details about the metadata being modified, which is a real pain in the arse when trying to debug build issues and why some items have unexpected values, but in any case I've now successfully changed the destination of NuGet dependencies, and probably project to project references, to a lib\ directory.
Grace to the zivkan's investigation I found the answer. Traditional project has target CopyFilesToOutputDirectory which depends on _CopyFilesMarkedCopyLocal target. In this last one we have task Copy:
<Copy
SourceFiles="#(ReferenceCopyLocalPaths)"
DestinationFiles="#(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForCopyLocalIfPossible)"
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyLocalIfPossible)"
Condition="'$(UseCommonOutputDirectory)' != 'true'"
>
And here I found metadata DestinationSubDirectory which is exactly what I need to change.
So finally
First, we need to change csproj file and add these lines:
<ItemDefinitionGroup>
<ReferenceCopyLocalPaths>
<DestinationSubDirectory>lib\</DestinationSubDirectory>
</ReferenceCopyLocalPaths>
</ItemDefinitionGroup>
Second, we need to change app.config file to let the assembly know the path to the libraries:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="lib;libs" />
</assemblyBinding>
</runtime>
That's all. All referenced libraries will be copied into subfolder lib
Related
In the last few days I've battled against an issue with debugging nuget packages that I still wasn't able to solve the way I want.
All this has been tested with visual studio 2022, updated to the latest version available.
We have a .NET 3.1 library published on github (private package).
We consume such library in many .NET 3.1 WebAPI backends that we develop.
We need to be able to debug such library.
GitHUB doesn't seem to support symbol servers at the moment, so I thought we had these 2 possibilities:
embed the pdb in the dll: this seemed the most straightforward solution and was the first I tried. By doing so on the core library, when we import that in a .NET webapi project and inspect the "modules" window, we can see that symbols for the dll have been loaded correctly; symbol file column reads: "OurLibrary.dll (embedded)"
include the pdb in the nupkg and then add a piece of code (found here https://github.com/dotnet/sdk/issues/1458#issuecomment-420456386) in the .csproj of consuming .NET webapi project that ensures that the dll pdb, contained in the nuget package, is copied to the bin folder. Also in this case, inspecting the modules window, it looks that the symbols have been loaded from metadata (which I guess is the pdb file itself).
STILL, in both cases, if the project is running, when I try to set a breakpoint in the Startup.cs file of the consuming WebAPI project, and step into an IServiceCollection extension method, which is defined in the library, I'm able to do so, but many symbols used in that file (referring to PUBLIC types defined in the library, or from the framework itself) are white, and I can't explore them by doing "go to definintion".
Instead, if I try to peek at the source code of the extension method mentioned before, when the project is not running, I'm able to peek at code by doing "go to definition" without issues. So, I set a breakpoint..
Then, when I launch the project:
you can see that it's a different "SessionFactory" file, and all the symbols have become white. If I try to go back to the original file (with all the types correctly resolved by intellisense), and set a breakpoint inside it, visual studio automatically switches to the other file (with "broken" intellisense) and sets the breakpoint there, which is very frustrating..
At the moment the only solution that really behaves the way I want requires me to remove the library nuget package from the project and reference the library as an "Existing project" inside the solution. By doing so everything works perfectly (of course, now library source code is part of the project) but of course seems wrong / time consuming / error prone.
Somebody is able to shed some light on what is going on? Thanks
EDIT: FURTHER DETAILS FOR USER #Transformer
I tried your suggestion to include the supplied code in .csproj of both the library and the consuming application:
By doing so in the library .csproj, it contains these settings related to PDB generation, in a property group:
<EmbedAllSources>true</EmbedAllSources>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<AllowedReferenceRelatedFileExtensions>.pdb</AllowedReferenceRelatedFileExtensions>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
It also includes this, outside of the property group:
<Target Name="AddReferenceRelatedPathsToCopyLocal" AfterTargets="ResolveAssemblyReferences">
<ItemGroup>
<ReferenceCopyLocalPaths Include="#(_ReferenceRelatedPaths)" />
</ItemGroup>
</Target>
Unfortunately, by doing so, the pdb doesn't seem to be included in the nupkg. Instead, if I add the following (a setting I've already found in the past):
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
then the pdb is included in nupkg, but still, I experience the same debugging problem in the consuming library, even if I add the code you supplied to the consuming library as well...any other idea?
Thanks a lot for your kind help
The issue is probably because the nuget packages are built with Release configuration which optimizes out the symbols. One way you can debug the package itself is if you open the project (from which you deploy the nuget), build it with Debug configuration, then copy the dll and pdb file into the bin\Debug\ folder of your consumer app. Then start the consumer app with no debugger attached, after it starts, you can attach your nuget project to that process, and your nuget code breakpoints will be hit.
Hello, from what I can see - that's because the embedded files are not copied to local, please try this in your cs.proj file and paste your comments
<PropertyGroup>
<AllowedReferenceRelatedFileExtensions>.pdb</AllowedReferenceRelatedFileExtensions>
</PropertyGroup>
<Target Name="AddReferenceRelatedPathsToCopyLocal" AfterTargets="ResolveAssemblyReferences">
<ItemGroup>
<ReferenceCopyLocalPaths Include="#(_ReferenceRelatedPaths)" />
</ItemGroup>
</Target>
Question from Carlo Arnaboldi : Where does this go?
Update: These are settings that go into the .proj file.
You you to do this in your nuget pacakge && your consuming project to see the pdb for symbol debugging
Also delete you old pdbs - exist visual studio/code and then delete the bin and obj folders
I need to use a NuGet package containing a utility for my project. It contains several binaries (EXEs and DLLs).
I've added it to my project successfully but I suspect the nupkg isn't formed correctly because I cannot use any of its DLLs or EXEs in my project without manually pointing to the package in my local NuGet cache. When compiling, none of its resources are added to the output (I assume this is because nothing is referenced in my code).
I'd like to create a wrapper project to call the binaries but I'd also like other project devs to be able to compile the solution without adjusting directory variables. Ideally, I could configure the csproj to pull in the bits directly from the local package cache. I think this would be possible by setting the Generate Path Property value to Yes in Visual Studio, but the variable cannot be found when I attempt to use an <Include/> statement in the csproj file.
Is what I'm asking possible? Namely, reference the NuGet package bits within my csproj to ensure the binaries are dropped in the compilation output? Can I do this with the Path Property, or is there something else I can do without directly committing the package's binaries into my project?
(I realize I need to work with the developer to fix whatever issue they have with their package, but I have no direct influence at the moment so this is the best I can do at the moment).
I figured this out, mostly due to misunderstanding how some of the different tags and attributes are meant to be used.
To achieve the desired effect, I did the following:
<ItemGroup>
<Content Include="$(Pkg{PackageId})\**">
<Link>{NameOfSolutionDirectory}\%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
Where {PackageId} is the name of the NuGet package (this step requires setting 'Generate Path Property' to 'Yes' in the package properties via Solution Explorer), and {NameOfSolutionDirectory} is the name of a folder within the solution I'd like to use for containing those bits, if you're as concerned about keeping the project as organized as I am. The {} should be excluded when replacing these values.
If you want to scope to a specific directory within the package contents, do it within the Include attribute. The ** is necessary if you want to include all files within that directory, or else you can scope by extension or whatever additional pattern you'd like.
I need to create csproj file that will be usable as project reference in VS2013 and will output prebuilt binary as it's "Build" result.
We use referenced projects for build, however company policy doesn't allow access to some of that projects for everyone. As a result projects need to be updated manually to make them build. This is really a major inconvenience when switching branches and when making edits to project files, so I want to create dummy project that will be bound to pre-built binaries as their "output" and will be placed instead of real projects.
EDIT: Moving that assembly to Nuget package is not an option for now since Nuget has some issues with dev flow (when you need to debug/test/develop package). I saw some VS extension that implements switching between Nuget package and local project which might solve this issue, but I'm not sure if it will be accepted and want to explore other options.
To be clear - the thing I want to avoid is editing project in any way, so that project can be built cleanly after pulling it from Git, and I don't have to clean it every time before commit.
I haven't properly tested it, but the solution seems really simple (if I understand the question properly).
Just add this to the existing .csproj, overriding the Build target to just give the path to the pre-built assembly.
<Target
Name="Build"
Returns="$(TargetPath)" />
This assumes the TargetPath property already defined, and it should automatically be if you're modifying the original .csproj. Otherwise just define it yourself in a <PropertyGroup> before the Build task.
Note that having TargetPath defined is important for the ProjectReferences in your own project to resolve.
How about having those restricted (binary only) projects reside in an internal Nuget package feed, so that Nuget can install the packages as needed, on build?
So this article talks about an awesome way of using XSD and LINQ to process XML data. It's all good and fun, but the author claims that he is no longer interested in maintaining the project on CodePlex. In addition, the NuGet package requires manual edit of the CS project file
<LinqToXsdBinDir Condition="'$(LinqToXsdBinDir)' == ''">$(SolutionDir)\LinqToXSD\</LinqToXsdBinDir>
to be added to each of the PropertyGroups (Build/Release). So why would Microsoft leave the
LinqToXsdSchema as a build action in VS 2013? Is there any other use of this build action?
Why would you claim that Microsoft does? I don't see how they're involved in this. If the action is defined in the project file or a targets file the project depends on, it's shown. That just how MsBuild works. <Import Project="$(LinqToXsdBinDir)\LinqToXsd.targets" /> is what will add the Build Action to the dropdown.
These lines to be exact:
<!-- This ItemGroup is needed to make sure that LinqToXsdSchema and LinqToXsdConfiguration are available in VisualStudio-->
<ItemGroup>
<AvailableItemName Include="LinqToXsdSchema" />
<AvailableItemName Include="LinqToXsdConfiguration" />
</ItemGroup>
You can view the targets file here.
Remove the .targets file from your project and remove the mention of "LinqToXsdSchema" from your items in the project file. It might also be registered in your local MsBuild folder and load automatically for all projects, in that case remove the .targets file from your MsBuild installation.
Of course there is no use to the build action, unless there is another tools that picked the same name for the action.
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).