Choose position of ItemGroup files generated in a Target via a Task - f#

I've got the following set up (uninteresting XML removed for brevity):
MyProject.fsproj
<Project ...>
<Import Project="MyTask.props" />
...
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
MyTask.props
<Project ...>
<UsingTask XXX.UpdateAssemblyInfo />
<Target Name="UpdateAssemblyInfo"
BeforeTargets="CoreCompile">
<UpdateAssemblyInfo ...>
<Output
TaskParameter="AssemblyInfoTempFilePath"
PropertyName="AssemblyInfoTempFilePath" />
</UpdateAssemblyInfo>
<ItemGroup>
<Compile Include="$(AssemblyInfoTempFilePath)" />
</ItemGroup>
</Target>
</Project>
The problem is that the ItemGroup added by MyTask.props is added last, despite being imported right at the very start of the project. I assume that this is because the ItemGroup is not actually imported then - it's added by when the task is run.
This isn't a good thing in F#, as file order is important - including the file at the end of the build list means it's impossible to build an EXE, for example (as the entrypoint must be in the last file).
Hence my question - is there a way for me to output an ItemGroup as part of a Target and have that generated ItemGroup be first?

A bit late, but this may help someone in the future, I'm not using the import tag on this sample, but it will work the same way, the important part is the "UpdateAssemblyInfo" target, the main idea is to clear and regenerate the Compile ItemGroup using the appropriate sort order.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Target Name="Build" DependsOnTargets="UpdateAssemblyInfo">
</Target>
<Target Name="UpdateAssemblyInfo">
<!-- Generate your property -->
<PropertyGroup>
<AssemblyInfoTempFilePath>ABC.xyz</AssemblyInfoTempFilePath>
</PropertyGroup>
<!-- Copy current Compile ItemGroup to TempCompile -->
<ItemGroup>
<TempCompile Include="#(Compile)"></TempCompile>
</ItemGroup>
<!-- Clear the Compile ItemGroup-->
<ItemGroup>
<Compile Remove="#(Compile)"/>
</ItemGroup>
<!-- Create the new Compile ItemGroup using the required order -->
<ItemGroup>
<Compile Include="$(AssemblyInfoTempFilePath)"/>
<Compile Include="#(TempCompile)"/>
</ItemGroup>
<!-- Display the Compile ItemGroup ordered -->
<Message Text="Compile %(Compile.Identity)"/>
</Target>
</Project>

Related

MsDeploySkipRules being ignored on project publish

I want to exclude ALL files & folders in wwwroot\data from being published to my website folder. The files in my website folder are the latest copy and should never be overwritten when I publish the project.
Reference Microsoft documentation
The documentation indicates using this xml in .csproj file but I can't get it to work. I've tried putting this in both my .csproj file and .pubxml with no success.
What am I doing wrong? Can someone tell me if this actually works?
<ItemGroup>
<MsDeploySkipRules Include="CustomSkipFolder">
<ObjectName>dirPath</ObjectName>
<AbsolutePath>wwwroot\\data</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
Here is my .pubxml file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>C:\www\VisualStudio2019-Core3</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectGuid>881d6bfb-ed04-41d7-b600-0e26765ff90e</ProjectGuid>
<SelfContained>false</SelfContained>
</PropertyGroup>
Tried putting code here -->>
</Project>
Here is my .csproj file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>VisualStudio2019_Core3</RootNamespace>
<UserSecretsId>93d2ec1f-4bcc-4aa5-ad65-4bdbdce5690a</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.5" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.2" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="2.19.60" />
</ItemGroup>
Tried putting code here -->>
</Project>
Additional information:
Visual Studio 2019
Web Application Core 3.1
Publishing via Studio menus...not command line.
I used this (referring to my webapp) after end tag PropertyGroup
<ItemGroup>
<Content Remove="appsettings.Development.*" />
<Content Remove="bundleconfig.json" />
<Content Remove="wwwroot\js\Site.js" />
<Content Remove="wwwroot\js\webcam-easy.js" />
<Content Remove="wwwroot\js\webcam-app.js" />
<Content Remove="wwwroot\css\Site.css" />
<Content Update="wwwroot\js\Site.js" CopyToPublishDirectory="Never" />
<Content Update="wwwroot\js\webcam-easy.js" CopyToPublishDirectory="Never" />
<Content Update="wwwroot\js\webcam-app.js" CopyToPublishDirectory="Never" />
<Content Update="wwwroot\css\Site.css" CopyToPublishDirectory="Never" />
<ResolvedFileToPublish Include="wwwroot\css\xxxxx.min.css">
<RelativePath>wwwroot\css\xxxxx.min.css</RelativePath>
</ResolvedFileToPublish>
<ResolvedFileToPublish Include="wwwroot\js\xxxxx.min.js">
<RelativePath>wwwroot\js\xxxxx.min.js</RelativePath>
</ResolvedFileToPublish>
<MsDeploySkipRules Include="connectionconfig">
<ObjectName>filePath</ObjectName>
<AbsolutePath>connection.json</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>

