dartle 0.5.1

  • Readme
  • Changelog
  • Example
  • Installing
  • 51

Dartle #

A simple build system (or task runner, really) written in Dart.

Dartle is designed to integrate well with pub and Dart's own build system, but help with automation tasks not covered by other tools.

It is inspired by Gradle and, loosely, Make.

How to use #

Add dartle to your dev_dependencies:

pubspec.yaml

dev_dependencies:
  dartle:

Write a dartle build file #

dartle.dart

import 'package:dartle/dartle.dart';

final allTasks = [
  Task(hello, argsValidator: const ArgsCount.range(min: 0, max: 1)),
  Task(bye, dependsOn: const {'hello'}),
  Task(clean),
];

main(List<String> args) async =>
    run(args, tasks: allTasks.toSet(), defaultTasks: {allTasks[0]});

/// To pass an argument to a task, use a ':' prefix, e.g.:
/// dartle hello :joe
hello(List<String> args) =>
    print("Hello ${args.isEmpty ? 'World' : args[0]}!");

/// If no arguments are expected, use `_` as the function parameter.
bye(_) => print("Bye!");

clean(_) => deleteOutputs(allTasks);

Run your build! #

In dev mode, use dart to run the build file directly:

dart dartle.dart

Notice that all dev_dependencies can be used in your build! And all Dart tools work with it, including the Observatory and debugger, after all this is just plain Dart!

Once you're done making changes to the build file (at least for a while), run it with dartle instead:

  1. Activate dartle (only first time)
pub global activate dartle
  1. Run the build
dartle

This will execute the default tasks in the build file, dartle.dart, which should be located in the working directory, after compiling it to native using dart2native (if available, otherwise it will use dart --snapshot) whenever necessary (i.e. every time a change is made to the build file or pubspec.yaml).

Selecting tasks #

In the examples above, the defaultTasks ran because no argument was provided to Dartle.

To run specific task(s), give them as arguments when invoking dartle:

dartle hello bye

Output:

2019-11-17 14:35:09.597494 - dartle[main] - INFO - Executing 2 tasks out of a total of 3 tasks: 2 tasks selected, 0 due to dependencies
2019-11-17 14:35:09.597619 - dartle[main] - INFO - Running task 'hello'
Hello World!
2019-11-17 14:35:09.597702 - dartle[main] - INFO - Running task 'bye'
Bye!
2019-11-17 14:35:09.597824 - dartle[main] - INFO - Build succeeded in 0 ms

Notice that the dartle executable will cache resources to make builds run faster. It uses the .dartle_tool/ directory, in the working directory, to manage the cache. You should not commit the .dartle_tool/ directory into source control.

To provide arguments to a task, provide the argument immediately following the task invocation, prefixing it with ::

dartle hello :Joe

Prints:

2019-11-17 14:37:26.503476 - dartle[main] - INFO - Executing 1 task out of a total of 3 tasks: 1 task selected, 0 due to dependencies
2019-11-17 14:37:26.503686 - dartle[main] - INFO - Running task 'hello'
Hello Joe!
2019-11-17 14:37:26.504267 - dartle[main] - INFO - Build succeeded in 1 ms

Declaring tasks #

The preferred way to declare a task is by wrapping a top-level function, as shown in the example above.

Basically:

import 'package:dartle/dartle.dart';

final allTasks = {Task(hello)};

main(List<String> args) async => run(args, tasks: allTasks);

hello(_) => print("Hello Dartle!");

This allows the task to run in parallel with other tasks on different Isolates (potentially on different CPU cores).

If that's not important, a lambda can be used, but in such case the task's name must be provided explicitly (because lambdas have no name):

import 'package:dartle/dartle.dart';

final allTasks = {Task((_) => print("Hello Dartle!"), name: 'hello')};

main(List<String> args) async => run(args, tasks: allTasks);

A Task's function should only take arguments if it declares an ArgsValidator, as shown in the example:

Task(hello, argsValidator: const ArgsCount.range(min: 0, max: 1))

...

hello(List<String> args) => ...

A Task will not be executed if its argsValidator is not satisfied (Dartle will fail the build if that happens).

Task dependencies and run conditions #

A Task can depend on other task(s), so that whenever it runs, its dependencies also run (as long as they are not up-to-date).

In the example above, the bye task depends on the hello task:

Task(bye, dependsOn: const {'hello'})

This means that whenever bye runs, hello runs first.

