Initial draft of making BUILD files for y2014.

Change-Id: Ib62acfe4ee8f20ccd5b5d684acdd8cc5a7e79d8d
diff --git a/doc/gyp_to_bazel.py b/doc/gyp_to_bazel.py
new file mode 100755
index 0000000..b3da95b
--- /dev/null
+++ b/doc/gyp_to_bazel.py
@@ -0,0 +1,285 @@
+#!/usr/bin/python3
+
+# This script converts simple Gyp files to their Bazel equivalents.
+# It is not intended to be particularly extensible; it is only going to be used
+# once over all of our code and then forgotten about.
+# This "only has to work on exactly the code we have now" property also means
+# it's pretty picky about many things and doesn't try to handle anything
+# beyond what we actually have.
+#
+# It takes a list of folders to deal with.
+#
+# Running this script requires PyYAML <pyyaml.org> to be installed.
+
+import sys
+import os
+import yaml
+import collections
+
+'''Converts a Gyp filename to its Bazel equivalent.
+
+Args:
+  gyp_file_name: The name of the gyp file this is contained in.
+                 This is important for resolving relative paths etc.
+  file: The name of the file to deal with.
+'''
+def gyp_file_to_bazel(gyp_file_name, file):
+  if file.startswith('<(AOS)/'):
+    return '//aos' + file[6:]
+  elif file.startswith('<(DEPTH)/'):
+    return '//' + file[9:]
+  else:
+    if '(' in file:
+      raise RuntimeError('Bad variable in file "%s"' % file)
+    split_gyp = os.path.dirname(gyp_file_name).split('/')
+    rest = []
+    dotdots = 0
+    doing_dotdots = True
+    for component in file.split('/'):
+      if component == '..':
+        dotdots += 1
+        if not doing_dotdots:
+          raise RuntimeError('Bad use of .. in file "%s"' % file)
+      else:
+        doing_dotdots = False
+        rest.append(component)
+    return '/'.join(['/'] + split_gyp[:-dotdots] + rest)
+
+'''Converts a Gyp target to its Bazel equivalent.
+
+Args:
+  gyp_file_name: The name of the gyp file this is contained in.
+                 This is important for resolving relative paths etc.
+  target: The name of the target to deal with.
+'''
+def gyp_target_to_bazel(gyp_file_name, target):
+  if not ':' in target:
+    if '.' in target:
+      raise RuntimeError('Have a filename instead of a target: "%s"' % target)
+    return ':' + target
+  if target[0] == ':':
+    return target
+
+  if target == '<(AOS)/build/aos.gyp:logging':
+    return '//aos/common/logging'
+
+  split = target.split(':')
+  if len(split) != 2:
+    raise RuntimeError('Not sure how to parse target "%s"' % target)
+
+  if split[0] == '<(EXTERNALS)':
+    return '//third_party/' + split[1]
+
+  split_path = split[0].rsplit('/', 1)
+  if len(split_path) != 2:
+    raise RuntimeError('TODO(Brian): Handle referring to this .gyp file as %s!' % split[0])
+  if not split_path[1].endswith('.gyp'):
+    raise RuntimeError('Not sure how to deal with gyp filename "%s"' % split[0])
+
+  folder = gyp_file_to_bazel(gyp_file_name, split_path[0])
+
+  if not folder.endswith(split_path[1][:-4]):
+    raise RuntimeError('Not sure how to deal with non-matching gyp file "%s"' % target)
+
+  return '%s:%s' % (folder, split[1])
+
+'''Represents a Bazel build target.
+
+Subclasses represent actual concrete things which are emitted into a BUILD
+file.'''
+class BuildTarget(object):
+  def __init__(self, type, name):
+    self.__type = type
+    self.__name = name
+
+  def add_dep(self, bazel_dep):
+    self.__deps.append(bazel_dep)
+
+  '''Returns a collections.OrderedDict with all of the attributes on the
+  Bazel rule this represents.
+
+  Subclasses are expected to override this and add their own attributes
+  in the appropriate order.'''
+  def attrs(self):
+    r = collections.OrderedDict()
+    r['name'] = self.__name
+    return r
+
+  '''Returns a set of load statements.
+
+  Subclasses are expected to override this and add their own loads.
+
+  Each element of the result is the arguments to a single load call.'''
+  def loads(self):
+    return set()
+
+  '''Returns the Bazel representation of a given object as an attribute
+  value.'''
+  def __to_bazel_string(o):
+    if isinstance(o, str):
+      return repr(o)
+    if hasattr(o, '__iter__'):
+      r = ['[']
+      for c in o:
+        r.append('    %s,' % BuildTarget.__to_bazel_string(c))
+      r.append('  ]')
+      return '\n'.join(r)
+    else:
+      return str(o)
+
+  def __str__(self):
+    r = [self.__type + '(']
+    for name, value in self.attrs().items():
+      if value:
+        r.append('  %s = %s,' % (name, BuildTarget.__to_bazel_string(value)))
+    r.append(')')
+    return '\n'.join(r)
+
+'''Represents a cc_* target.'''
+class CcBuildTarget(BuildTarget):
+  def __init__(self, type, name):
+    if not type.startswith('cc_'):
+      raise
+
+    super(CcBuildTarget, self).__init__(type, name)
+
+    self.__srcs = []
+    self.__deps = []
+
+  def add_src(self, src):
+    self.__srcs.append(src)
+
+  def add_dep(self, dep):
+    self.__deps.append(dep)
+
+  def attrs(self):
+    r = super(CcBuildTarget, self).attrs();
+    r['srcs'] = self.__srcs
+    r['deps'] = self.__deps
+    return r
+
+'''Represents a filegroup target.'''
+class FilegroupTarget(BuildTarget):
+  def __init__(self, name):
+    super(FilegroupTarget, self).__init__('filegroup', name)
+
+    self.__srcs = []
+
+  def add_src(self, src):
+    self.__srcs.append(src)
+
+  def attrs(self):
+    r = super(FilegroupTarget, self).attrs();
+    r['srcs'] = self.__srcs
+    return r
+
+'''Represents a queue_library target.'''
+class QueueTarget(BuildTarget):
+  def __init__(self, name):
+    super(QueueTarget, self).__init__('queue_library', name)
+
+    self.__srcs = []
+
+  def add_src(self, src):
+    self.__srcs.append(src)
+
+  def loads(self):
+    return set((('aos/build/queues', 'queue_library'),))
+
+  def attrs(self):
+    r = super(QueueTarget, self).attrs();
+    r['srcs'] = self.__srcs
+    return r
+
+def main(argv):
+  for d in argv:
+    build_targets = []
+
+    d = d.rstrip('/')
+    gyp_file_name = os.path.join(d, os.path.split(d)[-1] + '.gyp')
+    with open(gyp_file_name, 'r') as gyp_file:
+      gyp = yaml.load(gyp_file)
+      if 'targets' not in gyp:
+        print('No targets entry found in %s!' % gyp_file_name, file=sys.stderr)
+        return 1
+      if list(gyp.keys()) != ['targets']:
+        print('Unknown keys of %s from %s' % (gyp.keys(), gyp_file_name),
+              file=sys.stderr)
+      targets = gyp['targets']
+      for gyp_target in targets:
+        target = None
+        name = gyp_target['target_name']
+        type = gyp_target['type']
+        if (type in ['static_library', 'executable'] and
+            not 'includes' in gyp_target):
+          cc_type = {
+              'static_library': 'cc_library',
+              'executable': 'cc_binary',
+            }[type]
+          target = CcBuildTarget(cc_type, name)
+
+          for dep in gyp_target['dependencies']:
+            target.add_dep(gyp_target_to_bazel(gyp_file_name, dep))
+          for src in gyp_target['sources']:
+            target.add_src(src)
+        elif type == 'none':
+          target = FilegroupTarget(name)
+          for dep in gyp_target['dependencies']:
+            target.add_src(gyp_target_to_bazel(gyp_file_name, dep))
+        elif 'includes' in gyp_target:
+          includes = gyp_target['includes']
+          if len(includes) != 1:
+            raise RuntimeError(
+              'Not sure how to handle multiple includes in %s' % gyp_target)
+          include = gyp_file_to_bazel(gyp_file_name, includes[0])
+          if include == '//aos/build/queues.gypi':
+            vars = gyp_target['variables']
+            if 'header_path' not in vars:
+              raise RuntimeError(
+                'No header_path for target %s in %s' % (name, gyp_file_name))
+            if list(vars.keys()) != ['header_path']:
+              raise RuntimeError(
+                'Extra variables for target %s in %s' % (name, gyp_file_name))
+            if vars['header_path'] != os.path.dirname(gyp_file_name):
+              raise RuntimeError(
+                'Incorrect header_path for target %s in %s' % (name,
+                                                               gyp_file_name))
+
+            target = QueueTarget(name)
+            for src in gyp_target['sources']:
+              if '/' in src:
+                raise RuntimeError(
+                  '.q src %s in bad dir for target %s in %s' % (src,
+                                                                name,
+                                                                gyp_file_name))
+              target.add_src(src)
+          else:
+            raise RuntimeError(
+              'Unknown include %s for target %s in %s' % (include, name,
+                                                          gyp_file_name))
+        else:
+          raise RuntimeError(
+            'Unknown type %s for target %s in %s' % (type, name, gyp_file_name))
+
+        if not target:
+          raise
+        build_targets.append(target)
+
+    with open(os.path.join(d, 'BUILD'), 'w') as build_file:
+      build_file.write(
+          'package(default_visibility = [\'//visibility:public\'])\n')
+      loads = set()
+      for t in build_targets:
+        loads |= t.loads()
+      if loads:
+        build_file.write('\n')
+        for load in sorted(loads):
+          build_file.write('load(%s)\n' % (', '.join([repr(part) for part
+                                                      in load])))
+      for t in build_targets:
+        build_file.write('\n')
+        build_file.write(str(t))
+        build_file.write('\n')
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))