blob: c3c1fccebed718c2ed80926c89fccf9417155c6a [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.
28'''
29
30import sys
31import os
32import argparse
33
34parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.RawTextHelpFormatter)
35
36parser.add_argument('projects_and_deps_file',
37 help='''Config file containing projects to build and
38dependencies between those''')
39
Neels Hofmeyr450dac72017-08-22 19:27:08 +020040parser.add_argument('configure_opts_files',
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020041 help='''Config file containing project name and
42./configure options''',
Neels Hofmeyr450dac72017-08-22 19:27:08 +020043 nargs='*')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020044
45parser.add_argument('-m', '--make-dir', dest='make_dir',
46 help='''Place Makefile in this dir (default: create
47a new dir named after deps and opts files).''')
48
49parser.add_argument('-s', '--src-dir', dest='src_dir', default='./src',
50 help='Parent dir for all git clones.')
51
52parser.add_argument('-b', '--build-dir', dest='build_dir',
53 help='''Parent dir for all build trees (default:
54directly in the make-dir).''')
55
Neels Hofmeyrbffdc302017-12-06 00:31:49 +010056parser.add_argument('-u', '--url', dest='url', default='git://git.osmocom.org',
57 help='''git clone base URL. Default is 'git://git.osmocom.org'.
58e.g. with a config like this in your ~/.ssh/config:
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020059 host go
60 hostname gerrit.osmocom.org
61 port 29418
Neels Hofmeyrbffdc302017-12-06 00:31:49 +010062you may pass '-u ssh://go' to be able to submit to gerrit.''')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020063
64parser.add_argument('-o', '--output', dest='output', default='Makefile',
65 help='''Makefile filename (default: 'Makefile').''')
66
67parser.add_argument('-j', '--jobs', dest='jobs', default='9',
68 help='''-j option to pass to 'make'.''')
69
70args = parser.parse_args()
71
Neels Hofmeyr450dac72017-08-22 19:27:08 +020072class listdict(dict):
73 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
74
75 def add(self, name, item):
76 l = self.get(name)
77 if not l:
78 l = []
79 self[name] = l
80 l.append(item)
81
82 def extend(self, name, l):
83 for v in l:
84 self.add(name, v)
85
86 def add_dict(self, d):
87 for k,v in d.items():
88 self.add(k, v)
89
90 def extend_dict(self, d):
91 for k,v in d.items():
92 l = self.extend(k, v)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +020093
94def read_projects_deps(path):
95 'Read deps config and return tuples of (project_name, which-other-to-build-first).'
96 l = []
97 for line in open(path):
98 line = line.strip()
99 if not line or line.startswith('#'):
100 continue
101 tokens = line.split()
102 l.append((tokens[0], tokens[1:]))
103 return l
104
105def read_configure_opts(path):
106 'Read config opts file and return tuples of (project_name, config-opts).'
107 if not path:
108 return {}
109 return dict(read_projects_deps(path))
110
111def gen_make(proj, deps, configure_opts, jobs, make_dir, src_dir, build_dir, url):
112 src_proj = os.path.join(src_dir, proj)
113 build_proj = os.path.join(build_dir, proj)
114
115 make_to_src = os.path.relpath(src_dir, make_dir)
116 make_to_src_proj = os.path.relpath(src_proj, make_dir)
117 make_to_build_proj = os.path.relpath(build_proj, make_dir)
118 build_to_src = os.path.relpath(src_proj, build_proj)
119
120 if configure_opts:
121 configure_opts_str = ' '.join(configure_opts)
122 else:
123 configure_opts_str = ''
124
125 # special hack for libsmpp34: cannot build in parallel
126 if proj == 'libsmpp34':
127 jobs = 1
128
129 return r'''
130### {proj} ###
131
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200132{proj}_files := $(shell find {src_proj} -name "*.[hc]" -or -name "Makefile.am" -or -name "*.py" -or -name "*.in" )
133
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200134.make.{proj}.clone:
135 @echo "\n\n\n===== $@\n"
136 test -d {src} || mkdir -p {src}
137 test -d {src_proj} || git -C {src} clone {url}/{proj}
138 touch $@
139
140.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
141 @echo "\n\n\n===== $@\n"
Neels Hofmeyr7f586cf2017-11-14 09:42:57 +0100142 -rm {src_proj}/.version
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200143 cd {src_proj}; autoreconf -fi
144 touch $@
145
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200146.make.{proj}.configure: .make.{proj}.autoconf {deps_installed} $({proj}_files)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200147 @echo "\n\n\n===== $@\n"
148 -chmod -R ug+w {build_proj}
149 -rm -rf {build_proj}
150 mkdir -p {build_proj}
151 cd {build_proj}; {build_to_src}/configure {configure_opts}
152 touch $@
153
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200154.make.{proj}.build: .make.{proj}.configure
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200155 @echo "\n\n\n===== $@\n"
156 $(MAKE) -C {build_proj} -j {jobs} check
157 touch $@
158
159.make.{proj}.install: .make.{proj}.build
160 @echo "\n\n\n===== $@\n"
161 $(MAKE) -C {build_proj} install
162 touch $@
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200163
Neels Hofmeyr3f934122017-08-29 12:31:59 +0200164{proj}: .make.{proj}.install
165
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200166.PHONY: {proj}-clean
167{proj}-clean:
168 @echo "\n\n\n===== $@\n"
169 -chmod -R ug+w {build_proj}
170 -rm -rf {build_proj}
171 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200172'''.format(
173 url=url,
174 proj=proj,
175 jobs=jobs,
176 src=make_to_src,
177 src_proj=make_to_src_proj,
178 build_proj=make_to_build_proj,
179 build_to_src=build_to_src,
180 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
181 configure_opts=configure_opts_str)
182
183
184projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200185configure_opts = listdict()
186configure_opts_files = sorted(args.configure_opts_files or [])
187for configure_opts_file in configure_opts_files:
188 r = read_configure_opts(configure_opts_file)
189 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200190
191make_dir = args.make_dir
192if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200193 deps_name = args.projects_and_deps_file.replace('.deps', '')
194 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
195 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200196
197if not os.path.isdir(make_dir):
198 os.makedirs(make_dir)
199
200build_dir = args.build_dir
201if not build_dir:
202 build_dir = make_dir
203
204output = os.path.join(make_dir, args.output)
205print('Writing to %r' % output)
206
207with open(output, 'w') as out:
208 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
209
210 # convenience: add a regen target that updates the generated makefile itself
211 out.write(r'''
212default: all
213
214# regenerate this Makefile, in case the deps or opts changed
215.PHONY: regen
216regen:
Neels Hofmeyrbffdc302017-12-06 00:31:49 +0100217 {script} {projects_and_deps} {configure_opts} -m {make_dir} -o {makefile} -s {src_dir} -b {build_dir} -u "{url}"
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200218
219'''.format(
220 script=os.path.relpath(sys.argv[0], make_dir),
221 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200222 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200223 make_dir='.',
224 makefile=args.output,
225 src_dir=os.path.relpath(args.src_dir, make_dir),
226 build_dir=os.path.relpath(build_dir, make_dir),
Neels Hofmeyrbffdc302017-12-06 00:31:49 +0100227 url=args.url
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200228 ))
229
Neels Hofmeyre274d352017-08-22 17:31:03 +0200230 # convenience target: clone all repositories first
231 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
232
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200233 # convenience target: clean all
234 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
235
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200236 # now the actual useful build rules
Neels Hofmeyr1c1e4d22017-09-11 01:32:50 +0200237 out.write('all: clone all-install\n\n')
238
239 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 +0200240
241 for proj, deps in projects_deps:
242 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
243 make_dir, args.src_dir, build_dir, args.url))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200244
245# vim: expandtab tabstop=2 shiftwidth=2