Monday, September 08, 2008

I was looking at the documentation for the Exec task (yes I'm writing a new MSBuild book!) and discovered that it is lacking. So I decided to post the complete list of properties here. So here it is for those of you using it.

Name

Description 

Command 

The command which is to be executed. This is the only required parameter.

CustomErrorRegularExpression *

If provided this will be the regex used to determine if an error occurred.

CustomWarningRegularExpression *

If provided this will be the regex used to determine that a warning occurred.

ExitCode

Output property containing the exit code provided by the executed command.

IgnoreExitCode

If true then the Exec task will not fail the build based on the exit code. Otherwise the build is failed for any non-zero exit code.

IgnoreStandardErrorWarningFormat *

If true the output is not examined for errors and warnings.

Outputs

An input/output parameter that contains the output items from the task. This is not set by the Exec task itself but made available to be set by the consumer.

StdErrEncoding

An input/output parameter that specifies the encoding that is used for the standard error stream.

StdOutEncoding

An input/output parameter that specifies the encoding that is used for the standard output stream.

Timeout

Specifies the timout, in milliseconds for the command. After the specified amount of time has passed the command will be terminated. By default there is no timeout

ToolPath

Specifies the location of the tool.

WorkingDirectory 

Specifies the working directory.

* Denotes new properties in MSBuild 3.5

There is a slightly new behavior in this version of the task relating to detecting errors & warnings from the output of the command. Take a look at the forum entry Exec task and "error :" in output.

More to come on the new book later keep an eye here soon. If you have ideas about specific examples that should be demonstrated let me know, for example; how to zip a set of file, how to ftp files to a server, your example here, etc.

Sayed Ibrahim Hashimi

9/8/2008 12:23:19 AM (Eastern Daylight Time, UTC-04:00)  #     | 
Wednesday, July 30, 2008

I looked around for an extension method that would just randomize a list. I found a couple but they either didn't seem to work right or the modified the collection itself instead of creating a new collection and returning that. So I created one, it is pretty simple anywayz. The definition for it is shown below.

public static class IListExtensions
{
    public static IList<T> Randomize<T>(this IList<T> input)
    {
        if (input == null)
        { throw new ArgumentException("input"); }

        var test = (from p in input
                    select new { Id = rand.Next(), ListObject = p }).OrderBy(t => t.Id);

        IList<T> randomList = new List<T>();
        foreach (var item in test)
        {
            randomList.Add(item.ListObject);
        }

        return randomList;
    }

    static Random rand = new Random();
}

From here we use this method just like any other IList method, an example is shown below.

public void SampleRandomize()
{
    int numElemnets = 10;
    IList<int> numList = new List<int>();
    for (int i = 0; i < numElemnets; i++)
    {
        numList.Add(i);
    }

    IList<int> radnomList = numList.Randomize();

    for (int i = 0; i < randomList.Count; i++)
    {
        System.Diagnostics.Debug.WriteLine(string.Format("i: {0}", randomList[i]));
    }
}

I think it's pretty cool how you can create methods that can be soo easily used thanks to extension methods.


Sayed Ibrahim Hashimi

7/30/2008 1:31:15 AM (Eastern Daylight Time, UTC-04:00)  #     | 
Friday, June 27, 2008

This post is related to my  previous post MSBuild RE: Enforcing the Build Agent in a Team Build which is a response on the post by Michael Ruminer at http://manicprogrammer.com/cs/blogs/michaelruminer/archive/2008/06/19/enforcing-the-build-agent-in-a-team-build.aspx.

Basically every MSBuild element can contain a Condtions attribute. You can read more at MSBuild Conditions. So even targets can have conditions attached to them! Despite the fact that you can do this, you should not. I recommend that you do not use conditions on targets. Conditions on targets seem straight forward but after you take a closer look they are more complicated. We can take a look at some of the items that come to mind here.

Conditions on targets and DependsOnTargets don’t play well

Take a look at this simple project file

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="2.0"
         xmlns="
http://schemas.microsoft.com/developer/msbuild/2003"
         DefaultTargets="Demo">

  <PropertyGroup>
    <AllowTarget>true</AllowTarget>
  </PropertyGroup>

  <Target Name="SupressTarget">
    <CreateProperty Value="false">
      <Output PropertyName="AllowTarget" TaskParameter="Value"/>
    </CreateProperty>
    <Message Text=" ==== SupressTarget ==== " Importance="high"/>
    <Message Text="AllowTarget: $(AllowTarget)"/>
  </Target>
 
 
  <Target Name="Demo" Condition="'$(AllowTarget)'=='true'"
          DependsOnTargets="SupressTarget">
    <Message Text=" ===== Demo ===== " Importance="high" />

    <Message Text="AllowTarget: $(AllowTarget)"/>
  </Target>
 
</Project>

In this project the main target is Demo and it depends on a target SupressTarget which actually disables the Demo target based on its condition. If you execute the command msbuild /t:Demo you get the results shown below.

Most users would expect that the target SupressTarget target would execute which sets the AllowTarget value to false, and then the Demo target is skipped. But what is happening here is that by the time DependsOnTargets is evaluated the condition has already been evaluated and has passed! The even more interesting thing here is if you execute msbuild /t:SupressTarget;Demo the results are shown below.

 

So this time it was skipped, because SupressTarget was called before the Demo target was invoked so the condition evaluated to false.

 

Conditions and target batching doesn’t work well either

If you are batching a target and you want to execute the target for some batches but not others, this cannot be achieved with target conditions, for a few reasons but the simplest is: Either a target is or is not defined. When the target is going to be executed for the first time the condition is evaluated. If the condition is true it will exist, otherwise it will not.

I thought of some other issues but they are not coming to me at this time, but I think this is enough to deter you from using target dependencies. Instead of target dependencies you should take a look at other way of achieving the same results.

 

Sayed Ibrahim Hashimi

6/27/2008 2:28:35 AM (Eastern Daylight Time, UTC-04:00)  #     | 
Friday, June 20, 2008

This is a post that responds to a post by Michael Ruminer at Enforcing the Build Agent in a Team Build. I hope I correctly understand his situation. Here is my summary of it.

  • You have a specified value for the property BuildAgentName
  • You create various MSBuild scripts that will hook into a TeamBuild
  • You want to specify in each MSBuild script the names of the build agents that are allowed to run the build
  • If the current value for BuildAgentName is not in the list above, fail the build otherwise allow it to continue

My solution to this problem is to create a property, AllowedBuildAgents, which will contain a semi-colon separated list of the allowed build agent names. Then this is converted into an item, AllowedBuildAgentsItem, so batching can be preformed over it. If you are not familiar with MSBuild batching you can see my previous postings at:

The main objective here is to create an item that contains all the allowed names, AllowedBuildAgentsItem, and batch over them. If the value for BuildAgentName is equal to any value in that item then we want to allow the build. I create a property BuildAgentAllowed which defaults to false, and if that condition is true then I set BuildAgentAllowed to true. So I created a target DetermineIfAuthorizedBuildAgent that depends will throw an error if BuildAgentAllowed is false. Also this target depends on the target that will actually populate that value, which is the GetBuildAgentAllowed target. The build script is placed below. I added a few elements that were for demo only. Those are; value for BuildAgentName and BeforeEndToEndIteration target. See comments in the file (which can be downloaded at the end of this post).

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="2.0"
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         DefaultTargets="BeforeEndToEndIteration">

  <!--
  Assume that this value has been set already.
  I'm declaring it here for this demonstration.
  -->
  <PropertyGroup>
    <BuildAgentName>Sayed_001</BuildAgentName>
  </PropertyGroup>

 
 
  <PropertyGroup>
    <BeforeEndToEndIterationDependsOn>
      $(BeforeEndToEndIterationDependsOn);
      DetermineIfAuthorizedBuildAgent;
    </BeforeEndToEndIterationDependsOn>
  </PropertyGroup>

  <!--
  Define this here as a property so it can be passed
  via Properties from various callers.
  -->
  <PropertyGroup>
    <AllowedBuildAgents>Sayed_001;Sayed_003;Sayed_005</AllowedBuildAgents>
  </PropertyGroup>

  <!--
  Convert the property into an item so we can batch over it.
  -->
  <ItemGroup>
    <AllowedBuildAgentsItem Include="$(AllowedBuildAgents)"/>
  </ItemGroup>
  <!--
  Specify this value to false. If allowed it will be set to true.
  -->
  <PropertyGroup>
    <BuildAgentAllowed>false</BuildAgentAllowed>
  </PropertyGroup>
 
 

  <Target Name="PrintInfo">
    <Message Text="AllowedBuildAgentsItem: @(AllowedBuildAgentsItem,'%0a%0d')"/>
  </Target>

  <!-- Executing this target will make the GetBuildAgentAllowed target execute first -->
  <Target Name="DetermineIfAuthorizedBuildAgent"
          DependsOnTargets="GetBuildAgentAllowed">
    <Message Text="BuildAgentAllowed: $(BuildAgentAllowed)"/>
    <Error Text="This build can only be run on one of the following build agents: @(AllowedBuildAgentsItem)" 
           Condition="'$(BuildAgentAllowed)'=='false'" />
  </Target>
 
  <!--
  This target will be executed for each value in the AllowedBuildAgentsItem.
  If the BuildAgentName is equal to any value in AllowedBuildAgentsItem then the
  property BuildAgentAllowed will be set to true.
  -->
  <Target Name="GetBuildAgentAllowed" Outputs="%(AllowedBuildAgentsItem.Identity)">
    <Message Text="GetBuildAgentAllowed %25(AllowedBuildAgentsItem.Identity): %(AllowedBuildAgentsItem.Identity)"/>
   
    <CreateProperty Value="true" Condition="'$(BuildAgentName)'=='%(AllowedBuildAgentsItem.Identity)'">
      <Output PropertyName="BuildAgentAllowed" TaskParameter="Value"/>
    </CreateProperty>
   
  </Target>


 
  <!--
  This target is really executed by TeamBuild but I will
  put it here for demonstration. If this target executes then the build
  was allowed otherwise an error will be shown.
  -->
  <Target Name="BeforeEndToEndIteration" DependsOnTargets="$(BeforeEndToEndIterationDependsOn)">
    <Message Text="EndToEndIteration starting"/>
  </Target>
 
</Project>

The only reason that I have defined the allowed build agents in a property (AllowedBuildAgents) is because I want to allow external sources to specify it. You can specify properties through the command line or when using the MSBuild Task. Then the script converts this to an item. If I execute this project I would expect it to succeed because the defined value for BuildAgentName is contained in the list of AllowedBuildAgents. The results of executing this project are shown below.

Now we can change the value for BuildAgentName, by using the command msbuild.exe BuildAgent01.proj /p:BuildAgentName=Sayed_010. We would expect the build to fail. The results of this are below.

   

So the build failed as expected so we are good. Any questions?

   

BuildAgent01.proj

Sayed Ibrahim Hashimi

6/20/2008 12:22:57 AM (Eastern Daylight Time, UTC-04:00)  #     | 
Monday, June 16, 2008

If you are customizing your build process you may need to increase the verbosity that Visual Studio uses when building your projects. It is pretty easy to change this value, you just go to Tools->Options then find the Project and Solutions->Build and Run node. The dialog is shown here

The area where the verbosity setting lies is highlighted. I've got mine set to Detailed, which produces a lot of output. If you set it to Diagnostic, then you should be ready for a lot of output. Most like much more then you need!


Sayed Ibrahim Hashimi

6/16/2008 1:50:26 AM (Eastern Daylight Time, UTC-04:00)  #     | 
Monday, June 09, 2008

If you take a look the MSDN documentation for the reserved MSBuild Reserved Properties you will see many properties listed. For one reason or another there are actually several reserved properties that are missing from that list. Here is the list of all the reserved properties, the ones missing from that page are presented here in bold. For those that are listed on that page the description is taken from the MSDN. Also this is the list for MSBuild 3.5.

 

  • MSBuildNodeCount    
    • Contains the number of nodes, processes, which is being used to build the project.
  • MSBuildExtensionsPath32
    • Path to MSBuild extensions path specifically for the 32 bit directory. If you are using a 32 bit machine then this will be the same value as MSBuildExtensionsPath property.
    • This property can be overridden.
  • MSBuildProjectDirectoryNoRoot    
    • Same as MSBuildProjectDirectory but without the root listed in front.
  • MSBuildToolsPath    
    • Directory for the framework that is used to build the project.
  • MSBuildToolsVersion    
    • Tools version for the current build.
  • MSBuildBinPath    
    • The absolute path of the directory where the MSBuild binaries that are currently being used are located, for example, C:\Windows\Microsoft.Net\Framework\v2.0. This property is useful if you need to refer to files in the MSBuild directory.
  • MSBuildExtensionsPath    
    • The MSBuild folder under the Program Files directory. This location is a useful place to put custom target files. For example, your targets files could be installed at \Program Files\MSBuild\MyFiles\Northwind.targets and then imported in project files with the following XML.
      <Import Project="$(MSBuildExtensionsPath)\MyFiles\Northwind.targets"/>
      This property can be overridden.
  • MSBuildProjectDefaultTargets    
    • The complete list of targets specified in the DefaultTargets attribute of the Project element. For example, the following Project element would have an MSBuildDefaultTargets property value of A;B;C.
      <Project DefaultTargets="A;B;C" >
  • MSBuildProjectDirectory    
    • The absolute path of the directory where the project file is located, for example, C:\MyCompany\MyProduct.
  • MSBuildProjectExtension    
    • The file name extension of the project file, including the period, for example, .proj.
  • MSBuildProjectFile    
    • The complete file name of the project file, including the file name extension, for example, MyApp.proj.
  • MSBuildProjectFullPath    
    • The absolute path and complete file name of the project file, for example, C:\MyCompany\MyProduct\MyApp.proj.
  • MSBuildProjectName
    • The file name of the project file without the file name extension, for example, MyApp.
  • MSBuildStartupDirectory
    • The absolute path of the directory where MSBuild is invoked.

 

Take a look at this project file that prints out these values.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="PrintProperties"
ToolsVersion="3.5">

<Target Name="PrintProperties">
<Message Text="MSBuildNodeCount: $(MSBuildNodeCount)"/>
<Message Text="MSBuildExtensionsPath32: $(MSBuildExtensionsPath32)"/>
<Message Text="MSBuildProjectDirectoryNoRoot: $(MSBuildProjectDirectoryNoRoot)"/>
<Message Text="MSBuildToolsPath: $(MSBuildToolsPath)"/>
<Message Text="MSBuildToolsVersion: $(MSBuildToolsVersion)"/>
<Message Text="MSBuildBinPath: $(MSBuildBinPath)"/>
<Message Text="MSBuildExtensionsPath: $(MSBuildExtensionsPath)"/>
<Message Text="MSBuildProjectDefaultTargets: $(MSBuildProjectDefaultTargets)"/>
<Message Text="MSBuildProjectDirectory: $(MSBuildProjectDirectory)"/>
<Message Text="MSBuildProjectExtension: $(MSBuildProjectExtension)"/>
<Message Text="MSBuildProjectFile: $(MSBuildProjectFile)"/>
<Message Text="MSBuildProjectFullPath: $(MSBuildProjectFullPath)"/>
<Message Text="MSBuildProjectName: $(MSBuildProjectName)"/>
<Message Text="MSBuildStartupDirectory: $(MSBuildStartupDirectory)"/>

</Target>
</Project>

If you execute this you will get something like what's shown here.

 

I'm not sure how important this, but you may need to know one day.

ReservedProperties.proj

Sayed Ibrahim Hashimi

6/9/2008 1:52:31 AM (Eastern Daylight Time, UTC-04:00)  #     | 
Wednesday, June 04, 2008

Lately I've been doing some work with WCF and I have had a need to easily convert from object->XML and from XML->object. These XML strings were created using the DataContractSerializer. So I threw together this util class [yeah I know I probably shouldn't have these, but you know you do too J ]. There are really 2 methods GetDataContractXml and BuildFromDataContractXml. The GetDataContractXml returns the XML for the object provided and the BuildFromDataContractXml method will create the object from the XML string. These methods have both a generic and non-generic method. The non-generic versions are particularly useful if you are using this with reflection. Anywayz, the class itself is shown below, and you can download the file at the bottom of this post.

