| #! /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)) |