Reland "AOT support for Linux Desktop I: switch Linux builds to assemble (#41612)" (#42030)" (#42038)

This reverts commit 142a8630ec7282ddf892e63234b7d84a06bffc5d.
diff --git a/packages/flutter_tools/bin/tool_backend.dart b/packages/flutter_tools/bin/tool_backend.dart
index cb3c533..64253a5 100644
--- a/packages/flutter_tools/bin/tool_backend.dart
+++ b/packages/flutter_tools/bin/tool_backend.dart
@@ -17,7 +17,6 @@
   final String flutterEngine = Platform.environment['FLUTTER_ENGINE'];
   final String localEngine = Platform.environment['LOCAL_ENGINE'];
   final String flutterRoot = Platform.environment['FLUTTER_ROOT'];
-
   Directory.current = projectDirectory;
 
   if (localEngine != null && !localEngine.contains(buildMode)) {
@@ -33,22 +32,34 @@
 ''');
     exit(1);
   }
+  final String flutterExecutable = path.join(
+    flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
 
-  String cacheDirectory;
-  switch (targetPlatform) {
-    case 'linux-x64':
-      cacheDirectory = 'linux/flutter/ephemeral';
-      break;
-    case 'windows-x64':
-      cacheDirectory = 'windows/flutter/ephemeral';
-      break;
-    default:
-      stderr.write('Unsupported target platform $targetPlatform');
+  if (targetPlatform == 'linux-x64') {
+    // TODO(jonahwilliams): currently all builds are debug builds. Remove the
+    // hardcoded mode when profile and release support is added.
+    final ProcessResult unpackResult = await Process.run(
+        flutterExecutable,
+        <String>[
+          '--suppress-analytics',
+          '--verbose',
+          if (flutterEngine != null) '--local-engine-src-path=$flutterEngine',
+          if (localEngine != null) '--local-engine=$localEngine',
+          'assemble',
+          '-dTargetPlatform=$targetPlatform',
+          '-dBuildMode=debug',
+          '-dTargetFile=$flutterTarget',
+          '--output=build',
+          'debug_bundle_linux_assets',
+        ]);
+    if (unpackResult.exitCode != 0) {
+      stderr.write(unpackResult.stderr);
       exit(1);
+    }
+    return;
   }
 
-  final String flutterExecutable = path.join(
-      flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
+  const String cacheDirectory = 'windows/flutter/ephemeral';
   final ProcessResult unpackResult = await Process.run(
     flutterExecutable,
     <String>[
diff --git a/packages/flutter_tools/lib/src/build_system/targets/linux.dart b/packages/flutter_tools/lib/src/build_system/targets/linux.dart
index b57ab3b..4821163 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart
@@ -2,18 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:pool/pool.dart';
+
 import '../../artifacts.dart';
+import '../../asset.dart';
 import '../../base/file_system.dart';
 import '../../build_info.dart';
+import '../../devfs.dart';
 import '../../globals.dart';
 import '../build_system.dart';
+import '../exceptions.dart';
+import 'assets.dart';
+import 'dart.dart';
 
 /// Copies the Linux desktop embedding files to the copy directory.
-class UnpackLinux extends Target {
-  const UnpackLinux();
+class UnpackLinuxDebug extends Target {
+  const UnpackLinuxDebug();
 
   @override
-  String get name => 'unpack_linux';
+  String get name => 'unpack_linux_debug';
 
   @override
   List<Source> get inputs => const <Source>[
@@ -23,12 +30,12 @@
 
   @override
   List<Source> get outputs => const <Source>[
-    Source.pattern('{PROJECT_DIR}/linux/flutter/libflutter_linux_glfw.so'),
-    Source.pattern('{PROJECT_DIR}/linux/flutter/flutter_export.h'),
-    Source.pattern('{PROJECT_DIR}/linux/flutter/flutter_messenger.h'),
-    Source.pattern('{PROJECT_DIR}/linux/flutter/flutter_plugin_registrar.h'),
-    Source.pattern('{PROJECT_DIR}/linux/flutter/flutter_glfw.h'),
-    Source.pattern('{PROJECT_DIR}/linux/flutter/icudtl.dat'),
+    Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/libflutter_linux_glfw.so'),
+    Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/flutter_export.h'),
+    Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/flutter_messenger.h'),
+    Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/flutter_plugin_registrar.h'),
+    Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/flutter_glfw.h'),
+    Source.pattern('{PROJECT_DIR}/linux/flutter/ephemeral/icudtl.dat'),
   ];
 
   @override
@@ -44,6 +51,7 @@
         environment.projectDir.path,
         'linux',
         'flutter',
+        'ephemeral',
         fs.path.relative(input.path, from: basePath),
       );
       final File destinationFile = fs.file(outputPath);
@@ -54,3 +62,72 @@
     }
   }
 }
+
+/// Creates a debug bundle for the Linux desktop target.
+class DebugBundleLinuxAssets extends Target {
+  const DebugBundleLinuxAssets();
+
+  @override
+  String get name => 'debug_bundle_linux_assets';
+
+  @override
+  List<Target> get dependencies => const <Target>[
+    KernelSnapshot(),
+    UnpackLinuxDebug(),
+  ];
+
+  @override
+  List<Source> get inputs => const <Source>[
+    Source.pattern('{BUILD_DIR}/app.dill'),
+    Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'),
+    Source.behavior(AssetOutputBehavior('flutter_assets')),
+  ];
+
+  @override
+  List<Source> get outputs => const <Source>[
+    Source.behavior(AssetOutputBehavior('flutter_assets')),
+    Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'),
+    Source.pattern('{OUTPUT_DIR}/flutter_assets/AssetManifest.json'),
+    Source.pattern('{OUTPUT_DIR}/flutter_assets/FontManifest.json'),
+    Source.pattern('{OUTPUT_DIR}/flutter_assets/LICENSE'),
+  ];
+
+  @override
+  Future<void> build(Environment environment) async {
+    if (environment.defines[kBuildMode] == null) {
+      throw MissingDefineException(kBuildMode, 'debug_bundle_linux_assets');
+    }
+    final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
+    final Directory outputDirectory = environment.outputDir
+      .childDirectory('flutter_assets');
+    if (!outputDirectory.existsSync()) {
+      outputDirectory.createSync();
+    }
+
+    // Only copy the kernel blob in debug mode.
+    if (buildMode == BuildMode.debug) {
+      environment.buildDir.childFile('app.dill')
+        .copySync(outputDirectory.childFile('kernel_blob.bin').path);
+    }
+
+    final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
+    await assetBundle.build();
+    final Pool pool = Pool(kMaxOpenFiles);
+    await Future.wait<void>(
+      assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
+        final PoolResource resource = await pool.request();
+        try {
+          final File file = fs.file(fs.path.join(outputDirectory.path, entry.key));
+          file.parent.createSync(recursive: true);
+          final DevFSContent content = entry.value;
+          if (content is DevFSFileContent && content.file is File) {
+            await (content.file as File).copy(file.path);
+          } else {
+            await file.writeAsBytes(await entry.value.contentsAsBytes());
+          }
+        } finally {
+          resource.release();
+        }
+      }));
+  }
+}
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index c8048cb..ee1e72d 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -20,7 +20,6 @@
 
 /// All currently implemented targets.
 const List<Target> _kDefaultTargets = <Target>[
-  UnpackLinux(),
   UnpackWindows(),
   CopyAssets(),
   KernelSnapshot(),
@@ -32,6 +31,7 @@
   DebugMacOSBundleFlutterAssets(),
   ProfileMacOSBundleFlutterAssets(),
   ReleaseMacOSBundleFlutterAssets(),
+  DebugBundleLinuxAssets(),
   WebReleaseBundle(),
 ];
 
@@ -77,7 +77,7 @@
     final Target result = _kDefaultTargets
         .firstWhere((Target target) => target.name == name, orElse: () => null);
     if (result == null) {
-      throwToolExit('No target named "{target.name} defined."');
+      throwToolExit('No target named "$name" defined.');
     }
     return result;
   }
diff --git a/packages/flutter_tools/lib/src/commands/unpack.dart b/packages/flutter_tools/lib/src/commands/unpack.dart
index 6dfdc10..5fd57c0 100644
--- a/packages/flutter_tools/lib/src/commands/unpack.dart
+++ b/packages/flutter_tools/lib/src/commands/unpack.dart
@@ -5,7 +5,6 @@
 import '../artifacts.dart';
 import '../base/common.dart';
 import '../base/file_system.dart';
-import '../base/process.dart';
 import '../build_info.dart';
 import '../cache.dart';
 import '../globals.dart';
@@ -13,26 +12,12 @@
 
 /// The directory in the Flutter cache for each platform's artifacts.
 const Map<TargetPlatform, String> flutterArtifactPlatformDirectory = <TargetPlatform, String>{
-  TargetPlatform.linux_x64: 'linux-x64',
-  TargetPlatform.darwin_x64: 'darwin-x64',
   TargetPlatform.windows_x64: 'windows-x64',
 };
 
 // TODO(jonahwilliams): this should come from a configuration in each build
 // directory.
 const Map<TargetPlatform, List<String>> artifactFilesByPlatform = <TargetPlatform, List<String>>{
-  TargetPlatform.linux_x64: <String>[
-    'libflutter_linux_glfw.so',
-    'flutter_export.h',
-    'flutter_messenger.h',
-    'flutter_plugin_registrar.h',
-    'flutter_glfw.h',
-    'icudtl.dat',
-    'cpp_client_wrapper_glfw/',
-  ],
-  TargetPlatform.darwin_x64: <String>[
-    'FlutterMacOS.framework',
-  ],
   TargetPlatform.windows_x64: <String>[
     'flutter_windows.dll',
     'flutter_windows.dll.exp',
@@ -52,7 +37,7 @@
   UnpackCommand() {
     argParser.addOption(
       'target-platform',
-      allowed: <String>['darwin-x64', 'linux-x64', 'windows-x64'],
+      allowed: <String>['windows-x64'],
     );
     argParser.addOption('cache-dir',
         help: 'Location to output platform specific artifacts.');
@@ -74,15 +59,9 @@
     };
     final TargetPlatform targetPlatform = getTargetPlatformForName(argResults['target-platform']);
     switch (targetPlatform) {
-      case TargetPlatform.darwin_x64:
-        result.add(DevelopmentArtifact.macOS);
-        break;
       case TargetPlatform.windows_x64:
         result.add(DevelopmentArtifact.windows);
         break;
-      case TargetPlatform.linux_x64:
-        result.add(DevelopmentArtifact.linux);
-        break;
       default:
     }
     return result;
@@ -134,15 +113,9 @@
   bool copyCachedArtifacts(String targetDirectory) {
     String cacheStamp;
     switch (platform) {
-      case TargetPlatform.linux_x64:
-        cacheStamp = 'linux-sdk';
-        break;
       case TargetPlatform.windows_x64:
         cacheStamp = 'windows-sdk';
         break;
-      case TargetPlatform.darwin_x64:
-        cacheStamp = 'macos-sdk';
-        break;
       default:
         throwToolExit('Unsupported target platform: $platform');
     }
@@ -208,27 +181,17 @@
 
     try {
       fs.directory(targetDirectory).createSync(recursive: true);
-
-      // On macOS, delete the existing framework if any before copying in the
-      // new one, since it's a directory. On the other platforms, where files
-      // are just individual files, this isn't necessary since copying over
-      // existing files will do the right thing.
-      if (platform == TargetPlatform.darwin_x64) {
-        _copyMacOSFramework(
-            fs.path.join(sourceDirectory, artifactFiles[0]), targetDirectory);
-      } else {
-        for (final String entityName in artifactFiles) {
-          final String sourcePath = fs.path.join(sourceDirectory, entityName);
-          final String targetPath = fs.path.join(targetDirectory, entityName);
-          if (entityName.endsWith('/')) {
-            copyDirectorySync(
-              fs.directory(sourcePath),
-              fs.directory(targetPath),
-            );
-          } else {
-            fs.file(sourcePath)
-              .copySync(fs.path.join(targetDirectory, entityName));
-          }
+      for (final String entityName in artifactFiles) {
+        final String sourcePath = fs.path.join(sourceDirectory, entityName);
+        final String targetPath = fs.path.join(targetDirectory, entityName);
+        if (entityName.endsWith('/')) {
+          copyDirectorySync(
+            fs.directory(sourcePath),
+            fs.directory(targetPath),
+          );
+        } else {
+          fs.file(sourcePath)
+            .copySync(fs.path.join(targetDirectory, entityName));
         }
       }
 
@@ -270,43 +233,6 @@
     _lastCopiedHashFile(directory).writeAsStringSync(hash);
   }
 
-  /// Copies the framework at [frameworkPath] to [targetDirectory]
-  /// by invoking 'cp -R'.
-  ///
-  /// The shelling out is done to avoid complications with preserving special
-  /// files (e.g., symbolic links) in the framework structure.
-  ///
-  /// Removes any previous version of the framework that already exists in the
-  /// target directory.
-  void _copyMacOSFramework(String frameworkPath, String targetDirectory) {
-    _deleteFrameworkIfPresent(
-        fs.path.join(targetDirectory, fs.path.basename(frameworkPath)));
-
-    final RunResult result = processUtils
-        .runSync(<String>['cp', '-R', frameworkPath, targetDirectory]);
-    if (result.exitCode != 0) {
-      throw Exception(
-        'Failed to copy framework (exit ${result.exitCode}:\n'
-        '${result.stdout}\n---\n${result.stderr}',
-      );
-    }
-  }
-
-  /// Recursively deletes the framework at [frameworkPath], if it exists.
-  void _deleteFrameworkIfPresent(String frameworkPath) {
-    // Ensure that the path is a framework, to minimize the potential for
-    // catastrophic deletion bugs with bad arguments.
-    if (fs.path.extension(frameworkPath) != '.framework') {
-      throw Exception(
-          'Attempted to delete a non-framework directory: $frameworkPath');
-    }
-
-    final Directory directory = fs.directory(frameworkPath);
-    if (directory.existsSync()) {
-      directory.deleteSync(recursive: true);
-    }
-  }
-
   /// Returns the engine hash from [file] as a String, or null.
   ///
   /// If the file is missing, or cannot be read, returns null.
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart
index 6f6bcbf..d707235 100644
--- a/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart
@@ -5,6 +5,7 @@
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/build_system/build_system.dart';
+import 'package:flutter_tools/src/build_system/targets/dart.dart';
 import 'package:flutter_tools/src/build_system/targets/linux.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:mockito/mockito.dart';
@@ -28,11 +29,15 @@
     when(mockPlatform.isWindows).thenReturn(false);
     when(mockPlatform.isMacOS).thenReturn(false);
     when(mockPlatform.isLinux).thenReturn(true);
+    when(mockPlatform.environment).thenReturn(Map<String, String>.unmodifiable(<String, String>{}));
     testbed = Testbed(setup: () {
       Cache.flutterRoot = '';
       environment = Environment(
         outputDir: fs.currentDirectory,
         projectDir: fs.currentDirectory,
+        defines: <String, String>{
+          kBuildMode: 'debug',
+        }
       );
       fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux_glfw.so').createSync(recursive: true);
       fs.file('bin/cache/artifacts/engine/linux-x64/flutter_export.h').createSync();
@@ -49,36 +54,51 @@
   });
 
   test('Copies files to correct cache directory', () => testbed.run(() async {
-    final BuildResult result = await buildSystem.build(const UnpackLinux(), environment);
+    final BuildResult result = await buildSystem.build(const UnpackLinuxDebug(), environment);
 
     expect(result.hasException, false);
-    expect(fs.file('linux/flutter/libflutter_linux_glfw.so').existsSync(), true);
-    expect(fs.file('linux/flutter/flutter_export.h').existsSync(), true);
-    expect(fs.file('linux/flutter/flutter_messenger.h').existsSync(), true);
-    expect(fs.file('linux/flutter/flutter_plugin_registrar.h').existsSync(), true);
-    expect(fs.file('linux/flutter/flutter_glfw.h').existsSync(), true);
-    expect(fs.file('linux/flutter/icudtl.dat').existsSync(), true);
-    expect(fs.file('linux/flutter/cpp_client_wrapper_glfw/foo').existsSync(), true);
+    expect(fs.file('linux/flutter/ephemeral/libflutter_linux_glfw.so').existsSync(), true);
+    expect(fs.file('linux/flutter/ephemeral/flutter_export.h').existsSync(), true);
+    expect(fs.file('linux/flutter/ephemeral/flutter_messenger.h').existsSync(), true);
+    expect(fs.file('linux/flutter/ephemeral/flutter_plugin_registrar.h').existsSync(), true);
+    expect(fs.file('linux/flutter/ephemeral/flutter_glfw.h').existsSync(), true);
+    expect(fs.file('linux/flutter/ephemeral/icudtl.dat').existsSync(), true);
+    expect(fs.file('linux/flutter/ephemeral/cpp_client_wrapper_glfw/foo').existsSync(), true);
   }));
 
   test('Does not re-copy files unecessarily', () => testbed.run(() async {
-    await buildSystem.build(const UnpackLinux(), environment);
+    await buildSystem.build(const UnpackLinuxDebug(), environment);
     // Set a date in the far distant past to deal with the limited resolution
     // of the windows filesystem.
     final DateTime theDistantPast = DateTime(1991, 8, 23);
-    fs.file('linux/flutter/libflutter_linux_glfw.so').setLastModifiedSync(theDistantPast);
-    await buildSystem.build(const UnpackLinux(), environment);
+    fs.file('linux/flutter/ephemeral/libflutter_linux_glfw.so').setLastModifiedSync(theDistantPast);
+    await buildSystem.build(const UnpackLinuxDebug(), environment);
 
-    expect(fs.file('linux/flutter/libflutter_linux_glfw.so').statSync().modified, equals(theDistantPast));
+    expect(fs.file('linux/flutter/ephemeral/libflutter_linux_glfw.so').statSync().modified, equals(theDistantPast));
   }));
 
   test('Detects changes in input cache files', () => testbed.run(() async {
-    await buildSystem.build(const UnpackLinux(), environment);
+    await buildSystem.build(const UnpackLinuxDebug(), environment);
     fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux_glfw.so').writeAsStringSync('asd'); // modify cache.
 
-    await buildSystem.build(const UnpackLinux(), environment);
+    await buildSystem.build(const UnpackLinuxDebug(), environment);
 
-    expect(fs.file('linux/flutter/libflutter_linux_glfw.so').readAsStringSync(), 'asd');
+    expect(fs.file('linux/flutter/ephemeral/libflutter_linux_glfw.so').readAsStringSync(), 'asd');
+  }));
+
+  test('Copies artifacts to out directory', () => testbed.run(() async {
+    environment.buildDir.createSync(recursive: true);
+
+    // Create input files.
+    environment.buildDir.childFile('app.dill').createSync();
+
+    await const DebugBundleLinuxAssets().build(environment);
+    final Directory output = environment.outputDir
+      .childDirectory('flutter_assets');
+
+    expect(output.childFile('kernel_blob.bin').existsSync(), true);
+    expect(output.childFile('FontManifest.json').existsSync(), false);
+    expect(output.childFile('AssetManifest.json').existsSync(), true);
   }));
 }