[flutter_tools] Support profile and release builds on Linux (#57135)

diff --git a/packages/flutter_tools/bin/tool_backend.dart b/packages/flutter_tools/bin/tool_backend.dart
index 7ffd7f0..4f55437f 100644
--- a/packages/flutter_tools/bin/tool_backend.dart
+++ b/packages/flutter_tools/bin/tool_backend.dart
@@ -45,10 +45,7 @@
   final String flutterExecutable = path.join(
     flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
   final String bundlePlatform = targetPlatform == 'windows-x64' ? 'windows' : 'linux';
-  // TODO(jonahwilliams): currently all Linux builds are debug builds. Remove the
-  // hardcoded mode when profile and release support is added.
-  final String bundleMode = targetPlatform == 'windows-x64' ? buildMode : 'debug';
-  final String target = '${bundleMode}_bundle_${bundlePlatform}_assets';
+  final String target = '${buildMode}_bundle_${bundlePlatform}_assets';
 
   final Process assembleProcess = await Process.start(
     flutterExecutable,
@@ -61,7 +58,7 @@
       '--output=build',
       '-dTargetPlatform=$targetPlatform',
       '-dTrackWidgetCreation=$trackWidgetCreation',
-      '-dBuildMode=$bundleMode',
+      '-dBuildMode=$buildMode',
       '-dTargetFile=$flutterTarget',
       '-dTreeShakeIcons="$treeShakeIcons"',
       '-dDartObfuscation=$dartObfuscation',
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 754c3b1..9532f33 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -298,6 +298,7 @@
       TargetPlatform.android_x64,
       TargetPlatform.ios,
       TargetPlatform.darwin_x64,
+      TargetPlatform.linux_x64,
       TargetPlatform.windows_x64,
     ].contains(platform);
   }
diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
index ded87bd..dd5ba5b 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
@@ -340,7 +340,7 @@
     KernelSnapshot(),
   ];
 
-  final TargetPlatform  targetPlatform;
+  final TargetPlatform targetPlatform;
 }
 
 /// Generate an ELF binary from a dart kernel file in release mode.
@@ -373,7 +373,7 @@
     KernelSnapshot(),
   ];
 
-  final TargetPlatform  targetPlatform;
+  final TargetPlatform targetPlatform;
 }
 
 /// Copies the pre-built flutter aot bundle.
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 9d0a8e3..00a03c4 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart
@@ -98,12 +98,9 @@
   }
 }
 
-/// Creates a debug bundle for the Linux desktop target.
-class DebugBundleLinuxAssets extends Target {
-  const DebugBundleLinuxAssets();
-
-  @override
-  String get name => 'debug_bundle_linux_assets';
+/// Creates a bundle for the Linux desktop target.
+abstract class BundleLinuxAssets extends Target {
+  const BundleLinuxAssets();
 
   @override
   List<Target> get dependencies => const <Target>[
@@ -113,18 +110,12 @@
 
   @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.pattern('{PROJECT_DIR}/pubspec.yaml'),
     ...IconTreeShaker.inputs,
   ];
 
   @override
-  List<Source> get outputs => const <Source>[
-    Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'),
-  ];
-
-  @override
   List<String> get depfiles => const <String>[
     'flutter_assets.d',
   ];
@@ -132,7 +123,7 @@
   @override
   Future<void> build(Environment environment) async {
     if (environment.defines[kBuildMode] == null) {
-      throw MissingDefineException(kBuildMode, 'debug_bundle_linux_assets');
+      throw MissingDefineException(kBuildMode, 'bundle_linux_assets');
     }
     final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
     final Directory outputDirectory = environment.outputDir
@@ -157,3 +148,89 @@
     );
   }
 }
