~svn/ubuntu/raring/subversion/ppa

« back to all changes in this revision

Viewing changes to build/generator/gen_base.py

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-12-05 01:26:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051205012614-qom4xfypgtsqc2xq
Tags: 1.2.3dfsg1-3ubuntu1
Merge with the final Debian release of 1.2.3dfsg1-3, bringing in
fixes to the clean target, better documentation of the libdb4.3
upgrade and build fixes to work with swig1.3_1.3.27.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# gen_base.py -- infrastructure for generating makefiles, dependencies, etc.
 
3
#
 
4
 
 
5
import os
 
6
import sys
 
7
import string
 
8
import glob
 
9
import re
 
10
import fileinput
 
11
import ConfigParser
 
12
 
 
13
import getversion
 
14
 
 
15
 
 
16
class GeneratorBase:
 
17
 
 
18
  #
 
19
  # Derived classes should define a class attribute named _extension_map.
 
20
  # This attribute should be a dictionary of the form:
 
21
  #     { (target-type, file-type): file-extension ...}
 
22
  #
 
23
  # where: target-type is 'exe', 'lib', ...
 
24
  #        file-type is 'target', 'object', ...
 
25
  #
 
26
 
 
27
  def __init__(self, fname, verfname, options=None):
 
28
    # Retrieve major version from the C header, to avoid duplicating it in
 
29
    # build.conf - it is required because some file names include it.
 
30
    try:
 
31
      vsn_parser = getversion.Parser()
 
32
      vsn_parser.search('SVN_VER_MAJOR', 'libver')
 
33
      self.version = vsn_parser.parse(verfname).libver
 
34
    except:
 
35
      raise GenError('Unable to extract version.')
 
36
 
 
37
    # Now read and parse build.conf
 
38
    parser = ConfigParser.ConfigParser()
 
39
    parser.read(fname)
 
40
 
 
41
    self.sections = { }
 
42
    self.graph = DependencyGraph()
 
43
 
 
44
    # Allow derived classes to suppress certain configuration sections
 
45
    if not hasattr(self, 'skip_sections'):
 
46
      self.skip_sections = { }
 
47
 
 
48
    # The 'options' section does not represent a build target,
 
49
    # it simply contains global options
 
50
    self.skip_sections['options'] = None
 
51
 
 
52
    # Read in the global options
 
53
    self.includes = \
 
54
        _collect_paths(parser.get('options', 'includes'))
 
55
    self.apache_files = \
 
56
        _collect_paths(parser.get('options', 'static-apache-files'))
 
57
    self.scripts = \
 
58
        _collect_paths(parser.get('options', 'test-scripts'))
 
59
    self.bdb_scripts = \
 
60
        _collect_paths(parser.get('options', 'bdb-test-scripts'))
 
61
 
 
62
    self.swig_lang = string.split(parser.get('options', 'swig-languages'))
 
63
    self.swig_dirs = string.split(parser.get('options', 'swig-dirs'))
 
64
 
 
65
    # Visual C++ projects - contents are either TargetProject instances,
 
66
    # or other targets with an external-project attribute.
 
67
    self.projects = []
 
68
 
 
69
    # Lists of pathnames of various kinds
 
70
    self.test_deps = []      # Non-BDB dependent items to build for the tests
 
71
    self.test_progs = []     # Subset of the above to actually execute
 
72
    self.bdb_test_deps = []  # BDB-dependent items to build for the tests
 
73
    self.bdb_test_progs = [] # Subset of the above to actually execute
 
74
    self.target_dirs = []    # Directories in which files are built
 
75
    self.manpages = []       # Manpages
 
76
 
 
77
    # Collect the build targets
 
78
    parser_sections = parser.sections()
 
79
    parser_sections.sort() # Have a reproducible ordering
 
80
    for section_name in parser_sections:
 
81
      if self.skip_sections.has_key(section_name):
 
82
        continue
 
83
 
 
84
      options = {}
 
85
      for option in parser.options(section_name):
 
86
        options[option] = parser.get(section_name, option)
 
87
 
 
88
      type = options.get('type')
 
89
 
 
90
      target_class = _build_types.get(type)
 
91
      if not target_class:
 
92
        raise GenError('ERROR: unknown build type for ' + section_name)
 
93
 
 
94
      section = target_class.Section(target_class, section_name, options, self)
 
95
 
 
96
      self.sections[section_name] = section
 
97
 
 
98
      section.create_targets()
 
99
 
 
100
    # Compute intra-library dependencies
 
101
    for section in self.sections.values():
 
102
      dependencies = (( DT_LINK,   section.options.get('libs',    "") ),
 
103
                      ( DT_NONLIB, section.options.get('nonlibs', "") ))
 
104
 
 
105
      for dep_type, dep_names in dependencies:
 
106
        # Translate string names to Section objects
 
107
        dep_section_objects = []
 
108
        for section_name in string.split(dep_names):
 
109
          if self.sections.has_key(section_name):
 
110
            dep_section_objects.append(self.sections[section_name])
 
111
 
 
112
        # For each dep_section that this section declares a dependency on,
 
113
        # take the targets of this section, and register a dependency on
 
114
        # any 'matching' targets of the dep_section.
 
115
        #
 
116
        # At the moment, the concept of multiple targets per section is
 
117
        # employed only for the SWIG modules, which have 1 target
 
118
        # per language. Then, 'matching' means being of the same language.
 
