blob: 905f8e4084660ec9eecb713cbc548978a3e5ee95 [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
56parser.add_argument('-u', '--url', dest='url', default='ssh://go',
57 help='''git clone base URL. Default is 'ssh://go',
58e.g. add this to your ~/.ssh/config:
59 host go
60 hostname gerrit.osmocom.org
61 port 29418
62Alternatively pass '-u git://git.osmocom.org'.''')
63
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
132.make.{proj}.clone:
133 @echo "\n\n\n===== $@\n"
134 test -d {src} || mkdir -p {src}
135 test -d {src_proj} || git -C {src} clone {url}/{proj}
136 touch $@
137
138.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
139 @echo "\n\n\n===== $@\n"
140 cd {src_proj}; autoreconf -fi
141 touch $@
142
143.make.{proj}.configure: .make.{proj}.autoconf {deps_installed}
144 @echo "\n\n\n===== $@\n"
145 -chmod -R ug+w {build_proj}
146 -rm -rf {build_proj}
147 mkdir -p {build_proj}
148 cd {build_proj}; {build_to_src}/configure {configure_opts}
149 touch $@
150
151.make.{proj}.last_edited:
152 touch $@
153
154.PHONY: .make.{proj}.detect_edits
155.make.{proj}.detect_edits:
Neels Hofmeyr2509d092017-08-28 15:16:18 +0200156 @test -z "$(shell find {src_proj} -newer .make.{proj}.last_edited -name "*.[hc]" '(' -name "*.[hc]" -or -name "Makefile.am" -or -name "*.py" ')' )" || (touch .make.{proj}.last_edited; echo {proj} edited)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200157
158.make.{proj}.build: .make.{proj}.configure .make.{proj}.last_edited
159 @echo "\n\n\n===== $@\n"
160 $(MAKE) -C {build_proj} -j {jobs} check
161 touch $@
162
163.make.{proj}.install: .make.{proj}.build
164 @echo "\n\n\n===== $@\n"
165 $(MAKE) -C {build_proj} install
166 touch $@
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200167
Neels Hofmeyr3f934122017-08-29 12:31:59 +0200168{proj}: .make.{proj}.install
169
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200170.PHONY: {proj}-clean
171{proj}-clean:
172 @echo "\n\n\n===== $@\n"
173 -chmod -R ug+w {build_proj}
174 -rm -rf {build_proj}
175 -rm -rf .make.{proj}.*
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200176'''.format(
177 url=url,
178 proj=proj,
179 jobs=jobs,
180 src=make_to_src,
181 src_proj=make_to_src_proj,
182 build_proj=make_to_build_proj,
183 build_to_src=build_to_src,
184 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
185 configure_opts=configure_opts_str)
186
187
188projects_deps = read_projects_deps(args.projects_and_deps_file)
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200189configure_opts = listdict()
190configure_opts_files = sorted(args.configure_opts_files or [])
191for configure_opts_file in configure_opts_files:
192 r = read_configure_opts(configure_opts_file)
193 configure_opts.extend_dict(read_configure_opts(configure_opts_file))
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200194
195make_dir = args.make_dir
196if not make_dir:
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200197 deps_name = args.projects_and_deps_file.replace('.deps', '')
198 opts_names = '+'.join([f.replace('.opts', '') for f in configure_opts_files])
199 make_dir = 'make-%s-%s' % (deps_name, opts_names)
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200200
201if not os.path.isdir(make_dir):
202 os.makedirs(make_dir)
203
204build_dir = args.build_dir
205if not build_dir:
206 build_dir = make_dir
207
208output = os.path.join(make_dir, args.output)
209print('Writing to %r' % output)
210
211with open(output, 'w') as out:
212 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
213
214 # convenience: add a regen target that updates the generated makefile itself
215 out.write(r'''
216default: all
217
218# regenerate this Makefile, in case the deps or opts changed
219.PHONY: regen
220regen:
221 {script} {projects_and_deps} {configure_opts} -m {make_dir} -o {makefile} -s {src_dir} -b {build_dir}
222
223'''.format(
224 script=os.path.relpath(sys.argv[0], make_dir),
225 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
Neels Hofmeyr450dac72017-08-22 19:27:08 +0200226 configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]),
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200227 make_dir='.',
228 makefile=args.output,
229 src_dir=os.path.relpath(args.src_dir, make_dir),
230 build_dir=os.path.relpath(build_dir, make_dir),
231 ))
232
Neels Hofmeyre274d352017-08-22 17:31:03 +0200233 # convenience target: clone all repositories first
234 out.write('clone: \\\n\t' + ' \\\n\t'.join([ '.make.%s.clone' % p for p, d in projects_deps ]) + '\n\n')
235
Neels Hofmeyr277f4792017-08-29 12:30:32 +0200236 # convenience target: clean all
237 out.write('clean: \\\n\t' + ' \\\n\t'.join([ '%s-clean' % p for p, d in projects_deps ]) + '\n\n')
238
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200239 # now the actual useful build rules
Neels Hofmeyr20e169f2017-08-22 17:31:35 +0200240 out.write('all: \\\n\tclone \\\n\t' + ' \\\n\t'.join([ '.make.%s.detect_edits .make.%s.install' % (p,p) for p, d in projects_deps ]) + '\n\n')
Neels Hofmeyr0a1bdff2017-08-13 03:22:42 +0200241
242 for proj, deps in projects_deps:
243 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
244 make_dir, args.src_dir, build_dir, args.url))
Neels Hofmeyrd4d88482017-08-22 19:27:28 +0200245
246# vim: expandtab tabstop=2 shiftwidth=2