// Copyright 2023 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. import 'dart:io'; 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/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/build_system/targets/shader_compiler.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'; /// Prepares the pre-built flutter bundle. /// /// Source: [AndroidAssetBundle] in `android.dart` abstract class ELinuxAssetBundle extends Target { const ELinuxAssetBundle(this.buildInfo); final ELinuxBuildInfo buildInfo; @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 = BuildMode.fromCliName(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 TargetPlatform tp = buildInfo.targetArch == 'arm64' ? TargetPlatform.linux_arm64 : TargetPlatform.linux_x64; final Depfile assetDepfile = await copyAssets( environment, outputDirectory, targetPlatform: tp, buildMode: buildMode, shaderTarget: ShaderTarget.sksl, ); 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(super.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(super.buildInfo); @override String get name => 'release_elinux_application'; @override List get dependencies => [ ...super.dependencies, ELinuxAotElf(buildInfo.targetArch == 'arm64' ? TargetPlatform.linux_arm64 : TargetPlatform.linux_x64), 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 { const ELinuxAotElf(this.targetPlatform); @override String get name => 'elinux_aot_elf'; @override List get inputs => [ const Source.pattern( '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'), const Source.pattern('{BUILD_DIR}/app.dill'), const Source.artifact(Artifact.engineDartBinary), const Source.artifact(Artifact.skyEnginePath), Source.artifact( Artifact.genSnapshot, platform: targetPlatform, mode: BuildMode.release, ), ]; @override List get outputs => const [ Source.pattern('{BUILD_DIR}/app.so'), ]; @override List get dependencies => const [ KernelSnapshot(), ]; final TargetPlatform targetPlatform; } 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.fromCliName('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. { 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 environmentConfig = buildInfo!.buildInfo.toEnvironmentConfig(); environmentConfig['FLUTTER_TARGET'] = targetFile!; final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo; if (localEngineInfo != null) { final String engineOutPath = localEngineInfo.engineOutPath; environmentConfig['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath)); environmentConfig['LOCAL_ENGINE'] = localEngineInfo.localEngineName; } writeGeneratedCmakeConfig(Cache.flutterRoot!, eLinuxProject, buildInfo!.buildInfo, environmentConfig); } // Run the native build. final String cmakeBuildType = buildMode.isPrecompiled ? 'Release' : 'Debug'; final String targetArch = buildInfo!.targetArch == 'arm64' ? 'aarch64' : 'x86_64'; final String hostArch = _getCurrentHostPlatformArchName(); final String? targetCompilerTriple = buildInfo!.targetCompilerTriple; final String targetSysroot = buildInfo!.targetSysroot; final String? targetCompilerFlags = buildInfo!.targetCompilerFlags; final String? targetToolchain = buildInfo!.targetToolchain; final String? systemIncludeDirectories = buildInfo!.systemIncludeDirectories; RunResult result = await _processUtils.run( [ 'cmake', '-DCMAKE_BUILD_TYPE=$cmakeBuildType', '-DFLUTTER_TARGET_BACKEND_TYPE=${buildInfo!.targetBackendType}', '-DFLUTTER_TARGET_PLATFORM=elinux-${buildInfo!.targetArch}', if (targetSysroot != '/') '-DCMAKE_SYSROOT=$targetSysroot', if (buildInfo!.targetArch != hostArch) '-DCMAKE_SYSTEM_PROCESSOR=$targetArch', if (systemIncludeDirectories != null) '-DFLUTTER_SYSTEM_INCLUDE_DIRECTORIES=$systemIncludeDirectories', if (targetCompilerTriple != null) '-DCMAKE_C_COMPILER_TARGET=$targetCompilerTriple', if (targetCompilerTriple != null) '-DCMAKE_CXX_COMPILER_TARGET=$targetCompilerTriple', if (targetCompilerFlags != null) '-DCMAKE_C_FLAGS=$targetCompilerFlags', if (targetCompilerFlags != null) '-DCMAKE_CXX_FLAGS=$targetCompilerFlags', eLinuxDir.path, ], workingDirectory: outputDir.path, environment: (targetToolchain == null) ? {'CC': 'clang', 'CXX': 'clang++'} : { 'CC': '$targetToolchain/bin/clang', 'CXX': '$targetToolchain/bin/clang++' }, ); if (result.exitCode != 0) { throwToolExit('Failed to cmake:\n$result'); } final ProcessResult procResult = await Process.run('nproc', []); final String numProc = procResult.stdout.toString().trim(); result = await _processUtils.run( [ 'cmake', '--build', '.', '--parallel', numProc, ], 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 hostPlatform.platformName; } /// 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}'); }