119
        for dep_section in dep_section_objects:
 
120
          for target in section.get_targets():
 
121
            self.graph.bulk_add(dep_type, target.name,
 
122
                                dep_section.get_dep_targets(target))
 
123
 
 
124
  def compute_hdr_deps(self):
 
125
    all_includes = map(native_path, self.includes)
 
126
    for d in unique(self.target_dirs):
 
127
      hdrs = glob.glob(os.path.join(native_path(d), '*.h'))
 
128
      all_includes.extend(hdrs)
 
129
 
 
130
    include_deps = IncludeDependencyInfo(all_includes)
 
131
 
 
132
    for objectfile, sources in self.graph.get_deps(DT_OBJECT):
 
133
      assert len(sources) == 1
 
134
      source = sources[0]
 
135
 
 
136
      # Generated .c files must depend on all headers their parent .i file
 
137
      # includes
 
138
      if isinstance(objectfile, SWIGObject):
 
139
        swigsources = self.graph.get_sources(DT_SWIG_C, source)
 
140
        assert len(swigsources) == 1
 
141
        ifile = swigsources[0]
 
142
        assert isinstance(ifile, SWIGSource)
 
143
 
 
144
        for include_file in include_deps.query(native_path(ifile.filename)):
 
145
          self.graph.add(DT_SWIG_C, source, build_path(include_file))
 
146
 
 
147
      # Any non-swig C/C++ object must depend on the headers it's parent
 
148
      # .c or .cpp includes. Note that 'object' includes gettext .mo files,
 
149
      # Java .class files, and .h files generated from Java classes, so
 
150
      # we must filter here.
 
151
      elif isinstance(source, SourceFile) and \
 
152
          os.path.splitext(source.filename)[1] in ('.c', '.cpp'):
 
153
        for include_file in include_deps.query(native_path(source.filename)):
 
154
          self.graph.add(DT_OBJECT, objectfile, build_path(include_file))
 
155
 
 
156
 
 
157
class DependencyGraph:
 
158
  """Record dependencies between build items.
 
159
 
 
160
  See the DT_* values for the different dependency types. For each type,
 
161
  the target and source objects recorded will be different. They could
 
162
  be file names, Target objects, install types, etc.
 
163
  """
 
164
 
 
165
  def __init__(self):
 
166
    self.deps = { }     # type -> { target -> [ source ... ] }
 
167
    for dt in dep_types:
 
168
      self.deps[dt] = { }
 
169
 
 
170
  def add(self, type, target, source):
 
171
    if self.deps[type].has_key(target):
 
172
      self.deps[type][target].append(source)
 
173
    else:
 
174
      self.deps[type][target] = [ source ]
 
175
      
 
176
  def bulk_add(self, type, target, sources):
 
177
    if self.deps[type].has_key(target):
 
178
      self.deps[type][target].extend(sources)
 
179
    else:
 
180
      self.deps[type][target] = sources[:]  
 
181
 
 
182
  def get_sources(self, type, target, cls=None):
 
183
    sources = self.deps[type].get(target, [ ])
 
184
    if not cls:
 
185
      return sources
 
186
    filtered = [ ]
 
187
    for src in sources:
 
188
      if isinstance(src, cls):
 
189
        filtered.append(src)
 
190
    return filtered
 
191
 
 
192
  def get_all_sources(self, type):
 
193
    sources = [ ]
 
194
    for group in self.deps[type].values():
 
195
      sources.extend(group)
 
196
    return sources
 
197
 
 
198
  def get_deps(self, type):
 
199
    return self.deps[type].items()
 
200
 
 
201
# dependency types
 
202
dep_types = [
 
203
  'DT_INSTALL',  # install areas. e.g. 'lib', 'base-lib'
 
204
  'DT_OBJECT',   # an object filename, depending upon .c filenames
 
205
  'DT_SWIG_C',   # a swig-generated .c file, depending upon .i filename(s)
 
206
  'DT_LINK',     # a libtool-linked filename, depending upon object fnames
 
207
  'DT_NONLIB',   # filename depends on object fnames, but isn't linked to them
 
208
  ]
 
209
 
 
210
# create some variables for these
 
211
for _dt in dep_types:
 
212
  # e.g. DT_INSTALL = 'DT_INSTALL'
 
213
  globals()[_dt] = _dt
 
214
 
 
215
class DependencyNode:
 
216
  def __init__(self, filename):
 
217
    self.filename = filename
 
218
 
 
219
  def __str__(self):
 
220
    return self.filename
 
221
 
 
222
class ObjectFile(DependencyNode):
 
223
  def __init__(self, filename, compile_cmd = None):
 
224
    DependencyNode.__init__(self, filename)
 
225
    self.compile_cmd = compile_cmd
 
226
    self.source_generated = 0
 
227
 
 
228
class SWIGObject(ObjectFile):
 
229
  def __init__(self, filename, lang):
 
230
    ObjectFile.__init__(self, filename)
 
231
    self.lang = lang
 
232
    self.lang_abbrev = lang_abbrev[lang]
 
233
    ### hmm. this is Makefile-specific
 
234
    self.compile_cmd = '$(COMPILE_%s_WRAPPER)' % string.upper(self.lang_abbrev)
 
235
    self.source_generated = 1
 
236
 
 
237
class HeaderFile(DependencyNode):
 