Build project with multiple targetframeworks in TFS as a NuGet package

I want to build my NuGet package in one of my TFS build steps.
Because .NET Standard 2.0 is currently not supported by the TFS NuGet step, I am using /t:pack as the build argument in my MSBuild invocation.
When I only use one TargetFramework it is working fine. My assemblies get a version and my NuGet package too.
Even if I am using an AssemblyInfo.cs in .NET Standard 2.0 - explained in this answer.
So far no problem, but if I want to build now on two different TargetFrameworks like <TargetFramework>netstandard2.0;net45</TargetFramework> my NuGet package won't get a Version.
So in the end this is working and is generating a versioned NuGet file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);ReadPackageVersionFromOutputAssembly</GenerateNuspecDependsOn>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<Target Name="ReadPackageVersionFromOutputAssembly" DependsOnTargets="Build">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="PackAssembly" />
</GetAssemblyIdentity>
<PropertyGroup>
<PackageVersion>%(PackAssembly.Version)</PackageVersion>
</PropertyGroup>
</Target>
<ItemGroup>
<Compile Remove="AccessControl\**" />
<EmbeddedResource Remove="AccessControl\**" />
<None Remove="AccessControl\**" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\common\Version.cs" Link="Properties\Version.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.8.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="MyEngine">
<HintPath>..\libs\NetStandard\MyEngine.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>
So here is my current .csproj file. This builds too and also generates a NuGet file. But in this case my NuGet file is unversioned. Why is it this way and how can I fix it?
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);ReadPackageVersionFromOutputAssembly</GenerateNuspecDependsOn>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<Target Name="ReadPackageVersionFromOutputAssembly" DependsOnTargets="Build">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="PackAssembly" />
</GetAssemblyIdentity>
<PropertyGroup>
<PackageVersion>%(PackAssembly.Version)</PackageVersion>
</PropertyGroup>
</Target>
<ItemGroup>
<Compile Remove="AccessControl\**" />
<EmbeddedResource Remove="AccessControl\**" />
<None Remove="AccessControl\**" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\common\Version.cs" Link="Properties\Version.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.8.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Reference Include="MyEngine">
<HintPath>..\libs\NetStandard\MyEngine.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="MyEngine">
<HintPath>..\libs\NET45\MyEngine.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>
The targets can be adapted to support multi-targeting like this:
<Target Name="ReadPackageVersionFromOutputAssemblySingleTfm" Returns="#(PackAssembly)" Condition="'$(IsCrossTargetingBuild)' != 'true'">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="PackAssembly" />
</GetAssemblyIdentity>
<PropertyGroup>
<PackageVersion>%(PackAssembly.Version)</PackageVersion>
</PropertyGroup>
</Target>
<Target Name="ReadPackageVersionFromOutputAssemblyMultipleTfms" Condition="'$(IsCrossTargetingBuild)' == 'true'">
<PropertyGroup>
<FirstTargetFramework>$([System.String]::Copy($(TargetFrameworks)).Split(';').GetValue(0))</FirstTargetFramework>
</PropertyGroup>
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="ReadPackageVersionFromOutputAssemblySingleTfm" Properties="TargetFramework=$(FirstTargetFramework)">
<Output TaskParameter="TargetOutputs" ItemName="PackAssembly" />
</MSBuild>
<PropertyGroup>
<PackageVersion>%(PackAssembly.Version)</PackageVersion>
</PropertyGroup>
</Target>
<Target Name="ReadPackageVersionFromOutputAssembly" DependsOnTargets="Build;ReadPackageVersionFromOutputAssemblySingleTfm;ReadPackageVersionFromOutputAssemblyMultipleTfms" />

TFS Express 2012 build failing

