blob: 1a8b751e29ae833cf9a848d623d572c861903ddf [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 Hofmeyr367cb972017-12-15 04:01:13 +0100158{proj}_configure_files := $(shell find {src_proj} -name "Makefile.am" -or -name "*.in" )
159{proj}_files := $(shell find {src_proj} -name "*.[hc]" -or -name "*.py" )
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200160
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200161.make.{proj}.clone:
162 @echo "\n\n\n===== $@\n"
163 test -d {src} || mkdir -p {src}
164 test -d {src_proj} || git -C {src} clone {url}/{proj}
165 touch $@
166
167.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
168 @echo "\n\n\n===== $@\n"
Neels Hofmeyr7f586cf2017-11-14 09:42:57 +0100169 -rm {src_proj}/.version
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200170 cd {src_proj}; autoreconf -fi
171 touch $@
172
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100173.make.{proj}.configure: .make.{proj}.autoconf {deps_installed} $({proj}_configure_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200174 @echo "\n\n\n===== $@\n"
175 -chmod -R ug+w {build_proj}
176 -rm -rf {build_proj}
177 mkdir -p {build_proj}
178 cd {build_proj}; {build_to_src}/configure {configure_opts}
179 touch $@
180
Neels Hofmeyr367cb972017-12-15 04:01:13 +0100181.make.{proj}.build: .make.{proj}.configure $({proj}_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200182 @echo "\n\n\n===== $@\n"
183 $(MAKE) -C {build_proj} -j {jobs} check
184 touch $@
185
186.make.{proj}.install: .make.{proj}.build
187 @echo "\n\n\n===== $@\n"
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100188 {sudo_make_install}$(MAKE) -C {build_proj} install
189 {no_ldconfig}{sudo_ldconfig}ldconfig
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200190 touch $@
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200191
Neels Hofmeyr3f934122017-08-29 12:31:59 +0200192{proj}: .make.{proj}.install
193
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200194.PHONY: {proj}-clean
195{proj}-clean:
196 @echo "\n\n\n===== $@\n"
197 -chmod -R ug+w {build_proj}
198 -rm -rf {build_proj}
199 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200200'''.format(
201 url=url,
202 proj=proj,
203 jobs=jobs,
204 src=make_to_src,
205 src_proj=make_to_src_proj,
206 build_proj=make_to_build_proj,
207 build_to_src=build_to_src,
208 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100209 configure_opts=configure_opts_str,
210 sudo_make_install='sudo ' if sudo_make_install else '',
211 no_ldconfig='#' if no_ldconfig else '',
212 sudo_ldconfig='' if ldconfig_without_sudo else 'sudo '
213 )
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200214
215
216projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200217configure_opts = listdict()
218configure_opts_files = sorted(args.configure_opts_files or [])
219for configure_opts_file in configure_opts_files:
220 r = read_configure_opts(configure_opts_file)
221 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200222
223make_dir = args.make_dir
224if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200225 deps_name = args.projects_and_deps_file.replace('.deps', '')
226 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
227 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200228
229if not os.path.isdir(make_dir):
230 os.makedirs(make_dir)
231
232build_dir = args.build_dir
233if not build_dir:
234 build_dir = make_dir
235
236output = os.path.join(make_dir, args.output)
237print('Writing to %r' % output)
238
239with open(output, 'w') as out:
240 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
241
242 # convenience: add a regen target that updates the generated makefile itself
243 out.write(r'''
244default: all
245
246# regenerate this Makefile, in case the deps or opts changed
247.PHONY: regen
248regen:
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100249 {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 +0200250
251'''.format(
252 script=os.path.relpath(sys.argv[0], make_dir),
253 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200254 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200255 make_dir='.',
256 makefile=args.output,
257 src_dir=os.path.relpath(args.src_dir, make_dir),
258 build_dir=os.path.relpath(build_dir, make_dir),
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100259 url=args.url,
260 sudo_make_install=' -I' if args.sudo_make_install else '',
261 no_ldconfig=' -L' if args.no_ldconfig else '',
262 ldconfig_without_sudo=' --ldconfig-without-sudo' if args.ldconfig_without_sudo else ''
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200263 ))
264
Neels Hofmeyre274d352017-08-22 17:31:03 +0200265 # convenience target: clone all repositories first
266 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
267
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200268 # convenience target: clean all
269 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
270
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200271 # now the actual useful build rules
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200272 out.write('all: clone all-install\n\n')
273
274 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 +0200275
276 for proj, deps in projects_deps:
277 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
Neels Hofmeyr461c3bd2017-12-06 00:32:15 +0100278 make_dir, args.src_dir, build_dir, args.url,
279 args.sudo_make_install, args.no_ldconfig,
280 args.ldconfig_without_sudo))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200281
282# vim: expandtab tabstop=2 shiftwidth=2