I have been stumped on how to include the “Content” folder using the new content builder system when building for Android. I have it set up so that I can build it then run it using a target: Windows, DesktopGL, Android, and MacOSX.
The reason is when building the “Desktop” project in VS2022, it builds both Windows DX and OpenGL concurrently, where if trying to build the Content Builder concurrently errors out saying it’s in use. So I came up with the solution of just building it then running it afterwards. For example, here is how I handle it in my Desktop .csproj that builds both “Content” folders for DX and OGL, and works for both building in Visual Studio and when publishing:
A PowerShell script in the “ContentBuilder” directory to build it:
param([string]$Configuration = "Debug")
# Serializes concurrent attempts to build ContentBuilder.csproj (e.g. when both
# TargetFrameworks build in the same invocation) so they never collide on its
# own obj/bin files. A no-op when only one platform is building.
$mutex = New-Object System.Threading.Mutex($false, "Local\ProjectZ_ContentBuilder_Build")
if (-not $mutex.WaitOne([TimeSpan]::FromMinutes(5)))
{
Write-Error "Timed out waiting for the ContentBuilder build lock."
exit 1
}
try
{
dotnet build "$PSScriptRoot\ContentBuilder.csproj" -c $Configuration -o "$PSScriptRoot\bin\Tool"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
finally
{
$mutex.ReleaseMutex()
}
Which is executed in the .csproj.
<!-- Build the external ContentBuilder tool -->
<Target Name="BuildContentTool">
<Exec Command="powershell -NoProfile -ExecutionPolicy Bypass -File "$(MSBuildThisFileDirectory)..\ContentBuilder\Build-ContentTool.ps1" -Configuration $(Configuration)" />
</Target>
<!-- Generate XNBs before both Build and Publish -->
<Target Name="GenerateContent"
DependsOnTargets="BuildContentTool"
BeforeTargets="Build;Publish">
<Exec Condition="'$(TargetFramework)' == 'net8.0-windows'"
Command="dotnet "$(MSBuildThisFileDirectory)..\ContentBuilder\bin\Tool\ContentBuilder.dll" Windows" />
<Exec Condition="'$(TargetFramework)' == 'net8.0'"
Command="dotnet "$(MSBuildThisFileDirectory)..\ContentBuilder\bin\Tool\ContentBuilder.dll" DesktopGL" />
</Target>
<!-- Visual Studio: Copy generated content into the build output -->
<Target Name="CopyContentToOutput"
DependsOnTargets="GenerateContent"
AfterTargets="Build">
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-windows'">
<GeneratedContent Include="..\ContentBuilder\bin\DirectX9\Content\**\*" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<GeneratedContent Include="..\ContentBuilder\bin\DesktopGL\Content\**\*" />
</ItemGroup>
<Copy SourceFiles="@(GeneratedContent)"
DestinationFiles="@(GeneratedContent->'$(TargetDir)Content\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true" />
</Target>
<!-- Publishing: Copy generated content into the publish output -->
<Target Name="CopyContentToPublish"
DependsOnTargets="GenerateContent"
AfterTargets="Publish">
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-windows'">
<GeneratedContent Include="..\ContentBuilder\bin\DirectX9\Content\**\*" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<GeneratedContent Include="..\ContentBuilder\bin\DesktopGL\Content\**\*" />
</ItemGroup>
<Copy SourceFiles="@(GeneratedContent)"
DestinationFiles="@(GeneratedContent->'$(PublishDir)Content\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true" />
</Target>
It’s crazy how hard this was to accomplish, so that “Content” ends up in the right place whether I build with Visual Studio to test, or publish with my batch script. This works for all projects except Android. No matter what I try, when I go to publish the “Content” folder is missing from the resulting APK. It’s getting built, it’s just not being included. I am questioning whether or not this is even possible to accomplish within a .csproj file at this point. The closest I got to was this:
<!-- Build the external ContentBuilder tool -->
<Target Name="BuildContentTool">
<Exec Command="powershell -NoProfile -ExecutionPolicy Bypass -File "$(MSBuildThisFileDirectory)..\ContentBuilder\Build-ContentTool.ps1" -Configuration $(Configuration)" />
</Target>
<!-- Generate XNB files and inject them into AndroidAsset before asset paths are computed -->
<Target Name="GenerateContent"
DependsOnTargets="BuildContentTool"
BeforeTargets="_ComputeAndroidAssetsPaths;CoreCompile"
Condition="'$(DesignTimeBuild)' != 'true'">
<Exec Command="dotnet "$(MSBuildThisFileDirectory)..\ContentBuilder\bin\Tool\ContentBuilder.dll" Android" />
<ItemGroup>
<_GeneratedAndroidContent Include="..\ContentBuilder\bin\Android\Content\**\*" />
<AndroidAsset Include="@(_GeneratedAndroidContent)" Link="Content\%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
</Target>
<!-- Static content is fine to leave static — it already exists on disk at evaluation time -->
<ItemGroup>
<AndroidAsset
Include="..\ProjectZ.Core\Data\**\*"
Link="Data\%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
I have searched and searched on how to tell the .csproj file to build the content before trying to include it as an Android asset, but I can’t find anything on how to do it. I’ve asked AI and gotten a million different versions of things that don’t work. If I publish twice in a row, the Content is included because it was generated the first time, just not “in time”. Does anyone have a clever solution to this? Somehow this was seemingly not a problem with the old content builder.