I have a VS solution hosted in a TFS 2012 Express with a Build system (service, controller, agent) in place. I also created a build definition with this build project file as target:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutDir Condition=" '$(OutDir)'=='' ">$(MSBuildThisFileDirectory)bin\</OutDir>
<Configuration Condition=" '$(Configuration)'=='' ">Release</Configuration>
<SourceHome Condition=" '$(SourceHome)'=='' ">$(MSBuildThisFileDirectory)</SourceHome>
<ToolsHome Condition=" '$(ToolsHome)'=='' ">$(MSBuildThisFileDirectory)tools\</ToolsHome>
</PropertyGroup>
<ItemGroup>
<Solution Include="$(SourceHome)*.sln">
<AdditionalProperties>OutDir=$(OutDir);Configuration=$(Configuration)</AdditionalProperties>
</Solution>
</ItemGroup>
<Target Name="RestorePackages">
<Exec Command=""$(MSBuildProgramFiles32)\NuGet\NuGet.exe" config -set http_proxy=http://********" />
<Exec Command=""$(MSBuildProgramFiles32)\NuGet\NuGet.exe" config -set http_proxy.user=******" />
<Exec Command=""$(MSBuildProgramFiles32)\NuGet\NuGet.exe" config -set http_proxy.password=********" />
<Exec Command=""$(MSBuildProgramFiles32)\NuGet\NuGet.exe" restore "$(SourceHome)*.sln"" />
</Target>
<Target Name="Clean">
<MSBuild Targets="Clean"
Projects="#(Solution)" />
</Target>
<Target Name="Build" DependsOnTargets="RestorePackages">
<MSBuild Targets="Build"
Projects="#(Solution)" />
</Target>
<Target Name="Rebuild" DependsOnTargets="RestorePackages">
<MSBuild Targets="Rebuild"
Projects="#(Solution)" />
</Target>
</Project>
But somehow the build keeps failing again and again. This is the error:
Exception Message: Access to the path '******************\packages\AjaxControlToolkit.15.1.4.0\Content\Web.config.transform' is denied. (type UnauthorizedAccessException).
I have no clue what's going on, any ideas?
You may check whether the files are read-only.
At the end the issue was the NETWORK SERVICE account used for the build service. That account does not seem to have enough privileges to use the proxy. Changed it to my account finally worked. I know it's not the ideal approach, but does the trick for now.

ant large project. How to enforce common output directory structure - include,import and inhertAll=false?

I have a toplevel ant project and many subprojects under it.
./build.xml
./datamodel_src/src/build.xml
./datamodel_src/src/module1/build.xml
./datamodel_src/src/module2/build.xml
./infrastructure_src/src/build.xml
./interfaces_src/src/build.xml
Each of the subproject, I want to enforce a common output directory structure. Project will have a work area and each sub project will have its own work area under it. Each subproject should create its artifacts (lib, docs, classes etc) under a work area for the subproject.
So the output would be some thing like
c:/sandbox/mainprojectworkarea/subprojectworkarea/lib
c:/sandbox/mainprojectworkarea/subprojectworkarea/docs
c:/sandbox/mainprojectworkarea/subprojectworkarea/classes
Currently I do this as follows.
The toplevel build.xml is like below
<project name="toplevelproject" default="compile" basedir=".">
<target name="compile">
<ant dir="infrastructure_src/src" />
<ant dir="interfaces_src/src " /> <!--does not work-->
<ant dir="datamodel_src/src inhertAll=false" /> <!--works-->
</target>
</project>
common.xml is like below
<property environment="env" />
<property name="project.sandbox" value="${env.BUILD_HOME}/sandbox" />
<property name="sandbox" value="${project.sandbox}" />
<property name="pwa" value="${sandbox}/pwa" />
<property name="wa" value="${pwa}/${ant.project.name}" />
<property name="build" value="${wa}/build" />
<property name="lib" value="${wa}/lib" />
<property name="docs" value="${wa}/docs" />
<property name="exports" value="${wa}/exports" />
This is "included" into all projects. For example "datamodel_src/src/build.xml" is like below
<!DOCTYPE project [
<!ENTITY common SYSTEM "../../common.xml">
]>
<project name="dmodel" default="compile" basedir=".">
&common;
<target name="compile">
<echo message="will create lib in ${lib}"/>
<echo message="will create docs in ${docs}"/>
<ant dir="module1" inheritAll="false"/> <!--works fine-->
<ant dir="module2" /> <!--does not work -->
</target>
</project>
This works when I set inhertiAll=false for ant calls.
Is there a better and correct way to?
Expanding answer from Kevin to this question.
Using import the common.xml becomes a real project like below
<project name="toplevelproject" default="compile" basedir=".">
<property name="toplevel" value="settotrue"/>
<target name="compile">
<ant dir="infrastructure_src/src" />
<ant dir="interfaces_src/src" />
<ant dir="datamodel_src/src" />
</target>
</project>
The "datamodel_src/src/build.xml" is now some think like below.
<project name="dmodel" default="compile" basedir=".">
<import file="../../common.xml" />
<target name="compile">
<echo message="will create classes in ${build}"/>
<echo message="will create lib in ${lib}"/>
<ant dir="module1" inheritAll="false"/> <!--works fine-->
<ant dir="module2" /> <!--does not work -->
</target>
</project>
The import gives option to have common targets etc, hence I would go with it.
I'm doing something similar using imports rather than includes. All my common targets and properties are defined in a common build file and each subproject just imports the common file. When you import a file, the properties defined in that file become relative to the importing file.
So I would try doing the following:
Move your compile target from your subproject build files into your common.xml.
Import your common.xml into each subproject build.xml.

