Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run cli compilations in parallel dart isolates #2078

Merged
merged 9 commits into from
Sep 9, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 10 additions & 68 deletions bin/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'dart:async';
import 'dart:isolate';

import 'package:path/path.dart' as p;
import 'package:stack_trace/stack_trace.dart';
import 'package:term_glyph/term_glyph.dart' as term_glyph;

import 'package:sass/src/exception.dart';
import 'package:sass/src/executable/compile_stylesheet.dart';
import 'package:sass/src/executable/concurrent.dart';
import 'package:sass/src/executable/options.dart';
import 'package:sass/src/executable/repl.dart';
import 'package:sass/src/executable/watch.dart';
import 'package:sass/src/import_cache.dart';
import 'package:sass/src/io.dart';
import 'package:sass/src/io.dart' as io;
import 'package:sass/src/logger/deprecation_handling.dart';
import 'package:sass/src/stylesheet_graph.dart';
import 'package:sass/src/util/map.dart';
Expand All @@ -26,27 +25,6 @@ import 'package:sass/src/embedded/executable.dart'
as embedded;

Future<void> main(List<String> args) async {
var printedError = false;

// Prints [error] to stderr, along with a preceding newline if anything else
// has been printed to stderr.
//
// If [trace] is passed, its terse representation is printed after the error.
void printError(String error, StackTrace? stackTrace) {
var buffer = StringBuffer();
if (printedError) buffer.writeln();
printedError = true;
buffer.write(error);

if (stackTrace != null) {
buffer.writeln();
buffer.writeln();
buffer.write(Trace.from(stackTrace).terse.toString().trimRight());
}

io.printError(buffer);
}

if (args case ['--embedded', ...var rest]) {
embedded.main(rest);
return;
Expand Down Expand Up @@ -84,37 +62,9 @@ Future<void> main(List<String> args) async {
return;
}

for (var (source, destination) in options.sourcesToDestinations.pairs) {
try {
await compileStylesheet(options, graph, source, destination,
ifModified: options.update);
} on SassException catch (error, stackTrace) {
if (destination != null && !options.emitErrorCss) {
_tryDelete(destination);
}

printError(error.toString(color: options.color),
options.trace ? getTrace(error) ?? stackTrace : null);

// Exit code 65 indicates invalid data per
// https://www.freebsd.org/cgi/man.cgi?query=sysexits.
//
// We let exitCode 66 take precedence for deterministic behavior.
if (exitCode != 66) exitCode = 65;
if (options.stopOnError) return;
} on FileSystemException catch (error, stackTrace) {
var path = error.path;
printError(
path == null
? error.message
: "Error reading ${p.relative(path)}: ${error.message}.",
options.trace ? getTrace(error) ?? stackTrace : null);

// Error 66 indicates no input.
exitCode = 66;
if (options.stopOnError) return;
}
}
await compileStylesheets(
options, graph, options.sourcesToDestinations.pairs,
ifModified: options.update);
} on UsageException catch (error) {
print("${error.message}\n");
print("Usage: sass <input.scss> [output.css]\n"
Expand All @@ -128,8 +78,11 @@ Future<void> main(List<String> args) async {
if (options?.color ?? false) buffer.write('\u001b[0m');
buffer.writeln();
buffer.writeln(error);

printError(buffer.toString(), getTrace(error) ?? stackTrace);
buffer.writeln();
buffer.writeln();
buffer.write(
Trace.from(getTrace(error) ?? stackTrace).terse.toString().trimRight());
printError(buffer);
exitCode = 255;
}
}
Expand All @@ -154,14 +107,3 @@ Future<String> _loadVersion() async {
.split(" ")
.last;
}

/// Delete [path] if it exists and do nothing otherwise.
///
/// This is a separate function to work around dart-lang/sdk#53082.
void _tryDelete(String path) {
try {
deleteFile(path);
} on FileSystemException {
// If the file doesn't exist, that's fine.
}
}
71 changes: 71 additions & 0 deletions lib/src/executable/concurrent.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2023 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 '../io.dart';
import '../stylesheet_graph.dart';
import 'concurrent/js.dart' as c;
import 'concurrent/vm.dart'
// Never load the isolate when compiling to JS.
ntkme marked this conversation as resolved.
Show resolved Hide resolved
if (dart.library.js) 'concurrent/js.dart';
import 'options.dart';

/// Compiles the stylesheets concurrently and returns whether all stylesheets are compiled
/// successfully.
Future<bool> compileStylesheets(
ExecutableOptions options,
StylesheetGraph graph,
Iterable<(String?, String?)> sourcesToDestinationsPairs,
ntkme marked this conversation as resolved.
Show resolved Hide resolved
{bool ifModified = false}) async {
var errorsWithStackTraces = sourcesToDestinationsPairs.length == 1
? [
await c.compileStylesheet(
options,
graph,
sourcesToDestinationsPairs.first.$1,
sourcesToDestinationsPairs.first.$2,
ifModified: ifModified)
]
: await Future.wait([
for (var (source, destination) in sourcesToDestinationsPairs)
compileStylesheet(options, graph, source, destination,
ifModified: ifModified)
], eagerError: options.stopOnError);

var printedError = false;

// Print all errors in deterministic order.
for (var errorWithStackTrace in errorsWithStackTraces) {
if (errorWithStackTrace == null) continue;
var (code, error, stackTrace) = errorWithStackTrace;
switch (code) {
case 65:
// We let exitCode 66 take precedence for deterministic behavior.
if (exitCode != 66) exitCode = code;
case 66:
exitCode = code;
ntkme marked this conversation as resolved.
Show resolved Hide resolved
}
_printError(error, stackTrace, printedError);
printedError = true;
}

return !printedError;
}

// Prints [error] to stderr, along with a preceding newline if anything else
// has been printed to stderr.
//
// If [stackTrace] is passed, it is printed after the error.
void _printError(String error, String? stackTrace, bool printedError) {
var buffer = StringBuffer();
if (printedError) buffer.writeln();
buffer.write(error);
if (stackTrace != null) {
buffer.writeln();
buffer.writeln();
buffer.write(stackTrace);
}
printError(buffer);
}
73 changes: 73 additions & 0 deletions lib/src/executable/concurrent/js.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2023 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 'package:path/path.dart' as p;
import 'package:stack_trace/stack_trace.dart';

import '../compile_stylesheet.dart' as c;
import '../options.dart';
import '../../exception.dart';
import '../../io.dart';
import '../../stylesheet_graph.dart';
import '../../utils.dart';

/// Compiles the stylesheet at [source] to [destination].
///
/// Returns `(exitCode, error, stackTrace)` when error occurs.
ntkme marked this conversation as resolved.
Show resolved Hide resolved
ntkme marked this conversation as resolved.
Show resolved Hide resolved
Future<(int, String, String?)?> compileStylesheet(ExecutableOptions options,
ntkme marked this conversation as resolved.
Show resolved Hide resolved
StylesheetGraph graph, String? source, String? destination,
{bool ifModified = false}) async {
try {
await c.compileStylesheet(options, graph, source, destination,
ifModified: ifModified);
} on SassException catch (error, stackTrace) {
if (destination != null && !options.emitErrorCss) {
_tryDelete(destination);
}
var message = error.toString(color: options.color);

// Exit code 65 indicates invalid data per
// https://www.freebsd.org/cgi/man.cgi?query=sysexits.
return getErrorWithStackTrace(
65, message, options.trace ? getTrace(error) ?? stackTrace : null);
} on FileSystemException catch (error, stackTrace) {
var path = error.path;
var message = path == null
? error.message
: "Error reading ${p.relative(path)}: ${error.message}.";

// Exit code 66 indicates no input.
return getErrorWithStackTrace(
66, message, options.trace ? getTrace(error) ?? stackTrace : null);
}
return null;
}

/// Delete [path] if it exists and do nothing otherwise.
///
/// This is a separate function to work around dart-lang/sdk#53082.
void _tryDelete(String path) {
try {
deleteFile(path);
} on FileSystemException {
// If the file doesn't exist, that's fine.
}
}

// Prints [error] to stderr, along with a preceding newline if anything else
// has been printed to stderr.
//
// If [trace] is passed, its terse representation is printed after the error.
ntkme marked this conversation as resolved.
Show resolved Hide resolved
(int, String, String?) getErrorWithStackTrace(
ntkme marked this conversation as resolved.
Show resolved Hide resolved
int exitCode, String error, StackTrace? stackTrace) {
return (
exitCode,
error,
stackTrace != null
? Trace.from(stackTrace).terse.toString().trimRight()
: null
);
}
30 changes: 30 additions & 0 deletions lib/src/executable/concurrent/vm.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2023 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:isolate';

import 'package:term_glyph/term_glyph.dart' as term_glyph;

import '../options.dart';
import '../../stylesheet_graph.dart';
import 'js.dart' as c;
ntkme marked this conversation as resolved.
Show resolved Hide resolved

/// Compiles the stylesheet at [source] to [destination].
///
/// Runs in a new Dart Isolate, unless [source] is `null`.
Future<(int, String, String?)?> compileStylesheet(ExecutableOptions options,
StylesheetGraph graph, String? source, String? destination,
{bool ifModified = false}) async {
// Reading from stdin does not work properly in dart isolate.
if (source == null) {
return c.compileStylesheet(options, graph, source, destination,
ntkme marked this conversation as resolved.
Show resolved Hide resolved
ifModified: ifModified);
}

return Isolate.run(() {
term_glyph.ascii = !options.unicode;
return c.compileStylesheet(options, graph, source, destination,
ifModified: ifModified);
});
}
Loading