Build wildcard-fueled group of projects from .sln

2019-08-23 07:28发布

问题:

We have a solution created and maintained via VisualStudio2017 in which our .csprojs are placed inside virtual-folders like so:

Solution.sln
  \- VirtualFolder1
       \- Foo.Common.Bar.csproj -> Bar\bin
       \- Foo.Common.Ping.csproj -> Ping\bin
       \- Foo.Common.Pong.csproj -> Pong\bin
  \- VirtualFolder2
       \- Foo.Utils.Bar.csproj -> Utils.Bar\bin
       \- Foo.Utils.Ping.csproj -> Utils.Ping\bin
       \- Foo.Utils.Pong.csproj -> Utils.Pong\bin

As expected each and every .csproj file already contains a section which defines where the output path should be:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>[Bar/bin or Ping/bin etc]</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <LangVersion>7.1</LangVersion>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>[Bar/bin or Ping/bin etc]</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <LangVersion>7.1</LangVersion>
  </PropertyGroup>

We want to build all .Common .csproj and .Utils .csproj projects en-masse into their respective output folders without having to specify them in our msbuild-script (which is invoked by JenkinsCI btw) one by one. To achieve that we have tried:

<ItemGroup>      
  <ALL_PROJECTS_IN_SOLUTION_EXCEPT_TESTS
    Include="$(_codeFolderpath)\**\*.csproj"
  />
</ItemGroup>

<MSBuild
  Projects="@(ALL_PROJECTS_IN_SOLUTION_EXCEPT_TESTS)"
  Properties="Platform=$(Platform);Configuration=$(Configuration)"
  BuildInParallel="true"
/>

This however results in the following errors for all our .csproj:

The OutputPath property is not set for project [...].csproj

This is strange given the fact that the OutputPath is defined in our .csproj files (as shown above).

If we specify the 'Output' property then the problem goes away of course however what we really want is these projects to output themselves into their respective appropriate output directories (shown above). How can one go about achieving this?

回答1:

Looks like you have one separate project(Build Project) used to build the .Common .csproj and .Utils .csproj projects. And the script you write above is defined in a target in the Build Project. (Hope I didn't misunderstand.)

According to your error message The OutputPath property is not set... There is no OutputPath property defined in the Common..csproj or Utils..csproj.

If so, I suggest you use a folder structure like this:

Solution.sln
  \- VirtualFolder1
       \- Foo.Common.Bar.csproj -> Foo.Common.Bar\bin
       \- Foo.Common.Ping.csproj -> Foo.Common.Ping\bin
       \- Foo.Common.Pong.csproj -> Foo.Common.Pong\bin
  \- VirtualFolder2
       \- Foo.Utils.Bar.csproj -> Foo.Utils.Bar\bin
       \- Foo.Utils.Ping.csproj -> Foo.Utils.Ping\bin
       \- Foo.Utils.Pong.csproj -> Foo.Utils.Pong\bin

Because to get the same structure you want I think maybe there has much more complex work:

1.With no OutputPath in .csproj file, we can create a Directory.Build.props file in the Directory above its path to control the output path.

2.Pass the OutputPath property in your MSBuild Task. In this situation you need to get the second name for .common.csproj and .utils.csproj projects and add conditions like:

<MSBuild
      Projects="@(ALL_PROJECTS_IN_SOLUTION_EXCEPT_TESTS)"
      Properties="Platform=$(Platform);Configuration=$(Configuration);OutputPath=xxx\ThirdName\bin"
      BuildInParallel="true"
      Condition="judge if the common.csproj files"
    />
    <MSBuild
      Projects="@(ALL_PROJECTS_IN_SOLUTION_EXCEPT_TESTS)" 
      Properties="Platform=$(Platform);Configuration=$(Configuration);OutputPath=xxx\SecondName.ThirdName\bin"
      BuildInParallel="true"
      Condition="judge if the utils.csproj files"
    />

So both of these two directions may help achieve your particular goal, but the work would be much more than we expect.

As a workaround: What's reason you must put them in Utils.Bar\bin folder instead of Foo.Utils.Bar\bin folder? The latter one is pre-defined property for the Foo.Utils.Bar.csproj file. SO we can easily use the $(ProjectDir) or $(ProjectName) to represent it. You can Create a Directory.Build.props file, add the script below:

<Project>
 <PropertyGroup>
  <OutputPath>$(ProjectDir)bin\$(Configuration)</OutputPath>
</PropertyGroup>
</Project>

In this way, when loading the project files in VS, what you need to do is to build the solution. You won't need to build the Build project any more. And since you're using the virtual-path which I haven't tried, maybe you can use <OutputPath>AbsolutePathOfMyOutput\$(ProjectName)bin\$(Configuration)</OutputPath>

Update:(Haven't noticed your edit until today.)

According to your edit, you've set the output path in the .csproj.

Here are two suggestions:

1.If you build them in VS ID: Every time after you do some modify to xx.csproj outside VS IDE by notepad or what, I suggest you right-click the project to unload and reload the project file before you build them

2.Please check if the entire error message you get looks like this:

error : The OutputPath property is not set for project 'ConsoleApp1.csproj'.  Please check to make sure that
 you have specified a valid combination of Configuration and Platform for this project.  Configuration=''  Platform='An
yCPU'.

Since your OutputPath property are defined in PropertyGroup for Debug|AnyCPU and Release|AnyCPU.If you haven't passed the corresponding parameters to msbuild.exe, the process can't read the OutPutPath property from these ProppertyGroups.

For example: You define the OutPutPath in Debug|AnyCPU and Release|AnyCPU. Then the real value of Configuration and Platform you pass is Debug|X86, or Null|AnyCPU or CustomDebug|AnyCPU, and you haven't defined OutPutPath in this kind of Combination(PropertyGroup), you'll get the error messgae xxx not set.

To resolve this: Make sure you pass the correct Configuration and Platform combination can resolve it. Or define OutPutPath in the Combination you actually use.