3

At my place of work, we have a 'Core' solution containing a number of projects providing different utility functions which are packaged via nuget and published to an internal nuget server. In order to achieve the nuget packaging from our CI, we currently have an MSBuild script that looks like this:

<Exec Command="nuget pack &quot;$(ProjectRoot)\Domain\Domain.csproj&quot; -Properties Configuration=$(Configuration)" />
<Exec Command="nuget pack &quot;$(ProjectRoot)\Logging\Logging.csproj&quot; -Properties Configuration=$(Configuration)" />
<Exec Command="nuget pack &quot;$(ProjectRoot)\Logging.NLog\Logging.NLog.csproj&quot; -Properties Configuration=$(Configuration)" />
<Exec Command="nuget pack &quot;$(ProjectRoot)\Persistence\Persistence.csproj&quot; -Properties Configuration=$(Configuration)" />
<Exec Command="nuget pack &quot;$(ProjectRoot)\Persistence.NHibernate\Persistence.NHibernate.csproj&quot; -Properties Configuration=$(Configuration)" />

We have about 20 projects being packed this way, and every time we introduce a new project to be packed we have to add another line to the MSBuild script. Is it possible to enumerate the projects in a solution using MSbuild, and thus condense this script into something like...

<foreach project in solution>
    <Exec Command="nuget pack project -Properties Configuration=$(Configuration)" />
</foreach>

Additionally, we'd need to be able to interrogate each project in the loop to exclude test projects (these are all under a Tests directory so should be trivial?)

EDIT: Other questions deal with the issue of executing msbuild tasks in a loop, but the solutions involve manually enumerating the items to be looped upon in an ItemGroup, so would result in slightly simpler but still very verbose code. I want to avoid enumerating the projects by hand if possible.

Community
  • 1
  • 1
Henry Wilson
  • 3,281
  • 4
  • 31
  • 46
  • Possible [duplicate](http://stackoverflow.com/q/6036748/1997232). – Sinatr May 19 '14 at 09:19
  • 1
    @Sinatr I have seen that question but it doesn't solve my problem - a solution is posed whereby Exec can be looped on the contents of an ItemGroup, but you'd still have to type the project names manually into an ItemGroup. I want to avoid actually naming all the projects, if possible. – Henry Wilson May 19 '14 at 09:21
  • And what about [this one](http://stackoverflow.com/q/12948037/1997232/)? and [this](http://stackoverflow.com/q/3491308/1997232)? – Sinatr May 19 '14 at 09:23
  • That one is also using ItemGroups to enumerate the objects to be looped on, which is exactly what I wish to avoid - if I use Itemgroups, I still have to add a line to the ItemGroup every time I add a new project to be packed. – Henry Wilson May 19 '14 at 09:24

1 Answers1

2

You could use the properties SolutionDir and SolutionName to locate the solution file and then write a custom target to find all the projects included into your solution. I made it like this:

<Target Name="AfterBuild" DependsOnTargets="PublishNuGetPackages" />
<Target Name="PublishNuGetPackages">
    <CollectNuGetProjects SolutionDir="$(SolutionDir)" SolutionName="$(SolutionName)">
        <Output TaskParameter="NuGetProjects" ItemName="NuGetProjects" />
    </CollectNuGetProjects>
    <Message Text="Collected the following nuget projects: @(NuGetProjects)" Importance="high" />
    <Exec Command="nuget pack &quot;$(SolutionDir)\%(NuGetProjects.Identity)&quot; -Properties Configuration=$(Configuration)" />
    <!-- publish packages etc... -->
</Target>

<UsingTask TaskName="CollectNuGetProjects" 
       TaskFactory="CodeTaskFactory" 
       AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
        <SolutionName ParameterType="System.String" Required="true" />
        <SolutionDir ParameterType="System.String" Required="true" />
        <NuGetProjects ParameterType="System.String[]" Output="true" />
    </ParameterGroup>
    <Task>
        <Reference Include="System.Linq" />
        <Using Namespace="System" />
        <Using Namespace="System.Linq" />
        <Using Namespace="System.Text.RegularExpressions" />
        <Code Type="Fragment" Language="cs">
            <![CDATA[
                var solutionFile = Path.Combine(SolutionDir, SolutionName + ".sln");
                var solutionText = File.ReadAllText(solutionFile);
                NuGetProjects = Regex.Matches(solutionText, @"""(?<projectFile>[^""]+?\.(csproj|wixproj|vcxproj))""")
                    .Cast<Match>()
                    .Select(m => m.Groups["projectFile"].Value)
                    .Where(p => !p.Contains(@"\Tests\"))
                    .ToArray();
        ]]>
        </Code>
    </Task>
</UsingTask>

Also the custom task filtering out the projects located in folder Tests as you wanted.

Stepan Burguchev
  • 381
  • 1
  • 2
  • 4