Normalizing a list of items in MSBuild - msbuild

I am trying to get a list of all unit test assemblies under the root of my project. I can do this as follows:
<CreateItem Include="**\bin\**\*.UnitTest.*.dll">
<Output TaskParameter="Include" ItemName="Items"/>
</CreateItem>
However, this will find the same DLLs multiple times since they exist in multiple sub-directories. Is there an easy way for me to normalize based on item metadata (ie. the file name and extension) so that I get a list of unique unit test DLLs? Or do I have to resort to writing my own task?

Even though this is old, I could never get Thomas solution to work myself, but I did find sort of a workaround using only built-in commands with v4.0 of msbuild:
<ItemGroup>
<TestAssemblies Include="$(SolutionRoot)\**\bin\*.Tests.dll" />
<TestItems Include="%(TestAssemblies.FileName)%(TestAssemblies.Extension)">
<ItemPath>%(TestAssemblies.Identity)</ItemPath>
</TestItems>
<DistinctTestItems Include="#(TestItems->Distinct())"></DistinctTestItems>
</ItemGroup>
<Message Text="%(DistinctTestItems.ItemPath)" Importance="high" />
Documentation: Item Functions

The MSBuild Extension Pack contains the task MSBuildHelper, supporting the command RemoveDuplicateFiles.
<CreateItem Include="**\bin\**\*.UnitTest.*.dll">
<Output TaskParameter="Include" ItemName="Items"/>
</CreateItem>
<MSBuild.ExtensionPack.Framework.MsBuildHelper TaskAction="RemoveDuplicateFiles" InputItems1="#(Items)">
<Output TaskParameter="OutputItems" ItemName="Items"/>
</MSBuild.ExtensionPack.Framework.MsBuildHelper>

I had a good search online and couldn't find any way of doing this. If anyone knows a clean built-in way then please let me know. In the meantime, I wrote a simple task to do the job. The usage looks like this:
<NormalizeByMetadata Items="#(ItemsToNormalize)" MetadataName="Filename">
<Output TaskParameter="NormalizedItems" ItemName="MyNormalizedItems"/>
</NormalizeByMetadata>
After the above task has executed, MyNormalizedItems will contain only those items from ItemsToNormalize that have a unique value for the Filename metadata. If two or more items have the same value for their Filename metadata, the first match will be included in the output.
The code for the MSBuild task is:
public class NormalizeByMetadata : Task
{
[Required]
public ITaskItem[] Items
{
get;
set;
}
[Required]
public string MetadataName
{
get;
set;
}
[Output]
public ITaskItem[] NormalizedItems
{
get;
private set;
}
public override bool Execute()
{
NormalizedItems = Items.Distinct(new ItemEqualityComparer(MetadataName)).ToArray();
return true;
}
private sealed class ItemEqualityComparer : IEqualityComparer<ITaskItem>
{
private readonly string _metadataName;
public ItemEqualityComparer(string metadataName)
{
Debug.Assert(metadataName != null);
_metadataName = metadataName;
}
public bool Equals(ITaskItem x, ITaskItem y)
{
if (x == null || y == null)
{
return x == y;
}
var xMetadata = x.GetMetadata(_metadataName);
var yMetadata = y.GetMetadata(_metadataName);
return string.Equals(xMetadata, yMetadata);
}
public int GetHashCode(ITaskItem obj)
{
if (obj == null)
{
return 0;
}
var objMetadata = obj.GetMetadata(_metadataName);
return objMetadata.GetHashCode();
}
}
}

Related

.Net 6 is requiring all non-nullable fields when I try to deserialize JSON

