blob: 05db443aa528a8d334b1966d9391898b859aae5c [file] [log] [blame]
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +02001#!/usr/bin/env python3
2'''
3Generate a top-level makefile that builds the Osmocom 2G + 3G network components.
4
Neels Hofmeyr450dac72017-08-22 19:27:08 +02005 ./gen_makefile.py projects.deps [configure.opts [more.opts]] [-o Makefile.output]
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +02006
7Configured by text files:
8
9 *.deps: whitespace-separated listing of
10 project_name depends_on_project_1 depends_on_project_2 ...
11
12 *.opts: whitespace-separated listing of
13 project_name --config-opt-1 --config-opt-2 ...
14
15Thus it is possible to choose between e.g.
16- 2G+3G or 2G-only by picking a different projects_and_deps.conf,
17- and between building each of those with or without mgcp transcoding support
18 by picking a different configure_opts.conf.
19
20From the Makefile nature, the dependencies extend, no need to repeat common deps.
21
22When this script is done, a Makefile has been generated that allows you to
23build all projects at once by issuing 'make', but also to refresh only parts of
24it when some bits in the middle have changed. The makefile keeps local progress
25marker files like .make.libosmocore.configure; if such progress marker is
26removed or becomes outdated, that step and all dependent ones are re-run.
27This is helpful in daily hacking across several repositories.
28'''
29
30import sys
31import os
32import argparse
33
34parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.RawTextHelpFormatter)
35
36parser.add_argument('projects_and_deps_file',
37 help='''Config file containing projects to build and
38dependencies between those''')
39
Neels Hofmeyr450dac72017-08-22 19:27:08 +020040parser.add_argument('configure_opts_files',
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020041 help='''Config file containing project name and
42./configure options''',
Neels Hofmeyr450dac72017-08-22 19:27:08 +020043 nargs='*')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020044
45parser.add_argument('-m', '--make-dir', dest='make_dir',
46 help='''Place Makefile in this dir (default: create
47a new dir named after deps and opts files).''')
48
49parser.add_argument('-s', '--src-dir', dest='src_dir', default='./src',
50 help='Parent dir for all git clones.')
51
52parser.add_argument('-b', '--build-dir', dest='build_dir',
53 help='''Parent dir for all build trees (default:
54directly in the make-dir).''')
55
56parser.add_argument('-u', '--url', dest='url', default='ssh://go',
57 help='''git clone base URL. Default is 'ssh://go',
58e.g. add this to your ~/.ssh/config:
59 host go
60 hostname gerrit.osmocom.org
61 port 29418
62Alternatively pass '-u git://git.osmocom.org'.''')
63
64parser.add_argument('-o', '--output', dest='output', default='Makefile',
65 help='''Makefile filename (default: 'Makefile').''')
66
67parser.add_argument('-j', '--jobs', dest='jobs', default='9',
68 help='''-j option to pass to 'make'.''')
69
70args = parser.parse_args()
71
Neels Hofmeyr450dac72017-08-22 19:27:08 +020072class listdict(dict):
73 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
74
75 def add(self, name, item):
76 l = self.get(name)
77 if not l:
78 l = []
79 self[name] = l
80 l.append(item)
81
82 def extend(self, name, l):
83 for v in l:
84 self.add(name, v)
85
86 def add_dict(self, d):
87 for k,v in d.items():
88 self.add(k, v)
89
90 def extend_dict(self, d):
91 for k,v in d.items():
92 l = self.extend(k, v)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020093
94def read_projects_deps(path):
95 'Read deps config and return tuples of (project_name, which-other-to-build-first).'
96 l = []
97 for line in open(path):
98 line = line.strip()
99 if not line or line.startswith('#'):
100 continue
101 tokens = line.split()
102 l.append((tokens[0], tokens[1:]))
103 return l
104
105def read_configure_opts(path):
106 'Read config opts file and return tuples of (project_name, config-opts).'
107 if not path:
108 return {}
109 return dict(read_projects_deps(path))
110
111def gen_make(proj, deps, configure_opts, jobs, make_dir, src_dir, build_dir, url):
112 src_proj = os.path.join(src_dir, proj)
113 build_proj = os.path.join(build_dir, proj)
114
115 make_to_src = os.path.relpath(src_dir, make_dir)
116 make_to_src_proj = os.path.relpath(src_proj, make_dir)
117 make_to_build_proj = os.path.relpath(build_proj, make_dir)
118 build_to_src = os.path.relpath(src_proj, build_proj)
119
120 if configure_opts:
121 configure_opts_str = ' '.join(configure_opts)
122 else:
123 configure_opts_str = ''
124
125 # special hack for libsmpp34: cannot build in parallel
126 if proj == 'libsmpp34':
127 jobs = 1
128
129 return r'''
130### {proj} ###
131
132.make.{proj}.clone:
133 @echo "\n\n\n===== $@\n"
134 test -d {src} || mkdir -p {src}
135 test -d {src_proj} || git -C {src} clone {url}/{proj}
136 touch $@
137
138.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
139 @echo "\n\n\n===== $@\n"
140 cd {src_proj}; autoreconf -fi
141 touch $@
142
143.make.{proj}.configure: .make.{proj}.autoconf {deps_installed}
144 @echo "\n\n\n===== $@\n"
145 -chmod -R ug+w {build_proj}
146 -rm -rf {build_proj}
147 mkdir -p {build_proj}
148 cd {build_proj}; {build_to_src}/configure {configure_opts}
149 touch $@
150
151.make.{proj}.last_edited:
152 touch $@
153
154.PHONY: .make.{proj}.detect_edits
155.make.{proj}.detect_edits:
Neels Hofmeyr2509d092017-08-28 15:16:18 +0200156 @test -z "$(shell find {src_proj} -newer .make.{proj}.last_edited -name "*.[hc]" '(' -name "*.[hc]" -or -name "Makefile.am" -or -name "*.py" ')' )" || (touch .make.{proj}.last_edited; echo {proj} edited)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200157
158.make.{proj}.build: .make.{proj}.configure .make.{proj}.last_edited
159 @echo "\n\n\n===== $@\n"
160 $(MAKE) -C {build_proj} -j {jobs} check
161 touch $@
162
163.make.{proj}.install: .make.{proj}.build
164 @echo "\n\n\n===== $@\n"
165 $(MAKE) -C {build_proj} install
166 touch $@
167'''.format(
168 url=url,
169 proj=proj,
170 jobs=jobs,
171 src=make_to_src,
172 src_proj=make_to_src_proj,
173 build_proj=make_to_build_proj,
174 build_to_src=build_to_src,
175 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
176 configure_opts=configure_opts_str)
177
178
179projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200180configure_opts = listdict()
181configure_opts_files = sorted(args.configure_opts_files or [])
182for configure_opts_file in configure_opts_files:
183 r = read_configure_opts(configure_opts_file)
184 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200185
186make_dir = args.make_dir
187if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200188 deps_name = args.projects_and_deps_file.replace('.deps', '')
189 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
190 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200191
192if not os.path.isdir(make_dir):
193 os.makedirs(make_dir)
194
195build_dir = args.build_dir
196if not build_dir:
197 build_dir = make_dir
198
199output = os.path.join(make_dir, args.output)
200print('Writing to %r' % output)
201
202with open(output, 'w') as out:
203 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
204
205 # convenience: add a regen target that updates the generated makefile itself
206 out.write(r'''
207default: all
208
209# regenerate this Makefile, in case the deps or opts changed
210.PHONY: regen
211regen:
212 {script} {projects_and_deps} {configure_opts} -m {make_dir} -o {makefile} -s {src_dir} -b {build_dir}
213
214'''.format(
215 script=os.path.relpath(sys.argv[0], make_dir),
216 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200217 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200218 make_dir='.',
219 makefile=args.output,
220 src_dir=os.path.relpath(args.src_dir, make_dir),
221 build_dir=os.path.relpath(build_dir, make_dir),
222 ))
223
Neels Hofmeyre274d352017-08-22 17:31:03 +0200224 # convenience target: clone all repositories first
225 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
226
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200227 # now the actual useful build rules
Neels Hofmeyr20e169f2017-08-22 17:31:35 +0200228 out.write('all: \\\n\tclone \\\n\t' + ' \\\n\t'.join([ '.make.%s.detect_edits .make.%s.install' % (p,p) for p, d in projects_deps ]) + '\n\n')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200229
230 for proj, deps in projects_deps:
231 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
232 make_dir, args.src_dir, build_dir, args.url))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200233
234# vim: expandtab tabstop=2 shiftwidth=2