blob: e35331c02f0e0af14163c35f7da1071935c89630 [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 Hofmeyr0a1bdff2017-08-13 03:22:42 +020041'''
42
43import sys
44import os
45import argparse
46
47parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.RawTextHelpFormatter)
48
49parser.add_argument('projects_and_deps_file',
50 help='''Config file containing projects to build and
51dependencies between those''')
52
Neels Hofmeyr450dac72017-08-22 19:27:08 +020053parser.add_argument('configure_opts_files',
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020054 help='''Config file containing project name and
55./configure options''',
Neels Hofmeyr450dac72017-08-22 19:27:08 +020056 nargs='*')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020057
58parser.add_argument('-m', '--make-dir', dest='make_dir',
59 help='''Place Makefile in this dir (default: create
60a new dir named after deps and opts files).''')
61
62parser.add_argument('-s', '--src-dir', dest='src_dir', default='./src',
63 help='Parent dir for all git clones.')
64
65parser.add_argument('-b', '--build-dir', dest='build_dir',
66 help='''Parent dir for all build trees (default:
67directly in the make-dir).''')
68
Neels Hofmeyrbffdc302017-12-06 00:31:49 +010069parser.add_argument('-u', '--url', dest='url', default='git://git.osmocom.org',
70 help='''git clone base URL. Default is 'git://git.osmocom.org'.
71e.g. with a config like this in your ~/.ssh/config:
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020072 host go
73 hostname gerrit.osmocom.org
74 port 29418
Neels Hofmeyrbffdc302017-12-06 00:31:49 +010075you may pass '-u ssh://go' to be able to submit to gerrit.''')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020076
77parser.add_argument('-o', '--output', dest='output', default='Makefile',
78 help='''Makefile filename (default: 'Makefile').''')
79
80parser.add_argument('-j', '--jobs', dest='jobs', default='9',
81 help='''-j option to pass to 'make'.''')
82
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +010083parser.add_argument('-I', '--sudo-make-install', dest='sudo_make_install',
84 action='store_true',
85 help='''run 'make install' step with 'sudo'.''')
86
87parser.add_argument('-L', '--no-ldconfig', dest='no_ldconfig',
88 action='store_true',
89 help='''omit the 'sudo ldconfig' step.''')
90
91parser.add_argument('--ldconfig-without-sudo', dest='ldconfig_without_sudo',
92 action='store_true',
93 help='''call just 'ldconfig', without sudo, which implies
94root privileges (not recommended)''')
95
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020096args = parser.parse_args()
97
Neels Hofmeyr450dac72017-08-22 19:27:08 +020098class listdict(dict):
99 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
100
101 def add(self, name, item):
102 l = self.get(name)
103 if not l:
104 l = []
105 self[name] = l
106 l.append(item)
107
108 def extend(self, name, l):
109 for v in l:
110 self.add(name, v)
111
112 def add_dict(self, d):
113 for k,v in d.items():
114 self.add(k, v)
115
116 def extend_dict(self, d):
117 for k,v in d.items():
118 l = self.extend(k, v)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200119
120def read_projects_deps(path):
121 'Read deps config and return tuples of (project_name, which-other-to-build-first).'
122 l = []
123 for line in open(path):
124 line = line.strip()
125 if not line or line.startswith('#'):
126 continue
127 tokens = line.split()
128 l.append((tokens[0], tokens[1:]))
129 return l
130
131def read_configure_opts(path):
132 'Read config opts file and return tuples of (project_name, config-opts).'
133 if not path:
134 return {}
135 return dict(read_projects_deps(path))
136
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100137def gen_make(proj, deps, configure_opts, jobs, make_dir, src_dir, build_dir, url, sudo_make_install, no_ldconfig, ldconfig_without_sudo):
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200138 src_proj = os.path.join(src_dir, proj)
139 build_proj = os.path.join(build_dir, proj)
140
141 make_to_src = os.path.relpath(src_dir, make_dir)
142 make_to_src_proj = os.path.relpath(src_proj, make_dir)
143 make_to_build_proj = os.path.relpath(build_proj, make_dir)
144 build_to_src = os.path.relpath(src_proj, build_proj)
145
146 if configure_opts:
147 configure_opts_str = ' '.join(configure_opts)
148 else:
149 configure_opts_str = ''
150
151 # special hack for libsmpp34: cannot build in parallel
152 if proj == 'libsmpp34':
153 jobs = 1
154
155 return r'''
156### {proj} ###
157
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200158{proj}_files := $(shell find {src_proj} -name "*.[hc]" -or -name "Makefile.am" -or -name "*.py" -or -name "*.in" )
159
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200160.make.{proj}.clone:
161 @echo "\n\n\n===== $@\n"
162 test -d {src} || mkdir -p {src}
163 test -d {src_proj} || git -C {src} clone {url}/{proj}
164 touch $@
165
166.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
167 @echo "\n\n\n===== $@\n"
Neels Hofmeyr7f586cf2017-11-14 09:42:57 +0100168 -rm {src_proj}/.version
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200169 cd {src_proj}; autoreconf -fi
170 touch $@
171
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200172.make.{proj}.configure: .make.{proj}.autoconf {deps_installed} $({proj}_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200173 @echo "\n\n\n===== $@\n"
174 -chmod -R ug+w {build_proj}
175 -rm -rf {build_proj}
176 mkdir -p {build_proj}
177 cd {build_proj}; {build_to_src}/configure {configure_opts}
178 touch $@
179
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200180.make.{proj}.build: .make.{proj}.configure
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200181 @echo "\n\n\n===== $@\n"
182 $(MAKE) -C {build_proj} -j {jobs} check
183 touch $@
184
185.make.{proj}.install: .make.{proj}.build
186 @echo "\n\n\n===== $@\n"
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100187 {sudo_make_install}$(MAKE) -C {build_proj} install
188 {no_ldconfig}{sudo_ldconfig}ldconfig
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200189 touch $@
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200190
Neels Hofmeyr3f934122017-08-29 12:31:59 +0200191{proj}: .make.{proj}.install
192
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200193.PHONY: {proj}-clean
194{proj}-clean:
195 @echo "\n\n\n===== $@\n"
196 -chmod -R ug+w {build_proj}
197 -rm -rf {build_proj}
198 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200199'''.format(
200 url=url,
201 proj=proj,
202 jobs=jobs,
203 src=make_to_src,
204 src_proj=make_to_src_proj,
205 build_proj=make_to_build_proj,
206 build_to_src=build_to_src,
207 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100208 configure_opts=configure_opts_str,
209 sudo_make_install='sudo ' if sudo_make_install else '',
210 no_ldconfig='#' if no_ldconfig else '',
211 sudo_ldconfig='' if ldconfig_without_sudo else 'sudo '
212 )
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200213
214
215projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200216configure_opts = listdict()
217configure_opts_files = sorted(args.configure_opts_files or [])
218for configure_opts_file in configure_opts_files:
219 r = read_configure_opts(configure_opts_file)
220 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200221
222make_dir = args.make_dir
223if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200224 deps_name = args.projects_and_deps_file.replace('.deps', '')
225 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
226 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200227
228if not os.path.isdir(make_dir):
229 os.makedirs(make_dir)
230
231build_dir = args.build_dir
232if not build_dir:
233 build_dir = make_dir
234
235output = os.path.join(make_dir, args.output)
236print('Writing to %r' % output)
237
238with open(output, 'w') as out:
239 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
240
241 # convenience: add a regen target that updates the generated makefile itself
242 out.write(r'''
243default: all
244
245# regenerate this Makefile, in case the deps or opts changed
246.PHONY: regen
247regen:
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100248 {script} {projects_and_deps} {configure_opts} -m {make_dir} -o {makefile} -s {src_dir} -b {build_dir} -u "{url}"{sudo_make_install}{no_ldconfig}{ldconfig_without_sudo}
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200249
250'''.format(
251 script=os.path.relpath(sys.argv[0], make_dir),
252 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200253 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200254 make_dir='.',
255 makefile=args.output,
256 src_dir=os.path.relpath(args.src_dir, make_dir),
257 build_dir=os.path.relpath(build_dir, make_dir),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100258 url=args.url,
259 sudo_make_install=' -I' if args.sudo_make_install else '',
260 no_ldconfig=' -L' if args.no_ldconfig else '',
261 ldconfig_without_sudo=' --ldconfig-without-sudo' if args.ldconfig_without_sudo else ''
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200262 ))
263
Neels Hofmeyre274d352017-08-22 17:31:03 +0200264 # convenience target: clone all repositories first
265 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
266
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200267 # convenience target: clean all
268 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
269
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200270 # now the actual useful build rules
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200271 out.write('all: clone all-install\n\n')
272
273 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 +0200274
275 for proj, deps in projects_deps:
276 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100277 make_dir, args.src_dir, build_dir, args.url,
278 args.sudo_make_install, args.no_ldconfig,
279 args.ldconfig_without_sudo))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200280
281# vim: expandtab tabstop=2 shiftwidth=2