blob: 81d41e75a6da378c0edf361d7f6b903ce4d453a9 [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 Hofmeyrf6078c42018-09-04 14:34:33 +0200106parser.add_argument('-c', '--no-make-check', dest='make_check',
107 default=True, action='store_false',
108 help='''do not 'make check', just 'make' to build.''')
109
Oliver Smithc47eafb2021-08-12 12:45:36 +0200110parser.add_argument('--docker-cmd',
111 help='''prefix configure/make/make install calls with this command (used by ttcn3.sh)''')
112
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200113args = parser.parse_args()
114
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200115class listdict(dict):
116 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
117
118 def add(self, name, item):
119 l = self.get(name)
120 if not l:
121 l = []
122 self[name] = l
123 l.append(item)
124
125 def extend(self, name, l):
126 for v in l:
127 self.add(name, v)
128
129 def add_dict(self, d):
130 for k,v in d.items():
131 self.add(k, v)
132
133 def extend_dict(self, d):
134 for k,v in d.items():
135 l = self.extend(k, v)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200136
137def read_projects_deps(path):
138 'Read deps config and return tuples of (project_name, which-other-to-build-first).'
139 l = []
140 for line in open(path):
141 line = line.strip()
142 if not line or line.startswith('#'):
143 continue
144 tokens = line.split()
145 l.append((tokens[0], tokens[1:]))
146 return l
147
148def read_configure_opts(path):
149 'Read config opts file and return tuples of (project_name, config-opts).'
150 if not path:
151 return {}
152 return dict(read_projects_deps(path))
153
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200154def gen_make(proj, deps, configure_opts, jobs, make_dir, src_dir, build_dir, url, push_url, sudo_make_install, no_ldconfig, ldconfig_without_sudo, make_check):
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200155 src_proj = os.path.join(src_dir, proj)
Neels Hofmeyr29fde6f2018-04-01 15:57:02 +0200156 if proj == 'openbsc':
157 src_proj = os.path.join(src_proj, 'openbsc')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200158 build_proj = os.path.join(build_dir, proj)
159
160 make_to_src = os.path.relpath(src_dir, make_dir)
161 make_to_src_proj = os.path.relpath(src_proj, make_dir)
162 make_to_build_proj = os.path.relpath(build_proj, make_dir)
163 build_to_src = os.path.relpath(src_proj, build_proj)
164
165 if configure_opts:
166 configure_opts_str = ' '.join(configure_opts)
167 else:
168 configure_opts_str = ''
169
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200170 return r'''
171### {proj} ###
172
Neels Hofmeyr0ec45922019-07-12 01:22:21 +0200173{proj}_configure_files := $(shell find {src_proj} -name "Makefile.am" -or -name "*.in" -and -not -name "Makefile.in" -and -not -name "config.h.in" )
Neels Hofmeyr44543a92019-03-10 21:08:31 +0100174{proj}_files := $(shell find {src_proj} -name "*.[hc]" -or -name "*.py" -or -name "*.cpp" -or -name "*.tpl" -or -name "*.map")
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200175
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200176.make.{proj}.clone:
Vadim Yanitskiyd9cdec62020-06-01 00:52:12 +0700177 @echo -e "\n\n\n===== $@\n"
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200178 test -d {src} || mkdir -p {src}
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100179 test -d {src_proj} || ( git -C {src} clone "{url}/{proj}" "{proj}" && git -C "{src}/{proj}" remote set-url --push origin "{push_url}/{proj}" )
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200180 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200181 touch $@
182
183.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
Vadim Yanitskiyd9cdec62020-06-01 00:52:12 +0700184 @echo -e "\n\n\n===== $@\n"
Neels Hofmeyr1fa9b292018-04-23 17:04:25 +0200185 -rm -f {src_proj}/.version
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200186 cd {src_proj}; autoreconf -fi
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200187 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200188 touch $@
189
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100190.make.{proj}.configure: .make.{proj}.autoconf {deps_installed} $({proj}_configure_files)
Vadim Yanitskiyd9cdec62020-06-01 00:52:12 +0700191 @echo -e "\n\n\n===== $@\n"
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200192 -chmod -R ug+w {build_proj}
193 -rm -rf {build_proj}
194 mkdir -p {build_proj}
Oliver Smithc47eafb2021-08-12 12:45:36 +0200195 cd {build_proj}; {docker_cmd}{build_to_src}/configure {configure_opts}
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200196 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200197 touch $@
198
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100199.make.{proj}.build: .make.{proj}.configure $({proj}_files)
Vadim Yanitskiyd9cdec62020-06-01 00:52:12 +0700200 @echo -e "\n\n\n===== $@\n"
Oliver Smithc47eafb2021-08-12 12:45:36 +0200201 {docker_cmd}$(MAKE) -C {build_proj} -j {jobs} {check}
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200202 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200203 touch $@
204
205.make.{proj}.install: .make.{proj}.build
Vadim Yanitskiyd9cdec62020-06-01 00:52:12 +0700206 @echo -e "\n\n\n===== $@\n"
Oliver Smithc47eafb2021-08-12 12:45:36 +0200207 {docker_cmd}{sudo_make_install}$(MAKE) -C {build_proj} install
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100208 {no_ldconfig}{sudo_ldconfig}ldconfig
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200209 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200210 touch $@
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200211
Neels Hofmeyr6d16ae92018-09-04 14:36:15 +0200212.PHONY: {proj}
Neels Hofmeyr3f934122017-08-29 12:31:59 +0200213{proj}: .make.{proj}.install
214
Neels Hofmeyr4d38c1e2018-04-23 17:04:15 +0200215.PHONY: {proj}-reinstall
216{proj}-reinstall: {deps_reinstall}
217 {sudo_make_install}$(MAKE) -C {build_proj} install
218
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200219.PHONY: {proj}-clean
220{proj}-clean:
Vadim Yanitskiyd9cdec62020-06-01 00:52:12 +0700221 @echo -e "\n\n\n===== $@\n"
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200222 -chmod -R ug+w {build_proj}
223 -rm -rf {build_proj}
224 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200225'''.format(
226 url=url,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100227 push_url=push_url or url,
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200228 proj=proj,
229 jobs=jobs,
230 src=make_to_src,
231 src_proj=make_to_src_proj,
232 build_proj=make_to_build_proj,
233 build_to_src=build_to_src,
234 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
Neels Hofmeyr4d38c1e2018-04-23 17:04:15 +0200235 deps_reinstall=' '.join(['%s-reinstall' %d for d in deps]),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100236 configure_opts=configure_opts_str,
237 sudo_make_install='sudo ' if sudo_make_install else '',
238 no_ldconfig='#' if no_ldconfig else '',
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200239 sudo_ldconfig='' if ldconfig_without_sudo else 'sudo ',
240 check='check' if make_check else '',
Oliver Smithc47eafb2021-08-12 12:45:36 +0200241 docker_cmd=f'{args.docker_cmd} ' if args.docker_cmd else '',
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100242 )
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200243
244
245projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200246configure_opts = listdict()
247configure_opts_files = sorted(args.configure_opts_files or [])
248for configure_opts_file in configure_opts_files:
249 r = read_configure_opts(configure_opts_file)
250 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200251
252make_dir = args.make_dir
253if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200254 deps_name = args.projects_and_deps_file.replace('.deps', '')
255 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
256 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200257
258if not os.path.isdir(make_dir):
259 os.makedirs(make_dir)
260
261build_dir = args.build_dir
262if not build_dir:
263 build_dir = make_dir
264
265output = os.path.join(make_dir, args.output)
266print('Writing to %r' % output)
267
268with open(output, 'w') as out:
269 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
270
271 # convenience: add a regen target that updates the generated makefile itself
272 out.write(r'''
273default: all
274
Neels Hofmeyr1b0d34f2018-03-16 03:46:08 +0100275.PHONY: all_debug
276all_debug:
277 $(MAKE) --dry-run -d all | grep "is newer than target"
278 $(MAKE) all
279
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200280# regenerate this Makefile, in case the deps or opts changed
281.PHONY: regen
282regen:
Oliver Smithb3ae4b62021-01-28 11:20:04 +0100283 {script} \
284 {projects_and_deps} \
285 {configure_opts} \
286 -m {make_dir} \
287 -o {makefile} \
288 -s {src_dir} \
289 -b {build_dir} \
Oliver Smithc47eafb2021-08-12 12:45:36 +0200290 -u "{url}"{push_url}{sudo_make_install}{no_ldconfig}{ldconfig_without_sudo}{make_check}{docker_cmd}
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200291
292'''.format(
293 script=os.path.relpath(sys.argv[0], make_dir),
294 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Oliver Smithb3ae4b62021-01-28 11:20:04 +0100295 configure_opts=' \\\n\t\t'.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200296 make_dir='.',
297 makefile=args.output,
298 src_dir=os.path.relpath(args.src_dir, make_dir),
299 build_dir=os.path.relpath(build_dir, make_dir),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100300 url=args.url,
Oliver Smithb3ae4b62021-01-28 11:20:04 +0100301 push_url=(" \\\n\t\t-p '%s'"%args.push_url) if args.push_url else '',
302 sudo_make_install=' \\\n\t\t-I' if args.sudo_make_install else '',
303 no_ldconfig=' \\\n\t\t-L' if args.no_ldconfig else '',
304 ldconfig_without_sudo=' \\\n\t\t--ldconfig-without-sudo' if args.ldconfig_without_sudo else '',
305 make_check='' if args.make_check else " \\\n\t\t--no-make-check",
Oliver Smithc47eafb2021-08-12 12:45:36 +0200306 docker_cmd=f' \\\n\t\t--docker-cmd "{args.docker_cmd}"' if args.docker_cmd else ''
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200307 ))
308
Neels Hofmeyre274d352017-08-22 17:31:03 +0200309 # convenience target: clone all repositories first
310 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
311
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200312 # convenience target: clean all
313 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
314
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200315 # now the actual useful build rules
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200316 out.write('all: clone all-install\n\n')
317
318 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 +0200319
320 for proj, deps in projects_deps:
Neels Hofmeyra007eaa2018-09-04 14:37:13 +0200321 all_config_opts = []
322 all_config_opts.extend(configure_opts.get('ALL') or [])
323 all_config_opts.extend(configure_opts.get(proj) or [])
324 out.write(gen_make(proj, deps, all_config_opts, args.jobs,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100325 make_dir, args.src_dir, build_dir, args.url, args.push_url,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100326 args.sudo_make_install, args.no_ldconfig,
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200327 args.ldconfig_without_sudo, args.make_check))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200328
329# vim: expandtab tabstop=2 shiftwidth=2