238
  def __init__(self, filename, classname = None, compile_cmd = None):
 
239
    DependencyNode.__init__(self, filename)
 
240
    self.classname = classname
 
241
    self.compile_cmd = compile_cmd
 
242
 
 
243
class SourceFile(DependencyNode):
 
244
  def __init__(self, filename, reldir):
 
245
    DependencyNode.__init__(self, filename)
 
246
    self.reldir = reldir
 
247
 
 
248
class SWIGSource(SourceFile):
 
249
  def __init__(self, filename):
 
250
    SourceFile.__init__(self, filename, build_path_dirname(filename))
 
251
  pass
 
252
 
 
253
lang_abbrev = {
 
254
  'python' : 'py',
 
255
  'perl' : 'pl',
 
256
  'ruby' : 'rb',
 
257
  }
 
258
 
 
259
lang_full_name = {
 
260
  'python' : 'Python',
 
261
  'perl' : 'Perl',
 
262
  'ruby' : 'Ruby',
 
263
  }
 
264
 
 
265
lang_utillib_suffix = {
 
266
  'python' : 'py',
 
267
  'perl' : 'perl',
 
268
  'ruby' : 'ruby',
 
269
  }
 
270
  
 
271
class Target(DependencyNode):
 
272
  "A build target is a node in our dependency graph."
 
273
 
 
274
  def __init__(self, name, options, gen_obj):
 
275
    self.name = name
 
276
    self.gen_obj = gen_obj
 
277
    self.desc = options.get('description')
 
278
    self.path = options.get('path', '')
 
279
    self.add_deps = options.get('add-deps', '')
 
280
    self.add_install_deps = options.get('add-install-deps', '')
 
281
    self.msvc_name = options.get('msvc-name') # override project name
 
282
 
 
283
  def add_dependencies(self):
 
284
    # subclasses should override to provide behavior, as appropriate
 
285
    raise NotImplementedError
 
286
 
 
287
  class Section:
 
288
    """Represents an individual section of build.conf
 
289
    
 
290
    The Section class is sort of a factory class which is responsible for
 
291
    creating and keeping track of Target instances associated with a section
 
292
    of the configuration file. By default it only allows one Target per 
 
293
    section, but subclasses may create multiple Targets.
 
294
    """
 
295
 
 
296
    def __init__(self, target_class, name, options, gen_obj):
 
297
      self.target_class = target_class
 
298
      self.name = name
 
299
      self.options = options
 
300
      self.gen_obj = gen_obj
 
301
 
 
302
    def create_targets(self):
 
303
      """Create target instances"""
 
304
      self.target = self.target_class(self.name, self.options, self.gen_obj)
 
305
      self.target.add_dependencies()
 
306
 
 
307
    def get_targets(self):
 
308
      """Return list of target instances associated with this section"""
 
309
      return [self.target]
 
310
 
 
311
    def get_dep_targets(self, target):
 
312
      """Return list of targets from this section that "target" depends on"""
 
313
      return [self.target]
 
314
 
 
315
class TargetLinked(Target):
 
316
  "The target is linked (by libtool) against other libraries."
 
317
 
 
318
  def __init__(self, name, options, gen_obj):
 
319
    Target.__init__(self, name, options, gen_obj)
 
320
    self.install = options.get('install')
 
321
    self.compile_cmd = options.get('compile-cmd')
 
322
    self.sources = options.get('sources', '*.c')
 
323
    self.link_cmd = options.get('link-cmd', '$(LINK)')
 
324
 
 
325
    self.external_lib = options.get('external-lib')
 
326
    self.external_project = options.get('external-project')
 
327
    self.msvc_libs = string.split(options.get('msvc-libs', ''))
 
328
 
 
329
  def add_dependencies(self):
 
330
    if self.external_lib or self.external_project:
 
331
      if self.external_project:
 
332
        self.gen_obj.projects.append(self)
 
333
      return
 
334
 
 
335
    # the specified install area depends upon this target
 
336
    self.gen_obj.graph.add(DT_INSTALL, self.install, self)
 
337
 
 
338
    sources = _collect_paths(self.sources or '*.c', self.path)
 
339
    sources.sort()
 
340
 
 
341
    for src, reldir in sources:
 
342
      if src[-2:] == '.c':
 
343
        objname = src[:-2] + self.objext
 
344
      elif src[-4:] == '.cpp':
 
345
        objname = src[:-4] + self.objext
 
346
      else:
 
347
        raise GenError('ERROR: unknown file extension on ' + src)
 
348
 
 
349
      ofile = ObjectFile(objname, self.compile_cmd)
 
350
 
 
351
      # object depends upon source
 
352
      self.gen_obj.graph.add(DT_OBJECT, ofile, SourceFile(src, reldir))
 
353
 
 
354
      # target (a linked item) depends upon object
 
355
      self.gen_obj.graph.add(DT_LINK, self.name, ofile)
 
356
 
 
357
    # collect all the paths where stuff might get built
 
358
    ### we should collect this from the dependency nodes rather than
 
359
    ### the sources. "what dir are you going to put yourself into?"
 
360
    self.gen_obj.target_dirs.append(self.path)
 
361
    for pattern in string.split(self.sources):
 
362
      dirname = build_path_dirname(pattern)
 
363
      if dirname:
 
364
        self.gen_obj.target_dirs.append(build_path_join(self.path, dirname))
 
365
 
 
366
class TargetExe(TargetLinked):
 
