[SOLVED] Conditional compilation of pipeline Content based on Visual Studio project / platform

I have a project which I can compile for both DirectX and OpenGL. I constantly switch between the 2 so I can test and make sure my shaders (and other code) works for both platforms (which also helps if I want to target Android).

The only complication I currently have is that I need to manually set the Content target in the pipeline tool to Windows or DesktopGL when I change my project target in Visual Studio.

Is there a way to have the pipeline tool platform be set automatically based on the Visual Studio project target?

Thanks!

If I get it right, you could store same assets in a shared folder and create own Content.mgcb for each platform, targeting those assets with different settings.

Thanks for the reply!

I could try that… The downside being that whenever I add or change something in my content pipeline, I would need to do it for each content.mgcb that I have.

With the currently setup, I have a single content.mgcb that is shared by all projects, so I only need to manage content from one point (with the downside being I need to change the platform in the pipeline tool when changing project types).

If there is no easy way to do what I am hoping to be able to, I will consider the multiple content.mgcb option.

If you’re building from source, I believe you should be able to do some editing to the Content Builder targets file to change the “platform” argument that gets passed to MGCB.exe, which is the executable that actually builds the content when running your Visual Studio build.

The file should be located in your MonoGame install directory under the MonoGame.Framework.Content.Pipeline directory. As an example, my file is located at:
D:\solari\code\external\MonoGame-3.7.1\MonoGame.Framework.Content.Pipeline\MonoGame.Content.Builder.targets

If you look in this file, you’ll notice the Header section, which defines the /platform argument that gets passed when building content. By default, it uses the MonoGamePlatform variable, but you should be able to change that to be either “Windows” or “DesktopGL” based on your Visual Studio project configuration (I think it would be $(Platform) and then you would just have to be sure to send the correct monogame platform based on your Visual Studio build configuration if they’re not a 1-1 match)

Alternatively, you could decouple the default way content is built by removing your Content.mgcb from your Visual Studio project, and instead adding a build event in your VS project which would call MGCB.exe with the correct arguments. Note that MGCB in the command line can be a little finicky, but check out this other thread of mine with a working example of passed arguments: here

If you’re not building from source I’m not sure of any way to do this automatically. You will likely need to deal with multiple content files. If this ends up becoming troublesome or error prone, check this out. Another MG community member was looking into a way of auto generating the Content.mgcb files based on existing files in a given directory. I haven’t looked at it yet, but was planning on checking it out in the future for our current project.

@kgambill Thanks… these are some great tips!

I’ll try modifying the content builder targets to see if I can get that working.

The templates MonoGame provides use the MonoGamePlatform msbuild variable to determine what platform to build for when run using the MSBuild task from the content building .targets file. So make sure that variable is set to the right platform in your .csproj files, and if not, you could create a new project from the templates and copy over the relevant line.

1 Like

@Jjagg Thanks… I’ll give that a try.

With all the pointers in this thread, I managed to figure this out!

In MonoGame.Content.Builder.targets (which in my system is located in C:\Program Files (x86)\MSBuild\MonoGame\v3.0) there is a section:

  <Target Name="RunContentBuilder">
    <Exec Condition=" '%(ContentReferences.FullPath)' != '' "
          Command="$(MonoGameContentBuilderCmd) $(MonoGameMGCBAdditionalArguments) $(Header) /@:&quot;%(ContentReferences.FullPath)&quot; /outputDir:&quot;%(ContentReferences.ContentOutputDir)&quot; /intermediateDir:&quot;%(ContentReferences.ContentIntermediateOutputDir)&quot;"
          WorkingDirectory="%(ContentReferences.RootDir)%(ContentReferences.Directory)" />
     <CreateItem Include="%(ContentReferences.RelativeFullPath)%(ContentReferences.ContentOutputDir)\**\*.*"
            AdditionalMetadata="ContentOutputDir=%(ContentReferences.ContentDirectory)">
        <Output TaskParameter="Include" ItemName="ExtraContent" />
    </CreateItem>
  </Target>

I modified the Exec Command which is this line:

Command="$(MonoGameContentBuilderCmd) $(MonoGameMGCBAdditionalArguments) $(Header) /@:&quot;%(ContentReferences.FullPath)&quot; /outputDir:&quot;%(ContentReferences.ContentOutputDir)&quot; /intermediateDir:&quot;%(ContentReferences.ContentIntermediateOutputDir)&quot;"

And added the following to the end of it (staying within the double quotes):

/platform:&quot;$(MonoGamePlatform)&quot;

The line now looks like the following:

Command="$(MonoGameContentBuilderCmd) $(MonoGameMGCBAdditionalArguments) $(Header) /@:&quot;%(ContentReferences.FullPath)&quot; /outputDir:&quot;%(ContentReferences.ContentOutputDir)&quot; /intermediateDir:&quot;%(ContentReferences.ContentIntermediateOutputDir)&quot; /platform:&quot;$(MonoGamePlatform)&quot;"

