Created
August 25, 2020 01:39
-
-
Save GaryQian/1b65918904e9879b76c01f553e41105c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright 2020 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 'dart:convert'; | |
| import 'package:meta/meta.dart'; | |
| import 'package:yaml/yaml.dart'; | |
| import '../base/common.dart'; | |
| import '../base/file_system.dart'; | |
| import '../build_system/build_system.dart'; | |
| import '../globals.dart' as globals; | |
| import '../project.dart'; | |
| import '../runner/flutter_command.dart'; | |
| class FeatureModule { | |
| String name; | |
| List<String> dartLibs; | |
| List<String> assets; | |
| FeatureModule({ | |
| this.name, | |
| this.dartLibs, | |
| this.assets, | |
| }); | |
| } | |
| void setupBundleGradle(Environment env, BuildResult result) { | |
| print('setup Bundle Gradle'); | |
| Directory androidDir = env.projectDir.childDirectory('android'); | |
| List<String> loadingUnits = _getLoadingUnits(env); | |
| compareAgainstGolden(loadingUnits, androidDir); | |
| for (String loadingUnitName in loadingUnits) { | |
| _setupFiles(androidDir, loadingUnitName, result, env); | |
| } | |
| createLoadingUnitGolden(loadingUnits, androidDir); | |
| createBundleConfig(loadingUnits, env); | |
| } | |
| void moveSoToModuleLibs(Environment env, BuildResult result) { | |
| print('moveSoToModuleLibs'); | |
| Directory androidDir = env.projectDir.childDirectory('android'); | |
| _parseBundleConfig(env); | |
| List<String> loadingUnits = _getLoadingUnits(env); | |
| for (String loadingUnitName in loadingUnits) { | |
| _moveSoFiles(androidDir, loadingUnitName, result, env); | |
| } | |
| } | |
| List<String> _getLoadingUnits(Environment env) { | |
| List<String> loadingUnits = []; | |
| List<FileSystemEntity> files = env.outputDir.listSync(recursive: true); | |
| while (files.length != 0) { | |
| if (files.last is File) { | |
| File file = files.last; | |
| String subPath = file.path; | |
| if (!subPath.contains('manifest.json')) { | |
| files.removeLast(); | |
| continue; | |
| } | |
| // Read gen_snapshot manifest | |
| String fileString = file.readAsStringSync(); | |
| Map manifest = jsonDecode(fileString); | |
| // Setup android source directory | |
| print('FINDING MODULES ${file.path}'); | |
| for (Map loadingUnitMetadata in manifest['loadingUnits']) { | |
| if (loadingUnitMetadata['id'] == 1) continue; | |
| loadingUnits.add('module${loadingUnitMetadata['id'].toString()}'); | |
| } | |
| break; | |
| } | |
| files.removeLast(); | |
| } | |
| return loadingUnits; | |
| } | |
| List<FeatureModule> _parseBundleConfig(Environment env) { | |
| File configFile = env.projectDir.childFile('bundle_config.yaml'); | |
| if (!configFile.existsSync()) { | |
| print('No bundle_config.yaml found'); | |
| return []; | |
| } | |
| var yamlData = loadYaml(configFile.readAsStringSync()); | |
| print(yamlData); | |
| List<FeatureModule> results = []; | |
| for (Map module in yamlData) { | |
| results.add(FeatureModule(name: module['name'], dartLibs: module['libraries']?.cast<String>(), assets: module['assets']?.cast<String>())); | |
| } | |
| return results; | |
| } | |
| void _moveSoFiles(Directory androidDir, String moduleName, BuildResult result, Environment env) { | |
| // MOVE TO ASSEMBLE/POSTBUILD | |
| print('MOVING SO FILES FOR $moduleName'); | |
| Directory moduleDir = androidDir.childDirectory(moduleName); | |
| Directory jniLibsDir = moduleDir.childDirectory('src').childDirectory('main').childDirectory('jniLibs'); | |
| jniLibsDir.createSync(recursive: true); | |
| List<FileSystemEntity> files = env.outputDir.listSync(recursive: true); | |
| while (files.length != 0) { | |
| FileSystemEntity file = files.last; | |
| if (file is File) { | |
| String subPath = file.path; | |
| if (!subPath.contains('part.so')) { | |
| files.removeLast(); | |
| continue; | |
| } | |
| subPath = subPath.substring(subPath.lastIndexOf('release/') + 8); | |
| print(jniLibsDir.childFile(subPath).path); | |
| jniLibsDir.childFile(subPath).createSync(recursive: true); | |
| (file as File).copySync(jniLibsDir.childFile(subPath).path); | |
| } | |
| files.removeLast(); | |
| } | |
| } | |
| void _setupFiles(Directory androidDir, String moduleName, BuildResult result, Environment env) { | |
| print('setupFiles for $moduleName'); | |
| Directory moduleDir = androidDir.childDirectory(moduleName); | |
| File stringRes = androidDir.childDirectory('app').childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml'); | |
| stringRes.createSync(recursive: true); | |
| stringRes.writeAsStringSync( | |
| ''' | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <resources> | |
| <string name="${moduleName}Name">$moduleName</string> | |
| </resources> | |
| ''', flush: true); | |
| File androidManifest = moduleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml'); | |
| androidManifest.createSync(recursive: true); | |
| // androidManifest.writeAsStringSync( | |
| // ''' | |
| // <manifest xmlns:dist="http://schemas.android.com/apk/distribution" | |
| // package="com.example.$moduleName" | |
| // split="$moduleName" | |
| // android:isFeatureSplit="${isBase ? false : true}"> | |
| // <dist:module dist:instant="false" | |
| // dist:title="@string/$moduleName" | |
| // <dist:fusing dist:include="true" /> | |
| // </dist:module> | |
| // <dist:delivery> | |
| // <dist:install-time> | |
| // <dist:removable value="false" /> | |
| // </dist:install-time> | |
| // <dist:on-demand/> | |
| // </dist:delivery> | |
| // <application android:hasCode="${isBase ? 'true' : 'false'}"${isBase ? ' tools:replace="android:hasCode"' : ''}> | |
| // </application> | |
| // </manifest> | |
| // ''', flush: true); | |
| androidManifest.writeAsStringSync( | |
| ''' | |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
| xmlns:dist="http://schemas.android.com/apk/distribution" | |
| package="com.example.$moduleName"> | |
| <dist:module | |
| dist:instant="false" | |
| dist:title="@string/${moduleName}Name"> | |
| <dist:delivery> | |
| <dist:on-demand /> | |
| </dist:delivery> | |
| <dist:fusing dist:include="true" /> | |
| </dist:module> | |
| </manifest> | |
| ''', flush: true); | |
| // settings.gradle add ':module' | |
| File settingsGradle = androidDir.childFile('settings.gradle'); | |
| File settingsGradleTemp = androidDir.childFile('settings.gradle.temp'); | |
| if (settingsGradleTemp.existsSync()) settingsGradleTemp.deleteSync(); | |
| List<String> lines = settingsGradle.readAsLinesSync(); | |
| for (String line in lines) { | |
| if (line.length >= 7 && line.substring(0, 7) == 'include') { | |
| List<String> elements = line.substring(7).split(', '); | |
| bool moduleFound = false; | |
| for (int i = 1; i < elements.length; i++) { | |
| if (elements[i] == '\':$moduleName\'') { | |
| moduleFound = true; | |
| break; | |
| } | |
| } | |
| if (!moduleFound) { | |
| line += ', \':$moduleName\''; | |
| } | |
| } | |
| settingsGradleTemp.writeAsStringSync('$line\n', mode: FileMode.append, flush: true); | |
| } | |
| settingsGradleTemp.copySync(settingsGradle.path); | |
| settingsGradleTemp.deleteSync(); | |
| // app/build.gradle add dynamicFeatures = [':modules', ...] | |
| File appBuildGradle = androidDir.childDirectory('app').childFile('build.gradle'); | |
| File appBuildGradleTemp = androidDir.childDirectory('app').childFile('build.gradle.temp'); | |
| if (appBuildGradleTemp.existsSync()) appBuildGradleTemp.deleteSync(); | |
| lines = appBuildGradle.readAsLinesSync(); | |
| bool inAndroidBlock = false; | |
| int androidStartLineIndex = 0; | |
| int androidEndLineIndex = 0; | |
| for (int lineNum = 0; lineNum < lines.length; lineNum++) { | |
| String line = lines[lineNum]; | |
| if (line.contains('android') && line.contains('{')) { | |
| inAndroidBlock = true; | |
| androidStartLineIndex = lineNum; | |
| } else if (inAndroidBlock && line.length > 0 && line.substring(0, 1) == '}') { | |
| inAndroidBlock = false; | |
| androidEndLineIndex = lineNum; | |
| appBuildGradleTemp.writeAsStringSync(' dynamicFeatures = [\':$moduleName\']\n', mode: FileMode.append, flush: true); | |
| } | |
| if (inAndroidBlock) { | |
| if (line.contains('dynamicFeatures = [')) { | |
| List<String> components = line.substring(line.lastIndexOf('dynamicFeatures = [\'') + 20, line.length - 2).split(', '); | |
| if (!components.contains(':$moduleName')) { | |
| components.add(':$moduleName'); | |
| } | |
| line = ' dynamicFeatures = [\'${components.first}\''; | |
| components.removeAt(0); | |
| for (String component in components) { | |
| line += ', \'$component\''; | |
| } | |
| line += ']'; | |
| inAndroidBlock = false; | |
| } | |
| } | |
| appBuildGradleTemp.writeAsStringSync('$line\n', mode: FileMode.append, flush: true); | |
| } | |
| appBuildGradleTemp.copySync(appBuildGradle.path); | |
| appBuildGradleTemp.deleteSync(); | |
| File moduleBuildGradle = moduleDir.childFile('build.gradle'); | |
| moduleBuildGradle.createSync(recursive: true); | |
| moduleBuildGradle.writeAsStringSync( | |
| ''' | |
| apply plugin: "com.android.dynamic-feature" | |
| android { | |
| compileSdkVersion 30 | |
| defaultConfig { | |
| minSdkVersion 16 | |
| targetSdkVersion 30 | |
| versionCode 1 | |
| versionName "1.0" | |
| testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | |
| } | |
| compileOptions { | |
| sourceCompatibility 1.8 | |
| targetCompatibility 1.8 | |
| } | |
| } | |
| dependencies { | |
| implementation fileTree(dir: "libs", include: ["*.jar"]) | |
| implementation project(":app") | |
| testImplementation 'junit:junit:4.12' | |
| androidTestImplementation 'com.android.support.test:runner:1.0.2' | |
| androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' | |
| androidTestImplementation 'com.android.support:support-annotations:28.0.0' | |
| } | |
| ''', flush: true); | |
| } | |
| void createLoadingUnitGolden(List<String> loadingUnits, Directory androidDir) { | |
| File goldenFile = androidDir.childFile('loading_unit_golden.yaml'); | |
| goldenFile.createSync(recursive: true); | |
| String goldenContents = | |
| ''' | |
| # The contents of this file are generated via the prebuild step of | |
| # Flutter split AOT bundle building. It is not recommended to edit | |
| # this manually. | |
| # | |
| # This file contains a list of loading units/dylibs that were | |
| # produced by gen_snapshot during the prebuild/setup step of | |
| # building split AOT apps with split features. During the split | |
| # AOT build phase, we will verify the dylibs produced via | |
| # gen_snapshot matches this file. If not, the prebuild step | |
| # should be rerun and configuration verified. | |
| '''; | |
| for (String loadingUnit in loadingUnits) { | |
| goldenContents += '- $loadingUnit\n'; | |
| } | |
| goldenFile.writeAsStringSync(goldenContents, flush: true); | |
| } | |
| void compareAgainstGolden(List<String> loadingUnits, Directory androidDir) { | |
| File goldenFile = androidDir.childFile('loading_unit_golden.yaml'); | |
| if (!goldenFile.existsSync()) { | |
| print('No loading_unit_golden.yaml found, setting this run\'s output as initial golden'); | |
| return; | |
| } | |
| var yamlData = loadYaml(goldenFile.readAsStringSync()); | |
| List<String> goldenLoadingUnits = List<String>.from(yamlData.cast<String>()); | |
| List<String> newLoadingUnits = []; | |
| for (String loadingUnit in loadingUnits) { | |
| if (goldenLoadingUnits.contains(loadingUnit)) { | |
| goldenLoadingUnits.remove(loadingUnit); | |
| } else { | |
| newLoadingUnits.add(loadingUnit); | |
| } | |
| } | |
| if (newLoadingUnits.isNotEmpty) { | |
| print('New loading units were found: $newLoadingUnits'); | |
| } | |
| if (goldenLoadingUnits.isNotEmpty) { | |
| print('Previously existing loading units no longer exist: $goldenLoadingUnits'); | |
| } | |
| if (newLoadingUnits.isEmpty && goldenLoadingUnits.isEmpty) { | |
| print('No change in generated loading units.'); | |
| } else { | |
| // verify with user if they want to set this configuration as new golden. | |
| } | |
| } | |
| void createBundleConfig(List<String> loadingUnits, Environment env) { | |
| File configFile = env.projectDir.childFile('bundle_config.yaml'); | |
| if (configFile.existsSync()) { | |
| return; | |
| } | |
| configFile.createSync(recursive: true); | |
| String configContents = | |
| ''' | |
| # Defines the feature modules, which dart libraries and assets | |
| # belong in each feature. A dart library must only be included | |
| # in one feature. Any dart library not specified will be included | |
| # in the base module. Assets included in more than one feature | |
| # module will be replicated in each module. | |
| # | |
| # Example: | |
| # | |
| # - name: feature1 | |
| # libraries: | |
| # - dartlib1 | |
| # - dartlibExtra | |
| # assets: | |
| # - uri/to/assets/* | |
| # - assets/feature1/* | |
| # - assets/file_extra_1.png | |
| # - name: feature2 | |
| # libraries: | |
| # - dartlib2 | |
| # assets: | |
| # - assets/feature2/* | |
| # - assets/file_extra_2.png | |
| # | |
| # The default configuration will bundle all splits into the | |
| # base module. To create split dynamic modules, a valid config | |
| # should be specified and the setup script should be re-run. | |
| # | |
| # Available dart libraries: | |
| # | |
| '''; | |
| for (String loadingUnit in loadingUnits) { | |
| configContents += | |
| ''' | |
| # $loadingUnit | |
| '''; | |
| } | |
| configContents += | |
| ''' | |
| # | |
| '''; | |
| configFile.writeAsStringSync(configContents, flush: true); | |
| print("Created bundle_config.yaml. This file should be modified to specify desired splits, and the setup command should be re-run."); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment