Skip to main content

Fixing 'Multiple Commands Produce' Errors When Adding Smaato Adapter to AppLovin MAX

 There is a specific kind of dread reserved for the moment you update your mediation stack in Unity, generate the Xcode project, and hit Cmd+B, only to see the build fail instantly.

If you are integrating Smaato alongside other ad networks (specifically Verve, HyprMX, or InMobi) within the AppLovin MAX ecosystem, you have likely encountered the infamous Xcode build error:

error: Multiple commands produce '/Users/.../derivedData/Build/Products/Release-iphoneos/YourGame.app/OMIDSDK.bundle'

Or perhaps a variation involving PrivacyInfo.xcprivacy.

This is not a code logic error. It is a dependency collision that manual deletion will not permanently solve. This article details the root cause of this conflict and provides a production-grade, automated solution using Unity's IPostprocessBuildWithReport to patch your iOS build pipeline permanently.

The Root Cause: The Open Measurement SDK War

To understand the fix, you must understand the architecture. The error "Multiple commands produce" means that the Xcode build system has identified two distinct actions in the build phases that attempt to create a file at the exact same path in the application bundle.

The Shared Dependency

The culprit is almost always the IAB Open Measurement SDK (OM SDK). This is an industry-standard validation tool that allows third-party verification of ad viewability.

Both Smaato and Verve (among others) bundle the OM SDK to ensure their ads are measurable. However, they integrate it differently:

  1. Network A (e.g., Verve): Might include OMIDSDK.bundle as a raw resource in their Pod.
  2. Network B (e.g., Smaato): Might include OMIDSDK.bundle embedded inside their .framework or as a resource spec in their Podspec.

When CocoaPods generates the Pods-Unity-iPhone project, it creates a "Copy Pods Resources" build phase. Because both SDKs claim ownership of OMIDSDK.bundle, CocoaPods (or the underlying Xcode build system) attempts to copy both files to the root of your app package. Xcode 14 and 15 strictly forbid this file overwriting.

The Manual Fix (For Verification Only)

Before implementing the automation, verify that this is indeed your issue by attempting a manual fix in Xcode.

  1. Open your generated Xcode project.
  2. Navigate to the Pods project (not your main Unity project).
  3. Select the Target corresponding to one of the conflicting networks (e.g., SmaatoSDK or the specific adapter pod).
  4. Go to Build Phases -> Copy Bundle Resources.
  5. Locate OMIDSDK.bundle.
  6. Select it and hit Remove (-).
  7. Clean Build Folder (Cmd+Shift+K) and Build (Cmd+B).

If the build succeeds, you have confirmed the collision. However, do not stop here. The next time you build from Unity, this change will be overwritten. You need an automated script.

The Permanent Fix: Automated Podfile Patching

The most robust way to solve this in a Unity/AppLovin environment is to intercept the CocoaPods generation process. We will create a Unity Editor script that injects a post_install hook into the Podfile.

This hook will iterate through the Pod targets and programmatically remove the resource reference from one of the SDKs before Xcode ever tries to build it.

Step 1: Create the Post-Processor

Create a file named iOSPodfilePatcher.cs inside your Unity project at Assets/Editor/iOSPodfilePatcher.cs.

using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;

public class iOSPodfilePatcher
{
    // High priority to ensure this runs before the Podfile is executed by the resolver
    [PostProcessBuild(45)] 
    public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
    {
        if (buildTarget != BuildTarget.iOS) return;

        string podfilePath = Path.Combine(buildPath, "Podfile");

        if (!File.Exists(podfilePath))
        {
            UnityEngine.Debug.LogWarning("[iOSPodfilePatcher] No Podfile found at " + podfilePath);
            return;
        }

        string podfileContent = File.ReadAllText(podfilePath);

        // Check if we've already patched it to avoid duplicate hooks
        if (podfileContent.Contains("generated_duplicate_resource_fix"))
        {
            UnityEngine.Debug.Log("[iOSPodfilePatcher] Podfile already patched.");
            return;
        }

        // The Ruby script to append to the Podfile
        string rubyPatch = @"
# BEGIN: generated_duplicate_resource_fix
post_install do |installer|
  installer.pods_project.targets.each do |target|
    # Logic to handle OMIDSDK.bundle collision
    if target.name == 'SmaatoSDK' || target.name.include?('Smaato')
      puts ""[Fix] Processing #{target.name} for OMIDSDK conflicts...""
      target.build_phases.each do |phase|
        if phase.is_a?(Xcodeproj::Project::Object::PBXResourcesBuildPhase)
          phase.files_references.each do |file_ref|
            if file_ref.path.include? 'OMIDSDK.bundle'
              puts ""[Fix] Removing OMIDSDK.bundle from #{target.name} to prevent 'Multiple commands produce' error.""
              phase.remove_file_reference(file_ref)
            end
          end
        end
      end
    end
  end
end
# END: generated_duplicate_resource_fix
";

        // If a post_install block already exists, this simplistic append might fail syntax.
        // For standard AppLovin/Unity generated Podfiles, appending usually works 
        // because the MAX plugin often adds dependencies but leaves post_install open.
        // Ideally, we append this at the very end.
        
        File.AppendAllText(podfilePath, rubyPatch);
        UnityEngine.Debug.Log("[iOSPodfilePatcher] Successfully patched Podfile to handle Smaato OMIDSDK collision.");
    }
}

