How to publish for all target frameworks - c#

I am building a set of build template in TeamCity for .Net Core projects. Everything is working great, except for console projects. The problem is that when I go to publish the solution, I need to specify the framework version. At no other point in the build do I need to know the framework. At least this is true when publishing .sln files, with a console project that only has a single framework targeted.
So now I am in a situation where I need to figure out what framework the console project should target. I could read various XML files, but I'm hoping I don't need to. Is there some builtin way that I can query for the frameworks in use for a given solution?
For example, something like (PowerShell)
$frameworks = & dotnet.exe --<what I want> .\MySolution.sln
for ($framework in $frameworks) {
& dotnet.exe publish -f $framework .\MySolution.sln
}
That way I don't need to modify the build system every time a new framework is in use. I've poked around in the CLI repo, but I can't find a command that does what I need. Is opening .csproj files my only hope?

If you want to publish projects that target multiple frameworks, the default Publish target fails, but you can create a custom target that performs the multi-targeting itself. To do this, create a file named Directory.Build.props in the solution folder (with MSBuild > 15.1 this can and should be named Directory.Build.targets because there was a bug with multi-targeting projects):
<Project>
<Target Name="PublishProjectIfFrameworkSet"
DependsOnTargets="Publish"
Condition=" '$(TargetFramework)' != '' " />
<Target Name="PublishProjectForAllFrameworksIfFrameworkUnset" Condition=" '$(TargetFramework)' == '' ">
<ItemGroup>
<_PublishFramework Include="$(TargetFrameworks)" />
</ItemGroup>
<MSBuild Projects="$(MSBuildProjectFile)" Targets="Publish" Properties="TargetFramework=%(_PublishFramework.Identity)" />
</Target>
<Target Name="PublishAll"
DependsOnTargets="PublishProjectIfFrameworkSet;PublishProjectForAllFrameworksIfFrameworkUnset" />
</Project>
Then you can publish the projects for all defined frameworks by executing this in the solution directory:
$ dotnet msbuild /t:PublishAll /p:Configuration=Release
Microsoft (R) Build Engine version 15.1.1012.6693
Copyright (C) Microsoft Corporation. All rights reserved.
app2 -> /Users/martin/testproj/app2/bin/Release/netcoreapp1.1/app2.dll
app1 -> /Users/martin/testproj/app1/bin/Release/netcoreapp1.1/app1.dll
app1 -> /Users/martin/testproj/app1/bin/Release/netcoreapp1.0/app1.dll

Related

Nuget PackageReference found in one solution, but not in my own solution