///
/// © Copyright Sayed Ibrahim Hashimi
/// www.sedodream.com
///
using System;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Serialization;
namespace Sedodream.Sample.Serialization01.Util
{
    public static class XmlUtils
    {
        /// 
        /// Builds an object of the specified type from the given
        /// XML representation that can be passed to the DataContractSerializer
        /// 
        public static object BuildFromDataContractXml(string xml, Type type)
        {
            if (string.IsNullOrEmpty(xml)) {throw new ArgumentNullException("xml"); }
            if (type == null) { throw new ArgumentNullException("type"); }

            object result = null;
            DataContractSerializer dcs = new DataContractSerializer(type);
            using (StringReader reader = new StringReader(xml))
            using (XmlReader xmlReader = new XmlTextReader(reader))
            {
                result = dcs.ReadObject(xmlReader);
            }

            return result;
        }
        /// 
        /// Builds an object from its XML 
        /// representation that can be passed to the DataContractSerializer.
        /// 
        public static T BuildFromDataContractXml(string xml)
        {
            if (string.IsNullOrEmpty(xml)) { throw new ArgumentNullException("xml"); }

            T result = default(T);

            object objResult = BuildFromDataContractXml(xml, typeof(T));
            if (objResult != null)
            {
                result = (T)objResult;
            }
            return result;
        }
        /// 
        /// Gets the XML representation of the given object
        /// by using the DataContracSerializer.
        /// 
        public static string GetDataContractXml(T obj)
        {
            if (obj == null) { throw new ArgumentNullException("obj"); }

            return GetDataContractXml(obj.GetType(), obj);
        }
        /// 
        /// Gets the XML representation of the given object
        /// of specfiied type by using the DataContracSerializer.
        /// 
        public static string GetDataContractXml(Type type, object val)
        {
            if (type == null) { throw new ArgumentNullException("type"); }
            if (val == null) { throw new ArgumentNullException("val"); }

            MemoryStream ms = new MemoryStream();
            string xml = null;
            try
            {
                DataContractSerializer dcs = new DataContractSerializer(type);

                using (XmlTextWriter xmlTextWriter = new XmlTextWriter(ms, System.Text.Encoding.Default))
                {
                    xmlTextWriter.Formatting = System.Xml.Formatting.Indented;
                    dcs.WriteObject(xmlTextWriter, val);
                    xmlTextWriter.Flush();
                    ms = (MemoryStream)xmlTextWriter.BaseStream;
                    ms.Flush();
                    xml = UTF8ByteArrayToString(ms.ToArray());
                }
            }
            finally
            {
                if (ms != null)
                {
                    ms.Close();
                    ms = null;
                }
            }
            return xml;

        }
        /// 
        /// Writes the XML representation from the DataContractSerializer
        /// into the specified filename. If a file at filename
        /// already exists then an Exception will be thrown.
        /// 
        public static void WriteDateContractToFile(string filename, T obj)
        {
            WriteDateContractToFile(filename, obj.GetType(), obj);
        }
        /// 
        /// Writes the XML representation from the DataContractSerializer
        /// into the specified filename. If a file at filename
        /// already exists then an Exception will be thrown.
        /// 
        public static void WriteDateContractToFile(string filename, Type type, object val)
        {
            if (string.IsNullOrEmpty(filename)) { throw new ArgumentNullException("filename"); }
            if (val == null) { throw new ArgumentNullException("val"); }

            if (File.Exists(filename)) { throw new ArgumentException("filname"); }

            //TODO: Stream this into the file instead of this!!!
            File.WriteAllText(filename, GetDataContractXml(type, val));
        }
        private static String UTF8ByteArrayToString(Byte[] characters)
        {
            UTF8Encoding encoding = new UTF8Encoding();
            string constructedString = encoding.GetString(characters);
            return (constructedString);
        }
        private static Byte[] StringToUTF8ByteArray(String pXmlString)
        {
            UTF8Encoding encoding = new UTF8Encoding();
            byte[] byteArray = encoding.GetBytes(pXmlString);
            return byteArray;
        }
    }
}