367
  def __init__(self, name, options, gen_obj):
 
368
    TargetLinked.__init__(self, name, options, gen_obj)
 
369
 
 
370
    if not (self.external_lib or self.external_project):
 
371
      extmap = self.gen_obj._extension_map
 
372
      self.objext = extmap['exe', 'object']
 
373
      self.filename = build_path_join(self.path, name + extmap['exe', 'target'])
 
374
 
 
375
    self.manpages = options.get('manpages', '')
 
376
    self.testing = options.get('testing')
 
377
 
 
378
  def add_dependencies(self):
 
379
    TargetLinked.add_dependencies(self)
 
380
 
 
381
    # collect test programs
 
382
    if self.install == 'test':
 
383
      self.gen_obj.test_deps.append(self.filename)
 
384
      if self.testing != 'skip':
 
385
        self.gen_obj.test_progs.append(self.filename)
 
386
    elif self.install == 'bdb-test':
 
387
      self.gen_obj.bdb_test_deps.append(self.filename)
 
388
      if self.testing != 'skip':
 
389
        self.gen_obj.bdb_test_progs.append(self.filename)
 
390
 
 
391
    self.gen_obj.manpages.extend(string.split(self.manpages))
 
392
 
 
393
class TargetScript(Target):
 
394
  def add_dependencies(self):
 
395
    # we don't need to "compile" the sources, so there are no dependencies
 
396
    # to add here, except to get the script installed in the proper area.
 
397
    # note that the script might itself be generated, but that isn't a
 
398
    # concern here.
 
399
    self.gen_obj.graph.add(DT_INSTALL, self.install, self)
 
400
 
 
401
class TargetLib(TargetLinked):
 
402
  def __init__(self, name, options, gen_obj):
 
403
    TargetLinked.__init__(self, name, options, gen_obj)
 
404
 
 
405
    if not (self.external_lib or self.external_project):
 
406
      extmap = gen_obj._extension_map
 
407
      self.objext = extmap['lib', 'object']
 
408
 
 
409
      # the target file is the name, version, and appropriate extension
 
410
      tfile = '%s-%s%s' % (name, gen_obj.version, extmap['lib', 'target'])
 
411
      self.filename = build_path_join(self.path, tfile)
 
412
 
 
413
    # Is a library referencing symbols which are undefined at link time.
 
414
    self.undefined_lib_symbols = options.get('undefined-lib-symbols') == 'yes'
 
415
 
 
416
    self.msvc_static = options.get('msvc-static') == 'yes' # is a static lib
 
417
    self.msvc_fake = options.get('msvc-fake') == 'yes' # has fake target
 
418
    self.msvc_export = string.split(options.get('msvc-export', ''))
 
419
 
 
420
class TargetApacheMod(TargetLib):
 
421
 
 
422
  def __init__(self, name, options, gen_obj):
 
423
    TargetLib.__init__(self, name, options, gen_obj)
 
424
 
 
425
    tfile = name + self.gen_obj._extension_map['lib', 'target']
 
426
    self.filename = build_path_join(self.path, tfile)
 
427
 
 
428
    # we have a custom linking rule
 
429
    ### hmm. this is Makefile-specific
 
430
    self.compile_cmd = '$(COMPILE_APACHE_MOD)'
 
431
    self.link_cmd = '$(LINK_APACHE_MOD)'
 
432
 
 
433
class TargetRaModule(TargetLib):
 
434
  pass
 
435
 
 
436
class TargetFsModule(TargetLib):
 
437
  pass
 
438
 
 
439
class TargetDoc(Target):
 
440
  pass
 
441
 
 
442
class TargetI18N(Target):
 
443
  "The target is a collection of .po files to be compiled by msgfmt."
 
444
 
 
445
  def __init__(self, name, options, gen_obj):
 
446
    Target.__init__(self, name, options, gen_obj)
 
447
    self.install = options.get('install')
 
448
    self.sources = options.get('sources')
 
449
    # Let the Makefile determine this via .SUFFIXES
 
450
    self.compile_cmd = None
 
451
    self.objext = '.mo'
 
452
    self.external_project = options.get('external-project')
 
453
 
 
454
  def add_dependencies(self):
 
455
    self.gen_obj.graph.add(DT_INSTALL, self.install, self)
 
456
 
 
457
    sources = _collect_paths(self.sources or '*.po', self.path)
 
458
    sources.sort()
 
459
 
 
460
    for src, reldir in sources:
 
461
      if src[-3:] == '.po':
 
462
        objname = src[:-3] + self.objext
 
463
      else:
 
464
        raise GenError('ERROR: unknown file extension on ' + src)
 
465
 
 
466
      ofile = ObjectFile(objname, self.compile_cmd)
 
467
 
 
468
      # object depends upon source
 
469
      self.gen_obj.graph.add(DT_OBJECT, ofile, SourceFile(src, reldir))
 
470
 
 
471
      # target depends upon object
 
472
      self.gen_obj.graph.add(DT_LINK, self.name, ofile)
 
473
 
 
474
    # Add us to the list of target dirs, so we're created in mkdir-init.
 
475
    self.gen_obj.target_dirs.append(self.path)
 
476
 
 
477
class TargetSWIG(TargetLib):
 
478
  def __init__(self, name, options, gen_obj, lang):
 
479
    TargetLib.__init__(self, name, options, gen_obj)
 
