| // Copyright (c) 2026, 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:front_end/src/api_unstable/compiler_state.dart'; |
| import 'package:front_end/src/compute_platform_binaries_location.dart' |
| show computePlatformBinariesLocation; |
| import 'package:frontend_server/compute_kernel.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/binary/ast_from_binary.dart'; |
| |
| Future<void> main() async { |
| await runHelper( |
| ddc_summary_sources_change_standalone_standalone_require_both, |
| ); |
| await runHelper( |
| ddc_summary_sources_change_standalone_require_one_require_both, |
| ); |
| |
| if (_countFailures != 0) { |
| // Set the exit code so the bots go red. |
| throw "Got $_countFailures failures."; |
| } |
| } |
| |
| int _countFailures = 0; |
| |
| /// Compile one standalone package, then compile another standalone package, |
| /// then compile a third requiring both. |
| /// At one point we had a bug where the target wasn't updated properly and the |
| /// output of the second compile was empty. In that case the third compile would |
| /// be missing sources. |
| Future<void> ddc_summary_sources_change_standalone_standalone_require_both( |
| Directory dir, |
| ) async { |
| Uri outDirUri = dir.uri.resolve("out"); |
| new Directory.fromUri(outDirUri)..createSync(); |
| Uri packagesFileUri = dir.uri.resolve("packages.json"); |
| _writePackageFilePkg1To3(packagesFileUri); |
| |
| Uri ddcOutlineUri = computePlatformBinariesLocation( |
| forceBuildDir: true, |
| ).resolve("ddc_outline.dill"); |
| |
| final Map<Uri, List<int>> inputDigests = { |
| ddcOutlineUri: [0], |
| }; |
| |
| // Compile package:pkg1/file.dart - a standalone package. |
| Uri pkg1File = dir.uri.resolve("live/pkg1/lib/file.dart"); |
| new File.fromUri(pkg1File) |
| ..createSync(recursive: true) |
| ..writeAsStringSync("int pkg1() { return 42; }"); |
| |
| InitializedCompilerState? previousState; |
| Uri pkg1Output = outDirUri.resolve("1.dill"); |
| ComputeKernelResult result = await _compileSummaryAndCheck( |
| pkg1Output, |
| packagesFileUri, |
| ddcOutlineUri, |
| inputDigests, |
| "package:pkg1/file.dart", |
| previousState, |
| ); |
| previousState = result.previousState; |
| new File.fromUri(pkg1File).deleteSync(); |
| |
| // Compile package:pkg2/file.dart - a standalone package. |
| Uri pkg2File = dir.uri.resolve("live/pkg2/lib/file.dart"); |
| new File.fromUri(pkg2File) |
| ..createSync(recursive: true) |
| ..writeAsStringSync("int pkg2() { return 43; }"); |
| |
| Uri pkg2Output = outDirUri.resolve("2.dill"); |
| result = await _compileSummaryAndCheck( |
| pkg2Output, |
| packagesFileUri, |
| ddcOutlineUri, |
| inputDigests, |
| "package:pkg2/file.dart", |
| previousState, |
| ); |
| previousState = result.previousState; |
| new File.fromUri(pkg2File).deleteSync(); |
| |
| // Compile package:pkg3/file.dart - require both previous. |
| Uri pkg3File = dir.uri.resolve("live/pkg3/lib/file.dart"); |
| new File.fromUri(pkg3File) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(""" |
| import "package:pkg1/file.dart"; |
| import "package:pkg2/file.dart"; |
| int pkg3() { pkg1() + pkg2() + 3; } |
| """); |
| |
| Uri pkg3Output = outDirUri.resolve("3.dill"); |
| inputDigests[pkg1Output] = [1]; |
| inputDigests[pkg2Output] = [2]; |
| result = await _compileSummaryAndCheck( |
| pkg3Output, |
| packagesFileUri, |
| ddcOutlineUri, |
| inputDigests, |
| "package:pkg3/file.dart", |
| previousState, |
| inputSummaries: [pkg1Output.toFilePath(), pkg2Output.toFilePath()], |
| ); |
| previousState = result.previousState; |
| } |
| |
| /// Compile one standalone package, then compile another that requires the |
| /// first. Then compile a third that requires both the two previous. |
| /// At one point we had a bug where the target wasn't updated properly and the |
| /// second output had the same output as the first output, and trying to compile |
| /// the third threw because it had two summaries as input with the same library |
| /// inside. |
| Future<void> ddc_summary_sources_change_standalone_require_one_require_both( |
| Directory dir, |
| ) async { |
| Uri outDirUri = dir.uri.resolve("out"); |
| new Directory.fromUri(outDirUri)..createSync(); |
| Uri packagesFileUri = dir.uri.resolve("packages.json"); |
| _writePackageFilePkg1To3(packagesFileUri); |
| |
| Uri ddcOutlineUri = computePlatformBinariesLocation( |
| forceBuildDir: true, |
| ).resolve("ddc_outline.dill"); |
| |
| final Map<Uri, List<int>> inputDigests = { |
| ddcOutlineUri: [0], |
| }; |
| |
| // Compile package:pkg1/file.dart - a standalone package. |
| Uri pkg1File = dir.uri.resolve("live/pkg1/lib/file.dart"); |
| new File.fromUri(pkg1File) |
| ..createSync(recursive: true) |
| ..writeAsStringSync("int pkg1() { return 42; }"); |
| |
| InitializedCompilerState? previousState; |
| Uri pkg1Output = outDirUri.resolve("1.dill"); |
| ComputeKernelResult result = await _compileSummaryAndCheck( |
| pkg1Output, |
| packagesFileUri, |
| ddcOutlineUri, |
| inputDigests, |
| "package:pkg1/file.dart", |
| previousState, |
| ); |
| previousState = result.previousState; |
| new File.fromUri(pkg1File).deleteSync(); |
| |
| // Compile package:pkg2/file.dart - needs pkg1. |
| Uri pkg2File = dir.uri.resolve("live/pkg2/lib/file.dart"); |
| new File.fromUri(pkg2File) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(""" |
| import "package:pkg1/file.dart"; |
| int pkg2() { return pkg1() + 1; } |
| """); |
| inputDigests[pkg1Output] = [1]; |
| |
| Uri pkg2Output = outDirUri.resolve("2.dill"); |
| result = await _compileSummaryAndCheck( |
| pkg2Output, |
| packagesFileUri, |
| ddcOutlineUri, |
| inputDigests, |
| "package:pkg2/file.dart", |
| previousState, |
| inputSummaries: [pkg1Output.toFilePath()], |
| ); |
| previousState = result.previousState; |
| new File.fromUri(pkg2File).deleteSync(); |
| |
| // Compile package:pkg3/file.dart - needs pkg1 and pkg2. |
| Uri pkg3File = dir.uri.resolve("live/pkg3/lib/file.dart"); |
| new File.fromUri(pkg3File) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(""" |
| import "package:pkg1/file.dart"; |
| import "package:pkg2/file.dart"; |
| int pkg3() { return pkg1() + pkg2() + 2; } |
| """); |
| inputDigests[pkg2Output] = [2]; |
| |
| Uri pkg3Output = outDirUri.resolve("3.dill"); |
| result = await _compileSummaryAndCheck( |
| pkg3Output, |
| packagesFileUri, |
| ddcOutlineUri, |
| inputDigests, |
| "package:pkg3/file.dart", |
| previousState, |
| inputSummaries: [pkg1Output.toFilePath(), pkg2Output.toFilePath()], |
| ); |
| } |
| |
| Future<void> runHelper(Future<void> Function(Directory) runThis) async { |
| Directory tmpDir = Directory.systemTemp.createTempSync('compute_kernel_test'); |
| try { |
| await runThis(tmpDir); |
| } catch (e, st) { |
| stderr.writeln("Failure running $runThis:\n\n$e\n\n"); |
| stderr.writeln(st); |
| stderr.writeln("\n-----\n"); |
| _countFailures++; |
| exitCode = 1; |
| } finally { |
| try { |
| tmpDir.deleteSync(recursive: true); |
| } catch (e) { |
| // Wait a little and retry. |
| sleep(const Duration(milliseconds: 42)); |
| try { |
| tmpDir.deleteSync(recursive: true); |
| } catch (e) { |
| print('Warning: Got exception when deleting temp dir: $e'); |
| } |
| } |
| } |
| } |
| |
| Future<ComputeKernelResult> _compileSummaryAndCheck( |
| Uri outDill, |
| Uri packagesFileUri, |
| Uri ddcOutlineUri, |
| Map<Uri, List<int>> inputDigests, |
| String source, |
| InitializedCompilerState? previousState, { |
| List<String>? inputSummaries, |
| }) async { |
| ComputeKernelResult result = await computeKernel( |
| [ |
| "--output=${outDill.toFilePath()}", |
| "--packages-file=${packagesFileUri.toFilePath()}", |
| "--dart-sdk-summary=${ddcOutlineUri.toFilePath()}", |
| "--exclude-non-sources", |
| "--summary-only", |
| "--reuse-compiler-result", |
| "--use-incremental-compiler", |
| "--sound-null-safety", |
| "--source=$source", |
| if (inputSummaries != null) |
| for (String summary in inputSummaries) "--input-summary=$summary", |
| ], |
| isWorker: true, |
| outputBuffer: null, |
| inputDigests: inputDigests, |
| previousState: previousState, |
| ); |
| if (!result.succeeded) throw "Failed to compile."; |
| File outFile = new File.fromUri(outDill); |
| |
| Component component = new Component(); |
| new BinaryBuilder(outFile.readAsBytesSync()).readComponent(component); |
| List<String> outputLibs = component.libraries |
| .map((lib) => lib.importUri.toString()) |
| .toList(); |
| |
| if (outputLibs.length != 1 || outputLibs.single != source) { |
| throw "Failure: Output contained ${outputLibs.length} libraries, " |
| "expected exactly 1 with uri $source: " |
| "${outputLibs}"; |
| } |
| |
| return result; |
| } |
| |
| void _writePackageFilePkg1To3(Uri packagesFileUri) { |
| new File.fromUri(packagesFileUri)..writeAsStringSync(""" |
| { |
| "configVersion": 2, |
| "packages": [ |
| { |
| "name": "pkg1", |
| "rootUri": "live/pkg1", |
| "packageUri": "lib/", |
| "languageVersion": "3.10" |
| }, |
| { |
| "name": "pkg2", |
| "rootUri": "live/pkg2", |
| "packageUri": "lib/", |
| "languageVersion": "3.10" |
| }, |
| { |
| "name": "pkg3", |
| "rootUri": "live/pkg3", |
| "packageUri": "lib/", |
| "languageVersion": "3.10" |
| } |
| ] |
| } |
| """); |
| } |