Skip to content

Commit 1c0c66f

Browse files
nshahanCommit Queue
authored andcommitted
[ddc] Remove lib/ directories from source maps
Given the existing assumptions that original source files will be served in a directory structure similar to layout defined by the package_config.json but without `lib/` directories this change attempts to detect when a source map contains sources from multiple lib directories and clean them up from the relative paths. The strategy used relies on passing a package_config.json as a signal to detect when sources come from different packages. Fixes: dart-lang/webdev#1692 Issue: #40251 Change-Id: I6b82b33bae485d74fc61ef118dbe9ffddafab9a7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/456500 Reviewed-by: Sigmund Cherem <[email protected]> Commit-Queue: Nicholas Shahan <[email protected]>
1 parent a2e9153 commit 1c0c66f

File tree

4 files changed

+147
-2
lines changed

4 files changed

+147
-2
lines changed

pkg/dev_compiler/lib/src/command/command.dart

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import 'package:kernel/kernel.dart';
1919
import 'package:kernel/target/targets.dart';
2020
import 'package:kernel/text/ast_to_text.dart' as kernel show Printer;
2121
import 'package:kernel/text/debug_printer.dart';
22+
import 'package:package_config/package_config.dart';
2223
import 'package:path/path.dart' as p;
2324
import 'package:source_maps/source_maps.dart' show SourceMapBuilder;
2425

