Add usage event when iOS app is archived (#108643)
This commit is contained in:
parent
c7498f607b
commit
7f1a8f7948
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user