blob: 7e21db92645214140f3be747d569a7f1da9690b7 [file] [log] [blame]
// Copyright (c) 2014, 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.
library update_homebrew;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:googleapis/common/common.dart' show DownloadOptions, Media;
import 'package:googleapis/storage/v1.dart' as storage;
import 'package:http/http.dart' as http;
import 'package:stack_trace/stack_trace.dart';
String repository; // The path to the temporary git checkout of dart-homebrew.
Map gitEnvironment; // Pass a wrapper script for SSH to git in the environment.
const GITHUB_REPO = 'dart-lang/homebrew-dart';
const CHANNELS = const ['dev', 'stable'];
const SDK_FILES = const [
'sdk/dartsdk-macos-x64-release.zip',
'sdk/dartsdk-macos-ia32-release.zip'
];
const DARTIUM_FILES = const [
'dartium/dartium-macos-ia32-release.zip',
'dartium/content_shell-macos-ia32-release.zip'
];
final FILES = []..addAll(SDK_FILES)..addAll(DARTIUM_FILES);
Future<String> getHash256(
String channel, String revision, String download) async {
var client = new http.Client();
try {
var api = new storage.StorageApi(client);
var media = await api.objects.get('dart-archive',
'channels/$channel/release/$revision/$download.sha256sum',
downloadOptions: DownloadOptions.FullMedia);
var hashLine = await ASCII.decodeStream(media.stream);
return new RegExp('[0-9a-fA-F]*').stringMatch(hashLine);
} finally {
client.close();
}
}
Future<String> getVersion(String channel, String revision) async {
var client = new http.Client();
try {
var api = new storage.StorageApi(client);
var media = await api.objects.get(
'dart-archive', 'channels/$channel/release/$revision/VERSION',
downloadOptions: DownloadOptions.FullMedia);
var versionObject = await JSON.fuse(ASCII).decoder.bind(media.stream).first;
return versionObject['version'];
} finally {
client.close();
}
}
Future setCurrentRevisions(Map revisions) async {
var lines = await (new File('$repository/dart.rb')).readAsLines();
for (var channel in CHANNELS) {
/// 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 =
new RegExp('channels/$channel/release/(\\d[\\w\\d\\-\\.]*)/sdk');
revisions[channel] =
regExp.firstMatch(lines.firstWhere(regExp.hasMatch)).group(1);
}
}
Future setHashes(Map revisions, Map hashes) {
List waitOn = [];
for (var channel in CHANNELS) {
hashes[channel] = {};
for (var file in FILES) {
waitOn.add(getHash256(channel, revisions[channel], file).then((hash) {
hashes[channel][file] = hash;
}));
}
}
return Future.wait(waitOn);
}
Future writeHomebrewInfo(String channel, String revision) async {
var revisions = {};
var hashes = {};
await setCurrentRevisions(revisions);
if (revisions[channel] == revision) {
print("Channel $channel is already at revision $revision in homebrew.");
exit(0);
}
revisions[channel] = revision;
await setHashes(revisions, hashes);
var devVersion = await getVersion('dev', revisions['dev']);
var stableVersion = await getVersion('stable', revisions['stable']);
await (new File('$repository/dartium.rb').openWrite()
..write(DartiumFile(revisions, hashes, devVersion, stableVersion))).close();
await (new File('$repository/dart.rb').openWrite()
..write(DartFile(revisions, hashes, devVersion, stableVersion))).close();
}
String DartiumFile(
Map revisions, Map hashes, String devVersion, String stableVersion) {
final urlBase = 'https://storage.googleapis.com/dart-archive/channels';
final dartiumFile = 'dartium/dartium-macos-ia32-release.zip';
final contentShellFile = 'dartium/content_shell-macos-ia32-release.zip';
return '''
require 'formula'
class Dartium < Formula
homepage "https://www.dartlang.org"
version '$stableVersion'
url '$urlBase/stable/release/${revisions['stable']}/$dartiumFile'
sha256 '${hashes['stable'][dartiumFile]}'
devel do
version '$devVersion'
url '$urlBase/dev/release/${revisions['dev']}/$dartiumFile'
sha256 '${hashes['dev'][dartiumFile]}'
resource 'content_shell' do
url '$urlBase/dev/release/${revisions['dev']}/$contentShellFile'
version '$devVersion'
sha256 '${hashes['dev'][contentShellFile]}'
end
end
resource 'content_shell' do
url '$urlBase/stable/release/${revisions['stable']}/$contentShellFile'
version '$stableVersion'
sha256 '${hashes['stable'][contentShellFile]}'
end
def shim_script target
<<-EOS.undent
#!/bin/bash
exec "#{prefix}/#{target}" "\$@"
EOS
end
def install
dartium_binary = 'Chromium.app/Contents/MacOS/Chromium'
prefix.install Dir['*']
(bin+"dartium").write shim_script dartium_binary
content_shell_binary = 'Content Shell.app/Contents/MacOS/Content Shell'
prefix.install resource('content_shell')
(bin+"content_shell").write shim_script content_shell_binary
end
def caveats; <<-EOS.undent
To use with IntelliJ, set the Dartium execute home to:
#{prefix}/Chromium.app
EOS
end
test do
system "#{bin}/dartium"
end
end
''';
}
String DartFile(
Map revisions, Map hashes, String devVersion, String stableVersion) {
final urlBase = 'https://storage.googleapis.com/dart-archive/channels';
final x64File = 'sdk/dartsdk-macos-x64-release.zip';
final ia32File = 'sdk/dartsdk-macos-ia32-release.zip';
return '''
require 'formula'
class Dart < Formula
homepage 'https://www.dartlang.org/'
version '$stableVersion'
if MacOS.prefer_64_bit?
url '$urlBase/stable/release/${revisions['stable']}/$x64File'
sha256 '${hashes['stable'][x64File]}'
else
url '$urlBase/stable/release/${revisions['stable']}/$ia32File'
sha256 '${hashes['stable'][ia32File]}'
end
devel do
version '$devVersion'
if MacOS.prefer_64_bit?
url '$urlBase/dev/release/${revisions['dev']}/$x64File'
sha256 '${hashes['dev'][x64File]}'
else
url '$urlBase/dev/release/${revisions['dev']}/$ia32File'
sha256 '${hashes['dev'][ia32File]}'
end
end
def install
libexec.install Dir['*']
bin.install_symlink "#{libexec}/bin/dart"
bin.write_exec_script Dir["#{libexec}/bin/{pub,docgen,dart?*}"]
end
def caveats; <<-EOS.undent
Please note the path to the Dart SDK:
#{opt_libexec}
EOS
end
test do
(testpath/'sample.dart').write <<-EOS.undent
void main() {
print(r"test message");
}
EOS
assert_equal "test message\\n", shell_output("#{bin}/dart sample.dart")
end
end
''';
}
Future runGit(List<String> args) async {
print("git ${args.join(' ')}");
var result = await Process.run('git', args,
workingDirectory: repository, environment: gitEnvironment);
print(result.stdout);
print(result.stderr);
}
main(args) async {
final parser = new ArgParser()
..addOption('revision', abbr: 'r')
..addOption('channel', abbr: 'c', allowed: ['dev', 'stable'])
..addOption('key', abbr: 'k');
final options = parser.parse(args);
final revision = options['revision'];
final channel = options['channel'];
final key = options['key'];
if ([revision, channel, key].contains(null)) {
print("Usage: update_homebrew.dart -r revision -c channel -k ssh_key\n"
" ssh_key should allow pushes to ${GITHUB_REPO} on github");
return;
}
final sshWrapper = Platform.script.resolve('ssh_with_key').toFilePath();
gitEnvironment = {'GIT_SSH': sshWrapper, 'SSH_KEY_PATH': key};
Chain.capture(() async {
var tempDir = await Directory.systemTemp.createTemp('update_homebrew');
try {
repository = tempDir.path;
await runGit(['clone', 'git@github.com:${GITHUB_REPO}.git', '.']);
await writeHomebrewInfo(channel, revision);
await runGit([
'commit',
'-a',
'-m',
'Updated $channel branch to revision $revision'
]);
await runGit(['push']);
} finally {
await tempDir.delete(recursive: true);
}
}, onError: (error, chain) {
print(error);
print(chain.terse);
});
}