blob: c0b983d75bc89cb4033afb594d5e5d30c89c794e [file] [log] [blame]
#!/usr/bin/env python2.7
# Copyright 2017 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.
from collections import namedtuple
import argparse
import os
import errno
import fnmatch
import shlex
import shutil
import sys
manifest_entry = namedtuple(
'manifest_entry', [
'group',
'target',
'source',
'manifest',
])
def format_manifest_entry(entry):
return (
('' if entry.group is None else '{' + entry.group + '}') +
entry.target + '=' + entry.source)
def format_manifest_file(manifest):
return ''.join(format_manifest_entry(entry) + '\n' for entry in manifest)
def read_manifest_lines(sep, lines, title, manifest_cwd, result_cwd):
for line in lines:
# Remove the trailing newline.
assert line.endswith('\n'), 'Unterminated manifest line: %r' % line
line = line[:-1]
# Grok {group}... syntax.
group = None
if line.startswith('{'):
end = line.find('}')
assert end > 0, 'Unterminated { in manifest line: %r' % line
group = line[1:end]
line = line[end + 1:]
# Grok target=source syntax.
[target_file, build_file] = line.split(sep, 1)
if manifest_cwd != result_cwd:
# Expand the path based on the cwd presumed in the manifest.
build_file = os.path.normpath(
os.path.join(manifest_cwd, build_file))
# Make it relative to the cwd we want to work from.
build_file = os.path.relpath(build_file, result_cwd)
yield manifest_entry(group, target_file, build_file, title)
def partition_manifest(manifest, select, selected_group, unselected_group):
selected = []
unselected = []
for entry in manifest:
if select(entry.group):
selected.append(entry._replace(group=selected_group))
else:
unselected.append(entry._replace(group=unselected_group))
return selected, unselected
def ingest_manifest_lines(
sep, lines, title, in_cwd, groups, out_cwd, output_group):
groups_seen = set()
def select(group):
groups_seen.add(group)
if isinstance(groups, bool):
return groups
return group in groups
selected, unselected = partition_manifest(
read_manifest_lines(sep, lines, title, in_cwd, out_cwd), select,
output_group, None)
return selected, unselected, groups_seen
def apply_source_rewrites(rewrites, entry):
for rewrite in rewrites:
if entry.source == rewrite.source:
return entry._replace(source=rewrite.target)
return entry
def apply_rewrites(sep, rewrites, entry):
for pattern, line in rewrites:
if fnmatch.fnmatchcase(entry.target, pattern):
[new_entry] = read_manifest_lines(
sep, [line.format(**entry._asdict()) + '\n'], entry.manifest,
os.path.dirname(entry.manifest),
os.path.dirname(entry.manifest))
entry = new_entry._replace(group=entry.group)
return entry
def contents_entry(entry):
with open(entry.source) as file:
[line] = file.read().splitlines()
return entry._replace(source=line)
class input_action_base(argparse.Action):
def __init__(self, *args, **kwargs):
super(input_action_base, self).__init__(*args, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
outputs = getattr(namespace, 'output', None)
all_selected = getattr(namespace, 'selected', None)
if all_selected is None:
all_selected = []
setattr(namespace, 'selected', all_selected)
all_unselected = getattr(namespace, 'unselected', None)
if all_unselected is None:
all_unselected = []
setattr(namespace, 'unselected', all_unselected)
if namespace.groups is None or outputs is None:
groups = False
elif namespace.groups == 'all':
groups = True
else:
groups = set(
group if group else None
for group in namespace.groups.split(','))
cwd = getattr(namespace, 'cwd', '')
if outputs is not None:
output_group = len(outputs) - 1
else:
output_group = None
selected, unselected, groups_seen = self.get_manifest_lines(
namespace, values, cwd, groups, namespace.output_cwd, output_group)
include = getattr(namespace, 'include', [])
include_source = getattr(namespace, 'include_source', [])
exclude = getattr(namespace, 'exclude', [])
if include or exclude or include_source:
def included(entry):
def matches(file, patterns):
return any(
fnmatch.fnmatch(file, pattern) for pattern in patterns)
if matches(entry.target, exclude):
return False
if include and not matches(entry.target, include):
return False
return (
not include_source or matches(entry.source, include_source))
unselected += filter(lambda entry: not included(entry), selected)
selected = filter(included, selected)
if getattr(namespace, 'contents', False):
selected = map(contents_entry, selected)
unselected = map(contents_entry, unselected)
sep = getattr(namespace, 'separator', '=')
rewrites = [
entry.split('=', 1) for entry in getattr(namespace, 'rewrite', [])
]
selected = [apply_rewrites(sep, rewrites, entry) for entry in selected]
unselected = [
apply_rewrites(sep, rewrites, entry) for entry in unselected
]
if not isinstance(groups, bool):
unused_groups = groups - groups_seen - set([None])
if unused_groups:
raise Exception(
'%s not found in %r; try one of: %s' % (
', '.join(map(repr, unused_groups)), values, ', '.join(
map(repr, groups_seen - groups))))
all_selected += selected
all_unselected += unselected
class input_manifest_action(input_action_base):
def __init__(self, *args, **kwargs):
super(input_manifest_action, self).__init__(*args, **kwargs)
def get_manifest_lines(self, namespace, filename, *args):
all_inputs = getattr(namespace, 'manifest', None)
if all_inputs is None:
all_inputs = []
setattr(namespace, 'manifest', all_inputs)
all_inputs.append(filename)
with open(filename, 'r') as file:
return ingest_manifest_lines(
getattr(namespace, 'separator', '='), file, file.name, *args)
class input_entry_action(input_action_base):
def __init__(self, *args, **kwargs):
super(input_entry_action, self).__init__(*args, **kwargs)
def get_manifest_lines(self, namespace, entry, *args):
return ingest_manifest_lines(
getattr(namespace, 'separator', '='), [entry + '\n'],
namespace.entry_manifest, *args)
def common_parse_args(parser):
parser.fromfile_prefix_chars = '@'
parser.convert_arg_line_to_args = shlex.split
parser.add_argument(
'--output',
action='append',
required=True,
metavar='FILE',
help='Output file')
parser.add_argument(
'--output-cwd',
default='',
metavar='DIRECTORY',
help='Emit source paths relative to DIRECTORY')
parser.add_argument(
'--absolute',
action='store_true',
default=False,
help='Output source file names as absolute paths')
parser.add_argument(
'--cwd',
default='',
metavar='DIRECTORY',
help='Input entries are relative to this directory')
parser.add_argument(
'--groups',
default='all',
metavar='GROUP_LIST',
help='"all" or comma-separated groups to include')
parser.add_argument(
'--manifest',
action=input_manifest_action,
metavar='FILE',
default=[],
help='Input manifest file (must exist)')
parser.add_argument(
'--entry',
action=input_entry_action,
metavar='PATH=FILE',
help='Add a single entry as if from an input manifest')
parser.add_argument(
'--entry-manifest',
default='<command-line --entry>',
metavar='TITLE',
help=(
'Title in lieu of manifest file name for' +
' subsequent --entry arguments'))
parser.add_argument(
'--include',
action='append',
default=[],
metavar='TARGET',
help='Include only input entries matching TARGET'),
parser.add_argument(
'--include-source',
action='append',
default=[],
metavar='SOURCE',
help='Include only input entries matching SOURCE'),
parser.add_argument(
'--reset-include',
action='store_const',
const=[],
dest='include',
help='Reset previous --include')
parser.add_argument(
'--exclude',
action='append',
default=[],
metavar='TARGET',
help='Ignore input entries matching TARGET'),
parser.add_argument(
'--reset-exclude',
action='store_const',
const=[],
dest='exclude',
help='Reset previous --exclude')
parser.add_argument(
'--separator',
default='=',
metavar='SEP',
help='Use SEP between TARGET and SOURCE in entries')
return parser.parse_args()
def parse_args():
parser = argparse.ArgumentParser(description='Read manifest files.')
parser.add_argument(
'--copy-contentaddr',
action='store_true',
default=False,
help='Copy to content-addressed targets, not manifest')
parser.add_argument(
'--sources',
action='store_true',
default=False,
help='Write source file per line, not manifest entry')
parser.add_argument(
'--contents',
action='store_true',
default=False,
help='Replace each source file name with its contents')
parser.add_argument(
'--no-contents',
action='store_false',
dest='contents',
help='Reset previous --contents')
parser.add_argument(
'--rewrite',
action='append',
default=[],
metavar='PATTERN=ENTRY',
help='Replace entries whose target matches PATTERN with ENTRY,'
' which can use {source} and {target} substitutions'),
parser.add_argument(
'--reset-rewrite',
dest='rewrite',
action='store_const',
const=[],
help='Reset previous --rewrite')
parser.add_argument(
'--unique',
action='store_true',
default=False,
help='Elide duplicates even with different sources')
parser.add_argument(
'--stamp', metavar='FILE', help='Touch FILE at the end.')
args = common_parse_args(parser)
if args.copy_contentaddr:
if args.contents:
parser.error('--copy-contentaddr is incompatible with --contents')
args.unique = True
args.sources = True
return args
def main():
args = parse_args()
output_sets = [(dict() if args.unique else set()) for file in args.output]
for entry in getattr(args, 'selected', []):
assert entry.group is not None, entry
if args.absolute:
line = os.path.abspath(entry.source)
else:
line = entry.source
if not args.sources:
line = entry.target + args.separator + line
if args.unique:
output_sets[entry.group][entry.target] = line
else:
output_sets[entry.group].add(line)
for output_filename, output_set in zip(args.output, output_sets):
if args.copy_contentaddr:
created_dirs = set()
for target, source in output_set.iteritems():
target_path = os.path.join(output_filename, target)
if os.path.exists(target_path):
continue
target_dir = os.path.dirname(target_path)
if target_dir not in created_dirs:
if not os.path.exists(target_dir):
os.makedirs(target_dir)
created_dirs.add(target_dir)
shutil.copyfile(source, target_path)
else:
with open(output_filename, 'w') as file:
file.write(
''.join(
sorted(
line + '\n' for line in (
output_set.itervalues() if args.
unique else output_set))))
if args.stamp:
with open(args.stamp, 'w') as file:
os.utime(file.name, None)
return 0
if __name__ == '__main__':
sys.exit(main())