Dartle Documentation

Welcome to the Dartle Documentation.

Dartle CI pub package

To learn Dartle from the basics, check out the Dartle Overview page.

For a quick introduction, read on.

Introduction

Dartle is a task-based build system written in the Dart programming language.

Dartle can be used to build anything that can be automated!

How exactly you automate your build with Dartle depends on your needs:

Dartle makes it easy to build most things using an easy, familiar language.

For example, to compile all C files found in the src directory recursively, using gcc:

import 'dart:io';

import 'package:dartle/dartle.dart';

final cfiles = dir('src', fileExtensions: {'.c'});
const output = 'mybinary';

final gccTask = Task(gcc,
    description: 'Compiles all C files in src/',
    runCondition: RunOnChanges(inputs: cfiles, outputs: file(output)));

main(List<String> args) => run(args, tasks: {gccTask});

gcc(_) async => execProc(Process.start('gcc', [
  '-o',
  output,
  ...await cfiles.resolveFiles().map((f) => f.path).toList(),
]));

Pretty simple and highly readable, unlike most build solutions out there.

In the same directory as the above darle.dart script, you can run dartle gcc:

$ dartle gcc
2023-05-09 20:05:06.542846 - dartle[main 51081] - INFO - Executing 1 task out of a total of 1 task: 1 task selected
2023-05-09 20:05:06.542944 - dartle[main 51081] - INFO - Running task 'gcc'
✔ Build succeeded in 528 ms

$ ./mybinary 
It works!

The above gcc task will only execute if changes are detected on its inputs or outputs.

The Dartle Overview Section takes this basic example further and implements a fully incremental C build!

Real-world Example

This website you’re reading is built using Dartle! Here’s what the script looks like:

import 'dart:io';

import 'package:dartle/dartle.dart';
import 'package:path/path.dart' as paths;

String userHome() => homeDir() ?? failBuild(reason: 'Cannot find user HOME');

final magFile = File(paths.join(userHome(), '.magnanimous', 'mag'));

final magnanimousDownloadTask = Task(downloadMagnanimous,
    description: 'Download Magnanimous',
    runCondition: RunOnChanges(outputs: file(magFile.path)));

final magnanimousRunTask = Task(runMagnanimous,
    description: 'Builds the Dartle Website using Magnanimous',
    dependsOn: {magnanimousDownloadTask.name},
    runCondition: RunOnChanges(
        inputs: entities(['dartle.dart'], [DirectoryEntry(path: 'source')]),
        outputs: dir('target')));

void main(List<String> args) {
  run(args, tasks: {
    magnanimousRunTask,
    magnanimousDownloadTask,
    createCleanTask(tasks: [magnanimousRunTask]),
  }, defaultTasks: {
    magnanimousRunTask,
  });
}

String _osArch() {
  if (Platform.isWindows) return 'windows-386.exe';
  if (Platform.isLinux) return 'linux-386';
  if (Platform.isMacOS) return 'darwin-amd64';
  failBuild(reason: 'Unsupported OS: ${Platform.operatingSystem}');
}

Future<void> downloadMagnanimous(_) async {
  await magFile.parent.create();
  final magStream = download(
      Uri.parse('https://github.com/renatoathaydes/magnanimous/releases'
          '/download/0.11.1/magnanimous-${_osArch()}'));
  await magFile.writeBinary(magStream, makeExecutable: true);
}

Future<void> runMagnanimous(_) async {
  final code = await execProc(
      Process.start(magFile.path, const ['-style', 'nord'], runInShell: true));
  if (code != 0) failBuild(reason: 'magnanimous exited with code $code');
}

Hopefully, it’s easy to understand what’s going on.

The downloadMagnanimous task downloads Magnanimous, a static website generator (also written by me, by the way 😉), for the appropriate Operating System, and the runMagnanimous task runs it.

For this to work, task runMagnanimous depends on downloadMagnanimous, so that Dartle will ensure the binary has been downloaded before trying to run it!

Because runMagnanimous is the default task, this build can be executed by running the dartle command, without any arguments, in the project’s root directory.

$ dartle
2023-05-06 19:01:55.244210 - dartle[main 23751] - INFO - Executing 1 task out of a total of 3 tasks: 1 task (default), 1 dependency, 1 up-to-date
2023-05-06 19:01:55.244466 - dartle[main 23751] - INFO - Running task 'runMagnanimous'
✔ Build succeeded in 156 ms

You can execute any task you declared by passing its name as an argument to dartle.

For example, to run the runMagnanimous task:

$ dartle runMagnanimous
2023-05-06 19:03:44.552424 - dartle[main 24026] - INFO - Executing 0 tasks out of a total of 3 tasks: 1 task (default), 1 dependency, 2 up-to-date
✔ Build succeeded in 110 ms

Dartle requires only a few letters to find a task, so running dartle run, or dartle rM would work as well!

Thanks to Dartle’s Cache System, tasks only execute when necessary, so running the build again without changing anything does basically nothing, which is why the build log above shows that no tasks needed to be executed!

You can force tasks to run by using the -f flag. To see all options, check the Dart CLI page.

What next?

To find out more, check some of the pages below:

I hope you enjoy using Dartle.