| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as path; |
| import 'package:unified_analytics/unified_analytics.dart'; |
| |
| import 'sdk.dart'; |
| |
| const String _dartDirectoryName = '.dart'; |
| |
| const String analyticsDisabledNoticeMessage = 'Analytics reporting disabled. ' |
| 'In order to enable it, run: dart --enable-analytics'; |
| |
| /// Create the `Analytics` instance to be used to report analytics. |
| Analytics createUnifiedAnalytics({bool disableAnalytics = false}) { |
| if (disableAnalytics) { |
| return NoOpAnalytics(); |
| } |
| return Analytics( |
| tool: DashTool.dartTool, |
| dartVersion: Runtime.runtime.version, |
| ); |
| } |
| |
| String userHomeDir() { |
| var envKey = Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME'; |
| var value = Platform.environment[envKey]; |
| return value ?? '.'; |
| } |
| |
| /// Return the user's home directory for the current platform. |
| Directory? get homeDir { |
| var dir = Directory(userHomeDir()); |
| return dir.existsSync() ? dir : null; |
| } |
| |
| /// The directory used to store the analytics settings file. |
| /// |
| /// Typically, the directory is `~/.dart/` (and the settings file is |
| /// `dartdev.json`). |
| /// |
| /// This can return null under some conditions, including when the user's home |
| /// directory does not exist. |
| Directory? getDartStorageDirectory() { |
| var dir = homeDir; |
| if (dir == null) { |
| return null; |
| } else { |
| return Directory(path.join(dir.path, _dartDirectoryName)); |
| } |
| } |
| |
| /// The method used by dartdev to determine if this machine is a bot such as a |
| /// CI machine. |
| bool isBot() => _isRunningOnBot(); |
| |
| // Matches file:/, non-ws, /, non-ws, .dart |
| final RegExp _pathRegex = RegExp(r'file:/\S+/(\S+\.dart)'); |
| |
| // Match multiple tabs or spaces. |
| final RegExp _tabOrSpaceRegex = RegExp(r'[\t ]+'); |
| |
| /// Sanitize a stacktrace. This will shorten file paths in order to remove any |
| /// PII that may be contained in the full file path. For example, this will |
| /// shorten `file:///Users/foobar/tmp/error.dart` to `error.dart`. |
| /// |
| /// If [shorten] is `true`, this method will also attempt to compress the text |
| /// of the stacktrace. GA has a 100 char limit on the text that can be sent for |
| /// an exception. This will try and make those first 100 chars contain |
| /// information useful to debugging the issue. |
| String sanitizeStacktrace(dynamic st, {bool shorten = true}) { |
| var str = '$st'; |
| |
| Iterable<Match> iter = _pathRegex.allMatches(str); |
| iter = iter.toList().reversed; |
| |
| for (var match in iter) { |
| var replacement = match.group(1)!; |
| str = |
| str.substring(0, match.start) + replacement + str.substring(match.end); |
| } |
| |
| if (shorten) { |
| // Shorten the stacktrace up a bit. |
| str = str.replaceAll(_tabOrSpaceRegex, ' '); |
| } |
| |
| return str; |
| } |
| |
| /// Detect whether we're running on a bot / a continuous testing environment. |
| /// |
| /// We should periodically keep this code up to date with: |
| /// https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/base/bot_detector.dart#L30 |
| /// and |
| /// https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/reporting/usage.dart#L200. |
| bool _isRunningOnBot() { |
| final Map<String, String> env = Platform.environment; |
| |
| if ( |
| // Explicitly stated to not be a bot. |
| env['BOT'] == 'false' |
| // Set by the IDEs to the IDE name, so a strong signal that this is |
| // not a bot. |
| || |
| env.containsKey('FLUTTER_HOST') |
| // When set, GA logs to a local file (normally for tests) so we don't |
| // need to filter. |
| || |
| env.containsKey('FLUTTER_ANALYTICS_LOG_FILE')) { |
| return false; |
| } |
| |
| // TODO(jwren): Azure detection -- each call for this detection requires an |
| // http connection, the flutter cli tool captures the result on the first run, |
| // we should consider the same caching here. |
| |
| return env.containsKey('BOT') |
| // https://docs.travis-ci.com/user/environment-variables/ |
| // Example .travis.yml file: |
| // https://github.com/flutter/devtools/blob/master/.travis.yml |
| || |
| env['TRAVIS'] == 'true' || |
| env['CONTINUOUS_INTEGRATION'] == 'true' || |
| env.containsKey('CI') // Travis and AppVeyor |
| |
| // https://www.appveyor.com/docs/environment-variables/ |
| || |
| env.containsKey('APPVEYOR') |
| |
| // https://cirrus-ci.org/guide/writing-tasks/#environment-variables |
| || |
| env.containsKey('CIRRUS_CI') |
| |
| // https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html |
| || |
| (env.containsKey('AWS_REGION') && env.containsKey('CODEBUILD_INITIATOR')) |
| |
| // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables |
| || |
| env.containsKey('JENKINS_URL') |
| |
| // https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables |
| || |
| env.containsKey('GITHUB_ACTIONS') |
| |
| // Properties on Flutter's Chrome Infra bots. |
| || |
| env['CHROME_HEADLESS'] == '1' || |
| env.containsKey('BUILDBOT_BUILDERNAME') || |
| env.containsKey('SWARMING_TASK_ID') |
| |
| // Property when running on borg. |
| || |
| env.containsKey('BORG_ALLOC_DIR'); |
| } |