480
    self.lang = lang
 
481
    self.desc = self.desc + ' for ' + lang_full_name[lang]
 
482
    self.include_runtime = options.get('include-runtime') == 'yes'
 
483
 
 
484
    ### hmm. this is Makefile-specific
 
485
    self.link_cmd = '$(LINK_%s_WRAPPER)' % string.upper(lang_abbrev[lang])
 
486
 
 
487
  def add_dependencies(self):
 
488
    sources = _collect_paths(self.sources, self.path)
 
489
    assert len(sources) == 1  ### simple assertions for now
 
490
 
 
491
    # get path to SWIG .i file
 
492
    ipath = sources[0][0]
 
493
    iname = build_path_basename(ipath)
 
494
 
 
495
    assert iname[-2:] == '.i'
 
496
    cname = iname[:-2] + '.c'
 
497
    oname = iname[:-2] + self.gen_obj._extension_map['lib', 'object']
 
498
 
 
499
    # Extract SWIG module name from .i file name
 
500
    module_name = iname[:4] != 'svn_' and iname[:-2] or iname[4:-2]
 
501
 
 
502
    lib_extension = self.gen_obj._extension_map['lib', 'target']
 
503
    if self.lang == "ruby":
 
504
      lib_filename = module_name + lib_extension
 
505
    elif self.lang == "perl":
 
506
      lib_filename = '_' + string.capitalize(module_name) + lib_extension
 
507
    else:
 
508
      lib_filename = '_' + module_name + lib_extension
 
509
 
 
510
    self.name = self.lang + '_' + module_name
 
511
    self.path = build_path_join(self.path, self.lang)
 
512
    self.filename = build_path_join(self.path, lib_filename)
 
513
 
 
514
    ifile = SWIGSource(ipath)
 
515
    cfile = SWIGObject(build_path_join(self.path, cname), self.lang)
 
516
    ofile = SWIGObject(build_path_join(self.path, oname), self.lang)
 
517
 
 
518
    # the .c file depends upon the .i file
 
519
    self.gen_obj.graph.add(DT_SWIG_C, cfile, ifile)
 
520
 
 
521
    # the object depends upon the .c file
 
522
    self.gen_obj.graph.add(DT_OBJECT, ofile, cfile)
 
523
 
 
524
    # the library depends upon the object
 
525
    self.gen_obj.graph.add(DT_LINK, self.name, ofile)
 
526
 
 
527
    # Some languages may depend on swig runtime libraries
 
528
    if self.lang in ('python', 'perl'):
 
529
      self.gen_obj.graph.add(DT_LINK, self.name,
 
530
                             TargetSWIGRuntime(self.lang, {}, self.gen_obj))
 
531
 
 
532
    # the specified install area depends upon the library
 
533
    self.gen_obj.graph.add(DT_INSTALL, 'swig-' + lang_abbrev[self.lang], self)
 
534
 
 
535
  class Section(TargetLib.Section):
 
536
    def create_targets(self):
 
537
      self.targets = { }
 
538
      for lang in self.gen_obj.swig_lang:
 
539
        target = self.target_class(self.name, self.options, self.gen_obj, lang)
 
540
        target.add_dependencies()
 
541
        self.targets[lang] = target
 
542
 
 
543
    def get_targets(self):
 
544
      return self.targets.values()
 
545
 
 
546
    def get_dep_targets(self, target):
 
547
      target = self.targets.get(target.lang, None)
 
548
      return target and [target] or [ ]
 
549
 
 
550
class TargetSWIGRuntime(TargetLinked):
 
551
  def __init__(self, lang, options, gen_obj):
 
552
    name = "<SWIG Runtime Library for " + lang_full_name[lang] + ">"
 
553
    TargetLinked.__init__(self, name, options, gen_obj)
 
554
    self.external_lib = "$(LSWIG" + string.upper(lang_abbrev[lang]) + ")"
 
555
 
 
556
class TargetSWIGLib(TargetLib):
 
557
  def __init__(self, name, options, gen_obj):
 
558
    TargetLib.__init__(self, name, options, gen_obj)
 
559
    self.lang = options.get('lang')
 
560
 
 
561
  def add_dependencies(self):
 
562
    TargetLib.add_dependencies(self)
 
563
    # Some languages may depend on swig runtime libraries
 
564
    if self.lang in ('python', 'perl'):
 
565
      self.gen_obj.graph.add(DT_LINK, self.name,
 
566
                             TargetSWIGRuntime(self.lang, {}, self.gen_obj))
 
567
 
 
568
  class Section(TargetLib.Section):
 
569
    def get_dep_targets(self, target):
 
570
      if target.lang == self.target.lang:
 
571
        return [ self.target ]
 
572
      return [ ]
 
573
 
 
574
class TargetProject(Target):
 
575
  def __init__(self, name, options, gen_obj):
 
576
    Target.__init__(self, name, options, gen_obj)
 
577
    self.cmd = options.get('cmd')
 
578
    self.release = options.get('release')
 
579
    self.debug = options.get('debug')
 
580
 
 
581
  def add_dependencies(self):
 
582
    self.gen_obj.projects.append(self)
 
583
 
 
584
class TargetSWIGProject(TargetProject):
 
585
  def __init__(self, name, options, gen_obj):
 
586
    TargetProject.__init__(self, name, options, gen_obj)
 
587
    self.lang = options.get('lang')
 
