link function
- List<
String> arguments, - Future<
void> linker(- LinkInput input,
- LinkOutputBuilder output
Links assets in a hook/link.dart.
If a link hook is defined (hook/link.dart) then link must be called by
that hook, to write the BuildInput.outputFile, even if the linker
function has no work to do.
Can link native assets which are not already available, or expose existing files. Each individual asset is assigned a unique asset ID.
The linking script may receive assets from build scripts, which are accessed through LinkInputAssets.encodedAssets. They will only be bundled with the final application if included in the LinkOutput.
import 'package:data_assets/data_assets.dart';
import 'package:hooks/hooks.dart';
void main(List<String> args) async {
await link(args, (input, output) async {
final dataEncodedAssets = input.assets.encodedAssets.where(
(e) => e.isDataAsset,
);
output.assets.addEncodedAssets(dataEncodedAssets);
});
}
If the linker fails, it must throw a HookError. Link hooks are
guaranteed to be invoked with a process invocation and should return a
non-zero exit code on failure. Throwing will lead to an uncaught exception,
causing a non-zero exit code.
Custom Configurations (User-Defines)
Link hooks can read custom, package-specific configuration settings passed
by the end-user from the root package pubspec.yaml (or the root package
pub workspace pubspec.yaml if using a workspace) via the
input.userDefines property.
See HookInput.userDefines for detailed documentation, configuration schema, and code snippets.
Environment
Link hooks are executed in a semi-hermetic environment. This means that
Platform.environment does not expose all environment variables from the
parent process. This ensures that hook invocations are reproducible and
cacheable, and do not depend on accidental environment variables.
However, some environment variables are necessary for locating tools (like compilers) or configuring network access. The following environment variables are passed through to the hook process:
- Path and system roots:
PATH: Invoke native tools.HOME,USERPROFILE: Find tools in default install locations.APPDATA,LOCALAPPDATA: NuGet, dart_data_home, and pub on Windows.SYSTEMDRIVE,SYSTEMROOT,WINDIR: Process invocations and CMake on Windows.PROGRAMDATA: Forvswhere.exeon Windows.PROCESSOR_ARCHITECTURE: CMake Android on Windows.
- Temporary directories:
TEMP,TMP,TMPDIR: Temporary directories.
- HTTP proxies:
HTTP_PROXY,HTTPS_PROXY,NO_PROXY: Network access behind proxies.
- Clang/LLVM:
LIBCLANG_PATH: Rust'sbindgen+clang-sys.
- Android NDK:
ANDROID_HOME: Standard location for the Android SDK/NDK.ANDROID_NDK,ANDROID_NDK_HOME,ANDROID_NDK_LATEST_HOME,ANDROID_NDK_ROOT: Alternative locations for the NDK.
- Ccache:
- Any variable starting with
CCACHE_.
- Any variable starting with
- Nix:
- Any variable starting with
NIX_.
- Any variable starting with
- .NET and NuGet:
- Any variable starting with
DOTNET_. - Any variable starting with
NUGET_.
- Any variable starting with
Any changes to these environment variables will cause cache invalidation for hooks.
All other environment variables are stripped.
Caching
Hook execution is automatically cached by the hooks runner to avoid unnecessary runs.
An execution of this hook is skipped, and the cached output is reused, if and only if:
- The input to the hook didn't change (including the configuration fields
accessed via LinkInput.config and the
user-definesin the workspacepubspec.yaml). - No environment variables (that are not filtered out) changed.
- None of the files or directories declared in HookOutputBuilder.dependencies changed.
- None of the transitive Dart sources of the hook script itself changed.
- The workspace
package_config.jsondidn't change. - The Dart SDK version didn't change.
If any of these conditions are not met, the hook is re-run.
Hook Output Dependencies
To ensure cache correctness when external files or assets are modified,
hooks must explicitly declare their file and directory dependencies using
HookOutputBuilder.dependencies (e.g., via
output.dependencies.add(uri)).
If your hook resolves and reads local files referenced in user-defines (e.g.
using input.userDefines.path('key')), you must manually register those
files in HookOutputBuilder.dependencies to ensure the hook is re-run
when the referenced files' contents change.
Cache Isolation
Outputs are cached in a configuration-specific subdirectory inside
.dart_tool/hooks_runner/. This directory is unique per hook and is
derived from the configuration fields in LinkInput.config. Therefore,
different configurations (e.g., building for a different target OS or
architecture) do not collide.
The cache is reused for identical configurations across different builds, even when inputs outside the configuration or environment variables change.
Debugging
When a link hook doesn't work as expected, you can investigate the intermediate files generated by the Dart and Flutter SDK build process.
The most important files for debugging are located in a subdirectory
specific to your hook's execution. The path is of the form
.dart_tool/hooks_runner/<package_name>/<some_hash>/, where
<package_name> is the name of the package containing the hook. Inside, you
will find:
input.json: The configuration and data passed into your link hook.output.json: The JSON data that your link hook produced.stdout.txt: Any standard output from your link hook.stderr.txt: Any error messages or exceptions.
When you run a build, hooks for all dependencies are executed, so you might see multiple package directories.
The <some_hash> is a checksum of the LinkConfig in the input.json. If
you are unsure which hash directory to inspect within your package's hook
directory, you can delete the .dart_tool/hooks_runner/<package_name>/
directory and re-run the command that failed. The newly created directory
will be for the latest invocation.
You can step through your code with a debugger by running the link hook from
its source file and providing the input.json via the --config flag:
dart run hook/link.dart --config .dart_tool/hooks_runner/<package_name>/<some_hash>/input.json
To debug in VS Code, you can create a launch.json file in a .vscode
directory in your project root. This allows you to run your hook with a
debugger attached.
Here is an example configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Link Hook",
"type": "dart",
"request": "launch",
"program": "hook/link.dart",
"args": [
"--config",
".dart_tool/hooks_runner/your_package_name/some_hash/input.json"
]
}
]
}
Again, make sure to replace your_package_name, and some_hash with the
actual paths from your project. After setting this up, you can run the
"Debug Link Hook" configuration from the "Run and Debug" view in VS Code.
Implementation
Future<void> link(
List<String> arguments,
Future<void> Function(LinkInput input, LinkOutputBuilder output) linker,
) async {
final inputPath = getInputArgument(arguments);
final bytes = File(inputPath).readAsBytesSync();
final jsonInput =
const Utf8Decoder().fuse(const JsonDecoder()).convert(bytes)
as Map<String, Object?>;
final input = LinkInput(jsonInput);
final outputFile = input.outputFile;
final output = LinkOutputBuilder();
try {
await linker(input, output);
// ignore: avoid_catching_errors
} on HookError catch (e, st) {
output.setFailure(e.failureType);
await _writeOutput(output, outputFile);
_exitViaHookException(e, st);
}
final errors = await ProtocolBase.validateLinkOutput(
input,
LinkOutput(output.json),
);
if (errors.isNotEmpty) {
final message = [
'The output contained unsupported output:',
for (final error in errors) '- $error',
].join('\n');
stderr.writeln(message);
output.setFailure(.build);
await _writeOutput(output, outputFile);
exit(BuildError(message: message).exitCode);
}
await _writeOutput(output, outputFile);
}