checkReferences static method
Checks that every run_hooks_from and depends_on name refers to a
script in scripts, and that depends_on has no cycles. Run after the
root and package scripts are merged, since either may refer to the
other.
Implementation
static void checkReferences(
Map<String, Script> scripts, {
required String file,
}) {
ConfigException missing(Script script, String key, String name) {
final suggestion = closestMatch(name, scripts.keys);
return ConfigException(
'scripts.${script.name}.$key: no script named "$name".'
'${suggestion == null ? '' : ' Did you mean "$suggestion"?'}',
file: file,
);
}
for (final script in scripts.values) {
final from = script.runHooksFrom;
if (from != null && !scripts.containsKey(from) && !_isKnownTarget(from)) {
throw missing(script, 'run_hooks_from', from);
}
for (final dependency in script.dependsOn) {
final name = dependency.startsWith('^')
? dependency.substring(1)
: dependency;
final target = scripts[name];
if (target == null) {
throw missing(script, 'depends_on', name);
}
if (target.isHook) {
throw ConfigException(
'scripts.${script.name}.depends_on: "$name" is a hook. Hooks run '
'around their target; depend on a script instead.',
file: file,
);
}
}
}
final visiting = <String>{};
final done = <String>{};
void visit(String name, List<String> path) {
if (done.contains(name)) {
return;
}
if (!visiting.add(name)) {
final cycle = [...path.sublist(path.indexOf(name)), name];
throw ConfigException(
'scripts: depends_on forms a cycle: ${cycle.join(' -> ')}.',
file: file,
);
}
for (final dependency in scripts[name]?.dependsOn ?? const <String>[]) {
if (!dependency.startsWith('^')) {
visit(dependency, [...path, name]);
}
}
visiting.remove(name);
done.add(name);
}
for (final name in scripts.keys) {
visit(name, const []);
}
}