dartdoc.dart 8.38 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

9
import 'package:intl/intl.dart';
10
import 'package:path/path.dart' as path;
11
import 'update_versions.dart';
12

Seth Ladd's avatar
Seth Ladd committed
13 14
const String kDocRoot = 'dev/docs/doc';

15 16 17
/// This script expects to run with the cwd as the root of the flutter repo. It
/// will generate documentation for the packages in `//packages/` and write the
/// documentation to `//dev/docs/doc/api/`.
18
///
Seth Ladd's avatar
Seth Ladd committed
19 20 21 22
/// This script also updates the index.html file so that it can be placed
/// at the root of docs.flutter.io. We are keeping the files inside of
/// docs.flutter.io/flutter for now, so we need to manipulate paths
/// a bit. See https://github.com/flutter/flutter/issues/3900 for more info.
23 24 25 26 27
///
/// This will only work on UNIX systems, not Windows. It requires that 'git' be
/// in your path. It requires that 'flutter' has been run previously. It uses
/// the version of Dart downloaded by the 'flutter' tool in this repository and
/// will crash if that is absent.
28
Future<Null> main(List<String> args) async {
29 30 31 32
  // If we're run from the `tools` dir, set the cwd to the repo root.
  if (path.basename(Directory.current.path) == 'tools')
    Directory.current = Directory.current.parent.parent;

33 34
  final RawVersion version = new RawVersion('VERSION');

35
  // Create the pubspec.yaml file.
36
  final StringBuffer buf = new StringBuffer('''
37
name: Flutter
38
homepage: https://flutter.io
39
version: $version
40 41
dependencies:
''');
Seth Ladd's avatar
Seth Ladd committed
42
  for (String package in findPackageNames()) {
43
    buf.writeln('  $package:');
44
    buf.writeln('    sdk: flutter');
45
  }
46 47 48 49
  buf.writeln('  platform_integration: 0.0.1');
  buf.writeln('dependency_overrides:');
  buf.writeln('  platform_integration:');
  buf.writeln('    path: platform_integration');
50 51 52
  new File('dev/docs/pubspec.yaml').writeAsStringSync(buf.toString());

  // Create the library file.
53
  final Directory libDir = new Directory('dev/docs/lib');
54 55
  libDir.createSync();

56
  final StringBuffer contents = new StringBuffer('library temp_doc;\n\n');
Seth Ladd's avatar
Seth Ladd committed
57
  for (String libraryRef in libraryRefs()) {
58 59 60 61 62
    contents.writeln('import \'package:$libraryRef\';');
  }
  new File('dev/docs/lib/temp_doc.dart').writeAsStringSync(contents.toString());

  // Run pub.
63 64 65
  Process process = await Process.start(
    '../../bin/cache/dart-sdk/bin/pub',
    <String>['get'],
66 67
    workingDirectory: 'dev/docs',
    environment: <String, String>{
68 69
      'FLUTTER_ROOT': Directory.current.path,
    },
70
  );
Seth Ladd's avatar
Seth Ladd committed
71 72
  printStream(process.stdout);
  printStream(process.stderr);
73
  final int code = await process.exitCode;
74 75 76
  if (code != 0)
    exit(code);

77 78
  createFooter('dev/docs/lib/footer.html');

79
  // Verify which version of dartdoc we're using.
80 81 82 83 84
  final ProcessResult result = Process.runSync(
    '../../bin/cache/dart-sdk/bin/pub',
    <String>['global', 'run', 'dartdoc', '--version'],
    workingDirectory: 'dev/docs',
  );
85 86
  print('\n${result.stdout}');

87
  // Generate the documentation.
88
  final List<String> args = <String>[
89 90 91
    'global', 'run', 'dartdoc',
    '--header', 'styles.html',
    '--header', 'analytics.html',
92
    '--footer-text', 'lib/footer.html',
93 94
    '--exclude', 'temp_doc',
    '--favicon=favicon.ico',
95
    '--use-categories',
96
    '--category-order', 'flutter,Dart Core,flutter_test,flutter_driver',
97 98
  ];

99
  for (String libraryRef in libraryRefs(diskPath: true)) {
100
    args.add('--include-external');
101
    args.add(libraryRef);
102 103
  }

104 105 106 107 108
  process = await Process.start(
    '../../bin/cache/dart-sdk/bin/pub',
    args,
    workingDirectory: 'dev/docs',
  );
Seth Ladd's avatar
Seth Ladd committed
109 110
  printStream(process.stdout);
  printStream(process.stderr);
111
  final int exitCode = await process.exitCode;
Seth Ladd's avatar
Seth Ladd committed
112 113 114 115

  if (exitCode != 0)
    exit(exitCode);

116 117
  sanityCheckDocs();

Seth Ladd's avatar
Seth Ladd committed
118 119 120
  createIndexAndCleanup();
}

121
void createFooter(String footerPath) {
122 123
  final ProcessResult gitResult = Process.runSync('git', <String>['rev-parse', 'HEAD']);
  final String gitHead = (gitResult.exitCode == 0) ? gitResult.stdout.trim() : 'unknown';
124

125
  final String timestamp = new DateFormat('yyyy-MM-dd HH:mm').format(new DateTime.now());
126 127

  new File(footerPath).writeAsStringSync(
128 129
    '• </span class="no-break">$timestamp<span> '
    '• </span class="no-break">$gitHead</span>'
130 131 132
  );
}

