blob: 90ce98040c8aeffcc693c4055b4d3ebf4408b8d0 [file] [log] [blame]
// 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 copyDart2WasmTestScript();
await buildHostPage();
await copyTestFonts();
await copySkiaTestImages();
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> copyDart2WasmTestScript() async {
final io.File sourceFile = io.File(pathlib.join(
environment.webUiDevDir.path,
'test_dart2wasm.js',
));
final io.File targetFile = io.File(pathlib.join(
environment.webTestsArtifactsDir.path,
'test_dart2wasm.js',
));
await sourceFile.copy(targetFile.path);
}
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,
'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,
'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> 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);
}
}