How to Exclude Semicolons from MSBuild ExtensionPack.FileSystem.File Replacements - regex

I'm using MSBuild extension pack to replace lines in .proj files.
I'm replacing multiple lines with multiple lines. The lines it's outputting still have a semi colon at the end even when I do a transform.
<ItemGroup>
<TestFile Include="regextest.xml" />
<MyLines Include ="%3CItemGroup%3E%0A"/>
<MyLines Include ="%09%3CReference Include=%22Stuff%22%3E%0A" />
<MyLines Include ="%09%09%3CHintPath%3E..\..\packages\secret.dll%3C/HintPath%3E%0A" />
<MyLines Include ="%09%09%3CPrivate%3ETrue%3C/Private%3E%0A" />
<MyLines Include ="%09%3C/Reference%3E%0A" />
<MyLines Include ="%3C/ItemGroup%3E%0A" />
</ItemGroup>
<Target Name="Default">
<MSBuild.ExtensionPack.FileSystem.File TaskAction="Replace"
TextEncoding="ASCII"
RegexPattern="%3CProjectReference"
RegexOptionList="IgnoreCase"
Replacement="#(MyLines->'%(Identity)')"
Files="#(TestFile)" />
</Target>
And this is the output:
<ItemGroup>
; <Reference Include="Stuff">
; <HintPath>..\..\packages\secret.dll</HintPath>
; <Private>True</Private>
; </Reference>
;</ItemGroup>
Doing it without the transform still has them there too.

One easy way to handle multi-line replacement strings is to form them in a CDATA block inside of a property instead of a collection of single-line items (this is where the semicolons come from). In this case, you could create the multi-line replacement string as a property and then assign its value to an item, then pass the item to the Replace task action:
<PropertyGroup>
<MyMultiLine>
<![CDATA[
%3CItemGroup%3E
%3CReference Include="Stuff"%3E
%3CHintPath%3E..\..\packages\secret.dll%3C/HintPath%3E
%3CPrivate>True%3C/Private%3E
%3C/Reference%3E
%3C/ItemGroup%3E
]]>
</MyMultiLine>
</PropertyGroup>
<ItemGroup>
<TestFile Include="regextest.xml" />
<MyMultiLineItem Include="$(MyMultiLine)" />
</ItemGroup>
<Target Name="Default">
<MSBuild.ExtensionPack.FileSystem.File TaskAction="Replace"
TextEncoding="ASCII"
RegexPattern="%3CProjectReference"
RegexOptionList="IgnoreCase"
Replacement="#(MyMultiLineItem ->'%(Identity)')"
Files="#(TestFile)" />
</Target>

Related

Can ItemGroups in new VisualStudio 2017 csproj files be merged?

For example, I have a .csproj file with the following structure:
...
<ItemGroup>
<Compile Include="...">
<Link>...</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="..." Version="..." />
</ItemGroup>
... there may even be some other tags in between ItemGroups ...
<ItemGroup>
<ProjectReference Include="..." />
</ItemGroup>
<ItemGroup>
<Reference Include="...">
<HintPath>...</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Update="...">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
... There may be even more additional ItemGroups with other content ...
...
Can I merge them all into one, something like this?
...
<ItemGroup>
<PackageReference Include="..." Version="..." />
<ProjectReference Include="..." />
<Compile Include="...">
<Link>...</Link>
</Compile>
<Reference Include="...">
<HintPath>...</HintPath>
</Reference>
<None Update="...">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
...
I've done it, there seem to be no problem from what I observe at the moment, but I don't want me or other developers to run into them later, not knowing they may be linked to this action.
Merging them is no problem, it is equivalent from the MSBuild API.
However, some tooling may look for item groups containing only specific things (e.g. NuGet wants to add to item groups that already contain package references), but that means that in the worst case, tools may add additional item groups.

Force project references to be included in netstandard nuget package

