]> jfr.im git - irc/quakenet/newserv.git/blame - configure
Merge pull request #132 from retropc/lua_country
[irc/quakenet/newserv.git] / configure
CommitLineData
454f22b8 1#!/usr/bin/env python3
d5713c3b
Q
2
3import sys
4import os
5import platform
6import getopt
7b6c91a3
CP
7import subprocess
8import errno
9import shlex
d5713c3b
Q
10
11LOG = None
12VERBOSE = False
ddb996fc 13REQUIRE_ALL = False
7b6c91a3 14
1bf60a7d
CP
15class CheckCallException(Exception):
16 pass
d5713c3b
Q
17
18# standard line print
19def lprint(x):
454f22b8 20 print(x)
d5713c3b
Q
21 LOG.write(x + "\n")
22
23# verbose print
24def vprint(x=""):
25 if VERBOSE:
454f22b8 26 print(x)
d5713c3b
Q
27 LOG.write(x + "\n")
28
29# immediate (no newline) print
30def iprint(x):
31 sys.stdout.write(x)
32 sys.stdout.flush()
33 LOG.write(x)
93a4606a 34
d5713c3b 35class 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
69class 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
120class 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 136def 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
145def pkgconfig(args):
146 return check_call(["pkg-config"] + args)
147
148def 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
162def 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
181def 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 220def 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
240def 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
276def 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
294def 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
305def 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
339def check_require_all():
340 if REQUIRE_ALL:
341 lprint("configure: require-all selected, so failing")
342 sys.exit(1)
d5713c3b 343
93a4606a
JH
344validopts = {}
345
d5713c3b 346def 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 359def 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
427if __name__ == "__main__":
c33a5aa2 428 main()