#!/usr/bin/env dart // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file // 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. // Find the newest commit that has a full set of results on the bots. import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:args/args.dart'; import 'package:glob/glob.dart'; void main(List args) async { final parser = new ArgParser(); parser.addMultiOption("bot", abbr: "b", help: "Select the bots matching the glob pattern [option is repeatable]", splitCommas: false); parser.addOption("branch", abbr: "B", help: "Select the bots building this branch", defaultsTo: "master"); parser.addOption("count", abbr: "c", help: "List this many commits", defaultsTo: "1"); parser.addFlag("help", help: "Show the program usage.", negatable: false); final options = parser.parse(args); if (options["help"]) { print(""" Usage: find_base_commit.dart [OPTION]... Find the newest commit that has a full set of results on the bots. The options are as follows: ${parser.usage}"""); return; } int count = int.parse(options["count"]); final globs = new List.from( options["bot"].map((String pattern) => new Glob(pattern))); // Download the most recent builds from buildbucket. int maxBuilds = 1000; final url = Uri.parse( "https://cr-buildbucket.appspot.com/_ah/api/buildbucket/v1/search" "?bucket=luci.dart.ci.sandbox" "&max_builds=$maxBuilds" "&status=COMPLETED" "&fields=builds(url%2Cparameters_json)"); const maxRetries = 3; const timeout = const Duration(seconds: 30); Map object; for (int i = 1; i <= maxRetries; i++) { try { final client = new HttpClient(); final request = await client.getUrl(url).timeout(timeout); final response = await request.close().timeout(timeout); object = await response .transform(new Utf8Decoder()) .transform(new JsonDecoder()) .first .timeout(timeout); client.close(); break; } on TimeoutException catch (e) { final inSeconds = e.duration.inSeconds; stderr.writeln( "Attempt $i of $maxRetries timed out after $inSeconds seconds"); if (i == maxRetries) { stderr.writeln("error: Failed to download $url"); exit(1); } } } // Locate the builds we're interested in and map them to each commit. The // builds returned by the API are sorted with the newest first. Since bots // don't build back in time and always build the latest commit whenever they // can, the first time we see a commit, we know it's newer than all commits // we haven't seen yet. The insertion order into the botsForCommits map will // then sorted with the newest commit first. final builds = object["builds"]; final botsForCommits = >{}; for (final build in builds) { final parameters = jsonDecode(build["parameters_json"]); final bot = parameters["builder_name"]; if (bot.endsWith("-dev") || bot.endsWith("-stable")) { // Ignore the -dev and -stable builders. The -try builders aren't in the // bucket we're reading. continue; } if (globs.isNotEmpty && !globs.any((glob) => glob.matches(bot))) { // Filter way bots we're not interested in. continue; } final properties = parameters["properties"]; final branch = properties["branch"]; if (branch != null && branch != "refs/heads/${options['branch']}") { // Ignore bots that are building the wrong branch. continue; } final commit = properties["revision"]; if (commit == null) { // Ignore bots that aren't commit based, e.g. fuzz-linux. continue; } final botsForCommit = botsForCommits.putIfAbsent(commit, () => new Set()); botsForCommit.add(bot); } if (botsForCommits.isEmpty) { print("Failed to locate any commits having run on the bots"); exitCode = 1; return; } int maxBots = 0; for (final commit in botsForCommits.keys) { maxBots = max(maxBots, botsForCommits[commit].length); } // List commits run on the most bots. for (final commit in botsForCommits.keys .where((commit) => botsForCommits[commit].length == maxBots) .take(count)) { print(commit); } }