588
 
 
589
class TargetJava(TargetLinked):
 
590
  def __init__(self, name, options, gen_obj):
 
591
    TargetLinked.__init__(self, name, options, gen_obj)
 
592
    self.link_cmd = options.get('link-cmd')
 
593
    self.packages = string.split(options.get('package-roots', ''))
 
594
    self.jar = options.get('jar')
 
595
    self.deps = [ ]
 
596
 
 
597
class TargetJavaHeaders(TargetJava):
 
598
  def __init__(self, name, options, gen_obj):
 
599
    TargetJava.__init__(self, name, options, gen_obj)
 
600
    self.objext = '.class'
 
601
    self.javah_objext = '.h'
 
602
    self.headers = options.get('headers')
 
603
    self.classes = options.get('classes')
 
604
    self.package = options.get('package')
 
605
    self.output_dir = self.headers
 
606
 
 
607
  def add_dependencies(self):
 
608
    sources = _collect_paths(self.sources, self.path)
 
609
 
 
610
    for src, reldir in sources:
 
611
      if src[-5:] != '.java':
 
612
        raise GenError('ERROR: unknown file extension on ' + src)
 
613
 
 
614
      class_name = build_path_basename(src[:-5])
 
615
 
 
616
      class_header = build_path_join(self.headers, class_name + '.h')
 
617
      class_header_win = build_path_join(self.headers, 
 
618
                                         string.replace(self.package,".", "_")
 
619
                                         + "_" + class_name + '.h')
 
620
      class_pkg_list = string.split(self.package, '.')
 
621
      class_pkg = apply(build_path_join, class_pkg_list)
 
622
      class_file = ObjectFile(build_path_join(self.classes, class_pkg,
 
623
                                              class_name + self.objext))
 
624
      class_file.source_generated = 1
 
625
      class_file.class_name = class_name
 
626
      hfile = HeaderFile(class_header, self.package + '.' + class_name,
 
627
                         self.compile_cmd)
 
628
      hfile.filename_win = class_header_win
 
629
      hfile.source_generated = 1
 
630
      self.gen_obj.graph.add(DT_OBJECT, hfile, class_file)
 
631
      self.deps.append(hfile)
 
632
 
 
633
      # target (a linked item) depends upon object
 
634
      self.gen_obj.graph.add(DT_LINK, self.name, hfile)
 
635
 
 
636
 
 
637
    # collect all the paths where stuff might get built
 
638
    ### we should collect this from the dependency nodes rather than
 
639
    ### the sources. "what dir are you going to put yourself into?"
 
640
    self.gen_obj.target_dirs.append(self.path)
 
641
    self.gen_obj.target_dirs.append(self.classes)
 
642
    self.gen_obj.target_dirs.append(self.headers)
 
643
    for pattern in string.split(self.sources):
 
644
      dirname = build_path_dirname(pattern)
 
645
      if dirname:
 
646
        self.gen_obj.target_dirs.append(build_path_join(self.path, dirname))
 
647
 
 
648
    self.gen_obj.graph.add(DT_INSTALL, self.name, self)
 
649
 
 
650
class TargetJavaClasses(TargetJava):
 
651
  def __init__(self, name, options, gen_obj):
 
652
    TargetJava.__init__(self, name, options, gen_obj)
 
653
    self.objext = '.class'
 
654
    self.lang = 'java'
 
655
    self.classes = options.get('classes')
 
656
    self.output_dir = self.classes
 
657
 
 
658
  def add_dependencies(self):
 
659
    sources =_collect_paths(self.sources, self.path)
 
660
 
 
661
    for src, reldir in sources:
 
662
      if src[-5:] == '.java':
 
663
        objname = src[:-5] + self.objext
 
664
 
 
665
        # As .class files are likely not generated into the same
 
666
        # directory as the source files, the object path may need
 
667
        # adjustment.  To this effect, take "target_ob.classes" into
 
668
        # account.
 
669
        dirs = build_path_split(objname)
 
670
        sourcedirs = dirs[:-1]  # Last element is the .class file name.
 
671
        while sourcedirs:
 
672
          if sourcedirs.pop() in self.packages:
 
673
            sourcepath = apply(build_path_join, sourcedirs)
 
674
            objname = apply(build_path_join, 
 
675
                            [self.classes] + dirs[len(sourcedirs):])
 
676
            break
 
677
        else:
 
678
          raise GenError('Unable to find Java package root in path "%s"' % objname)
 
679
      else:
 
680
        raise GenError('ERROR: unknown file extension on "' + src + '"')
 
681
 
 
682
      ofile = ObjectFile(objname, self.compile_cmd)
 
683
      sfile = SourceFile(src, reldir)
 
684
      sfile.sourcepath = sourcepath
 
685
 
 
686
      # object depends upon source
 
687
      self.gen_obj.graph.add(DT_OBJECT, ofile, sfile)
 
688
 
 
689
      # target (a linked item) depends upon object
 
690
      self.gen_obj.graph.add(DT_LINK, self.name, ofile)
 
691
 
 
692
      # Add the class file to the dependency tree for this target
 
693
      self.deps.append(ofile)
 
694
 
 
695
    # collect all the paths where stuff might get built
 
696
    ### we should collect this from the dependency nodes rather than
 
697
    ### the sources. "what dir are you going to put yourself into?"
 
698
    self.gen_obj.target_dirs.append(self.path)
 
