4

I have an odd solution where I need one of the projects to "compile" files in another one.

The compiler (showing here a minimal example) is as follows (MSBuild custom task):

public class MyCompileTask : Task
{
    [Required]
    public ITaskItem[] InputFiles { get; set; }

    [Output]
    public ITaskItem[] OutputFiles { get; set; }

    public override bool Execute()
    {
        var generatedFileNames = new List<string>();

        foreach (var inputFile in this.InputFiles)
        {
            var inputFileName = inputFile.ItemSpec;
            var outputFileName = Path.ChangeExtension(inputFileName, ".res.txt");

            var source = File.ReadAllText(inputFileName);
            var compiled = source.ToUpper();
            File.WriteAllText(outputFileName, compiled + "\n\n" + DateTime.Now);

            generatedFileNames.Add(outputFileName);
        }

        this.OutputFiles = generatedFileNames.Select(name => new TaskItem(name)).ToArray();

        return true;
    }
}

As you see, it only uppercases the content of the input files.

This was project A - the "compiler" library.

Project B, for now the main application, has a file "lorem.txt" that needs to be "compiled" into "lorem.res.txt" and put as an EmbeddedResource in B.exe/B.dll.

In B.csproj I added the following:

<PropertyGroup>
<CoreCompileDependsOn>$(CoreCompileDependsOn);InvokeMyCompile</CoreCompileDependsOn>
</PropertyGroup>
<UsingTask TaskName="MyCompiler.MyCompileTask" AssemblyFile="$(MSBuildProjectDirectory)\..\MyCompiler\bin\$(Configuration)\MyCompiler.dll" />
<Target Name="MyCompile" Inputs="lorem.txt" Outputs="lorem.res.txt">
  <MyCompileTask InputFiles="lorem.txt">
    <Output TaskParameter="OutputFiles" PropertyName="OutputFiles" />
  </MyCompileTask>
</Target>
<Target Name="InvokeMyCompile" Inputs="lorem.txt" Outputs="lorem.res.txt">
  <Exec Command="&quot;$(MSBuildBinPath)\MSBuild.exe&quot; /t:MyCompile &quot;$(ProjectDir)$(ProjectFileName)&quot;" />
</Target>

(The 2 layers of targets and an explicit msbuild.exe invocation is a workaround to another problem. In fact, much of this example is stolen from that Q.)

The most important part works, i.e. when I change lorem.txt and build, lorem.res.txt gets regenerated.

However:

  • When lorem.res.txt is physically deleted, a build does nothing (says it's up-to-date) until I actually refresh the project in VS. So, MSBuild does not "know" that lorem.res.txt is actually required to build the project.
  • More importantly, when I change anything in project A, project B recompiles but does not re-run the compilation lorem.txt -> lorem.res.txt. So MSBuild does not "know" that the transformation is dependent on another project.

How can I declare these dependencies in the csproj file?

Bonus question: how to mark the output file (lorem.res.txt) as a generated EmbeddedResource so I don't have to track it in VS but it's still put into the assembly?

Community
  • 1
  • 1
Misza
  • 635
  • 4
  • 15
  • Adding MyCompiler.dll to the Inputs might deal with the last point already. Also instead of using CoreCompileDependsOn you could try to just add *BeforeTargets="Build"* to the target so execution doesn't depend on possible other mechanisms deciding whether the project is up to date or not. – stijn Nov 09 '16 at 08:49
  • what do you mean about "How can I declare these dependencies in the csproj file?" – Zhanglong Wu - MSFT Nov 15 '16 at 06:16
  • Do you resolve the issue, if the issue still exist, please feel free let me know. – Zhanglong Wu - MSFT Nov 17 '16 at 07:20

1 Answers1

0

•When lorem.res.txt is physically deleted, a build does nothing (says it's up-to-date) until I actually refresh the project in VS. So, MSBuild does not "know" that lorem.res.txt is actually required to build the project.

I create a demo and reproduce your issue on my side, you could use msbuild command line to avoid it.

•More importantly, when I change anything in project A, project B recompiles but does not re-run the compilation lorem.txt -> lorem.res.txt. So MSBuild does not "know" that the transformation is dependent on another project.

Because the custom task reference the DLL file, when change anything in project A, you need to rebuild project to generate newer DLL file.

Bonus question: how to mark the output file (lorem.res.txt) as a generated EmbeddedResource so I don't have to track it in VS but it's still put into the assembly?

You can add custom ItemGroup in BeforeBuild target to achieve it.

 <Target Name="BeforeBuild" DependsOnTargets="MyCompile">
    <ItemGroup>
      <Content Include="lorem.res.txt">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </Content>
    </ItemGroup>
  </Target>
Zhanglong Wu - MSFT
  • 1,652
  • 1
  • 7
  • 8