blob: 0e7de3e670f64d2833ecaa1e4e3cc5597bdcd75f [file] [log] [blame]
#! /usr/bin/env python3
# Copyright 2019 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.
import os.path
import sys
import datetime
def TargetNameFromInTreeSpec(spec):
dirname, name = ParseTargetSpec(spec)
head = dirname
path_list = [name]
while head != '':
head, tail = os.path.split(head)
path_list.insert(0, tail)
return '_'.join(path_list)
def IsSubdir(parent, child):
if parent == child:
return False
return (os.path.commonpath([parent, child]) == parent)
class BazelGlobals(object):
IGNORABLES = frozenset([
'load',
'package',
'licenses',
'exports_files',
'select',
])
def __init__(self, build_graph, dirpath):
self._build_graph = build_graph
self._dirpath = dirpath
self._set_objects = {}
def __getitem__(self, key):
if key in self._set_objects:
return self._set_objects[key]
if key == '__builtins__':
raise KeyError(key)
if key in self.IGNORABLES:
return lambda *args, **kwargs: None
return self._build_graph.GetTargetAdder(self._dirpath, key)
def __setitem__(self, key, value):
self._set_objects[key] = value
def __contains__(self, key):
if key == '__builtins__':
raise KeyError(key)
return True
def __len__(self):
return 1
class BuildGraph(object):
def __init__(self, root_path, ignored_targets):
self._targets = {}
if not os.path.isabs(root_path):
raise Exception('{} is not an absolute path.'.format(root_path))
self._root_path = root_path
self._ignored_targets = ignored_targets
# These are specs that will be remapped.
self._spec_map_old_to_new = {}
def AddTarget(self, target_cls, dirpath, name, deps=None, **kwargs):
if deps:
deps = [MakeTargetRef(dep, dirpath) for dep in deps]
deps = [dep for dep in deps if dep.spec() not in self._ignored_targets]
for i in range(len(deps)):
if deps[i].spec() not in self._spec_map_old_to_new:
continue
deps[i] = MakeTargetRef(self._spec_map_old_to_new[deps[i].spec()], dirpath)
target = target_cls(
dirpath=dirpath,
name=name,
sort_id=len(self._targets),
deps=deps,
**kwargs)
assert(isinstance(target, Target))
target = self.RemapTarget(target)
assert(isinstance(target, Target))
self._targets[target.spec()] = target
def RemapTarget(self, target):
"CMake does not like it if a cc_library has headers in a subdir. So we move the target."
if not isinstance(target, CcLibTarget):
return target
dirnames = [os.path.dirname(hdr) for hdr in target.hdrs]
if not any(d != '' for d in dirnames):
return target
if not all(d != '' for d in dirnames):
raise Exception('I do not know how to deal with targets that have some headers and sources in a subdirectory and some not: ' + target.spec)
if not all(d == dirnames[0] for d in dirnames):
raise Exception('I do not know how to deal with targets that have headers and sources in different subdirectories: ' + target.spec)
subdir = dirnames[0]
old_spec = target.spec()
dirpath = os.path.join(target.dirpath(), subdir)
name = target.name()
sort_id = target.sort_id()
deps = target.deps()
srcs = [os.path.relpath(src, subdir) for src in target.srcs]
hdrs = [os.path.relpath(hdr, subdir) for hdr in target.hdrs]
target = CcLibTarget(dirpath, name, sort_id, deps, srcs=srcs, hdrs=hdrs)
new_spec = target.spec()
self._spec_map_old_to_new[old_spec] = new_spec
for t in self._targets.values():
t.RemapDep(old_spec, new_spec)
return target
def GetTargetAdder(self, absdirpath, target_type):
if not IsSubdir(self._root_path, absdirpath):
Exception(
'GetTargetAdder expected a path under {} which {} is not.'.format(
self._root_path, absdirpath))
dirpath = os.path.relpath(absdirpath, self._root_path)
if target_type == 'proto_library':
return self.GetProtoLibraryAdder(dirpath)
elif target_type == 'cc_library':
return self.GetCcLibraryAdder(dirpath)
elif target_type == 'cc_proto_library':
return self.GetCcProtoLibraryAdder(dirpath)
return self.GetUnknownTargetAdder(dirpath, target_type)
def GetUnknownTargetAdder(self, dirpath, target_type):
def Add(*args, **kwargs):
if 'name' not in kwargs or args:
raise Exception(
'"{}" is not a target type. Please add to IGNORABLES.'.format(
target_type))
name = kwargs['name']
deps = kwargs.get('deps', [])
self.AddTarget(
UnknownTarget, dirpath, name, deps=deps, target_type=target_type)
return Add
def GetCcProtoLibraryAdder(self, dirpath):
def Add(name, deps=None, **kwargs):
self.AddTarget(CcProtoLibTarget, dirpath, name, deps=deps)
return Add
def GetProtoLibraryAdder(self, dirpath):
def Add(name, srcs, deps=None, **kwargs):
self.AddTarget(ProtoLibTarget, dirpath, name, srcs=srcs, deps=deps)
return Add
def GetCcLibraryAdder(self, dirpath):
def Add(name, srcs=None, hdrs=None, deps=None, **kwargs):
self.AddTarget(
CcLibTarget, dirpath, name, hdrs=hdrs, srcs=srcs, deps=deps)
return Add
def AddToGraph(self, dirpath):
filepath = os.path.join(dirpath, 'BUILD.bazel')
fp = open(filepath, 'r')
content = fp.read()
gs = BazelGlobals(self, dirpath)
exec(content, {}, gs)
def GetTarget(self, target_spec):
target_spec = self._spec_map_old_to_new.get(target_spec, target_spec)
if target_spec in self._ignored_targets:
raise Exception('{} is being ignored'.format(target_spec))
ref = MakeTargetRef(target_spec, '')
if not ref.InTree():
raise Exception('{} is not in the tree.'.format(target_spec))
if target_spec not in self._targets:
dirpath, _ = ParseTargetSpec(target_spec)
absdirpath = os.path.join(self._root_path, dirpath)
self.AddToGraph(absdirpath)
if target_spec not in self._targets:
print(self._targets.keys())
raise Exception('{} could not be found in {}'.format(
target_spec, absdirpath))
return self._targets[target_spec]
##############################################################
# The Targets themselves.
##############################################################
class Target(object):
def __init__(self, dirpath, name, sort_id, deps):
assert (isinstance(sort_id, int))
self._sort_id = sort_id
assert (isinstance(name, str))
self._name = name
assert (isinstance(dirpath, str))
self._dirpath = dirpath
self._deps = deps or []
assert (all(isinstance(dep, TargetRef) for dep in self._deps))
def __repr__(self):
raise NotImplementedError
def name(self):
return self._name
def spec(self):
return '//{}:{}'.format(self._dirpath, self._name)
def deps(self):
return self._deps
def dirpath(self):
return self._dirpath
def sort_id(self):
return self._sort_id
def target_type(self):
raise NotImplementedError
def IsUnknown(self):
return False
def IsProto(self):
return False
def RemapDep(self, old, new):
for i in range(len(self._deps)):
if self._deps[i].spec() == old:
self._deps[i] = MakeTargetRef(new, self._dirpath)
class UnknownTarget(Target):
def __init__(self, dirpath, name, sort_id, target_type, deps):
super(UnknownTarget, self).__init__(dirpath, name, sort_id, deps=deps)
assert (isinstance(target_type, str))
self._target_type = target_type
def __repr__(self):
return 'UnknownTarget<{}: {}>'.format(self._target_type, self.spec())
def IsUnknown(self):
return True
class LibTarget(Target):
def __init__(self, dirpath, name, sort_id, deps, srcs=None):
super(LibTarget, self).__init__(dirpath, name, sort_id, deps)
self.srcs = srcs or []
def __repr__(self):
return 'LibTarget<{}>'.format(self.spec())
class ProtoLibTarget(LibTarget):
def IsProto(self):
return True
def __repr__(self):
return 'ProtoLibTarget<{}>'.format(self.spec())
def target_type(self):
return 'Proto Library'
def GetCMakeDep(self):
spec = self.spec()
suffix = '_proto'
if not spec.endswith(suffix):
raise Exception('Unexpected name for proto_library: ' + self.spec())
spec = spec[:-len(suffix)] + '_lib'
return TargetNameFromInTreeSpec(spec)
class CcProtoLibTarget(Target):
def IsProto(self):
return True
def __repr__(self):
return 'CcProtoLibTarget<{}>'.format(self.spec())
def target_type(self):
return 'CC Proto Library'
def GetCMakeDep(self):
spec = self.spec()
suffix = '_cc_proto'
if not spec.endswith(suffix):
raise Exception('Unexpected name for cc_proto_library: ' + self.spec())
spec = spec[:-len(suffix)] + '_lib'
return TargetNameFromInTreeSpec(spec)
class CcLibTarget(LibTarget):
def __init__(self, dirpath, name, sort_id, deps, srcs=None, hdrs=None):
super(CcLibTarget, self).__init__(
dirpath, name, sort_id, srcs=srcs, deps=deps)
self.hdrs = hdrs or []
def __repr__(self):
return 'CcLibTarget<{}>'.format(self.spec())
def target_type(self):
return 'CC Library'
def GetCMakeDep(self):
return TargetNameFromInTreeSpec(self.spec())
##############################################################
# Target references. (Used mostly for dependencies)
##############################################################
def ParseTargetSpec(dep):
parts = dep.split(':')
dirpath = parts[0][2:]
if len(parts) == 1:
name = os.path.basename(dep)
elif len(parts) == 2:
name = parts[1]
else:
raise Exception('I do not know how to deal with this dep: "{}"'.format(dep))
return dirpath, name
def MakeTargetRef(dep, dirpath):
if dep[0] == ':':
return InTreeTargetRef(dirpath, dep[1:])
if dep[:2] == '//':
dirpath, name = ParseTargetSpec(dep)
return InTreeTargetRef(dirpath, name)
absl_prefix = '@com_google_absl'
if dep.startswith(absl_prefix):
dep = dep[len(absl_prefix):]
dirpath, name = ParseTargetSpec(dep)
return AbslTargetRef(dirpath, name)
if dep == '@com_google_protobuf//:protobuf_lite':
return ProtobufLiteTargetRef()
if dep == '@rapidjson':
return RapidJsonTargetRef()
googletest_prefix = '@com_google_googletest'
if dep.startswith(googletest_prefix):
dep = dep[len(googletest_prefix):]
dirpath, name = ParseTargetSpec(dep)
return GoogleTestTargetRef(dirpath, name)
boringssl_prefix = '@boringssl'
if dep.startswith(boringssl_prefix):
dep = dep[len(boringssl_prefix):]
dirpath, name = ParseTargetSpec(dep)
return BoringSslTargetRef(dirpath, name)
raise Exception('I do not know how to deal with this dep: "{}"'.format(dep))
class TargetRef(object):
def InTree(self):
return False
class PathTargetRef(TargetRef):
def __init__(self, dirpath, name):
self._dirpath = dirpath
self._name = name
def spec(self):
return '//{}:{}'.format(self._dirpath, self._name)
def dirpath(self):
return self._dirpath
def __repr__(self):
return '{}<{}>'.format(type(self).__name__, self.spec())
class InTreeTargetRef(PathTargetRef):
def InTree(self):
return True
class AbslTargetRef(PathTargetRef):
def GetCMakeDep(self):
return '::'.join(self._dirpath.split('/'))
class GoogleTestTargetRef(PathTargetRef):
pass
class ProtobufLiteTargetRef(TargetRef):
def GetCMakeDep(self):
return 'protobuf_lite'
def spec(self):
return '@protobuf_lite'
class RapidJsonTargetRef(TargetRef):
def spec(self):
return '@rapidjson'
class BoringSslTargetRef(PathTargetRef):
def GetCMakeDep(self):
return self._name
pass
##############################################################
# TargetFetcher grabs all the targets we need.
##############################################################
class TargetFetcher(object):
def __init__(self, build_graph, initial_targets, error_on_unknown):
# _to_fetch is a list of target specs.
self._to_fetch = initial_targets
self._targets = []
self._fetched = set()
self._build_graph = build_graph
self._error_on_unknown = error_on_unknown
def FetchAll(self):
while self._to_fetch:
self.FetchNext()
return self._targets
def FetchNext(self):
spec = self._to_fetch.pop(0)
if spec in self._fetched:
return
target = self._build_graph.GetTarget(spec)
if target.IsUnknown() and self._error_on_unknown:
raise Exception('Found unknown target: {}'.format(target))
self._targets.append(target)
# We add both the actual spec and the spec we tried to fetch just in
# case there was a remapping.
self._fetched.add(target.spec())
self._fetched.add(spec)
for dep in target.deps():
if not dep.InTree():
continue
if dep.spec() not in self._fetched:
self._to_fetch.append(dep.spec())
##############################################################
# BuildFile holds the information necessary to create a build file.
##############################################################
class BuildFile(object):
def __init__(self, dirpath, targets, subdirs):
self.dirpath = dirpath
self.targets = targets
self.subdirs = sorted(subdirs)
def HasProto(self):
return any(t.IsProto() for t in self.targets.values())
def CreateBuildFiles(targets):
build_file_targets = {}
for t in targets:
if t.dirpath() not in build_file_targets:
build_file_targets[t.dirpath()] = {}
build_file_targets[t.dirpath()][t.spec()] = t
dirpaths = [k for k in build_file_targets.keys()]
build_files = []
for dirpath, targets in build_file_targets.items():
subdirs = []
for d in dirpaths:
# If d is an immediate subdirectory of dirpath, add it to subdirs
if not IsSubdir(dirpath, d):
continue
rel = os.path.relpath(d, dirpath)
if os.path.basename(rel) == rel:
subdirs.append(rel)
build_files.append(BuildFile(dirpath, targets, subdirs))
return build_files
class BuildFileWriter(object):
def __init__(self, rootdir, build_graph, max_line):
self._rootdir = rootdir
self._max_line = max_line
self._build_graph = build_graph
def GetBuildFilePath(self, dirpath):
raise NotImplementedError
def WriteBuildFile(self, build_file):
self._indent = 0
self._filepath = self.GetBuildFilePath(build_file)
self._fp = open(self._filepath, 'w+')
self._build_file = build_file
self._nl = False
print('Writing to {},'.format(self._filepath))
self.WriteHeader()
targets = sorted(
build_file.targets.values(), key=lambda target: target.sort_id())
for target in targets:
self.WriteTarget(target)
assert (self._indent == 0)
def WriteHeader(self):
year = datetime.datetime.today().year
self.emitlines([
'# Copyright {} The Fuchsia Authors. All rights reserved.'.format(year),
'# Use of this source code is governed by a BSD-style license that can be',
'# found in the LICENSE file.'
])
def WriteTarget(self, target):
self.emitnl('# {} : {}'.format(target.target_type(), target.name()))
def emit(self, s):
if self._nl:
self._fp.write(' ' * self._indent)
self._nl = False
if not isinstance(s, str):
raise Exception('emit expected a string, not {}'.format(s))
self._fp.write(s)
def emitnl(self, s):
self.emit(s)
self.nl()
def emitlines(self, lines):
for line in lines:
self.emitnl(line)
def nl(self):
self._fp.write('\n')
self._nl = True
def indent(self):
self._indent += 1
def deindent(self):
self._indent -= 1
class CMakeWriter(BuildFileWriter):
def __init__(self, rootdir, target_prefix, build_graph, max_line):
super(CMakeWriter, self).__init__(
rootdir=rootdir, build_graph=build_graph, max_line=max_line)
self._target_prefix = target_prefix
def GetBuildFilePath(self, build_file):
return os.path.join(self._rootdir, build_file.dirpath, 'CMakeLists.txt')
def SingleLineFunction(self, func, *args):
return '{}({})'.format(func, ' '.join(args))
def WriteFunction(self, func, *args):
single = self.SingleLineFunction(func, *args)
if len(single) <= self._max_line:
self.emitnl(single)
return
self.emitnl('{}('.format(func))
self.indent()
for arg in args:
self.emitnl(arg)
self.deindent()
self.emitnl(')')
def WriteHeader(self):
super(CMakeWriter, self).WriteHeader()
self.nl()
for subdir in self._build_file.subdirs:
self.WriteFunction('add_subdirectory', subdir)
self.nl()
if not self._build_file.HasProto():
return
def WriteTarget(self, target):
# Skip cc_proto_lib targets.
if isinstance(target, CcProtoLibTarget):
return
super(CMakeWriter, self).WriteTarget(target)
if isinstance(target, CcLibTarget):
self.WriteCcLibTarget(target)
return
if isinstance(target, ProtoLibTarget):
self.WriteProtoLibTarget(target)
return
raise NotImplementedError('I do not know how to write {}'.format(
str(target)))
def WriteLibTargetDeps(self, target):
name = self.TargetName(target)
in_tree_deps = [self.DepName(dep) for dep in target.deps() if dep.InTree()]
deps = [self.DepName(dep) for dep in target.deps()]
if in_tree_deps:
self.WriteFunction('add_dependencies', name, *in_tree_deps)
if deps:
self.WriteFunction('target_link_libraries', name, *deps)
def WriteCcLibTarget(self, target):
name = self.TargetName(target)
self.WriteFunction('add_library', name, *target.hdrs, *target.srcs)
if not target.srcs:
self.WriteFunction('set_target_properties', name, 'PROPERTIES',
'LINKER_LANGUAGE', 'CXX')
if target.hdrs:
self.WriteFunction('tink_export_hdrs', *target.hdrs)
self.WriteLibTargetDeps(target)
self.nl()
def WriteProtoLibTarget(self, target):
name = self.TargetName(target)
srcs = []
for src in target.srcs:
suffix = '.proto'
if not src.endswith(suffix):
raise Exception('Unexpected name {} for proto in {}'.format(
src, target.spec()))
srcs.append(src[:-len(suffix)])
hdrs_out = name.upper() + '_PROTO_HDRS'
self.WriteFunction('tink_make_protobuf_cpp_lib', name, hdrs_out, *srcs)
self.WriteLibTargetDeps(target)
self.nl()
def TargetName(self, target):
return self._target_prefix + '_' + target.GetCMakeDep()
def DepName(self, dep):
if dep.InTree():
target = self._build_graph.GetTarget(dep.spec())
return self._target_prefix + '_' + target.GetCMakeDep()
return dep.GetCMakeDep()
def main(args):
script_path = os.path.abspath(args[0])
tools_path = os.path.dirname(script_path)
tink_path = os.path.dirname(tools_path)
cc_path = os.path.join(tink_path, 'cc')
# Targets listed her are the start of the fetch. Put all the targets you plan
# on directly depending upon here.
targets = [
'//cc/hybrid:hybrid_config',
'//cc/hybrid:hybrid_encrypt_factory',
'//cc/hybrid:hybrid_decrypt_factory',
'//cc:hybrid_decrypt',
'//cc:hybrid_encrypt',
'//cc:keyset_handle',
'//cc/hybrid:hybrid_key_templates',
'//cc:cleartext_keyset_handle',
]
# Targets listed here will be ignored.
ignored_targets = frozenset([])
graph = BuildGraph(tink_path, ignored_targets)
fetcher = TargetFetcher(graph, targets, error_on_unknown=True)
targets = fetcher.FetchAll()
print('Found {} targets.'.format(len(targets)))
build_files = CreateBuildFiles(targets)
cmake_writer = CMakeWriter(
rootdir=tink_path, target_prefix='tink', build_graph=graph, max_line=80)
for f in build_files:
cmake_writer.WriteBuildFile(f)
if __name__ == '__main__':
sys.exit(main(sys.argv))