blob: 658353a5a0d2f4c53b14477b7ca5ed791c8ccd98 [file] [log] [blame]
#!/usr/bin/env python3.8
# Copyright 2021 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generate Dart reference docs for one or more Fuchsia packages.
This script uses Dartdoc, which documents a single package. If called with more
than one package, an intermediary package is generated in order to document all
the given packages at once; in that case, the --gen-dir argument is required. If
this behavior is not desired, do not pass more than one package argument.
"""
import argparse
import json
import os
import shutil
import subprocess
import sys
import yaml
_PUBSPEC_CONTENT = """name: Fuchsia
homepage: https://fuchsia.dev/reference/dart
description: API documentation for fuchsia
environment:
sdk: '>=2.10.0 <3.0.0'
dependencies:
"""
def is_dart_package_dir(package_dir):
"""Returns whether or not a directory resembles a Dart package dir."""
if not os.path.isdir(package_dir):
print('%s is not a directory' % (package_dir))
return False
if not os.path.isdir(os.path.join(package_dir, 'lib')):
print('%s is missing a lib subdirectory' % (package_dir))
return False
pubspec_file = os.path.join(package_dir, 'pubspec.yaml')
if not os.path.exists(pubspec_file):
print('%s is missing a pubspec.yaml' % (package_dir))
return False
with open(pubspec_file) as f:
pubspec = yaml.load(f, Loader=yaml.Loader)
if not pubspec or pubspec['name'] != os.path.basename(package_dir):
print('%s has an invalid pubspec.yaml' % (package_dir))
return False
return True
def collect_top_level_files(package_dir):
"""Return a list of dart filenames under the package's lib directory."""
return sorted(
os.path.basename(p)
for p in os.listdir(os.path.join(package_dir, 'lib'))
if os.path.basename(p).endswith('.dart'))
def compose_pubspec_content(package_dict):
"""Compose suitable contents for a pubspec file.
The pubspec will have dependencies on the packages in package_dict.
Args:
package_dict: Dictionary mapping package name to path.
Returns:
String with the pubspec content.
"""
pubspec_content = _PUBSPEC_CONTENT
for pkg in package_dict:
pubspec_content += ' %s:\n path: %s/\n' % (pkg, package_dict[pkg])
return pubspec_content
def compose_imports_content(imports_dict):
"""Compose suitable contents for an imports file.
The contents will include import statements for all items in imports_dict.
Args:
imports_dict: Dictionary mapping package name to a list of file names
belonging to that package.
Returns:
String with the imports content.
"""
lines = []
for pkg in imports_dict:
for i in imports_dict[pkg]:
lines.append("import 'package:%s/%s';\n" % (pkg, i))
lines.sort()
return 'library Fuchsia;\n' + ''.join(lines)
def fabricate_package(gen_dir, pubspec_content, imports_content):
"""Write given pubspec and imports content in the given directory.
Args:
packages: A list of package directories to use as dependencies.
gen_dir: The directory for generated files.
"""
if not os.path.exists(os.path.join(gen_dir, 'lib')):
os.makedirs(os.path.join(gen_dir, 'lib'))
# Fabricate pubspec.yaml.
pubspec_file = os.path.join(gen_dir, 'pubspec.yaml')
if os.path.exists(pubspec_file):
os.remove(pubspec_file)
with open(pubspec_file, 'w') as f:
f.write(pubspec_content)
# Fabricate a dart file with all the imports collected.
lib_file = os.path.join(gen_dir, 'lib', 'lib.dart')
if os.path.exists(lib_file):
os.remove(lib_file)
with open(lib_file, 'w') as f:
f.write(imports_content)
def generate_docs(package_dir, out_dir, dart_prebuilt_dir):
"""Generate dart reference docs.
Args:
package_dir: The directory of the package to document.
out_dir: The output directory for documentation.
dart_prebuilt_dir: The directory with dart executables (dartdoc, pub).
Returns:
0 if documentation was generated successfully, non-zero otherwise.
"""
# Run pub over this package to fetch deps.
process = subprocess.run(
[os.path.join(dart_prebuilt_dir, 'pub'), 'get'],
cwd=package_dir,
capture_output=True,
universal_newlines=True)
if process.returncode:
print(process.stderr)
return 1
# Clear the outdir first.
if os.path.exists(out_dir):
shutil.rmtree(out_dir)
# Run dartdoc.
excluded_packages = ['Dart', 'logging']
process = subprocess.run(
[
os.path.join(dart_prebuilt_dir, 'dartdoc'),
'--auto-include-dependencies',
'--exclude-packages',
','.join(excluded_packages),
'--output',
out_dir,
'--format',
'md',
],
cwd=package_dir,
capture_output=True,
universal_newlines=True)
if process.returncode:
print(process.stderr)
return 1
return 0
def main():
parser = argparse.ArgumentParser(
description=__doc__, # Prepend help doc with this file's docstring.
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'-g',
'--gen-dir',
type=str,
required=False,
help='Location where intermediate files can be generated (should be '
'different from the output directory). This is required if more '
'than one package argument is given.')
parser.add_argument(
'-o',
'--out-dir',
type=str,
required=True,
help='Output location where generated docs should go')
parser.add_argument(
'-p',
'--prebuilts-dir',
type=str,
required=False,
default='',
help="Location of dart prebuilts, usually a Dart SDK's bin directory")
parser.add_argument(
'packages', type=str, nargs='+', help='Paths of packages to document')
args = parser.parse_args()
if len(args.packages) == 1:
package_dir = args.packages[0]
else:
# Dartdoc runs over a single package only. Fabricate a package that
# depends on all the other packages and document that one.
if not args.gen_dir:
print('ERROR: --gen-dir is required to document multiple packages.')
parser.print_help()
return 1
package_dir = args.gen_dir
packages_dict = {}
imports_dict = {}
for pkg in args.packages:
if not is_dart_package_dir(pkg):
return 1
package_basename = os.path.basename(pkg)
packages_dict[package_basename] = os.path.abspath(pkg)
imports_dict[package_basename] = collect_top_level_files(pkg)
pubspec_content = compose_pubspec_content(packages_dict)
imports_content = compose_imports_content(imports_dict)
fabricate_package(package_dir, pubspec_content, imports_content)
return generate_docs(package_dir, args.out_dir, args.prebuilts_dir)
if __name__ == '__main__':
sys.exit(main())