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.
Related
I wrote a class library in C# that I need to push to a private NuGet server (v3.4.1.0). I decorated my classes and methods with XML documentation comments.
XML documentation file option is checked on the Build tab of the project properties panel, the project builds successfully and the xml file gets generated on the project's root folder with the same name as the assembly.
In the .csproj file the related section looks like this:
<PropertyGroup>
<DocumentationFile>absolutePathTo\assemblyName.xml</DocumentationFile>
</PropertyGroup>
IntelliSense (VS2019 16.9.0) recognises the documentation and shows it properly even in other projects under the same solution.
When I generate the NuGet package it gets created in the project's bin\Debug folder. If I open it as a zip archive the DLL and the documentation XML can be found in the lib\netstandard2.1 folder having matching names.
Once I install this package to another project from the private NuGet server it works properly but loses the complete documentation. IntelliSense does not show my comments anymore and the assembly metadata seems not to have it either.
Could anyone support me on this one?
That is normal. For xml document, it is special under new-sdk style projects. The xml document could only be copied into the non-sdk net framework projects but new-sdk net core projects cannot. More similar to this issue I handled before.
So you should try these steps additionally to get what you want:
1) enter these node under csproj file of your nuget project.
<ItemGroup>
<None Include="xxx\absolutePathTo\assemblyName.xml"(the path of the xml file under your project folder) Pack="true">
<PackageCopyToOutput>true</PackageCopyToOutput>
</None>
</ItemGroup>
2) after that, re-pack your nuget project and before you install the new version, please clean nuget caches first or just delete all files under C:\Users\xxx\.nuget\packages
Personally, I let Visual Studio handle things for me.
If you right-click in the project, and choose Properties,
In the Build -> Output area, you should see a checkbox under Documentation file labelled Generate a file containing API documentation..
When you check this, a new option appears underneath: XML documentation file path. But the file selector is labelled Optional path for the API documentation file. Leave blank to use the default location..
FYI: The default location is alongside your EXE / DLL that is generated when you build your project.
When you next build your code (for anyone else reading, I assume you've got the Generate NuGet package on build in the Package area checked too) it will also package up the XML documentation into the NuGet package generated.
From the perspective of users of this new package, Visual Studio will pick up on the XML Documentation inside.
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>
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
Intro (how to pack resources into a nuget package)
To pack some resource files into a nuget package, what one would normally do, is the following.
Put all the resource files into the content\ directory of a nuget package. This would be specified by the following line in a .nuspec file:
<files>
<file src="Project\bin\Release\script.js" target="content\js\script.js" />
<files>
Now, when this nuget package gets installed into AnotherProject, the following file structure emerges:
Solution.sln
packages\Project.1.0.0\content\js\script.js // the original resource file
AnotherProject\js\script.js // a physical copy
AnotherProject\AnotherProject.csproj // <Content /> tag (see below)
During package installation, AnotherProject.csproj was injected with tag:
<Content Include="js\script.js" />
and this is for the physical copy of the original resource (which is under packages\ directory).
The actual problem (how to pack resources into a nuget package as link)
My aim is not to have the physical copy of a resource file in the AnotherProject directory but rather a "link" to the original resource under packages\ directory. In the csproj, this should look like this:
<Content Include="packages\Project.1.0.0\content\js\script.js">
<Link>js\script.js</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Brute force solution that I would rather avoid
Now, one "do it the hard way" workaround I can think of is:
not putting resource files under content\ so they do not get added automatically,
writing Install.ps1 script that would hack the csproj file structure and add the necessary XML piece manually,
This, however, has the following drawbacks:
all my nuget packages need the same script piece in their Install.ps1,
when installing my packages, there would be a nasty "project reload prompt" in Visual Studio.
Since NuGet currently does not support this out of the box your options are either to use PowerShell or to use a custom MSBuild target.
PowerShell
Leave your resources outside of the Content directory in your NuGet package (as you already suggested).
Add the file link using PowerShell in the install.ps1.
You should be able to avoid the project reload prompt if you use the Visual Studio object model (EnvDTE). I would take a look at Project.ProjectItems.AddFromFile(...) to see if that works for you.
MSBuild target
NuGet supports adding an import statement into a project that points to an MSBuild .props and/or .targets file. So you could put your resources into the tools directory of your NuGet package and reference them from a custom MSBuild .props/.targets file.
Typically the custom .props and .targets are used to customise the build process. However they are just MSBuild project files so you could add items for your resources into these project files.
Note that .props are imported at the start of the project file when a NuGet package is installed, whilst .targets are imported at the end of the project.
Customising NuGet
Another option, which would take more work, would be to modify NuGet to support what you want to do.
I have recently created project on visualstudio.com, and enabled continuous build on azure. I created web api project, and created some models and api controllers. Then I deployed it online and it was cool for a good while. Then I updated all dependencies through NuGet. Build went fine on local and also app worked on my local machine. Then I checked in to tfs, and automatic deploying kicked in, with build error.
It says:
C:\a\src\HitchStopApi\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.targets (74): The "EnsureBindingRedirects" task could not be loaded from the assembly C:\a\src\HitchStopApi\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.Tasks.dll. Could not load file or assembly 'file:///C:\a\src\HitchStopApi\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.Tasks.dll' or one of its dependencies. The system cannot find the file specified. Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.
On my local machine build I get warning for Tests project
D:\Programming\Projects\HitchStop\HitchStopApi\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.targets(220,5): warning : Project must install nuget package Microsoft.Bcl.
On local I use .NET 4.5, MVC4, Entity framework 5.0...
This is somewhat of a bug and is logged in several places. Bcl.Build isn't a project required to build on TFS, so you simply need to tell TFS not to include it if it doesn't exist. To do this, open up your .csproj file (for each project that references Bcl.Build) and change the following:
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.targets" />
to add a condition:
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.targets')" />
Note: If you update Bcl.Build via Nuget, it will also update your project file and the following will need to be done again. Create a second copy of this and comment it out if you don't want to lose it every update/have a reference.
Related References (same issue, different manifestation):
http://social.msdn.microsoft.com/Forums/en-US/TFService/thread/7bd2e96b-552a-4897-881c-4b3682ff835e
https://connect.microsoft.com/VisualStudio/feedback/details/788981/microsoft-bcl-build-targets-causes-project-loading-to-fail
https://nuget.codeplex.com/workitem/3135
Update: Microsoft wrote an official blog on this. While the above does work in some situations, its not a guarantee. Microsoft and the NuGet team are working together on a solution, but in the meantime have provided 3 (better?) workaround options:
http://blogs.msdn.com/b/dotnet/archive/2013/06/12/nuget-package-restore-issues.aspx
Stop using package restore and check-in all package files
Explicitly run package restore before building the project
Check-in the .targets files
Your problem is described here
Solution:
1. Add dummy project (NugetHelper for example), add package.config with
<package id="Microsoft.Bcl.Build" version="1.0.6" targetFramework="net45" />
Open Menu -> Project -> ProjectDependencies and make NugetHelper to build before other projects in solution
Replace
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.targets" />
with
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.6\tools\Microsoft.Bcl.Build.targets')" />
this will restore Microsoft.Bcl.Build.targets before actually loading it in your main project