From 26e7ed44232802e97643096313618aaff04cc95c Mon Sep 17 00:00:00 2001 From: Alex Forsythe Date: Wed, 9 Jul 2025 13:31:25 -0400 Subject: [PATCH] fix: Downgrade androidx.metrics dep to beta01 in Gradle build refs: RUM-10762 --- .../Editor/DatadogGradlePostProcessor.cs | 134 +++++++++++ .../Editor/DatadogGradlePostProcessor.cs.meta | 3 + .../Editor/DatadogGradlePostProcessorTests.cs | 215 ++++++++++++++++++ .../DatadogGradlePostProcessorTests.cs.meta | 3 + 4 files changed, 355 insertions(+) create mode 100644 packages/Datadog.Unity/Editor/DatadogGradlePostProcessor.cs create mode 100644 packages/Datadog.Unity/Editor/DatadogGradlePostProcessor.cs.meta create mode 100644 packages/Datadog.Unity/Tests/Editor/DatadogGradlePostProcessorTests.cs create mode 100644 packages/Datadog.Unity/Tests/Editor/DatadogGradlePostProcessorTests.cs.meta diff --git a/packages/Datadog.Unity/Editor/DatadogGradlePostProcessor.cs b/packages/Datadog.Unity/Editor/DatadogGradlePostProcessor.cs new file mode 100644 index 00000000..cdeb390b --- /dev/null +++ b/packages/Datadog.Unity/Editor/DatadogGradlePostProcessor.cs @@ -0,0 +1,134 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025-Present Datadog, Inc. + +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor.Android; +using UnityEngine; + +namespace Datadog.Unity.Editor +{ + /// + /// Modifies a Unity project's build.gradle file to ensure compatibility with certain transitive dependencies + /// included by dd-sdk-android. + /// + public class DatadogGradlePostProcessor : IPostGenerateGradleAndroidProject + { + // These comments mark the start and end of the section in the `dependencies` block where EDM4U writes gradle + // dependencies + private const string EdmHeaderText = "// Android Resolver Dependencies Start"; + private const string EdmFooterText = "// Android Resolver Dependencies End"; + + public int callbackOrder => 999; // Run after EDM4U + + public void OnPostGenerateGradleAndroidProject(string path) + { + // Early-out if we're building in an environment that requires no fixes + if (!RequiresAndroidxMetricsCompatibilityFix()) + { + return; + } + + // Modify the generated build.gradle file; abort silently if it doesn't exist + string gradlePath = Path.Combine(path, "build.gradle"); + if (!File.Exists(gradlePath)) + { + return; + } + + // Read the existing contents of build.gradle, normalizing line endings + string[] lines = File.ReadAllLines(gradlePath); + + // Ensure that our dependency on dd-sdk-android-rum is declared in such a way that all transitive + // dependencies are resolved to versions that are compatible with this version of Unity + lines = ApplyAndroidxMetricsCompatibilityFix(lines); + + // Write our modifications to the file + File.WriteAllLines(gradlePath, lines); + } + + /// + /// Evaluates whether we need to apply a compatibility fix to build.gradle in order to prevent build errors due + /// to the inclusion of `androidx.metrics:metrics-performance:1.0.0-beta02`, which requires AGP 8.6.0+. Unity + /// 2022 and 2021 use AGP 7.x, so they require the fix. + /// + /// True if the detected Unity version predates Unity 6. + private bool RequiresAndroidxMetricsCompatibilityFix() + { + string version = Application.unityVersion; + string[] parts = version.Split('.'); + if (int.TryParse(parts[0], out int majorVersion)) + { + return majorVersion < 6000; + } + return false; + } + + /// + /// Modifies the contents of a build.gradle file to apply the compatibility fix for + /// androidx.metrics:metrics-performance, downgrading it from 1.0.0-beta02 to 1.0.0-beta01. + /// + /// The complete set of lines parsed from a build.gradle file. + /// The same set of lines with androidx.metrics:metrics-performance downgraded to beta01. + public static string[] ApplyAndroidxMetricsCompatibilityFix(string[] lines) + { + // Find the start and end of the EDM4U dependencies, and abort silently if there's no such section + int edmHeaderIndex = Array.IndexOf(lines, EdmHeaderText); + int edmFooterIndex = Array.IndexOf(lines, EdmFooterText, edmHeaderIndex + 1); + if (edmHeaderIndex == -1 || edmFooterIndex == -1) + { + return lines; + } + + // Find the first `implementation` directive that declares dd-sdk-android-rum as a dependency, using a regex + // that will capture the relevant details of that declaration, and ensuring that it's located within the + // EDM4U-managed section of the file + var regex = new Regex(@"^(\s+)implementation([ \(])(['""]com\.datadoghq:dd-sdk-android-rum:.*['""])(\)\s*{)?(?:\s*(\/\/.*))?"); + var found = lines + .Select((line, index) => (Match: regex.Match(line), Index: index)) + .FirstOrDefault(t => t.Match.Success); + if (found.Match == null || !found.Match.Success || found.Index <= edmHeaderIndex || + found.Index >= edmFooterIndex) + { + return lines; + } + + // Parse the dependency declaration so we can examine whether it's a single-line statement as written by + // EDM4U, e.g.: + // implementation 'com.datadoghq:dd-sdk-android-rum:2.20.0' // DatadogDependencies.xml:12 + // ...or else a multi-line declaration that we've already modified, e.g.: + // implementation('com.datadoghq:dd-sdk-android-rum:2.20.0') { // DatadogDependencies.xml:12 + string indentStr = found.Match.Groups[1].Value; // Whitespace chars for a single-level indent + string openStr = found.Match.Groups[2].Value; // Either space or '(' + string packageSpecLiteral = found.Match.Groups[3].Value; // Full package specifier, as quoted string literal + string closeStr = found.Match.Groups[4].Value; // Either nothing or ') {' + string comment = found.Match.Groups[5].Value; // Any comment appearing at the end of the line, incl. '//' + + // If the declaration already has a body, we'll assume that the necessary edit has already been made by a + // previous invocation of our callback + if (openStr.Contains("(") || closeStr.Contains(") {")) + { + return lines; + } + + // Otherwise, the line at found.Index is a single-line 'implementation' declaration: replace it with an + // expanded dependency specifier that explicitly excludes the problematic version of + // `androidx.metrics:metrics-performance`, then follow it with another declaration that pulls in the version + // of that library that's compatible with AGP 7.x + string[] newDeclarationLines = + { + indentStr + "implementation(" + packageSpecLiteral + ") {" + (comment.Length > 0 ? $" {comment}" : string.Empty), + indentStr + indentStr + "// DatadogGradlePostProcessor: exclude the dependency on androidx.metrics:metrics-performance:1.0.0-beta02", + indentStr + indentStr + "// Version beta02 requires Android Gradle plugin 8.6.0+, which is not supported on Unity 2022 and older", + indentStr + indentStr + "exclude group: 'androidx.metrics', module: 'metrics-performance'", + indentStr + "}", + indentStr + "// DatadogGradlePostProcessor: Explicitly require version beta01 of the same dependency, as it works with AGP 7", + indentStr + "implementation 'androidx.metrics:metrics-performance:1.0.0-beta01'", + }; + return lines.Take(found.Index).Concat(newDeclarationLines).Concat(lines.Skip(found.Index + 1)).ToArray(); + } + } +} diff --git a/packages/Datadog.Unity/Editor/DatadogGradlePostProcessor.cs.meta b/packages/Datadog.Unity/Editor/DatadogGradlePostProcessor.cs.meta new file mode 100644 index 00000000..af50da3c --- /dev/null +++ b/packages/Datadog.Unity/Editor/DatadogGradlePostProcessor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e4e876d8df234c578b2d7a759f325383 +timeCreated: 1752067527 \ No newline at end of file diff --git a/packages/Datadog.Unity/Tests/Editor/DatadogGradlePostProcessorTests.cs b/packages/Datadog.Unity/Tests/Editor/DatadogGradlePostProcessorTests.cs new file mode 100644 index 00000000..568d39b4 --- /dev/null +++ b/packages/Datadog.Unity/Tests/Editor/DatadogGradlePostProcessorTests.cs @@ -0,0 +1,215 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025-Present Datadog, Inc. + +using NUnit.Framework; + +namespace Datadog.Unity.Editor.Tests +{ + public class DatadogGradlePostProcessorTests + { + [Test] + public void ModifiesAndroidxMetricsDependencyIfRumDependencyIsDeclared() + { + string[] lines = GradleFileAsWrittenByEdm.Split("\n"); + string[] gotLines = DatadogGradlePostProcessor.ApplyAndroidxMetricsCompatibilityFix(lines); + string got = string.Join("\n", gotLines); + Assert.AreEqual(GradleFileAsModifiedByDatadogGradlePostProcessor, got); + } + + [Test] + public void HasNoEffectWhenRunAgain() + { + string[] lines = GradleFileAsModifiedByDatadogGradlePostProcessor.Split("\n"); + string[] gotLines = DatadogGradlePostProcessor.ApplyAndroidxMetricsCompatibilityFix(lines); + Assert.AreEqual(lines, gotLines); + } + + [Test] + public void HasNoEffectIfEdmSectionDoesNotExist() + { + string[] lines = GradleFileWithoutEdmDependencies.Split("\n"); + string[] gotLines = DatadogGradlePostProcessor.ApplyAndroidxMetricsCompatibilityFix(lines); + Assert.AreEqual(lines, gotLines); + } + + private const string GradleFileAsWrittenByEdm = @"apply plugin: 'com.android.library' + + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + implementation 'com.datadoghq:dd-sdk-android-logs:2+' // Packages/com.datadoghq.unity/Editor/DatadogDependencies.xml:10 + implementation 'com.datadoghq:dd-sdk-android-ndk:2+' // Packages/com.datadoghq.unity/Editor/DatadogDependencies.xml:12 + implementation 'com.datadoghq:dd-sdk-android-rum:2+' // Packages/com.datadoghq.unity/Editor/DatadogDependencies.xml:14 + implementation 'com.example:some-other-dependency:4.13.0' // Packages/com.datadoghq.unity/Editor/DatadogDependencies.xml:16 +// Android Resolver Dependencies End + + constraints { + implementation(""org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0"") { + because(""kotlin-stdlib-jdk8 is now a part of kotlin-stdlib"") + } + } + +} + +// Android Resolver Exclusions Start +android { + namespace ""com.unity3d.player"" + packagingOptions { + exclude ('/lib/armeabi/*' + '*') + exclude ('/lib/mips/*' + '*') + exclude ('/lib/mips64/*' + '*') + exclude ('/lib/x86/*' + '*') + exclude ('/lib/x86_64/*' + '*') + } +} +// Android Resolver Exclusions End +android { + ndkPath ""/Applications/Unity/Hub/Editor/2022.3.55f1/PlaybackEngines/AndroidPlayer/NDK"" + + compileSdkVersion 35 + buildToolsVersion '34.0.0' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 35 + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a' + } + versionCode 1 + versionName '1.0' + consumerProguardFiles 'proguard-unity.txt' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress = ['.unity3d', '.ress', '.resource', '.obb', '.bundle', '.unityexp'] + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = ""!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"" + } + + packagingOptions { + doNotStrip '*/armeabi-v7a/*.so' + doNotStrip '*/arm64-v8a/*.so' + jniLibs { + useLegacyPackaging true + } + } +} +"; + + private const string GradleFileAsModifiedByDatadogGradlePostProcessor = @"apply plugin: 'com.android.library' + + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + implementation 'com.datadoghq:dd-sdk-android-logs:2+' // Packages/com.datadoghq.unity/Editor/DatadogDependencies.xml:10 + implementation 'com.datadoghq:dd-sdk-android-ndk:2+' // Packages/com.datadoghq.unity/Editor/DatadogDependencies.xml:12 + implementation('com.datadoghq:dd-sdk-android-rum:2+') { // Packages/com.datadoghq.unity/Editor/DatadogDependencies.xml:14 + // DatadogGradlePostProcessor: exclude the dependency on androidx.metrics:metrics-performance:1.0.0-beta02 + // Version beta02 requires Android Gradle plugin 8.6.0+, which is not supported on Unity 2022 and older + exclude group: 'androidx.metrics', module: 'metrics-performance' + } + // DatadogGradlePostProcessor: Explicitly require version beta01 of the same dependency, as it works with AGP 7 + implementation 'androidx.metrics:metrics-performance:1.0.0-beta01' + implementation 'com.example:some-other-dependency:4.13.0' // Packages/com.datadoghq.unity/Editor/DatadogDependencies.xml:16 +// Android Resolver Dependencies End + + constraints { + implementation(""org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0"") { + because(""kotlin-stdlib-jdk8 is now a part of kotlin-stdlib"") + } + } + +} + +// Android Resolver Exclusions Start +android { + namespace ""com.unity3d.player"" + packagingOptions { + exclude ('/lib/armeabi/*' + '*') + exclude ('/lib/mips/*' + '*') + exclude ('/lib/mips64/*' + '*') + exclude ('/lib/x86/*' + '*') + exclude ('/lib/x86_64/*' + '*') + } +} +// Android Resolver Exclusions End +android { + ndkPath ""/Applications/Unity/Hub/Editor/2022.3.55f1/PlaybackEngines/AndroidPlayer/NDK"" + + compileSdkVersion 35 + buildToolsVersion '34.0.0' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 35 + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a' + } + versionCode 1 + versionName '1.0' + consumerProguardFiles 'proguard-unity.txt' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress = ['.unity3d', '.ress', '.resource', '.obb', '.bundle', '.unityexp'] + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = ""!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"" + } + + packagingOptions { + doNotStrip '*/armeabi-v7a/*.so' + doNotStrip '*/arm64-v8a/*.so' + jniLibs { + useLegacyPackaging true + } + } +} +"; + + private const string GradleFileWithoutEdmDependencies = @"apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.example:some-other-dependency:4.13.0' + + constraints { + implementation(""org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0"") { + because(""kotlin-stdlib-jdk8 is now a part of kotlin-stdlib"") + } + } + +} + +android { + ndkPath ""/Applications/Unity/Hub/Editor/2022.3.55f1/PlaybackEngines/AndroidPlayer/NDK"" + + compileSdkVersion 35 + buildToolsVersion '34.0.0' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } +} +"; + } +} diff --git a/packages/Datadog.Unity/Tests/Editor/DatadogGradlePostProcessorTests.cs.meta b/packages/Datadog.Unity/Tests/Editor/DatadogGradlePostProcessorTests.cs.meta new file mode 100644 index 00000000..0653eadd --- /dev/null +++ b/packages/Datadog.Unity/Tests/Editor/DatadogGradlePostProcessorTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bf88d4e8310144da93027f0cd44e319f +timeCreated: 1752080426 \ No newline at end of file