The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#) https://grpc.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

242 lines
8.2 KiB

#!/usr/bin/env python3
7 years ago
# Copyright 2017 gRPC authors.
#
7 years ago
# 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
#
7 years ago
# http://www.apache.org/licenses/LICENSE-2.0
#
7 years ago
# 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.
import argparse
import collections
import operator
import os
import re
import subprocess
#
# Find the root of the git tree
#
git_root = (subprocess.check_output(['git', 'rev-parse', '--show-toplevel'
]).decode('utf-8').strip())
#
# Parse command line arguments
#
default_out = os.path.join(git_root, '.github', 'CODEOWNERS')
argp = argparse.ArgumentParser('Generate .github/CODEOWNERS file')
argp.add_argument('--out',
'-o',
type=str,
default=default_out,
help='Output file (default %s)' % default_out)
args = argp.parse_args()
#
# Walk git tree to locate all OWNERS files
#
7 years ago
owners_files = [
os.path.join(root, 'OWNERS')
for root, dirs, files in os.walk(git_root)
7 years ago
if 'OWNERS' in files
]
#
# Parse owners files
#
Owners = collections.namedtuple('Owners', 'parent directives dir')
Directive = collections.namedtuple('Directive', 'who globs')
7 years ago
def parse_owners(filename):
7 years ago
with open(filename) as f:
src = f.read().splitlines()
parent = True
directives = []
for line in src:
line = line.strip()
# line := directive | comment
if not line:
continue
if line[0] == '#':
continue
7 years ago
# it's a directive
directive = None
if line == 'set noparent':
parent = False
elif line == '*':
directive = Directive(who='*', globs=[])
elif ' ' in line:
(who, globs) = line.split(' ', 1)
globs_list = [glob for glob in globs.split(' ') if glob]
directive = Directive(who=who, globs=globs_list)
else:
directive = Directive(who=line, globs=[])
if directive:
directives.append(directive)
return Owners(parent=parent,
directives=directives,
dir=os.path.relpath(os.path.dirname(filename), git_root))
7 years ago
owners_data = sorted([parse_owners(filename) for filename in owners_files],
key=operator.attrgetter('dir'))
#
# Modify owners so that parented OWNERS files point to the actual
# Owners tuple with their parent field
#
new_owners_data = []
for owners in owners_data:
7 years ago
if owners.parent == True:
best_parent = None
best_parent_score = None
for possible_parent in owners_data:
if possible_parent is owners:
continue
7 years ago
rel = os.path.relpath(owners.dir, possible_parent.dir)
# '..' ==> we had to walk up from possible_parent to get to owners
# ==> not a parent
if '..' in rel:
continue
7 years ago
depth = len(rel.split(os.sep))
if not best_parent or depth < best_parent_score:
best_parent = possible_parent
best_parent_score = depth
if best_parent:
owners = owners._replace(parent=best_parent.dir)
else:
owners = owners._replace(parent=None)
new_owners_data.append(owners)
owners_data = new_owners_data
#
# In bottom to top order, process owners data structures to build up
# a CODEOWNERS file for GitHub
#
7 years ago
def full_dir(rules_dir, sub_path):
7 years ago
return os.path.join(rules_dir, sub_path) if rules_dir != '.' else sub_path
# glob using git
gg_cache = {}
7 years ago
def git_glob(glob):
7 years ago
global gg_cache
if glob in gg_cache:
return gg_cache[glob]
7 years ago
r = set(
subprocess.check_output([
'git', 'ls-files', os.path.join(git_root, glob)
]).decode('utf-8').strip().splitlines())
7 years ago
gg_cache[glob] = r
return r
def expand_directives(root, directives):
7 years ago
globs = collections.OrderedDict()
# build a table of glob --> owners
for directive in directives:
for glob in directive.globs or ['**']:
if glob not in globs:
globs[glob] = []
if directive.who not in globs[glob]:
globs[glob].append(directive.who)
# expand owners for intersecting globs
sorted_globs = sorted(globs.keys(),
key=lambda g: len(git_glob(full_dir(root, g))),
reverse=True)
7 years ago
out_globs = collections.OrderedDict()
for glob_add in sorted_globs:
who_add = globs[glob_add]
pre_items = [i for i in out_globs.items()]
7 years ago
out_globs[glob_add] = who_add.copy()
for glob_have, who_have in pre_items:
files_add = git_glob(full_dir(root, glob_add))
files_have = git_glob(full_dir(root, glob_have))
intersect = files_have.intersection(files_add)
if intersect:
for f in sorted(files_add): # sorted to ensure merge stability
if f not in intersect:
out_globs[os.path.relpath(f, start=root)] = who_add
for who in who_have:
if who not in out_globs[glob_add]:
out_globs[glob_add].append(who)
return out_globs
def add_parent_to_globs(parent, globs, globs_dir):
if not parent:
return
7 years ago
for owners in owners_data:
if owners.dir == parent:
owners_globs = expand_directives(owners.dir, owners.directives)
for oglob, oglob_who in owners_globs.items():
for gglob, gglob_who in globs.items():
7 years ago
files_parent = git_glob(full_dir(owners.dir, oglob))
files_child = git_glob(full_dir(globs_dir, gglob))
intersect = files_parent.intersection(files_child)
gglob_who_orig = gglob_who.copy()
if intersect:
for f in sorted(files_child
): # sorted to ensure merge stability
if f not in intersect:
who = gglob_who_orig.copy()
globs[os.path.relpath(f, start=globs_dir)] = who
for who in oglob_who:
if who not in gglob_who:
gglob_who.append(who)
add_parent_to_globs(owners.parent, globs, globs_dir)
return
assert (False)
todo = owners_data.copy()
done = set()
with open(args.out, 'w') as out:
7 years ago
out.write('# Auto-generated by the tools/mkowners/mkowners.py tool\n')
out.write('# Uses OWNERS files in different modules throughout the\n')
out.write('# repository as the source of truth for module ownership.\n')
written_globs = []
while todo:
head, *todo = todo
if head.parent and not head.parent in done:
todo.append(head)
continue
globs = expand_directives(head.dir, head.directives)
add_parent_to_globs(head.parent, globs, head.dir)
for glob, owners in globs.items():
7 years ago
skip = False
for glob1, owners1, dir1 in reversed(written_globs):
files = git_glob(full_dir(head.dir, glob))
files1 = git_glob(full_dir(dir1, glob1))
intersect = files.intersection(files1)
if files == intersect:
if sorted(owners) == sorted(owners1):
skip = True # nothing new in this rule
break
elif intersect:
# continuing would cause a semantic change since some files are
# affected differently by this rule and CODEOWNERS is order dependent
break
if not skip:
out.write('/%s %s\n' %
(full_dir(head.dir, glob), ' '.join(owners)))
7 years ago
written_globs.append((glob, owners, head.dir))
done.add(head.dir)