I am working on decreasing compile time for a large C# / ASP.NET solution. Our solution is translated into about a dozen foreign languages using the usual resx file method. The parsing and compiling of these resource files greatly slows down our compile time and is a daily frustration.
I am aware that it is possible to create custom resource providers and get away from .resx files. For now, please assume we must stick with .resx files.
By excluding all but the default locale .resx files from our .csproj files, I am able to cut our compile time in half. Our developers don't need to be compiling a dozen other languages during day-to-day development.
I'm looking for ways to prevent the compilation of the foreign language .resx files. I've come up with two methods, and I'm looking for advice on whether one is superior, or whether there are other better methods.
The two I've come up with:
Update
I wrote the following Powershell script to move all my foreign language EmbeddedResources and Compile elements into a new ItemGroup which has a Conditional attribute.
$root = "C:\Code\solution_root\"
# Find all csproj files.
$projects = Get-ChildItem -Path $root -Recurse -ErrorAction SilentlyContinue -Filter *.csproj | Where-Object { $_.Extension -eq '.csproj' }
# Use a hashtable to gather a unique list of suffixes we moved for sanity checking - make sure we didn't
# relocate anything we didn't intend to. This is how I caught I was moving javascript files I didn't intend to.
$suffixes = @{}
# Find foreign resources ending in .resx and .Designer.cs
# Use a regex capture to so we can count uniques.
$pattern = "\.(?<locale>\w{2}(-\w{2,3})?\.(Designer.cs|resx))$"
foreach ($project in $projects)
{
"Processing {0}" -f $project.FullName
# Load the csproj file as XML
$xmlDoc = new-object XML
$xmlDoc.Load($project.FullName)
# Set namespace for XPath queries
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("ns", $xmlDoc.DocumentElement.NamespaceURI)
$count = 0
$embeds = $xmlDoc.SelectNodes("//ns:EmbeddedResource",$ns)
$compiles = $xmlDoc.SelectNodes("//ns:Compile",$ns)
# Create new conditional ItemGroup node if it does not exist.
# Side-effect - every csproj will get this new element regardless of whether it
# contains foreign resources. That works for us, might not for you.
$moveToNode = $xmlDoc.SelectSingleNode("//ns:ItemGroup[@Condition=`" '`$(Configuration)'=='Release' `"]", $ns)
if ($moveToNode -eq $null) {
# When creating new elements, pass in the NamespaceURI from the parent node.
# If we don't do this, elements will get a blank namespace like xmlns="", and this will break compilation.
# Hat tip to https://stackoverflow.com/questions/135000/how-to-prevent-blank-xmlns-attributes-in-output-from-nets-xmldocument
$conditionAtt = $xmlDoc.CreateAttribute("Condition")
$conditionAtt.Value = " '`$(Configuration)'=='Release' "
$moveToNode = $xmlDoc.CreateElement("ItemGroup", $xmlDoc.Project.NamespaceURI)
$ignore = $moveToNode.Attributes.Append($conditionAtt)
$ignore = $xmlDoc.LastChild.AppendChild($moveToNode)
}
# Loop over the EmbeddedResource and Compile elements.
foreach ($resource in ($embeds += $compiles)) {
# Exclude javascript files which I found in our Web project.
# These look like *.js.resx or *.js.Designer.cs and were getting picked up by my regex.
# Yeah, I could make a better regex, but I'd like to see my kids today.
if ($resource.Include -notmatch "js\.(Designer.cs|resx)$" -and $resource.Include -match $pattern ) {
# We have a foreign-language resource.
# Track unique suffixes for reporting later.
$suffix = $matches['locale']
if (!$suffixes.ContainsKey($suffix)) {
$ignore = $suffixes.Add($suffix,"")
}
$ignore = $moveToNode.InsertBefore($resource, $null)
# Count how many we moved per project.
$count += 1
}
}
"Moved {0} resources in {1}.`n" -f $count, $project.Name
$xmlDoc.Save($project.FullName)
}
echo "The following unique suffixes were processed."
$suffixes.Keys | sort
You could make use of existing mechanisms in MSBuild that allow you to handle this scenario, like the <Choose>
element documentated here and the option to introduce your own build configurations. If your developers only build Debug configurations during their day-to-day work you may not even need your own build configuration: you can set up your project so that that all the foreign language resources are only included in Release builds.
This project file section would make sure that Polish resources are only built in the Release configuration (the language codes are probably wrong, but you get the idea):
<Choose>
<When Condition=" '$(Configuration)'=='Release' ">
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.pl.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.pl.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.pl.Designer.cs">
<DependentUpon>Resources.pl.resx</DependentUpon>
<AutoGen>True</AutoGen>
</Compile>
<!-- TODO: add other language resources as necessary-->
</ItemGroup>
</When>
</Choose>
In case your developers need to build Release configurations you can create your own configuration ReleaseResx and include the foreign language .resx files only in that configuration.
Both your suggestions would work, too, I just think this approach is cleaner.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With