699
    self.gen_obj.target_dirs.append(self.classes)
 
700
    for pattern in string.split(self.sources):
 
701
      dirname = build_path_dirname(pattern)
 
702
      if dirname:
 
703
        self.gen_obj.target_dirs.append(build_path_join(self.path, dirname))
 
704
 
 
705
    self.gen_obj.graph.add(DT_INSTALL, self.name, self)
 
706
 
 
707
 
 
708
_build_types = {
 
709
  'exe' : TargetExe,
 
710
  'script' : TargetScript,
 
711
  'lib' : TargetLib,
 
712
  'doc' : TargetDoc,
 
713
  'swig' : TargetSWIG,
 
714
  'project' : TargetProject,
 
715
  'swig_lib' : TargetSWIGLib,
 
716
  'swig_project' : TargetSWIGProject,
 
717
  'ra-module': TargetRaModule,
 
718
  'fs-module': TargetFsModule,
 
719
  'apache-mod': TargetApacheMod,
 
720
  'javah' : TargetJavaHeaders,
 
721
  'java' : TargetJavaClasses,
 
722
  'i18n' : TargetI18N,
 
723
  }
 
724
 
 
725
 
 
726
class GenError(Exception):
 
727
  pass
 
728
 
 
729
 
 
730
# Path Handling Functions
 
731
#
 
732
# Build paths specified in build.conf are assumed to be always separated
 
733
# by forward slashes, regardless of the current running os.
 
734
#
 
735
# Native paths are paths seperated by os.sep.
 
736
 
 
737
def native_path(path):
 
738
  """Convert a build path to a native path"""
 
739
  return string.replace(path, '/', os.sep)
 
740
 
 
741
def build_path(path):
 
742
  """Convert a native path to a build path"""
 
743
  path = string.replace(path, os.sep, '/')
 
744
  if os.altsep:
 
745
    path = string.replace(path, os.altsep, '/')
 
746
  return path
 
747
 
 
748
def build_path_join(*path_parts):
 
749
  """Join path components into a build path"""
 
750
  return string.join(path_parts, '/')
 
751
 
 
752
def build_path_split(path):
 
753
  """Return list of components in a build path"""
 
754
  return string.split(path, '/')
 
755
 
 
756
def build_path_splitfile(path):
 
757
  """Return the filename and directory portions of a file path"""
 
758
  pos = string.rfind(path, '/')
 
759
  if pos > 0:
 
760
    return path[:pos], path[pos+1:]
 
761
  elif pos == 0:
 
762
    return path[0], path[1:]
 
763
  else:
 
764
    return "", path
 
765
 
 
766
def build_path_dirname(path):
 
767
  """Return the directory portion of a file path"""
 
768
  return build_path_splitfile(path)[0]
 
769
 
 
770
def build_path_basename(path):
 
771
  """Return the filename portion of a file path"""
 
772
  return build_path_splitfile(path)[1]
 
773
 
 
774
def build_path_retreat(path):
 
775
  "Given a relative directory, return ../ paths to retreat to the origin."
 
776
  return ".." + "/.." * string.count(path, '/')
 
777
 
 
778
def build_path_strip(path, files):
 
779
  "Strip the given path from each file."
 
780
  l = len(path)
 
781
  result = [ ]
 
782
  for file in files:
 
783
    if len(file) > l and file[:l] == path and file[l] == '/':
 
784
      result.append(file[l+1:])
 
785
    else:
 
786
      result.append(file)
 
787
  return result
 
788
 
 
789
def _collect_paths(pats, path=None):
 
790
  """Find files matching a space separated list of globs
 
791
  
 
792
  pats (string) is the list of glob patterns
 
793
 
 
794
  path (string), if specified, is a path that will be prepended to each
 
795
    glob pattern before it is evaluated
 
796
    
 
797
  If path is none the return value is a list of filenames, otherwise
 
798
  the return value is a list of 2-tuples. The first element in each tuple
 
799
  is a matching filename and the second element is the portion of the
 
800
  glob pattern which matched the file before its last forward slash (/)
 
801
  """
 
802
  result = [ ]
 
803
  for base_pat in string.split(pats):
 
804
    if path:
 
805
      pattern = build_path_join(path, base_pat)
 
806
    else:
 
807
      pattern = base_pat
 
808
    files = glob.glob(native_path(pattern)) or [pattern]
 
809
 
 
810
    if path is None:
 
811
      # just append the names to the result list
 
812
      for file in files:
 
813
        result.append(build_path(file))
 
814
    else:
 
815
      # if we have paths, then we need to record how each source is located
 
816
      # relative to the specified path
 
817
      reldir = build_path_dirname(base_pat)
 
818
      for file in files:
 
819
        result.append((build_path(file), reldir))
 
820
 
 
821
  return result
 
822
 
 
823
 
 
824
class IncludeDependencyInfo:
 
825
  """Finds all dependencies between a named set of headers, and computes
 
826
  closure, so that individual C files can then be scanned, and the stored
 
827
  dependency data used to return all directly and indirectly referenced
 
828
  headers.
 
829
 
 
830
  This class works exclusively in native-style paths.
 
831
  
 
832
  Note: Has the requirement that the basenames of all headers under
 
833
  consideration are unique. This is currently the case for Subversion, and
 
834
  it allows the code to be quite a bit simpler."""
 
835
 
 
836
  def __init__(self, filenames):
 
