flutter脚本构建流程分析

1,331 阅读1分钟

一.环境变量配置说明

  • flutter_home

通过 命令增加flutter的安装目录到系统环境变量

vim ~/.bash_profile
export PATH=$PATH:/xxxxx/xxxx/flutter/bin
  • dart_home

位于flutter_home下的cache目录下的dart_sdk下 flutter_home/cache/dart_sdk

二.入口 flutter/flutter.bat

flutter build aar

这是一条构建aar包的命令,由于用到了flutter命令,根据我们设置的环境变量可知,启动代码位于 flutter的安装目录下的bin文件夹下的flutter中,(windows下为flutter.bat)

"%dart%" --disable-dart-dev --packages="%flutter_tools_dir%\.packages" %FLUTTER_TOOL_ARGS% "%snapshot_path%" %* & exit /B !ERRORLEVEL!

打印下环境变量,实际执行代码为

flutter_home/cache/dart-sdk/bin/dart FLUTTER_TOOL_ARGS= SNAPSHOT_PATH=flutter_home/bin/cache/flutter_tools.snapshot build aar

它会调用flutter_tools.snaphot,

snapshot 是dart五种编译产物的其中一种,可以通过命令指定要生成的产物:

dart compile aot-snapshot bin/myapp.dart

,五种产物分别是:

  • exe
  • aot-snapshot
  • jit-snapshot
  • kernal
  • js

详细说明参考官网

它与java的模型简单映射是这样:

  • java - dart
  • jar - snapshot
  • jvm - dartvm

因此我们可以找到flutter_tool.dart看下后面的流程

三.flutter_tool.dart

位于flutter_home/../packages/flutter_tools/bin下

代码很简单,执行了executable.dart的main方法

import 'package:flutter_tools/executable.dart' as executable;

void main(List<String> args) {
  executable.main(args);
}

四.executable.dart

位于flutter_home/../packages/flutter_tools/lib/executable.dart

主要执行逻辑为BuildCommand()

List<FlutterCommand> generateCommands({
  @required bool verboseHelp,
  @required bool verbose,
}) => <FlutterCommand>[  AnalyzeCommand(    verboseHelp: verboseHelp,    fileSystem: globals.fs,    platform: globals.platform,    processManager: globals.processManager,    logger: globals.logger,    terminal: globals.terminal,    artifacts: globals.artifacts,  ),  AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem),  AttachCommand(verboseHelp: verboseHelp),  BuildCommand(verboseHelp: verboseHelp),  ....

五.build.dart

位于位于flutter_home/../packages/flutter_tools/bin/src/build.dart中,我们就找到了BuildAarCommand ,也就是开始的flutter build aar命令

class BuildCommand extends FlutterCommand {
  BuildCommand({ bool verboseHelp = false }) {
    _addSubcommand(BuildAarCommand(verboseHelp: verboseHelp));
    _addSubcommand(BuildApkCommand(verboseHelp: verboseHelp));
    ...
    }
}
}

六.build_aar.dart

位于位于flutter_home/../packages/flutter_tools/bin/src/commands/build_aar.dart中

实际运行方法runCommand中的androidBuilder.buildAar

@override
  Future<FlutterCommandResult> runCommand() async {
    ....
    await androidBuilder.buildAar(
      project: _getProject(),
      target: targetFile.path,
      androidBuildInfo: androidBuildInfo,
      outputDirectoryPath: stringArg('output-dir'),
      buildNumber: buildNumber,
    );
    return FlutterCommandResult.success();
  }

七.android_builder.dart

位于flutter_home/../packages/flutter_tools/bin/src/android/android_builder.dart中,它是一个抽象类,它的实现在gradle.dart中的AndroidGradleBuilder

八.gradle.dart

位于flutter_home/../packages/flutter_tools/bin/src/android/gradle.dart中

