#!/usr/bin/env python

# Copyright (c) 2009 Google Inc. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
SCons generator.

This contains class definitions and supporting functions for generating
pieces of SCons files for the different types of GYP targets.
"""

import os


def WriteList(fp, list, prefix='',
                        separator=',\n    ',
                        preamble=None,
                        postamble=None):
  fp.write(preamble or '')
  fp.write((separator or ' ').join([prefix + l for l in list]))
  fp.write(postamble or '')


class TargetBase(object):
  """
  Base class for a SCons representation of a GYP target.
  """
  is_ignored = False
  target_prefix = ''
  target_suffix = ''
  def __init__(self, spec):
    self.spec = spec
  def full_product_name(self):
    """
    Returns the full name of the product being built:

      * Uses 'product_name' if it's set, else prefix + 'target_name'.
      * Prepends 'product_dir' if set.
      * Appends SCons suffix variables for the target type (or
        product_extension).
    """
    suffix = self.target_suffix
    product_extension = self.spec.get('product_extension')
    if product_extension:
      suffix = '.' + product_extension
    prefix = self.spec.get('product_prefix', self.target_prefix)
    name = self.spec['target_name']
    name = prefix + self.spec.get('product_name', name) + suffix
    product_dir = self.spec.get('product_dir')
    if product_dir:
      name = os.path.join(product_dir, name)
    else:
      name = os.path.join(self.out_dir, name)
    return name

  def write_input_files(self, fp):
    """
    Writes the definition of the input files (sources).
    """
    sources = self.spec.get('sources')
    if not sources:
      fp.write('\ninput_files = []\n')
      return
    preamble = '\ninput_files = [\n    '
    postamble = ',\n]\n'
    WriteList(fp, map(repr, sources), preamble=preamble, postamble=postamble)

  def builder_call(self):
    """
    Returns the actual SCons builder call to build this target.
    """
    name = self.full_product_name()
    return 'env.%s(env.File(%r), input_files)' % (self.builder_name, name)
  def write_target(self, fp, src_dir='', pre=''):
    """
    Writes the lines necessary to build this target.
    """
    fp.write('\n' + pre)
    fp.write('_outputs = %s\n' % self.builder_call())
    fp.write('target_files.extend(_outputs)\n')


class NoneTarget(TargetBase):
  """
  A GYP target type of 'none', implicitly or explicitly.
  """
  def write_target(self, fp, pre=''):
    fp.write('\ntarget_files.extend(input_files)\n')


class SettingsTarget(TargetBase):
  """
  A GYP target type of 'settings'.
  """
  is_ignored = True


compilable_sources_template = """
_result = []
for infile in input_files:
  if env.compilable(infile):
    if (type(infile) == type('')
        and (infile.startswith(%(src_dir)r)
             or not os.path.isabs(env.subst(infile)))):
      # Force files below the build directory by replacing all '..'
      # elements in the path with '__':
      base, ext = os.path.splitext(os.path.normpath(infile))
      base = [d == '..' and '__' or d for d in base.split('/')]
      base = os.path.join(*base)
      object = '${OBJ_DIR}/${COMPONENT_NAME}/${TARGET_NAME}/' + base
      if not infile.startswith(%(src_dir)r):
        infile = %(src_dir)r + infile
      infile = env.%(name)s(object, infile)[0]
    else:
      infile = env.%(name)s(infile)[0]
  _result.append(infile)
input_files = _result
"""

class CompilableSourcesTargetBase(TargetBase):
  """
  An abstract base class for targets that compile their source files.

  We explicitly transform compilable files into object files,
  even though SCons could infer that for us, because we want
  to control where the object file ends up.  (The implicit rules
  in SCons always put the object file next to the source file.)
  """
  intermediate_builder_name = None
  def write_target(self, fp, src_dir='', pre=''):
    if self.intermediate_builder_name is None:
      raise NotImplementedError
    if src_dir and not src_dir.endswith('/'):
      src_dir += '/'
    variables = {
        'src_dir': src_dir,
        'name': self.intermediate_builder_name,
    }
    fp.write(compilable_sources_template % variables)
    super(CompilableSourcesTargetBase, self).write_target(fp)


class ProgramTarget(CompilableSourcesTargetBase):
  """
  A GYP target type of 'executable'.
  """
  builder_name = 'GypProgram'
  intermediate_builder_name = 'StaticObject'
  target_prefix = '${PROGPREFIX}'
  target_suffix = '${PROGSUFFIX}'
  out_dir = '${TOP_BUILDDIR}'


class StaticLibraryTarget(CompilableSourcesTargetBase):
  """
  A GYP target type of 'static_library'.
  """
  builder_name = 'GypStaticLibrary'
  intermediate_builder_name = 'StaticObject'
  target_prefix = '${LIBPREFIX}'
  target_suffix = '${LIBSUFFIX}'
  out_dir = '${LIB_DIR}'


class SharedLibraryTarget(CompilableSourcesTargetBase):
  """
  A GYP target type of 'shared_library'.
  """
  builder_name = 'GypSharedLibrary'
  intermediate_builder_name = 'SharedObject'
  target_prefix = '${SHLIBPREFIX}'
  target_suffix = '${SHLIBSUFFIX}'
  out_dir = '${LIB_DIR}'


class LoadableModuleTarget(CompilableSourcesTargetBase):
  """
  A GYP target type of 'loadable_module'.
  """
  builder_name = 'GypLoadableModule'
  intermediate_builder_name = 'SharedObject'
  target_prefix = '${SHLIBPREFIX}'
  target_suffix = '${SHLIBSUFFIX}'
  out_dir = '${TOP_BUILDDIR}'


TargetMap = {
  None : NoneTarget,
  'none' : NoneTarget,
  'settings' : SettingsTarget,
  'executable' : ProgramTarget,
  'static_library' : StaticLibraryTarget,
  'shared_library' : SharedLibraryTarget,
  'loadable_module' : LoadableModuleTarget,
}

def Target(spec):
  return TargetMap[spec.get('type')](spec)