+
+/// A wrapper for AOT compilation that copies app.so into the output directory.
+class LinuxAotBundle extends Target {
+  /// Create a [LinuxAotBundle] wrapper for [aotTarget].
+  const LinuxAotBundle(this.aotTarget);
+
+  /// The [AotElfBase] subclass that produces the app.so.
+  final AotElfBase aotTarget;
+
+  @override
+  String get name => 'linux_aot_bundle';
+
+  @override
+  List<Source> get inputs => const <Source>[
+    Source.pattern('{BUILD_DIR}/app.so'),
+  ];
+
+  @override
+  List<Source> get outputs => const <Source>[
+    Source.pattern('{OUTPUT_DIR}/lib/libapp.so'),
+  ];
+
+  @override
+  List<Target> get dependencies => <Target>[
+    aotTarget,
+  ];
+
+  @override
+  Future<void> build(Environment environment) async {
+    final File outputFile = environment.buildDir.childFile('app.so');
+    final Directory outputDirectory = environment.outputDir.childDirectory('lib');
+    if (!outputDirectory.existsSync()) {
+      outputDirectory.createSync(recursive: true);
+    }
+    outputFile.copySync(outputDirectory.childFile('libapp.so').path);
+  }
+}
+
+class DebugBundleLinuxAssets extends BundleLinuxAssets {
+  const DebugBundleLinuxAssets();
+
+  @override
+  String get name => 'debug_bundle_linux_assets';
+
+  @override
+  List<Source> get inputs => <Source>[
+    const Source.pattern('{BUILD_DIR}/app.dill'),
+  ];
+
+  @override
+  List<Source> get outputs => <Source>[
+    const Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'),
+  ];
+}
+
+class ProfileBundleLinuxAssets extends BundleLinuxAssets {
+  const ProfileBundleLinuxAssets();
+
+  @override
+  String get name => 'profile_bundle_linux_assets';
+
+  @override
+  List<Source> get outputs => const <Source>[];
+
+  @override
+  List<Target> get dependencies => <Target>[
+    ...super.dependencies,
+    const LinuxAotBundle(AotElfProfile(TargetPlatform.linux_x64)),
+  ];
+}
+
+class ReleaseBundleLinuxAssets extends BundleLinuxAssets {
+  const ReleaseBundleLinuxAssets();
+
+  @override
+  String get name => 'release_bundle_linux_assets';
+
+  @override
+  List<Source> get outputs => const <Source>[];
+
+  @override
+  List<Target> get dependencies => <Target>[
+    ...super.dependencies,
+    const LinuxAotBundle(AotElfRelease(TargetPlatform.linux_x64)),
+  ];
+}
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index 09443ef..74aa1ad 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -40,6 +40,8 @@
   ReleaseMacOSBundleFlutterAssets(),
   // Linux targets
   DebugBundleLinuxAssets(),
+  ProfileBundleLinuxAssets(),
+  ReleaseBundleLinuxAssets(),
   // Web targets
   WebServiceWorker(),
   ReleaseAndroidApplication(),
diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart
index d24c4fd..42c92fa 100644
--- a/packages/flutter_tools/lib/src/linux/build_linux.dart
+++ b/packages/flutter_tools/lib/src/linux/build_linux.dart
@@ -55,15 +55,6 @@
 
   createPluginSymlinks(linuxProject.project);
 
