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

You can run iOS/macOS Flutter apps through either the Flutter CLI or Xcode. However, it's often required to first run the Flutter CLI to generate required files and settings. Some of these settings/files (like dev dependencies) are specific to the build mode. However, you can change the build mode through Xcode too. When you change the build mode through Xcode, the Flutter-generated files and setting may not be correct. This PR checks if the current build mode matches the one last used by the Flutter CLI. If it doesn't, it'll print a warning like this:  If the build action is `install`, which indicates the app is being built for distribution, this will print as an error and fail the build:  ## 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]. - [ ] 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
589 lines
18 KiB
Dart
589 lines
18 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 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
|
|
import '../src/common.dart';
|
|
import 'test_utils.dart';
|
|
|
|
void main() {
|
|
late Directory tempDir;
|
|
late Directory projectDir;
|
|
|
|
setUpAll(() async {
|
|
tempDir = createResolvedTempDirectorySync('xcode_dev_dependencies_test.');
|
|
projectDir = tempDir.childDirectory('project')..createSync();
|
|
final Directory tempPluginADir = tempDir.childDirectory('plugin_a')..createSync();
|
|
|
|
// Create a Flutter project.
|
|
await processManager.run(<String>[
|
|
flutterBin,
|
|
'create',
|
|
projectDir.path,
|
|
'--project-name=testapp',
|
|
], workingDirectory: projectDir.path);
|
|
|
|
// Create a Flutter plugin to add as a dev dependency to the Flutter project.
|
|
await processManager.run(<String>[
|
|
flutterBin,
|
|
'create',
|
|
tempPluginADir.path,
|
|
'--template=plugin',
|
|
'--project-name=plugin_a',
|
|
'--platforms=ios,macos',
|
|
], workingDirectory: tempPluginADir.path);
|
|
|
|
// Add a dev dependency on plugin_a
|
|
await processManager.run(<String>[
|
|
flutterBin,
|
|
'pub',
|
|
'add',
|
|
'dev:plugin_a',
|
|
'--path',
|
|
tempPluginADir.path,
|
|
], workingDirectory: projectDir.path);
|
|
});
|
|
|
|
tearDownAll(() {
|
|
tryToDelete(tempDir);
|
|
});
|
|
|
|
group(
|
|
'Xcode build iOS app',
|
|
() {
|
|
test(
|
|
'succeeds when Flutter CLI last used configuration matches Xcode configuration',
|
|
() async {
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'ios',
|
|
'--config-only',
|
|
'--debug',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'-workspace',
|
|
'ios/Runner.xcworkspace',
|
|
'-scheme',
|
|
'Runner',
|
|
'-destination',
|
|
'generic/platform=iOS Simulator',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Debug',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(xcodeResult, const ProcessResultMatcher(stdoutPattern: '** BUILD SUCCEEDED **'));
|
|
},
|
|
);
|
|
|
|
test(
|
|
'fails if Flutter CLI last used configuration does not match Xcode configuration when archiving',
|
|
() async {
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'ios',
|
|
'--config-only',
|
|
'--debug',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'archive',
|
|
'-workspace',
|
|
'ios/Runner.xcworkspace',
|
|
'-scheme',
|
|
'Runner',
|
|
'-destination',
|
|
'generic/platform=iOS',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Release',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(
|
|
xcodeResult,
|
|
const ProcessResultMatcher(
|
|
exitCode: 65,
|
|
stdoutPattern: 'error: Your Flutter project is currently configured for debug mode.',
|
|
stderrPattern: '** ARCHIVE FAILED **',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'warns if Flutter CLI last used configuration does not match Xcode configuration when building',
|
|
() async {
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'ios',
|
|
'--config-only',
|
|
'--release',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'-workspace',
|
|
'ios/Runner.xcworkspace',
|
|
'-scheme',
|
|
'Runner',
|
|
'-destination',
|
|
'generic/platform=iOS',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Debug',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(
|
|
xcodeResult,
|
|
const ProcessResultMatcher(
|
|
stdoutPattern:
|
|
'warning: Your Flutter project is currently configured for release mode.',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
skip: !platform.isMacOS, // [intended] iOS builds only work on macos.
|
|
);
|
|
|
|
group(
|
|
'Xcode build iOS module',
|
|
() {
|
|
test(
|
|
'succeeds when Flutter CLI last used configuration matches Xcode configuration',
|
|
() async {
|
|
final Directory moduleDirectory = projectDir.childDirectory('hello');
|
|
await processManager.run(<String>[
|
|
flutterBin,
|
|
'create',
|
|
moduleDirectory.path,
|
|
'--template=module',
|
|
'--project-name=hello',
|
|
], workingDirectory: projectDir.path);
|
|
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'ios',
|
|
'--config-only',
|
|
'--debug',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: moduleDirectory.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final Directory hostAppDirectory = projectDir.childDirectory('hello_host_app');
|
|
hostAppDirectory.createSync();
|
|
|
|
copyDirectory(
|
|
fileSystem.directory(
|
|
fileSystem.path.join(getFlutterRoot(), 'dev', 'integration_tests', 'ios_host_app'),
|
|
),
|
|
hostAppDirectory,
|
|
);
|
|
|
|
final ProcessResult podResult = await processManager.run(
|
|
const <String>['pod', 'install'],
|
|
workingDirectory: hostAppDirectory.path,
|
|
environment: const <String, String>{'LANG': 'en_US.UTF-8'},
|
|
);
|
|
|
|
expect(podResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'-workspace',
|
|
'Host.xcworkspace',
|
|
'-scheme',
|
|
'Host',
|
|
'-destination',
|
|
'generic/platform=iOS',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Debug',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: hostAppDirectory.path,
|
|
);
|
|
|
|
expect(xcodeResult, const ProcessResultMatcher(stdoutPattern: '** BUILD SUCCEEDED **'));
|
|
},
|
|
);
|
|
|
|
test(
|
|
'fails if Flutter CLI last used configuration does not match Xcode configuration when archiving',
|
|
() async {
|
|
final Directory moduleDirectory = projectDir.childDirectory('hello');
|
|
await processManager.run(<String>[
|
|
flutterBin,
|
|
'create',
|
|
moduleDirectory.path,
|
|
'--template=module',
|
|
'--project-name=hello',
|
|
], workingDirectory: projectDir.path);
|
|
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'ios',
|
|
'--config-only',
|
|
'--debug',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: moduleDirectory.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final Directory hostAppDirectory = projectDir.childDirectory('hello_host_app');
|
|
hostAppDirectory.createSync();
|
|
|
|
copyDirectory(
|
|
fileSystem.directory(
|
|
fileSystem.path.join(getFlutterRoot(), 'dev', 'integration_tests', 'ios_host_app'),
|
|
),
|
|
hostAppDirectory,
|
|
);
|
|
|
|
final ProcessResult podResult = await processManager.run(
|
|
const <String>['pod', 'install'],
|
|
workingDirectory: hostAppDirectory.path,
|
|
environment: const <String, String>{'LANG': 'en_US.UTF-8'},
|
|
);
|
|
|
|
expect(podResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'archive',
|
|
'-workspace',
|
|
'Host.xcworkspace',
|
|
'-scheme',
|
|
'Host',
|
|
'-destination',
|
|
'generic/platform=iOS',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Release',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: hostAppDirectory.path,
|
|
);
|
|
|
|
expect(
|
|
xcodeResult,
|
|
const ProcessResultMatcher(
|
|
exitCode: 65,
|
|
stdoutPattern: 'error: Your Flutter project is currently configured for debug mode.',
|
|
stderrPattern: '** ARCHIVE FAILED **',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'warns if Flutter CLI last used configuration does not match Xcode configuration when building',
|
|
() async {
|
|
final Directory moduleDirectory = projectDir.childDirectory('hello');
|
|
await processManager.run(<String>[
|
|
flutterBin,
|
|
'create',
|
|
moduleDirectory.path,
|
|
'--template=module',
|
|
'--project-name=hello',
|
|
], workingDirectory: projectDir.path);
|
|
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'ios',
|
|
'--config-only',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: moduleDirectory.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final Directory hostAppDirectory = projectDir.childDirectory('hello_host_app');
|
|
hostAppDirectory.createSync();
|
|
|
|
copyDirectory(
|
|
fileSystem.directory(
|
|
fileSystem.path.join(getFlutterRoot(), 'dev', 'integration_tests', 'ios_host_app'),
|
|
),
|
|
hostAppDirectory,
|
|
);
|
|
|
|
final ProcessResult podResult = await processManager.run(
|
|
const <String>['pod', 'install'],
|
|
workingDirectory: hostAppDirectory.path,
|
|
environment: const <String, String>{'LANG': 'en_US.UTF-8'},
|
|
);
|
|
|
|
expect(podResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'-workspace',
|
|
'Host.xcworkspace',
|
|
'-scheme',
|
|
'Host',
|
|
'-destination',
|
|
'generic/platform=iOS',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Debug',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: hostAppDirectory.path,
|
|
);
|
|
|
|
expect(
|
|
xcodeResult,
|
|
const ProcessResultMatcher(
|
|
stdoutPattern:
|
|
'warning: Your Flutter project is currently configured for release mode.',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
skip: !platform.isMacOS, // [intended] iOS builds only work on macos.
|
|
);
|
|
|
|
group(
|
|
'Xcode build macOS app',
|
|
() {
|
|
test(
|
|
'succeeds when Flutter CLI last used configuration matches Xcode configuration',
|
|
() async {
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'macos',
|
|
'--config-only',
|
|
'--debug',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'-workspace',
|
|
'macos/Runner.xcworkspace',
|
|
'-scheme',
|
|
'Runner',
|
|
'-destination',
|
|
'platform=macOS',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Debug',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(xcodeResult, const ProcessResultMatcher(stdoutPattern: '** BUILD SUCCEEDED **'));
|
|
},
|
|
);
|
|
|
|
test(
|
|
'fails if Flutter CLI last used configuration does not match Xcode configuration when archiving',
|
|
() async {
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'macos',
|
|
'--config-only',
|
|
'--debug',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'archive',
|
|
'-workspace',
|
|
'macos/Runner.xcworkspace',
|
|
'-scheme',
|
|
'Runner',
|
|
'-destination',
|
|
'platform=macOS',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Release',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(
|
|
xcodeResult,
|
|
const ProcessResultMatcher(
|
|
exitCode: 65,
|
|
stdoutPattern: 'error: Your Flutter project is currently configured for debug mode.',
|
|
stderrPattern: '** ARCHIVE FAILED **',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'warns if Flutter CLI last used configuration does not match Xcode configuration when building',
|
|
() async {
|
|
final List<String> flutterCommand = <String>[
|
|
flutterBin,
|
|
...getLocalEngineArguments(),
|
|
'build',
|
|
'macos',
|
|
'--config-only',
|
|
'--release',
|
|
];
|
|
final ProcessResult flutterResult = await processManager.run(
|
|
flutterCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(flutterResult, const ProcessResultMatcher());
|
|
|
|
final List<String> xcodeCommand = <String>[
|
|
'xcodebuild',
|
|
'-workspace',
|
|
'macos/Runner.xcworkspace',
|
|
'-scheme',
|
|
'Runner',
|
|
'-destination',
|
|
'platform=macOS',
|
|
'CODE_SIGNING_ALLOWED=NO',
|
|
'CODE_SIGNING_REQUIRED=NO',
|
|
'CODE_SIGN_IDENTITY=-',
|
|
'EXPANDED_CODE_SIGN_IDENTITY=-',
|
|
'COMPILER_INDEX_STORE_ENABLE=NO',
|
|
'VERBOSE_SCRIPT_LOGGING=true',
|
|
'-configuration',
|
|
'Debug',
|
|
];
|
|
final ProcessResult xcodeResult = await processManager.run(
|
|
xcodeCommand,
|
|
workingDirectory: projectDir.path,
|
|
);
|
|
|
|
expect(
|
|
xcodeResult,
|
|
const ProcessResultMatcher(
|
|
stdoutPattern:
|
|
'warning: Your Flutter project is currently configured for release mode.',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
skip: !platform.isMacOS, // [intended] iOS builds only work on macos.
|
|
);
|
|
}
|