Future _createDevCompilerModule()

in build_web_compilers/lib/src/dev_compiler_builder.dart [154:315]


Future<void> _createDevCompilerModule(
    Module module,
    BuildStep buildStep,
    bool useIncrementalCompiler,
    bool generateFullDill,
    bool emitDebugSymbols,
    bool trackUnusedInputs,
    String dartSdk,
    String sdkKernelPath,
    String librariesPath,
    Map<String, String> environment,
    bool soundNullSafety,
    {bool debugMode = true}) async {
  var transitiveDeps = await buildStep.trackStage('CollectTransitiveDeps',
      () => module.computeTransitiveDependencies(buildStep));
  var transitiveKernelDeps = [
    for (var dep in transitiveDeps)
      dep.primarySource.changeExtension(ddcKernelExtension(soundNullSafety)),
  ];
  var scratchSpace = await buildStep.fetchResource(scratchSpaceResource);

  var allAssetIds = <AssetId>{...module.sources, ...transitiveKernelDeps};
  await buildStep.trackStage(
      'EnsureAssets', () => scratchSpace.ensureAssets(allAssetIds, buildStep));
  var jsId =
      module.primarySource.changeExtension(jsModuleExtension(soundNullSafety));
  var jsOutputFile = scratchSpace.fileFor(jsId);
  var sdkSummary = p.url.join(dartSdk, sdkKernelPath);

  // Maps the inputs paths we provide to the ddc worker to asset ids, if
  // `trackUnusedInputs` is `true`.
  Map<String, AssetId>? kernelInputPathToId;

  // If `trackUnusedInputs` is `true`, this is the file we will use to
  // communicate the used inputs with the ddc worker.
  File? usedInputsFile;

  if (trackUnusedInputs) {
    usedInputsFile = await File(p.join(
            (await Directory.systemTemp.createTemp('ddk_builder_')).path,
            'used_inputs.txt'))
        .create();
    kernelInputPathToId = {};
  }

  var request = WorkRequest()
    ..arguments.addAll([
      '--dart-sdk-summary=$sdkSummary',
      '--modules=amd',
      '--no-summarize',
      if (generateFullDill) '--experimental-output-compiled-kernel',
      if (emitDebugSymbols) '--emit-debug-symbols',
      '-o',
      jsOutputFile.path,
      debugMode ? '--source-map' : '--no-source-map',
      for (var dep in transitiveDeps) _summaryArg(dep, soundNullSafety),
      '--packages=$multiRootScheme:///.dart_tool/package_config.json',
      '--module-name=${ddcModuleName(jsId, soundNullSafety)}',
      '--multi-root-scheme=$multiRootScheme',
      '--multi-root=.',
      '--track-widget-creation',
      '--inline-source-map',
      '--libraries-file=${p.toUri(librariesPath)}',
      '--experimental-emit-debug-metadata',
      if (useIncrementalCompiler) ...[
        '--reuse-compiler-result',
        '--use-incremental-compiler',
      ],
      if (usedInputsFile != null)
        '--used-inputs-file=${usedInputsFile.uri.toFilePath()}',
      for (var source in module.sources) _sourceArg(source),
      for (var define in environment.entries) '-D${define.key}=${define.value}',
      for (var experiment in enabledExperiments)
        '--enable-experiment=$experiment',
      '--${soundNullSafety ? '' : 'no-'}sound-null-safety',
    ])
    ..inputs.add(Input()
      ..path = sdkSummary
      ..digest = [0])
    ..inputs.addAll(await Future.wait(transitiveKernelDeps.map((dep) async {
      var file = scratchSpace.fileFor(dep);
      if (kernelInputPathToId != null) {
        kernelInputPathToId[file.uri.toString()] = dep;
      }
      return Input()
        ..path = file.path
        ..digest = (await buildStep.digest(dep)).bytes;
    })));

  try {
    var driverResource = dartdevkDriverResource;
    var driver = await buildStep.fetchResource(driverResource);
    var response = await driver.doWork(request,
        trackWork: (response) =>
            buildStep.trackStage('Compile', () => response, isExternal: true));

    // TODO(jakemac53): Fix the ddc worker mode so it always sends back a bad
    // status code if something failed. Today we just make sure there is an output
    // JS file to verify it was successful.
    var message = response.output
        .replaceAll('${scratchSpace.tempDir.path}/', '')
        .replaceAll('$multiRootScheme:///', '');
    if (response.exitCode != EXIT_CODE_OK ||
        !jsOutputFile.existsSync() ||
        message.contains('Error:')) {
      throw DartDevcCompilationException(jsId, message);
    }

    if (message.isNotEmpty) {
      log.info('\n$message');
    }

    // Copy the output back using the buildStep.
    await scratchSpace.copyOutput(jsId, buildStep);

    if (generateFullDill) {
      var currentFullKernelId = module.primarySource
          .changeExtension(fullKernelExtension(soundNullSafety));
      await scratchSpace.copyOutput(currentFullKernelId, buildStep);
    }

    if (debugMode) {
      // We need to modify the sources in the sourcemap to remove the custom
      // `multiRootScheme` that we use.
      var sourceMapId = module.primarySource
          .changeExtension(jsSourceMapExtension(soundNullSafety));
      var file = scratchSpace.fileFor(sourceMapId);
      var content = await file.readAsString();
      var json = jsonDecode(content);
      json['sources'] = fixSourceMapSources((json['sources'] as List).cast());
      await buildStep.writeAsString(sourceMapId, jsonEncode(json));

      // Copy the metadata output, modifying its contents to remove the temp
      // directory from paths
      var metadataId = module.primarySource
          .changeExtension(metadataExtension(soundNullSafety));
      file = scratchSpace.fileFor(metadataId);
      content = await file.readAsString();
      json = jsonDecode(content);
      _fixMetadataSources(
          json as Map<String, dynamic>, scratchSpace.tempDir.uri);
      await buildStep.writeAsString(metadataId, jsonEncode(json));

      // Copy the symbols output, modifying its contents to remove the temp
      // directory from paths
      if (emitDebugSymbols) {
        var symbolsId = module.primarySource
            .changeExtension(symbolsExtension(soundNullSafety));
        await scratchSpace.copyOutput(symbolsId, buildStep);
      }
    }

    // Note that we only want to do this on success, we can't trust the unused
    // inputs if there is a failure.
    if (usedInputsFile != null) {
      await reportUnusedKernelInputs(usedInputsFile, transitiveKernelDeps,
          kernelInputPathToId!, buildStep);
    }
  } finally {
    await usedInputsFile?.parent.delete(recursive: true);
  }
}