Initialize SDK and regular projects on creation
Runs either `dart pub get` or `gclient sync`, based on project type. For the SDK, copies the .gclient and .gclient_entries files as well into the parent dir. Also adds a `--timeout` argument, controls how long to wait for analyzer messages. Change-Id: Id2c28e6d0251e94914bb0650be104c1f90a66651 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/468620 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Jake Macdonald <jakemac@google.com> Auto-Submit: Jake Macdonald <jakemac@google.com>
This commit is contained in:
committed by
Commit Queue
parent
ee4ec6c84d
commit
db06bb7f13
@@ -15,6 +15,13 @@ import 'package:collection/collection.dart';
|
||||
import 'log.dart';
|
||||
import 'server_driver.dart';
|
||||
|
||||
/// Some messages from the analysis server should just be ignored.
|
||||
bool _shouldSkip(Message message) =>
|
||||
// The server always sends this but we don't record it.
|
||||
message.method == 'workspace/configuration' ||
|
||||
// This is the response to the initialize request.
|
||||
message.id == 0;
|
||||
|
||||
/// An object used to play back the messages in a log.
|
||||
///
|
||||
/// A reasonable attempt is made to retain the same timing of messages as was
|
||||
@@ -33,7 +40,10 @@ class LogPlayer {
|
||||
/// options from command line arguments.
|
||||
final driverArgParser = Driver.createArgParser();
|
||||
|
||||
LogPlayer({required this.log});
|
||||
/// How long to wait for expected analyzer logs to come back.
|
||||
final Duration timeout;
|
||||
|
||||
LogPlayer({required this.log, this.timeout = const Duration(seconds: 5)});
|
||||
|
||||
/// Plays the log.
|
||||
Future<void> play() async {
|
||||
@@ -73,7 +83,7 @@ class LogPlayer {
|
||||
actualServerMessageIds[foundMessage.id] = message.id;
|
||||
}
|
||||
pendingServerMessageExpectations.remove(foundMessage);
|
||||
} else {
|
||||
} else if (!_shouldSkip(message)) {
|
||||
stderr.writeln(
|
||||
'Unexpected message from analysis server:\n'
|
||||
'${jsonEncode(message)}',
|
||||
@@ -171,7 +181,7 @@ receiver: ${entry.receiver}
|
||||
) async {
|
||||
if (pendingServerMessageExpectations.isEmpty) return;
|
||||
var watch = Stopwatch()..start();
|
||||
while (watch.elapsed < const Duration(seconds: 5)) {
|
||||
while (watch.elapsed < timeout) {
|
||||
if (pendingServerMessageExpectations.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ class GitCloneProjectGenerator implements ProjectGenerator {
|
||||
await runGitCommand(['clone', repo, '.'], outputDir);
|
||||
await runGitCommand(['fetch', 'origin', ref], outputDir);
|
||||
await runGitCommand(['checkout', ref], outputDir);
|
||||
await runPubGet(outputDir);
|
||||
return [outputDir];
|
||||
}
|
||||
|
||||
|
||||
+51
-1
@@ -4,6 +4,8 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../utilities/git.dart';
|
||||
import 'project_generator.dart';
|
||||
|
||||
@@ -16,7 +18,18 @@ class GitWorktreeProjectGenerator implements ProjectGenerator {
|
||||
/// The ref (commit sha, tag, or branch) to check out into a new working tree.
|
||||
final String ref;
|
||||
|
||||
GitWorktreeProjectGenerator(this.originalRepo, this.ref);
|
||||
/// Whether or not this is an SDK repo. If it is, we use gclient and set things
|
||||
/// up a bit differently.
|
||||
final bool isSdkRepo;
|
||||
|
||||
/// The root temp dir to clean up, if it isn't the same as the project dir.
|
||||
Directory? tmpDir;
|
||||
|
||||
GitWorktreeProjectGenerator(
|
||||
this.originalRepo,
|
||||
this.ref, {
|
||||
this.isSdkRepo = false,
|
||||
});
|
||||
|
||||
@override
|
||||
String get description =>
|
||||
@@ -25,12 +38,27 @@ class GitWorktreeProjectGenerator implements ProjectGenerator {
|
||||
@override
|
||||
Future<Iterable<Directory>> setUp() async {
|
||||
var projectDir = await Directory.systemTemp.createTemp('as_git_worktree');
|
||||
if (isSdkRepo) {
|
||||
if (tmpDir != null) {
|
||||
throw StateError(
|
||||
'Project already set up, must wait for tearDown to complete to call '
|
||||
'setUp again',
|
||||
);
|
||||
}
|
||||
tmpDir = projectDir;
|
||||
projectDir = Directory(p.join(projectDir.path, 'sdk'));
|
||||
}
|
||||
await runGitCommand([
|
||||
'worktree',
|
||||
'add',
|
||||
'-d',
|
||||
projectDir.path,
|
||||
], originalRepo);
|
||||
if (isSdkRepo) {
|
||||
await _setUpSdk(projectDir);
|
||||
} else {
|
||||
await runPubGet(projectDir);
|
||||
}
|
||||
return [projectDir];
|
||||
}
|
||||
|
||||
@@ -45,5 +73,27 @@ class GitWorktreeProjectGenerator implements ProjectGenerator {
|
||||
'-f',
|
||||
workspaceDirs.single.path,
|
||||
], originalRepo);
|
||||
await tmpDir?.delete(recursive: true);
|
||||
tmpDir = null;
|
||||
}
|
||||
|
||||
Future<void> _setUpSdk(Directory projectDir) async {
|
||||
print('Running gclient sync in ${projectDir.path}');
|
||||
var newGclientDir = p.dirname(projectDir.path);
|
||||
var oldGclientDir = p.dirname(p.normalize(originalRepo.path));
|
||||
for (var file in ['.gclient', '.gclient_entries']) {
|
||||
await File(p.join(oldGclientDir, file)).copy(p.join(newGclientDir, file));
|
||||
}
|
||||
|
||||
var gclientSyncResult = await Process.run('gclient', [
|
||||
'sync',
|
||||
], workingDirectory: projectDir.path);
|
||||
if (gclientSyncResult.exitCode != 0) {
|
||||
throw StateError(
|
||||
'Failed to run `gclient sync`:\n'
|
||||
'StdOut:\n${gclientSyncResult.stdout}\n'
|
||||
'StdErr:\n${gclientSyncResult.stderr}',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,31 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// Runs `dart pub get` in [projectDir] if it contains a pubspec.
|
||||
//
|
||||
// TODO(jakemac): Support flutter projects and workspaces.
|
||||
Future<void> runPubGet(Directory projectDir) async {
|
||||
var pubspec = File(p.join(p.normalize(projectDir.path), 'pubspec.yaml'));
|
||||
if (pubspec.existsSync()) {
|
||||
print('Fetching dependencies with pub in ${projectDir.path}');
|
||||
var pubGetResult = await Process.run('dart', [
|
||||
'pub',
|
||||
'get',
|
||||
], workingDirectory: projectDir.path);
|
||||
if (pubGetResult.exitCode != 0) {
|
||||
throw StateError(
|
||||
'Failed to run `dart pub get`:\n'
|
||||
'StdOut:\n${pubGetResult.stdout}\n'
|
||||
'StdErr:\n${pubGetResult.stderr}',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
print('No pubspec.yaml found in ${projectDir.path}, skipping `pub get`');
|
||||
}
|
||||
}
|
||||
|
||||
/// A [ProjectGenerator] represents a reproducible way to create a pristine
|
||||
/// copy of a codebase.
|
||||
///
|
||||
|
||||
@@ -23,7 +23,7 @@ void main(List<String> args) async {
|
||||
if (scenarioNames.isNotEmpty && !scenarioNames.contains(scenario.name)) {
|
||||
continue;
|
||||
}
|
||||
await scenario.run();
|
||||
await scenario.run(Duration(seconds: int.parse(parsed.option('timeout')!)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,12 @@ final argParser = ArgParser()
|
||||
help: 'The name(s) of specific scenario(s) to run',
|
||||
allowed: scenarios.map((s) => s.name).toList(),
|
||||
)
|
||||
..addOption(
|
||||
'timeout',
|
||||
abbr: 't',
|
||||
help: 'Number of seconds to wait for analyzer responses',
|
||||
defaultsTo: '5',
|
||||
)
|
||||
..addFlag('help');
|
||||
|
||||
final logsRoot = analysisServerRoot.resolve(
|
||||
@@ -52,7 +58,11 @@ final List<Scenario> scenarios = () {
|
||||
logFile: fileSystem.getFile(
|
||||
logsRoot.resolve('sdk_rename_driver_class.json').toFilePath(),
|
||||
),
|
||||
project: GitWorktreeProjectGenerator(Directory.fromUri(sdkRoot), 'main'),
|
||||
project: GitWorktreeProjectGenerator(
|
||||
Directory.fromUri(sdkRoot),
|
||||
'main',
|
||||
isSdkRepo: true,
|
||||
),
|
||||
),
|
||||
Scenario(
|
||||
name: 'initialize',
|
||||
@@ -67,4 +77,4 @@ final List<Scenario> scenarios = () {
|
||||
];
|
||||
}();
|
||||
|
||||
final sdkRoot = analysisServerRoot.resolve('../../');
|
||||
final sdkRoot = analysisServerRoot.resolve('../../../');
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// for details. 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:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:analyzer/file_system/file_system.dart';
|
||||
@@ -16,42 +17,55 @@ final dartSdkRoot = p.dirname(p.dirname(Platform.resolvedExecutable));
|
||||
/// A [Scenario] represents a combination of a [project] and a [logFile] to
|
||||
/// replay in that project.
|
||||
class Scenario {
|
||||
/// Can be used on the command line to select this scenario.
|
||||
///
|
||||
/// Should be lowercase with underscores and no spaces.
|
||||
final String name;
|
||||
|
||||
/// The log file to replay for this scenario.
|
||||
final File logFile;
|
||||
|
||||
/// Handles project setup.
|
||||
final ProjectGenerator project;
|
||||
|
||||
Scenario({required this.name, required this.logFile, required this.project});
|
||||
|
||||
Future<void> run() async {
|
||||
Future<void> run(Duration timeout) async {
|
||||
var watch = Stopwatch()..start();
|
||||
void log(String message) {
|
||||
print('${watch.elapsed}: $message');
|
||||
}
|
||||
await runZoned(
|
||||
() => _run(timeout),
|
||||
zoneSpecification: ZoneSpecification(
|
||||
print: (_, _, _, message) =>
|
||||
stdout.writeln('${watch.elapsed}: $message'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
log('Initializing scenario for project: ${project.description}');
|
||||
Future<void> _run(Duration timeout) async {
|
||||
print('Initializing scenario for project: ${project.description}');
|
||||
|
||||
log('Setting up project');
|
||||
print('Setting up project');
|
||||
var projectDirs = await project.setUp();
|
||||
|
||||
log('Reading logs');
|
||||
print('Reading logs');
|
||||
var logs = Log.fromFile(logFile, {
|
||||
for (var i = 0; i < projectDirs.length; i++)
|
||||
'{{workspaceFolder-$i}}': projectDirs.elementAt(i).path,
|
||||
'{{dartSdkRoot}}': dartSdkRoot,
|
||||
});
|
||||
|
||||
log('Creating log player');
|
||||
var logPlayer = LogPlayer(log: logs);
|
||||
print('Creating log player');
|
||||
var logPlayer = LogPlayer(log: logs, timeout: timeout);
|
||||
|
||||
log(
|
||||
print(
|
||||
'Scenario initialized with workpace dirs:\n'
|
||||
'${projectDirs.map((dir) => ' - ${dir.path}').join('\n')}',
|
||||
);
|
||||
try {
|
||||
var scenarioWatch = Stopwatch()..start();
|
||||
log('Replaying scenario');
|
||||
print('Replaying scenario');
|
||||
await logPlayer.play();
|
||||
log('Scenario completed, took ${scenarioWatch.elapsed} to replay');
|
||||
print('Scenario completed, took ${scenarioWatch.elapsed} to replay');
|
||||
} catch (e, s) {
|
||||
print('''
|
||||
Scenario failed with Error: $e
|
||||
@@ -60,9 +74,9 @@ StackTrace:
|
||||
$s
|
||||
''');
|
||||
} finally {
|
||||
log('Tearing down scenario for project');
|
||||
print('Tearing down scenario for project');
|
||||
await project.tearDown(projectDirs);
|
||||
log('Scenario cleaned up');
|
||||
print('Scenario cleaned up');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user