diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index 2d43fa71df3..c454bfeb8f2 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -55,7 +55,7 @@ dependencies: sse: ^4.1.2 stack_trace: ^1.12.0 string_scanner: ^1.4.0 - unified_analytics: ^7.0.0 + unified_analytics: ^8.4.0 vm_service: ^15.3.0 vm_service_protos: ^2.0.0 vm_snapshot_analysis: ^0.7.6 diff --git a/packages/devtools_shared/CHANGELOG.md b/packages/devtools_shared/CHANGELOG.md index 40ab21429aa..61d8d705d40 100644 --- a/packages/devtools_shared/CHANGELOG.md +++ b/packages/devtools_shared/CHANGELOG.md @@ -11,6 +11,14 @@ found in the LICENSE file or at https://developers.google.com/open-source/licens `DeeplinkManager`. * Add `UniversalLinkSettings.fromErrorJson`, `UniversalLinkSettings.error`, and `UniversalLinkSettings.error` getter to surface iOS universal link settings errors. +* `DeeplinkManager` now builds the environment for the `flutter` processes it + starts with `getEnvironment()` from `package:unified_analytics`, so + `DASH__TOOL` passes on this process's root tool. A known `ide` still overrides + it, and an unknown `ide` now falls back to the inherited root tool instead of + always using `devtools`. When `suppressAnalytics` is false, an inherited + `DASH__SUPPRESS_ANALYTICS=true` is now passed on instead of being reset to + `false`. +* Require `package:unified_analytics` `^8.4.0`. # 14.0.0 diff --git a/packages/devtools_shared/lib/src/deeplink/deeplink_manager.dart b/packages/devtools_shared/lib/src/deeplink/deeplink_manager.dart index 3026bcc0ef7..a376a2b8abb 100644 --- a/packages/devtools_shared/lib/src/deeplink/deeplink_manager.dart +++ b/packages/devtools_shared/lib/src/deeplink/deeplink_manager.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; +import 'package:unified_analytics/unified_analytics.dart'; import 'xcode_build_options.dart'; @@ -53,26 +54,22 @@ class DeeplinkManager { _iosBuildOptionsCache.clear(); } - // TODO(https://github.com/flutter/devtools/issues/9702): Use the `DashTool` - // and `DashEnvVar` enums and `getEnvironment()` helper directly from - // `package:unified_analytics` once the pinned Flutter candidate SDK in this - // repository is bumped to a stable Dart SDK version >= 3.10.0 (resolving the - // dev SDK version solving conflict on CI). - /// Mappings from case-insensitive IDE query parameter values to their - /// corresponding DashTool canonical label strings used by `package:unified_analytics`. + /// Mappings from case-insensitive IDE query parameter values to the + /// [DashTool] that should be reported as the root tool of the `flutter` + /// processes DevTools starts for that IDE. /// /// Contains multiple spelling and format variations (with/without hyphens /// or suffixes) passed by different IDE integrations to ensure O(1) lookup. - static const _ideToDashToolMap = { - 'vs-code': 'vscode-plugins', - 'vscode': 'vscode-plugins', - 'vscodeplugins': 'vscode-plugins', - 'intellij-idea': 'intellij-plugins', - 'intellij': 'intellij-plugins', - 'intellijplugins': 'intellij-plugins', - 'android-studio': 'android-studio-plugins', - 'androidstudio': 'android-studio-plugins', - 'androidstudioplugins': 'android-studio-plugins', + static const _ideToDashTool = { + 'vs-code': DashTool.vscodePlugins, + 'vscode': DashTool.vscodePlugins, + 'vscodeplugins': DashTool.vscodePlugins, + 'intellij-idea': DashTool.intellijPlugins, + 'intellij': DashTool.intellijPlugins, + 'intellijplugins': DashTool.intellijPlugins, + 'android-studio': DashTool.androidStudioPlugins, + 'androidstudio': DashTool.androidStudioPlugins, + 'androidstudioplugins': DashTool.androidStudioPlugins, }; /// A regex to retrieve the file path from the stdout of iOS or Android @@ -91,27 +88,45 @@ class DeeplinkManager { required String? ide, required bool suppressAnalytics, }) { - final environment = { - ...Platform.environment, - 'DASH__SUPPRESS_ANALYTICS': suppressAnalytics.toString(), - 'DASH__TOOL': ide != null ? _mapIdeToDashToolLabel(ide) : 'devtools', - }; - return Process.run( executable, arguments, workingDirectory: workingDirectory, - environment: environment, + environment: buildEnvironment( + ide: ide, + suppressAnalytics: suppressAnalytics, + ), ); } - String _mapIdeToDashToolLabel(String ide) { - final lowerIde = ide.toLowerCase(); - final mappedTool = _ideToDashToolMap[lowerIde]; - if (mappedTool != null) { - return mappedTool; - } - return 'devtools'; + /// Returns the environment for a `flutter` process that DevTools starts. + /// + /// Uses `getEnvironment()` from `package:unified_analytics`, which sets + /// `DASH__SUPPRESS_ANALYTICS` and passes on the root tool from this + /// process's `DASH__TOOL` (falling back to `devtools`). If [ide] names a + /// known IDE, its [DashTool] overrides `DASH__TOOL`, because the IDE that + /// opened DevTools is the user's entry point for this request. + /// + /// If [suppressAnalytics] is true, analytics are always suppressed. + /// `getEnvironment()` alone would let an inherited + /// `DASH__SUPPRESS_ANALYTICS=false` (which `dart` sets for the processes it + /// starts) win over it. + // TODO(https://github.com/dart-lang/tools/issues/2803): Pass the IDE as a + // `fallbackRootTool` to `getEnvironment()` once it supports one. + @visibleForTesting + Map buildEnvironment({ + required String? ide, + required bool suppressAnalytics, + }) { + final ideTool = ide == null ? null : _ideToDashTool[ide.toLowerCase()]; + return { + ...getEnvironment( + currentTool: DashTool.devtools, + suppressAnalytics: suppressAnalytics, + ), + if (ideTool != null) DashEnvVar.tool.name: ideTool.label, + if (suppressAnalytics) DashEnvVar.suppressAnalytics.name: 'true', + }; } @visibleForTesting diff --git a/packages/devtools_shared/pubspec.yaml b/packages/devtools_shared/pubspec.yaml index 4507c5e3cfc..c4ff623fdb4 100644 --- a/packages/devtools_shared/pubspec.yaml +++ b/packages/devtools_shared/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: web_socket_channel: '>=2.4.0 <4.0.0' webkit_inspection_protocol: ">=0.5.0 <2.0.0" yaml: ^3.1.2 + unified_analytics: ^8.4.0 yaml_edit: ^2.1.1 dev_dependencies: diff --git a/packages/devtools_shared/test/deeplink/deeplink_manager_test.dart b/packages/devtools_shared/test/deeplink/deeplink_manager_test.dart index b1766792414..7d22907a91b 100644 --- a/packages/devtools_shared/test/deeplink/deeplink_manager_test.dart +++ b/packages/devtools_shared/test/deeplink/deeplink_manager_test.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:devtools_shared/src/deeplink/deeplink_manager.dart'; import 'package:test/test.dart'; +import 'package:unified_analytics/unified_analytics.dart'; void main() { group('DeeplinkManager', () { @@ -435,6 +436,94 @@ Running Gradle task 'printBuildVariants'... 10.4s }, ); }); + + group('DeeplinkManager.buildEnvironment', () { + final manager = DeeplinkManager(); + // What `getEnvironment()` passes on when DevTools starts a sub-tool and no + // IDE override applies: this process's root tool, or `devtools`. + final inheritedRootTool = getEnvironment( + currentTool: DashTool.devtools, + )[DashEnvVar.tool.name]; + + test('sets DASH__TOOL to the IDE that opened DevTools', () { + for (final MapEntry(key: ide, value: tool) in { + 'VS-Code': DashTool.vscodePlugins, + 'vscode': DashTool.vscodePlugins, + 'IntelliJ-IDEA': DashTool.intellijPlugins, + 'intellijplugins': DashTool.intellijPlugins, + 'Android-Studio': DashTool.androidStudioPlugins, + 'androidstudio': DashTool.androidStudioPlugins, + }.entries) { + final environment = manager.buildEnvironment( + ide: ide, + suppressAnalytics: false, + ); + expect(environment[DashEnvVar.tool.name], tool.label, reason: ide); + } + }); + + test('uses the inherited root tool when there is no known IDE', () { + expect(inheritedRootTool, isNotNull); + for (final ide in [null, 'unknown-ide']) { + final environment = manager.buildEnvironment( + ide: ide, + suppressAnalytics: false, + ); + expect(environment[DashEnvVar.tool.name], inheritedRootTool); + } + }); + + test('never passes on a shared component or an invalid label', () { + final environment = manager.buildEnvironment( + ide: 'VS-Code', + suppressAnalytics: false, + ); + expect( + () => DashTool.fromLabel(environment[DashEnvVar.tool.name]!), + returnsNormally, + ); + expect( + DashTool.fromLabel(environment[DashEnvVar.tool.name]!).canBeRootTool, + isTrue, + ); + }); + + test('suppresses analytics when requested, even if inherited is false', () { + // `dart test` runs under `dart`, which sets + // DASH__SUPPRESS_ANALYTICS=false for its child processes. + expect( + manager.buildEnvironment( + ide: null, + suppressAnalytics: true, + )[DashEnvVar.suppressAnalytics.name], + 'true', + ); + }); + + test('otherwise passes on the inherited suppression value', () { + expect( + manager.buildEnvironment( + ide: null, + suppressAnalytics: false, + )[DashEnvVar.suppressAnalytics.name], + areAnalyticsSuppressed().toString(), + ); + }); + + test('keeps the rest of the process environment', () { + final environment = manager.buildEnvironment( + ide: null, + suppressAnalytics: false, + ); + for (final MapEntry(:key, :value) in Platform.environment.entries) { + if (key == DashEnvVar.tool.name || + key == DashEnvVar.suppressAnalytics.name) { + continue; + } + expect(environment[key], value, reason: key); + } + }); + }); } class StubbedDeeplinkManager extends DeeplinkManager { diff --git a/pubspec.lock b/pubspec.lock index b1b1b8f86a0..59f29e3493c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -899,26 +899,26 @@ packages: dependency: transitive description: name: test - sha256: de5d145b0afff7921e5e788a880f52d7e5f3ae24068a202f6fd3b58e4ba26323 + sha256: "44b2fccf4407bcdb5e105c064c5d6b5e2a640c87761f8b03cdce8cf532d98cfa" url: "https://pub.dev" source: hosted - version: "1.32.0" + version: "1.33.0" test_api: dependency: transitive description: name: test_api - sha256: "0a10344e901e5b2e63819567951cb6a06673ed6b84f40462188ff5a0c41f371f" + sha256: "47481c58bb92d0b1d218f0510ff5647e768f8e1256d07077a151fb520923b51d" url: "https://pub.dev" source: hosted - version: "0.7.14" + version: "0.7.15" test_core: dependency: transitive description: name: test_core - sha256: "80f3fb49087454e07e7e07c67578cfdd156c8c3a5227d8b3f47c7b2d019c2e93" + sha256: "9dec4c178bd61493e1cd2060ffe701fcb61c43da2493e99590fb1b0cc82eb4af" url: "https://pub.dev" source: hosted - version: "0.6.20" + version: "0.6.21" typed_data: dependency: transitive description: @@ -931,10 +931,10 @@ packages: dependency: transitive description: name: unified_analytics - sha256: d581cd7007f5a60594195ee5d35337edea7d53757e4cff5a2f5168b82ec31d05 + sha256: "4013958a461aafcd9b80baf96b85e1537ae10991a05e7b05f34e4bee6202dfb0" url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "8.4.0" url_launcher: dependency: transitive description: @@ -1003,10 +1003,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d" + sha256: fa49e1674cb84acb900141714e6c6e1f9ae099827c24d141430ff6819db4e1bc url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" vm_service: dependency: transitive description: