Search code examples
msbuildmsbuild-taskmsbuild-target

Target for new language on top of Microsoft.Common.CurrentVersion.targets


I'm writing a simple extension to allow MSBuild to compile solidity source files.

My goal is when my solidproj gets compiled it runs specific executable which produces several bin and abi files that should be copied to the target directory. That sounds easy, however in practice it isn't.

I currently have following targets:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  ...

  <Import Project="$(MSBuildToolsPath)\Microsoft.Common.CurrentVersion.targets" />
  <Target Name="CreateManifestResourceNames" />
  <Target Name="CoreCompile">
    <Exec Command='$(RunCommand) "-o @(OutDir) @(SolCompile)" $(RunCommandArguments)' WorkingDirectory="$(RunWorkingDirectory)" />
  </Target>
</Project>

It almost works except that it expect that single exe file gets compiled

<!-- Copy the build product (.dll or .exe). -->
<Copy
    SourceFiles="@(IntermediateAssembly)"
    DestinationFolder="$(OutDir)"
    SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
    OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
    Retries="$(CopyRetryCount)"
    RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
    UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
    UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)"
    Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'"
        >

  <Output TaskParameter="DestinationFiles" ItemName="MainAssembly"/>
  <Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>

</Copy>

Error MSB3030 Could not copy the file "obj\Debug\Solidity9.exe" because it was not found. Solidity9 C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets 4118

How can I ask it to copy everything of this spefic extension to the output?

The whole project is on github here.


Solution

  • I ended up with creating all build steps from scratch:

      <ItemGroup>
        <CopyUpToDateMarker Include="$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)', '$(MSBuildProjectFile).CopyComplete'))" />
      </ItemGroup>
    
      <UsingTask
        TaskName="GetOutdatedFiles"
        TaskFactory="CodeTaskFactory"
        AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    
        <ParameterGroup>
          <FileList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
          <UpToDateMarker ParameterType="System.String" Required="true" />
          <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
        </ParameterGroup>
        <Task>
          <Using Namespace="System"/>
          <Code Type="Fragment" Language="cs">
            <![CDATA[
              var modifiedFiles = new List<ITaskItem>();
              if(!File.Exists(UpToDateMarker))
              {
                Result = FileList;
              }
              else 
              {
                DateTime markerTimeStamp = File.GetLastWriteTime(UpToDateMarker);
                Result = FileList.Where(file => DateTime.Parse(file.GetMetadata("ModifiedTime")) > markerTimeStamp).ToArray();
              }
              ]]>
          </Code>
        </Task>
      </UsingTask>
    
      <Target Name="Build" DependsOnTargets="CoreBuild;AfterBuild" />
    
      <Target Name="CoreBuild">
        <Error Condition="'$(OutDir)' == ''" Text="The OutDir property is missing" />
        <Error Condition="'$(IntermediateOutputPath)' == ''" Text="The IntermediateOutputPath property is missing" />
    
        <GetOutdatedFiles FileList="@(SolCompile)" UpToDateMarker="@(CopyUpToDateMarker)">
          <Output TaskParameter="Result" ItemName="ChangedFiles"/>
        </GetOutdatedFiles>
    
        <Exec Command='$(RunCommand) "%(ChangedFiles.Identity)" -o "$(IntermediateOutputPath)" $(RunCommandArguments)' WorkingDirectory="$(RunWorkingDirectory)" Condition="'@(ChangedFiles)' != ''"/>
      </Target>
    
      <Target Name="AfterBuild">
        <ItemGroup>
          <BinFiles Include="$(IntermediateOutputPath)/*.bin"/>
          <AbiFiles Include="$(IntermediateOutputPath)/*.abi"/>
        </ItemGroup>
    
        <Copy SourceFiles="@(BinFiles)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true">
          <Output TaskParameter="CopiedFiles" ItemName="BinFilesCopiedInThisBuild"/>
        </Copy>
        <Copy SourceFiles="@(AbiFiles)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true">
          <Output TaskParameter="CopiedFiles" ItemName="AbiFilesCopiedInThisBuild"/>
        </Copy>
        <Touch Files="@(CopyUpToDateMarker)" AlwaysCreate="true" Condition="'@(BinFilesCopiedInThisBuild)'!='' or '@(AbiFilesCopiedInThisBuild)'!=''" />
      </Target>
    
      <Target Name="Clean">
        <Delete Files="@(CopyUpToDateMarker)" />
        <RemoveDir Directories="$(OutDir)" />
      </Target>
    
      <Target Name="Rebuild" DependsOnTargets="Clean;Build" />
    

    Still need solve problems with transitive dependencies, but it's another question