| // Copyright 2023 The Flutter Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. | |
| 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); | |
| } | |
| } |