From 2d006e904567958aeef4cda5c51ad0352794509f Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Thu, 26 Apr 2018 13:40:18 -0700 Subject: [PATCH 01/17] Ready. --- .travis.yml | 1 + README.md | 10 +- analysis_options.yaml | 1 + bin/dartdoc.dart | 2 +- lib/dartdoc.dart | 55 ++-- lib/src/dartdoc_options.dart | 273 +++++++++++++----- lib/src/html/html_generator.dart | 12 +- lib/src/model.dart | 42 ++- lib/src/package_meta.dart | 32 +- pubspec.lock | 6 +- pubspec.yaml | 2 +- test/dartdoc_options_test.dart | 145 ++++++---- test/dartdoc_test.dart | 48 ++- .../test_package_flutter_plugin/.gitignore | 3 + .../lib/testlib.dart | 12 + .../test_package_flutter_plugin/pubspec.yaml | 12 + tool/grind.dart | 257 ++++++++++++++--- tool/travis.sh | 18 +- 18 files changed, 672 insertions(+), 259 deletions(-) create mode 100644 testing/test_package_flutter_plugin/.gitignore create mode 100644 testing/test_package_flutter_plugin/lib/testlib.dart create mode 100644 testing/test_package_flutter_plugin/pubspec.yaml diff --git a/.travis.yml b/.travis.yml index e61ecd38b2..794905e56d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,3 +15,4 @@ branches: cache: directories: - $HOME/.pub-cache + - $HOME/.dartdoc_grinder diff --git a/README.md b/README.md index d0fcbcfe14..029fc6ac87 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,14 @@ Unrecognized options will be ignored. Supported options: * **linkTo**: For other packages depending on this one, if this map is defined those packages will use the settings here to control how hyperlinks to the package are generated. This will override the default for packages hosted on pub.dartlang.org. - * url: A string indicating the base URL for documentation of this package. The following - strings will be substituted in to complete the URL: + * **url**: A string indicating the base URL for documentation of this package. Ordinarily + you do not need to set this in the package: consider --link-to-hosted and + --link-to-sdks instead of this option if you need to build your own website with + dartdoc. + + The following strings will be substituted in to complete the URL: + * `%b%`: The branch as indicated by text in the version. 2.0.0-dev.3 is branch "dev". + No branch is considered to be "stable". * `%n%`: The name of this package, as defined in pubspec.yaml. * `%v%`: The version of this package as defined in pubspec.yaml. diff --git a/analysis_options.yaml b/analysis_options.yaml index c421e35483..ee60de8645 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -9,6 +9,7 @@ analyzer: - 'lib/templates/*.html' - 'pub.dartlang.org/**' - 'testing/**' + - 'testing/test_package_flutter_plugin/**' linter: rules: - annotate_overrides diff --git a/bin/dartdoc.dart b/bin/dartdoc.dart index 1984c7fbe8..583913e892 100644 --- a/bin/dartdoc.dart +++ b/bin/dartdoc.dart @@ -69,7 +69,7 @@ main(List arguments) async { logInfo("Generating documentation for '${config.topLevelPackageMeta}' into " "${outputDir.absolute.path}${Platform.pathSeparator}"); - Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(config, outputDir); + Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(config); dartdoc.onCheckProgress.listen(logProgress); await Chain.capture(() async { diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index 00ca2aa6e1..e80abb61d4 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -43,32 +43,30 @@ const String dartdocVersion = '0.18.1'; /// directory. class Dartdoc extends PackageBuilder { final List generators; - final Directory outputDir; final Set writtenFiles = new Set(); + Directory outputDir; // Fires when the self checks make progress. final StreamController _onCheckProgress = new StreamController(sync: true); - Dartdoc._(DartdocOptionContext config, this.generators, this.outputDir) - : super(config) { + Dartdoc._(DartdocOptionContext config, this.generators) : super(config) { + outputDir = new Directory(config.output)..createSync(recursive: true); generators.forEach((g) => g.onFileCreated.listen(logProgress)); } /// An asynchronous factory method that builds Dartdoc's file writers /// and returns a Dartdoc object with them. - static withDefaultGenerators( - DartdocOptionContext config, Directory outputDir) async { + static withDefaultGenerators(DartdocOptionContext config) async { List generators = await initGenerators(config as GeneratorContext); - return new Dartdoc._(config, generators, outputDir); + return new Dartdoc._(config, generators); } /// Basic synchronous factory that gives a stripped down Dartdoc that won't /// use generators. Useful for testing. - factory Dartdoc.withoutGenerators( - DartdocOptionContext config, Directory outputDir) { - return new Dartdoc._(config, [], outputDir); + factory Dartdoc.withoutGenerators(DartdocOptionContext config) { + return new Dartdoc._(config, []); } Stream get onCheckProgress => _onCheckProgress.stream; @@ -345,25 +343,28 @@ class Dartdoc extends PackageBuilder { // (newPathToCheck, newFullPath) Set> toVisit = new Set(); + final RegExp ignoreHyperlinks = new RegExp(r'^(https:|http:|mailto:|ftp:)'); for (String href in stringLinks) { - Uri uri; - try { - uri = Uri.parse(href); - } catch (FormatError) {} - - if (uri == null || !uri.hasAuthority && !uri.hasFragment) { - var full; - if (baseHref != null) { - full = '${pathLib.dirname(pathToCheck)}/$baseHref/$href'; - } else { - full = '${pathLib.dirname(pathToCheck)}/$href'; - } - var newPathToCheck = pathLib.normalize(full); - String newFullPath = pathLib.joinAll([origin, newPathToCheck]); - newFullPath = pathLib.normalize(newFullPath); - if (!visited.contains(newFullPath)) { - toVisit.add(new Tuple2(newPathToCheck, newFullPath)); - visited.add(newFullPath); + if (!href.startsWith(ignoreHyperlinks)) { + Uri uri; + try { + uri = Uri.parse(href); + } catch (FormatError) {} + + if (uri == null || !uri.hasAuthority && !uri.hasFragment) { + var full; + if (baseHref != null) { + full = '${pathLib.dirname(pathToCheck)}/$baseHref/$href'; + } else { + full = '${pathLib.dirname(pathToCheck)}/$href'; + } + var newPathToCheck = pathLib.normalize(full); + String newFullPath = pathLib.joinAll([origin, newPathToCheck]); + newFullPath = pathLib.normalize(newFullPath); + if (!visited.contains(newFullPath)) { + toVisit.add(new Tuple2(newPathToCheck, newFullPath)); + visited.add(newFullPath); + } } } } diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart index 627bd9f36b..661e8524bd 100644 --- a/lib/src/dartdoc_options.dart +++ b/lib/src/dartdoc_options.dart @@ -32,7 +32,7 @@ const int _kIntVal = 0; const double _kDoubleVal = 0.0; const bool _kBoolVal = true; -String _resolveTildePath(String originalPath) { +String resolveTildePath(String originalPath) { if (originalPath == null || !originalPath.startsWith('~/')) { return originalPath; } @@ -97,11 +97,11 @@ class _OptionValueWithContext { T get resolvedValue { if (value is List) { return (value as List) - .map((v) => pathContext.canonicalize(_resolveTildePath(v))) + .map((v) => pathContext.canonicalize(resolveTildePath(v))) .cast() .toList() as T; } else if (value is String) { - return pathContext.canonicalize(_resolveTildePath(value as String)) as T; + return pathContext.canonicalize(resolveTildePath(value as String)) as T; } else { throw new UnsupportedError('Type $T is not supported for resolvedValue'); } @@ -117,7 +117,7 @@ class _OptionValueWithContext { /// of sanity checks are also built in to these classes so that file existence /// can be verified, types constrained, and defaults provided. /// -/// Use via implementations [DartdocOptionSet], [DartdocOptionBoth], +/// Use via implementations [DartdocOptionSet], [DartdocOptionArgFile], /// [DartdocOptionArgOnly], and [DartdocOptionFileOnly]. abstract class DartdocOption { /// This is the value returned if we couldn't find one otherwise. @@ -154,7 +154,6 @@ abstract class DartdocOption { // command line arguments and yaml data to real types. // // Condense the ugly all in one place, this set of getters. - bool get _isString => _kStringVal is T; bool get _isListString => _kListStringVal is T; bool get _isMapString => _kMapStringVal is T; @@ -181,7 +180,7 @@ abstract class DartdocOption { /// Parse these as string arguments (from argv) with the argument parser. /// Call before calling [valueAt] for any [DartdocOptionArgOnly] or - /// [DartdocOptionBoth] in this tree. + /// [DartdocOptionArgFile] in this tree. void _parseArguments(List arguments) { __argResults = argParser.parse(arguments); } @@ -303,23 +302,121 @@ abstract class DartdocOption { } } +/// A class that defaults to a value computed from a closure, but can be +/// overridden by a file. +class DartdocOptionFileSynth extends DartdocOption + with DartdocSyntheticOption, _DartdocFileOption { + bool _parentDirOverridesChild; + @override + T Function(DartdocSyntheticOption, Directory) _compute; + DartdocOptionFileSynth(String name, this._compute, + {bool mustExist = false, + String help = '', + bool isDir = false, + bool isFile = false, + bool parentDirOverridesChild}) + : super._(name, null, help, isDir, isFile, mustExist) { + _parentDirOverridesChild = parentDirOverridesChild; + } + + @override + T valueAt(Directory dir) { + _OptionValueWithContext result = _valueAtFromFile(dir); + if (result?.definingFile != null) { + return _handlePathsInContext(result); + } + return _valueAtFromSynthetic(dir); + } + + @override + void _onMissing( + _OptionValueWithContext valueWithContext, String missingPath) { + if (valueWithContext.definingFile != null) { + _onMissingFromFiles(valueWithContext, missingPath); + } else { + _onMissingFromSynthetic(valueWithContext, missingPath); + } + } + + @override + bool get parentDirOverridesChild => _parentDirOverridesChild; +} + +/// A class that defaults to a value computed from a closure, but can +/// be overridden on the command line. +class DartdocOptionArgSynth extends DartdocOption + with DartdocSyntheticOption, _DartdocArgOption { + String _abbr; + bool _hide; + bool _negatable; + bool _splitCommas; + + @override + T Function(DartdocSyntheticOption, Directory) _compute; + DartdocOptionArgSynth(String name, this._compute, + {String abbr, + bool mustExist = false, + String help = '', + bool isDir = false, + bool isFile = false, + bool negatable, + bool splitCommas}) + : super._(name, null, help, isDir, isFile, mustExist) { + _hide = hide; + _negatable = negatable; + _splitCommas = splitCommas; + _abbr = abbr; + } + + @override + void _onMissing( + _OptionValueWithContext valueWithContext, String missingPath) { + _onMissingFromArgs(valueWithContext, missingPath); + } + + @override + T valueAt(Directory dir) { + if (_argResults.wasParsed(argName)) { + return _valueAtFromArgs(); + } + return _valueAtFromSynthetic(dir); + } + + @override + String get abbr => _abbr; + @override + bool get hide => _hide; + @override + bool get negatable => _negatable; + + @override + bool get splitCommas => _splitCommas; +} + /// A synthetic option takes a closure at construction time that computes /// the value of the configuration option based on other configuration options. /// Does not protect against closures that self-reference. If [mustExist] and /// [isDir] or [isFile] is set, computed values will be resolved to canonical /// paths. -class DartdocOptionSynthetic extends DartdocOption { - T Function(DartdocOptionSynthetic, Directory) _compute; - - DartdocOptionSynthetic(String name, this._compute, +class DartdocOptionSyntheticOnly extends DartdocOption + with DartdocSyntheticOption { + @override + T Function(DartdocSyntheticOption, Directory) _compute; + DartdocOptionSyntheticOnly(String name, this._compute, {bool mustExist = false, String help = '', bool isDir = false, bool isFile = false}) : super._(name, null, help, isDir, isFile, mustExist); +} + +abstract class DartdocSyntheticOption implements DartdocOption { + T Function(DartdocSyntheticOption, Directory) get _compute; @override - T valueAt(Directory dir) { + T valueAt(Directory dir) => _valueAtFromSynthetic(dir); + + T _valueAtFromSynthetic(Directory dir) { _OptionValueWithContext context = new _OptionValueWithContext(_compute(this, dir), dir.path); return _handlePathsInContext(context); @@ -327,6 +424,10 @@ class DartdocOptionSynthetic extends DartdocOption { @override void _onMissing( + _OptionValueWithContext valueWithContext, String missingPath) => + _onMissingFromSynthetic(valueWithContext, missingPath); + + void _onMissingFromSynthetic( _OptionValueWithContext valueWithContext, String missingPath) { String description = 'Synthetic configuration option ${name} from '; @@ -409,7 +510,7 @@ class DartdocOptionArgOnly extends DartdocOption } /// A [DartdocOption] that works with command line arguments and dartdoc_options files. -class DartdocOptionBoth extends DartdocOption +class DartdocOptionArgFile extends DartdocOption with _DartdocArgOption, _DartdocFileOption { String _abbr; bool _hide; @@ -417,7 +518,7 @@ class DartdocOptionBoth extends DartdocOption bool _parentDirOverridesChild; bool _splitCommas; - DartdocOptionBoth(String name, T defaultsTo, + DartdocOptionArgFile(String name, T defaultsTo, {String abbr, bool mustExist = false, String help: '', @@ -438,24 +539,16 @@ class DartdocOptionBoth extends DartdocOption @override void _onMissing( _OptionValueWithContext valueWithContext, String missingPath) { - String dartdocYaml; - String description; if (valueWithContext.definingFile != null) { - dartdocYaml = pathLib.canonicalize(pathLib.join( - valueWithContext.canonicalDirectoryPath, - valueWithContext.definingFile)); - description = 'Field ${fieldName} from ${dartdocYaml}'; + _onMissingFromFiles(valueWithContext, missingPath); } else { - description = 'Argument --${argName}'; + _onMissingFromArgs(valueWithContext, missingPath); } - throw new DartdocFileMissing( - '$description, set to ${valueWithContext.value}, resolves to missing path: "${missingPath}"'); } - @override - /// Try to find an explicit argument setting this value, but if not, fall back to files /// finally, the default. + @override T valueAt(Directory dir) { T value = _valueAtFromArgs(); if (value == null) value = _valueAtFromFiles(dir); @@ -488,16 +581,6 @@ class DartdocOptionFileOnly extends DartdocOption _parentDirOverridesChild = parentDirOverridesChild; } - @override - void _onMissing( - _OptionValueWithContext valueWithContext, String missingPath) { - String dartdocYaml = pathLib.canonicalize(pathLib.join( - valueWithContext.canonicalDirectoryPath, - valueWithContext.definingFile)); - throw new DartdocFileMissing( - 'Field ${fieldName} from ${dartdocYaml}, set to ${valueWithContext.value}, resolves to missing path: "${missingPath}"'); - } - @override bool get parentDirOverridesChild => _parentDirOverridesChild; } @@ -520,6 +603,10 @@ abstract class _DartdocFileOption implements DartdocOption { @override void _onMissing( + _OptionValueWithContext valueWithContext, String missingPath) => + _onMissingFromFiles(valueWithContext, missingPath); + + void _onMissingFromFiles( _OptionValueWithContext valueWithContext, String missingPath) { String dartdocYaml = pathLib.join( valueWithContext.canonicalDirectoryPath, valueWithContext.definingFile); @@ -643,16 +730,16 @@ abstract class _DartdocFileOption implements DartdocOption { /// Mixin class implementing command-line arguments for [DartdocOption]. abstract class _DartdocArgOption implements DartdocOption { - /// For [ArgsParser], set to true if the argument can be negated with --no on the command line. + /// For [ArgParser], set to true if the argument can be negated with --no on the command line. bool get negatable; - /// For [ArgsParser], set to true if a single string argument will be broken into a list on commas. + /// For [ArgParser], set to true if a single string argument will be broken into a list on commas. bool get splitCommas; - /// For [ArgsParser], set to true to hide this from the help menu. + /// For [ArgParser], set to true to hide this from the help menu. bool get hide; - /// For [ArgsParser], set to a single character to have a short version of the command line argument. + /// For [ArgParser], set to a single character to have a short version of the command line argument. String get abbr; /// valueAt for arguments ignores the [dir] parameter and only uses command @@ -682,6 +769,10 @@ abstract class _DartdocArgOption implements DartdocOption { @override void _onMissing( + _OptionValueWithContext valueWithContext, String missingPath) => + _onMissingFromArgs(valueWithContext, missingPath); + + void _onMissingFromArgs( _OptionValueWithContext valueWithContext, String missingPath) { throw new DartdocFileMissing( 'Argument --${argName}, set to ${valueWithContext.value}, resolves to missing path: "${missingPath}"'); @@ -757,7 +848,7 @@ abstract class _DartdocArgOption implements DartdocOption { } else if (_isInt || _isDouble || _isString) { argParser.addOption(argName, abbr: abbr, - defaultsTo: defaultsTo.toString(), + defaultsTo: defaultsTo?.toString() ?? null, help: help, hide: hide); } else if (_isListString || _isMapString) { @@ -832,6 +923,7 @@ class DartdocOptionContext { List get excludePackages => optionSet['excludePackages'].valueAt(context); + String get flutterRoot => optionSet['flutterRoot'].valueAt(context); bool get hideSdkText => optionSet['hideSdkText'].valueAt(context); List get include => optionSet['include'].valueAt(context); List get includeExternal => @@ -842,9 +934,8 @@ class DartdocOptionContext { // ignore: unused_element String get _input => optionSet['input'].valueAt(context); String get inputDir => optionSet['inputDir'].valueAt(context); - bool get linkToExternal => optionSet['linkTo']['external'].valueAt(context); - String get linkToExternalUrl => - optionSet['linkTo']['externalUrl'].valueAt(context); + bool get linkToRemote => optionSet['linkTo']['remote'].valueAt(context); + String get linkToUrl => optionSet['linkTo']['url'].valueAt(context); /// _linkToHosted is only used to construct synthetic options. // ignore: unused_element @@ -878,19 +969,20 @@ Future> createDartdocOptions() async { new DartdocOptionArgOnly('addCrossdart', false, help: 'Add Crossdart links to the source code pieces.', negatable: true), - new DartdocOptionBoth('ambiguousReexportScorerMinConfidence', 0.1, + new DartdocOptionArgFile( + 'ambiguousReexportScorerMinConfidence', 0.1, help: 'Minimum scorer confidence to suppress warning on ambiguous reexport.'), new DartdocOptionArgOnly('autoIncludeDependencies', false, help: 'Include all the used libraries into the docs, even the ones not in the current package or "include-external"', negatable: true), - new DartdocOptionBoth>('categoryOrder', [], + new DartdocOptionArgFile>('categoryOrder', [], help: "A list of categories (not package names) to place first when grouping symbols on dartdoc's sidebar. " 'Unmentioned categories are sorted after these.'), - new DartdocOptionSynthetic>('dropTextFrom', - (DartdocOptionSynthetic option, Directory dir) { + new DartdocOptionSyntheticOnly>('dropTextFrom', + (DartdocSyntheticOption> option, Directory dir) { if (option.parent['hideSdkText'].valueAt(dir)) { return [ 'dart.async', @@ -913,22 +1005,32 @@ Future> createDartdocOptions() async { } return []; }, help: 'Remove text from libraries with the following names.'), - new DartdocOptionBoth('examplePathPrefix', null, + new DartdocOptionArgFile('examplePathPrefix', null, isDir: true, help: 'Prefix for @example paths.\n(defaults to the project root)', mustExist: true), - new DartdocOptionBoth>('exclude', [], + new DartdocOptionArgFile>('exclude', [], help: 'Library names to ignore.', splitCommas: true), new DartdocOptionArgOnly>('excludePackages', [], help: 'Package names to ignore.', splitCommas: true), + // This could be a ArgOnly, but trying to not provide too many ways + // to set the flutter root. + new DartdocOptionSyntheticOnly( + 'flutterRoot', + (DartdocSyntheticOption option, Directory dir) => + resolveTildePath(Platform.environment['FLUTTER_ROOT']), + isDir: true, + help: 'Root of the Flutter SDK, specified from environment.', + mustExist: true, + ), new DartdocOptionArgOnly('hideSdkText', false, hide: true, help: 'Drop all text for SDK components. Helpful for integration tests for dartdoc, probably not useful for anything else.', negatable: true), - new DartdocOptionBoth>('include', [], + new DartdocOptionArgFile>('include', [], help: 'Library names to generate docs for.', splitCommas: true), - new DartdocOptionBoth>('includeExternal', null, + new DartdocOptionArgFile>('includeExternal', null, isFile: true, help: 'Additional (external) dart files to include; use "dir/fileName", ' @@ -939,8 +1041,8 @@ Future> createDartdocOptions() async { help: 'Show source code blocks.', negatable: true), new DartdocOptionArgOnly('input', Directory.current.path, isDir: true, help: 'Path to source directory', mustExist: true), - new DartdocOptionSynthetic('inputDir', - (DartdocOptionSynthetic option, Directory dir) { + new DartdocOptionSyntheticOnly('inputDir', + (DartdocSyntheticOption option, Directory dir) { if (option.parent['sdkDocs'].valueAt(dir)) { return option.parent['sdkDir'].valueAt(dir); } @@ -951,23 +1053,6 @@ Future> createDartdocOptions() async { mustExist: true), new DartdocOptionSet('linkTo') ..addAll([ - new DartdocOptionArgOnly('external', false, - help: 'Allow links to be generated for packages outside this one.', - negatable: true), - new DartdocOptionSynthetic('externalUrl', - (DartdocOptionSynthetic option, Directory dir) { - String url = option.parent['url'].valueAt(dir); - if (url != null) { - return url; - } - String hostedAt = - option.parent.parent['packageMeta'].valueAt(dir).hostedAt; - if (hostedAt != null) { - Map hostMap = option.parent['hosted'].valueAt(dir); - if (hostMap.containsKey(hostedAt)) return hostMap[hostedAt]; - } - return ''; - }, help: 'Url to use for this particular package.'), new DartdocOptionArgOnly>( 'hosted', { @@ -975,14 +1060,41 @@ Future> createDartdocOptions() async { 'https://pub.dartlang.org/documentation/%n%/%v%' }, help: 'Specify URLs for hosted pub packages'), - new DartdocOptionFileOnly('url', null, - help: 'Use external linkage for this package, with this base url'), + new DartdocOptionArgOnly>( + 'sdks', + { + 'Dart': 'https://api.dartlang.org/%b%/%v%', + 'Flutter': 'https://docs.flutter.io/flutter', + }, + help: 'Specify URLs for SDKs.', + ), + new DartdocOptionFileSynth('url', + (DartdocSyntheticOption option, Directory dir) { + PackageMeta packageMeta = + option.parent.parent['packageMeta'].valueAt(dir); + // Prefer SDK check first, then pub cache check. + String inSdk = packageMeta + .sdkType(option.parent.parent['flutterRoot'].valueAt(dir)); + if (inSdk != null) { + Map sdks = option.parent['sdks'].valueAt(dir); + if (sdks.containsKey(inSdk)) return sdks[inSdk]; + } + String hostedAt = packageMeta.hostedAt; + if (hostedAt != null) { + Map hostMap = option.parent['hosted'].valueAt(dir); + if (hostMap.containsKey(hostedAt)) return hostMap[hostedAt]; + } + return ''; + }, help: 'Url to use for this particular package.'), + new DartdocOptionArgOnly('remote', false, + help: 'Allow links to be generated for packages outside this one.', + negatable: true), ]), new DartdocOptionArgOnly('output', pathLib.join('doc', 'api'), isDir: true, help: 'Path to output directory.'), - new DartdocOptionSynthetic( + new DartdocOptionSyntheticOnly( 'packageMeta', - (DartdocOptionSynthetic option, Directory dir) { + (DartdocSyntheticOption option, Directory dir) { PackageMeta packageMeta = new PackageMeta.fromDir(dir); if (packageMeta == null) { throw new DartdocOptionError( @@ -997,12 +1109,19 @@ Future> createDartdocOptions() async { 'Unmentioned categories are sorted after these.'), new DartdocOptionArgOnly('sdkDocs', false, help: 'Generate ONLY the docs for the Dart SDK.', negatable: false), - new DartdocOptionArgOnly('sdkDir', defaultSdkDir.absolute.path, - help: 'Path to the SDK directory.', isDir: true, mustExist: true), + new DartdocOptionArgSynth('sdkDir', + (DartdocSyntheticOption option, Directory dir) { + if ((option.root['topLevelPackageMeta'].valueAt(dir) as PackageMeta) + .requiresFlutter) { + return pathLib.join(option.root['flutterRoot'].valueAt(dir), 'bin', + 'cache', 'dart-sdk'); + } + return defaultSdkDir.absolute.path; + }, help: 'Path to the SDK directory.', isDir: true, mustExist: true), new DartdocOptionArgOnly('showWarnings', false, help: 'Display all warnings.', negatable: false), - new DartdocOptionSynthetic('topLevelPackageMeta', - (DartdocOptionSynthetic option, Directory dir) { + new DartdocOptionSyntheticOnly('topLevelPackageMeta', + (DartdocSyntheticOption option, Directory dir) { PackageMeta packageMeta = new PackageMeta.fromDir( new Directory(option.parent['inputDir'].valueAt(dir))); if (packageMeta == null) { diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart index 1210cf4da6..2bebbd7d66 100644 --- a/lib/src/html/html_generator.dart +++ b/lib/src/html/html_generator.dart @@ -179,24 +179,24 @@ abstract class GeneratorContext implements DartdocOptionContext { Future> createGeneratorOptions() async { await _setSdkFooterCopyrightUri(); return [ - new DartdocOptionBoth('favicon', null, + new DartdocOptionArgFile('favicon', null, isFile: true, help: 'A path to a favicon for the generated docs.', mustExist: true), - new DartdocOptionBoth>('footer', [], + new DartdocOptionArgFile>('footer', [], isFile: true, help: 'paths to footer files containing HTML text.', mustExist: true, splitCommas: true), - new DartdocOptionBoth>('footerText', [], + new DartdocOptionArgFile>('footerText', [], isFile: true, help: 'paths to footer-text files (optional text next to the package name ' 'and version).', mustExist: true, splitCommas: true), - new DartdocOptionSynthetic>('footerTextPaths', - (DartdocOptionSynthetic option, Directory dir) { + new DartdocOptionSyntheticOnly>('footerTextPaths', + (DartdocSyntheticOption> option, Directory dir) { List footerTextPaths = []; // TODO(jcollins-g): Eliminate special casing for SDK and use config file. if (new PackageMeta.fromDir(dir).isSdk) { @@ -205,7 +205,7 @@ Future> createGeneratorOptions() async { footerTextPaths.addAll(option.parent['footerText'].valueAt(dir)); return footerTextPaths; }), - new DartdocOptionBoth>('header', [], + new DartdocOptionArgFile>('header', [], isFile: true, help: 'paths to header files containing HTML text.', splitCommas: true), diff --git a/lib/src/model.dart b/lib/src/model.dart index b5570f69de..b3fc2e8ebc 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -48,6 +48,7 @@ import 'package:dartdoc/src/warnings.dart'; import 'package:front_end/src/byte_store/byte_store.dart'; import 'package:front_end/src/base/performance_logger.dart'; import 'package:path/path.dart' as pathLib; +import 'package:pub_semver/pub_semver.dart'; import 'package:tuple/tuple.dart'; import 'package:package_config/discovery.dart' as package_config; @@ -80,7 +81,7 @@ int byFeatureOrdering(String a, String b) { } final RegExp locationSplitter = new RegExp(r"(package:|[\\/;.])"); -final RegExp substituteName = new RegExp(r"%([nv])%"); +final RegExp substituteNameVersion = new RegExp(r"%([bnv])%"); /// Mixin for subclasses of ModelElement representing Elements that can be /// inherited from one class to another. @@ -544,7 +545,7 @@ class Class extends ModelElement } /// This class might be canonical for elements it does not contain. - /// See [canonicalEnclosingElement]. + /// See [Inheritable.canonicalEnclosingElement]. bool contains(Element element) => allElements.containsKey(element); ModelElement findModelElement(Element element) => allElements[element]; @@ -1755,7 +1756,7 @@ class Library extends ModelElement with Categorization { /// [allModelElements] resolved to their original names. /// - /// A collection of [ModelElement.fullyQualifiedNames] for [ModelElement]s + /// A collection of [ModelElement.fullyQualifiedName]s for [ModelElement]s /// documented with this library, but these ModelElements and names correspond /// to the defining library where each originally came from with respect /// to inheritance and reexporting. Most useful for error reporting. @@ -3463,11 +3464,11 @@ abstract class ModelElement extends Canonicalization return lib; } - /// Replace {@example ...} in API comments with the content of named file. + /// Replace {@example ...} in API comments with the content of named file. /// /// Syntax: /// - /// {@example PATH [region=NAME] [lang=NAME]} + /// {@example PATH [region=NAME] [lang=NAME]} /// /// where PATH and NAME are tokens _without_ whitespace; NAME can optionally be /// quoted (use of quotes is for backwards compatibility and discouraged). @@ -3476,10 +3477,11 @@ abstract class ModelElement extends Canonicalization /// named `dir/file-r.ext.md`, relative to the project root directory (of the /// project for which the docs are being generated). /// - /// Examples: + /// Examples: (escaped in this comment to show literal values in dartdoc's + /// dartdoc) /// - /// {@example examples/angular/quickstart/web/main.dart} - /// {@example abc/def/xyz_component.dart region=template lang=html} + /// {@example examples/angular/quickstart/web/main.dart} + /// {@example abc/def/xyz_component.dart region=template lang=html} /// String _injectExamples(String rawdocs) { final dirPath = package.packageMeta.dir.path; @@ -3621,7 +3623,7 @@ class ModelFunction extends ModelFunctionTyped { FunctionElement get _func => (element as FunctionElement); } -/// A [ModelElement] for a [GenericModelFunctionElement] that is an +/// A [ModelElement] for a [FunctionTypedElement] that is an /// explicit typedef. /// /// Distinct from ModelFunctionTypedef in that it doesn't @@ -3642,7 +3644,7 @@ class ModelFunctionAnonymous extends ModelFunctionTyped { bool get isPublic => false; } -/// A [ModelElement] for a [GenericModelFunctionElement] that is part of an +/// A [ModelElement] for a [FunctionTypedElement] that is part of an /// explicit typedef. class ModelFunctionTypedef extends ModelFunctionTyped { ModelFunctionTypedef( @@ -3948,7 +3950,7 @@ class PackageGraph extends Canonicalization String get location => '(top level package)'; /// Flush out any warnings we might have collected while - /// [_packageWarningOptions.autoFlush] was false. + /// [PackageWarningOptions.autoFlush] was false. void flushWarnings() { _packageWarningCounter.maybeFlush(); } @@ -4669,7 +4671,7 @@ abstract class LibraryContainer extends Nameable /// A category is a subcategory of a package, containing libraries tagged /// with a @category identifier. Comparable so it can be sorted according to -/// [config.categoryOrder]. +/// [DartdocOptionContext.categoryOrder]. class Category extends LibraryContainer { final String _name; @@ -4773,7 +4775,7 @@ class Package extends LibraryContainer DocumentLocation get documentedWhere { if (!isLocal) { - if (config.linkToExternal && config.linkToExternalUrl.isNotEmpty) { + if (config.linkToRemote && config.linkToUrl.isNotEmpty) { return DocumentLocation.remote; } else { return DocumentLocation.missing; @@ -4793,10 +4795,22 @@ class Package extends LibraryContainer if (_baseHref == null) { if (documentedWhere == DocumentLocation.remote) { _baseHref = - config.linkToExternalUrl.replaceAllMapped(substituteName, (m) { + config.linkToUrl.replaceAllMapped(substituteNameVersion, (m) { switch (m.group(1)) { + // Return the prerelease tag of the release if a prerelease, + // or 'stable' otherwise. Mostly coded around + // the Dart SDK's use of dev/stable, but theoretically applicable + // elsewhere. + case 'b': + { + Version version = new Version.parse(packageMeta.version); + return version.isPreRelease + ? version.preRelease.first + : 'stable'; + } case 'n': return name; + // The full version string of the package. case 'v': return packageMeta.version; } diff --git a/lib/src/package_meta.dart b/lib/src/package_meta.dart index fe44179daa..3ec9eb4373 100644 --- a/lib/src/package_meta.dart +++ b/lib/src/package_meta.dart @@ -132,9 +132,32 @@ abstract class PackageMeta { return _packageMetaCache[dir.absolute.path]; } + /// Returns true if this represents a 'Dart' SDK. A package can be part of + /// Dart and Flutter at the same time, but if we are part of a Dart SDK + /// sdkType should never return null. bool get isSdk; + + /// Returns 'Dart' or 'Flutter' (preferentially, 'Flutter' when the answer is + /// "both"), or null if this package is not part of a SDK. + String sdkType(String flutterRootPath) { + String flutterPackages = pathLib.join(flutterRootPath, 'packages'); + String flutterBinCache = pathLib.join(flutterRootPath, 'bin', 'cache'); + + /// Don't include examples or other non-SDK components as being the + /// "Flutter SDK". + if (pathLib.isWithin( + flutterPackages, pathLib.canonicalize(dir.absolute.path)) || + pathLib.isWithin( + flutterBinCache, pathLib.canonicalize(dir.absolute.path))) { + return 'Flutter'; + } + return isSdk ? 'Dart' : null; + } + bool get needsPubGet => false; + bool get requiresFlutter; + void runPubGet(); String get name; @@ -255,7 +278,7 @@ class _FilePackageMeta extends PackageMeta { StringBuffer buf = new StringBuffer(); buf.writeln('${result.stdout}'); buf.writeln('${result.stderr}'); - throw DartdocFailure('pub get failed: ${buf.toString().trim()}'); + throw new DartdocFailure('pub get failed: ${buf.toString().trim()}'); } } @@ -268,6 +291,10 @@ class _FilePackageMeta extends PackageMeta { @override String get homepage => _pubspec['homepage']; + @override + bool get requiresFlutter => + _pubspec['environment']?.containsKey('flutter') == true; + @override FileContents getReadmeContents() { if (_readme != null) return _readme; @@ -355,6 +382,9 @@ class _SdkMeta extends PackageMeta { @override String get homepage => 'https://github.com/dart-lang/sdk'; + @override + bool get requiresFlutter => false; + @override FileContents getReadmeContents() { File f = new File(pathLib.join(dir.path, 'lib', 'api_readme.md')); diff --git a/pubspec.lock b/pubspec.lock index ccedc2df36..b8ef531cd4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -254,12 +254,12 @@ packages: source: hosted version: "1.3.4" pub_semver: - dependency: "direct dev" + dependency: "direct main" description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.3.2" + version: "1.3.7" quiver: dependency: "direct main" description: @@ -408,4 +408,4 @@ packages: source: hosted version: "2.1.13" sdks: - dart: ">=2.0.0-dev.23.0 <=2.0.0-dev.49.0" + dart: ">=2.0.0-dev.23.0 <=2.0.0-edge.af1436931b93e755d38223c487d33a0a1f5eadf5" diff --git a/pubspec.yaml b/pubspec.yaml index 7331b9e274..d4cb17c21a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: mustache4dart: ^2.1.1 package_config: '>=0.1.5 <2.0.0' path: ^1.3.0 + pub_semver: ^1.3.7 quiver: ^0.27.0 resource: ^2.1.2 stack_trace: ^1.4.2 @@ -33,7 +34,6 @@ dev_dependencies: io: ^0.3.0 http: ^0.11.0 meta: ^1.0.0 - pub_semver: ^1.0.0 test: '^0.12.24' executables: dartdoc: null diff --git a/test/dartdoc_options_test.dart b/test/dartdoc_options_test.dart index 0390ca2d61..a3b4fdd85d 100644 --- a/test/dartdoc_options_test.dart +++ b/test/dartdoc_options_test.dart @@ -13,7 +13,8 @@ import 'package:test/test.dart'; void main() { DartdocOptionSet dartdocOptionSetFiles; DartdocOptionSet dartdocOptionSetArgs; - DartdocOptionSet dartdocOptionSetBoth; + DartdocOptionSet dartdocOptionSetAll; + DartdocOptionSet dartdocOptionSetSynthetic; Directory tempDir; Directory firstDir; Directory secondDir; @@ -26,6 +27,33 @@ void main() { File firstExisting; setUpAll(() { + dartdocOptionSetSynthetic = new DartdocOptionSet('dartdoc'); + dartdocOptionSetSynthetic + .add(new DartdocOptionArgFile('mySpecialInteger', 91)); + dartdocOptionSetSynthetic.add( + new DartdocOptionSyntheticOnly>('vegetableLoader', + (DartdocSyntheticOption> option, Directory dir) { + if (option.root['mySpecialInteger'].valueAt(dir) > 20) { + return ['existing.dart']; + } else { + return ['not_existing.dart']; + } + })); + dartdocOptionSetSynthetic.add( + new DartdocOptionSyntheticOnly>('vegetableLoaderChecked', + (DartdocSyntheticOption> option, Directory dir) { + return option.root['vegetableLoader'].valueAt(dir); + }, isFile: true, mustExist: true)); + dartdocOptionSetSynthetic.add(new DartdocOptionFileSynth('double', + (DartdocSyntheticOption option, Directory dir) { + return 3.7 + 4.1; + })); + dartdocOptionSetSynthetic.add( + new DartdocOptionArgSynth('nonCriticalFileOption', + (DartdocSyntheticOption option, Directory dir) { + return option.root['vegetableLoader'].valueAt(dir).first; + }, isFile: true)); + dartdocOptionSetFiles = new DartdocOptionSet('dartdoc'); dartdocOptionSetFiles .add(new DartdocOptionFileOnly>('categoryOrder', [])); @@ -82,33 +110,20 @@ void main() { 'unimportantFile', 'whatever', isFile: true)); - dartdocOptionSetBoth = new DartdocOptionSet('dartdoc'); - dartdocOptionSetBoth - .add(new DartdocOptionBoth>('categoryOrder', [])); - dartdocOptionSetBoth - .add(new DartdocOptionBoth('mySpecialInteger', 91)); - dartdocOptionSetBoth.add(new DartdocOptionSet('warn') - ..addAll([new DartdocOptionBoth('unrecognizedVegetable', false)])); - dartdocOptionSetBoth.add(new DartdocOptionBoth>( + dartdocOptionSetAll = new DartdocOptionSet('dartdoc'); + dartdocOptionSetAll + .add(new DartdocOptionArgFile>('categoryOrder', [])); + dartdocOptionSetAll + .add(new DartdocOptionArgFile('mySpecialInteger', 91)); + dartdocOptionSetAll.add(new DartdocOptionSet('warn') + ..addAll( + [new DartdocOptionArgFile('unrecognizedVegetable', false)])); + dartdocOptionSetAll.add(new DartdocOptionArgFile>( 'mapOption', {'hi': 'there'})); - dartdocOptionSetBoth - .add(new DartdocOptionBoth('notInAnyFile', 'so there')); - dartdocOptionSetBoth.add(new DartdocOptionBoth('fileOption', null, + dartdocOptionSetAll + .add(new DartdocOptionArgFile('notInAnyFile', 'so there')); + dartdocOptionSetAll.add(new DartdocOptionArgFile('fileOption', null, isFile: true, mustExist: true)); - dartdocOptionSetBoth.add(new DartdocOptionSynthetic>( - 'vegetableLoader', (DartdocOptionSynthetic option, Directory dir) { - if (option.root['mySpecialInteger'].valueAt(dir) > 20) { - return ['existing.dart']; - } else { - return ['not_existing.dart']; - } - })); - dartdocOptionSetBoth.add(new DartdocOptionSynthetic>( - 'vegetableLoaderChecked', - (DartdocOptionSynthetic option, Directory dir) => - option.root['vegetableLoader'].valueAt(dir), - isFile: true, - mustExist: true)); tempDir = Directory.systemTemp.createTempSync('options_test'); firstDir = new Directory(pathLib.join(tempDir.path, 'firstDir')) @@ -168,25 +183,26 @@ dartdoc: group('new style synthetic option', () { test('validate argument override changes value', () { - dartdocOptionSetBoth.parseArguments(['--my-special-integer', '12']); - expect(dartdocOptionSetBoth['vegetableLoader'].valueAt(tempDir), + dartdocOptionSetSynthetic.parseArguments(['--my-special-integer', '12']); + expect(dartdocOptionSetSynthetic['vegetableLoader'].valueAt(tempDir), orderedEquals(['not_existing.dart'])); }); test('validate default value of synthetic', () { - dartdocOptionSetBoth.parseArguments([]); - expect(dartdocOptionSetBoth['vegetableLoader'].valueAt(tempDir), + dartdocOptionSetSynthetic.parseArguments([]); + expect(dartdocOptionSetSynthetic['vegetableLoader'].valueAt(tempDir), orderedEquals(['existing.dart'])); }); test('file validation of synthetic', () { - dartdocOptionSetBoth.parseArguments([]); - expect(dartdocOptionSetBoth['vegetableLoaderChecked'].valueAt(firstDir), + dartdocOptionSetSynthetic.parseArguments([]); + expect( + dartdocOptionSetSynthetic['vegetableLoaderChecked'].valueAt(firstDir), orderedEquals([pathLib.canonicalize(firstExisting.path)])); String errorMessage; try { - dartdocOptionSetBoth['vegetableLoaderChecked'].valueAt(tempDir); + dartdocOptionSetSynthetic['vegetableLoaderChecked'].valueAt(tempDir); } on DartdocFileMissing catch (e) { errorMessage = e.message; } @@ -195,17 +211,43 @@ dartdoc: equals( 'Synthetic configuration option dartdoc from , computed as [existing.dart], resolves to missing path: "${pathLib.canonicalize(pathLib.join(tempDir.absolute.path, 'existing.dart'))}"')); }); + + test('file can override synthetic in FileSynth', () { + dartdocOptionSetSynthetic.parseArguments([]); + expect( + dartdocOptionSetSynthetic['double'].valueAt(firstDir), equals(3.3)); + expect(dartdocOptionSetSynthetic['double'].valueAt(tempDir), equals(7.8)); + }); + + test('arg can override synthetic in ArgSynth', () { + dartdocOptionSetSynthetic + .parseArguments(['--non-critical-file-option', 'stuff.zip']); + // Since this is an ArgSynth, it ignores the yaml option and resolves to the CWD + expect( + dartdocOptionSetSynthetic['nonCriticalFileOption'].valueAt(firstDir), + equals(pathLib.canonicalize( + pathLib.join(Directory.current.path, 'stuff.zip')))); + }); + + test('ArgSynth defaults to synthetic', () { + dartdocOptionSetSynthetic.parseArguments([]); + // This option is composed of FileOptions which make use of firstDir. + expect( + dartdocOptionSetSynthetic['nonCriticalFileOption'].valueAt(firstDir), + equals(pathLib + .canonicalize(pathLib.join(firstDir.path, 'existing.dart')))); + }); }); group('new style dartdoc both file and argument options', () { test( 'validate argument with wrong file throws error even if dartdoc_options is right', () { - dartdocOptionSetBoth + dartdocOptionSetAll .parseArguments(['--file-option', 'override-not-existing.dart']); String errorMessage; try { - dartdocOptionSetBoth['fileOption'].valueAt(firstDir); + dartdocOptionSetAll['fileOption'].valueAt(firstDir); } on DartdocFileMissing catch (e) { errorMessage = e.message; } @@ -216,17 +258,17 @@ dartdoc: }); test('validate argument can override missing file', () { - dartdocOptionSetBoth.parseArguments( + dartdocOptionSetAll.parseArguments( ['--file-option', pathLib.canonicalize(firstExisting.path)]); - expect(dartdocOptionSetBoth['fileOption'].valueAt(secondDir), + expect(dartdocOptionSetAll['fileOption'].valueAt(secondDir), equals(pathLib.canonicalize(firstExisting.path))); }); test('File errors still get passed through', () { - dartdocOptionSetBoth.parseArguments([]); + dartdocOptionSetAll.parseArguments([]); String errorMessage; try { - dartdocOptionSetBoth['fileOption'].valueAt(secondDir); + dartdocOptionSetAll['fileOption'].valueAt(secondDir); } on DartdocFileMissing catch (e) { errorMessage = e.message; } @@ -238,36 +280,35 @@ dartdoc: }); test('validate override behavior basic', () { - dartdocOptionSetBoth.parseArguments( + dartdocOptionSetAll.parseArguments( ['--not-in-any-file', 'aha', '--map-option', 'over::theSea']); - expect(dartdocOptionSetBoth['mapOption'].valueAt(tempDir), + expect(dartdocOptionSetAll['mapOption'].valueAt(tempDir), equals({'over': 'theSea'})); - expect(dartdocOptionSetBoth['mapOption'].valueAt(firstDir), + expect(dartdocOptionSetAll['mapOption'].valueAt(firstDir), equals({'over': 'theSea'})); - expect(dartdocOptionSetBoth['notInAnyFile'].valueAt(firstDir), - equals('aha')); - expect(dartdocOptionSetBoth['mySpecialInteger'].valueAt(firstDir), + expect( + dartdocOptionSetAll['notInAnyFile'].valueAt(firstDir), equals('aha')); + expect(dartdocOptionSetAll['mySpecialInteger'].valueAt(firstDir), equals(30)); }); test('validate override behavior for parent directories', () { - dartdocOptionSetBoth.parseArguments(['--my-special-integer', '14']); - expect( - dartdocOptionSetBoth['mySpecialInteger'].valueAt(secondDirFirstSub), + dartdocOptionSetAll.parseArguments(['--my-special-integer', '14']); + expect(dartdocOptionSetAll['mySpecialInteger'].valueAt(secondDirFirstSub), equals(14)); }); test('validate arg defaults do not override file', () { - dartdocOptionSetBoth.parseArguments([]); - expect(dartdocOptionSetBoth['mySpecialInteger'].valueAt(secondDir), + dartdocOptionSetAll.parseArguments([]); + expect(dartdocOptionSetAll['mySpecialInteger'].valueAt(secondDir), equals(11)); }); test( 'validate setting the default manually in an argument overrides the file', () { - dartdocOptionSetBoth.parseArguments(['--my-special-integer', '91']); - expect(dartdocOptionSetBoth['mySpecialInteger'].valueAt(secondDir), + dartdocOptionSetAll.parseArguments(['--my-special-integer', '91']); + expect(dartdocOptionSetAll['mySpecialInteger'].valueAt(secondDir), equals(91)); }); }); diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart index cf91192261..c3aac04fba 100644 --- a/test/dartdoc_test.dart +++ b/test/dartdoc_test.dart @@ -4,12 +4,12 @@ library dartdoc.dartdoc_test; +import 'dart:async'; import 'dart:io'; import 'package:dartdoc/dartdoc.dart'; import 'package:dartdoc/src/model.dart'; import 'package:dartdoc/src/package_meta.dart'; -import 'package:io/io.dart'; import 'package:path/path.dart' as pathLib; import 'package:test/test.dart'; @@ -18,20 +18,23 @@ import 'src/utils.dart'; void main() { group('dartdoc without generators', () { Directory tempDir; - + List outputParam; setUp(() { tempDir = Directory.systemTemp.createTempSync('dartdoc.test.'); + outputParam = ['--output', tempDir.path]; }); tearDown(() { delete(tempDir); }); + Future contextFromArgvTemp(List argv) async { + return await contextFromArgv(argv..addAll(outputParam)); + } + test('basic interlinking test', () async { - Dartdoc dartdoc = new Dartdoc.withoutGenerators( - await contextFromArgv( - ['--input', testPackageDir.path, '--link-to-external']), - tempDir); + Dartdoc dartdoc = new Dartdoc.withoutGenerators(await contextFromArgvTemp( + ['--input', testPackageDir.path, '--link-to-remote'])); DartdocResults results = await dartdoc.generateDocs(); PackageGraph p = results.packageGraph; Package tuple = p.publicPackages.firstWhere((p) => p.name == 'tuple'); @@ -44,17 +47,16 @@ void main() { useSomethingInAnotherPackage.modelType.linkedName, startsWith( 'Tuple2')); - // Uncomment after SDK has appropriate configuration option added - // RegExp stringLink = new RegExp( - // 'https://api.dartlang.org/.*/${Platform.version.split(' ').first}/dart-core/String-class.html">String'); - // expect(useSomethingInAnotherPackage.modelType.linkedName, - // contains(stringLink)); + RegExp stringLink = new RegExp( + 'https://api.dartlang.org/(dev|stable|be)/${Platform.version.split(' ').first}/dart-core/String-class.html">String'); + expect(useSomethingInAnotherPackage.modelType.linkedName, + contains(stringLink)); }); test('generate docs for ${pathLib.basename(testPackageDir.path)} works', () async { Dartdoc dartdoc = new Dartdoc.withoutGenerators( - await contextFromArgv(['--input', testPackageDir.path]), tempDir); + await contextFromArgvTemp(['--input', testPackageDir.path])); DartdocResults results = await dartdoc.generateDocs(); expect(results.packageGraph, isNotNull); @@ -69,7 +71,7 @@ void main() { test('generate docs for ${pathLib.basename(testPackageBadDir.path)} fails', () async { Dartdoc dartdoc = new Dartdoc.withoutGenerators( - await contextFromArgv(['--input', testPackageBadDir.path]), tempDir); + await contextFromArgvTemp(['--input', testPackageBadDir.path])); try { await dartdoc.generateDocs(); @@ -81,8 +83,7 @@ void main() { test('generate docs for a package that does not have a readme', () async { Dartdoc dartdoc = new Dartdoc.withoutGenerators( - await contextFromArgv(['--input', testPackageWithNoReadme.path]), - tempDir); + await contextFromArgvTemp(['--input', testPackageWithNoReadme.path])); DartdocResults results = await dartdoc.generateDocs(); expect(results.packageGraph, isNotNull); @@ -95,10 +96,8 @@ void main() { }); test('generate docs including a single library', () async { - Dartdoc dartdoc = new Dartdoc.withoutGenerators( - await contextFromArgv( - ['--input', testPackageDir.path, '--include', 'fake']), - tempDir); + Dartdoc dartdoc = new Dartdoc.withoutGenerators(await contextFromArgvTemp( + ['--input', testPackageDir.path, '--include', 'fake'])); DartdocResults results = await dartdoc.generateDocs(); expect(results.packageGraph, isNotNull); @@ -111,10 +110,8 @@ void main() { }); test('generate docs excluding a single library', () async { - Dartdoc dartdoc = new Dartdoc.withoutGenerators( - await contextFromArgv( - ['--input', testPackageDir.path, '--exclude', 'fake']), - tempDir); + Dartdoc dartdoc = new Dartdoc.withoutGenerators(await contextFromArgvTemp( + ['--input', testPackageDir.path, '--exclude', 'fake'])); DartdocResults results = await dartdoc.generateDocs(); expect(results.packageGraph, isNotNull); @@ -130,9 +127,8 @@ void main() { test('generate docs for package with embedder yaml', () async { PackageMeta meta = new PackageMeta.fromDir(testPackageWithEmbedderYaml); if (meta.needsPubGet) meta.runPubGet(); - Dartdoc dartdoc = new Dartdoc.withoutGenerators( - await contextFromArgv(['--input', testPackageWithEmbedderYaml.path]), - tempDir); + Dartdoc dartdoc = new Dartdoc.withoutGenerators(await contextFromArgvTemp( + ['--input', testPackageWithEmbedderYaml.path])); DartdocResults results = await dartdoc.generateDocs(); expect(results.packageGraph, isNotNull); diff --git a/testing/test_package_flutter_plugin/.gitignore b/testing/test_package_flutter_plugin/.gitignore new file mode 100644 index 0000000000..33b0ba3c74 --- /dev/null +++ b/testing/test_package_flutter_plugin/.gitignore @@ -0,0 +1,3 @@ +ios/ +doc/api/ +pubspec.lock diff --git a/testing/test_package_flutter_plugin/lib/testlib.dart b/testing/test_package_flutter_plugin/lib/testlib.dart new file mode 100644 index 0000000000..94cb275299 --- /dev/null +++ b/testing/test_package_flutter_plugin/lib/testlib.dart @@ -0,0 +1,12 @@ +/// This is a demonstration of interlinking with Flutter-using pub packages. +library testlib; + +import 'package:flutter/material.dart'; + +/// This widget is the best stateful widget ever. +class MyAwesomeWidget extends StatefulWidget { + MyAwesomeWidget({Key key}) : super(key: key) {} + + @override + State createState() => null; +} diff --git a/testing/test_package_flutter_plugin/pubspec.yaml b/testing/test_package_flutter_plugin/pubspec.yaml new file mode 100644 index 0000000000..3612d8e1cb --- /dev/null +++ b/testing/test_package_flutter_plugin/pubspec.yaml @@ -0,0 +1,12 @@ +name: test_package_flutter_plugin +version: 0.1.0 +description: A happy flutter almost plugin being documented +homepage: http://github.com/dart-lang/dartdoc/tree/master/testing/test_package_flutter_plugin + +dependencies: + flutter: + sdk: flutter + +environment: + sdk: ">2.0.0-dev.48.0 <3.0.0" + flutter: ">=0.1.4 <2.0.0" diff --git a/tool/grind.dart b/tool/grind.dart index 28ef9bd27b..3cbc281e5a 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -5,14 +5,34 @@ import 'dart:async'; import 'dart:io' hide ProcessException; +import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/io_utils.dart'; import 'package:dartdoc/src/model_utils.dart'; import 'package:grinder/grinder.dart'; +import 'package:io/io.dart'; import 'package:path/path.dart' as pathLib; import 'package:yaml/yaml.dart' as yaml; main([List args]) => grind(args); +/// Thrown on failure to find something in a file. +class GrindTestFailure { + final String message; + GrindTestFailure(this.message); +} + +/// Kind of an inefficient grepper for now. +expectFileContains(String path, List items) { + File source = new File(path); + if (!source.existsSync()) + throw new GrindTestFailure('file not found: ${path}'); + for (Pattern item in items) { + if (!new File(path).readAsStringSync().contains(item)) { + throw new GrindTestFailure('Can not find ${item} in ${path}'); + } + } +} + /// Run no more than 6 futures in parallel with this. final MultiFutureTracker testFutures = new MultiFutureTracker(6); @@ -22,14 +42,72 @@ Directory createTempSync(String prefix) => final Memoizer tempdirsCache = new Memoizer(); +RandomAccessFile _updateLock; +Future _lockFuture; + +/// Returns true if we need to replace the existing flutter. We never release +/// this lock until the program exits. +Future acquireUpdateLock() async { + if (_updateLock != null) { + await _lockFuture; + return false; + } + cleanFlutterDir.parent.createSync(recursive: true); + // no await allowed between assignment of _updateLock and _lockFuture. + _updateLock = + await new File(pathLib.join(cleanFlutterDir.parent.path, 'lock')) + .open(mode: FileMode.WRITE); + _lockFuture = _updateLock.lock(); + await _lockFuture; + File lastSynced = + new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); + if (lastSynced.existsSync()) { + DateTime lastSyncedTime = new DateTime.fromMillisecondsSinceEpoch( + int.parse(await lastSynced.readAsString())); + if (lastSyncedTime.difference(new DateTime.now()) < new Duration(hours: 4)) + return false; + } + return true; +} + +Future _cleanFlutterRepo; + +/// Get a Future returning a reference to a completely clean, recent checkout of the Flutter +/// repository, fully initialized. +Future get cleanFlutterRepo async { + if (_cleanFlutterRepo == null) { + _cleanFlutterRepo = FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); + if (await acquireUpdateLock()) { + await cleanFlutterDir.delete(recursive: true); + await cleanFlutterDir.create(recursive: true); + FlutterRepo repo = await _cleanFlutterRepo; + await repo._init(); + File lastSynced = + new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); + await lastSynced.writeAsString( + (new DateTime.now()).millisecondsSinceEpoch.toString()); + } + } + return _cleanFlutterRepo; +} + Directory get dartdocDocsDir => tempdirsCache.memoized1(createTempSync, 'dartdoc'); +Directory get dartdocDocsDirRemote => + tempdirsCache.memoized1(createTempSync, 'dartdoc_remote'); Directory get sdkDocsDir => tempdirsCache.memoized1(createTempSync, 'sdkdocs'); +Directory cleanFlutterDir = new Directory( + pathLib.join(resolveTildePath('~/.dartdoc_grinder'), 'cleanFlutter')); Directory get flutterDir => tempdirsCache.memoized1(createTempSync, 'flutter'); Directory get testPackage => new Directory(pathLib.joinAll(['testing', 'test_package'])); +Directory get pluginPackage => + new Directory(pathLib.joinAll(['testing', 'test_package_flutter_plugin'])); + Directory get testPackageDocsDir => tempdirsCache.memoized1(createTempSync, 'test_package'); +Directory get pluginPackageDocsDir => + tempdirsCache.memoized1(createTempSync, 'test_package_flutter_plugin'); /// Version of dartdoc we should use when making comparisons. String get dartdocOriginalBranch { @@ -194,6 +272,7 @@ WarningsCollection jsonMessageIterableToWarnings(Iterable messageIterable, String tempPath, String pubDir, String branch) { WarningsCollection warningTexts = new WarningsCollection(tempPath, pubDir, branch); + if (messageIterable == null) return warningTexts; for (Map message in messageIterable) { if (message.containsKey('level') && message['level'] == 'WARNING' && @@ -341,9 +420,12 @@ Future compareFlutterWarnings() async { Map envCurrent = _createThrowawayPubCache(); Map envOriginal = _createThrowawayPubCache(); Future currentDartdocFlutterBuild = _buildFlutterDocs(flutterDir.path, - new Future.value(Directory.current.path), envCurrent, 'current'); + new Future.value(Directory.current.path), envCurrent, 'docs-current'); Future originalDartdocFlutterBuild = _buildFlutterDocs( - originalDartdocFlutter.path, originalDartdoc, envOriginal, 'original'); + originalDartdocFlutter.path, + originalDartdoc, + envOriginal, + 'docs-original'); WarningsCollection currentDartdocWarnings = jsonMessageIterableToWarnings( await currentDartdocFlutterBuild, flutterDir.absolute.path, @@ -397,52 +479,101 @@ Future serveFlutterDocs() async { ]); } +@Task('Validate flutter docs') +@Depends(testDartdocFlutterPlugin, buildFlutterDocs) +validateFlutterDocs() {} + @Task('Build flutter docs') Future buildFlutterDocs() async { log('building flutter docs into: $flutterDir'); Map env = _createThrowawayPubCache(); await _buildFlutterDocs( - flutterDir.path, new Future.value(Directory.current.path), env); + flutterDir.path, new Future.value(Directory.current.path), env, 'docs'); String index = new File( pathLib.join(flutterDir.path, 'dev', 'docs', 'doc', 'index.html')) .readAsStringSync(); stdout.write(index); } +/// A class wrapping a flutter SDK. +class FlutterRepo { + final String flutterPath; + final Map env; + final String bin = pathLib.join('bin', 'flutter'); + + FlutterRepo._(this.flutterPath, this.env, String label) { + cacheDart = + pathLib.join(flutterPath, 'bin', 'cache', 'dart-sdk', 'bin', 'dart'); + cachePub = + pathLib.join(flutterPath, 'bin', 'cache', 'dart-sdk', 'bin', 'pub'); + env['PATH'] = + '${pathLib.join(pathLib.canonicalize(flutterPath), "bin")}:${env['PATH'] ?? Platform.environment['PATH']}'; + env['FLUTTER_ROOT'] = flutterPath; + launcher = + new SubprocessLauncher('flutter${label == null ? "" : "-$label"}', env); + } + + Future _init() async { + new Directory(flutterPath).createSync(recursive: true); + await launcher.runStreamed( + 'git', ['clone', 'https://github.com/flutter/flutter.git', '.'], + workingDirectory: flutterPath); + await launcher.runStreamed( + bin, + ['--version'], + workingDirectory: flutterPath, + ); + await launcher.runStreamed( + bin, + ['precache'], + workingDirectory: flutterPath, + ); + } + + static Future fromPath( + String flutterPath, Map env, + [String label]) async { + FlutterRepo flutterRepo = new FlutterRepo._(flutterPath, env, label); + return flutterRepo; + } + + /// Copy an existing, initialized flutter repo. + static Future copyFromExistingFlutterRepo( + FlutterRepo origRepo, String flutterPath, Map env, + [String label]) async { + await copyPath(origRepo.flutterPath, flutterPath); + FlutterRepo flutterRepo = new FlutterRepo._(flutterPath, env, label); + return flutterRepo; + } + + /// Doesn't actually copy the existing repo; use for read-only operations only. + static Future fromExistingFlutterRepo(FlutterRepo origRepo, + [String label]) async { + FlutterRepo flutterRepo = + new FlutterRepo._(origRepo.flutterPath, {}, label); + return flutterRepo; + } + + String cacheDart; + String cachePub; + SubprocessLauncher launcher; +} + Future> _buildFlutterDocs( String flutterPath, Future futureCwd, Map env, [String label]) async { - env['PATH'] = '${pathLib.join(flutterPath, "bin")}:${env['PATH']}'; - var launcher = new SubprocessLauncher( - 'build-flutter-docs${label == null ? "" : "-$label"}', env); - await launcher.runStreamed( - 'git', ['clone', 'https://github.com/flutter/flutter.git', '.'], - workingDirectory: flutterPath); - String flutterBin = pathLib.join('bin', 'flutter'); - String flutterCacheDart = - pathLib.join(flutterPath, 'bin', 'cache', 'dart-sdk', 'bin', 'dart'); - String flutterCachePub = - pathLib.join(flutterPath, 'bin', 'cache', 'dart-sdk', 'bin', 'pub'); - await launcher.runStreamed( - flutterBin, - ['--version'], - workingDirectory: flutterPath, - ); - await launcher.runStreamed( - flutterBin, - ['precache'], - workingDirectory: flutterPath, - ); - await launcher.runStreamed( - flutterCachePub, + FlutterRepo flutterRepo = await FlutterRepo.copyFromExistingFlutterRepo( + await cleanFlutterRepo, flutterPath, env, label); + await flutterRepo.launcher.runStreamed( + flutterRepo.cachePub, ['get'], workingDirectory: pathLib.join(flutterPath, 'dev', 'tools'), ); - await launcher.runStreamed( - flutterCachePub, ['global', 'activate', '-spath', '.'], + await flutterRepo.launcher.runStreamed( + flutterRepo.cachePub, ['global', 'activate', '-spath', '.'], workingDirectory: await futureCwd); - return await launcher.runStreamed( - flutterCacheDart, + return await flutterRepo.launcher.runStreamed( + flutterRepo.cacheDart, [pathLib.join('dev', 'tools', 'dartdoc.dart'), '-c', '--json'], workingDirectory: flutterPath, ); @@ -624,7 +755,6 @@ testPreviewDart2() async { testDart1() async { List parameters = ['--checked']; - for (File dartFile in testFiles) { // absolute path to work around dart-lang/sdk#32901 await testFutures.addFuture(new SubprocessLauncher( @@ -642,8 +772,69 @@ testDartdoc() async { var launcher = new SubprocessLauncher('test-dartdoc'); await launcher.runStreamed(Platform.resolvedExecutable, ['--checked', 'bin/dartdoc.dart', '--output', dartdocDocsDir.path]); - File indexHtml = joinFile(dartdocDocsDir, ['index.html']); - if (!indexHtml.existsSync()) fail('docs not generated'); + expectFileContains(pathLib.join(dartdocDocsDir.path, 'index.html'), + ['dartdoc - Dart API docs']); + final RegExp object = new RegExp('
  • Object
  • ', multiLine: true); + expectFileContains( + pathLib.join(dartdocDocsDir.path, 'dartdoc', 'ModelElement-class.html'), + [object]); +} + +@Task('Generate docs for dartdoc with remote linking') +testDartdocRemote() async { + var launcher = new SubprocessLauncher('test-dartdoc-remote'); + final RegExp object = new RegExp( + 'Object', + multiLine: true); + await launcher.runStreamed(Platform.resolvedExecutable, [ + '--checked', + 'bin/dartdoc.dart', + '--link-to-remote', + '--output', + dartdocDocsDir.path + ]); + expectFileContains(pathLib.join(dartdocDocsDir.path, 'index.html'), + ['dartdoc - Dart API docs']); + expectFileContains( + pathLib.join(dartdocDocsDir.path, 'dartdoc', 'ModelElement-class.html'), + [object]); +} + +@Task('serve docs for a package that requires flutter with remote linking') +@Depends(buildDartdocFlutterPluginDocs) +Future serveDartdocFlutterPluginDocs() async { + await _serveDocsFrom( + pluginPackageDocsDir.path, 8005, 'serve-dartdoc-flutter-plugin-docs'); +} + +@Task('Build docs for a package that requires flutter with remote linking') +buildDartdocFlutterPluginDocs() async { + FlutterRepo flutterRepo = await FlutterRepo.fromExistingFlutterRepo( + await cleanFlutterRepo, 'docs-flutter-plugin'); + + await flutterRepo.launcher.runStreamed( + Platform.resolvedExecutable, + [ + '--checked', + pathLib.join(Directory.current.path, 'bin', 'dartdoc.dart'), + '--link-to-remote', + '--output', + pluginPackageDocsDir.path + ], + workingDirectory: pluginPackage.path); +} + +@Task('Verify docs for a package that requires flutter with remote linking') +@Depends(buildDartdocFlutterPluginDocs) +testDartdocFlutterPlugin() async { + // Verify that links to Dart SDK and Flutter SDK go to the flutter site. + expectFileContains( + pathLib.join( + pluginPackageDocsDir.path, 'testlib', 'MyAwesomeWidget-class.html'), + [ + 'Widget', + 'Object' + ]); } @Task('update test_package_docs') diff --git a/tool/travis.sh b/tool/travis.sh index ec4731f3b9..6f4cceb121 100755 --- a/tool/travis.sh +++ b/tool/travis.sh @@ -15,26 +15,12 @@ if [ "$DARTDOC_BOT" = "sdk-docs" ]; then # silence stdout but echo stderr echo "" echo "Building and validating SDK docs..." - pub run grinder validate-sdk-docs - echo "SDK docs process finished" elif [ "$DARTDOC_BOT" = "flutter" ]; then echo "Running flutter dartdoc bot" - - pub run grinder build-flutter-docs + pub run grinder validate-flutter-docs else echo "Running main dartdoc bot" - - # Verify that the libraries are error free. - pub run grinder analyze - - # Run dartdoc on test_package. - (cd testing/test_package; dart -c ../../bin/dartdoc.dart) - - # And on test_package_small. - (cd testing/test_package_small; dart -c ../../bin/dartdoc.dart) - - # Run the tests. - pub run test + pub run grinder buildbot fi From 2826c6ed3a0709cbf762f45db8df44963fc6863b Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Thu, 26 Apr 2018 15:04:25 -0700 Subject: [PATCH 02/17] Fix case when FLUTTER_ROOT is not set --- lib/src/dartdoc_options.dart | 3 --- lib/src/package_meta.dart | 22 ++++++++++++---------- test/dartdoc_test.dart | 7 +++++++ tool/grind.dart | 8 ++++++-- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart index 661e8524bd..223c17305f 100644 --- a/lib/src/dartdoc_options.dart +++ b/lib/src/dartdoc_options.dart @@ -941,9 +941,6 @@ class DartdocOptionContext { // ignore: unused_element String get _linkToHosted => optionSet['linkTo']['hosted'].valueAt(context); - /// _linkToUrl is only used to construct synthetic options. - // ignore: unused_element - String get _linkToUrl => optionSet['linkTo']['url'].valueAt(context); String get output => optionSet['output'].valueAt(context); PackageMeta get packageMeta => optionSet['packageMeta'].valueAt(context); List get packageOrder => optionSet['packageOrder'].valueAt(context); diff --git a/lib/src/package_meta.dart b/lib/src/package_meta.dart index 3ec9eb4373..a2949b984b 100644 --- a/lib/src/package_meta.dart +++ b/lib/src/package_meta.dart @@ -140,16 +140,18 @@ abstract class PackageMeta { /// Returns 'Dart' or 'Flutter' (preferentially, 'Flutter' when the answer is /// "both"), or null if this package is not part of a SDK. String sdkType(String flutterRootPath) { - String flutterPackages = pathLib.join(flutterRootPath, 'packages'); - String flutterBinCache = pathLib.join(flutterRootPath, 'bin', 'cache'); - - /// Don't include examples or other non-SDK components as being the - /// "Flutter SDK". - if (pathLib.isWithin( - flutterPackages, pathLib.canonicalize(dir.absolute.path)) || - pathLib.isWithin( - flutterBinCache, pathLib.canonicalize(dir.absolute.path))) { - return 'Flutter'; + if (flutterRootPath != null) { + String flutterPackages = pathLib.join(flutterRootPath, 'packages'); + String flutterBinCache = pathLib.join(flutterRootPath, 'bin', 'cache'); + + /// Don't include examples or other non-SDK components as being the + /// "Flutter SDK". + if (pathLib.isWithin( + flutterPackages, pathLib.canonicalize(dir.absolute.path)) || + pathLib.isWithin( + flutterBinCache, pathLib.canonicalize(dir.absolute.path))) { + return 'Flutter'; + } } return isSdk ? 'Dart' : null; } diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart index c3aac04fba..6499e38dcf 100644 --- a/test/dartdoc_test.dart +++ b/test/dartdoc_test.dart @@ -43,6 +43,13 @@ void main() { .properties .firstWhere((p) => p.name == 'useSomethingInAnotherPackage'); expect(tuple.documentedWhere, equals(DocumentLocation.remote)); + expect( + (useSomethingInAnotherPackage.modelType.typeArguments.first + as ParameterizedElementType) + .element + .package + .documentedWhere, + equals(DocumentLocation.remote)); expect( useSomethingInAnotherPackage.modelType.linkedName, startsWith( diff --git a/tool/grind.dart b/tool/grind.dart index 3cbc281e5a..99b18c2624 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -740,8 +740,12 @@ testPreviewDart2() async { List parameters = ['--preview-dart-2', '--enable-asserts']; // sdk#32901 is really bad on Windows. - for (File dartFile in testFiles.where((f) => - !f.path.endsWith('html_generator_test.dart') && !Platform.isWindows)) { + for (File dartFile in testFiles + .where((f) => + !f.path.endsWith('html_generator_test.dart') && !Platform.isWindows) + .where((f) => + // grinder stopped working with preview-dart-2. + !f.path.endsWith('grind_test.dart'))) { // absolute path to work around dart-lang/sdk#32901 await testFutures.addFuture(new SubprocessLauncher( 'dart2-${pathLib.basename(dartFile.absolute.path)}') From 95aa3898c82214c2dab4ee8545131d06e4311390 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Thu, 26 Apr 2018 15:12:59 -0700 Subject: [PATCH 03/17] deletes throw if target does not exist --- tool/grind.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tool/grind.dart b/tool/grind.dart index 99b18c2624..2a5b9d2376 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -78,7 +78,9 @@ Future get cleanFlutterRepo async { if (_cleanFlutterRepo == null) { _cleanFlutterRepo = FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); if (await acquireUpdateLock()) { - await cleanFlutterDir.delete(recursive: true); + if (cleanFlutterDir.existsSync()) { + await cleanFlutterDir.delete(recursive: true); + } await cleanFlutterDir.create(recursive: true); FlutterRepo repo = await _cleanFlutterRepo; await repo._init(); From b9702681c2a4a39e6c4705fab2cd3608502f3aaf Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 09:55:37 -0700 Subject: [PATCH 04/17] Get rid of infinite recursion in some cases for option handling --- lib/src/dartdoc_options.dart | 5 +++-- lib/src/html/html_generator.dart | 28 ++++++++++++++++++---------- pubspec.lock | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart index 223c17305f..8ee26a6745 100644 --- a/lib/src/dartdoc_options.dart +++ b/lib/src/dartdoc_options.dart @@ -1108,8 +1108,9 @@ Future> createDartdocOptions() async { help: 'Generate ONLY the docs for the Dart SDK.', negatable: false), new DartdocOptionArgSynth('sdkDir', (DartdocSyntheticOption option, Directory dir) { - if ((option.root['topLevelPackageMeta'].valueAt(dir) as PackageMeta) - .requiresFlutter) { + if (!option.parent['sdkDocs'].valueAt(dir) && + (option.root['topLevelPackageMeta'].valueAt(dir) as PackageMeta) + .requiresFlutter) { return pathLib.join(option.root['flutterRoot'].valueAt(dir), 'bin', 'cache', 'dart-sdk'); } diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart index 2bebbd7d66..35f681699f 100644 --- a/lib/src/html/html_generator.dart +++ b/lib/src/html/html_generator.dart @@ -195,16 +195,24 @@ Future> createGeneratorOptions() async { 'and version).', mustExist: true, splitCommas: true), - new DartdocOptionSyntheticOnly>('footerTextPaths', - (DartdocSyntheticOption> option, Directory dir) { - List footerTextPaths = []; - // TODO(jcollins-g): Eliminate special casing for SDK and use config file. - if (new PackageMeta.fromDir(dir).isSdk) { - footerTextPaths.add(_sdkFooterCopyrightUri.toFilePath()); - } - footerTextPaths.addAll(option.parent['footerText'].valueAt(dir)); - return footerTextPaths; - }), + new DartdocOptionSyntheticOnly>( + 'footerTextPaths', + (DartdocSyntheticOption> option, Directory dir) { + List footerTextPaths = []; + // TODO(jcollins-g): Eliminate special casing for SDK and use config file. + if ((option.root['topLevelPackageMeta'].valueAt(dir) as PackageMeta) + .isSdk == + true) { + footerTextPaths + .add(pathLib.canonicalize(_sdkFooterCopyrightUri.toFilePath())); + } + footerTextPaths.addAll(option.parent['footerText'].valueAt(dir)); + return footerTextPaths; + }, + isFile: true, + help: 'paths to footer-text-files (adding special case for SDK)', + mustExist: true, + ), new DartdocOptionArgFile>('header', [], isFile: true, help: 'paths to header files containing HTML text.', diff --git a/pubspec.lock b/pubspec.lock index b8ef531cd4..20f4ce2376 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -408,4 +408,4 @@ packages: source: hosted version: "2.1.13" sdks: - dart: ">=2.0.0-dev.23.0 <=2.0.0-edge.af1436931b93e755d38223c487d33a0a1f5eadf5" + dart: ">=2.0.0-dev.23.0 <=2.0.0-dev.50.0" From 2d15b69dd3f78291c9b52db3d6b630f5bfd5aa45 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 10:43:43 -0700 Subject: [PATCH 05/17] Fix grinder stream handling to not chop off error messages --- lib/src/io_utils.dart | 14 ++++++++------ tool/grind.dart | 5 +++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/src/io_utils.dart b/lib/src/io_utils.dart index d91f44164b..02e4b4757b 100644 --- a/lib/src/io_utils.dart +++ b/lib/src/io_utils.dart @@ -103,11 +103,11 @@ class SubprocessLauncher { String get prefix => context.isNotEmpty ? '$context: ' : ''; // from flutter:dev/tools/dartdoc.dart, modified - static void _printStream(Stream> stream, Stdout output, + static Future _printStream(Stream> stream, Stdout output, {String prefix: '', Iterable Function(String line) filter}) { assert(prefix != null); if (filter == null) filter = (line) => [line]; - stream + return stream .transform(utf8.decoder) .transform(const LineSplitter()) .expand(filter) @@ -116,7 +116,7 @@ class SubprocessLauncher { output.write('$prefix$line'.trim()); output.write('\n'); } - }); + }).asFuture(); } SubprocessLauncher(this.context, [Map environment]) @@ -186,9 +186,11 @@ class SubprocessLauncher { Process process = await Process.start(executable, arguments, workingDirectory: workingDirectory, environment: environment); - _printStream(process.stdout, stdout, prefix: prefix, filter: jsonCallback); - _printStream(process.stderr, stderr, prefix: prefix, filter: jsonCallback); - await process.exitCode; + Future stdoutFuture = _printStream(process.stdout, stdout, + prefix: prefix, filter: jsonCallback); + Future stderrFuture = _printStream(process.stderr, stderr, + prefix: prefix, filter: jsonCallback); + await Future.wait([stderrFuture, stdoutFuture, process.exitCode]); int exitCode = await process.exitCode; if (exitCode != 0) { diff --git a/tool/grind.dart b/tool/grind.dart index 2a5b9d2376..c3fa2d1053 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -46,7 +46,8 @@ RandomAccessFile _updateLock; Future _lockFuture; /// Returns true if we need to replace the existing flutter. We never release -/// this lock until the program exits. +/// this lock until the program exits to prevent edge cases from spontaneously +/// deciding to download a new Flutter SDK in the middle of a run. Future acquireUpdateLock() async { if (_updateLock != null) { await _lockFuture; @@ -64,7 +65,7 @@ Future acquireUpdateLock() async { if (lastSynced.existsSync()) { DateTime lastSyncedTime = new DateTime.fromMillisecondsSinceEpoch( int.parse(await lastSynced.readAsString())); - if (lastSyncedTime.difference(new DateTime.now()) < new Duration(hours: 4)) + if (new DateTime.now().difference(lastSyncedTime) < new Duration(hours: 4)) return false; } return true; From aba45305c6028c881cb3970fdc4193bdc10b936f Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 11:43:59 -0700 Subject: [PATCH 06/17] Can't seem to get any information for failure, so hack travis script --- tool/travis.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tool/travis.sh b/tool/travis.sh index 6f4cceb121..b9b0186392 100755 --- a/tool/travis.sh +++ b/tool/travis.sh @@ -21,6 +21,10 @@ elif [ "$DARTDOC_BOT" = "flutter" ]; then echo "Running flutter dartdoc bot" pub run grinder validate-flutter-docs else + echo "running model_test" + pub get + /home/travis/dart-sdk/bin/dart --preview-dart-2 --enable-asserts /home/travis/build/dart-lang/dartdoc/test/model_test.dart + echo "Running main dartdoc bot" pub run grinder buildbot fi From e584f101875b54c35eacf1cf4b2868ca432a1c62 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 12:22:33 -0700 Subject: [PATCH 07/17] reduce parallelism --- tool/grind.dart | 4 ++-- tool/travis.sh | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tool/grind.dart b/tool/grind.dart index c3fa2d1053..70ff947ae7 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -33,8 +33,8 @@ expectFileContains(String path, List items) { } } -/// Run no more than 6 futures in parallel with this. -final MultiFutureTracker testFutures = new MultiFutureTracker(6); +/// Run no more than 4 futures in parallel with this. +final MultiFutureTracker testFutures = new MultiFutureTracker(4); // Directory.systemTemp is not a constant. So wrap it. Directory createTempSync(String prefix) => diff --git a/tool/travis.sh b/tool/travis.sh index b9b0186392..6f4cceb121 100755 --- a/tool/travis.sh +++ b/tool/travis.sh @@ -21,10 +21,6 @@ elif [ "$DARTDOC_BOT" = "flutter" ]; then echo "Running flutter dartdoc bot" pub run grinder validate-flutter-docs else - echo "running model_test" - pub get - /home/travis/dart-sdk/bin/dart --preview-dart-2 --enable-asserts /home/travis/build/dart-lang/dartdoc/test/model_test.dart - echo "Running main dartdoc bot" pub run grinder buildbot fi From d682e3da47b70712322f4824ce9672736e6a261e Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 12:36:22 -0700 Subject: [PATCH 08/17] Disable parallelization on travis for testing --- tool/grind.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/grind.dart b/tool/grind.dart index 70ff947ae7..82a0dbd431 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -34,7 +34,7 @@ expectFileContains(String path, List items) { } /// Run no more than 4 futures in parallel with this. -final MultiFutureTracker testFutures = new MultiFutureTracker(4); +final MultiFutureTracker testFutures = new MultiFutureTracker(Platform.environment.containsKey('TRAVIS') ? 1 : 6); // Directory.systemTemp is not a constant. So wrap it. Directory createTempSync(String prefix) => From d19951cd84097fb20fd53e3dae0fb07e84976636 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 12:45:39 -0700 Subject: [PATCH 09/17] Use numberOfProcessors --- tool/grind.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/grind.dart b/tool/grind.dart index 82a0dbd431..fb92080711 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -34,7 +34,7 @@ expectFileContains(String path, List items) { } /// Run no more than 4 futures in parallel with this. -final MultiFutureTracker testFutures = new MultiFutureTracker(Platform.environment.containsKey('TRAVIS') ? 1 : 6); +final MultiFutureTracker testFutures = new MultiFutureTracker(Platform.numberOfProcessors); // Directory.systemTemp is not a constant. So wrap it. Directory createTempSync(String prefix) => From f0e3afe523228f990b391730797582b2535b1ca4 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 12:49:31 -0700 Subject: [PATCH 10/17] Travis has two processors and may not be detected properly by dart --- tool/grind.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/grind.dart b/tool/grind.dart index fb92080711..22e2b3db11 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -34,7 +34,7 @@ expectFileContains(String path, List items) { } /// Run no more than 4 futures in parallel with this. -final MultiFutureTracker testFutures = new MultiFutureTracker(Platform.numberOfProcessors); +final MultiFutureTracker testFutures = new MultiFutureTracker(Platform.environment.containsKey('TRAVIS') ? 2 : Platform.numberOfProcessors); // Directory.systemTemp is not a constant. So wrap it. Directory createTempSync(String prefix) => From 12be1cff02cb16380dcc5e3859996056a754d478 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 12:51:01 -0700 Subject: [PATCH 11/17] print processors and adjust to two cores for travis --- tool/grind.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/tool/grind.dart b/tool/grind.dart index 22e2b3db11..5160aa8088 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -728,6 +728,7 @@ publish() async { @Task('Run all the tests.') test() async { + print ('processors: ${Platform.numberOfProcessors}'); await testPreviewDart2(); await testDart1(); await testFutures.wait(); From 13d425edf60eb029cca19f6d96f37dc999f081f6 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 12:56:19 -0700 Subject: [PATCH 12/17] Fix travis parallelization once and for all. --- tool/grind.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tool/grind.dart b/tool/grind.dart index 5160aa8088..413b2ddc06 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -33,8 +33,11 @@ expectFileContains(String path, List items) { } } -/// Run no more than 4 futures in parallel with this. -final MultiFutureTracker testFutures = new MultiFutureTracker(Platform.environment.containsKey('TRAVIS') ? 2 : Platform.numberOfProcessors); +/// Run no more than the number of processors available in parallel. +final MultiFutureTracker testFutures = new MultiFutureTracker( + Platform.environment.containsKey('TRAVIS') + ? 1 + : Platform.numberOfProcessors); // Directory.systemTemp is not a constant. So wrap it. Directory createTempSync(String prefix) => @@ -728,7 +731,6 @@ publish() async { @Task('Run all the tests.') test() async { - print ('processors: ${Platform.numberOfProcessors}'); await testPreviewDart2(); await testDart1(); await testFutures.wait(); From 9e2c42772fbbdb3e7a4be6f6f14f601734e88be2 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Fri, 27 Apr 2018 13:04:41 -0700 Subject: [PATCH 13/17] Fix MultiFutureTracker parallelism restriction --- lib/src/io_utils.dart | 8 +++++--- tool/grind.dart | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/src/io_utils.dart b/lib/src/io_utils.dart index 02e4b4757b..a97da00b83 100644 --- a/lib/src/io_utils.dart +++ b/lib/src/io_utils.dart @@ -66,7 +66,7 @@ final newLinePartOfRegexp = new RegExp('\npart of '); final RegExp quotables = new RegExp(r'[ "\r\n\$]'); -/// Best used with Future. +/// Best used with Future. class MultiFutureTracker { /// Approximate maximum number of simultaneous active Futures. final int parallel; @@ -76,13 +76,15 @@ class MultiFutureTracker { MultiFutureTracker(this.parallel); /// Adds a Future to the queue of outstanding Futures, and returns a Future - /// that completes only when the number of Futures outstanding is <= parallel. + /// that completes only when the number of Futures outstanding is < [parallel] + /// (and so it is OK to start another). + /// /// That can be extremely brief and there's no longer a guarantee after that /// point that another async task has not added a Future to the list. void addFuture(Future future) async { _queue.add(future); future.then((f) => _queue.remove(future)); - await _waitUntil(parallel); + await _waitUntil(parallel - 1); } /// Wait until fewer or equal to this many Futures are outstanding. diff --git a/tool/grind.dart b/tool/grind.dart index 413b2ddc06..aef75b180d 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -36,7 +36,7 @@ expectFileContains(String path, List items) { /// Run no more than the number of processors available in parallel. final MultiFutureTracker testFutures = new MultiFutureTracker( Platform.environment.containsKey('TRAVIS') - ? 1 + ? 2 : Platform.numberOfProcessors); // Directory.systemTemp is not a constant. So wrap it. From ad38288fcc4999349320456616e6902d0bd3960e Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Mon, 30 Apr 2018 09:20:48 -0700 Subject: [PATCH 14/17] Review comments and analyzer exclude for flutter package (ok that it doesn't analyze) --- lib/src/html/html_generator.dart | 8 ++++---- lib/src/model.dart | 4 ++-- testing/test_package_flutter_plugin/analysis_options.yaml | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 testing/test_package_flutter_plugin/analysis_options.yaml diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart index 35f681699f..f7060601b5 100644 --- a/lib/src/html/html_generator.dart +++ b/lib/src/html/html_generator.dart @@ -198,11 +198,11 @@ Future> createGeneratorOptions() async { new DartdocOptionSyntheticOnly>( 'footerTextPaths', (DartdocSyntheticOption> option, Directory dir) { - List footerTextPaths = []; + final List footerTextPaths = []; + final PackageMeta topLevelPackageMeta = + option.root['topLevelPackageMeta'].valueAt(dir); // TODO(jcollins-g): Eliminate special casing for SDK and use config file. - if ((option.root['topLevelPackageMeta'].valueAt(dir) as PackageMeta) - .isSdk == - true) { + if (topLevelPackageMeta.isSdk == true) { footerTextPaths .add(pathLib.canonicalize(_sdkFooterCopyrightUri.toFilePath())); } diff --git a/lib/src/model.dart b/lib/src/model.dart index b3fc2e8ebc..21c7593632 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -80,8 +80,8 @@ int byFeatureOrdering(String a, String b) { return compareAsciiLowerCaseNatural(a, b); } -final RegExp locationSplitter = new RegExp(r"(package:|[\\/;.])"); -final RegExp substituteNameVersion = new RegExp(r"%([bnv])%"); +final RegExp locationSplitter = new RegExp(r'(package:|[\\/;.])'); +final RegExp substituteNameVersion = new RegExp(r'%([bnv])%'); /// Mixin for subclasses of ModelElement representing Elements that can be /// inherited from one class to another. diff --git a/testing/test_package_flutter_plugin/analysis_options.yaml b/testing/test_package_flutter_plugin/analysis_options.yaml new file mode 100644 index 0000000000..4c1615ac5a --- /dev/null +++ b/testing/test_package_flutter_plugin/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + exclude: + - '**' From 7e0fc4aa6a6f4cbcfcbf78fb4533ba4762cfdd24 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Mon, 30 Apr 2018 11:31:17 -0700 Subject: [PATCH 15/17] Redo flutter repo build to fix reentrancy problems --- pubspec.lock | 2 +- tool/grind.dart | 74 ++++++++++++++++++++++--------------------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 20f4ce2376..f646ccc9ee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -408,4 +408,4 @@ packages: source: hosted version: "2.1.13" sdks: - dart: ">=2.0.0-dev.23.0 <=2.0.0-dev.50.0" + dart: ">=2.0.0-dev.23.0 <=2.0.0-dev.51.0" diff --git a/tool/grind.dart b/tool/grind.dart index aef75b180d..1a3f38672a 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -45,55 +45,48 @@ Directory createTempSync(String prefix) => final Memoizer tempdirsCache = new Memoizer(); -RandomAccessFile _updateLock; +/// Global so that the lock is retained for the life of the process. Future _lockFuture; +Future _cleanFlutterRepo; /// Returns true if we need to replace the existing flutter. We never release /// this lock until the program exits to prevent edge cases from spontaneously /// deciding to download a new Flutter SDK in the middle of a run. -Future acquireUpdateLock() async { - if (_updateLock != null) { - await _lockFuture; - return false; +Future get cleanFlutterRepo async { + if (_cleanFlutterRepo != null) { + return await _cleanFlutterRepo; } + // No await is allowed between check of _cleanFlutterRepo and its assignment, + // to prevent reentering this function. cleanFlutterDir.parent.createSync(recursive: true); - // no await allowed between assignment of _updateLock and _lockFuture. - _updateLock = - await new File(pathLib.join(cleanFlutterDir.parent.path, 'lock')) - .open(mode: FileMode.WRITE); + RandomAccessFile _updateLock = + new File(pathLib.join(cleanFlutterDir.parent.path, 'lock')) + .openSync(mode: FileMode.WRITE); _lockFuture = _updateLock.lock(); - await _lockFuture; - File lastSynced = - new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); - if (lastSynced.existsSync()) { - DateTime lastSyncedTime = new DateTime.fromMillisecondsSinceEpoch( - int.parse(await lastSynced.readAsString())); - if (new DateTime.now().difference(lastSyncedTime) < new Duration(hours: 4)) - return false; - } - return true; -} - -Future _cleanFlutterRepo; -/// Get a Future returning a reference to a completely clean, recent checkout of the Flutter -/// repository, fully initialized. -Future get cleanFlutterRepo async { - if (_cleanFlutterRepo == null) { - _cleanFlutterRepo = FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); - if (await acquireUpdateLock()) { - if (cleanFlutterDir.existsSync()) { - await cleanFlutterDir.delete(recursive: true); - } - await cleanFlutterDir.create(recursive: true); - FlutterRepo repo = await _cleanFlutterRepo; - await repo._init(); - File lastSynced = - new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); - await lastSynced.writeAsString( - (new DateTime.now()).millisecondsSinceEpoch.toString()); + Future _doUpdate() async { + await _lockFuture; + File lastSynced = + new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); + FlutterRepo newRepo = + new FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); + if (lastSynced.existsSync()) { + DateTime lastSyncedTime = new DateTime.fromMillisecondsSinceEpoch( + int.parse(lastSynced.readAsStringSync())); + if (new DateTime.now().difference(lastSyncedTime) < + new Duration(hours: 4)) return newRepo; } + if (cleanFlutterDir.existsSync()) { + cleanFlutterDir.deleteSync(recursive: true); + } + cleanFlutterDir.createSync(recursive: true); + await newRepo._init(); + await lastSynced + .writeAsString((new DateTime.now()).millisecondsSinceEpoch.toString()); + return newRepo; } + + _cleanFlutterRepo = _doUpdate(); return _cleanFlutterRepo; } @@ -536,9 +529,8 @@ class FlutterRepo { ); } - static Future fromPath( - String flutterPath, Map env, - [String label]) async { + factory FlutterRepo.fromPath(String flutterPath, Map env, + [String label]) { FlutterRepo flutterRepo = new FlutterRepo._(flutterPath, env, label); return flutterRepo; } From 328e1c5d04c6d4a7b6912d774c9891f66565bfe2 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Mon, 30 Apr 2018 14:34:14 -0700 Subject: [PATCH 16/17] Use a Completer --- tool/grind.dart | 64 ++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/tool/grind.dart b/tool/grind.dart index 1a3f38672a..260968e8af 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -47,47 +47,47 @@ final Memoizer tempdirsCache = new Memoizer(); /// Global so that the lock is retained for the life of the process. Future _lockFuture; -Future _cleanFlutterRepo; +Completer _cleanFlutterRepo; /// Returns true if we need to replace the existing flutter. We never release -/// this lock until the program exits to prevent edge cases from spontaneously -/// deciding to download a new Flutter SDK in the middle of a run. +/// this lock until the program exits to prevent edge case runs from +/// spontaneously deciding to download a new Flutter SDK in the middle of a run. Future get cleanFlutterRepo async { - if (_cleanFlutterRepo != null) { - return await _cleanFlutterRepo; - } - // No await is allowed between check of _cleanFlutterRepo and its assignment, - // to prevent reentering this function. - cleanFlutterDir.parent.createSync(recursive: true); - RandomAccessFile _updateLock = - new File(pathLib.join(cleanFlutterDir.parent.path, 'lock')) - .openSync(mode: FileMode.WRITE); - _lockFuture = _updateLock.lock(); - - Future _doUpdate() async { + if (_cleanFlutterRepo == null) { + // No await is allowed between check of _cleanFlutterRepo and its assignment, + // to prevent reentering this function. + _cleanFlutterRepo = new Completer(); + + // Figure out where the repository is supposed to be and lock updates for + // it. + await cleanFlutterDir.parent.create(recursive: true); + assert(_lockFuture == null); + _lockFuture = new File(pathLib.join(cleanFlutterDir.parent.path, 'lock')) + .openSync(mode: FileMode.WRITE).lock(); await _lockFuture; - File lastSynced = - new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); - FlutterRepo newRepo = - new FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); + File lastSynced = new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); + FlutterRepo newRepo = new FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); + + // We have a repository, but is it up to date? + DateTime lastSyncedTime; if (lastSynced.existsSync()) { - DateTime lastSyncedTime = new DateTime.fromMillisecondsSinceEpoch( + lastSyncedTime = new DateTime.fromMillisecondsSinceEpoch( int.parse(lastSynced.readAsStringSync())); - if (new DateTime.now().difference(lastSyncedTime) < - new Duration(hours: 4)) return newRepo; } - if (cleanFlutterDir.existsSync()) { - cleanFlutterDir.deleteSync(recursive: true); + if (lastSyncedTime == null || + new DateTime.now().difference(lastSyncedTime) > new Duration(hours: 4)) { + // Rebuild the repository. + if (cleanFlutterDir.existsSync()) { + cleanFlutterDir.deleteSync(recursive: true); + } + cleanFlutterDir.createSync(recursive: true); + await newRepo._init(); + await lastSynced + .writeAsString((new DateTime.now()).millisecondsSinceEpoch.toString()); } - cleanFlutterDir.createSync(recursive: true); - await newRepo._init(); - await lastSynced - .writeAsString((new DateTime.now()).millisecondsSinceEpoch.toString()); - return newRepo; + _cleanFlutterRepo.complete(newRepo); } - - _cleanFlutterRepo = _doUpdate(); - return _cleanFlutterRepo; + return _cleanFlutterRepo.future; } Directory get dartdocDocsDir => From 9d8c6b2c160caffc0de32a941c008e187b2aeec0 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Mon, 30 Apr 2018 14:35:41 -0700 Subject: [PATCH 17/17] dartfmt --- tool/grind.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tool/grind.dart b/tool/grind.dart index 260968e8af..195c58c015 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -63,10 +63,13 @@ Future get cleanFlutterRepo async { await cleanFlutterDir.parent.create(recursive: true); assert(_lockFuture == null); _lockFuture = new File(pathLib.join(cleanFlutterDir.parent.path, 'lock')) - .openSync(mode: FileMode.WRITE).lock(); + .openSync(mode: FileMode.WRITE) + .lock(); await _lockFuture; - File lastSynced = new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); - FlutterRepo newRepo = new FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); + File lastSynced = + new File(pathLib.join(cleanFlutterDir.parent.path, 'lastSynced')); + FlutterRepo newRepo = + new FlutterRepo.fromPath(cleanFlutterDir.path, {}, 'clean'); // We have a repository, but is it up to date? DateTime lastSyncedTime; @@ -75,15 +78,16 @@ Future get cleanFlutterRepo async { int.parse(lastSynced.readAsStringSync())); } if (lastSyncedTime == null || - new DateTime.now().difference(lastSyncedTime) > new Duration(hours: 4)) { + new DateTime.now().difference(lastSyncedTime) > + new Duration(hours: 4)) { // Rebuild the repository. if (cleanFlutterDir.existsSync()) { cleanFlutterDir.deleteSync(recursive: true); } cleanFlutterDir.createSync(recursive: true); await newRepo._init(); - await lastSynced - .writeAsString((new DateTime.now()).millisecondsSinceEpoch.toString()); + await lastSynced.writeAsString( + (new DateTime.now()).millisecondsSinceEpoch.toString()); } _cleanFlutterRepo.complete(newRepo); }