C# – MSBuild Item Include (wildcard) not expanding

cmsbuildvisual studio 2012

This is very weird. We've been trying to figure it out for a while, but it really doesn't make any sense.

Our web project imports a targets file that has a target similar to this:

<Target Name="CSSCheckInternal">
    <ItemGroup>
        <CSSFiles Include="$(MSBuildProjectDirectory)\**\*.css" />
    </ItemGroup>
    <CSSChecker Files="@(CSSFiles)" />
</Target>

At the moment, one branch is building perfectly, executing the task as desired; but the other branch is failing on the above target.

The failure is because the @(CSSFiles) item, when received by the task, appears not to be expanding into an ITaskItem array.

The task is written as follows (up to the point where I get the FullPath metadata):

public class CSSChecker : Task
{
    [Required]
    public ITaskItem[] Files
    {
        get;
        set;
    }

    public override bool Execute()
    {
        string fullFilePath = null;
        if (Files != null)
        {
            foreach (var item in Files)
            {
                fullFilePath = item.GetMetadata("FullPath");
                if(!File.Exists(fullFilePath))
                  throw new InvalidOperationException(
                   string.Format("{0} does not exist", fullFilePath));


        //rest of the code elided

The build that's failing is throwing the InvalidOperationException on the last line there, like this:

File does not exist: C:\Code\Project\**\*.css

So it would seem that MSBuild, instead of expanding the wildcard in the Include attribute, is merely passing the string over, thus creating only one ITaskItem on the task.

The target folder does exist on the disk, and the only difference between the broken project file and the working one is a single file include much earlier in the project file.

Update

I asked Sayed Hashimi on twitter (wrote the MSBuild book) and through that, tried taking out the ** folder wildcard and it's now started working. This isn't really suitable as the task is meant to be re-usable between projects. But it would appear to be something to do with this.

End update

Please if anyone knows under what situation MSBuild would not correctly expand a wildcard, it would be a great help!

Best Answer

I've figured this out - I had to delete the obj\ folder in my project directory, and all of a sudden the folder wildcard starting working again.

The short answer for my situation is that it appears MSBuild's wildcard handling code craps out completely if any path is too long, and just doesn't build an item group.

The thing here being how did I manage to create paths that were that long? Well, I didn't. It was the built-in web publishing task - which I use like this (for a custom deployment that we do):

<MSBuild Projects="$(Proj)" Properties="Platform=$(Platform);
 Configuration=$(Configuration);DeployOnBuild=true;PackageAsSingleFile=False;
 AutoParameterizationWebConfigConnectionStrings=False" />

When you do PackageAsSingleFile=False, which I use to prevent building the zip as I want the website deployables, in the obj folder you get a folder structure like this:

[Project_Dir]\obj\[configuration]\Package\PackageTemp\[Project Dir]\[output *]

If [Project_Dir] is c:\my project\, then the base folder for the temporary package files will be something like c:\my project\obj\debug\Package\PackageTemp\c_c\my project\.

As you can see, that's quite a deep folder structure already, and in reality projects are generally not top-level folders on the root of a drive.

I've found, with some of our projects that use this deployment method, that it becomes impossible to delete the obj\ folder in Explorer or at the command line because the path has got too long. What I do to get around this is to rename as many parent folders as required simply to 1 in order to shorten the full path and then do the delete. I.e in the previous example, I would rename as follows:

c:\my project\obj\1\1\1\1\1

Which works well.

You can imagine - if the project starts off in a deep-enough folder - then the eventual paths of items generated for the publish task will get very long. I've found that if I just use the Publish task from within VS this actually causes an error during the publish - but it would seem that shelling MSBuild in the way I show above, actually somehow sidesteps the folder max path limitation. I'm going to put together a project that proves this soon.

So, in my case, I've had to rewrite my Task to take the base folders that are to be processed, and then have it recurse through the folders and files itself, ignoring any 'obj' folder it finds.

I tried excluding any files in the obj folder using the 'Exclude' attribute, but it made no difference (presumably because both were crapping out!).

Related Topic