1#!/usr/bin/env python
2
3# Copyright (c) 2009 Google Inc. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""
8SCons generator.
9
10This contains class definitions and supporting functions for generating
11pieces of SCons files for the different types of GYP targets.
12"""
13
14import os
15
16
17def WriteList(fp, list, prefix='',
18                        separator=',\n    ',
19                        preamble=None,
20                        postamble=None):
21  fp.write(preamble or '')
22  fp.write((separator or ' ').join([prefix + l for l in list]))
23  fp.write(postamble or '')
24
25
26class TargetBase(object):
27  """
28  Base class for a SCons representation of a GYP target.
29  """
30  is_ignored = False
31  target_prefix = ''
32  target_suffix = ''
33  def __init__(self, spec):
34    self.spec = spec
35  def full_product_name(self):
36    """
37    Returns the full name of the product being built:
38
39      * Uses 'product_name' if it's set, else prefix + 'target_name'.
40      * Prepends 'product_dir' if set.
41      * Appends SCons suffix variables for the target type (or
42        product_extension).
43    """
44    suffix = self.target_suffix
45    product_extension = self.spec.get('product_extension')
46    if product_extension:
47      suffix = '.' + product_extension
48    prefix = self.spec.get('product_prefix', self.target_prefix)
49    name = self.spec['target_name']
50    name = prefix + self.spec.get('product_name', name) + suffix
51    product_dir = self.spec.get('product_dir')
52    if product_dir:
53      name = os.path.join(product_dir, name)
54    else:
55      name = os.path.join(self.out_dir, name)
56    return name
57
58  def write_input_files(self, fp):
59    """
60    Writes the definition of the input files (sources).
61    """
62    sources = self.spec.get('sources')
63    if not sources:
64      fp.write('\ninput_files = []\n')
65      return
66    preamble = '\ninput_files = [\n    '
67    postamble = ',\n]\n'
68    WriteList(fp, map(repr, sources), preamble=preamble, postamble=postamble)
69
70  def builder_call(self):
71    """
72    Returns the actual SCons builder call to build this target.
73    """
74    name = self.full_product_name()
75    return 'env.%s(env.File(%r), input_files)' % (self.builder_name, name)
76  def write_target(self, fp, src_dir='', pre=''):
77    """
78    Writes the lines necessary to build this target.
79    """
80    fp.write('\n' + pre)
81    fp.write('_outputs = %s\n' % self.builder_call())
82    fp.write('target_files.extend(_outputs)\n')
83
84
85class NoneTarget(TargetBase):
86  """
87  A GYP target type of 'none', implicitly or explicitly.
88  """
89  def write_target(self, fp, pre=''):
90    fp.write('\ntarget_files.extend(input_files)\n')
91
92
93class SettingsTarget(TargetBase):
94  """
95  A GYP target type of 'settings'.
96  """
97  is_ignored = True
98
99
100compilable_sources_template = """
101_result = []
102for infile in input_files:
103  if env.compilable(infile):
104    if (type(infile) == type('')
105        and (infile.startswith(%(src_dir)r)
106             or not os.path.isabs(env.subst(infile)))):
107      # Force files below the build directory by replacing all '..'
108      # elements in the path with '__':
109      base, ext = os.path.splitext(os.path.normpath(infile))
110      base = [d == '..' and '__' or d for d in base.split('/')]
111      base = os.path.join(*base)
112      object = '${OBJ_DIR}/${COMPONENT_NAME}/${TARGET_NAME}/' + base
113      if not infile.startswith(%(src_dir)r):
114        infile = %(src_dir)r + infile
115      infile = env.%(name)s(object, infile)[0]
116    else:
117      infile = env.%(name)s(infile)[0]
118  _result.append(infile)
119input_files = _result
120"""
121
122class CompilableSourcesTargetBase(TargetBase):
123  """
124  An abstract base class for targets that compile their source files.
125
126  We explicitly transform compilable files into object files,
127  even though SCons could infer that for us, because we want
128  to control where the object file ends up.  (The implicit rules
129  in SCons always put the object file next to the source file.)
130  """
131  intermediate_builder_name = None
132  def write_target(self, fp, src_dir='', pre=''):
133    if self.intermediate_builder_name is None:
134      raise NotImplementedError
135    if src_dir and not src_dir.endswith('/'):
136      src_dir += '/'
137    variables = {
138        'src_dir': src_dir,
139        'name': self.intermediate_builder_name,
140    }
141    fp.write(compilable_sources_template % variables)
142    super(CompilableSourcesTargetBase, self).write_target(fp)
143
144
145class ProgramTarget(CompilableSourcesTargetBase):
146  """
147  A GYP target type of 'executable'.
148  """
149  builder_name = 'GypProgram'
150  intermediate_builder_name = 'StaticObject'
151  target_prefix = '${PROGPREFIX}'
152  target_suffix = '${PROGSUFFIX}'
153  out_dir = '${TOP_BUILDDIR}'
154
155
156class StaticLibraryTarget(CompilableSourcesTargetBase):
157  """
158  A GYP target type of 'static_library'.
159  """
160  builder_name = 'GypStaticLibrary'
161  intermediate_builder_name = 'StaticObject'
162  target_prefix = '${LIBPREFIX}'
163  target_suffix = '${LIBSUFFIX}'
164  out_dir = '${LIB_DIR}'
165
166
167class SharedLibraryTarget(CompilableSourcesTargetBase):
168  """
169  A GYP target type of 'shared_library'.
170  """
171  builder_name = 'GypSharedLibrary'
172  intermediate_builder_name = 'SharedObject'
173  target_prefix = '${SHLIBPREFIX}'
174  target_suffix = '${SHLIBSUFFIX}'
175  out_dir = '${LIB_DIR}'
176
177
178class LoadableModuleTarget(CompilableSourcesTargetBase):
179  """
180  A GYP target type of 'loadable_module'.
181  """
182  builder_name = 'GypLoadableModule'
183  intermediate_builder_name = 'SharedObject'
184  target_prefix = '${SHLIBPREFIX}'
185  target_suffix = '${SHLIBSUFFIX}'
186  out_dir = '${TOP_BUILDDIR}'
187
188
189TargetMap = {
190  None : NoneTarget,
191  'none' : NoneTarget,
192  'settings' : SettingsTarget,
193  'executable' : ProgramTarget,
194  'static_library' : StaticLibraryTarget,
195  'shared_library' : SharedLibraryTarget,
196  'loadable_module' : LoadableModuleTarget,
197}
198
199def Target(spec):
200  return TargetMap[spec.get('type')](spec)
201