#!/usr/bin/env python EnsureSConsVersion(0, 98, 1) # System import glob import os import pickle import sys import re import subprocess from collections import OrderedDict #from compat import iteritems, isbasestring, open_utf8, decode_utf8, qualname from SCons import Node from SCons.Script import Glob def isbasestring(s): return isinstance(s, (str, bytes)) def add_source_files(self, sources, files, warn_duplicates=True): # Convert string to list of absolute paths (including expanding wildcard) if isbasestring(files): # Keep SCons project-absolute path as they are (no wildcard support) if files.startswith("#"): if "*" in files: print("ERROR: Wildcards can't be expanded in SCons project-absolute path: '{}'".format(files)) return files = [files] else: dir_path = self.Dir(".").abspath files = sorted(glob.glob(dir_path + "/" + files)) # Add each path as compiled Object following environment (self) configuration for path in files: obj = self.Object(path) if obj in sources: if warn_duplicates: print('WARNING: Object "{}" already included in environment sources.'.format(obj)) else: continue sources.append(obj) def add_library(env, name, sources, **args): library = env.Library(name, sources, **args) env.NoCache(library) return library def add_program(env, name, sources, **args): program = env.Program(name, sources, **args) env.NoCache(program) return program # Based on https://github.com/godotengine/godot/pull/53443 # uses the graph/topo sort from: # https://www.geeksforgeeks.org/python-program-for-topological-sorting/ # albeit highly modified class ModuleDepGraph: def __init__(self, modules, dependencies: OrderedDict): self.graph = dict() v = [] for m in modules: v.append(m[0]) self.vertices = v # construct the edges for name, deps in dependencies.items(): self.graph[name] = [] for dep_name in deps: self.graph[name].append(dep_name) # A recursive function used by dependency_sort def topological_sort_util(self, v, visited, stack): # Mark the current node as visited. visited[v] = True # Recur for all the vertices adjacent to this vertex for i in self.graph[v]: if i in visited and visited[i] == False: self.topological_sort_util(i, visited, stack) # Push current vertex to stack which stores result stack.insert(0, v) # The function to performs a topological sort, and then reverses it to obtain the dependency sort. def dependency_sort(self):# -> []: # Mark all the vertices as not visited visited = dict() for v in self.vertices: visited[v] = False stack = [] # Call the recursive helper function to store Topological # Sort starting from all vertices one by one for v in self.vertices: if visited[v] == False: self.topological_sort_util(v, visited, stack) # reverse the topological sort stack.reverse() return stack def sort_modules_dependencies(modules): deps = {} for mod in modules: name = mod[0] path = mod[1] sys.path.insert(0, path) import detect try: deps[name] = detect.get_module_dependencies() except AttributeError: deps[name] = {} sys.path.remove(path) sys.modules.pop("detect") graph = ModuleDepGraph(modules, deps) dep_sorted_names = graph.dependency_sort() for n in dep_sorted_names: for i in range(len(modules)): if modules[i][0] == n: mt = modules[i] for j in range(i + 1, len(modules)): modules[j - 1] = modules[j] modules[len(modules) - 1] = mt break #return modules env_base = Environment() env_base.__class__.add_source_files = add_source_files env_base.__class__.add_library = add_library env_base.__class__.add_program = add_program if "TERM" in os.environ: env_base["ENV"]["TERM"] = os.environ["TERM"] env_base.AppendENVPath("PATH", os.getenv("PATH")) env_base.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) env_base.disabled_modules = [] env_base.use_ptrcall = False env_base.module_version_string = "" env_base.msvc = False # avoid issues when building with different versions of python out of the same directory env_base.SConsignFile(".sconsign{0}.dblite".format(pickle.HIGHEST_PROTOCOL)) # Build options opts = Variables([], ARGUMENTS) opts.Add(EnumVariable("target", "Compilation target", "debug", ("debug", "release_debug", "release"))) opts.Add("folders", "App folders to compile", "") opts.Add("main_file", "The main file", "") opts.Add("module_folders", "App module folders to compile", "") opts.Add("databases", "Whether to have database support", False) # Compilation environment setup opts.Add("CXX", "C++ compiler") opts.Add("CC", "C compiler") opts.Add("LINK", "Linker") opts.Add("CCFLAGS", "Custom flags for both the C and C++ compilers") opts.Add("CFLAGS", "Custom flags for the C compiler") opts.Add("CXXFLAGS", "Custom flags for the C++ compiler") opts.Add("LINKFLAGS", "Custom flags for the linker") opts.Update(env_base) # add default include paths env_base.Prepend(CPPPATH=["#"]) env_base.Prepend(CPPPATH=["#libs"]) env_base.Prepend(LINKFLAGS=["-lpthread"]) #env_base.Append(CXX=["-o3"]) env_base.Append(CXX=["-g"]) #env_base.Append(CXX=["-g2"]) #env_base.Append(CXX=["-fno-rtti"]) # Compilation DB requires SCons 3.1.1+. from SCons import __version__ as scons_raw_version scons_ver = env_base._get_major_minor_revision(scons_raw_version) if scons_ver >= (4, 0, 0): env_base.Tool("compilation_db") database_list = [] if env_base["databases"]: env_base.Append(CPPDEFINES=["DATABASES_ENABLED"]) for x in sorted(glob.glob("database/*")): if not os.path.isdir(x) or not os.path.exists(x + "/detect.py"): continue tmppath = "./" + x sys.path.insert(0, tmppath) import detect if detect.is_active() and detect.can_build(): x = x.replace("database/", "") # rest of world x = x.replace("database\\", "") # win32 database_list += [x] sys.path.remove(tmppath) sys.modules.pop("detect") modfol = env_base["module_folders"].split(";") modfol.append("modules") #temporarily, these should be handled in a different pass modfol.append("backends") module_folders = list() for fol in modfol: folt = fol.strip() if folt == "": continue module_folders.append(os.path.abspath(folt) + "/*") module_list = [] for mf in module_folders: for x in sorted(glob.glob(mf)): if not os.path.isdir(x) or not os.path.exists(x + "/detect.py"): continue tmppath = os.path.realpath(os.path.expanduser(os.path.expandvars(x))) + "/" sys.path.insert(0, tmppath) import detect if detect.is_active() and detect.can_build(): #x = x.replace("/", "") # rest of world x = x.replace("\\", "/") # win32 tx = x.split("/") module_list.append([ tx[len(tx) - 1], tmppath ]) sys.path.remove(tmppath) sys.modules.pop("detect") # Sort modules dependencies sort_modules_dependencies(module_list) env = env_base.Clone() if scons_ver >= (4, 0, 0): env.Tool("compilation_db") env.Alias("compiledb", env_base.CompilationDatabase()) Export("env") SConscript("core/SCsub") if env_base["databases"]: for d in database_list: tmppath = "./database/" + d sys.path.insert(0, tmppath) import detect env_db = env_base.Clone() if scons_ver >= (4, 0, 0): env_db.Tool("compilation_db") env_db.Alias("compiledb", env_base.CompilationDatabase()) detect.configure(env_db) detect.configure(env) Export("env_db") SConscript("database/" + d + "/SCsub") sys.path.remove(tmppath) sys.modules.pop("detect") for m in module_list: tmppath = m[1] sys.path.insert(0, tmppath) import detect env_mod = env_base.Clone() # Compilation DB requires SCons 3.1.1+. from SCons import __version__ as scons_raw_version scons_ver = env_mod._get_major_minor_revision(scons_raw_version) if scons_ver >= (4, 0, 0): env_mod.Tool("compilation_db") env_mod.Alias("compiledb", env_base.CompilationDatabase()) detect.configure(env_mod) detect.configure(env) Export("env_mod") SConscript(m[1] + "/SCsub") sys.path.remove(tmppath) sys.modules.pop("detect") folders = env_base["folders"].split(";") files = [] for fol in folders: folt = fol.strip() if folt == "": continue ff = os.listdir(folt) for f in ff: if f.endswith("cpp"): files.append(os.path.abspath(fol + "/" + f)) #files.append(fol + "/" + f) env.prg_sources = files libapp = env.add_library("application", env.prg_sources) env.Prepend(LIBS=[libapp]) mfp = os.path.abspath(env_base["main_file"]) prog = env.add_program("#bin/server", [ mfp ])