625bfe5cae
Change-Id: I1d06861b5838a12b807d47cfbb53b0537a68d52f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194205 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
224 lines
7.3 KiB
Dart
224 lines
7.3 KiB
Dart
// Copyright (c) 2017, 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:meta/meta.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:usage/src/usage_impl.dart';
|
|
import 'package:usage/src/usage_impl_io.dart';
|
|
import 'package:usage/src/usage_impl_io.dart' as usage_io show getDartVersion;
|
|
import 'package:usage/usage.dart';
|
|
import 'package:usage/usage_io.dart';
|
|
|
|
export 'package:usage/usage.dart' show Analytics;
|
|
|
|
// TODO(devoncarew): Don't show the UI until we're ready to ship.
|
|
final bool SHOW_ANALYTICS_UI = false;
|
|
|
|
final String _dartDirectoryName = '.dart';
|
|
final String _settingsFileName = 'analytics.json';
|
|
|
|
/// Dart SDK tools with analytics should display this notice.
|
|
///
|
|
/// In addition, they should support displaying the analytics' status, and have
|
|
/// a flag to toggle analytics. This may look something like:
|
|
///
|
|
/// `Analytics are currently enabled (and can be disabled with --no-analytics).`
|
|
final String analyticsNotice =
|
|
"Dart SDK tools anonymously report feature usage statistics and basic crash\n"
|
|
"reports to help improve Dart tools over time. See Google's privacy policy:\n"
|
|
"https://www.google.com/intl/en/policies/privacy/.";
|
|
|
|
/// Return a customized message for command-line tools to display about the
|
|
/// state of analytics, and how users can enabled or disable analytics.
|
|
///
|
|
/// An example return value might be `'Analytics are currently enabled (and can
|
|
/// be disabled with --no-analytics).'`
|
|
String createAnalyticsStatusMessage(
|
|
bool enabled, {
|
|
String command: 'analytics',
|
|
}) {
|
|
String currentState = enabled ? 'enabled' : 'disabled';
|
|
String toggleState = enabled ? 'disabled' : 'enabled';
|
|
String commandToggle = enabled ? 'no-$command' : command;
|
|
|
|
return 'Analytics are currently $currentState '
|
|
'(and can be $toggleState with --$commandToggle).';
|
|
}
|
|
|
|
/// Create an [Analytics] instance with the given trackingID and
|
|
/// applicationName.
|
|
///
|
|
/// This analytics instance will share a common enablement state with the rest
|
|
/// of the Dart SDK tools.
|
|
Analytics createAnalyticsInstance(
|
|
String trackingId,
|
|
String applicationName, {
|
|
bool disableForSession = false,
|
|
bool forceEnabled = false,
|
|
}) {
|
|
final dir = getDartStorageDirectory();
|
|
if (dir == null) {
|
|
// Some systems don't support user home directories; for those, fail
|
|
// gracefully by returning a disabled analytics object.
|
|
return new _DisabledAnalytics(trackingId, applicationName);
|
|
}
|
|
|
|
if (!dir.existsSync()) {
|
|
try {
|
|
dir.createSync();
|
|
} catch (e) {
|
|
// If we can't create the directory for the analytics settings, fail
|
|
// gracefully by returning a disabled analytics object.
|
|
return new _DisabledAnalytics(trackingId, applicationName);
|
|
}
|
|
}
|
|
|
|
File settingsFile = new File(path.join(dir.path, _settingsFileName));
|
|
return new _TelemetryAnalytics(
|
|
trackingId,
|
|
applicationName,
|
|
getDartVersion(),
|
|
settingsFile,
|
|
disableForSession: disableForSession,
|
|
forceEnabled: forceEnabled,
|
|
);
|
|
}
|
|
|
|
/// The directory used to store the analytics settings file.
|
|
///
|
|
/// Typically, the directory is `~/.dart/` (and the settings file is
|
|
/// `analytics.json`).
|
|
///
|
|
/// This can return null under some conditions, including when the user's home
|
|
/// directory does not exist.
|
|
@visibleForTesting
|
|
Directory? getDartStorageDirectory() {
|
|
Directory homeDirectory = new Directory(userHomeDir());
|
|
if (!homeDirectory.existsSync()) return null;
|
|
|
|
return new Directory(path.join(homeDirectory.path, _dartDirectoryName));
|
|
}
|
|
|
|
/// Return the version of the Dart SDK.
|
|
String getDartVersion() => usage_io.getDartVersion();
|
|
|
|
class _TelemetryAnalytics extends AnalyticsImpl {
|
|
final bool disableForSession;
|
|
final bool forceEnabled;
|
|
|
|
_TelemetryAnalytics(
|
|
String trackingId,
|
|
String applicationName,
|
|
String applicationVersion,
|
|
File settingsFile, {
|
|
required this.disableForSession,
|
|
required this.forceEnabled,
|
|
}) : super(
|
|
trackingId,
|
|
new IOPersistentProperties.fromFile(settingsFile),
|
|
new IOPostHandler(),
|
|
applicationName: applicationName,
|
|
applicationVersion: applicationVersion,
|
|
) {
|
|
final locale = getPlatformLocale();
|
|
if (locale != null) {
|
|
setSessionValue('ul', locale);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool get enabled {
|
|
if (disableForSession || isRunningOnBot()) {
|
|
return false;
|
|
}
|
|
|
|
// This is only used in special cases.
|
|
if (forceEnabled) {
|
|
return true;
|
|
}
|
|
|
|
// If there's no explicit setting (enabled or disabled) then we don't send.
|
|
return (properties['enabled'] as bool?) ?? false;
|
|
}
|
|
}
|
|
|
|
class _DisabledAnalytics extends AnalyticsMock {
|
|
@override
|
|
final String trackingId;
|
|
@override
|
|
final String applicationName;
|
|
|
|
_DisabledAnalytics(this.trackingId, this.applicationName);
|
|
|
|
@override
|
|
bool get enabled => false;
|
|
}
|
|
|
|
/// Detect whether we're running on a bot or in 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;
|
|
}
|
|
|
|
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');
|
|
|
|
// 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.
|
|
}
|