Step 2: Regenerate the Project

  1. In Unity, go to File > Build Settings.
  2. Build the iOS project (Append or Replace).
  3. Watch the Unity Console. You should see [iOSPodfilePatcher] Successfully patched Podfile....
  4. Open the terminal in the build directory and run pod install (or let Unity/External Dependency Manager do it automatically).

During the pod install phase in the terminal, you will now see the Ruby output: [Fix] Removing OMIDSDK.bundle from SmaatoSDK...

Deep Dive: Why This Code Works

The PostProcessBuild Attribute

We use [PostProcessBuild(45)]. The External Dependency Manager for Unity (EDM4U) typically runs its pod generation around priority 40-50. By timing this correctly, we modify the Podfile after Unity generates the skeleton but before the command line executes pod install.

The Ruby Script

CocoaPods is built on Ruby. The post_install hook gives us access to installer.pods_project. This is a direct reference to the .xcodeproj file that CocoaPods is generating.

  1. Iterate Targets: We loop through every pod target (Smaato, AppLovin, Verve, GoogleMobileAds, etc.).
  2. Identify Smaato: We specifically look for target.name == 'SmaatoSDK'. We choose Smaato to strip because, in my experience, the Verve/HyprMX implementation is often more fragile regarding the OM SDK location, whereas Smaato continues to function correctly if it falls back to the bundle provided by the other network, provided the versions are compatible.
  3. PBXResourcesBuildPhase: We find the build phase responsible for copying assets.
  4. remove_file_reference: This is the key method from the Xcodeproj ruby gem. It cleanly detaches the file from the target without deleting the physical file from the disk, ensuring that checksums don't fail, but the copy command is removed from Xcode.

Edge Cases and Pitfalls

1. PrivacyInfo.xcprivacy Conflicts

Recently, Apple mandated privacy manifests. Similar to the OM SDK, you might get a "Multiple commands produce" error for PrivacyInfo.xcprivacy.

You can adapt the script above by changing the string check:

if file_ref.path.include? 'PrivacyInfo.xcprivacy'
    # Logic to decide which one to keep
end

Note: Be careful removing privacy manifests. You must ensure the merged application still accurately reports privacy usage. Usually, CocoaPods attempts to merge these, but if it fails, manual intervention via this script logic is required.

2. Existing post_install Hooks

If you use other plugins that heavily modify the Podfile (like certain notifications plugins), simply appending post_install at the end might create a Podfile with two post_install blocks, which is invalid Ruby syntax.

If your Podfile is complex, you should modify the C# script to Regex replace the existing post_install block or insert your logic inside the existing block rather than appending a new one.

3. "Library not found"

If you remove the bundle and the build fails with a linker error (symbol not found) rather than a resource error, it means the adapter was linking against binary code inside that bundle. This is rare for OMIDSDK.bundle (which is usually assets/JS), but if it happens, you must keep the file reference and instead rename the input file in one of the pods—a much more complex operation.

Conclusion

The "Multiple commands produce" error is a rite of passage for mobile developers dealing with mediation. While it looks intimidating, it is simply Xcode protecting you from indeterminate behavior (which file should be used?).

By using the iOSPodfilePatcher script provided above, you move the fix from a manual, error-prone step into your automated build pipeline. This ensures that every build—whether on your local machine or a CI/CD server—compiles flawlessly, allowing you to focus on revenue optimization rather than build errors.