1import os
2import platform
3import sys
4from methods import get_compiler_version, using_gcc, using_clang
5
6
7def is_active():
8    return True
9
10
11def get_name():
12    return "X11"
13
14
15def can_build():
16
17    if os.name != "posix" or sys.platform == "darwin":
18        return False
19
20    # Check the minimal dependencies
21    x11_error = os.system("pkg-config --version > /dev/null")
22    if x11_error:
23        return False
24
25    x11_error = os.system("pkg-config x11 --modversion > /dev/null ")
26    if x11_error:
27        return False
28
29    x11_error = os.system("pkg-config xcursor --modversion > /dev/null ")
30    if x11_error:
31        print("xcursor not found.. x11 disabled.")
32        return False
33
34    x11_error = os.system("pkg-config xinerama --modversion > /dev/null ")
35    if x11_error:
36        print("xinerama not found.. x11 disabled.")
37        return False
38
39    x11_error = os.system("pkg-config xrandr --modversion > /dev/null ")
40    if x11_error:
41        print("xrandr not found.. x11 disabled.")
42        return False
43
44    x11_error = os.system("pkg-config xrender --modversion > /dev/null ")
45    if x11_error:
46        print("xrender not found.. x11 disabled.")
47        return False
48
49    x11_error = os.system("pkg-config xi --modversion > /dev/null ")
50    if x11_error:
51        print("xi not found.. Aborting.")
52        return False
53
54    return True
55
56
57def get_opts():
58    from SCons.Variables import BoolVariable, EnumVariable
59
60    return [
61        BoolVariable("use_llvm", "Use the LLVM compiler", False),
62        BoolVariable("use_lld", "Use the LLD linker", False),
63        BoolVariable("use_thinlto", "Use ThinLTO", False),
64        BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", False),
65        BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False),
66        BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False),
67        BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN))", False),
68        BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False),
69        BoolVariable("pulseaudio", "Detect and use PulseAudio", True),
70        BoolVariable("udev", "Use udev for gamepad connection callbacks", False),
71        EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")),
72        BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False),
73        BoolVariable("touch", "Enable touch events", True),
74        BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False),
75    ]
76
77
78def get_flags():
79
80    return []
81
82
83def configure(env):
84
85    ## Build type
86
87    if env["target"] == "release":
88        if env["optimize"] == "speed":  # optimize for speed (default)
89            env.Prepend(CCFLAGS=["-O3"])
90        else:  # optimize for size
91            env.Prepend(CCFLAGS=["-Os"])
92
93        if env["debug_symbols"] == "yes":
94            env.Prepend(CCFLAGS=["-g1"])
95        if env["debug_symbols"] == "full":
96            env.Prepend(CCFLAGS=["-g2"])
97
98    elif env["target"] == "release_debug":
99        if env["optimize"] == "speed":  # optimize for speed (default)
100            env.Prepend(CCFLAGS=["-O2"])
101        else:  # optimize for size
102            env.Prepend(CCFLAGS=["-Os"])
103        env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
104
105        if env["debug_symbols"] == "yes":
106            env.Prepend(CCFLAGS=["-g1"])
107        if env["debug_symbols"] == "full":
108            env.Prepend(CCFLAGS=["-g2"])
109
110    elif env["target"] == "debug":
111        env.Prepend(CCFLAGS=["-g3"])
112        env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
113        env.Append(LINKFLAGS=["-rdynamic"])
114
115    ## Architecture
116
117    is64 = sys.maxsize > 2 ** 32
118    if env["bits"] == "default":
119        env["bits"] = "64" if is64 else "32"
120
121    ## Compiler configuration
122
123    if "CXX" in env and "clang" in os.path.basename(env["CXX"]):
124        # Convenience check to enforce the use_llvm overrides when CXX is clang(++)
125        env["use_llvm"] = True
126
127    if env["use_llvm"]:
128        if "clang++" not in os.path.basename(env["CXX"]):
129            env["CC"] = "clang"
130            env["CXX"] = "clang++"
131            env["LINK"] = "clang++"
132        env.Append(CPPDEFINES=["TYPED_METHOD_BIND"])
133        env.extra_suffix = ".llvm" + env.extra_suffix
134
135    if env["use_lld"]:
136        if env["use_llvm"]:
137            env.Append(LINKFLAGS=["-fuse-ld=lld"])
138            if env["use_thinlto"]:
139                # A convenience so you don't need to write use_lto too when using SCons
140                env["use_lto"] = True
141        else:
142            print("Using LLD with GCC is not supported yet, try compiling with 'use_llvm=yes'.")
143            sys.exit(255)
144
145    if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"]:
146        env.extra_suffix += "s"
147
148        if env["use_ubsan"]:
149            env.Append(CCFLAGS=["-fsanitize=undefined"])
150            env.Append(LINKFLAGS=["-fsanitize=undefined"])
151
152        if env["use_asan"]:
153            env.Append(CCFLAGS=["-fsanitize=address"])
154            env.Append(LINKFLAGS=["-fsanitize=address"])
155
156        if env["use_lsan"]:
157            env.Append(CCFLAGS=["-fsanitize=leak"])
158            env.Append(LINKFLAGS=["-fsanitize=leak"])
159
160        if env["use_tsan"]:
161            env.Append(CCFLAGS=["-fsanitize=thread"])
162            env.Append(LINKFLAGS=["-fsanitize=thread"])
163
164    if env["use_lto"]:
165        if not env["use_llvm"] and env.GetOption("num_jobs") > 1:
166            env.Append(CCFLAGS=["-flto"])
167            env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))])
168        else:
169            if env["use_lld"] and env["use_thinlto"]:
170                env.Append(CCFLAGS=["-flto=thin"])
171                env.Append(LINKFLAGS=["-flto=thin"])
172            else:
173                env.Append(CCFLAGS=["-flto"])
174                env.Append(LINKFLAGS=["-flto"])
175
176        if not env["use_llvm"]:
177            env["RANLIB"] = "gcc-ranlib"
178            env["AR"] = "gcc-ar"
179
180    env.Append(CCFLAGS=["-pipe"])
181    env.Append(LINKFLAGS=["-pipe"])
182
183    # Check for gcc version >= 6 before adding -no-pie
184    version = get_compiler_version(env) or [-1, -1]
185    if using_gcc(env):
186        if version[0] >= 6:
187            env.Append(CCFLAGS=["-fpie"])
188            env.Append(LINKFLAGS=["-no-pie"])
189    # Do the same for clang should be fine with Clang 4 and higher
190    if using_clang(env):
191        if version[0] >= 4:
192            env.Append(CCFLAGS=["-fpie"])
193            env.Append(LINKFLAGS=["-no-pie"])
194
195    ## Dependencies
196
197    env.ParseConfig("pkg-config x11 --cflags --libs")
198    env.ParseConfig("pkg-config xcursor --cflags --libs")
199    env.ParseConfig("pkg-config xinerama --cflags --libs")
200    env.ParseConfig("pkg-config xrandr --cflags --libs")
201    env.ParseConfig("pkg-config xrender --cflags --libs")
202    env.ParseConfig("pkg-config xi --cflags --libs")
203
204    if env["touch"]:
205        env.Append(CPPDEFINES=["TOUCH_ENABLED"])
206
207    # FIXME: Check for existence of the libs before parsing their flags with pkg-config
208
209    # freetype depends on libpng and zlib, so bundling one of them while keeping others
210    # as shared libraries leads to weird issues
211    if env["builtin_freetype"] or env["builtin_libpng"] or env["builtin_zlib"]:
212        env["builtin_freetype"] = True
213        env["builtin_libpng"] = True
214        env["builtin_zlib"] = True
215
216    if not env["builtin_freetype"]:
217        env.ParseConfig("pkg-config freetype2 --cflags --libs")
218
219    if not env["builtin_libpng"]:
220        env.ParseConfig("pkg-config libpng16 --cflags --libs")
221
222    if not env["builtin_bullet"]:
223        # We need at least version 2.89
224        import subprocess
225
226        bullet_version = subprocess.check_output(["pkg-config", "bullet", "--modversion"]).strip()
227        if str(bullet_version) < "2.89":
228            # Abort as system bullet was requested but too old
229            print(
230                "Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(
231                    bullet_version, "2.89"
232                )
233            )
234            sys.exit(255)
235        env.ParseConfig("pkg-config bullet --cflags --libs")
236
237    if False:  # not env['builtin_assimp']:
238        # FIXME: Add min version check
239        env.ParseConfig("pkg-config assimp --cflags --libs")
240
241    if not env["builtin_enet"]:
242        env.ParseConfig("pkg-config libenet --cflags --libs")
243
244    if not env["builtin_squish"]:
245        env.ParseConfig("pkg-config libsquish --cflags --libs")
246
247    if not env["builtin_zstd"]:
248        env.ParseConfig("pkg-config libzstd --cflags --libs")
249
250    # Sound and video libraries
251    # Keep the order as it triggers chained dependencies (ogg needed by others, etc.)
252
253    if not env["builtin_libtheora"]:
254        env["builtin_libogg"] = False  # Needed to link against system libtheora
255        env["builtin_libvorbis"] = False  # Needed to link against system libtheora
256        env.ParseConfig("pkg-config theora theoradec --cflags --libs")
257    else:
258        list_of_x86 = ["x86_64", "x86", "i386", "i586"]
259        if any(platform.machine() in s for s in list_of_x86):
260            env["x86_libtheora_opt_gcc"] = True
261
262    if not env["builtin_libvpx"]:
263        env.ParseConfig("pkg-config vpx --cflags --libs")
264
265    if not env["builtin_libvorbis"]:
266        env["builtin_libogg"] = False  # Needed to link against system libvorbis
267        env.ParseConfig("pkg-config vorbis vorbisfile --cflags --libs")
268
269    if not env["builtin_opus"]:
270        env["builtin_libogg"] = False  # Needed to link against system opus
271        env.ParseConfig("pkg-config opus opusfile --cflags --libs")
272
273    if not env["builtin_libogg"]:
274        env.ParseConfig("pkg-config ogg --cflags --libs")
275
276    if not env["builtin_libwebp"]:
277        env.ParseConfig("pkg-config libwebp --cflags --libs")
278
279    if not env["builtin_mbedtls"]:
280        # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228
281        env.Append(LIBS=["mbedtls", "mbedcrypto", "mbedx509"])
282
283    if not env["builtin_wslay"]:
284        env.ParseConfig("pkg-config libwslay --cflags --libs")
285
286    if not env["builtin_miniupnpc"]:
287        # No pkgconfig file so far, hardcode default paths.
288        env.Prepend(CPPPATH=["/usr/include/miniupnpc"])
289        env.Append(LIBS=["miniupnpc"])
290
291    # On Linux wchar_t should be 32-bits
292    # 16-bit library shouldn't be required due to compiler optimisations
293    if not env["builtin_pcre2"]:
294        env.ParseConfig("pkg-config libpcre2-32 --cflags --libs")
295
296    ## Flags
297
298    if os.system("pkg-config --exists alsa") == 0:  # 0 means found
299        print("Enabling ALSA")
300        env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"])
301        # Don't parse --cflags, we don't need to add /usr/include/alsa to include path
302        env.ParseConfig("pkg-config alsa --libs")
303    else:
304        print("ALSA libraries not found, disabling driver")
305
306    if env["pulseaudio"]:
307        if os.system("pkg-config --exists libpulse") == 0:  # 0 means found
308            print("Enabling PulseAudio")
309            env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"])
310            env.ParseConfig("pkg-config --cflags --libs libpulse")
311        else:
312            print("PulseAudio development libraries not found, disabling driver")
313
314    if platform.system() == "Linux":
315        env.Append(CPPDEFINES=["JOYDEV_ENABLED"])
316
317        if env["udev"]:
318            if os.system("pkg-config --exists libudev") == 0:  # 0 means found
319                print("Enabling udev support")
320                env.Append(CPPDEFINES=["UDEV_ENABLED"])
321                env.ParseConfig("pkg-config libudev --cflags --libs")
322            else:
323                print("libudev development libraries not found, disabling udev support")
324
325    # Linkflags below this line should typically stay the last ones
326    if not env["builtin_zlib"]:
327        env.ParseConfig("pkg-config zlib --cflags --libs")
328
329    env.Prepend(CPPPATH=["#platform/x11"])
330    env.Append(CPPDEFINES=["X11_ENABLED", "UNIX_ENABLED", "OPENGL_ENABLED", "GLES_ENABLED"])
331    env.Append(LIBS=["GL", "pthread"])
332
333    if platform.system() == "Linux":
334        env.Append(LIBS=["dl"])
335
336    if platform.system().find("BSD") >= 0:
337        env["execinfo"] = True
338
339    if env["execinfo"]:
340        env.Append(LIBS=["execinfo"])
341
342    if not env["tools"]:
343        import subprocess
344        import re
345
346        linker_version_str = subprocess.check_output([env.subst(env["LINK"]), "-Wl,--version"]).decode("utf-8")
347        gnu_ld_version = re.search("^GNU ld [^$]*(\d+\.\d+)$", linker_version_str, re.MULTILINE)
348        if not gnu_ld_version:
349            print(
350                "Warning: Creating template binaries enabled for PCK embedding is currently only supported with GNU ld"
351            )
352        else:
353            if float(gnu_ld_version.group(1)) >= 2.30:
354                env.Append(LINKFLAGS=["-T", "platform/x11/pck_embed.ld"])
355            else:
356                env.Append(LINKFLAGS=["-T", "platform/x11/pck_embed.legacy.ld"])
357
358    ## Cross-compilation
359
360    if is64 and env["bits"] == "32":
361        env.Append(CCFLAGS=["-m32"])
362        env.Append(LINKFLAGS=["-m32", "-L/usr/lib/i386-linux-gnu"])
363    elif not is64 and env["bits"] == "64":
364        env.Append(CCFLAGS=["-m64"])
365        env.Append(LINKFLAGS=["-m64", "-L/usr/lib/i686-linux-gnu"])
366
367    # Link those statically for portability
368    if env["use_static_cpp"]:
369        env.Append(LINKFLAGS=["-static-libgcc", "-static-libstdc++"])
370