To show how this can be used here is a quick sample unit test shown below, this will be a part of a later post on a related topic.

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using Sedodream.Sample.Serialization01;
using Sedodream.Sample.Serialization01.Util;

namespace unittest.Sedodream.Sample.Serialization01
{
    [TestFixture]
    public class TestXmlUtils
    {
        [Test]
        public void TestCreateXml()
        {
            string name = "Sayed Ibrahim Hashimi";
            string email = "sayed.hashimi@gmail.com";


            Person person = new Person();
            person.Name = name;
            person.Email = email;

            string xml = XmlUtils.GetDataContractXml(person);
            Assert.IsTrue(!string.IsNullOrEmpty(xml));
            Assert.IsTrue(xml.Contains(name));
            Assert.IsTrue(xml.Contains(email));
        }
        [Test]
        public void TestCreateFromXml()
        {
            const string xml =
@"
  sayed.hashimi@gmail.com
  9891668d-8107-4f3e-85a4-79e941453be2
  Sayed Ibrahim Hashimi
";

            string name = "Sayed Ibrahim Hashimi";
            string email = "sayed.hashimi@gmail.com";
            Guid id = new Guid("9891668d-8107-4f3e-85a4-79e941453be2");

            Person person = XmlUtils.BuildFromDataContractXml(xml);
            Assert.IsNotNull(person);
            Assert.AreEqual(name, person.Name);
            Assert.AreEqual(email, person.Email);
            Assert.AreEqual(id, person.Id);
        }
    }
}

