mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* adding an apple script to quit safari * making changes to utils and adding cleanup after unit tests failed * switch saai quit to pkill instead of applescript * adding toolexception * adding a cleanup function option to felt. * removing unnecassary await's added for the previos version of the PR * removing unnecassary async * fix analyze error
371 lines
12 KiB
Dart
371 lines
12 KiB
Dart
// 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.
|
|
// @dart = 2.6
|
|
import 'dart:io' as io;
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:http/http.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import 'common.dart';
|
|
import 'environment.dart';
|
|
import 'exceptions.dart';
|
|
|
|
class FirefoxArgParser extends BrowserArgParser {
|
|
static final FirefoxArgParser _singletonInstance = FirefoxArgParser._();
|
|
|
|
/// The [FirefoxArgParser] singleton.
|
|
static FirefoxArgParser get instance => _singletonInstance;
|
|
|
|
String _version;
|
|
|
|
FirefoxArgParser._();
|
|
|
|
@override
|
|
void populateOptions(ArgParser argParser) {
|
|
final YamlMap browserLock = BrowserLock.instance.configuration;
|
|
String firefoxVersion = browserLock['firefox']['version'] as String;
|
|
|
|
argParser
|
|
..addOption(
|
|
'firefox-version',
|
|
defaultsTo: '$firefoxVersion',
|
|
help: 'The Firefox version to use while running tests. If the requested '
|
|
'version has not been installed, it will be downloaded and installed '
|
|
'automatically. Value "latest" will use the latest '
|
|
'stable build of Firefox, installing it if necessary. Value "system" '
|
|
'will use the manually installed version of Firefox on this computer.',
|
|
);
|
|
}
|
|
|
|
@override
|
|
void parseOptions(ArgResults argResults) {
|
|
_version = argResults['firefox-version'] as String;
|
|
}
|
|
|
|
@override
|
|
String get version => _version;
|
|
}
|
|
|
|
/// Returns the installation of Firefox, installing it if necessary.
|
|
///
|
|
/// If [requestedVersion] is null, uses the version specified on the
|
|
/// command-line. If not specified on the command-line, uses the version
|
|
/// specified in the "browser_lock.yaml" file.
|
|
///
|
|
/// If [requestedVersion] is not null, installs that version. The value
|
|
/// may be "latest" (the latest stable Firefox version), "system"
|
|
/// (manually installed Firefox on the current operating system), or an
|
|
/// exact version number such as 69.0.3. Versions of Firefox can be found here:
|
|
///
|
|
/// https://download-installer.cdn.mozilla.net/pub/firefox/releases/
|
|
Future<BrowserInstallation> getOrInstallFirefox(
|
|
String requestedVersion, {
|
|
StringSink infoLog,
|
|
}) async {
|
|
// These tests are aimed to run only on the Linux containers in Cirrus.
|
|
// Therefore Firefox installation is implemented only for Linux now.
|
|
if (!io.Platform.isLinux && !io.Platform.isMacOS) {
|
|
throw UnimplementedError('Firefox Installer is only supported on Linux '
|
|
'and Mac operating systems');
|
|
}
|
|
|
|
infoLog ??= io.stdout;
|
|
|
|
if (requestedVersion == 'system') {
|
|
return BrowserInstallation(
|
|
version: 'system',
|
|
executable: await _findSystemFirefoxExecutable(),
|
|
);
|
|
}
|
|
|
|
FirefoxInstaller installer;
|
|
try {
|
|
installer = requestedVersion == 'latest'
|
|
? await FirefoxInstaller.latest()
|
|
: FirefoxInstaller(version: requestedVersion);
|
|
|
|
if (installer.isInstalled) {
|
|
infoLog.writeln(
|
|
'Installation was skipped because Firefox version ${installer.version} is already installed.');
|
|
} else {
|
|
infoLog.writeln('Installing Firefox version: ${installer.version}');
|
|
await installer.install();
|
|
final BrowserInstallation installation = installer.getInstallation();
|
|
infoLog.writeln(
|
|
'Installations complete. To launch it run ${installation.executable}');
|
|
}
|
|
return installer.getInstallation();
|
|
} finally {
|
|
installer?.close();
|
|
}
|
|
}
|
|
|
|
/// Manages the installation of a particular [version] of Firefox.
|
|
class FirefoxInstaller {
|
|
factory FirefoxInstaller({
|
|
@required String version,
|
|
}) {
|
|
if (version == 'system') {
|
|
throw BrowserInstallerException(
|
|
'Cannot install system version of Firefox. System Firefox must be installed manually.');
|
|
}
|
|
if (version == 'latest') {
|
|
throw BrowserInstallerException(
|
|
'Expected a concrete Firefox version, but got $version. Maybe use FirefoxInstaller.latest()?');
|
|
}
|
|
final io.Directory firefoxInstallationDir = io.Directory(
|
|
path.join(environment.webUiDartToolDir.path, 'firefox'),
|
|
);
|
|
final io.Directory versionDir = io.Directory(
|
|
path.join(firefoxInstallationDir.path, version),
|
|
);
|
|
return FirefoxInstaller._(
|
|
version: version,
|
|
firefoxInstallationDir: firefoxInstallationDir,
|
|
versionDir: versionDir,
|
|
);
|
|
}
|
|
|
|
static Future<FirefoxInstaller> latest() async {
|
|
final String latestVersion = io.Platform.isLinux
|
|
? await fetchLatestFirefoxVersionLinux()
|
|
: await fetchLatestFirefoxVersionMacOS();
|
|
return FirefoxInstaller(version: latestVersion);
|
|
}
|
|
|
|
FirefoxInstaller._({
|
|
@required this.version,
|
|
@required this.firefoxInstallationDir,
|
|
@required this.versionDir,
|
|
});
|
|
|
|
/// Firefox version managed by this installer.
|
|
final String version;
|
|
|
|
/// HTTP client used to download Firefox.
|
|
final Client client = Client();
|
|
|
|
/// Root directory that contains Firefox versions.
|
|
final io.Directory firefoxInstallationDir;
|
|
|
|
/// Installation directory for Firefox of the requested [version].
|
|
final io.Directory versionDir;
|
|
|
|
bool get isInstalled {
|
|
return versionDir.existsSync();
|
|
}
|
|
|
|
BrowserInstallation getInstallation() {
|
|
if (!isInstalled) {
|
|
return null;
|
|
}
|
|
|
|
return BrowserInstallation(
|
|
version: version,
|
|
executable: PlatformBinding.instance.getFirefoxExecutablePath(versionDir),
|
|
);
|
|
}
|
|
|
|
/// Install the browser by downloading from the web.
|
|
Future<void> install() async {
|
|
final io.File downloadedFile = await _download();
|
|
if (io.Platform.isLinux) {
|
|
await _uncompress(downloadedFile);
|
|
} else if (io.Platform.isMacOS) {
|
|
await _mountDmgAndCopy(downloadedFile);
|
|
}
|
|
downloadedFile.deleteSync();
|
|
}
|
|
|
|
/// Downloads the browser version from web into a target file.
|
|
/// See [version].
|
|
Future<io.File> _download() async {
|
|
if (versionDir.existsSync()) {
|
|
versionDir.deleteSync(recursive: true);
|
|
}
|
|
|
|
versionDir.createSync(recursive: true);
|
|
final String url = PlatformBinding.instance.getFirefoxDownloadUrl(version);
|
|
final StreamedResponse download = await client.send(Request(
|
|
'GET',
|
|
Uri.parse(url),
|
|
));
|
|
|
|
final io.File downloadedFile =
|
|
io.File(path.join(versionDir.path, PlatformBinding.instance.getFirefoxDownloadFilename(version)));
|
|
io.IOSink sink = downloadedFile.openWrite();
|
|
await download.stream.pipe(sink);
|
|
await sink.flush();
|
|
await sink.close();
|
|
|
|
return downloadedFile;
|
|
}
|
|
|
|
/// Uncompress the downloaded browser files for operating systems that
|
|
/// use a zip archive.
|
|
/// See [version].
|
|
Future<void> _uncompress(io.File downloadedFile) async {
|
|
final io.ProcessResult unzipResult = await io.Process.run('tar', <String>[
|
|
'-x',
|
|
'-f',
|
|
downloadedFile.path,
|
|
'-C',
|
|
versionDir.path,
|
|
]);
|
|
|
|
if (unzipResult.exitCode != 0) {
|
|
throw BrowserInstallerException(
|
|
'Failed to unzip the downloaded Firefox archive ${downloadedFile.path}.\n'
|
|
'The unzip process exited with code ${unzipResult.exitCode}.');
|
|
}
|
|
}
|
|
|
|
/// Mounts the dmg file using hdiutil, copies content of the volume to
|
|
/// target path and then unmounts dmg ready for deletion.
|
|
Future<void> _mountDmgAndCopy(io.File dmgFile) async {
|
|
String volumeName = await _hdiUtilMount(dmgFile);
|
|
|
|
final String sourcePath = '$volumeName/Firefox.app';
|
|
final String targetPath = path.dirname(dmgFile.path);
|
|
try {
|
|
io.ProcessResult installResult = await io.Process.run('cp', <String>[
|
|
'-r',
|
|
sourcePath,
|
|
targetPath,
|
|
]);
|
|
if (installResult.exitCode != 0) {
|
|
throw BrowserInstallerException(
|
|
'Failed to copy Firefox disk image contents from '
|
|
'$sourcePath to $targetPath.\n'
|
|
'Exit code ${installResult.exitCode}.\n'
|
|
'${installResult.stderr}');
|
|
}
|
|
} finally {
|
|
await _hdiUtilUnmount(volumeName);
|
|
}
|
|
}
|
|
|
|
Future<String> _hdiUtilMount(io.File dmgFile) async {
|
|
io.ProcessResult mountResult = await io.Process.run('hdiutil', <String>[
|
|
'attach',
|
|
'-readonly',
|
|
'${dmgFile.path}',
|
|
]);
|
|
if (mountResult.exitCode != 0) {
|
|
throw BrowserInstallerException(
|
|
'Failed to mount Firefox disk image ${dmgFile.path}.\n'
|
|
'Exit code ${mountResult.exitCode}.\n${mountResult.stderr}');
|
|
}
|
|
|
|
List<String> processOutput = (mountResult.stdout as String).split('\n');
|
|
String volumePath = _volumeFromMountResult(processOutput);
|
|
if (volumePath == null) {
|
|
throw BrowserInstallerException(
|
|
'Failed to parse mount dmg result ${processOutput.join('\n')}.\n'
|
|
'Expected /Volumes/{volume name}');
|
|
}
|
|
return volumePath;
|
|
}
|
|
|
|
// Parses volume from mount result.
|
|
// Output is of form: {devicename} /Volumes/{name}.
|
|
String _volumeFromMountResult(List<String> lines) {
|
|
for (String line in lines) {
|
|
int pos = line.indexOf('/Volumes');
|
|
if (pos != -1) {
|
|
return line.substring(pos);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<void> _hdiUtilUnmount(String volumeName) async {
|
|
io.ProcessResult unmountResult = await io.Process.run('hdiutil', <String>[
|
|
'unmount',
|
|
'$volumeName',
|
|
]);
|
|
if (unmountResult.exitCode != 0) {
|
|
throw BrowserInstallerException(
|
|
'Failed to unmount Firefox disk image ${volumeName}.\n'
|
|
'Exit code ${unmountResult.exitCode}. ${unmountResult.stderr}');
|
|
}
|
|
}
|
|
|
|
void close() {
|
|
client.close();
|
|
}
|
|
}
|
|
|
|
Future<String> _findSystemFirefoxExecutable() async {
|
|
final io.ProcessResult which =
|
|
await io.Process.run('which', <String>['firefox']);
|
|
bool found = which.exitCode != 0;
|
|
const String fireFoxDefaultInstallPath =
|
|
'/Applications/Firefox.app/Contents/MacOS/firefox';
|
|
if (!found) {
|
|
if (io.Platform.isMacOS &&
|
|
io.File(fireFoxDefaultInstallPath).existsSync()) {
|
|
return Future.value(fireFoxDefaultInstallPath);
|
|
}
|
|
throw BrowserInstallerException(
|
|
'Failed to locate system Firefox installation.');
|
|
}
|
|
return which.stdout as String;
|
|
}
|
|
|
|
/// Fetches the latest available Firefox build version on Linux.
|
|
Future<String> fetchLatestFirefoxVersionLinux() async {
|
|
final RegExp forFirefoxVersion = RegExp("firefox-[0-9.]\+[0-9]");
|
|
final io.HttpClientRequest request = await io.HttpClient()
|
|
.getUrl(Uri.parse(PlatformBinding.instance.getFirefoxLatestVersionUrl()));
|
|
request.followRedirects = false;
|
|
// We will parse the HttpHeaders to find the redirect location.
|
|
final io.HttpClientResponse response = await request.close();
|
|
|
|
final String location = response.headers.value('location');
|
|
final String version = forFirefoxVersion.stringMatch(location);
|
|
|
|
return version.substring(version.lastIndexOf('-') + 1);
|
|
}
|
|
|
|
/// Fetches the latest available Firefox build version on Mac OS.
|
|
Future<String> fetchLatestFirefoxVersionMacOS() async {
|
|
final RegExp forFirefoxVersion = RegExp("firefox\/releases\/[0-9.]\+[0-9]");
|
|
final io.HttpClientRequest request = await io.HttpClient()
|
|
.getUrl(Uri.parse(PlatformBinding.instance.getFirefoxLatestVersionUrl()));
|
|
request.followRedirects = false;
|
|
// We will parse the HttpHeaders to find the redirect location.
|
|
final io.HttpClientResponse response = await request.close();
|
|
|
|
final String location = response.headers.value('location');
|
|
final String version = forFirefoxVersion.stringMatch(location);
|
|
return version.substring(version.lastIndexOf('/') + 1);
|
|
}
|
|
|
|
Future<BrowserInstallation> getInstaller({String requestedVersion = 'latest'}) async {
|
|
FirefoxInstaller installer;
|
|
try {
|
|
installer = requestedVersion == 'latest'
|
|
? await FirefoxInstaller.latest()
|
|
: FirefoxInstaller(version: requestedVersion);
|
|
|
|
if (installer.isInstalled) {
|
|
print('Installation was skipped because Firefox version '
|
|
'${installer.version} is already installed.');
|
|
} else {
|
|
print('Installing Firefox version: ${installer.version}');
|
|
await installer.install();
|
|
final BrowserInstallation installation = installer.getInstallation();
|
|
print(
|
|
'Installations complete. To launch it run ${installation.executable}');
|
|
}
|
|
return installer.getInstallation();
|
|
} finally {
|
|
installer?.close();
|
|
}
|
|
}
|