How to create custom project file that works with fast-up-to-date (and avoids other problems)?

Viewed 86

I am trying to create a project file that performs few custom steps (specifically, it "wraps" existing Angular CLI project).

Here is my best attempt (myproject.csproj):

<Project ToolsVersion="Current" DefaultTargets="Build">
  <PropertyGroup>
    <ProjectGuid>{...some-guid...}</ProjectGuid>
    <!-- do not include files by default -->
    <EnableDefaultItems>false</EnableDefaultItems>
    <!-- this removes 'Publish...' menu in VS -->
    <OutputType>Library</OutputType>
    <!-- output directory name -->
    <AngularProject>MyWebFiles</AngularProject>
  </PropertyGroup>
  
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <PlatformTarget>x64</PlatformTarget>
    <OutputPath>bin\Debug\</OutputPath>
  </PropertyGroup>
  
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <PlatformTarget>x64</PlatformTarget>
    <OutputPath>bin\Release\</OutputPath>
  </PropertyGroup>
  
  <ItemGroup>
    <AngularFile Include="**" Exclude="node_modules\**" />
  </ItemGroup>

  <Target Name="Build" Inputs="@(AngularFile)" Outputs="$(OutputPath)$(AngularProject)\index.html">
    <Exec Command="ng build --no-progress --output-path $(OutputPath)$(AngularProject)\" Condition="'$(Configuration)'=='Debug'" />
    <Exec Command="ng build --no-progress --output-path $(OutputPath)$(AngularProject)\ --prod" Condition="'$(Configuration)'=='Release'" />
  </Target>
  
  <Target Name="Clean">
    <RemoveDir Directories="$(OutputPath)$(AngularProject)\" />
  </Target>
  
  <Target Name="Rebuild" DependsOnTargets="Clean;Build" />
</Project>

Everything works fine, I can add this project to VS2019 solution, compile, etc. But it has problems:

  1. Fast up-to-date check doesn't work. Related logging produces this:
  Build started...
  1>Project 'myproject' is not up to date. Error (0x8000FFFF). 

I've tried specifying fast up-to-date files manually (via UpToDateCheckInput, etc), but it didn't work (presumably because it relies on additional definitions pulled in when you specify Sdk attribute of Project tag).

  1. VS configuration manager has empty 'Platform' combo box. I'd like to be able to have x64 in it:

1

it is rather obvious that PlatformTarget is getting ignored by VS.

  1. Opening project in VS results in creation of obj\x64\Debug\TempPE\ directory (if current Configuration is Debug). Nothing ever gets generated in it -- would be nice to avoid it being created.

Is it possible to fix these 3 problems? I suspect relates subsystems expect certain values/properties to be generated, I've tried digging in .props/.targets that come with VS in attempt to locate them, but quickly got lost.

1 Answers

Here is how to do it:

<Project Sdk="Microsoft.Build.NoTargets/3.2.14">
  <ItemGroup>
    <PackageReference Include="Microsoft.Build.NoTargets" Version="3.2.14" />
  </ItemGroup>

  <PropertyGroup>
    <!-- Any target framework you want as long as its compatible with your referenced NuGet packages -->
    <TargetFramework>net462</TargetFramework>
    <Platforms>x64</Platforms>
    <!-- Do not add TargetFramework to OutputPath -->
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
    <!-- Do not expect pdb files to be generated (this is for fast up-to-date check) -->
    <DebugType>None</DebugType>
    <!-- Do not include files by default -->
    <EnableDefaultItems>false</EnableDefaultItems>
    <!-- Output subdir name -->
    <AngularProject>MyWebFiles</AngularProject>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <OutputPath>..\..\Bin\Debug\</OutputPath>
    <BuildCommand>ng build --no-progress --output-path $(OutputPath)$(AngularProject)\</BuildCommand>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <OutputPath>..\..\Bin\Release\</OutputPath>
    <BuildCommand>ng build --no-progress --output-path $(OutputPath)$(AngularProject)\ --prod</BuildCommand>
  </PropertyGroup>

  <ItemGroup>
    <None Include="**" Exclude="node_modules\**;$(BaseIntermediateOutputPath)\**;$(MSBuildProjectFile)" />
    
    <!-- This deals with fast up-to-date checks -->
    <UpToDateCheckBuilt Original="package-lock.json" Include="node_modules/.build" />
    
    <UpToDateCheckInput Include="@(None);$(MSBuildProjectFile)" Set="AngularFiles" />
    <UpToDateCheckOutput Include="$(OutputPath)$(AngularProject)\index.html" Set="AngularFiles" />
  </ItemGroup>

  <Target Name="InitModules" Inputs="package-lock.json" Outputs="node_modules/.build">
    <Exec Command="npm ci --no-progress --no-color" YieldDuringToolExecution="true" />
    <Exec Command="cd . &gt; node_modules/.build" />
  </Target>

  <Target Name="BuildAngular" BeforeTargets="AfterBuild" Inputs="@(None);$(MSBuildProjectFile)" Outputs="$(OutputPath)$(AngularProject)\index.html" DependsOnTargets="InitModules">
    <Exec Command="$(BuildCommand)" YieldDuringToolExecution="true" />
  </Target>

  <Target Name="CleanAngular" BeforeTargets="AfterClean">
    <RemoveDir Directories="$(OutputPath)$(AngularProject)\" />
  </Target>
</Project>

Notes:

  • it will still generate additional local directory (obj), but it can be moved away by overriding IntermediateOutputPath
Related