Where the simple Person class is shown here

[DataContract(Namespace="http://schemas.sedodream.com/Serialization/2008/04")]
public class Person
{
    #region Constructors
    public Person()
    {
        Id = Guid.NewGuid();
    }
    #endregion

    [DataMember]
    public string Name
    {
        get;
        set;
    }
    [DataMember]
    public string Email
    {
        get;
        set;
    }
    [DataMember]
    public Guid Id
    {
        get;
        set;
    }
}

From the two simply test you can see how to create the object from the XML string and vice versa. As far as I know these work pretty good, if you find any issues please let me know and I can update them. At some point I will post some more information that is related to this keep an eye for it.

 

XmlUtils.cs

Sayed Ibrahim Hashimi

6/4/2008 1:54:58 AM (Eastern Daylight Time, UTC-04:00)  #     | 
Thursday, May 22, 2008

Ok there is this hidden feature of MSBuild its called output inferral. This is used when a target is skipped due its Inputs & Outputs (incremental building) but the target creates properties and items. Eventhough the inputs and outputs are up to date the side effects (properties & items) may change the rest of the build process. Because of this output inferral is required. When a target is skipped MSBuild will inspect the target for properties & items that were created and create those. This will ensure that other targets down the chain will not be affected by the target being skipped. In MSBuild 3.5 there is a new property on the CreateProperty task, this property is ValueSetByTask. The whole purpose of this is to only be used when you do not want its value to be inferred. By using this property instead of Value you would be guaranteed that the target was actually executed. Let's clear this up with an example take a look at the project file shown below.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!--
This target will always be skipped .
Neither property One or Two exists but One will be inferred
by MSBuild.
-->
<Target Name="SetupValues"
Inputs="$(MSBuildProjectFullPath)"
Outputs="$(MSBuildProjectFullPath)">

