blob: f1decd36e84de0a7bd85a09f02299b66e3c37014 [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 Hofmeyr28d4be52018-03-16 03:44:07 +010084parser.add_argument('-p', '--push-url', dest='push_url', default=None,
85 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)
149 build_proj = os.path.join(build_dir, proj)
150
151 make_to_src = os.path.relpath(src_dir, make_dir)
152 make_to_src_proj = os.path.relpath(src_proj, make_dir)
153 make_to_build_proj = os.path.relpath(build_proj, make_dir)
154 build_to_src = os.path.relpath(src_proj, build_proj)
155
156 if configure_opts:
157 configure_opts_str = ' '.join(configure_opts)
158 else:
159 configure_opts_str = ''
160
161 # special hack for libsmpp34: cannot build in parallel
162 if proj == 'libsmpp34':
163 jobs = 1
164
165 return r'''
166### {proj} ###
167
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100168{proj}_configure_files := $(shell find {src_proj} -name "Makefile.am" -or -name "*.in" )
169{proj}_files := $(shell find {src_proj} -name "*.[hc]" -or -name "*.py" )
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200170
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200171.make.{proj}.clone:
172 @echo "\n\n\n===== $@\n"
173 test -d {src} || mkdir -p {src}
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100174 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 +0200175 touch $@
176
177.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
178 @echo "\n\n\n===== $@\n"
Neels Hofmeyr7f586cf2017-11-14 09:42:57 +0100179 -rm {src_proj}/.version
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200180 cd {src_proj}; autoreconf -fi
181 touch $@
182
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100183.make.{proj}.configure: .make.{proj}.autoconf {deps_installed} $({proj}_configure_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200184 @echo "\n\n\n===== $@\n"
185 -chmod -R ug+w {build_proj}
186 -rm -rf {build_proj}
187 mkdir -p {build_proj}
188 cd {build_proj}; {build_to_src}/configure {configure_opts}
189 touch $@
190
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100191.make.{proj}.build: .make.{proj}.configure $({proj}_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200192 @echo "\n\n\n===== $@\n"
193 $(MAKE) -C {build_proj} -j {jobs} check
194 touch $@
195
196.make.{proj}.install: .make.{proj}.build
197 @echo "\n\n\n===== $@\n"
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100198 {sudo_make_install}$(MAKE) -C {build_proj} install
199 {no_ldconfig}{sudo_ldconfig}ldconfig
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200200 touch $@
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200201
Neels Hofmeyr3f934122017-08-29 12:31:59 +0200202{proj}: .make.{proj}.install
203
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200204.PHONY: {proj}-clean
205{proj}-clean:
206 @echo "\n\n\n===== $@\n"
207 -chmod -R ug+w {build_proj}
208 -rm -rf {build_proj}
209 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200210'''.format(
211 url=url,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100212 push_url=push_url or url,
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200213 proj=proj,
214 jobs=jobs,
215 src=make_to_src,
216 src_proj=make_to_src_proj,
217 build_proj=make_to_build_proj,
218 build_to_src=build_to_src,
219 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100220 configure_opts=configure_opts_str,
221 sudo_make_install='sudo ' if sudo_make_install else '',
222 no_ldconfig='#' if no_ldconfig else '',
223 sudo_ldconfig='' if ldconfig_without_sudo else 'sudo '
224 )
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200225
226
227projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200228configure_opts = listdict()
229configure_opts_files = sorted(args.configure_opts_files or [])
230for configure_opts_file in configure_opts_files:
231 r = read_configure_opts(configure_opts_file)
232 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200233
234make_dir = args.make_dir
235if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200236 deps_name = args.projects_and_deps_file.replace('.deps', '')
237 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
238 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200239
240if not os.path.isdir(make_dir):
241 os.makedirs(make_dir)
242
243build_dir = args.build_dir
244if not build_dir:
245 build_dir = make_dir
246
247output = os.path.join(make_dir, args.output)
248print('Writing to %r' % output)
249
250with open(output, 'w') as out:
251 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
252
253 # convenience: add a regen target that updates the generated makefile itself
254 out.write(r'''
255default: all
256
257# regenerate this Makefile, in case the deps or opts changed
258.PHONY: regen
259regen:
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100260 {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 +0200261
262'''.format(
263 script=os.path.relpath(sys.argv[0], make_dir),
264 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200265 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200266 make_dir='.',
267 makefile=args.output,
268 src_dir=os.path.relpath(args.src_dir, make_dir),
269 build_dir=os.path.relpath(build_dir, make_dir),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100270 url=args.url,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100271 push_url=args.push_url,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100272 sudo_make_install=' -I' if args.sudo_make_install else '',
273 no_ldconfig=' -L' if args.no_ldconfig else '',
274 ldconfig_without_sudo=' --ldconfig-without-sudo' if args.ldconfig_without_sudo else ''
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200275 ))
276
Neels Hofmeyre274d352017-08-22 17:31:03 +0200277 # convenience target: clone all repositories first
278 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
279
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200280 # convenience target: clean all
281 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
282
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200283 # now the actual useful build rules
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200284 out.write('all: clone all-install\n\n')
285
286 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 +0200287
288 for proj, deps in projects_deps:
289 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
Neels Hofmeyr28d4be52018-03-16 03:44:07 +0100290 make_dir, args.src_dir, build_dir, args.url, args.push_url,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100291 args.sudo_make_install, args.no_ldconfig,
292 args.ldconfig_without_sudo))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200293
294# vim: expandtab tabstop=2 shiftwidth=2