Previous title:
The "GenerateFileFromTemplate" task was not found
.NET project template - GeneratedContent
.csproj.in file transformations
The troubled package is Microsoft.DotNet.Build.Tasks.Templating.
I've created a git-repository containing multiple .NET project templates. When opened in Visual Studio, VS had a horrible performance when adding more files to the template project. This turned out to be caused by my template's project files having .csproj extension. Therefor I've changed the extensions of all my template csproj files to csproj.in.
Because of this, I need to add a msbuild task that transforms this .csproj.in to .csproj. There are several examples out on the internet:
ASP.NET Core project templates
spa-templates (Seems to use the Arcade SDK)
dotnet-template-samples (very basic)
microsoft/SEAL
In the above samples, there is no nuget.config in the project.
Your root csproj file contains a <GeneratedContentProperties> and <GeneratedContent> section:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<GeneratedContentProperties>
DefaultNetCoreTargetFramework=$(DefaultNetCoreTargetFramework);
</GeneratedContentProperties>
</PropertyGroup>
<ItemGroup>
<GeneratedContent Include="Angular-CSharp.csproj.in" OutputPath="content/Angular-CSharp/Company.WebApplication1.csproj" />
<GeneratedContent Include="React-CSharp.csproj.in" OutputPath="content/React-CSharp/Company.WebApplication1.csproj" />
</ItemGroup>
</Project>
The .csproj.in files reference the GeneratedContentProperties:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>${DefaultNetCoreTargetFramework}</TargetFramework>
...
</PropertyGroup>
...
</Project>
I've tried applying the same files to my project in this commit, but I'm still getting the following error when building the project:
dotnet build --configuration Release
MSBuild version 17.3.0+92e077650 for .NET
Determining projects to restore...
C:\Program Files\dotnet\sdk\6.0.400\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.DefaultItems.Shared.targets(152,5): warning NETSDK1023: A PackageReference for 'Microsoft.DotNet.Build.Tasks.Templating' was included in your project. This package is implicitly referenced by the .NET SDK and you do not typically need to reference it from your project. For more information, see https://aka.ms/sdkimplicitrefs [C:\repos\MintPlayer.AspNetCore.Templates\MintPlayer.AspNetCore.
IdentityServer.Templates\MintPlayer.AspNetCore.IdentityServer.Templates.csproj]
...
All projects are up-to-date for restore.
...
C:\repos\MintPlayer.AspNetCore.Templates\eng\GenerateContent.targets(27,3): error MSB4036: The "GenerateFileFromTemplate" task was not found. Check the following:
1.) The name of the task in the project file is the same as the name of the task class.
2.) The task class is "public" and implements the Microsoft.Build.Framework.ITask interface.
3.) The task is correctly declared with <UsingTask> in the project file, or in the *.tasks files located in the "C:\Program Files\dotnet\sdk\6.0.400" directory
[C:\repos\MintPlayer.AspNetCore.Templates\MintPlayer.AspNetCore.IdentityServer.Templates\MintPlayer.AspNetCore.IdentityServer.Templates.csproj]
Build FAILED.
It seems that dotnet cannot find the GenerateFileFromTemplate Task...
I also see that the spa-templates project is using the Arcade SDK, but I don't think I'd actually need that...
How can I fix this? What am I still missing here?
EDIT
When I open both projects in Visual Studio, this is what I see:
Nuget packages for my template project:
C:\repos\MintPlayer.AspNetCore.Templates\MintPlayer.AspNetCore.IdentityServer.Templates> dotnet restore
Determining projects to restore...C:\repos\MintPlayer.AspNetCore.Templates\MintPlayer.AspNetCore.IdentityServer.Templates\MintPlayer.AspNetCore.IdentityServer.Templates.csproj : warning NU1604: Project dependency Microsoft.DotNet.Build.Tasks.Templating does not contain an inclusive lower bound.
Include a lower bound in the dependency version to ensure consistent restore results.
C:\repos\MintPlayer.AspNetCore.Templates\MintPlayer.AspNetCore.IdentityServer.Templates\MintPlayer.AspNetCore.IdentityServer.Templates.csproj : error NU1101: Unable to find package Microsoft.DotNet.Build.Tasks.Templating. No packages exist with this id in source(s): C:\Program Files\dotnet\library-packs, Local, Microsoft Visual Studio Offline Packages, nuget.org
Failed to restore C:\repos\MintPlayer.AspNetCore.Templates\MintPlayer.AspNetCore.IdentityServer.Templates\MintPlayer.AspNetCore.IdentityServer.Templates.csproj (in 516 ms).
Nuget packages for spa-templates:
C:\repos\spa-templates\src> dotnet restore
Determining projects to restore...
C:\repos\spa-templates\src\Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj : warning NU1603: Microsoft.DotNet.Web.Spa.ProjectTemplates.7.0 depends on Microsoft.DotNet.Build.Tasks.Templating (>= 6.0.0-beta.21373.11) but Microsoft.DotNet.Build.Tasks.Templating 6.0.0-beta.21373.11 was not found. An approximate best match of Microsoft.DotNet.Build.Tasks.Templating 6.0.0-beta.22212.5 was resolved.
All projects are up-to-date for restore.
So it seems that dotnet restore is unable to restore this package. However, the nuget sources are the same for both projects:
IdentityServer.Templates>dotnet nuget list source
Registered Sources:
1. nuget.org [Enabled]
https://api.nuget.org/v3/index.json
2. Local [Enabled]
C:\packages
3. Microsoft Visual Studio Offline Packages [Enabled]
C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\
C:\repos\spa-templates>dotnet nuget list source
Registered Sources:
1. nuget.org [Enabled]
https://api.nuget.org/v3/index.json
2. Local [Enabled]
C:\packages
3. Microsoft Visual Studio Offline Packages [Enabled]
C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\
EDIT 2
Hmm, it seems that GenerateFileFromTemplate is part of the Arcade SDK... (Howto)
How to install Microsoft.DotNet.Arcade.Sdk into the dotnet sdk folder?
It seems that the package is only available through the following NuGet feed:
https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json
So I added a nuget.config in the root of the project/repository:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
</packageSources>
</configuration>
And since there's only preview versions of the package, you need for example the following version (eng/Versions.props):
<Project>
...
<PropertyGroup Label="Package versions">
<MicrosoftDotNetBuildTasksTemplatingVersion>6.0.0-beta.21373.11</MicrosoftDotNetBuildTasksTemplatingVersion>
...
</PropertyGroup>
</Project>

How to create resource sattelite assemblies for localization from resx and restext files in .NET Core desktop app using MSBuild

