MSBuild auto increment build version differently for release/debug - c#

Context:
I use MSBuild to build my projects. Currently I use a date of release version number that, unfortunately, lacks clarity when multiple releases occur in the same day. In Directory.Build.props:
<PropertyGroup>
<Version>
$([System.DateTime]::Now.Year).
$([System.DateTime]::Now.Month).
$([System.DateTime]::Now.Day).
$([System.Convert]::ToUInt16(
$([MSBuild]::Divide(
$([System.DateTime]::Now.TimeOfDay.TotalSeconds),
1.32
))
))
</Version>
</PropertyGroup>
Goal:
Create a versioning scheme that looks something like this:
3/23/20:
Release Build: 2020.3.23.0
3/24/20:
Debug Build: 2020.3.24.0
Debug Build: 2020.3.24.1
Debug Build: 2020.3.24.2
Release Build: 2020.3.24.0
Debug Build: 2020.3.24.3
Release Build: 2020.3.24.1
Essentially: the first three numbers are year/month/day, because date of release is frequently important. Then use auto incrementing version numbers for releases within the same day. Incrementing on debug is useful so I can confirm the correct version of software is being loaded and run, but I don't want confusingly high numbers on release builds. I may play around with some additional indicator for debug builds, but I should be able to figure that out on my own.
Question:
How can I auto increment builds within the same day, having a separate version for debug and release? Ideally solutions that don't add additional dependencies are preferred, but if there is no way without, then it is acceptable.

MSBuild auto increment build version differently for release/debug
In general, MSBuild did not have a function to see the version number of the obvious incremental build but only used the timestamp of the system build determines the build order as you used before.
In fact, if you create a custom property in msbuild to record the version number of the incremental build, it still needs to use an entity to store the record, and if it is not used, the parameter is reinitialized for each build (the msbuild attribute can only be identified in msbuild).
So the ideal way it that use textfile as an intermediate. You can follow my solution:
Solution
1) create a custom msbuild task which does increment the value of the record property.
--a) Create a class library project called MyCustomTask then Right-click on the project-->Add Reference-->reference Microsoft.Build.Framework dll and Microsoft.Build.Utilities.v4.0 dll.
--b) add these into CustomTask.cs(this is the name of the task which will be used in xxx.csproj file).
public class CustomTask : Task
{
private int _number;
[Required]
public int number //input taskitem
{
get { return _number; }
set { _number = value; }
}
private int _lastnumber;
[Output]
public int LastNumber //output value
{
get { return _lastnumber; }
set { _lastnumber = value; }
}
public override bool Execute() // Execution logic
{
LastNumber = number + 1;
return true;
}
}
--c) Then build the project and remember to store its MyCustomTask dll.
2) Aim to your main project and then create two txt files called Debug.txt,Release.txt and give each of them an initial value of 0.
3) add these into your Directory.Build.props file:
<Project>
<UsingTask TaskName="CustomTask" AssemblyFile="xxxxxx\MyCustomTask\MyCustomTask\MyCustomTask\bin\Debug\MyCustomTask.dll(the local path of the dll)"> </UsingTask>
<PropertyGroup>
<Record></Record>
</PropertyGroup>
<Target Name="WriteToFile1" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<Record Condition="'$(Configuration)'=='Debug' and !Exists('$(TargetPath)')">
0
</Record>
<Record Condition="'$(Configuration)'=='Release'and !Exists('$(TargetPath)')">
0
</Record>
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<MyTextFile Include="Debug.txt">
<Number>$(Record)</Number>
</MyTextFile>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Release'">
<MyTextFile Include="Release.txt">
<Number>$(Record)</Number>
</MyTextFile>
</ItemGroup>
<WriteLinesToFile
File="#(MyTextFile)"
Lines="$(Record)"
Overwrite="true"
Encoding="Unicode" Condition="'$(Configuration)'=='Debug'"/>
<WriteLinesToFile
File="#(MyTextFile)"
Lines="$(Record)"
Overwrite="true"
Encoding="Unicode" Condition="'$(Configuration)'=='Release'"/>
<PropertyGroup>
<Version>
$([System.DateTime]::Now.Year).
$([System.DateTime]::Now.Month).
$([System.DateTime]::Now.Day).
$(Record)
</Version>
</PropertyGroup>
</Target>
<Target Name="ReadLineFromFile" BeforeTargets="WriteToFile1">
<ReadLinesFromFile File="Debug.txt" Condition="'$(Configuration)'=='Debug'">
<Output TaskParameter="Lines" PropertyName="Record"/>
</ReadLinesFromFile>
<ReadLinesFromFile File="Release.txt" Condition="'$(Configuration)'=='Release'">
<Output TaskParameter="Lines" PropertyName="Record"/>
</ReadLinesFromFile>
<CustomTask number="$(Record)">
<Output TaskParameter="LastNumber" PropertyName="Record"/>
</CustomTask>
</Target>
</Project>
4) When you execute a task which depends on Build to show the property Version, it will work well as you hope.
Note that it will work for incremental build and if you click Rebuild(which execute Clean and then Build), it will set the version number to zero and start the rethrow.
Overall, this is an ideal solution which I try to realize it.

