blob: cce155127d3208013a9ffcba16f40a0e251c631c [file]
# Copyright 2016 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Extractor for Skylark rule documentation."""
import ast
from src.main.protobuf import build_pb2
from skydoc import common
from skydoc.stubs import attr
from skydoc.stubs import skylark_globals
SKYLARK_STUBS = {
"attr": attr,
"aspect": skylark_globals.aspect,
"DATA_CFG": skylark_globals.DATA_CFG,
"HOST_CFG": skylark_globals.HOST_CFG,
"PACKAGE_NAME": skylark_globals.PACKAGE_NAME,
"REPOSITORY_NAME": skylark_globals.REPOSITORY_NAME,
"provider": skylark_globals.provider,
"FileType": skylark_globals.FileType,
"Label": skylark_globals.Label,
"select": skylark_globals.select,
"struct": skylark_globals.struct,
"repository_rule": skylark_globals.repository_rule,
"rule": skylark_globals.rule,
}
"""Stubs for Skylark globals to be used to evaluate the .bzl file."""
class RuleDocExtractor(object):
"""Extracts documentation for rules from a .bzl file."""
def __init__(self):
"""Inits RuleDocExtractor with a new BuildLanguage proto"""
self.__language = build_pb2.BuildLanguage()
self.__extracted_rules = {}
def _process_skylark(self, bzl_file):
"""Evaluates the Skylark code in the .bzl file.
This function evaluates the Skylark code in the .bzl file as Python against
Skylark stubs to extract the rules and attributes defined in the file. The
extracted rules are kept in the __extracted_rules map keyed by rule name.
Args:
bzl_file: The .bzl file to evaluate.
"""
skylark_locals = {}
compiled = compile(open(bzl_file).read(), bzl_file, "exec")
exec(compiled) in SKYLARK_STUBS, skylark_locals
for name, obj in skylark_locals.iteritems():
if hasattr(obj, "is_rule") and not name.startswith("_"):
obj.attrs["name"] = attr.AttrDescriptor(
type=build_pb2.Attribute.UNKNOWN, mandatory=True, name="name")
self.__extracted_rules[name] = obj
def _add_rule_doc(self, name, doc):
"""Parses the attribute documentation from the docstring.
Parses the attribute documentation in the given docstring and associates the
rule and attribute documentation with the corresponding rule extracted from
the .bzl file.
Args:
name: The name of the rule.
doc: The docstring extracted for the rule.
"""
doc, attr_doc = common.parse_attribute_doc(doc)
if name in self.__extracted_rules:
rule = self.__extracted_rules[name]
rule.doc = doc.strip()
for attr_name, attr_doc in attr_doc.iteritems():
if attr_name in rule.attrs:
rule.attrs[attr_name].doc = attr_doc
def _parse_docstrings(self, bzl_file):
"""Extracts the docstrings for all public rules in the .bzl file.
This function parses the .bzl file and extracts the docstrings for all
public rules in the file that were extracted in _process_skylark. It calls
_add_rule_doc for to parse the attribute documentation in each docstring
and associate them with the extracted rules and attributes.
Args:
bzl_file: The .bzl file to extract docstrings from.
"""
try:
tree = ast.parse(open(bzl_file).read(), bzl_file)
key = None
for node in ast.iter_child_nodes(tree):
if isinstance(node, ast.Assign):
name = node.targets[0].id
if not name.startswith("_"):
key = name
continue
elif isinstance(node, ast.Expr) and key:
self._add_rule_doc(key, node.value.s.strip())
key = None
except IOError:
print("Failed to parse {0}: {1}".format(bzl_file, e.strerror))
pass
def _assemble_protos(self):
"""Builds the BuildLanguage protos for the extracted rule documentation.
Iterates through the map of extracted rule documentation and builds a
BuildLanguage proto containing the documentation for publid rules extracted
from the .bzl file.
"""
rules = []
for rule_name, rule_desc in self.__extracted_rules.iteritems():
rule_desc.name = rule_name
rules.append(rule_desc)
rules = sorted(rules, key=lambda rule_desc: rule_desc.name)
for rule_desc in rules:
rule = self.__language.rule.add()
rule.name = rule_desc.name
rule.documentation = rule_desc.doc
attrs = sorted(rule_desc.attrs.values(), cmp=attr.attr_compare)
for attr_desc in attrs:
if attr_desc.name.startswith("_"):
continue
attr_proto = rule.attribute.add()
attr_proto.name = attr_desc.name
attr_proto.documentation = attr_desc.doc
attr_proto.type = attr_desc.type
attr_proto.mandatory = attr_desc.mandatory
# TODO(dzc): Save the default value of the attribute. This will require
# adding a proto field to the AttributeDefinition proto, perhaps as a
# oneof.
def parse_bzl(self, bzl_file):
"""Extracts the documentation for all public rules from the given .bzl file.
The Skylark code is first evaluated against stubs to extract rule and
attributes with complete type information. Then, the .bzl file is parsed
to extract the docstrings for each of the rules. Finally, the BuildLanguage
proto is assembled with the extracted rule documentation.
Args:
bzl_file: The .bzl file to extract rule documentation from.
"""
self._process_skylark(bzl_file)
self._parse_docstrings(bzl_file)
self._assemble_protos()
def proto(self):
"""Returns the proto containing the macro documentation."""
return self.__language