Make sure add-to-app build bundle from outer xcodebuild/gradlew sends analytics (#36122)
diff --git a/dev/devicelab/bin/tasks/module_test.dart b/dev/devicelab/bin/tasks/module_test.dart
index 0eb67c3..1b6e891 100644
--- a/dev/devicelab/bin/tasks/module_test.dart
+++ b/dev/devicelab/bin/tasks/module_test.dart
@@ -154,13 +154,18 @@
Directory(path.join(hostApp.path, 'gradle', 'wrapper')),
);
+ final File analyticsOutputFile = File(path.join(tempDir.path, 'analytics.log'));
+
await inDirectory(hostApp, () async {
if (!Platform.isWindows) {
await exec('chmod', <String>['+x', 'gradlew']);
}
await exec(gradlewExecutable,
<String>['app:assembleDebug'],
- environment: <String, String>{ 'JAVA_HOME': javaHome },
+ environment: <String, String>{
+ 'JAVA_HOME': javaHome,
+ 'FLUTTER_ANALYTICS_LOG_FILE': analyticsOutputFile.path,
+ },
);
});
@@ -173,10 +178,21 @@
'debug',
'app-debug.apk',
)));
-
if (!existingAppBuilt) {
return TaskResult.failure('Failed to build existing app .apk');
}
+
+ final String analyticsOutput = analyticsOutputFile.readAsStringSync();
+ if (!analyticsOutput.contains('cd24: android-arm64')
+ || !analyticsOutput.contains('cd25: true')
+ || !analyticsOutput.contains('viewName: build/bundle')) {
+ return TaskResult.failure(
+ 'Building outer app produced the following analytics: "$analyticsOutput"'
+ 'but not the expected strings: "cd24: android-arm64", "cd25: true" and '
+ '"viewName: build/bundle"'
+ );
+ }
+
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
diff --git a/dev/devicelab/bin/tasks/module_test_ios.dart b/dev/devicelab/bin/tasks/module_test_ios.dart
index a54b555..ec71e03 100644
--- a/dev/devicelab/bin/tasks/module_test_ios.dart
+++ b/dev/devicelab/bin/tasks/module_test_ios.dart
@@ -219,6 +219,8 @@
hostApp,
);
+ final File analyticsOutputFile = File(path.join(tempDir.path, 'analytics.log'));
+
await inDirectory(hostApp, () async {
await exec('pod', <String>['install']);
await exec(
@@ -236,6 +238,9 @@
'EXPANDED_CODE_SIGN_IDENTITY=-',
'CONFIGURATION_BUILD_DIR=${tempDir.path}',
],
+ environment: <String, String> {
+ 'FLUTTER_ANALYTICS_LOG_FILE': analyticsOutputFile.path,
+ }
);
});
@@ -244,11 +249,20 @@
'Host.app',
'Host',
)));
-
if (!existingAppBuilt) {
return TaskResult.failure('Failed to build existing app .app');
}
+ final String analyticsOutput = analyticsOutputFile.readAsStringSync();
+ if (!analyticsOutput.contains('cd24: ios')
+ || !analyticsOutput.contains('cd25: true')
+ || !analyticsOutput.contains('viewName: build/bundle')) {
+ return TaskResult.failure(
+ 'Building outer app produced the following analytics: "$analyticsOutput"'
+ 'but not the expected strings: "cd24: ios", "cd25: true", "viewName: build/bundle"'
+ );
+ }
+
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh
index 0863b15..cd8299a 100755
--- a/packages/flutter_tools/bin/xcode_backend.sh
+++ b/packages/flutter_tools/bin/xcode_backend.sh
@@ -249,7 +249,7 @@
fi
StreamOutput " ├─Assembling Flutter resources..."
- RunCommand "${FLUTTER_ROOT}/bin/flutter" \
+ RunCommand "${FLUTTER_ROOT}/bin/flutter" \
${verbose_flag} \
build bundle \
--target-platform=ios \
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index 372a1a0..52317dc 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -223,7 +223,7 @@
initWith debug
}
}
-
+
pluginProject.android.buildTypes.each {
def buildMode = buildModeFor(it)
addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] ))
@@ -652,6 +652,7 @@
project.exec {
executable flutterExecutable.absolutePath
workingDir sourceDir
+
if (localEngine != null) {
args "--local-engine", localEngine
args "--local-engine-src-path", localEngineSrcPath
diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart
index ebdc745..2cfbf7a 100644
--- a/packages/flutter_tools/lib/src/base/utils.dart
+++ b/packages/flutter_tools/lib/src/base/utils.dart
@@ -27,6 +27,8 @@
// 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;
}
diff --git a/packages/flutter_tools/lib/src/reporting/usage.dart b/packages/flutter_tools/lib/src/reporting/usage.dart
index fbd6604..2cd2138 100644
--- a/packages/flutter_tools/lib/src/reporting/usage.dart
+++ b/packages/flutter_tools/lib/src/reporting/usage.dart
@@ -69,8 +69,18 @@
Usage({ String settingsName = 'flutter', String versionOverride, String configDirOverride}) {
final FlutterVersion flutterVersion = FlutterVersion.instance;
final String version = versionOverride ?? flutterVersion.getVersionString(redactUnknownBranches: true);
- _analytics = AnalyticsIO(_kFlutterUA, settingsName, version,
- documentDirectory: configDirOverride != null ? fs.directory(configDirOverride) : null);
+
+ final String logFilePath = platform.environment['FLUTTER_ANALYTICS_LOG_FILE'];
+
+ _analytics = logFilePath == null || logFilePath.isEmpty ?
+ AnalyticsIO(
+ _kFlutterUA,
+ settingsName,
+ version,
+ documentDirectory: configDirOverride != null ? fs.directory(configDirOverride) : null,
+ ) :
+ // Used for testing.
+ LogToFileAnalytics(logFilePath);
// Report a more detailed OS version string than package:usage does by default.
_analytics.setSessionValue(kSessionHostOsDetails, os.name);
@@ -94,6 +104,7 @@
_analytics.analyticsOpt = AnalyticsOpt.optOut;
final bool suppressEnvFlag = platform.environment['FLUTTER_SUPPRESS_ANALYTICS'] == 'true';
+ _analytics.sendScreenView('version is $version, is bot $isRunningOnBot, suppressed $suppressEnvFlag');
// Many CI systems don't do a full git checkout.
if (version.endsWith('/unknown') || isRunningOnBot || suppressEnvFlag) {
// If we think we're running on a CI system, suppress sending analytics.
@@ -213,3 +224,22 @@
''', emphasis: true);
}
}
+
+// An Analytics mock that logs to file. Unimplemented methods goes to stdout.
+// But stdout can't be used for testing since wrapper scripts like
+// xcode_backend.sh etc manipulates them.
+class LogToFileAnalytics extends AnalyticsMock {
+ LogToFileAnalytics(String logFilePath) :
+ logFile = fs.file(logFilePath)..createSync(recursive: true),
+ super(true);
+
+ final File logFile;
+
+ @override
+ Future<void> sendScreenView(String viewName, {Map<String, String> parameters}) {
+ parameters ??= <String, String>{};
+ parameters['viewName'] = viewName;
+ logFile.writeAsStringSync('screenView $parameters\n');
+ return Future<void>.value(null);
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/base/utils_test.dart b/packages/flutter_tools/test/general.shard/base/utils_test.dart
index 9046c5e..6a98157 100644
--- a/packages/flutter_tools/test/general.shard/base/utils_test.dart
+++ b/packages/flutter_tools/test/general.shard/base/utils_test.dart
@@ -51,6 +51,15 @@
Stdio: () => mockStdio,
Platform: () => fakePlatform,
});
+
+ testUsingContext('can test analytics outputs on bots when outputting to a file', () async {
+ fakePlatform.environment['TRAVIS'] = 'true';
+ fakePlatform.environment['FLUTTER_ANALYTICS_LOG_FILE'] = '/some/file';
+ expect(botDetector.isRunningOnBot, isFalse);
+ }, overrides: <Type, Generator>{
+ Stdio: () => mockStdio,
+ Platform: () => fakePlatform,
+ });
});
});
}