<Message Text="SetuptValues Executed!" Importance="high"/>

<CreateProperty Value="1111">
<Output PropertyName="One" TaskParameter="Value"/>
<!-- Property One is actually inferred by this statement. -->
</CreateProperty>
<CreateProperty Value="2222">
<Output PropertyName="Two" TaskParameter="ValueSetByTask"/>
<!-- Using ValueSetByTask we can ensure bypassing any inferring here -->
</CreateProperty>

<PropertyGroup>
<!-- 3.5 syntax: Property Three is also inferred -->
<Three>3333</Three>
</PropertyGroup>

<!-- Items are inferred too! -->
<ItemGroup>
<File Include="app.config"/>
</ItemGroup>
</Target>

<Target Name="PrintValues" DependsOnTargets="SetupValues">
<Message Text="One: $(One)" />
<Message Text="Two: $(Two)" />
<Message Text="Three: $(Three)"/>
<Message Text="File: @(File)"/>
</Target>

</Project>

In this file (you can download from link at end) there are two targets, SetupValues and PrintValues. PrintValues depends on SetupValues. Because the Inputs and Outputs on SetupValues point to the same file the target will always be skipped. But we declare properties and items so they have to be inferred by the MSBuidl engine so that the remainder of the build will not be hosed because of it. If you execute the PrintValues target the result would be what you see in the image below.

