diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index 8fb8615302..8affae2609 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -39,9 +39,22 @@ enum Artifact { kernelWorkerSnapshot, /// The root of the web implementation of the dart SDK. flutterWebSdk, + /// The libraries JSON file for web release builds. flutterWebLibrariesJson, /// The summary dill for the dartdevc target. webPlatformKernelDill, + /// The summary dill with null safety enabled for the dartdevc target. + webPlatformSoundKernelDill, + /// The precompiled SDKs and sourcemaps for web debug builds. + webPrecompiledSdk, + webPrecompiledSdkSourcemaps, + webPrecompiledCanvaskitSdk, + webPrecompiledCanvaskitSdkSourcemaps, + webPrecompiledSoundSdk, + webPrecompiledSoundSdkSourcemaps, + webPrecompiledCanvaskitSoundSdk, + webPrecompiledCanvaskitSoundSdkSourcemaps, + iosDeploy, idevicesyslog, idevicescreenshot, @@ -129,6 +142,8 @@ String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMo return 'FlutterMacOS.podspec'; case Artifact.webPlatformKernelDill: return 'flutter_ddc_sdk.dill'; + case Artifact.webPlatformSoundKernelDill: + return 'flutter_ddc_sdk_sound.dill'; case Artifact.fuchsiaKernelCompiler: return 'kernel_compiler.snapshot'; case Artifact.fuchsiaFlutterRunner: @@ -141,6 +156,16 @@ String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMo return 'const_finder.dart.snapshot'; case Artifact.flutterWebLibrariesJson: return 'libraries.json'; + case Artifact.webPrecompiledSdk: + case Artifact.webPrecompiledCanvaskitSdk: + case Artifact.webPrecompiledSoundSdk: + case Artifact.webPrecompiledCanvaskitSoundSdk: + return 'dart_sdk.js'; + case Artifact.webPrecompiledSdkSourcemaps: + case Artifact.webPrecompiledCanvaskitSdkSourcemaps: + case Artifact.webPrecompiledSoundSdkSourcemaps: + case Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps: + return 'dart_sdk.js.map'; } assert(false, 'Invalid artifact $artifact.'); return null; @@ -348,6 +373,8 @@ class CachedArtifacts implements Artifacts { return _fileSystem.path.join(_getFlutterWebSdkPath(), _artifactToFileName(artifact)); case Artifact.webPlatformKernelDill: return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _artifactToFileName(artifact)); + case Artifact.webPlatformSoundKernelDill: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _artifactToFileName(artifact)); case Artifact.dart2jsSnapshot: return _fileSystem.path.join(_dartSdkPath(_fileSystem), 'bin', 'snapshots', _artifactToFileName(artifact)); case Artifact.dartdevcSnapshot: @@ -380,6 +407,22 @@ class CachedArtifacts implements Artifacts { .childDirectory(getNameForTargetPlatform(platform)) .childFile(_artifactToFileName(artifact, platform, mode)) .path; + case Artifact.webPrecompiledSdk: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _artifactToFileName(artifact, platform, mode)); + case Artifact.webPrecompiledSdkSourcemaps: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _artifactToFileName(artifact, platform, mode)); + case Artifact.webPrecompiledCanvaskitSdk: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _artifactToFileName(artifact, platform, mode)); + case Artifact.webPrecompiledCanvaskitSdkSourcemaps: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _artifactToFileName(artifact, platform, mode)); + case Artifact.webPrecompiledSoundSdk: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _artifactToFileName(artifact, platform, mode)); + case Artifact.webPrecompiledSoundSdkSourcemaps: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _artifactToFileName(artifact, platform, mode)); + case Artifact.webPrecompiledCanvaskitSoundSdk: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _artifactToFileName(artifact, platform, mode)); + case Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _artifactToFileName(artifact, platform, mode)); default: assert(false, 'Artifact $artifact not available for platform $platform.'); return null; @@ -542,6 +585,8 @@ class LocalEngineArtifacts implements Artifacts { return _fileSystem.path.join(_hostEngineOutPath, _artifactToFileName(artifact)); case Artifact.webPlatformKernelDill: return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _artifactToFileName(artifact)); + case Artifact.webPlatformSoundKernelDill: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _artifactToFileName(artifact)); case Artifact.fuchsiaKernelCompiler: final String hostPlatform = getNameForHostPlatform(getCurrentHostPlatform()); final String modeName = mode.isRelease ? 'release' : mode.toString(); @@ -557,6 +602,22 @@ class LocalEngineArtifacts implements Artifacts { return _fileSystem.path.join(_hostEngineOutPath, 'gen', artifactFileName); case Artifact.flutterWebLibrariesJson: return _fileSystem.path.join(_getFlutterWebSdkPath(), artifactFileName); + case Artifact.webPrecompiledSdk: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', artifactFileName); + case Artifact.webPrecompiledSdkSourcemaps: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', artifactFileName); + case Artifact.webPrecompiledCanvaskitSdk: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', artifactFileName); + case Artifact.webPrecompiledCanvaskitSdkSourcemaps: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', artifactFileName); + case Artifact.webPrecompiledSoundSdk: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', artifactFileName); + case Artifact.webPrecompiledSoundSdkSourcemaps: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', artifactFileName); + case Artifact.webPrecompiledCanvaskitSoundSdk: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', artifactFileName); + case Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps: + return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', artifactFileName); } assert(false, 'Invalid artifact $artifact.'); return null; diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 563863509d..29381e98bd 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -30,10 +30,16 @@ class BuildInfo { @required this.treeShakeIcons, this.performanceMeasurementFile, this.packagesPath = '.packages', + this.nullSafetyMode = NullSafetyMode.autodetect, }); final BuildMode mode; + /// The null safety mode the application should be run in. + /// + /// If not provided, defaults to [NullSafetyMode.autodetect]. + final NullSafetyMode nullSafetyMode; + /// Whether the build should subdset icon fonts. final bool treeShakeIcons; @@ -688,3 +694,10 @@ List decodeDartDefines(Map environmentDefines, String ke .cast() .toList(); } + +/// The null safety runtime mode the app should be built in. +enum NullSafetyMode { + sound, + unsound, + autodetect, +} diff --git a/packages/flutter_tools/lib/src/build_runner/devfs_web.dart b/packages/flutter_tools/lib/src/build_runner/devfs_web.dart index e27d9c1aab..abc8cc72f8 100644 --- a/packages/flutter_tools/lib/src/build_runner/devfs_web.dart +++ b/packages/flutter_tools/lib/src/build_runner/devfs_web.dart @@ -103,6 +103,7 @@ class WebAssetServer implements AssetReader { this.internetAddress, this._modules, this._digests, + this._buildInfo, ); // Fallback to "application/octet-stream" on null which @@ -167,6 +168,7 @@ class WebAssetServer implements AssetReader { address, modules, digests, + buildInfo, ); if (testMode) { return server; @@ -263,6 +265,7 @@ class WebAssetServer implements AssetReader { return null; } + final BuildInfo _buildInfo; final HttpServer _httpServer; // If holding these in memory is too much overhead, this can be switched to a // RandomAccessFile and read on demand. @@ -458,50 +461,31 @@ class WebAssetServer implements AssetReader { /// Whether to use the cavaskit SDK for rendering. bool canvasKitRendering = false; - @visibleForTesting - final File dartSdk = globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), - 'kernel', - 'amd', - 'dart_sdk.js', - )); - - @visibleForTesting - final File canvasKitDartSdk = globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), - 'kernel', - 'amd-canvaskit', - 'dart_sdk.js', - )); - - @visibleForTesting - final File dartSdkSourcemap = globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), - 'kernel', - 'amd', - 'dart_sdk.js.map', - )); - - @visibleForTesting - final File canvasKitDartSdkSourcemap = globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), - 'kernel', - 'amd-canvaskit', - 'dart_sdk.js.map', - )); - // Attempt to resolve `path` to a dart file. File _resolveDartFile(String path) { // Return the actual file objects so that local engine changes are automatically picked up. switch (path) { case 'dart_sdk.js': - return canvasKitRendering - ? canvasKitDartSdk - : dartSdk; + if (_buildInfo.nullSafetyMode == NullSafetyMode.unsound) { + return globals.fs.file(canvasKitRendering + ? globals.artifacts.getArtifactPath(Artifact.webPrecompiledCanvaskitSdk) + : globals.artifacts.getArtifactPath(Artifact.webPrecompiledSdk)); + } else { + return globals.fs.file(canvasKitRendering + ? globals.artifacts.getArtifactPath(Artifact.webPrecompiledCanvaskitSoundSdk) + : globals.artifacts.getArtifactPath(Artifact.webPrecompiledSoundSdk)); + } + break; case 'dart_sdk.js.map': - return canvasKitRendering - ? canvasKitDartSdkSourcemap - : dartSdkSourcemap; + if (_buildInfo.nullSafetyMode == NullSafetyMode.unsound) { + return globals.fs.file(canvasKitRendering + ? globals.artifacts.getArtifactPath(Artifact.webPrecompiledCanvaskitSdkSourcemaps) + : globals.artifacts.getArtifactPath(Artifact.webPrecompiledSdkSourcemaps)); + } else { + return globals.fs.file(canvasKitRendering + ? globals.artifacts.getArtifactPath(Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps) + : globals.artifacts.getArtifactPath(Artifact.webPrecompiledSoundSdkSourcemaps)); + } } // This is the special generated entrypoint. if (path == 'web_entrypoint.dart') { diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 950ff03657..9e0f7d5b28 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -92,6 +92,20 @@ class FlutterDevice { // a warning message and dump some debug information which can be // used to file a bug, but the compiler will still start up correctly. if (targetPlatform == TargetPlatform.web_javascript) { + Artifact platformDillArtifact; + List extraFrontEndOptions; + if (buildInfo.nullSafetyMode == NullSafetyMode.unsound) { + platformDillArtifact = Artifact.webPlatformKernelDill; + extraFrontEndOptions = buildInfo.extraFrontEndOptions; + } else { + platformDillArtifact = Artifact.webPlatformSoundKernelDill; + extraFrontEndOptions = [ + ...?buildInfo?.extraFrontEndOptions, + if (!(buildInfo?.extraFrontEndOptions?.contains('--sound-null-safety') ?? false)) + '--sound-null-safety' + ]; + } + generator = ResidentCompiler( globals.artifacts.getArtifactPath(Artifact.flutterWebSdk, mode: buildInfo.mode), buildMode: buildInfo.mode, @@ -105,9 +119,9 @@ class FlutterDevice { dartDefines: buildInfo.dartDefines, ), targetModel: TargetModel.dartdevc, - extraFrontEndOptions: buildInfo.extraFrontEndOptions, + extraFrontEndOptions: extraFrontEndOptions, platformDill: globals.fs.file(globals.artifacts - .getArtifactPath(Artifact.webPlatformKernelDill, mode: buildInfo.mode)) + .getArtifactPath(platformDillArtifact, mode: buildInfo.mode)) .absolute.uri.toString(), dartDefines: buildInfo.dartDefines, librariesSpec: globals.fs.file(globals.artifacts diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 8c6aeec53a..7cd47bb14c 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -479,8 +479,9 @@ abstract class FlutterCommand extends Command { help: 'Whether to override the inferred null safety mode. This allows null-safe ' 'libraries to depend on un-migrated (non-null safe) libraries. By default, ' - 'Flutter applications will attempt to run at the null safety level of their ' - 'entrypoint library (usually lib/main.dart).', + 'Flutter mobile & desktop applications will attempt to run at the null safety ' + 'level of their entrypoint library (usually lib/main.dart). Flutter web ' + 'applications will default to sound null-safety, unless specifically configured.', defaultsTo: null, hide: hide, ); @@ -617,15 +618,20 @@ abstract class FlutterCommand extends Command { } } + NullSafetyMode nullSafetyMode = NullSafetyMode.unsound; if (argParser.options.containsKey(FlutterOptions.kNullSafety)) { final bool nullSafety = boolArg(FlutterOptions.kNullSafety); // Explicitly check for `true` and `false` so that `null` results in not // passing a flag. This will use the automatically detected null-safety // value based on the entrypoint if (nullSafety == true) { + nullSafetyMode = NullSafetyMode.sound; extraFrontEndOptions.add('--sound-null-safety'); } else if (nullSafety == false) { + nullSafetyMode = NullSafetyMode.unsound; extraFrontEndOptions.add('--no-sound-null-safety'); + } else if (extraFrontEndOptions.contains('--enable-experiment=non-nullable')) { + nullSafetyMode = NullSafetyMode.autodetect; } } @@ -685,7 +691,8 @@ abstract class FlutterCommand extends Command { bundleSkSLPath: bundleSkSLPath, dartExperiments: experiments, performanceMeasurementFile: performanceMeasurementFile, - packagesPath: globalResults['packages'] as String ?? '.packages' + packagesPath: globalResults['packages'] as String ?? '.packages', + nullSafetyMode: nullSafetyMode, ); } diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index ddea118d62..e68146a3f0 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -53,7 +53,7 @@ Future buildWeb( kCspMode: csp.toString(), kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(), if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false) - kExtraFrontEndOptions: buildInfo.extraFrontEndOptions.join(',') + kExtraFrontEndOptions: encodeDartDefines(buildInfo.extraFrontEndOptions), }, artifacts: globals.artifacts, fileSystem: globals.fs, diff --git a/packages/flutter_tools/test/general.shard/artifacts_test.dart b/packages/flutter_tools/test/general.shard/artifacts_test.dart index 03bd21b47b..afe090bdc1 100644 --- a/packages/flutter_tools/test/general.shard/artifacts_test.dart +++ b/packages/flutter_tools/test/general.shard/artifacts_test.dart @@ -52,6 +52,41 @@ void main() { ); }); + testWithoutContext('precompiled web artifact paths are correct', () { + expect( + artifacts.getArtifactPath(Artifact.webPrecompiledSdk), + 'root/bin/cache/flutter_web_sdk/kernel/amd/dart_sdk.js', + ); + expect( + artifacts.getArtifactPath(Artifact.webPrecompiledSdkSourcemaps), + 'root/bin/cache/flutter_web_sdk/kernel/amd/dart_sdk.js.map', + ); + expect( + artifacts.getArtifactPath(Artifact.webPrecompiledCanvaskitSdk), + 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit/dart_sdk.js', + ); + expect( + artifacts.getArtifactPath(Artifact.webPrecompiledCanvaskitSdkSourcemaps), + 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit/dart_sdk.js.map', + ); + expect( + artifacts.getArtifactPath(Artifact.webPrecompiledSoundSdk), + 'root/bin/cache/flutter_web_sdk/kernel/amd-sound/dart_sdk.js', + ); + expect( + artifacts.getArtifactPath(Artifact.webPrecompiledSoundSdkSourcemaps), + 'root/bin/cache/flutter_web_sdk/kernel/amd-sound/dart_sdk.js.map', + ); + expect( + artifacts.getArtifactPath(Artifact.webPrecompiledCanvaskitSoundSdk), + 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit-sound/dart_sdk.js', + ); + expect( + artifacts.getArtifactPath(Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps), + 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit-sound/dart_sdk.js.map', + ); + }); + testWithoutContext('getEngineType', () { expect( artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug), diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index d99879672d..765c47e9b6 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -1540,7 +1540,7 @@ void main() { expect(fakeVmServiceHost.hasRemainingExpectations, false); })); - testUsingContext('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async { + testUsingContext('FlutterDevice uses dartdevc configuration when targeting web', () async { fakeVmServiceHost = FakeVmServiceHost(requests: []); final MockDevice mockDevice = MockDevice(); when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { @@ -1549,7 +1549,12 @@ void main() { final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create( mockDevice, - buildInfo: BuildInfo.debug, + buildInfo: const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + nullSafetyMode: NullSafetyMode.unsound, + ), flutterProject: FlutterProject.current(), target: null, )).generator as DefaultResidentCompiler; @@ -1562,12 +1567,46 @@ void main() { expect(residentCompiler.targetModel, TargetModel.dartdevc); expect(residentCompiler.sdkRoot, globals.artifacts.getArtifactPath(Artifact.flutterWebSdk, mode: BuildMode.debug) + '/'); - expect( - residentCompiler.platformDill, - globals.fs.file(globals.artifacts.getArtifactPath(Artifact.webPlatformKernelDill, mode: BuildMode.debug)) - .absolute.uri.toString(), - ); - })); + expect(residentCompiler.platformDill, 'file:///Artifact.webPlatformKernelDill.debug'); + }, overrides: { + Artifacts: () => Artifacts.test(), + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('FlutterDevice uses dartdevc configuration when targeting web with null-safety autodetected', () async { + fakeVmServiceHost = FakeVmServiceHost(requests: []); + final MockDevice mockDevice = MockDevice(); + when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { + return TargetPlatform.web_javascript; + }); + + final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create( + mockDevice, + buildInfo: const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + extraFrontEndOptions: ['--enable-experiment=non-nullable'], + ), + flutterProject: FlutterProject.current(), + target: null, + )).generator as DefaultResidentCompiler; + + expect(residentCompiler.initializeFromDill, + globals.fs.path.join(getBuildDirectory(), 'cache.dill')); + expect(residentCompiler.librariesSpec, + globals.fs.file(globals.artifacts.getArtifactPath(Artifact.flutterWebLibrariesJson)) + .uri.toString()); + expect(residentCompiler.targetModel, TargetModel.dartdevc); + expect(residentCompiler.sdkRoot, + globals.artifacts.getArtifactPath(Artifact.flutterWebSdk, mode: BuildMode.debug) + '/'); + expect(residentCompiler.platformDill, 'file:///Artifact.webPlatformSoundKernelDill.debug'); + }, overrides: { + Artifacts: () => Artifacts.test(), + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }); testUsingContext('connect sets up log reader', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: []); diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index 2a47f5e4c2..c09f2a235a 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -764,7 +764,7 @@ void main() { testUsingContext('web resident runner can toggle CanvasKit', () async { final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice); fakeVmServiceHost = FakeVmServiceHost(requests: []); - final WebAssetServer webAssetServer = WebAssetServer(null, null, null, null, null); + final WebAssetServer webAssetServer = WebAssetServer(null, null, null, null, null, null); when(mockWebDevFS.webAssetServer).thenReturn(webAssetServer); expect(residentWebRunner.supportsCanvasKit, true); diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index c69200b4cd..3937b7dfe4 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:dwds/dwds.dart'; +import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/platform.dart'; @@ -53,6 +54,8 @@ void main() { InternetAddress.loopbackIPv4, null, null, + null, + ); }); }); @@ -416,7 +419,12 @@ void main() { packagesFilePath: '.packages', urlTunneller: null, useSseForDebugProxy: true, - buildInfo: BuildInfo.debug, + buildInfo: const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + nullSafetyMode: NullSafetyMode.unsound, + ), enableDwds: false, entrypoint: Uri.base, testMode: true, @@ -428,24 +436,31 @@ void main() { final Uri uri = await webDevFS.create(); webDevFS.webAssetServer.entrypointCacheDirectory = globals.fs.currentDirectory; + final String webPrecompiledSdk = globals.artifacts + .getArtifactPath(Artifact.webPrecompiledSdk); + final String webPrecompiledSdkSourcemaps = globals.artifacts + .getArtifactPath(Artifact.webPrecompiledSdkSourcemaps); + final String webPrecompiledCanvaskitSdk = globals.artifacts + .getArtifactPath(Artifact.webPrecompiledCanvaskitSdk); + final String webPrecompiledCanvaskitSdkSourcemaps = globals.artifacts + .getArtifactPath(Artifact.webPrecompiledCanvaskitSdkSourcemaps); globals.fs.currentDirectory .childDirectory('lib') .childFile('web_entrypoint.dart') ..createSync(recursive: true) ..writeAsStringSync('GENERATED'); - webDevFS.webAssetServer.dartSdk + globals.fs.file(webPrecompiledSdk) ..createSync(recursive: true) ..writeAsStringSync('HELLO'); - webDevFS.webAssetServer.dartSdkSourcemap + globals.fs.file(webPrecompiledSdkSourcemaps) ..createSync(recursive: true) ..writeAsStringSync('THERE'); - webDevFS.webAssetServer.canvasKitDartSdk + globals.fs.file(webPrecompiledCanvaskitSdk) ..createSync(recursive: true) ..writeAsStringSync('OL'); - webDevFS.webAssetServer.canvasKitDartSdkSourcemap + globals.fs.file(webPrecompiledCanvaskitSdkSourcemaps) ..createSync(recursive: true) ..writeAsStringSync('CHUM'); - webDevFS.webAssetServer.dartSdkSourcemap.createSync(recursive: true); await webDevFS.update( mainUri: globals.fs.file(globals.fs.path.join('lib', 'main.dart')).uri, @@ -465,7 +480,7 @@ void main() { expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'THERE'); // Update to the SDK. - webDevFS.webAssetServer.dartSdk.writeAsStringSync('BELLOW'); + globals.fs.file(webPrecompiledSdk).writeAsStringSync('BELLOW'); // New SDK should be visible.. expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW'); @@ -483,6 +498,116 @@ void main() { expect(uri, Uri.http('localhost:0', '')); await webDevFS.destroy(); + }, overrides: { + Artifacts: () => Artifacts.test(), + })); + + test('Can start web server with specified assets in sound null safety mode', () => testbed.run(() async { + globals.fs.file('.packages').writeAsStringSync('\n'); + final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + outputFile.parent.childFile('.packages').writeAsStringSync('\n'); + + final ResidentCompiler residentCompiler = MockResidentCompiler(); + when(residentCompiler.recompile( + any, + any, + outputPath: anyNamed('outputPath'), + packageConfig: anyNamed('packageConfig'), + )).thenAnswer((Invocation invocation) async { + return const CompilerOutput('a', 0, []); + }); + + final WebDevFS webDevFS = WebDevFS( + hostname: 'localhost', + port: 0, + packagesFilePath: '.packages', + urlTunneller: null, + useSseForDebugProxy: true, + buildInfo: const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + nullSafetyMode: NullSafetyMode.autodetect, + ), + enableDwds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: null, + chromiumLauncher: null, + ); + webDevFS.requireJS.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + + final Uri uri = await webDevFS.create(); + webDevFS.webAssetServer.entrypointCacheDirectory = globals.fs.currentDirectory; + globals.fs.currentDirectory + .childDirectory('lib') + .childFile('web_entrypoint.dart') + ..createSync(recursive: true) + ..writeAsStringSync('GENERATED'); + final String webPrecompiledSoundSdk = globals.artifacts + .getArtifactPath(Artifact.webPrecompiledSoundSdk); + final String webPrecompiledSoundSdkSourcemaps = globals.artifacts + .getArtifactPath(Artifact.webPrecompiledSoundSdkSourcemaps); + final String webPrecompiledCanvaskitSoundSdk = globals.artifacts + .getArtifactPath(Artifact.webPrecompiledCanvaskitSoundSdk); + final String webPrecompiledCanvaskitSoundSdkSourcemaps = globals.artifacts + .getArtifactPath(Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps); + globals.fs.file(webPrecompiledSoundSdk) + ..createSync(recursive: true) + ..writeAsStringSync('HELLO'); + globals.fs.file(webPrecompiledSoundSdkSourcemaps) + ..createSync(recursive: true) + ..writeAsStringSync('THERE'); + globals.fs.file(webPrecompiledCanvaskitSoundSdk) + ..createSync(recursive: true) + ..writeAsStringSync('OL'); + globals.fs.file(webPrecompiledCanvaskitSoundSdkSourcemaps) + ..createSync(recursive: true) + ..writeAsStringSync('CHUM'); + + await webDevFS.update( + mainUri: globals.fs.file(globals.fs.path.join('lib', 'main.dart')).uri, + generator: residentCompiler, + trackWidgetCreation: true, + bundleFirstUpload: true, + invalidatedFiles: [], + packageConfig: PackageConfig.empty, + ); + + expect(webDevFS.webAssetServer.getFile('require.js'), isNotNull); + expect(webDevFS.webAssetServer.getFile('stack_trace_mapper.js'), isNotNull); + expect(webDevFS.webAssetServer.getFile('main.dart'), isNotNull); + expect(webDevFS.webAssetServer.getFile('manifest.json'), isNotNull); + expect(webDevFS.webAssetServer.getFile('flutter_service_worker.js'), isNotNull); + expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'HELLO'); + expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'THERE'); + + // Update to the SDK. + globals.fs.file(webPrecompiledSoundSdk).writeAsStringSync('BELLOW'); + + // New SDK should be visible.. + expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW'); + + // Toggle CanvasKit + webDevFS.webAssetServer.canvasKitRendering = true; + expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'OL'); + expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'CHUM'); + + // Generated entrypoint. + expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'), + contains('GENERATED')); + + // served on localhost + expect(uri, Uri.http('localhost:0', '')); + + await webDevFS.destroy(); + }, overrides: { + Artifacts: () => Artifacts.test(), })); test('Can start web server with hostname any', () => testbed.run(() async {