blob: 7549b1e12d590eb45e22e63c1ac519ec7ea796c8 [file] [log] [blame] [edit]
// Copyright 2023 The Chromium 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:io';
import 'package:args/command_runner.dart';
import 'package:devtools_tool/model.dart';
import 'package:io/io.dart';
import 'package:path/path.dart' as path;
import '../utils.dart';
import 'shared.dart';
const _buildFlag = 'build';
const _authFlag = 'auth';
class UpdatePerfettoCommand extends Command {
UpdatePerfettoCommand() {
argParser
..addOption(
_buildFlag,
abbr: 'b',
help: 'The build location of the Perfetto assets. When this is not '
'specified, the Perfetto assets will be fetched from the latest '
'source code at "android.googlesource.com".',
valueHelp: '/Users/me/path/to/perfetto/out/ui/ui/dist',
)
..addFlag(
_authFlag,
negatable: true,
defaultsTo: true,
help: 'Whether to authenticate via "gcert" before cloning the Perfetto '
'repository.',
);
}
@override
String get name => 'update-perfetto';
@override
String get description =>
'Updates the Perfetto assets that are included in the DevTools app bundle.';
@override
Future run() async {
if (Platform.isWindows) {
// In tools/install-build-deps in Perfetto:
// "Building the UI on Windows is unsupported".
throw 'Updating Perfetto is not currently supported on Windows';
}
final processManager = ProcessManager();
final authenticate = argResults![_authFlag] as bool;
if (authenticate) {
await processManager.runProcess(CliCommand('gcert', []));
}
final perfettoUiCompiledLibPath = pathFromRepoRoot(
path.join('third_party', 'packages', 'perfetto_ui_compiled', 'lib'),
);
final perfettoUiCompiledBuildPath =
path.join(perfettoUiCompiledLibPath, 'dist');
final perfettoDevToolsPath =
path.join(perfettoUiCompiledBuildPath, 'devtools');
logStatus(
'moving DevTools-Perfetto integration files to a temp directory.',
);
final tempPerfettoDevTools =
Directory.systemTemp.createTempSync('perfetto_devtools');
await copyPath(perfettoDevToolsPath, tempPerfettoDevTools.path);
logStatus('deleting existing Perfetto build');
final existingBuild = Directory(perfettoUiCompiledBuildPath);
existingBuild.deleteSync(recursive: true);
logStatus('updating Perfetto build');
final buildLocation = argResults![_buildFlag] as String?;
if (buildLocation != null) {
logStatus('using Perfetto build from $buildLocation');
logStatus(
'copying content from $buildLocation to $perfettoUiCompiledLibPath',
);
await copyPath(buildLocation, perfettoUiCompiledBuildPath);
} else {
logStatus('cloning Perfetto from HEAD and building from source');
final tempPerfettoClone =
Directory.systemTemp.createTempSync('perfetto_clone');
await processManager.runProcess(
CliCommand.git(
[
'clone',
'https://android.googlesource.com/platform/external/perfetto',
],
),
workingDirectory: tempPerfettoClone.path,
);
logStatus('installing build deps and building the Perfetto UI');
await processManager.runAll(
commands: [
CliCommand(path.join('tools', 'install-build-deps'), ['--ui']),
CliCommand(path.join('ui', 'build'), []),
],
workingDirectory: path.join(tempPerfettoClone.path, 'perfetto'),
);
final buildOutputPath = path.join(
tempPerfettoClone.path,
'perfetto',
'out',
'ui',
'ui',
'dist',
);
logStatus(
'copying content from $buildOutputPath to $perfettoUiCompiledLibPath',
);
await copyPath(buildOutputPath, perfettoUiCompiledLibPath);
logStatus('deleting perfetto clone');
tempPerfettoClone.deleteSync(recursive: true);
}
logStatus('deleting unnecessary js source map files from build');
final deleteMatchers = [
RegExp(r'\.js\.map'),
RegExp(r'\.css\.map'),
RegExp(r'traceconv\.wasm'),
RegExp(r'traceconv_bundle\.js'),
RegExp(r'catapult_trace_viewer\..*'),
RegExp(r'rec_.*\.png'),
];
final libDirectory = Directory(perfettoUiCompiledLibPath);
final libFiles = libDirectory.listSync(recursive: true);
for (final file in libFiles) {
if (deleteMatchers.any((matcher) => matcher.hasMatch(file.path))) {
logStatus('deleting ${file.path}');
file.deleteSync();
}
}
logStatus(
'moving DevTools-Perfetto integration files back from the temp directory',
);
Directory(perfettoDevToolsPath).createSync(recursive: true);
await copyPath(tempPerfettoDevTools.path, perfettoDevToolsPath);
logStatus('deleting temporary directory');
tempPerfettoDevTools.deleteSync(recursive: true);
_updateIndexFileForDevToolsEmbedding(
path.join(perfettoUiCompiledBuildPath, 'index.html'),
);
_updatePerfettoAssetsInPubspec();
}
void _updateIndexFileForDevToolsEmbedding(String indexFilePath) {
logStatus(
'updating index.html headers to include DevTools-Perfetto integration files',
);
final indexFile = File(indexFilePath);
final fileLines = indexFile.readAsLinesSync();
final fileLinesCopy = <String>[];
for (final line in fileLines) {
if (line == '</head>') {
fileLinesCopy.addAll([
' <link id="devtools-style" rel="stylesheet" href="devtools/devtools_dark.css">',
' <script src="devtools/devtools_theme_handler.js"></script>',
]);
}
fileLinesCopy.add(line);
}
indexFile.writeAsStringSync(fileLinesCopy.joinWithNewLine());
}
void _updatePerfettoAssetsInPubspec() {
logStatus('updating perfetto assets in the devtools_app pubspec.yaml file');
final repo = DevToolsRepo.getInstance();
final perfettoDistDir = Directory(
path.join(
repo.repoPath,
'third_party',
'packages',
'perfetto_ui_compiled',
'lib',
'dist',
),
);
// Find the new perfetto version number.
String newVersionNumber = '';
final versionRegExp = RegExp(r'v\d+[.]\d+-[0-9a-fA-F]+');
final entities = perfettoDistDir.listSync();
for (final entity in entities) {
final path = entity.path;
final match = versionRegExp.firstMatch(path);
if (match != null) {
newVersionNumber = path.split('/').last;
logStatus('new Perfetto version: $newVersionNumber');
break;
}
}
if (newVersionNumber.isEmpty) {
throw Exception(
'Error updating Perfetto assets: could not find Perfetto version number '
'from entities: ${entities.map((e) => e.path).toList()}',
);
}
final pubspec = File(
path.join(repo.devtoolsAppDirectoryPath, 'pubspec.yaml'),
);
// TODO(kenz): Ensure the pubspec.yaml contains an entry for each file in
// [perfettoDistDir].
final perfettoAssetRegExp = RegExp(
r'(?<prefix>^.*packages\/perfetto_ui_compiled\/dist\/)(?<version>v\d+[.]\d+-[0-9a-fA-F]+)(?<suffix>\/.*$)',
);
final lines = pubspec.readAsLinesSync();
for (int i = 0; i < lines.length; i++) {
final line = lines[i];
final match = perfettoAssetRegExp.firstMatch(line);
if (match != null) {
final prefix = match.namedGroup('prefix')!;
final suffix = match.namedGroup('suffix')!;
lines[i] = '$prefix$newVersionNumber$suffix';
}
}
logStatus(
'updating devtools_app/pubspec.yaml for new Perfetto version'
'$newVersionNumber',
);
final pubspecLinesAsString = '${lines.join('\n')}\n';
pubspec.writeAsStringSync(pubspecLinesAsString);
}
}