Reland double gzip wrapping NOTICES to reduce on-disk installed space (#80897)
This commit is contained in:
parent
a8e41f8206
commit
ff2dde2c7f
@ -18,6 +18,7 @@ dependencies:
|
|||||||
|
|
||||||
_fe_analyzer_shared: 21.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
_fe_analyzer_shared: 21.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
analyzer: 1.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
analyzer: 1.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
archive: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
async: 2.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
async: 2.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
boolean_selector: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
boolean_selector: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
charcode: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
charcode: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
@ -61,4 +62,4 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test_api: 0.3.0
|
test_api: 0.3.0
|
||||||
|
|
||||||
# PUBSPEC CHECKSUM: 4292
|
# PUBSPEC CHECKSUM: a6c4
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
import 'package:flutter_devicelab/framework/apk_utils.dart';
|
import 'package:flutter_devicelab/framework/apk_utils.dart';
|
||||||
import 'package:flutter_devicelab/framework/framework.dart';
|
import 'package:flutter_devicelab/framework/framework.dart';
|
||||||
import 'package:flutter_devicelab/framework/task_result.dart';
|
import 'package:flutter_devicelab/framework/task_result.dart';
|
||||||
@ -314,6 +317,24 @@ Future<void> main() async {
|
|||||||
'lib/armeabi-v7a/libflutter.so',
|
'lib/armeabi-v7a/libflutter.so',
|
||||||
], await getFilesInApk(releaseHostApk));
|
], await getFilesInApk(releaseHostApk));
|
||||||
|
|
||||||
|
section('Check the NOTICE file is correct');
|
||||||
|
|
||||||
|
await inDirectory(hostApp, () async {
|
||||||
|
final File apkFile = File(releaseHostApk);
|
||||||
|
final Archive apk = ZipDecoder().decodeBytes(apkFile.readAsBytesSync());
|
||||||
|
// Shouldn't be missing since we already checked it exists above.
|
||||||
|
final ArchiveFile noticesFile = apk.findFile('assets/flutter_assets/NOTICES.Z');
|
||||||
|
|
||||||
|
final Uint8List licenseData = noticesFile.content as Uint8List;
|
||||||
|
if (licenseData == null) {
|
||||||
|
return TaskResult.failure('Invalid license file.');
|
||||||
|
}
|
||||||
|
final String licenseString = utf8.decode(gzip.decode(licenseData));
|
||||||
|
if (!licenseString.contains('skia') || !licenseString.contains('Flutter Authors')) {
|
||||||
|
return TaskResult.failure('License content missing.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
section('Check release AndroidManifest.xml');
|
section('Check release AndroidManifest.xml');
|
||||||
|
|
||||||
final String androidManifestRelease = await getAndroidManifest(debugHostApk);
|
final String androidManifestRelease = await getAndroidManifest(debugHostApk);
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_devicelab/framework/framework.dart';
|
import 'package:flutter_devicelab/framework/framework.dart';
|
||||||
import 'package:flutter_devicelab/framework/host_agent.dart';
|
import 'package:flutter_devicelab/framework/host_agent.dart';
|
||||||
@ -214,6 +216,8 @@ Future<void> main() async {
|
|||||||
|
|
||||||
final File objectiveCAnalyticsOutputFile = File(path.join(tempDir.path, 'analytics-objc.log'));
|
final File objectiveCAnalyticsOutputFile = File(path.join(tempDir.path, 'analytics-objc.log'));
|
||||||
final Directory objectiveCBuildDirectory = Directory(path.join(tempDir.path, 'build-objc'));
|
final Directory objectiveCBuildDirectory = Directory(path.join(tempDir.path, 'build-objc'));
|
||||||
|
|
||||||
|
section('Build iOS Objective-C host app');
|
||||||
await inDirectory(objectiveCHostApp, () async {
|
await inDirectory(objectiveCHostApp, () async {
|
||||||
await exec(
|
await exec(
|
||||||
'pod',
|
'pod',
|
||||||
@ -282,6 +286,28 @@ Future<void> main() async {
|
|||||||
'isolate_snapshot_data',
|
'isolate_snapshot_data',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
section('Check the NOTICE file is correct');
|
||||||
|
|
||||||
|
final String licenseFilePath = path.join(
|
||||||
|
objectiveCBuildDirectory.path,
|
||||||
|
'Host.app',
|
||||||
|
'Frameworks',
|
||||||
|
'App.framework',
|
||||||
|
'flutter_assets',
|
||||||
|
'NOTICES.Z',
|
||||||
|
);
|
||||||
|
checkFileExists(licenseFilePath);
|
||||||
|
|
||||||
|
await inDirectory(objectiveCBuildDirectory, () async {
|
||||||
|
final Uint8List licenseData = File(licenseFilePath).readAsBytesSync();
|
||||||
|
final String licenseString = utf8.decode(gzip.decode(licenseData));
|
||||||
|
if (!licenseString.contains('skia') || !licenseString.contains('Flutter Authors')) {
|
||||||
|
return TaskResult.failure('License content missing');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
section('Check that the host build sends the correct analytics');
|
||||||
|
|
||||||
final String objectiveCAnalyticsOutput = objectiveCAnalyticsOutputFile.readAsStringSync();
|
final String objectiveCAnalyticsOutput = objectiveCAnalyticsOutputFile.readAsStringSync();
|
||||||
if (!objectiveCAnalyticsOutput.contains('cd24: ios')
|
if (!objectiveCAnalyticsOutput.contains('cd24: ios')
|
||||||
|| !objectiveCAnalyticsOutput.contains('cd25: true')
|
|| !objectiveCAnalyticsOutput.contains('cd25: true')
|
||||||
|
@ -13,7 +13,7 @@ final String platformLineSep = Platform.isWindows ? '\r\n' : '\n';
|
|||||||
|
|
||||||
final List<String> flutterAssets = <String>[
|
final List<String> flutterAssets = <String>[
|
||||||
'assets/flutter_assets/AssetManifest.json',
|
'assets/flutter_assets/AssetManifest.json',
|
||||||
'assets/flutter_assets/NOTICES',
|
'assets/flutter_assets/NOTICES.Z',
|
||||||
'assets/flutter_assets/fonts/MaterialIcons-Regular.otf',
|
'assets/flutter_assets/fonts/MaterialIcons-Regular.otf',
|
||||||
'assets/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf',
|
'assets/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf',
|
||||||
];
|
];
|
||||||
|
@ -1186,7 +1186,7 @@ class CompileTest {
|
|||||||
|
|
||||||
final _UnzipListEntry libflutter = fileToMetadata['lib/armeabi-v7a/libflutter.so'];
|
final _UnzipListEntry libflutter = fileToMetadata['lib/armeabi-v7a/libflutter.so'];
|
||||||
final _UnzipListEntry libapp = fileToMetadata['lib/armeabi-v7a/libapp.so'];
|
final _UnzipListEntry libapp = fileToMetadata['lib/armeabi-v7a/libapp.so'];
|
||||||
final _UnzipListEntry license = fileToMetadata['assets/flutter_assets/NOTICES'];
|
final _UnzipListEntry license = fileToMetadata['assets/flutter_assets/NOTICES.Z'];
|
||||||
|
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'libflutter_uncompressed_bytes': libflutter.uncompressedSize,
|
'libflutter_uncompressed_bytes': libflutter.uncompressedSize,
|
||||||
|
@ -7,6 +7,7 @@ environment:
|
|||||||
sdk: ">=2.3.0 <3.0.0"
|
sdk: ">=2.3.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
archive: 3.1.2
|
||||||
args: 2.1.0
|
args: 2.1.0
|
||||||
file: 6.1.0
|
file: 6.1.0
|
||||||
http: 0.13.1
|
http: 0.13.1
|
||||||
@ -20,6 +21,7 @@ dependencies:
|
|||||||
|
|
||||||
charcode: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
charcode: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
collection: 1.15.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
collection: 1.15.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
crypto: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
http_parser: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
http_parser: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
pedantic: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
pedantic: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
source_span: 1.8.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
source_span: 1.8.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
@ -37,7 +39,6 @@ dev_dependencies:
|
|||||||
cli_util: 0.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
cli_util: 0.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
convert: 3.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
convert: 3.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
coverage: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
coverage: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
crypto: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
|
||||||
glob: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
glob: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
http_multi_server: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
http_multi_server: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
io: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
io: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
@ -63,4 +64,4 @@ dev_dependencies:
|
|||||||
webkit_inspection_protocol: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
webkit_inspection_protocol: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
yaml: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
yaml: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
|
||||||
# PUBSPEC CHECKSUM: 4292
|
# PUBSPEC CHECKSUM: a6c4
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
@ -104,7 +106,21 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
|
|||||||
// TODO(ianh): Remove this complexity once these bugs are fixed.
|
// TODO(ianh): Remove this complexity once these bugs are fixed.
|
||||||
final Completer<String> rawLicenses = Completer<String>();
|
final Completer<String> rawLicenses = Completer<String>();
|
||||||
scheduleTask(() async {
|
scheduleTask(() async {
|
||||||
rawLicenses.complete(await rootBundle.loadString('NOTICES', cache: false));
|
rawLicenses.complete(
|
||||||
|
kIsWeb
|
||||||
|
// NOTICES for web isn't compressed since we don't have access to
|
||||||
|
// dart:io on the client side and it's already compressed between
|
||||||
|
// the server and client.
|
||||||
|
? rootBundle.loadString('NOTICES', cache: false)
|
||||||
|
: () async {
|
||||||
|
// The compressed version doesn't have a more common .gz extension
|
||||||
|
// because gradle for Android non-transparently manipulates .gz files.
|
||||||
|
final ByteData licenseBytes = await rootBundle.load('NOTICES.Z');
|
||||||
|
List<int> bytes = licenseBytes.buffer.asUint8List();
|
||||||
|
bytes = gzip.decode(bytes);
|
||||||
|
return utf8.decode(bytes);
|
||||||
|
}()
|
||||||
|
);
|
||||||
}, Priority.animation);
|
}, Priority.animation);
|
||||||
await rawLicenses.future;
|
await rawLicenses.future;
|
||||||
final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
|
final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
|
||||||
|
@ -15,10 +15,10 @@ class TestAssetBundle extends CachingAssetBundle {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ByteData> load(String key) async {
|
Future<ByteData> load(String key) async {
|
||||||
|
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
|
||||||
if (key == 'AssetManifest.json')
|
if (key == 'AssetManifest.json')
|
||||||
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert('{"one": ["one"]}')).buffer);
|
return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert('{"one": ["one"]}')).buffer);
|
||||||
|
|
||||||
loadCallCount[key] = loadCallCount[key] ?? 0 + 1;
|
|
||||||
if (key == 'one')
|
if (key == 'one')
|
||||||
return ByteData(1)..setInt8(0, 49);
|
return ByteData(1)..setInt8(0, 49);
|
||||||
throw FlutterError('key not found');
|
throw FlutterError('key not found');
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -45,6 +49,15 @@ class TestBinding extends BindingBase with SchedulerBinding, ServicesBinding {
|
|||||||
return const StringCodec().encodeMessage(licenses);
|
return const StringCodec().encodeMessage(licenses);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
})
|
||||||
|
..setMockMessageHandler('flutter/assets', (ByteData? message) async {
|
||||||
|
if (const StringCodec().decodeMessage(message) == 'NOTICES.Z' && !kIsWeb) {
|
||||||
|
return Uint8List.fromList(gzip.encode(utf8.encode(licenses))).buffer.asByteData();
|
||||||
|
}
|
||||||
|
if (const StringCodec().decodeMessage(message) == 'NOTICES' && kIsWeb) {
|
||||||
|
return const StringCodec().encodeMessage(licenses);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import 'package:flutter_tools/src/base/context.dart';
|
|||||||
import 'package:flutter_tools/src/base/file_system.dart' as libfs;
|
import 'package:flutter_tools/src/base/file_system.dart' as libfs;
|
||||||
import 'package:flutter_tools/src/base/io.dart';
|
import 'package:flutter_tools/src/base/io.dart';
|
||||||
import 'package:flutter_tools/src/bundle.dart';
|
import 'package:flutter_tools/src/bundle.dart';
|
||||||
|
import 'package:flutter_tools/src/build_info.dart';
|
||||||
import 'package:flutter_tools/src/cache.dart';
|
import 'package:flutter_tools/src/cache.dart';
|
||||||
import 'package:flutter_tools/src/context_runner.dart';
|
import 'package:flutter_tools/src/context_runner.dart';
|
||||||
import 'package:flutter_tools/src/devfs.dart';
|
import 'package:flutter_tools/src/devfs.dart';
|
||||||
@ -61,6 +62,7 @@ Future<void> run(List<String> args) async {
|
|||||||
manifestPath: argResults[_kOptionManifest] as String ?? defaultManifestPath,
|
manifestPath: argResults[_kOptionManifest] as String ?? defaultManifestPath,
|
||||||
assetDirPath: assetDir,
|
assetDirPath: assetDir,
|
||||||
packagesPath: argResults[_kOptionPackages] as String,
|
packagesPath: argResults[_kOptionPackages] as String,
|
||||||
|
targetPlatform: TargetPlatform.fuchsia_arm64 // This is not arch specific.
|
||||||
);
|
);
|
||||||
|
|
||||||
if (assets == null) {
|
if (assets == null) {
|
||||||
|
@ -82,6 +82,7 @@ abstract class AssetBundle {
|
|||||||
String assetDirPath,
|
String assetDirPath,
|
||||||
@required String packagesPath,
|
@required String packagesPath,
|
||||||
bool deferredComponentsEnabled = false,
|
bool deferredComponentsEnabled = false,
|
||||||
|
TargetPlatform targetPlatform,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +142,13 @@ class ManifestAssetBundle implements AssetBundle {
|
|||||||
|
|
||||||
static const String _kAssetManifestJson = 'AssetManifest.json';
|
static const String _kAssetManifestJson = 'AssetManifest.json';
|
||||||
static const String _kNoticeFile = 'NOTICES';
|
static const String _kNoticeFile = 'NOTICES';
|
||||||
|
// Comically, this can't be name with the more common .gz file extension
|
||||||
|
// because when it's part of an AAR and brought into another APK via gradle,
|
||||||
|
// gradle individually traverses all the files of the AAR and unzips .gz
|
||||||
|
// files (b/37117906). A less common .Z extension still describes how the
|
||||||
|
// file is formatted if users want to manually inspect the application
|
||||||
|
// bundle and is recognized by default file handlers on OS such as macOS.˚
|
||||||
|
static const String _kNoticeZippedFile = 'NOTICES.Z';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool wasBuiltOnce() => _lastBuildTimestamp != null;
|
bool wasBuiltOnce() => _lastBuildTimestamp != null;
|
||||||
@ -180,6 +188,7 @@ class ManifestAssetBundle implements AssetBundle {
|
|||||||
String assetDirPath,
|
String assetDirPath,
|
||||||
@required String packagesPath,
|
@required String packagesPath,
|
||||||
bool deferredComponentsEnabled = false,
|
bool deferredComponentsEnabled = false,
|
||||||
|
TargetPlatform targetPlatform,
|
||||||
}) async {
|
}) async {
|
||||||
assetDirPath ??= getAssetBuildDirectory();
|
assetDirPath ??= getAssetBuildDirectory();
|
||||||
FlutterProject flutterProject;
|
FlutterProject flutterProject;
|
||||||
@ -407,7 +416,6 @@ class ManifestAssetBundle implements AssetBundle {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final DevFSStringContent licenses = DevFSStringContent(licenseResult.combinedLicenses);
|
|
||||||
additionalDependencies = licenseResult.dependencies;
|
additionalDependencies = licenseResult.dependencies;
|
||||||
|
|
||||||
if (wildcardDirectories.isNotEmpty) {
|
if (wildcardDirectories.isNotEmpty) {
|
||||||
@ -425,7 +433,7 @@ class ManifestAssetBundle implements AssetBundle {
|
|||||||
|
|
||||||
_setIfChanged(_kAssetManifestJson, assetManifest);
|
_setIfChanged(_kAssetManifestJson, assetManifest);
|
||||||
_setIfChanged(kFontManifestJson, fontManifest);
|
_setIfChanged(kFontManifestJson, fontManifest);
|
||||||
_setIfChanged(_kNoticeFile, licenses);
|
_setLicenseIfChanged(licenseResult.combinedLicenses, targetPlatform);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,6 +451,35 @@ class ManifestAssetBundle implements AssetBundle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setLicenseIfChanged(
|
||||||
|
String combinedLicenses,
|
||||||
|
TargetPlatform targetPlatform,
|
||||||
|
) {
|
||||||
|
// On the web, don't compress the NOTICES file since the client doesn't have
|
||||||
|
// dart:io to decompress it. So use the standard _setIfChanged to check if
|
||||||
|
// the strings still match.
|
||||||
|
if (targetPlatform == TargetPlatform.web_javascript) {
|
||||||
|
_setIfChanged(_kNoticeFile, DevFSStringContent(combinedLicenses));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On other platforms, let the NOTICES file be compressed. But use a
|
||||||
|
// specialized DevFSStringCompressingBytesContent class to compare
|
||||||
|
// the uncompressed strings to not incur decompression/decoding while making
|
||||||
|
// the comparison.
|
||||||
|
if (!entries.containsKey(_kNoticeZippedFile) ||
|
||||||
|
!(entries[_kNoticeZippedFile] as DevFSStringCompressingBytesContent)
|
||||||
|
.equals(combinedLicenses)) {
|
||||||
|
entries[_kNoticeZippedFile] = DevFSStringCompressingBytesContent(
|
||||||
|
combinedLicenses,
|
||||||
|
// A zlib dictionary is a hinting string sequence with the most
|
||||||
|
// likely string occurrences at the end. This ends up just being
|
||||||
|
// common English words with domain specific words like copyright.
|
||||||
|
hintString: 'copyrightsoftwaretothisinandorofthe',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<_Asset> _getMaterialAssets() {
|
List<_Asset> _getMaterialAssets() {
|
||||||
final List<_Asset> result = <_Asset>[];
|
final List<_Asset> result = <_Asset>[];
|
||||||
for (final Map<String, Object> family in kMaterialFonts) {
|
for (final Map<String, Object> family in kMaterialFonts) {
|
||||||
|
@ -102,7 +102,8 @@ export 'dart:io'
|
|||||||
systemEncoding,
|
systemEncoding,
|
||||||
WebSocket,
|
WebSocket,
|
||||||
WebSocketException,
|
WebSocketException,
|
||||||
WebSocketTransformer;
|
WebSocketTransformer,
|
||||||
|
ZLibEncoder;
|
||||||
|
|
||||||
/// Exits the process with the given [exitCode].
|
/// Exits the process with the given [exitCode].
|
||||||
typedef ExitFunction = void Function(int exitCode);
|
typedef ExitFunction = void Function(int exitCode);
|
||||||
|
@ -58,6 +58,7 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
|
|||||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||||
assetDirPath: null,
|
assetDirPath: null,
|
||||||
deferredComponentsEnabled: environment.defines[kDeferredComponents] == 'true',
|
deferredComponentsEnabled: environment.defines[kDeferredComponents] == 'true',
|
||||||
|
targetPlatform: targetPlatform,
|
||||||
);
|
);
|
||||||
if (resultCode != 0) {
|
if (resultCode != 0) {
|
||||||
throw Exception('Failed to bundle asset files.');
|
throw Exception('Failed to bundle asset files.');
|
||||||
|
@ -182,6 +182,7 @@ Future<AssetBundle> buildAssets({
|
|||||||
String manifestPath,
|
String manifestPath,
|
||||||
String assetDirPath,
|
String assetDirPath,
|
||||||
@required String packagesPath,
|
@required String packagesPath,
|
||||||
|
TargetPlatform targetPlatform,
|
||||||
}) async {
|
}) async {
|
||||||
assetDirPath ??= getAssetBuildDirectory();
|
assetDirPath ??= getAssetBuildDirectory();
|
||||||
packagesPath ??= globals.fs.path.absolute(packagesPath);
|
packagesPath ??= globals.fs.path.absolute(packagesPath);
|
||||||
@ -192,6 +193,7 @@ Future<AssetBundle> buildAssets({
|
|||||||
manifestPath: manifestPath,
|
manifestPath: manifestPath,
|
||||||
assetDirPath: assetDirPath,
|
assetDirPath: assetDirPath,
|
||||||
packagesPath: packagesPath,
|
packagesPath: packagesPath,
|
||||||
|
targetPlatform: targetPlatform,
|
||||||
);
|
);
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -203,6 +203,61 @@ class DevFSStringContent extends DevFSByteContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A string compressing DevFSContent.
|
||||||
|
///
|
||||||
|
/// A specialized DevFSContent similar to DevFSByteContent where the contents
|
||||||
|
/// are the compressed bytes of a string. Its difference is that the original
|
||||||
|
/// uncompressed string can be compared with directly without the indirection
|
||||||
|
/// of a compute-expensive uncompress/decode and compress/encode to compare
|
||||||
|
/// the strings.
|
||||||
|
///
|
||||||
|
/// The `hintString` parameter is a zlib dictionary hinting mechanism to suggest
|
||||||
|
/// the most common string occurrences to potentially assist with compression.
|
||||||
|
class DevFSStringCompressingBytesContent extends DevFSContent {
|
||||||
|
DevFSStringCompressingBytesContent(this._string, { String hintString })
|
||||||
|
: _compressor = ZLibEncoder(
|
||||||
|
dictionary: hintString == null
|
||||||
|
? null
|
||||||
|
: utf8.encode(hintString),
|
||||||
|
gzip: true,
|
||||||
|
level: 9,
|
||||||
|
);
|
||||||
|
|
||||||
|
final String _string;
|
||||||
|
final ZLibEncoder _compressor;
|
||||||
|
final DateTime _modificationTime = DateTime.now();
|
||||||
|
|
||||||
|
List<int> _bytes;
|
||||||
|
bool _isModified = true;
|
||||||
|
|
||||||
|
List<int> get bytes => _bytes ??= _compressor.convert(utf8.encode(_string));
|
||||||
|
|
||||||
|
/// Return true only once so that the content is written to the device only once.
|
||||||
|
@override
|
||||||
|
bool get isModified {
|
||||||
|
final bool modified = _isModified;
|
||||||
|
_isModified = false;
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isModifiedAfter(DateTime time) {
|
||||||
|
return time == null || _modificationTime.isAfter(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get size => bytes.length;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<int>> contentsAsBytes() async => bytes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<int>> contentsAsStream() => Stream<List<int>>.value(bytes);
|
||||||
|
|
||||||
|
/// This checks the source string with another string.
|
||||||
|
bool equals(String string) => _string == string;
|
||||||
|
}
|
||||||
|
|
||||||
class DevFSException implements Exception {
|
class DevFSException implements Exception {
|
||||||
DevFSException(this.message, [this.error, this.stackTrace]);
|
DevFSException(this.message, [this.error, this.stackTrace]);
|
||||||
final String message;
|
final String message;
|
||||||
|
@ -483,7 +483,10 @@ class ResidentWebRunner extends ResidentRunner {
|
|||||||
final bool rebuildBundle = assetBundle.needsBuild();
|
final bool rebuildBundle = assetBundle.needsBuild();
|
||||||
if (rebuildBundle) {
|
if (rebuildBundle) {
|
||||||
_logger.printTrace('Updating assets');
|
_logger.printTrace('Updating assets');
|
||||||
final int result = await assetBundle.build(packagesPath: debuggingOptions.buildInfo.packagesPath);
|
final int result = await assetBundle.build(
|
||||||
|
packagesPath: debuggingOptions.buildInfo.packagesPath,
|
||||||
|
targetPlatform: TargetPlatform.web_javascript,
|
||||||
|
);
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
return UpdateFSReport(success: false);
|
return UpdateFSReport(success: false);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ flutter:
|
|||||||
|
|
||||||
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/AssetManifest.json'), exists);
|
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/AssetManifest.json'), exists);
|
||||||
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/FontManifest.json'), exists);
|
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/FontManifest.json'), exists);
|
||||||
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/NOTICES'), exists);
|
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/NOTICES.Z'), exists);
|
||||||
// See https://github.com/flutter/flutter/issues/35293
|
// See https://github.com/flutter/flutter/issues/35293
|
||||||
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/foo/bar.png'), exists);
|
expect(fileSystem.file('${environment.buildDir.path}/flutter_assets/assets/foo/bar.png'), exists);
|
||||||
// See https://github.com/flutter/flutter/issues/46163
|
// See https://github.com/flutter/flutter/issues/46163
|
||||||
|
@ -111,6 +111,16 @@ void main() {
|
|||||||
expect(content.isModified, isFalse);
|
expect(content.isModified, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWithoutContext('DevFSStringCompressingBytesContent', () {
|
||||||
|
final DevFSStringCompressingBytesContent content =
|
||||||
|
DevFSStringCompressingBytesContent('uncompressed string');
|
||||||
|
|
||||||
|
expect(content.equals('uncompressed string'), isTrue);
|
||||||
|
expect(content.bytes, isNotNull);
|
||||||
|
expect(content.isModified, isTrue);
|
||||||
|
expect(content.isModified, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
testWithoutContext('DevFS create throws a DevFSException when vmservice disconnects unexpectedly', () async {
|
testWithoutContext('DevFS create throws a DevFSException when vmservice disconnects unexpectedly', () async {
|
||||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
final OperatingSystemUtils osUtils = MockOperatingSystemUtils();
|
final OperatingSystemUtils osUtils = MockOperatingSystemUtils();
|
||||||
|
@ -132,7 +132,7 @@ flutter:
|
|||||||
'C:/build/flutter_assets/assets/foo.png',
|
'C:/build/flutter_assets/assets/foo.png',
|
||||||
'C:/build/flutter_assets/AssetManifest.json',
|
'C:/build/flutter_assets/AssetManifest.json',
|
||||||
'C:/build/flutter_assets/FontManifest.json',
|
'C:/build/flutter_assets/FontManifest.json',
|
||||||
'C:/build/flutter_assets/NOTICES',
|
'C:/build/flutter_assets/NOTICES.Z',
|
||||||
'C:/winuwp/flutter/ephemeral/flutter_windows_winuwp.dll',
|
'C:/winuwp/flutter/ephemeral/flutter_windows_winuwp.dll',
|
||||||
'C:/winuwp/flutter/ephemeral/flutter_windows_winuwp.dll.pdb',
|
'C:/winuwp/flutter/ephemeral/flutter_windows_winuwp.dll.pdb',
|
||||||
'C:/winuwp/flutter/ephemeral/icudtl.dat'
|
'C:/winuwp/flutter/ephemeral/icudtl.dat'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user