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.