mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

This moves the logic for `FLUTTER_APP_FLAVOR` into `flutter assemble`,
so that it also works when ran through Xcode and not just through the
Flutter CLI.
However, there's no definitive way to get the the flavor/scheme in
`flutter assemble`, so this makes a best effort to get it by parsing it
out of the `CONFIGURATION`. `CONFIGURATION` should have the name of the
scheme in it, although, this is only
[semi-enforced](1d85de0fc8/packages/flutter_tools/lib/src/ios/mac.dart (L201-L203)
),
so may not always work. If it's unable to get the scheme name from the
`CONFIGURATION`, it falls back to using the `FLAVOR` environment
variable, which is set by the Flutter CLI and used currently.
Verified `Mac_ios flavors_test_ios` passes:
https://ci.chromium.org/ui/p/flutter/builders/prod.shadow/Mac_ios%20flavors_test_ios/7/overview
Verified `Mac flavors_test_macos` passes:
https://ci.chromium.org/ui/p/flutter/builders/try.shadow/Mac%20flavors_test_macos/2/overview
Fixes https://github.com/flutter/flutter/issues/155951.
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
160 lines
5.4 KiB
Dart
160 lines
5.4 KiB
Dart
// Copyright 2014 The Flutter Authors. 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 'dart:typed_data';
|
|
|
|
import 'package:collection/collection.dart';
|
|
import 'package:flutter_devicelab/framework/devices.dart';
|
|
import 'package:flutter_devicelab/framework/framework.dart';
|
|
import 'package:flutter_devicelab/framework/ios.dart';
|
|
import 'package:flutter_devicelab/framework/task_result.dart';
|
|
import 'package:flutter_devicelab/framework/utils.dart';
|
|
import 'package:flutter_devicelab/tasks/integration_tests.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:standard_message_codec/standard_message_codec.dart';
|
|
|
|
Future<void> main() async {
|
|
deviceOperatingSystem = DeviceOperatingSystem.ios;
|
|
await task(() async {
|
|
await createFlavorsTest().call();
|
|
await createIntegrationTestFlavorsTest().call();
|
|
// test install and uninstall of flavors app
|
|
final String projectDir = '${flutterDirectory.path}/dev/integration_tests/flavors';
|
|
final TaskResult installTestsResult = await inDirectory(projectDir, () async {
|
|
final List<TaskResult> testResults = <TaskResult>[
|
|
await _testInstallDebugPaidFlavor(projectDir),
|
|
await _testInstallBogusFlavor(),
|
|
];
|
|
|
|
final TaskResult? firstInstallFailure = testResults.firstWhereOrNull(
|
|
(TaskResult element) => element.failed,
|
|
);
|
|
|
|
return firstInstallFailure ?? TaskResult.success(null);
|
|
});
|
|
|
|
await _testFlavorWhenBuiltFromXcode(projectDir);
|
|
|
|
return installTestsResult;
|
|
});
|
|
}
|
|
|
|
Future<TaskResult> _testInstallDebugPaidFlavor(String projectDir) async {
|
|
await evalFlutter('install', options: <String>['--flavor', 'paid']);
|
|
final Uint8List assetManifestFileData =
|
|
File(
|
|
path.join(
|
|
projectDir,
|
|
'build',
|
|
'ios',
|
|
'iphoneos',
|
|
'Paid App.app',
|
|
'Frameworks',
|
|
'App.framework',
|
|
'flutter_assets',
|
|
'AssetManifest.bin',
|
|
),
|
|
).readAsBytesSync();
|
|
|
|
final Map<Object?, Object?> assetManifest =
|
|
const StandardMessageCodec().decodeMessage(ByteData.sublistView(assetManifestFileData))
|
|
as Map<Object?, Object?>;
|
|
|
|
if (assetManifest.containsKey('assets/free/free.txt')) {
|
|
return TaskResult.failure(
|
|
'Expected the asset "assets/free/free.txt", which '
|
|
' was declared with a flavor of "free" to not be included in the asset bundle '
|
|
' because the --flavor was set to "paid".',
|
|
);
|
|
}
|
|
|
|
if (!assetManifest.containsKey('assets/paid/paid.txt')) {
|
|
return TaskResult.failure(
|
|
'Expected the asset "assets/paid/paid.txt", which '
|
|
' was declared with a flavor of "paid" to be included in the asset bundle '
|
|
' because the --flavor was set to "paid".',
|
|
);
|
|
}
|
|
|
|
await flutter('install', options: <String>['--flavor', 'paid', '--uninstall-only']);
|
|
|
|
return TaskResult.success(null);
|
|
}
|
|
|
|
Future<TaskResult> _testInstallBogusFlavor() async {
|
|
final StringBuffer stderr = StringBuffer();
|
|
await evalFlutter(
|
|
'install',
|
|
canFail: true,
|
|
stderr: stderr,
|
|
options: <String>['--flavor', 'bogus'],
|
|
);
|
|
|
|
final String stderrString = stderr.toString();
|
|
if (!stderrString.contains('The Xcode project defines schemes: free, paid')) {
|
|
print(stderrString);
|
|
return TaskResult.failure('Should not succeed with bogus flavor');
|
|
}
|
|
|
|
return TaskResult.success(null);
|
|
}
|
|
|
|
Future<TaskResult> _testFlavorWhenBuiltFromXcode(String projectDir) async {
|
|
final Device device = await devices.workingDevice;
|
|
await inDirectory(projectDir, () async {
|
|
// This will put FLAVOR=free in the Flutter/Generated.xcconfig file
|
|
await flutter(
|
|
'build',
|
|
options: <String>['ios', '--config-only', '--debug', '--flavor', 'free'],
|
|
);
|
|
});
|
|
|
|
final File generatedXcconfig = File(path.join(projectDir, 'ios/Flutter/Generated.xcconfig'));
|
|
if (!generatedXcconfig.existsSync()) {
|
|
throw TaskResult.failure('Unable to find Generated.xcconfig');
|
|
}
|
|
if (!generatedXcconfig.readAsStringSync().contains('FLAVOR=free')) {
|
|
throw TaskResult.failure('Generated.xcconfig does not contain FLAVOR=free');
|
|
}
|
|
|
|
const String configuration = 'Debug Paid';
|
|
const String productName = 'Paid App';
|
|
const String buildDir = 'build/ios';
|
|
|
|
// Delete app bundle before build to ensure checks below do not use previously
|
|
// built bundle.
|
|
final String appPath = '$projectDir/$buildDir/$configuration-iphoneos/$productName.app';
|
|
final Directory appBundle = Directory(appPath);
|
|
if (appBundle.existsSync()) {
|
|
appBundle.deleteSync(recursive: true);
|
|
}
|
|
|
|
if (!await runXcodeBuild(
|
|
platformDirectory: path.join(projectDir, 'ios'),
|
|
destination: 'id=${device.deviceId}',
|
|
testName: 'flavors_test_ios',
|
|
configuration: configuration,
|
|
scheme: 'paid',
|
|
actions: <String>['clean', 'build'],
|
|
extraOptions: <String>['BUILD_DIR=${path.join(projectDir, buildDir)}'],
|
|
)) {
|
|
throw TaskResult.failure('Build failed');
|
|
}
|
|
|
|
if (!appBundle.existsSync()) {
|
|
throw TaskResult.failure('App not found at $appPath');
|
|
}
|
|
|
|
if (!generatedXcconfig.readAsStringSync().contains('FLAVOR=free')) {
|
|
throw TaskResult.failure('Generated.xcconfig does not contain FLAVOR=free');
|
|
}
|
|
|
|
// Despite FLAVOR=free being in the Generated.xcconfig, the flavor found in
|
|
// the test should be "paid" because it was built with the "Debug Paid" configuration.
|
|
return createFlavorsTest(
|
|
extraOptions: <String>['--flavor', 'paid', '--use-application-binary=$appPath'],
|
|
).call();
|
|
}
|