Dartle Documentation
If your goal is to build Dart projects, this section is for you.
Dartle for Dart Projects
Dartle has built-in support for Dart Projects, making it easy to manage the lifecycle of Dart projects without having to remember when you need to invoke each Dart tool.
Even after all separate tools were unified in Dart 2.10, remembering which commands to run, and when, is a task better left to Dartle.
To add support for Dartle to an existing Dart project, run dartle
on the project directory. It will automatically
detect it’s a Dart project and setup Dartle accordingly.
Hint: to create a new DartleDart project from scratch, run
dart create <dir> && cd <dir> && dartle
!
Alternatively, you can manually add Dartle by first running this command:
$ dart pub add -d dartle
This adds Dartle as a dev dependency.
After that, create a dartle.dart
script as shown below (which is the file dartle
would generate automatically):
import 'package:dartle/dartle_dart.dart';
final dartleDart = DartleDart();
void main(List<String> args) {
run(args, tasks: {
...dartleDart.tasks,
}, defaultTasks: {
dartleDart.build
});
}
That’s all you need!
Working with Dart Projects
DartleDart
can be used to configure the project.
By default, it will contain the following tasks:
$ dartle -s
======== Showing build information only, no tasks will be executed ========
Tasks declared in this build:
==> Setup Phase:
* clean
Deletes the outputs of all other tasks in this build.
==> Build Phase:
* analyzeCode [up-to-date]
Analyzes Dart source code
* build [default] [always-runs]
Runs all enabled tasks.
* compileExe
Compiles Dart executables declared in pubspec. Argument may specify the name(s) of the executable(s) to compile.
* format [up-to-date]
Formats all Dart source code.
* runPubGet [up-to-date]
Runs "pub get" in order to update dependencies.
* test [up-to-date]
Runs Dart tests.
==> TearDown Phase:
No tasks in this phase.
The following tasks were selected to run, in order:
format
runPubGet
analyzeCode
test
build
As you can see, the build
task is the default task, and it will automatically run:
format
, invokesdart format
.runPubGet
, invokesdart pub get
once a week, can be configured.analyzeCode
, invokesdart analyze
.test
, invokesdart test
.
That is, to make sure all the above tasks are up-to-date, you just need to run dartle
without any arguments!
The other tasks, namely clean
and compileExe
, are not executed in the default pipeline.
Tasks are only executed if needed, i.e. if changes are detected to their inputs or outputs, or in the case of
runPubGet
, at most once a week.
If you explicitly invoke a certain task, it will cause any tasks it depends on to also run.
Run with the -g
flag to see which tasks would run for a certain invocation, without actually running it:
$ dartle -g compile
======== Showing build information only, no tasks will be executed ========
Tasks Graph:
- analyzeCode
+--- format
\--- runPubGet
- build
+--- analyzeCode ...
|--- format
|--- runPubGet
\--- test
\--- analyzeCode ...
- clean
- compileExe
\--- analyzeCode ...
The following tasks were selected to run, in order:
runPubGet
format
analyzeCode
compileExe
See the Dartle CLI page for more details about options accepted by the
dartle
command.
Configuring DartleDart
Almost everything in DartleDart
is configurable via the DartleConfig
object.
Here’s an example script configuring all possibilities (check the API reference for an up-to-date API):
import 'package:dartle/dartle_dart.dart';
final dartleDart = DartleDart(DartConfig(
formatCode: false,
runAnalyzer: false,
compileExe: false,
runPubGetAtMostEvery: const Duration(days: 30),
runTests: true,
rootDir: '.',
testOutput: DartTestOutput.dart,
buildRunnerRunCondition: RunOnChanges(
inputs: file('pubspec.yaml'),
outputs: file('lib/src/version.g.dart'),
),
));
void main(List<String> args) {
dartleDart.test.dependsOn({dartleDart.runBuildRunner.name});
run(args, tasks: {
...dartleDart.tasks,
}, defaultTasks: {
dartleDart.build
});
}
The test
task needs to explicitly depend on runBuildRunner
because the output file of the latter overlaps
with the input files of the former… without such dependency, the tasks wouldn’t run in the correct order.
Dartle automatically detects this kind of situation… without that dependency being present, an error would’ve
occurred:
2023-05-27 09:58:34.634616 - dartle[main 78846] - ERROR - The following tasks have implicit dependencies due to their inputs depending on other tasks' outputs:
* Task 'test' must dependOn 'runBuildRunner' (clashing outputs: {lib/src/version.g.dart}).
Please add the dependencies explicitly.
Running dartle -s
, you can see that the set of tasks now changed:
$ dartle -s
======== Showing build information only, no tasks will be executed ========
Tasks declared in this build:
==> Setup Phase:
* clean
Deletes the outputs of all other tasks in this build.
==> Build Phase:
* build [default] [always-runs]
Runs all enabled tasks.
* runBuildRunner [out-of-date]
Runs the Dart build_runner tool.
* runPubGet [up-to-date]
Runs "pub get" in order to update dependencies.
* test [dependency-out-of-date]
Runs Dart tests.
==> TearDown Phase:
No tasks in this phase.
The following tasks were selected to run, in order:
runPubGet
runBuildRunner
test
build
The Dart build-runner would now automatically run when needed only, which is very helpful as it takes a long time to complete.
Changing the default task
The default task is given to Dartle when invoking its run
method. If you prefer to use a different set of default
tasks to run, change the run
method invocation:
import 'package:dartle/dartle_dart.dart';
final dartleDart = DartleDart();
void main(List<String> args) {
run(args, tasks: {
...dartleDart.tasks,
}, defaultTasks: {
dartleDart.formatCode, dartleDart.analyzeCode
});
}
With this build script, the default tasks to be executed become:
The following tasks were selected to run, in order:
format
runPubGet
analyzeCode
Adding custom tasks
Adding custom tasks is straightforward. Just make sure to include the task in the tasks
Set when calling run
:
void main(List<String> args) {
run(args, tasks: {
customTask,
...dartleDart.tasks,
}, defaultTasks: {
dartleDart.build
});
}
The following example shows how to generate a version.g.dart
file, allowing a
Dart application to have access to its own version at runtime, a common requirement.
As explained in the Dartle Overview, it’s recommended to always write implementation code on Dart files inside the
dartle-src
directory to avoid polluting the build script.
dartle-src/generate_version.dart
:
import 'dart:io';
import 'package:dartle/dartle_dart.dart';
import 'package:yaml/yaml.dart';
const pubspec = 'pubspec.yaml';
const versionFile = 'lib/src/version.g.dart';
Future<void> generateVersion(_) async {
final yaml = loadYaml(await File(pubspec).readAsString());
await File(versionFile)
.writeAsString('const version = "${yaml['version']}";');
}
final generateVersionTask = Task(generateVersion,
description:
'Generates a Dart file containing the version of this project.',
runCondition: RunOnChanges(
inputs: file(pubspec),
outputs: file(versionFile),
));
dartle.dart
:
import 'package:dartle/dartle_dart.dart';
import 'dartle-src/generate_version.dart';
final dartleDart = DartleDart();
void main(List<String> args) {
dartleDart.formatCode.dependsOn({generateVersionTask.name});
run(args, tasks: {
generateVersionTask,
...dartleDart.tasks,
}, defaultTasks: {
dartleDart.build
});
}
The custom task, generateVersion
, is added as a dependency of the existing formatCode
task, so it will automatically
run during a build where the latter task runs… And it will be skipped if the YAML file hasn’t changed!