| // Copyright 2013 The Flutter Authors. 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:convert' show JsonEncoder; |
| import 'dart:io' as io; |
| |
| import 'package:path/path.dart' as pathlib; |
| |
| import '../environment.dart'; |
| import '../exceptions.dart'; |
| import '../felt_config.dart'; |
| import '../pipeline.dart'; |
| import '../utils.dart'; |
| |
| class CopyArtifactsStep implements PipelineStep { |
| CopyArtifactsStep(this.artifactDeps, { required this.runtimeMode }); |
| |
| final ArtifactDependencies artifactDeps; |
| final RuntimeMode runtimeMode; |
| |
| @override |
| String get description => 'copy_artifacts'; |
| |
| @override |
| bool get isSafeToInterrupt => true; |
| |
| @override |
| Future<void> interrupt() async { |
| await cleanup(); |
| } |
| |
| @override |
| Future<void> run() async { |
| await environment.webTestsArtifactsDir.create(recursive: true); |
| await buildHostPage(); |
| await copyTestFonts(); |
| await copySkiaTestImages(); |
| await copyFlutterJsFiles(); |
| if (artifactDeps.canvasKit) { |
| print('Copying CanvasKit...'); |
| await copyCanvasKitFiles('canvaskit', 'canvaskit'); |
| } |
| if (artifactDeps.canvasKitChromium) { |
| print('Copying CanvasKit (Chromium)...'); |
| await copyCanvasKitFiles('canvaskit_chromium', 'canvaskit/chromium'); |
| } |
| if (artifactDeps.skwasm) { |
| print('Copying Skwasm...'); |
| await copySkwasm(); |
| } |
| } |
| |
| Future<void> copyTestFonts() async { |
| const Map<String, String> testFonts = <String, String>{ |
| 'Ahem': 'ahem.ttf', |
| 'Roboto': 'Roboto-Regular.ttf', |
| 'RobotoVariable': 'RobotoSlab-VariableFont_wght.ttf', |
| 'Noto Naskh Arabic UI': 'NotoNaskhArabic-Regular.ttf', |
| 'Noto Color Emoji': 'NotoColorEmoji.ttf', |
| }; |
| |
| final String fontsPath = pathlib.join( |
| environment.flutterDirectory.path, |
| 'third_party', |
| 'txt', |
| 'third_party', |
| 'fonts', |
| ); |
| |
| final List<dynamic> fontManifest = <dynamic>[]; |
| for (final MapEntry<String, String> fontEntry in testFonts.entries) { |
| final String family = fontEntry.key; |
| final String fontFile = fontEntry.value; |
| |
| fontManifest.add(<String, dynamic>{ |
| 'family': family, |
| 'fonts': <dynamic>[ |
| <String, String>{ |
| 'asset': 'fonts/$fontFile', |
| }, |
| ], |
| }); |
| |
| final io.File sourceTtf = io.File(pathlib.join(fontsPath, fontFile)); |
| final io.File destinationTtf = io.File(pathlib.join( |
| environment.webTestsArtifactsDir.path, |
| 'assets', |
| 'fonts', |
| fontFile, |
| )); |
| await destinationTtf.create(recursive: true); |
| await sourceTtf.copy(destinationTtf.path); |
| } |
| |
| final io.File fontManifestFile = io.File(pathlib.join( |
| environment.webTestsArtifactsDir.path, |
| 'assets', |
| 'FontManifest.json', |
| )); |
| await fontManifestFile.create(recursive: true); |
| await fontManifestFile.writeAsString( |
| const JsonEncoder.withIndent(' ').convert(fontManifest), |
| ); |
| |
| final io.Directory fallbackFontsSource = io.Directory(pathlib.join( |
| environment.engineSrcDir.path, |
| 'flutter', |
| 'third_party', |
| 'google_fonts_for_unit_tests', |
| )); |
| final String fallbackFontsDestinationPath = pathlib.join( |
| environment.webTestsArtifactsDir.path, |
| 'assets', |
| 'fallback_fonts', |
| ); |
| for (final io.File file in |
| fallbackFontsSource.listSync(recursive: true).whereType<io.File>() |
| ) { |
| final String relativePath = pathlib.relative(file.path, from: fallbackFontsSource.path); |
| final io.File destinationFile = io.File(pathlib.join(fallbackFontsDestinationPath, relativePath)); |
| if (!destinationFile.parent.existsSync()) { |
| destinationFile.parent.createSync(recursive: true); |
| } |
| file.copySync(destinationFile.path); |
| } |
| } |
| |
| Future<void> copySkiaTestImages() async { |
| final io.Directory testImagesDir = io.Directory(pathlib.join( |
| environment.engineSrcDir.path, |
| 'flutter', |
| 'third_party', |
| 'skia', |
| 'resources', |
| 'images', |
| )); |
| |
| for (final io.File imageFile in testImagesDir.listSync(recursive: true).whereType<io.File>()) { |
| final io.File destination = io.File(pathlib.join( |
| environment.webTestsArtifactsDir.path, |
| 'test_images', |
| pathlib.relative(imageFile.path, from: testImagesDir.path), |
| )); |
| destination.createSync(recursive: true); |
| await imageFile.copy(destination.path); |
| } |
| } |
| |
| Future<void> copyFlutterJsFiles() async { |
| final io.Directory flutterJsInputDirectory = io.Directory(pathlib.join( |
| outBuildPath, |
| 'flutter_web_sdk', |
| 'flutter_js', |
| )); |
| final String targetDirectoryPath = pathlib.join( |
| environment.webTestsArtifactsDir.path, |
| 'flutter_js', |
| ); |
| |
| for (final io.File sourceFile in flutterJsInputDirectory |
| .listSync(recursive: true) |
| .whereType<io.File>() |
| ) { |
| final String relativePath = pathlib.relative( |
| sourceFile.path, |
| from: flutterJsInputDirectory.path |
| ); |
| final String targetPath = pathlib.join( |
| targetDirectoryPath, |
| relativePath, |
| ); |
| final io.File targetFile = io.File(targetPath); |
| if (!targetFile.parent.existsSync()) { |
| targetFile.parent.createSync(recursive: true); |
| } |
| sourceFile.copySync(targetPath); |
| } |
| } |
| |
| Future<void> copyCanvasKitFiles(String sourcePath, String destinationPath) async { |
| final String sourceDirectoryPath = pathlib.join( |
| outBuildPath, |
| sourcePath, |
| ); |
| |
| final String targetDirectoryPath = pathlib.join( |
| environment.webTestsArtifactsDir.path, |
| destinationPath, |
| ); |
| |
| for (final String filename in <String>[ |
| 'canvaskit.js', |
| 'canvaskit.wasm', |
| 'canvaskit.wasm.map', |
| ]) { |
| final io.File sourceFile = io.File(pathlib.join( |
| sourceDirectoryPath, |
| filename, |
| )); |
| final io.File targetFile = io.File(pathlib.join( |
| targetDirectoryPath, |
| filename, |
| )); |
| if (!sourceFile.existsSync()) { |
| if (filename.endsWith('.map')) { |
| // Sourcemaps are only generated under certain build conditions, so |
| // they are optional. |
| continue; |
| } { |
| throw ToolExit('Built CanvasKit artifact not found at path "$sourceFile".'); |
| } |
| } |
| await targetFile.create(recursive: true); |
| await sourceFile.copy(targetFile.path); |
| } |
| } |
| |
| String get outBuildPath => getBuildDirectoryForRuntimeMode(runtimeMode).path; |
| |
| Future<void> copySkwasm() async { |
| final io.Directory targetDir = io.Directory(pathlib.join( |
| environment.webTestsArtifactsDir.path, |
| 'canvaskit', |
| )); |
| |
| await targetDir.create(recursive: true); |
| |
| for (final String fileName in <String>[ |
| 'skwasm.wasm', |
| 'skwasm.wasm.map', |
| 'skwasm.js', |
| 'skwasm.worker.js', |
| ]) { |
| final io.File sourceFile = io.File(pathlib.join( |
| outBuildPath, |
| 'flutter_web_sdk', |
| 'canvaskit', |
| fileName, |
| )); |
| if (!sourceFile.existsSync()) { |
| if (fileName.endsWith('.map')) { |
| // Sourcemaps are only generated under certain build conditions, so |
| // they are optional. |
| continue; |
| } { |
| throw ToolExit('Built Skwasm artifact not found at path "$sourceFile".'); |
| } |
| } |
| final io.File targetFile = io.File(pathlib.join( |
| targetDir.path, |
| fileName, |
| )); |
| await sourceFile.copy(targetFile.path); |
| } |
| } |
| |
| Future<void> buildHostPage() async { |
| final String hostDartPath = pathlib.join('lib', 'static', 'host.dart'); |
| final io.File hostDartFile = io.File(pathlib.join( |
| environment.webEngineTesterRootDir.path, |
| hostDartPath, |
| )); |
| final String targetDirectoryPath = pathlib.join( |
| environment.webTestsArtifactsDir.path, |
| 'host', |
| ); |
| io.Directory(targetDirectoryPath).createSync(recursive: true); |
| final String targetFilePath = pathlib.join( |
| targetDirectoryPath, |
| 'host.dart', |
| ); |
| |
| const List<String> staticFiles = <String>[ |
| 'favicon.ico', |
| 'host.css', |
| 'index.html', |
| ]; |
| for (final String staticFilePath in staticFiles) { |
| final io.File source = io.File(pathlib.join( |
| environment.webEngineTesterRootDir.path, |
| 'lib', |
| 'static', |
| staticFilePath, |
| )); |
| final io.File destination = io.File(pathlib.join( |
| targetDirectoryPath, |
| staticFilePath, |
| )); |
| await source.copy(destination.path); |
| } |
| |
| final io.File timestampFile = io.File(pathlib.join( |
| environment.webEngineTesterRootDir.path, |
| '$targetFilePath.js.timestamp', |
| )); |
| |
| final String timestamp = |
| hostDartFile.statSync().modified.millisecondsSinceEpoch.toString(); |
| if (timestampFile.existsSync()) { |
| final String lastBuildTimestamp = timestampFile.readAsStringSync(); |
| if (lastBuildTimestamp == timestamp) { |
| // The file is still fresh. No need to rebuild. |
| return; |
| } else { |
| // Record new timestamp, but don't return. We need to rebuild. |
| print('${hostDartFile.path} timestamp changed. Rebuilding.'); |
| } |
| } else { |
| print('Building ${hostDartFile.path}.'); |
| } |
| |
| int exitCode = await runProcess( |
| environment.dartExecutable, |
| <String>[ |
| 'pub', |
| 'get', |
| ], |
| workingDirectory: environment.webEngineTesterRootDir.path |
| ); |
| |
| if (exitCode != 0) { |
| throw ToolExit( |
| 'Failed to run pub get for web_engine_tester, exit code $exitCode', |
| exitCode: exitCode, |
| ); |
| } |
| |
| exitCode = await runProcess( |
| environment.dartExecutable, |
| <String>[ |
| 'compile', |
| 'js', |
| hostDartPath, |
| '-o', |
| '$targetFilePath.js', |
| ], |
| workingDirectory: environment.webEngineTesterRootDir.path, |
| ); |
| |
| if (exitCode != 0) { |
| throw ToolExit( |
| 'Failed to compile ${hostDartFile.path}. Compiler ' |
| 'exited with exit code $exitCode', |
| exitCode: exitCode, |
| ); |
| } |
| |
| // Record the timestamp to avoid rebuilding unless the file changes. |
| timestampFile.writeAsStringSync(timestamp); |
| } |
| } |