Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/flutter_tools/lib/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import 'package:intl/intl.dart' as intl;
import 'package:intl/intl_standalone.dart' as intl_standalone;
import 'package:meta/meta.dart';

import 'src/base/bot_detector.dart';
import 'src/base/common.dart';
import 'src/base/context.dart';
import 'src/base/file_system.dart';
Expand All @@ -35,8 +34,8 @@ Future<int> run(
bool reportCrashes,
String flutterVersion,
Map<Type, Generator> overrides,
}) {
reportCrashes ??= !isRunningOnBot(globals.platform);
}) async {
reportCrashes ??= !await globals.isRunningOnBot;

if (muteCommandLogging) {
// Remove the verbose option; for help and doctor, users don't need to see
Expand Down
113 changes: 82 additions & 31 deletions packages/flutter_tools/lib/src/base/bot_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,107 @@
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';

import 'context.dart';
import 'io.dart';
import 'net.dart';

class BotDetector {
BotDetector({
@required HttpClientFactory httpClientFactory,
@required Platform platform,
}) : _platform = platform;
}) :
_platform = platform,
_azureDetector = AzureDetector(
httpClientFactory: httpClientFactory,
);

final Platform _platform;
final AzureDetector _azureDetector;

bool get isRunningOnBot {
bool _isRunningOnBot;

Future<bool> get isRunningOnBot async {
if (_isRunningOnBot != null) {
return _isRunningOnBot;
}
if (
// Explicitly stated to not be a bot.
_platform.environment['BOT'] == 'false'
// Explicitly stated to not be a bot.
_platform.environment['BOT'] == 'false'

// Set by the IDEs to the IDE name, so a strong signal that this is not a bot.
|| _platform.environment.containsKey('FLUTTER_HOST')
// When set, GA logs to a local file (normally for tests) so we don't need to filter.
|| _platform.environment.containsKey('FLUTTER_ANALYTICS_LOG_FILE')
// Set by the IDEs to the IDE name, so a strong signal that this is not a bot.
|| _platform.environment.containsKey('FLUTTER_HOST')
// When set, GA logs to a local file (normally for tests) so we don't need to filter.
|| _platform.environment.containsKey('FLUTTER_ANALYTICS_LOG_FILE')
) {
return false;
return _isRunningOnBot = false;
}

return _platform.environment['BOT'] == 'true'
return _isRunningOnBot = _platform.environment['BOT'] == 'true'

// https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
|| _platform.environment['TRAVIS'] == 'true'
|| _platform.environment['CONTINUOUS_INTEGRATION'] == 'true'
|| _platform.environment.containsKey('CI') // Travis and AppVeyor

// https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
|| _platform.environment['TRAVIS'] == 'true'
|| _platform.environment['CONTINUOUS_INTEGRATION'] == 'true'
|| _platform.environment.containsKey('CI') // Travis and AppVeyor
// https://www.appveyor.com/docs/environment-variables/
|| _platform.environment.containsKey('APPVEYOR')

// https://www.appveyor.com/docs/environment-variables/
|| _platform.environment.containsKey('APPVEYOR')
// https://cirrus-ci.org/guide/writing-tasks/#environment-variables
|| _platform.environment.containsKey('CIRRUS_CI')

// https://cirrus-ci.org/guide/writing-tasks/#environment-variables
|| _platform.environment.containsKey('CIRRUS_CI')
// https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
|| (_platform.environment.containsKey('AWS_REGION') &&
_platform.environment.containsKey('CODEBUILD_INITIATOR'))

// https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
|| (_platform.environment.containsKey('AWS_REGION') &&
_platform.environment.containsKey('CODEBUILD_INITIATOR'))
// https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables
|| _platform.environment.containsKey('JENKINS_URL')

// https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables
|| _platform.environment.containsKey('JENKINS_URL')
// https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
|| _platform.environment.containsKey('GITHUB_ACTIONS')

// Properties on Flutter's Chrome Infra bots.
|| _platform.environment['CHROME_HEADLESS'] == '1'
|| _platform.environment.containsKey('BUILDBOT_BUILDERNAME')
|| _platform.environment.containsKey('SWARMING_TASK_ID');
// Properties on Flutter's Chrome Infra bots.
|| _platform.environment['CHROME_HEADLESS'] == '1'
|| _platform.environment.containsKey('BUILDBOT_BUILDERNAME')
|| _platform.environment.containsKey('SWARMING_TASK_ID')
|| await _azureDetector.isRunningOnAzure;
}
}

bool isRunningOnBot(Platform platform) {
final BotDetector botDetector = context.get<BotDetector>() ?? BotDetector(platform: platform);
return botDetector.isRunningOnBot;
// Are we running on Azure?
// https://docs.microsoft.com/en-us/azure/virtual-machines/linux/instance-metadata-service
@visibleForTesting
class AzureDetector {
AzureDetector({
@required HttpClientFactory httpClientFactory,
}) : _httpClientFactory = httpClientFactory;

static const String _serviceUrl = 'http://169.254.169.254/metadata/instance';

final HttpClientFactory _httpClientFactory;

bool _isRunningOnAzure;

Future<bool> get isRunningOnAzure async {
if (_isRunningOnAzure != null) {
return _isRunningOnAzure;
}
final HttpClient client = _httpClientFactory()
..connectionTimeout = const Duration(seconds: 1);
try {
final HttpClientRequest request = await client.getUrl(
Uri.parse(_serviceUrl),
);
request.headers.add('Metadata', true);
await request.close();
} on SocketException {
// If there is an error on the socket, it probalby means that we are not
// running on Azure.
return _isRunningOnAzure = false;
} on HttpException {
// If the connection gets set up, but encounters an error condition, it
// still means we're on Azure.
return _isRunningOnAzure = true;
}
// We got a response. We're running on Azure.
return _isRunningOnAzure = true;
}
}
15 changes: 13 additions & 2 deletions packages/flutter_tools/lib/src/context_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,18 @@ Future<T> runInContext<T>(
FutureOr<T> runner(), {
Map<Type, Generator> overrides,
}) async {

// Wrap runner with any asynchronous initialization that should run with the
// overrides and callbacks.
bool runningOnBot;
FutureOr<T> runnerWrapper() async {
runningOnBot = await globals.isRunningOnBot;
return runner();
}

return await context.run<T>(
name: 'global fallbacks',
body: runner,
body: runnerWrapper,
overrides: overrides,
fallbacks: <Type, Generator>{
AndroidLicenseValidator: () => AndroidLicenseValidator(),
Expand Down Expand Up @@ -159,7 +168,9 @@ Future<T> runInContext<T>(
Stdio: () => const Stdio(),
SystemClock: () => const SystemClock(),
TimeoutConfiguration: () => const TimeoutConfiguration(),
Usage: () => Usage(),
Usage: () => Usage(
runningOnBot: runningOnBot,
),
UserMessages: () => UserMessages(),
VisualStudio: () => VisualStudio(),
VisualStudioValidator: () => const VisualStudioValidator(),
Expand Down
15 changes: 7 additions & 8 deletions packages/flutter_tools/lib/src/dart/pub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'dart:async';

import 'package:meta/meta.dart';

import '../base/bot_detector.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
Expand Down Expand Up @@ -232,7 +231,7 @@ class _DefaultPub implements Pub {
@required bool retry,
bool showTraceForErrors,
}) async {
showTraceForErrors ??= isRunningOnBot(globals.platform);
showTraceForErrors ??= await globals.isRunningOnBot;

String lastPubMessage = 'no message';
bool versionSolvingFailed = false;
Expand All @@ -259,7 +258,7 @@ class _DefaultPub implements Pub {
_pubCommand(arguments),
workingDirectory: directory,
mapFunction: filterWrapper, // may set versionSolvingFailed, lastPubMessage
environment: _createPubEnvironment(context),
environment: await _createPubEnvironment(context),
);
String message;
switch (code) {
Expand Down Expand Up @@ -304,7 +303,7 @@ class _DefaultPub implements Pub {
final io.Process process = await processUtils.start(
_pubCommand(arguments),
workingDirectory: directory,
environment: _createPubEnvironment(PubContext.interactive),
environment: await _createPubEnvironment(PubContext.interactive),
);

// Pipe the Flutter tool stdin to the pub stdin.
Expand Down Expand Up @@ -348,10 +347,10 @@ typedef MessageFilter = String Function(String message);
///
/// [context] provides extra information to package server requests to
/// understand usage.
Map<String, String> _createPubEnvironment(PubContext context) {
Future<Map<String, String>> _createPubEnvironment(PubContext context) async {
final Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': Cache.flutterRoot,
_pubEnvironmentKey: _getPubEnvironmentValue(context),
_pubEnvironmentKey: await _getPubEnvironmentValue(context),
};
final String pubCache = _getRootPubCacheIfAvailable();
if (pubCache != null) {
Expand All @@ -374,13 +373,13 @@ const String _pubCacheEnvironmentKey = 'PUB_CACHE';
///
/// [context] provides extra information to package server requests to
/// understand usage.
String _getPubEnvironmentValue(PubContext pubContext) {
Future<String> _getPubEnvironmentValue(PubContext pubContext) async {
// DO NOT update this function without contacting kevmoo.
// We have server-side tooling that assumes the values are consistent.
final String existing = globals.platform.environment[_pubEnvironmentKey];
final List<String> values = <String>[
if (existing != null && existing.isNotEmpty) existing,
if (isRunningOnBot(globals.platform)) 'flutter_bot',
if (await globals.isRunningOnBot) 'flutter_bot',
'flutter_cli',
...pubContext._values,
];
Expand Down
11 changes: 11 additions & 0 deletions packages/flutter_tools/lib/src/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import 'package:process/process.dart';
import 'android/android_sdk.dart';
import 'android/android_studio.dart';
import 'artifacts.dart';
import 'base/bot_detector.dart';
import 'base/config.dart';
import 'base/context.dart';
import 'base/error_handling_file_system.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/net.dart';
import 'base/os.dart';
import 'base/terminal.dart';
import 'base/user_messages.dart';
Expand Down Expand Up @@ -65,6 +67,15 @@ Xcode get xcode => context.get<Xcode>();

XCDevice get xcdevice => context.get<XCDevice>();

final BotDetector _defaultBotDetector = BotDetector(
httpClientFactory: context.get<HttpClientFactory>() ?? () => HttpClient(),
platform: platform,
);

BotDetector get botDetector => context.get<BotDetector>() ?? _defaultBotDetector;

Future<bool> get isRunningOnBot => botDetector.isRunningOnBot;

/// Display an error level message to the user. Commands should use this if they
/// fail in some way.
///
Expand Down
1 change: 0 additions & 1 deletion packages/flutter_tools/lib/src/reporting/reporting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import 'package:usage/usage_io.dart';

import '../base/bot_detector.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
Expand Down
7 changes: 5 additions & 2 deletions packages/flutter_tools/lib/src/reporting/usage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ abstract class Usage {
String versionOverride,
String configDirOverride,
String logFile,
@required bool runningOnBot,
}) => _DefaultUsage(settingsName: settingsName,
versionOverride: versionOverride,
configDirOverride: configDirOverride,
logFile: logFile);
logFile: logFile,
runningOnBot: runningOnBot);

/// Returns [Usage] active in the current app context.
static Usage get instance => context.get<Usage>();
Expand Down Expand Up @@ -161,6 +163,7 @@ class _DefaultUsage implements Usage {
String versionOverride,
String configDirOverride,
String logFile,
@required bool runningOnBot,
}) {
final FlutterVersion flutterVersion = globals.flutterVersion;
final String version = versionOverride ?? flutterVersion.getVersionString(redactUnknownBranches: true);
Expand All @@ -176,7 +179,7 @@ class _DefaultUsage implements Usage {
// Many CI systems don't do a full git checkout.
version.endsWith('/unknown') ||
// Ignore bots.
isRunningOnBot(globals.platform) ||
runningOnBot ||
// Ignore when suppressed by FLUTTER_SUPPRESS_ANALYTICS.
suppressEnvFlag
)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ class AlwaysTrueBotDetector implements BotDetector {
const AlwaysTrueBotDetector();

@override
bool get isRunningOnBot => true;
Future<bool> get isRunningOnBot async => true;
}


class AlwaysFalseBotDetector implements BotDetector {
const AlwaysFalseBotDetector();

@override
bool get isRunningOnBot => false;
Future<bool> get isRunningOnBot async => false;
}


Expand Down
Loading