-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A minimal
engine_tools_lib
to use for local-repo Dart tooling (#45154)
Partial work towards re-landing #44936. Both the `clang_tidy` and `githooks` passage could benefit from being able to automatically find the latest `compile_commands.json` output, which means that some common code should exist in the `tools/` directory. This is a very minimal (but tested) library for doing exactly that.
- Loading branch information
1 parent
9778c2c
commit ddbef31
Showing
6 changed files
with
564 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# engine_repo_tools | ||
|
||
This is a repo-internal library for `flutter/engine`, that contains shared code | ||
for writing tools that operate on the engine repository. For example, finding | ||
the latest compiled engine artifacts in the `out/` directory: | ||
|
||
```dart | ||
import 'package:engine_repo_tools/engine_repo_tools.dart'; | ||
void main() { | ||
final engine = Engine.findWithin(); | ||
final latest = engine.latestOutput(); | ||
if (latest != null) { | ||
print('Latest compile_commands.json: ${latest.compileCommandsJson?.path}'); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
/// A minimal library for discovering and probing a local engine repository. | ||
/// | ||
/// This library is intended to be used by tools that need to interact with a | ||
/// local engine repository, such as `clang_tidy` or `githooks`. For example, | ||
/// finding the `compile_commands.json` file for the most recently built output: | ||
/// | ||
/// ```dart | ||
/// final Engine engine = Engine.findWithin(); | ||
/// final Output? output = engine.latestOutput(); | ||
/// if (output == null) { | ||
/// print('No output targets found.'); | ||
/// } else { | ||
/// final io.File? compileCommandsJson = output.compileCommandsJson; | ||
/// if (compileCommandsJson == null) { | ||
/// print('No compile_commands.json file found.'); | ||
/// } else { | ||
/// print('Found compile_commands.json file at ${compileCommandsJson.path}'); | ||
/// } | ||
/// } | ||
/// ``` | ||
library; | ||
|
||
import 'dart:io' as io; | ||
|
||
import 'package:path/path.dart' as p; | ||
|
||
/// Represents the `$ENGINE` directory (i.e. a checked-out Flutter engine). | ||
/// | ||
/// If you have a path to the `$ENGINE/src` directory, use [Engine.fromSrcPath]. | ||
/// | ||
/// If you have a path to a directory within the `$ENGINE/src` directory, or | ||
/// want to use the current working directory, use [Engine.findWithin]. | ||
final class Engine { | ||
/// Creates an [Engine] from a path such as `/Users/.../flutter/engine/src`. | ||
/// | ||
/// ```dart | ||
/// final Engine engine = Engine.findWithin('/Users/.../engine/src'); | ||
/// print(engine.srcDir.path); // /Users/.../engine/src | ||
/// ``` | ||
/// | ||
/// Throws a [InvalidEngineException] if the path is not a valid engine root. | ||
factory Engine.fromSrcPath(String srcPath) { | ||
// If the path does not end in `/src`, fail. | ||
if (p.basename(srcPath) != 'src') { | ||
throw InvalidEngineException.doesNotEndWithSrc(srcPath); | ||
} | ||
|
||
// If the directory does not exist, or is not a directory, fail. | ||
final io.Directory srcDir = io.Directory(srcPath); | ||
if (!srcDir.existsSync()) { | ||
throw InvalidEngineException.notADirectory(srcPath); | ||
} | ||
|
||
// Check for the existence of a `flutter` directory within `src`. | ||
final io.Directory flutterDir = io.Directory(p.join(srcPath, 'flutter')); | ||
if (!flutterDir.existsSync()) { | ||
throw InvalidEngineException.missingFlutterDirectory(srcPath); | ||
} | ||
|
||
// We do **NOT** check for the existence of a `out` directory within `src`, | ||
// it's not required to exist (i.e. a new checkout of the engine), and we | ||
// don't want to fail if it doesn't exist. | ||
final io.Directory outDir = io.Directory(p.join(srcPath, 'out')); | ||
|
||
return Engine._(srcDir, flutterDir, outDir); | ||
} | ||
|
||
/// Creates an [Engine] by looking for a `src/` directory in the given path. | ||
/// | ||
/// ```dart | ||
/// // Use the current working directory. | ||
/// final Engine engine = Engine.findWithin(); | ||
/// print(engine.srcDir.path); // /Users/.../engine/src | ||
/// | ||
/// // Use a specific directory. | ||
/// final Engine engine = Engine.findWithin('/Users/.../engine/src/foo/bar/baz'); | ||
/// print(engine.srcDir.path); // /Users/.../engine/src | ||
/// ``` | ||
/// | ||
/// If a path is not provided, the current working directory is used. | ||
/// | ||
/// Throws a [StateError] if the path is not within a valid engine. | ||
factory Engine.findWithin([String? path]) { | ||
path ??= p.current; | ||
|
||
// Search parent directories for a `src` directory. | ||
io.Directory maybeSrcDir = io.Directory(path); | ||
|
||
if (!maybeSrcDir.existsSync()) { | ||
throw StateError( | ||
'The path "$path" does not exist or is not a directory.' | ||
); | ||
} | ||
|
||
do { | ||
try { | ||
return Engine.fromSrcPath(maybeSrcDir.path); | ||
} on InvalidEngineException { | ||
// Ignore, we'll keep searching. | ||
} | ||
maybeSrcDir = maybeSrcDir.parent; | ||
} while (maybeSrcDir.parent.path != maybeSrcDir.path /* at root */); | ||
|
||
throw StateError( | ||
'The path "$path" is not within a Flutter engine source directory.' | ||
); | ||
} | ||
|
||
const Engine._( | ||
this.srcDir, | ||
this.flutterDir, | ||
this.outDir, | ||
); | ||
|
||
/// The path to the `$ENGINE/src` directory. | ||
final io.Directory srcDir; | ||
|
||
/// The path to the `$ENGINE/src/flutter` directory. | ||
final io.Directory flutterDir; | ||
|
||
/// The path to the `$ENGINE/src/out` directory. | ||
/// | ||
/// **NOTE**: This directory may not exist. | ||
final io.Directory outDir; | ||
|
||
/// Returns a list of all output targets in [outDir]. | ||
List<Output> outputs() { | ||
return outDir | ||
.listSync() | ||
.whereType<io.Directory>() | ||
.map<Output>(Output._) | ||
.toList(); | ||
} | ||
|
||
/// Returns the most recently modified output target in [outDir]. | ||
/// | ||
/// If there are no output targets, returns `null`. | ||
Output? latestOutput() { | ||
final List<Output> outputs = this.outputs(); | ||
if (outputs.isEmpty) { | ||
return null; | ||
} | ||
outputs.sort((Output a, Output b) { | ||
return b.dir.statSync().modified.compareTo(a.dir.statSync().modified); | ||
}); | ||
return outputs.first; | ||
} | ||
} | ||
|
||
/// Thrown when an [Engine] could not be created from a path. | ||
sealed class InvalidEngineException implements Exception { | ||
/// Thrown when an [Engine] was created from a path not ending in `src`. | ||
factory InvalidEngineException.doesNotEndWithSrc(String path) { | ||
return InvalidEngineSrcPathException._(path); | ||
} | ||
|
||
/// Thrown when an [Engine] was created from a directory that does not exist. | ||
factory InvalidEngineException.notADirectory(String path) { | ||
return InvalidEngineNotADirectoryException._(path); | ||
} | ||
|
||
/// Thrown when an [Engine] was created from a path not containing `flutter/`. | ||
factory InvalidEngineException.missingFlutterDirectory(String path) { | ||
return InvalidEngineMissingFlutterDirectoryException._(path); | ||
} | ||
} | ||
|
||
/// Thrown when an [Engine] was created from a path not ending in `src`. | ||
final class InvalidEngineSrcPathException implements InvalidEngineException { | ||
InvalidEngineSrcPathException._(this.path); | ||
|
||
/// The path that was used to create the [Engine]. | ||
final String path; | ||
|
||
@override | ||
String toString() { | ||
return 'The path $path does not end in `${p.separator}src`.'; | ||
} | ||
} | ||
|
||
/// Thrown when an [Engine] was created from a path that is not a directory. | ||
final class InvalidEngineNotADirectoryException implements InvalidEngineException { | ||
InvalidEngineNotADirectoryException._(this.path); | ||
|
||
/// The path that was used to create the [Engine]. | ||
final String path; | ||
|
||
@override | ||
String toString() { | ||
return 'The path "$path" does not exist or is not a directory.'; | ||
} | ||
} | ||
|
||
/// Thrown when an [Engine] was created from a path not containing `flutter/`. | ||
final class InvalidEngineMissingFlutterDirectoryException implements InvalidEngineException { | ||
InvalidEngineMissingFlutterDirectoryException._(this.path); | ||
|
||
/// The path that was used to create the [Engine]. | ||
final String path; | ||
|
||
@override | ||
String toString() { | ||
return 'The path "$path" does not contain a "flutter" directory.'; | ||
} | ||
} | ||
|
||
/// Represents a single output target in the `$ENGINE/src/out` directory. | ||
final class Output { | ||
const Output._(this.dir); | ||
|
||
/// The directory containing the output target. | ||
final io.Directory dir; | ||
|
||
/// The `compile_commands.json` file for this output target. | ||
/// | ||
/// Returns `null` if the file does not exist. | ||
io.File? get compileCommandsJson { | ||
final io.File file = io.File(p.join(dir.path, 'compile_commands.json')); | ||
if (!file.existsSync()) { | ||
return null; | ||
} | ||
return file; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Copyright 2013 The Flutter Authors. All rights reserved. | ||
# Use of this source code is governed by a BSD-style license that can be | ||
# found in the LICENSE file. | ||
|
||
name: engine_repo_tools | ||
publish_to: none | ||
environment: | ||
sdk: ^3.0.0 | ||
|
||
# Do not add any dependencies that require more than what is provided in | ||
# //third_party/pkg, //third_party/dart/pkg, or | ||
# //third_party/dart/third_party/pkg. In particular, package:test is not usable | ||
# here. | ||
|
||
# If you do add packages here, make sure you can run `pub get --offline`, and | ||
# check the .packages and .package_config to make sure all the paths are | ||
# relative to this directory into //third_party/dart | ||
|
||
dependencies: | ||
meta: any | ||
path: any | ||
|
||
dev_dependencies: | ||
async_helper: any | ||
expect: any | ||
litetest: any | ||
smith: any | ||
|
||
dependency_overrides: | ||
async_helper: | ||
path: ../../../../third_party/dart/pkg/async_helper | ||
expect: | ||
path: ../../../../third_party/dart/pkg/expect | ||
litetest: | ||
path: ../../../testing/litetest | ||
meta: | ||
path: ../../../../third_party/dart/pkg/meta | ||
path: | ||
path: ../../../../third_party/dart/third_party/pkg/path | ||
smith: | ||
path: ../../../../third_party/dart/pkg/smith |
Oops, something went wrong.