Split build and test builders for web engine (#160550)

This splits the web engine into a build phase and a test phase. This
requires a few changes:
* Modify the `generate_builder_json.dart` command to produce two
separate json builder files, `linux_web_engine_build.json` and
`linux_web_engine_test.json`. The former is left as the existing builder
with `release_build: true` (which will run in the merge queue) and the
latter is added as a new builder that runs in the second CI phase.
* Change the `copy_artifacts_step.dart` to pull built artifacts from the
GCS bucket when on LUCI, instead of relying on the CAS bucket. This is
necessary to do the tests separately from the build.
* Move the `felt test --copy-artifacts` command from the build steps to
the test steps, since the test steps themselves need to pull from GCS
instead of relying on the artifacts being populated by CAS.
This commit is contained in:
Jackson Gardner 2024-12-19 14:58:29 -08:00 committed by GitHub
parent 17c1b91037
commit d363fdc31b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 464 additions and 181 deletions

View File

@ -385,7 +385,24 @@ targets:
timeout: 120
properties:
release_build: "true"
config_name: linux_web_engine
config_name: linux_web_engine_build
# Do not remove(https://github.com/flutter/flutter/issues/144644)
# Scheduler will fail to get the platform
drone_dimensions:
- os=Linux
dimensions:
# This is needed so that orchestrators that only spawn subbuilds are not
# assigned to the large 32 core workers when doing release builds.
# For more details see the issue
# at https://github.com/flutter/flutter/issues/152186.
cores: "8"
- name: Linux linux_web_engine_tests
bringup: true
recipe: engine_v2/engine_v2
timeout: 120
properties:
config_name: linux_web_engine_test
# Do not remove(https://github.com/flutter/flutter/issues/144644)
# Scheduler will fail to get the platform
drone_dimensions:

View File

@ -0,0 +1,44 @@
{
"_comment": "THIS IS A GENERATED FILE. Do not edit this file directly.",
"_comment2": "See `generate_builder_json.dart` for the generator code",
"builds": [
{
"name": "ci/wasm_release",
"drone_dimensions": [
"device_type=none",
"os=Linux",
"cores=32"
],
"gclient_variables": {
"download_android_deps": false,
"download_jdk": false,
"download_emsdk": true
},
"gn": [
"--web",
"--runtime-mode=release",
"--no-rbe",
"--no-goma"
],
"ninja": {
"config": "wasm_release",
"targets": [
"flutter/web_sdk:flutter_web_sdk_archive"
]
},
"archives": [
{
"name": "wasm_release",
"base_path": "out/wasm_release/zip_archives/",
"type": "gcs",
"include_paths": [
"out/wasm_release/zip_archives/flutter-web-sdk.zip"
],
"realm": "production"
}
],
"cas_archive": false
}
],
"tests": []
}

View File

@ -2,73 +2,6 @@
"_comment": "THIS IS A GENERATED FILE. Do not edit this file directly.",
"_comment2": "See `generate_builder_json.dart` for the generator code",
"builds": [
{
"name": "web_tests/artifacts",
"drone_dimensions": [
"device_type=none",
"os=Linux",
"cores=32"
],
"gclient_variables": {
"download_android_deps": false,
"download_jdk": false,
"download_emsdk": true
},
"gn": [
"--web",
"--runtime-mode=release",
"--no-goma"
],
"ninja": {
"config": "wasm_release",
"targets": [
"flutter/web_sdk:flutter_web_sdk_archive"
]
},
"archives": [
{
"name": "wasm_release",
"base_path": "out/wasm_release/zip_archives/",
"type": "gcs",
"include_paths": [
"out/wasm_release/zip_archives/flutter-web-sdk.zip"
],
"realm": "production"
}
],
"generators": {
"tasks": [
{
"name": "check licenses",
"parameters": [
"check-licenses"
],
"scripts": [
"flutter/lib/web_ui/dev/felt"
]
},
{
"name": "web engine analysis",
"parameters": [
"analyze"
],
"scripts": [
"flutter/lib/web_ui/dev/felt"
]
},
{
"name": "copy artifacts for web tests",
"parameters": [
"test",
"--copy-artifacts"
],
"scripts": [
"flutter/lib/web_ui/dev/felt"
]
}
]
}
},
{
"name": "web_tests/test_bundles/dart2js-html-html",
"drone_dimensions": [
@ -247,6 +180,34 @@
}
],
"tests": [
{
"name": "web engine analysis and license checks",
"recipe": "engine_v2/tester_engine",
"drone_dimensions": [
"device_type=none",
"os=Linux"
],
"tasks": [
{
"name": "check licenses",
"parameters": [
"check-licenses"
],
"scripts": [
"flutter/lib/web_ui/dev/felt"
]
},
{
"name": "web engine analysis",
"parameters": [
"analyze"
],
"scripts": [
"flutter/lib/web_ui/dev/felt"
]
}
]
},
{
"name": "Linux run chrome suites",
"recipe": "engine_v2/tester_engine",
@ -280,6 +241,46 @@
}
],
"tasks": [
{
"name": "copy artifacts",
"parameters": [
"test",
"--copy-artifacts",
"--suite=chrome-dart2js-html-html",
"--suite=chrome-dart2js-html-ui",
"--suite=chrome-dart2js-canvaskit-engine",
"--suite=chrome-dart2js-canvaskit-canvaskit",
"--suite=chrome-dart2js-canvaskit-ui",
"--suite=chrome-full-dart2js-canvaskit-canvaskit",
"--suite=chrome-full-dart2js-canvaskit-ui",
"--suite=edge-dart2js-html-html",
"--suite=edge-dart2js-html-ui",
"--suite=edge-dart2js-canvaskit-engine",
"--suite=edge-dart2js-canvaskit-canvaskit",
"--suite=edge-dart2js-canvaskit-ui",
"--suite=edge-full-dart2js-canvaskit-canvaskit",
"--suite=edge-full-dart2js-canvaskit-ui",
"--suite=firefox-dart2js-html-html",
"--suite=firefox-dart2js-html-ui",
"--suite=firefox-dart2js-canvaskit-engine",
"--suite=firefox-dart2js-canvaskit-canvaskit",
"--suite=firefox-dart2js-canvaskit-ui",
"--suite=safari-dart2js-html-html",
"--suite=safari-dart2js-html-ui",
"--suite=safari-dart2js-canvaskit-engine",
"--suite=safari-dart2js-canvaskit-canvaskit",
"--suite=safari-dart2js-canvaskit-ui",
"--suite=chrome-dart2wasm-canvaskit-engine",
"--suite=chrome-coi-dart2wasm-skwasm-ui",
"--suite=chrome-force-st-dart2wasm-skwasm-ui",
"--suite=chrome-fallbacks",
"--suite=chrome-coi-fallbacks",
"--suite=chrome-force-st-fallbacks",
"--suite=firefox-fallbacks",
"--suite=safari-fallbacks"
],
"script": "flutter/lib/web_ui/dev/felt"
},
{
"name": "run suite chrome-dart2js-html-html",
"parameters": [
@ -430,6 +431,46 @@
}
],
"tasks": [
{
"name": "copy artifacts",
"parameters": [
"test",
"--copy-artifacts",
"--suite=chrome-dart2js-html-html",
"--suite=chrome-dart2js-html-ui",
"--suite=chrome-dart2js-canvaskit-engine",
"--suite=chrome-dart2js-canvaskit-canvaskit",
"--suite=chrome-dart2js-canvaskit-ui",
"--suite=chrome-full-dart2js-canvaskit-canvaskit",
"--suite=chrome-full-dart2js-canvaskit-ui",
"--suite=edge-dart2js-html-html",
"--suite=edge-dart2js-html-ui",
"--suite=edge-dart2js-canvaskit-engine",
"--suite=edge-dart2js-canvaskit-canvaskit",
"--suite=edge-dart2js-canvaskit-ui",
"--suite=edge-full-dart2js-canvaskit-canvaskit",
"--suite=edge-full-dart2js-canvaskit-ui",
"--suite=firefox-dart2js-html-html",
"--suite=firefox-dart2js-html-ui",
"--suite=firefox-dart2js-canvaskit-engine",
"--suite=firefox-dart2js-canvaskit-canvaskit",
"--suite=firefox-dart2js-canvaskit-ui",
"--suite=safari-dart2js-html-html",
"--suite=safari-dart2js-html-ui",
"--suite=safari-dart2js-canvaskit-engine",
"--suite=safari-dart2js-canvaskit-canvaskit",
"--suite=safari-dart2js-canvaskit-ui",
"--suite=chrome-dart2wasm-canvaskit-engine",
"--suite=chrome-coi-dart2wasm-skwasm-ui",
"--suite=chrome-force-st-dart2wasm-skwasm-ui",
"--suite=chrome-fallbacks",
"--suite=chrome-coi-fallbacks",
"--suite=chrome-force-st-fallbacks",
"--suite=firefox-fallbacks",
"--suite=safari-fallbacks"
],
"script": "flutter/lib/web_ui/dev/felt"
},
{
"name": "run suite firefox-dart2js-html-html",
"parameters": [
@ -514,6 +555,46 @@
}
],
"tasks": [
{
"name": "copy artifacts",
"parameters": [
"test",
"--copy-artifacts",
"--suite=chrome-dart2js-html-html",
"--suite=chrome-dart2js-html-ui",
"--suite=chrome-dart2js-canvaskit-engine",
"--suite=chrome-dart2js-canvaskit-canvaskit",
"--suite=chrome-dart2js-canvaskit-ui",
"--suite=chrome-full-dart2js-canvaskit-canvaskit",
"--suite=chrome-full-dart2js-canvaskit-ui",
"--suite=edge-dart2js-html-html",
"--suite=edge-dart2js-html-ui",
"--suite=edge-dart2js-canvaskit-engine",
"--suite=edge-dart2js-canvaskit-canvaskit",
"--suite=edge-dart2js-canvaskit-ui",
"--suite=edge-full-dart2js-canvaskit-canvaskit",
"--suite=edge-full-dart2js-canvaskit-ui",
"--suite=firefox-dart2js-html-html",
"--suite=firefox-dart2js-html-ui",
"--suite=firefox-dart2js-canvaskit-engine",
"--suite=firefox-dart2js-canvaskit-canvaskit",
"--suite=firefox-dart2js-canvaskit-ui",
"--suite=safari-dart2js-html-html",
"--suite=safari-dart2js-html-ui",
"--suite=safari-dart2js-canvaskit-engine",
"--suite=safari-dart2js-canvaskit-canvaskit",
"--suite=safari-dart2js-canvaskit-ui",
"--suite=chrome-dart2wasm-canvaskit-engine",
"--suite=chrome-coi-dart2wasm-skwasm-ui",
"--suite=chrome-force-st-dart2wasm-skwasm-ui",
"--suite=chrome-fallbacks",
"--suite=chrome-coi-fallbacks",
"--suite=chrome-force-st-fallbacks",
"--suite=firefox-fallbacks",
"--suite=safari-fallbacks"
],
"script": "flutter/lib/web_ui/dev/felt"
},
{
"name": "run suite safari-dart2js-html-html",
"parameters": [

View File

@ -73,6 +73,21 @@ class BuildCommand extends Command<bool> with ArgUtils<bool> {
List<String> get targets => argResults?.rest ?? <String>[];
bool get embedDwarf => boolArg('dwarf');
RuntimeMode get runtimeMode {
final bool isProfile = boolArg('profile');
final bool isDebug = boolArg('debug');
if (isProfile && isDebug) {
throw ToolExit('Cannot specify both --profile and --debug at the same time.');
}
if (isProfile) {
return RuntimeMode.profile;
} else if (isDebug) {
return RuntimeMode.debug;
} else {
return RuntimeMode.release;
}
}
@override
FutureOr<bool> run() async {
if (embedDwarf && runtimeMode != RuntimeMode.debug) {

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io' as io;
import 'package:path/path.dart' as path;
@ -10,6 +11,7 @@ import 'browser.dart';
import 'chrome.dart';
import 'edge.dart';
import 'environment.dart';
import 'exceptions.dart';
import 'felt_config.dart';
import 'firefox.dart';
import 'safari_macos.dart';
@ -206,6 +208,33 @@ class DevNull implements StringSink {
void writeln([Object? obj = '']) {}
}
enum LuciRealm { Prod, Staging, Try, Unknown }
class LuciConfig {
LuciConfig(this.realm);
factory LuciConfig.fromJson(String contextJson) {
final json = jsonDecode(contextJson) as Map<String, Object?>;
final LuciRealm realm = switch ((json['realm'] as Map<String, Object?>?)?['name']) {
'flutter:prod' => LuciRealm.Prod,
'flutter:staging' => LuciRealm.Staging,
'flutter:try' => LuciRealm.Try,
_ => LuciRealm.Unknown,
};
return LuciConfig(realm);
}
final LuciRealm realm;
}
final LuciConfig? luciConfig = () {
final String? contextPath = io.Platform.environment['LUCI_CONTEXT'];
if (contextPath == null) {
return null;
}
return LuciConfig.fromJson(io.File(contextPath).readAsStringSync());
}();
/// Whether the felt command is running on LUCI.
bool get isLuci => io.Platform.environment['LUCI_CONTEXT'] != null;
@ -213,6 +242,22 @@ bool get isLuci => io.Platform.environment['LUCI_CONTEXT'] != null;
/// environements.
bool get isCi => isLuci;
final String gitRevision = () {
final result = io.Process.runSync(
'git',
<String>['rev-parse', 'HEAD'],
workingDirectory: path.join(environment.engineSrcDir.path, 'flutter'),
stderrEncoding: utf8,
stdoutEncoding: utf8,
);
if (result.exitCode != 0) {
throw ToolExit(
'Failed to get git revision. Exit code: ${result.exitCode} Error: ${result.stderr}',
);
}
return (result.stdout as String).trim();
}();
const String kChrome = 'chrome';
const String kEdge = 'edge';
const String kFirefox = 'firefox';

View File

@ -28,37 +28,51 @@ class GenerateBuilderJsonCommand extends Command<bool> {
final FeltConfig config = FeltConfig.fromFile(
path.join(environment.webUiTestDir.path, 'felt_config.yaml'),
);
final String configString = generate(config, packageLock);
final io.File configFile = io.File(
path.join(environment.flutterDirectory.path, 'ci', 'builders', 'linux_web_engine.json'),
_writeBuilderJson(
_generateBuilderJson([_getArtifactBuildStep()], []),
'linux_web_engine_build.json',
);
_writeBuilderJson(
_generateBuilderJson(
config.testBundles.map((TestBundle bundle) => _getBundleBuildStep(bundle)).toList(),
_getAllTestSteps(config.testSuites, packageLock),
),
'linux_web_engine_test.json',
);
configFile.writeAsStringSync('$configString\n');
return true;
}
String generate(FeltConfig config, PackageLock packageLock) {
void _writeBuilderJson(String builderConfig, String filename) {
final io.File buildConfigFile = io.File(
path.join(environment.flutterDirectory.path, 'ci', 'builders', filename),
);
buildConfigFile.createSync(recursive: true);
buildConfigFile.writeAsStringSync('$builderConfig\n');
}
String _generateBuilderJson(
Iterable<Map<String, dynamic>> builds,
Iterable<Map<String, dynamic>> tests,
) {
final Map<String, dynamic> outputJson = <String, dynamic>{
'_comment': 'THIS IS A GENERATED FILE. Do not edit this file directly.',
'_comment2': 'See `generate_builder_json.dart` for the generator code',
'builds': <dynamic>[
_getArtifactBuildStep(),
for (final TestBundle bundle in config.testBundles) _getBundleBuildStep(bundle),
],
'tests': _getAllTestSteps(config.testSuites, packageLock),
'builds': builds,
'tests': tests,
};
return const JsonEncoder.withIndent(' ').convert(outputJson);
}
Map<String, dynamic> _getArtifactBuildStep() {
return <String, dynamic>{
'name': 'web_tests/artifacts',
'name': 'ci/wasm_release',
'drone_dimensions': <String>['device_type=none', 'os=Linux', 'cores=32'],
'gclient_variables': <String, dynamic>{
'download_android_deps': false,
'download_jdk': false,
'download_emsdk': true,
},
'gn': <String>['--web', '--runtime-mode=release', '--no-goma'],
'gn': <String>['--web', '--runtime-mode=release', '--no-rbe', '--no-goma'],
'ninja': <String, dynamic>{
'config': 'wasm_release',
'targets': <String>['flutter/web_sdk:flutter_web_sdk_archive'],
@ -72,25 +86,7 @@ class GenerateBuilderJsonCommand extends Command<bool> {
'realm': 'production',
},
],
'generators': <String, dynamic>{
'tasks': <dynamic>[
<String, dynamic>{
'name': 'check licenses',
'parameters': <String>['check-licenses'],
'scripts': <String>['flutter/lib/web_ui/dev/felt'],
},
<String, dynamic>{
'name': 'web engine analysis',
'parameters': <String>['analyze'],
'scripts': <String>['flutter/lib/web_ui/dev/felt'],
},
<String, dynamic>{
'name': 'copy artifacts for web tests',
'parameters': <String>['test', '--copy-artifacts'],
'scripts': <String>['flutter/lib/web_ui/dev/felt'],
},
],
},
'cas_archive': false,
};
}
@ -110,8 +106,29 @@ class GenerateBuilderJsonCommand extends Command<bool> {
};
}
Iterable<dynamic> _getAllTestSteps(List<TestSuite> suites, PackageLock packageLock) {
return <dynamic>[
Map<String, dynamic> _getAnalysisAndLicenseStep() {
return <String, dynamic>{
'name': 'web engine analysis and license checks',
'recipe': 'engine_v2/tester_engine',
'drone_dimensions': <String>['device_type=none', 'os=Linux'],
'tasks': <dynamic>[
<String, dynamic>{
'name': 'check licenses',
'parameters': <String>['check-licenses'],
'scripts': <String>['flutter/lib/web_ui/dev/felt'],
},
<String, dynamic>{
'name': 'web engine analysis',
'parameters': <String>['analyze'],
'scripts': <String>['flutter/lib/web_ui/dev/felt'],
},
],
};
}
Iterable<Map<String, dynamic>> _getAllTestSteps(List<TestSuite> suites, PackageLock packageLock) {
return <Map<String, dynamic>>[
_getAnalysisAndLicenseStep(),
_getTestStepForPlatformAndBrowser(suites, packageLock, 'Linux', BrowserName.chrome),
_getTestStepForPlatformAndBrowser(suites, packageLock, 'Linux', BrowserName.firefox),
_getTestStepForPlatformAndBrowser(
@ -164,16 +181,24 @@ class GenerateBuilderJsonCommand extends Command<bool> {
'version': 'version:${packageLock.firefoxLock.version}',
},
],
'tasks':
filteredSuites
.map(
(suite) => <String, dynamic>{
'name': 'run suite ${suite.name}',
'parameters': <String>['test', '--run', '--suite=${suite.name}'],
'script': 'flutter/lib/web_ui/dev/felt',
},
)
.toList(),
'tasks': [
<String, dynamic>{
'name': 'copy artifacts',
'parameters': <String>[
'test',
'--copy-artifacts',
for (final TestSuite suite in suites) '--suite=${suite.name}',
],
'script': 'flutter/lib/web_ui/dev/felt',
},
...filteredSuites.map(
(suite) => <String, dynamic>{
'name': 'run suite ${suite.name}',
'parameters': <String>['test', '--run', '--suite=${suite.name}'],
'script': 'flutter/lib/web_ui/dev/felt',
},
),
],
};
}
}

View File

@ -5,19 +5,36 @@
import 'dart:convert' show JsonEncoder;
import 'dart:io' as io;
import 'package:archive/archive_io.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as pathlib;
import '../common.dart';
import '../environment.dart';
import '../exceptions.dart';
import '../felt_config.dart';
import '../pipeline.dart';
import '../utils.dart';
sealed class ArtifactSource {}
class LocalArtifactSource implements ArtifactSource {
LocalArtifactSource({required this.mode});
final RuntimeMode mode;
}
class GcsArtifactSource implements ArtifactSource {
GcsArtifactSource({required this.realm});
final LuciRealm realm;
}
class CopyArtifactsStep implements PipelineStep {
CopyArtifactsStep(this.artifactDeps, {required this.runtimeMode});
CopyArtifactsStep(this.artifactDeps, {required this.source});
final ArtifactDependencies artifactDeps;
final RuntimeMode runtimeMode;
final ArtifactSource source;
@override
String get description => 'copy_artifacts';
@ -30,24 +47,75 @@ class CopyArtifactsStep implements PipelineStep {
await cleanup();
}
Future<io.Directory> _downloadArtifacts(LuciRealm realm) async {
final String realmComponent = switch (realm) {
LuciRealm.Prod || LuciRealm.Staging => '',
LuciRealm.Try => 'flutter_archives_v2/',
LuciRealm.Unknown =>
throw ToolExit('Could not generate artifact bucket url for unknown realm.'),
};
final Uri url = Uri.https(
'storage.googleapis.com',
'${realmComponent}flutter_infra_release/flutter/$gitRevision/flutter-web-sdk.zip',
);
final http.Response response = await http.Client().get(url);
if (response.statusCode != 200) {
throw ToolExit(
'Could not download flutter-web-sdk.zip from cloud bucket at URL: $url. Response status code: ${response.statusCode}',
);
}
final Archive archive = ZipDecoder().decodeBytes(response.bodyBytes);
final io.Directory tempDirectory = await io.Directory.systemTemp.createTemp();
await extractArchiveToDisk(archive, tempDirectory.absolute.path);
return tempDirectory;
}
@override
Future<void> run() async {
final String flutterJsSourceDirectory;
final String canvaskitSourceDirectory;
final String canvaskitChromiumSourceDirectory;
final String skwasmSourceDirectory;
final String skwasmStSourceDirectory;
switch (source) {
case LocalArtifactSource(:final mode):
final buildDirectory = getBuildDirectoryForRuntimeMode(mode).path;
flutterJsSourceDirectory = pathlib.join(buildDirectory, 'flutter_web_sdk', 'flutter_js');
canvaskitSourceDirectory = pathlib.join(buildDirectory, 'canvaskit');
canvaskitChromiumSourceDirectory = pathlib.join(buildDirectory, 'canvaskit_chromium');
skwasmSourceDirectory = pathlib.join(buildDirectory, 'skwasm');
skwasmStSourceDirectory = pathlib.join(buildDirectory, 'skwasm_st');
case GcsArtifactSource(:final realm):
final artifactsDirectory = (await _downloadArtifacts(realm)).path;
flutterJsSourceDirectory = pathlib.join(artifactsDirectory, 'flutter_js');
canvaskitSourceDirectory = pathlib.join(artifactsDirectory, 'canvaskit');
canvaskitChromiumSourceDirectory = pathlib.join(
artifactsDirectory,
'canvaskit',
'chromium',
);
skwasmSourceDirectory = pathlib.join(artifactsDirectory, 'canvaskit');
skwasmStSourceDirectory = pathlib.join(artifactsDirectory, 'canvaskit');
}
await environment.webTestsArtifactsDir.create(recursive: true);
await buildHostPage();
await copyTestFonts();
await copySkiaTestImages();
await copyFlutterJsFiles();
await copyFlutterJsFiles(flutterJsSourceDirectory);
if (artifactDeps.canvasKit) {
print('Copying CanvasKit...');
await copyCanvasKitFiles('canvaskit', 'canvaskit');
await copyWasmLibrary('canvaskit', canvaskitSourceDirectory, 'canvaskit');
}
if (artifactDeps.canvasKitChromium) {
print('Copying CanvasKit (Chromium)...');
await copyCanvasKitFiles('canvaskit_chromium', 'canvaskit/chromium');
await copyWasmLibrary('canvaskit', canvaskitChromiumSourceDirectory, 'canvaskit/chromium');
}
if (artifactDeps.skwasm) {
print('Copying Skwasm...');
await copySkwasm();
await copyWasmLibrary('skwasm', skwasmSourceDirectory, 'canvaskit');
await copyWasmLibrary('skwasm_st', skwasmStSourceDirectory, 'canvaskit');
}
}
@ -148,10 +216,8 @@ class CopyArtifactsStep implements PipelineStep {
}
}
Future<void> copyFlutterJsFiles() async {
final io.Directory flutterJsInputDirectory = io.Directory(
pathlib.join(outBuildPath, 'flutter_web_sdk', 'flutter_js'),
);
Future<void> copyFlutterJsFiles(String sourcePath) async {
final io.Directory flutterJsInputDirectory = io.Directory(sourcePath);
final String targetDirectoryPath = pathlib.join(
environment.webTestsArtifactsDir.path,
'flutter_js',
@ -172,20 +238,22 @@ class CopyArtifactsStep implements PipelineStep {
}
}
Future<void> copyCanvasKitFiles(String sourcePath, String destinationPath) async {
final String sourceDirectoryPath = pathlib.join(outBuildPath, sourcePath);
Future<void> copyWasmLibrary(
String libraryName,
String sourcePath,
String destinationPath,
) async {
final String targetDirectoryPath = pathlib.join(
environment.webTestsArtifactsDir.path,
destinationPath,
);
for (final String filename in <String>[
'canvaskit.js',
'canvaskit.wasm',
'canvaskit.wasm.map',
'$libraryName.js',
'$libraryName.wasm',
'$libraryName.wasm.map',
]) {
final io.File sourceFile = io.File(pathlib.join(sourceDirectoryPath, filename));
final io.File sourceFile = io.File(pathlib.join(sourcePath, filename));
final io.File targetFile = io.File(pathlib.join(targetDirectoryPath, filename));
if (!sourceFile.existsSync()) {
if (filename.endsWith('.map')) {
@ -194,7 +262,7 @@ class CopyArtifactsStep implements PipelineStep {
continue;
}
{
throw ToolExit('Built CanvasKit artifact not found at path "$sourceFile".');
throw ToolExit('Built artifact not found at path "$sourceFile".');
}
}
await targetFile.create(recursive: true);
@ -202,41 +270,6 @@ class CopyArtifactsStep implements PipelineStep {
}
}
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_st.wasm',
'skwasm_st.wasm.map',
'skwasm_st.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(

View File

@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
import 'package:watcher/src/watch_event.dart';
import 'common.dart';
import 'environment.dart';
import 'exceptions.dart';
import 'felt_config.dart';
@ -67,6 +68,10 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
)
..addFlag('profile', help: 'Use artifacts from the profile build instead of release.')
..addFlag('debug', help: 'Use artifacts from the debug build instead of release.')
..addFlag('release', help: 'Use artifacts from the release build. This is the default.')
..addFlag('gcs-prod', help: 'Use artifacts from the prod gcs bucket populated by CI.')
..addFlag('gcs-staging', help: 'Use artifacts from the staging gcs bucket populated by CI.')
..addFlag('gcs-try', help: 'Use artifacts from the try gcs bucket populated by CI.')
..addFlag('dwarf', help: 'Debug wasm modules using embedded DWARF data.')
..addFlag(
'require-skia-gold',
@ -300,6 +305,40 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
);
}
ArtifactSource get artifactSource {
final List<ArtifactSource> sources = <ArtifactSource>[];
if (boolArg('debug')) {
sources.add(LocalArtifactSource(mode: RuntimeMode.debug));
}
if (boolArg('profile')) {
sources.add(LocalArtifactSource(mode: RuntimeMode.profile));
}
if (boolArg('release')) {
sources.add(LocalArtifactSource(mode: RuntimeMode.release));
}
if (boolArg('gcs-prod')) {
sources.add(GcsArtifactSource(realm: LuciRealm.Prod));
}
if (boolArg('gcs-staging')) {
sources.add(GcsArtifactSource(realm: LuciRealm.Staging));
}
if (boolArg('gcs-try')) {
sources.add(GcsArtifactSource(realm: LuciRealm.Try));
}
if (sources.length > 1) {
throw ToolExit('Cannot specify more than one artifact source.');
}
if (sources.length == 1) {
return sources.first;
}
final realm = luciConfig?.realm;
if (realm != null) {
return GcsArtifactSource(realm: realm);
} else {
return LocalArtifactSource(mode: RuntimeMode.release);
}
}
@override
Future<bool> run() async {
final List<TestSuite> filteredSuites = _filterTestSuites();
@ -343,7 +382,7 @@ class TestCommand extends Command<bool> with ArgUtils<bool> {
final Pipeline testPipeline = Pipeline(
steps: <PipelineStep>[
if (isWatchMode) ClearTerminalScreenStep(),
if (shouldCopyArtifacts) CopyArtifactsStep(artifacts, runtimeMode: runtimeMode),
if (shouldCopyArtifacts) CopyArtifactsStep(artifacts, source: artifactSource),
if (shouldCompile)
for (final TestBundle bundle in bundles)
CompileBundleStep(bundle: bundle, isVerbose: isVerbose, testFiles: testFiles),

View File

@ -12,7 +12,6 @@ import 'package:path/path.dart' as path;
import 'common.dart';
import 'environment.dart';
import 'exceptions.dart';
import 'felt_config.dart';
enum RuntimeMode { debug, profile, release }
@ -298,21 +297,6 @@ mixin ArgUtils<T> on Command<T> {
/// Extracts a string argument from [argResults].
String stringArg(String name) => argResults![name] as String;
RuntimeMode get runtimeMode {
final bool isProfile = boolArg('profile');
final bool isDebug = boolArg('debug');
if (isProfile && isDebug) {
throw ToolExit('Cannot specify both --profile and --debug at the same time.');
}
if (isProfile) {
return RuntimeMode.profile;
} else if (isDebug) {
return RuntimeMode.debug;
} else {
return RuntimeMode.release;
}
}
}
io.Directory getBuildDirectoryForRuntimeMode(RuntimeMode runtimeMode) => switch (runtimeMode) {