Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions packages/flutter_tools/lib/src/commands/build_ios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import '../convert.dart';
import '../globals.dart' as globals;
import '../ios/application_package.dart';
import '../ios/mac.dart';
import '../ios/plist_parser.dart';
import '../runner/flutter_command.dart';
import 'build.dart';

Expand Down Expand Up @@ -129,12 +130,49 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
return super.validateCommand();
}

Future<void> _validateXcodeBuildSettingsAfterArchive() async {
final BuildableIOSApp app = await buildableIOSApp;

final String plistPath = app.builtInfoPlistPathAfterArchive;

if (!globals.fs.file(plistPath).existsSync()) {
globals.printError('Invalid iOS archive. Does not contain Info.plist.');
return;
}

final Map<String, String?> xcodeProjectSettingsMap = <String, String?>{};

xcodeProjectSettingsMap['Version Number'] = globals.plistParser.getStringValueFromFile(plistPath, PlistParser.kCFBundleShortVersionStringKey);
xcodeProjectSettingsMap['Build Number'] = globals.plistParser.getStringValueFromFile(plistPath, PlistParser.kCFBundleVersionKey);
xcodeProjectSettingsMap['Display Name'] = globals.plistParser.getStringValueFromFile(plistPath, PlistParser.kCFBundleDisplayNameKey);
xcodeProjectSettingsMap['Deployment Target'] = globals.plistParser.getStringValueFromFile(plistPath, PlistParser.kMinimumOSVersionKey);
xcodeProjectSettingsMap['Bundle Identifier'] = globals.plistParser.getStringValueFromFile(plistPath, PlistParser.kCFBundleIdentifierKey);

final StringBuffer buffer = StringBuffer();
xcodeProjectSettingsMap.forEach((String title, String? info) {
buffer.writeln('$title: ${info ?? "Missing"}');
});

final String message;
if (xcodeProjectSettingsMap.values.any((String? element) => element == null)) {
buffer.writeln('\nYou must set up the missing settings');
buffer.write('Instructions: https://docs.flutter.dev/deployment/ios');
message = buffer.toString();
} else {
// remove the new line
message = buffer.toString().trim();
}
globals.printBox(message, title: 'App Settings');
}