133
void sanityCheckDocs() {
134 135
  // TODO(jcollins-g): remove old_sdk_canaries for dartdoc >= 0.10.0
  final List<String> oldSdkCanaries = <String>[
136
    '$kDocRoot/api/dart.io/File-class.html',
Adam Barth's avatar
Adam Barth committed
137 138
    '$kDocRoot/api/dart.ui/Canvas-class.html',
    '$kDocRoot/api/dart.ui/Canvas/drawRect.html',
139 140 141 142 143 144 145
  ];
  final List<String> newSdkCanaries = <String>[
    '$kDocRoot/api/dart-io/File-class.html',
    '$kDocRoot/api/dart-ui/Canvas-class.html',
    '$kDocRoot/api/dart-ui/Canvas/drawRect.html',
  ];
  final List<String> canaries = <String>[
146
    '$kDocRoot/api/flutter_test/WidgetTester/pumpWidget.html',
147 148 149 150
    '$kDocRoot/api/material/Material-class.html',
    '$kDocRoot/api/material/Tooltip-class.html',
    '$kDocRoot/api/widgets/Widget-class.html',
  ];
151 152 153 154 155 156 157 158 159
  bool oldMissing = false;
  for (String canary in oldSdkCanaries) {
    if (!new File(canary).existsSync()) {
      oldMissing = true;
      break;
    }
  }
  if (oldMissing)
    canaries.addAll(newSdkCanaries);
160 161 162 163 164 165
  for (String canary in canaries) {
    if (!new File(canary).existsSync())
      throw new Exception('Missing "$canary", which probably means the documentation failed to build correctly.');
  }
}

Seth Ladd's avatar
Seth Ladd committed
166 167 168 169
/// Creates a custom index.html because we try to maintain old
/// paths. Cleanup unused index.html files no longer needed.
void createIndexAndCleanup() {
  print('\nCreating a custom index.html in $kDocRoot/index.html');
170
  removeOldFlutterDocsDir();
Seth Ladd's avatar
Seth Ladd committed
171 172 173 174 175 176 177
  renameApiDir();
  copyIndexToRootOfDocs();
  addHtmlBaseToIndex();
  putRedirectInOldIndexLocation();
  print('\nDocs ready to go!');
}

178 179 180
void removeOldFlutterDocsDir() {
  try {
    new Directory('$kDocRoot/flutter').deleteSync(recursive: true);
181
  } catch (e) {
182 183 184 185
    // If the directory does not exist, that's OK.
  }
}

Seth Ladd's avatar
Seth Ladd committed
186 187 188 189
void renameApiDir() {
  new Directory('$kDocRoot/api').renameSync('$kDocRoot/flutter');
}

190 191
void copyIndexToRootOfDocs() {
  new File('$kDocRoot/flutter/index.html').copySync('$kDocRoot/index.html');
Seth Ladd's avatar
Seth Ladd committed
192 193 194
}

void addHtmlBaseToIndex() {
195
  final File indexFile = new File('$kDocRoot/index.html');
Seth Ladd's avatar
Seth Ladd committed
196 197 198
  String indexContents = indexFile.readAsStringSync();
  indexContents = indexContents.replaceFirst('</title>\n',
    '</title>\n  <base href="./flutter/">\n');
199 200 201 202
  indexContents = indexContents.replaceAll(
    'href="Android/Android-library.html"',
    'href="https://docs.flutter.io/javadoc/"'
  );
203 204 205 206 207
  indexContents = indexContents.replaceAll(
      'href="iOS/iOS-library.html"',
      'href="https://docs.flutter.io/objc/"'
  );

Seth Ladd's avatar
Seth Ladd committed
208 209 210 211
  indexFile.writeAsStringSync(indexContents);
}

void putRedirectInOldIndexLocation() {
212
  final String metaTag = '<meta http-equiv="refresh" content="0;URL=../index.html">';
Seth Ladd's avatar
Seth Ladd committed
213
  new File('$kDocRoot/flutter/index.html').writeAsStringSync(metaTag);
214 215
}

Seth Ladd's avatar
Seth Ladd committed
216 217
List<String> findPackageNames() {
  return findPackages().map((Directory dir) => path.basename(dir.path)).toList();
218 219
}

220
/// Finds all packages in the Flutter SDK
Seth Ladd's avatar
Seth Ladd committed
221
List<Directory> findPackages() {
222 223
  return new Directory('packages')
    .listSync()
224 225 226
    .where((FileSystemEntity entity) {
      if (entity is! Directory)
        return false;
227
      final File pubspec = new File('${entity.path}/pubspec.yaml');
228 229
      // TODO(ianh): Use a real YAML parser here
      return !pubspec.readAsStringSync().contains('nodoc: true');
230 231 232 233
    })
    .toList();
}

234 235 236 237
/// Returns import or on-disk paths for all libraries in the Flutter SDK.
///
/// diskPath toggles between import paths vs. disk paths.
Iterable<String> libraryRefs({ bool diskPath: false }) sync* {
Seth Ladd's avatar
Seth Ladd committed
238
  for (Directory dir in findPackages()) {
239
    final String dirName = path.basename(dir.path);
240
    for (FileSystemEntity file in new Directory('${dir.path}/lib').listSync()) {
241 242 243 244 245 246
      if (file is File && file.path.endsWith('.dart')) {
        if (diskPath)
          yield '$dirName/lib/${path.basename(file.path)}';
        else
          yield '$dirName/${path.basename(file.path)}';
       }
247 248
    }
  }
249 250

  // Add a fake package for platform integration APIs.
251
  if (diskPath) {
252
    yield 'platform_integration/lib/android.dart';
253 254
    yield 'platform_integration/lib/ios.dart';
  } else {
255
    yield 'platform_integration/android.dart';
256 257
    yield 'platform_integration/ios.dart';
  }
258 259
}

Seth Ladd's avatar
Seth Ladd committed
260
void printStream(Stream<List<int>> stream) {
261 262 263 264 265
  stream
    .transform(UTF8.decoder)
    .transform(const LineSplitter())
    .listen(print);
}