| // Copyright (c) 2013, 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. | |
| library; | |
| import 'dart:async' show Future, StreamSubscription; | |
| import 'dart:convert' show utf8, LineSplitter; | |
| import 'dart:io' show exit, File, FileMode, Platform, stdin, stderr; | |
| import 'dart:isolate' show Isolate; | |
| // ignore: implementation_imports | |
| import 'package:front_end/src/api_unstable/dart2js.dart' as fe; | |
| import 'package:shell_arg_splitter/shell_arg_splitter.dart'; | |
| import '../compiler_api.dart' as api; | |
| import 'commandline_options.dart'; | |
| import 'common/ram_usage.dart'; | |
| import 'compiler.dart' as default_compiler show Compiler; | |
| import 'io/mapped_file.dart'; | |
| import 'options.dart' | |
| show CompilerOptions, CompilerStage, DumpInfoFormat, FeatureOptions; | |
| import 'source_file_provider.dart'; | |
| import 'util/util.dart' show stackTraceFilePrefix; | |
| const String _defaultSpecificationUri = '../../../../sdk/lib/libraries.json'; | |
| const String outputLanguageDart = 'Dart'; | |
| /// A string to identify the revision or build. | |
| /// | |
| /// This ID is displayed if the compiler crashes and in verbose mode, and is | |
| /// an aid in reproducing bug reports. | |
| /// | |
| /// The actual string is rewritten by a wrapper script when included in the sdk. | |
| String? buildID; | |
| /// The data passed to the [HandleOption] callback is either a single | |
| /// string argument, or the arguments iterator for multiple arguments | |
| /// handlers. | |
| typedef HandleOption = void Function(String data); | |
| typedef HandleMultiOption = void Function(Iterator<String> data); | |
| abstract class OptionHandler<T extends Object> { | |
| String get pattern; | |
| void handle(T argument); | |
| } | |
| class _OneOption implements OptionHandler<String> { | |
| @override | |
| final String pattern; | |
| final HandleOption _handle; | |
| @override | |
| void handle(String argument) { | |
| _handle(argument); | |
| } | |
| _OneOption(this.pattern, this._handle); | |
| } | |
| class _ManyOptions implements OptionHandler<Iterator<String>> { | |
| @override | |
| final String pattern; | |
| final HandleMultiOption _handle; | |
| @override | |
| void handle(Iterator<String> argument) { | |
| _handle(argument); | |
| } | |
| _ManyOptions(this.pattern, this._handle); | |
| } | |
| /// Extract the parameter of an option. | |
| /// | |
| /// For example, in ['--out=fisk.js'] and ['-ohest.js'], the parameters | |
| /// are ['fisk.js'] and ['hest.js'], respectively. | |
| String? extractOptionalParameter(String argument) { | |
| // m[0] is the entire match (which will be equal to argument). m[1] | |
| // is something like "-o" or "--out=", and m[2] is the parameter. | |
| final m = RegExp('^(-[a-zA-Z]|--.+=)(.*)').firstMatch(argument); | |
| return m?[2]; | |
| } | |
| /// Extract the parameter of an option. | |
| /// | |
| /// For example, in ['--out=fisk.js'] and ['-ohest.js'], the parameters | |
| /// are ['fisk.js'] and ['hest.js'], respectively. | |
| String extractParameter(String argument) { | |
| // m[0] is the entire match (which will be equal to argument). m[1] | |
| // is something like "-o" or "--out=", and m[2] is the parameter. | |
| final m = RegExp('^(-[a-zA-Z]|--.+=)(.*)').firstMatch(argument); | |
| if (m == null) { | |
| _helpAndFail('Unknown option "$argument".'); | |
| } | |
| return m[2]!; | |
| } | |
| String extractPath(String argument, {bool isDirectory = true}) { | |
| String path = fe.nativeToUriPath(extractParameter(argument)); | |
| return !path.endsWith("/") && isDirectory ? "$path/" : path; | |
| } | |
| void parseCommandLine(List<OptionHandler> handlers, List<String> argv) { | |
| // TODO(ahe): Use ../../args/args.dart for parsing options instead. | |
| var patterns = <String>[]; | |
| for (OptionHandler handler in handlers) { | |
| patterns.add(handler.pattern); | |
| } | |
| var pattern = RegExp('^(${patterns.join(")\$|^(")})\$'); | |
| Iterator<String> arguments = argv.iterator; | |
| OUTER: | |
| while (arguments.moveNext()) { | |
| String argument = arguments.current; | |
| final match = pattern.firstMatch(argument)!; | |
| assert(match.groupCount == handlers.length); | |
| for (int i = 0; i < handlers.length; i++) { | |
| if (match[i + 1] != null) { | |
| OptionHandler handler = handlers[i]; | |
| if (handler is _ManyOptions) { | |
| handler.handle(arguments); | |
| } else { | |
| handler.handle(argument); | |
| } | |
| continue OUTER; | |
| } | |
| } | |
| throw 'Internal error: "$argument" did not match'; | |
| } | |
| } | |
| FormattingDiagnosticHandler? diagnosticHandler; | |
| Future<api.CompilationResult> compile( | |
| List<String> argv, { | |
| fe.InitializedCompilerState? kernelInitializedCompilerState, | |
| }) { | |
| Stopwatch wallclock = Stopwatch()..start(); | |
| stackTraceFilePrefix = '${Uri.base}'; | |
| Uri? entryUri; | |
| Uri? inputDillUri; | |
| Uri librariesSpecificationUri = Uri.base.resolve('lib/libraries.json'); | |
| Uri? out; | |
| Uri? sourceMapOut; | |
| List<String>? bazelPaths; | |
| List<Uri>? multiRoots; | |
| String? multiRootScheme = 'org-dartlang-app'; | |
| Uri? packageConfig; | |
| List<String> options = <String>[]; | |
| bool wantHelp = false; | |
| bool wantVersion = false; | |
| bool trustTypeAnnotations = false; | |
| bool checkedMode = false; | |
| bool strongMode = true; | |
| List<String> hints = <String>[]; | |
| bool? verbose; | |
| bool? throwOnError; | |
| int? throwOnErrorCount; | |
| bool? showWarnings; | |
| bool? showHints; | |
| bool? enableColors; | |
| int? optimizationLevel; | |
| Uri? platformBinaries; | |
| Map<String, String> environment = <String, String>{}; | |
| FeatureOptions features = FeatureOptions(); | |
| String? invoker; | |
| void passThrough(String argument) => options.add(argument); | |
| void ignoreOption(String argument) {} | |
| if (buildID != null) { | |
| passThrough("--build-id=$buildID"); | |
| } | |
| Uri extractResolvedFileUri(String argument) { | |
| return Uri.base.resolve(extractPath(argument, isDirectory: false)); | |
| } | |
| void setEntryUri(String argument) { | |
| entryUri = extractResolvedFileUri(argument); | |
| options.add('${Flags.entryUri}=$entryUri'); | |
| } | |
| void setInputDillUri(String argument) { | |
| inputDillUri = extractResolvedFileUri(argument); | |
| options.add('${Flags.inputDill}=$inputDillUri'); | |
| } | |
| void setLibrarySpecificationUri(String argument) { | |
| librariesSpecificationUri = extractResolvedFileUri(argument); | |
| } | |
| void setPackageConfig(String argument) { | |
| packageConfig = extractResolvedFileUri(argument); | |
| } | |
| void Function(String) setDataUri(String flag) { | |
| return (String argument) { | |
| final uri = fe.nativeToUri(extractPath(argument, isDirectory: false)); | |
| options.add('$flag=$uri'); | |
| }; | |
| } | |
| void setOutput(Iterator<String> arguments) { | |
| String option = arguments.current; | |
| String path; | |
| if (option == '-o' || option == '--out' || option == '--output') { | |
| if (!arguments.moveNext()) { | |
| _helpAndFail("Missing file after '$option' option."); | |
| } | |
| path = arguments.current; | |
| } else { | |
| path = extractParameter(option); | |
| } | |
| out = Uri.base.resolve(fe.nativeToUriPath(path)); | |
| options.add('--out=$out'); | |
| } | |
| void setOptimizationLevel(String argument) { | |
| final value = int.tryParse(extractParameter(argument)); | |
| if (value == null || value < 0 || value > 4) { | |
| _helpAndFail( | |
| "Unsupported optimization level '$argument', " | |
| "supported levels are: 0, 1, 2, 3, 4", | |
| ); | |
| } | |
| if (optimizationLevel != null) { | |
| print( | |
| "Optimization level '$argument' ignored " | |
| "due to preceding '-O$optimizationLevel'", | |
| ); | |
| return; | |
| } | |
| optimizationLevel = value; | |
| } | |
| void setOutputType(String argument) { | |
| if (argument == '--output-type=dart' || | |
| argument == '--output-type=dart-multi') { | |
| _helpAndFail( | |
| "--output-type=dart is no longer supported. It was deprecated " | |
| "since Dart 1.11 and removed in Dart 1.19.", | |
| ); | |
| } | |
| } | |
| Never setStrip(String argument) { | |
| _helpAndFail( | |
| "Option '--force-strip' is not in use now that" | |
| "--output-type=dart is no longer supported.", | |
| ); | |
| } | |
| void setBazelPaths(String argument) { | |
| String paths = extractParameter(argument); | |
| bazelPaths = <String>[...paths.split(',')]; | |
| } | |
| void setMultiRoots(String argument) { | |
| String paths = extractParameter(argument); | |
| (multiRoots ??= <Uri>[]).addAll(paths.split(',').map(fe.nativeToUri)); | |
| } | |
| void setMultiRootScheme(String argument) { | |
| multiRootScheme = extractParameter(argument); | |
| } | |
| String getDepsOutput(Iterable<Uri> sourceFiles) { | |
| var filenames = sourceFiles.map((uri) => '$uri').toList(); | |
| filenames.sort(); | |
| return filenames.join("\n"); | |
| } | |
| void setAllowNativeExtensions(String argument) { | |
| _helpAndFail("Option '${Flags.allowNativeExtensions}' is not supported."); | |
| } | |
| void setVerbose(_) { | |
| verbose = true; | |
| passThrough('--verbose'); | |
| } | |
| void setTrustTypeAnnotations(String argument) { | |
| trustTypeAnnotations = true; | |
| } | |
| void setCheckedMode(String argument) { | |
| checkedMode = true; | |
| passThrough(argument); | |
| } | |
| void addInEnvironment(Iterator<String> arguments) { | |
| final isDefine = arguments.current.startsWith('--define'); | |
| String argument; | |
| if (arguments.current == '--define') { | |
| arguments.moveNext(); | |
| argument = arguments.current; | |
| } else { | |
| argument = arguments.current.substring(isDefine ? '--define='.length : 2); | |
| } | |
| // Allow for ' ' or '=' after --define | |
| int eqIndex = argument.indexOf('='); | |
| if (eqIndex <= 0) { | |
| _helpAndFail('Invalid value for --define: $argument'); | |
| } | |
| String name = argument.substring(0, eqIndex); | |
| String value = argument.substring(eqIndex + 1); | |
| environment[name] = value; | |
| } | |
| void setCategories(String argument) { | |
| List<String> categories = extractParameter(argument).split(','); | |
| bool isServerMode = categories.length == 1 && categories.single == "Server"; | |
| if (isServerMode) { | |
| hints.add( | |
| "The --categories flag is deprecated and will be deleted in a " | |
| "future release, please use '${Flags.serverMode}' instead of " | |
| "'--categories=Server'.", | |
| ); | |
| passThrough(Flags.serverMode); | |
| } else { | |
| hints.add( | |
| "The --categories flag is deprecated, see the usage for details.", | |
| ); | |
| } | |
| } | |
| void setPlatformBinaries(String argument) { | |
| platformBinaries = Uri.base.resolve( | |
| extractPath(argument, isDirectory: true), | |
| ); | |
| } | |
| List<Uri> setUriList(String flag, String argument) { | |
| String list = extractParameter(argument); | |
| List<Uri> uris = list.split(',').map(fe.nativeToUri).toList(); | |
| String uriList = uris.map((uri) => '$uri').join(','); | |
| options.add('$flag=$uriList'); | |
| return uris; | |
| } | |
| void setDillDependencies(String argument) { | |
| setUriList(Flags.dillDependencies, argument); | |
| } | |
| void setDumpInfo(String argument) { | |
| final hasEnumFormatMatch = DumpInfoFormat.values.any( | |
| (e) => '${Flags.dumpInfo}=${e.name}' == argument, | |
| ); | |
| if (argument == Flags.dumpInfo || hasEnumFormatMatch) { | |
| passThrough(argument); | |
| return; | |
| } | |
| _helpAndFail( | |
| "Unsupported dump-info format '$argument', " | |
| "supported formats: " | |
| "${DumpInfoFormat.values.map((e) => e.name).join(', ')}", | |
| ); | |
| } | |
| void setInvoker(String argument) { | |
| invoker = extractParameter(argument); | |
| } | |
| void handleThrowOnError(String argument) { | |
| throwOnError = true; | |
| final parameter = extractOptionalParameter(argument); | |
| if (parameter != null) { | |
| var count = int.parse(parameter); | |
| throwOnErrorCount = count; | |
| } | |
| } | |
| void handleShortOptions(String argument) { | |
| var shortOptions = argument.substring(1).split(""); | |
| for (var shortOption in shortOptions) { | |
| switch (shortOption) { | |
| case 'v': | |
| setVerbose(null); | |
| break; | |
| case 'h': | |
| case '?': | |
| wantHelp = true; | |
| break; | |
| case 'c': | |
| setCheckedMode(Flags.enableCheckedMode); | |
| break; | |
| case 'm': | |
| passThrough(Flags.minify); | |
| break; | |
| default: | |
| throw 'Internal error: "$shortOption" did not match'; | |
| } | |
| } | |
| } | |
| List<String> arguments = <String>[]; | |
| List<OptionHandler> handlers = <OptionHandler>[ | |
| _OneOption('${Flags.entryUri}=.+', setEntryUri), | |
| _OneOption('${Flags.inputDill}=.+', setInputDillUri), | |
| _OneOption('-[chvm?]+', handleShortOptions), | |
| _OneOption('--throw-on-error(?:=[0-9]+)?', handleThrowOnError), | |
| _OneOption(Flags.suppressWarnings, (String argument) { | |
| showWarnings = false; | |
| passThrough(argument); | |
| }), | |
| _OneOption(Flags.fatalWarnings, passThrough), | |
| _OneOption(Flags.suppressHints, (String argument) { | |
| showHints = false; | |
| passThrough(argument); | |
| }), | |
| // TODO(sigmund): remove entirely after Dart 1.20 | |
| _OneOption( | |
| '--output-type=dart|--output-type=dart-multi|--output-type=js', | |
| setOutputType, | |
| ), | |
| _OneOption('--use-kernel', ignoreOption), | |
| _OneOption(Flags.platformBinaries, setPlatformBinaries), | |
| _OneOption(Flags.noFrequencyBasedMinification, passThrough), | |
| _OneOption(Flags.verbose, setVerbose), | |
| _OneOption(Flags.progress, passThrough), | |
| _OneOption(Flags.reportMetrics, passThrough), | |
| _OneOption(Flags.reportAllMetrics, passThrough), | |
| _OneOption(Flags.version, (_) => wantVersion = true), | |
| _OneOption('--library-root=.+', ignoreOption), | |
| _OneOption('--libraries-spec=.+', setLibrarySpecificationUri), | |
| _OneOption('${Flags.dillDependencies}=.+', setDillDependencies), | |
| _OneOption('${Flags.sources}=.+', ignoreOption), | |
| _OneOption( | |
| '${Flags.globalInferenceUri}=.+', | |
| setDataUri(Flags.globalInferenceUri), | |
| ), | |
| _OneOption('${Flags.closedWorldUri}=.+', setDataUri(Flags.closedWorldUri)), | |
| _OneOption('${Flags.codegenUri}=.+', setDataUri(Flags.codegenUri)), | |
| _OneOption('${Flags.codegenShard}=.+', passThrough), | |
| _OneOption('${Flags.codegenShards}=.+', passThrough), | |
| _OneOption(Flags.cfeOnly, passThrough), | |
| _OneOption(Flags.memoryMappedFiles, passThrough), | |
| _OneOption('${Flags.stage}=.+', passThrough), | |
| _OneOption(Flags.debugGlobalInference, passThrough), | |
| _ManyOptions('--output(?:=.+)?|--out(?:=.+)?|-o.*', setOutput), | |
| _OneOption('-O.*', setOptimizationLevel), | |
| _OneOption(Flags.allowMockCompilation, ignoreOption), | |
| _OneOption(Flags.fastStartup, ignoreOption), | |
| _OneOption(Flags.genericMethodSyntax, ignoreOption), | |
| _OneOption(Flags.initializingFormalAccess, ignoreOption), | |
| _OneOption(Flags.minify, passThrough), | |
| _OneOption(Flags.noMinify, passThrough), | |
| _OneOption(Flags.omitLateNames, passThrough), | |
| _OneOption(Flags.noOmitLateNames, passThrough), | |
| _OneOption(Flags.preserveUris, ignoreOption), | |
| _OneOption('--force-strip=.*', setStrip), | |
| _OneOption(Flags.disableDiagnosticColors, (_) { | |
| enableColors = false; | |
| }), | |
| _OneOption(Flags.enableDiagnosticColors, (_) { | |
| enableColors = true; | |
| }), | |
| _OneOption( | |
| '--enable[_-]checked[_-]mode|--checked', | |
| (_) => setCheckedMode(Flags.enableCheckedMode), | |
| ), | |
| _OneOption(Flags.enableAsserts, passThrough), | |
| _OneOption(Flags.enableNullAssertions, passThrough), | |
| _OneOption(Flags.nativeNullAssertions, passThrough), | |
| _OneOption(Flags.noNativeNullAssertions, passThrough), | |
| _OneOption(Flags.interopNullAssertions, passThrough), | |
| _OneOption(Flags.noInteropNullAssertions, passThrough), | |
| _OneOption(Flags.deprecatedJsInterop, passThrough), | |
| _OneOption(Flags.noDeprecatedJsInterop, passThrough), | |
| _OneOption(Flags.trustTypeAnnotations, setTrustTypeAnnotations), | |
| _OneOption(Flags.trustPrimitives, passThrough), | |
| _OneOption(Flags.trustJSInteropTypeAnnotations, ignoreOption), | |
| _OneOption(r'--help|/\?|/h', (_) => wantHelp = true), | |
| _OneOption('--packages=.+', setPackageConfig), | |
| _OneOption(Flags.noSourceMaps, passThrough), | |
| _OneOption(Option.resolutionInput, ignoreOption), | |
| _OneOption(Option.bazelPaths, setBazelPaths), | |
| _OneOption(Option.multiRoots, setMultiRoots), | |
| _OneOption(Option.multiRootScheme, setMultiRootScheme), | |
| _OneOption(Flags.resolveOnly, ignoreOption), | |
| _OneOption(Flags.disableNativeLiveTypeAnalysis, passThrough), | |
| _OneOption('--categories=.*', setCategories), | |
| _OneOption(Flags.serverMode, passThrough), | |
| _OneOption(Flags.disableInlining, passThrough), | |
| _OneOption(Flags.disableProgramSplit, passThrough), | |
| _OneOption(Flags.stopAfterProgramSplit, passThrough), | |
| _OneOption(Flags.disableTypeInference, passThrough), | |
| _OneOption(Flags.useTrivialAbstractValueDomain, passThrough), | |
| _OneOption(Flags.disableRtiOptimization, passThrough), | |
| _OneOption(Flags.terse, passThrough), | |
| _OneOption('--deferred-map=.+', passThrough), | |
| _OneOption( | |
| '${Flags.deferredLoadIdMapUri}=.+', | |
| setDataUri(Flags.deferredLoadIdMapUri), | |
| ), | |
| _OneOption('${Flags.writeProgramSplit}=.+', passThrough), | |
| _OneOption('${Flags.readProgramSplit}=.+', passThrough), | |
| _OneOption('${Flags.dumpInfo}|${Flags.dumpInfo}=.+', setDumpInfo), | |
| _OneOption( | |
| '${Flags.dumpInfoDataUri}=.+', | |
| setDataUri(Flags.dumpInfoDataUri), | |
| ), | |
| _OneOption('--disallow-unsafe-eval', ignoreOption), | |
| _OneOption(Option.showPackageWarnings, passThrough), | |
| _OneOption(Option.enableLanguageExperiments, passThrough), | |
| _OneOption('--enable-experimental-mirrors', ignoreOption), | |
| _OneOption(Flags.enableAssertMessage, passThrough), | |
| _OneOption('--strong', ignoreOption), | |
| _OneOption(Flags.previewDart2, ignoreOption), | |
| _OneOption(Flags.omitImplicitChecks, passThrough), | |
| _OneOption(Flags.omitAsCasts, passThrough), | |
| _OneOption(Flags.laxRuntimeTypeToString, passThrough), | |
| _OneOption(Flags.enableProtoShaking, passThrough), | |
| _OneOption(Flags.enableProtoMixinShaking, passThrough), | |
| _OneOption(Flags.benchmarkingProduction, passThrough), | |
| _OneOption(Flags.benchmarkingExperiment, passThrough), | |
| _OneOption(Flags.soundNullSafety, passThrough), | |
| _OneOption(Flags.dumpUnusedLibraries, passThrough), | |
| _OneOption(Flags.writeRecordedUses, passThrough), | |
| // TODO(floitsch): remove conditional directives flag. | |
| // We don't provide the info-message yet, since we haven't publicly | |
| // launched the feature yet. | |
| _OneOption(Flags.conditionalDirectives, ignoreOption), | |
| _OneOption('--enable-async', ignoreOption), | |
| _OneOption('--enable-null-aware-operators', ignoreOption), | |
| _OneOption('--enable-enum', ignoreOption), | |
| _OneOption(Flags.allowNativeExtensions, setAllowNativeExtensions), | |
| _OneOption(Flags.generateCodeWithCompileTimeErrors, ignoreOption), | |
| _OneOption(Flags.useMultiSourceInfo, passThrough), | |
| _OneOption(Flags.useNewSourceInfo, passThrough), | |
| _OneOption(Flags.useSimpleLoadIds, passThrough), | |
| _OneOption(Flags.testMode, passThrough), | |
| _OneOption('${Flags.dumpSsa}=.+', passThrough), | |
| _OneOption('${Flags.cfeInvocationModes}=.+', passThrough), | |
| _OneOption('${Flags.invoker}=.+', setInvoker), | |
| _OneOption('${Flags.verbosity}=.+', passThrough), | |
| _OneOption(Flags.disableDiagnosticByteCache, passThrough), | |
| _OneOption(Flags.enableDeferredLoadingEventLog, passThrough), | |
| _OneOption(Flags.omitMemorySummary, passThrough), | |
| // Experimental features. | |
| // We don't provide documentation for these yet. | |
| // TODO(29574): provide documentation when this feature is supported. | |
| // TODO(29574): provide a warning/hint/error, when profile-based data is | |
| // used without `--fast-startup`. | |
| _OneOption(Flags.experimentalTrackAllocations, passThrough), | |
| _OneOption(Flags.experimentLocalNames, ignoreOption), | |
| _OneOption(Flags.experimentStartupFunctions, passThrough), | |
| _OneOption(Flags.experimentToBoolean, passThrough), | |
| _OneOption(Flags.experimentUnreachableMethodsThrow, passThrough), | |
| _OneOption(Flags.experimentCallInstrumentation, passThrough), | |
| _OneOption('${Flags.mergeFragmentsThreshold}=.+', passThrough), | |
| // Wire up feature flags. | |
| _OneOption(Flags.canary, passThrough), | |
| _OneOption(Flags.noShipping, passThrough), | |
| // Shipped features. | |
| for (var feature in features.shipped) | |
| _OneOption('--${feature.flag}', passThrough), | |
| for (var feature in features.shipped) | |
| _OneOption('--no-${feature.flag}', passThrough), | |
| // Shipping features. | |
| for (var feature in features.shipping) | |
| _OneOption('--${feature.flag}', passThrough), | |
| for (var feature in features.shipping) | |
| _OneOption('--no-${feature.flag}', passThrough), | |
| // Canary features. | |
| for (var feature in features.canary) | |
| _OneOption('--${feature.flag}', passThrough), | |
| for (var feature in features.canary) | |
| _OneOption('--no-${feature.flag}', passThrough), | |
| // The following three options must come last. | |
| _ManyOptions('-D.+=.*|--define=.+=.*|--define', addInEnvironment), | |
| _OneOption('-.*', (String argument) { | |
| _helpAndFail("Unknown option '$argument'."); | |
| }), | |
| _OneOption('.*', (String argument) { | |
| arguments.add(fe.nativeToUriPath(argument)); | |
| }), | |
| ]; | |
| parseCommandLine(handlers, argv); | |
| final diagnostic = diagnosticHandler = FormattingDiagnosticHandler(); | |
| if (verbose != null) { | |
| diagnostic.verbose = verbose!; | |
| } | |
| if (throwOnError != null) { | |
| diagnostic.throwOnError = throwOnError!; | |
| } | |
| if (throwOnErrorCount != null) { | |
| diagnostic.throwOnErrorCount = throwOnErrorCount!; | |
| } | |
| if (showWarnings != null) { | |
| diagnostic.showWarnings = showWarnings!; | |
| } | |
| if (showHints != null) { | |
| diagnostic.showHints = showHints!; | |
| } | |
| if (enableColors != null) { | |
| diagnostic.enableColors = enableColors!; | |
| } | |
| if (checkedMode && strongMode) { | |
| checkedMode = false; | |
| hints.add( | |
| "Option '${Flags.enableCheckedMode}' is not needed in Dart 2.0. " | |
| "To enable assertions use '${Flags.enableAsserts}' instead.", | |
| ); | |
| } | |
| if (trustTypeAnnotations && strongMode) { | |
| hints.add( | |
| "Option '${Flags.trustTypeAnnotations}' is not available " | |
| "in Dart 2.0. Try using '${Flags.omitImplicitChecks}' instead.", | |
| ); | |
| } | |
| if (options.contains(Flags.soundNullSafety)) { | |
| warning( | |
| "Option '${Flags.soundNullSafety}' is deprecated. As of Dart 3, Dart " | |
| "only supports sound null safety. This flag will be removed in a future " | |
| "version of Dart.", | |
| ); | |
| } | |
| for (String hint in hints) { | |
| diagnostic.info(hint, api.Diagnostic.hint); | |
| } | |
| if (wantHelp || wantVersion) { | |
| helpAndExit(wantHelp, wantVersion, diagnostic.verbose); | |
| } | |
| if (invoker == null) { | |
| final message = | |
| "The 'dart2js' entrypoint script is deprecated, " | |
| "please use 'dart compile js' instead."; | |
| // Aside from asking for `-h`, dart2js fails when it is invoked from its | |
| // snapshot directly and not using the supported workflows. However, we | |
| // allow invoking dart2js from Dart sources to support the dart2js team | |
| // local workflows and testing. | |
| if (!Platform.script.path.endsWith(".dart")) { | |
| _fail(message); | |
| } else { | |
| warning(message); | |
| } | |
| } else if (verbose != null) { | |
| print("Compiler invoked from: '$invoker'"); | |
| } | |
| if (arguments.isEmpty && entryUri == null && inputDillUri == null) { | |
| _helpAndFail('No Dart file specified.'); | |
| } | |
| if (arguments.length > 1) { | |
| var extra = arguments.sublist(1); | |
| _helpAndFail('Extra arguments: ${extra.join(" ")}'); | |
| } | |
| if (trustTypeAnnotations && checkedMode) { | |
| _helpAndFail( | |
| "Option '${Flags.trustTypeAnnotations}' may not be used in " | |
| "checked mode.", | |
| ); | |
| } | |
| if (arguments.isNotEmpty) { | |
| String sourceOrDill = arguments[0]; | |
| Uri file = Uri.base.resolve(fe.nativeToUriPath(sourceOrDill)); | |
| if (sourceOrDill.endsWith('.dart')) { | |
| options.add('${Flags.entryUri}=$file'); | |
| entryUri = file; | |
| } else if (sourceOrDill.endsWith('.dill')) { | |
| options.add('${Flags.inputDill}=$file'); | |
| inputDillUri = file; | |
| } else { | |
| _fail( | |
| "Unexpected file '$sourceOrDill'.\n" | |
| "Only .dart and .dill extensions are supported.", | |
| ); | |
| } | |
| } | |
| // Make [scriptName] a relative path. | |
| String scriptName = fe.relativizeUri( | |
| Uri.base, | |
| inputDillUri ?? entryUri!, | |
| Platform.isWindows, | |
| ); | |
| CompilerOptions compilerOptions = | |
| CompilerOptions.parse( | |
| options, | |
| featureOptions: features, | |
| librariesSpecificationUri: librariesSpecificationUri, | |
| platformBinaries: platformBinaries, | |
| useDefaultOutputUri: true, | |
| onError: (String message) => _fail(message), | |
| onWarning: (String message) => print(message), | |
| ) | |
| ..packageConfig = packageConfig | |
| ..environment = environment | |
| ..kernelInitializedCompilerState = kernelInitializedCompilerState | |
| ..optimizationLevel = optimizationLevel; | |
| final errorMessage = compilerOptions.validateStage(); | |
| if (errorMessage != null) { | |
| _fail(errorMessage); | |
| } | |
| out = compilerOptions.setResolvedOutputUri(); | |
| if (compilerOptions.stage.emitsJs) { | |
| sourceMapOut = Uri.parse('$out.map'); | |
| compilerOptions.sourceMapUri ??= sourceMapOut; | |
| } | |
| // TODO(johnniwinther): Measure time for reading files. | |
| SourceFileByteReader byteReader = compilerOptions.memoryMappedFiles | |
| ? const MemoryMapSourceFileByteReader() | |
| : const MemoryCopySourceFileByteReader(); | |
| SourceFileProvider inputProvider; | |
| if (bazelPaths != null) { | |
| if (multiRoots != null) { | |
| _helpAndFail( | |
| 'The options --bazel-root and --multi-root cannot be supplied ' | |
| 'together, please choose one or the other.', | |
| ); | |
| } | |
| inputProvider = BazelInputProvider( | |
| bazelPaths!, | |
| byteReader, | |
| disableByteCache: compilerOptions.disableDiagnosticByteCache, | |
| ); | |
| } else if (multiRoots != null) { | |
| inputProvider = MultiRootInputProvider( | |
| multiRootScheme!, | |
| multiRoots!, | |
| byteReader, | |
| disableByteCache: compilerOptions.disableDiagnosticByteCache, | |
| ); | |
| } else { | |
| inputProvider = CompilerSourceFileProvider( | |
| byteReader: byteReader, | |
| disableByteCache: compilerOptions.disableDiagnosticByteCache, | |
| ); | |
| } | |
| diagnostic.registerFileProvider(inputProvider); | |
| RandomAccessFileOutputProvider outputProvider = | |
| RandomAccessFileOutputProvider( | |
| out, | |
| sourceMapOut, | |
| onInfo: diagnostic.info, | |
| onFailure: _fail, | |
| ); | |
| Future<api.CompilationResult> compilationDone( | |
| api.CompilationResult result, | |
| ) async { | |
| if (!result.isSuccess) { | |
| _fail('Compilation failed.'); | |
| } | |
| if (out != null) { | |
| writeString( | |
| Uri.parse('$out.deps'), | |
| getDepsOutput(inputProvider.getSourceUris()), | |
| ); | |
| } | |
| String input = scriptName; | |
| int inputSize; | |
| String processName; | |
| String inputName; | |
| int outputSize; | |
| int? primaryOutputSize; | |
| String outputName; | |
| String? summary; | |
| switch (compilerOptions.stage) { | |
| case CompilerStage.all: | |
| case CompilerStage.cfe: | |
| case CompilerStage.dumpInfoAll: | |
| final sourceCharCount = _formatCharacterCount( | |
| inputProvider.sourceBytesFromDill, | |
| ); | |
| inputName = 'input bytes ($sourceCharCount characters source)'; | |
| inputSize = inputProvider.bytesRead; | |
| summary = 'Dart file $input '; | |
| break; | |
| case CompilerStage.closedWorld: | |
| case CompilerStage.deferredLoadIds: | |
| inputName = 'input bytes'; | |
| inputSize = inputProvider.bytesRead; | |
| summary = 'Dart file $input '; | |
| break; | |
| case CompilerStage.globalInference: | |
| inputName = 'bytes data'; | |
| inputSize = inputProvider.bytesRead; | |
| String dataInput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(CompilerStage.closedWorld), | |
| Platform.isWindows, | |
| ); | |
| summary = 'Data files $input and $dataInput '; | |
| break; | |
| case CompilerStage.codegenSharded: | |
| case CompilerStage.codegenAndJsEmitter: | |
| case CompilerStage.dumpInfo: | |
| inputName = 'bytes data'; | |
| inputSize = inputProvider.bytesRead; | |
| String worldInput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(CompilerStage.closedWorld), | |
| Platform.isWindows, | |
| ); | |
| String dataInput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(CompilerStage.globalInference), | |
| Platform.isWindows, | |
| ); | |
| summary = 'Data files $input, $worldInput, and $dataInput '; | |
| break; | |
| case CompilerStage.jsEmitter: | |
| inputName = 'bytes data'; | |
| inputSize = inputProvider.bytesRead; | |
| String worldInput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(CompilerStage.closedWorld), | |
| Platform.isWindows, | |
| ); | |
| String dataInput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(CompilerStage.globalInference), | |
| Platform.isWindows, | |
| ); | |
| String codeInput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(CompilerStage.codegenSharded), | |
| Platform.isWindows, | |
| ); | |
| summary = | |
| 'Data files $input, $worldInput, $dataInput and ' | |
| '$codeInput[0-${compilerOptions.codegenShards! - 1}] '; | |
| break; | |
| } | |
| switch (compilerOptions.stage) { | |
| case CompilerStage.all: | |
| case CompilerStage.dumpInfoAll: | |
| case CompilerStage.jsEmitter: | |
| case CompilerStage.codegenAndJsEmitter: | |
| case CompilerStage.dumpInfo: | |
| processName = 'Compiled'; | |
| outputName = 'characters JavaScript'; | |
| outputSize = outputProvider.totalCharactersWrittenJavaScript; | |
| primaryOutputSize = outputProvider.totalCharactersWrittenPrimary; | |
| String output = fe.relativizeUri( | |
| Uri.base, | |
| out ?? Uri.parse('out.js'), | |
| Platform.isWindows, | |
| ); | |
| summary += 'compiled to JavaScript: $output'; | |
| break; | |
| case CompilerStage.cfe: | |
| processName = 'Compiled'; | |
| outputName = 'kernel bytes'; | |
| outputSize = outputProvider.totalDataWritten; | |
| String output = fe.relativizeUri(Uri.base, out!, Platform.isWindows); | |
| summary += 'compiled to dill: $output.'; | |
| break; | |
| case CompilerStage.closedWorld: | |
| processName = 'Serialized'; | |
| outputName = 'bytes data'; | |
| outputSize = outputProvider.totalDataWritten; | |
| final producesDill = compilerOptions.producesModifiedDill; | |
| String dataOutput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(compilerOptions.stage), | |
| Platform.isWindows, | |
| ); | |
| String summaryLine = dataOutput; | |
| if (producesDill) { | |
| summaryLine += ' and '; | |
| summaryLine += fe.relativizeUri(Uri.base, out!, Platform.isWindows); | |
| } | |
| summary += 'serialized to data: $summaryLine.'; | |
| break; | |
| case CompilerStage.deferredLoadIds: | |
| processName = 'Serialized'; | |
| outputName = 'character map'; | |
| outputSize = outputProvider.totalCharactersWritten; | |
| String dataOutput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(compilerOptions.stage), | |
| Platform.isWindows, | |
| ); | |
| summary += 'mapped to: $dataOutput.'; | |
| break; | |
| case CompilerStage.globalInference: | |
| processName = 'Serialized'; | |
| outputName = 'bytes data'; | |
| outputSize = outputProvider.totalDataWritten; | |
| String dataOutput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(compilerOptions.stage), | |
| Platform.isWindows, | |
| ); | |
| summary += 'serialized to data: $dataOutput.'; | |
| break; | |
| case CompilerStage.codegenSharded: | |
| processName = 'Serialized'; | |
| outputName = 'bytes data'; | |
| outputSize = outputProvider.totalDataWritten; | |
| String codeOutput = fe.relativizeUri( | |
| Uri.base, | |
| compilerOptions.dataUriForStage(compilerOptions.stage), | |
| Platform.isWindows, | |
| ); | |
| summary += | |
| 'serialized to codegen data: ' | |
| '$codeOutput${compilerOptions.codegenShard}.'; | |
| break; | |
| } | |
| final memoryUsed = compilerOptions.omitMemorySummary | |
| ? null | |
| : await currentHeapCapacityInMb(); | |
| final memoryUsedString = memoryUsed != null | |
| ? ' using $memoryUsed of memory' | |
| : ''; | |
| print( | |
| '$processName ' | |
| '${_formatCharacterCount(inputSize)} $inputName to ' | |
| '${_formatCharacterCount(outputSize)} $outputName in ' | |
| '${_formatDurationAsSeconds(wallclock.elapsed)} seconds$memoryUsedString', | |
| ); | |
| if (primaryOutputSize != null && out != null) { | |
| diagnostic.info( | |
| '${_formatCharacterCount(primaryOutputSize)} $outputName ' | |
| 'in ${fe.relativizeUri(Uri.base, out!, Platform.isWindows)}', | |
| ); | |
| } | |
| if (compilerOptions.stage.emitsJs) { | |
| if (diagnostic.verbose) { | |
| print(summary); | |
| if (diagnostic.verbose) { | |
| var files = outputProvider.allOutputFiles; | |
| int jsCount = files.where((f) => f.endsWith('.js')).length; | |
| print('Emitted file $jsCount JavaScript files.'); | |
| } | |
| } | |
| } else { | |
| print(summary); | |
| } | |
| return result; | |
| } | |
| return compileFunc( | |
| compilerOptions, | |
| inputProvider, | |
| diagnostic, | |
| outputProvider, | |
| ).then(compilationDone); | |
| } | |
| /// Returns the non-negative integer formatted with a thousands separator. | |
| String _formatCharacterCount(int value, [String separator = ',']) { | |
| String text = '$value'; | |
| // 'Insert' separators right-to-left. Inefficient, but used just a few times. | |
| for (int position = text.length - 3; position > 0; position -= 3) { | |
| text = text.substring(0, position) + separator + text.substring(position); | |
| } | |
| return text; | |
| } | |
| /// Formats [duration] in seconds in fixed-point format, preferring to keep the | |
| /// result at to below [width] characters. | |
| String _formatDurationAsSeconds(Duration duration, [int width = 4]) { | |
| num seconds = duration.inMilliseconds / 1000.0; | |
| late String text; | |
| for (int digits = 3; digits >= 0; digits--) { | |
| text = seconds.toStringAsFixed(digits); | |
| if (text.length <= width) return text; | |
| } | |
| return text; | |
| } | |
| void writeString(Uri uri, String text) { | |
| if (!enableWriteString) return; | |
| if (!uri.isScheme('file')) { | |
| _fail('Unhandled scheme ${uri.scheme}.'); | |
| } | |
| var file = (File( | |
| uri.toFilePath(), | |
| )..createSync(recursive: true)).openSync(mode: FileMode.write); | |
| file.writeStringSync(text); | |
| file.closeSync(); | |
| } | |
| Never _fail(String message) { | |
| if (diagnosticHandler != null) { | |
| diagnosticHandler!.report( | |
| null, | |
| null, | |
| -1, | |
| -1, | |
| message, | |
| api.Diagnostic.error, | |
| ); | |
| } else { | |
| print('Error: $message'); | |
| } | |
| exitFunc(1); | |
| } | |
| Future<api.CompilationResult> compilerMain( | |
| List<String> arguments, { | |
| fe.InitializedCompilerState? kernelInitializedCompilerState, | |
| }) async { | |
| if (!arguments.any((a) => a.startsWith('--libraries-spec='))) { | |
| Uri script = Platform.script; | |
| if (script.isScheme("package")) { | |
| script = (await Isolate.resolvePackageUri(script))!; | |
| } | |
| Uri librariesJson = script.resolve(_defaultSpecificationUri); | |
| arguments = <String>[ | |
| '--libraries-spec=${librariesJson.toFilePath()}', | |
| ...arguments, | |
| ]; | |
| } | |
| return compile( | |
| arguments, | |
| kernelInitializedCompilerState: kernelInitializedCompilerState, | |
| ); | |
| } | |
| void help() { | |
| // This message should be no longer than 20 lines. The default | |
| // terminal size normally 80x24. Two lines are used for the prompts | |
| // before and after running the compiler. Another two lines may be | |
| // used to print an error message. | |
| print( | |
| ''' | |
| Compile Dart to JavaScript. | |
| Usage: dart compile js [arguments] <dart entry point> | |
| -h, --help Print this usage information. | |
| -h -v Show detailed information about all options. | |
| -o, --output Write the output to <file name>. | |
| -O<0,1,2,3,4> Set the compiler optimization level (defaults to -O1). | |
| -O0 No optimizations (only meant for debugging the compiler). | |
| -O1 Default (includes whole program analyses and inlining). | |
| -O2 Safe production-oriented optimizations (like minification). | |
| -O3 Potentially unsafe optimizations (see -h -v for details). | |
| -O4 More aggressive unsafe optimizations (see -h -v for details). | |
| ''' | |
| .trim(), | |
| ); | |
| } | |
| void verboseHelp() { | |
| print( | |
| r''' | |
| Compile Dart to JavaScript. | |
| Usage: dart compile js [arguments] <dart entry point> | |
| -h, /h, /?, --help | |
| Print this usage information (add -v for information about all options). | |
| -o <file name>, --output=<file name> | |
| Write the output to <file name>. | |
| -m, --minify | |
| Generate minified output. | |
| --enable-asserts | |
| Enable assertions. | |
| -v, --verbose | |
| Display verbose information. | |
| -D<name>=<value>, --define=<name>=<value> | |
| Define an environment declaration. | |
| --version | |
| Display version information. | |
| --packages=<path> | |
| Path to the package resolution configuration file, which supplies a mapping | |
| of package names to paths. | |
| --suppress-warnings | |
| Do not display any warnings. | |
| --fatal-warnings | |
| Treat warnings as compilation errors. | |
| --suppress-hints | |
| Do not display any hints. | |
| --enable-diagnostic-colors | |
| Add colors to diagnostic messages. | |
| --terse | |
| Emit diagnostics without suggestions for how to get rid of the diagnosed | |
| problems. | |
| --show-package-warnings | |
| Show warnings and hints generated from packages. | |
| --csp | |
| Disable dynamic generation of code in the generated output. This is | |
| necessary to satisfy CSP restrictions (see https://www.w3.org/TR/CSP/). | |
| --no-source-maps | |
| Do not generate a source map file. | |
| --omit-late-names | |
| Do not include names of late variables in error messages. This allows | |
| the compiler to generate smaller code by removing late variable names from | |
| the generated JavaScript. | |
| --native-null-assertions | |
| Add assertions to web library APIs to ensure that non-nullable APIs do not | |
| return null. This is set to true by default unless -O3 or higher is passed. | |
| --no-deprecated-js-interop | |
| Disallow the deprecated JS interop libraries (dart:html, dart:html_common, | |
| dart:indexed_db, dart:js, dart:js_util, dart:svg, dart:web_audio and | |
| dart:web_gl). Conditional imports on these libraries resolve to false, and | |
| importing any of them is an error. The import paths through which they are | |
| reached are reported as a tree. | |
| -O<0,1,2,3,4> | |
| Controls optimizations that can help reduce code-size and improve | |
| performance of the generated code for deployment. | |
| -O0 | |
| Disables all optimizations. Equivalent to calling the compiler with these | |
| extra flags: | |
| --disable-inlining | |
| --disable-type-inference | |
| --disable-rti-optimizations | |
| Some optimizations cannot be disabled at this time, as we add the option | |
| to disable them, they will be added here as well. | |
| -O1 | |
| Enables default optimizations. Equivalent to calling the compiler with no | |
| extra flags. | |
| -O2 | |
| Enables optimizations that respect the language semantics and are safe | |
| for all programs. It however changes the string representation of types, | |
| which will no longer be consistent with the Dart VM or DDC. | |
| Equivalent to calling the compiler with these extra flags: | |
| --minify | |
| --lax-runtime-type-to-string | |
| --omit-late-names | |
| -O3 | |
| Enables optimizations that respect the language semantics only on | |
| programs that do not ever throw any subtype of `Error`. These | |
| optimizations improve the generated code, but they may cause programs to | |
| behave unexpectedly if this assumption is not met. To use this | |
| option, we recommend that you properly test your application first | |
| without it, and ensure that no subtype of `Error` (such as `TypeError`) | |
| is ever thrown. | |
| Equivalent to calling the compiler with these extra flags: | |
| -O2 | |
| --omit-implicit-checks | |
| -O4 | |
| Enables more aggressive optimizations than -O3, but with the same | |
| assumptions. These optimizations are on a separate group because they | |
| are more susceptible to variations in input data. To use this option we | |
| recommend to pay special attention to test edge cases in user input. | |
| Equivalent to calling the compiler with these extra flags: | |
| -O3 | |
| --trust-primitives | |
| While some of the individual optimizations and flags may change with time, | |
| we intend to keep the -O* flags stable. New safe optimizations may be added | |
| on any level, and optimizations that only work on some programs may move up | |
| from one level to the next (for instance, once alternative safe | |
| optimizations are implemented, `omit-implicit-checks` may be removed or may | |
| move to the O4 level). | |
| The following individual options are included in some of the -O optimization | |
| levels above. They help reduce the size of the generated code, but they may | |
| cause programs to behave unexpectedly if assumptions are not met. Only turn on | |
| these flags if you have enough test coverage to ensure they are safe to use: | |
| --omit-implicit-checks | |
| Omit implicit runtime checks, such as parameter checks and implicit | |
| downcasts. These checks are included by default in Dart 2.0. By | |
| using this flag the checks are removed, however the compiler will assume | |
| that all such checks were valid and may use this information for | |
| optimizations. Use this option only if you have enough testing to ensure | |
| that your program works with the checks. | |
| --trust-primitives | |
| Assume that operations on numbers, strings, and lists have valid inputs. | |
| This option allows the compiler to drop runtime checks for those operations. | |
| Note: a well-typed program is not guaranteed to have valid inputs. For | |
| example, an int index argument may be null or out of range. | |
| --lax-runtime-type-to-string | |
| Omits reified class type arguments when these are only needed for `toString` | |
| on `runtimeType`. This is useful if `runtimeType.toString()` is only used | |
| for debugging. Note that semantics of other uses of `.runtimeType`, for | |
| instance `a.runtimeType == b.runtimeType`, is not affected by this flag. | |
| The following options are only used for compiler development and may | |
| be removed in a future version: | |
| --throw-on-error | |
| Throw an exception if a compile-time error is detected. | |
| --libraries-spec=<file> | |
| A .json file containing the SDK libraries specification. | |
| --disable-native-live-type-analysis | |
| Disable the optimization that removes unused native types from dart:html | |
| and related libraries. | |
| --server-mode | |
| Compile with server support. The compiler will use a library specification | |
| that disables dart:html but supports dart:js in conditional imports. | |
| --categories=<categories> | |
| (deprecated) | |
| Use '--server-mode' instead of '--categories=Server'. All other category | |
| values have no effect on the compiler behavior. | |
| --deferred-map=<file> | |
| Generates a json file with a mapping from each deferred import to a list of | |
| the part.js files that will be loaded. | |
| --dump-info[=<format>] | |
| Generates information about the generated code. 'format' can be either | |
| 'json' or 'binary'. | |
| You can inspect the generated data using tools from 'package:dart2js_info'. | |
| --generate-code-with-compile-time-errors | |
| Generates output even if the program contains compile-time errors. Use the | |
| exit code to determine if compilation failed. | |
| --no-frequency-based-minification | |
| Experimental. Disable the new frequency based minifying namer and use the | |
| old namer instead. | |
| ''' | |
| .trim(), | |
| ); | |
| } | |
| void helpAndExit(bool wantHelp, bool wantVersion, bool verbose) { | |
| if (wantVersion) { | |
| var version = (buildID == null) ? '<non-SDK build>' : buildID; | |
| print('Dart-to-JavaScript compiler (dart2js) version: $version'); | |
| } | |
| if (wantHelp) { | |
| if (verbose) { | |
| verboseHelp(); | |
| } else { | |
| help(); | |
| } | |
| } | |
| exitFunc(0); | |
| } | |
| Never _helpAndFail(String message) { | |
| help(); | |
| print(''); | |
| _fail(message); | |
| } | |
| void warning(String message) { | |
| if (diagnosticHandler != null) { | |
| diagnosticHandler!.report( | |
| null, | |
| null, | |
| -1, | |
| -1, | |
| message, | |
| api.Diagnostic.warning, | |
| ); | |
| } else { | |
| print('Warning: $message'); | |
| } | |
| } | |
| Future<void> main(List<String> arguments) async { | |
| // Expand `@path/to/file` | |
| // When running from bazel, argument of the form `@path/to/file` might be | |
| // provided. It needs to be replaced by reading all the contents of the | |
| // file and expanding them into the resulting argument list. | |
| // | |
| // TODO: Remove when internal tooling targets bazelMain instead of this. | |
| if (arguments.isNotEmpty && arguments.last.startsWith('@')) { | |
| var extra = _readLines(arguments.last.substring(1)); | |
| arguments = arguments.take(arguments.length - 1).followedBy(extra).toList(); | |
| } | |
| // Since the sdk/bin/dart2js script adds its own arguments in front of | |
| // user-supplied arguments we search for '--batch' at the end of the list. | |
| if (arguments.isNotEmpty && arguments.last == "--batch") { | |
| batchMain(arguments.sublist(0, arguments.length - 1)); | |
| return; | |
| } | |
| await internalMain(arguments); | |
| } | |
| Future<String?> bazelMain(List<String> arguments) async { | |
| if (arguments.isNotEmpty && arguments.last.startsWith('@')) { | |
| var extra = _readLines(arguments.last.substring(1)); | |
| arguments = arguments.take(arguments.length - 1).followedBy(extra).toList(); | |
| } | |
| final compiler = (await internalMain(arguments)).compiler; | |
| if (compiler is default_compiler.Compiler) { | |
| final buffer = StringBuffer(); | |
| compiler.collectMetrics(buffer); | |
| return buffer.toString(); | |
| } | |
| return null; | |
| } | |
| /// Return all non-empty lines in a file found at [path]. | |
| Iterable<String> _readLines(String path) { | |
| return File(path).readAsLinesSync().where((line) => line.isNotEmpty); | |
| } | |
| typedef ExitFunc = Never Function(int exitCode); | |
| typedef CompileFunc = Future<api.CompilationResult> Function( | |
| CompilerOptions compilerOptions, | |
| api.CompilerInput compilerInput, | |
| api.CompilerDiagnostics compilerDiagnostics, | |
| api.CompilerOutput compilerOutput, | |
| ); | |
| ExitFunc exitFunc = exit; | |
| CompileFunc compileFunc = api.compile; | |
| /// If `true` a '.deps' file will be generated after compilation. | |
| /// | |
| /// Set this to `false` in end-to-end tests to avoid generating '.deps' files. | |
| bool enableWriteString = true; | |
| Future<api.CompilationResult> internalMain( | |
| List<String> arguments, { | |
| fe.InitializedCompilerState? kernelInitializedCompilerState, | |
| }) { | |
| Future<api.CompilationResult> onError(Object exception, StackTrace? trace) { | |
| // If we are already trying to exit, just continue exiting. | |
| if (exception == _exitSignal) throw exception; | |
| try { | |
| print('The compiler crashed: $exception'); | |
| } catch (ignored) { | |
| print('The compiler crashed: error while printing exception'); | |
| } | |
| try { | |
| if (trace != null) { | |
| print(trace); | |
| } | |
| } finally { | |
| exitFunc(253); // 253 is recognized as a crash by our test scripts. | |
| } | |
| } | |
| try { | |
| return compilerMain( | |
| arguments, | |
| kernelInitializedCompilerState: kernelInitializedCompilerState, | |
| ).catchError(onError); | |
| } catch (exception, trace) { | |
| return onError(exception, trace); | |
| } | |
| } | |
| class _ExitSignal { | |
| const _ExitSignal(); | |
| } | |
| const _exitSignal = _ExitSignal(); | |
| void batchMain(List<String> batchArguments) { | |
| int? exitCode; | |
| exitFunc = (errorCode) { | |
| // Since we only throw another part of the compiler might intercept our | |
| // exception and try to exit with a different code. | |
| if (exitCode == 0) { | |
| exitCode = errorCode; | |
| } | |
| throw _exitSignal; | |
| }; | |
| var stream = stdin.transform(utf8.decoder).transform(LineSplitter()); | |
| late StreamSubscription<String> subscription; | |
| fe.InitializedCompilerState? kernelInitializedCompilerState; | |
| subscription = stream.listen((String line) { | |
| Future.sync(() { | |
| subscription.pause(); | |
| exitCode = 0; | |
| List<String> testArgs = splitLine(line, windows: Platform.isWindows); | |
| // Ignore experiment flags given to the batch runner. | |
| // | |
| // Batch arguments are provided when the batch compiler is created, and | |
| // contain flags that are generally enabled for all tests. Tests | |
| // may have more specific flags that could conflict with the batch flags. | |
| // For example, the batch runner might be setup to run the non-nullable | |
| // experiment, but the test may enable more experiments. | |
| // | |
| // At this time we are only aware of these kind of conflicts with | |
| // experiment flags, so we handle those directly. Currently the test | |
| // runner passes experiment flags on both the batch runner and the test | |
| // itself, so it is safe to ignore the flag that was given to the batch | |
| // runner. | |
| List<String> args = [ | |
| for (var arg in batchArguments) | |
| if (!arg.startsWith('--enable-experiment')) arg, | |
| ...testArgs, | |
| ]; | |
| return internalMain( | |
| args, | |
| kernelInitializedCompilerState: kernelInitializedCompilerState, | |
| ); | |
| }) | |
| .then((api.CompilationResult? result) { | |
| if (result != null) { | |
| kernelInitializedCompilerState = | |
| result.kernelInitializedCompilerState; | |
| } | |
| }) | |
| .catchError((Object exception, StackTrace trace) { | |
| if (!identical(exception, _exitSignal)) { | |
| exitCode = 253; | |
| } | |
| }) | |
| .whenComplete(() { | |
| // The testing framework waits for a status line on stdout and | |
| // stderr before moving to the next test. | |
| if (exitCode == 0) { | |
| print(">>> TEST OK"); | |
| } else if (exitCode == 253) { | |
| print(">>> TEST CRASH"); | |
| } else { | |
| print(">>> TEST FAIL"); | |
| } | |
| stderr.writeln(">>> EOF STDERR"); | |
| subscription.resume(); | |
| }); | |
| }); | |
| } |