I have a netstandard project which includes two project references. Visual studio 2017 is being used to build the nukpg. When the project is built the produced nupkg only contains the assembly produced by that project and lists the two project references as nuget dependencies. Is there a way to force the packaging to include those assemblies as lib files?
csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<RootNamespace>Verifier.Observations.DevOps.Health</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<VersionPrefix>1.0.1</VersionPrefix>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Verifier.Observations.Aspects\Verifier.Observations.Aspects.csproj" />
<ProjectReference Include="..\Verifier.Observations\Verifier.Observations.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.Composition"/>
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
Update
Based upon feedback from #alexgiondea-msft the package is now created as desired using the following
csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>1.0.1</VersionPrefix>
<TargetFramework>net462</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<NuspecFile>Verifier.Observations.DevOps.Health.Nuspec</NuspecFile>
<NuspecProperties>version=$(VersionPrefix);id=$(MSBuildProjectName);author=$(Authors);copy=$(Copyright);iconUrl=$(PackageIconUrl)</NuspecProperties>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Verifier.Observations.Aspects\Verifier.Observations.Aspects.csproj" />
<ProjectReference Include="..\Verifier.Observations\Verifier.Observations.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
nuspec
<package >
<metadata>
<id>$id$</id>
<version>$version$</version>
<title>$title$</title>
<authors>$author$</authors>
<owners>$author$</owners>
<iconUrl>$iconUrl$</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Inspect automation service to ensure it is up and operational</description>
<releaseNotes></releaseNotes>
<copyright>$copy$</copyright>
<tags>verifier-observation-plugin automation</tags>
<dependencies>
<group targetFramework="net462" />
</dependencies>
<references>
<group targetFramework="net462">
<reference file="Verifier.Observations.DevOps.Automation.dll" />
</group>
</references>
</metadata>
<files>
<file src="bin\*\net462\*.dll" target="lib\net462" />
<file src="bin\*\net462\*.pdb" target="lib\net462" />
</files>
</package>
You can control where assemblies are deployed in the nuget package using an item in an itemgroup, similar to this:
<ItemGroup>
<None Include="!!path_to_assembly!!">
<PackagePath>lib\net462</PackagePath>
<Pack>true</Pack>
<Visible>false</Visible>
</None>
</ItemGroup>
That should include the specified assembly in the package.
You can add the following target to your .csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net47</TargetFrameworks>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ClassLibrary2\ClassLibrary2.csproj" PrivateAssets="all" />
<ProjectReference Include="..\ClassLibrary3\ClassLibrary3.csproj" Condition="'$(TargetFramework)' == 'net47'" PrivateAssets="all" />
</ItemGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
<ItemGroup>
<BuildOutputInPackage Include="#(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
</ItemGroup>
</Target>
</Project>
Source 1
Source 2
Reference: Advanced extension points to create customized package

MSBuild: Do I need Target Rebuild?

I use a build file to build and test my project.
I have a Compile-Target which has this line "Targets = "Rebuild". Do I really need this line? Using Visual Studio I know that I can clean a Solution and build it again, or I can just rebuild the solution.
In my msbuild-file I delete my main folder BuildArtifacts before creating him again. I used this Tutorial and I don't know why he uses Target=Rebuild?
This is my build file:
<Project ToolsVersion="4.0" DefaultTargets="RunUnitTests" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Falls Eigenschaften nicht gesetzt -> Release & Any CPU als default-->
<PropertyGroup>
<!-- ... -->
</PropertyGroup>
<ItemGroup>
<!-- ... -->
</ItemGroup>
<!-- All the stuff go into my main folder -->
<Target Name="Init" DependsOnTargets="Clean">
<MakeDir Directories="#(BuildArtifacts)" />
</Target>
<!-- delete my main folder -->
<Target Name="Clean">
<RemoveDir Directories="#(BuildArtifactsDir)" />
</Target>
<!-- delete NUnit-Files -->
<Target Name="CleanAfter">
<RemoveDir Directories="#(NunitDir)" />
</Target>
<Target Name="Compile" DependsOnTargets="Init">
<MSBuild Projects="#(SolutionFile)"
Targets="Rebuild"
Properties="OutDir=%(BuildArtifactsDir.FullPath);
Configuration=$(Configuration);
Platform=$(BuildPlatform)" />
</Target>
<Target Name="RunUnitTests" DependsOnTargets="Compile">
<Exec Command='"#(NUnitConsole)" "#(UnitTestsDLL)" --result=console-test.xml --work=BuildArtifacts' />
<CallTarget Targets="CleanAfter" />
</Target>
</Project>
This depends on your own needs: do you need the whole solution to be rebuilt or not? Arguably, on a build server, you want to do a complete clean/rebuild after every commit to make sure the codebase is sane. Removing just the output directory (I assume that is what the Clean starget does) doesn't necessarily remove all object files as well since they typically go into the intermediate directory which might not be the same as the output directory.

