Benilda Key
Benilda Key

Reputation: 3092

How to create a MSBuild Target that will only run if necessary

I have been experimenting with MSBuild to create custom targets. I am currently attempting to add support for compiling file with the JAWS script compiler. This is what I have so far.

Scripts.props

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <DEVDOCS_DIR>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\..\..\..\DevDocs'))</DEVDOCS_DIR>
        <BUILTIN_MASTER>$(DEVDOCS_DIR)\jsd\enu\builtin_master.jsd</BUILTIN_MASTER>
        <TOOLBOX_DIR>$(DEVDOCS_DIR)\Toolbox</TOOLBOX_DIR>
        <JAWS_VER>17.0</JAWS_VER>
        <!-- If perl.exe is not in your path, set the following variable to the full path and file name of perl.exe.
        Optionally you could set the PERL_EXE environment variable. -->
        <PERL_EXE Condition="'$(PERL_EXE)'==''">perl.exe</PERL_EXE>
        <!-- If scompile.exe is not in your path, set the following variable to the full path and file name of scompile.exe.
        Optionally you could set the SCOMPILE_EXE environment variable. -->
        <SCOMPILE_EXE Condition="'$(SCOMPILE_EXE)'==''">scompile.exe</SCOMPILE_EXE>
    </PropertyGroup>
</Project>

Scripts.targets

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="GenerateBuiltinJSD">
        <Exec Command="&quot;$(PERL_EXE)&quot; compilejsd.pl -v Jaws/$(JAWS_VER) -o &quot;$(MSBuildProjectDirectory)\builtin.jsd&quot; &quot;$(BUILTIN_MASTER)&quot;" WorkingDirectory="$(TOOLBOX_DIR)" />
    </Target>
    <Target Name="CompileScripts">
        <Exec Command="&quot;$(SCOMPILE_EXE)&quot; /d &quot;%(ScriptSourceFiles.FullPath)&quot;" Outputs="%(ScriptSourceFiles.RootDir)%(ScriptSourceFiles.Directory)%(ScriptSourceFiles.Filename).jsb" WorkingDirectory="$(MSBuildProjectDirectory)" />
    </Target>
    <Target Name="Clean">
        <ItemGroup>
            <JSBFiles Include="$(MSBuildProjectDirectory)\*.jsb" />
        </ItemGroup>
        <Delete Files="@(JSBFiles)" />
    </Target>
    <Target Name="Build">
        <CallTarget Targets="GenerateBuiltinJSD;CompileScripts" />
    </Target>
</Project>

Scripts.vcxproj

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Release|Win32">
      <Configuration>Release</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
  </ItemGroup>
  <ItemGroup>
    <ScriptSourceFiles Include="Default.jss" />
  </ItemGroup>
  <ItemGroup>
    <ScriptMessageFiles Include="Default.jsm" />
  </ItemGroup>
  <ItemGroup>
    <ScriptHeaderFiles Include="HJConst.jsh" />
  </ItemGroup>
  <ItemGroup>
    <ScriptDocumentationFiles Include="default.jsd" />
  </ItemGroup>
  <PropertyGroup Label="Globals">
    <ProjectGuid>{B2C4363D-D228-425D-AB04-38997EA229C0}</ProjectGuid>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Label="Configuration">
    <PlatformToolset>v120</PlatformToolset>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  <ImportGroup Label="PropertySheets">
    <Import Project="Scripts.props" />
  </ImportGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <Import Project="$(MSBuildProjectDirectory)\Scripts.targets" />
</Project>

This works up to a point. The problem is that when I do a build, it rebuilds all the script files, even if the relevant source file has not changed.

My question is how can I modify this so that each file is only built if necessary?

Upvotes: 1

Views: 487

Answers (1)

stukselbax
stukselbax

Reputation: 5935

The feature you are asking about is called Incremental Build, and hopefully it is supported by MSBuild utility. All you have to do is to specify inputs and outputs for a target you are planning to skip if no source files are changed.

To specify inputs and outputs for a target: Use the Inputs and Outputs attributes of the Target element. For example:

<Target Name="Build"
    Inputs="@(CSFile)"
    Outputs="hello.exe">

MSBuild can compare the timestamps of the input files with the timestamps of the output files and determine whether to skip, build, or partially rebuild a target.

To succeed in your task you have to properly generate the Inputs and Outputs of your target. This can be achieved by using special targets, which will populate the required items, or you can use the transformation explicitly:

<Target Name="CompileScripts"
        Inputs="@(ScriptSourceFiles)"
        Outputs="@(ScriptSourceFiles->'%(RootDir)%(Directory)%(FileName).jsb')">
    <Exec Command="&quot;$(SCOMPILE_EXE)&quot; /d &quot;%(ScriptSourceFiles.FullPath)&quot;" Outputs="%(ScriptSourceFiles.RootDir)%(ScriptSourceFiles.Directory)%(ScriptSourceFiles.Filename).jsb" WorkingDirectory="$(MSBuildProjectDirectory)" />
</Target>

(Also note that instead of referencing %(RootDir) item metadata you should use $(OutDir) property, because it is more straightforward, but you should ensure that this property is specified and exists when the target is executed).

Upvotes: 2

Related Questions