blob: aedb948717ed06db1dbb9cae6fbafff91bba8f9d [file] [log] [blame] [edit]
// Copyright 2022 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:devtools_tool/utils.dart';
import 'package:io/io.dart';
import 'package:path/path.dart' as path;
import 'shared.dart';
const _buildAppFlag = 'build-app';
const _debugServerFlag = 'debug-server';
// TODO(https://github.com/flutter/devtools/issues/7232): Consider using
// AllowAnythingParser instead of manually passing these args through.
const _machineFlag = 'machine';
const _dtdUriFlag = 'dtd-uri';
const _allowEmbeddingFlag = 'allow-embedding';
const _serveWithDartSdkFlag = 'serve-with-dart-sdk';
/// This command builds DevTools in release mode by running the
/// `devtools_tool build` command and then serves DevTools with a locally
/// running DevTools server.
///
/// If the [_buildAppFlag] argument is negated (e.g. --no-build-app), then the
/// DevTools web app will not be rebuilt before serving. The following arguments
/// are ignored if '--no-build-app' is present in the list of arguments passed
/// to this command. All of the following commands are passed along to the
/// `devtools_tool build` command.
///
/// If the [_debugServerFlag] argument is present, the DevTools server will be
/// started with the `--observe` flag. This will allow you to debug and profile
/// the server with a local VM service connection. By default, this will set
/// `--pause-isolates-on-start` and `--pause-isolates-on-unhandled-exceptions`
/// for the DevTools server VM service connection.
///
/// If the [BuildCommandArgs.useFlutterFromPath] argument is present, the
/// Flutter SDK will not be updated to the latest Flutter candidate before
/// building DevTools. Use this flag to save the cost of updating the Flutter
/// SDK when you already have the proper SDK checked out. This is helpful when
/// developing with the DevTools server.
///
/// If the [BuildCommandArgs.updatePerfetto] argument is present, the
/// precompiled bits for Perfetto will be updated from the
/// `devtools_tool update-perfetto` command as part of the DevTools build
/// process.
///
/// If [BuildCommandArgs.pubGet] argument is negated (e.g. --no-pub-get), then
/// `devtools_tool pub-get --only-main` command will not be run before building
/// the DevTools web app. Use this flag to save the cost of updating pub
/// packages if your pub cahce does not need to be updated. This is helpful when
/// developing with the DevTools server.
///
/// The [BuildCommandArgs.buildMode] argument specifies the Flutter build mode
/// that the DevTools web app will be built in ('release', 'profile', 'debug').
/// This defaults to 'release' if unspecified.
class ServeCommand extends Command {
ServeCommand() {
argParser
..addFlag(
_buildAppFlag,
negatable: true,
defaultsTo: true,
help:
'Whether to build the DevTools web app before starting the DevTools'
' server.',
)
..addFlag(
_debugServerFlag,
negatable: false,
defaultsTo: false,
help: 'Enable debugging for the DevTools server.',
)
..addUpdateFlutterFlag()
..addUpdatePerfettoFlag()
..addPubGetFlag()
..addBulidModeOption()
..addWasmFlag()
..addNoStripWasmFlag()
// Flags defined in the server in DDS.
..addFlag(
_machineFlag,
negatable: false,
help: 'Sets output format to JSON for consumption in tools.',
)
..addOption(
_dtdUriFlag,
help: 'Sets the dtd uri when starting the devtools server',
)
..addFlag(
_allowEmbeddingFlag,
help: 'Allow embedding DevTools inside an iframe.',
)
..addOption(
_serveWithDartSdkFlag,
help: 'Uses the specified Dart SDK to serve the DevTools server',
valueHelp:
'/Users/me/absolute_path_to/sdk/xcodebuild/ReleaseX64/dart-sdk/bin/dart',
);
}
@override
String get name => 'serve';
@override
String get description =>
'Builds DevTools in release mode and serves the web app with a locally '
'running DevTools server.';
@override
Future run() async {
logStatus(
'WARNING: if you have local changes in packages/devtools_shared, you will'
' need to add a path dependency override in sdk/pkg/dds/pubspec.yaml in'
' order for these changes to be picked up.',
);
final repo = DevToolsRepo.getInstance();
final processManager = ProcessManager();
final results = argResults!;
final buildApp = results[_buildAppFlag] as bool;
final debugServer = results[_debugServerFlag] as bool;
final updateFlutter =
results[BuildCommandArgs.updateFlutter.flagName] as bool;
final updatePerfetto =
results[BuildCommandArgs.updatePerfetto.flagName] as bool;
final useWasm = results[BuildCommandArgs.wasm.flagName] as bool;
final noStripWasm = results[BuildCommandArgs.noStripWasm.flagName] as bool;
final runPubGet = results[BuildCommandArgs.pubGet.flagName] as bool;
final devToolsAppBuildMode =
results[BuildCommandArgs.buildMode.flagName] as String;
final serveWithDartSdk = results[_serveWithDartSdkFlag] as String?;
// Any flag that we aren't removing here is intended to be passed through.
final remainingArguments = List.of(results.arguments)
..remove(BuildCommandArgs.updateFlutter.asArg())
..remove(BuildCommandArgs.updateFlutter.asArg(negated: true))
..remove(BuildCommandArgs.updatePerfetto.asArg())
..remove(BuildCommandArgs.wasm.asArg())
..remove(BuildCommandArgs.noStripWasm.asArg())
..remove(valueAsArg(_buildAppFlag))
..remove(valueAsArg(_buildAppFlag, negated: true))
..remove(valueAsArg(_debugServerFlag))
..remove(BuildCommandArgs.pubGet.asArg())
..remove(BuildCommandArgs.pubGet.asArg(negated: true))
..removeWhere(
(element) => element.startsWith(BuildCommandArgs.buildMode.asArg()),
)
..removeWhere(
(element) => element.startsWith(valueAsArg(_serveWithDartSdkFlag)),
);
final localDartSdkLocation = Platform.environment['LOCAL_DART_SDK'];
if (localDartSdkLocation == null) {
throw Exception('LOCAL_DART_SDK environment variable not set. Please add '
'the following to your \'.bash_profile\' or \'.bashrc\' file:\n'
'export LOCAL_DART_SDK=<absolute/path/to/my/sdk>');
}
// Validate the path looks correct in case it was set without the /sdk or
// similar.
final pkgDir = Directory(path.join(localDartSdkLocation, 'pkg'));
if (!pkgDir.existsSync()) {
throw Exception(
'No pkg directory found in LOCAL_DART_SDK at "${pkgDir.path}"\n'
'Is LOCAL_DART_SDK set correctly to the "sdk" directory?',
);
}
final devToolsBuildLocation =
path.join(repo.devtoolsAppDirectoryPath, 'build', 'web');
if (buildApp) {
final process = await processManager.runProcess(
CliCommand.tool([
'build',
BuildCommandArgs.updateFlutter.asArg(negated: !updateFlutter),
if (updatePerfetto) BuildCommandArgs.updatePerfetto.asArg(),
if (useWasm) BuildCommandArgs.wasm.asArg(),
if (noStripWasm) BuildCommandArgs.noStripWasm.asArg(),
'${BuildCommandArgs.buildMode.asArg()}=$devToolsAppBuildMode',
BuildCommandArgs.pubGet.asArg(negated: !runPubGet),
]),
);
if (process.exitCode == 1) {
throw Exception(
'Something went wrong while running `devtools_tool build`',
);
}
logStatus('completed building DevTools: $devToolsBuildLocation');
}
logStatus('running pub get for DDS in the local dart sdk');
await processManager.runProcess(
CliCommand.dart(['pub', 'get']),
workingDirectory: path.join(localDartSdkLocation, 'pkg', 'dds'),
);
logStatus('serving DevTools with a local devtools server...');
final serveLocalScriptPath = path.join(
'pkg',
'dds',
'tool',
'devtools_server',
'serve_local.dart',
);
// This call will not exit until explicitly terminated by the user.
await processManager.runProcess(
CliCommand.dart(
[
if (debugServer) ...[
'run',
'--observe=0',
],
serveLocalScriptPath,
'--devtools-build=$devToolsBuildLocation',
// Pass any args that were provided to our script along. This allows IDEs
// to pass `--machine` (etc.) so that this script can behave the same as
// the "dart devtools" command for testing local DevTools/server changes.
...remainingArguments,
],
sdkOverride: serveWithDartSdk,
),
workingDirectory: localDartSdkLocation,
);
}
}