blob: a68bc008f84cc2f0f3b76677576636b5698d01d4 [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
5 ./gen_makefile.py projects.deps [configuration.opts] [-o Makefile.output]
6
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
40parser.add_argument('configure_opts_file',
41 help='''Config file containing project name and
42./configure options''',
43 default=None, nargs='?')
44
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
72
73def read_projects_deps(path):
74 'Read deps config and return tuples of (project_name, which-other-to-build-first).'
75 l = []
76 for line in open(path):
77 line = line.strip()
78 if not line or line.startswith('#'):
79 continue
80 tokens = line.split()
81 l.append((tokens[0], tokens[1:]))
82 return l
83
84def read_configure_opts(path):
85 'Read config opts file and return tuples of (project_name, config-opts).'
86 if not path:
87 return {}
88 return dict(read_projects_deps(path))
89
90def gen_make(proj, deps, configure_opts, jobs, make_dir, src_dir, build_dir, url):
91 src_proj = os.path.join(src_dir, proj)
92 build_proj = os.path.join(build_dir, proj)
93
94 make_to_src = os.path.relpath(src_dir, make_dir)
95 make_to_src_proj = os.path.relpath(src_proj, make_dir)
96 make_to_build_proj = os.path.relpath(build_proj, make_dir)
97 build_to_src = os.path.relpath(src_proj, build_proj)
98
99 if configure_opts:
100 configure_opts_str = ' '.join(configure_opts)
101 else:
102 configure_opts_str = ''
103
104 # special hack for libsmpp34: cannot build in parallel
105 if proj == 'libsmpp34':
106 jobs = 1
107
108 return r'''
109### {proj} ###
110
111.make.{proj}.clone:
112 @echo "\n\n\n===== $@\n"
113 test -d {src} || mkdir -p {src}
114 test -d {src_proj} || git -C {src} clone {url}/{proj}
115 touch $@
116
117.make.{proj}.autoconf: .make.{proj}.clone {src_proj}/configure.ac
118 @echo "\n\n\n===== $@\n"
119 cd {src_proj}; autoreconf -fi
120 touch $@
121
122.make.{proj}.configure: .make.{proj}.autoconf {deps_installed}
123 @echo "\n\n\n===== $@\n"
124 -chmod -R ug+w {build_proj}
125 -rm -rf {build_proj}
126 mkdir -p {build_proj}
127 cd {build_proj}; {build_to_src}/configure {configure_opts}
128 touch $@
129
130.make.{proj}.last_edited:
131 touch $@
132
133.PHONY: .make.{proj}.detect_edits
134.make.{proj}.detect_edits:
135 @test -z "$(shell find {src_proj} -newer .make.{proj}.last_edited -name "*.[hc]")" || (touch .make.{proj}.last_edited; echo {proj} edited)
136
137.make.{proj}.build: .make.{proj}.configure .make.{proj}.last_edited
138 @echo "\n\n\n===== $@\n"
139 $(MAKE) -C {build_proj} -j {jobs} check
140 touch $@
141
142.make.{proj}.install: .make.{proj}.build
143 @echo "\n\n\n===== $@\n"
144 $(MAKE) -C {build_proj} install
145 touch $@
146'''.format(
147 url=url,
148 proj=proj,
149 jobs=jobs,
150 src=make_to_src,
151 src_proj=make_to_src_proj,
152 build_proj=make_to_build_proj,
153 build_to_src=build_to_src,
154 deps_installed=' '.join(['.make.%s.install' % d for d in deps]),
155 configure_opts=configure_opts_str)
156
157
158projects_deps = read_projects_deps(args.projects_and_deps_file)
159configure_opts = read_configure_opts(args.configure_opts_file)
160
161make_dir = args.make_dir
162if not make_dir:
163 make_dir = 'make-%s-%s' % (args.projects_and_deps_file, args.configure_opts_file)
164
165if not os.path.isdir(make_dir):
166 os.makedirs(make_dir)
167
168build_dir = args.build_dir
169if not build_dir:
170 build_dir = make_dir
171
172output = os.path.join(make_dir, args.output)
173print('Writing to %r' % output)
174
175with open(output, 'w') as out:
176 out.write('# This Makefile was generated by %s\n' % os.path.basename(sys.argv[0]))
177
178 # convenience: add a regen target that updates the generated makefile itself
179 out.write(r'''
180default: all
181
182# regenerate this Makefile, in case the deps or opts changed
183.PHONY: regen
184regen:
185 {script} {projects_and_deps} {configure_opts} -m {make_dir} -o {makefile} -s {src_dir} -b {build_dir}
186
187'''.format(
188 script=os.path.relpath(sys.argv[0], make_dir),
189 projects_and_deps=os.path.relpath(args.projects_and_deps_file, make_dir),
190 configure_opts=os.path.relpath(args.configure_opts_file, make_dir),
191 make_dir='.',
192 makefile=args.output,
193 src_dir=os.path.relpath(args.src_dir, make_dir),
194 build_dir=os.path.relpath(build_dir, make_dir),
195 ))
196
197 # now the actual useful build rules
198 out.write('all: \\\n\t' + ' \\\n\t'.join([ '.make.%s.detect_edits .make.%s.install' % (p,p) for p, d in projects_deps ]) + '\n\n')
199
200 for proj, deps in projects_deps:
201 out.write(gen_make(proj, deps, configure_opts.get(proj), args.jobs,
202 make_dir, args.src_dir, build_dir, args.url))