blob: 82f71df1d1d0738e8ee306ecffaa5780f5614312 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// This script should be run by every project that consumes Mojom IDL
/// interfaces. It populates the 'mojom' package with the generated Dart
/// bindings for the Mojom IDL files.
///
/// From a consuming project, it should be invoked as follows:
///
/// $ dart packages/mojom/generate.dart [-p package-root]
/// [-a additional-dirs]
/// [-m mojo-sdk]
/// [-g] # Generate from .mojom files
/// [-v] # verbose
/// [-d] # Dry run
import 'dart:async';
import 'dart:io';
import 'package:args/args.dart' as args;
import 'package:path/path.dart' as path;
bool verbose;
bool dryRun;
bool isMojomDart(String path) => path.endsWith('.mojom.dart');
bool isMojom(String path) => path.endsWith('.mojom');
/// An Error for problems on the command line.
class CommandLineError extends Error {
final _msg;
CommandLineError(this._msg);
toString() => _msg;
}
/// An Error for failures of the bindings generation script.
class GenerationError extends Error {
final _msg;
GenerationError(this._msg);
toString() => _msg;
}
/// The base type of data passed to actions for [mojomDirIter].
class PackageIterData {
final Directory _mojomPackage;
PackageIterData(this._mojomPackage);
Directory get mojomPackage => _mojomPackage;
}
/// Data for [mojomDirIter] that includes the path to the Mojo SDK for bindings
/// generation.
class GenerateIterData extends PackageIterData {
final Directory _mojoSdk;
GenerateIterData(this._mojoSdk, Directory mojomPackage)
: super(mojomPackage);
Directory get mojoSdk => _mojoSdk;
}
/// The type of action performed by [mojomDirIter].
typedef Future MojomAction(PackageIterData data, Directory mojomDirectory);
/// Iterates over mojom directories of Dart packages, taking some action for
/// each.
///
/// For each 'mojom' subdirectory of each subdirectory in [packages], runs
/// [action] on the subdirectory passing along [data] to [action].
mojomDirIter(
Directory packages, PackageIterData data, MojomAction action) async {
await for (var package in packages.list()) {
if (package is Directory) {
if (package.path == data.mojomPackage.path) continue;
if (verbose) print("package = $package");
final mojomDirectory = new Directory(path.join(package.path, 'mojom'));
if (verbose) print("looking for = $mojomDirectory");
if (await mojomDirectory.exists()) {
await action(data, mojomDirectory);
} else if (verbose) {
print("$mojomDirectory not found");
}
}
}
}
/// Searches for .mojom.dart files under [mojomDirectory] and copies them to
/// the 'mojom' packages.
copyAction(PackageIterData data, Directory mojomDirectory) async {
await for (var mojom in mojomDirectory.list(recursive: true)) {
if (mojom is! File) continue;
if (!isMojomDart(mojom.path)) continue;
if (verbose) print("Found $mojom");
final relative = path.relative(mojom.path, from: mojomDirectory.path);
final dest = path.join(data.mojomPackage.path, relative);
final destDirectory = new Directory(path.dirname(dest));
if (verbose || dryRun) {
print('Copying $mojom to $dest');
}
if (!dryRun) {
final File source = new File(mojom.path);
if (verbose) print("Ensuring $destDirectory exists");
await destDirectory.create(recursive: true);
source.copy(dest);
}
}
}
/// Searches for .mojom files under [mojomDirectory], generates .mojom.dart
/// files for them, and copies them to the 'mojom' package.
generateAction(GenerateIterData data, Directory mojomDirectory) async {
await for (var mojom in mojomDirectory.list(recursive: true)) {
if (mojom is! File) continue;
if (!isMojom(mojom.path)) continue;
if (verbose) print("Found $mojom");
final script = path.join(data.mojoSdk.path,
'mojo', 'public', 'tools', 'bindings', 'mojom_bindings_generator.py');
final outputDir = await data.mojomPackage.createTemp();
final output = outputDir.path;
final arguments = [
'--use_bundled_pylibs',
'-o', output,
// TODO(zra): Are other include paths needed?
'-I', data.mojoSdk.path,
'-I', mojomDirectory.path,
mojom.path];
if (verbose || dryRun) {
print('Generating $mojom');
print('$script ${arguments.join(" ")}');
}
if (!dryRun) {
final result = await Process.run(script, arguments);
if (result.exitCode != 0) {
throw new GenerationError("$script failed:\n${result.stderr}");
}
// Generated .mojom.dart is under $output/dart-gen/mojom/lib/X
// Move X to $mojomPackage. Then rm -rf $output
final generatedDirName = path.join(output, 'dart-gen', 'mojom', 'lib');
final generatedDir = new Directory(generatedDirName);
await copyAction(data, generatedDir);
await outputDir.delete(recursive: true);
}
}
}
/// Ensures that the directories in [additionalPaths] are absolute and exist,
/// and creates Directories for them, which are returned.
validateAdditionalDirs(Iterable additionalPaths) async {
var additionalDirs = [];
for (var mojomPath in additionalPaths) {
final mojomDir = new Directory(mojomPath);
if (!mojomDir.isAbsolute) {
throw new CommandLineError(
"All --additional-mojom-dir parameters must be absolute paths.");
}
if (!(await mojomDir.exists())) {
throw new CommandLineError(
"The additional mojom directory $mojomDir must exist");
}
additionalDirs.add(mojomDir);
}
if (verbose) print("additional_mojom_dirs = $additionalDirs");
return additionalDirs;
}
main(List<String> arguments) async {
final parser = new args.ArgParser()
..addOption('additional-mojom-dir',
abbr: 'a',
allowMultiple: true,
help: 'Absolute path to an additional directory containing mojom.dart'
'files to put in the mojom package. May be specified multiple times.')
..addFlag('dry-run',
abbr: 'd',
defaultsTo: false,
help: 'Print the copy operations that would have been run, but'
'do not copy anything.')
..addFlag('generate',
abbr: 'g',
defaultsTo: false,
help: 'Generate Dart bindings for .mojom files.')
..addOption('mojo-sdk',
abbr: 'm',
defaultsTo: Platform.environment['MOJO_SDK'],
help: 'Absolute path to the Mojo SDK, which can also be specified '
'with the environment variable MOJO_SDK.')
..addOption('package-root',
abbr: 'p',
defaultsTo: path.join(Directory.current.path, 'packages'),
help: 'An absolute path to an application\'s package root')
..addFlag('verbose', abbr: 'v', defaultsTo: false);
final result = parser.parse(arguments);
verbose = result['verbose'];
dryRun = result['dry-run'];
final packages = new Directory(result['package-root']);
if (!packages.isAbsolute) {
throw new CommandLineError(
"The --package-root parameter must be an absolute path.");
}
if (verbose) print("packages = $packages");
if (!(await packages.exists())) {
throw new CommandLineError(
"The packages directory $packages must exist");
}
final mojomPackage = new Directory(path.join(packages.path, 'mojom'));
if (verbose) print("mojom package = $mojomPackage");
if (!(await mojomPackage.exists())) {
throw new CommandLineError(
"The mojom package directory $mojomPackage must exist");
}
final generate = result['generate'];
var mojoSdk = null;
if (generate) {
final mojoSdkPath = result['mojo-sdk'];
if (mojoSdkPath == null) {
throw new CommandLineError(
"The Mojo SDK directory must be specified with the --mojo-sdk flag or"
"the MOJO_SDK environment variable.");
}
mojoSdk = new Directory(mojoSdkPath);
if (verbose) print("Mojo SDK = $mojoSdk");
if (!(await mojoSdk.exists())) {
throw new CommandLineError(
"The specified Mojo SDK directory $mojoSdk must exist.");
}
}
await mojomDirIter(packages, new PackageIterData(mojomPackage), copyAction);
if (generate) {
await mojomDirIter(packages, new GenerateIterData(mojoSdk, mojomPackage),
generateAction);
}
final additionalDirs =
await validateAdditionalDirs(result['additional-mojom-dir']);
final data = new GenerateIterData(mojoSdk, mojomPackage);
for (var mojomDir in additionalDirs) {
await copyAction(data, mojomDir);
if (generate) {
await generateAction(data, mojomDir);
}
}
}