MSBuild RegEx Assembly Version

Trying to create an MSBuild task that outputs my code to a folder. All is working except the Regular Expression. My code:
<Target Name="AfterBuild">
<GetAssemblyIdentity AssemblyFiles="$(OutDir)$(TargetFileName)">
<Output TaskParameter="Assemblies" ItemName="TheVersion" />
</GetAssemblyIdentity>
<PropertyGroup>
<Pattern>(\d+)\.(\d+)\.(\d+)</Pattern>
<In>%(TheVersion.Version)</In>
<OutVersion>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern)))</OutVersion>
</PropertyGroup>
<ItemGroup>
<OutputFiles Include="$(OutDir)*" Exclude="*.tmp" />
<SolnFiles Include="$(SolutionDir)INDIVIDUAL.txt;$(SolutionDir)LICENSE.txt;$(SolutionDir)README.md" />
</ItemGroup>
<Copy SourceFiles="#(OutputFiles)" DestinationFolder="$(SolutionDir)dache-%(OutVersion)\Tests" SkipUnchangedFiles="true" />
<Copy SourceFiles="#(SolnFiles)" DestinationFolder="$(SolutionDir)dache-%(OutVersion)\" SkipUnchangedFiles="true" />
</Target>
When I run this, I get this error:
The item "D:\Dache\INDIVIDUAL.txt" in item list "SolnFiles" does not define a value for metadata "OutVersion". In order to use this metadata, either qualify it by specifying %(SolnFiles.OutVersion), or ensure that all items in this list define a value for this metadata.
When I try %(SolnFiles.OutVersion) it comes up blank. I'm doing something dumb here, what is it?
Took me a few to figure it out. PropertyGroup variables are referenced as $(Var) while ItemGroup output variables are #() and GetAssemblyIdentity is %() - so I changed:
<Copy SourceFiles="#(OutputFiles)" DestinationFolder="$(SolutionDir)dache-%(OutVersion)\Tests" SkipUnchangedFiles="true" />
<Copy SourceFiles="#(SolnFiles)" DestinationFolder="$(SolutionDir)dache-%(OutVersion)\" SkipUnchangedFiles="true" />
to this:
<Copy SourceFiles="#(OutputFiles)" DestinationFolder="$(SolutionDir)dache-$(OutVersion)\Tests" SkipUnchangedFiles="true" />
<Copy SourceFiles="#(SolnFiles)" DestinationFolder="$(SolutionDir)dache-$(OutVersion)\" SkipUnchangedFiles="true" />
and it worked.

I want to use NAnt's foreach to iterate files in a folder, how to force alphabetic iteration?

I have an NAnt task "ship" to package my current .sql scripts into a build, then name the build with an incrementing int {######} and copy it to a build folder.
I have another NAnt task which executes those build scripts.
They must execute in order, but in my last attempt, they were not. Can I "force" NAnt to work alphabetically?
FAIL:
<fileset basedir="source\tsql\builds\" id="buildfiles">
<include name="*.sql.template.sql" />
<exclude name="*.sql" />
<exclude name="*asSentTo*" />
</fileset>
<foreach item="File" property"filename">
<in refid="buildfiles">
<echo message="${filename}" />
</in>
</foreach>
PASS:
<foreach item="File" property="filename" in="source\tsql\builds">
<do>
<if test="${string::ends-with(filename,'.sql.template.sql')}">
<echo message="${filename}" />
</if>
</do>
</foreach>
To satisfy my curiosity I tried to reproduce the problem with this script:
<?xml version="1.0"?>
<project name="foreach.test" default="foreach.alpha">
<target name="foreach.alpha">
<foreach item="File" in="C:\foo" property="filename">
<do>
<echo message="${filename}" />
</do>
</foreach>
</target>
</project>
The filenames are printed out in alphabetical order. So conventional use of foreach already seems to be the solution to the problem.
Here is how you do it with a fileset
<fileset id="mySet">
<include name="*.sql" />
</fileset>
<copy>
<fileset refid="mySet" />
</copy>
<foreach item="File" property="filename">
<in>
<items refid="mySet" />
</in>
<do>
<echo message="Copied files: ${filename} to directory: ${Folder}." />
</do>
</foreach>