Search code examples
c#visual-studio-2013msbuildmsbuild-taskmsbuild-4.0

MSBuild How to pass same task to multiple projects


I am wanting to run the same task on multiple projects from one build file without having to copy the task to each project file.

I am getting all project files in my solution and:

<ItemGroup>
    <ProjectsToBuild Include="..\Modules\**\*csproj"/>
</ItemGroup>

I am then calling the MSBuild task on each ProjectToBuild

<MSBuild Projects ="@(ProjectsToBuild)" 
             Targets="DoStuff"
             ContinueOnError ="false" 
             Properties="Configuration=$(Configuration)">
    <Output ItemName="OutputFiles" TaskParameter="TargetOutputs"/>
</MSBuild>

This doesn't work, as the Target must exist in the project you are building.

From MSDN

The targets must occur in all the project files. If they do not, a build error occurs.

Is there a way I can pass the DoStuff task to the projects that are being passed into the MSBuild task?


Solution

  • Since this project has a few different people from different timezones and locations, I didn't want to add another requirement for people to update their proj files manually everytime. I was able to do what I wanted in the end however.

    <Target Name="BuildAll" DependsOnTargets="$(BuildAllDependsOn)"/>
    
    <PropertyGroup>
        <BuildAllDependsOn>UpdateProjects;CoreBuild</BuildAllDependsOn>
    </PropertyGroup>
    
    <ItemGroup>
        <ProjectsToBuild Include="$(MSBuildProjectDirectory)\..\MyProjects\**\*csproj" Exclude="$(MSBuildProjectDirectory)\..\MyProjects\**\Team*csproj;$(MSBuildProjectDirectory)\..\MyProjects\**\Image*csproj"/>
    </ItemGroup>
    
    <Target Name="CoreBuild">
    <Message Text="CoreBuild" />
        <MSBuild Projects ="@(ProjectsToBuild)" 
                 Targets ="DoStuff" 
                 ContinueOnError ="false" 
                 Properties="Configuration=$(Configuration)">
            <Output ItemName="OutputFiles" TaskParameter="TargetOutputs"/>
        </MSBuild>
    </Target>
    
    <Target Name="UpdateProjects">
        <Message Text="$(MSBuildProjectDirectory)" />
        <ItemGroup>
          <CSProjects Include="$(MSBuildProjectDirectory)\..\MyProjects\**\*csproj" Exclude="$(MSBuildProjectDirectory)\..\MyProjects\**\Team*csproj;$(MSBuildProjectDirectory)\..\MyProjects\**\Image*csproj" />
        </ItemGroup>
        <Message Text="Updating Projects:[@(CSProjects)]" />
        <UpdateProject ProjectPath="%(CSProjects.Identity)" ImportsPath="$(MSBuildProjectDirectory)\projectImports.Targets" ModuleTargets="$(MSBuildProjectDirectory)\DoStuff.Targets" CommunityTasksPath="$(MSBuildProjectDirectory)" />
     </Target>
    
    <UsingTask
      TaskName="UpdateProject"
      TaskFactory="CodeTaskFactory"
      AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
      <ParameterGroup>
          <ProjectPath Required="true" />
          <ImportsPath Required="true" />
          <ModuleTargets Required="true" />
          <CommunityTasksPath Required="true" />
      </ParameterGroup>
      <Task>
          <Reference Include="System.Xml" />
          <Reference Include="System.Xml.Linq" />
          <Using Namespace="System" />
          <Using Namespace="System.Linq" />
          <Using Namespace="System.Xml.Linq" />
          <Code Type="Fragment" Language="cs">
              <![CDATA[
                  var document = XDocument.Load(ProjectPath);
                  XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
                  var alreadyAdded = document.Descendants(ns + "Target").Any(el => (string)el.Attribute("DependsOnTargets").Value == "DoStuff");
                  if (!alreadyAdded)
                      {
                          var importsDoc = XDocument.Load(ImportsPath);
                          importsDoc.Descendants(ns + "Import").FirstOrDefault().Attribute("Project").Value = ModuleTargets;
                          importsDoc.Descendants(ns + "MSBuildCommunityTasksPath").FirstOrDefault().Value = CommunityTasksPath;
                          document.Descendants(ns + "Project").FirstOrDefault().Add(importsDoc.Root.Elements());
                          document.Save(ProjectPath);
                      }
              ]]>
          </Code>
      </Task>
    </UsingTask>
    

    I first call UpdateProjects which updates each projects file with the location of the task. I use a code block (linq to xml) to find and update the project file. If it has already been updated, it won't add the same thing twice. I then call build on each project file and tell msbuild the name of the target that was added to each project file.