[dart2js] Support running modular analysis alongside building a dill.

Change-Id: If10385445c5f6267e2de20b090b37e1741b7ace7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/237925
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Joshua Litt 2022-03-30 23:59:00 +00:00 committed by Commit Bot
parent 818f886f1d
commit b1fbe0c01d
13 changed files with 301 additions and 125 deletions

View file

@ -112,6 +112,7 @@ class Flags {
static const String reportAllMetrics = '--report-all-metrics';
static const String dillDependencies = '--dill-dependencies';
static const String sources = '--sources';
static const String readData = '--read-data';
static const String writeData = '--write-data';
static const String noClosedWorldInData = '--no-closed-world-in-data';

View file

@ -133,6 +133,7 @@ Future<api.CompilationResult> compile(List<String> argv,
bool showWarnings;
bool showHints;
bool enableColors;
List<Uri> sources;
int optimizationLevel = null;
Uri platformBinaries;
Map<String, String> environment = Map<String, String>();
@ -289,11 +290,12 @@ Future<api.CompilationResult> compile(List<String> argv,
Uri.base.resolve(extractPath(argument, isDirectory: true));
}
void setUriList(String flag, String argument) {
List<Uri> setUriList(String flag, String argument) {
String list = extractParameter(argument);
String uriList = list.splitMapJoin(',',
onMatch: (_) => ',', onNonMatch: (p) => '${fe.nativeToUri(p)}');
List<Uri> uris = list.split(',').map(fe.nativeToUri).toList();
String uriList = uris.map((uri) => '$uri').join(',');
options.add('${flag}=${uriList}');
return uris;
}
void setModularAnalysisInputs(String argument) {
@ -361,6 +363,10 @@ Future<api.CompilationResult> compile(List<String> argv,
setUriList(Flags.dillDependencies, argument);
}
void setSources(String argument) {
sources = setUriList(Flags.sources, argument);
}
void setCfeOnly(String argument) {
if (writeStrategy == WriteStrategy.toModularAnalysis) {
fail("Cannot use ${Flags.cfeOnly} "
@ -547,6 +553,7 @@ Future<api.CompilationResult> compile(List<String> argv,
OptionHandler('--library-root=.+', ignoreOption),
OptionHandler('--libraries-spec=.+', setLibrarySpecificationUri),
OptionHandler('${Flags.dillDependencies}=.+', setDillDependencies),
OptionHandler('${Flags.sources}=.+', setSources),
OptionHandler('${Flags.readModularAnalysis}=.+', setModularAnalysisInputs),
OptionHandler(
'${Flags.writeModularAnalysis}|${Flags.writeModularAnalysis}=.+',
@ -761,7 +768,10 @@ Future<api.CompilationResult> compile(List<String> argv,
helpAndExit(wantHelp, wantVersion, diagnosticHandler.verbose);
}
if (arguments.isEmpty && entryUri == null && inputDillUri == null) {
if (arguments.isEmpty &&
entryUri == null &&
inputDillUri == null &&
sources == null) {
helpAndFail('No Dart file specified.');
}
@ -787,8 +797,11 @@ Future<api.CompilationResult> compile(List<String> argv,
}
// Make [scriptName] a relative path..
String scriptName =
fe.relativizeUri(Uri.base, inputDillUri ?? entryUri, Platform.isWindows);
String scriptName = sources == null
? fe.relativizeUri(Uri.base, inputDillUri ?? entryUri, Platform.isWindows)
: sources
.map((uri) => fe.relativizeUri(Uri.base, uri, Platform.isWindows))
.join(',');
switch (writeStrategy) {
case WriteStrategy.toJs:

View file

@ -171,6 +171,7 @@ class CompilerOptions implements DiagnosticOptions {
Uri? get compilationTarget => inputDillUri ?? entryUri;
bool get fromDill {
if (sources != null) return false;
var targetPath = compilationTarget!.path;
return targetPath.endsWith('.dill');
}
@ -191,6 +192,9 @@ class CompilerOptions implements DiagnosticOptions {
/// files for linking.
List<Uri>? dillDependencies;
/// A list of sources to compile, only used for modular analysis.
List<Uri>? sources;
Uri? writeModularAnalysisUri;
/// Helper to determine if compiler is being run just for modular analysis.
@ -676,6 +680,7 @@ class CompilerOptions implements DiagnosticOptions {
..showInternalProgress = _hasOption(options, Flags.progress)
..dillDependencies =
_extractUriListOption(options, '${Flags.dillDependencies}')
..sources = _extractUriListOption(options, '${Flags.sources}')
..readProgramSplit =
_extractUriOption(options, '${Flags.readProgramSplit}=')
..writeModularAnalysisUri =

View file

@ -195,8 +195,10 @@ Future<_LoadFromKernelResult> _loadFromKernel(CompilerOptions options,
class _LoadFromSourceResult {
final ir.Component component;
final fe.InitializedCompilerState initializedCompilerState;
final List<Uri> moduleLibraries;
_LoadFromSourceResult(this.component, this.initializedCompilerState);
_LoadFromSourceResult(
this.component, this.initializedCompilerState, this.moduleLibraries);
}
Future<_LoadFromSourceResult> _loadFromSource(
@ -215,21 +217,36 @@ Future<_LoadFromSourceResult> _loadFromSource(
reportFrontEndMessage(reporter, message);
}
};
fe.CompilerOptions feOptions = fe.CompilerOptions()
..target = target
..librariesSpecificationUri = options.librariesSpecificationUri
..packagesFileUri = options.packageConfig
..explicitExperimentalFlags = options.explicitExperimentalFlags
..verbose = verbose
..fileSystem = fileSystem
..onDiagnostic = onDiagnostic
..verbosity = verbosity;
Uri resolvedUri = options.compilationTarget;
bool isLegacy = await fe.uriUsesLegacyLanguageVersion(resolvedUri, feOptions);
_inferNullSafetyMode(options, !isLegacy);
// If we are passed a list of sources, then we are performing a modular
// compile. In this case, we cannot infer null safety from the source files
// and must instead rely on the options passed in on the command line.
bool isModularCompile = false;
List<Uri> sources = [];
if (options.sources != null) {
isModularCompile = true;
sources.addAll(options.sources);
} else {
fe.CompilerOptions feOptions = fe.CompilerOptions()
..target = target
..librariesSpecificationUri = options.librariesSpecificationUri
..packagesFileUri = options.packageConfig
..explicitExperimentalFlags = options.explicitExperimentalFlags
..verbose = verbose
..fileSystem = fileSystem
..onDiagnostic = onDiagnostic
..verbosity = verbosity;
Uri resolvedUri = options.compilationTarget;
bool isLegacy =
await fe.uriUsesLegacyLanguageVersion(resolvedUri, feOptions);
_inferNullSafetyMode(options, !isLegacy);
sources.add(options.compilationTarget);
}
// If we are performing a modular compile, we expect the platform binary to be
// supplied along with other dill dependencies.
List<Uri> dependencies = [];
if (options.platformBinaries != null) {
if (options.platformBinaries != null && !isModularCompile) {
dependencies.add(options.platformBinaries
.resolve(_getPlatformFilename(options, targetName)));
}
@ -248,10 +265,17 @@ Future<_LoadFromSourceResult> _loadFromSource(
options.useLegacySubtyping ? fe.NnbdMode.Weak : fe.NnbdMode.Strong,
invocationModes: options.cfeInvocationModes,
verbosity: verbosity);
ir.Component component = await fe.compile(
initializedCompilerState, verbose, fileSystem, onDiagnostic, resolvedUri);
ir.Component component = await fe.compile(initializedCompilerState, verbose,
fileSystem, onDiagnostic, sources, isModularCompile);
_validateNullSafetyMode(options);
return _LoadFromSourceResult(component, initializedCompilerState);
// We have to compute canonical names on the component here to avoid missing
// canonical names downstream.
if (isModularCompile) {
component.computeCanonicalNames();
}
return _LoadFromSourceResult(
component, initializedCompilerState, isModularCompile ? sources : []);
}
Output _createOutput(
@ -336,6 +360,7 @@ Future<Output> run(Input input) async {
reporter, input.initializedCompilerState, targetName);
component = result.component;
initializedCompilerState = result.initializedCompilerState;
moduleLibraries = result.moduleLibraries;
}
if (component == null) return null;
if (input.forceSerialization) {

View file

@ -30,7 +30,8 @@ main() {
(fe.DiagnosticMessage message) {
message.plainTextFormatted.forEach(print);
Expect.notEquals(fe.Severity.error, message.severity);
}, Uri.base.resolve('pkg/compiler/test/end_to_end/data/hello_world.dart'));
}, [Uri.base.resolve('pkg/compiler/test/end_to_end/data/hello_world.dart')],
false);
Expect.isNotNull(new ir.CoreTypes(component).futureClass);
}

View file

@ -21,7 +21,8 @@ main(List<String> args) async {
options,
IOPipeline([
OutlineDillCompilationStep(),
FullDillCompilationStep(),
FullDillCompilationStep(onlyOnSdk: true),
ModularAnalysisStep(onlyOnSdk: true),
ModularAnalysisStep(),
ConcatenateDillsStep(useModularAnalysis: true),
ComputeClosedWorldStep(useModularAnalysis: true),

View file

@ -51,10 +51,95 @@ String _packageConfigEntry(String name, Uri root,
return '{${fields.join(',')}}';
}
abstract class CFEStep implements IOModularStep {
String getRootScheme(Module module) {
// We use non file-URI schemes for representeing source locations in a
// root-agnostic way. This allows us to refer to file across modules and
// across steps without exposing the underlying temporary folders that are
// created by the framework. In build systems like bazel this is especially
// important because each step may be run on a different machine.
//
// Files in packages are defined in terms of `package:` URIs, while
// non-package URIs are defined using the `dart-dev-app` scheme.
return module.isSdk ? 'dart-dev-sdk' : 'dev-dart-app';
}
String sourceToImportUri(Module module, Uri relativeUri) {
if (module.isPackage) {
var basePath = module.packageBase.path;
var packageRelativePath = basePath == "./"
? relativeUri.path
: relativeUri.path.substring(basePath.length);
return 'package:${module.name}/$packageRelativePath';
} else {
return '${getRootScheme(module)}:/$relativeUri';
}
}
List<String> getSources(Module module) {
return module.sources.map((uri) => sourceToImportUri(module, uri)).toList();
}
void writePackageConfig(
Module module, Set<Module> transitiveDependencies, Uri root) async {
// TODO(joshualitt): Figure out a way to support package configs in
// tests/modular.
var packageConfig = await loadPackageConfigUri(packageConfigUri);
// We create both a .packages and package_config.json file which defines
// the location of this module if it is a package. The CFE requires that
// if a `package:` URI of a dependency is used in an import, then we need
// that package entry in the associated file. However, after it checks that
// the definition exists, the CFE will not actually use the resolved URI if
// a library for the import URI is already found in one of the provide
// .dill files of the dependencies. For that reason, and to ensure that
// a step only has access to the files provided in a module, we generate a
// config file with invalid folders for other packages.
// TODO(sigmund): follow up with the CFE to see if we can remove the need
// for these dummy entries..
// TODO(joshualitt): Generate just the json file.
var packagesJson = [];
var packagesContents = StringBuffer();
if (module.isPackage) {
packagesContents.write('${module.name}:${module.packageBase}\n');
packagesJson.add(_packageConfigEntry(
module.name, Uri.parse('../${module.packageBase}')));
}
int unusedNum = 0;
for (Module dependency in transitiveDependencies) {
if (dependency.isPackage) {
// rootUri should be ignored for dependent modules, so we pass in a
// bogus value.
var rootUri = Uri.parse('unused$unusedNum');
unusedNum++;
var dependentPackage = packageConfig[dependency.name];
var packageJson = dependentPackage == null
? _packageConfigEntry(dependency.name, rootUri)
: _packageConfigEntry(dependentPackage.name, rootUri,
version: dependentPackage.languageVersion);
packagesJson.add(packageJson);
packagesContents.write('${dependency.name}:$rootUri\n');
}
}
if (module.isPackage) {
await File.fromUri(root.resolve(packageConfigJsonPath))
.create(recursive: true);
await File.fromUri(root.resolve(packageConfigJsonPath)).writeAsString('{'
' "configVersion": ${packageConfig.version},'
' "packages": [ ${packagesJson.join(',')} ]'
'}');
}
await File.fromUri(root.resolve('.packages'))
.writeAsString('$packagesContents');
}
abstract class CFEStep extends IOModularStep {
final String stepName;
CFEStep(this.stepName);
CFEStep(this.stepName, this.onlyOnSdk);
@override
bool get needsSources => true;
@ -62,87 +147,18 @@ abstract class CFEStep implements IOModularStep {
@override
bool get onlyOnMain => false;
@override
final bool onlyOnSdk;
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
if (_options.verbose) print("\nstep: $stepName on $module");
// TODO(joshualitt): Figure out a way to support package configs in
// tests/modular.
var packageConfig = await loadPackageConfigUri(packageConfigUri);
// We use non file-URI schemes for representeing source locations in a
// root-agnostic way. This allows us to refer to file across modules and
// across steps without exposing the underlying temporary folders that are
// created by the framework. In build systems like bazel this is especially
// important because each step may be run on a different machine.
//
// Files in packages are defined in terms of `package:` URIs, while
// non-package URIs are defined using the `dart-dev-app` scheme.
String rootScheme = module.isSdk ? 'dart-dev-sdk' : 'dev-dart-app';
String sourceToImportUri(Uri relativeUri) {
if (module.isPackage) {
var basePath = module.packageBase.path;
var packageRelativePath = basePath == "./"
? relativeUri.path
: relativeUri.path.substring(basePath.length);
return 'package:${module.name}/$packageRelativePath';
} else {
return '$rootScheme:/$relativeUri';
}
}
// We create both a .packages and package_config.json file which defines
// the location of this module if it is a package. The CFE requires that
// if a `package:` URI of a dependency is used in an import, then we need
// that package entry in the associated file. However, after it checks that
// the definition exists, the CFE will not actually use the resolved URI if
// a library for the import URI is already found in one of the provide
// .dill files of the dependencies. For that reason, and to ensure that
// a step only has access to the files provided in a module, we generate a
// config file with invalid folders for other packages.
// TODO(sigmund): follow up with the CFE to see if we can remove the need
// for these dummy entries..
// TODO(joshualitt): Generate just the json file.
var packagesJson = [];
var packagesContents = StringBuffer();
if (module.isPackage) {
packagesContents.write('${module.name}:${module.packageBase}\n');
packagesJson.add(_packageConfigEntry(
module.name, Uri.parse('../${module.packageBase}')));
}
Set<Module> transitiveDependencies = computeTransitiveDependencies(module);
int unusedNum = 0;
for (Module dependency in transitiveDependencies) {
if (dependency.isPackage) {
// rootUri should be ignored for dependent modules, so we pass in a
// bogus value.
var rootUri = Uri.parse('unused$unusedNum');
unusedNum++;
var dependentPackage = packageConfig[dependency.name];
var packageJson = dependentPackage == null
? _packageConfigEntry(dependency.name, rootUri)
: _packageConfigEntry(dependentPackage.name, rootUri,
version: dependentPackage.languageVersion);
packagesJson.add(packageJson);
packagesContents.write('${dependency.name}:$rootUri\n');
}
}
if (module.isPackage) {
await File.fromUri(root.resolve(packageConfigJsonPath))
.create(recursive: true);
await File.fromUri(root.resolve(packageConfigJsonPath)).writeAsString('{'
' "configVersion": ${packageConfig.version},'
' "packages": [ ${packagesJson.join(',')} ]'
'}');
}
await File.fromUri(root.resolve('.packages'))
.writeAsString('$packagesContents');
writePackageConfig(module, transitiveDependencies, root);
String rootScheme = getRootScheme(module);
List<String> sources;
List<String> extraArgs = ['--packages-file', '$rootScheme:/.packages'];
if (module.isSdk) {
@ -165,7 +181,7 @@ abstract class CFEStep implements IOModularStep {
];
assert(transitiveDependencies.isEmpty);
} else {
sources = module.sources.map(sourceToImportUri).toList();
sources = getSources(module);
}
// TODO(joshualitt): Ensure the kernel worker has some way to specify
@ -228,7 +244,7 @@ class OutlineDillCompilationStep extends CFEStep {
@override
DataId get outputData => dillSummaryId;
OutlineDillCompilationStep() : super('outline-dill-compilation');
OutlineDillCompilationStep() : super('outline-dill-compilation', false);
}
// Step that compiles sources in a module to a .dill file.
@ -255,42 +271,81 @@ class FullDillCompilationStep extends CFEStep {
@override
DataId get outputData => dillId;
FullDillCompilationStep() : super('full-dill-compilation');
FullDillCompilationStep({bool onlyOnSdk = false})
: super('full-dill-compilation', onlyOnSdk);
}
class ModularAnalysisStep implements IOModularStep {
class ModularAnalysisStep extends IOModularStep {
@override
List<DataId> get resultData => const [modularDataId, modularUpdatedDillId];
List<DataId> get resultData => [modularDataId, modularUpdatedDillId];
@override
bool get needsSources => false;
bool get needsSources => !onlyOnSdk;
/// The SDK has no dependencies, and for all other modules we only need
/// summaries.
@override
List<DataId> get dependencyDataNeeded => const [dillId];
List<DataId> get dependencyDataNeeded => [dillSummaryId];
/// All non SDK modules only need sources for module data.
@override
List<DataId> get moduleDataNeeded => const [dillId];
List<DataId> get moduleDataNeeded => onlyOnSdk ? [dillId] : const [];
@override
bool get onlyOnMain => false;
@override
final bool onlyOnSdk;
@override
bool get notOnSdk => !onlyOnSdk;
// TODO(joshualitt): We currently special case the SDK both because it is not
// trivial to build it in the same fashion as other modules, and because it is
// a special case in other build environments. Eventually, we should
// standardize this a bit more and always build the SDK modularly, if we have
// to build it.
ModularAnalysisStep({this.onlyOnSdk = false});
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
if (_options.verbose) print("\nstep: modular analysis on $module");
Set<Module> transitiveDependencies = computeTransitiveDependencies(module);
Iterable<String> dillDependencies =
transitiveDependencies.map((m) => '${toUri(m, dillId)}');
List<String> dillDependencies = [];
List<String> sources = [];
List<String> extraArgs = [];
if (!module.isSdk) {
writePackageConfig(module, transitiveDependencies, root);
String rootScheme = getRootScheme(module);
sources = getSources(module);
dillDependencies = transitiveDependencies
.map((m) => '${toUri(m, dillSummaryId)}')
.toList();
extraArgs = [
'--packages=${root.resolve('.packages')}',
'--multi-root=$root',
'--multi-root-scheme=$rootScheme',
];
}
List<String> args = [
'--packages=${sdkRoot.toFilePath()}/.packages',
_dart2jsScript,
'--no-sound-null-safety',
if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
'${Flags.inputDill}=${toUri(module, dillId)}',
// If we have sources, then we aren't building the SDK, otherwise we
// assume we are building the sdk and pass in a full dill.
if (sources.isNotEmpty)
'${Flags.sources}=${sources.join(',')}'
else
'${Flags.inputDill}=${toUri(module, dillId)}',
if (dillDependencies.isNotEmpty)
'--dill-dependencies=${dillDependencies.join(',')}',
'--out=${toUri(module, modularUpdatedDillId)}',
'${Flags.writeModularAnalysis}=${toUri(module, modularDataId)}',
for (String flag in flags) '--enable-experiment=$flag',
...extraArgs
];
var result =
await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
@ -306,7 +361,7 @@ class ModularAnalysisStep implements IOModularStep {
}
}
class ConcatenateDillsStep implements IOModularStep {
class ConcatenateDillsStep extends IOModularStep {
final bool useModularAnalysis;
DataId get idForDill => useModularAnalysis ? modularUpdatedDillId : dillId;
@ -368,7 +423,7 @@ class ConcatenateDillsStep implements IOModularStep {
}
// Step that invokes the dart2js closed world computation.
class ComputeClosedWorldStep implements IOModularStep {
class ComputeClosedWorldStep extends IOModularStep {
final bool useModularAnalysis;
ComputeClosedWorldStep({this.useModularAnalysis});
@ -429,7 +484,7 @@ class ComputeClosedWorldStep implements IOModularStep {
}
// Step that runs the dart2js modular analysis.
class GlobalAnalysisStep implements IOModularStep {
class GlobalAnalysisStep extends IOModularStep {
@override
List<DataId> get resultData => const [globalDataId];
@ -479,7 +534,7 @@ class GlobalAnalysisStep implements IOModularStep {
// Step that invokes the dart2js code generation on the main module given the
// results of the global analysis step and produces one shard of the codegen
// output.
class Dart2jsCodegenStep implements IOModularStep {
class Dart2jsCodegenStep extends IOModularStep {
final ShardDataId codeId;
Dart2jsCodegenStep(this.codeId);
@ -531,7 +586,7 @@ class Dart2jsCodegenStep implements IOModularStep {
// Step that invokes the dart2js codegen enqueuer and emitter on the main module
// given the results of the global analysis step and codegen shards.
class Dart2jsEmissionStep implements IOModularStep {
class Dart2jsEmissionStep extends IOModularStep {
@override
List<DataId> get resultData => const [jsId];
@ -583,7 +638,7 @@ class Dart2jsEmissionStep implements IOModularStep {
}
/// Step that runs the output of dart2js in d8 and saves the output.
class RunD8 implements IOModularStep {
class RunD8 extends IOModularStep {
@override
List<DataId> get resultData => const [txtId];

View file

@ -72,6 +72,12 @@ class SourceToSummaryDillStep implements IOModularStep {
@override
bool get onlyOnMain => false;
@override
bool get onlyOnSdk => false;
@override
bool get notOnSdk => false;
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
@ -162,6 +168,12 @@ class DDCStep implements IOModularStep {
@override
bool get onlyOnMain => false;
@override
bool get onlyOnSdk => false;
@override
bool get notOnSdk => false;
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
@ -244,6 +256,12 @@ class RunD8 implements IOModularStep {
@override
bool get onlyOnMain => true;
@override
bool get onlyOnSdk => false;
@override
bool get notOnSdk => false;
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {

View file

@ -72,6 +72,12 @@ class SourceToSummaryDillStep implements IOModularStep {
@override
bool get onlyOnMain => false;
@override
bool get onlyOnSdk => false;
@override
bool get notOnSdk => false;
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
@ -164,6 +170,12 @@ class DDCStep implements IOModularStep {
@override
bool get onlyOnMain => false;
@override
bool get onlyOnSdk => false;
@override
bool get notOnSdk => false;
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
@ -248,6 +260,12 @@ class RunD8 implements IOModularStep {
@override
bool get onlyOnMain => true;
@override
bool get onlyOnSdk => false;
@override
bool get notOnSdk => false;
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {

View file

@ -172,7 +172,9 @@ Future<Component?> compile(
bool verbose,
FileSystem fileSystem,
DiagnosticMessageHandler onDiagnostic,
Uri input) async {
List<Uri> inputs,
bool isModularCompile) async {
assert(inputs.length == 1 || isModularCompile);
CompilerOptions options = state.options;
options
..onDiagnostic = onDiagnostic
@ -181,7 +183,7 @@ Future<Component?> compile(
ProcessedOptions processedOpts = state.processedOpts;
processedOpts.inputs.clear();
processedOpts.inputs.add(input);
processedOpts.inputs.addAll(inputs);
processedOpts.clearFileSystemCache();
CompilerResult? compilerResult = await CompilerContext.runWithOptions(
@ -189,9 +191,10 @@ Future<Component?> compile(
CompilerResult compilerResult = await generateKernelInternal();
Component? component = compilerResult.component;
if (component == null) return null;
if (component.mainMethod == null) {
if (component.mainMethod == null && !isModularCompile) {
context.options.report(
messageMissingMain.withLocation(input, -1, 0), Severity.error);
messageMissingMain.withLocation(inputs.single, -1, 0),
Severity.error);
return null;
}
return compilerResult;

View file

@ -42,12 +42,20 @@ class ModularStep {
/// Whether this step is only executed on the main module.
final bool onlyOnMain;
/// Whether this step is only exceuted on the SDK.
final bool onlyOnSdk;
/// Whether this step is not executed on the SDK.
final bool notOnSdk;
ModularStep(
{this.needsSources: true,
this.dependencyDataNeeded: const [],
this.moduleDataNeeded: const [],
this.resultData,
this.onlyOnMain: false});
this.onlyOnMain: false,
this.onlyOnSdk: false,
this.notOnSdk: false});
/// Notifies that the step was not executed, but cached instead.
void notifyCached(Module module) {}
@ -77,6 +85,11 @@ abstract class Pipeline<S extends ModularStep> {
}
void _validate() {
// Whether or not two steps run on mutually exclusive input data.
bool areMutuallyExclusive(S a, S b) {
return (a.onlyOnSdk && b.notOnSdk) || (b.onlyOnSdk && a.notOnSdk);
}
// Ensure that steps consume only data that was produced by previous steps
// or by the same step on a dependency.
Map<DataId, S> previousKinds = {};
@ -86,7 +99,8 @@ abstract class Pipeline<S extends ModularStep> {
"'${step.runtimeType}' needs to declare what data it produces.");
}
for (var resultKind in step.resultData) {
if (previousKinds.containsKey(resultKind)) {
if (previousKinds.containsKey(resultKind) &&
!areMutuallyExclusive(step, previousKinds[resultKind])) {
_validationError("Cannot produce the same data on two modular steps."
" '$resultKind' was previously produced by "
"'${previousKinds[resultKind].runtimeType}' but "
@ -140,7 +154,9 @@ abstract class Pipeline<S extends ModularStep> {
deps.addAll(transitiveDependencies[dependency]);
}
if (step.onlyOnMain && !module.isMain) return;
if ((step.onlyOnMain && !module.isMain) ||
(step.onlyOnSdk && !module.isSdk) ||
(step.notOnSdk && module.isSdk)) return;
// Include only requested data from transitive dependencies.
Map<Module, Set<DataId>> visibleData = {};

View file

@ -107,6 +107,8 @@ class SourceOnlyStep implements IOModularStep {
List<DataId> get moduleDataNeeded => const [];
List<DataId> get resultData => [resultId];
bool get onlyOnMain => false;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
SourceOnlyStep(this.action, this.resultId, this.needsSources);
@ -137,6 +139,8 @@ class ModuleDataStep implements IOModularStep {
final DataId resultId;
final DataId inputId;
bool get onlyOnMain => false;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
ModuleDataStep(this.action, this.inputId, this.resultId, bool requestInput)
: moduleDataNeeded = requestInput ? [inputId] : [];
@ -166,6 +170,8 @@ class TwoOutputStep implements IOModularStep {
final DataId result2Id;
final DataId inputId;
bool get onlyOnMain => false;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
TwoOutputStep(
this.action1, this.action2, this.inputId, this.result1Id, this.result2Id);
@ -198,6 +204,8 @@ class LinkStep implements IOModularStep {
final DataId depId;
final DataId resultId;
bool get onlyOnMain => false;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
LinkStep(this.action, this.inputId, this.depId, this.resultId,
bool requestDependencies)
@ -230,6 +238,8 @@ class MainOnlyStep implements IOModularStep {
final DataId depId;
final DataId resultId;
bool get onlyOnMain => true;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
MainOnlyStep(this.action, this.inputId, this.depId, this.resultId,
bool requestDependencies)

View file

@ -86,6 +86,8 @@ class SourceOnlyStep implements MemoryModularStep {
List<DataId> get moduleDataNeeded => const [];
List<DataId> get resultData => [resultId];
bool get onlyOnMain => false;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
SourceOnlyStep(this.action, this.resultId, this.needsSources);
@ -114,6 +116,8 @@ class ModuleDataStep implements MemoryModularStep {
final DataId resultId;
final DataId inputId;
bool get onlyOnMain => false;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
ModuleDataStep(this.action, this.inputId, this.resultId, bool requestInput)
: moduleDataNeeded = requestInput ? [inputId] : [];
@ -144,6 +148,8 @@ class TwoOutputStep implements MemoryModularStep {
final DataId result2Id;
final DataId inputId;
bool get onlyOnMain => false;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
TwoOutputStep(
this.action1, this.action2, this.inputId, this.result1Id, this.result2Id);
@ -177,6 +183,8 @@ class LinkStep implements MemoryModularStep {
final DataId resultId;
List<DataId> get resultData => [resultId];
bool get onlyOnMain => false;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
LinkStep(this.action, this.inputId, this.depId, this.resultId,
bool requestDependencies)
@ -208,6 +216,8 @@ class MainOnlyStep implements MemoryModularStep {
final DataId resultId;
List<DataId> get resultData => [resultId];
bool get onlyOnMain => true;
bool get onlyOnSdk => false;
bool get notOnSdk => false;
MainOnlyStep(this.action, this.inputId, this.depId, this.resultId,
bool requestDependencies)