blob: 2e3f1704bbfc38ac62f3e650721b3c5bb552761c [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 Hofmeyr7f586cf2017-11-14 09:42:57 +0100181 -rm {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 Hofmeyr277f4792017-08-29 12:30:32 +0200206.PHONY: {proj}-clean
207{proj}-clean:
208 @echo "\n\n\n===== $@\n"
209 -chmod -R ug+w {build_proj}
210 -rm -rf {build_proj}
211 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200212'''.format(
213 url=url,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100214 push_url=push_url or url,
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200215 proj=proj,
216 jobs=jobs,
217 src=make_to_src,
218 src_proj=make_to_src_proj,
219 build_proj=make_to_build_proj,
220 build_to_src=build_to_src,
221 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100222 configure_opts=configure_opts_str,
223 sudo_make_install='sudo ' if sudo_make_install else '',
224 no_ldconfig='#' if no_ldconfig else '',
225 sudo_ldconfig='' if ldconfig_without_sudo else 'sudo '
226 )
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200227
228
229projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200230configure_opts = listdict()
231configure_opts_files = sorted(args.configure_opts_files or [])
232for configure_opts_file in configure_opts_files:
233 r = read_configure_opts(configure_opts_file)
234 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200235
236make_dir = args.make_dir
237if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200238 deps_name = args.projects_and_deps_file.replace('.deps', '')
239 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
240 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200241
242if not os.path.isdir(make_dir):
243 os.makedirs(make_dir)
244
245build_dir = args.build_dir
246if not build_dir:
247 build_dir = make_dir
248
249output = os.path.join(make_dir, args.output)
250print('Writing to %r' % output)
251
252with open(output, 'w') as out:
253 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
254
255 # convenience: add a regen target that updates the generated makefile itself
256 out.write(r'''
257default: all
258
Neels Hofmeyr1b0d34f2018-03-16 03:46:08 +0100259.PHONY: all_debug
260all_debug:
261 $(MAKE) --dry-run -d all | grep "is newer than target"
262 $(MAKE) all
263
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200264# regenerate this Makefile, in case the deps or opts changed
265.PHONY: regen
266regen:
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100267 {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 +0200268
269'''.format(
270 script=os.path.relpath(sys.argv[0], make_dir),
271 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200272 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200273 make_dir='.',
274 makefile=args.output,
275 src_dir=os.path.relpath(args.src_dir, make_dir),
276 build_dir=os.path.relpath(build_dir, make_dir),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100277 url=args.url,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100278 push_url=args.push_url,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100279 sudo_make_install=' -I' if args.sudo_make_install else '',
280 no_ldconfig=' -L' if args.no_ldconfig else '',
281 ldconfig_without_sudo=' --ldconfig-without-sudo' if args.ldconfig_without_sudo else ''
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200282 ))
283
Neels Hofmeyre274d352017-08-22 17:31:03 +0200284 # convenience target: clone all repositories first
285 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
286
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200287 # convenience target: clean all
288 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
289
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200290 # now the actual useful build rules
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200291 out.write('all: clone all-install\n\n')
292
293 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 +0200294
295 for proj, deps in projects_deps:
296 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100297 make_dir, args.src_dir, build_dir, args.url, args.push_url,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100298 args.sudo_make_install, args.no_ldconfig,
299 args.ldconfig_without_sudo))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200300
301# vim: expandtab tabstop=2 shiftwidth=2