@override
  Future<void> buildAar({
    required FlutterProject project,
    required Set<AndroidBuildInfo> androidBuildInfo,
    required String target,
    String? outputDirectoryPath,
    required String buildNumber,
  }) async {
    Directory outputDirectory =
      _fileSystem.directory(outputDirectoryPath ?? project.android.buildDirectory);
    //设置输出目录
    if (project.isModule) {
      // Module projects artifacts are located in `build/host`.
      outputDirectory = outputDirectory.childDirectory('host');
    }
    //执行buildaar
    for (final AndroidBuildInfo androidBuildInfo in androidBuildInfo) {
      await buildGradleAar(
        project: project,
        androidBuildInfo: androidBuildInfo,
        target: target,
        outputDirectory: outputDirectory,
        buildNumber: buildNumber,
      );
    }
    //输出使用方法
    printHowToConsumeAar(
      buildModes: androidBuildInfo
        .map<String>((AndroidBuildInfo androidBuildInfo) {
          return androidBuildInfo.buildInfo.modeName;
        }).toSet(),
      androidPackage: project.manifest.androidPackage,
      repoDirectory: getRepoDirectory(outputDirectory),
      buildNumber: buildNumber,
      logger: _logger,
      fileSystem: _fileSystem,
    );
  }

这个方法包含三部分

  • 1.设置build输出目录为./build/host
  • 2.执行buildaar命令
  • 3.输出使用帮助信息

其中第二步是我们需要的

九.正主gradle.dart - buildGradleAar

Future<void> buildGradleAar({
    required FlutterProject project,
    required AndroidBuildInfo androidBuildInfo,
    required String target,
    required Directory outputDirectory,
    required String buildNumber,
  }) async {
    assert(project != null);
    assert(target != null);
    assert(androidBuildInfo != null);
    assert(outputDirectory != null);

    final FlutterManifest manifest = project.manifest;
    if (!manifest.isModule && !manifest.isPlugin) {
      throwToolExit('AARs can only be built for plugin or module projects.');
    }

    final BuildInfo buildInfo = androidBuildInfo.buildInfo;
    final String aarTask = getAarTaskFor(buildInfo);
    final Status status = _logger.startProgress(
      "Running Gradle task '$aarTask'...",
    );

    final String flutterRoot = _fileSystem.path.absolute(Cache.flutterRoot!);
    final String initScript = _fileSystem.path.join(
      flutterRoot,
      'packages',
      'flutter_tools',
      'gradle',
      'aar_init_script.gradle',
    );
    final List<String> command = <String>[
      _gradleUtils.getExecutable(project),
      '-I=$initScript',
      '-Pflutter-root=$flutterRoot',
      '-Poutput-dir=${outputDirectory.path}',
      '-Pis-plugin=${manifest.isPlugin}',
      '-PbuildNumber=$buildNumber'
    ];
    if (_logger.isVerbose) {
      command.add('-Pverbose=true');
    } else {
      command.add('-q');
    }
    if (!buildInfo.androidGradleDaemon) {
      command.add('--no-daemon');
    }

    if (target != null && target.isNotEmpty) {
      command.add('-Ptarget=$target');
    }
    command.addAll(androidBuildInfo.buildInfo.toGradleConfig());
    if (buildInfo.dartObfuscation && buildInfo.mode != BuildMode.release) {
      _logger.printStatus(
        'Dart obfuscation is not supported in ${toTitleCase(buildInfo.friendlyModeName)}'
            ' mode, building as un-obfuscated.',
      );
    }

    if (_artifacts is LocalEngineArtifacts) {
      final LocalEngineArtifacts localEngineArtifacts = _artifacts as LocalEngineArtifacts;
      final Directory localEngineRepo = _getLocalEngineRepo(
        engineOutPath: localEngineArtifacts.engineOutPath,
        androidBuildInfo: androidBuildInfo,
        fileSystem: _fileSystem,
      );
      _logger.printTrace(
        'Using local engine: ${localEngineArtifacts.engineOutPath}\n'
        'Local Maven repo: ${localEngineRepo.path}'
      );
      command.add('-Plocal-engine-repo=${localEngineRepo.path}');
      command.add('-Plocal-engine-build-mode=${buildInfo.modeName}');
      command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}');

      // Copy the local engine repo in the output directory.
      try {
        copyDirectory(
          localEngineRepo,
          getRepoDirectory(outputDirectory),
        );
      } on FileSystemException catch (error, st) {
        throwToolExit(
            'Failed to copy the local engine ${localEngineRepo.path} repo '
                'in ${outputDirectory.path}: $error, $st'
        );
      }
      command.add('-Ptarget-platform=${_getTargetPlatformByLocalEnginePath(
          localEngineArtifacts.engineOutPath)}');
    } else if (androidBuildInfo.targetArchs.isNotEmpty) {
      final String targetPlatforms = androidBuildInfo.targetArchs
          .map(getPlatformNameForAndroidArch).join(',');
      command.add('-Ptarget-platform=$targetPlatforms');
    }

    command.add(aarTask);

    final Stopwatch sw = Stopwatch()
      ..start();
    RunResult result;
    try {
      result = await _processUtils.run(
        command,
        workingDirectory: project.android.hostAppGradleRoot.path,
        allowReentrantFlutter: true,
        environment: <String, String>{
          if (javaPath != null)
            'JAVA_HOME': javaPath!,
        },
      );
    } finally {
      status.stop();
    }
    _usage.sendTiming('build', 'gradle-aar', sw.elapsed);

    if (result.exitCode != 0) {
      _logger.printStatus(result.stdout, wrap: false);
      _logger.printError(result.stderr, wrap: false);
      throwToolExit(
        'Gradle task $aarTask failed with exit code ${result.exitCode}.',
        exitCode: result.exitCode,
      );
    }
    final Directory repoDirectory = getRepoDirectory(outputDirectory);
    if (!repoDirectory.existsSync()) {
      _logger.printStatus(result.stdout, wrap: false);
      _logger.printError(result.stderr, wrap: false);
      throwToolExit(
        'Gradle task $aarTask failed to produce $repoDirectory.',
        exitCode: exitCode,
      );
    }
    _logger.printStatus(
      '${_logger.terminal.successMark} Built ${_fileSystem.path.relative(repoDirectory.path)}.',
      color: TerminalColor.green,
    );
  }
}

