Reorganize and clarify API doc generator (#132353)
## Description This cleans up a lot of issues with the API doc generation. Here are the main changes: - Rename `dartdoc.dart` to `create_api_docs.dart` - Move the bulk of the operations out of `dev/bots/docs.sh` into `create_api_docs.dart`. - Delete `dashing_postprocess.dart` and `java_and_objc.dart` and incorporate those operations into `create_api_docs.dart`. - Refactor the doc generation into more understandable classes - Bump the snippets tool version to 0.4.0 (the latest one) - Centralize the information gathering about the Flutter repo into the new `FlutterInformation` class. - Clean up the directory handling, and convert to using the `file` package for all file and directory paths. - Add an `--output` option to docs.sh that specifies the location of the output ZIP file containing the docs. - Defaults to placing the output in `dev/docs/api_docs.zip` (i.e. where the previous code generates the file). - Moved all document generation into a temporary folder that is removed once the documents are generated, to avoid VSCode and other IDEs trying to index the thousands of HTML and JS files in the docs output. - Updated pubspec dependencies. ## Tests - Added tests for doc generation.
This commit is contained in:
parent
301577a34f
commit
899a29f830
200
dev/bots/docs.sh
200
dev/bots/docs.sh
@ -16,102 +16,13 @@ function script_location() {
|
|||||||
cd -P "$(dirname "$script_location")" >/dev/null && pwd
|
cd -P "$(dirname "$script_location")" >/dev/null && pwd
|
||||||
}
|
}
|
||||||
|
|
||||||
function generate_docs() {
|
|
||||||
# Install and activate dartdoc.
|
|
||||||
# When updating to a new dartdoc version, please also update
|
|
||||||
# `dartdoc_options.yaml` to include newly introduced error and warning types.
|
|
||||||
"$DART" pub global activate dartdoc 6.3.0
|
|
||||||
|
|
||||||
# Install and activate the snippets tool, which resides in the
|
|
||||||
# assets-for-api-docs repo:
|
|
||||||
# https://github.com/flutter/assets-for-api-docs/tree/master/packages/snippets
|
|
||||||
"$DART" pub global activate snippets 0.3.1
|
|
||||||
|
|
||||||
# This script generates a unified doc set, and creates
|
|
||||||
# a custom index.html, placing everything into dev/docs/doc.
|
|
||||||
(cd "$FLUTTER_ROOT/dev/tools" && "$FLUTTER" pub get)
|
|
||||||
(cd "$FLUTTER_ROOT/dev/tools" && "$DART" pub get)
|
|
||||||
(cd "$FLUTTER_ROOT" && "$DART" --disable-dart-dev --enable-asserts "$FLUTTER_ROOT/dev/tools/dartdoc.dart")
|
|
||||||
(cd "$FLUTTER_ROOT" && "$DART" --disable-dart-dev --enable-asserts "$FLUTTER_ROOT/dev/tools/java_and_objc_doc.dart")
|
|
||||||
}
|
|
||||||
|
|
||||||
# Zip up the docs so people can download them for offline usage.
|
|
||||||
function create_offline_zip() {
|
|
||||||
# Must be run from "$FLUTTER_ROOT/dev/docs"
|
|
||||||
echo "$(date): Zipping Flutter offline docs archive."
|
|
||||||
rm -rf flutter.docs.zip doc/offline
|
|
||||||
(cd ./doc; zip -r -9 -q ../flutter.docs.zip .)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate the docset for Flutter docs for use with Dash, Zeal, and Velocity.
|
|
||||||
function create_docset() {
|
|
||||||
# Must be run from "$FLUTTER_ROOT/dev/docs"
|
|
||||||
# Must have dashing installed: go get -u github.com/technosophos/dashing
|
|
||||||
# Dashing produces a LOT of log output (~30MB), so we redirect it, and just
|
|
||||||
# show the end of it if there was a problem.
|
|
||||||
echo "$(date): Building Flutter docset."
|
|
||||||
rm -rf flutter.docset
|
|
||||||
# If dashing gets stuck, Cirrus will time out the build after an hour, and we
|
|
||||||
# never get to see the logs. Thus, we run it in the background and tail the logs
|
|
||||||
# while we wait for it to complete.
|
|
||||||
dashing_log=/tmp/dashing.log
|
|
||||||
dashing build --source ./doc --config ./dashing.json > $dashing_log 2>&1 &
|
|
||||||
dashing_pid=$!
|
|
||||||
wait $dashing_pid && \
|
|
||||||
cp ./doc/flutter/static-assets/favicon.png ./flutter.docset/icon.png && \
|
|
||||||
"$DART" --disable-dart-dev --enable-asserts ./dashing_postprocess.dart && \
|
|
||||||
tar cf flutter.docset.tar.gz --use-compress-program="gzip --best" flutter.docset
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
>&2 echo "Dashing docset generation failed"
|
|
||||||
tail -200 $dashing_log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function deploy_docs() {
|
|
||||||
case "$LUCI_BRANCH" in
|
|
||||||
master)
|
|
||||||
echo "$(date): Updating $LUCI_BRANCH docs: https://master-api.flutter.dev/"
|
|
||||||
# Disable search indexing on the master staging site so searches get only
|
|
||||||
# the stable site.
|
|
||||||
echo -e "User-agent: *\nDisallow: /" > "$FLUTTER_ROOT/dev/docs/doc/robots.txt"
|
|
||||||
;;
|
|
||||||
stable)
|
|
||||||
echo "$(date): Updating $LUCI_BRANCH docs: https://api.flutter.dev/"
|
|
||||||
# Enable search indexing on the master staging site so searches get only
|
|
||||||
# the stable site.
|
|
||||||
echo -e "# All robots welcome!" > "$FLUTTER_ROOT/dev/docs/doc/robots.txt"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
>&2 echo "Docs deployment cannot be run on the $LUCI_BRANCH branch."
|
|
||||||
exit 0
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Move the offline archives into place, after all the processing of the doc
|
|
||||||
# directory is done. This avoids the tools recursively processing the archives
|
|
||||||
# as part of their process.
|
|
||||||
function move_offline_into_place() {
|
|
||||||
# Must be run from "$FLUTTER_ROOT/dev/docs"
|
|
||||||
echo "$(date): Moving offline data into place."
|
|
||||||
mkdir -p doc/offline
|
|
||||||
mv flutter.docs.zip doc/offline/flutter.docs.zip
|
|
||||||
du -sh doc/offline/flutter.docs.zip
|
|
||||||
if [[ "$LUCI_BRANCH" == "stable" ]]; then
|
|
||||||
echo -e "<entry>\n <version>${FLUTTER_VERSION_STRING}</version>\n <url>https://api.flutter.dev/offline/flutter.docset.tar.gz</url>\n</entry>" > doc/offline/flutter.xml
|
|
||||||
else
|
|
||||||
echo -e "<entry>\n <version>${FLUTTER_VERSION_STRING}</version>\n <url>https://master-api.flutter.dev/offline/flutter.docset.tar.gz</url>\n</entry>" > doc/offline/flutter.xml
|
|
||||||
fi
|
|
||||||
mv flutter.docset.tar.gz doc/offline/flutter.docset.tar.gz
|
|
||||||
du -sh doc/offline/flutter.docset.tar.gz
|
|
||||||
}
|
|
||||||
|
|
||||||
# So that users can run this script from anywhere and it will work as expected.
|
# So that users can run this script from anywhere and it will work as expected.
|
||||||
SCRIPT_LOCATION="$(script_location)"
|
SCRIPT_LOCATION="$(script_location)"
|
||||||
# Sets the Flutter root to be "$(script_location)/../..": This script assumes
|
# Sets the Flutter root to be "$(script_location)/../..": This script assumes
|
||||||
# that it resides two directory levels down from the root, so if that changes,
|
# that it resides two directory levels down from the root, so if that changes,
|
||||||
# then this line will need to as well.
|
# then this line will need to as well.
|
||||||
FLUTTER_ROOT="$(dirname "$(dirname "$SCRIPT_LOCATION")")"
|
FLUTTER_ROOT="$(dirname "$(dirname "$SCRIPT_LOCATION")")"
|
||||||
|
export FLUTTER_ROOT
|
||||||
|
|
||||||
echo "$(date): Running docs.sh"
|
echo "$(date): Running docs.sh"
|
||||||
|
|
||||||
@ -124,31 +35,106 @@ FLUTTER_BIN="$FLUTTER_ROOT/bin"
|
|||||||
DART_BIN="$FLUTTER_ROOT/bin/cache/dart-sdk/bin"
|
DART_BIN="$FLUTTER_ROOT/bin/cache/dart-sdk/bin"
|
||||||
FLUTTER="$FLUTTER_BIN/flutter"
|
FLUTTER="$FLUTTER_BIN/flutter"
|
||||||
DART="$DART_BIN/dart"
|
DART="$DART_BIN/dart"
|
||||||
export PATH="$FLUTTER_BIN:$DART_BIN:$PATH"
|
PATH="$FLUTTER_BIN:$DART_BIN:$PATH"
|
||||||
|
|
||||||
# Make sure dart is installed by invoking Flutter to download it.
|
# Make sure dart is installed by invoking Flutter to download it if it is missing.
|
||||||
# This also creates the 'version' file.
|
# Also make sure the flutter command is ready to run before capturing output from
|
||||||
FLUTTER_VERSION=$("$FLUTTER" --version --machine)
|
# it: if it has to rebuild itself or something, it'll spoil our JSON output.
|
||||||
|
"$FLUTTER" > /dev/null 2>&1
|
||||||
|
FLUTTER_VERSION="$("$FLUTTER" --version --machine)"
|
||||||
export FLUTTER_VERSION
|
export FLUTTER_VERSION
|
||||||
FLUTTER_VERSION_STRING=$(cat "$FLUTTER_ROOT/version")
|
|
||||||
|
|
||||||
# If the pub cache directory exists in the root, then use that.
|
# If the pub cache directory exists in the root, then use that.
|
||||||
FLUTTER_PUB_CACHE="$FLUTTER_ROOT/.pub-cache"
|
FLUTTER_PUB_CACHE="$FLUTTER_ROOT/.pub-cache"
|
||||||
if [[ -d "$FLUTTER_PUB_CACHE" ]]; then
|
if [[ -d "$FLUTTER_PUB_CACHE" ]]; then
|
||||||
# This has to be exported, because pub interprets setting it to the empty
|
# This has to be exported, because pub interprets setting it to the empty
|
||||||
# string in the same way as setting it to ".".
|
# string in the same way as setting it to ".".
|
||||||
export PUB_CACHE="${PUB_CACHE:-"$FLUTTER_PUB_CACHE"}"
|
PUB_CACHE="${PUB_CACHE:-"$FLUTTER_PUB_CACHE"}"
|
||||||
|
export PUB_CACHE
|
||||||
fi
|
fi
|
||||||
|
|
||||||
generate_docs
|
OUTPUT_DIR=$(mktemp -d /tmp/dartdoc.XXXXX)
|
||||||
# Skip publishing docs for PRs and release candidate branches
|
DOC_DIR="$OUTPUT_DIR/doc"
|
||||||
if [[ -n "$LUCI_CI" && -z "$LUCI_PR" ]]; then
|
|
||||||
(cd "$FLUTTER_ROOT/dev/docs"; create_offline_zip)
|
|
||||||
(cd "$FLUTTER_ROOT/dev/docs"; create_docset)
|
|
||||||
(cd "$FLUTTER_ROOT/dev/docs"; move_offline_into_place)
|
|
||||||
deploy_docs
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Zip docs
|
function usage() {
|
||||||
cd "$FLUTTER_ROOT/dev/docs"
|
echo "Usage: $(basename "${BASH_SOURCE[0]}") [--keep-temp] [--output <output.zip>]"
|
||||||
zip -r api_docs.zip doc
|
echo ""
|
||||||
|
echo " --keep-temp Do not delete the temporary output directory created while generating docs."
|
||||||
|
echo " Normally the script deletes the temporary directory after generating the"
|
||||||
|
echo " output ZIP file."
|
||||||
|
echo " --output <output.zip> specifies where the output ZIP file containing the documentation data"
|
||||||
|
echo " will be written."
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_args() {
|
||||||
|
local arg
|
||||||
|
local args=()
|
||||||
|
KEEP_TEMP=0
|
||||||
|
DESTINATION="$FLUTTER_ROOT/dev/docs/api_docs.zip"
|
||||||
|
while (( "$#" )); do
|
||||||
|
case "$1" in
|
||||||
|
--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--keep-temp)
|
||||||
|
KEEP_TEMP=1
|
||||||
|
;;
|
||||||
|
--output)
|
||||||
|
DESTINATION="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
args=("${args[@]}" "$1")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
if [[ ${#args[@]} != 0 ]]; then
|
||||||
|
>&2 echo "ERROR: Unknown arguments: ${args[@]}"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate_docs() {
|
||||||
|
# Install and activate dartdoc.
|
||||||
|
# When updating to a new dartdoc version, please also update
|
||||||
|
# `dartdoc_options.yaml` to include newly introduced error and warning types.
|
||||||
|
"$DART" pub global activate dartdoc 6.3.0
|
||||||
|
|
||||||
|
# Install and activate the snippets tool, which resides in the
|
||||||
|
# assets-for-api-docs repo:
|
||||||
|
# https://github.com/flutter/assets-for-api-docs/tree/master/packages/snippets
|
||||||
|
"$DART" pub global activate snippets 0.4.0
|
||||||
|
|
||||||
|
# This script generates a unified doc set, and creates
|
||||||
|
# a custom index.html, placing everything into DOC_DIR.
|
||||||
|
|
||||||
|
# Make sure that create_api_docs.dart has all the dependencies it needs.
|
||||||
|
(cd "$FLUTTER_ROOT/dev/tools" && "$FLUTTER" pub get)
|
||||||
|
(cd "$FLUTTER_ROOT" && "$DART" --disable-dart-dev --enable-asserts "$FLUTTER_ROOT/dev/tools/create_api_docs.dart" --output-dir="$DOC_DIR")
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
echo "Writing docs build temporary output to $DOC_DIR"
|
||||||
|
mkdir -p "$DOC_DIR"
|
||||||
|
generate_docs
|
||||||
|
# If the destination isn't an absolute path, make it into one.
|
||||||
|
if ! [[ "$DESTINATION" =~ ^/ ]]; then
|
||||||
|
DESTINATION="$PWD/$DESTINATION"
|
||||||
|
fi
|
||||||
|
# Zip up doc directory and write the output to the destination.
|
||||||
|
(cd "$OUTPUT_DIR"; zip -r -9 -q "$DESTINATION" ./doc)
|
||||||
|
if [[ $KEEP_TMP == 1 ]]; then
|
||||||
|
echo "Temporary document generation output left in $OUTPUT_DIR"
|
||||||
|
else
|
||||||
|
echo "Removing Temporary document generation output from $OUTPUT_DIR"
|
||||||
|
rm -rf "$OUTPUT_DIR"
|
||||||
|
fi
|
||||||
|
echo "Wrote docs ZIP file to $DESTINATION"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args "$@"
|
||||||
|
main
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
/// This changes the DocSetPlatformFamily key to be "dartlang" instead of the
|
|
||||||
/// name of the package (usually "flutter").
|
|
||||||
///
|
|
||||||
/// This is so that the IntelliJ plugin for Dash will be able to go directly to
|
|
||||||
/// the docs for a symbol from a keystroke. Without this, flutter isn't part
|
|
||||||
/// of the list of package names it searches. After this, it finds the flutter
|
|
||||||
/// docs because they're declared here to be part of the "dartlang" family of
|
|
||||||
/// docs.
|
|
||||||
///
|
|
||||||
/// Dashing doesn't have a way to configure this, so we modify the Info.plist
|
|
||||||
/// directly to make the change.
|
|
||||||
void main(List<String> args) {
|
|
||||||
final File infoPlist = File('flutter.docset/Contents/Info.plist');
|
|
||||||
String contents = infoPlist.readAsStringSync();
|
|
||||||
|
|
||||||
// Since I didn't want to add the XML package as a dependency just for this,
|
|
||||||
// I just used a regular expression to make this simple change.
|
|
||||||
final RegExp findRe = RegExp(r'(\s*<key>DocSetPlatformFamily</key>\s*<string>)[^<]+(</string>)', multiLine: true);
|
|
||||||
contents = contents.replaceAllMapped(findRe, (Match match) {
|
|
||||||
return '${match.group(1)}dartlang${match.group(2)}';
|
|
||||||
});
|
|
||||||
infoPlist.writeAsStringSync(contents);
|
|
||||||
}
|
|
1095
dev/tools/create_api_docs.dart
Normal file
1095
dev/tools/create_api_docs.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,606 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:args/args.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
import 'package:platform/platform.dart';
|
|
||||||
import 'package:process/process.dart';
|
|
||||||
|
|
||||||
import 'dartdoc_checker.dart';
|
|
||||||
|
|
||||||
const String kDocsRoot = 'dev/docs';
|
|
||||||
const String kPublishRoot = '$kDocsRoot/doc';
|
|
||||||
|
|
||||||
const String kDummyPackageName = 'Flutter';
|
|
||||||
const String kPlatformIntegrationPackageName = 'platform_integration';
|
|
||||||
|
|
||||||
/// This script expects to run with the cwd as the root of the flutter repo. It
|
|
||||||
/// will generate documentation for the packages in `//packages/` and write the
|
|
||||||
/// documentation to `//dev/docs/doc/api/`.
|
|
||||||
///
|
|
||||||
/// This script also updates the index.html file so that it can be placed
|
|
||||||
/// at the root of api.flutter.dev. We are keeping the files inside of
|
|
||||||
/// api.flutter.dev/flutter for now, so we need to manipulate paths
|
|
||||||
/// a bit. See https://github.com/flutter/flutter/issues/3900 for more info.
|
|
||||||
///
|
|
||||||
/// This will only work on UNIX systems, not Windows. It requires that 'git' be
|
|
||||||
/// in your path. It requires that 'flutter' has been run previously. It uses
|
|
||||||
/// the version of Dart downloaded by the 'flutter' tool in this repository and
|
|
||||||
/// will crash if that is absent.
|
|
||||||
Future<void> main(List<String> arguments) async {
|
|
||||||
final ArgParser argParser = _createArgsParser();
|
|
||||||
final ArgResults args = argParser.parse(arguments);
|
|
||||||
if (args['help'] as bool) {
|
|
||||||
print ('Usage:');
|
|
||||||
print (argParser.usage);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
// If we're run from the `tools` dir, set the cwd to the repo root.
|
|
||||||
if (path.basename(Directory.current.path) == 'tools') {
|
|
||||||
Directory.current = Directory.current.parent.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ProcessResult flutter = Process.runSync('flutter', <String>[]);
|
|
||||||
final File versionFile = File('version');
|
|
||||||
if (flutter.exitCode != 0 || !versionFile.existsSync()) {
|
|
||||||
throw Exception('Failed to determine Flutter version.');
|
|
||||||
}
|
|
||||||
final String version = versionFile.readAsStringSync();
|
|
||||||
|
|
||||||
// Create the pubspec.yaml file.
|
|
||||||
final StringBuffer buf = StringBuffer();
|
|
||||||
buf.writeln('name: $kDummyPackageName');
|
|
||||||
buf.writeln('homepage: https://flutter.dev');
|
|
||||||
buf.writeln('version: 0.0.0');
|
|
||||||
buf.writeln('environment:');
|
|
||||||
buf.writeln(" sdk: '>=3.0.0-0 <4.0.0'");
|
|
||||||
buf.writeln('dependencies:');
|
|
||||||
for (final String package in findPackageNames()) {
|
|
||||||
buf.writeln(' $package:');
|
|
||||||
buf.writeln(' sdk: flutter');
|
|
||||||
}
|
|
||||||
buf.writeln(' $kPlatformIntegrationPackageName: 0.0.1');
|
|
||||||
buf.writeln('dependency_overrides:');
|
|
||||||
buf.writeln(' $kPlatformIntegrationPackageName:');
|
|
||||||
buf.writeln(' path: $kPlatformIntegrationPackageName');
|
|
||||||
File('$kDocsRoot/pubspec.yaml').writeAsStringSync(buf.toString());
|
|
||||||
|
|
||||||
// Create the library file.
|
|
||||||
final Directory libDir = Directory('$kDocsRoot/lib');
|
|
||||||
libDir.createSync();
|
|
||||||
|
|
||||||
final StringBuffer contents = StringBuffer('library temp_doc;\n\n');
|
|
||||||
for (final String libraryRef in libraryRefs()) {
|
|
||||||
contents.writeln("import 'package:$libraryRef';");
|
|
||||||
}
|
|
||||||
File('$kDocsRoot/lib/temp_doc.dart').writeAsStringSync(contents.toString());
|
|
||||||
|
|
||||||
final String flutterRoot = Directory.current.path;
|
|
||||||
final Map<String, String> pubEnvironment = <String, String>{
|
|
||||||
'FLUTTER_ROOT': flutterRoot,
|
|
||||||
};
|
|
||||||
|
|
||||||
// If there's a .pub-cache dir in the flutter root, use that.
|
|
||||||
final String pubCachePath = '$flutterRoot/.pub-cache';
|
|
||||||
if (Directory(pubCachePath).existsSync()) {
|
|
||||||
pubEnvironment['PUB_CACHE'] = pubCachePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String dartExecutable = '$flutterRoot/bin/cache/dart-sdk/bin/dart';
|
|
||||||
|
|
||||||
// Run pub.
|
|
||||||
ProcessWrapper process = ProcessWrapper(await runPubProcess(
|
|
||||||
dartBinaryPath: dartExecutable,
|
|
||||||
arguments: <String>['get'],
|
|
||||||
workingDirectory: kDocsRoot,
|
|
||||||
environment: pubEnvironment,
|
|
||||||
));
|
|
||||||
printStream(process.stdout, prefix: 'pub:stdout: ');
|
|
||||||
printStream(process.stderr, prefix: 'pub:stderr: ');
|
|
||||||
final int code = await process.done;
|
|
||||||
if (code != 0) {
|
|
||||||
exit(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
createFooter('$kDocsRoot/lib/', version);
|
|
||||||
copyAssets();
|
|
||||||
createSearchMetadata('$kDocsRoot/lib/opensearch.xml', '$kDocsRoot/doc/opensearch.xml');
|
|
||||||
cleanOutSnippets();
|
|
||||||
|
|
||||||
final List<String> dartdocBaseArgs = <String>[
|
|
||||||
'global',
|
|
||||||
'run',
|
|
||||||
if (args['checked'] as bool) '--enable-asserts',
|
|
||||||
'dartdoc',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Verify which version of snippets and dartdoc we're using.
|
|
||||||
final ProcessResult snippetsResult = Process.runSync(
|
|
||||||
dartExecutable,
|
|
||||||
<String>[
|
|
||||||
'pub',
|
|
||||||
'global',
|
|
||||||
'list',
|
|
||||||
],
|
|
||||||
workingDirectory: kDocsRoot,
|
|
||||||
environment: pubEnvironment,
|
|
||||||
stdoutEncoding: utf8,
|
|
||||||
);
|
|
||||||
print('');
|
|
||||||
final Iterable<RegExpMatch> versionMatches = RegExp(r'^(?<name>snippets|dartdoc) (?<version>[^\s]+)', multiLine: true)
|
|
||||||
.allMatches(snippetsResult.stdout as String);
|
|
||||||
for (final RegExpMatch match in versionMatches) {
|
|
||||||
print('${match.namedGroup('name')} version: ${match.namedGroup('version')}');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('flutter version: $version\n');
|
|
||||||
|
|
||||||
// Dartdoc warnings and errors in these packages are considered fatal.
|
|
||||||
// All packages owned by flutter should be in the list.
|
|
||||||
final List<String> flutterPackages = <String>[
|
|
||||||
kDummyPackageName,
|
|
||||||
kPlatformIntegrationPackageName,
|
|
||||||
...findPackageNames(),
|
|
||||||
// TODO(goderbauer): Figure out how to only include `dart:ui` of `sky_engine` below, https://github.com/dart-lang/dartdoc/issues/2278.
|
|
||||||
// 'sky_engine',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Generate the documentation.
|
|
||||||
// We don't need to exclude flutter_tools in this list because it's not in the
|
|
||||||
// recursive dependencies of the package defined at dev/docs/pubspec.yaml
|
|
||||||
final List<String> dartdocArgs = <String>[
|
|
||||||
...dartdocBaseArgs,
|
|
||||||
'--allow-tools',
|
|
||||||
if (args['json'] as bool) '--json',
|
|
||||||
if (args['validate-links'] as bool) '--validate-links' else '--no-validate-links',
|
|
||||||
'--link-to-source-excludes', '../../bin/cache',
|
|
||||||
'--link-to-source-root', '../..',
|
|
||||||
'--link-to-source-uri-template', 'https://github.com/flutter/flutter/blob/master/%f%#L%l%',
|
|
||||||
'--inject-html',
|
|
||||||
'--use-base-href',
|
|
||||||
'--header', 'styles.html',
|
|
||||||
'--header', 'analytics.html',
|
|
||||||
'--header', 'survey.html',
|
|
||||||
'--header', 'snippets.html',
|
|
||||||
'--header', 'opensearch.html',
|
|
||||||
'--footer-text', 'lib/footer.html',
|
|
||||||
'--allow-warnings-in-packages', flutterPackages.join(','),
|
|
||||||
'--exclude-packages',
|
|
||||||
<String>[
|
|
||||||
'analyzer',
|
|
||||||
'args',
|
|
||||||
'barback',
|
|
||||||
'csslib',
|
|
||||||
'flutter_goldens',
|
|
||||||
'flutter_goldens_client',
|
|
||||||
'front_end',
|
|
||||||
'fuchsia_remote_debug_protocol',
|
|
||||||
'glob',
|
|
||||||
'html',
|
|
||||||
'http_multi_server',
|
|
||||||
'io',
|
|
||||||
'isolate',
|
|
||||||
'js',
|
|
||||||
'kernel',
|
|
||||||
'logging',
|
|
||||||
'mime',
|
|
||||||
'mockito',
|
|
||||||
'node_preamble',
|
|
||||||
'plugin',
|
|
||||||
'shelf',
|
|
||||||
'shelf_packages_handler',
|
|
||||||
'shelf_static',
|
|
||||||
'shelf_web_socket',
|
|
||||||
'utf',
|
|
||||||
'watcher',
|
|
||||||
'yaml',
|
|
||||||
].join(','),
|
|
||||||
'--exclude',
|
|
||||||
<String>[
|
|
||||||
'dart:io/network_policy.dart', // dart-lang/dartdoc#2437
|
|
||||||
'package:Flutter/temp_doc.dart',
|
|
||||||
'package:http/browser_client.dart',
|
|
||||||
'package:intl/intl_browser.dart',
|
|
||||||
'package:matcher/mirror_matchers.dart',
|
|
||||||
'package:quiver/io.dart',
|
|
||||||
'package:quiver/mirrors.dart',
|
|
||||||
'package:vm_service_client/vm_service_client.dart',
|
|
||||||
'package:web_socket_channel/html.dart',
|
|
||||||
].join(','),
|
|
||||||
'--favicon=favicon.ico',
|
|
||||||
'--package-order', 'flutter,Dart,$kPlatformIntegrationPackageName,flutter_test,flutter_driver',
|
|
||||||
'--auto-include-dependencies',
|
|
||||||
];
|
|
||||||
|
|
||||||
String quote(String arg) => arg.contains(' ') ? "'$arg'" : arg;
|
|
||||||
print('Executing: (cd $kDocsRoot ; $dartExecutable ${dartdocArgs.map<String>(quote).join(' ')})');
|
|
||||||
|
|
||||||
process = ProcessWrapper(await runPubProcess(
|
|
||||||
dartBinaryPath: dartExecutable,
|
|
||||||
arguments: dartdocArgs,
|
|
||||||
workingDirectory: kDocsRoot,
|
|
||||||
environment: pubEnvironment,
|
|
||||||
));
|
|
||||||
printStream(process.stdout, prefix: args['json'] as bool ? '' : 'dartdoc:stdout: ',
|
|
||||||
filter: args['verbose'] as bool ? const <Pattern>[] : <Pattern>[
|
|
||||||
RegExp(r'^Generating docs for library '), // unnecessary verbosity
|
|
||||||
],
|
|
||||||
);
|
|
||||||
printStream(process.stderr, prefix: args['json'] as bool ? '' : 'dartdoc:stderr: ',
|
|
||||||
filter: args['verbose'] as bool ? const <Pattern>[] : <Pattern>[
|
|
||||||
RegExp(r'^ warning: .+: \(.+/\.pub-cache/hosted/pub.dartlang.org/.+\)'), // packages outside our control
|
|
||||||
],
|
|
||||||
);
|
|
||||||
final int exitCode = await process.done;
|
|
||||||
|
|
||||||
if (exitCode != 0) {
|
|
||||||
exit(exitCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
sanityCheckDocs();
|
|
||||||
checkForUnresolvedDirectives('$kPublishRoot/api');
|
|
||||||
|
|
||||||
createIndexAndCleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgParser _createArgsParser() {
|
|
||||||
final ArgParser parser = ArgParser();
|
|
||||||
parser.addFlag('help', abbr: 'h', negatable: false,
|
|
||||||
help: 'Show command help.');
|
|
||||||
parser.addFlag('verbose', defaultsTo: true,
|
|
||||||
help: 'Whether to report all error messages (on) or attempt to '
|
|
||||||
'filter out some known false positives (off). Shut this off '
|
|
||||||
'locally if you want to address Flutter-specific issues.');
|
|
||||||
parser.addFlag('checked', abbr: 'c',
|
|
||||||
help: 'Run dartdoc with asserts enabled.');
|
|
||||||
parser.addFlag('json',
|
|
||||||
help: 'Display json-formatted output from dartdoc and skip stdout/stderr prefixing.');
|
|
||||||
parser.addFlag('validate-links',
|
|
||||||
help: 'Display warnings for broken links generated by dartdoc (slow)');
|
|
||||||
return parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
final RegExp gitBranchRegexp = RegExp(r'^## (.*)');
|
|
||||||
|
|
||||||
/// Get the name of the release branch.
|
|
||||||
///
|
|
||||||
/// On LUCI builds, the git HEAD is detached, so first check for the env
|
|
||||||
/// variable "LUCI_BRANCH"; if it is not set, fall back to calling git.
|
|
||||||
String getBranchName({
|
|
||||||
@visibleForTesting
|
|
||||||
Platform platform = const LocalPlatform(),
|
|
||||||
@visibleForTesting
|
|
||||||
ProcessManager processManager = const LocalProcessManager(),
|
|
||||||
}) {
|
|
||||||
final String? luciBranch = platform.environment['LUCI_BRANCH'];
|
|
||||||
if (luciBranch != null && luciBranch.trim().isNotEmpty) {
|
|
||||||
return luciBranch.trim();
|
|
||||||
}
|
|
||||||
final ProcessResult gitResult = processManager.runSync(<String>['git', 'status', '-b', '--porcelain']);
|
|
||||||
if (gitResult.exitCode != 0) {
|
|
||||||
throw 'git status exit with non-zero exit code: ${gitResult.exitCode}';
|
|
||||||
}
|
|
||||||
final RegExpMatch? gitBranchMatch = gitBranchRegexp.firstMatch(
|
|
||||||
(gitResult.stdout as String).trim().split('\n').first);
|
|
||||||
return gitBranchMatch == null ? '' : gitBranchMatch.group(1)!.split('...').first;
|
|
||||||
}
|
|
||||||
|
|
||||||
String gitRevision() {
|
|
||||||
const int kGitRevisionLength = 10;
|
|
||||||
|
|
||||||
final ProcessResult gitResult = Process.runSync('git', <String>['rev-parse', 'HEAD']);
|
|
||||||
if (gitResult.exitCode != 0) {
|
|
||||||
throw 'git rev-parse exit with non-zero exit code: ${gitResult.exitCode}';
|
|
||||||
}
|
|
||||||
final String gitRevision = (gitResult.stdout as String).trim();
|
|
||||||
|
|
||||||
return gitRevision.length > kGitRevisionLength ? gitRevision.substring(0, kGitRevisionLength) : gitRevision;
|
|
||||||
}
|
|
||||||
|
|
||||||
void createFooter(String footerPath, String version) {
|
|
||||||
final String timestamp = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());
|
|
||||||
final String gitBranch = getBranchName();
|
|
||||||
final String gitBranchOut = gitBranch.isEmpty ? '' : '• $gitBranch';
|
|
||||||
File('${footerPath}footer.html').writeAsStringSync('<script src="footer.js"></script>');
|
|
||||||
File('$kPublishRoot/api/footer.js')
|
|
||||||
..createSync(recursive: true)
|
|
||||||
..writeAsStringSync('''
|
|
||||||
(function() {
|
|
||||||
var span = document.querySelector('footer>span');
|
|
||||||
if (span) {
|
|
||||||
span.innerText = 'Flutter $version • $timestamp • ${gitRevision()} $gitBranchOut';
|
|
||||||
}
|
|
||||||
var sourceLink = document.querySelector('a.source-link');
|
|
||||||
if (sourceLink) {
|
|
||||||
sourceLink.href = sourceLink.href.replace('/master/', '/${gitRevision()}/');
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
''');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates an OpenSearch XML description that can be used to add a custom
|
|
||||||
/// search for Flutter API docs to the browser. Unfortunately, it has to know
|
|
||||||
/// the URL to which site to search, so we customize it here based upon the
|
|
||||||
/// branch name.
|
|
||||||
void createSearchMetadata(String templatePath, String metadataPath) {
|
|
||||||
final String template = File(templatePath).readAsStringSync();
|
|
||||||
final String branch = getBranchName();
|
|
||||||
final String metadata = template.replaceAll(
|
|
||||||
'{SITE_URL}',
|
|
||||||
branch == 'stable' ? 'https://api.flutter.dev/' : 'https://master-api.flutter.dev/',
|
|
||||||
);
|
|
||||||
Directory(path.dirname(metadataPath)).create(recursive: true);
|
|
||||||
File(metadataPath).writeAsStringSync(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively copies `srcDir` to `destDir`, invoking [onFileCopied], if
|
|
||||||
/// specified, for each source/destination file pair.
|
|
||||||
///
|
|
||||||
/// Creates `destDir` if needed.
|
|
||||||
void copyDirectorySync(Directory srcDir, Directory destDir, [void Function(File srcFile, File destFile)? onFileCopied]) {
|
|
||||||
if (!srcDir.existsSync()) {
|
|
||||||
throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!destDir.existsSync()) {
|
|
||||||
destDir.createSync(recursive: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final FileSystemEntity entity in srcDir.listSync()) {
|
|
||||||
final String newPath = path.join(destDir.path, path.basename(entity.path));
|
|
||||||
if (entity is File) {
|
|
||||||
final File newFile = File(newPath);
|
|
||||||
entity.copySync(newPath);
|
|
||||||
onFileCopied?.call(entity, newFile);
|
|
||||||
} else if (entity is Directory) {
|
|
||||||
copyDirectorySync(entity, Directory(newPath));
|
|
||||||
} else {
|
|
||||||
throw Exception('${entity.path} is neither File nor Directory');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void copyAssets() {
|
|
||||||
final Directory assetsDir = Directory(path.join(kPublishRoot, 'assets'));
|
|
||||||
if (assetsDir.existsSync()) {
|
|
||||||
assetsDir.deleteSync(recursive: true);
|
|
||||||
}
|
|
||||||
copyDirectorySync(
|
|
||||||
Directory(path.join(kDocsRoot, 'assets')),
|
|
||||||
Directory(path.join(kPublishRoot, 'assets')),
|
|
||||||
(File src, File dest) => print('Copied ${src.path} to ${dest.path}'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clean out any existing snippets so that we don't publish old files from
|
|
||||||
/// previous runs accidentally.
|
|
||||||
void cleanOutSnippets() {
|
|
||||||
final Directory snippetsDir = Directory(path.join(kPublishRoot, 'snippets'));
|
|
||||||
if (snippetsDir.existsSync()) {
|
|
||||||
snippetsDir
|
|
||||||
..deleteSync(recursive: true)
|
|
||||||
..createSync(recursive: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _sanityCheckExample(String fileString, String regExpString) {
|
|
||||||
final File file = File(fileString);
|
|
||||||
if (file.existsSync()) {
|
|
||||||
final RegExp regExp = RegExp(regExpString, dotAll: true);
|
|
||||||
final String contents = file.readAsStringSync();
|
|
||||||
if (!regExp.hasMatch(contents)) {
|
|
||||||
throw Exception("Missing example code matching '$regExpString' in ${file.path}.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Exception(
|
|
||||||
"Missing example code sanity test file ${file.path}. Either it didn't get published, or you might have to update the test to look at a different file.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs a sanity check by running a test.
|
|
||||||
void sanityCheckDocs([Platform platform = const LocalPlatform()]) {
|
|
||||||
final List<String> canaries = <String>[
|
|
||||||
'$kPublishRoot/assets/overrides.css',
|
|
||||||
'$kPublishRoot/api/dart-io/File-class.html',
|
|
||||||
'$kPublishRoot/api/dart-ui/Canvas-class.html',
|
|
||||||
'$kPublishRoot/api/dart-ui/Canvas/drawRect.html',
|
|
||||||
'$kPublishRoot/api/flutter_driver/FlutterDriver/FlutterDriver.connectedTo.html',
|
|
||||||
'$kPublishRoot/api/flutter_test/WidgetTester/pumpWidget.html',
|
|
||||||
'$kPublishRoot/api/material/Material-class.html',
|
|
||||||
'$kPublishRoot/api/material/Tooltip-class.html',
|
|
||||||
'$kPublishRoot/api/widgets/Widget-class.html',
|
|
||||||
'$kPublishRoot/api/widgets/Listener-class.html',
|
|
||||||
];
|
|
||||||
for (final String canary in canaries) {
|
|
||||||
if (!File(canary).existsSync()) {
|
|
||||||
throw Exception('Missing "$canary", which probably means the documentation failed to build correctly.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Make sure at least one example of each kind includes source code.
|
|
||||||
|
|
||||||
// Check a "sample" example, any one will do.
|
|
||||||
_sanityCheckExample(
|
|
||||||
'$kPublishRoot/api/widgets/showGeneralDialog.html',
|
|
||||||
r'\s*<pre\s+id="longSnippet1".*<code\s+class="language-dart">\s*import 'package:flutter/material.dart';',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check a "snippet" example, any one will do.
|
|
||||||
_sanityCheckExample(
|
|
||||||
'$kPublishRoot/api/widgets/ModalRoute/barrierColor.html',
|
|
||||||
r'\s*<pre.*id="sample-code">.*Color\s+get\s+barrierColor.*</pre>',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check a "dartpad" example, any one will do, and check for the correct URL
|
|
||||||
// arguments.
|
|
||||||
// Just use "master" for any branch other than the LUCI_BRANCH.
|
|
||||||
final String? luciBranch = platform.environment['LUCI_BRANCH']?.trim();
|
|
||||||
final String expectedBranch = luciBranch != null && luciBranch.isNotEmpty ? luciBranch : 'master';
|
|
||||||
final List<String> argumentRegExps = <String>[
|
|
||||||
r'split=\d+',
|
|
||||||
r'run=true',
|
|
||||||
r'sample_id=widgets\.Listener\.\d+',
|
|
||||||
'sample_channel=$expectedBranch',
|
|
||||||
'channel=$expectedBranch',
|
|
||||||
];
|
|
||||||
for (final String argumentRegExp in argumentRegExps) {
|
|
||||||
_sanityCheckExample(
|
|
||||||
'$kPublishRoot/api/widgets/Listener-class.html',
|
|
||||||
r'\s*<iframe\s+class="snippet-dartpad"\s+src="'
|
|
||||||
r'https:\/\/dartpad.dev\/embed-flutter.html\?.*?\b'
|
|
||||||
'$argumentRegExp'
|
|
||||||
r'\b.*">\s*<\/iframe>',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a custom index.html because we try to maintain old
|
|
||||||
/// paths. Cleanup unused index.html files no longer needed.
|
|
||||||
void createIndexAndCleanup() {
|
|
||||||
print('\nCreating a custom index.html in $kPublishRoot/index.html');
|
|
||||||
removeOldFlutterDocsDir();
|
|
||||||
renameApiDir();
|
|
||||||
copyIndexToRootOfDocs();
|
|
||||||
addHtmlBaseToIndex();
|
|
||||||
changePackageToSdkInTitlebar();
|
|
||||||
putRedirectInOldIndexLocation();
|
|
||||||
writeSnippetsIndexFile();
|
|
||||||
print('\nDocs ready to go!');
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeOldFlutterDocsDir() {
|
|
||||||
try {
|
|
||||||
Directory('$kPublishRoot/flutter').deleteSync(recursive: true);
|
|
||||||
} on FileSystemException {
|
|
||||||
// If the directory does not exist, that's OK.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void renameApiDir() {
|
|
||||||
Directory('$kPublishRoot/api').renameSync('$kPublishRoot/flutter');
|
|
||||||
}
|
|
||||||
|
|
||||||
void copyIndexToRootOfDocs() {
|
|
||||||
File('$kPublishRoot/flutter/index.html').copySync('$kPublishRoot/index.html');
|
|
||||||
}
|
|
||||||
|
|
||||||
void changePackageToSdkInTitlebar() {
|
|
||||||
final File indexFile = File('$kPublishRoot/index.html');
|
|
||||||
String indexContents = indexFile.readAsStringSync();
|
|
||||||
indexContents = indexContents.replaceFirst(
|
|
||||||
'<li><a href="https://flutter.dev">Flutter package</a></li>',
|
|
||||||
'<li><a href="https://flutter.dev">Flutter SDK</a></li>',
|
|
||||||
);
|
|
||||||
|
|
||||||
indexFile.writeAsStringSync(indexContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addHtmlBaseToIndex() {
|
|
||||||
final File indexFile = File('$kPublishRoot/index.html');
|
|
||||||
String indexContents = indexFile.readAsStringSync();
|
|
||||||
indexContents = indexContents.replaceFirst(
|
|
||||||
'</title>\n',
|
|
||||||
'</title>\n <base href="./flutter/">\n',
|
|
||||||
);
|
|
||||||
indexContents = indexContents.replaceAll(
|
|
||||||
'href="Android/Android-library.html"',
|
|
||||||
'href="/javadoc/"',
|
|
||||||
);
|
|
||||||
indexContents = indexContents.replaceAll(
|
|
||||||
'href="iOS/iOS-library.html"',
|
|
||||||
'href="/objcdoc/"',
|
|
||||||
);
|
|
||||||
|
|
||||||
indexFile.writeAsStringSync(indexContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
void putRedirectInOldIndexLocation() {
|
|
||||||
const String metaTag = '<meta http-equiv="refresh" content="0;URL=../index.html">';
|
|
||||||
File('$kPublishRoot/flutter/index.html').writeAsStringSync(metaTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeSnippetsIndexFile() {
|
|
||||||
final Directory snippetsDir = Directory(path.join(kPublishRoot, 'snippets'));
|
|
||||||
if (snippetsDir.existsSync()) {
|
|
||||||
const JsonEncoder jsonEncoder = JsonEncoder.withIndent(' ');
|
|
||||||
final Iterable<File> files = snippetsDir
|
|
||||||
.listSync()
|
|
||||||
.whereType<File>()
|
|
||||||
.where((File file) => path.extension(file.path) == '.json');
|
|
||||||
// Combine all the metadata into a single JSON array.
|
|
||||||
final Iterable<String> fileContents = files.map((File file) => file.readAsStringSync());
|
|
||||||
final List<dynamic> metadataObjects = fileContents.map<dynamic>(json.decode).toList();
|
|
||||||
final String jsonArray = jsonEncoder.convert(metadataObjects);
|
|
||||||
File('$kPublishRoot/snippets/index.json').writeAsStringSync(jsonArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> findPackageNames() {
|
|
||||||
return findPackages().map<String>((FileSystemEntity file) => path.basename(file.path)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds all packages in the Flutter SDK
|
|
||||||
List<Directory> findPackages() {
|
|
||||||
return Directory('packages')
|
|
||||||
.listSync()
|
|
||||||
.where((FileSystemEntity entity) {
|
|
||||||
if (entity is! Directory) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final File pubspec = File('${entity.path}/pubspec.yaml');
|
|
||||||
if (!pubspec.existsSync()) {
|
|
||||||
print("Unexpected package '${entity.path}' found in packages directory");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// TODO(ianh): Use a real YAML parser here
|
|
||||||
return !pubspec.readAsStringSync().contains('nodoc: true');
|
|
||||||
})
|
|
||||||
.cast<Directory>()
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns import or on-disk paths for all libraries in the Flutter SDK.
|
|
||||||
Iterable<String> libraryRefs() sync* {
|
|
||||||
for (final Directory dir in findPackages()) {
|
|
||||||
final String dirName = path.basename(dir.path);
|
|
||||||
for (final FileSystemEntity file in Directory('${dir.path}/lib').listSync()) {
|
|
||||||
if (file is File && file.path.endsWith('.dart')) {
|
|
||||||
yield '$dirName/${path.basename(file.path)}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a fake package for platform integration APIs.
|
|
||||||
yield '$kPlatformIntegrationPackageName/android.dart';
|
|
||||||
yield '$kPlatformIntegrationPackageName/ios.dart';
|
|
||||||
}
|
|
||||||
|
|
||||||
void printStream(Stream<List<int>> stream, { String prefix = '', List<Pattern> filter = const <Pattern>[] }) {
|
|
||||||
stream
|
|
||||||
.transform<String>(utf8.decoder)
|
|
||||||
.transform<String>(const LineSplitter())
|
|
||||||
.listen((String line) {
|
|
||||||
if (!filter.any((Pattern pattern) => line.contains(pattern))) {
|
|
||||||
print('$prefix$line'.trim());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Process> runPubProcess({
|
|
||||||
required String dartBinaryPath,
|
|
||||||
required List<String> arguments,
|
|
||||||
String? workingDirectory,
|
|
||||||
Map<String, String>? environment,
|
|
||||||
@visibleForTesting
|
|
||||||
ProcessManager processManager = const LocalProcessManager(),
|
|
||||||
}) {
|
|
||||||
return processManager.start(
|
|
||||||
<Object>[dartBinaryPath, 'pub', ...arguments],
|
|
||||||
workingDirectory: workingDirectory,
|
|
||||||
environment: environment,
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:archive/archive.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
|
|
||||||
const String kDocRoot = 'dev/docs/doc';
|
|
||||||
|
|
||||||
/// This script downloads an archive of Javadoc and objc doc for the engine from
|
|
||||||
/// the artifact store and extracts them to the location used for Dartdoc.
|
|
||||||
Future<void> main(List<String> args) async {
|
|
||||||
final String engineVersion = File('bin/internal/engine.version').readAsStringSync().trim();
|
|
||||||
String engineRealm = File('bin/internal/engine.realm').readAsStringSync().trim();
|
|
||||||
if (engineRealm.isNotEmpty) {
|
|
||||||
engineRealm = '$engineRealm/';
|
|
||||||
}
|
|
||||||
|
|
||||||
final String javadocUrl = 'https://storage.googleapis.com/${engineRealm}flutter_infra_release/flutter/$engineVersion/android-javadoc.zip';
|
|
||||||
generateDocs(javadocUrl, 'javadoc', 'io/flutter/view/FlutterView.html');
|
|
||||||
|
|
||||||
final String objcdocUrl = 'https://storage.googleapis.com/${engineRealm}flutter_infra_release/flutter/$engineVersion/ios-objcdoc.zip';
|
|
||||||
generateDocs(objcdocUrl, 'objcdoc', 'Classes/FlutterViewController.html');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetches the zip archive at the specified url.
|
|
||||||
///
|
|
||||||
/// Returns null if the archive fails to download after [maxTries] attempts.
|
|
||||||
Future<Archive?> fetchArchive(String url, int maxTries) async {
|
|
||||||
List<int>? responseBytes;
|
|
||||||
for (int i = 0; i < maxTries; i++) {
|
|
||||||
final http.Response response = await http.get(Uri.parse(url));
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
responseBytes = response.bodyBytes;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
stderr.writeln('Failed attempt ${i+1} to fetch $url.');
|
|
||||||
|
|
||||||
// On failure print a short snipped from the body in case it's helpful.
|
|
||||||
final int bodyLength = min(1024, response.body.length);
|
|
||||||
stderr.writeln('Response status code ${response.statusCode}. Body: ${response.body.substring(0, bodyLength)}');
|
|
||||||
sleep(const Duration(seconds: 1));
|
|
||||||
}
|
|
||||||
return responseBytes == null ? null : ZipDecoder().decodeBytes(responseBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> generateDocs(String url, String docName, String checkFile) async {
|
|
||||||
const int maxTries = 5;
|
|
||||||
final Archive? archive = await fetchArchive(url, maxTries);
|
|
||||||
if (archive == null) {
|
|
||||||
stderr.writeln('Failed to fetch zip archive from: $url after $maxTries attempts. Giving up.');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Directory output = Directory('$kDocRoot/$docName');
|
|
||||||
print('Extracting $docName to ${output.path}');
|
|
||||||
output.createSync(recursive: true);
|
|
||||||
|
|
||||||
for (final ArchiveFile af in archive) {
|
|
||||||
if (!af.name.endsWith('/')) {
|
|
||||||
final File file = File('${output.path}/${af.name}');
|
|
||||||
file.createSync(recursive: true);
|
|
||||||
file.writeAsBytesSync(af.content as List<int>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If object then copy files to old location if the archive is using the new location.
|
|
||||||
final bool exists = Directory('$kDocRoot/$docName/objectc_docs').existsSync();
|
|
||||||
if (exists) {
|
|
||||||
copyFolder(Directory('$kDocRoot/$docName/objectc_docs'), Directory('$kDocRoot/$docName/'));
|
|
||||||
}
|
|
||||||
|
|
||||||
final File testFile = File('${output.path}/$checkFile');
|
|
||||||
if (!testFile.existsSync()) {
|
|
||||||
print('Expected file ${testFile.path} not found');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
print('$docName ready to go!');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copies the files in a directory recursively to a new location.
|
|
||||||
void copyFolder(Directory source, Directory destination) {
|
|
||||||
source.listSync()
|
|
||||||
.forEach((FileSystemEntity entity) {
|
|
||||||
if (entity is Directory) {
|
|
||||||
final Directory newDirectory = Directory(path.join(destination.absolute.path, path.basename(entity.path)));
|
|
||||||
newDirectory.createSync();
|
|
||||||
copyFolder(entity.absolute, newDirectory);
|
|
||||||
} else if (entity is File) {
|
|
||||||
entity.copySync(path.join(destination.path, path.basename(entity.path)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -12,6 +12,7 @@ dependencies:
|
|||||||
meta: 1.9.1
|
meta: 1.9.1
|
||||||
path: 1.8.3
|
path: 1.8.3
|
||||||
process: 4.2.4
|
process: 4.2.4
|
||||||
|
pub_semver: 2.1.4
|
||||||
|
|
||||||
async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
@ -45,7 +46,6 @@ dev_dependencies:
|
|||||||
node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
pub_semver: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
|
||||||
shelf: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
shelf: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
shelf_static: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
shelf_static: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
195
dev/tools/test/create_api_docs_test.dart
Normal file
195
dev/tools/test/create_api_docs_test.dart
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
import 'package:pub_semver/pub_semver.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
|
||||||
|
import '../create_api_docs.dart' as apidocs;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('getBranchName does not call git if env LUCI_BRANCH provided', () {
|
||||||
|
final Platform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'LUCI_BRANCH': branchName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final ProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['flutter', '--version', '--machine'],
|
||||||
|
stdout: testVersionInfo,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
apidocs.FlutterInformation(platform: platform, processManager: processManager).getBranchName(),
|
||||||
|
branchName,
|
||||||
|
);
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getBranchName calls git if env LUCI_BRANCH not provided', () {
|
||||||
|
final Platform platform = FakePlatform(
|
||||||
|
environment: <String, String>{},
|
||||||
|
);
|
||||||
|
|
||||||
|
final ProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['flutter', '--version', '--machine'],
|
||||||
|
stdout: testVersionInfo,
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'status', '-b', '--porcelain'],
|
||||||
|
stdout: '## $branchName',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
apidocs.FlutterInformation(platform: platform, processManager: processManager).getBranchName(),
|
||||||
|
branchName,
|
||||||
|
);
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getBranchName calls git if env LUCI_BRANCH is empty', () {
|
||||||
|
final Platform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'LUCI_BRANCH': '',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final ProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['flutter', '--version', '--machine'],
|
||||||
|
stdout: testVersionInfo,
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'status', '-b', '--porcelain'],
|
||||||
|
stdout: '## $branchName',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
apidocs.FlutterInformation(platform: platform, processManager: processManager).getBranchName(),
|
||||||
|
branchName,
|
||||||
|
);
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("runPubProcess doesn't use the pub binary", () {
|
||||||
|
final Platform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'FLUTTER_ROOT': '/flutter',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final ProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['/flutter/bin/dart', 'pub', '--one', '--two'],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
apidocs.FlutterInformation.instance =
|
||||||
|
apidocs.FlutterInformation(platform: platform, processManager: processManager);
|
||||||
|
|
||||||
|
apidocs.runPubProcess(
|
||||||
|
arguments: <String>['--one', '--two'],
|
||||||
|
processManager: processManager,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('FlutterInformation', () {
|
||||||
|
late FakeProcessManager fakeProcessManager;
|
||||||
|
late FakePlatform fakePlatform;
|
||||||
|
late MemoryFileSystem memoryFileSystem;
|
||||||
|
late apidocs.FlutterInformation flutterInformation;
|
||||||
|
|
||||||
|
void setUpWithEnvironment(Map<String, String> environment) {
|
||||||
|
fakePlatform = FakePlatform(environment: environment);
|
||||||
|
flutterInformation = apidocs.FlutterInformation(
|
||||||
|
filesystem: memoryFileSystem,
|
||||||
|
processManager: fakeProcessManager,
|
||||||
|
platform: fakePlatform,
|
||||||
|
);
|
||||||
|
apidocs.FlutterInformation.instance = flutterInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fakeProcessManager = FakeProcessManager.empty();
|
||||||
|
memoryFileSystem = MemoryFileSystem();
|
||||||
|
setUpWithEnvironment(<String, String>{});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls out to flutter if FLUTTER_VERSION is not set', () async {
|
||||||
|
fakeProcessManager.addCommand(
|
||||||
|
const FakeCommand(command: <Pattern>['flutter', '--version', '--machine'], stdout: testVersionInfo));
|
||||||
|
fakeProcessManager.addCommand(
|
||||||
|
const FakeCommand(command: <Pattern>['git', 'status', '-b', '--porcelain'], stdout: testVersionInfo));
|
||||||
|
final Map<String, dynamic> info = flutterInformation.getFlutterInformation();
|
||||||
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||||
|
expect(info['frameworkVersion'], equals(Version.parse('2.5.0')));
|
||||||
|
});
|
||||||
|
test("doesn't call out to flutter if FLUTTER_VERSION is set", () async {
|
||||||
|
setUpWithEnvironment(<String, String>{
|
||||||
|
'FLUTTER_VERSION': testVersionInfo,
|
||||||
|
});
|
||||||
|
fakeProcessManager.addCommand(
|
||||||
|
const FakeCommand(command: <Pattern>['git', 'status', '-b', '--porcelain'], stdout: testVersionInfo));
|
||||||
|
final Map<String, dynamic> info = flutterInformation.getFlutterInformation();
|
||||||
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||||
|
expect(info['frameworkVersion'], equals(Version.parse('2.5.0')));
|
||||||
|
});
|
||||||
|
test('getFlutterRoot calls out to flutter if FLUTTER_ROOT is not set', () async {
|
||||||
|
fakeProcessManager.addCommand(
|
||||||
|
const FakeCommand(command: <Pattern>['flutter', '--version', '--machine'], stdout: testVersionInfo));
|
||||||
|
fakeProcessManager.addCommand(
|
||||||
|
const FakeCommand(command: <Pattern>['git', 'status', '-b', '--porcelain'], stdout: testVersionInfo));
|
||||||
|
final Directory root = flutterInformation.getFlutterRoot();
|
||||||
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||||
|
expect(root.path, equals('/home/user/flutter'));
|
||||||
|
});
|
||||||
|
test("getFlutterRoot doesn't call out to flutter if FLUTTER_ROOT is set", () async {
|
||||||
|
setUpWithEnvironment(<String, String>{'FLUTTER_ROOT': '/home/user/flutter'});
|
||||||
|
final Directory root = flutterInformation.getFlutterRoot();
|
||||||
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||||
|
expect(root.path, equals('/home/user/flutter'));
|
||||||
|
});
|
||||||
|
test('parses version properly', () async {
|
||||||
|
fakePlatform.environment['FLUTTER_VERSION'] = testVersionInfo;
|
||||||
|
fakeProcessManager.addCommand(
|
||||||
|
const FakeCommand(command: <Pattern>['git', 'status', '-b', '--porcelain'], stdout: testVersionInfo));
|
||||||
|
final Map<String, dynamic> info = flutterInformation.getFlutterInformation();
|
||||||
|
expect(info['frameworkVersion'], isNotNull);
|
||||||
|
expect(info['frameworkVersion'], equals(Version.parse('2.5.0')));
|
||||||
|
expect(info['dartSdkVersion'], isNotNull);
|
||||||
|
expect(info['dartSdkVersion'], equals(Version.parse('2.14.0-360.0.dev')));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const String branchName = 'stable';
|
||||||
|
const String testVersionInfo = '''
|
||||||
|
{
|
||||||
|
"frameworkVersion": "2.5.0",
|
||||||
|
"channel": "$branchName",
|
||||||
|
"repositoryUrl": "git@github.com:flutter/flutter.git",
|
||||||
|
"frameworkRevision": "0000000000000000000000000000000000000000",
|
||||||
|
"frameworkCommitDate": "2021-07-28 13:03:40 -0700",
|
||||||
|
"engineRevision": "0000000000000000000000000000000000000001",
|
||||||
|
"dartSdkVersion": "2.14.0 (build 2.14.0-360.0.dev)",
|
||||||
|
"flutterRoot": "/home/user/flutter"
|
||||||
|
}
|
||||||
|
''';
|
@ -1,98 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'package:platform/platform.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
|
|
||||||
import '../dartdoc.dart' show getBranchName, runPubProcess;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
const String branchName = 'stable';
|
|
||||||
test('getBranchName does not call git if env LUCI_BRANCH provided', () {
|
|
||||||
final Platform platform = FakePlatform(
|
|
||||||
environment: <String, String>{
|
|
||||||
'LUCI_BRANCH': branchName,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final ProcessManager processManager = FakeProcessManager.empty();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
getBranchName(
|
|
||||||
platform: platform,
|
|
||||||
processManager: processManager,
|
|
||||||
),
|
|
||||||
branchName,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getBranchName calls git if env LUCI_BRANCH not provided', () {
|
|
||||||
final Platform platform = FakePlatform(
|
|
||||||
environment: <String, String>{},
|
|
||||||
);
|
|
||||||
|
|
||||||
final ProcessManager processManager = FakeProcessManager.list(
|
|
||||||
<FakeCommand>[
|
|
||||||
const FakeCommand(
|
|
||||||
command: <String>['git', 'status', '-b', '--porcelain'],
|
|
||||||
stdout: '## $branchName',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
getBranchName(
|
|
||||||
platform: platform,
|
|
||||||
processManager: processManager,
|
|
||||||
),
|
|
||||||
branchName,
|
|
||||||
);
|
|
||||||
expect(processManager, hasNoRemainingExpectations);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getBranchName calls git if env LUCI_BRANCH is empty', () {
|
|
||||||
final Platform platform = FakePlatform(
|
|
||||||
environment: <String, String>{
|
|
||||||
'LUCI_BRANCH': '',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final ProcessManager processManager = FakeProcessManager.list(
|
|
||||||
<FakeCommand>[
|
|
||||||
const FakeCommand(
|
|
||||||
command: <String>['git', 'status', '-b', '--porcelain'],
|
|
||||||
stdout: '## $branchName',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
getBranchName(
|
|
||||||
platform: platform,
|
|
||||||
processManager: processManager,
|
|
||||||
),
|
|
||||||
branchName,
|
|
||||||
);
|
|
||||||
expect(processManager, hasNoRemainingExpectations);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("runPubProcess doesn't use the pub binary", () {
|
|
||||||
final ProcessManager processManager = FakeProcessManager.list(
|
|
||||||
<FakeCommand>[
|
|
||||||
const FakeCommand(
|
|
||||||
command: <String>['dart', 'pub', '--one', '--two'],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
runPubProcess(
|
|
||||||
dartBinaryPath: 'dart',
|
|
||||||
arguments: <String>['--one', '--two'],
|
|
||||||
processManager: processManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(processManager, hasNoRemainingExpectations);
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user