2
# gen_base.py -- infrastructure for generating makefiles, dependencies, etc.
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 ...}
23
# where: target-type is 'exe', 'lib', ...
24
# file-type is 'target', 'object', ...
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.
31
vsn_parser = getversion.Parser()
32
vsn_parser.search('SVN_VER_MAJOR', 'libver')
33
self.version = vsn_parser.parse(verfname).libver
35
raise GenError('Unable to extract version.')
37
# Now read and parse build.conf
38
parser = ConfigParser.ConfigParser()
42
self.graph = DependencyGraph()
44
# Allow derived classes to suppress certain configuration sections
45
if not hasattr(self, 'skip_sections'):
46
self.skip_sections = { }
48
# The 'options' section does not represent a build target,
49
# it simply contains global options
50
self.skip_sections['options'] = None
52
# Read in the global options
54
_collect_paths(parser.get('options', 'includes'))
56
_collect_paths(parser.get('options', 'static-apache-files'))
58
_collect_paths(parser.get('options', 'test-scripts'))
60
_collect_paths(parser.get('options', 'bdb-test-scripts'))
62
self.swig_lang = string.split(parser.get('options', 'swig-languages'))
63
self.swig_dirs = string.split(parser.get('options', 'swig-dirs'))
65
# Visual C++ projects - contents are either TargetProject instances,
66
# or other targets with an external-project attribute.
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
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):
85
for option in parser.options(section_name):
86
options[option] = parser.get(section_name, option)
88
type = options.get('type')
90
target_class = _build_types.get(type)
92
raise GenError('ERROR: unknown build type for ' + section_name)
94
section = target_class.Section(target_class, section_name, options, self)
96
self.sections[section_name] = section
98
section.create_targets()
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', "") ))
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])
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.
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))
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)
130
include_deps = IncludeDependencyInfo(all_includes)
132
for objectfile, sources in self.graph.get_deps(DT_OBJECT):
133
assert len(sources) == 1
136
# Generated .c files must depend on all headers their parent .i file
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)
144
for include_file in include_deps.query(native_path(ifile.filename)):
145
self.graph.add(DT_SWIG_C, source, build_path(include_file))
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))
157
class DependencyGraph:
158
"""Record dependencies between build items.
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.
166
self.deps = { } # type -> { target -> [ source ... ] }
170
def add(self, type, target, source):
171
if self.deps[type].has_key(target):
172
self.deps[type][target].append(source)
174
self.deps[type][target] = [ source ]
176
def bulk_add(self, type, target, sources):
177
if self.deps[type].has_key(target):
178
self.deps[type][target].extend(sources)
180
self.deps[type][target] = sources[:]
182
def get_sources(self, type, target, cls=None):
183
sources = self.deps[type].get(target, [ ])
188
if isinstance(src, cls):
192
def get_all_sources(self, type):
194
for group in self.deps[type].values():
195
sources.extend(group)
198
def get_deps(self, type):
199
return self.deps[type].items()
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
210
# create some variables for these
211
for _dt in dep_types:
212
# e.g. DT_INSTALL = 'DT_INSTALL'
215
class DependencyNode:
216
def __init__(self, filename):
217
self.filename = filename
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
228
class SWIGObject(ObjectFile):
229
def __init__(self, filename, lang):
230
ObjectFile.__init__(self, filename)
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
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
243
class SourceFile(DependencyNode):
244
def __init__(self, filename, reldir):
245
DependencyNode.__init__(self, filename)
248
class SWIGSource(SourceFile):
249
def __init__(self, filename):
250
SourceFile.__init__(self, filename, build_path_dirname(filename))
265
lang_utillib_suffix = {
271
class Target(DependencyNode):
272
"A build target is a node in our dependency graph."
274
def __init__(self, name, options, gen_obj):
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
283
def add_dependencies(self):
284
# subclasses should override to provide behavior, as appropriate
285
raise NotImplementedError
288
"""Represents an individual section of build.conf
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.
296
def __init__(self, target_class, name, options, gen_obj):
297
self.target_class = target_class
299
self.options = options
300
self.gen_obj = gen_obj
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()
307
def get_targets(self):
308
"""Return list of target instances associated with this section"""
311
def get_dep_targets(self, target):
312
"""Return list of targets from this section that "target" depends on"""
315
class TargetLinked(Target):
316
"The target is linked (by libtool) against other libraries."
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)')
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', ''))
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)
335
# the specified install area depends upon this target
336
self.gen_obj.graph.add(DT_INSTALL, self.install, self)
338
sources = _collect_paths(self.sources or '*.c', self.path)
341
for src, reldir in sources:
343
objname = src[:-2] + self.objext
344
elif src[-4:] == '.cpp':
345
objname = src[:-4] + self.objext
347
raise GenError('ERROR: unknown file extension on ' + src)
349
ofile = ObjectFile(objname, self.compile_cmd)
351
# object depends upon source
352
self.gen_obj.graph.add(DT_OBJECT, ofile, SourceFile(src, reldir))
354
# target (a linked item) depends upon object
355
self.gen_obj.graph.add(DT_LINK, self.name, ofile)
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)
364
self.gen_obj.target_dirs.append(build_path_join(self.path, dirname))
366
class TargetExe(TargetLinked):
367
def __init__(self, name, options, gen_obj):
368
TargetLinked.__init__(self, name, options, gen_obj)
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'])
375
self.manpages = options.get('manpages', '')
376
self.testing = options.get('testing')
378
def add_dependencies(self):
379
TargetLinked.add_dependencies(self)
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)
391
self.gen_obj.manpages.extend(string.split(self.manpages))
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
399
self.gen_obj.graph.add(DT_INSTALL, self.install, self)
401
class TargetLib(TargetLinked):
402
def __init__(self, name, options, gen_obj):
403
TargetLinked.__init__(self, name, options, gen_obj)
405
if not (self.external_lib or self.external_project):
406
extmap = gen_obj._extension_map
407
self.objext = extmap['lib', 'object']
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)
413
# Is a library referencing symbols which are undefined at link time.
414
self.undefined_lib_symbols = options.get('undefined-lib-symbols') == 'yes'
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', ''))
420
class TargetApacheMod(TargetLib):
422
def __init__(self, name, options, gen_obj):
423
TargetLib.__init__(self, name, options, gen_obj)
425
tfile = name + self.gen_obj._extension_map['lib', 'target']
426
self.filename = build_path_join(self.path, tfile)
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)'
433
class TargetRaModule(TargetLib):
436
class TargetFsModule(TargetLib):
439
class TargetDoc(Target):
442
class TargetI18N(Target):
443
"The target is a collection of .po files to be compiled by msgfmt."
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
452
self.external_project = options.get('external-project')
454
def add_dependencies(self):
455
self.gen_obj.graph.add(DT_INSTALL, self.install, self)
457
sources = _collect_paths(self.sources or '*.po', self.path)
460
for src, reldir in sources:
461
if src[-3:] == '.po':
462
objname = src[:-3] + self.objext
464
raise GenError('ERROR: unknown file extension on ' + src)
466
ofile = ObjectFile(objname, self.compile_cmd)
468
# object depends upon source
469
self.gen_obj.graph.add(DT_OBJECT, ofile, SourceFile(src, reldir))
471
# target depends upon object
472
self.gen_obj.graph.add(DT_LINK, self.name, ofile)
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)
477
class TargetSWIG(TargetLib):
478
def __init__(self, name, options, gen_obj, lang):
479
TargetLib.__init__(self, name, options, gen_obj)
481
self.desc = self.desc + ' for ' + lang_full_name[lang]
482
self.include_runtime = options.get('include-runtime') == 'yes'
484
### hmm. this is Makefile-specific
485
self.link_cmd = '$(LINK_%s_WRAPPER)' % string.upper(lang_abbrev[lang])
487
def add_dependencies(self):
488
sources = _collect_paths(self.sources, self.path)
489
assert len(sources) == 1 ### simple assertions for now
491
# get path to SWIG .i file
492
ipath = sources[0][0]
493
iname = build_path_basename(ipath)
495
assert iname[-2:] == '.i'
496
cname = iname[:-2] + '.c'
497
oname = iname[:-2] + self.gen_obj._extension_map['lib', 'object']
499
# Extract SWIG module name from .i file name
500
module_name = iname[:4] != 'svn_' and iname[:-2] or iname[4:-2]
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
508
lib_filename = '_' + module_name + lib_extension
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)
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)
518
# the .c file depends upon the .i file
519
self.gen_obj.graph.add(DT_SWIG_C, cfile, ifile)
521
# the object depends upon the .c file
522
self.gen_obj.graph.add(DT_OBJECT, ofile, cfile)
524
# the library depends upon the object
525
self.gen_obj.graph.add(DT_LINK, self.name, ofile)
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))
532
# the specified install area depends upon the library
533
self.gen_obj.graph.add(DT_INSTALL, 'swig-' + lang_abbrev[self.lang], self)
535
class Section(TargetLib.Section):
536
def create_targets(self):
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
543
def get_targets(self):
544
return self.targets.values()
546
def get_dep_targets(self, target):
547
target = self.targets.get(target.lang, None)
548
return target and [target] or [ ]
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]) + ")"
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')
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))
568
class Section(TargetLib.Section):
569
def get_dep_targets(self, target):
570
if target.lang == self.target.lang:
571
return [ self.target ]
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')
581
def add_dependencies(self):
582
self.gen_obj.projects.append(self)
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')
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')
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
607
def add_dependencies(self):
608
sources = _collect_paths(self.sources, self.path)
610
for src, reldir in sources:
611
if src[-5:] != '.java':
612
raise GenError('ERROR: unknown file extension on ' + src)
614
class_name = build_path_basename(src[:-5])
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,
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)
633
# target (a linked item) depends upon object
634
self.gen_obj.graph.add(DT_LINK, self.name, hfile)
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)
646
self.gen_obj.target_dirs.append(build_path_join(self.path, dirname))
648
self.gen_obj.graph.add(DT_INSTALL, self.name, self)
650
class TargetJavaClasses(TargetJava):
651
def __init__(self, name, options, gen_obj):
652
TargetJava.__init__(self, name, options, gen_obj)
653
self.objext = '.class'
655
self.classes = options.get('classes')
656
self.output_dir = self.classes
658
def add_dependencies(self):
659
sources =_collect_paths(self.sources, self.path)
661
for src, reldir in sources:
662
if src[-5:] == '.java':
663
objname = src[:-5] + self.objext
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
669
dirs = build_path_split(objname)
670
sourcedirs = dirs[:-1] # Last element is the .class file name.
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):])
678
raise GenError('Unable to find Java package root in path "%s"' % objname)
680
raise GenError('ERROR: unknown file extension on "' + src + '"')
682
ofile = ObjectFile(objname, self.compile_cmd)
683
sfile = SourceFile(src, reldir)
684
sfile.sourcepath = sourcepath
686
# object depends upon source
687
self.gen_obj.graph.add(DT_OBJECT, ofile, sfile)
689
# target (a linked item) depends upon object
690
self.gen_obj.graph.add(DT_LINK, self.name, ofile)
692
# Add the class file to the dependency tree for this target
693
self.deps.append(ofile)
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)
703
self.gen_obj.target_dirs.append(build_path_join(self.path, dirname))
705
self.gen_obj.graph.add(DT_INSTALL, self.name, self)
710
'script' : TargetScript,
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,
726
class GenError(Exception):
730
# Path Handling Functions
732
# Build paths specified in build.conf are assumed to be always separated
733
# by forward slashes, regardless of the current running os.
735
# Native paths are paths seperated by os.sep.
737
def native_path(path):
738
"""Convert a build path to a native path"""
739
return string.replace(path, '/', os.sep)
741
def build_path(path):
742
"""Convert a native path to a build path"""
743
path = string.replace(path, os.sep, '/')
745
path = string.replace(path, os.altsep, '/')
748
def build_path_join(*path_parts):
749
"""Join path components into a build path"""
750
return string.join(path_parts, '/')
752
def build_path_split(path):
753
"""Return list of components in a build path"""
754
return string.split(path, '/')
756
def build_path_splitfile(path):
757
"""Return the filename and directory portions of a file path"""
758
pos = string.rfind(path, '/')
760
return path[:pos], path[pos+1:]
762
return path[0], path[1:]
766
def build_path_dirname(path):
767
"""Return the directory portion of a file path"""
768
return build_path_splitfile(path)[0]
770
def build_path_basename(path):
771
"""Return the filename portion of a file path"""
772
return build_path_splitfile(path)[1]
774
def build_path_retreat(path):
775
"Given a relative directory, return ../ paths to retreat to the origin."
776
return ".." + "/.." * string.count(path, '/')
778
def build_path_strip(path, files):
779
"Strip the given path from each file."
783
if len(file) > l and file[:l] == path and file[l] == '/':
784
result.append(file[l+1:])
789
def _collect_paths(pats, path=None):
790
"""Find files matching a space separated list of globs
792
pats (string) is the list of glob patterns
794
path (string), if specified, is a path that will be prepended to each
795
glob pattern before it is evaluated
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 (/)
803
for base_pat in string.split(pats):
805
pattern = build_path_join(path, base_pat)
808
files = glob.glob(native_path(pattern)) or [pattern]
811
# just append the names to the result list
813
result.append(build_path(file))
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)
819
result.append((build_path(file), reldir))
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
830
This class works exclusively in native-style paths.
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."""
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."""
842
basenames = map(os.path.basename, filenames)
844
# This data structure is:
845
# { 'basename.h': ('full/path/to/basename.h', { 'depbase1.h': None, } ) }
847
for fname in filenames:
848
bname = os.path.basename(fname)
849
self._deps[bname] = (fname, self._scan_for_includes(fname, basenames))
851
# Keep recomputing closures until we see no more changes
854
for bname in basenames:
855
changes = self._include_closure(self._deps[bname][1]) or changes
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
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.
873
Return a boolean indicating whether any changes were made."""
876
hdrs.update(self._deps[h][1])
877
return (len(keys) != len(hdrs))
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.
884
Return a dictionary with included file basenames as keys and None as
887
for line in fileinput.input(fname):
888
match = self._re_include.match(line)
890
h = os.path.basename(native_path(match.group(1)))
896
def _sorted_files(graph, area):
897
"Given a list of targets, sort them based on their dependencies."
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.
902
inst_targets = graph.get_sources(DT_INSTALL, area)
904
# first we need our own copy of the target list since we're going to
906
targets = inst_targets[:]
908
# the output list of the targets' files
911
# loop while we have targets remaining:
913
# find a target that has no dependencies in our current targets list.
915
s = graph.get_sources(DT_LINK, t.name, Target) \
916
+ graph.get_sources(DT_NONLIB, t.name, Target)
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.
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)
932
files.append(d.filename)
934
files.append(t.filename)
936
# don't consider this target any more
939
# break out of search through targets
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()
951
class CircularDependencies(Exception):
955
"Eliminate duplicates from a sequence"
959
if not dupes.has_key(e):