As you can see the values for the properties One & Three were provided by inference but the value for Two was passed over because it uses the ValueSetByTask instead of Value. I would suggest that you continue to use the Value property and not the ValueSetByTask unless you are trying to detect this exact scenario, which most of the time shouldn't matter anywayz.

OutputInferral.proj


Sayed Ibrahim Hashimi

5/22/2008 1:09:41 AM (Eastern Daylight Time, UTC-04:00)  #     | 
Wednesday, May 07, 2008

After some discussion at the MSBuild MSDN Forum I started investigating building the same project with different Compiler constants defined. I was actually pretty surprised by what I found. I created a simple Windows Form Application with a lone button one it. The image below show the simple app.

Here is the entire code behind file.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}

 

private void buttonExample_Click(object sender, EventArgs e)

{

string message = "default message";

#if CONSTANT_ONE

message = "CONSTANT_ONE";

#endif

#if CONSTANT_TWO

message = "CONSTANT_TWO";

#endif

 

MessageBox.Show(message);

}

}

}

Ddd

The parts to take note are the regions highlighted in yellow. So if the constant CONSTANT_ONE is defined the message results to CONSTANT_ONE and if CONSTANT_TWO is defined it will show the message CONSTANT_TWO.

I then created the simple build file.

<?xml version="1.0" encoding="utf-8"?>

