]>
Commit | Line | Data |
---|---|---|
454f22b8 | 1 | #!/usr/bin/env python3 |
d5713c3b Q |
2 | |
3 | import sys | |
4 | import os | |
5 | import platform | |
6 | import getopt | |
7b6c91a3 CP |
7 | import subprocess |
8 | import errno | |
9 | import shlex | |
d5713c3b Q |
10 | |
11 | LOG = None | |
12 | VERBOSE = False | |
ddb996fc | 13 | REQUIRE_ALL = False |
7b6c91a3 | 14 | |
1bf60a7d CP |
15 | class CheckCallException(Exception): |
16 | pass | |
d5713c3b Q |
17 | |
18 | # standard line print | |
19 | def lprint(x): | |
454f22b8 | 20 | print(x) |
d5713c3b Q |
21 | LOG.write(x + "\n") |
22 | ||
23 | # verbose print | |
24 | def vprint(x=""): | |
25 | if VERBOSE: | |
454f22b8 | 26 | print(x) |
d5713c3b Q |
27 | LOG.write(x + "\n") |
28 | ||
29 | # immediate (no newline) print | |
30 | def iprint(x): | |
31 | sys.stdout.write(x) | |
32 | sys.stdout.flush() | |
33 | LOG.write(x) | |
93a4606a | 34 | |
d5713c3b | 35 | class IniParser: |
454f22b8 | 36 | def __init__(self, f): |
d5713c3b Q |
37 | self.__d = {} |
38 | sectiondata = None | |
454f22b8 | 39 | for x in f.readlines(): |
d5713c3b Q |
40 | x = x.replace("\r\n", "").replace("\n", "") |
41 | xs = x.strip() | |
42 | if xs == "" or xs.startswith("#"): | |
43 | continue | |
44 | ||
45 | if x.startswith("[") and x.endswith("]"): | |
46 | sectiondata = {} | |
47 | keydata = [] | |
48 | self.__d[x[1:-1]] = (sectiondata, keydata) | |
49 | continue | |
50 | ||
7b6c91a3 CP |
51 | key, value = x.split("=", 1) |
52 | sectiondata[key] = value | |
53 | keydata.append((key, value)) | |
d5713c3b Q |
54 | |
55 | def has_key(self, key): | |
454f22b8 | 56 | return key in self.__d |
d5713c3b | 57 | |
454f22b8 CP |
58 | def get(self, key, value=None): |
59 | if key not in self.__d: | |
60 | return value | |
61 | return self[key] | |
d5713c3b Q |
62 | |
63 | def __getitem__(self, key): | |
64 | return self.__d[key][0] | |
65 | ||
66 | def keys(self, key): | |
67 | return self.__d[key][1] | |
68 | ||
69 | class ConfigureIniParser(IniParser): | |
454f22b8 CP |
70 | def __init__(self, f): |
71 | super().__init__(f) | |
d5713c3b Q |
72 | |
73 | self.modules = {} | |
74 | self.buildorder = [] | |
75 | self.updatemodules(self.keys("modules")) | |
76 | ||
93a4606a | 77 | self.selectlibs = {} |
454f22b8 | 78 | for k, v in self.get("selectlibs", {}).items(): |
d5713c3b Q |
79 | self.selectlibs[k] = v.split() |
80 | ||
7b6c91a3 CP |
81 | self.libs = {} |
82 | for x in self["core"]["libs"].split(): | |
1bf60a7d | 83 | section = f"lib{x}" |
7b6c91a3 CP |
84 | if "run" in self[section]: |
85 | subsections = self[section]["run"].split(" ") | |
86 | else: | |
87 | subsections = [section] | |
d5713c3b | 88 | |
7b6c91a3 CP |
89 | self.libs[x] = [] |
90 | for subsection in subsections: | |
91 | self.libs[x].append(dict(self[subsection])) | |
d5713c3b Q |
92 | |
93 | self.osflags = {} | |
94 | if self.has_key("osvars"): | |
95 | for k, v in self.keys("osvars"): | |
2b0fc7ed | 96 | self.osflags.setdefault(k, []).append(v) |
d5713c3b | 97 | |
d5713c3b Q |
98 | self.options = self["options"] |
99 | ||
100 | def configprint(self): | |
101 | vprint("--- config --------------------------------------------") | |
102 | for x in dir(self): | |
103 | if x.startswith("_"): | |
104 | continue | |
105 | v = getattr(self, x) | |
106 | if not isinstance(v, list) and not isinstance(v, dict): | |
107 | continue | |
1bf60a7d | 108 | vprint(f"{x!r:50}: {v!r}") |
d5713c3b Q |
109 | vprint("--- config --------------------------------------------") |
110 | ||
111 | def updatemodules(self, x, workspace = None): | |
112 | for k, v in x: | |
113 | if workspace and workspace != ".": | |
114 | name = workspace + "/" + k | |
115 | else: | |
116 | name = k | |
117 | self.buildorder.append(name) | |
118 | self.modules[name] = v.split() | |
119 | ||
120 | class MultiConfigureIniParser(ConfigureIniParser): | |
121 | def __init__(self, files): | |
454f22b8 | 122 | super().__init__(files[0][1]) |
d5713c3b Q |
123 | |
124 | for workspace, file in files[1:]: | |
125 | c2 = IniParser(file) | |
126 | if c2.has_key("modules"): | |
127 | self.updatemodules(c2.keys("modules"), workspace) | |
128 | ||
d5713c3b Q |
129 | if c2.has_key("options"): |
130 | self.options.update(c2["options"]) | |
131 | ||
49378f95 CP |
132 | if c2.has_key("osvars"): |
133 | for k, v in c2.keys("osvars"): | |
2b0fc7ed | 134 | self.osflags.setdefault(k, []).append(v) |
49378f95 | 135 | |
7b6c91a3 | 136 | def check_call(args): |
1bf60a7d | 137 | vprint(f"invoking: {args!r}") |
454f22b8 | 138 | p = subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="latin-1") |
7b6c91a3 CP |
139 | stdout, stderr = p.communicate() |
140 | if p.returncode != 0: | |
1bf60a7d CP |
141 | raise CheckCallException(f"bad return code: {p.returncode}, stdout: {stdout!r} stderr: {stderr!r}") |
142 | vprint(f"return code: {p.returncode}, stdout: {stdout!r} stderr: {stderr!r}") | |
7b6c91a3 CP |
143 | return stdout.split("\n")[0] |
144 | ||
145 | def pkgconfig(args): | |
146 | return check_call(["pkg-config"] + args) | |
147 | ||
148 | def check_pkgconfig(d): | |
149 | package = d.get("pkgconfig") | |
150 | if not package: | |
151 | return | |
152 | ||
153 | try: | |
154 | pkgconfig(["--exists", package]) | |
155 | except CheckCallException: | |
156 | return | |
157 | ||
158 | libpath = pkgconfig(["--libs", package]) | |
159 | incpath = pkgconfig(["--cflags", package]) | |
160 | return libpath, incpath | |
d5713c3b | 161 | |
7b6c91a3 CP |
162 | def check_exec(d): |
163 | if "libexec" not in d or "incexec" not in d: | |
164 | return | |
165 | ||
166 | try: | |
167 | libpath = check_call(shlex.split(d["libexec"])) | |
168 | incpath = check_call(shlex.split(d["incexec"])) | |
454f22b8 CP |
169 | except FileNotFoundError: |
170 | # already logged | |
7b6c91a3 CP |
171 | return |
172 | ||
173 | libspec = d.get("libspec", "{}") | |
174 | lib = libspec.replace("{}", libpath) | |
d5713c3b | 175 | |
7b6c91a3 CP |
176 | incspec = d.get("incspec", "{}") |
177 | inc = incspec.replace("{}", incpath) | |
d5713c3b | 178 | |
7b6c91a3 CP |
179 | return lib, inc |
180 | ||
181 | def librarycheck(libraries): | |
182 | def findlibrarypaths(library, defn): | |
183 | for x in defn: | |
184 | if x.get("alwayspresent"): | |
185 | return True | |
186 | ||
187 | v = check_pkgconfig(x) | |
188 | if v is not None: | |
189 | return v | |
190 | v = check_exec(x) | |
191 | if v is not None: | |
192 | return v | |
d5713c3b Q |
193 | |
194 | libsfound = [] | |
195 | output = [] | |
196 | ||
1bf60a7d CP |
197 | for k in libraries: |
198 | iprint(f"checking for {k}... ") | |
7b6c91a3 | 199 | ret = findlibrarypaths(k, libraries[k]) |
d5713c3b Q |
200 | if not ret: |
201 | lprint("failed") | |
202 | continue | |
203 | ||
204 | libsfound.append(k) | |
205 | ||
206 | if ret is not True: | |
7b6c91a3 | 207 | lib, inc = ret |
1bf60a7d CP |
208 | libline = f"LIB{k.upper()}={lib}" |
209 | incline = f"INC{k.upper()}={inc}" | |
d5713c3b Q |
210 | output.append(libline) |
211 | output.append(incline) | |
212 | ||
213 | lprint("ok") | |
214 | if ret is not True: | |
1bf60a7d CP |
215 | vprint(f"library path: {libline}") |
216 | vprint(f"include path: {incline}") | |
d5713c3b Q |
217 | |
218 | return output, libsfound | |
219 | ||
bb98972a | 220 | def systemcheck(osflags): |
d5713c3b Q |
221 | output = [] |
222 | ||
223 | iprint("checking for system... ") | |
224 | system = platform.system() | |
225 | lprint(system) | |
226 | ||
7b6c91a3 CP |
227 | iprint("checking for pkg-config... ") |
228 | try: | |
229 | pkgconfig(["--version"]) | |
454f22b8 | 230 | except FileNotFoundError: |
7b6c91a3 CP |
231 | lprint("not found") |
232 | lprint("pkg-config is required to continue, aborting!") | |
233 | sys.exit(1) | |
234 | lprint("ok") | |
235 | ||
454f22b8 | 236 | for v in osflags.get(system, []): |
d5713c3b | 237 | output.append(v) |
bb98972a | 238 | return output, |
d5713c3b Q |
239 | |
240 | def modulecheck(modules, libsfound, buildorder, selectlibs, overrides): | |
241 | defaultselections = {} | |
242 | ||
243 | for k, v in selectlibs.items(): | |
454f22b8 | 244 | if k in overrides: |
d5713c3b Q |
245 | assert overrides[k] in libsfound |
246 | libsfound.append(k) | |
247 | defaultselections[k] = overrides[k] | |
248 | else: | |
249 | for x in v: | |
250 | if x in libsfound: | |
251 | libsfound.append(k) | |
252 | defaultselections[k] = x | |
253 | break | |
254 | ||
255 | building = set() | |
256 | for k, v in modules.items(): | |
257 | for x in v: | |
258 | if x not in libsfound: | |
259 | break | |
260 | else: | |
261 | building.add(k) | |
262 | ||
263 | notfound = set(filter(lambda x: not os.path.exists(x), building)) | |
264 | ||
265 | cantbuild = set(modules) - building | |
266 | building = building - notfound | |
267 | ||
268 | orderedbuild = [] | |
269 | for x in buildorder: | |
270 | if x in building: | |
271 | orderedbuild.append(x) | |
272 | ||
1bf60a7d | 273 | build = [f"DIRS={' '.join(orderedbuild)}"] |
d5713c3b Q |
274 | return build, orderedbuild, notfound, cantbuild, defaultselections |
275 | ||
276 | def writemakefile(inc, out, appendstart=None, appendend=None, silent=False): | |
1bf60a7d CP |
277 | with open(out, "w") as p: |
278 | p.write(f"## AUTOMATICALLY GENERATED -- EDIT {inc} INSTEAD\n\n") | |
279 | if appendstart: | |
280 | p.write("\n".join(appendstart)) | |
281 | p.write("\n") | |
d5713c3b | 282 | |
1bf60a7d CP |
283 | with open(inc, "r") as f: |
284 | for l in f.readlines(): | |
285 | p.write(l) | |
d5713c3b | 286 | |
1bf60a7d CP |
287 | if appendend: |
288 | p.write("\n".join(appendend)) | |
289 | p.write("\n") | |
d5713c3b | 290 | |
d5713c3b | 291 | if not silent: |
1bf60a7d | 292 | lprint(f"configure: wrote {out}") |
d5713c3b | 293 | |
1bf60a7d CP |
294 | def writeconfigh(filename, modules, defaultselections): |
295 | with open(filename, "w") as f: | |
296 | f.write("/* AUTOMATICALLY GENERATED -- DO NOT EDIT */\n") | |
d5713c3b | 297 | |
1bf60a7d CP |
298 | for x in modules: |
299 | f.write(f"#define HAVE_{x.upper()}\n") | |
300 | for k, v in defaultselections.items(): | |
301 | f.write(f"#define USE_{k.upper()}_{v.upper()}\n") | |
d5713c3b | 302 | |
1bf60a7d | 303 | lprint(f"configure: wrote {filename}") |
d5713c3b Q |
304 | |
305 | def configure(config, selectoverrides, workspaces): | |
bb98972a CP |
306 | # figure out any custom OS flags |
307 | flags, = systemcheck(config.osflags) | |
d5713c3b Q |
308 | |
309 | # find the libraries/includes we have and their paths | |
7b6c91a3 | 310 | f, libsfound = librarycheck(config.libs) |
d5713c3b Q |
311 | for k, v in selectoverrides.items(): |
312 | if not v in libsfound: | |
1bf60a7d | 313 | lprint(f"configure: can't set {k} to {v} as {v} was not found.") |
d5713c3b Q |
314 | return |
315 | ||
316 | flags = flags + f | |
317 | ||
318 | # see which modules we can build | |
319 | buildlines, building, notfound, cantbuild, defaultselections = modulecheck(config.modules, libsfound, config.buildorder, config.selectlibs, selectoverrides) | |
320 | ||
321 | for k, v in defaultselections.items(): | |
1bf60a7d CP |
322 | lprint(f"configure: selected {v} as {k}") |
323 | flags.append(f"LIB{k.upper()}=$(LIB{v.upper()})") | |
324 | flags.append(f"INC{k.upper()}=$(INC{v.upper()})") | |
d5713c3b Q |
325 | |
326 | writemakefile("build.mk.in", "build.mk", appendend=flags + ["=".join(x) for x in config.options.items()] + ["DIRS=" + " ".join(building), "WORKSPACES=" + " ".join(workspaces)]) | |
327 | ||
328 | writeconfigh("config.h", libsfound, defaultselections) | |
329 | ||
1bf60a7d | 330 | lprint(f"configure: selected: {' '.join(building)}") |
d5713c3b | 331 | if len(notfound) > 0: |
1bf60a7d | 332 | lprint(f"configure: couldn't find: {' '.join(notfound)}") |
ddb996fc | 333 | check_require_all() |
d5713c3b Q |
334 | |
335 | if len(cantbuild) > 0: | |
1bf60a7d | 336 | lprint(f"configure: can't select: {' '.join(cantbuild)}") |
ddb996fc CP |
337 | check_require_all() |
338 | ||
339 | def check_require_all(): | |
340 | if REQUIRE_ALL: | |
341 | lprint("configure: require-all selected, so failing") | |
342 | sys.exit(1) | |
d5713c3b | 343 | |
93a4606a JH |
344 | validopts = {} |
345 | ||
d5713c3b | 346 | def usage(): |
454f22b8 | 347 | print() |
1bf60a7d | 348 | print(f" Usage: {sys.argv[0]} [-h] [-v] [options]") |
454f22b8 CP |
349 | print() |
350 | print(" Additional options are:") | |
d5713c3b | 351 | for k, v in validopts.items(): |
1bf60a7d | 352 | print(f" --with-{v[0]}=[{'|'.join(v[1])}]") |
454f22b8 CP |
353 | print(" -L [additional lib dir]") |
354 | print(" -I [additional include dir]") | |
355 | print(" -m [additional module]") | |
356 | print(" -R: require everything") | |
357 | print(" -v: verbose") | |
d5713c3b | 358 | |
c33a5aa2 | 359 | def main(): |
ddb996fc | 360 | global LOG, VERBOSE, REQUIRE_ALL |
d5713c3b | 361 | |
c33a5aa2 CP |
362 | files, workspaces = [], [] |
363 | for root, _, file_list in os.walk("."): | |
364 | if "configure.ini" not in file_list: | |
365 | continue | |
d5713c3b | 366 | |
1bf60a7d | 367 | print(f"found workspace: {root}") |
c33a5aa2 | 368 | workspaces.append(root) |
d5713c3b | 369 | |
c33a5aa2 CP |
370 | path = os.path.join(root, "configure.ini") |
371 | files.append( (root, open(path, "r")) ) | |
d5713c3b | 372 | |
c33a5aa2 CP |
373 | local_path = os.path.join(root, "configure.ini.local") |
374 | if os.path.exists(local_path): | |
375 | files.append( (root, open(local_path, "r")) ) | |
d5713c3b Q |
376 | |
377 | config = MultiConfigureIniParser(files) | |
378 | ||
379 | mopts = [] | |
93a4606a | 380 | |
d5713c3b | 381 | for k, v in config.selectlibs.items(): |
1bf60a7d CP |
382 | mopts.append(f"with-{k}=") |
383 | validopts[f"--with-{k}"] = (k, v) | |
d5713c3b Q |
384 | |
385 | try: | |
ddb996fc | 386 | opts, args = getopt.getopt(sys.argv[1:], "hvcI:L:m:R", mopts) |
454f22b8 CP |
387 | except getopt.GetoptError as err: |
388 | print(str(err)) | |
d5713c3b | 389 | usage() |
454f22b8 | 390 | sys.exit(1) |
d5713c3b Q |
391 | |
392 | overrides = {} | |
393 | libs = [] | |
394 | includes = [] | |
395 | modules = [] | |
396 | ||
397 | for o, a in opts: | |
454f22b8 | 398 | if o in validopts: |
d5713c3b Q |
399 | v = validopts[o] |
400 | if not a in v[1]: | |
401 | usage() | |
402 | return | |
403 | overrides[v[0]] = a | |
404 | elif o == "-h": | |
405 | usage() | |
406 | return | |
407 | elif o == "-v": | |
408 | VERBOSE = True | |
ddb996fc CP |
409 | elif o == "-R": |
410 | REQUIRE_ALL = True | |
d5713c3b Q |
411 | elif o == "-L": |
412 | libs.append(a) | |
413 | elif o == "-I": | |
414 | includes.append(a) | |
415 | elif o == "-m": | |
416 | modules.append(a) | |
417 | else: | |
1bf60a7d | 418 | raise Exception(f"unknown option: {o!r}") |
d5713c3b | 419 | |
1bf60a7d CP |
420 | with open(".configure.log", "w") as LOG: |
421 | vprint(f"invoked as: {sys.argv!r}") | |
422 | config.updatemodules([(x, "") for x in modules]) | |
423 | config.configprint() | |
d5713c3b | 424 | |
1bf60a7d | 425 | configure(config, overrides, workspaces) |
d5713c3b Q |
426 | |
427 | if __name__ == "__main__": | |
c33a5aa2 | 428 | main() |