4

I'm a make(1) guy by nature. What I want to do is the equivalent of this make fragment:

FILES = generated.cs

app.exe : $(FILES)
    csc -out:$@ $(FILES)

generated.cs :
    echo "// generated file" > $@

That is, $(FILES) contains a list of files, some of which may be generated by other targets within the Makefile. This Just Works.

I would like to do the ~same thing with MSBuild. Unfortunately, my attempt has failed:

<Target Name="BuildGenerated"
    Outputs="Generated.cs"
    >
  <WriteLinesToFile
    File="Generated.cs"
    Lines="// generated file"
    Overwrite="True"
    />
</Target>
<ItemGroup>
  <Compile Include="Generated.cs" />
</ItemGroup>

That is, use <Compile/> to include a generated file and have MSBuild deduce that since Generated.cs doesn't exist, it should find some <Target/> which will generate that file and then execute it.

It looks like the only way to do something like this is to add pre-build steps, but that seems like a hack. Are pre-build steps the only way to do this? Or is there some way to make MSBuild act like it has a brain?

Update 1: For reference, this would be the pre-build incantation needed to make it work, and (again) this is something I'd rather avoid if possible:

<PropertyGroup>
  <CompileDependsOn>
    BuildGenerated;$(CompileDependsOn)
  </CompileDependsOn>
</PropertyGroup>

This would need to occur after the <Import Project="..." /> element(s) defining <CompileDependsOn/>.

jonp
  • 13,512
  • 5
  • 45
  • 60

1 Answers1

2

The problem is that your file is a generated one and MsBuild parses the item and property group only once (at the beginning of your build). Thus, when MsBuild tries to include the generated.cs file, it is not created yet and MsBuild does include nothing at all.

A correct way of doing it would be to do the include part inside the BuildGenerated target which would cause it to be dynamically evaluated.

<Target Name="BuildGenerated"
    Outputs="Generated.cs"
    >
  <WriteLinesToFile
    File="Generated.cs"
    Lines="// generated file"
    Overwrite="True"
    />
<ItemGroup>
  <Compile Include="Generated.cs" />
</ItemGroup>
</Target>

More information in my answer to this question : Bin folder not being copied with MSBuild, Teamcity

EDIT
Sorry, I did not read correctly your question. in your makefile, in the app.exe target (the default one) you explicitely call your generated.cs target. You can do the same with MsBuild.

By default, MsBuild search for the Build target (the 'all' in makefile), if your main target is "app.exe" you have to call BuildGenerated target within with one of the following option :

<Target Name="app.exe" DependsOnTargets="BuildGenerated>
  <!-- Stuff here -->
</Target>

or

<Target Name="app.exe">
  <CallTarget Targets="BuildGenerated"/>
  <!-- Stuff here -->
</Target>

If you don't set a default target, you can do it either via commandline with the /t parameter or in the project declaration :

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="app.exe">
Community
  • 1
  • 1
Benjamin Baumann
  • 4,035
  • 2
  • 25
  • 35
  • That works nicely when I test improperly and use 'msbuild /t:BuildGenerated'...which of course defeats the point. This re-raises the question: what am I doing wrong that the above fragment isn't evaluated in a default build? – jonp Dec 03 '10 at 20:00
  • I updated my answer with even more answers ^^ Sorry for being late though. – Benjamin Baumann Dec 07 '10 at 15:05