<Project DefaultTargets="BuildAll" ToolsVersion="3.5"

xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 

<PropertyGroup>

<ConstOne>CONSTANT_ONE</ConstOne>

<ConstTwo>CONSTANT_TWO</ConstTwo>

</PropertyGroup>

 

<ItemGroup>

<Projects Include="$(MSBuildProjectDirectory)\..\WindowsFormsApplication1\WindowsFormsApplication1.csproj">

</Projects>

</ItemGroup>

 

<!--

Cleans the project before we start the build process.

The only reason I am calling this is to remove artifacts of

previous builds, to clearly demonstrate what's going on.

-->

<Target Name="CleanAll">

<MSBuild Projects="@(Projects)" Targets="Clean" />

</Target>

 

<Target Name="BuildAll" DependsOnTargets="CleanAll;BuildConstOne;BuildConstTwo" />

 

<Target Name="BuildConstOne">

<MSBuild Projects="@(Projects)"

Properties="DefineConstants=$(ConstOne);OutputPath=binOne\;"/>

</Target>

<Target Name="BuildConstTwo">

<MSBuild Projects="@(Projects)"

Properties="DefineConstants=$(ConstTwo);OutputPath=binTwo\;"

 

/>

</Target>

 

<!-- ===========================================================================

This region rebuilds the projects, so after it is built once it is then cleaned

before building it again.

This produces the assemblies with the desired behavior

================================================================================-->

 

<Target Name="ReBuildAll" DependsOnTargets="CleanAll;ReBuildConstOne;ReBuildConstTwo" />

<Target Name="ReBuildConstOne">

<MSBuild Projects="@(Projects)" Targets="Rebuild"

Properties="DefineConstants=$(ConstOne);OutputPath=binOne\;"/>

</Target>

<Target Name="ReBuildConstTwo">

<MSBuild Projects="@(Projects)" Targets="Rebuild"

Properties="DefineConstants=$(ConstTwo);OutputPath=binTwo\;"/>

</Target>

 

<!--==========================================================================

This region calls the default target (Build) but also specifies the

BaseIntermediateOutputPath so it works as well because the projects

are building to different locations on disk.

==============================================================================-->

<Target Name="BuildAllBetter" DependsOnTargets="CleanAll;BuildConstOneBetter;BuildConstTwoBetter" />

<Target Name="BuildConstOneBetter">

<MSBuild Projects="@(Projects)"

Properties="DefineConstants=$(ConstOne);OutputPath=binOne\BaseIntermediateOutputPath=objOne\"/>

</Target>

<Target Name="BuildConstTwoBetter">

<MSBuild Projects="@(Projects)"