I'm converting an app from .Net standard to .Net 6, but my controllers are throwing errors if I don't pass in all non-nullable fields. e.g.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string CustomerType { get; set; }
}
When I try to pass that object to an action without one of the fields, it throws an error like "The FirstName field is required." How can I get around that? I'm using NewtonSoft via the startup file like this:
builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.UseMemberCasing();
});
builder.Services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
{
options.UseMemberCasing();
});
the easiest way is to open a project configuratuon file (just click on project name in a VS solution explorer) and remove option nullable
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable> <!-- change from enable or remove -->
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
or if you want to dissable nullabel check only in the controllers actions, you can try another option
builder.Services.AddControllers(
options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Building WP8.0 assembly from code

I'm trying to build a WP 8.0 project in code. I'm using the Project class and the Build method, source code is available here.
The code worked on Windows 8 using VS 2012, but while I upgraded to Windows 8.1 and VS2013, it stopped working. I know that Microsoft changed and removed the Build framework from .NET in VS, but I am still not sure why it happens and how can I fix it.
The error:
C:\Program Files
(x86)\MSBuild\Microsoft\WindowsPhone\v8.0\Microsoft.WindowsPhone.Common.targets(75,5):
error MSB4127: The "GetSilverlightFrameworkPath" task could not be
instantiated from the assembly "C:\Program Files
(x86)\MSBuild\Microsoft\WindowsPhone\v8.0\Microsoft.Silverlight.WindowsPhone.Build.Tasks.dll".
Please verify the task assembly has been built using the same version
of the Microsoft.Build.Framework assembly as the one installed on your
computer and that your host application is not missing a binding
redirect for Microsoft.Build.Framework. Unable to cast object of type
'Microsoft.Silverlight.Build.Tasks.GetSilverlightFrameworkPath' to
type 'Microsoft.Build.Framework.ITask'.C:\Program Files
(x86)\MSBuild\Microsoft\WindowsPhone\v8.0\Microsoft.WindowsPhone.Common.targets(75,5):
error MSB4060: The "GetSilverlightFrameworkPath" task has been
declared or used incorrectly, or failed during construction. Check the
spelling of the task name and the assembly name.
UPDATE:
By the way the WP 8.0 is compiled using VS2013, the problem occurs when trying to build it from code.
You are getting this error because Microsoft.Silverlight.WindowsPhone.Build.Tasks.dll is the assembly which holds GetSilverlightFrameworkPath, e.g. in the Silverlight 4 and 5 SDK:
// Decompiled with JetBrains decompiler
// Type: Microsoft.Silverlight.Build.Tasks.GetSilverlightFrameworkPath
// Assembly: Microsoft.Silverlight.Build.Tasks, Version=9.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// MVID: 2E9B62C4-1C0C-4AAD-8CBE-F7E074602879
// Assembly location: C:\Program Files (x86)\MSBuild\Microsoft\Silverlight\v5.0
//omitted for brevity
namespace Microsoft.Silverlight.Build.Tasks
{
public sealed class GetSilverlightFrameworkPath : Task
{
private const string SDKAssemblyFoldersExRegKey = "v5.0\\AssemblyFoldersEx";
public const string SDKLibrariesRegKey = "v5.0\\AssemblyFoldersEx\\Silverlight SDK Client Libraries";
public const string SDKRuntimeInstallPathRegKey = "SOFTWARE\\Microsoft\\Microsoft SDKs\\Silverlight\\v5.0\\ReferenceAssemblies";
public string RegistryBase { get; set; }
public string SilverlightPath { get; set; }
public string[] SilverlightSDKPaths { get; set; }
public string SilverlightRuntimeVersion { get; set; }
public override bool Execute()
{
//omitted for brevity
}
private string GetSilverlightPath()
{
//omitted for brevity
}
private string GetSDKRuntimeVersion()
{
//omitted for brevity
}
private string[] GetAllSilverlightSDKPaths()
{
//omitted for brevity
}
}
}
However, its implementation in WP v8.0 seems quite different indeed:
// Decompiled with JetBrains decompiler
// Type: Microsoft.Silverlight.Build.Tasks.GetSilverlightFrameworkPath
// Assembly: Microsoft.Silverlight.WindowsPhone.Build.Tasks, Version=9.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// MVID: 899BFFD0-786C-472A-8935-576EED0F4F90
// Assembly location: C:\Program Files (x86)\MSBuild\Microsoft\WindowsPhone\v8.0\Microsoft.Silverlight.WindowsPhone.Build.Tasks.dll
//omitted for brevity
namespace Microsoft.Silverlight.Build.Tasks
{
public sealed class GetSilverlightFrameworkPath : Task
{
public string RegistryBase { get; set; }
public string RuntimePathRegistryKey { get; set; }
public string RuntimeVersionRegistryKey { get; set; }
public string AdditionalRegistryBasePaths { get; set; }
public string SilverlightPath { get; set; }
public string[] SilverlightSDKPaths { get; set; }
public string SilverlightRuntimeVersion { get; set; }
public override bool Execute()
{
// omitted for brevity
}
private string GetSilverlightPath()
{
// omitted for brevity
}
private string GetSDKRuntimeVersion()
{
// omitted for brevity
}
internal string[] GetAllSilverlightSDKPaths()
{
// omitted for brevity
}
}
Therefore, it is quite natural that things do not work.
What you need to do is check that you are referencing the same SDK versions in all your projects.
The Task you are trying to instantiate during the build is in SL SDK 5 whereas currently for some reason the build process is trying to use the WP8.0 SDK.
Moreover, the code you referenced is part of a csproj that references Toolset 12.0:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
Are you certain that is desired?
This info might also be relevant to you.
If you provide more info on your actual project, it can probably be easily fixed. Right now, it is a bit difficult to judge how everything is set up.

HeatDirectory componentid duplicated

In my project there is several modules. Source files for them are generated by heatdirectory.
<HeatDirectory DirectoryRefId="ServerAdminService" OutputFile="Source\ServerAdminServiceSource.wxs" Transforms="Filter.xsl" Directory="..\..\Server\ServerServiceManager\bin\Debug\" PreprocessorVariable="var.ServerAdminServicePath" ComponentGroupName="ServerAdminServiceGroup" ToolPath="$(WixToolPath)" SuppressCom="true" SuppressFragments="true" SuppressRegistry="true" SuppressRootDirectory="true" AutoGenerateGuids="true" GenerateGuidsNow="false">
</HeatDirectory>
<HeatDirectory DirectoryRefId="ServerAdminService" OutputFile="Source\ServerAdminClientSource.wxs" Transforms="Filter.xsl" Directory="..\Setup\C24.ServerAdmin.UI\bin\Debug\" PreprocessorVariable="var.ServerAdminClientPath" ComponentGroupName="ServerAdminClientGroup" ToolPath="$(WixToolPath)" SuppressCom="true" SuppressFragments="true" SuppressRegistry="true" SuppressRootDirectory="true" AutoGenerateGuids="true" GenerateGuidsNow="false">
</HeatDirectory>
It work fine. I need to install them in one directory. But they use several libraries, which presents in both modules, and after generating source files consist the component with duplicate ID. Actually i don't know what to do. Does somebody have an idea?
I had the exact same issue.
I had solved it by creating a custom build task to run after the HeatDirectory task to append a suffix to the Id attribute.
<AddSuffixToHeatDirectory File="ReportFiles.Generated.wxs" Suffix="_r" />
The AddSuffixToHeatDirectory task is as such
public class AddSuffixToHeatDirectory : Task
{
public override bool Execute()
{
bool result = true;
Log.LogMessage("Opening file '{0}'.", File);
var document = XElement.Load(File);
var defaultNamespace = GetDefaultNamespace(document);
AddSuffixToAttribute(document, defaultNamespace, "Component", "Id");
AddSuffixToAttribute(document, defaultNamespace, "File", "Id");
AddSuffixToAttribute(document, defaultNamespace, "ComponentRef", "Id");
AddSuffixToAttribute(document, defaultNamespace, "Directory", "Id");
var files = (from x in document.Descendants(defaultNamespace.GetName("File")) select x).ToList();
Log.LogMessage("Saving file '{0}'.", File);
document.Save(File);
return result;
}
private void AddSuffixToAttribute(XElement xml, XNamespace defaultNamespace, string elementName, string attributeName)
{
var items = (from x in xml.Descendants(defaultNamespace.GetName(elementName)) select x).ToList();
foreach (var item in items)
{
var attribute = item.Attribute(attributeName);
attribute.Value = string.Format("{0}{1}", attribute.Value, Suffix);
}
}
private XNamespace GetDefaultNamespace(XElement root)
{
// I pieced together this query from hanselman's post.
// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
//
// Basically I'm just getting the namespace that doesn't have a localname.
var result = root.Attributes()
.Where(a => a.IsNamespaceDeclaration)
.GroupBy(a => a.Name.Namespace == XNamespace.None ? String.Empty : a.Name.LocalName, a => XNamespace.Get(a.Value))
.ToDictionary(g => g.Key, g => g.First());
return result[string.Empty];
}
/// <summary>
/// File to modify.
/// </summary>
[Required]
public string File { get; set; }
/// <summary>
/// Suffix to append.
/// </summary>
[Required]
public string Suffix { get; set; }
}
Hope that helps. I'm still using this method today and I've not done the legwork to look into a Transform or extending HeatDirectory instead.

How do I call static class methods from msbuild?

How does one call a class static method from msbuild and store its results in a list?
EDIT: Okay, let me explain a bit further. I am using sandcastle help file builder to generate documentation for my application. One of the requirements is that you must specify the documentation sources as follows:
<DocumentationSources>
<DocumentationSource sourceFile="$(MSBuildProjectDirectory)\..\src\myApp\bin\Debug\myApp.exe" xmlns="" />
<DocumentationSource sourceFile="$(MSBuildProjectDirectory)\..\src\myApp\bin\Debug\myApp.xml" xmlns="" />
</DocumentationSources>
Sandcastle Help File Builder comes with a utils assembly that has a way of retrieving all dll and xml files from a specified directory. I want to call the method from this assembly and store its result as a list of <DocumentationSource>. This is a static method which returns Collection<string>
Custom Tasks are great but potential overkill if you want to do something simple. I believe Draco is asking about the Property Functions feature in MSBuild 4.
An example of setting a property by using a static function (ripped directly from above page):
<Today>$([System.DateTime]::Now)</Today>
And to call a static function on parameters:
$([Class]:: Property.Method(Parameters))
Or, perhaps you want something crazier like inline tasks.
Create a custom task calling that static method and returning an array of ITaskItem.
Or
You could try using the MSBuild Extension Pack Assembly.Invoke :
<PropertyGroup>
<StaticMethodAssemblyPath>path</StaticMethodAssemblyPath>
</PropertyGroup>
<MSBuild.ExtensionPack.Framework.Assembly TaskAction="Invoke"
NetArguments="#(ArgsM)"
NetClass="StaticMethodClassName"
NetMethod="StaticMethodName"
NetAssembly="${StaticMethodAssemblyPath}">
<Output TaskParameter="Result" PropertyName="R"/>
</MSBuild.ExtensionPack.Framework.Assembly>
Generally, the most flexible option is to create a custom MSBuild task. This is all untested code meant to just to give you the idea:
In your msbuild file:
<UsingTask TaskName="FindFiles" AssemblyFile="FindFiles.dll" />
<!--
As you'll see below, SearchDirectory and SearchPatterns are input parameters,
MatchingFiles is an output parameter, SourceFiles is an ItemGroup assigned to
the output.
-->
<FindFiles SearchDirectory="$(MyDirectory)" SearchPatterns="*.dll;*.xml">
<Output ItemName="SourceFiles" TaskParameter="MatchingFiles" />
</FindFiles>
<!-- You can then use the generated ItemGroup output elsewhere. -->
<DocumentationSources>
<DocumentationSource sourceFile="#(SourceFiles)" xmlns="" />
</DocumentationSources>
FindFiles.cs:
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace FindFiles
{
public class FindFiles : Task
{
// input parameter
[Required]
public string SearchDirectory { get; set; }
// output parameter
[Required]
public string[] SearchPatterns { get; set; }
[Output]
public string[] MatchingFiles { get; private set; }
private bool ValidateParameters()
{
if (String.IsNullOrEmpty(SearchDirectory))
{
return false;
}
if (!Directory.Exists(SearchDirectory))
{
return false;
}
if (SearchPatterns == null || SearchPatterns.Length == 0)
{
return false;
}
return true;
}
// MSBuild tasks use the command pattern, this is where the magic happens,
// refactor as needed
public override bool Execute()
{
if (!ValidateParameters())
{
return false;
}
List<string> matchingFiles = new List<string>();
try
{
foreach (string searchPattern in SearchPatterns)
{
matchingFiles.AddRange(
Directory.GetFiles(SearchDirectory, searchPattern)
);
}
}
catch (IOException)
{
// it might be smarter to just let this exception fly, depending on
// how you want the task to behave
return false;
}
MatchingFiles = matchingFiles.ToArray();
return true;
}
}
}

MSBuild: How can I check if a process exists?

Is it possible to write a Condition in msbuild that checks if a certain process exists?
Or, alternatively, does anyone know of such a task?
Today, my process creates a pid file, which existence I check. But I do not like all the extra maintenance involved with such a file.
Any ideas?
There is no such task in MSBuild Extension Pack or in MSBuild Community Tasks. But you could easily create a such one. Something like this:
using System.Diagnostics;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace StackOverflow.MSBuild
{
public class IsProcessRunning : Task
{
private string processName;
private bool isRunning;
[Required]
public string ProcessName
{
get { return processName; }
set { processName = value; }
}
[Output]
public bool IsRunning
{
get { return isRunning; }
}
public override bool Execute()
{
if(string.IsNullOrEmpty(processName))
{
Log.LogError("ProcessName could not be empty");
return false;
}
foreach(Process clsProcess in Process.GetProcesses())
{
if(clsProcess.ProcessName.Contains(processName))
{
isRunning = true;
}
}
return true;
}
}
}
And you use it like that:
<UsingTask AssemblyFile="$(Task_Assembly_path)"
TaskName="StackOverflow.MSBuild.IsProcessRunning" />
<Target Name="TestTask">
<IsProcessRunning ProcessName="${Process}">
<Output ItemName="Result" TaskParameter="IsRunning"/>
</IsProcessRunning>
<Message Text="Process ${Process} is running"
Condition="'${Result}' == 'true'"/>
</Target>