Click here to Skip to main content
15,887,214 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hi,

I manage to build a custom build rule with VS2008 and upgrade it in VS2010.
But I have a problem with the Outputs Property of the Rule in VS2010.

Basically, I add a custom property name OutputSubDir (String) and I want to use it in the Outputs property like this : $(Target)%(OutputSubDir)%(Filename).txt

When I put a default value (in the props file) for OutputSubDir, my Custom Build Rule used this value even if I set a new one for the selected item in the project.
The only way, I made MSBuild use the new one is to override the Outputs property as well (using the same value as the default one !)

It looks like :
- Project Item has Outputs property, then use the OutputSubDir Property of the Project Item
- Project Item does not have Outputs property, use the Outputs property of the Project then use the OutputSubDir property at the same location of the Outputs property (i.e.: at the Project level = default value)

It looks like MSBuild does not allow Polymorphism on its Items.

Do you have any idea how can I configure the properties, the rule, and/or the target to make the Outputs using the good value ?

Do you know if the problem still exists with VS2013 ?

I can provide sample files (.props, .xml & .targets) but there are quite long to put them directly in the question. (and I don't know if it is possible to upload them here)

Here are the files :
TestRule.xml
XML
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:transformCallback="Microsoft.Cpp.Dev10.ConvertPropertyCallback">
  <Rule
    Name="TestRule"
    PageTemplate="tool"
    DisplayName="Test Rule"
    Order="200">
    <Rule.DataSource>
      <DataSource
        Persistence="ProjectFile"
        ItemType="TestRule" />
    </Rule.DataSource>
    <Rule.Categories>
      <Category
        Name="General">
        <Category.DisplayName>
          <sys:String>General</sys:String>
        </Category.DisplayName>
      </Category>
      <Category
        Name="Command Line"
        Subtype="CommandLine">
        <Category.DisplayName>
          <sys:String>Command Line</sys:String>
        </Category.DisplayName>
      </Category>
    </Rule.Categories>
    <StringListProperty
      Name="Inputs"
      Category="Command Line"
      IsRequired="true"
      Switch=" ">
      <StringListProperty.DataSource>
        <DataSource
          Persistence="ProjectFile"
          ItemType="TestRule"
          SourceType="Item" />
      </StringListProperty.DataSource>
    </StringListProperty>
    <StringProperty
      Name="OutputSubDir"
      Category="General"
      HelpContext="0"
      DisplayName="Output SubDir"
      Description="Sub Folder of $(Target) where output file should be"
      Switch="[value]" />
    <StringProperty
      Name="CommandLineTemplate"
      DisplayName="Command Line"
      Visible="False"
      IncludeInCommandLine="False" />
    <DynamicEnumProperty
      Name="TestRuleBeforeTargets"
      Category="General"
      EnumProvider="Targets"
      IncludeInCommandLine="False">
      <DynamicEnumProperty.DisplayName>
        <sys:String>Execute Before</sys:String>
      </DynamicEnumProperty.DisplayName>
      <DynamicEnumProperty.Description>
        <sys:String>Specifies the targets for the build customization to run before.</sys:String>
      </DynamicEnumProperty.Description>
      <DynamicEnumProperty.ProviderSettings>
        <NameValuePair
          Name="Exclude"
          Value="^TestRuleBeforeTargets|^Compute" />
      </DynamicEnumProperty.ProviderSettings>
      <DynamicEnumProperty.DataSource>
        <DataSource
          Persistence="ProjectFile"
          HasConfigurationCondition="true" />
      </DynamicEnumProperty.DataSource>
    </DynamicEnumProperty>
    <DynamicEnumProperty
      Name="TestRuleAfterTargets"
      Category="General"
      EnumProvider="Targets"
      IncludeInCommandLine="False">
      <DynamicEnumProperty.DisplayName>
        <sys:String>Execute After</sys:String>
      </DynamicEnumProperty.DisplayName>
      <DynamicEnumProperty.Description>
        <sys:String>Specifies the targets for the build customization to run after.</sys:String>
      </DynamicEnumProperty.Description>
      <DynamicEnumProperty.ProviderSettings>
        <NameValuePair
          Name="Exclude"
          Value="^TestRuleAfterTargets|^Compute" />
      </DynamicEnumProperty.ProviderSettings>
      <DynamicEnumProperty.DataSource>
        <DataSource
          Persistence="ProjectFile"
          ItemType=""
          HasConfigurationCondition="true" />
      </DynamicEnumProperty.DataSource>
    </DynamicEnumProperty>
    <StringListProperty
      Name="Outputs"
      DisplayName="Outputs"
	    Visible="True"
      IncludeInCommandLine="False"/>
    <StringProperty
      Name="ExecutionDescription"
      DisplayName="Execution Description"
      IncludeInCommandLine="False" />
    <StringListProperty
      Name="AdditionalDependencies"
      DisplayName="Additional Dependencies"
      Visible="False"
      IncludeInCommandLine="False" />
    <StringProperty
      Subtype="AdditionalOptions"
      Name="AdditionalOptions"
      Category="Command Line"
      Visible="False">
      <StringProperty.DisplayName>
        <sys:String>Additional Options</sys:String>
      </StringProperty.DisplayName>
      <StringProperty.Description>
        <sys:String>Additional Options</sys:String>
      </StringProperty.Description>
    </StringProperty>
  </Rule>
  <ItemType
    Name="TestRule"
    DisplayName="Test Rule" />
  <FileExtension
    Name="*.ext"
    ContentType="TestRule" />
  <ContentType
    Name="TestRule"
    DisplayName="Test Rule"
    ItemType="TestRule" />
</ProjectSchemaDefinitions>


TestRule.props
XML
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup
    Condition="'$(TestRuleBeforeTargets)' == '' and '$(TestRuleAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
    <TestRuleBeforeTargets>Midl</TestRuleBeforeTargets>
    <TestRuleAfterTargets>CustomBuild</TestRuleAfterTargets>
  </PropertyGroup>
  <PropertyGroup>
    <TestRuleDependsOn
      Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(TestRuleDependsOn)</TestRuleDependsOn>
  </PropertyGroup>
  <ItemDefinitionGroup>
    <TestRule>
      <CommandLineTemplate>
        ECHO OutputDir should be $(TargetDir)[OutputSubDir]%(Filename).txt
      </CommandLineTemplate>
      <Outputs>$(TargetDir)%(OutputSubDir)%(Filename).txt</Outputs>
      <ExecutionDescription>Test Rule</ExecutionDescription>
    </TestRule>
  </ItemDefinitionGroup>
</Project>


TestRule.targets
XML
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <PropertyPageSchema
      Include="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml" />
    <AvailableItemName
      Include="TestRule">
      <Targets>_TestRule</Targets>
    </AvailableItemName>
  </ItemGroup>
  <UsingTask
    TaskName="TestRule"
    TaskFactory="XamlTaskFactory"
    AssemblyName="Microsoft.Build.Tasks.v4.0">
    <Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
  </UsingTask>
  <Target
    Name="_TestRule"
    BeforeTargets="$(TestRuleBeforeTargets)"
    AfterTargets="$(TestRuleAfterTargets)"
    Condition="'@(TestRule)' != ''"
    DependsOnTargets="$(TestRuleDependsOn);ComputeTestRuleOutput"
    Outputs="%(TestRule.Outputs)"
    Inputs="%(TestRule.Identity);%(TestRule.AdditionalDependencies);$(MSBuildProjectFile)">
    <ItemGroup
      Condition="'@(SelectedFiles)' != ''">
      <TestRule
        Remove="@(TestRule)"
        Condition="'%(Identity)' != '@(SelectedFiles)'" />
    </ItemGroup>
    <ItemGroup>
      <TestRule_tlog
        Include="%(TestRule.Outputs)"
        Condition="'%(TestRule.Outputs)' != '' and '%(TestRule.ExcludedFromBuild)' != 'true'">
        <Source>@(TestRule, '|')</Source>
      </TestRule_tlog>
    </ItemGroup>
    <Message
      Importance="High"
      Text="%(TestRule.ExecutionDescription)" />
	<Message
		Importance="High"
		Text="OutputSubDir = %(TestRule.OutputSubDir)" />
	<Message
		Importance="High"
		Text="Outputs = %(TestRule.Outputs)" />
    <WriteLinesToFile
      Condition="'@(TestRule_tlog)' != '' and '%(TestRule_tlog.ExcludedFromBuild)' != 'true'"
      File="$(IntDir)$(ProjectName).write.1.tlog"
      Lines="^%(TestRule_tlog.Source);@(TestRule_tlog->'%(Fullpath)')" />
    <TestRule
      Condition="'@(TestRule)' != '' and '%(TestRule.ExcludedFromBuild)' != 'true'"
      CommandLineTemplate="%(TestRule.CommandLineTemplate)"
      OutputSubDir="%(TestRule.OutputSubDir)"
      AdditionalOptions="%(TestRule.AdditionalOptions)"
      Inputs="%(TestRule.Identity)" />
  </Target>
  <PropertyGroup>
    <ComputeLinkInputsTargets>
            $(ComputeLinkInputsTargets);
            ComputeTestRuleOutput;
          </ComputeLinkInputsTargets>
    <ComputeLibInputsTargets>
            $(ComputeLibInputsTargets);
            ComputeTestRuleOutput;
          </ComputeLibInputsTargets>
  </PropertyGroup>
  <Target
    Name="ComputeTestRuleOutput"
    Condition="'@(TestRule)' != ''">
    <ItemGroup>
      <TestRuleDirsToMake
        Condition="'@(TestRule)' != '' and '%(TestRule.ExcludedFromBuild)' != 'true'"
        Include="%(TestRule.Outputs)" />
      <Link
        Include="%(TestRuleDirsToMake.Identity)"
        Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
      <Lib
        Include="%(TestRuleDirsToMake.Identity)"
        Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
      <ImpLib
        Include="%(TestRuleDirsToMake.Identity)"
        Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
    </ItemGroup>
    <MakeDir
      Directories="@(TestRuleDirsToMake->'%(RootDir)%(Directory)')" />
  </Target>
</Project>


and the Extract of .VCXPROJ :
XML
<ItemGroup>
  <TestRule Include="test.ext">
    <OutputSubDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">info\</OutputSubDir>
    <FileType>Document</FileType>
  </TestRule>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
  <Import Project="TestRule.targets" />
</ImportGroup>


I discovered something else:
XML
<ItemGroup>
  <TestRule Include="test.ext">
    <OutputSubDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">info\</OutputSubDir>
    <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(TargetDir)%(OutputSubDir).dll\</Outputs>
    <FileType>Document</FileType>
  </TestRule>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
  <Import Project="TestRule.targets" />
</ImportGroup>


is not the same as:
XML
<ItemGroup>
  <TestRule Include="test.ext">
    <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(TargetDir)%(OutputSubDir).dll\</Outputs>
    <OutputSubDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">info\</OutputSubDir>
    <FileType>Document</FileType>
  </TestRule>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
  <Import Project="TestRule.targets" />
</ImportGroup>


In the first case, the Outputs will use the value of OutputSubDir but not in the second case where it will use the value defined in the props file.
MSBuild looks to be sequential
Posted
Updated 2-Feb-15 1:57am
v3
Comments
Tomas Takac 1-Feb-15 7:32am    
It would be useful if you would post the relevant parts of the actual msbuild project.
Pascal-78 1-Feb-15 8:40am    
I put the files in the question.

1 solution

I experienced a similar problem. Where the output it was using was the default value for the rule. And Visual Studio would recompile the file every time because it was checking the wrong file for changes.

I have something like this in my .targets file (that was auto-generated from a VS project upgrade).
XML
<Target
    Name="_APIGen"
    BeforeTargets="$(APIGenBeforeTargets)"
    AfterTargets="$(APIGenAfterTargets)"
    Condition="'@(APIGen)' != ''"
    DependsOnTargets="$(APIGenDependsOn);ComputeAPIGenOutput"
    Outputs="@(APIGen->Metadata('Outputs')->Distinct())"
    Inputs="@(APIGen);%(APIGen.AdditionalDependencies);$(MSBuildProjectFile)">

I changed the Output variable to reference the variables themselves:
XML
<Target
    Name="_APIGen"
    BeforeTargets="$(APIGenBeforeTargets)"
    AfterTargets="$(APIGenAfterTargets)"
    Condition="'@(APIGen)' != ''"
    DependsOnTargets="$(APIGenDependsOn);ComputeAPIGenOutput"
    Outputs="@(APIGen->Metadata('Param1')->Distinct());@(APIGen->Metadata('Param2')->Distinct())"
    Inputs="@(APIGen);%(APIGen.AdditionalDependencies);$(MSBuildProjectFile)">

If all else fails I recommend checking out the MASM files that are included in MSBuild and compare:
C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V120\BuildCustomizations

This article also includes common changes that may need to be made:

MSBuild evaluation sequences
 
Share this answer
 
v3

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900