Properties="DefineConstants=$(ConstTwo);OutputPath=binTwo\;BaseIntermediateOutputPath=objTwo\;"

 

/>

</Target>

 

</Project>

Ddddd

Let's take a look at what's going on here. The BuildConstOne target passes the CONSTANT_ONE value as a property when invoking the MSBuild task, and builds to the binOne folder. The BuildConstTwo target passes the CONSTANT_TWO value and builds to binTwo. If you execute the target BuildAll, you would expect that the exe in binOne would show the message CONSTANT_ONE and the exe in the binTwo folder has CONSTANT_TWO message showing. What you actually get is that both actually show CONSTANT_ONE. This is because MSBuild doesn't entirely rebuild the project when building it for the second time in BuildConstTwo. The real reason behind this would be because both projects are building to the same BaseIntermediateOutputPath, obj\. There are two ways to of getting around this, one is to call Rebuild instead of Build, and the other is to specify the BaseIntermediateOutputPath as well. In the above I have demonstrated both approaches. The ReBuildAll calls Rebuild, and the BuildAllBetter specifies the BaseIntermediateOutputPath as well as OutputPath.

Questions?!

You can download all the files at:

DefineConst.zip

Sayed Ibrahim Hashimi

5/7/2008 7:52:14 PM (Eastern Daylight Time, UTC-04:00)  #     | 
Tuesday, April 29, 2008

A question that comes up pretty frequently is "How can I debug an MSBuild task"? It's actually pretty simple. In this post I will describe how to easily an effectively debug MSBuild tasks that you are creating. In this example I will be demonstrating a task from my open source tasks at www.codeplex.com/Sedodream. The task is one that was contributed by Grant Holliday.

First in the project where your tasks are contained create a folder that will be used to contain sample MSBuid files that can be used to debug the tasks. This is also a good idea, because it will show people how to use your tasks. If you are don't want to mix samples & code in the same project then just make sure in your build you copy the files to the correct locations. In the sample project, which you can download at the bottom, the folder is named Samples. When you add MSBuild files to the folder make sure you set the file to be copied to the output folder as well. See the image below.

By setting this, the file will be copied to the output folder when the project is built. Since it will be in the output folder we can use a relative path to get to the assembly that contains the task. Take a look at the sample MSBuild file for this task.

<?xml version="1.0" encoding="utf-8"?>

<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

   <PropertyGroup>

    <TaskLocation Condition="$(TaskLocation)==''">$(MSBuildProjectDirectory)\..\DebugTask.dll</TaskLocation>

  </PropertyGroup>

   <UsingTask TaskName="CreateGuid" AssemblyFile="$(TaskLocation)"/>

 

  <Target Name="Example">

    <Message Text="Starting example" />

    <CreateGuid>

        <Output PropertyName="Guid" TaskParameter="Output"/>

      </CreateGuid>

    <Message Text="Guid: $(Guid)" />

   </Target>

</Project>

 

The line highlighted contains the path to the assembly that contains the task. This path is relative to the location in the output folder, not the source folder. Also another thing to take note, is that we only write to the TaskLocation property if it is empty. This is useful because you can overwrite the location through command line parameters if necessary. But this shouldn't be needed for what we are trying to accomplish here.

After you create the sample, build the solution. And open a command prompt to verify that it works. Here is a sample of the result of this project file.

Once you've verified that that MSBuild file works then you can right click on the project that contains your task, and select properties. From there go to the debug tab. What we want to do is start the msbuild.exe executable on that sample project file. To do this fill in the full path to it in the Start External program text box, i.e. 'C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe'. In the command line arguments you should pass the name of the project file followed by any msbuild parameters. Typically I will attach a file logger to the process. Finally you should set the working directory to the folder containing the sample. The result should look something like the image shown below.

Following this set a break point in your task, set the project as the startup project and hit F5! Another thing to take note of is that these properties are stored in the .user file so it shouldn't affect any other developers on your team.

Below is the link containing a simple solution that was used here.

 

DebugTask.zip

Sayed Ibrahim Hashimi

4/29/2008 1:10:03 AM (Eastern Daylight Time, UTC-04:00)  #     | 

Theme design by Jelle Druyts