837
    """Scan all files in FILENAMES, which should be a sequence of paths to
 
838
    all header files that this IncludeDependencyInfo instance should
 
839
    consider as interesting when following and reporting dependencies - i.e.
 
840
    all the Subversion header files, no system header files."""
 
841
    
 
842
    basenames = map(os.path.basename, filenames)
 
843
 
 
844
    # This data structure is:
 
845
    # { 'basename.h': ('full/path/to/basename.h', { 'depbase1.h': None, } ) }
 
846
    self._deps = {}
 
847
    for fname in filenames:
 
848
      bname = os.path.basename(fname)
 
849
      self._deps[bname] = (fname, self._scan_for_includes(fname, basenames))
 
850
 
 
851
    # Keep recomputing closures until we see no more changes
 
852
    while 1:
 
853
      changes = 0
 
854
      for bname in basenames:
 
855
        changes = self._include_closure(self._deps[bname][1]) or changes
 
856
      if not changes:
 
857
        return
 
858
 
 
859
  def query(self, fname):
 
860
    """Scan the C file FNAME, and return the full paths of each include file
 
861
    that is a direct or indirect dependency."""
 
862
    hdrs = self._scan_for_includes(fname, self._deps.keys())
 
863
    self._include_closure(hdrs)
 
864
    filenames = map(lambda x, self=self: self._deps[x][0], hdrs.keys())
 
865
    filenames.sort() # Be independent of hash ordering
 
866
    return filenames
 
867
 
 
868
  def _include_closure(self, hdrs):
 
869
    """Mutate the passed dictionary HDRS, by performing a single pass
 
870
    through the listed headers, adding the headers on which the first group
 
871
    of headers depend, if not already present.
 
872
    
 
873
    Return a boolean indicating whether any changes were made."""
 
874
    keys = hdrs.keys()
 
875
    for h in keys:
 
876
      hdrs.update(self._deps[h][1])
 
877
    return (len(keys) != len(hdrs))
 
878
 
 
879
  _re_include = re.compile(r'^#\s*include\s*[<"]([^<"]+)[>"]')
 
880
  def _scan_for_includes(self, fname, limit):
 
881
    """Scan C source file FNAME and return the basenames of any headers
 
882
    which are directly included, and listed in LIMIT.
 
883
 
 
884
    Return a dictionary with included file basenames as keys and None as
 
885
    values."""
 
886
    hdrs = { }
 
887
    for line in fileinput.input(fname):
 
888
      match = self._re_include.match(line)
 
889
      if match:
 
890
        h = os.path.basename(native_path(match.group(1)))
 
891
        if h in limit:
 
892
          hdrs[h] = None
 
893
    return hdrs
 
894
 
 
895
 
 
896
def _sorted_files(graph, area):
 
897
  "Given a list of targets, sort them based on their dependencies."
 
898
 
 
899
  # we're going to just go with a naive algorithm here. these lists are
 
900
  # going to be so short, that we can use O(n^2) or whatever this is.
 
901
 
 
902
  inst_targets = graph.get_sources(DT_INSTALL, area)
 
903
 
 
904
  # first we need our own copy of the target list since we're going to
 
905
  # munge it.
 
906
  targets = inst_targets[:]
 
907
 
 
908
  # the output list of the targets' files
 
909
  files = [ ]
 
910
 
 
911
  # loop while we have targets remaining:
 
912
  while targets:
 
913
    # find a target that has no dependencies in our current targets list.
 
914
    for t in targets:
 
915
      s = graph.get_sources(DT_LINK, t.name, Target) \
 
916
          + graph.get_sources(DT_NONLIB, t.name, Target)
 
917
      for d in s:
 
918
        if d in targets:
 
919
          break
 
920
      else:
 
921
        # no dependencies found in the targets list. this is a good "base"
 
922
        # to add to the files list now.
 
923
        if isinstance(t, TargetJava):
 
924
          # Java targets have no filename, and we just ignore them.
 
925
          pass
 
926
        elif isinstance(t, TargetI18N):
 
927
          # I18N targets have no filename, we recurse one level deeper, and
 
928
          # get the filenames of their dependencies.
 
929
          s = graph.get_sources(DT_LINK, t.name)
 
930
          for d in s:
 
931
            if d not in targets:
 
932
              files.append(d.filename)
 
933
        else:
 
934
          files.append(t.filename)
 
935
 
 
936
        # don't consider this target any more
 
937
        targets.remove(t)
 
938
 
 
939
        # break out of search through targets
 
940
        break
 
941
    else:
 
942
      # we went through the entire target list and everything had at least
 
943
      # one dependency on another target. thus, we have a circular dependency
 
944
      # tree. somebody messed up the .conf file, or the app truly does have
 
945
      # a loop (and if so, they're screwed; libtool can't relink a lib at
 
946
      # install time if the dependent libs haven't been installed yet)
 
947
      raise CircularDependencies()
 
948
 
 
949
  return files
 
950
 
 
951
class CircularDependencies(Exception):
 
952
  pass
 
953
 
 
954
def unique(seq):
 
955
  "Eliminate duplicates from a sequence"
 
956
  list = [ ]
 
957
  dupes = { }
 
958
  for e in seq:
 
959
    if not dupes.has_key(e):
 
960
      dupes[e] = None
 
961
      list.append(e)
 
962
  return list
 
963
 
 
964
### End of file.