-  if (!buildInfo.isDebug) {
-    const String warning = '🚧 ';
-    globals.printStatus(warning * 20);
-    globals.printStatus('Warning: Only debug is currently implemented for Linux. This is effectively a debug build.');
-    globals.printStatus('See https://github.com/flutter/flutter/issues/38478 for details and updates.');
-    globals.printStatus(warning * 20);
-    globals.printStatus('');
-  }
-
   final Status status = globals.logger.startProgress(
     'Building Linux application...',
     timeout: null,
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/app/linux.tmpl/CMakeLists.txt.tmpl
index a181158..2fb186a 100644
--- a/packages/flutter_tools/templates/app/linux.tmpl/CMakeLists.txt.tmpl
+++ b/packages/flutter_tools/templates/app/linux.tmpl/CMakeLists.txt.tmpl
@@ -82,3 +82,9 @@
   " COMPONENT Runtime)
 INSTALL(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
   DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the lib directory on non-Debug builds only.
+if (NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+  INSTALL(DIRECTORY "${PROJECT_BUILD_DIR}/lib"
+    DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime)
+endif()
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/flutter/.template_version b/packages/flutter_tools/templates/app/linux.tmpl/flutter/.template_version
index d8263ee..e440e5c 100644
--- a/packages/flutter_tools/templates/app/linux.tmpl/flutter/.template_version
+++ b/packages/flutter_tools/templates/app/linux.tmpl/flutter/.template_version
@@ -1 +1 @@
-2
\ No newline at end of file
+3
\ No newline at end of file
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/main.cc b/packages/flutter_tools/templates/app/linux.tmpl/main.cc
index e92332d..5211469 100644
--- a/packages/flutter_tools/templates/app/linux.tmpl/main.cc
+++ b/packages/flutter_tools/templates/app/linux.tmpl/main.cc
@@ -16,9 +16,10 @@
 // Runs the application in headless mode, without a window.
 void RunHeadless(const std::string& icu_data_path,
                  const std::string& assets_path,
-                 const std::vector<std::string>& arguments) {
+                 const std::vector<std::string>& arguments,
+                 const std::string& aot_library_path) {
   flutter::FlutterEngine engine;
-  engine.Start(icu_data_path, assets_path, arguments);
+  engine.Start(icu_data_path, assets_path, arguments, aot_library_path);
   RegisterPlugins(&engine);
   while (true) {
     engine.RunEventLoopWithTimeout();
@@ -32,6 +33,9 @@
   std::string assets_path = data_directory + "/flutter_assets";
   std::string icu_data_path = data_directory + "/icudtl.dat";
 
+  std::string lib_directory = "lib";
+  std::string aot_library_path = lib_directory + "/libapp.so";
+
   // Arguments for the Flutter Engine.
   std::vector<std::string> arguments;
 
@@ -43,10 +47,10 @@
 
   // Start the engine.
   if (!flutter_controller.CreateWindow(window_properties, assets_path,
-                                       arguments)) {
+                                       arguments, aot_library_path)) {
     if (getenv("DISPLAY") == nullptr) {
       std::cout << "No DISPLAY; falling back to headless mode." << std::endl;
-      RunHeadless(icu_data_path, assets_path, arguments);
+      RunHeadless(icu_data_path, assets_path, arguments, aot_library_path);
       return EXIT_SUCCESS;
     }
     return EXIT_FAILURE;
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
index e85dbb9..c88a4bd 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
@@ -401,25 +401,6 @@
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
   });
 
-  testUsingContext('Release build prints an under-construction warning', () async {
-    final BuildCommand command = BuildCommand();
-    setUpMockProjectFilesForBuild();
-    processManager = FakeProcessManager.list(<FakeCommand>[
-      cmakeCommand('release'),
-      ninjaCommand('release'),
-    ]);
-
-    await createTestCommandRunner(command).run(
-      const <String>['build', 'linux', '--no-pub']
-    );
-    expect(testLogger.statusText, contains('🚧'));
-  }, overrides: <Type, Generator>{
-    FileSystem: () => fileSystem,
-    ProcessManager: () => processManager,
-    Platform: () => linuxPlatform,
-    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
-  });
-
   testUsingContext('hidden when not enabled on Linux host', () {
     expect(BuildLinuxCommand().hidden, true);
   }, overrides: <Type, Generator>{
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 05a6e90..9daba1f 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
@@ -7,6 +7,7 @@
 import 'package:flutter_tools/src/artifacts.dart';
 import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/base/file_system.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/targets/dart.dart';
 import 'package:flutter_tools/src/build_system/targets/linux.dart';
@@ -100,6 +101,72 @@
     FileSystem: () => fileSystem,
     ProcessManager: () => FakeProcessManager.any(),
   });
+
+  testUsingContext('ProfileBundleLinuxAssets copies artifacts to out directory', () async {
+    final Environment testEnvironment = Environment.test(
+      fileSystem.currentDirectory,
+      defines: <String, String>{
+        kBuildMode: 'profile',
+      },
+      artifacts: MockArtifacts(),
+      processManager: FakeProcessManager.any(),
+      fileSystem: fileSystem,
+      logger: BufferLogger.test(),
+    );
+
+    testEnvironment.buildDir.createSync(recursive: true);
+
+    // Create input files.
+    testEnvironment.buildDir.childFile('app.so').createSync();
+
+    await const LinuxAotBundle(AotElfProfile(TargetPlatform.linux_x64)).build(testEnvironment);
+    await const ProfileBundleLinuxAssets().build(testEnvironment);
+    final Directory libDir = testEnvironment.outputDir
+      .childDirectory('lib');
+    final Directory assetsDir = testEnvironment.outputDir
+      .childDirectory('flutter_assets');
+
+    expect(libDir.childFile('libapp.so'), exists);
+    expect(assetsDir.childFile('AssetManifest.json'), exists);
+    // No bundled fonts
+    expect(assetsDir.childFile('FontManifest.json'), isNot(exists));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => FakeProcessManager.any(),
+  });
+
+  testUsingContext('ReleaseBundleLinuxAssets copies artifacts to out directory', () async {
+    final Environment testEnvironment = Environment.test(
+      fileSystem.currentDirectory,
+      defines: <String, String>{
+        kBuildMode: 'release',
+      },
+      artifacts: MockArtifacts(),
+      processManager: FakeProcessManager.any(),
+      fileSystem: fileSystem,
+      logger: BufferLogger.test(),
+    );
+
+    testEnvironment.buildDir.createSync(recursive: true);
+
+    // Create input files.
+    testEnvironment.buildDir.childFile('app.so').createSync();
+
+    await const LinuxAotBundle(AotElfRelease(TargetPlatform.linux_x64)).build(testEnvironment);
+    await const ReleaseBundleLinuxAssets().build(testEnvironment);
+    final Directory libDir = testEnvironment.outputDir
+      .childDirectory('lib');
+    final Directory assetsDir = testEnvironment.outputDir
+      .childDirectory('flutter_assets');
+
+    expect(libDir.childFile('libapp.so'), exists);
+    expect(assetsDir.childFile('AssetManifest.json'), exists);
+    // No bundled fonts
+    expect(assetsDir.childFile('FontManifest.json'), isNot(exists));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => FakeProcessManager.any(),
+  });
 }
 
 void setUpCacheDirectory(FileSystem fileSystem) {