I am using a Linux development system and the Rider IDE to develop a cross-platform desktop application with AvaloniaUI. I am trying to follow the following Microsoft Documentation regarding the creation of resource-files for localization: https://learn.microsoft.com/en-us/dotnet/core/extensions/resources
Given the resources I want to capture and localize are all strings, I have opted to extract constant string definitions from my view models into dedicated .restext files.
Then, I updated the .csproj project file as follows:
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="GenerateAsembliesFromResourceBinaries">
...
<ItemGroup>
<StringResource Include="**/*.restext" />
</ItemGroup>
<Target Name="GenerateResourceBinaries">
<!-- resgen.exe resources.fr.txt -->
<GenerateResource Sources="#(StringResource)" OutputResources="#(StringResource->'$(IntermediateOutputPath)%(Filename).resources')">
<Output TaskParameter="OutputResources" ItemName="ResourceBinary" />
</GenerateResource>
</Target>
<Target Name="GenerateAsembliesFromResourceBinaries" DependsOnTargets="GenerateResourceBinaries">
<!-- al /t:lib /embed:resources.fr.resources /culture:fr /out:fr\Example1.resources.dll -->
<AL TargetType="library"
EmbedResources="#(ResourceBinary)"
Culture="%(ResourceBinary.Culture)"
OutputAssembly="%(ResourceBinary.Culture)\$(TargetName).resources.dll">
<Output TaskParameter="OutputAssembly"
ItemName="SatelliteAssemblies"/>
</AL>
</Target>
For reference:
GenerateResource Task corresponds to ResGen.exe: https://learn.microsoft.com/en-us/visualstudio/msbuild/generateresource-task?view=vs-2022
AL Task corresponds to AL.exe: https://learn.microsoft.com/en-us/visualstudio/msbuild/al-assembly-linker-task?view=vs-2022
However, building the project now results in the following error:
MyApp.csproj(135, 9): ResGen.exe not supported on .NET Core MSBuild
I cannot find anything in the linked .NET resources documentation which suggests the process does not work for .NET Core, nor any alternative approach for creating resource sattelite-assemblies using .NET Core MSBuild.
What is the proper way to accomplish this without using ResGen.exe and AL.exe?
Edit: this may be a duplicate of How to generate resources files on linux using dotnet

dotnet core 3.1 standalone app fails with "No such file or directory", Am I missing a dependency?

