blob: 588a37b628fcbc82f37a32126ed3ff5ae5481af6 [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
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200110args = parser.parse_args()
111
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200112class listdict(dict):
113 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
114
115 def add(self, name, item):
116 l = self.get(name)
117 if not l:
118 l = []
119 self[name] = l
120 l.append(item)
121
122 def extend(self, name, l):
123 for v in l:
124 self.add(name, v)
125
126 def add_dict(self, d):
127 for k,v in d.items():
128 self.add(k, v)
129
130 def extend_dict(self, d):
131 for k,v in d.items():
132 l = self.extend(k, v)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200133
134def read_projects_deps(path):
135 'Read deps config and return tuples of (project_name, which-other-to-build-first).'
136 l = []
137 for line in open(path):
138 line = line.strip()
139 if not line or line.startswith('#'):
140 continue
141 tokens = line.split()
142 l.append((tokens[0], tokens[1:]))
143 return l
144
145def read_configure_opts(path):
146 'Read config opts file and return tuples of (project_name, config-opts).'
147 if not path:
148 return {}
149 return dict(read_projects_deps(path))
150
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200151def 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 +0200152 src_proj = os.path.join(src_dir, proj)
Neels Hofmeyr29fde6f2018-04-01 15:57:02 +0200153 if proj == 'openbsc':
154 src_proj = os.path.join(src_proj, 'openbsc')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200155 build_proj = os.path.join(build_dir, proj)
156
157 make_to_src = os.path.relpath(src_dir, make_dir)
158 make_to_src_proj = os.path.relpath(src_proj, make_dir)
159 make_to_build_proj = os.path.relpath(build_proj, make_dir)
160 build_to_src = os.path.relpath(src_proj, build_proj)
161
162 if configure_opts:
163 configure_opts_str = ' '.join(configure_opts)
164 else:
165 configure_opts_str = ''
166
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200167 return r'''
168### {proj} ###
169
Neels Hofmeyr6257d9e2019-02-12 15:02:59 +0100170{proj}_configure_files := $(shell find {src_proj} -name "Makefile.am" -or -name "*.in" -and -not -name "Makefile.in" )
Neels Hofmeyra4fefee2019-02-12 15:02:59 +0100171{proj}_files := $(shell find {src_proj} -name "*.[hc]" -or -name "*.py" -or -name "*.cpp" -or -name "*.tpl" )
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 Hofmeyr7bd5c312018-07-26 16:43:01 +0200177 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200178 touch $@
179
180.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
181 @echo "\n\n\n===== $@\n"
Neels Hofmeyr1fa9b292018-04-23 17:04:25 +0200182 -rm -f {src_proj}/.version
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200183 cd {src_proj}; autoreconf -fi
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200184 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200185 touch $@
186
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100187.make.{proj}.configure: .make.{proj}.autoconf {deps_installed} $({proj}_configure_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200188 @echo "\n\n\n===== $@\n"
189 -chmod -R ug+w {build_proj}
190 -rm -rf {build_proj}
191 mkdir -p {build_proj}
192 cd {build_proj}; {build_to_src}/configure {configure_opts}
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200193 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200194 touch $@
195
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100196.make.{proj}.build: .make.{proj}.configure $({proj}_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200197 @echo "\n\n\n===== $@\n"
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200198 $(MAKE) -C {build_proj} -j {jobs} {check}
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200199 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200200 touch $@
201
202.make.{proj}.install: .make.{proj}.build
203 @echo "\n\n\n===== $@\n"
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100204 {sudo_make_install}$(MAKE) -C {build_proj} install
205 {no_ldconfig}{sudo_ldconfig}ldconfig
Neels Hofmeyr7bd5c312018-07-26 16:43:01 +0200206 sync
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200207 touch $@
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200208
Neels Hofmeyr6d16ae92018-09-04 14:36:15 +0200209.PHONY: {proj}
Neels Hofmeyr3f934122017-08-29 12:31:59 +0200210{proj}: .make.{proj}.install
211
Neels Hofmeyr4d38c1e2018-04-23 17:04:15 +0200212.PHONY: {proj}-reinstall
213{proj}-reinstall: {deps_reinstall}
214 {sudo_make_install}$(MAKE) -C {build_proj} install
215
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200216.PHONY: {proj}-clean
217{proj}-clean:
218 @echo "\n\n\n===== $@\n"
219 -chmod -R ug+w {build_proj}
220 -rm -rf {build_proj}
221 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200222'''.format(
223 url=url,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100224 push_url=push_url or url,
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200225 proj=proj,
226 jobs=jobs,
227 src=make_to_src,
228 src_proj=make_to_src_proj,
229 build_proj=make_to_build_proj,
230 build_to_src=build_to_src,
231 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
Neels Hofmeyr4d38c1e2018-04-23 17:04:15 +0200232 deps_reinstall=' '.join(['%s-reinstall' %d for d in deps]),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100233 configure_opts=configure_opts_str,
234 sudo_make_install='sudo ' if sudo_make_install else '',
235 no_ldconfig='#' if no_ldconfig else '',
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200236 sudo_ldconfig='' if ldconfig_without_sudo else 'sudo ',
237 check='check' if make_check else '',
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100238 )
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200239
240
241projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200242configure_opts = listdict()
243configure_opts_files = sorted(args.configure_opts_files or [])
244for configure_opts_file in configure_opts_files:
245 r = read_configure_opts(configure_opts_file)
246 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200247
248make_dir = args.make_dir
249if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200250 deps_name = args.projects_and_deps_file.replace('.deps', '')
251 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
252 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200253
254if not os.path.isdir(make_dir):
255 os.makedirs(make_dir)
256
257build_dir = args.build_dir
258if not build_dir:
259 build_dir = make_dir
260
261output = os.path.join(make_dir, args.output)
262print('Writing to %r' % output)
263
264with open(output, 'w') as out:
265 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
266
267 # convenience: add a regen target that updates the generated makefile itself
268 out.write(r'''
269default: all
270
Neels Hofmeyr1b0d34f2018-03-16 03:46:08 +0100271.PHONY: all_debug
272all_debug:
273 $(MAKE) --dry-run -d all | grep "is newer than target"
274 $(MAKE) all
275
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200276# regenerate this Makefile, in case the deps or opts changed
277.PHONY: regen
278regen:
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200279 {script} {projects_and_deps} {configure_opts} -m {make_dir} -o {makefile} -s {src_dir} -b {build_dir} -u "{url}"{push_url}{sudo_make_install}{no_ldconfig}{ldconfig_without_sudo}{make_check}
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200280
281'''.format(
282 script=os.path.relpath(sys.argv[0], make_dir),
283 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200284 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200285 make_dir='.',
286 makefile=args.output,
287 src_dir=os.path.relpath(args.src_dir, make_dir),
288 build_dir=os.path.relpath(build_dir, make_dir),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100289 url=args.url,
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200290 push_url=(" -p '%s'"%args.push_url) if args.push_url else '',
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100291 sudo_make_install=' -I' if args.sudo_make_install else '',
292 no_ldconfig=' -L' if args.no_ldconfig else '',
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200293 ldconfig_without_sudo=' --ldconfig-without-sudo' if args.ldconfig_without_sudo else '',
294 make_check='' if args.make_check else " --no-make-check",
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200295 ))
296
Neels Hofmeyre274d352017-08-22 17:31:03 +0200297 # convenience target: clone all repositories first
298 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
299
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200300 # convenience target: clean all
301 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
302
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200303 # now the actual useful build rules
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200304 out.write('all: clone all-install\n\n')
305
306 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 +0200307
308 for proj, deps in projects_deps:
Neels Hofmeyra007eaa2018-09-04 14:37:13 +0200309 all_config_opts = []
310 all_config_opts.extend(configure_opts.get('ALL') or [])
311 all_config_opts.extend(configure_opts.get(proj) or [])
312 out.write(gen_make(proj, deps, all_config_opts, args.jobs,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100313 make_dir, args.src_dir, build_dir, args.url, args.push_url,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100314 args.sudo_make_install, args.no_ldconfig,
Neels Hofmeyrf6078c42018-09-04 14:34:33 +0200315 args.ldconfig_without_sudo, args.make_check))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200316
317# vim: expandtab tabstop=2 shiftwidth=2