Msbuild and SLN unbindig

I want to unbind my sln file from TFS server and publish it on SVN is there any "easy" option to do this. It's easy to open sln and chose unbind option in Visual Studio, but does any one ever tried to automate this process? There is a solution to edit sln file using xmlpoke and deleting binding information, but is it safe?
I have some samples published on the MSDN Code Gallery for the TFS 2010 SDK that illustrate how to do this with MSBuild and the MSBuild Community Tasks. Here's a snippet of MSBuild script from the WorkItemObjectModel sample's WorkItemType.csproj file:
<Import Project="$(MSBuildExtensionsPath32)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<ItemGroup>
<SourceFiles
Include="$(SolutionDir)**/*.*"
Exclude="$(SolutionDir)Package/**/*.*;$(SolutionDir)**/bin/**/*.*;$(SolutionDir)**/obj/**/*.*;$(SolutionDir)**/internal.proj;$(SolutionDir)**/*.*scc;$(SolutionDir)$(SolutionName).zip">
<Visible>False</Visible>
</SourceFiles>
</ItemGroup>
<Target Name="AfterBuild" Condition="'$(Configuration)'=='Release'"
Inputs="#(SourceFiles)" Outputs="$(SolutionDir)$(SolutionName).zip">
<Delete
Files="$(SolutionDir)$(SolutionName).zip"
Condition="Exists('$(SolutionDir)$(SolutionName).zip')" />
<PropertyGroup>
<PackageDir>$(SolutionDir)Package\</PackageDir>
</PropertyGroup>
<MakeDir
Directories="$(PackageDir)" />
<Copy
SourceFiles="#(SourceFiles)"
DestinationFiles="$(PackageDir)%(RecursiveDir)%(Filename)%(Extension)" />
<Delete
Files="$(PackageDir)**/bin/**/*.*;$(PackageDir)**/obj/**/*.*" />
<RemoveDir
Directories="$(PackageDir)**/bin;$(PackageDir)**/obj" />
<Attrib
Files="#(PackageFiles)"
ReadOnly="false" />
<FileUpdate
Files="$(PackageDir)$(SolutionFileName)"
IgnoreCase="true"
Regex="^\s+GlobalSection\(TeamFoundationVersionControl\).+\n(\s*Scc.*\n)+\s+EndGlobalSection"
ReplacementText=" "
Multiline="true"
Singleline="false" />
<ItemGroup>
<ProjectFiles Include="$(PackageDir)**/*.*proj" />
</ItemGroup>
<FileUpdate
Files="#(ProjectFiles)"
Regex="<Scc[A-z]+>.+</Scc[A-z]+>"
ReplacementText=" " />
<ItemGroup>
<PackageFiles Include="$(PackageDir)**\*.*" />
</ItemGroup>
<Zip
Files="#(PackageFiles)"
WorkingDirectory="$(PackageDir)"
ZipFileName="$(SolutionDir)$(SolutionName).zip" />
<Delete
Files="#(PackageFiles)" />
<RemoveDir
Directories="$(PackageDir)" />
</Target>
In a nutshell, this script copies the source files to a temporary directory, removes the source control bindings from the solution and project files, then zips up the sources and finally deletes the temporary directory.

Resources