Unverified Commit 7f1a8f79 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Add usage event when iOS app is archived (#108643)

parent c7498f60
......@@ -17,9 +17,16 @@ Future<void> main() async {
section('Archive');
await inDirectory(flutterProject.rootPath, () async {
await flutter('build', options: <String>[
final String output = await evalFlutter('build', options: <String>[
'xcarchive',
'-v',
]);
// Note this isBot so usage won't actually be sent,
// this log line is printed whenever the app is archived.
if (!output.contains('Sending archive event if usage enabled')) {
throw TaskResult.failure('Usage archive event not sent');
}
});
final String archivePath = path.join(
......
......@@ -366,6 +366,7 @@ class Context {
'-dTrackWidgetCreation=${environment['TRACK_WIDGET_CREATION'] ?? ''}',
'-dDartObfuscation=${environment['DART_OBFUSCATION'] ?? ''}',
'-dEnableBitcode=$bitcodeFlag',
'-dAction=${environment['ACTION'] ?? ''}',
'--ExtraGenSnapshotOptions=${environment['EXTRA_GEN_SNAPSHOT_OPTIONS'] ?? ''}',
'--DartDefines=${environment['DART_DEFINES'] ?? ''}',
'--ExtraFrontEndOptions=${environment['EXTRA_FRONT_END_OPTIONS'] ?? ''}',
......
......@@ -950,6 +950,11 @@ const String kBuildName = 'BuildName';
/// The define to pass build number
const String kBuildNumber = 'BuildNumber';
/// The action Xcode is taking.
///
/// Will be "build" when building and "install" when archiving.
const String kXcodeAction = 'Action';
final Converter<String, String> _defineEncoder = utf8.encoder.fuse(base64.encoder);
final Converter<String, String> _defineDecoder = base64.decoder.fuse(utf8.decoder);
......
......@@ -17,6 +17,7 @@ import '../base/platform.dart';
import '../base/utils.dart';
import '../cache.dart';
import '../convert.dart';
import '../reporting/reporting.dart';
import 'exceptions.dart';
import 'file_store.dart';
import 'source.dart';
......@@ -332,6 +333,7 @@ class Environment {
required Artifacts artifacts,
required ProcessManager processManager,
required Platform platform,
required Usage usage,
String? engineVersion,
required bool generateDartPluginRegistry,
Directory? buildDir,
......@@ -372,6 +374,7 @@ class Environment {
artifacts: artifacts,
processManager: processManager,
platform: platform,
usage: usage,
engineVersion: engineVersion,
inputs: inputs,
generateDartPluginRegistry: generateDartPluginRegistry,
......@@ -392,6 +395,7 @@ class Environment {
Map<String, String> inputs = const <String, String>{},
String? engineVersion,
Platform? platform,
Usage? usage,
bool generateDartPluginRegistry = false,
required FileSystem fileSystem,
required Logger logger,
......@@ -411,6 +415,7 @@ class Environment {
artifacts: artifacts,
processManager: processManager,
platform: platform ?? FakePlatform(),
usage: usage ?? TestUsage(),
engineVersion: engineVersion,
generateDartPluginRegistry: generateDartPluginRegistry,
);
......@@ -429,6 +434,7 @@ class Environment {
required this.logger,
required this.fileSystem,
required this.artifacts,
required this.usage,
this.engineVersion,
required this.inputs,
required this.generateDartPluginRegistry,
......@@ -509,6 +515,8 @@ class Environment {
final FileSystem fileSystem;
final Usage usage;
/// The version of the current engine, or `null` if built with a local engine.
final String? engineVersion;
......
......@@ -13,6 +13,7 @@ import '../../build_info.dart';
import '../../globals.dart' as globals show xcode;
import '../../macos/xcode.dart';
import '../../project.dart';
import '../../reporting/reporting.dart';
import '../build_system.dart';
import '../depfile.dart';
import '../exceptions.dart';
......@@ -570,6 +571,30 @@ class ReleaseIosApplicationBundle extends IosAssetBundle {
List<Target> get dependencies => const <Target>[
AotAssemblyRelease(),
];
@override
Future<void> build(Environment environment) async {
bool buildSuccess = true;
try {
await super.build(environment);
} catch (_) { // ignore: avoid_catches_without_on_clauses
buildSuccess = false;
rethrow;
} finally {
// Send a usage event when the app is being archived.
// Since assemble is run during a `flutter build`/`run` as well as an out-of-band
// archive command from Xcode, this is a more accurate count than `flutter build ipa` alone.
if (environment.defines[kXcodeAction]?.toLowerCase() == 'install') {
environment.logger.printTrace('Sending archive event if usage enabled.');
UsageEvent(
'assemble',
'ios-archive',
label: buildSuccess ? 'success' : 'fail',
flutterUsage: environment.usage,
).send();
}
}
}
}
/// Create an App.framework for debug iOS targets.
......
......@@ -65,6 +65,7 @@ class BundleBuilder {
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
usage: globals.flutterUsage,
platform: globals.platform,
generateDartPluginRegistry: true,
);
......
......@@ -238,6 +238,7 @@ class AssembleCommand extends FlutterCommand {
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
usage: globals.flutterUsage,
platform: globals.platform,
engineVersion: artifacts.isLocalEngine
? null
......
......@@ -433,6 +433,7 @@ end
logger: globals.logger,
processManager: globals.processManager,
platform: globals.platform,
usage: globals.flutterUsage,
engineVersion: globals.artifacts!.isLocalEngine
? null
: globals.flutterVersion.engineRevision,
......
......@@ -204,6 +204,7 @@ end
logger: globals.logger,
processManager: globals.processManager,
platform: globals.platform,
usage: globals.flutterUsage,
engineVersion: globals.artifacts!.isLocalEngine ? null : globals.flutterVersion.engineRevision,
generateDartPluginRegistry: true,
);
......
......@@ -538,6 +538,7 @@ abstract class CreateBase extends FlutterCommand {
outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager,
platform: globals.platform,
usage: globals.flutterUsage,
projectDir: project.directory,
generateDartPluginRegistry: true,
);
......
......@@ -124,6 +124,7 @@ class PackagesGetCommand extends FlutterCommand {
outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager,
platform: globals.platform,
usage: globals.flutterUsage,
projectDir: flutterProject.directory,
generateDartPluginRegistry: true,
);
......@@ -332,6 +333,7 @@ class PackagesInteractiveGetCommand extends FlutterCommand {
outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager,
platform: globals.platform,
usage: globals.flutterUsage,
projectDir: flutterProject.directory,
generateDartPluginRegistry: true,
);
......
......@@ -1217,6 +1217,7 @@ abstract class ResidentRunner extends ResidentHandlers {
outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager,
platform: globals.platform,
usage: globals.flutterUsage,
projectDir: globals.fs.currentDirectory,
generateDartPluginRegistry: generateDartPluginRegistry,
defines: <String, String>{
......
......@@ -1343,6 +1343,7 @@ abstract class FlutterCommand extends Command<void> {
outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager,
platform: globals.platform,
usage: globals.flutterUsage,
projectDir: project.directory,
generateDartPluginRegistry: true,
);
......
......@@ -72,6 +72,7 @@ Future<void> buildWeb(
logger: globals.logger,
processManager: globals.processManager,
platform: globals.platform,
usage: globals.flutterUsage,
cacheDir: globals.cache.getRoot(),
engineVersion: globals.artifacts!.isLocalEngine
? null
......
......@@ -9,7 +9,6 @@ import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/deferred_component.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
......@@ -23,21 +22,13 @@ void main() {
Environment createEnvironment() {
final Map<String, String> defines = <String, String>{ kDeferredComponents: 'true' };
final Environment result = Environment(
outputDir: fileSystem.directory('/output'),
buildDir: fileSystem.directory('/build'),
projectDir: fileSystem.directory('/project'),
final Environment result = Environment.test(
fileSystem.directory('/project'),
defines: defines,
inputs: <String, String>{},
cacheDir: fileSystem.directory('/cache'),
flutterRootDir: fileSystem.directory('/flutter_root'),
artifacts: Artifacts.test(),
fileSystem: fileSystem,
logger: logger,
processManager: FakeProcessManager.any(),
platform: FakePlatform(),
engineVersion: 'invalidEngineVersion',
generateDartPluginRegistry: false,
);
return result;
}
......
......@@ -12,9 +12,11 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/ios.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import '../../../src/common.dart';
import '../../../src/context.dart';
import '../../../src/fake_process_manager.dart';
final Platform macPlatform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{});
......@@ -42,12 +44,14 @@ void main() {
late FakeProcessManager processManager;
late Artifacts artifacts;
late BufferLogger logger;
late TestUsage usage;
setUp(() {
fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.empty();
logger = BufferLogger.test();
artifacts = Artifacts.test();
usage = TestUsage();
environment = Environment.test(
fileSystem.currentDirectory,
defines: <String, String>{
......@@ -59,6 +63,7 @@ void main() {
logger: logger,
fileSystem: fileSystem,
engineVersion: '2',
usage: usage,
);
});
......@@ -216,9 +221,10 @@ void main() {
expect(assetDirectory.childFile('io.flutter.shaders.json').readAsStringSync(), '{"data":{"A":"B"}}');
});
testUsingContext('ReleaseIosApplicationBundle', () async {
testUsingContext('ReleaseIosApplicationBundle build', () async {
environment.defines[kBuildMode] = 'release';
environment.defines[kCodesignIdentity] = 'ABC123';
environment.defines[kXcodeAction] = 'build';
// Project info
fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello');
......@@ -247,7 +253,7 @@ void main() {
);
await const ReleaseIosApplicationBundle().build(environment);
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
expect(frameworkDirectoryBinary, exists);
expect(frameworkDirectory.childFile('Info.plist'), exists);
......@@ -257,6 +263,53 @@ void main() {
expect(assetDirectory.childFile('AssetManifest.json'), exists);
expect(assetDirectory.childFile('vm_snapshot_data'), isNot(exists));
expect(assetDirectory.childFile('isolate_snapshot_data'), isNot(exists));
expect(usage.events, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => macPlatform,
});
testUsingContext('ReleaseIosApplicationBundle sends archive success event', () async {
environment.defines[kBuildMode] = 'release';
environment.defines[kXcodeAction] = 'install';
fileSystem.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
.createSync(recursive: true);
environment.buildDir
.childDirectory('App.framework')
.childFile('App')
.createSync(recursive: true);
final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
processManager.addCommand(
FakeCommand(command: <String>[
'codesign',
'--force',
'--sign',
'-',
frameworkDirectoryBinary.path,
]),
);
await const ReleaseIosApplicationBundle().build(environment);
expect(usage.events, contains(const TestUsageEvent('assemble', 'ios-archive', label: 'success')));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => macPlatform,
});
testUsingContext('ReleaseIosApplicationBundle sends archive fail event', () async {
environment.defines[kBuildMode] = 'release';
environment.defines[kXcodeAction] = 'install';
// Throws because the project files are not set up.
await expectLater(() => const ReleaseIosApplicationBundle().build(environment),
throwsA(const TypeMatcher<FileSystemException>()));
expect(usage.events, contains(const TestUsageEvent('assemble', 'ios-archive', label: 'fail')));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
......@@ -286,7 +339,7 @@ void main() {
contains('release/profile builds are only supported for physical devices.'),
)
));
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
......@@ -313,7 +366,7 @@ void main() {
'description',
contains('required define SdkRoot but it was not provided'),
)));
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
......@@ -414,7 +467,7 @@ void main() {
await const DebugUnpackIOS().build(environment);
expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('fails when frameworks missing', () async {
......@@ -565,7 +618,7 @@ void main() {
expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('thins fat framework', () async {
......@@ -613,7 +666,7 @@ void main() {
]);
await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('fails when bitcode strip fails', () async {
......@@ -656,7 +709,7 @@ void main() {
)),
);
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('strips framework', () async {
......@@ -685,7 +738,7 @@ void main() {
]);
await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('fails when codesign fails', () async {
......@@ -730,7 +783,7 @@ void main() {
)),
);
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('codesigns framework', () async {
......@@ -767,7 +820,7 @@ void main() {
]);
await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse);
expect(processManager, hasNoRemainingExpectations);
});
});
}
......@@ -29,6 +29,7 @@ void main() {
final TestContext context = TestContext(
<String>['build'],
<String, String>{
'ACTION': 'build',
'BUILT_PRODUCTS_DIR': buildDir.path,
'ENABLE_BITCODE': 'YES',
'FLUTTER_ROOT': flutterRoot.path,
......@@ -51,6 +52,7 @@ void main() {
'-dTrackWidgetCreation=',
'-dDartObfuscation=',
'-dEnableBitcode=',
'-dAction=build',
'--ExtraGenSnapshotOptions=',
'--DartDefines=',
'--ExtraFrontEndOptions=',
......@@ -104,6 +106,7 @@ void main() {
'-dTrackWidgetCreation=',
'-dDartObfuscation=',
'-dEnableBitcode=',
'-dAction=',
'--ExtraGenSnapshotOptions=',
'--DartDefines=',
'--ExtraFrontEndOptions=',
......@@ -179,6 +182,7 @@ void main() {
'-dTrackWidgetCreation=$trackWidgetCreation',
'-dDartObfuscation=$dartObfuscation',
'-dEnableBitcode=true',
'-dAction=install',
'--ExtraGenSnapshotOptions=$extraGenSnapshotOptions',
'--DartDefines=$dartDefines',
'--ExtraFrontEndOptions=$extraFrontEndOptions',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment