// Copyright 2021 Sony Group Corporation. All rights reserved. // Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. // Copyright 2014 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. // @dart = 2.8 import 'dart:io'; import 'package:file/file.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/depfile.dart'; import 'package:flutter_tools/src/build_system/exceptions.dart'; import 'package:flutter_tools/src/build_system/source.dart'; import 'package:flutter_tools/src/build_system/targets/android.dart'; import 'package:flutter_tools/src/build_system/targets/assets.dart'; import 'package:flutter_tools/src/build_system/targets/common.dart'; import 'package:flutter_tools/src/build_system/targets/icon_tree_shaker.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cmake.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; import 'elinux_builder.dart'; import 'elinux_cmake_project.dart'; import 'elinux_plugins.dart'; /// Prepares the pre-built flutter bundle. /// /// Source: [AndroidAssetBundle] in `android.dart` abstract class ELinuxAssetBundle extends Target { const ELinuxAssetBundle(); @override String get name => 'elinux_asset_bundle'; @override List get inputs => const [ Source.pattern('{BUILD_DIR}/app.dill'), ...IconTreeShaker.inputs, ]; @override List get outputs => const []; @override List get depfiles => [ 'flutter_assets.d', ]; @override List get dependencies => const [ KernelSnapshot(), ]; @override Future build(Environment environment) async { if (environment.defines[kBuildMode] == null) { throw MissingDefineException(kBuildMode, name); } final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final Directory outputDirectory = environment.outputDir .childDirectory('flutter_assets') ..createSync(recursive: true); // Only copy the prebuilt runtimes and kernel blob in debug mode. if (buildMode == BuildMode.debug) { final String vmSnapshotData = environment.artifacts .getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug); final String isolateSnapshotData = environment.artifacts .getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug); environment.buildDir .childFile('app.dill') .copySync(outputDirectory.childFile('kernel_blob.bin').path); environment.fileSystem .file(vmSnapshotData) .copySync(outputDirectory.childFile('vm_snapshot_data').path); environment.fileSystem .file(isolateSnapshotData) .copySync(outputDirectory.childFile('isolate_snapshot_data').path); } final Depfile assetDepfile = await copyAssets( environment, outputDirectory, targetPlatform: null, // corresponds to flutter-tester ); final DepfileService depfileService = DepfileService( fileSystem: environment.fileSystem, logger: environment.logger, ); depfileService.writeToFile( assetDepfile, environment.buildDir.childFile('flutter_assets.d'), ); } } /// Source: [DebugAndroidApplication] in `android.dart` class DebugELinuxApplication extends ELinuxAssetBundle { DebugELinuxApplication(this.buildInfo); final ELinuxBuildInfo buildInfo; @override String get name => 'debug_elinux_application'; @override List get inputs => [ ...super.inputs, const Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug), const Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug), ]; @override List get outputs => [ ...super.outputs, const Source.pattern('{OUTPUT_DIR}/flutter_assets/vm_snapshot_data'), const Source.pattern( '{OUTPUT_DIR}/flutter_assets/isolate_snapshot_data'), const Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'), ]; @override List get dependencies => [ ...super.dependencies, ELinuxPlugins(buildInfo), ]; } /// See: [ReleaseAndroidApplication] in `android.dart` class ReleaseELinuxApplication extends ELinuxAssetBundle { ReleaseELinuxApplication(this.buildInfo); final ELinuxBuildInfo buildInfo; @override String get name => 'release_elinux_application'; @override List get dependencies => [ ...super.dependencies, ELinuxAotElf(), ELinuxPlugins(buildInfo), ]; } /// Compiles eLinux native plugins into a single shared object. class ELinuxPlugins extends Target { ELinuxPlugins(this.buildInfo); final ELinuxBuildInfo buildInfo; @override String get name => 'elinux_plugins'; @override List get inputs => const [ Source.pattern('{FLUTTER_ROOT}/../lib/elinux_build_target.dart'), Source.pattern('{PROJECT_DIR}/.packages'), ]; @override List get outputs => const []; @override List get depfiles => [ 'elinux_plugins.d', ]; @override List get dependencies => const []; @override Future build(Environment environment) async { // todo: add plugin build support. } } /// Generates an AOT snapshot (app.so) of the Dart code. /// /// Source: [AotElfRelease] in `common.dart` class ELinuxAotElf extends AotElfBase { ELinuxAotElf(); @override String get name => 'elinux_aot_elf'; @override List get inputs => [ const Source.pattern('{BUILD_DIR}/app.dill'), const Source.hostArtifact(HostArtifact.engineDartBinary), const Source.artifact(Artifact.skyEnginePath), // Any type of gen_snapshot is applicable here because engine artifacts // are assumed to be updated at once, not one by one for each platform // or build mode. const Source.artifact(Artifact.genSnapshot, mode: BuildMode.release), ]; @override List get outputs => const [ Source.pattern('{BUILD_DIR}/app.so'), ]; @override List get dependencies => const [ KernelSnapshot(), ]; } class NativeBundle { NativeBundle(this.buildInfo, this.targetFile); final ELinuxBuildInfo buildInfo; final String targetFile; final ProcessUtils _processUtils = ProcessUtils( logger: globals.logger, processManager: globals.processManager); Future build(Environment environment) async { final FlutterProject project = FlutterProject.fromDirectory(environment.projectDir); final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project); // Clean up the intermediate and output directories. final Directory eLinuxDir = eLinuxProject.editableDirectory; final BuildMode buildMode = buildInfo.buildInfo.mode; final Directory outputDir = environment.outputDir .childDirectory(buildInfo.targetArch) .childDirectory(buildMode.toString()); if (outputDir.existsSync()) { outputDir.deleteSync(recursive: true); } outputDir.createSync(recursive: true); final Directory outputBundleDir = outputDir.childDirectory('bundle'); if (outputBundleDir.existsSync()) { outputBundleDir.deleteSync(recursive: true); } outputBundleDir.createSync(recursive: true); final Directory outputBundleLibDir = outputBundleDir.childDirectory('lib'); if (outputBundleLibDir.existsSync()) { outputBundleLibDir.deleteSync(recursive: true); } outputBundleLibDir.createSync(recursive: true); final Directory outputBundleDataDir = outputBundleDir.childDirectory('data'); if (outputBundleDataDir.existsSync()) { outputBundleDataDir.deleteSync(recursive: true); } outputBundleDataDir.createSync(recursive: true); // Copy necessary files final Directory engineDir = _getEngineArtifactsDirectory(buildInfo.targetArch, buildMode); final Directory commonDir = engineDir.parent.childDirectory('elinux-common'); final File engineBinary = engineDir.childFile('libflutter_engine.so'); // libflutter_elinux_*.so in profile mode is under the debug mode's directory. final Directory embedderDir = _getEngineArtifactsDirectory( buildInfo.targetArch, buildMode.isRelease ? buildMode : BuildMode.fromName('debug')); final File embedder = embedderDir.childFile(buildInfo.targetBackendType == 'gbm' ? 'libflutter_elinux_gbm.so' : buildInfo.targetBackendType == 'eglstream' ? 'libflutter_elinux_eglstream.so' : buildInfo.targetBackendType == 'x11' ? 'libflutter_elinux_x11.so' : 'libflutter_elinux_wayland.so'); final Directory clientWrapperDir = commonDir.childDirectory('cpp_client_wrapper'); final Directory pluginsDir = environment.buildDir.childDirectory('elinux_plugins'); final File pluginsLib = pluginsDir.childFile('libflutter_plugins.so'); if (pluginsLib.existsSync()) { pluginsLib .copySync(outputBundleLibDir.childFile(pluginsLib.basename).path); } final Directory pluginsUserLibDir = pluginsDir.childDirectory('lib'); if (pluginsUserLibDir.existsSync()) { pluginsUserLibDir.listSync().whereType().forEach((File lib) => lib.copySync(outputBundleLibDir.childFile(lib.basename).path)); } final Directory flutterDir = eLinuxDir.childDirectory('flutter'); final Directory flutterEphemeralDir = flutterDir.childDirectory('ephemeral'); // Copy necessary files. { if (flutterEphemeralDir.existsSync()) { flutterEphemeralDir.deleteSync(recursive: true); } flutterEphemeralDir.createSync(recursive: true); flutterEphemeralDir .childDirectory('cpp_client_wrapper') .createSync(recursive: true); copyDirectory( clientWrapperDir, flutterEphemeralDir.childDirectory('cpp_client_wrapper'), ); commonDir.listSync().whereType().forEach((File lib) => lib.copySync(flutterEphemeralDir.childFile(lib.basename).path)); engineBinary .copySync(flutterEphemeralDir.childFile(engineBinary.basename).path); embedder.copySync(flutterEphemeralDir.childFile(embedder.basename).path); final File icuData = commonDir.childDirectory('icu').childFile('icudtl.dat'); icuData.copySync(flutterEphemeralDir.childFile(icuData.basename).path); if (buildMode.isPrecompiled) { final File aotSharedLib = environment.buildDir.childFile('app.so'); aotSharedLib.copySync(flutterEphemeralDir.childFile('libapp.so').path); } } // Build the environment that needs to be set for the re-entrant flutter build // step. { final Map environment = { if (targetFile != null) 'FLUTTER_TARGET': targetFile, ...buildInfo.buildInfo.toEnvironmentConfig(), }; if (globals.artifacts is LocalEngineArtifacts) { final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts; final String engineOutPath = localEngineArtifacts.engineOutPath; environment['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath)); environment['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath); } writeGeneratedCmakeConfig(Cache.flutterRoot, eLinuxProject, environment); await refreshELinuxPluginsList(eLinuxProject.parent); } // Run the native build. final String cmakeBuildType = buildMode.isPrecompiled ? 'Release' : 'Debug'; final String targetArch = buildInfo.targetArch; final String hostArch = _getCurrentHostPlatformArchName(); final String targetSysroot = buildInfo.targetSysroot; final String systemIncludeDirectories = buildInfo.systemIncludeDirectories; final bool needCrossBuild = targetArch != hostArch; final bool needCrossBuildOptionsForArm64 = targetArch == 'arm64'; RunResult result = await _processUtils.run( [ 'cmake', '-DCMAKE_BUILD_TYPE=$cmakeBuildType', '-DFLUTTER_TARGET_BACKEND_TYPE=${buildInfo.targetBackendType}', if (needCrossBuild) '-DFLUTTER_TARGET_PLATFORM_SYSROOT=$targetSysroot', if (needCrossBuild && systemIncludeDirectories != null) '-DFLUTTER_SYSTEM_INCLUDE_DIRECTORIES=$systemIncludeDirectories', if (needCrossBuildOptionsForArm64) '-DCMAKE_C_COMPILER_TARGET=aarch64-linux-gnu', if (needCrossBuildOptionsForArm64) '-DCMAKE_CXX_COMPILER_TARGET=aarch64-linux-gnu', eLinuxDir.path, ], workingDirectory: outputDir.path, environment: {'CC': 'clang', 'CXX': 'clang++'}, ); if (result.exitCode != 0) { throwToolExit('Failed to cmake:\n$result'); } result = await _processUtils.run( ['cmake', '--build', '.'], workingDirectory: outputDir.path, ); if (result.exitCode != 0) { throwToolExit('Failed to cmake build:\n$result'); } // Create flutter app's bunle. result = await _processUtils.run( ['cmake', '--install', '.'], workingDirectory: outputDir.path, ); if (result.exitCode != 0) { throwToolExit('Failed to cmake install:\n$result'); } { final Directory flutterAssetsDir = outputBundleDataDir.childDirectory('flutter_assets'); copyDirectory( environment.outputDir.childDirectory('flutter_assets'), flutterAssetsDir, ); } } } String _getCurrentHostPlatformArchName() { final HostPlatform hostPlatform = getCurrentHostPlatform(); return getNameForHostPlatformArch(hostPlatform); } /// Converts [targetArch] to an arch name that corresponds to the `BUILD_ARCH` /// value used by the eLinux native builder. String getELinuxBuildArch(String targetArch) { switch (targetArch) { case 'arm': return 'armel'; case 'arm64': return 'aarch64'; case 'x86': return 'i586'; default: return targetArch; } } /// On non-Windows, returns [path] unchanged. /// /// On Windows, converts Windows-style [path] (e.g. 'C:\x\y') into Unix path /// ('/c/x/y') and returns. String getUnixPath(String path) { if (Platform.isWindows) { path = path.replaceAll(r'\', '/'); if (path.startsWith(':', 1)) { path = '/${path[0].toLowerCase()}${path.substring(2)}'; } } return path; } /// On non-Windows, returns the PATH environment variable. /// /// On Windows, appends the msys2 executables directory to PATH and returns. String getDefaultPathVariable() { final Map variables = globals.platform.environment; return variables.containsKey('PATH') ? variables['PATH'] : ''; } /// See: [CachedArtifacts._getEngineArtifactsPath] Directory _getEngineArtifactsDirectory(String arch, BuildMode mode) { assert(mode != null, 'Need to specify a build mode.'); return globals.cache .getArtifactDirectory('engine') .childDirectory('elinux-$arch-${mode.name}'); }