The modified Target section now looks like:

  <Target Name="RunContentBuilder">
    <Exec Condition=" '%(ContentReferences.FullPath)' != '' "
          Command="$(MonoGameContentBuilderCmd) $(MonoGameMGCBAdditionalArguments) $(Header) /@:&quot;%(ContentReferences.FullPath)&quot; /outputDir:&quot;%(ContentReferences.ContentOutputDir)&quot; /intermediateDir:&quot;%(ContentReferences.ContentIntermediateOutputDir)&quot; /platform:&quot;$(MonoGamePlatform)&quot;"
          WorkingDirectory="%(ContentReferences.RootDir)%(ContentReferences.Directory)" />
     <CreateItem Include="%(ContentReferences.RelativeFullPath)%(ContentReferences.ContentOutputDir)\**\*.*"
            AdditionalMetadata="ContentOutputDir=%(ContentReferences.ContentDirectory)">
        <Output TaskParameter="Include" ItemName="ExtraContent" />
    </CreateItem>
  </Target>

This works perfectly and I can now just change my project in Visual Studio from OpenGL to DirectX (and vice versa) and it will recompile the shaders for the target platform correctly without having to change it manually via the pipeline tool.

Note, this change does ignore the /platform attribute which is set in the Content.mgcb when compiling via the IDE.

You don’t need to add the /platform parameter by yourself. This information is already set with the $(Header) property.

Take a look:

You just need to define the property in your .csproj file like this:

@sqrMin1 The MonoGamePlatform property is set correctly in the .csproj files, so it didn’t look like that was the issue.

Not sure why it wasn’t working previously if the /platform option was already part of the header, but making the changes that I outlined above did resolve it and get it working again.

tl;dr: If your shared Content.mgcb file has a “/platform:<some_platform>” line defined somewhere near the top, remove it. Save the file. Clean, rebuild, and redeploy your solution.


Ok, so I’ve made a discovery. I also have the correct MonoGamePlatform setting in my project files. I took a look at the targets file and everything looks like it checks out there as well. That header definition gets referenced in the command and should work.

Having said that, it looks like if a /platform line is defined in the content file, this will override the project setting. I have a suspicion that this used to not work before and was fixed in MonoGame 3.7+ because my project worked just fine until I upgraded. Afterwards, for whatever platform was not defined in the content file, it wouldn’t work, giving an exception saying “This MGFX effect was built for a different platform!”

By simply removing the “/platform” line from my content file, everything works correctly on my build again. No additional steps needed. This line probably ended up in here in the first place because back when I was creating my game project, I started with a Windows project. I then moved the content file out to a central location and linked my Windows and Android projects to that instead.

Aaaaaanyway, I think something definitely changed in MonoGame 3.7… and it actually sounds like a fix for a defect. It is kind of not obvious though that you’d have to remove this line when setting up your content file for multiple platform builds.

It’s seems to be an older bug then, because I just tested different cases with a MonoGame 3.7+ targets file and it was always possible to generate content for the platform defined in the .csproj file - even when the content response file had the /platform command with a different platform set.

I came across this issue recently and I agree that the problem is that any “/platform:” in the .mgcb file will take priority over any argument passed from the command line. This problem is made worse because of the fact that using the pipeline tool will add the “/platform:” line to the file when you save, even if it was removed. I found a solution to this by setting up a target to run before the content build that will comment out the problematic line.

To fix it just append this to the end of your .csproj file.

<UsingTask TaskName="ReplaceFileText" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
        <ParameterGroup>
            <InputFilename ParameterType="System.String" Required="true" />
            <OutputFilename ParameterType="System.String" Required="true" />
            <MatchExpression ParameterType="System.String" Required="true" />
            <ReplacementText ParameterType="System.String" Required="true" />
        </ParameterGroup>
        <Task>
            <Reference Include="System.Core" />
            <Using Namespace="System" />
            <Using Namespace="System.IO" />
            <Using Namespace="System.Text.RegularExpressions" />
            <Code Type="Fragment" Language="cs">
                <![CDATA[
                            File.WriteAllText(
                                    OutputFilename,
                                    Regex.Replace(File.ReadAllText(InputFilename), MatchExpression, ReplacementText)
                                    );
                        ]]>
            </Code>
        </Task>
    </UsingTask>
    <Target Name="RemovePlatformBeforeContent" BeforeTargets="BuildContent">
    <ReplaceFileText 
    InputFilename="Content\Content.mgcb" 
    OutputFilename="Content\Content.mgcb" 
    MatchExpression="/platform:" 
    ReplacementText="#" />
  </Target>
1 Like