@@ -786,7 +787,7 @@ class JSCode {
786787
/// Converts [moduleTree] to [JSCode], using [format].
787788
///
788789
/// See [placeSourceMap] for a description of [sourceMapBase], [customScheme],
789-
/// and [multiRootOutputPath] arguments.
790+
/// [multiRootOutputPath] and [packageConfig] arguments.
790791
JSCode jsProgramToCode(
791792
js_ast.Program moduleTree,
792793
ModuleFormat format, {
@@ -802,6 +803,7 @@ JSCode jsProgramToCode(
802803
String? multiRootOutputPath,
803804
Compiler? compiler,
804805
Component? component,
806+
PackageConfig? packageConfig,
805807
}) {
806808
var opts = js_ast.JavaScriptPrintingOptions(
807809
allowKeywordsInProperties: true,
@@ -835,6 +837,7 @@ JSCode jsProgramToCode(
835837
customScheme,
836838
multiRootOutputPath: multiRootOutputPath,
837839
sourceMapBase: sourceMapBase,
840+
packageConfig: packageConfig,
838841
);
839842
var jsDir = p.dirname(p.fromUri(jsUrl));
840843
var relative = p.relative(p.fromUri(mapUrl), from: jsDir);
@@ -1102,6 +1105,17 @@ Uri sourcePathToRelativeUri(String source, {bool? windows}) {
11021105
return uri;
11031106
}
11041107

1108+
/// Pattern to match when a relative path appears to leave the `lib/` directory
1109+
/// of one package to enter the `lib/` directory of another package.
1110+
final _crossPackageLib = RegExp(
1111+
// Two or more '../'.
1112+
r'\.\.\/(?:\.\.\/)+'
1113+
// The package name (captured).
1114+
r'([^\/]*)'
1115+
// The `/lib/` directory.
1116+
r'\/lib\/',
1117+
);
1118+
11051119
/// Adjusts the source uris in [sourceMap] to be relative uris, and returns
11061120
/// the new map.
11071121
///
@@ -1117,6 +1131,11 @@ Uri sourcePathToRelativeUri(String source, {bool? windows}) {
11171131
/// to the [multiRootOutputPath], and assert that [multiRootOutputPath]
11181132
/// starts with `/packages` (more explanation inline).
11191133
///
1134+
/// Passing a [packageConfig] will enable the rewriting of original source paths
1135+
/// when the sourcemap contains files from `lib/` directories across multiple
1136+
/// packages. The rewriting assumes that the sources will be served in a package
1137+
/// directory structure without `lib/` directories.
1138+
///
11201139
// TODO(#40251): Remove this logic from dev_compiler itself, push it to the
11211140
// invokers of dev_compiler which have more knowledge about how they want
11221141
// source paths to look.
@@ -1126,12 +1145,14 @@ Map<String, Object?> placeSourceMap(
11261145
String? multiRootScheme, {
11271146
String? multiRootOutputPath,
11281147
String? sourceMapBase,
1148+
PackageConfig? packageConfig,
11291149
}) {
11301150
var map = Map.of(sourceMap);
11311151
// Convert to a local file path if it's not.
11321152
sourceMapPath = sourcePathToUri(p.absolute(p.fromUri(sourceMapPath))).path;
11331153
var sourceMapDir = p.url.dirname(sourceMapPath);
11341154
sourceMapBase ??= sourceMapDir;
1155+
var basePackageName = packageConfig?.packageOf(p.toUri(sourceMapBase))?.name;
11351156
var list = (map['sources'] as List).toList();
11361157

11371158
String makeRelative(String sourcePath) {
@@ -1161,7 +1182,31 @@ Map<String, Object?> placeSourceMap(
11611182
sourcePath = p.url.relative(sourcePath, from: sourceMapBase);
11621183

11631184
// Convert from relative local path to relative URI.
1164-
return p.toUri(sourcePath).path;
1185+
var relativeUriPath = p.toUri(sourcePath).path;
1186+
// Handle source paths when the original sources come from the `lib/`
1187+
// directories from different packages.
1188+
//
1189+
// This assumes that the original source files and source map will be hosted
1190+
// in a packages directory structure with all of the `lib/` directories
1191+
// removed.
1192+
// For reference:
1193+
// - https://github.com/dart-lang/sdk/issues/40251
1194+
// - https://github.com/dart-lang/webdev/issues/1692
1195+
if (basePackageName != null && uri.isScheme('file')) {
1196+
var sourcePackage = packageConfig?.packageOf(uri);
1197+
if (sourcePackage != null && basePackageName != sourcePackage.name) {
1198+
// The source location is part of a different package.
1199+
var match = _crossPackageLib.matchAsPrefix(relativeUriPath);
1200+
if (match != null) {
1201+
var crossPackageName = match.group(1);
1202+
return relativeUriPath.replaceFirst(
1203+
'../../$crossPackageName/lib/',
1204+
'../$crossPackageName/',
1205+
);
1206+
}
1207+
}
1208+
}
1209+
return relativeUriPath;
11651210
}
11661211

11671212
for (var i = 0; i < list.length; i++) {

pkg/dev_compiler/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies:
1919
front_end: any
2020
js_shared: any
2121
kernel: any
22+
package_config: any
2223
path: any
2324
shell_arg_splitter: any
2425
source_maps: any

pkg/frontend_server/lib/src/javascript_bundle.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ class IncrementalJavaScriptBundler {
351351
customScheme: _fileSystemScheme,
352352
compiler: compiler,
353353
component: summaryComponent,
354+
packageConfig: packageConfig,
354355
);
355356
final Uint8List codeBytes = utf8.encode(code.code);
356357
final Uint8List sourceMapBytes = utf8.encode(json.encode(code.sourceMap));

pkg/frontend_server/test/frontend_server_test.dart

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,6 +2684,104 @@ e() {
26842684
await runTests(moduleFormat: 'ddc', canary: true);
26852685
});
26862686
});
2687+
group(
2688+
'sourcemaps with cross package sources do not reference `lib/` '
2689+
'directories', () {
2690+
Future<void> runTests(
2691+
{required String moduleFormat, bool canary = false}) async {
2692+
// Create two packages in a dependency cycle.
2693+
final String packagesDir = '${tempDir.path}/packages';
2694+
final File packageConfig =
2695+
new File('${tempDir.path}/.dart_tool/package_config.json')
2696+
..createSync(recursive: true)
2697+
..writeAsStringSync('''
2698+
{
2699+
"configVersion": 2,
2700+
"packages": [
2701+
{
2702+
"name": "a",
2703+
"rootUri": "$packagesDir/a",
2704+
"packageUri": "lib/"
2705+
},
2706+
{
2707+
"name": "b",
2708+
"rootUri": "$packagesDir/b",
2709+
"packageUri": "lib/"
2710+
}
2711+
]
2712+
}
2713+
''');
2714+
new File('$packagesDir/a/lib/a.dart')
2715+
..createSync(recursive: true)
2716+
..writeAsStringSync("import 'package:b/b.dart';\n"
2717+
"String a1() => 'a1';\n"
2718+
"String a2() => 'a2' + b2();\n");
2719+
new File('$packagesDir/b/lib/b.dart')
2720+
..createSync(recursive: true)
2721+
..writeAsStringSync("import 'package:a/a.dart';\n"
2722+
"String b1() => 'b1' + a1();\n"
2723+
"String b2() => 'b2';\n");
2724+
// Compile the cycle as a single output.
2725+
final String library = 'package:a/a.dart';
2726+
final File dillFile = new File('${tempDir.path}/a.dill');
2727+
final File manifestFile = new File('${dillFile.path}.json');
2728+
final File sourceMapsFile = new File('${dillFile.path}.map');
2729+
final List<String> args = <String>[
2730+
'--sdk-root=${sdkRoot.toFilePath()}',
2731+
'--incremental',
2732+
'--platform=${ddcPlatformKernel.path}',
2733+
'--output-dill=${dillFile.path}',
2734+
'--target=dartdevc',
2735+
'--dartdevc-module-format=$moduleFormat',
2736+
if (canary) '--dartdevc-canary',
2737+
'--packages=${packageConfig.path}',
2738+
];
2739+
final FrontendServer frontendServer = new FrontendServer();
2740+
final Future<int> result = frontendServer.open(args);
2741+
frontendServer.compile(library);
2742+
final Completer<bool> expectationCompleter = new Completer<bool>();
2743+
frontendServer.listen((Result compiledResult) {
2744+
final CompilationResult result =
2745+
new CompilationResult.parse(compiledResult.status);
2746+
expect(result.errorsCount, 0);
2747+
expect(result.filename, dillFile.path);
2748+
expect(manifestFile.existsSync(), true);
2749+
// Extract the sourcemap.
2750+
final Map<String, dynamic> manifest =
2751+
json.decode(utf8.decode(manifestFile.readAsBytesSync()));
2752+
final List<dynamic> offsets =
2753+
manifest['/packages/a/a.dart.lib.js']['sourcemap'];
2754+
final Map<String, dynamic> sourcemap = json.decode(utf8.decode(
2755+
sourceMapsFile
2756+
.readAsBytesSync()
2757+
.sublist(offsets.first, offsets.last)));
2758+
// Verify source maps are pointing at correct source files.
2759+
//
2760+
// There is a built in assumption that the original sources for all
2761+
// packages are served in a structure similar to the definition from
2762+
// the package_config.json file but without the `lib/` directories.
2763+
final List<dynamic> sources = sourcemap['sources'];
2764+
expect(sources.length, 2);
2765+
expect(sources, containsAll(['a.dart', '../b/b.dart']));
2766+
2767+
frontendServer.accept();
2768+
frontendServer.quit();
2769+
expectationCompleter.complete(true);
2770+
});
2771+
2772+
await expectationCompleter.future;
2773+
expect(await result, 0);
2774+
frontendServer.close();
2775+
}
2776+
2777+
test('AMD module format', () async {
2778+
await runTests(moduleFormat: 'amd');
2779+
});
2780+
2781+
test('DDC module format and canary', () async {
2782+
await runTests(moduleFormat: 'ddc', canary: true);
2783+
});
2784+
});
26872785

26882786
group('compile expression to JavaScript', () {
26892787
Future<void> runTests(

0 commit comments

Comments
 (0)