它的流程包含

  • 1.组装命令,也就是在command.add(aarTask); 之前的代码 ,这个aartask 就是我们常用的assembleAarRelease,组装完之后,完整命令如下:
./gradlew \
-I=/Users/hucaihua/Documents/soft/flutter/packages/flutter_tools/gradle/aar_init_script.gradle \
-Pflutter-root=/Users/hucaihua/Documents/soft/flutter \
-Poutput-dir=/Users/hucaihua/Documents/code/git/hive/dartmodule/build/host \
-Pis-plugin=false \
-PbuildNumber=1.0.1 \
-Pverbose=true \
-Ptarget=lib/main.dart -Pdart-obfuscation=false \
-Ptrack-widget-creation=true \
-Ptree-shake-icons=true \
-Ptarget-platform=android-arm,android-arm64 assembleAarRelease

这里会依赖aar_init_script.gradle 这个脚本,它会先执行gradle构建流程中的init和configure流程,并在执行assembleAarRelease结束之后,挂一个mavenpublish任务,将构建的aar推送到本地maven仓库

参见gradle构建的三大流程

  • 2.执行命令,这里要主意的是,它切换了工作目录project.android.hostAppGradleRoot.path,实际上代表的就是.android目录,如果直接在根目录下执行./gradlew assembleAarRelease 会提示找不projectEvaluated{} ,因为根目录不是一个android的project
try {
  result = await _processUtils.run(
    command,
    workingDirectory: project.android.hostAppGradleRoot.path,
    allowReentrantFlutter: true,
    environment: <String, String>{
      if (javaPath != null)
        'JAVA_HOME': javaPath!,
    },
  );
} finally {
  status.stop();
}

执行完这个命令就进入了gradle构建命令assembleAarRelease 进行aar的打包