[homebrew] Add support for the beta channel formula

This changes to the homebrew tooling to modify instead of replace the
formulas. The formulas can now be changed by editing them in the
dart-lang/homebrew-dart repo, rather than in the release tooling.

Also adds a dry-run mode for local testing.

https://github.com/dart-lang/sdk/issues/40990

Change-Id: Ibed90894800e4d84fac3c4558e6f08d629baed62
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/141244
Reviewed-by: William Hesse <whesse@google.com>
This commit is contained in:
Alexander Thomas 2020-03-27 14:17:40 +00:00
parent 050c7e1ac7
commit a96b4f6dfe
3 changed files with 56 additions and 171 deletions

View file

@ -10,14 +10,17 @@ import 'package:update_homebrew/update_homebrew.dart';
void main(List<String> args) async { void main(List<String> args) async {
final parser = ArgParser() final parser = ArgParser()
..addFlag('dry-run', abbr: 'n')
..addOption('revision', abbr: 'r') ..addOption('revision', abbr: 'r')
..addOption('channel', abbr: 'c', allowed: supportedChannels) ..addOption('channel', abbr: 'c', allowed: supportedChannels)
..addOption('key', abbr: 'k'); ..addOption('key', abbr: 'k');
final options = parser.parse(args); final options = parser.parse(args);
final dryRun = options['dry-run'] as bool;
final revision = options['revision'] as String; final revision = options['revision'] as String;
final channel = options['channel'] as String; final channel = options['channel'] as String;
if ([revision, channel].contains(null)) { if ([revision, channel].contains(null)) {
print("Usage: update_homebrew.dart -r revision -c channel [-k ssh_key]\n" print(
"Usage: update_homebrew.dart -r version -c channel [-k ssh_key] [-n]\n"
" ssh_key should allow pushes to $githubRepo on github"); " ssh_key should allow pushes to $githubRepo on github");
exitCode = 1; exitCode = 1;
return; return;
@ -46,8 +49,11 @@ void main(List<String> args) async {
'-m', '-m',
'Updated $channel branch to revision $revision' 'Updated $channel branch to revision $revision'
], repository, gitEnvironment); ], repository, gitEnvironment);
if (dryRun) {
await runGit(['push'], repository, gitEnvironment); await runGit(['diff', 'origin/master'], repository, gitEnvironment);
} else {
await runGit(['push'], repository, gitEnvironment);
}
} finally { } finally {
await tempDir.delete(recursive: true); await tempDir.delete(recursive: true);
} }

View file

