| // 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'; |
| } |
| } |