blob: e2fb747e1ca93cc0ba8d7975c23557f319dd8896 [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.
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +010028
29Note that by default, this includes 'sudo ldconfig' calls following each
30installation. You may want to permit your user to run 'sudo ldconfig' without
31needing a password, e.g. by
32
33 sudo sh -c "echo '$USER ALL= NOPASSWD: /sbin/ldconfig' > /etc/sudoers.d/${USER}_ldconfig"
34
35You can skip the 'sudo ldconfig' by issuing the --no-ldconfig option.
36
37You can run 'ldconfig' without sudo by issuing the --ldconfig-without-sudo option.
38
39By default, it is assumed that your user has write permission to /usr/local. If you
40need sudo to install there, you may issue the --sudo-make-install option.
Neels Hofmeyr2535a262018-01-16 16:34:32 +010041
42EXAMPLE:
43
44 ./gen_makefile.py 3G+2G.deps default.opts iu.opts -I -m build
45 cd build
46 make
47
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020048'''
49
50import sys
51import os
52import argparse
53
54parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.RawTextHelpFormatter)
55
56parser.add_argument('projects_and_deps_file',
57 help='''Config file containing projects to build and
58dependencies between those''')
59
Neels Hofmeyr450dac72017-08-22 19:27:08 +020060parser.add_argument('configure_opts_files',
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020061 help='''Config file containing project name and
62./configure options''',
Neels Hofmeyr450dac72017-08-22 19:27:08 +020063 nargs='*')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020064
65parser.add_argument('-m', '--make-dir', dest='make_dir',
66 help='''Place Makefile in this dir (default: create
67a new dir named after deps and opts files).''')
68
69parser.add_argument('-s', '--src-dir', dest='src_dir', default='./src',
70 help='Parent dir for all git clones.')
71
72parser.add_argument('-b', '--build-dir', dest='build_dir',
73 help='''Parent dir for all build trees (default:
74directly in the make-dir).''')
75
Neels Hofmeyrbffdc302017-12-06 00:31:49 +010076parser.add_argument('-u', '--url', dest='url', default='git://git.osmocom.org',
77 help='''git clone base URL. Default is 'git://git.osmocom.org'.
78e.g. with a config like this in your ~/.ssh/config:
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020079 host go
80 hostname gerrit.osmocom.org
81 port 29418
Neels Hofmeyrbffdc302017-12-06 00:31:49 +010082you may pass '-u ssh://go' to be able to submit to gerrit.''')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020083
Neels Hofmeyr972c2942018-03-16 03:49:58 +010084parser.add_argument('-p', '--push-url', dest='push_url', default='',
Neels Hofmeyr28d4be52018-03-16 03:44:07 +010085 help='''git push-URL. Default is to not configure a separate push-URL.''')
86
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020087parser.add_argument('-o', '--output', dest='output', default='Makefile',
88 help='''Makefile filename (default: 'Makefile').''')
89
90parser.add_argument('-j', '--jobs', dest='jobs', default='9',
91 help='''-j option to pass to 'make'.''')
92
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +010093parser.add_argument('-I', '--sudo-make-install', dest='sudo_make_install',
94 action='store_true',
95 help='''run 'make install' step with 'sudo'.''')
96
97parser.add_argument('-L', '--no-ldconfig', dest='no_ldconfig',
98 action='store_true',
99 help='''omit the 'sudo ldconfig' step.''')
100
101parser.add_argument('--ldconfig-without-sudo', dest='ldconfig_without_sudo',
102 action='store_true',
103 help='''call just 'ldconfig', without sudo, which implies
104root privileges (not recommended)''')
105
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200106args = parser.parse_args()
107
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200108class listdict(dict):
109 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
110
111 def add(self, name, item):
112 l = self.get(name)
113 if not l:
114 l = []
115 self[name] = l
116 l.append(item)
117
118 def extend(self, name, l):
119 for v in l:
120 self.add(name, v)
121
122 def add_dict(self, d):
123 for k,v in d.items():
124 self.add(k, v)
125
126 def extend_dict(self, d):
127 for k,v in d.items():
128 l = self.extend(k, v)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200129
130def read_projects_deps(path):
131 'Read deps config and return tuples of (project_name, which-other-to-build-first).'
132 l = []
133 for line in open(path):
134 line = line.strip()
135 if not line or line.startswith('#'):
136 continue
137 tokens = line.split()
138 l.append((tokens[0], tokens[1:]))
139 return l
140
141def read_configure_opts(path):
142 'Read config opts file and return tuples of (project_name, config-opts).'
143 if not path:
144 return {}
145 return dict(read_projects_deps(path))
146
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100147def gen_make(proj, deps, configure_opts, jobs, make_dir, src_dir, build_dir, url, push_url, sudo_make_install, no_ldconfig, ldconfig_without_sudo):
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200148 src_proj = os.path.join(src_dir, proj)
Neels Hofmeyr29fde6f2018-04-01 15:57:02 +0200149 if proj == 'openbsc':
150 src_proj = os.path.join(src_proj, 'openbsc')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200151 build_proj = os.path.join(build_dir, proj)
152
153 make_to_src = os.path.relpath(src_dir, make_dir)
154 make_to_src_proj = os.path.relpath(src_proj, make_dir)
155 make_to_build_proj = os.path.relpath(build_proj, make_dir)
156 build_to_src = os.path.relpath(src_proj, build_proj)
157
158 if configure_opts:
159 configure_opts_str = ' '.join(configure_opts)
160 else:
161 configure_opts_str = ''
162
163 # special hack for libsmpp34: cannot build in parallel
164 if proj == 'libsmpp34':
165 jobs = 1
166
167 return r'''
168### {proj} ###
169
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100170{proj}_configure_files := $(shell find {src_proj} -name "Makefile.am" -or -name "*.in" )
171{proj}_files := $(shell find {src_proj} -name "*.[hc]" -or -name "*.py" )
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200172
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200173.make.{proj}.clone:
174 @echo "\n\n\n===== $@\n"
175 test -d {src} || mkdir -p {src}
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100176 test -d {src_proj} || ( git -C {src} clone "{url}/{proj}" "{proj}" && git -C "{src}/{proj}" remote set-url --push origin "{push_url}/{proj}" )
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200177 touch $@
178
179.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
180 @echo "\n\n\n===== $@\n"
Neels Hofmeyr1fa9b292018-04-23 17:04:25 +0200181 -rm -f {src_proj}/.version
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200182 cd {src_proj}; autoreconf -fi
183 touch $@
184
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100185.make.{proj}.configure: .make.{proj}.autoconf {deps_installed} $({proj}_configure_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200186 @echo "\n\n\n===== $@\n"
187 -chmod -R ug+w {build_proj}
188 -rm -rf {build_proj}
189 mkdir -p {build_proj}
190 cd {build_proj}; {build_to_src}/configure {configure_opts}
191 touch $@
192
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100193.make.{proj}.build: .make.{proj}.configure $({proj}_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200194 @echo "\n\n\n===== $@\n"
195 $(MAKE) -C {build_proj} -j {jobs} check
196 touch $@
197
198.make.{proj}.install: .make.{proj}.build
199 @echo "\n\n\n===== $@\n"
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100200 {sudo_make_install}$(MAKE) -C {build_proj} install
201 {no_ldconfig}{sudo_ldconfig}ldconfig
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200202 touch $@
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200203
Neels Hofmeyr3f934122017-08-29 12:31:59 +0200204{proj}: .make.{proj}.install
205
Neels Hofmeyr4d38c1e2018-04-23 17:04:15 +0200206.PHONY: {proj}-reinstall
207{proj}-reinstall: {deps_reinstall}
208 {sudo_make_install}$(MAKE) -C {build_proj} install
209
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200210.PHONY: {proj}-clean
211{proj}-clean:
212 @echo "\n\n\n===== $@\n"
213 -chmod -R ug+w {build_proj}
214 -rm -rf {build_proj}
215 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200216'''.format(
217 url=url,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100218 push_url=push_url or url,
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200219 proj=proj,
220 jobs=jobs,
221 src=make_to_src,
222 src_proj=make_to_src_proj,
223 build_proj=make_to_build_proj,
224 build_to_src=build_to_src,
225 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
Neels Hofmeyr4d38c1e2018-04-23 17:04:15 +0200226 deps_reinstall=' '.join(['%s-reinstall' %d for d in deps]),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100227 configure_opts=configure_opts_str,
228 sudo_make_install='sudo ' if sudo_make_install else '',
229 no_ldconfig='#' if no_ldconfig else '',
230 sudo_ldconfig='' if ldconfig_without_sudo else 'sudo '
231 )
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200232
233
234projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200235configure_opts = listdict()
236configure_opts_files = sorted(args.configure_opts_files or [])
237for configure_opts_file in configure_opts_files:
238 r = read_configure_opts(configure_opts_file)
239 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200240
241make_dir = args.make_dir
242if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200243 deps_name = args.projects_and_deps_file.replace('.deps', '')
244 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
245 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200246
247if not os.path.isdir(make_dir):
248 os.makedirs(make_dir)
249
250build_dir = args.build_dir
251if not build_dir:
252 build_dir = make_dir
253
254output = os.path.join(make_dir, args.output)
255print('Writing to %r' % output)
256
257with open(output, 'w') as out:
258 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
259
260 # convenience: add a regen target that updates the generated makefile itself
261 out.write(r'''
262default: all
263
Neels Hofmeyr1b0d34f2018-03-16 03:46:08 +0100264.PHONY: all_debug
265all_debug:
266 $(MAKE) --dry-run -d all | grep "is newer than target"
267 $(MAKE) all
268
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200269# regenerate this Makefile, in case the deps or opts changed
270.PHONY: regen
271regen:
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100272 {script} {projects_and_deps} {configure_opts} -m {make_dir} -o {makefile} -s {src_dir} -b {build_dir} -u "{url}" -p "{push_url}"{sudo_make_install}{no_ldconfig}{ldconfig_without_sudo}
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200273
274'''.format(
275 script=os.path.relpath(sys.argv[0], make_dir),
276 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200277 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200278 make_dir='.',
279 makefile=args.output,
280 src_dir=os.path.relpath(args.src_dir, make_dir),
281 build_dir=os.path.relpath(build_dir, make_dir),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100282 url=args.url,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100283 push_url=args.push_url,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100284 sudo_make_install=' -I' if args.sudo_make_install else '',
285 no_ldconfig=' -L' if args.no_ldconfig else '',
286 ldconfig_without_sudo=' --ldconfig-without-sudo' if args.ldconfig_without_sudo else ''
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200287 ))
288
Neels Hofmeyre274d352017-08-22 17:31:03 +0200289 # convenience target: clone all repositories first
290 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
291
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200292 # convenience target: clean all
293 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
294
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200295 # now the actual useful build rules
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200296 out.write('all: clone all-install\n\n')
297
298 out.write('all-install: \\\n\t' + ' \\\n\t'.join([ '.make.%s.install' % p for p, d in projects_deps ]) + '\n\n')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200299
300 for proj, deps in projects_deps:
301 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100302 make_dir, args.src_dir, build_dir, args.url, args.push_url,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100303 args.sudo_make_install, args.no_ldconfig,
304 args.ldconfig_without_sudo))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200305
306# vim: expandtab tabstop=2 shiftwidth=2