diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 20ac79153..95e4f5c00 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,5 +1,6 @@ ## 14.0.4-dev - Port some `dwds` files to null safety. +- Fix failing `frontend_server_evaluate` tests. ## 14.0.3 - Make data types null safe. diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart index 2146ef34a..30419d815 100644 --- a/dwds/lib/src/debugging/location.dart +++ b/dwds/lib/src/debugging/location.dart @@ -5,6 +5,7 @@ // @dart = 2.9 import 'package:async/async.dart'; +import 'package:dwds/src/loaders/require.dart'; import 'package:path/path.dart' as p; import 'package:source_maps/parser.dart'; import 'package:source_maps/source_maps.dart'; @@ -272,7 +273,7 @@ class Locations { await globalLoadStrategy.sourceMapPathForModule(_entrypoint, module); final sourceMapContents = await _assetReader.sourceMapContents(sourceMapPath); - final scriptLocation = p.url.dirname('/$modulePath'); + final scriptLocation = p.url.dirname('/${relativizePath(modulePath)}'); if (sourceMapContents == null) return result; // This happens to be a [SingleMapping] today in DDC. final mapping = parse(sourceMapContents); diff --git a/dwds/lib/src/handlers/dev_handler.dart b/dwds/lib/src/handlers/dev_handler.dart index 37f7c0ea8..02d3d3758 100644 --- a/dwds/lib/src/handlers/dev_handler.dart +++ b/dwds/lib/src/handlers/dev_handler.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:dwds/src/loaders/require.dart'; import 'package:logging/logging.dart'; import 'package:shelf/shelf.dart'; import 'package:sse/server/sse_handler.dart'; @@ -201,7 +202,7 @@ class DevHandler { 'localhost', webkitDebugger, executionContext, - appTab.url, + basePathForServerUri(appTab.url), _assetReader, _loadStrategy, appConnection, diff --git a/dwds/lib/src/loaders/frontend_server_require.dart b/dwds/lib/src/loaders/frontend_server_require.dart index d3b7eb84f..8205dc431 100644 --- a/dwds/lib/src/loaders/frontend_server_require.dart +++ b/dwds/lib/src/loaders/frontend_server_require.dart @@ -22,8 +22,7 @@ class FrontendServerRequireStrategyProvider { RequireStrategy _requireStrategy; FrontendServerRequireStrategyProvider(this._configuration, this._assetReader, - this._digestsProvider, String basePath) - : _basePath = basePathForServerUri(basePath); + this._digestsProvider, this._basePath); RequireStrategy get strategy => _requireStrategy ??= RequireStrategy( _configuration, @@ -37,8 +36,12 @@ class FrontendServerRequireStrategyProvider { _assetReader, ); - String _removeBasePath(String path) => - path.startsWith(_basePath) ? path.substring(_basePath.length) : null; + String _removeBasePath(String path) { + if (_basePath.isEmpty) return path; + // If path is a server path it might start with a '/'. + final base = path.startsWith('/') ? '/$_basePath' : _basePath; + return path.startsWith(base) ? path.substring(base.length) : path; + } String _addBasePath(String serverPath) => _basePath == null || _basePath.isEmpty diff --git a/dwds/lib/src/loaders/require.dart b/dwds/lib/src/loaders/require.dart index 30413d7af..ab309da13 100644 --- a/dwds/lib/src/loaders/require.dart +++ b/dwds/lib/src/loaders/require.dart @@ -17,16 +17,13 @@ import '../services/expression_compiler.dart'; /// Find the path we are serving from the url. /// /// Example: -/// https://localhost/base/index.html => /base -/// https://localhost/base => /base +/// https://localhost/base/index.html => base +/// https://localhost/base => base String basePathForServerUri(String url) { if (url == null) return null; final uri = Uri.parse(url); var base = uri.path.endsWith('.html') ? p.dirname(uri.path) : uri.path; - if (base.isNotEmpty) { - base = base.startsWith('/') ? base : '/$base'; - } - return base; + return base = base.startsWith('/') ? base.substring(1) : base; } String relativizePath(String path) => diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 7bc8aa21a..ba872c4cb 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -62,8 +62,8 @@ class ChromeProxyService implements VmServiceInterface { Completer _compilerCompleter = Completer(); Future get isCompilerInitialized => _compilerCompleter.future; - /// The root URI at which we're serving. - final String uri; + /// The root at which we're serving. + final String root; final RemoteDebugger remoteDebugger; final ExecutionContext executionContext; @@ -104,7 +104,7 @@ class ChromeProxyService implements VmServiceInterface { ChromeProxyService._( this._vm, - this.uri, + this.root, this._assetReader, this.remoteDebugger, this._modules, @@ -120,14 +120,14 @@ class ChromeProxyService implements VmServiceInterface { appInspectorProvider, _locations, _skipLists, - uri, + root, ); _debuggerCompleter.complete(debugger); } static Future create( RemoteDebugger remoteDebugger, - String tabUrl, + String root, AssetReader assetReader, LoadStrategy loadStrategy, AppConnection appConnection, @@ -150,12 +150,12 @@ class ChromeProxyService implements VmServiceInterface { pid: -1, ); - final modules = Modules(tabUrl); - final locations = Locations(assetReader, modules, tabUrl); + final modules = Modules(root); + final locations = Locations(assetReader, modules, root); final skipLists = SkipLists(); final service = ChromeProxyService._( vm, - tabUrl, + root, assetReader, remoteDebugger, modules, @@ -229,7 +229,7 @@ class ChromeProxyService implements VmServiceInterface { remoteDebugger, _assetReader, _locations, - uri, + root, debugger, executionContext, sdkConfiguration, @@ -352,7 +352,7 @@ class ChromeProxyService implements VmServiceInterface { 'The VM is unable to add a breakpoint ' 'at the specified line or function'); } - final dartUri = DartUri(scriptUri, uri); + final dartUri = DartUri(scriptUri, root); final ref = await _inspector.scriptRefFor(dartUri.serverPath); return (await _debugger) .addBreakpoint(isolateId, ref.id, line, column: column); diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart index 9df8d705c..d30af4071 100644 --- a/dwds/lib/src/services/debug_service.dart +++ b/dwds/lib/src/services/debug_service.dart @@ -210,7 +210,7 @@ class DebugService { String hostname, RemoteDebugger remoteDebugger, ExecutionContext executionContext, - String tabUrl, + String root, AssetReader assetReader, LoadStrategy loadStrategy, AppConnection appConnection, @@ -225,7 +225,7 @@ class DebugService { useSse ??= false; final chromeProxyService = await ChromeProxyService.create( remoteDebugger, - tabUrl, + root, assetReader, loadStrategy, appConnection, diff --git a/dwds/lib/src/utilities/dart_uri.dart b/dwds/lib/src/utilities/dart_uri.dart index 131f2cb9c..1afefb784 100644 --- a/dwds/lib/src/utilities/dart_uri.dart +++ b/dwds/lib/src/utilities/dart_uri.dart @@ -8,7 +8,6 @@ import 'package:logging/logging.dart'; import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as p; -import '../loaders/require.dart'; import '../loaders/strategy.dart'; import 'sdk_configuration.dart'; @@ -30,12 +29,8 @@ class DartUri { /// path is a web server path and so relative to the directory being /// served, not to the package. /// - /// The optional [serverUri] is a temporary workaround for a bug with construction. - /// Older SDKs (before D24) gave us a path that didn't include the full path, - /// e.g. main.dart rather than hello_world/main.dart and src/path.dart rather than - /// packages/path/src/path.dart. The optional [serverUri] is the full URI of the - /// JS script. The dirname of that path should give us the missing prefix. - factory DartUri(String uri, [String serverUri]) { + /// The optional [root] is the directory the app is served from. + factory DartUri(String uri, [String root]) { final serverPath = globalLoadStrategy.serverPathForAppUri(uri); if (serverPath != null) { return DartUri._(serverPath); @@ -43,13 +38,13 @@ class DartUri { // TODO(annagrin): Support creating DartUris from `dart:` uris. // Issue: https://github.com/dart-lang/webdev/issues/1584 if (uri.startsWith('package:')) { - return DartUri._fromPackageUri(uri, serverUri: serverUri); + return DartUri._fromPackageUri(uri, root: root); } if (uri.startsWith('file:')) { - return DartUri._fromFileUri(uri, serverUri: serverUri); + return DartUri._fromFileUri(uri, root: root); } if (uri.startsWith('/packages/')) { - return DartUri._fromRelativePath(uri, serverUri: serverUri); + return DartUri._fromRelativePath(uri, root: root); } if (uri.startsWith('/')) { return DartUri._fromRelativePath(uri); @@ -65,32 +60,30 @@ class DartUri { String toString() => 'DartUri: $serverPath'; /// Construct from a package: URI - factory DartUri._fromPackageUri(String uri, {String serverUri}) { - final basePath = basePathForServerUri(serverUri); + factory DartUri._fromPackageUri(String uri, {String root}) { final packagePath = 'packages/${uri.substring("package:".length)}'; - if (serverUri != null) { - final relativePath = p.url.join(basePath, packagePath); + if (root != null) { + final relativePath = p.url.join(root, packagePath); return DartUri._fromRelativePath(relativePath); } return DartUri._(packagePath); } /// Construct from a file: URI - factory DartUri._fromFileUri(String uri, {String serverUri}) { + factory DartUri._fromFileUri(String uri, {String root}) { final libraryName = _resolvedUriToUri[uri]; - if (libraryName != null) return DartUri(libraryName, serverUri); + if (libraryName != null) return DartUri(libraryName, root); // This is not one of our recorded libraries. throw ArgumentError.value(uri, 'uri', 'Unknown library'); } /// Construct from a path, relative to the directory being served. - factory DartUri._fromRelativePath(String uri, {String serverUri}) { + factory DartUri._fromRelativePath(String uri, {String root}) { uri = uri[0] == '.' ? uri.substring(1) : uri; uri = uri[0] == '/' ? uri.substring(1) : uri; - if (serverUri != null) { - final basePath = basePathForServerUri(serverUri); - return DartUri._fromRelativePath(p.url.join(basePath, uri)); + if (root != null) { + return DartUri._fromRelativePath(p.url.join(root, uri)); } return DartUri._(uri); } diff --git a/dwds/test/build_daemon_callstack_test.dart b/dwds/test/build_daemon_callstack_test.dart index 84aee2cc5..63eee63f6 100644 --- a/dwds/test/build_daemon_callstack_test.dart +++ b/dwds/test/build_daemon_callstack_test.dart @@ -46,15 +46,20 @@ void main() { // Enable verbose logging for debugging. final debug = false; - for (var soundNullSafety in [false, true]) { + for (var nullSafety in NullSafety.values) { + final soundNullSafety = nullSafety == NullSafety.sound; final setup = soundNullSafety ? TestSetup.sound() : TestSetup.unsound(); final context = setup.context; - group('${soundNullSafety ? "sound" : "weak"} null safety |', () { + group('${nullSafety.name} null safety |', () { setUpAll(() async { setCurrentLogWriter(debug: debug); await context.setUp( - enableExpressionEvaluation: true, verboseCompiler: debug); + compilationMode: CompilationMode.buildDaemon, + nullSafety: nullSafety, + enableExpressionEvaluation: true, + verboseCompiler: debug, + ); }); tearDownAll(() async { diff --git a/dwds/test/build_daemon_evaluate_test.dart b/dwds/test/build_daemon_evaluate_test.dart index 365d499cf..45a099532 100644 --- a/dwds/test/build_daemon_evaluate_test.dart +++ b/dwds/test/build_daemon_evaluate_test.dart @@ -15,18 +15,13 @@ void main() async { // Enable verbose logging for debugging. final debug = false; - for (var soundNullSafety in [false, true]) { - group('${soundNullSafety ? "sound" : "weak"} null safety |', () { - for (var basePath in ['', 'abc']) { - group('with base "$basePath" |', () { - testAll( - compilationMode: CompilationMode.buildDaemon, - soundNullSafety: soundNullSafety, - basePath: basePath, - debug: debug, - ); - }); - } + for (var nullSafety in NullSafety.values) { + group('${nullSafety.name} null safety |', () { + testAll( + compilationMode: CompilationMode.buildDaemon, + nullSafety: nullSafety, + debug: debug, + ); }); } } diff --git a/dwds/test/dart_uri_test.dart b/dwds/test/dart_uri_test.dart index ef03ad0e5..2dfdbbacf 100644 --- a/dwds/test/dart_uri_test.dart +++ b/dwds/test/dart_uri_test.dart @@ -33,8 +33,7 @@ void main() { }); test('parses package : paths with root', () { - final uri = DartUri( - 'package:path/path.dart', 'http://localhost:8080/foo/bar/blah'); + final uri = DartUri('package:path/path.dart', 'foo/bar/blah'); expect(uri.serverPath, 'foo/bar/blah/packages/path/path.dart'); }); diff --git a/dwds/test/evaluate_common.dart b/dwds/test/evaluate_common.dart index ad452f692..c62bfbbeb 100644 --- a/dwds/test/evaluate_common.dart +++ b/dwds/test/evaluate_common.dart @@ -18,36 +18,51 @@ import 'fixtures/context.dart'; import 'fixtures/logging.dart'; class TestSetup { - static final contextUnsound = TestContext( + static TestContext contextUnsound(String index) => TestContext( directory: p.join('..', 'fixtures', '_testPackage'), entry: p.join('..', 'fixtures', '_testPackage', 'web', 'main.dart'), - path: 'index.html', + path: index, pathToServe: 'web'); - static final contextSound = TestContext( + static TestContext contextSound(String index) => TestContext( directory: p.join('..', 'fixtures', '_testPackageSound'), entry: p.join('..', 'fixtures', '_testPackageSound', 'web', 'main.dart'), - path: 'index.html', + path: index, pathToServe: 'web'); TestContext context; - TestSetup.sound() : context = contextSound; + TestSetup.sound(IndexBaseMode baseMode) + : context = contextSound(_index(baseMode)); - TestSetup.unsound() : context = contextUnsound; + TestSetup.unsound(IndexBaseMode baseMode) + : context = contextUnsound(_index(baseMode)); + + factory TestSetup.create(NullSafety nullSafety, IndexBaseMode baseMode) => + nullSafety == NullSafety.sound + ? TestSetup.sound(baseMode) + : TestSetup.unsound(baseMode); ChromeProxyService get service => fetchChromeProxyService(context.debugConnection); WipConnection get tabConnection => context.tabConnection; + + static String _index(IndexBaseMode baseMode) => + baseMode == IndexBaseMode.base ? 'base_index.html' : 'index.html'; } void testAll({ CompilationMode compilationMode = CompilationMode.buildDaemon, - bool soundNullSafety = false, - String basePath = '', + IndexBaseMode indexBaseMode = IndexBaseMode.noBase, + NullSafety nullSafety, bool debug = false, }) { - final setup = soundNullSafety ? TestSetup.sound() : TestSetup.unsound(); + if (compilationMode == CompilationMode.buildDaemon && + indexBaseMode == IndexBaseMode.base) { + throw StateError( + 'build daemon scenario does not support non-empty base in index file'); + } + final setup = TestSetup.create(nullSafety, indexBaseMode); final context = setup.context; Future onBreakPoint(String isolate, ScriptRef script, @@ -71,9 +86,10 @@ void testAll({ setUpAll(() async { setCurrentLogWriter(debug: debug); await context.setUp( + compilationMode: compilationMode, + nullSafety: nullSafety, enableExpressionEvaluation: true, verboseCompiler: debug, - basePath: basePath, ); }); @@ -102,6 +118,7 @@ void testAll({ await setup.service.streamListen('Debug'); stream = setup.service.onEvent('Debug'); + final soundNullSafety = nullSafety == NullSafety.sound; final testPackage = soundNullSafety ? '_test_package_sound' : '_test_package'; final test = soundNullSafety ? '_test_sound' : '_test'; @@ -568,9 +585,10 @@ void testAll({ setUpAll(() async { setCurrentLogWriter(debug: debug); await context.setUp( + compilationMode: compilationMode, + nullSafety: nullSafety, enableExpressionEvaluation: false, verboseCompiler: debug, - basePath: basePath, ); }); diff --git a/dwds/test/expression_compiler_service_test.dart b/dwds/test/expression_compiler_service_test.dart index 7e02d2014..376930513 100644 --- a/dwds/test/expression_compiler_service_test.dart +++ b/dwds/test/expression_compiler_service_test.dart @@ -4,6 +4,7 @@ // @dart = 2.9 +@TestOn('vm') import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -17,11 +18,9 @@ import 'package:test/test.dart'; import 'fixtures/logging.dart'; -Logger _logger = Logger('ExpressionCompilerServiceTest'); - -@TestOn('vm') void main() async { group('expression compiler service with fake asset server', () { + final logger = Logger('ExpressionCompilerServiceTest'); ExpressionCompilerService service; HttpServer server; StreamController output; @@ -70,7 +69,7 @@ void main() async { serveHttpRequests( server, Cascade().add(service.handler).add(assetHandler).handler, (e, s) { - _logger.warning('Error serving requests', e, s); + logger.warning('Error serving requests', e, s); }); // generate full dill diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart index 198bb46a4..1d1a26b68 100644 --- a/dwds/test/fixtures/context.dart +++ b/dwds/test/fixtures/context.dart @@ -42,6 +42,10 @@ final Matcher throwsSentinelException = throwsA(isSentinelException); enum CompilationMode { buildDaemon, frontendServer } +enum IndexBaseMode { noBase, base } + +enum NullSafety { weak, sound } + class TestContext { String appUrl; WipConnection tabConnection; @@ -59,7 +63,8 @@ class TestContext { int port; Directory _outputDir; File _entryFile; - String _packagesFilePath; + Uri _packageConfigFile; + Uri _projectDirectory; String _entryContents; /// Null safety mode for the frontend server. @@ -76,7 +81,7 @@ class TestContext { /// TODO(annagrin): Currently setting sound null safety for frontend /// server tests fails due to missing sound SDK JavaScript and maps. /// Issue: https://github.com/dart-lang/webdev/issues/1591 - bool soundNullSafety; + NullSafety nullSafety; final _logger = logging.Logger('Context'); /// Top level directory in which we run the test server.. @@ -102,14 +107,18 @@ class TestContext { .absolute(directory ?? p.relative(relativeDirectory, from: p.current))); DartUri.currentDirectory = workingDirectory; - _packagesFilePath = - p.join(workingDirectory, '.dart_tool/package_config.json'); + + // package_config.json is located in /.dart_tool/package_config + _projectDirectory = p.toUri(workingDirectory); + _packageConfigFile = + p.toUri(p.join(workingDirectory, '.dart_tool/package_config.json')); final entryFilePath = p.normalize( p.absolute(entry ?? p.relative(relativeEntry, from: p.current))); _logger.info('Serving: $pathToServe/$path'); - _logger.info('Packages: $_packagesFilePath'); + _logger.info('Project: $_projectDirectory'); + _logger.info('Packages: $_packageConfigFile'); _logger.info('Entry: $entryFilePath'); _entryFile = File(entryFilePath); @@ -129,11 +138,10 @@ class TestContext { UrlEncoder urlEncoder, bool restoreBreakpoints, CompilationMode compilationMode, - bool soundNullSafety, + NullSafety nullSafety, bool enableExpressionEvaluation, bool verboseCompiler, SdkConfigurationProvider sdkConfigurationProvider, - String basePath, }) async { reloadConfiguration ??= ReloadConfiguration.none; serveDevTools ??= false; @@ -146,8 +154,7 @@ class TestContext { spawnDds ??= true; verboseCompiler ??= false; sdkConfigurationProvider ??= DefaultSdkConfigurationProvider(); - soundNullSafety ??= false; - basePath ??= ''; + nullSafety ??= NullSafety.weak; try { configureLogWriter(); @@ -196,6 +203,7 @@ class TestContext { Handler assetHandler; Stream buildResults; RequireStrategy requireStrategy; + String basePath = ''; port = await findUnusedPort(); switch (compilationMode) { @@ -254,26 +262,32 @@ class TestContext { break; case CompilationMode.frontendServer: { - final projectDirectory = p.dirname(p.dirname(_packagesFilePath)); - final entryPath = - _entryFile.path.substring(projectDirectory.length + 1); + _logger.warning('Index: $path'); + + final entry = p.toUri(_entryFile.path + .substring(_projectDirectory.toFilePath().length + 1)); + webRunner = ResidentWebRunner( - '${Uri.file(entryPath)}', - urlEncoder, - _packagesFilePath, - [projectDirectory], - 'org-dartlang-app', - _outputDir.path, - soundNullSafety, - verboseCompiler); + entry, + urlEncoder, + _projectDirectory, + _packageConfigFile, + [_projectDirectory], + 'org-dartlang-app', + _outputDir.path, + nullSafety == NullSafety.sound, + verboseCompiler, + ); final assetServerPort = await findUnusedPort(); - await webRunner.run(hostname, assetServerPort, pathToServe); + await webRunner.run( + hostname, assetServerPort, p.join(pathToServe, path)); if (enableExpressionEvaluation) { expressionCompiler = webRunner.expressionCompiler; } + basePath = webRunner.devFS.assetServer.basePath; assetReader = webRunner.devFS.assetServer; assetHandler = webRunner.devFS.assetServer.handleRequest; @@ -333,7 +347,10 @@ class TestContext { ddcService, ); - appUrl = 'http://localhost:$port/$path'; + appUrl = basePath.isEmpty + ? 'http://localhost:$port/$path' + : 'http://localhost:$port/$basePath/$path'; + await webDriver.get(appUrl); final tab = await connection.getTab((t) => t.url == appUrl); tabConnection = await tab.connect(); diff --git a/dwds/test/frontend_server_callstack_test.dart b/dwds/test/frontend_server_callstack_test.dart index 215ffe1f4..bfd57eaf6 100644 --- a/dwds/test/frontend_server_callstack_test.dart +++ b/dwds/test/frontend_server_callstack_test.dart @@ -46,16 +46,17 @@ void main() { // Enable verbose logging for debugging. final debug = false; - for (var soundNullSafety in [false, true]) { + for (var nullSafety in NullSafety.values) { + final soundNullSafety = nullSafety == NullSafety.sound; final setup = soundNullSafety ? TestSetup.sound() : TestSetup.unsound(); final context = setup.context; - group('${soundNullSafety ? "sound" : "weak"} null safety |', () { + group('${nullSafety.name} null safety |', () { setUpAll(() async { setCurrentLogWriter(debug: debug); await context.setUp( compilationMode: CompilationMode.frontendServer, - soundNullSafety: soundNullSafety, + nullSafety: nullSafety, enableExpressionEvaluation: true, verboseCompiler: debug); }); diff --git a/dwds/test/frontend_server_evaluate_test.dart b/dwds/test/frontend_server_evaluate_test.dart index fd200512f..10fbb7fda 100644 --- a/dwds/test/frontend_server_evaluate_test.dart +++ b/dwds/test/frontend_server_evaluate_test.dart @@ -6,6 +6,8 @@ @TestOn('vm') +import 'dart:io'; + import 'package:test/test.dart'; import 'fixtures/context.dart'; @@ -15,20 +17,27 @@ void main() async { // Enable verbose logging for debugging. final debug = false; - for (var soundNullSafety in [false, true]) { - group('${soundNullSafety ? "sound" : "weak"} null safety |', () { - for (var basePath in ['', 'abc']) { - group('with base "$basePath" |', () { - testAll( - compilationMode: CompilationMode.frontendServer, - soundNullSafety: soundNullSafety, - basePath: basePath, - debug: debug, - ); - }, - skip: - soundNullSafety // https://github.com/dart-lang/webdev/issues/1591 + for (var nullSafety in NullSafety.values) { + group('${nullSafety.name} null safety |', () { + for (var indexBaseMode in IndexBaseMode.values) { + group( + 'with ${indexBaseMode.name} |', + () { + testAll( + compilationMode: CompilationMode.frontendServer, + indexBaseMode: indexBaseMode, + nullSafety: nullSafety, + debug: debug, ); + }, + skip: (nullSafety == + NullSafety + .sound) // https://github.com/dart-lang/webdev/issues/1591 + || + (indexBaseMode == IndexBaseMode.base && + Platform + .isWindows), // https://github.com/dart-lang/sdk/issues/49277 + ); } }); } diff --git a/fixtures/_testPackage/web/base_index.html b/fixtures/_testPackage/web/base_index.html new file mode 100644 index 000000000..affdbcb5c --- /dev/null +++ b/fixtures/_testPackage/web/base_index.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/fixtures/_testPackageSound/web/base_index.html b/fixtures/_testPackageSound/web/base_index.html new file mode 100644 index 000000000..affdbcb5c --- /dev/null +++ b/fixtures/_testPackageSound/web/base_index.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend_server_common/lib/src/asset_server.dart b/frontend_server_common/lib/src/asset_server.dart index 776e35e6a..d096875c8 100644 --- a/frontend_server_common/lib/src/asset_server.dart +++ b/frontend_server_common/lib/src/asset_server.dart @@ -21,21 +21,31 @@ import 'package:shelf/shelf.dart' as shelf; import 'utilities.dart'; -Logger _logger = Logger('TestAssetServer'); - class TestAssetServer implements AssetReader { - TestAssetServer( - this._root, - this._httpServer, - this._packageConfig, - this.internetAddress, - this._fileSystem, - ); + final String basePath; + final String index; + + final _logger = Logger('TestAssetServer'); // Fallback to "application/octet-stream" on null which // makes no claims as to the structure of the data. static const String _defaultMimeType = 'application/octet-stream'; final FileSystem _fileSystem; + final HttpServer _httpServer; + final Map _files = {}; + final Map _sourcemaps = {}; + final Map _metadata = {}; + String _mergedMetadata; + final PackageConfig _packageConfig; + final InternetAddress internetAddress; + + TestAssetServer( + this.index, + this._httpServer, + this._packageConfig, + this.internetAddress, + this._fileSystem, + ) : basePath = _parseBasePathFromIndexHtml(index); /// Start the web asset server on a [hostname] and [port]. /// @@ -43,7 +53,7 @@ class TestAssetServer implements AssetReader { /// trace. static Future start( FileSystem fileSystem, - String root, + String index, String hostname, int port, UrlEncoder urlTunneller, @@ -52,19 +62,10 @@ class TestAssetServer implements AssetReader { var address = (await InternetAddress.lookup(hostname)).first; var httpServer = await HttpServer.bind(address, port); var server = - TestAssetServer(root, httpServer, packageConfig, address, fileSystem); + TestAssetServer(index, httpServer, packageConfig, address, fileSystem); return server; } - final String _root; - final HttpServer _httpServer; - final Map _files = {}; - final Map _sourcemaps = {}; - final Map _metadata = {}; - String _mergedMetadata; - final PackageConfig _packageConfig; - final InternetAddress internetAddress; - Uint8List getFile(String path) => _files[path]; Uint8List getSourceMap(String path) => _sourcemaps[path]; @@ -73,11 +74,8 @@ class TestAssetServer implements AssetReader { Future handleRequest(shelf.Request request) async { var headers = {}; - // Index file is serverd from the _root directory if (request.url.path.endsWith('index.html')) { - final indexFile = _fileSystem.currentDirectory - .childDirectory(_root) - .childFile('index.html'); + var indexFile = _fileSystem.file(index); if (indexFile.existsSync()) { headers[HttpHeaders.contentTypeHeader] = 'text/html'; headers[HttpHeaders.contentLengthHeader] = @@ -88,27 +86,35 @@ class TestAssetServer implements AssetReader { } // NOTE: shelf removes leading `/` for some reason. - var requestPath = request.url.path.startsWith('/') - ? request.url.path - : '/${request.url.path}'; - - // If this is a JavaScript file, it must be in the in-memory cache. - // Attempt to look up the file by URI. - if (_files.containsKey(requestPath)) { - final List bytes = getFile(requestPath); - headers[HttpHeaders.contentLengthHeader] = bytes.length.toString(); - headers[HttpHeaders.contentTypeHeader] = 'application/javascript'; - return shelf.Response.ok(bytes, headers: headers); - } - // If this is a sourcemap file, then it might be in the in-memory cache. - // Attempt to lookup the file by URI. - if (_sourcemaps.containsKey(requestPath)) { - final List bytes = getSourceMap(requestPath); - headers[HttpHeaders.contentLengthHeader] = bytes.length.toString(); - headers[HttpHeaders.contentTypeHeader] = 'application/json'; - return shelf.Response.ok(bytes, headers: headers); - } + var requestPath = request.url.path; + requestPath = requestPath.startsWith('/') ? requestPath : '/$requestPath'; + + if (!request.url.path.endsWith('/require.js') && + !request.url.path.endsWith('/dart_stack_trace_mapper.js')) { + requestPath = _stripBasePath(requestPath, basePath) ?? requestPath; + + requestPath = requestPath.startsWith('/') ? requestPath : '/$requestPath'; + if (requestPath == null) { + return shelf.Response.notFound(''); + } + // If this is a JavaScript file, it must be in the in-memory cache. + // Attempt to look up the file by URI. + if (_files.containsKey(requestPath)) { + final List bytes = getFile(requestPath); + headers[HttpHeaders.contentLengthHeader] = bytes.length.toString(); + headers[HttpHeaders.contentTypeHeader] = 'application/javascript'; + return shelf.Response.ok(bytes, headers: headers); + } + // If this is a sourcemap file, then it might be in the in-memory cache. + // Attempt to lookup the file by URI. + if (_sourcemaps.containsKey(requestPath)) { + final List bytes = getSourceMap(requestPath); + headers[HttpHeaders.contentLengthHeader] = bytes.length.toString(); + headers[HttpHeaders.contentTypeHeader] = 'application/json'; + return shelf.Response.ok(bytes, headers: headers); + } + } var file = _resolveDartFile(requestPath); if (!file.existsSync()) { return shelf.Response.notFound(''); @@ -152,13 +158,13 @@ class TestAssetServer implements AssetReader { var sourcemapBytes = sourcemapFile.readAsBytesSync(); var metadataBytes = metadataFile.readAsBytesSync(); var manifest = - castStringKeyedMap(json.decode(manifestFile.readAsStringSync())); + _castStringKeyedMap(json.decode(manifestFile.readAsStringSync())); for (var filePath in manifest.keys) { if (filePath == null) { _logger.severe('Invalid manfiest file: $filePath'); continue; } - var offsets = castStringKeyedMap(manifest[filePath]); + var offsets = _castStringKeyedMap(manifest[filePath]); var codeOffsets = (offsets['code'] as List).cast(); var sourcemapOffsets = (offsets['sourcemap'] as List).cast(); @@ -254,24 +260,29 @@ class TestAssetServer implements AssetReader { @override Future dartSourceContents(String serverPath) { + serverPath = _stripBasePath(serverPath, basePath); var result = _resolveDartFile(serverPath); if (result.existsSync()) { return result.readAsString(); } + _logger.severe('Source not found: $serverPath'); return null; } @override Future sourceMapContents(String serverPath) async { + serverPath = _stripBasePath(serverPath, basePath); var path = '/$serverPath'; if (_sourcemaps.containsKey(path)) { return utf8.decode(_sourcemaps[path]); } + _logger.severe('Source map not found: $serverPath'); return null; } @override Future metadataContents(String serverPath) async { + serverPath = _stripBasePath(serverPath, basePath); if (serverPath.endsWith('.ddc_merged_metadata')) { return _mergedMetadata; } @@ -279,13 +290,42 @@ class TestAssetServer implements AssetReader { if (_metadata.containsKey(path)) { return utf8.decode(_metadata[path]); } + _logger.severe('Metadata not found: $serverPath'); return null; } } /// Given a data structure which is a Map of String to dynamic values, return /// the same structure (`Map`) with the correct runtime types. -Map castStringKeyedMap(dynamic untyped) { +Map _castStringKeyedMap(dynamic untyped) { var map = untyped as Map; return map?.cast(); } + +String _stripLeadingSlashes(String path) { + while (path.startsWith('/')) { + path = path.substring(1); + } + return path; +} + +String _stripBasePath(String path, String basePath) { + path = _stripLeadingSlashes(path); + if (path.startsWith(basePath)) { + path = path.substring(basePath.length); + } else { + // The given path isn't under base path, return null to indicate that. + return null; + } + return _stripLeadingSlashes(path); +} + +String _parseBasePathFromIndexHtml(String index) { + final file = fileSystem.file(index); + if (!file.existsSync()) { + throw StateError('Index file $index is not found'); + } + final contents = file.readAsStringSync(); + final matches = RegExp(r'').allMatches(contents); + return matches.isEmpty ? '' : matches.first.group(1); +} diff --git a/frontend_server_common/lib/src/devfs.dart b/frontend_server_common/lib/src/devfs.dart index 323c92fc1..c9805984e 100644 --- a/frontend_server_common/lib/src/devfs.dart +++ b/frontend_server_common/lib/src/devfs.dart @@ -26,8 +26,9 @@ class WebDevFS { this.fileSystem, this.hostname, this.port, - this.packageConfigPath, - this.root, + this.projectDirectory, + this.packageConfigFile, + this.index, this.urlTunneller, this.soundNullSafety, }); @@ -36,8 +37,9 @@ class WebDevFS { TestAssetServer assetServer; final String hostname; final int port; - final String packageConfigPath; - final String root; + final Uri projectDirectory; + final Uri packageConfigFile; + final String index; final UrlEncoder urlTunneller; final bool soundNullSafety; Directory _savedCurrentDirectory; @@ -46,16 +48,14 @@ class WebDevFS { Future create() async { _savedCurrentDirectory = fileSystem.currentDirectory; - // package_config.json is located in /.dart_tool/package_config - var projectDirectory = p.dirname(p.dirname(packageConfigPath)); - fileSystem.currentDirectory = projectDirectory; + fileSystem.currentDirectory = projectDirectory.toFilePath(); - _packageConfig = await loadPackageConfigUri(Uri.file(packageConfigPath), + _packageConfig = await loadPackageConfigUri(packageConfigFile, loader: (Uri uri) => fileSystem.file(uri).readAsBytes()); assetServer = await TestAssetServer.start( - fileSystem, root, hostname, port, urlTunneller, _packageConfig); + fileSystem, index, hostname, port, urlTunneller, _packageConfig); return Uri.parse('http://$hostname:$port'); } @@ -65,14 +65,15 @@ class WebDevFS { } Future update({ - String mainPath, + Uri mainUri, String dillOutputPath, @required ResidentCompiler generator, List invalidatedFiles, }) async { assert(generator != null); - var outputDirectoryPath = fileSystem.file(mainPath).parent.path; - var entryPoint = mainPath; + final mainPath = mainUri.toFilePath(); + final outputDirectoryPath = fileSystem.file(mainPath).parent.path; + final entryPoint = mainUri.toString(); assetServer.writeFile( '/main.dart.js', @@ -98,9 +99,11 @@ class WebDevFS { generator.reset(); var compilerOutput = await generator.recompile( - Uri.parse('org-dartlang-app:///$mainPath'), invalidatedFiles, - outputPath: p.join(dillOutputPath, 'app.dill'), - packageConfig: _packageConfig); + Uri.parse('org-dartlang-app:///$mainUri'), + invalidatedFiles, + outputPath: p.join(dillOutputPath, 'app.dill'), + packageConfig: _packageConfig, + ); if (compilerOutput == null || compilerOutput.errorCount > 0) { return UpdateFSReport(success: false); } diff --git a/frontend_server_common/lib/src/frontend_server_client.dart b/frontend_server_common/lib/src/frontend_server_client.dart index d534ea891..92dc8ee51 100644 --- a/frontend_server_common/lib/src/frontend_server_client.dart +++ b/frontend_server_common/lib/src/frontend_server_client.dart @@ -253,20 +253,20 @@ class _RejectRequest extends _CompilationRequest { /// restarts the Flutter app. class ResidentCompiler { ResidentCompiler( - String sdkRoot, { - this.packageConfigPath, + this.sdkRoot, { + this.projectDirectory, + this.packageConfigFile, this.fileSystemRoots, this.fileSystemScheme, this.platformDill, this.verbose, CompilerMessageConsumer compilerMessageConsumer = defaultConsumer, }) : assert(sdkRoot != null), - _stdoutHandler = StdoutHandler(consumer: compilerMessageConsumer), - // This is a URI, not a file path, so the forward slash is correct even on Windows. - sdkRoot = sdkRoot.endsWith('/') ? sdkRoot : '$sdkRoot/'; + _stdoutHandler = StdoutHandler(consumer: compilerMessageConsumer); - final String packageConfigPath; - final List fileSystemRoots; + final Uri projectDirectory; + final Uri packageConfigFile; + final List fileSystemRoots; final String fileSystemScheme; final String platformDill; final bool verbose; @@ -364,14 +364,14 @@ class ResidentCompiler { '-Ddart.developer.causal_async_stacks=true', '--output-dill', outputFilePath, - if (packageConfigPath != null) ...[ + if (packageConfigFile != null) ...[ '--packages', - packageConfigPath, + '$packageConfigFile', ], if (fileSystemRoots != null) - for (final String root in fileSystemRoots) ...[ + for (final root in fileSystemRoots) ...[ '--filesystem-root', - root, + '$root', ], if (fileSystemScheme != null) ...[ '--filesystem-scheme', @@ -387,9 +387,9 @@ class ResidentCompiler { ]; _logger.info(args.join(' ')); - var projectDirectory = p.dirname(p.dirname(packageConfigPath)); + final workingDirectory = projectDirectory.toFilePath(); _server = await Process.start(Platform.resolvedExecutable, args, - workingDirectory: projectDirectory); + workingDirectory: workingDirectory); _server.stdout .transform(utf8.decoder) .transform(const LineSplitter()) @@ -620,15 +620,15 @@ class TestExpressionCompiler implements ExpressionCompiler { /// Convert a file URI into a multi-root scheme URI if provided, otherwise /// return unmodified. @visibleForTesting -String toMultiRootPath( - Uri fileUri, String scheme, List fileSystemRoots) { +String toMultiRootPath(Uri fileUri, String scheme, List fileSystemRoots) { if (scheme == null || fileSystemRoots.isEmpty || fileUri.scheme != 'file') { return fileUri.toString(); } final filePath = fileUri.toFilePath(windows: Platform.isWindows); for (final fileSystemRoot in fileSystemRoots) { - if (filePath.startsWith(fileSystemRoot)) { - return '$scheme://${filePath.substring(fileSystemRoot.length)}'; + final rootPath = fileSystemRoot.toFilePath(windows: Platform.isWindows); + if (filePath.startsWith(rootPath)) { + return '$scheme://${filePath.substring(rootPath.length)}'; } } return fileUri.toString(); diff --git a/frontend_server_common/lib/src/resident_runner.dart b/frontend_server_common/lib/src/resident_runner.dart index 81c90c3cf..a49866fb9 100644 --- a/frontend_server_common/lib/src/resident_runner.dart +++ b/frontend_server_common/lib/src/resident_runner.dart @@ -22,33 +22,38 @@ final Uri platformDillUnsound = final Uri platformDillSound = Uri.file(p.join(dartSdkPath, 'lib', '_internal', 'ddc_outline_sound.dill')); -Logger _logger = Logger('ResidentWebRunner'); - class ResidentWebRunner { + final _logger = Logger('ResidentWebRunner'); + ResidentWebRunner( - this.mainPath, + this.mainUri, this.urlTunneller, - this.packageConfigPath, + this.projectDirectory, + this.packageConfigFile, this.fileSystemRoots, this.fileSystemScheme, this.outputPath, this.soundNullSafety, bool verbose) { - generator = ResidentCompiler(dartSdkPath, - packageConfigPath: packageConfigPath, - platformDill: - soundNullSafety ? '$platformDillSound' : '$platformDillUnsound', - fileSystemRoots: fileSystemRoots, - fileSystemScheme: fileSystemScheme, - verbose: verbose); + generator = ResidentCompiler( + dartSdkPath, + projectDirectory: projectDirectory, + packageConfigFile: packageConfigFile, + platformDill: + soundNullSafety ? '$platformDillSound' : '$platformDillUnsound', + fileSystemRoots: fileSystemRoots, + fileSystemScheme: fileSystemScheme, + verbose: verbose, + ); expressionCompiler = TestExpressionCompiler(generator); } final UrlEncoder urlTunneller; - final String mainPath; - final String packageConfigPath; + final Uri mainUri; + final Uri projectDirectory; + final Uri packageConfigFile; final String outputPath; - final List fileSystemRoots; + final List fileSystemRoots; final String fileSystemScheme; final bool soundNullSafety; @@ -58,15 +63,16 @@ class ResidentWebRunner { Uri uri; Iterable modules; - Future run(String hostname, int port, String root) async { + Future run(String hostname, int port, String index) async { hostname ??= 'localhost'; devFS = WebDevFS( fileSystem: fileSystem, hostname: hostname, port: port, - packageConfigPath: packageConfigPath, - root: root, + projectDirectory: projectDirectory, + packageConfigFile: packageConfigFile, + index: index, urlTunneller: urlTunneller, soundNullSafety: soundNullSafety, ); @@ -86,7 +92,7 @@ class ResidentWebRunner { Future _updateDevFS() async { var report = await devFS.update( - mainPath: mainPath, + mainUri: mainUri, dillOutputPath: outputPath, generator: generator, invalidatedFiles: []);