Notice that tasks that have no dependencies between themselves can run at the same time - either on the same Isolate or in separate Isolates (use the -p flag to indicate that tasks may run in different Isolates when possible, i.e. when their action is a top-level function and there's no dependencies with the other tasks).

A task may be skipped if it's up-to-date according to its RunCondition. The example Dart file demonstrates that:

Task(encodeBase64,
  description: 'Encodes input.txt in base64, writing to output.txt',
  runCondition: RunOnChanges(
    inputs: file('input.txt'),
    outputs: file('output.txt'),
  ))

The above task only runs if at least one of these conditions is true:

  • output.txt does not yet exist.
  • either input.txt or output.txt changed since last time this task ran.
  • the -f or --force-tasks flag is used.

If a RunCondition is not provided, the task is always considered out-of-date.

To force all tasks to run, use the -z or --reset-cache flag.

Help #

For more help, run dartle -h. Proper documentation is going to be available soon!

Future release #

  • forbid tasks from accessing IO resources not declared in inputs/outputs.

0.5.1 #

  • Fixed Dartle version in published package.

0.5.0 #

  • Added CLI --version option.
  • Improved help messages.
  • Run tasks in parallel when no dependencies force an ordering.
  • Use different Isolates to run parallel tasks when their actions is a top-level function and the -p flag is used.
  • Let tasks take arguments (e.g. dartle task :arg).
  • Verify task's arguments using its ArgsValidator.
  • Changed Task action's parameter list to take a non-optional List<String>.
  • Fixed bug where not all executable tasks were shown with the -s flag.

0.4.0 #

  • Implemented task dependencies.
  • New option to show all build tasks.
  • New option to show task graph.
  • Use dart2native to compile dartle build file where available.
  • Better error handling to avoid crashes.
  • Improved process execution functions.
  • Fixed RunOnChanges: must run task if its outputs do not exist.
  • Changed failBuild function to throw DartleException (not call exit).

0.3.0 #

  • Improved dartle caching.
  • Attempt to use fastest snapshot method available to make script runs faster.
  • Support choosing tasks by fuzzy name selection.

0.2.0 #

  • Implemented dartle executable. Snapshots dartle.dart in order to run faster.

0.1.0 #

  • Added basic functionality: run tasks, logging.

0.0.0 #

  • Initial version, created by Stagehand

example/dartle.dart

import 'dart:convert';
import 'dart:io';

import 'package:dartle/dartle.dart';

final allTasks = [
  Task(hello, argsValidator: const ArgsCount.range(min: 0, max: 1)),
  Task(bye, dependsOn: const {'hello'}),
  Task(clean),
  Task(encodeBase64,
      description: 'Encodes input.txt in base64, writing to output.txt',
      runCondition: RunOnChanges(
        inputs: file('input.txt'),
        outputs: file('output.txt'),
      )),
];

main(List<String> args) async =>
    run(args, tasks: allTasks.toSet(), defaultTasks: {allTasks[0]});

/// To pass an argument to a task, use a ':' prefix, e.g.:
/// dartle hello :joe
hello(List<String> args) => print("Hello ${args.isEmpty ? 'World' : args[0]}!");

/// If no arguments are expected, use `_` as the function parameter.
bye(_) => print("Bye!");

encodeBase64(_) async {
  final input = await File('input.txt').readAsBytes();
  await File('output.txt').writeAsString(base64.encode(input));
}

clean(_) => deleteOutputs(allTasks);

Use this package as an executable

1. Install it

You can install the package from the command line:


$ pub global activate dartle

2. Use it

The package has the following executables:


$ dartle

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  dartle: ^0.5.1

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:dartle/dartle.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
1
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
51
Learn more about scoring.

We analyzed this package on Jan 16, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.0
  • pana: 0.13.4

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.4.0 <3.0.0
actors ^0.6.0 0.6.0
ansicolor ^1.0.2 1.0.2
args ^1.5.2 1.5.2
collection ^1.14.12 1.14.12
crypto ^2.1.2 2.1.4
logging ^0.11.3+2 0.11.4
meta ^1.1.7 1.1.8
path ^1.6.4 1.6.4
Transitive dependencies
charcode 1.1.2
convert 2.1.1
typed_data 1.1.6
Dev dependencies
file ^5.0.10
mockito ^4.1.1
pedantic ^1.7.0
test ^1.5.0
yaml ^2.1.16