blob: 5f4328b4782756db38380088a275b5cf3d7e89d1 [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
import collections
class GnVariables(collections.OrderedDict):
def __setitem__(self, key, value):
if isinstance(value, list):
value = SetGnListValue(value)
super().__setitem__(key, value)
super().move_to_end(key)
class GnListValue(object):
def __init__(self, items):
assert (isinstance(items, list))
self._items = items
def __getitem__(self, idx):
return self._items[idx]
def __len__(self):
return len(self._items)
def __iter__(self):
yield from self._items
class RemoveGnListValue(GnListValue):
pass
class SetGnListValue(GnListValue):
pass
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))
self._targets[target.spec()] = 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):
# When target is added to graph, the relative dirpath is computed by
# os.path.relpath(absdirpath, root_path). The function returns '.' as
# dirpath for the targets lives in root dir.
#
# Skip dirpath '.' to avoid returning spec like '//.:target'.
dirpath = self._dirpath if self._dirpath != '.' else ''
return '//{}:{}'.format(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().__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().__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'
class CcProtoLibTarget(Target):
def IsProto(self):
return True
def __repr__(self):
return 'CcProtoLibTarget<{}>'.format(self.spec())
def target_type(self):
return 'CC Proto Library'
class CcLibTarget(LibTarget):
def __init__(self, dirpath, name, sort_id, deps, srcs=None, hdrs=None):
super().__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'
##############################################################
# 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)
tink_base_prefix = '@tink_base'
if dep.startswith(tink_base_prefix):
dep = dep[len(tink_base_prefix):]
dirpath, name = ParseTargetSpec(dep)
return TinkBaseTargetRef(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 == '@com_google_protobuf//:protobuf':
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)
return UnknownTargetRef(dirpath, dep)
class TargetRef(object):
def InTree(self):
return False
def IsUnknown(self):
return False
class UnknownTargetRef(TargetRef):
def __init__(self, dirpath, dep):
self._dirpath = dirpath
self._dep = dep
def IsUnknown(self):
return True
def spec(self):
return '<{} in {}'.format(self._dep, self._dirpath)
class PathTargetRef(TargetRef):
def __init__(self, dirpath, name):
self._dirpath = dirpath
self._name = name
def name(self):
return self._name
def spec(self):
# When target is added to graph, the relative dirpath is computed by
# os.path.relpath(absdirpath, root_path). The function returns '.' as
# dirpath for the targets lives in root dir. The relative dirpath is
# also used to make TargetRefs for the target's dependencies.
#
# Skip dirpath '.' to avoid returning spec like '//.:target'.
dirpath = self._dirpath if self._dirpath != '.' else ''
return '//{}:{}'.format(dirpath, self._name)
def dirpath(self):
return self._dirpath
def __repr__(self):
return '{}<{}>'.format(type(self).__name__, self.spec())
class TinkBaseTargetRef(PathTargetRef):
def GetGnDep(self):
spec = self.spec()
prefix = '//'
if not spec.startswith(prefix):
raise Exception(
'I do not know how to deal with Tink base dependency {}'.format(spec))
return '//third_party/tink/' + spec[len(prefix):]
class InTreeTargetRef(PathTargetRef):
def InTree(self):
return True
class AbslTargetRef(PathTargetRef):
def GetGnDep(self):
spec = self.spec()
prefix = '//'
if not spec.startswith(prefix):
raise Exception(
'I do not know how to deal with Absl dependency {}'.format(spec))
return '//third_party/abseil-cpp/' + spec[len(prefix):]
class GoogleTestTargetRef(PathTargetRef):
pass
class ProtobufLiteTargetRef(TargetRef):
def GetGnDep(self):
return '//third_party/protobuf:protobuf_lite'
def spec(self):
return '@protobuf_lite'
class RapidJsonTargetRef(TargetRef):
def spec(self):
return '@rapidjson'
class BoringSslTargetRef(PathTargetRef):
def GetGnDep(self):
if self._dirpath:
raise Exception(
'BoringSSL dep {} has dirpath {}. I do not know how to deal with that.'
.format(self.spec(), self._dirpath))
return '//third_party/boringssl:' + self._name
##############################################################
# 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 dep.IsUnknown():
raise Exception('Found unknown dep: {} for {}'.format(dep.spec(), target.spec()))
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
# Directory mapping spec to target.
self.targets = targets
self.subdirs = sorted(subdirs)
def HasProto(self):
return any(t.IsProto() for t in self.targets.values())
def HasCcLib(self):
return any(isinstance(t, CcLibTarget) for t in self.targets.values())
def __contains__(self, target_or_spec):
spec = target_or_spec
if hasattr(target_or_spec, 'spec'):
spec = target_or_spec.spec()
if not isinstance(spec, str):
raise Exception(
'{} should be a spec string or have a spec method.'.format(
target_or_spec))
return spec in self.targets
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 GetTargetForInTreeDep(self, dep):
assert (isinstance(dep, InTreeTargetRef))
target = self._build_graph.GetTarget(dep.spec())
# We don't generate CcProtoLibTargets, so we try to dereference them to
# find the ProtoLibTarget that they refer to.
if isinstance(target, CcProtoLibTarget):
assert (len(target.deps()) == 1)
target = self._build_graph.GetTarget(target.deps()[0].spec())
assert (isinstance(target, ProtoLibTarget))
return target
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()
# We generate a stable sort order to stabilize the .gn files.
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.',
'#',
'# WARNING: This file is automatically generated by convert_for_cobalt.',
'# Do not edit manually.'
])
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 GnWriter(BuildFileWriter):
def __init__(self, rootdir, deps_prefix, build_graph, max_line):
super().__init__(
rootdir=rootdir, build_graph=build_graph, max_line=max_line)
assert (deps_prefix.startswith('//'))
assert (not deps_prefix.endswith('/'))
self._deps_prefix = deps_prefix
def GetBuildFilePath(self, build_file):
return os.path.join(self._rootdir, build_file.dirpath, 'BUILD.gn')
def WriteHeader(self):
super().WriteHeader()
if self._build_file.HasProto():
self.nl()
self.emitnl('import("//third_party/protobuf/proto_library.gni")')
self.nl()
def WriteTarget(self, target):
# Skip cc_proto_lib targets.
if isinstance(target, CcProtoLibTarget):
return
super().WriteTarget(target)
if isinstance(target, CcLibTarget):
self.WriteCcLibTarget(target)
elif isinstance(target, ProtoLibTarget):
self.WriteProtoLibTarget(target)
else:
raise NotImplementedError('I do not know how to write {}'.format(
str(target)))
self.nl()
def GnDep(self, dep):
if not dep.InTree():
return dep.GetGnDep()
target = self.GetTargetForInTreeDep(dep)
if target in self._build_file:
return ':{}'.format(target.name())
if target.spec().startswith('//:'):
return '{}:{}'.format(self._deps_prefix, target.spec()[3:])
return '{}/{}'.format(self._deps_prefix, target.spec()[2:])
def FormatValue(self, value):
if isinstance(value, str):
return '"{}"'.format(value)
if isinstance(value, TargetRef):
return '"{}"'.format(self.GnDep(value))
raise Exception('I do not know how to write the {} value'.format(
repr(value)))
def WriteGnVariable(self, name, value):
if isinstance(value, SetGnListValue):
self.WriteGnSetListVariable(name, value)
return
if isinstance(value, RemoveGnListValue):
self.WriteGnRemoveListVariable(name, value)
return
self.emitnl('{} = {}'.format(name, self.FormatValue(value)))
def WriteGnListVariable(self, name, value, op):
if len(value) == 0:
return
if len(value) == 1:
single_line = '{} {} [ {} ]'.format(name, op, self.FormatValue(value[0]))
if len(single_line) <= self._max_line:
self.emitnl(single_line)
return
self.emitnl('{} {} ['.format(name, op))
self.indent()
for item in value:
self.emitnl('{},'.format(self.FormatValue(item)))
self.deindent()
self.emitnl(']')
def WriteGnSetListVariable(self, name, value):
self.WriteGnListVariable(name, value, "=")
def WriteGnRemoveListVariable(self, name, value):
# Add and then remove the variable in case the var wasn't already added
self.WriteGnListVariable(name, value, "+=")
self.WriteGnListVariable(name, value, "-=")
def WriteGnTarget(self, target_type, name, variables):
self.emitnl('{}("{}") {{'.format(target_type, name))
self.indent()
for var, value in variables.items():
self.WriteGnVariable(var, value)
self.deindent()
self.emitnl('}')
pass
def WriteProtoLibTarget(self, target):
variables = GnVariables()
variables['cc_generator_options'] = 'lite'
variables['proto_in_dir'] = '//third_party/tink'
variables['extra_configs'] = ['//third_party/tink:tink_config']
variables['sources'] = sorted(target.srcs)
variables['deps'] = target.deps()
self.WriteGnTarget('proto_library', target.name(), variables)
def WriteCcLibTarget(self, target):
variables = GnVariables()
variables['configs'] = RemoveGnListValue(['//build/config:no_rtti'])
variables['sources'] = sorted(target.srcs + target.hdrs)
variables['public_deps'] = target.deps()
variables['public_configs'] = ['//third_party/tink:tink_config']
self.WriteGnTarget('source_set', target.name(), variables)
def FetchTargets(targets, root_path, ignored_targets):
graph = BuildGraph(root_path, ignored_targets)
fetcher = TargetFetcher(graph, targets, error_on_unknown=True)
targets = fetcher.FetchAll()
print('Found {} targets.'.format(len(targets)))
return targets, graph
def WriteTargetsToGn(targets, graph, root_path, deps_prefix):
build_files = CreateBuildFiles(targets)
gn_writer = GnWriter(
rootdir=root_path,
build_graph=graph,
max_line=80,
deps_prefix=deps_prefix)
for f in build_files:
gn_writer.WriteBuildFile(f)
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 here will be ignored.
ignored_targets = frozenset([])
# Targets listed her are the start of the fetch. Put all the targets you plan
# on directly depending upon here.
targets = [
'//hybrid:hybrid_config',
'//hybrid:hpke_config',
'//hybrid:hybrid_decrypt_factory',
'//hybrid:hybrid_encrypt_factory',
'//hybrid:hybrid_key_templates',
'//:binary_keyset_reader',
'//:binary_keyset_writer',
'//:cleartext_keyset_handle',
'//:hybrid_decrypt',
'//:hybrid_encrypt',
'//:keyset_handle',
]
targets, graph = FetchTargets(targets, cc_path, ignored_targets)
WriteTargetsToGn(targets, graph, cc_path, '//third_party/tink/cc')
if __name__ == '__main__':
sys.exit(main(sys.argv))