|  | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file | 
|  | // for details. 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:args/args.dart'; | 
|  | import 'package:frontend_server/resident_frontend_server_utils.dart' | 
|  | show | 
|  | computeCachedDillAndCompilerOptionsPaths, | 
|  | ResidentCompilerInfo, | 
|  | sendAndReceiveResponse; | 
|  | import 'package:kernel/binary/tag.dart' show isValidSdkHash; | 
|  | import 'package:path/path.dart' as p; | 
|  | import 'package:pub/pub.dart'; | 
|  |  | 
|  | import 'core.dart'; | 
|  | import 'resident_frontend_constants.dart'; | 
|  | import 'resident_frontend_utils.dart'; | 
|  | import 'sdk.dart'; | 
|  | import 'unified_analytics.dart'; | 
|  |  | 
|  | typedef CompileRequestGeneratorCallback = String Function({ | 
|  | required String executable, | 
|  | required String outputDill, | 
|  | required ArgResults args, | 
|  | String? packages, | 
|  | }); | 
|  |  | 
|  | /// Uses the resident frontend compiler to compute a kernel file for | 
|  | /// [executable]. Throws a [FrontendCompilerException] if the compilation | 
|  | /// fails or if the source code contains compilation errors. | 
|  | /// | 
|  | /// [executable] is expected to contain a path to the dart source file and | 
|  | /// a package_config file. | 
|  | /// | 
|  | /// [serverInfoFile] is the location that should be checked to find an existing | 
|  | /// Resident Frontend Compiler. If one does not exist, a server is created and | 
|  | /// its address and port information is written to this file location. | 
|  | /// | 
|  | /// [args] is the [ArgResults] object that is created by the DartDev commands. | 
|  | /// This is where the optional path override for the serverInfoFile is passed | 
|  | /// in. | 
|  | /// | 
|  | /// [compileRequestGenerator] is applied to produce a request for the Resident | 
|  | /// Frontend Server. | 
|  | Future<DartExecutableWithPackageConfig> generateKernel( | 
|  | DartExecutableWithPackageConfig executable, | 
|  | File serverInfoFile, | 
|  | ArgResults args, | 
|  | CompileRequestGeneratorCallback compileRequestGenerator, { | 
|  | required bool quiet, | 
|  | bool aot = false, | 
|  | }) async { | 
|  | // Locates the package_config.json and cached kernel file, makes sure the | 
|  | // resident frontend server is up and running, and computes a kernel. | 
|  | await ensureCompilationServerIsRunning( | 
|  | serverInfoFile, | 
|  | quiet: quiet, | 
|  | ); | 
|  |  | 
|  | final packageRoot = _packageRootFor(executable); | 
|  | final packageConfig = | 
|  | packageRoot != null ? p.join(packageRoot, packageConfigName) : null; | 
|  |  | 
|  | final canonicalizedExecutablePath = p.canonicalize(executable.executable); | 
|  | final cachedDillPath = | 
|  | computeCachedDillAndCompilerOptionsPaths(canonicalizedExecutablePath) | 
|  | .cachedDillPath; | 
|  |  | 
|  | Map<String, dynamic> result; | 
|  | try { | 
|  | result = await sendAndReceiveResponse( | 
|  | compileRequestGenerator( | 
|  | executable: canonicalizedExecutablePath, | 
|  | outputDill: cachedDillPath, | 
|  | packages: packageConfig, | 
|  | args: args, | 
|  | ), | 
|  | serverInfoFile, | 
|  | ); | 
|  | } on FileSystemException catch (e) { | 
|  | throw FrontendCompilerException._(e.message, CompilationIssue.serverError); | 
|  | } | 
|  | if (!result[responseSuccessString]) { | 
|  | if (result.containsKey(responseErrorString)) { | 
|  | throw FrontendCompilerException._( | 
|  | result[responseErrorString], | 
|  | CompilationIssue.serverError, | 
|  | ); | 
|  | } else { | 
|  | throw FrontendCompilerException._( | 
|  | (result[responseOutputString] as List<dynamic>).join('\n'), | 
|  | CompilationIssue.compilationError, | 
|  | ); | 
|  | } | 
|  | } | 
|  | return DartExecutableWithPackageConfig( | 
|  | executable: cachedDillPath, | 
|  | packageConfig: packageConfig, | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// Ensures that a Resident Frontend Compiler associated with [serverInfoFile] | 
|  | /// will be running at the point when the future returned by this function | 
|  | /// completes. Throws a [FrontendCompilerException] if starting the server | 
|  | /// fails. | 
|  | Future<void> ensureCompilationServerIsRunning( | 
|  | File serverInfoFile, { | 
|  | required bool quiet, | 
|  | }) async { | 
|  | if (serverInfoFile.existsSync()) { | 
|  | final residentCompilerInfo = ResidentCompilerInfo.fromFile(serverInfoFile); | 
|  | if (residentCompilerInfo.sdkHash != null && | 
|  | isValidSdkHash(residentCompilerInfo.sdkHash!)) { | 
|  | // There is already a Resident Frontend Compiler associated with | 
|  | // [serverInfoFile] that is running and compatible with the Dart SDK that | 
|  | // the user is currently using. | 
|  | return; | 
|  | } else { | 
|  | if (!quiet) { | 
|  | log.stderr( | 
|  | 'The Dart SDK has been upgraded or downgraded since the Resident ' | 
|  | 'Frontend Compiler was started, so the Resident Frontend Compiler will ' | 
|  | 'now be restarted for compatibility reasons.', | 
|  | ); | 
|  | } | 
|  | await shutDownOrForgetResidentFrontendCompiler(serverInfoFile); | 
|  | } | 
|  | } | 
|  | try { | 
|  | Directory(p.dirname(serverInfoFile.path)).createSync(recursive: true); | 
|  | final Process frontendServerProcess; | 
|  | if (File(sdk.frontendServerAotSnapshot).existsSync()) { | 
|  | frontendServerProcess = await Process.start( | 
|  | sdk.dartAotRuntime, | 
|  | [ | 
|  | sdk.frontendServerAotSnapshot, | 
|  | '--resident-info-file-name=${serverInfoFile.absolute.path}' | 
|  | ], | 
|  | workingDirectory: homeDir?.path, | 
|  | mode: ProcessStartMode.detachedWithStdio, | 
|  | ); | 
|  | } else { | 
|  | throw StateError('Unable to find snapshot for frontend server'); | 
|  | } | 
|  |  | 
|  | final serverOutput = | 
|  | String.fromCharCodes(await frontendServerProcess.stdout.first).trim(); | 
|  | if (serverOutput.startsWith('Error')) { | 
|  | throw StateError(serverOutput); | 
|  | } | 
|  | if (!quiet) { | 
|  | // Prints the server's address and port information | 
|  | log.stdout(serverOutput); | 
|  | log.stdout(''); | 
|  | log.stdout( | 
|  | 'Run dart compilation-server shutdown to terminate the process.', | 
|  | ); | 
|  | } | 
|  | } catch (e) { | 
|  | throw FrontendCompilerException._( | 
|  | e.toString(), | 
|  | CompilationIssue.serverCreationError, | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns the path to the root of the [executable]'s package, or null | 
|  | /// if it is a standalone dart file. | 
|  | String? _packageRootFor(DartExecutableWithPackageConfig executable) { | 
|  | Directory currentDirectory = | 
|  | Directory(p.dirname(p.canonicalize(executable.executable))); | 
|  |  | 
|  | while (currentDirectory.parent.path != currentDirectory.path) { | 
|  | if (File(p.join(currentDirectory.path, packageConfigName)).existsSync()) { | 
|  | return currentDirectory.path; | 
|  | } | 
|  | currentDirectory = currentDirectory.parent; | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /// Indicates the type of issue encountered with the | 
|  | /// Resident Frontend Compiler | 
|  | enum CompilationIssue { | 
|  | /// Communication with the Resident Frontend Compiler failed. | 
|  | serverError, | 
|  |  | 
|  | /// The Resident Frontend Compiler failed to launch | 
|  | serverCreationError, | 
|  |  | 
|  | /// There were compilation errors in the Dart source code. | 
|  | compilationError, | 
|  |  | 
|  | /// Resident mode is only supported for sources within Dart packages | 
|  | standaloneProgramError, | 
|  | } | 
|  |  | 
|  | /// Indicates an error with the Resident Frontend Compiler. | 
|  | class FrontendCompilerException implements Exception { | 
|  | final String message; | 
|  | final CompilationIssue issue; | 
|  |  | 
|  | FrontendCompilerException._(this.message, this.issue); | 
|  |  | 
|  | @override | 
|  | String toString() { | 
|  | return 'FrontendCompilerException: $message'; | 
|  | } | 
|  | } |