Get flavor/scheme in assemble command from the build configuration (#162907)
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
This commit is contained in:
parent
54b972084a
commit
8d100a6416
@ -8,6 +8,7 @@ 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';
|
||||
@ -34,6 +35,8 @@ Future<void> main() async {
|
||||
return firstInstallFailure ?? TaskResult.success(null);
|
||||
});
|
||||
|
||||
await _testFlavorWhenBuiltFromXcode(projectDir);
|
||||
|
||||
return installTestsResult;
|
||||
});
|
||||
}
|
||||
@ -97,3 +100,60 @@ Future<TaskResult> _testInstallBogusFlavor() async {
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import 'dart:typed_data';
|
||||
|
||||
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';
|
||||
@ -83,6 +84,67 @@ Future<void> main() async {
|
||||
return TaskResult.success(null);
|
||||
});
|
||||
|
||||
await _testFlavorWhenBuiltFromXcode(projectDir);
|
||||
|
||||
return installTestsResult;
|
||||
});
|
||||
}
|
||||
|
||||
Future<TaskResult> _testFlavorWhenBuiltFromXcode(String projectDir) async {
|
||||
await inDirectory(projectDir, () async {
|
||||
// This will put FLAVOR=free in the Flutter/ephemeral/Flutter-Generated.xcconfig file
|
||||
await flutter(
|
||||
'build',
|
||||
options: <String>['macos', '--config-only', '--debug', '--flavor', 'free'],
|
||||
);
|
||||
});
|
||||
|
||||
final File generatedXcconfig = File(
|
||||
path.join(projectDir, 'macos/Flutter/ephemeral/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 = 'Debug Paid';
|
||||
const String buildDir = 'build/macos';
|
||||
final String appPath = '$projectDir/$buildDir/$configuration/$productName.app';
|
||||
|
||||
// Delete app bundle before build to ensure checks below do not use previously
|
||||
// built bundle.
|
||||
final Directory appBundle = Directory(appPath);
|
||||
if (appBundle.existsSync()) {
|
||||
appBundle.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
if (!await runXcodeBuild(
|
||||
platformDirectory: path.join(projectDir, 'macos'),
|
||||
destination: 'platform=macOS',
|
||||
testName: 'flavors_test_macos',
|
||||
configuration: configuration,
|
||||
scheme: 'paid',
|
||||
actions: <String>['clean', 'build'],
|
||||
extraOptions: <String>['BUILD_DIR=${path.join(projectDir, buildDir)}'],
|
||||
skipCodesign: true,
|
||||
)) {
|
||||
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();
|
||||
}
|
||||
|
@ -142,7 +142,32 @@ Future<bool> runXcodeTests({
|
||||
required String platformDirectory,
|
||||
required String destination,
|
||||
required String testName,
|
||||
List<String> actions = const <String>['test'],
|
||||
String configuration = 'Release',
|
||||
List<String> extraOptions = const <String>[],
|
||||
String scheme = 'Runner',
|
||||
bool skipCodesign = false,
|
||||
}) {
|
||||
return runXcodeBuild(
|
||||
platformDirectory: platformDirectory,
|
||||
destination: destination,
|
||||
testName: testName,
|
||||
actions: actions,
|
||||
configuration: configuration,
|
||||
extraOptions: extraOptions,
|
||||
scheme: scheme,
|
||||
skipCodesign: skipCodesign,
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> runXcodeBuild({
|
||||
required String platformDirectory,
|
||||
required String destination,
|
||||
required String testName,
|
||||
List<String> actions = const <String>['build'],
|
||||
String configuration = 'Release',
|
||||
List<String> extraOptions = const <String>[],
|
||||
String scheme = 'Runner',
|
||||
bool skipCodesign = false,
|
||||
}) async {
|
||||
final Map<String, String> environment = Platform.environment;
|
||||
@ -170,14 +195,15 @@ Future<bool> runXcodeTests({
|
||||
'-workspace',
|
||||
'Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
scheme,
|
||||
'-configuration',
|
||||
configuration,
|
||||
'-destination',
|
||||
destination,
|
||||
'-resultBundlePath',
|
||||
resultBundlePath,
|
||||
'test',
|
||||
...actions,
|
||||
...extraOptions,
|
||||
'COMPILER_INDEX_STORE_ENABLE=NO',
|
||||
if (developmentTeam != null) 'DEVELOPMENT_TEAM=$developmentTeam',
|
||||
if (codeSignStyle != null) 'CODE_SIGN_STYLE=$codeSignStyle',
|
||||
|
@ -22,11 +22,11 @@ TaskFunction createPlatformInteractionTest() {
|
||||
).call;
|
||||
}
|
||||
|
||||
TaskFunction createFlavorsTest({Map<String, String>? environment}) {
|
||||
TaskFunction createFlavorsTest({Map<String, String>? environment, List<String>? extraOptions}) {
|
||||
return DriverTest(
|
||||
'${flutterDirectory.path}/dev/integration_tests/flavors',
|
||||
'lib/main.dart',
|
||||
extraOptions: <String>['--flavor', 'paid'],
|
||||
extraOptions: extraOptions ?? <String>['--flavor', 'paid'],
|
||||
environment: environment,
|
||||
).call;
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ BuildApp() {
|
||||
"-dTrackWidgetCreation=${TRACK_WIDGET_CREATION}"
|
||||
"-dAction=${ACTION}"
|
||||
"-dFrontendServerStarterPath=${FRONTEND_SERVER_STARTER_PATH}"
|
||||
"-dConfiguration=${CONFIGURATION}"
|
||||
"--DartDefines=${DART_DEFINES}"
|
||||
"--ExtraGenSnapshotOptions=${EXTRA_GEN_SNAPSHOT_OPTIONS}"
|
||||
"--ExtraFrontEndOptions=${EXTRA_FRONT_END_OPTIONS}"
|
||||
@ -162,6 +163,8 @@ BuildApp() {
|
||||
flutter_args+=("-dPreBuildAction=PrepareFramework")
|
||||
fi
|
||||
|
||||
# FLAVOR is set by the Flutter CLI in the Flutter/ephemeral/Flutter-Generated.xcconfig
|
||||
# file when the --flavor flag is used, so it may not always be present.
|
||||
if [[ -n "$FLAVOR" ]]; then
|
||||
flutter_args+=("-dFlavor=${FLAVOR}")
|
||||
fi
|
||||
|
@ -439,7 +439,10 @@ class Context {
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=$targetPath',
|
||||
'-dBuildMode=$buildMode',
|
||||
// FLAVOR is set by the Flutter CLI in the Flutter/Generated.xcconfig file
|
||||
// when the --flavor flag is used, so it may not always be present.
|
||||
if (environment['FLAVOR'] != null) '-dFlavor=${environment['FLAVOR']}',
|
||||
'-dConfiguration=${environment['CONFIGURATION']}',
|
||||
'-dIosArchs=$archs',
|
||||
'-dSdkRoot=${environment['SDKROOT'] ?? ''}',
|
||||
'-dSplitDebugInfo=${environment['SPLIT_DEBUG_INFO'] ?? ''}',
|
||||
|
@ -966,6 +966,13 @@ const String kBuildName = 'BuildName';
|
||||
/// The app flavor to build.
|
||||
const String kFlavor = 'Flavor';
|
||||
|
||||
/// Environment variable of the flavor to be set in dartDefines to be accessed
|
||||
/// by the [appFlavor] service.
|
||||
const String kAppFlavor = 'FLUTTER_APP_FLAVOR';
|
||||
|
||||
/// The Xcode configuration used to build the project.
|
||||
const String kXcodeConfiguration = 'Configuration';
|
||||
|
||||
/// The define to pass build number
|
||||
const String kBuildNumber = 'BuildNumber';
|
||||
|
||||
|
@ -6,13 +6,16 @@ import 'package:package_config/package_config.dart';
|
||||
|
||||
import '../../artifacts.dart';
|
||||
import '../../base/build.dart';
|
||||
import '../../base/common.dart';
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/io.dart';
|
||||
import '../../build_info.dart';
|
||||
import '../../compile.dart';
|
||||
import '../../dart/package_map.dart';
|
||||
import '../../devfs.dart';
|
||||
import '../../globals.dart' as globals show xcode;
|
||||
import '../../globals.dart' as globals show platform, xcode;
|
||||
import '../../project.dart';
|
||||
import '../../runner/flutter_command.dart';
|
||||
import '../build_system.dart';
|
||||
import '../depfile.dart';
|
||||
import '../exceptions.dart';
|
||||
@ -251,6 +254,9 @@ class KernelSnapshot extends Target {
|
||||
|
||||
final String dillPath = environment.buildDir.childFile(dillName).path;
|
||||
|
||||
final List<String> dartDefines = decodeDartDefines(environment.defines, kDartDefines);
|
||||
await _addFlavorToDartDefines(dartDefines, environment, targetPlatform);
|
||||
|
||||
final CompilerOutput? output = await compiler.compile(
|
||||
sdkRoot: environment.artifacts.getArtifactPath(
|
||||
Artifact.flutterPatchedSdkPath,
|
||||
@ -271,7 +277,7 @@ class KernelSnapshot extends Target {
|
||||
extraFrontEndOptions: extraFrontEndOptions,
|
||||
fileSystemRoots: fileSystemRoots,
|
||||
fileSystemScheme: fileSystemScheme,
|
||||
dartDefines: decodeDartDefines(environment.defines, kDartDefines),
|
||||
dartDefines: dartDefines,
|
||||
packageConfig: packageConfig,
|
||||
buildDir: environment.buildDir,
|
||||
targetOS: targetOS,
|
||||
@ -281,6 +287,40 @@ class KernelSnapshot extends Target {
|
||||
throw Exception();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addFlavorToDartDefines(
|
||||
List<String> dartDefines,
|
||||
Environment environment,
|
||||
TargetPlatform targetPlatform,
|
||||
) async {
|
||||
final String? flavor;
|
||||
|
||||
// For iOS and macOS projects, parse the flavor from the configuration, do
|
||||
// not get from the FLAVOR environment variable. This is because when built
|
||||
// from Xcode, the scheme/flavor can be changed through the UI and is not
|
||||
// reflected in the environment variable.
|
||||
if (targetPlatform == TargetPlatform.ios || targetPlatform == TargetPlatform.darwin) {
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
|
||||
final XcodeBasedProject xcodeProject =
|
||||
targetPlatform == TargetPlatform.ios ? flutterProject.ios : flutterProject.macos;
|
||||
flavor = await xcodeProject.parseFlavorFromConfiguration(environment);
|
||||
} else {
|
||||
flavor = environment.defines[kFlavor];
|
||||
}
|
||||
if (flavor == null) {
|
||||
return;
|
||||
}
|
||||
if (globals.platform.environment[kAppFlavor] != null) {
|
||||
throwToolExit('$kAppFlavor is used by the framework and cannot be set in the environment.');
|
||||
}
|
||||
if (dartDefines.any((String define) => define.startsWith(kAppFlavor))) {
|
||||
throwToolExit(
|
||||
'$kAppFlavor is used by the framework and cannot be '
|
||||
'set using --${FlutterOptions.kDartDefinesOption} or --${FlutterOptions.kDartDefineFromFileOption}',
|
||||
);
|
||||
}
|
||||
dartDefines.add('$kAppFlavor=$flavor');
|
||||
}
|
||||
}
|
||||
|
||||
/// Supports compiling a dart kernel file to an ELF binary.
|
||||
|
@ -526,6 +526,7 @@ abstract class IosAssetBundle extends Target {
|
||||
}
|
||||
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
|
||||
final String? flavor = await flutterProject.ios.parseFlavorFromConfiguration(environment);
|
||||
|
||||
// Copy the assets.
|
||||
final Depfile assetDepfile = await copyAssets(
|
||||
@ -542,7 +543,7 @@ abstract class IosAssetBundle extends Target {
|
||||
environment.buildDir.childFile('native_assets.json'),
|
||||
),
|
||||
},
|
||||
flavor: environment.defines[kFlavor],
|
||||
flavor: flavor,
|
||||
);
|
||||
environment.depFileService.writeToFile(
|
||||
assetDepfile,
|
||||
|
@ -12,6 +12,7 @@ import '../../base/process.dart';
|
||||
import '../../build_info.dart';
|
||||
import '../../devfs.dart';
|
||||
import '../../globals.dart' as globals show xcode;
|
||||
import '../../project.dart';
|
||||
import '../build_system.dart';
|
||||
import '../depfile.dart';
|
||||
import '../exceptions.dart';
|
||||
@ -487,12 +488,15 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
.childDirectory('flutter_assets');
|
||||
assetDirectory.createSync(recursive: true);
|
||||
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
|
||||
final String? flavor = await flutterProject.macos.parseFlavorFromConfiguration(environment);
|
||||
|
||||
final Depfile assetDepfile = await copyAssets(
|
||||
environment,
|
||||
assetDirectory,
|
||||
targetPlatform: TargetPlatform.darwin,
|
||||
buildMode: buildMode,
|
||||
flavor: environment.defines[kFlavor],
|
||||
flavor: flavor,
|
||||
additionalContent: <String, DevFSContent>{
|
||||
'NativeAssetsManifest.json': DevFSFileContent(
|
||||
environment.buildDir.childFile('native_assets.json'),
|
||||
|
@ -1418,20 +1418,6 @@ abstract class FlutterCommand extends Command<void> {
|
||||
final String? defaultFlavor = project.manifest.defaultFlavor;
|
||||
final String? cliFlavor = argParser.options.containsKey('flavor') ? stringArg('flavor') : null;
|
||||
final String? flavor = cliFlavor ?? defaultFlavor;
|
||||
if (flavor != null) {
|
||||
if (globals.platform.environment['FLUTTER_APP_FLAVOR'] != null) {
|
||||
throwToolExit(
|
||||
'FLUTTER_APP_FLAVOR is used by the framework and cannot be set in the environment.',
|
||||
);
|
||||
}
|
||||
if (dartDefines.any((String define) => define.startsWith('FLUTTER_APP_FLAVOR'))) {
|
||||
throwToolExit(
|
||||
'FLUTTER_APP_FLAVOR is used by the framework and cannot be '
|
||||
'set using --${FlutterOptions.kDartDefinesOption} or --${FlutterOptions.kDartDefineFromFileOption}',
|
||||
);
|
||||
}
|
||||
dartDefines.add('FLUTTER_APP_FLAVOR=$flavor');
|
||||
}
|
||||
|
||||
return BuildInfo(
|
||||
buildMode,
|
||||
|
@ -7,6 +7,7 @@ import 'base/file_system.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'base/version.dart';
|
||||
import 'build_info.dart';
|
||||
import 'build_system/build_system.dart';
|
||||
import 'bundle.dart' as bundle;
|
||||
import 'convert.dart';
|
||||
import 'features.dart';
|
||||
@ -283,6 +284,42 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// When flutter assemble runs within an Xcode run script, it does not know
|
||||
/// the scheme and therefore doesn't know what flavor is being used. This
|
||||
/// makes a best effort to parse the scheme name from the [kXcodeConfiguration].
|
||||
/// Most flavor's [kXcodeConfiguration] should follow the naming convention
|
||||
/// of '$baseConfiguration-$scheme'. This is only semi-enforced by
|
||||
/// [buildXcodeProject], so it may not work. Also check if separated by a
|
||||
/// space instead of a `-`. Once parsed, match it with a scheme/flavor name.
|
||||
/// If the flavor cannot be parsed or matched, use the [kFlavor] environment
|
||||
/// variable, which may or may not be set/correct, as a fallback.
|
||||
Future<String?> parseFlavorFromConfiguration(Environment environment) async {
|
||||
final String? configuration = environment.defines[kXcodeConfiguration];
|
||||
final String? flavor = environment.defines[kFlavor];
|
||||
if (configuration == null) {
|
||||
return flavor;
|
||||
}
|
||||
List<String> splitConfiguration = configuration.split('-');
|
||||
if (splitConfiguration.length == 1) {
|
||||
splitConfiguration = configuration.split(' ');
|
||||
}
|
||||
if (splitConfiguration.length == 1) {
|
||||
return flavor;
|
||||
}
|
||||
final String parsedScheme = splitConfiguration[1];
|
||||
|
||||
final XcodeProjectInfo? info = await projectInfo();
|
||||
if (info == null) {
|
||||
return flavor;
|
||||
}
|
||||
for (final String schemeName in info.schemes) {
|
||||
if (schemeName.toLowerCase() == parsedScheme.toLowerCase()) {
|
||||
return schemeName;
|
||||
}
|
||||
}
|
||||
return flavor;
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the iOS sub-project of a Flutter project.
|
||||
|
@ -13,6 +13,8 @@ import 'package:flutter_tools/src/build_system/exceptions.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/common.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/ios.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../../src/common.dart';
|
||||
import '../../../src/context.dart';
|
||||
@ -346,7 +348,7 @@ void main() {
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
});
|
||||
|
||||
testWithoutContext(
|
||||
testUsingContext(
|
||||
'KernelSnapshot forces platform linking on debug for darwin target platforms',
|
||||
() async {
|
||||
fileSystem.file('.dart_tool/package_config.json')
|
||||
@ -395,6 +397,214 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'KernelSnapshot sets flavor in dartDefines if found in environment variable for non ios/darwin app',
|
||||
() async {
|
||||
fileSystem.file('.dart_tool/package_config.json')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"configVersion": 2, "packages":[]}');
|
||||
final String build = androidEnvironment.buildDir.path;
|
||||
final String flutterPatchedSdkPath = artifacts.getArtifactPath(
|
||||
Artifact.flutterPatchedSdkPath,
|
||||
platform: TargetPlatform.android,
|
||||
mode: BuildMode.debug,
|
||||
);
|
||||
processManager.addCommands(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
artifacts.getArtifactPath(Artifact.engineDartAotRuntime),
|
||||
artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk),
|
||||
'--sdk-root',
|
||||
'$flutterPatchedSdkPath/',
|
||||
'--target=flutter',
|
||||
'--no-print-incremental-dependencies',
|
||||
'-D$kAppFlavor=strawberry',
|
||||
...buildModeOptions(BuildMode.debug, <String>[]),
|
||||
'--no-link-platform',
|
||||
'--packages',
|
||||
'/.dart_tool/package_config.json',
|
||||
'--output-dill',
|
||||
'$build/app.dill',
|
||||
'--depfile',
|
||||
'$build/kernel_snapshot_program.d',
|
||||
'--incremental',
|
||||
'--initialize-from-dill',
|
||||
'$build/app.dill',
|
||||
'--verbosity=error',
|
||||
'file:///lib/main.dart',
|
||||
],
|
||||
stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n',
|
||||
),
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
"tool exits when $kAppFlavor is already set in user's environment",
|
||||
() async {
|
||||
fileSystem.file('.dart_tool/package_config.json')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"configVersion": 2, "packages":[]}');
|
||||
final Future<void> buildResult = const KernelSnapshot().build(
|
||||
androidEnvironment
|
||||
..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.android)
|
||||
..defines[kBuildMode] = BuildMode.debug.cliName
|
||||
..defines[kFlavor] = 'strawberry'
|
||||
..defines[kTrackWidgetCreation] = 'false',
|
||||
);
|
||||
|
||||
expect(
|
||||
buildResult,
|
||||
throwsToolExit(
|
||||
message: '$kAppFlavor is used by the framework and cannot be set in the environment.',
|
||||
),
|
||||
);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
Platform: () => FakePlatform(environment: <String, String>{kAppFlavor: 'I was already set'}),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'tool exits when $kAppFlavor is set in --dart-define or --dart-define-from-file',
|
||||
() async {
|
||||
fileSystem.file('.dart_tool/package_config.json')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"configVersion": 2, "packages":[]}');
|
||||
final Future<void> buildResult = const KernelSnapshot().build(
|
||||
androidEnvironment
|
||||
..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.android)
|
||||
..defines[kBuildMode] = BuildMode.debug.cliName
|
||||
..defines[kFlavor] = 'strawberry'
|
||||
..defines[kDartDefines] = encodeDartDefines(<String>[kAppFlavor, 'strawberry'])
|
||||
..defines[kTrackWidgetCreation] = 'false',
|
||||
);
|
||||
|
||||
expect(
|
||||
buildResult,
|
||||
throwsToolExit(
|
||||
message:
|
||||
'$kAppFlavor is used by the framework and cannot be set using --dart-define or --dart-define-from-file',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'KernelSnapshot sets flavor in dartDefines from Xcode build configuration if ios app',
|
||||
() async {
|
||||
fileSystem.file('.dart_tool/package_config.json')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"configVersion": 2, "packages":[]}');
|
||||
final String build = iosEnvironment.buildDir.path;
|
||||
final String flutterPatchedSdkPath = artifacts.getArtifactPath(
|
||||
Artifact.flutterPatchedSdkPath,
|
||||
platform: TargetPlatform.ios,
|
||||
mode: BuildMode.debug,
|
||||
);
|
||||
fileSystem.directory('/ios/Runner.xcodeproj').createSync(recursive: true);
|
||||
processManager.addCommands(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
artifacts.getArtifactPath(Artifact.engineDartAotRuntime),
|
||||
artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk),
|
||||
'--sdk-root',
|
||||
'$flutterPatchedSdkPath/',
|
||||
'--target=flutter',
|
||||
'--no-print-incremental-dependencies',
|
||||
'-D$kAppFlavor=chocolate',
|
||||
...buildModeOptions(BuildMode.debug, <String>[]),
|
||||
'--no-link-platform',
|
||||
'--packages',
|
||||
'/.dart_tool/package_config.json',
|
||||
'--output-dill',
|
||||
'$build/app.dill',
|
||||
'--depfile',
|
||||
'$build/kernel_snapshot_program.d',
|
||||
'--incremental',
|
||||
'--initialize-from-dill',
|
||||
'$build/app.dill',
|
||||
'--verbosity=error',
|
||||
'file:///lib/main.dart',
|
||||
],
|
||||
stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n',
|
||||
),
|
||||
]);
|
||||
|
||||
await const KernelSnapshot().build(
|
||||
iosEnvironment
|
||||
..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.ios)
|
||||
..defines[kBuildMode] = BuildMode.debug.cliName
|
||||
..defines[kFlavor] = 'strawberry'
|
||||
..defines[kXcodeConfiguration] = 'Debug-chocolate'
|
||||
..defines[kTrackWidgetCreation] = 'false',
|
||||
);
|
||||
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter:
|
||||
() => FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'chocolate']),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'KernelSnapshot sets flavor in dartDefines from Xcode build configuration if macos app',
|
||||
() async {
|
||||
fileSystem.file('.dart_tool/package_config.json')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{"configVersion": 2, "packages":[]}');
|
||||
final String build = iosEnvironment.buildDir.path;
|
||||
final String flutterPatchedSdkPath = artifacts.getArtifactPath(
|
||||
Artifact.flutterPatchedSdkPath,
|
||||
platform: TargetPlatform.darwin,
|
||||
mode: BuildMode.debug,
|
||||
);
|
||||
fileSystem.directory('/macos/Runner.xcodeproj').createSync(recursive: true);
|
||||
processManager.addCommands(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
artifacts.getArtifactPath(Artifact.engineDartAotRuntime),
|
||||
artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk),
|
||||
'--sdk-root',
|
||||
'$flutterPatchedSdkPath/',
|
||||
'--target=flutter',
|
||||
'--no-print-incremental-dependencies',
|
||||
'-D$kAppFlavor=chocolate',
|
||||
...buildModeOptions(BuildMode.debug, <String>[]),
|
||||
'--packages',
|
||||
'/.dart_tool/package_config.json',
|
||||
'--output-dill',
|
||||
'$build/app.dill',
|
||||
'--depfile',
|
||||
'$build/kernel_snapshot_program.d',
|
||||
'--incremental',
|
||||
'--initialize-from-dill',
|
||||
'$build/app.dill',
|
||||
'--verbosity=error',
|
||||
'file:///lib/main.dart',
|
||||
],
|
||||
stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n',
|
||||
),
|
||||
]);
|
||||
|
||||
await const KernelSnapshot().build(
|
||||
iosEnvironment
|
||||
..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.darwin)
|
||||
..defines[kBuildMode] = BuildMode.debug.cliName
|
||||
..defines[kFlavor] = 'strawberry'
|
||||
..defines[kXcodeConfiguration] = 'Debug-chocolate'
|
||||
..defines[kTrackWidgetCreation] = 'false',
|
||||
);
|
||||
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter:
|
||||
() => FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'chocolate']),
|
||||
},
|
||||
);
|
||||
|
||||
testWithoutContext('KernelSnapshot does use track widget creation on debug builds', () async {
|
||||
fileSystem.file('.dart_tool/package_config.json')
|
||||
..createSync(recursive: true)
|
||||
@ -707,3 +917,17 @@ void main() {
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
});
|
||||
}
|
||||
|
||||
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
||||
FakeXcodeProjectInterpreter({this.isInstalled = true, this.schemes = const <String>['Runner']});
|
||||
|
||||
@override
|
||||
final bool isInstalled;
|
||||
|
||||
List<String> schemes;
|
||||
|
||||
@override
|
||||
Future<XcodeProjectInfo?> getInfo(String projectPath, {String? projectFilename}) async {
|
||||
return XcodeProjectInfo(<String>[], <String>[], schemes, BufferLogger.test());
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ 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';
|
||||
import 'package:flutter_tools/src/build_system/targets/ios.dart';
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:test/fake.dart';
|
||||
import 'package:unified_analytics/unified_analytics.dart';
|
||||
|
||||
import '../../../src/common.dart';
|
||||
@ -267,6 +269,106 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'DebugIosApplicationBundle with flavor',
|
||||
() async {
|
||||
environment.defines[kBuildMode] = 'debug';
|
||||
environment.defines[kCodesignIdentity] = 'ABC123';
|
||||
environment.defines[kFlavor] = 'vanilla';
|
||||
environment.defines[kXcodeConfiguration] = 'Debug-strawberry';
|
||||
fileSystem.directory('/ios/Runner.xcodeproj').createSync(recursive: true);
|
||||
fileSystem.file('pubspec.yaml')
|
||||
..createSync()
|
||||
..writeAsStringSync('''
|
||||
name: example
|
||||
flutter:
|
||||
assets:
|
||||
- assets/common/
|
||||
- path: assets/vanilla/
|
||||
flavors:
|
||||
- vanilla
|
||||
- path: assets/strawberry/
|
||||
flavors:
|
||||
- strawberry
|
||||
''');
|
||||
|
||||
fileSystem.file('assets/common/image.png').createSync(recursive: true);
|
||||
fileSystem.file('assets/vanilla/ice-cream.png').createSync(recursive: true);
|
||||
fileSystem.file('assets/strawberry/ice-cream.png').createSync(recursive: true);
|
||||
// Precompiled dart data
|
||||
fileSystem
|
||||
.file(artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug))
|
||||
.createSync();
|
||||
fileSystem
|
||||
.file(artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug))
|
||||
.createSync();
|
||||
// Project info
|
||||
fileSystem
|
||||
.directory('.dart_tool')
|
||||
.childFile('package_config.json')
|
||||
.createSync(recursive: true);
|
||||
// Plist file
|
||||
fileSystem
|
||||
.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
|
||||
.createSync(recursive: true);
|
||||
// App kernel
|
||||
environment.buildDir.childFile('app.dill').createSync(recursive: true);
|
||||
environment.buildDir.childFile('native_assets.json').createSync();
|
||||
// Stub framework
|
||||
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.addCommands(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'xattr',
|
||||
'-r',
|
||||
'-d',
|
||||
'com.apple.FinderInfo',
|
||||
frameworkDirectoryBinary.path,
|
||||
],
|
||||
),
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'codesign',
|
||||
'--force',
|
||||
'--sign',
|
||||
'ABC123',
|
||||
'--timestamp=none',
|
||||
frameworkDirectoryBinary.path,
|
||||
],
|
||||
),
|
||||
]);
|
||||
await const DebugIosApplicationBundle().build(environment);
|
||||
|
||||
expect(
|
||||
fileSystem.file('${frameworkDirectory.path}/flutter_assets/assets/common/image.png'),
|
||||
exists,
|
||||
);
|
||||
expect(
|
||||
fileSystem.file('${frameworkDirectory.path}/flutter_assets/assets/vanilla/ice-cream.png'),
|
||||
isNot(exists),
|
||||
);
|
||||
expect(
|
||||
fileSystem.file(
|
||||
'${frameworkDirectory.path}/flutter_assets/assets/strawberry/ice-cream.png',
|
||||
),
|
||||
exists,
|
||||
);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
Platform: () => macPlatform,
|
||||
XcodeProjectInterpreter:
|
||||
() => FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'strawberry']),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'DebugIosApplicationBundle with impeller and shader compilation',
|
||||
() async {
|
||||
@ -1058,3 +1160,17 @@ void main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
||||
FakeXcodeProjectInterpreter({this.isInstalled = true, this.schemes = const <String>['Runner']});
|
||||
|
||||
@override
|
||||
final bool isInstalled;
|
||||
|
||||
List<String> schemes;
|
||||
|
||||
@override
|
||||
Future<XcodeProjectInfo?> getInfo(String projectPath, {String? projectFilename}) async {
|
||||
return XcodeProjectInfo(<String>[], <String>[], schemes, BufferLogger.test());
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import 'package:flutter_tools/src/base/logger.dart';
|
||||
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/macos.dart';
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:test/fake.dart';
|
||||
import 'package:unified_analytics/unified_analytics.dart';
|
||||
|
||||
import '../../../src/common.dart';
|
||||
@ -465,6 +467,93 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'debug macOS application copies correct assets with flavor',
|
||||
() async {
|
||||
fileSystem
|
||||
.directory(
|
||||
artifacts.getArtifactPath(Artifact.flutterMacOSFramework, mode: BuildMode.debug),
|
||||
)
|
||||
.createSync();
|
||||
fileSystem
|
||||
.file(
|
||||
artifacts.getArtifactPath(
|
||||
Artifact.vmSnapshotData,
|
||||
platform: TargetPlatform.darwin,
|
||||
mode: BuildMode.debug,
|
||||
),
|
||||
)
|
||||
.createSync(recursive: true);
|
||||
fileSystem
|
||||
.file(
|
||||
artifacts.getArtifactPath(
|
||||
Artifact.isolateSnapshotData,
|
||||
platform: TargetPlatform.darwin,
|
||||
mode: BuildMode.debug,
|
||||
),
|
||||
)
|
||||
.createSync(recursive: true);
|
||||
fileSystem.file('${environment.buildDir.path}/App.framework/App').createSync(recursive: true);
|
||||
|
||||
final String inputKernel = '${environment.buildDir.path}/app.dill';
|
||||
fileSystem.file(inputKernel)
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('testing');
|
||||
environment.buildDir.childFile('native_assets.json').createSync();
|
||||
|
||||
environment.defines[kXcodeConfiguration] = 'Debug-strawberry';
|
||||
fileSystem.directory('/macos/Runner.xcodeproj').createSync(recursive: true);
|
||||
|
||||
fileSystem.file('pubspec.yaml')
|
||||
..createSync()
|
||||
..writeAsStringSync('''
|
||||
name: example
|
||||
flutter:
|
||||
assets:
|
||||
- assets/common/
|
||||
- path: assets/vanilla/
|
||||
flavors:
|
||||
- vanilla
|
||||
- path: assets/strawberry/
|
||||
flavors:
|
||||
- strawberry
|
||||
''');
|
||||
|
||||
fileSystem.file('assets/common/image.png').createSync(recursive: true);
|
||||
fileSystem.file('assets/vanilla/ice-cream.png').createSync(recursive: true);
|
||||
fileSystem.file('assets/strawberry/ice-cream.png').createSync(recursive: true);
|
||||
fileSystem
|
||||
.directory('.dart_tool')
|
||||
.childFile('package_config.json')
|
||||
.createSync(recursive: true);
|
||||
|
||||
await const DebugMacOSBundleFlutterAssets().build(environment);
|
||||
final Directory frameworkDirectory = environment.outputDir.childDirectory(
|
||||
'/App.framework/Versions/A/Resources/',
|
||||
);
|
||||
expect(
|
||||
fileSystem.file('${frameworkDirectory.path}/flutter_assets/assets/common/image.png'),
|
||||
exists,
|
||||
);
|
||||
expect(
|
||||
fileSystem.file('${frameworkDirectory.path}/flutter_assets/assets/vanilla/ice-cream.png'),
|
||||
isNot(exists),
|
||||
);
|
||||
expect(
|
||||
fileSystem.file(
|
||||
'${frameworkDirectory.path}/flutter_assets/assets/strawberry/ice-cream.png',
|
||||
),
|
||||
exists,
|
||||
);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
XcodeProjectInterpreter:
|
||||
() => FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'strawberry']),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'release/profile macOS application has no blob or precompiled runtime',
|
||||
() async {
|
||||
@ -883,3 +972,17 @@ void main() {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
||||
FakeXcodeProjectInterpreter({this.isInstalled = true, this.schemes = const <String>['Runner']});
|
||||
|
||||
@override
|
||||
final bool isInstalled;
|
||||
|
||||
List<String> schemes;
|
||||
|
||||
@override
|
||||
Future<XcodeProjectInfo?> getInfo(String projectPath, {String? projectFilename}) async {
|
||||
return XcodeProjectInfo(<String>[], <String>[], schemes, BufferLogger.test());
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import 'package:flutter_tools/src/base/time.dart';
|
||||
import 'package:flutter_tools/src/base/user_messages.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/run.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/pre_run_validator.dart';
|
||||
@ -1209,108 +1208,12 @@ void main() {
|
||||
});
|
||||
|
||||
group('--flavor', () {
|
||||
late _TestDeviceManager testDeviceManager;
|
||||
late Logger logger;
|
||||
late FileSystem fileSystem;
|
||||
|
||||
setUp(() {
|
||||
logger = BufferLogger.test();
|
||||
testDeviceManager = _TestDeviceManager(logger: logger);
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
});
|
||||
|
||||
testUsingContext(
|
||||
"tool exits when FLUTTER_APP_FLAVOR is already set in user's environment",
|
||||
() async {
|
||||
fileSystem.file('lib/main.dart').createSync(recursive: true);
|
||||
fileSystem.file('pubspec.yaml').createSync();
|
||||
|
||||
final FakeDevice device = FakeDevice(
|
||||
'name',
|
||||
'id',
|
||||
type: PlatformType.android,
|
||||
supportsFlavors: true,
|
||||
);
|
||||
testDeviceManager.devices = <Device>[device];
|
||||
final _TestRunCommandThatOnlyValidates command = _TestRunCommandThatOnlyValidates();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
|
||||
expect(
|
||||
runner.run(<String>['run', '--no-pub', '--no-hot', '--flavor=strawberry']),
|
||||
throwsToolExit(
|
||||
message:
|
||||
'FLUTTER_APP_FLAVOR is used by the framework and cannot be set in the environment.',
|
||||
),
|
||||
);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
DeviceManager: () => testDeviceManager,
|
||||
Platform:
|
||||
() => FakePlatform(
|
||||
environment: <String, String>{'FLUTTER_APP_FLAVOR': 'I was already set'},
|
||||
),
|
||||
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'tool exits when FLUTTER_APP_FLAVOR is set in --dart-define or --dart-define-from-file',
|
||||
() async {
|
||||
fileSystem.file('lib/main.dart').createSync(recursive: true);
|
||||
fileSystem.file('pubspec.yaml').createSync();
|
||||
fileSystem.file('config.json')
|
||||
..createSync()
|
||||
..writeAsStringSync('{"FLUTTER_APP_FLAVOR": "strawberry"}');
|
||||
|
||||
final FakeDevice device = FakeDevice(
|
||||
'name',
|
||||
'id',
|
||||
type: PlatformType.android,
|
||||
supportsFlavors: true,
|
||||
);
|
||||
testDeviceManager.devices = <Device>[device];
|
||||
final _TestRunCommandThatOnlyValidates command = _TestRunCommandThatOnlyValidates();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
|
||||
expect(
|
||||
runner.run(<String>[
|
||||
'run',
|
||||
'--dart-define=FLUTTER_APP_FLAVOR=strawberry',
|
||||
'--no-pub',
|
||||
'--no-hot',
|
||||
'--flavor=strawberry',
|
||||
]),
|
||||
throwsToolExit(
|
||||
message:
|
||||
'FLUTTER_APP_FLAVOR is used by the framework and cannot be set using --dart-define or --dart-define-from-file',
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
runner.run(<String>[
|
||||
'run',
|
||||
'--dart-define-from-file=config.json',
|
||||
'--no-pub',
|
||||
'--no-hot',
|
||||
'--flavor=strawberry',
|
||||
]),
|
||||
throwsToolExit(
|
||||
message:
|
||||
'FLUTTER_APP_FLAVOR is used by the framework and cannot be set using --dart-define or --dart-define-from-file',
|
||||
),
|
||||
);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
DeviceManager: () => testDeviceManager,
|
||||
Platform: () => FakePlatform(),
|
||||
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'CLI option overrides default flavor from manifest',
|
||||
() async {
|
||||
@ -1475,22 +1378,3 @@ class FakeClock extends Fake implements SystemClock {
|
||||
return DateTime.fromMillisecondsSinceEpoch(times.removeAt(0));
|
||||
}
|
||||
}
|
||||
|
||||
class _TestDeviceManager extends DeviceManager {
|
||||
_TestDeviceManager({required super.logger});
|
||||
List<Device> devices = <Device>[];
|
||||
|
||||
@override
|
||||
List<DeviceDiscovery> get deviceDiscoverers {
|
||||
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
|
||||
devices.forEach(discoverer.addDevice);
|
||||
return <DeviceDiscovery>[discoverer];
|
||||
}
|
||||
}
|
||||
|
||||
class _TestRunCommandThatOnlyValidates extends RunCommand {
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
return FlutterCommandResult.success();
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ void main() {
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dConfiguration=$buildMode',
|
||||
'-dIosArchs=',
|
||||
'-dSdkRoot=',
|
||||
'-dSplitDebugInfo=',
|
||||
@ -139,7 +140,7 @@ void main() {
|
||||
'ARCHS': archs,
|
||||
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||
'CODE_SIGNING_REQUIRED': 'YES',
|
||||
'CONFIGURATION': buildMode,
|
||||
'CONFIGURATION': '$buildMode-strawberry',
|
||||
'DART_DEFINES': dartDefines,
|
||||
'DART_OBFUSCATION': dartObfuscation,
|
||||
'EXPANDED_CODE_SIGN_IDENTITY': expandedCodeSignIdentity,
|
||||
@ -165,6 +166,7 @@ void main() {
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dFlavor=strawberry',
|
||||
'-dConfiguration=$buildMode-strawberry',
|
||||
'-dIosArchs=$archs',
|
||||
'-dSdkRoot=$sdkRoot',
|
||||
'-dSplitDebugInfo=$splitDebugInfo',
|
||||
@ -284,6 +286,7 @@ void main() {
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dConfiguration=$buildMode',
|
||||
'-dIosArchs=',
|
||||
'-dSdkRoot=',
|
||||
'-dSplitDebugInfo=',
|
||||
@ -330,7 +333,6 @@ void main() {
|
||||
'ARCHS': archs,
|
||||
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||
'CODE_SIGNING_REQUIRED': 'YES',
|
||||
'CONFIGURATION': buildMode,
|
||||
'DART_DEFINES': dartDefines,
|
||||
'DART_OBFUSCATION': dartObfuscation,
|
||||
'EXPANDED_CODE_SIGN_IDENTITY': expandedCodeSignIdentity,
|
||||
@ -340,6 +342,7 @@ void main() {
|
||||
'FRONTEND_SERVER_STARTER_PATH': frontendServerStarterPath,
|
||||
'INFOPLIST_PATH': 'Info.plist',
|
||||
'SDKROOT': sdkRoot,
|
||||
'CONFIGURATION': '$buildMode-strawberry',
|
||||
'FLAVOR': 'strawberry',
|
||||
'SPLIT_DEBUG_INFO': splitDebugInfo,
|
||||
'TRACK_WIDGET_CREATION': trackWidgetCreation,
|
||||
@ -356,6 +359,7 @@ void main() {
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dFlavor=strawberry',
|
||||
'-dConfiguration=$buildMode-strawberry',
|
||||
'-dIosArchs=$archs',
|
||||
'-dSdkRoot=$sdkRoot',
|
||||
'-dSplitDebugInfo=$splitDebugInfo',
|
||||
@ -406,6 +410,7 @@ void main() {
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dConfiguration=$buildMode',
|
||||
'-dIosArchs=arm64',
|
||||
'-dSdkRoot=',
|
||||
'-dSplitDebugInfo=',
|
||||
@ -456,6 +461,7 @@ void main() {
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dConfiguration=$buildMode',
|
||||
'-dIosArchs=arm64',
|
||||
'-dSdkRoot=',
|
||||
'-dSplitDebugInfo=',
|
||||
@ -505,6 +511,7 @@ void main() {
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dConfiguration=$buildMode',
|
||||
'-dIosArchs=arm64 x86_64',
|
||||
'-dSdkRoot=',
|
||||
'-dSplitDebugInfo=',
|
||||
|
@ -4,9 +4,12 @@
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/version.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/features.dart';
|
||||
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
@ -216,6 +219,102 @@ void main() {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('parseFlavorFromConfiguration', () {
|
||||
testWithoutContext('from FLAVOR when CONFIGURATION is null', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs));
|
||||
final Environment env = Environment.test(
|
||||
fs.currentDirectory,
|
||||
fileSystem: fs,
|
||||
logger: BufferLogger.test(),
|
||||
artifacts: Artifacts.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
defines: <String, String>{kFlavor: 'strawberry'},
|
||||
);
|
||||
expect(await project.parseFlavorFromConfiguration(env), 'strawberry');
|
||||
});
|
||||
|
||||
testWithoutContext('from FLAVOR when CONFIGURATION is does not contain delimiter', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs));
|
||||
final Environment env = Environment.test(
|
||||
fs.currentDirectory,
|
||||
fileSystem: fs,
|
||||
logger: BufferLogger.test(),
|
||||
artifacts: Artifacts.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
defines: <String, String>{kFlavor: 'strawberry', kXcodeConfiguration: 'Debug'},
|
||||
);
|
||||
expect(await project.parseFlavorFromConfiguration(env), 'strawberry');
|
||||
});
|
||||
|
||||
testUsingContext(
|
||||
'from CONFIGURATION when has flavor following a hyphen that matches a scheme',
|
||||
() async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs));
|
||||
final Environment env = Environment.test(
|
||||
fs.currentDirectory,
|
||||
fileSystem: fs,
|
||||
logger: BufferLogger.test(),
|
||||
artifacts: Artifacts.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
defines: <String, String>{kFlavor: 'strawberry', kXcodeConfiguration: 'Debug-vanilla'},
|
||||
);
|
||||
project.xcodeProject.createSync(recursive: true);
|
||||
expect(await project.parseFlavorFromConfiguration(env), 'vanilla');
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter:
|
||||
() => FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'vanilla']),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'from CONFIGURATION when has flavor following a space that matches a scheme',
|
||||
() async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs));
|
||||
final Environment env = Environment.test(
|
||||
fs.currentDirectory,
|
||||
fileSystem: fs,
|
||||
logger: BufferLogger.test(),
|
||||
artifacts: Artifacts.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
defines: <String, String>{kFlavor: 'strawberry', kXcodeConfiguration: 'Debug vanilla'},
|
||||
);
|
||||
project.xcodeProject.createSync(recursive: true);
|
||||
expect(await project.parseFlavorFromConfiguration(env), 'vanilla');
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter:
|
||||
() => FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'vanilla']),
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'from FLAVOR when CONFIGURATION does not match a scheme',
|
||||
() async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(FakeFlutterProject(fileSystem: fs));
|
||||
final Environment env = Environment.test(
|
||||
fs.currentDirectory,
|
||||
fileSystem: fs,
|
||||
logger: BufferLogger.test(),
|
||||
artifacts: Artifacts.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
defines: <String, String>{kFlavor: 'strawberry', kXcodeConfiguration: 'Debug-random'},
|
||||
);
|
||||
project.xcodeProject.createSync(recursive: true);
|
||||
expect(await project.parseFlavorFromConfiguration(env), 'strawberry');
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter:
|
||||
() => FakeXcodeProjectInterpreter(schemes: <String>['Runner', 'vanilla']),
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('MacOSProject', () {
|
||||
@ -365,7 +464,11 @@ class FakeFlutterProject extends Fake implements FlutterProject {
|
||||
}
|
||||
|
||||
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
||||
FakeXcodeProjectInterpreter({this.isInstalled = true, this.version});
|
||||
FakeXcodeProjectInterpreter({
|
||||
this.isInstalled = true,
|
||||
this.version,
|
||||
this.schemes = const <String>['Runner'],
|
||||
});
|
||||
|
||||
@override
|
||||
final bool isInstalled;
|
||||
@ -373,9 +476,11 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete
|
||||
@override
|
||||
final Version? version;
|
||||
|
||||
List<String> schemes;
|
||||
|
||||
@override
|
||||
Future<XcodeProjectInfo?> getInfo(String projectPath, {String? projectFilename}) async {
|
||||
return XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], BufferLogger.test());
|
||||
return XcodeProjectInfo(<String>[], <String>[], schemes, BufferLogger.test());
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user