blob: 357b9a6ba227009803b57a54943f9f586f7a76a0 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Linter for various README.md files."""
from __future__ import print_function
import difflib
import os
import re
import sys
TOP_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
# Find chromite!
sys.path.insert(0, os.path.join(TOP_DIR, '..', '..'))
from chromite.lib import commandline
from chromite.lib import git
from chromite.lib import cros_logging as logging
from chromite.lib import osutils
def GetActiveProjects():
"""Return the list of active projects."""
# Look at all the paths (files & dirs) in the top of the git repo. This way
# we ignore local directories devs created that aren't actually committed.
cmd = ['ls-tree', '--name-only', '-z', 'HEAD']
result = git.RunGit(TOP_DIR, cmd)
# Split the output on NULs to avoid whitespace/etc... issues.
paths = result.stdout.split('\0')
# ls-tree -z will include a trailing NUL on all entries, not just seperation,
# so filter it out if found (in case ls-tree behavior changes on us).
for path in [x for x in paths if x]:
if os.path.isdir(os.path.join(TOP_DIR, path)):
yield path
def CheckTopLevel():
"""Check the top level README.md list."""
ret = 0
path = os.path.join(TOP_DIR, 'README.md')
data = osutils.ReadFile(path).splitlines()
# Look for the directory header.
try:
i = data.index('# Local Project Directory')
except ValueError:
logging.error('README.md index out of sync')
return 1
data = data[i + 1:]
# Pull out all the linked projects.
listed_projs = []
listed_dirs = []
for line in data:
# Break once we hit the end of the table.
if line.startswith('#'):
break
m = re.match(r'^[|] \[([^]]+)*\]\(\./([^)]*)/\)', line)
if m:
listed_projs.append(m.group(1))
listed_dirs.append(m.group(2))
logging.debug('README.md projects: %s', listed_projs)
sorted_projs = sorted(listed_projs)
if listed_projs != sorted_projs:
ret = 1
lines = list(difflib.unified_diff(listed_projs, sorted_projs, lineterm=''))
logging.error('README.md project listing should be kept sorted:\n%s',
'\n'.join(lines[2:]))
# Check the list in README.md for outdated entries.
old_dirs = []
for project_dir in listed_dirs:
path = os.path.join(TOP_DIR, project_dir)
if not os.path.isdir(path):
old_dirs.append(path)
if old_dirs:
ret = 1
logging.error('README.md: found stale project entries: %s', old_dirs)
# Check the local source repo for missing entries.
existing_projs = sorted(GetActiveProjects())
logging.debug('Found active projects: %s', existing_projs)
new_projs = []
for proj in existing_projs:
path = os.path.join(TOP_DIR, proj)
if os.path.isdir(path) and proj not in listed_projs:
new_projs.append(proj)
if new_projs:
ret = 1
logging.error('README.md: document new projects: %s', new_projs)
return ret
def CheckSubdirs():
"""Check the subdir README.md files exist."""
# Legacy projects that don't have a README.md file.
# Someone should write some docs :D.
LEGACYLIST = (
'attestation',
'avtest_label_detect',
'buffet',
'cros-disks',
'fitpicker',
'image-burner',
'init',
'libchromeos-ui',
'libcontainer',
'lorgnette',
'modem-utilities',
'mtpd',
'salsa',
'thd',
'timberslide',
'tpm_manager',
'trim',
'userfeedback',
'userspace_touchpad',
'vpn-manager',
)
ret = 0
for proj in GetActiveProjects():
readme = os.path.join(TOP_DIR, proj, 'README.md')
if os.path.exists(readme):
if proj in LEGACYLIST:
logging.error(
'*** Project "%s" is in no-README LEGACYLIST, but actually has '
'one. Please remove it from LEGACYLIST!', proj)
else:
if not proj in LEGACYLIST:
logging.error('*** Project "%s" needs a README.md file', proj)
ret = 1
return ret
def GetParser():
"""Return an argument parser."""
parser = commandline.ArgumentParser(description=__doc__)
parser.add_argument('--extensions', default='gyp,gypi',
help='Comma delimited file extensions to check. '
'(default: %(default)s)')
parser.add_argument('files', nargs='*',
help='Files to run lint.')
return parser
def main(argv):
parser = GetParser()
opts = parser.parse_args(argv)
opts.Freeze()
return CheckTopLevel() | CheckSubdirs()
if __name__ == '__main__':
commandline.ScriptWrapperMain(lambda _: main)