2019-11-27 23:04:02 +00:00
// Copyright 2014 The Flutter Authors. All rights reserved.
2016-05-01 22:52:51 +00:00
// 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 ' ;
2018-02-05 17:31:18 +00:00
import ' package:args/args.dart ' ;
2017-02-10 17:55:58 +00:00
import ' package:intl/intl.dart ' ;
2021-07-19 19:20:00 +00:00
import ' package:meta/meta.dart ' ;
2016-05-01 22:52:51 +00:00
import ' package:path/path.dart ' as path ;
2021-07-19 19:20:00 +00:00
import ' package:platform/platform.dart ' ;
2018-11-10 01:51:25 +00:00
import ' package:process/process.dart ' ;
2016-05-01 22:52:51 +00:00
2020-07-28 22:26:33 +00:00
import ' dartdoc_checker.dart ' ;
2018-10-23 20:50:24 +00:00
const String kDocsRoot = ' dev/docs ' ;
const String kPublishRoot = ' $ kDocsRoot /doc ' ;
2016-05-16 15:28:58 +00:00
2020-07-30 21:11:23 +00:00
const String kDummyPackageName = ' Flutter ' ;
const String kPlatformIntegrationPackageName = ' platform_integration ' ;
2016-05-01 22:52:51 +00:00
/// 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/`.
2016-08-29 23:20:18 +00:00
///
2016-05-16 15:28:58 +00:00
/// This script also updates the index.html file so that it can be placed
2021-01-08 00:28:12 +00:00
/// 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
2016-05-16 15:28:58 +00:00
/// a bit. See https://github.com/flutter/flutter/issues/3900 for more info.
2017-05-11 16:42:08 +00:00
///
/// 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.
2018-10-04 16:44:23 +00:00
Future < void > main ( List < String > arguments ) async {
2018-02-05 17:31:18 +00:00
final ArgParser argParser = _createArgsParser ( ) ;
final ArgResults args = argParser . parse ( arguments ) ;
2019-12-05 21:34:06 +00:00
if ( args [ ' help ' ] as bool ) {
2018-02-05 17:31:18 +00:00
print ( ' Usage: ' ) ;
print ( argParser . usage ) ;
exit ( 0 ) ;
}
2016-05-01 22:52:51 +00:00
// If we're run from the `tools` dir, set the cwd to the repo root.
2022-07-28 16:07:49 +00:00
if ( path . basename ( Directory . current . path ) = = ' tools ' ) {
2016-05-01 22:52:51 +00:00
Directory . current = Directory . current . parent . parent ;
2022-07-28 16:07:49 +00:00
}
2016-05-01 22:52:51 +00:00
2018-01-18 15:59:06 +00:00
final ProcessResult flutter = Process . runSync ( ' flutter ' , < String > [ ] ) ;
2018-09-12 06:29:29 +00:00
final File versionFile = File ( ' version ' ) ;
2022-07-28 16:07:49 +00:00
if ( flutter . exitCode ! = 0 | | ! versionFile . existsSync ( ) ) {
2018-09-12 06:29:29 +00:00
throw Exception ( ' Failed to determine Flutter version. ' ) ;
2022-07-28 16:07:49 +00:00
}
2018-01-18 15:59:06 +00:00
final String version = versionFile . readAsStringSync ( ) ;
2017-06-20 18:27:37 +00:00
2016-05-01 22:52:51 +00:00
// Create the pubspec.yaml file.
2018-09-12 06:29:29 +00:00
final StringBuffer buf = StringBuffer ( ) ;
2020-07-30 21:11:23 +00:00
buf . writeln ( ' name: $ kDummyPackageName ' ) ;
2019-04-05 18:39:30 +00:00
buf . writeln ( ' homepage: https://flutter.dev ' ) ;
2019-06-21 00:59:37 +00:00
buf . writeln ( ' version: 0.0.0 ' ) ;
2020-11-09 18:02:10 +00:00
buf . writeln ( ' environment: ' ) ;
2022-12-20 00:46:14 +00:00
buf . writeln ( " sdk: '>=2.19.0 <4.0.0' " ) ;
2018-01-18 15:59:06 +00:00
buf . writeln ( ' dependencies: ' ) ;
2020-01-07 15:32:04 +00:00
for ( final String package in findPackageNames ( ) ) {
2016-05-01 22:52:51 +00:00
buf . writeln ( ' $ package : ' ) ;
2016-09-23 03:39:35 +00:00
buf . writeln ( ' sdk: flutter ' ) ;
2016-05-01 22:52:51 +00:00
}
2020-07-30 21:11:23 +00:00
buf . writeln ( ' $ kPlatformIntegrationPackageName : 0.0.1 ' ) ;
2017-02-10 00:12:09 +00:00
buf . writeln ( ' dependency_overrides: ' ) ;
2020-07-30 21:11:23 +00:00
buf . writeln ( ' $ kPlatformIntegrationPackageName : ' ) ;
buf . writeln ( ' path: $ kPlatformIntegrationPackageName ' ) ;
2018-10-23 20:50:24 +00:00
File ( ' $ kDocsRoot /pubspec.yaml ' ) . writeAsStringSync ( buf . toString ( ) ) ;
2016-05-01 22:52:51 +00:00
// Create the library file.
2018-10-23 20:50:24 +00:00
final Directory libDir = Directory ( ' $ kDocsRoot /lib ' ) ;
2016-05-01 22:52:51 +00:00
libDir . createSync ( ) ;
2018-09-12 06:29:29 +00:00
final StringBuffer contents = StringBuffer ( ' library temp_doc; \n \n ' ) ;
2020-01-07 15:32:04 +00:00
for ( final String libraryRef in libraryRefs ( ) ) {
2020-02-11 19:58:27 +00:00
contents . writeln ( " import 'package: $ libraryRef '; " ) ;
2016-05-01 22:52:51 +00:00
}
2018-10-23 20:50:24 +00:00
File ( ' $ kDocsRoot /lib/temp_doc.dart ' ) . writeAsStringSync ( contents . toString ( ) ) ;
2016-05-01 22:52:51 +00:00
2017-12-05 22:46:39 +00:00
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 ' ;
2018-09-12 06:29:29 +00:00
if ( Directory ( pubCachePath ) . existsSync ( ) ) {
2017-12-05 22:46:39 +00:00
pubEnvironment [ ' PUB_CACHE ' ] = pubCachePath ;
}
2022-02-04 20:45:20 +00:00
final String dartExecutable = ' $ flutterRoot /bin/cache/dart-sdk/bin/dart ' ;
2017-12-05 22:46:39 +00:00
2016-05-01 22:52:51 +00:00
// Run pub.
2022-02-04 20:45:20 +00:00
ProcessWrapper process = ProcessWrapper ( await runPubProcess (
dartBinaryPath: dartExecutable ,
arguments: < String > [ ' get ' ] ,
2018-10-23 20:50:24 +00:00
workingDirectory: kDocsRoot ,
2017-12-05 22:46:39 +00:00
environment: pubEnvironment ,
2018-11-10 01:51:25 +00:00
) ) ;
2017-07-28 22:44:38 +00:00
printStream ( process . stdout , prefix: ' pub:stdout: ' ) ;
printStream ( process . stderr , prefix: ' pub:stderr: ' ) ;
2018-11-10 01:51:25 +00:00
final int code = await process . done ;
2022-07-28 16:07:49 +00:00
if ( code ! = 0 ) {
2016-05-01 22:52:51 +00:00
exit ( code ) ;
2022-07-28 16:07:49 +00:00
}
2016-05-01 22:52:51 +00:00
2019-06-21 00:59:37 +00:00
createFooter ( ' $ kDocsRoot /lib/ ' , version ) ;
2018-10-23 20:50:24 +00:00
copyAssets ( ) ;
2018-12-11 17:53:33 +00:00
createSearchMetadata ( ' $ kDocsRoot /lib/opensearch.xml ' , ' $ kDocsRoot /doc/opensearch.xml ' ) ;
2018-10-23 20:50:24 +00:00
cleanOutSnippets ( ) ;
2017-02-10 17:55:58 +00:00
2019-09-17 14:23:44 +00:00
final List < String > dartdocBaseArgs = < String > [
' global ' ,
' run ' ,
2019-12-05 21:34:06 +00:00
if ( args [ ' checked ' ] as bool ) ' -c ' ,
2019-09-17 14:23:44 +00:00
' dartdoc ' ,
] ;
2018-02-05 17:31:18 +00:00
2021-08-12 02:48:29 +00:00
// Verify which version of snippets and dartdoc we're using.
final ProcessResult snippetsResult = Process . runSync (
2022-02-04 20:45:20 +00:00
dartExecutable ,
2021-08-12 02:48:29 +00:00
< String > [
2022-02-04 20:45:20 +00:00
' pub ' ,
2021-08-12 02:48:29 +00:00
' global ' ,
' list ' ,
] ,
2018-10-23 20:50:24 +00:00
workingDirectory: kDocsRoot ,
2017-12-05 22:46:39 +00:00
environment: pubEnvironment ,
2021-08-12 02:48:29 +00:00
stdoutEncoding: utf8 ,
2017-05-11 16:42:08 +00:00
) ;
2021-08-12 02:48:29 +00:00
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 ' ) ;
2017-04-19 17:00:18 +00:00
2020-07-30 21:11:23 +00:00
// Dartdoc warnings and errors in these packages are considered fatal.
// All packages owned by flutter should be in the list.
// TODO(goderbauer): Figure out how to add 'dart:ui'.
final List < String > flutterPackages = < String > [
kDummyPackageName ,
kPlatformIntegrationPackageName ,
. . . findPackageNames ( ) ,
2020-08-03 17:46:04 +00:00
] ;
2020-07-30 21:11:23 +00:00
2016-08-29 23:20:18 +00:00
// Generate the documentation.
2018-05-04 00:47:50 +00:00
// 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
2019-06-27 19:23:16 +00:00
final List < String > dartdocArgs = < String > [
. . . dartdocBaseArgs ,
2019-09-17 14:23:44 +00:00
' --allow-tools ' ,
2019-12-05 21:34:06 +00:00
if ( args [ ' json ' ] as bool ) ' --json ' ,
if ( args [ ' validate-links ' ] as bool ) ' --validate-links ' else ' --no-validate-links ' ,
2019-09-17 14:23:44 +00:00
' --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% ' ,
2018-10-23 20:50:24 +00:00
' --inject-html ' ,
2020-03-19 21:56:03 +00:00
' --use-base-href ' ,
2016-05-01 22:52:51 +00:00
' --header ' , ' styles.html ' ,
' --header ' , ' analytics.html ' ,
2019-03-01 23:42:43 +00:00
' --header ' , ' survey.html ' ,
2018-10-23 20:50:24 +00:00
' --header ' , ' snippets.html ' ,
2018-12-11 17:53:33 +00:00
' --header ' , ' opensearch.html ' ,
2017-05-25 16:42:55 +00:00
' --footer-text ' , ' lib/footer.html ' ,
2020-07-30 21:11:23 +00:00
' --allow-warnings-in-packages ' , flutterPackages . join ( ' , ' ) ,
2017-12-08 16:08:49 +00:00
' --exclude-packages ' ,
2018-10-23 20:50:24 +00:00
< String > [
' analyzer ' ,
' args ' ,
' barback ' ,
' csslib ' ,
' flutter_goldens ' ,
2018-11-10 01:51:25 +00:00
' flutter_goldens_client ' ,
2018-10-23 20:50:24 +00:00
' 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 ( ' , ' ) ,
2017-12-08 16:08:49 +00:00
' --exclude ' ,
2018-10-23 20:50:24 +00:00
< String > [
2020-11-20 19:18:07 +00:00
' dart:io/network_policy.dart ' , // dart-lang/dartdoc#2437
2018-10-23 20:50:24 +00:00
' 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 ( ' , ' ) ,
2016-05-01 22:52:51 +00:00
' --favicon=favicon.ico ' ,
2020-07-30 21:11:23 +00:00
' --package-order ' , ' flutter,Dart, $ kPlatformIntegrationPackageName ,flutter_test,flutter_driver ' ,
2017-07-28 22:44:38 +00:00
' --auto-include-dependencies ' ,
2019-06-27 19:23:16 +00:00
] ;
2016-05-01 22:52:51 +00:00
2018-03-30 19:19:44 +00:00
String quote ( String arg ) = > arg . contains ( ' ' ) ? " ' $ arg ' " : arg ;
2022-02-04 20:45:20 +00:00
print ( ' Executing: (cd $ kDocsRoot ; $ dartExecutable ${ dartdocArgs . map < String > ( quote ) . join ( ' ' ) } ) ' ) ;
2018-03-30 19:19:44 +00:00
2022-02-04 20:45:20 +00:00
process = ProcessWrapper ( await runPubProcess (
dartBinaryPath: dartExecutable ,
arguments: dartdocArgs ,
2018-10-23 20:50:24 +00:00
workingDirectory: kDocsRoot ,
2017-12-05 22:46:39 +00:00
environment: pubEnvironment ,
2018-11-10 01:51:25 +00:00
) ) ;
2019-12-05 21:34:06 +00:00
printStream ( process . stdout , prefix: args [ ' json ' ] as bool ? ' ' : ' dartdoc:stdout: ' ,
filter: args [ ' verbose ' ] as bool ? const < Pattern > [ ] : < Pattern > [
2018-09-12 06:29:29 +00:00
RegExp ( r'^generating docs for library ' ) , // unnecessary verbosity
RegExp ( r'^pars' ) , // unnecessary verbosity
2017-07-28 22:44:38 +00:00
] ,
) ;
2019-12-05 21:34:06 +00:00
printStream ( process . stderr , prefix: args [ ' json ' ] as bool ? ' ' : ' dartdoc:stderr: ' ,
filter: args [ ' verbose ' ] as bool ? const < Pattern > [ ] : < Pattern > [
2018-09-12 06:29:29 +00:00
RegExp ( r'^ warning: .+: \(.+/\.pub-cache/hosted/pub.dartlang.org/.+\)' ) , // packages outside our control
2017-07-28 22:44:38 +00:00
] ,
) ;
2018-11-10 01:51:25 +00:00
final int exitCode = await process . done ;
2016-05-16 15:28:58 +00:00
2022-07-28 16:07:49 +00:00
if ( exitCode ! = 0 ) {
2016-05-16 15:28:58 +00:00
exit ( exitCode ) ;
2022-07-28 16:07:49 +00:00
}
2016-05-16 15:28:58 +00:00
2016-11-09 23:16:13 +00:00
sanityCheckDocs ( ) ;
2020-07-28 22:26:33 +00:00
checkForUnresolvedDirectives ( ' $ kPublishRoot /api ' ) ;
2016-11-09 23:16:13 +00:00
2016-05-16 15:28:58 +00:00
createIndexAndCleanup ( ) ;
}
2018-02-05 17:31:18 +00:00
ArgParser _createArgsParser ( ) {
2018-09-12 06:29:29 +00:00
final ArgParser parser = ArgParser ( ) ;
2018-02-05 17:31:18 +00:00
parser . addFlag ( ' help ' , abbr: ' h ' , negatable: false ,
help: ' Show command help. ' ) ;
2021-10-08 16:25:14 +00:00
parser . addFlag ( ' verbose ' , defaultsTo: true ,
2018-02-05 17:31:18 +00:00
help: ' Whether to report all error messages (on) or attempt to '
2022-09-02 04:00:58 +00:00
' filter out some known false positives (off). Shut this off '
2018-02-05 17:31:18 +00:00
' locally if you want to address Flutter-specific issues. ' ) ;
2021-10-08 16:25:14 +00:00
parser . addFlag ( ' checked ' , abbr: ' c ' ,
2018-02-05 17:31:18 +00:00
help: ' Run dartdoc in checked mode. ' ) ;
2021-10-08 16:25:14 +00:00
parser . addFlag ( ' json ' ,
2018-02-05 17:31:18 +00:00
help: ' Display json-formatted output from dartdoc and skip stdout/stderr prefixing. ' ) ;
2021-10-08 16:25:14 +00:00
parser . addFlag ( ' validate-links ' ,
2018-02-27 22:39:42 +00:00
help: ' Display warnings for broken links generated by dartdoc (slow) ' ) ;
2018-02-05 17:31:18 +00:00
return parser ;
}
2018-09-12 06:29:29 +00:00
final RegExp gitBranchRegexp = RegExp ( r'^## (.*)' ) ;
2018-08-17 21:10:33 +00:00
2021-07-19 19:20:00 +00:00
/// 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 ' ] ) ;
2022-07-28 16:07:49 +00:00
if ( gitResult . exitCode ! = 0 ) {
2018-12-11 17:53:33 +00:00
throw ' git status exit with non-zero exit code: ${ gitResult . exitCode } ' ;
2022-07-28 16:07:49 +00:00
}
2021-06-08 00:09:03 +00:00
final RegExpMatch ? gitBranchMatch = gitBranchRegexp . firstMatch (
2019-12-05 21:34:06 +00:00
( gitResult . stdout as String ) . trim ( ) . split ( ' \n ' ) . first ) ;
2021-06-08 00:09:03 +00:00
return gitBranchMatch = = null ? ' ' : gitBranchMatch . group ( 1 ) ! . split ( ' ... ' ) . first ;
2018-12-11 17:53:33 +00:00
}
2019-02-04 22:40:22 +00:00
String gitRevision ( ) {
2017-10-13 20:31:32 +00:00
const int kGitRevisionLength = 10 ;
2018-12-11 17:53:33 +00:00
final ProcessResult gitResult = Process . runSync ( ' git ' , < String > [ ' rev-parse ' , ' HEAD ' ] ) ;
2022-07-28 16:07:49 +00:00
if ( gitResult . exitCode ! = 0 ) {
2018-08-17 21:10:33 +00:00
throw ' git rev-parse exit with non-zero exit code: ${ gitResult . exitCode } ' ;
2022-07-28 16:07:49 +00:00
}
2019-12-05 21:34:06 +00:00
final String gitRevision = ( gitResult . stdout as String ) . trim ( ) ;
2018-08-17 21:10:33 +00:00
2019-02-04 22:40:22 +00:00
return gitRevision . length > kGitRevisionLength ? gitRevision . substring ( 0 , kGitRevisionLength ) : gitRevision ;
}
2017-02-10 17:55:58 +00:00
2019-06-21 00:59:37 +00:00
void createFooter ( String footerPath , String version ) {
2018-09-12 06:29:29 +00:00
final String timestamp = DateFormat ( ' yyyy-MM-dd HH:mm ' ) . format ( DateTime . now ( ) ) ;
2019-02-04 22:40:22 +00:00
final String gitBranch = getBranchName ( ) ;
2019-06-21 00:59:37 +00:00
final String gitBranchOut = gitBranch . isEmpty ? ' ' : ' • $ gitBranch ' ;
File ( ' ${ footerPath } footer.html ' ) . writeAsStringSync ( ' <script src="footer.js"></script> ' ) ;
File ( ' $ kPublishRoot /api/footer.js ' )
. . createSync ( recursive: true )
2020-02-20 09:16:28 +00:00
. . writeAsStringSync ( '''
( function ( ) {
2019-06-21 05:08:52 +00:00
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 ( ) } / ' ) ;
}
} ) ( ) ;
2019-06-21 00:59:37 +00:00
''' );
2017-02-10 17:55:58 +00:00
}
2018-12-11 17:53:33 +00:00
/// 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} ' ,
2021-01-08 00:28:12 +00:00
branch = = ' stable ' ? ' https://api.flutter.dev/ ' : ' https://master-api.flutter.dev/ ' ,
2018-12-11 17:53:33 +00:00
) ;
Directory ( path . dirname ( metadataPath ) ) . create ( recursive: true ) ;
File ( metadataPath ) . writeAsStringSync ( metadata ) ;
}
2018-10-23 20:50:24 +00:00
/// Recursively copies `srcDir` to `destDir`, invoking [onFileCopied], if
/// specified, for each source/destination file pair.
///
/// Creates `destDir` if needed.
2021-06-08 00:09:03 +00:00
void copyDirectorySync ( Directory srcDir , Directory destDir , [ void Function ( File srcFile , File destFile ) ? onFileCopied ] ) {
2022-07-28 16:07:49 +00:00
if ( ! srcDir . existsSync ( ) ) {
2018-10-23 20:50:24 +00:00
throw Exception ( ' Source directory " ${ srcDir . path } " does not exist, nothing to copy ' ) ;
2022-07-28 16:07:49 +00:00
}
2018-10-23 20:50:24 +00:00
2022-07-28 16:07:49 +00:00
if ( ! destDir . existsSync ( ) ) {
2018-10-23 20:50:24 +00:00
destDir . createSync ( recursive: true ) ;
2022-07-28 16:07:49 +00:00
}
2018-10-23 20:50:24 +00:00
2020-01-07 15:32:04 +00:00
for ( final FileSystemEntity entity in srcDir . listSync ( ) ) {
2018-10-23 20:50:24 +00:00
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 } ' ) ) ;
}
2018-12-11 17:53:33 +00:00
/// Clean out any existing snippets so that we don't publish old files from
/// previous runs accidentally.
2018-10-23 20:50:24 +00:00
void cleanOutSnippets ( ) {
final Directory snippetsDir = Directory ( path . join ( kPublishRoot , ' snippets ' ) ) ;
if ( snippetsDir . existsSync ( ) ) {
snippetsDir
. . deleteSync ( recursive: true )
. . createSync ( recursive: true ) ;
}
}
2022-09-21 18:33:08 +00:00
void _sanityCheckExample ( String fileString , String regExpString ) {
final File file = File ( fileString ) ;
2022-02-25 02:51:22 +00:00
if ( file . existsSync ( ) ) {
2022-09-21 18:33:08 +00:00
final RegExp regExp = RegExp ( regExpString , dotAll: true ) ;
final String contents = file . readAsStringSync ( ) ;
if ( ! regExp . hasMatch ( contents ) ) {
2022-11-09 23:47:05 +00:00
throw Exception ( " Missing example code matching ' $ regExpString ' in ${ file . path } . " ) ;
2022-02-25 02:51:22 +00:00
}
} else {
2022-09-21 18:33:08 +00:00
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. " ) ;
2022-02-25 02:51:22 +00:00
}
}
/// Runs a sanity check by running a test.
2022-11-09 23:47:05 +00:00
void sanityCheckDocs ( [ Platform platform = const LocalPlatform ( ) ] ) {
2018-03-30 19:19:44 +00:00
final List < String > canaries = < String > [
2018-10-23 20:50:24 +00:00
' $ 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 ' ,
2022-11-09 23:47:05 +00:00
' $ kPublishRoot /api/widgets/Listener-class.html ' ,
2016-11-09 23:16:13 +00:00
] ;
2020-01-07 15:32:04 +00:00
for ( final String canary in canaries ) {
2022-07-28 16:07:49 +00:00
if ( ! File ( canary ) . existsSync ( ) ) {
2018-09-12 06:29:29 +00:00
throw Exception ( ' Missing " $ canary ", which probably means the documentation failed to build correctly. ' ) ;
2022-07-28 16:07:49 +00:00
}
2016-11-09 23:16:13 +00:00
}
2022-02-25 02:51:22 +00:00
// Make sure at least one example of each kind includes source code.
// Check a "sample" example, any one will do.
2022-09-21 18:33:08 +00:00
_sanityCheckExample (
' $ kPublishRoot /api/widgets/showGeneralDialog.html ' ,
r'\s*<pre\s+id="longSnippet1".*<code\s+class="language-dart">\s*import 'package:flutter/material.dart';' ,
) ;
2022-02-25 02:51:22 +00:00
// Check a "snippet" example, any one will do.
2022-09-21 18:33:08 +00:00
_sanityCheckExample (
' $ kPublishRoot /api/widgets/ModalRoute/barrierColor.html ' ,
r'\s*<pre.*id="sample-code">.*Color\s+get\s+barrierColor.*</pre>' ,
) ;
2022-02-25 02:51:22 +00:00
2022-11-09 23:47:05 +00:00
// Check a "dartpad" example, any one will do, and check for the correct URL
// arguments.
// Just use "master" for any branch other than the LUCH_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'null_safety=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>' ,
) ;
}
2016-11-09 23:16:13 +00:00
}
2016-05-16 15:28:58 +00:00
/// Creates a custom index.html because we try to maintain old
/// paths. Cleanup unused index.html files no longer needed.
void createIndexAndCleanup ( ) {
2018-10-23 20:50:24 +00:00
print ( ' \n Creating a custom index.html in $ kPublishRoot /index.html ' ) ;
2016-05-16 20:05:13 +00:00
removeOldFlutterDocsDir ( ) ;
2016-05-16 15:28:58 +00:00
renameApiDir ( ) ;
copyIndexToRootOfDocs ( ) ;
addHtmlBaseToIndex ( ) ;
2018-03-30 19:19:44 +00:00
changePackageToSdkInTitlebar ( ) ;
2016-05-16 15:28:58 +00:00
putRedirectInOldIndexLocation ( ) ;
2019-01-08 10:38:59 +00:00
writeSnippetsIndexFile ( ) ;
2016-05-16 15:28:58 +00:00
print ( ' \n Docs ready to go! ' ) ;
}
2016-05-16 20:05:13 +00:00
void removeOldFlutterDocsDir ( ) {
try {
2018-10-23 20:50:24 +00:00
Directory ( ' $ kPublishRoot /flutter ' ) . deleteSync ( recursive: true ) ;
2018-08-17 20:17:23 +00:00
} on FileSystemException {
2016-05-16 20:05:13 +00:00
// If the directory does not exist, that's OK.
}
}
2016-05-16 15:28:58 +00:00
void renameApiDir ( ) {
2018-10-23 20:50:24 +00:00
Directory ( ' $ kPublishRoot /api ' ) . renameSync ( ' $ kPublishRoot /flutter ' ) ;
2016-05-16 15:28:58 +00:00
}
2016-07-23 05:21:04 +00:00
void copyIndexToRootOfDocs ( ) {
2018-10-23 20:50:24 +00:00
File ( ' $ kPublishRoot /flutter/index.html ' ) . copySync ( ' $ kPublishRoot /index.html ' ) ;
2016-05-16 15:28:58 +00:00
}
2018-03-30 19:19:44 +00:00
void changePackageToSdkInTitlebar ( ) {
2018-10-23 20:50:24 +00:00
final File indexFile = File ( ' $ kPublishRoot /index.html ' ) ;
2018-03-30 19:19:44 +00:00
String indexContents = indexFile . readAsStringSync ( ) ;
indexContents = indexContents . replaceFirst (
2019-04-05 18:39:30 +00:00
' <li><a href="https://flutter.dev">Flutter package</a></li> ' ,
' <li><a href="https://flutter.dev">Flutter SDK</a></li> ' ,
2018-03-30 19:19:44 +00:00
) ;
indexFile . writeAsStringSync ( indexContents ) ;
}
2016-05-16 15:28:58 +00:00
void addHtmlBaseToIndex ( ) {
2018-10-23 20:50:24 +00:00
final File indexFile = File ( ' $ kPublishRoot /index.html ' ) ;
2016-05-16 15:28:58 +00:00
String indexContents = indexFile . readAsStringSync ( ) ;
2017-07-28 22:44:38 +00:00
indexContents = indexContents . replaceFirst (
' </title> \n ' ,
' </title> \n <base href="./flutter/"> \n ' ,
) ;
2017-02-10 00:12:09 +00:00
indexContents = indexContents . replaceAll (
' href="Android/Android-library.html" ' ,
2017-07-28 22:44:38 +00:00
' href="/javadoc/" ' ,
2017-02-10 00:12:09 +00:00
) ;
2017-06-26 10:15:24 +00:00
indexContents = indexContents . replaceAll (
' href="iOS/iOS-library.html" ' ,
2017-07-28 22:44:38 +00:00
' href="/objcdoc/" ' ,
2017-06-26 10:15:24 +00:00
) ;
2016-05-16 15:28:58 +00:00
indexFile . writeAsStringSync ( indexContents ) ;
}
void putRedirectInOldIndexLocation ( ) {
2018-02-01 06:51:26 +00:00
const String metaTag = ' <meta http-equiv="refresh" content="0;URL=../index.html"> ' ;
2018-10-23 20:50:24 +00:00
File ( ' $ kPublishRoot /flutter/index.html ' ) . writeAsStringSync ( metaTag ) ;
2016-05-01 22:52:51 +00:00
}
2019-01-08 10:38:59 +00:00
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 ) ;
}
}
2016-05-16 15:28:58 +00:00
List < String > findPackageNames ( ) {
2018-10-01 19:29:08 +00:00
return findPackages ( ) . map < String > ( ( FileSystemEntity file ) = > path . basename ( file . path ) ) . toList ( ) ;
2016-05-01 22:52:51 +00:00
}
2017-01-11 17:00:10 +00:00
/// Finds all packages in the Flutter SDK
2019-12-05 21:34:06 +00:00
List < Directory > findPackages ( ) {
2018-09-12 06:29:29 +00:00
return Directory ( ' packages ' )
2016-05-01 22:52:51 +00:00
. listSync ( )
2016-11-15 20:35:50 +00:00
. where ( ( FileSystemEntity entity ) {
2022-07-28 16:07:49 +00:00
if ( entity is ! Directory ) {
2016-11-15 20:35:50 +00:00
return false ;
2022-07-28 16:07:49 +00:00
}
2018-09-12 06:29:29 +00:00
final File pubspec = File ( ' ${ entity . path } /pubspec.yaml ' ) ;
2020-12-17 22:47:45 +00:00
if ( ! pubspec . existsSync ( ) ) {
print ( " Unexpected package ' ${ entity . path } ' found in packages directory " ) ;
return false ;
}
2016-11-15 20:35:50 +00:00
// TODO(ianh): Use a real YAML parser here
return ! pubspec . readAsStringSync ( ) . contains ( ' nodoc: true ' ) ;
2016-05-01 22:52:51 +00:00
} )
2018-07-13 17:35:23 +00:00
. cast < Directory > ( )
2016-05-01 22:52:51 +00:00
. toList ( ) ;
}
2017-01-11 17:00:10 +00:00
/// Returns import or on-disk paths for all libraries in the Flutter SDK.
2018-10-11 20:27:43 +00:00
Iterable < String > libraryRefs ( ) sync * {
2020-01-07 15:32:04 +00:00
for ( final Directory dir in findPackages ( ) ) {
2017-03-04 02:06:08 +00:00
final String dirName = path . basename ( dir . path ) ;
2020-01-07 15:32:04 +00:00
for ( final FileSystemEntity file in Directory ( ' ${ dir . path } /lib ' ) . listSync ( ) ) {
2017-01-11 17:00:10 +00:00
if ( file is File & & file . path . endsWith ( ' .dart ' ) ) {
2018-10-11 20:27:43 +00:00
yield ' $ dirName / ${ path . basename ( file . path ) } ' ;
}
2016-05-01 22:52:51 +00:00
}
}
2017-02-10 00:12:09 +00:00
// Add a fake package for platform integration APIs.
2020-07-30 21:11:23 +00:00
yield ' $ kPlatformIntegrationPackageName /android.dart ' ;
yield ' $ kPlatformIntegrationPackageName /ios.dart ' ;
2016-05-01 22:52:51 +00:00
}
2018-06-05 06:50:40 +00:00
void printStream ( Stream < List < int > > stream , { String prefix = ' ' , List < Pattern > filter = const < Pattern > [ ] } ) {
2017-07-28 22:44:38 +00:00
assert ( prefix ! = null ) ;
assert ( filter ! = null ) ;
2016-05-01 22:52:51 +00:00
stream
2018-10-01 19:29:08 +00:00
. transform < String > ( utf8 . decoder )
. transform < String > ( const LineSplitter ( ) )
2017-07-28 22:44:38 +00:00
. listen ( ( String line ) {
2022-07-28 16:07:49 +00:00
if ( ! filter . any ( ( Pattern pattern ) = > line . contains ( pattern ) ) ) {
2017-07-28 22:44:38 +00:00
print ( ' $ prefix $ line ' . trim ( ) ) ;
2022-07-28 16:07:49 +00:00
}
2017-07-28 22:44:38 +00:00
} ) ;
2016-05-01 22:52:51 +00:00
}
2022-02-04 20:45:20 +00:00
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 ,
) ;
}