chromium/native_client_sdk/src/build_tools/build_app.py

#!/usr/bin/env python
# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import argparse
import json
import os
import re
import sys

if sys.version_info < (2, 7, 0):
  sys.stderr.write("python 2.7 or later is required run this script\n")
  sys.exit(1)

import buildbot_common
import build_projects
import build_version
import easy_template
import parse_dsc

from build_paths import SDK_SRC_DIR, OUT_DIR, SDK_RESOURCE_DIR

sys.path.append(os.path.join(SDK_SRC_DIR, 'tools'))
import getos
import oshelpers


def RemoveBuildCruft(outdir):
  for root, _, files in os.walk(outdir):
    for f in files:
      path = os.path.join(root, f)
      ext = os.path.splitext(path)[1]
      # Remove unwanted files from the package. Also remove manifest.json files
      # (which we usually want). These ones are the manifests of the invidual
      # examples, though, which CWS complains about. The master manifest.json
      # is generated after we call RemoveBuildCruft.
      if (ext in ('.d', '.o') or
          f == 'dir.stamp' or
          f == 'manifest.json' or
          re.search(r'_unstripped_.*?\.nexe', f)):
        buildbot_common.RemoveFile(path)


def StripNexes(outdir, platform, pepperdir):
  for root, _, files in os.walk(outdir):
    for f in files:
      path = os.path.join(root, f)
      m = re.search(r'lib(32|64).*\.so', path)
      arch = None
      if m:
        # System .so file. Must be x86, because ARM doesn't support glibc yet.
        arch = 'x86_' + m.group(1)
      else:
        basename, ext = os.path.splitext(f)
        if ext in ('.nexe', '.so'):
          # We can get the arch from the filename...
          valid_arches = ('x86_64', 'x86_32', 'arm')
          for a in valid_arches:
            if basename.endswith(a):
              arch = a
              break
      if not arch:
        continue

      strip = GetStrip(pepperdir, platform, arch, 'newlib')
      buildbot_common.Run([strip, path])


def GetStrip(pepperdir, platform, arch, toolchain):
  base_arch = {'x86_32': 'x86', 'x86_64': 'x86', 'arm': 'arm'}[arch]
  bin_dir = os.path.join(pepperdir, 'toolchain',
                         '%s_%s_%s' % (platform, base_arch, toolchain), 'bin')
  strip_prefix = {'x86_32': 'i686', 'x86_64': 'x86_64', 'arm': 'arm'}[arch]
  strip_name = '%s-nacl-strip' % strip_prefix
  return os.path.join(bin_dir, strip_name)


def main(args):
  parser = argparse.ArgumentParser()
  parser.add_argument('-c', '--channel',
      help='Channel to display in the name of the package.')

  # To setup bash completion for this command first install optcomplete
  # and then add this line to your .bashrc:
  #  complete -F _optcomplete build_app.py
  try:
    import optcomplete
    optcomplete.autocomplete(parser)
  except ImportError:
    pass

  options = parser.parse_args(args)

  if options.channel:
    if options.channel not in ('Dev', 'Beta'):
      parser.error('Unknown channel: %s' % options.channel)

  toolchains = ['newlib', 'glibc']

  pepper_ver = str(int(build_version.ChromeMajorVersion()))
  pepperdir = os.path.join(OUT_DIR, 'pepper_' + pepper_ver)
  app_dir = os.path.join(OUT_DIR, 'naclsdk_app')
  app_examples_dir = os.path.join(app_dir, 'examples')
  sdk_resources_dir = SDK_RESOURCE_DIR
  platform = getos.GetPlatform()

  buildbot_common.RemoveDir(app_dir)
  buildbot_common.MakeDir(app_dir)

  # Add some dummy directories so build_projects doesn't complain...
  buildbot_common.MakeDir(os.path.join(app_dir, 'tools'))
  buildbot_common.MakeDir(os.path.join(app_dir, 'toolchain'))

  config = 'Release'

  filters = {}
  filters['DISABLE_PACKAGE'] = False
  filters['EXPERIMENTAL'] = False
  filters['TOOLS'] = toolchains
  filters['DEST'] = ['examples/api', 'examples/getting_started',
                     'examples/demo', 'examples/tutorial']
  tree = parse_dsc.LoadProjectTree(SDK_SRC_DIR, include=filters)
  build_projects.UpdateHelpers(app_dir, clobber=True)
  build_projects.UpdateProjects(app_dir, tree, clobber=False,
                                toolchains=toolchains, configs=[config],
                                first_toolchain=True)

  # Collect permissions from each example, and aggregate them.
  def MergeLists(list1, list2):
    return list1 + [x for x in list2 if x not in list1]
  all_permissions = []
  all_socket_permissions = []
  all_filesystem_permissions = []
  for _, project in parse_dsc.GenerateProjects(tree):
    permissions = project.get('PERMISSIONS', [])
    all_permissions = MergeLists(all_permissions, permissions)
    socket_permissions = project.get('SOCKET_PERMISSIONS', [])
    all_socket_permissions = MergeLists(all_socket_permissions,
                                        socket_permissions)
    filesystem_permissions = project.get('FILESYSTEM_PERMISSIONS', [])
    all_filesystem_permissions = MergeLists(all_filesystem_permissions,
                                            filesystem_permissions)
  if all_socket_permissions:
    all_permissions.append({'socket': all_socket_permissions})
  if all_filesystem_permissions:
    all_permissions.append({'fileSystem': all_filesystem_permissions})
  pretty_permissions = json.dumps(all_permissions, sort_keys=True, indent=4)

  for filename in ['background.js', 'icon128.png']:
    buildbot_common.CopyFile(os.path.join(sdk_resources_dir, filename),
                             os.path.join(app_examples_dir, filename))

  os.environ['NACL_SDK_ROOT'] = pepperdir

  build_projects.BuildProjects(app_dir, tree, deps=False, clean=False,
                               config=config)

  RemoveBuildCruft(app_dir)
  StripNexes(app_dir, platform, pepperdir)

  # Add manifest.json after RemoveBuildCruft... that function removes the
  # manifest.json files for the individual examples.
  name = 'Native Client SDK'
  if options.channel:
    name += ' (%s)' % options.channel
  template_dict = {
    'name': name,
    'channel': options.channel,
    'description':
        'Native Client SDK examples, showing API use and key concepts.',
    'key': False,  # manifests with "key" are rejected when uploading to CWS.
    'permissions': pretty_permissions,
    'version': build_version.ChromeVersionNoTrunk()
  }
  easy_template.RunTemplateFile(
      os.path.join(sdk_resources_dir, 'manifest.json.template'),
      os.path.join(app_examples_dir, 'manifest.json'),
      template_dict)

  app_zip = os.path.join(app_dir, 'examples.zip')
  os.chdir(app_examples_dir)
  oshelpers.Zip([app_zip, '-r', '*'])

  return 0


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))