Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | ''' |
| 3 | Generate a top-level makefile that builds the Osmocom 2G + 3G network components. |
| 4 | |
Neels Hofmeyr | 450dac7 | 2017-08-22 19:27:08 +0200 | [diff] [blame] | 5 | ./gen_makefile.py projects.deps [configure.opts [more.opts]] [-o Makefile.output] |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 6 | |
| 7 | Configured 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 | |
| 15 | Thus 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 | |
| 20 | From the Makefile nature, the dependencies extend, no need to repeat common deps. |
| 21 | |
| 22 | When this script is done, a Makefile has been generated that allows you to |
| 23 | build all projects at once by issuing 'make', but also to refresh only parts of |
| 24 | it when some bits in the middle have changed. The makefile keeps local progress |
| 25 | marker files like .make.libosmocore.configure; if such progress marker is |
| 26 | removed or becomes outdated, that step and all dependent ones are re-run. |
| 27 | This is helpful in daily hacking across several repositories. |
| 28 | ''' |
| 29 | |
| 30 | import sys |
| 31 | import os |
| 32 | import argparse |
| 33 | |
| 34 | parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.RawTextHelpFormatter) |
| 35 | |
| 36 | parser.add_argument('projects_and_deps_file', |
| 37 | help='''Config file containing projects to build and |
| 38 | dependencies between those''') |
| 39 | |
Neels Hofmeyr | 450dac7 | 2017-08-22 19:27:08 +0200 | [diff] [blame] | 40 | parser.add_argument('configure_opts_files', |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 41 | help='''Config file containing project name and |
| 42 | ./configure options''', |
Neels Hofmeyr | 450dac7 | 2017-08-22 19:27:08 +0200 | [diff] [blame] | 43 | nargs='*') |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 44 | |
| 45 | parser.add_argument('-m', '--make-dir', dest='make_dir', |
| 46 | help='''Place Makefile in this dir (default: create |
| 47 | a new dir named after deps and opts files).''') |
| 48 | |
| 49 | parser.add_argument('-s', '--src-dir', dest='src_dir', default='./src', |
| 50 | help='Parent dir for all git clones.') |
| 51 | |
| 52 | parser.add_argument('-b', '--build-dir', dest='build_dir', |
| 53 | help='''Parent dir for all build trees (default: |
| 54 | directly in the make-dir).''') |
| 55 | |
Neels Hofmeyr | bffdc30 | 2017-12-06 00:31:49 +0100 | [diff] [blame^] | 56 | parser.add_argument('-u', '--url', dest='url', default='git://git.osmocom.org', |
| 57 | help='''git clone base URL. Default is 'git://git.osmocom.org'. |
| 58 | e.g. with a config like this in your ~/.ssh/config: |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 59 | host go |
| 60 | hostname gerrit.osmocom.org |
| 61 | port 29418 |
Neels Hofmeyr | bffdc30 | 2017-12-06 00:31:49 +0100 | [diff] [blame^] | 62 | you may pass '-u ssh://go' to be able to submit to gerrit.''') |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 63 | |
| 64 | parser.add_argument('-o', '--output', dest='output', default='Makefile', |
| 65 | help='''Makefile filename (default: 'Makefile').''') |
| 66 | |
| 67 | parser.add_argument('-j', '--jobs', dest='jobs', default='9', |
| 68 | help='''-j option to pass to 'make'.''') |
| 69 | |
| 70 | args = parser.parse_args() |
| 71 | |
Neels Hofmeyr | 450dac7 | 2017-08-22 19:27:08 +0200 | [diff] [blame] | 72 | class 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 Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 93 | |
| 94 | def 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 | |
| 105 | def 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 | |
| 111 | def 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 Hofmeyr | 1c1e4d2 | 2017-09-11 01:32:50 +0200 | [diff] [blame] | 132 | {proj}_files := $(shell find {src_proj} -name "*.[hc]" -or -name "Makefile.am" -or -name "*.py" -or -name "*.in" ) |
| 133 | |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 134 | .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 Hofmeyr | 7f586cf | 2017-11-14 09:42:57 +0100 | [diff] [blame] | 142 | -rm {src_proj}/.version |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 143 | cd {src_proj}; autoreconf -fi |
| 144 | touch $@ |
| 145 | |
Neels Hofmeyr | 1c1e4d2 | 2017-09-11 01:32:50 +0200 | [diff] [blame] | 146 | .make.{proj}.configure: .make.{proj}.autoconf {deps_installed} $({proj}_files) |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 147 | @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 Hofmeyr | 1c1e4d2 | 2017-09-11 01:32:50 +0200 | [diff] [blame] | 154 | .make.{proj}.build: .make.{proj}.configure |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 155 | @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 Hofmeyr | 277f479 | 2017-08-29 12:30:32 +0200 | [diff] [blame] | 163 | |
Neels Hofmeyr | 3f93412 | 2017-08-29 12:31:59 +0200 | [diff] [blame] | 164 | {proj}: .make.{proj}.install |
| 165 | |
Neels Hofmeyr | 277f479 | 2017-08-29 12:30:32 +0200 | [diff] [blame] | 166 | .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 Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 172 | '''.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 | |
| 184 | projects_deps = read_projects_deps(args.projects_and_deps_file) |
Neels Hofmeyr | 450dac7 | 2017-08-22 19:27:08 +0200 | [diff] [blame] | 185 | configure_opts = listdict() |
| 186 | configure_opts_files = sorted(args.configure_opts_files or []) |
| 187 | for 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 Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 190 | |
| 191 | make_dir = args.make_dir |
| 192 | if not make_dir: |
Neels Hofmeyr | 450dac7 | 2017-08-22 19:27:08 +0200 | [diff] [blame] | 193 | 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 Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 196 | |
| 197 | if not os.path.isdir(make_dir): |
| 198 | os.makedirs(make_dir) |
| 199 | |
| 200 | build_dir = args.build_dir |
| 201 | if not build_dir: |
| 202 | build_dir = make_dir |
| 203 | |
| 204 | output = os.path.join(make_dir, args.output) |
| 205 | print('Writing to %r' % output) |
| 206 | |
| 207 | with 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''' |
| 212 | default: all |
| 213 | |
| 214 | # regenerate this Makefile, in case the deps or opts changed |
| 215 | .PHONY: regen |
| 216 | regen: |
Neels Hofmeyr | bffdc30 | 2017-12-06 00:31:49 +0100 | [diff] [blame^] | 217 | {script} {projects_and_deps} {configure_opts} -m {make_dir} -o {makefile} -s {src_dir} -b {build_dir} -u "{url}" |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 218 | |
| 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 Hofmeyr | 450dac7 | 2017-08-22 19:27:08 +0200 | [diff] [blame] | 222 | configure_opts=' '.join([os.path.relpath(f, make_dir) for f in configure_opts_files]), |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 223 | 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 Hofmeyr | bffdc30 | 2017-12-06 00:31:49 +0100 | [diff] [blame^] | 227 | url=args.url |
Neels Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 228 | )) |
| 229 | |
Neels Hofmeyr | e274d35 | 2017-08-22 17:31:03 +0200 | [diff] [blame] | 230 | # 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 Hofmeyr | 277f479 | 2017-08-29 12:30:32 +0200 | [diff] [blame] | 233 | # 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 Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 236 | # now the actual useful build rules |
Neels Hofmeyr | 1c1e4d2 | 2017-09-11 01:32:50 +0200 | [diff] [blame] | 237 | 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 Hofmeyr | 0a1bdff | 2017-08-13 03:22:42 +0200 | [diff] [blame] | 240 | |
| 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 Hofmeyr | d4d8848 | 2017-08-22 19:27:28 +0200 | [diff] [blame] | 244 | |
| 245 | # vim: expandtab tabstop=2 shiftwidth=2 |