blob: 47a05822c40147395c9543564018511881ce1ff4 [file] [log] [blame]
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
void main(List<String> args) async {
// Validate we're running from the repo root.
if (!File('README.dart-sdk').existsSync() || !File('DEPS').existsSync()) {
stderr.writeln('Please run this script from the root of the SDK repo.');
exit(1);
}
final gclient = GClientHelper();
final deps = await gclient.getPackageDependencies();
print('${deps.length} package dependencies found.');
// Remove pinned deps.
final pinnedDeps = calculatePinnedDeps();
deps.removeWhere((dep) => pinnedDeps.contains(dep.name));
print('Not attempting to move forward the revisions for: '
'${pinnedDeps.toList().join(', ')}.');
print('');
deps.sort((a, b) => a.name.compareTo(b.name));
for (var dep in deps) {
final git = GitHelper(dep.relativePath);
await git.fetch();
var commit = await git.findLatestUnsyncedCommit();
if (commit.isNotEmpty) {
var gitLog = await git.calculateUnsyncedCommits();
var currentHash = await gclient.getHash(dep);
// Construct the github diff URL.
print('${dep.name} (${dep.getGithubDiffUrl(currentHash, commit)}):');
// Print out the new commits.
print(gitLog.split('\n').map((l) => ' $l').join('\n').trimRight());
// Update the DEPS file.
await gclient.setHash(dep, commit);
print('');
}
}
}
// By convention, pinned deps are deps with an eol comment.
Set<String> calculatePinnedDeps() {
final packageRevision = RegExp(r'"(\w+)_rev":');
// "markdown_rev": "e3f4bd28c9...cfeccd83ee", # b/236358256
var depsFile = File('DEPS');
return depsFile
.readAsLinesSync()
.where((line) => packageRevision.hasMatch(line) && line.contains('", #'))
.map((line) => packageRevision.firstMatch(line)!.group(1)!)
.toSet();
}
class GitHelper {
final String dir;
GitHelper(this.dir);
Future<String> fetch() {
return exec(['git', 'fetch'], cwd: dir);
}
Future<String> findLatestUnsyncedCommit() async {
// git log HEAD..origin --format=%H -1
var result = await exec(
[
'git',
'log',
'HEAD..origin',
'--format=%H',
'-1',
],
cwd: dir,
);
return result.trim();
}
Future<String> calculateUnsyncedCommits() async {
// git log HEAD..origin --format="%h %ad %aN %s" -1
var result = await exec(
[
'git',
'log',
'HEAD..origin',
'--format=%h %ad %aN %s',
],
cwd: dir,
);
return result.trim();
}
}
class GClientHelper {
Future<List<PackageDependency>> getPackageDependencies() async {
// gclient revinfo --output-json=<file> --ignore-dep-type=cipd
final tempDir = Directory.systemTemp.createTempSync();
final outFile = File(path.join(tempDir.path, 'deps.json'));
await exec([
'gclient',
'revinfo',
'--output-json=${outFile.path}',
'--ignore-dep-type=cipd',
]);
Map<String, dynamic> m = jsonDecode(outFile.readAsStringSync());
tempDir.deleteSync(recursive: true);
return m.entries.map((entry) {
return PackageDependency(
entry: entry.key,
url: (entry.value as Map)['url'],
rev: (entry.value as Map)['rev'],
);
}).where((PackageDependency deps) {
return deps.entry.startsWith('sdk/third_party/pkg/');
}).toList();
}
Future<String> getHash(PackageDependency dep) async {
// DEPOT_TOOLS_UPDATE=0 gclient getdep --var=path_rev
var depName = dep.name;
var result = await exec(
[
'gclient',
'getdep',
'--var=${depName}_rev',
],
environment: {
'DEPOT_TOOLS_UPDATE': '0',
},
);
return result.trim();
}
Future<String> setHash(PackageDependency dep, String hash) async {
// gclient setdep --var=args_rev=9879dsf7g9d87d9f8g7
var depName = dep.name;
return await exec(
[
'gclient',
'setdep',
'--var=${depName}_rev=$hash',
],
environment: {
'DEPOT_TOOLS_UPDATE': '0',
},
);
}
}
class PackageDependency {
final String entry;
final String url;
final String? rev;
PackageDependency({
required this.entry,
required this.url,
required this.rev,
});
String get name => entry.substring(entry.lastIndexOf('/') + 1);
String get relativePath => entry.substring('sdk/'.length);
String getGithubDiffUrl(String fromCommit, String toCommit) {
// https://github.com/dart-lang/<repo>/compare/<old>..<new>
final from = fromCommit.substring(0, 7);
final to = toCommit.substring(0, 7);
var repo = url.substring(url.lastIndexOf('/') + 1);
if (repo.endsWith('git')) {
repo = repo.substring(0, repo.length - '.git'.length);
}
var org = 'dart-lang';
if (url.contains('/external/')) {
// https://dart.googlesource.com/external/github.com/google/webdriver.dart.git
final parts = url.split('/');
org = parts[parts.length - 2];
}
// TODO(devoncarew): Eliminate this special-casing; see #48830.
const orgOverrides = {
'platform.dart': 'google',
};
if (orgOverrides.containsKey(repo)) {
org = orgOverrides[repo]!;
}
return 'https://github.com/$org/$repo/compare/$from..$to';
}
@override
String toString() => '${rev?.substring(0, 8)} $relativePath';
}
Future<String> exec(
List<String> cmd, {
String? cwd,
Map<String, String>? environment,
}) async {
var result = await Process.run(
cmd.first,
cmd.sublist(1),
workingDirectory: cwd,
environment: environment,
);
if (result.exitCode != 0) {
var cwdLocation = cwd == null ? '' : ' ($cwd)';
print('${cmd.join(' ')}$cwdLocation');
if ((result.stdout as String).isNotEmpty) {
stdout.write(result.stdout);
}
if ((result.stderr as String).isNotEmpty) {
stderr.write(result.stderr);
}
exit(1);
}
return result.stdout;
}