mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-26 20:24:42 +01:00
Merge pull request #2 from sass/dummy
Add a very basic protocol dispatcher and a few tests
This commit is contained in:
commit
6ccc5c3892
18
.travis.yml
18
.travis.yml
@ -9,6 +9,24 @@ cache:
|
||||
directories:
|
||||
- $HOME/.pub-cache
|
||||
|
||||
env:
|
||||
global:
|
||||
- PROTOBUF_VERSION=3.10.1
|
||||
- PATH="$HOME/protoc/bin:$PATH"
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" = windows ]]; then
|
||||
curl -Lo protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-win64.zip;
|
||||
else
|
||||
curl -Lo protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-${TRAVIS_OS_NAME}-x86_64.zip;
|
||||
fi
|
||||
- unzip -d "$HOME/protoc" protoc.zip
|
||||
|
||||
before_script:
|
||||
- pub run grinder protobuf
|
||||
# Format the generated code or else the formatter task will get upset.
|
||||
- dartfmt -w --fix lib/src/embedded_sass.pb*
|
||||
|
||||
jobs:
|
||||
include:
|
||||
|
||||
|
31
bin/dart_sass_embedded.dart
Normal file
31
bin/dart_sass_embedded.dart
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2019 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
import 'package:sass_embedded/src/dispatcher.dart';
|
||||
import 'package:sass_embedded/src/embedded_sass.pb.dart';
|
||||
import 'package:sass_embedded/src/util/length_delimited_transformer.dart';
|
||||
|
||||
void main(List<String> args) {
|
||||
if (args.isNotEmpty) {
|
||||
stderr.writeln(
|
||||
"This executable is not intended to be executed with arguments.\n"
|
||||
"See https://github.com/sass/embedded-protocol#readme for details.");
|
||||
// USAGE error from https://bit.ly/2poTt90
|
||||
exitCode = 64;
|
||||
return;
|
||||
}
|
||||
|
||||
var dispatcher = Dispatcher(
|
||||
StreamChannel.withGuarantees(stdin, stdout, allowSinkErrors: false)
|
||||
.transform(lengthDelimited));
|
||||
dispatcher.listen((request) {
|
||||
return OutboundMessage_CompileResponse()
|
||||
..success = (OutboundMessage_CompileResponse_CompileSuccess()
|
||||
..css = "a {\n b: c;\n}\n");
|
||||
});
|
||||
}
|
120
lib/src/dispatcher.dart
Normal file
120
lib/src/dispatcher.dart
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright 2019 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:stack_trace/stack_trace.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
import 'embedded_sass.pb.dart';
|
||||
|
||||
/// A class that dispatches messages to and from the host.
|
||||
class Dispatcher {
|
||||
/// The channel of encoded protocol buffers, connected to the host.
|
||||
final StreamChannel<Uint8List> _channel;
|
||||
|
||||
/// Creates a [Dispatcher] that sends and receives encoded protocol buffers
|
||||
/// over [channel].
|
||||
Dispatcher(this._channel);
|
||||
|
||||
/// Listens for incoming `CompileRequests` and passes them to [callback].
|
||||
///
|
||||
/// The callback must return a `CompileResponse` which is sent to the host. It
|
||||
/// doesn't need to set [OutboundMessage_CompileResponse.id]; the [Dispatcher]
|
||||
/// will take care of that.
|
||||
///
|
||||
/// This may only be called once.
|
||||
void listen(
|
||||
FutureOr<OutboundMessage_CompileResponse> callback(
|
||||
InboundMessage_CompileRequest request)) {
|
||||
_channel.stream.listen((binaryMessage) async {
|
||||
InboundMessage message;
|
||||
try {
|
||||
try {
|
||||
message = InboundMessage.fromBuffer(binaryMessage);
|
||||
} on InvalidProtocolBufferException catch (error) {
|
||||
throw _parseError(error.message);
|
||||
}
|
||||
|
||||
switch (message.whichMessage()) {
|
||||
case InboundMessage_Message.error:
|
||||
var error = message.ensureError();
|
||||
stderr
|
||||
.write("Host reported ${error.type.name.toLowerCase()} error");
|
||||
if (error.id != -1) stderr.write(" with request ${error.id}");
|
||||
stderr.writeln(": ${error.message}");
|
||||
// SOFTWARE error from https://bit.ly/2poTt90
|
||||
exitCode = 70;
|
||||
_channel.sink.close();
|
||||
break;
|
||||
|
||||
case InboundMessage_Message.compileRequest:
|
||||
var request = message.ensureCompileRequest();
|
||||
var response = await callback(request);
|
||||
response.id = request.id;
|
||||
_send(OutboundMessage()..compileResponse = response);
|
||||
break;
|
||||
|
||||
case InboundMessage_Message.notSet:
|
||||
// PROTOCOL error from https://bit.ly/2poTt90
|
||||
exitCode = 76;
|
||||
throw _parseError("InboundMessage.message is not set.");
|
||||
|
||||
default:
|
||||
// PROTOCOL error from https://bit.ly/2poTt90
|
||||
exitCode = 76;
|
||||
throw _parseError(
|
||||
"Unknown message type: ${message.toDebugString()}");
|
||||
}
|
||||
} on ProtocolError catch (error) {
|
||||
error.id = _messageId(message) ?? -1;
|
||||
stderr.write("Host caused ${error.type.name.toLowerCase()} error");
|
||||
if (error.id != -1) stderr.write(" with request ${error.id}");
|
||||
stderr.writeln(": ${error.message}");
|
||||
_send(OutboundMessage()..error = error);
|
||||
} catch (error, stackTrace) {
|
||||
var errorMessage = "$error\n${Chain.forTrace(stackTrace)}";
|
||||
stderr.write("Internal compiler error: $errorMessage");
|
||||
_send(OutboundMessage()
|
||||
..error = (ProtocolError()
|
||||
..type = ProtocolError_ErrorType.INTERNAL
|
||||
..id = _messageId(message) ?? -1
|
||||
..message = errorMessage));
|
||||
_channel.sink.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Sends [message] to the host.
|
||||
void _send(OutboundMessage message) =>
|
||||
_channel.sink.add(message.writeToBuffer());
|
||||
|
||||
/// Returns a [ProtocolError] with type `PARSE` and the given [message].
|
||||
ProtocolError _parseError(String message) => ProtocolError()
|
||||
..type = ProtocolError_ErrorType.PARSE
|
||||
..message = message;
|
||||
|
||||
/// Returns the id for [message] if it it's a request or response, or `null`
|
||||
/// otherwise.
|
||||
int _messageId(InboundMessage message) {
|
||||
if (message == null) return null;
|
||||
switch (message.whichMessage()) {
|
||||
case InboundMessage_Message.compileRequest:
|
||||
return message.ensureCompileRequest().id;
|
||||
case InboundMessage_Message.canonicalizeResponse:
|
||||
return message.ensureCanonicalizeResponse().id;
|
||||
case InboundMessage_Message.importResponse:
|
||||
return message.ensureImportResponse().id;
|
||||
case InboundMessage_Message.functionCallRequest:
|
||||
return message.ensureFunctionCallRequest().id;
|
||||
case InboundMessage_Message.functionCallResponse:
|
||||
return message.ensureFunctionCallResponse().id;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,9 +7,17 @@ homepage: https://github.com/sass/dart-sass-embedded
|
||||
environment:
|
||||
sdk: '>=2.4.0 <3.0.0'
|
||||
|
||||
executables:
|
||||
dart-sass-embedded: dart_sass_embedded
|
||||
|
||||
dependencies:
|
||||
async: ">=1.13.0 <3.0.0"
|
||||
protobuf: ^1.0.0
|
||||
stack_trace: ^1.6.0
|
||||
stream_channel: ">=1.6.0 <3.0.0"
|
||||
|
||||
dev_dependencies:
|
||||
grinder: ^0.8.0
|
||||
protoc_plugin: ^19.0.0
|
||||
path: ^1.6.0
|
||||
test: ^1.0.0
|
||||
|
183
test/embedded_process.dart
Normal file
183
test/embedded_process.dart
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright 2019 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'package:sass_embedded/src/embedded_sass.pb.dart';
|
||||
import 'package:sass_embedded/src/util/length_delimited_transformer.dart';
|
||||
|
||||
/// A wrapper for [Process] that provides a convenient API for testing the
|
||||
/// embedded Sass process.
|
||||
///
|
||||
/// If the test fails, this will automatically print out any stderr and protocol
|
||||
/// buffers from the process to aid debugging.
|
||||
///
|
||||
/// This API is based on the `test_process` package.
|
||||
class EmbeddedProcess {
|
||||
/// The underlying process.
|
||||
final Process _process;
|
||||
|
||||
/// A [StreamQueue] that emits each outbound protocol buffer from the process.
|
||||
StreamQueue<OutboundMessage> get outbound => _outbound;
|
||||
StreamQueue<OutboundMessage> _outbound;
|
||||
|
||||
/// A [StreamQueue] that emits each line of stderr from the process.
|
||||
StreamQueue<String> get stderr => _stderr;
|
||||
StreamQueue<String> _stderr;
|
||||
|
||||
/// A splitter that can emit new copies of [outbound].
|
||||
final StreamSplitter<OutboundMessage> _outboundSplitter;
|
||||
|
||||
/// A splitter that can emit new copies of [stderr].
|
||||
final StreamSplitter<String> _stderrSplitter;
|
||||
|
||||
/// A sink into which inbound messages can be passed to the process.
|
||||
final Sink<InboundMessage> inbound;
|
||||
|
||||
/// The raw standard input byte sink.
|
||||
IOSink get stdin => _process.stdin;
|
||||
|
||||
/// A log that includes lines from [stderr] and human-friendly serializations
|
||||
/// of protocol buffers from [outbound]
|
||||
final _log = <String>[];
|
||||
|
||||
/// Whether [_log] has been passed to [printOnFailure] yet.
|
||||
bool _loggedOutput = false;
|
||||
|
||||
/// Returns a [Future] which completes to the exit code of the process, once
|
||||
/// it completes.
|
||||
Future<int> get exitCode => _process.exitCode;
|
||||
|
||||
/// The process ID of the process.
|
||||
int get pid => _process.pid;
|
||||
|
||||
/// Completes to [_process]'s exit code if it's exited, otherwise completes to
|
||||
/// `null` immediately.
|
||||
Future<int> get _exitCodeOrNull async =>
|
||||
await exitCode.timeout(Duration.zero, onTimeout: () => null);
|
||||
|
||||
/// Starts a process.
|
||||
///
|
||||
/// [executable], [workingDirectory], [environment],
|
||||
/// [includeParentEnvironment], and [runInShell] have the same meaning as for
|
||||
/// [Process.start].
|
||||
///
|
||||
/// If [forwardOutput] is `true`, the process's [outbound] messages and
|
||||
/// [stderr] will be printed to the console as they appear. This is only
|
||||
/// intended to be set temporarily to help when debugging test failures.
|
||||
static Future<EmbeddedProcess> start(
|
||||
{String workingDirectory,
|
||||
Map<String, String> environment,
|
||||
bool includeParentEnvironment = true,
|
||||
bool runInShell = false,
|
||||
bool forwardOutput = false}) async {
|
||||
// TODO(nweiz): Support running from a native executable.
|
||||
var process = await Process.start(
|
||||
Platform.executable, ["bin/dart_sass_embedded.dart"],
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
includeParentEnvironment: includeParentEnvironment,
|
||||
runInShell: runInShell);
|
||||
|
||||
return EmbeddedProcess._(process, forwardOutput: forwardOutput);
|
||||
}
|
||||
|
||||
/// Creates a [EmbeddedProcess] for [process].
|
||||
///
|
||||
/// The [forwardOutput] argument is the same as that to [start].
|
||||
EmbeddedProcess._(Process process, {bool forwardOutput = false})
|
||||
: _process = process,
|
||||
_outboundSplitter = StreamSplitter(process.stdout
|
||||
.transform(lengthDelimitedDecoder)
|
||||
.map((message) => OutboundMessage.fromBuffer(message))),
|
||||
_stderrSplitter = StreamSplitter(process.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())),
|
||||
inbound = StreamSinkTransformer<InboundMessage, List<int>>.fromHandlers(
|
||||
handleData: (message, sink) =>
|
||||
sink.add(message.writeToBuffer())).bind(
|
||||
StreamSinkTransformer.fromStreamTransformer(lengthDelimitedEncoder)
|
||||
.bind(process.stdin)) {
|
||||
addTearDown(_tearDown);
|
||||
expect(_process.exitCode.then((_) => _logOutput()), completes,
|
||||
reason: "Process `dart_sass_embedded` never exited.");
|
||||
|
||||
_outbound = StreamQueue(_outboundSplitter.split());
|
||||
_stderr = StreamQueue(_stderrSplitter.split());
|
||||
|
||||
_outboundSplitter.split().listen((message) {
|
||||
for (var line in message.toDebugString().split("\n")) {
|
||||
if (forwardOutput) print(line);
|
||||
_log.add(" $line");
|
||||
}
|
||||
});
|
||||
|
||||
_stderrSplitter.split().listen((line) {
|
||||
if (forwardOutput) print(line);
|
||||
_log.add("[e] $line");
|
||||
});
|
||||
}
|
||||
|
||||
/// A callback that's run when the test completes.
|
||||
Future _tearDown() async {
|
||||
// If the process is already dead, do nothing.
|
||||
if (await _exitCodeOrNull != null) return;
|
||||
|
||||
_process.kill(ProcessSignal.sigkill);
|
||||
|
||||
// Log output now rather than waiting for the exitCode callback so that
|
||||
// it's visible even if we time out waiting for the process to die.
|
||||
await _logOutput();
|
||||
}
|
||||
|
||||
/// Formats the contents of [_log] and passes them to [printOnFailure].
|
||||
Future _logOutput() async {
|
||||
if (_loggedOutput) return;
|
||||
_loggedOutput = true;
|
||||
|
||||
var exitCodeOrNull = await _exitCodeOrNull;
|
||||
|
||||
// Wait a timer tick to ensure that all available lines have been flushed to
|
||||
// [_log].
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
var buffer = StringBuffer();
|
||||
buffer.write("Process `dart_sass_embedded` ");
|
||||
if (exitCodeOrNull == null) {
|
||||
buffer.write("was killed with SIGKILL in a tear-down.");
|
||||
} else {
|
||||
buffer.write("exited with exitCode $exitCodeOrNull.");
|
||||
}
|
||||
buffer.writeln(" Output:");
|
||||
buffer.writeln(_log.join("\n"));
|
||||
|
||||
printOnFailure(buffer.toString());
|
||||
}
|
||||
|
||||
/// Kills the process (with SIGKILL on POSIX operating systems), and returns a
|
||||
/// future that completes once it's dead.
|
||||
///
|
||||
/// If this is called after the process is already dead, it does nothing.
|
||||
Future kill() async {
|
||||
_process.kill(ProcessSignal.sigkill);
|
||||
await exitCode;
|
||||
}
|
||||
|
||||
/// Waits for the process to exit, and verifies that the exit code matches
|
||||
/// [expectedExitCode] (if given).
|
||||
///
|
||||
/// If this is called after the process is already dead, it verifies its
|
||||
/// existing exit code.
|
||||
Future shouldExit([expectedExitCode]) async {
|
||||
var exitCode = await this.exitCode;
|
||||
if (expectedExitCode == null) return;
|
||||
expect(exitCode, expectedExitCode,
|
||||
reason: "Process `dart_sass_embedded` had an unexpected exit code.");
|
||||
}
|
||||
}
|
47
test/protocol_test.dart
Normal file
47
test/protocol_test.dart
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2019 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'package:sass_embedded/src/embedded_sass.pb.dart';
|
||||
|
||||
import 'embedded_process.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
EmbeddedProcess process;
|
||||
setUp(() async {
|
||||
process = await EmbeddedProcess.start();
|
||||
});
|
||||
|
||||
group("gracefully handles a protocol error", () {
|
||||
test("caused by an empty message", () async {
|
||||
process.inbound.add(InboundMessage());
|
||||
await expectParseError(process, "InboundMessage.message is not set.");
|
||||
await process.kill();
|
||||
});
|
||||
|
||||
test("caused by an invalid message", () async {
|
||||
process.stdin.add([1, 0, 0, 0, 0]);
|
||||
await expectParseError(
|
||||
process, "Protocol message contained an invalid tag (zero).");
|
||||
await process.kill();
|
||||
});
|
||||
|
||||
test("without shutting down the compiler", () async {
|
||||
process.inbound.add(InboundMessage());
|
||||
await expectParseError(process, "InboundMessage.message is not set.");
|
||||
|
||||
process.inbound.add(compileString("a {b: c}"));
|
||||
await expectLater(process.outbound, emits(isSuccess("a { b: c; }")));
|
||||
await process.kill();
|
||||
});
|
||||
});
|
||||
|
||||
test("compiles a CSS from a string", () async {
|
||||
process.inbound.add(compileString("a {b: c}"));
|
||||
await expectLater(process.outbound, emits(isSuccess("a { b: c; }")));
|
||||
await process.kill();
|
||||
});
|
||||
}
|
61
test/utils.dart
Normal file
61
test/utils.dart
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2019 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'package:sass_embedded/src/embedded_sass.pb.dart';
|
||||
|
||||
import 'embedded_process.dart';
|
||||
|
||||
/// Returns a [InboundMessage] that compiles the given plain CSS
|
||||
/// string.
|
||||
InboundMessage compileString(String css) => InboundMessage()
|
||||
..compileRequest = (InboundMessage_CompileRequest()
|
||||
..string = (InboundMessage_CompileRequest_StringInput()..source = css));
|
||||
|
||||
/// Asserts that [process] emits a [ProtocolError] parse error with the given
|
||||
/// [message] on its protobuf stream and prints a notice on stderr.
|
||||
Future<void> expectParseError(EmbeddedProcess process, message) async {
|
||||
await expectLater(process.outbound,
|
||||
emits(isProtocolError(-1, ProtocolError_ErrorType.PARSE, message)));
|
||||
await expectLater(process.stderr, emits("Host caused parse error: $message"));
|
||||
}
|
||||
|
||||
/// Asserts that an [OutboundMessage] is a [ProtocolError] with the given [id],
|
||||
/// [type], and optionally [message].
|
||||
Matcher isProtocolError(int id, ProtocolError_ErrorType type, [message]) =>
|
||||
predicate((value) {
|
||||
expect(value, isA<OutboundMessage>());
|
||||
var outboundMessage = value as OutboundMessage;
|
||||
expect(outboundMessage.hasError(), isTrue,
|
||||
reason: "Expected $message to be a ProtocolError");
|
||||
expect(outboundMessage.error.id, equals(id));
|
||||
expect(outboundMessage.error.type, equals(type));
|
||||
if (message != null) expect(outboundMessage.error.message, message);
|
||||
return true;
|
||||
});
|
||||
|
||||
/// Asserts that [message] is an [OutboundMessage] with a [CompileResponse] and
|
||||
/// returns it.
|
||||
OutboundMessage_CompileResponse getCompileResponse(value) {
|
||||
expect(value, isA<OutboundMessage>());
|
||||
var message = value as OutboundMessage;
|
||||
expect(message.hasCompileResponse(), isTrue,
|
||||
reason: "Expected $message to have a CompileResponse");
|
||||
return message.compileResponse;
|
||||
}
|
||||
|
||||
/// Asserts that an [OutboundMessage] is a [CompileResponse] with CSS that
|
||||
/// matches [css].
|
||||
///
|
||||
/// If [css] is a [String], this automatically wraps it in
|
||||
/// [equalsIgnoringWhitespace].
|
||||
Matcher isSuccess(css) => predicate((value) {
|
||||
var response = getCompileResponse(value);
|
||||
expect(response.hasSuccess(), isTrue,
|
||||
reason: "Expected $response to be successful");
|
||||
expect(response.success.css,
|
||||
css is String ? equalsIgnoringWhitespace(css) : css);
|
||||
return true;
|
||||
});
|
43
tool/grind.dart
Normal file
43
tool/grind.dart
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2016 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:grinder/grinder.dart';
|
||||
|
||||
import 'utils.dart';
|
||||
|
||||
main(List<String> args) => grind(args);
|
||||
|
||||
@Task('Compile the protocol buffer definition to a Dart library.')
|
||||
protobuf() async {
|
||||
Directory('build').createSync(recursive: true);
|
||||
|
||||
// Make sure we use the version of protoc_plugin defined by our pubspec,
|
||||
// rather than whatever version the developer might have globally installed.
|
||||
log("Writing protoc-gen-dart");
|
||||
if (Platform.isWindows) {
|
||||
File('build/protoc-gen-dart.bat').writeAsStringSync('''
|
||||
@echo off
|
||||
pub run protoc_plugin %*
|
||||
''');
|
||||
} else {
|
||||
File('build/protoc-gen-dart')
|
||||
.writeAsStringSync('pub run protoc_plugin "\$@"');
|
||||
runProcess('chmod', arguments: ['a+x', 'build/protoc-gen-dart']);
|
||||
}
|
||||
|
||||
await cloneOrPull("git://github.com/sass/embedded-protocol");
|
||||
await runAsync("protoc",
|
||||
arguments: [
|
||||
"-Ibuild/embedded-protocol",
|
||||
"embedded_sass.proto",
|
||||
"--dart_out=lib/src/"
|
||||
],
|
||||
runOptions: RunOptions(environment: {
|
||||
"PATH": 'build' +
|
||||
(Platform.isWindows ? ";" : ":") +
|
||||
Platform.environment["PATH"]
|
||||
}));
|
||||
}
|
42
tool/utils.dart
Normal file
42
tool/utils.dart
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2019 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:grinder/grinder.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// Ensure that the repository at [url] is cloned into the build directory and
|
||||
/// pointing to the latest master revision.
|
||||
///
|
||||
/// Returns the path to the repository.
|
||||
Future<String> cloneOrPull(String url) async =>
|
||||
cloneOrCheckout(url, "origin/master");
|
||||
|
||||
/// Ensure that the repository at [url] is cloned into the build directory and
|
||||
/// pointing to [ref].
|
||||
///
|
||||
/// Returns the path to the repository.
|
||||
Future<String> cloneOrCheckout(String url, String ref) async {
|
||||
var name = p.url.basename(url);
|
||||
if (p.url.extension(name) == ".git") name = p.url.withoutExtension(name);
|
||||
|
||||
var path = p.join("build", name);
|
||||
|
||||
if (Directory(p.join(path, '.git')).existsSync()) {
|
||||
log("Updating $url");
|
||||
await runAsync("git",
|
||||
arguments: ["fetch", "origin"], workingDirectory: path);
|
||||
} else {
|
||||
delete(Directory(path));
|
||||
await runAsync("git", arguments: ["clone", url, path]);
|
||||
await runAsync("git",
|
||||
arguments: ["config", "advice.detachedHead", "false"],
|
||||
workingDirectory: path);
|
||||
}
|
||||
await runAsync("git", arguments: ["checkout", ref], workingDirectory: path);
|
||||
log("");
|
||||
|
||||
return path;
|
||||
}
|
Loading…
Reference in New Issue
Block a user