[tools]build ipa validate launch image using template files (#116242)
* [tools]build ipa validate launch image using template files * reuse more code by sharing the same file key * fix space
This commit is contained in:
parent
0234b18f88
commit
be81e9eae7
@ -46,10 +46,13 @@ Future<void> main() async {
|
|||||||
throw TaskResult.failure('Must validate incorrect app icon image size.');
|
throw TaskResult.failure('Must validate incorrect app icon image size.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// The project is still using Flutter template icon.
|
// The project is still using Flutter template icon and launch image.
|
||||||
if (!output.contains('Warning: App icon is set to the default placeholder icon. Replace with unique icons.')) {
|
if (!output.contains('Warning: App icon is set to the default placeholder icon. Replace with unique icons.')) {
|
||||||
throw TaskResult.failure('Must validate template app icon.');
|
throw TaskResult.failure('Must validate template app icon.');
|
||||||
}
|
}
|
||||||
|
if (!output.contains('Warning: Launch image is set to the default placeholder. Replace with unique launch images.')) {
|
||||||
|
throw TaskResult.failure('Must validate template launch image.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final String archivePath = path.join(
|
final String archivePath = path.join(
|
||||||
|
@ -57,30 +57,32 @@ class BuildIOSCommand extends _BuildIOSSubCommand {
|
|||||||
Directory _outputAppDirectory(String xcodeResultOutput) => globals.fs.directory(xcodeResultOutput).parent;
|
Directory _outputAppDirectory(String xcodeResultOutput) => globals.fs.directory(xcodeResultOutput).parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The key that uniquely identifies an image file in an app icon asset.
|
/// The key that uniquely identifies an image file in an image asset.
|
||||||
/// It consists of (idiom, size, scale).
|
/// It consists of (idiom, scale, size?), where size is present for app icon
|
||||||
|
/// asset, and null for launch image asset.
|
||||||
@immutable
|
@immutable
|
||||||
class _AppIconImageFileKey {
|
class _ImageAssetFileKey {
|
||||||
const _AppIconImageFileKey(this.idiom, this.size, this.scale);
|
const _ImageAssetFileKey(this.idiom, this.scale, this.size);
|
||||||
|
|
||||||
/// The idiom (iphone or ipad).
|
/// The idiom (iphone or ipad).
|
||||||
final String idiom;
|
final String idiom;
|
||||||
/// The logical size in point (e.g. 83.5).
|
|
||||||
final double size;
|
|
||||||
/// The scale factor (e.g. 2).
|
/// The scale factor (e.g. 2).
|
||||||
final int scale;
|
final int scale;
|
||||||
|
/// The logical size in point (e.g. 83.5).
|
||||||
|
/// Size is present for app icon, and null for launch image.
|
||||||
|
final double? size;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(idiom, size, scale);
|
int get hashCode => Object.hash(idiom, scale, size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => other is _AppIconImageFileKey
|
bool operator ==(Object other) => other is _ImageAssetFileKey
|
||||||
&& other.idiom == idiom
|
&& other.idiom == idiom
|
||||||
&& other.size == size
|
&& other.scale == scale
|
||||||
&& other.scale == scale;
|
&& other.size == size;
|
||||||
|
|
||||||
/// The pixel size.
|
/// The pixel size based on logical size and scale.
|
||||||
int get pixelSize => (size * scale).toInt(); // pixel size must be an int.
|
int? get pixelSize => size == null ? null : (size! * scale).toInt(); // pixel size must be an int.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds an .xcarchive and optionally .ipa for an iOS app to be generated for
|
/// Builds an .xcarchive and optionally .ipa for an iOS app to be generated for
|
||||||
@ -159,11 +161,16 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
|
|||||||
return super.validateCommand();
|
return super.validateCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses Contents.json into a map, with the key to be _AppIconImageFileKey, and value to be the icon image file name.
|
// A helper function to parse Contents.json of an image asset into a map,
|
||||||
Map<_AppIconImageFileKey, String> _parseIconContentsJson(String contentsJsonDirName) {
|
// with the key to be _ImageAssetFileKey, and value to be the image file name.
|
||||||
|
// Some assets have size (e.g. app icon) and others do not (e.g. launch image).
|
||||||
|
Map<_ImageAssetFileKey, String> _parseImageAssetContentsJson(
|
||||||
|
String contentsJsonDirName,
|
||||||
|
{ required bool requiresSize })
|
||||||
|
{
|
||||||
final Directory contentsJsonDirectory = globals.fs.directory(contentsJsonDirName);
|
final Directory contentsJsonDirectory = globals.fs.directory(contentsJsonDirName);
|
||||||
if (!contentsJsonDirectory.existsSync()) {
|
if (!contentsJsonDirectory.existsSync()) {
|
||||||
return <_AppIconImageFileKey, String>{};
|
return <_ImageAssetFileKey, String>{};
|
||||||
}
|
}
|
||||||
final File contentsJsonFile = contentsJsonDirectory.childFile('Contents.json');
|
final File contentsJsonFile = contentsJsonDirectory.childFile('Contents.json');
|
||||||
final Map<String, dynamic> contents = json.decode(contentsJsonFile.readAsStringSync()) as Map<String, dynamic>? ?? <String, dynamic>{};
|
final Map<String, dynamic> contents = json.decode(contentsJsonFile.readAsStringSync()) as Map<String, dynamic>? ?? <String, dynamic>{};
|
||||||
@ -171,10 +178,10 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
|
|||||||
final Map<String, dynamic> info = contents['info'] as Map<String, dynamic>? ?? <String, dynamic>{};
|
final Map<String, dynamic> info = contents['info'] as Map<String, dynamic>? ?? <String, dynamic>{};
|
||||||
if ((info['version'] as int?) != 1) {
|
if ((info['version'] as int?) != 1) {
|
||||||
// Skips validation for unknown format.
|
// Skips validation for unknown format.
|
||||||
return <_AppIconImageFileKey, String>{};
|
return <_ImageAssetFileKey, String>{};
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<_AppIconImageFileKey, String> iconInfo = <_AppIconImageFileKey, String>{};
|
final Map<_ImageAssetFileKey, String> iconInfo = <_ImageAssetFileKey, String>{};
|
||||||
for (final dynamic image in images) {
|
for (final dynamic image in images) {
|
||||||
final Map<String, dynamic> imageMap = image as Map<String, dynamic>;
|
final Map<String, dynamic> imageMap = image as Map<String, dynamic>;
|
||||||
final String? idiom = imageMap['idiom'] as String?;
|
final String? idiom = imageMap['idiom'] as String?;
|
||||||
@ -182,10 +189,15 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
|
|||||||
final String? scale = imageMap['scale'] as String?;
|
final String? scale = imageMap['scale'] as String?;
|
||||||
final String? fileName = imageMap['filename'] as String?;
|
final String? fileName = imageMap['filename'] as String?;
|
||||||
|
|
||||||
if (size == null || idiom == null || scale == null || fileName == null) {
|
// requiresSize must match the actual presence of size in json.
|
||||||
|
if (requiresSize != (size != null)
|
||||||
|
|| idiom == null || scale == null || fileName == null)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final double? parsedSize;
|
||||||
|
if (size != null) {
|
||||||
// for example, "64x64". Parse the width since it is a square.
|
// for example, "64x64". Parse the width since it is a square.
|
||||||
final Iterable<double> parsedSizes = size.split('x')
|
final Iterable<double> parsedSizes = size.split('x')
|
||||||
.map((String element) => double.tryParse(element))
|
.map((String element) => double.tryParse(element))
|
||||||
@ -193,7 +205,10 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
|
|||||||
if (parsedSizes.isEmpty) {
|
if (parsedSizes.isEmpty) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final double parsedSize = parsedSizes.first;
|
parsedSize = parsedSizes.first;
|
||||||
|
} else {
|
||||||
|
parsedSize = null;
|
||||||
|
}
|
||||||
|
|
||||||
// for example, "3x".
|
// for example, "3x".
|
||||||
final Iterable<int> parsedScales = scale.split('x')
|
final Iterable<int> parsedScales = scale.split('x')
|
||||||
@ -203,61 +218,110 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final int parsedScale = parsedScales.first;
|
final int parsedScale = parsedScales.first;
|
||||||
|
iconInfo[_ImageAssetFileKey(idiom, parsedScale, parsedSize)] = fileName;
|
||||||
iconInfo[_AppIconImageFileKey(idiom, parsedSize, parsedScale)] = fileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return iconInfo;
|
return iconInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _validateIconsAfterArchive(StringBuffer messageBuffer) async {
|
// A helper function to check if an image asset is still using template files.
|
||||||
final BuildableIOSApp app = await buildableIOSApp;
|
bool _isAssetStillUsingTemplateFiles({
|
||||||
final String templateIconImageDirName = await app.templateAppIconDirNameForImages;
|
required Map<_ImageAssetFileKey, String> templateImageInfoMap,
|
||||||
|
required Map<_ImageAssetFileKey, String> projectImageInfoMap,
|
||||||
final Map<_AppIconImageFileKey, String> templateIconMap = _parseIconContentsJson(app.templateAppIconDirNameForContentsJson);
|
required String templateImageDirName,
|
||||||
final Map<_AppIconImageFileKey, String> projectIconMap = _parseIconContentsJson(app.projectAppIconDirName);
|
required String projectImageDirName,
|
||||||
|
}) {
|
||||||
// validate each of the project icon images.
|
return projectImageInfoMap.entries.any((MapEntry<_ImageAssetFileKey, String> entry) {
|
||||||
final List<String> filesWithTemplateIcon = <String>[];
|
final String projectFileName = entry.value;
|
||||||
final List<String> filesWithWrongSize = <String>[];
|
final String? templateFileName = templateImageInfoMap[entry.key];
|
||||||
for (final MapEntry<_AppIconImageFileKey, String> entry in projectIconMap.entries) {
|
if (templateFileName == null) {
|
||||||
final String projectIconFileName = entry.value;
|
return false;
|
||||||
final String? templateIconFileName = templateIconMap[entry.key];
|
|
||||||
final File projectIconFile = globals.fs.file(globals.fs.path.join(app.projectAppIconDirName, projectIconFileName));
|
|
||||||
if (!projectIconFile.existsSync()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
final Uint8List projectIconBytes = projectIconFile.readAsBytesSync();
|
final File projectFile = globals.fs.file(
|
||||||
|
globals.fs.path.join(projectImageDirName, projectFileName));
|
||||||
|
final File templateFile = globals.fs.file(
|
||||||
|
globals.fs.path.join(templateImageDirName, templateFileName));
|
||||||
|
|
||||||
// validate conflict with template icon file.
|
return projectFile.existsSync()
|
||||||
if (templateIconFileName != null) {
|
&& templateFile.existsSync()
|
||||||
final File templateIconFile = globals.fs.file(globals.fs.path.join(
|
&& md5.convert(projectFile.readAsBytesSync()) ==
|
||||||
templateIconImageDirName, templateIconFileName));
|
md5.convert(templateFile.readAsBytesSync());
|
||||||
if (templateIconFile.existsSync() && md5.convert(projectIconBytes) ==
|
});
|
||||||
md5.convert(templateIconFile.readAsBytesSync())) {
|
|
||||||
filesWithTemplateIcon.add(entry.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A helper function to return a list of image files in an image asset with
|
||||||
|
// wrong sizes (as specified in its Contents.json file).
|
||||||
|
List<String> _imageFilesWithWrongSize({
|
||||||
|
required Map<_ImageAssetFileKey, String> imageInfoMap,
|
||||||
|
required String imageDirName,
|
||||||
|
}) {
|
||||||
|
return imageInfoMap.entries.where((MapEntry<_ImageAssetFileKey, String> entry) {
|
||||||
|
final String fileName = entry.value;
|
||||||
|
final File imageFile = globals.fs.file(globals.fs.path.join(imageDirName, fileName));
|
||||||
|
if (!imageFile.existsSync()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// validate image size is correct.
|
// validate image size is correct.
|
||||||
// PNG file's width is at byte [16, 20), and height is at byte [20, 24), in big endian format.
|
// PNG file's width is at byte [16, 20), and height is at byte [20, 24), in big endian format.
|
||||||
// Based on https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format
|
// Based on https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format
|
||||||
final ByteData projectIconData = projectIconBytes.buffer.asByteData();
|
final ByteData imageData = imageFile.readAsBytesSync().buffer.asByteData();
|
||||||
if (projectIconData.lengthInBytes < 24) {
|
if (imageData.lengthInBytes < 24) {
|
||||||
continue;
|
return false;
|
||||||
}
|
}
|
||||||
final int width = projectIconData.getInt32(16);
|
final int width = imageData.getInt32(16);
|
||||||
final int height = projectIconData.getInt32(20);
|
final int height = imageData.getInt32(20);
|
||||||
if (width != entry.key.pixelSize || height != entry.key.pixelSize) {
|
// The size must not be null.
|
||||||
filesWithWrongSize.add(entry.value);
|
final int expectedSize = entry.key.pixelSize!;
|
||||||
|
return width != expectedSize || height != expectedSize;
|
||||||
|
})
|
||||||
|
.map((MapEntry<_ImageAssetFileKey, String> entry) => entry.value)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _validateIconAssetsAfterArchive(StringBuffer messageBuffer) async {
|
||||||
|
final BuildableIOSApp app = await buildableIOSApp;
|
||||||
|
|
||||||
|
final Map<_ImageAssetFileKey, String> templateInfoMap = _parseImageAssetContentsJson(
|
||||||
|
app.templateAppIconDirNameForContentsJson,
|
||||||
|
requiresSize: true);
|
||||||
|
final Map<_ImageAssetFileKey, String> projectInfoMap = _parseImageAssetContentsJson(
|
||||||
|
app.projectAppIconDirName,
|
||||||
|
requiresSize: true);
|
||||||
|
|
||||||
|
final bool usesTemplate = _isAssetStillUsingTemplateFiles(
|
||||||
|
templateImageInfoMap: templateInfoMap,
|
||||||
|
projectImageInfoMap: projectInfoMap,
|
||||||
|
templateImageDirName: await app.templateAppIconDirNameForImages,
|
||||||
|
projectImageDirName: app.projectAppIconDirName);
|
||||||
|
if (usesTemplate) {
|
||||||
|
messageBuffer.writeln('\nWarning: App icon is set to the default placeholder icon. Replace with unique icons.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> filesWithWrongSize = _imageFilesWithWrongSize(
|
||||||
|
imageInfoMap: projectInfoMap,
|
||||||
|
imageDirName: app.projectAppIconDirName);
|
||||||
|
if (filesWithWrongSize.isNotEmpty) {
|
||||||
|
messageBuffer.writeln('\nWarning: App icon is using the wrong size (e.g. ${filesWithWrongSize.first}).');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filesWithTemplateIcon.isNotEmpty) {
|
Future<void> _validateLaunchImageAssetsAfterArchive(StringBuffer messageBuffer) async {
|
||||||
messageBuffer.writeln('\nWarning: App icon is set to the default placeholder icon. Replace with unique icons.');
|
final BuildableIOSApp app = await buildableIOSApp;
|
||||||
}
|
|
||||||
if (filesWithWrongSize.isNotEmpty) {
|
final Map<_ImageAssetFileKey, String> templateInfoMap = _parseImageAssetContentsJson(
|
||||||
messageBuffer.writeln('\nWarning: App icon is using the wrong size (e.g. ${filesWithWrongSize.first}).');
|
app.templateLaunchImageDirNameForContentsJson,
|
||||||
|
requiresSize: false);
|
||||||
|
final Map<_ImageAssetFileKey, String> projectInfoMap = _parseImageAssetContentsJson(
|
||||||
|
app.projectLaunchImageDirName,
|
||||||
|
requiresSize: false);
|
||||||
|
|
||||||
|
final bool usesTemplate = _isAssetStillUsingTemplateFiles(
|
||||||
|
templateImageInfoMap: templateInfoMap,
|
||||||
|
projectImageInfoMap: projectInfoMap,
|
||||||
|
templateImageDirName: await app.templateLaunchImageDirNameForImages,
|
||||||
|
projectImageDirName: app.projectLaunchImageDirName);
|
||||||
|
|
||||||
|
if (usesTemplate) {
|
||||||
|
messageBuffer.writeln('\nWarning: Launch image is set to the default placeholder. Replace with unique launch images.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +360,9 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
|
|||||||
|
|
||||||
final StringBuffer validationMessageBuffer = StringBuffer();
|
final StringBuffer validationMessageBuffer = StringBuffer();
|
||||||
await _validateXcodeBuildSettingsAfterArchive(validationMessageBuffer);
|
await _validateXcodeBuildSettingsAfterArchive(validationMessageBuffer);
|
||||||
await _validateIconsAfterArchive(validationMessageBuffer);
|
await _validateIconAssetsAfterArchive(validationMessageBuffer);
|
||||||
|
await _validateLaunchImageAssetsAfterArchive(validationMessageBuffer);
|
||||||
|
|
||||||
validationMessageBuffer.write('\nTo update the settings, please refer to https://docs.flutter.dev/deployment/ios');
|
validationMessageBuffer.write('\nTo update the settings, please refer to https://docs.flutter.dev/deployment/ios');
|
||||||
globals.printBox(validationMessageBuffer.toString(), title: 'App Settings');
|
globals.printBox(validationMessageBuffer.toString(), title: 'App Settings');
|
||||||
|
|
||||||
|
@ -153,30 +153,21 @@ class BuildableIOSApp extends IOSApp {
|
|||||||
_hostAppBundleName == null ? 'Runner.app' : _hostAppBundleName!,
|
_hostAppBundleName == null ? 'Runner.app' : _hostAppBundleName!,
|
||||||
'Info.plist');
|
'Info.plist');
|
||||||
|
|
||||||
// Both project icon's image assets and Contents.json are in the same directory.
|
String get projectAppIconDirName => _projectImageAssetDirName(_appIconAsset);
|
||||||
String get projectAppIconDirName => globals.fs.path.join('ios', _appIconDirNameSuffix);
|
|
||||||
|
|
||||||
// template icon's Contents.json is in flutter_tools.
|
String get projectLaunchImageDirName => _projectImageAssetDirName(_launchImageAsset);
|
||||||
String get templateAppIconDirNameForContentsJson => globals.fs.path.join(
|
|
||||||
Cache.flutterRoot!,
|
|
||||||
'packages',
|
|
||||||
'flutter_tools',
|
|
||||||
'templates',
|
|
||||||
'app_shared',
|
|
||||||
'ios.tmpl',
|
|
||||||
_appIconDirNameSuffix,
|
|
||||||
);
|
|
||||||
|
|
||||||
// template icon's image assets are in flutter_template_images package.
|
String get templateAppIconDirNameForContentsJson
|
||||||
Future<String> get templateAppIconDirNameForImages async {
|
=> _templateImageAssetDirNameForContentsJson(_appIconAsset);
|
||||||
final Directory imageTemplate = await templateImageDirectory(null, globals.fs, globals.logger);
|
|
||||||
return globals.fs.path.join(
|
String get templateLaunchImageDirNameForContentsJson
|
||||||
imageTemplate.path,
|
=> _templateImageAssetDirNameForContentsJson(_launchImageAsset);
|
||||||
'app_shared',
|
|
||||||
'ios.tmpl',
|
Future<String> get templateAppIconDirNameForImages async
|
||||||
_appIconDirNameSuffix,
|
=> _templateImageAssetDirNameForImages(_appIconAsset);
|
||||||
);
|
|
||||||
}
|
Future<String> get templateLaunchImageDirNameForImages async
|
||||||
|
=> _templateImageAssetDirNameForImages(_launchImageAsset);
|
||||||
|
|
||||||
String get ipaOutputPath =>
|
String get ipaOutputPath =>
|
||||||
globals.fs.path.join(getIosBuildDirectory(), 'ipa');
|
globals.fs.path.join(getIosBuildDirectory(), 'ipa');
|
||||||
@ -185,10 +176,35 @@ class BuildableIOSApp extends IOSApp {
|
|||||||
return globals.fs.path.join(getIosBuildDirectory(), type, _hostAppBundleName);
|
return globals.fs.path.join(getIosBuildDirectory(), type, _hostAppBundleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
String get _appIconDirNameSuffix => globals.fs.path.join(
|
String _projectImageAssetDirName(String asset)
|
||||||
|
=> globals.fs.path.join('ios', 'Runner', 'Assets.xcassets', asset);
|
||||||
|
|
||||||
|
// Template asset's Contents.json file is in flutter_tools, but the actual
|
||||||
|
String _templateImageAssetDirNameForContentsJson(String asset)
|
||||||
|
=> globals.fs.path.join(
|
||||||
|
Cache.flutterRoot!,
|
||||||
|
'packages',
|
||||||
|
'flutter_tools',
|
||||||
|
'templates',
|
||||||
|
_templateImageAssetDirNameSuffix(asset),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Template asset's images are in flutter_template_images package.
|
||||||
|
Future<String> _templateImageAssetDirNameForImages(String asset) async {
|
||||||
|
final Directory imageTemplate = await templateImageDirectory(null, globals.fs, globals.logger);
|
||||||
|
return globals.fs.path.join(imageTemplate.path, _templateImageAssetDirNameSuffix(asset));
|
||||||
|
}
|
||||||
|
|
||||||
|
String _templateImageAssetDirNameSuffix(String asset) => globals.fs.path.join(
|
||||||
|
'app_shared',
|
||||||
|
'ios.tmpl',
|
||||||
'Runner',
|
'Runner',
|
||||||
'Assets.xcassets',
|
'Assets.xcassets',
|
||||||
'AppIcon.appiconset');
|
asset,
|
||||||
|
);
|
||||||
|
|
||||||
|
String get _appIconAsset => 'AppIcon.appiconset';
|
||||||
|
String get _launchImageAsset => 'LaunchImage.imageset';
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrebuiltIOSApp extends IOSApp implements PrebuiltApplicationPackage {
|
class PrebuiltIOSApp extends IOSApp implements PrebuiltApplicationPackage {
|
||||||
|
@ -1476,6 +1476,162 @@ void main() {
|
|||||||
Platform: () => macosPlatform,
|
Platform: () => macosPlatform,
|
||||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
|
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testUsingContext('Validate template launch images with conflicts', () async {
|
||||||
|
const String projectLaunchImageContentsJsonPath = 'ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json';
|
||||||
|
const String projectLaunchImagePath = 'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png';
|
||||||
|
final String templateLaunchImageContentsJsonPath = '${Cache.flutterRoot!}/packages/flutter_tools/templates/app_shared/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json';
|
||||||
|
const String templateLaunchImagePath = '/flutter_template_images/templates/app_shared/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png';
|
||||||
|
|
||||||
|
fakeProcessManager.addCommands(<FakeCommand>[
|
||||||
|
xattrCommand,
|
||||||
|
setUpFakeXcodeBuildHandler(onRun: () {
|
||||||
|
fileSystem.file(templateLaunchImageContentsJsonPath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('''
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"idiom": "iphone",
|
||||||
|
"filename": "LaunchImage@2x.png",
|
||||||
|
"scale": "2x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
fileSystem.file(templateLaunchImagePath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytes(<int>[1, 2, 3]);
|
||||||
|
|
||||||
|
fileSystem.file(projectLaunchImageContentsJsonPath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('''
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"idiom": "iphone",
|
||||||
|
"filename": "LaunchImage@2x.png",
|
||||||
|
"scale": "2x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
fileSystem.file(projectLaunchImagePath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytes(<int>[1, 2, 3]);
|
||||||
|
}),
|
||||||
|
exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist),
|
||||||
|
]);
|
||||||
|
|
||||||
|
createMinimalMockProjectFiles();
|
||||||
|
|
||||||
|
final BuildCommand command = BuildCommand(
|
||||||
|
androidSdk: FakeAndroidSdk(),
|
||||||
|
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
|
||||||
|
fileSystem: MemoryFileSystem.test(),
|
||||||
|
logger: BufferLogger.test(),
|
||||||
|
osUtils: FakeOperatingSystemUtils(),
|
||||||
|
);
|
||||||
|
await createTestCommandRunner(command).run(
|
||||||
|
<String>['build', 'ipa', '--no-pub']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
testLogger.statusText,
|
||||||
|
contains('Warning: Launch image is set to the default placeholder. Replace with unique launch images.'),
|
||||||
|
);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => fakeProcessManager,
|
||||||
|
Platform: () => macosPlatform,
|
||||||
|
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testUsingContext('Validate template launch images without conflicts', () async {
|
||||||
|
const String projectLaunchImageContentsJsonPath = 'ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json';
|
||||||
|
const String projectLaunchImagePath = 'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png';
|
||||||
|
final String templateLaunchImageContentsJsonPath = '${Cache.flutterRoot!}/packages/flutter_tools/templates/app_shared/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json';
|
||||||
|
const String templateLaunchImagePath = '/flutter_template_images/templates/app_shared/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png';
|
||||||
|
|
||||||
|
fakeProcessManager.addCommands(<FakeCommand>[
|
||||||
|
xattrCommand,
|
||||||
|
setUpFakeXcodeBuildHandler(onRun: () {
|
||||||
|
fileSystem.file(templateLaunchImageContentsJsonPath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('''
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"idiom": "iphone",
|
||||||
|
"filename": "LaunchImage@2x.png",
|
||||||
|
"scale": "2x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
fileSystem.file(templateLaunchImagePath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytes(<int>[1, 2, 3]);
|
||||||
|
|
||||||
|
fileSystem.file(projectLaunchImageContentsJsonPath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('''
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"idiom": "iphone",
|
||||||
|
"filename": "LaunchImage@2x.png",
|
||||||
|
"scale": "2x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
fileSystem.file(projectLaunchImagePath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytes(<int>[4, 5, 6]);
|
||||||
|
}),
|
||||||
|
exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist),
|
||||||
|
]);
|
||||||
|
|
||||||
|
createMinimalMockProjectFiles();
|
||||||
|
|
||||||
|
final BuildCommand command = BuildCommand(
|
||||||
|
androidSdk: FakeAndroidSdk(),
|
||||||
|
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
|
||||||
|
fileSystem: MemoryFileSystem.test(),
|
||||||
|
logger: BufferLogger.test(),
|
||||||
|
osUtils: FakeOperatingSystemUtils(),
|
||||||
|
);
|
||||||
|
await createTestCommandRunner(command).run(
|
||||||
|
<String>['build', 'ipa', '--no-pub']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
testLogger.statusText,
|
||||||
|
isNot(contains('Warning: Launch image is set to the default placeholder. Replace with unique launch images.')),
|
||||||
|
);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => fakeProcessManager,
|
||||||
|
Platform: () => macosPlatform,
|
||||||
|
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -476,6 +476,92 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, overrides: overrides);
|
}, overrides: overrides);
|
||||||
|
|
||||||
|
testUsingContext('returns project launch image dirname', () async {
|
||||||
|
final BuildableIOSApp iosApp = BuildableIOSApp(
|
||||||
|
IosProject.fromFlutter(FlutterProject.fromDirectory(globals.fs.currentDirectory)),
|
||||||
|
'com.foo.bar',
|
||||||
|
'Runner',
|
||||||
|
);
|
||||||
|
final String launchImageDirSuffix = globals.fs.path.join(
|
||||||
|
'Runner',
|
||||||
|
'Assets.xcassets',
|
||||||
|
'LaunchImage.imageset',
|
||||||
|
);
|
||||||
|
expect(iosApp.projectLaunchImageDirName, globals.fs.path.join('ios', launchImageDirSuffix));
|
||||||
|
}, overrides: overrides);
|
||||||
|
|
||||||
|
testUsingContext('returns template launch image dirname for Contents.json', () async {
|
||||||
|
final BuildableIOSApp iosApp = BuildableIOSApp(
|
||||||
|
IosProject.fromFlutter(FlutterProject.fromDirectory(globals.fs.currentDirectory)),
|
||||||
|
'com.foo.bar',
|
||||||
|
'Runner',
|
||||||
|
);
|
||||||
|
final String launchImageDirSuffix = globals.fs.path.join(
|
||||||
|
'Runner',
|
||||||
|
'Assets.xcassets',
|
||||||
|
'LaunchImage.imageset',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
iosApp.templateLaunchImageDirNameForContentsJson,
|
||||||
|
globals.fs.path.join(
|
||||||
|
Cache.flutterRoot!,
|
||||||
|
'packages',
|
||||||
|
'flutter_tools',
|
||||||
|
'templates',
|
||||||
|
'app_shared',
|
||||||
|
'ios.tmpl',
|
||||||
|
launchImageDirSuffix,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, overrides: overrides);
|
||||||
|
|
||||||
|
testUsingContext('returns template launch image dirname for images', () async {
|
||||||
|
final String toolsDir = globals.fs.path.join(
|
||||||
|
Cache.flutterRoot!,
|
||||||
|
'packages',
|
||||||
|
'flutter_tools',
|
||||||
|
);
|
||||||
|
final String packageConfigPath = globals.fs.path.join(
|
||||||
|
toolsDir,
|
||||||
|
'.dart_tool',
|
||||||
|
'package_config.json'
|
||||||
|
);
|
||||||
|
globals.fs.file(packageConfigPath)
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('''
|
||||||
|
{
|
||||||
|
"configVersion": 2,
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "flutter_template_images",
|
||||||
|
"rootUri": "/flutter_template_images",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "2.12"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
final BuildableIOSApp iosApp = BuildableIOSApp(
|
||||||
|
IosProject.fromFlutter(FlutterProject.fromDirectory(globals.fs.currentDirectory)),
|
||||||
|
'com.foo.bar',
|
||||||
|
'Runner');
|
||||||
|
final String launchImageDirSuffix = globals.fs.path.join(
|
||||||
|
'Runner',
|
||||||
|
'Assets.xcassets',
|
||||||
|
'LaunchImage.imageset',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await iosApp.templateLaunchImageDirNameForImages,
|
||||||
|
globals.fs.path.absolute(
|
||||||
|
'flutter_template_images',
|
||||||
|
'templates',
|
||||||
|
'app_shared',
|
||||||
|
'ios.tmpl',
|
||||||
|
launchImageDirSuffix,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, overrides: overrides);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('FuchsiaApp', () {
|
group('FuchsiaApp', () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user