@override
Future<FlutterCommandResult> runCommand() async {
final BuildInfo buildInfo = await cachedBuildInfo;
displayNullSafetyMode(buildInfo);
final FlutterCommandResult xcarchiveResult = await super.runCommand();

await _validateXcodeBuildSettingsAfterArchive();

// xcarchive failed or not at expected location.
if (xcarchiveResult.exitStatus != ExitStatus.success) {
globals.printStatus('Skipping IPA.');
Expand Down Expand Up @@ -289,6 +327,7 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand {
/// The result of the Xcode build command. Null until it finishes.
@protected
XcodeBuildResult? xcodeBuildResult;

EnvironmentType get environmentType;
bool get configOnly;

Expand Down
6 changes: 6 additions & 0 deletions packages/flutter_tools/lib/src/ios/application_package.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ class BuildableIOSApp extends IOSApp {
String get archiveBundleOutputPath =>
globals.fs.path.setExtension(archiveBundlePath, '.xcarchive');

String get builtInfoPlistPathAfterArchive => globals.fs.path.join(archiveBundleOutputPath,
'Products',
'Applications',
_hostAppBundleName == null ? 'Runner.app' : _hostAppBundleName!,
'Info.plist');

String get ipaOutputPath =>
globals.fs.path.join(getIosBuildDirectory(), 'ipa');

Expand Down
5 changes: 4 additions & 1 deletion packages/flutter_tools/lib/src/ios/plist_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ class PlistParser {

static const String kCFBundleIdentifierKey = 'CFBundleIdentifier';
static const String kCFBundleShortVersionStringKey = 'CFBundleShortVersionString';
static const String kCFBundleExecutable = 'CFBundleExecutable';
static const String kCFBundleExecutableKey = 'CFBundleExecutable';
static const String kCFBundleVersionKey = 'CFBundleVersion';
static const String kCFBundleDisplayNameKey = 'CFBundleDisplayName';
static const String kMinimumOSVersionKey = 'MinimumOSVersion';

/// Returns the content, converted to XML, of the plist file located at
/// [plistFilePath].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ abstract class MacOSApp extends ApplicationPackage {
}
final Map<String, dynamic> propertyValues = globals.plistParser.parseFile(plistPath);
final String? id = propertyValues[PlistParser.kCFBundleIdentifierKey] as String?;
final String? executableName = propertyValues[PlistParser.kCFBundleExecutable] as String?;
final String? executableName = propertyValues[PlistParser.kCFBundleExecutableKey] as String?;
if (id == null) {
globals.printError('Invalid prebuilt macOS app. Info.plist does not contain bundle identifier');
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/build_ios.dart';
import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:test/fake.dart';

import '../../general.shard/ios/xcresult_test_data.dart';
import '../../src/common.dart';
Expand Down Expand Up @@ -50,10 +52,20 @@ final Platform notMacosPlatform = FakePlatform(
}
);

class FakePlistUtils extends Fake implements PlistParser {
final Map<String, Map<String, Object>> fileContents = <String, Map<String, Object>>{};

@override
String? getStringValueFromFile(String plistFilePath, String key) {
return fileContents[plistFilePath]![key] as String?;
}
}

void main() {
late FileSystem fileSystem;
late TestUsage usage;
late FakeProcessManager fakeProcessManager;
late FakePlistUtils plistUtils;

setUpAll(() {
Cache.disableLocking();
Expand All @@ -63,6 +75,7 @@ void main() {
fileSystem = MemoryFileSystem.test();
usage = TestUsage();
fakeProcessManager = FakeProcessManager.empty();
plistUtils = FakePlistUtils();
});

// Sets up the minimal mock project files necessary to look like a Flutter project.
Expand Down Expand Up @@ -246,8 +259,7 @@ void main() {
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macosPlatform,
XcodeProjectInterpreter: () =>
FakeXcodeProjectInterpreterWithBuildSettings(),
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});

testUsingContext('ipa build fails when --export-options-plist and --export-method are used together', () async {
Expand All @@ -270,8 +282,7 @@ void main() {
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macosPlatform,
XcodeProjectInterpreter: () =>
FakeXcodeProjectInterpreterWithBuildSettings(),
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});

testUsingContext('ipa build reports when IPA fails', () async {
Expand Down Expand Up @@ -521,8 +532,7 @@ void main() {
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macosPlatform,
XcodeProjectInterpreter: () =>
FakeXcodeProjectInterpreterWithBuildSettings(),
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});

testUsingContext('Performs code size analysis and sends analytics', () async {
Expand Down Expand Up @@ -601,8 +611,7 @@ void main() {
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
Platform: () => macosPlatform,
XcodeProjectInterpreter: () =>
FakeXcodeProjectInterpreterWithBuildSettings(),
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});

testUsingContext('Trace error if xcresult is empty.', () async {
Expand Down Expand Up @@ -735,6 +744,97 @@ void main() {
Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});

testUsingContext(
'Validate basic Xcode settings with missing settings', () async {

const String plistPath = 'build/ios/archive/Runner.xcarchive/Products/Applications/Runner.app/Info.plist';
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
setUpFakeXcodeBuildHandler(onRun: () {
fileSystem.file(plistPath).createSync(recursive: true);
}),
exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist),
]);

createMinimalMockProjectFiles();

plistUtils.fileContents[plistPath] = <String,String>{
'CFBundleIdentifier': 'io.flutter.someProject',
};

final BuildCommand command = BuildCommand();
await createTestCommandRunner(command).run(
<String>['build', 'ipa', '--no-pub']);

expect(
testLogger.statusText,
contains(
'┌─ App Settings ────────────────────────────────────────┐\n'
'│ Version Number: Missing │\n'
'│ Build Number: Missing │\n'
'│ Display Name: Missing │\n'
'│ Deployment Target: Missing │\n'
'│ Bundle Identifier: io.flutter.someProject │\n'
'│ │\n'
'│ You must set up the missing settings │\n'
'│ Instructions: https://docs.flutter.dev/deployment/ios │\n'
'└───────────────────────────────────────────────────────┘'
)
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
PlistParser: () => plistUtils,
});

testUsingContext(
'Validate basic Xcode settings with full settings', () async {
const String plistPath = 'build/ios/archive/Runner.xcarchive/Products/Applications/Runner.app/Info.plist';
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
setUpFakeXcodeBuildHandler(onRun: () {
fileSystem.file(plistPath).createSync(recursive: true);
}),
exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist),
]);

createMinimalMockProjectFiles();

plistUtils.fileContents[plistPath] = <String,String>{
'CFBundleIdentifier': 'io.flutter.someProject',
'CFBundleDisplayName': 'Awesome Gallery',
'MinimumOSVersion': '11.0',
'CFBundleVersion': '666',
'CFBundleShortVersionString': '12.34.56',
};

final BuildCommand command = BuildCommand();
await createTestCommandRunner(command).run(
<String>['build', 'ipa', '--no-pub']);

expect(
testLogger.statusText,
contains(
'┌─ App Settings ────────────────────────────┐\n'
'│ Version Number: 12.34.56 │\n'
'│ Build Number: 666 │\n'
'│ Display Name: Awesome Gallery │\n'
'│ Deployment Target: 11.0 │\n'
'│ Bundle Identifier: io.flutter.someProject │\n'
'└───────────────────────────────────────────┘\n'
)
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
PlistParser: () => plistUtils,
});

}

const String _xcBundleFilePath = '/.tmp_rand0/flutter_ios_build_temp_dirrand0/temporary_xcresult_bundle';
Expand Down