imbACE BasicPack – Getting Started with multi-target projects

imbACE BasicPack extension

Initial steps:

• Install the extension
• Create new Solution and follow the tutorials below

Tutorial:

Creating multi-target Project in Visual Studio 2017

The main purpose of this template is to help you with setting up a multi-target project – a feature that is still only partially supported by the Visual Studio 2017 GUI (15.7.4). Other goals of the template design are to streamline Build/Pack/Local Deployement of the NuGet Packages, now integrated with the new Visual Studio Project structure – aka: the SDK project type / PackageReference / .NET Standard project.

Blank Multi-target project template

Selected framework, in Add New Project dialog is not applied. Instead, you’ll have 3 targets predefined in the Project Template (regardless the framework you selected in the dialog):

Multi-target project content

To activate other features of the Template, open Project file .csproj (Right click on the Project in Solution Explorer > Edit project).

Project file – multi-target project

Important tasks, when it comes to NuGet Package development cycle, are:

• deployment to local repository
• version number auto-increment

Doing these tasks manually is really annoying. To enable auto execution of the first task, scroll to the bottom of the project XML tree. At the bottom of the XML you’ll find two commented out blocks. The lower, if enabled (by removing XML comment opening and closing tags), activates custom MSBuild Target. The Target “CopyPackage” takes .nupkg (NuGet) package, after each build (Pack) and copies to the specified DestinationFolder. Make sure to set DestinationFolder to your local NuGet repository. Using this feature, you don’t need buggy Visual Studio Extensions like NuGet Deploy. The extension is really useful, but it has a bug in getting proper Solution/Project path, so it creates chaos on root of the harddrive.

In case of single-target project
You may use the MSBuild Target trick/patch in single-target projects, just in such case, you’ll have to adjust SourceFiles attribute, since current value will give you Build error as MSBuild will find no NuGet packages at path being set.

For the single-target projects, the node below would probably do the job:

<Target Name="CopyPackage" AfterTargets="Pack">
<Copy SourceFiles="$(OutputPath)..\$(PackageId).$(PackageVersion).nupkg" DestinationFolder="G:\imbVelesOpenSource\LocalNuGet\imbVelesSecondGeneration\" /> </Target> Of course, here too, you have to adjust DestinationFolder, to match your Local NuGet Repository. If you still have no local NuGet feed (or repository) set, open Tools > Options > NuGet Package Manager tab, and click + to add directory on your PC or available network location: NuGet local feed – screenshot from my VS instance When it comes to version number auto-increase, there are few extensions (NuGet Deploy also has such function but I really don’t like how it works), where Automatic Versions 1 provides really robust configuration system. The extension allows you to manage the settings on several levels of hierarchy: Global > Solution > Project > Build Configuration. While in description, it promises to manage Package Version increase, current build, of the extension, has no Package Version properties accessible trough Options/Settings GUI. However, with small hack, in the Project’s XML, the Package version auto-increase can be activated. To do so, remove comment tags, from the block above the mentioned Target instruction. With these two XML nodes, you can control if version auto-increase should happen on each Rebuild, or only on Release Build. Just by removing the comment tags, you’ll not get the Automatic Versions 1, working as desired – you’ll have to make few clicks in their GUI (see) too. These nodes are included in the Template just because they are not mentioned nor supported in the official tutorial/documentation of the extension. <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
<UpdatePackageVersion>True</UpdatePackageVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'"> <UpdatePackageVersion>True</UpdatePackageVersion> </PropertyGroup> Now, above these XML nodes you’ll find compiler flags/constants declarations. Each flag, corresponds to one of the targets. Using #if and #endif compiler directives, you can use these constants to add target-specific code, anywhere in the project. #if NET40 // Some code that you have to insert just to help poor old .NET 4.0 Framework to catch up with the teens #endif #if NET45 // Some code that you wouldn't need to use if they haven't removed that much from NET 4.0 #endif #if NETCORE // Well, cross-platform development has its price. So, usually you have to include many things, normally available in the full framework, just to get project built #endif (just noticed, that by mistake, the Template declares “NETCORE” flag/constant, not NETSTANDARD20 – however, it could be any string you like, it is only important you know what means what) In my experience, very few times it is really required to use #if / #endif code blocks to make your project cross-platform. In practice, much often, you’ll need to include different packages, for different build targets, in order to get things going. To do it, you go back to the Project .csproj file and add something like shown below (wherever you want, just make sure that the nodes are direct children of the root node ( <Project></Project>): <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="MySql.Data" Version="8.0.11" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net45'"> <PackageReference Include="MySql.Data" Version="6.9.12" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'net40'">
<PackageReference Include="MySql.Data" Version="6.9.12" />
</ItemGroup>

Such, conditional inclusion of NuGet packages is required since, in this example, MySql.Data 6.9.12 will not work on netstandard2.0 target and 8.0.11 support only newest frameworks. Below is another example. In this case we are providing, additional packages for System.Drawing.Primitives and System.ComponentModel.(Data)Annotations namespaces – that are not available in .NET Standard 2.0 or .NET 4.0, by default.

<ItemGroup Condition="'$(TargetFramework)' == 'net45'"> <PackageReference Include="System.Drawing.Primitives"> <Version>4.3.0</Version> </PackageReference> <PackageReference Include="System.ComponentModel.Annotations"> <Version>4.4.1</Version> </PackageReference> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.ComponentModel.Annotations">
<Version>4.4.1</Version>
</PackageReference>
<PackageReference Include="System.Drawing.Primitives">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net40'"> <Reference Include="System.ComponentModel.DataAnnotations"> </Reference> </ItemGroup> For the end: do not forget to update your assembly/package information at the main node of the Project XML. <PropertyGroup> <TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks> <ApplicationIcon>imbACE.ico</ApplicationIcon> <GeneratePackageOnBuild>True</GeneratePackageOnBuild> <PackageId>$safeprojectname$</PackageId> <Version>1.0.0</Version> <Title>$safeprojectname$</Title> <Summary></Summary> <PackageIconUrl>http://doc.veles.rs/imbACE.ico</PackageIconUrl> <PackageProjectUrl>http://blog.veles.rs/</PackageProjectUrl> <PackageLicenseUrl>http://doc.veles.rs/LICENCE.txt</PackageLicenseUrl> <Copyright>Copyright ©$registeredorganizationyear$</Copyright> <Description></Description> <NeutralLanguage>en-US</NeutralLanguage> <PackageReleaseNotes> </PackageReleaseNotes> <PackageTags></PackageTags> <RepositoryUrl>https://github.com/$registeredorganization$</RepositoryUrl> <RepositoryType>GitHub</RepositoryType> <Authors>$registeredorganization$</Authors> <Company>$registeredorganization$</Company> <AssemblyVersion>1.0.0</AssemblyVersion> <FileVersion>1.0.0</FileVersion> <Product>$safeprojectname\$</Product>
</PropertyGroup>