@ -1,30 +1,23 @@
part of '../update_homebrew.dart'; part of '../update_homebrew.dart';
const _files = { const _files = [
'dev': [_x64Files, _ia32Files], 'dartsdk-macos-x64-release.zip',
'stable': [_x64Files, _ia32Files] 'dartsdk-linux-x64-release.zip',
}; 'dartsdk-linux-arm64-release.zip',
'dartsdk-linux-ia32-release.zip',
'dartsdk-linux-arm-release.zip',
];
const _urlBase = 'https://storage.googleapis.com/dart-archive/channels'; const _host = 'https://storage.googleapis.com/dart-archive/channels';
const _x64Files = {
'mac': 'sdk/dartsdk-macos-x64-release.zip',
'linux': 'sdk/dartsdk-linux-x64-release.zip',
'linux-arm': 'sdk/dartsdk-linux-arm64-release.zip',
};
const _ia32Files = {
'linux': 'sdk/dartsdk-linux-ia32-release.zip',
'linux-arm': 'sdk/dartsdk-linux-arm-release.zip',
};
Future<String> _getHash256( Future<String> _getHash256(
String channel, String revision, String download) async { String channel, String version, String download) async {
var client = http.Client(); var client = http.Client();
try { try {
var api = storage.StorageApi(client); var api = storage.StorageApi(client);
var media = await api.objects.get('dart-archive', var url = 'channels/$channel/release/$version/sdk/$download.sha256sum';
'channels/$channel/release/$revision/$download.sha256sum', var media = await api.objects.get('dart-archive', url,
downloadOptions: DownloadOptions.FullMedia) as Media; downloadOptions: DownloadOptions.FullMedia) as Media;
var hashLine = await ascii.decodeStream(media.stream); var hashLine = await ascii.decodeStream(media.stream);
return RegExp('[0-9a-fA-F]*').stringMatch(hashLine); return RegExp('[0-9a-fA-F]*').stringMatch(hashLine);
} finally { } finally {
@ -32,138 +25,32 @@ Future<String> _getHash256(
} }
} }
Future<String> _getVersion(String channel, String revision) async { Future<void> _updateFormula(String channel, File file, String version,
var client = http.Client(); Map<String, String> hashes) async {
try { // Extract files and hashes that are stored in the formula in this format:
var api = storage.StorageApi(client); // url "<url base>/<channel>/release/<version>/sdk/<artifact>.zip"
// sha256 "<hash>"
var media = await api.objects.get( var filesAndHashes = RegExp(
'dart-archive', 'channels/$channel/release/$revision/VERSION', 'channels/$channel/release' +
downloadOptions: DownloadOptions.FullMedia) as Media; r'/(\d[\w\d\-\.]*)/sdk/([\w\d\-\.]+)\"\n(\s+)sha256 \"[\da-f]+\"',
multiLine: true);
var versionObject = var contents = await file.readAsString();
await json.fuse(ascii).decoder.bind(media.stream).first as Map; contents = contents.replaceAllMapped(filesAndHashes, (m) {
return versionObject['version'] as String; var currentVersion = m.group(1);
} finally { if (currentVersion == version) {
client.close(); throw new ArgumentError(
} "Channel $channel is already at version $version in homebrew.");
}
Future<Map<String, String>> _getCurrentRevisions(String repository) async {
var revisions = <String, String>{};
var lines = await (File(p.join(repository, dartRbFileName))).readAsLines();
for (var channel in supportedChannels) {
/// This RegExp between release/ and /sdk matches
/// * 1 digit followed by
/// * Any number of letters, numbers, dashes and dots
/// This covers both numeric- and version-formatted revisions
///
/// Note: all of the regexp escape slashes `\` are double-escaped within the
/// Dart string
final regExp = RegExp('channels/$channel/release/(\\d[\\w\\d\\-\\.]*)/sdk');
revisions[channel] =
regExp.firstMatch(lines.firstWhere(regExp.hasMatch)).group(1);
}
return revisions;
}
Future<Map<String, Map>> _getHashes(Map<String, String> revisions) async {
var hashes = <String, Map>{};
for (var channel in supportedChannels) {
hashes[channel] = {};
for (var files in _files[channel]) {
for (var file in files.values) {
var hash = await _getHash256(channel, revisions[channel], file);
hashes[channel][file] = hash;
}
} }
} var artifact = m.group(2);
return hashes; var indent = m.group(3);
return 'channels/$channel/release/$version/sdk/$artifact"\n'
'${indent}sha256 "${hashes[artifact]}"';
});
await file.writeAsString(contents, flush: true);
} }
String _createDartFormula( Future<Map<String, String>> _getHashes(String channel, String version) async {
Map revisions, Map hashes, String devVersion, String stableVersion) => return <String, String>{
''' for (var file in _files) file: await _getHash256(channel, version, file)
class Dart < Formula };
desc "The Dart SDK" }
homepage "https://dart.dev"
version "$stableVersion"
if OS.mac?
url "$_urlBase/stable/release/${revisions['stable']}/${_x64Files['mac']}"
sha256 "${hashes['stable'][_x64Files['mac']]}"
elsif OS.linux? && Hardware::CPU.intel?
if Hardware::CPU.is_64_bit?
url "$_urlBase/stable/release/${revisions['stable']}/${_x64Files['linux']}"
sha256 "${hashes['stable'][_x64Files['linux']]}"
else
url "$_urlBase/stable/release/${revisions['stable']}/${_ia32Files['linux']}"
sha256 "${hashes['stable'][_ia32Files['linux']]}"
end
elsif OS.linux? && Hardware::CPU.arm?
if Hardware::CPU.is_64_bit?
url "$_urlBase/stable/release/${revisions['stable']}/${_x64Files['linux-arm']}"
sha256 "${hashes['stable'][_x64Files['linux-arm']]}"
else
url "$_urlBase/stable/release/${revisions['stable']}/${_ia32Files['linux-arm']}"
sha256 "${hashes['stable'][_ia32Files['linux-arm']]}"
end
end
devel do
version "$devVersion"
if OS.mac?
url "$_urlBase/dev/release/${revisions['dev']}/${_x64Files['mac']}"
sha256 "${hashes['dev'][_x64Files['mac']]}"
elsif OS.linux? && Hardware::CPU.intel?
if Hardware::CPU.is_64_bit?
url "$_urlBase/dev/release/${revisions['dev']}/${_x64Files['linux']}"
sha256 "${hashes['dev'][_x64Files['linux']]}"
else
url "$_urlBase/dev/release/${revisions['dev']}/${_ia32Files['linux']}"
sha256 "${hashes['dev'][_ia32Files['linux']]}"
end
elsif OS.linux? && Hardware::CPU.arm?
if Hardware::CPU.is_64_bit?
url "$_urlBase/dev/release/${revisions['dev']}/${_x64Files['linux-arm']}"
sha256 "${hashes['dev'][_x64Files['linux-arm']]}"
else
url "$_urlBase/dev/release/${revisions['dev']}/${_ia32Files['linux-arm']}"
sha256 "${hashes['dev'][_ia32Files['linux-arm']]}"
end
end
end
def install
libexec.install Dir["*"]
bin.install_symlink "#{libexec}/bin/dart"
bin.write_exec_script Dir["#{libexec}/bin/{pub,dart?*}"]
end
def shim_script(target)
<<~EOS
#!/usr/bin/env bash
exec "#{prefix}/#{target}" "\$@"
EOS
end
def caveats
<<~EOS
Please note the path to the Dart SDK:
#{opt_libexec}
EOS
end
test do
(testpath/"sample.dart").write <<~EOS
void main() {
print(r"test message");
}
EOS
assert_equal "test message\\n", shell_output("#{bin}/dart sample.dart")
end
end
''';

View file

@ -11,27 +11,19 @@ part 'src/impl.dart';
const githubRepo = 'dart-lang/homebrew-dart'; const githubRepo = 'dart-lang/homebrew-dart';
const dartRbFileName = 'dart.rb'; const formulaByChannel = {
'beta': 'dart-beta.rb',
'dev': 'dart.rb',
'stable': 'dart.rb'
};
Iterable<String> get supportedChannels => _files.keys; Iterable<String> get supportedChannels => formulaByChannel.keys;
Future<void> writeHomebrewInfo( Future<void> writeHomebrewInfo(
String channel, String revision, String repository) async { String channel, String version, String repository) async {
var revisions = await _getCurrentRevisions(repository); var formula = File(p.join(repository, formulaByChannel[channel]));
var hashes = await _getHashes(channel, version);
if (revisions[channel] == revision) { await _updateFormula(channel, formula, version, hashes);
print("Channel $channel is already at revision $revision in homebrew.");
exit(0);
}
revisions[channel] = revision;
var hashes = await _getHashes(revisions);
var devVersion = await _getVersion('dev', revisions['dev']);
var stableVersion = await _getVersion('stable', revisions['stable']);
await File(p.join(repository, dartRbFileName)).writeAsString(
_createDartFormula(revisions, hashes, devVersion, stableVersion),
flush: true);
} }
Future<void> runGit(List<String> args, String repository, Future<void> runGit(List<String> args, String repository,