Related

throws “Process is terminated due to StackOverflowException” when compiling

I've got an error in only one project when compiling. I already check every classes and cannot find recursive in classes.
I can't fix the problem. I think you can check file of Microsoft.CSharp.Core.targets, and may be you can fix the problem. I make project in VS2017, and i can run. Now, I use VS2019 but i can't build the project.
Error:
1>------ Derleme başladı: Proje: Flight.Utility, Yapılandırma: Debug Any CPU ------
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn\Microsoft.CSharp.Core.targets(59,5): error :
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn\Microsoft.CSharp.Core.targets(59,5): error : Process is terminated due to StackOverflowException.
Someone can help to me ?
Thank you.
Microsoft.CSharp.Core.targets file:
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="Microsoft.Managed.Core.targets"/>
<Target Name="CoreCompile"
Inputs="$(MSBuildAllProjects);
#(Compile);
#(_CoreCompileResourceInputs);
$(ApplicationIcon);
$(AssemblyOriginatorKeyFile);
#(ReferencePathWithRefAssemblies);
#(CompiledLicenseFile);
#(LinkResource);
#(EmbeddedDocumentation);
$(Win32Resource);
$(Win32Manifest);
#(CustomAdditionalCompileInputs);
$(ResolvedCodeAnalysisRuleSet);
#(AdditionalFiles);
#(EmbeddedFiles);
#(EditorConfigFiles)"
Outputs="#(DocFileItem);
#(IntermediateAssembly);
#(IntermediateRefAssembly);
#(_DebugSymbolsIntermediatePath);
$(NonExistentFile);
#(CustomAdditionalCompileOutputs)"
Returns="#(CscCommandLineArgs)"
DependsOnTargets="$(CoreCompileDependsOn);_BeforeVBCSCoreCompile">
<!-- These two compiler warnings are raised when a reference is bound to a different version
than specified in the assembly reference version number. MSBuild raises the same warning in this case,
so the compiler warning would be redundant. -->
<PropertyGroup Condition="('$(TargetFrameworkVersion)' != 'v1.0') and ('$(TargetFrameworkVersion)' != 'v1.1')">
<NoWarn>$(NoWarn);1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup>
<!-- To match historical behavior, when inside VS11+ disable the warning from csc.exe indicating that no sources were passed in-->
<NoWarn Condition="'$(BuildingInsideVisualStudio)' == 'true' AND '$(VisualStudioVersion)' != '' AND '$(VisualStudioVersion)' > '10.0'">$(NoWarn);2008</NoWarn>
</PropertyGroup>
<PropertyGroup>
<!-- If the user has specified AppConfigForCompiler, we'll use it. If they have not, but they set UseAppConfigForCompiler,
then we'll use AppConfig -->
<AppConfigForCompiler Condition="'$(AppConfigForCompiler)' == '' AND '$(UseAppConfigForCompiler)' == 'true'">$(AppConfig)</AppConfigForCompiler>
<!-- If we are targeting winmdobj we want to specifically the pdbFile property since we do not want it to collide with the output of winmdexp-->
<PdbFile Condition="'$(PdbFile)' == '' AND '$(OutputType)' == 'winmdobj' AND '$(_DebugSymbolsProduced)' == 'true'">$(IntermediateOutputPath)$(TargetName).compile.pdb</PdbFile>
</PropertyGroup>
<PropertyGroup>
<LangVersion Condition="'$(LangVersion)' == '' AND
(('$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND '$(TargetFrameworkVersion)' == 'v3.0') OR
('$(TargetFrameworkIdentifier)' == '.NETStandard' AND '$(TargetFrameworkVersion)' == 'v2.1'))">preview</LangVersion>
</PropertyGroup>
<!-- Condition is to filter out the _CoreCompileResourceInputs so that it doesn't pass in culture resources to the compiler -->
<Csc Condition="'%(_CoreCompileResourceInputs.WithCulture)' != 'true'"
AdditionalLibPaths="$(AdditionalLibPaths)"
AddModules="#(AddModules)"
AdditionalFiles="#(AdditionalFiles)"
AllowUnsafeBlocks="$(AllowUnsafeBlocks)"
AnalyzerConfigFiles="#(EditorConfigFiles)"
Analyzers="#(Analyzer)"
ApplicationConfiguration="$(AppConfigForCompiler)"
BaseAddress="$(BaseAddress)"
CheckForOverflowUnderflow="$(CheckForOverflowUnderflow)"
ChecksumAlgorithm="$(ChecksumAlgorithm)"
CodeAnalysisRuleSet="$(ResolvedCodeAnalysisRuleSet)"
CodePage="$(CodePage)"
DebugType="$(DebugType)"
DefineConstants="$(DefineConstants)"
DelaySign="$(DelaySign)"
DisabledWarnings="$(NoWarn)"
DisableSdkPath="$(DisableSdkPath)"
DocumentationFile="#(DocFileItem)"
EmbedAllSources="$(EmbedAllSources)"
EmbeddedFiles="#(EmbeddedFiles)"
EmitDebugInformation="$(DebugSymbols)"
EnvironmentVariables="$(CscEnvironment)"
ErrorEndLocation="$(ErrorEndLocation)"
ErrorLog="$(ErrorLog)"
ErrorReport="$(ErrorReport)"
Features="$(Features)"
FileAlignment="$(FileAlignment)"
GenerateFullPaths="$(GenerateFullPaths)"
HighEntropyVA="$(HighEntropyVA)"
Instrument="$(Instrument)"
KeyContainer="$(KeyContainerName)"
KeyFile="$(KeyOriginatorFile)"
LangVersion="$(LangVersion)"
LinkResources="#(LinkResource)"
MainEntryPoint="$(StartupObject)"
ModuleAssemblyName="$(ModuleAssemblyName)"
NoConfig="true"
NoLogo="$(NoLogo)"
NoStandardLib="$(NoCompilerStandardLib)"
NoWin32Manifest="$(NoWin32Manifest)"
Nullable="$(Nullable)"
Optimize="$(Optimize)"
Deterministic="$(Deterministic)"
PublicSign="$(PublicSign)"
OutputAssembly="#(IntermediateAssembly)"
OutputRefAssembly="#(IntermediateRefAssembly)"
PdbFile="$(PdbFile)"
Platform="$(PlatformTarget)"
Prefer32Bit="$(Prefer32Bit)"
PreferredUILang="$(PreferredUILang)"
ProvideCommandLineArgs="$(ProvideCommandLineArgs)"
References="#(ReferencePathWithRefAssemblies)"
RefOnly="$(ProduceOnlyReferenceAssembly)"
ReportAnalyzer="$(ReportAnalyzer)"
Resources="#(_CoreCompileResourceInputs);#(CompiledLicenseFile)"
ResponseFiles="$(CompilerResponseFile)"
RuntimeMetadataVersion="$(RuntimeMetadataVersion)"
SharedCompilationId="$(SharedCompilationId)"
SkipCompilerExecution="$(SkipCompilerExecution)"
Sources="#(Compile)"
SubsystemVersion="$(SubsystemVersion)"
TargetType="$(OutputType)"
ToolExe="$(CscToolExe)"
ToolPath="$(CscToolPath)"
TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
UseHostCompilerIfAvailable="$(UseHostCompilerIfAvailable)"
UseSharedCompilation="$(UseSharedCompilation)"
Utf8Output="$(Utf8Output)"
VsSessionGuid="$(VsSessionGuid)"
WarningLevel="$(WarningLevel)"
WarningsAsErrors="$(WarningsAsErrors)"
WarningsNotAsErrors="$(WarningsNotAsErrors)"
Win32Icon="$(ApplicationIcon)"
Win32Manifest="$(Win32Manifest)"
Win32Resource="$(Win32Resource)"
PathMap="$(PathMap)"
SourceLink="$(SourceLink)">
<Output TaskParameter="CommandLineArgs" ItemName="CscCommandLineArgs" />
</Csc>
<ItemGroup>
<_CoreCompileResourceInputs Remove="#(_CoreCompileResourceInputs)" />
</ItemGroup>
<CallTarget Targets="$(TargetsTriggeredByCompilation)" Condition="'$(TargetsTriggeredByCompilation)' != ''" />
</Target>
</Project>
I had the same problem only in VS 2019. In VS 2017 everything worked fine. In my case the problem was occurred because of my Fluent API, the call chain was too long (about 400 lines of codes). When I split the call chain into small parts it started working.
I don't know why this happened, probably some problem with the code parser in VS 2019.

Auto-generate main method from referenced assembly

I am editing my question I think it is a little confusing and it does not explain what my intent is.
Edit:
My goal is that when my HelloWorld application references MyClassLibrary my code does not compile so that I ensure to initialize some code prior to running the main method. Kind of like a constructor of a class. When I reference MyClassLibrary I will like to run some code in there before running the main method of my HelloWorld application. NUnit has a similar functionality. When my HelloWorld application references NUnit I get the error: Error CS0017 Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point. As #Alex pointed out that Main method that NUnit creates is auto-generated. I will like to auto-generate a main method with some custom code. How can I do that from MyClassLibrary without doing anything on my HelloWorld application just like NUnit does it?
OLD Question:
I want to perform the same behavior that NUnit tests perform that it prevents using a Main method. In this case the error that I need is a good thing. Let me explain what I mean.
I create a hello world application targeting the .net core
Project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>
Code file: (default hello world c# code)
If I then run that application it runs fine
Add a reference to NUnit and my project file now contains.
.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
</ItemGroup>
</Project>
When I try to compile the project I get the error:
Error CS0017 Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point.
That means that there is another Main method. That method is probably located on the NUnit nuget package I am referencing. This is the error I am trying to replicate!.
Now this is how I try to replicate the same error:
I remove the NUnit nugget package having no references to NUnit on my hello world application.
Create a Project ClassLibrary1 with the following code:
.
public class MyLib
{
static void Main()
{
Console.WriteLine("fooooo");
// do something
}
}
Have my hello world application reference that project:
When I compile I get no errors even though there are 2 Main methods!
How does NUnit manages to prevent using a Main method? How can I replicate the same behavior? I want to create an assembly that when referenced it prevents executing the Main method.
It's just Microsoft.NET.Test.Sdk making build fail.
Adding <GenerateProgramFile>false</GenerateProgramFile> into <PropertyGroup> makes it compile and work anyway.
But adding another class with static void Main to the application makes build fail again regardless <GenerateProgramFile>.
In your example build fails because Microsoft.NET.Test.Sdk adds some auto-generated code to your application before compilation. That code is in ...\.nuget\packages\microsoft.net.test.sdk\16.2.0\build\netcoreapp1.0\Microsoft.NET.Test.Sdk.Program.cs. It's a class with another Main:
// <auto-generated> This file has been auto generated. </auto-generated>
using System;
[Microsoft.VisualStudio.TestPlatform.TestSDKAutoGeneratedCode]
class AutoGeneratedProgram {static void Main(string[] args){}}
BTW: it's absolutely legal to have Main method in another assembly. You just cannot have 2 Mains in one exe. But you can have any number of them in dll like this:
public class Class1
{
public static void Main() { }
public static void Main(string[] args) { }
}
public class Class2
{
public static void Main() { }
public static void Main(string[] args) { }
}
It compiles.
Update:
I found the solution. It's all about installing nuget, not just adding a reference.
Create a .NET Core Class Library and name it MyCoreLib.
Add MyCoreClass.
namespace MyCoreLib
{
public static class MyCoreClass
{
public static void Initialize()
{
System.Console.WriteLine("Initialized from 'MyCoreLib'");
}
}
}
Build the library.
Create the following file structure:
├───nuget
└───src
│ MyCoreLib.nuspec
│
├───build
│ └───netcoreapp2.1
│ ForcedEntryPoint.cs
│ MyCoreLib.targets
│
└───lib
└───netcoreapp2.1
MyCoreLib.dll
MyCoreLib.nuspec
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>MyCoreLib</id>
<version>1.0.0</version>
<authors>MyCoreLib</authors>
<owners>MyCoreLib</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Some description here</description>
<dependencies>
<group targetFramework=".NETCoreApp2.1" />
</dependencies>
</metadata>
</package>
ForcedEntryPoint.cs
//╔════════════════════════════════════╗
//║ This code was added automatically. ║
//║ Do not change or remove it. ║
//╚════════════════════════════════════╝
public static class ForcedEntryPoint
{
public static void Main(string[] args)
{
MyCoreLib.MyCoreClass.Initialize();
}
}
MyCoreLib.targets
<Project InitialTargets="ForceEntryPoint" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
<PropertyGroup>
<ForcedEntryPoint Condition="'$(ForcedEntryPoint)' == ''">$(MSBuildThisFileDirectory)ForcedEntryPoint$(DefaultLanguageSourceExtension)</ForcedEntryPoint>
<ForceEntryPoint Condition="'$(ForceEntryPoint)' == ''">true</ForceEntryPoint>
</PropertyGroup>
<Target Name="ForceEntryPoint" Condition="'$(ForceEntryPoint)' == 'true'">
<ItemGroup>
<Compile Include="$(ForcedEntryPoint)"/>
</ItemGroup>
</Target>
</Project>
Use NuGet Commandline to build a package like this:
D:\nugetwalkthrough\nuget>D:\nugetwalkthrough\nuget.exe pack D:\nugetwalkthrough\src\MyCoreLib.nuspec
Create a .NET Core Console App and make sure it works.
Install the created package.
Try to run the application and get error:
CS0017 Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point.
Remove the Main method from the application, run it and see it prints Initialized from 'MyCoreLib'.
Put the Main method back to the application and change the project file so that <PropertyGroup> contains <ForceEntryPoint>false</ForceEntryPoint>
Now it compiles and prints Hello World! from its own Main method.
Changing <ForceEntryPoint> to true makes it use another entry point (not that one of the application) again.
I think you should learn how to make multiple project under same solution.
So helloworld is main project.
Then create new test project helloworld.test as test project use Add a reference to NUnit there.
now all will work fine you can change your start up project to helloworld.test and debug or run it from visual studio or command line.
Anyway I never saw a test project inside main project in professional coding. May be for testing we comment main method and run test case.
Test project is also Executable.

MSBuild custom task depending on another project

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=""$(MSBuildBinPath)\MSBuild.exe" /t:MyCompile "$(ProjectDir)$(ProjectFileName)"" />
</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?
•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>

xbuild failing with (.html) resource files

I'm having a problem getting xbuild to compile a web application project.
We have some resource files, which are .html files.
The one that's failing currently is 'KwasantCore\Resources\HTMLEventInvitation.html'
The resource is defined in KwasantCore.csproj as
<Content Include="Resources\HTMLEventInvitation.html" />
When building on ubuntu, the file is located here:
/home/gitlab_ci_runner/gitlab-ci-runner/tmp/builds/project-1/KwasantCore/Resources/HTMLEventInvitation.html
When running xbuild, I get this error:
/home/gitlab_ci_runner/gitlab-ci-runner/tmp/builds/project-1/Kwasant.sln (default targets) ->
(Build target) ->
/home/gitlab_ci_runner/gitlab-ci-runner/tmp/builds/project-1/KwasantCore/KwasantCore.csproj (default targets) ->
/usr/lib/mono/xbuild/12.0/bin/Microsoft.Common.targets (GenerateResources target) ->
/usr/lib/mono/xbuild/12.0/bin/Microsoft.Common.targets: error : Tool exited with code: 1. Output: Error: Invalid ResX input.
Position: Line 123, Column 5.
Inner exception: Could not find a part of the path "/home/gitlab_ci_runner/gitlab-ci-runner/tmp/builds/project-1/KwasantCore/resources/htmleventinvitation.html".
I checked the file, and it's there - the problem is case sensitivity. The resource is correctly referenced in the .csproj, so somewhere along the line, the resource is getting lowercased from 'Resources/HTMLEventInvitation.html' to 'resources/htmleventinvitation.html'
I've taken a look at the Microsoft.Common.targets file on the ubuntu box. Line 125 is something completely unrelated (it shows me </PropertyGroup>). Looking at the GenerateResources target, it shows me this:
<Target Name = "GenerateResources">
<GenerateResource
Sources = "#(ResxWithNoCulture)"
UseSourcePath = "true"
OutputResources = "#(ManifestResourceWithNoCultureName->'$(IntermediateOutputPath)%(Identity).resources')"
Condition = "'#(ResxWithNoCulture)' != '' ">
<Output TaskParameter = "OutputResources" ItemName = "ManifestResourceWithNoCulture"/>
<Output TaskParameter = "FilesWritten" ItemName = "FileWrites"/>
</GenerateResource>
<GenerateResource
Sources = "#(ResxWithCulture)"
UseSourcePath = "true"
OutputResources = "#(ManifestResourceWithCultureName->'$(IntermediateOutputPath)%(Identity).resources')"
Condition = "'#(ResxWithCulture)' != '' ">
<Output TaskParameter = "OutputResources" ItemName = "ManifestResourceWithCulture"/>
<Output TaskParameter = "FilesWritten" ItemName = "FileWrites"/>
</GenerateResource>
</Target>
with the referenced targets being:
<CreateItem Include="#(ResourcesWithNoCulture)" Condition="'%(Extension)' == '.resx'">
<Output TaskParameter="Include" ItemName="ResxWithNoCulture"/>
</CreateItem>
<CreateItem Include="#(ResourcesWithNoCulture)" Condition="'%(Extension)' != '.resx'">
<Output TaskParameter="Include" ItemName="NonResxWithNoCulture"/>
</CreateItem>
<CreateItem Include="#(ResourcesWithCulture)" Condition="'%(Extension)' == '.resx'">
<Output TaskParameter="Include" ItemName="ResxWithCulture"/>
</CreateItem>
<CreateItem Include="#(ResourcesWithCulture)" Condition="'%(Extension)' != '.resx'">
<Output TaskParameter="Include" ItemName="NonResxWithCulture"/>
</CreateItem>
Now, this looks suspicious to me, but I can't figure out what these Include="#(ResourcesWithNoCulture)" lines are doing - a search for them elsewhere doesn't give me any hints.
The fact that it's a .html file (and not .resx), makes me suspicious of the GenerateTargets target, as it's only calling the resx versions of the targets.
I'm not an expert on .targets files - can anyone give me a hand? I've googled around, but found no help. I would assume that it would be a fairly common bug, as resources aren't extremely rare (but perhaps without .resx they are).
Edit: Having looked at it again, the error related to 'GenerateResources' doesn't exactly make sense: it should be failing at 'CopyNonResxEmbeddedResources', as the resources are not .resx. They GenerateResources target shouldn't be touching the .html files - as it's only looking at 'ResxWithNoCulture' and 'ResxWithCulture'
<Target Name = "CopyNonResxEmbeddedResources"
Condition = "'#(NonResxWithCulture)' != '' or '#(NonResxWithNoCulture)' != '' or '#(ManifestNonResxWithCulture)' != '' or '#(ManifestNonResxWithNoCulture)' != ''">
<MakeDir Directories="$(IntermediateOutputPath)%(ManifestNonResxWithCulture.Culture)"/>
<Copy SourceFiles = "#(NonResxWithCulture)"
DestinationFiles = "#(ManifestNonResxWithCulture->'$(IntermediateOutputPath)%(Identity)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)">
<Output TaskParameter = "DestinationFiles" ItemName = "ManifestNonResxWithCultureOnDisk"/>
<Output TaskParameter = "DestinationFiles" ItemName = "FileWrites"/>
</Copy>
<Copy SourceFiles = "#(NonResxWithNoCulture)"
DestinationFiles = "#(ManifestNonResxWithNoCulture->'$(IntermediateOutputPath)%(Identity)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)">
<Output TaskParameter = "DestinationFiles" ItemName = "ManifestNonResxWithNoCultureOnDisk"/>
<Output TaskParameter = "DestinationFiles" ItemName = "FileWrites"/>
</Copy>
</Target>
The target 'CopyNonResxEmbeddedResources' is called directly before 'GenerateResources'
I don't know why this happens (my brain just can't hold any more build systems' configuration nuances), but one of the tricks I've picked up along the way is:
MONO_IOMAP=case xbuild ...
That environment variable tells Mono to be case-insensitive when searching for files. The Mono documentation uses this for solving case sensitivity porting across Windows <-> Mac <-> Linux filesystems, but the MONO_IOMAP facility provides several other filesystem and I/O mapping operations.
In the event that doesn't work, you could try ciopfs, which is a Linux user-space case-insensitive filesystem. I've never used it, though.
I can't tell you WHY it's doing that, but my solution would be to change the name of the resource to match what it's looking for.
It DOES look like it's trying to process something as Resx...
Tool exited with code: 1. Output: Error: Invalid ResX input.
Maybe check your settings?
The compiler is trying to interpret this as a resource file rather than a resource. A resource file is a .txt or .resx file which is used to specify resources (e.g. strings, images) in a specific format, rather than a resource itself.
The GenerateResource task shouldn't be run on resources at all, because its purpose is to convert .txt or .resx files into .resource files to be embedded into an assembly.
If there are no actual resource files (.txt or .resx), then you should remove that task from the project's build altogether. Otherwise you just need to make sure that only the correct files are passed to it. I can't tell you exactly how to do this without being able to see more of your configuration, but this is a common configuration task, so you should be able to find guidance on Google.

How to transform files before adding them to an assembly?

I would like to do the following :
(project is a User Control library for WPF)
add a bunch of .FX (shader source code) files to the project as resources (Build action)
transform each to a .PS file (compiled shader) by invoking FXC.EXE utility
use the resulting file in place of the inputted file
I have been looking to write a CustomTool, unfortunately the tool is never seen by Visual Studio as it's mentioned in the article. In the article it is said that sometimes it is not seen but in my case it translates to every time.
I also looked at MSBuild Transforms but I'm not really sure if it would be appropriate for the task.
The goal of this is to include shader files source code and transform them at build time instead of manually building them from command line and dropping them every time to the project.
Do you know how one can achieve this ? Any methods are welcome
EDIT
Answer thanks to #Luaan :
public class CompileEffectTask : Task
{
public string[] Files { get; set; }
public override bool Execute()
{
if (Files != null)
{
foreach (string file in Files)
{
if (file != null)
{
Log.LogMessage(MessageImportance.High, file);
string s = #"C:\Program Files (x86)\Windows Kits\8.1\bin\x86\fxc.exe";
string changeExtension = Path.ChangeExtension(file, "ps");
string arguments = string.Format("/T ps_3_0 /Fo \"{0}\"" + " " + "\"{1}\"", changeExtension,
file);
Log.LogMessage(MessageImportance.High, arguments);
var process = new Process
{
StartInfo = new ProcessStartInfo(s, arguments)
};
process.Start();
process.WaitForExit();
}
}
}
return true;
}
}
And the MSBuild part :
<UsingTask TaskName="CompileEffectTask" AssemblyFile="D:\HLSLCompiler.dll" />
<PropertyGroup>
<BuildDependsOn>
MyCustomTarget1;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
<Target Name="MyCustomTarget1">
<Message Text="CompileEffectTask started" Importance="high" />
<Message Text="Compiling FX files ..." Importance="high" />
<CompileEffectTask Files="#(CompileEffectTask)"/>
<Message Text="Adding resulting .PS files as resources ..." Importance="high" />
<ItemGroup>
<Resource Include="**\*.ps" />
</ItemGroup>
</Target>
<Target Name="AfterBuild">
<CreateItem Include="**\*.ps">
<Output TaskParameter="Include" ItemName="DeleteAfterBuild" />
</CreateItem>
<Delete Files="#(DeleteAfterBuild)" />
</Target>
(still needs some cleaning but it works :D)
Custom tools do work, in fact, but they're rather tricky to setup - they're COM extensions to Visual Studio. However, the better solution for your case would be a custom build target or a pre-build event anyway - custom tools (code generators) are better suited for generating code (text) rather than binary files.
So, the pre-build event is the simple one. It's just some script that's run before the project starts building. You can find it in project properties. The simplest way would be to have all your .fx files in one directory, and in the pre-build event, you'd just call fxc.exe on each of them.
Now, build targets are cooler. They allow you to add your own build actions to files, among other things. So you'd just select CompileEffect in Build action of your files, and magic happens.
The target file can be quite simple:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="CompileEffect"></AvailableItemName>
</ItemGroup>
</Project>
Or you can just put the ItemGroup part inside of your project file directly (otherwise you'd want to include this target file).
Next, you want to set the task as part of your build:
<PropertyGroup>
<BuildDependsOn>
MyCompileTarget;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
This basically says "run my build target first, and after that whatever you'd want".
Now, for the building:
<Target Name="MyCompileTarget">
<CompileEffectTask
ProjectDirectory="$(ProjectDir)"
Files="#(CompileEffect)"
RootNamespace="$(RootNamespace)">
</CompileEffectTaskTask>
</Target>
How does Visual Studio know what CompileEffectTask is?
<UsingTask TaskName="MyAssembly.CompileEffectTask"
AssemblyFile="C:\MyAssembly.dll"/>
And then you just need to implement the compiler task itself.
Now, if you only want to call an executable or a batch script, you don't even need that custom task, because there's a lot of built-in tasks in MSBuild (and even more in MSBuild Community Tasks). Exec task should work:
<Target Name="MyCompileTarget">
<Exec Command="fxc.exe #(CompileEffect)" />
</Target>
You might have to write a for cycle there, I'm not entirely sure. There's a lot of things you can do to customize project builds, http://msdn.microsoft.com/en-us/library/0k6kkbsd.aspx (especially the Task refecence part) is a rather good start.

Categories