summaryrefslogtreecommitdiffstats
path: root/build/gen-build.py
blob: 53ca595f54f5c5d6ccba1ea2d5683c574d72a980 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#!/usr/bin/env python
#
# USAGE: gen-build.py TYPE
#
# where TYPE is one of: make, dsp, vcproj
#
# It reads build.conf from the current directory, and produces its output
# into the current directory.
#


import os
try:
  import configparser
except ImportError:
  import ConfigParser as configparser
import codecs
import getopt
import string
import glob
import re

#import ezt

#
# legal platforms: aix, beos, netware, os2, os390, unix, win32
# 'make' users: aix, beos, os2, os390, unix, win32 (mingw)
#
PLATFORMS = [ 'aix', 'beos', 'netware', 'os2', 'os390', 'unix', 'win32' ]
MAKE_PLATFORMS = [
  ('unix', None),
  ('aix', 'unix'),
  ('beos', 'unix'),
  ('os2', 'unix'),
  ('os390', 'unix'),
  ('win32', 'unix'),
  ]
# note: MAKE_PLATFORMS is an ordered set. we want to generate unix symbols
#       first, so that the later platforms can reference them.


def main():
  parser = configparser.ConfigParser()
  parser.read('build.conf')

  if parser.has_option('options', 'dsp'):
    dsp_file = parser.get('options', 'dsp')
  else:
    dsp_file = None

  headers = get_files(parser.get('options', 'headers'))

  # compute the relevant headers, along with the implied includes
  legal_deps = { }
  for fname in headers:
    legal_deps[os.path.basename(fname)] = fname

  h_deps = { }
  for fname in headers:
    h_deps[os.path.basename(fname)] = extract_deps(fname, legal_deps)
  resolve_deps(h_deps)

  f = open('build-outputs.mk', 'w')
  f.write('# DO NOT EDIT. AUTOMATICALLY GENERATED.\n\n')

  # write out the platform-independent files
  files = get_files(parser.get('options', 'paths'))
  objects, dirs = write_objects(f, legal_deps, h_deps, files)
  f.write('\nOBJECTS_all = %s\n\n' % " ".join(objects))

  # for each platform and each subdirectory holding platform-specific files,
  # write out their compilation rules, and an OBJECT_<subdir>_<plat> symbol.
  for platform, parent in MAKE_PLATFORMS:

    # record the object symbols to build for each platform
    group = [ '$(OBJECTS_all)' ]

    # If we're doing win32, we're going to look in the libapr.dsp file
    # for those files that we have to manually add to our list.
    inherit_parent = { }
    if platform == 'win32' and dsp_file:
      for line in open(dsp_file).readlines():
        if line[:7] != 'SOURCE=':
          continue
        if line[7:].find('unix') != -1:
          # skip the leading .\ and split it out
          inherit_files = line[9:].strip().split('\\')
          # change the .c to .lo
          assert inherit_files[-1][-2:] == '.c'
          inherit_files[-1] = inherit_files[-1][:-2] + '.lo'
          # replace the \\'s with /'s
          inherit_line = '/'.join(inherit_files)
          if inherit_files[0] not in inherit_parent:
            inherit_parent[inherit_files[0]] = []
          inherit_parent[inherit_files[0]].append(inherit_line)

    for subdir in parser.get('options', 'platform_dirs').split():
      path = '%s/%s' % (subdir, platform)
      if not os.path.exists(path):
        # this subdir doesn't have a subdir for this platform, so we'll
        # use the parent-platform's set of symbols
        if parent:
          group.append('$(OBJECTS_%s_%s)' % (subdir, parent))
        continue

      # remember that this directory has files/objects
      dirs[path] = None

      # write out the compilation lines for this subdir
      files = get_files(path + '/*.c')
      objects, _unused = write_objects(f, legal_deps, h_deps, files)

      if subdir in inherit_parent:
        objects = objects + inherit_parent[subdir]

      symname = 'OBJECTS_%s_%s' % (subdir, platform)

      objects.sort()

      # and write the symbol for the whole group
      f.write('\n%s = %s\n\n' % (symname, " ".join(objects)))

      # and include that symbol in the group
      group.append('$(%s)' % symname)

    group.sort()

    # write out a symbol which contains the necessary files
    f.write('OBJECTS_%s = %s\n\n' % (platform, " ".join(group)))

  f.write('HEADERS = $(top_srcdir)/%s\n\n' % ' $(top_srcdir)/'.join(headers))
  f.write('SOURCE_DIRS = %s $(EXTRA_SOURCE_DIRS)\n\n' % " ".join(dirs.keys()))

  if parser.has_option('options', 'modules'):
    modules = parser.get('options', 'modules')

    for mod in modules.split():
      files = get_files(parser.get(mod, 'paths'))
      objects, _unused = write_objects(f, legal_deps, h_deps, files)
      flat_objects = " ".join(objects)
      f.write('OBJECTS_%s = %s\n' % (mod, flat_objects))

      if parser.has_option(mod, 'target'):
        target = parser.get(mod, 'target')
        f.write('MODULE_%s = %s\n' % (mod, target))
        f.write('%s: %s\n' % (target, flat_objects))
        f.write('\t$(LINK_MODULE) -o $@ $(OBJECTS_%s) $(LDADD_%s)\n' % (mod, mod))

      f.write('\n')

  # Build a list of all necessary directories in build tree
  alldirs = { }
  for dir in dirs.keys():
    d = dir
    while d:
      alldirs[d] = None
      d = os.path.dirname(d)

  # Sort so 'foo' is before 'foo/bar'
  keys = list(alldirs.keys())
  keys.sort()
  f.write('BUILD_DIRS = %s\n\n' % " ".join(keys))

  f.write('.make.dirs: $(srcdir)/build-outputs.mk\n' \
          '\t@for d in $(BUILD_DIRS); do test -d $$d || mkdir $$d; done\n' \
          '\t@echo timestamp > $@\n')


def write_objects(f, legal_deps, h_deps, files):
  dirs = { }
  objects = [ ]

  for file in files:
    if file[-10:] == '/apr_app.c':
      continue
    assert file[-2:] == '.c'
    obj = file[:-2] + '.lo'
    objects.append(obj)

    dirs[os.path.dirname(file)] = None

    # what headers does this file include, along with the implied headers
    deps = extract_deps(file, legal_deps)
    for hdr in list(deps.keys()):
      deps.update(h_deps.get(hdr, {}))

    vals = list(deps.values())
    vals.sort()
    f.write('%s: %s .make.dirs %s\n' % (obj, file, " ".join(vals)))

  objects.sort()

  return objects, dirs


def extract_deps(fname, legal_deps):
  "Extract the headers this file includes."
  deps = { }
  for line in codecs.open(fname, 'r', 'utf-8').readlines():
    if line[:8] != '#include':
      continue
    inc = _re_include.match(line).group(1)
    if inc in legal_deps.keys():
      deps[inc] = legal_deps[inc]
  return deps
_re_include = re.compile('#include *["<](.*)[">]')


def resolve_deps(header_deps):
  "Alter the provided dictionary to flatten includes-of-includes."
  altered = 1
  while altered:
    altered = 0
    for hdr, deps in header_deps.items():
      # print hdr, deps
      start = len(deps)
      for dep in list(deps.keys()):
        deps.update(header_deps.get(dep, {}))
      if len(deps) != start:
        altered = 1

def clean_path(path):
    return path.replace("\\", "/")

def get_files(patterns):
  files = [ ]
  for pat in patterns.split():
    files.extend(map(clean_path, glob.glob(pat)))
  files.sort()
  return files


if __name__ == '__main__':
  main()