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

"""Build script to generate a new sdk_tools bundle.

This script packages the files necessary to generate the SDK updater -- the
tool users run to download new bundles, update existing bundles, etc.
"""

import argparse
import buildbot_common
import build_version
import glob
import os
import sys

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SDK_SRC_DIR = os.path.dirname(SCRIPT_DIR)
SDK_DIR = os.path.dirname(SDK_SRC_DIR)
SRC_DIR = os.path.dirname(SDK_DIR)
NACL_DIR = os.path.join(SRC_DIR, 'native_client')
CYGTAR = os.path.join(NACL_DIR, 'build', 'cygtar.py')

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

import oshelpers


UPDATER_FILES = [
  # launch scripts
  ('build_tools/naclsdk', 'nacl_sdk/naclsdk'),
  ('build_tools/naclsdk.bat', 'nacl_sdk/naclsdk.bat'),

  # base manifest
  ('build_tools/json/naclsdk_manifest0.json',
      'nacl_sdk/sdk_cache/naclsdk_manifest2.json'),

  # SDK tools
  ('build_tools/sdk_tools/cacerts.txt', 'nacl_sdk/sdk_tools/cacerts.txt'),
  ('build_tools/sdk_tools/*.py', 'nacl_sdk/sdk_tools/'),
  ('build_tools/sdk_tools/command/*.py', 'nacl_sdk/sdk_tools/command/'),
  ('build_tools/sdk_tools/third_party/*.py', 'nacl_sdk/sdk_tools/third_party/'),
  ('build_tools/sdk_tools/third_party/fancy_urllib/*.py',
      'nacl_sdk/sdk_tools/third_party/fancy_urllib/'),
  ('build_tools/sdk_tools/third_party/fancy_urllib/README.chromium',
      'nacl_sdk/sdk_tools/third_party/fancy_urllib/README.chromium'),
  ('build_tools/manifest_util.py', 'nacl_sdk/sdk_tools/manifest_util.py'),
  ('LICENSE', 'nacl_sdk/sdk_tools/LICENSE'),
  (CYGTAR, 'nacl_sdk/sdk_tools/cygtar.py'),
]


def MakeUpdaterFilesAbsolute(out_dir):
  """Return the result of changing all relative paths in UPDATER_FILES to
  absolute paths.

  Args:
    out_dir: The output directory.
  Returns:
    A list of 2-tuples. The first element in each tuple is the source path and
    the second is the destination path.
  """
  assert os.path.isabs(out_dir)

  result = []
  for in_file, out_file in UPDATER_FILES:
    if not os.path.isabs(in_file):
      in_file = os.path.join(SDK_SRC_DIR, in_file)
    out_file = os.path.join(out_dir, out_file)
    result.append((in_file, out_file))
  return result


def GlobFiles(files):
  """Expand wildcards for 2-tuples of sources/destinations.

  This function also will convert destinations from directories into filenames.
  For example:
    ('foo/*.py', 'bar/') => [('foo/a.py', 'bar/a.py'), ('foo/b.py', 'bar/b.py')]

  Args:
    files: A list of 2-tuples of (source, dest) paths.
  Returns:
    A new list of 2-tuples, after the sources have been wildcard-expanded, and
    the destinations have been changed from directories to filenames.
  """
  result = []
  for in_file_glob, out_file in files:
    if out_file.endswith('/'):
      for in_file in glob.glob(in_file_glob):
        result.append((in_file,
                      os.path.join(out_file, os.path.basename(in_file))))
    else:
      result.append((in_file_glob, out_file))
  return result


def CopyFiles(files):
  """Given a list of 2-tuples (source, dest), copy each source file to a dest
  file.

  Args:
    files: A list of 2-tuples."""
  for in_file, out_file in files:
    buildbot_common.MakeDir(os.path.dirname(out_file))
    buildbot_common.CopyFile(in_file, out_file)


def UpdateRevisionNumber(out_dir, revision_number):
  """Update the sdk_tools bundle to have the given revision number.

  This function finds all occurrences of the string "{REVISION}" in
  sdk_update_main.py and replaces them with |revision_number|. The only
  observable effect of this change should be that running:

    naclsdk -v

  will contain the new |revision_number|.

  Args:
    out_dir: The output directory containing the scripts to update.
    revision_number: The revision number as an integer, or None to use the
        current Chrome revision (as retrieved through svn/git).
  """
  if revision_number is None:
    revision_number = build_version.ChromeRevision()

  SDK_UPDATE_MAIN = os.path.join(out_dir,
      'nacl_sdk/sdk_tools/sdk_update_main.py')

  contents = open(SDK_UPDATE_MAIN, 'r').read().replace(
      '{REVISION}', str(revision_number))
  open(SDK_UPDATE_MAIN, 'w').write(contents)


def BuildUpdater(out_dir, revision_number=None):
  """Build naclsdk.zip and sdk_tools.tgz in |out_dir|.

  Args:
    out_dir: The output directory.
    revision_number: The revision number of this updater, as an integer. Or
        None, to use the current Chrome revision."""
  out_dir = os.path.abspath(out_dir)

  # Build SDK directory
  buildbot_common.RemoveDir(os.path.join(out_dir, 'nacl_sdk'))

  updater_files = MakeUpdaterFilesAbsolute(out_dir)
  updater_files = GlobFiles(updater_files)

  CopyFiles(updater_files)
  UpdateRevisionNumber(out_dir, revision_number)

  out_files = [os.path.relpath(out_file, out_dir)
               for _, out_file in updater_files]

  # Make zip
  buildbot_common.RemoveFile(os.path.join(out_dir, 'nacl_sdk.zip'))
  buildbot_common.Run([sys.executable, oshelpers.__file__, 'zip',
                      'nacl_sdk.zip'] + out_files,
                      cwd=out_dir)

  # Tar of all files under nacl_sdk/sdk_tools
  sdktoolsdir = os.path.join('nacl_sdk', 'sdk_tools')
  tarname = os.path.join(out_dir, 'sdk_tools.tgz')
  files_to_tar = [os.path.relpath(out_file, sdktoolsdir)
      for out_file in out_files if out_file.startswith(sdktoolsdir)]
  buildbot_common.RemoveFile(tarname)
  buildbot_common.Run([sys.executable, CYGTAR, '-C',
      os.path.join(out_dir, sdktoolsdir), '-czf', tarname] + files_to_tar)
  sys.stdout.write('\n')


def main(args):
  parser = argparse.ArgumentParser(description=__doc__)
  parser.add_argument('-o', '--out', help='output directory',
      dest='out_dir', default=os.path.join(SRC_DIR, 'out'))
  parser.add_argument('-r', '--revision', dest='revision', default=None,
      help='revision number of this updater')
  parser.add_argument('-v', '--verbose', help='verbose output')
  options = parser.parse_args(args)

  buildbot_common.verbose = options.verbose

  if options.revision:
    options.revision = int(options.revision)
  BuildUpdater(options.out_dir, options.revision)


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