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:
Greg Spencer
2023-08-15 15:12:16 -07:00
committed by GitHub
parent 301577a34f
commit 899a29f830
8 changed files with 1384 additions and 938 deletions

View File

@@ -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)));
}
});
}