I'm just trying to get dotnet core running on an NVidia Jetson Nano.
I've created a simple "hello world" app in dotnet core and packaged it as a stand-alone app targeting linux-arm.
When I put it on my Synology NAS, I can navigate to the publish directly and type ./HelloDotNetCore and the app runs, albeit with a few errors.
/HelloDotNetCore/HelloDotNetCore/bin/Release/netcoreapp3.1/linux-arm$ ./HelloDotNetCore
./HelloDotNetCore: /lib/libstdc++.so.6: no version information available (required by ./HelloDotNetCore)
./HelloDotNetCore: /lib/libstdc++.so.6: no version information available (required by ./HelloDotNetCore)
./HelloDotNetCore: /lib/libstdc++.so.6: no version information available (required by ./HelloDotNetCore)
./HelloDotNetCore: /lib/libstdc++.so.6: no version information available (required by ./HelloDotNetCore)
./HelloDotNetCore: /lib/libstdc++.so.6: no version information available (required by ./HelloDotNetCore)
Hello World!
I can run it on my Raspberry Pi, as sudo
/HelloDotNetCore/HelloDotNetCore/bin/Release/netcoreapp3.1/linux-arm $ sudo ./HelloDotNetCore
Hello World!
I've "installed" dotnet core by following the tutorial here:
https://blog.headforcloud.com/2019/04/03/jetson-nano-a-quick-start-with-.net-core-3/
(it's not actually an install, just exposing the binary to bash)
/code/HelloDotNetCore/HelloDotNetCore$ dotnet run
Hello World!
However, attempting to run this as a stand-alone app on my NVidia Jetson results in "No such file or directory". I've tried the old obvious chmod +x and chmod 777 tricks along with running as sudo, but there's no other clue as to what it's looking for that isn't there.
/code/HelloDotNetCore/HelloDotNetCore/bin/Release/netcoreapp3.1/linux-arm$ ./HelloDotNetCore
-bash: ./HelloDotNetCore: No such file or directory
So it seems that something that should be packaged with this stand-alone app isn't there, but I'm lost as for how to figure out what it needs. Any ideas?
I found the culprit. The runtime for the NVidia Jetson needs to be explicitly set to linux-arm64, and not linux-arm. If you run the application from a Jetson using the dotnet command
dotnet run
it will compile the application into the associated debug or release folder and then you can run it from that folder using
./HelloDotNetCore
However, in order to "publish" the app from visual studio, I had to update my Microsoft.NETCore.Platforms package via NuGet from here
https://www.nuget.org/packages/Microsoft.NETCore.Platforms/
This automatically updated my .csproj file to
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.Platforms" Version="3.1.3" />
</ItemGroup>
</Project>
Then manually alter the RuntimeIdentifier element of the .pubxml file to reflect the linux-arm64 architecture.
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\netcoreapp3.1\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>True</PublishSingleFile>
<PublishTrimmed>False</PublishTrimmed>
</PropertyGroup>
</Project>
I was then able to publish the app using the publish command in Visual Studio, which built a stand-alone application inside a folder called 'publish'
Now, I get the expected result.
Jetson:/code/publish$ ./HelloDotNetCore
Hello World!

How to run MSBuild pack target in Visual Studio 2017

My question is similar to this and this.
I want to package a .Net Framework library in Visual Studio 2017 RC. In VS 2015 with project.json build system, I was able to accomplish this by importing a custom targets file into the .xproj file. This custom targets file took care of creating the nuspec file if it didn't exist, then running nuget pack, copying the resulting packages to local feed location, etc.
Do I need to do something similar in 2017 (haven't made a serious effort yet) or can I somehow enable the pack target in my .csproj file?
The docs here only show to run the pack target from the command line.
EDIT:
I'm trying the below custom target referencing a Nuget 4.0 exe from the nightly builds...
<Target Name="PackNugets" AfterTargets="Build">
<PropertyGroup>
<NugetV4Path>$([System.IO.Path]::GetFullPath('path to nuget.exe'))</NugetV4Path>
</PropertyGroup>
<Exec Command=""$(NugetV4Path)\nuget.exe" pack "$(MSBuildProjectDirectory)\$(PackageId).csproj" -Symbols -OutputDirectory bin -Properties Configuration=Release"/>
</Target>
But I get the following error
System.InvalidCastException: Unable to cast object of type 'System.String' to type 'NuGet.Frameworks.NuGetFramework'.
at NuGet.ProjectManagement.NuGetProject.GetMetadata[T](String key)
at NuGet.ProjectManagement.PackagesConfigNuGetProject..ctor(String folderPath, Dictionary`2 metadata)
at CallSite.Target(Closure , CallSite , Type , Object , Dictionary`2 )
at System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2)
at NuGet.CommandLine.ProjectFactory.AddDependencies(Dictionary`2 packagesAndDependencies)
at NuGet.CommandLine.ProjectFactory.ProcessDependencies(PackageBuilder builder)
at NuGet.CommandLine.ProjectFactory.CreateBuilder(String basePath, NuGetVersion version, String suffix, Boolean buildIfNeeded, PackageBuilder builder)
at NuGet.Commands.PackCommandRunner.BuildFromProjectFile(String path)
at NuGet.CommandLine.PackCommand.ExecuteCommand()
at NuGet.CommandLine.Command.ExecuteCommandAsync()
at NuGet.CommandLine.Command.Execute()
at NuGet.CommandLine.Program.MainCore(String workingDirectory, String[] args)
Something to do with one of the properties in the csproj? Does NugetFramework refer to the TargetFramework?
I am targeting net452 in my csproj, in case that helps.
EDIT:
That exception is indeed about nuget attempting to parse the TargetFramework, but it is not clear whether it is failing at my csproj or at a dependency...
EDIT: The latest updates to VS2017 have added the Pack action to the project context menu, so the below action can be changed as follows:
AfterTargets=Pack
Remove the Exec element
If you are targeting multiple frameworks, you may have to insert a \$(Configuration) after the \bin in the NugetPackages Include.
Ok, this issue solved it for me. For now, we have to use dotnet instead of msbuild or nuget.
So, my custom target in the imported .targets file becomes
<Project ToolsVersion="15.0">
<Target Name="PackNugets" AfterTargets="AfterBuild">
<!-- Swap out nuget for dotnet and update the cli args -->
<!-- Might actually be able to leave out the csproj path, since
this target should run in the project directory. Test it first. -->
<Exec Command="dotnet pack "$(MSBuildProjectDirectory)\$(PackageId).csproj" --no-build --include-symbols -o bin -c Release"/>
<!-- If you want to copy to a local feed -->
<ItemGroup>
<NugetPackages Include="$(MSBuildProjectDirectory)\bin\$(PackageId).$(PackageVersion)*.nupkg"/>
</ItemGroup>
<Copy SourceFiles="#(NugetPackages)" DestinationFolder="path to local feed" />
</Target>
</Project>
I was wanting to know the same thing, so I went spelunking in the MSBuild files, namely: C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\Sdks\NuGet.Build.Tasks.Pack\build and look for the "Pack" target.
Beyond that, I would refer to general MSBuild command line syntax here, but a few examples:
msbuild.exe -t:Pack -p:IncludeSymbols=true
msbuild.exe -t:Pack -p:Configuration=Release -p:VersionSuffix="alpha" # or "-alpha" - not sure
EDIT: A little more research and work on my scenario and I found this documentation of the important properties.
Please note that dotnet pack or msbuild /t:Pack currently doesn't support .NET Framework projects. They only work NETCore projects.
You can even improve Ken G answer if you want to push the nuget using this:
<Target Name="PushNugetPackage" AfterTargets="Pack" Condition="'$(Configuration)' == 'Release'">
<Exec Command="nuget.exe push -Source "mysource" -ApiKey VSTS $(OutputPath)..\$(PackageId).$(PackageVersion).nupkg" />
</Target>
It will only run after you choose pack from context menu and configuration is Release, so you don't push debug packages, remove that condition if you really don't need it
Source

ItemGroup Item scope, alternatively "Why does MSBuild hate me?"

I have a solution I'm trying to get to build on TFS. I want to update the versions of all appropriate files, and I've been stuck trying to get this done. There are plenty of links on how to do it, but none of them work for me, due to one little issue... Scope.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="DesktopBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<Target Name="DesktopBuild">
<CallTarget Targets="GetFiles" />
<Message Text="CSFiles: '#(CSFiles)'" />
</Target>
<Target Name="GetFiles">
<ItemGroup>
<CSFiles Include="**\AssemblyInfo.cs" />
</ItemGroup>
<Message Text="CSFiles: '#(CSFiles)'" />
</Target>
</Project>
My tree looks like this:
test.proj
application.sln
application (Folder)
main.cs
Properties (Folder)
AssemblyInfo.cs
When I run "c:\Windows\Microsoft.NET\Framework\v3.5\MSBuild.exe test.proj" from the solution folder... I get the following output:
Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3074]
Copyright (C) Microsoft Corporation 2007. All rights reserved.
Build started 7/6/2009 3:54:10 PM.
Project "D:\src\test.proj" on node 0 (default targets).
CSFiles: 'application\Properties\AssemblyInfo.cs'
DesktopBuild:
CSFiles: ''
Done Building Project "D:\src\test.proj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.04
So, how can I make my ItemGroup have global scope? All the Targets files used by the compiler and TeamBuild do this same thing, and theirs all seem to be global... I don't understand why this isn't working for me.
Any help?
Have you tried using DependsOnTarget rather than CallTarget? It could be that CallTarget is causing the scope issue.
The previous commenter was correct, you should change this to use DependsOnTargets instead of using the CallTarget task. What you are seeing is a bug not a scoping inssue. The way to avoid this bug is to use DependsOnTargets (which is a much better approach anywayz).
Sayed Ibrahim Hashimi
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
As said, you should use DependsOnTargets. I've done some research on MSBuild scope, you can find my results on my blog : http://blog.qetza.net/2009/10/23/scope-of-properties-and-item-in-an-msbuild-script/
The thing is there seems to be a global scope for the project and a local scope for the target . When entering the target, the global scope is copied and when exiting the target, the local scope is merged back. So a CallTarget will not get the modified local scope values but the DependsOnTargets will since the first target is exited before the entering the second target.
We do something similar in our build. We pass the version as a command line parameter.
In our TFSBuild.proj we set the version to 0.0.0.0 if no version was supplied:
<!--Our assembly version. Pass it in from the command prompt like this: /property:Version=1.0.0.0-->
<PropertyGroup>
<Version>0.0.0.0</Version>
</PropertyGroup>
<PropertyGroup>
<!--Used to ensure there is a newline before our text to not break the .cs files if there is no newline at the end of the file.-->
<newLine>%0D%0A</newLine>
Then we do this:
<Target Name="BeforeCompile">
<!--Update our assembly version. Pass it in from the command prompt like this: /property:Version=1.0.0.0-->
<!--attrib needs to be run first to remove the read only attribute since files from tfs are read only by default.-->
<Exec Command='attrib -R $(SolutionRoot)\Source\Project\GlobalAssemblyInfo.cs' />
<WriteLinesToFile File="$(SolutionRoot)\Source\Project\GlobalAssemblyInfo.cs"
Lines='$(newLine)[assembly: AssemblyVersion("$(Version)")]'/>
</Target>

Categories