diff options
author | Spartan322 <Megacake1234@gmail.com> | 2023-07-25 02:24:01 +0200 |
---|---|---|
committer | Spartan322 <Megacake1234@gmail.com> | 2023-07-26 23:54:58 +0200 |
commit | be1d0545c2f7a85a63d05b4bdc1020ee284e72cb (patch) | |
tree | 09cb0fa0a1dbe83d4833bcd62dc8832161e4329b | |
parent | 65443efcc2f4c7d687b2bd9c631f6bb426688bbf (diff) |
Initial structural commit
-rw-r--r-- | .gitignore | 76 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | SConstruct | 277 | ||||
-rw-r--r-- | deps/SCsub | 45 | ||||
m--------- | deps/lexy | 0 | ||||
-rw-r--r-- | include/openvic-dataloader/ovscript/.gitkeep | 0 | ||||
-rw-r--r-- | include/openvic-dataloader/v2script/Parser.hpp | 27 | ||||
-rw-r--r-- | scripts/build/cache.py | 127 | ||||
-rw-r--r-- | scripts/build/glob_recursive.py | 15 | ||||
-rw-r--r-- | scripts/build/option_handler.py | 39 | ||||
-rw-r--r-- | src/headless/main.cpp | 3 | ||||
-rw-r--r-- | src/openvic-dataloader/ovscript/.gitkeep | 0 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/Grammar.cpp | 74 | ||||
-rw-r--r-- | tools/linux.py | 35 | ||||
-rw-r--r-- | tools/macos.py | 51 | ||||
-rw-r--r-- | tools/macos_osxcross.py | 29 | ||||
-rw-r--r-- | tools/my_spawn.py | 53 | ||||
-rw-r--r-- | tools/targets.py | 93 | ||||
-rw-r--r-- | tools/windows.py | 73 |
19 files changed, 1020 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8b9d7f --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# clangd +.cache/* + +# VSCode +.vscode/* +!.vscode/launch.json +!.vscode/tasks.json + +# Visual Studio +.vs/ + +# Godot 4+ specific ignores +.godot/ +game/bin/openvic/* +.sconsign*.dblite + +# Binaries +*.o +*.os +*.so +*.obj +*.bc +*.pyc +*.dblite +*.pdb +*.lib +bin/* +*.config +*.creator +*.creator.user +*.files +*.includes +*.idb +*.exp + +# Build configuarion. +/custom.py + +# MacOS stuff +.DS_Store + +*.translation +!game/common/map/*.obj
\ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..af9d8f4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/lexy"] + path = deps/lexy + url = https://github.com/foonathan/lexy diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..423994d --- /dev/null +++ b/SConstruct @@ -0,0 +1,277 @@ +#!/usr/bin/env python + +# This file is heavily based on https://github.com/godotengine/godot-cpp/blob/8155f35b29b4b08bc54b2eb0c57e1e9effe9f093/SConstruct +import os +import platform +import sys +import subprocess +from glob import glob +from pathlib import Path + +import SCons + +# Local +from scripts.build.option_handler import OptionsClass +from scripts.build.glob_recursive import GlobRecursive +from scripts.build.cache import show_progress + +# Try to detect the host platform automatically. +# This is used if no `platform` argument is passed +if sys.platform.startswith("linux"): + default_platform = "linux" +elif sys.platform == "darwin": + default_platform = "macos" +elif sys.platform == "win32" or sys.platform == "msys": + default_platform = "windows" +elif ARGUMENTS.get("platform", ""): + default_platform = ARGUMENTS.get("platform") +else: + raise ValueError("Could not detect platform automatically, please specify with platform=<platform>") + +is_standalone = SCons.Script.sconscript_reading == 1 + +try: + Import("env") + old_env = env + env = old_env.Clone() +except: + # Default tools with no platform defaults to gnu toolchain. + # We apply platform specific toolchains via our custom tools. + env = Environment(tools=["default"], PLATFORM="") + old_env = env + +env.PrependENVPath("PATH", os.getenv("PATH")) + +# Default num_jobs to local cpu count if not user specified. +# SCons has a peculiarity where user-specified options won't be overridden +# by SetOption, so we can rely on this to know if we should use our default. +initial_num_jobs = env.GetOption("num_jobs") +altered_num_jobs = initial_num_jobs + 1 +env.SetOption("num_jobs", altered_num_jobs) +if env.GetOption("num_jobs") == altered_num_jobs: + cpu_count = os.cpu_count() + if cpu_count is None: + print("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.") + else: + safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1 + print( + "Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument." + % (cpu_count, safer_cpu_count) + ) + env.SetOption("num_jobs", safer_cpu_count) + +opts = OptionsClass(ARGUMENTS) + +platforms = ("linux", "macos", "windows", "android", "ios", "javascript") +unsupported_known_platforms = ("android", "ios", "javascript") +opts.Add( + EnumVariable( + key="platform", + help="Target platform", + default=env.get("platform", default_platform), + allowed_values=platforms, + ignorecase=2, + ) +) + +opts.Add( + EnumVariable( + key="target", + help="Compilation target", + default=env.get("target", "template_debug"), + allowed_values=("editor", "template_release", "template_debug"), + ) +) + +opts.Add(BoolVariable(key="build_ovdl_library", help="Build the openvic dataloader library.", default=env.get("build_ovdl_library", not is_standalone))) +opts.Add( + EnumVariable( + key="precision", + help="Set the floating-point precision level", + default=env.get("precision", "single"), + allowed_values=("single", "double"), + ) +) + +# Add platform options +tools = {} +for pl in set(platforms) - set(unsupported_known_platforms): + tool = Tool(pl, toolpath=["tools"]) + if hasattr(tool, "options"): + tool.options(opts) + tools[pl] = tool + +# CPU architecture options. +architecture_array = ["", "universal", "x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32"] +architecture_aliases = { + "x64": "x86_64", + "amd64": "x86_64", + "armv7": "arm32", + "armv8": "arm64", + "arm64v8": "arm64", + "aarch64": "arm64", + "rv": "rv64", + "riscv": "rv64", + "riscv64": "rv64", + "ppcle": "ppc32", + "ppc": "ppc32", + "ppc64le": "ppc64", +} +opts.Add( + EnumVariable( + key="arch", + help="CPU architecture", + default=env.get("arch", ""), + allowed_values=architecture_array, + map=architecture_aliases, + ) +) + +opts.Add(BoolVariable("build_ovdl_headless", "Build the openvic dataloader headless executable", is_standalone)) + +opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False)) +opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) +opts.Add(BoolVariable("intermediate_delete", "Enables automatically deleting unassociated intermediate binary files.", True)) +opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True)) + +# Targets flags tool (optimizations, debug symbols) +target_tool = Tool("targets", toolpath=["tools"]) +target_tool.options(opts) + +# Custom options and profile flags. +opts.Make(["custom.py"]) +opts.Finalize(env) +Help(opts.GenerateHelpText(env)) + +if env["platform"] in unsupported_known_platforms: + print("Unsupported platform: " + env["platform"]+". Only supports " + ", ".join(set(platforms) - set(unsupported_known_platforms))) + Exit() + +# Process CPU architecture argument. +if env["arch"] == "": + # No architecture specified. Default to arm64 if building for Android, + # universal if building for macOS or iOS, wasm32 if building for web, + # otherwise default to the host architecture. + if env["platform"] in ["macos", "ios"]: + env["arch"] = "universal" + elif env["platform"] == "android": + env["arch"] = "arm64" + elif env["platform"] == "javascript": + env["arch"] = "wasm32" + else: + host_machine = platform.machine().lower() + if host_machine in architecture_array: + env["arch"] = host_machine + elif host_machine in architecture_aliases.keys(): + env["arch"] = architecture_aliases[host_machine] + elif "86" in host_machine: + # Catches x86, i386, i486, i586, i686, etc. + env["arch"] = "x86_32" + else: + print("Unsupported CPU architecture: " + host_machine) + Exit() + +tool = Tool(env["platform"], toolpath=["tools"]) + +if tool is None or not tool.exists(env): + raise ValueError("Required toolchain not found for platform " + env["platform"]) + +tool.generate(env) +target_tool.generate(env) + +print("Building for architecture " + env["arch"] + " on platform " + env["platform"]) + +# Require C++20 +if env.get("is_msvc", False): + env.Append(CXXFLAGS=["/std:c++20"]) +else: + env.Append(CXXFLAGS=["-std=c++20"]) + +if env["precision"] == "double": + env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) + +scons_cache_path = os.environ.get("SCONS_CACHE") +if scons_cache_path != None: + CacheDir(scons_cache_path) + print("Scons cache enabled... (path: '" + scons_cache_path + "')") + +if env["compiledb"]: + # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. + from SCons import __version__ as scons_raw_version + + scons_ver = env._get_major_minor_revision(scons_raw_version) + + if scons_ver < (4, 0, 0): + print("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version) + Exit(255) + + env.Tool("compilation_db") + env.Alias("compiledb", env.CompilationDatabase()) + +Export("env") +env.GlobRecursive = GlobRecursive + +SConscript("deps/SCsub") + +# For the reference: +# - CCFLAGS are compilation flags shared between C and C++ +# - CFLAGS are for C-specific compilation flags +# - CXXFLAGS are for C++-specific compilation flags +# - CPPFLAGS are for pre-processor flags +# - CPPDEFINES are for pre-processor defines +# - LINKFLAGS are for linking flags + +# tweak this if you want to use different folders, or more folders, to store your source code in. +paths = ["include", "src/openvic-dataloader"] +env.Append(CPPPATH=[[env.Dir(p) for p in paths]]) +sources = GlobRecursive("*.cpp", paths) +env.dataloader_sources = sources + +suffix = ".{}.{}".format(env["platform"], env["target"]) +if env.dev_build: + suffix += ".dev" +if env["precision"] == "double": + suffix += ".double" +suffix += "." + env["arch"] + +# Expose it when included from another project +env["suffix"] = suffix + +library = None +env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"] +library_name = "libopenvic-dataloader{}{}".format(suffix, env["LIBSUFFIX"]) + +if env["build_ovdl_library"]: + library = env.StaticLibrary(target=env.File("bin/%s" % library_name), source=sources) + Default(library) + +env.Append(LIBPATH=[env.Dir("bin")]) + +if library != None or os.path.exists("bin/%s" % library_name): + env.Append(LIBS=[library_name]) + +headless_program = None +env["PROGSUFFIX"] = suffix + env["PROGSUFFIX"] + +if env["build_ovdl_headless"]: + headless_name = "openvic-dataloader" + headless_env = env.Clone() + headless_path = ["src/headless"] + headless_env.Append(CPPDEFINES=["OPENVIC_DATALOADER_HEADLESS"]) + headless_env.Append(CPPPATH=[headless_env.Dir(headless_path)]) + headless_env.headless_sources = GlobRecursive("*.cpp", headless_path) + if not env["build_ovdl_library"]: + headless_env.headless_sources += sources + headless_program = env.Program( + target="bin/%s" % headless_name, + source=headless_env.headless_sources, + PROGSUFFIX=".headless" + env["PROGSUFFIX"] + ) + Default(headless_program) + + +if "env" in locals(): + # FIXME: This method mixes both cosmetic progress stuff and cache handling... + show_progress(env) + +Return("env")
\ No newline at end of file diff --git a/deps/SCsub b/deps/SCsub new file mode 100644 index 0000000..16ee889 --- /dev/null +++ b/deps/SCsub @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +Import("env") + +def build_lexy(env): + env.Append(CPPDEFINES=["LEXY_HAS_UNICODE_DATABASE=1"]) + lexy_env = env.Clone() + + # Require C++20 + if lexy_env.get("is_msvc", False): + lexy_env.Append(CXXFLAGS=["/std:c++20"]) + + lexy_env.Append(CXXFLAGS=["/WX", "/W3", "/D", "_CRT_SECURE_NO_WARNINGS"]) + if not lexy_env.get("use_clang_cl"): + lexy_env.Append(CXXFLAGS=["/wd5105"]) + else: + lexy_env.Append(CXXFLAGS=["-std=c++20"]) + + lexy_env.Append(CXXFLAGS=["-pedantic-errors", "-Werror", "-Wall", "-Wextra", "-Wconversion", "-Wsign-conversion"]) + if lexy_env.get("use_llvm"): + lexy_env.Append(CXXFLAGS=["-Wno-shift-op-parentheses", "-Wno-parentheses-equality"]) + else: + lexy_env.Append(CXXFLAGS=[ + "-Wno-parentheses", "-Wno-unused-local-typedefs", "-Wno-array-bounds", "-Wno-maybe-uninitialized", "-Wno-restrict" + ]) + + paths = ["lexy/include", "lexy/src"] + lexy_env.Append(CPPPATH=[[lexy_env.Dir(p) for p in paths]]) + sources = env.GlobRecursive("*.cpp", paths) + env.lexy_sources = sources + library_name = "liblexy_file" + env["LIBSUFFIX"] + library = lexy_env.StaticLibrary(target="lexy/src/" + library_name, source=sources) + Default(library) + + env.Append(CPPPATH=[env.Dir("lexy/include")]) + if env.get("is_msvc", False): + env.Append(CXXFLAGS=["/external:I", env.Dir("lexy/include"), "/external:W0"]) + else: + env.Append(CXXFLAGS=["-isystem", env.Dir("lexy/include")]) + env.Append(CXXFLAGS=[""]) + env.Append(LIBPATH=[env.Dir("lexy/src")]) + env.Append(LIBS=[library_name]) + + +build_lexy(env)
\ No newline at end of file diff --git a/deps/lexy b/deps/lexy new file mode 160000 +Subproject 5e2601b3de57e979b5d23b8936cf040eb228fb9 diff --git a/include/openvic-dataloader/ovscript/.gitkeep b/include/openvic-dataloader/ovscript/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/include/openvic-dataloader/ovscript/.gitkeep diff --git a/include/openvic-dataloader/v2script/Parser.hpp b/include/openvic-dataloader/v2script/Parser.hpp new file mode 100644 index 0000000..53aab90 --- /dev/null +++ b/include/openvic-dataloader/v2script/Parser.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include <cstddef> +#include <cstdio> +#include <ostream> + +namespace ovdl::v2script { + class Parser { + public: + static Parser from_buffer(char8_t* data, std::size_t size); + static Parser from_buffer(char8_t* start, char8_t* end); + static Parser from_file(const char8_t* path); + + void set_error_log_to_stderr(); + void set_error_log_path(const char8_t* path); + void set_error_log_to(std::basic_ostream<char8_t> stream); + void set_error_log_to(std::FILE* file); + + bool parse(); + + bool has_error(); + bool has_warning(); + + private: + Parser(); + }; +}
\ No newline at end of file diff --git a/scripts/build/cache.py b/scripts/build/cache.py new file mode 100644 index 0000000..d48b0e0 --- /dev/null +++ b/scripts/build/cache.py @@ -0,0 +1,127 @@ +# Copied from https://github.com/godotengine/godot/blob/c3b0a92c3cd9a219c1b1776b48c147f1d0602f07/methods.py#L1049-L1172 +def show_progress(env): + import os + import sys + import glob + from SCons.Script import Progress, Command, AlwaysBuild + + screen = sys.stdout + # Progress reporting is not available in non-TTY environments since it + # messes with the output (for example, when writing to a file) + show_progress = env["progress"] and sys.stdout.isatty() + node_count = 0 + node_count_max = 0 + node_count_interval = 1 + node_count_fname = str(env.Dir("#")) + "/.scons_node_count" + + import time, math + + class cache_progress: + # The default is 1 GB cache and 12 hours half life + def __init__(self, path=None, limit=1073741824, half_life=43200): + self.path = path + self.limit = limit + self.exponent_scale = math.log(2) / half_life + if env["verbose"] and path != None: + screen.write( + "Current cache limit is {} (used: {})\n".format( + self.convert_size(limit), self.convert_size(self.get_size(path)) + ) + ) + self.delete(self.file_list()) + + def __call__(self, node, *args, **kw): + nonlocal node_count, node_count_max, node_count_interval, node_count_fname, show_progress + if show_progress: + # Print the progress percentage + node_count += node_count_interval + if node_count_max > 0 and node_count <= node_count_max: + screen.write("\r[%3d%%] " % (node_count * 100 / node_count_max)) + screen.flush() + elif node_count_max > 0 and node_count > node_count_max: + screen.write("\r[100%] ") + screen.flush() + else: + screen.write("\r[Initial build] ") + screen.flush() + + def delete(self, files): + if len(files) == 0: + return + if env["verbose"]: + # Utter something + screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file")) + [os.remove(f) for f in files] + + def file_list(self): + if self.path is None: + # Nothing to do + return [] + # Gather a list of (filename, (size, atime)) within the + # cache directory + file_stat = [(x, os.stat(x)[6:8]) for x in glob.glob(os.path.join(self.path, "*", "*"))] + if file_stat == []: + # Nothing to do + return [] + # Weight the cache files by size (assumed to be roughly + # proportional to the recompilation time) times an exponential + # decay since the ctime, and return a list with the entries + # (filename, size, weight). + current_time = time.time() + file_stat = [(x[0], x[1][0], (current_time - x[1][1])) for x in file_stat] + # Sort by the most recently accessed files (most sensible to keep) first + file_stat.sort(key=lambda x: x[2]) + # Search for the first entry where the storage limit is + # reached + sum, mark = 0, None + for i, x in enumerate(file_stat): + sum += x[1] + if sum > self.limit: + mark = i + break + if mark is None: + return [] + else: + return [x[0] for x in file_stat[mark:]] + + def convert_size(self, size_bytes): + if size_bytes == 0: + return "0 bytes" + size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + i = int(math.floor(math.log(size_bytes, 1024))) + p = math.pow(1024, i) + s = round(size_bytes / p, 2) + return "%s %s" % (int(s) if i == 0 else s, size_name[i]) + + def get_size(self, start_path="."): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(start_path): + for f in filenames: + fp = os.path.join(dirpath, f) + total_size += os.path.getsize(fp) + return total_size + + def progress_finish(target, source, env): + nonlocal node_count, progressor + try: + with open(node_count_fname, "w") as f: + f.write("%d\n" % node_count) + progressor.delete(progressor.file_list()) + except Exception: + pass + + try: + with open(node_count_fname) as f: + node_count_max = int(f.readline()) + except Exception: + pass + + cache_directory = os.environ.get("SCONS_CACHE") + # Simple cache pruning, attached to SCons' progress callback. Trim the + # cache directory to a size not larger than cache_limit. + cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024 + progressor = cache_progress(cache_directory, cache_limit) + Progress(progressor, interval=node_count_interval) + + progress_finish_command = Command("progress_finish", [], progress_finish) + AlwaysBuild(progress_finish_command) diff --git a/scripts/build/glob_recursive.py b/scripts/build/glob_recursive.py new file mode 100644 index 0000000..db6eb80 --- /dev/null +++ b/scripts/build/glob_recursive.py @@ -0,0 +1,15 @@ +def GlobRecursive(pattern, nodes=['.']): + import SCons + import glob + fs = SCons.Node.FS.get_default_fs() + Glob = fs.Glob + + results = [] + for node in nodes: + nnodes = [] + for f in Glob(str(node) + '/*', source=True): + if type(f) is SCons.Node.FS.Dir: + nnodes.append(f) + results += GlobRecursive(pattern, nnodes) + results += Glob(str(node) + '/' + pattern, source=True) + return results diff --git a/scripts/build/option_handler.py b/scripts/build/option_handler.py new file mode 100644 index 0000000..3cebc1a --- /dev/null +++ b/scripts/build/option_handler.py @@ -0,0 +1,39 @@ +from typing import Tuple, Iterable +from SCons.Variables import Variables + +class OptionsClass: + def __init__(self, args): + self.opts = None + self.opt_list = [] + self.args = args + self.saved_args = args.copy() + + def Add(self, variableOrKey, *argv, **kwarg): + + self.opt_list.append([variableOrKey, argv, kwarg]) + # Neccessary to have our own build options without errors + if isinstance(variableOrKey, str): + self.args.pop(variableOrKey, True) + else: + self.args.pop(variableOrKey[0], True) + + def Make(self, customs : Iterable[str]): + self.args = self.saved_args + profile = self.args.get("profile", "") + if profile: + if os.path.isfile(profile): + customs.append(profile) + elif os.path.isfile(profile + ".py"): + customs.append(profile + ".py") + self.opts = Variables(customs, self.args) + for opt in self.opt_list: + if opt[1] == None and opt[2] == None: + self.opts.Add(opt[0]) + else: + self.opts.Add(opt[0], *opt[1], **opt[2]) + + def Finalize(self, env): + self.opts.Update(env) + + def GenerateHelpText(self, env): + return self.opts.GenerateHelpText(env) diff --git a/src/headless/main.cpp b/src/headless/main.cpp new file mode 100644 index 0000000..ffc6dab --- /dev/null +++ b/src/headless/main.cpp @@ -0,0 +1,3 @@ +int main() { + return 0; +}
\ No newline at end of file diff --git a/src/openvic-dataloader/ovscript/.gitkeep b/src/openvic-dataloader/ovscript/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/openvic-dataloader/ovscript/.gitkeep diff --git a/src/openvic-dataloader/v2script/Grammar.cpp b/src/openvic-dataloader/v2script/Grammar.cpp new file mode 100644 index 0000000..ec9fac2 --- /dev/null +++ b/src/openvic-dataloader/v2script/Grammar.cpp @@ -0,0 +1,74 @@ +#include <lexy/dsl.hpp> +#include <openvic-dataloader/v2script/Parser.hpp> + +using namespace ovdl::v2script; + +// Node Definitions // +namespace dsl = lexy::dsl; + +namespace ovdl::v2script::nodes { + struct StatementListBlock; + + static constexpr auto whitespace_specifier = dsl::code_point.range<0x09, 0x0A>() / dsl::lit_cp<0x0D> / dsl::lit_cp<0x20>; + static constexpr auto comment_specifier = LEXY_LIT("#") >> dsl::until(dsl::newline).or_eof(); + + static constexpr auto data_specifier = + dsl::ascii::alpha_digit_underscore / + dsl::code_point.range<0x25, 0x27>() / dsl::lit_cp<0x2B> / dsl::code_point.range<0x2D, 0x2E>() / + dsl::lit_cp<0x3A> / + dsl::lit_cp<0x8A> / dsl::lit_cp<0x8C> / dsl::lit_cp<0x8E> / + dsl::lit_cp<0x92> / dsl::lit_cp<0x9A> / dsl::lit_cp<0x9C> / dsl::code_point.range<0x9E, 0x9F>() / + dsl::code_point.range<0xC0, 0xD6>() / dsl::code_point.range<0xD8, 0xF6>() / dsl::code_point.range<0xF8, 0xFF>(); + + static constexpr auto data_char_class = LEXY_CHAR_CLASS("DataSpecifier", data_specifier); + + struct Identifier { + static constexpr auto rule = dsl::identifier(data_char_class); + }; + + struct StringExpression { + static constexpr auto escaped_symbols = lexy::symbol_table<char> // + .map<'"'>('"') + .map<'\''>('\'') + .map<'\\'>('\\') + .map<'/'>('/') + .map<'b'>('\b') + .map<'f'>('\f') + .map<'n'>('\n') + .map<'r'>('\r') + .map<'t'>('\t'); + static constexpr auto rule = [] { + // Arbitrary code points that aren't control characters. + auto c = -dsl::unicode::control; + + // Escape sequences start with a backlash. + // They either map one of the symbols, + // or a Unicode code point of the form uXXXX. + auto escape = dsl::backslash_escape // + .symbol<escaped_symbols>() + .rule(dsl::lit_c<'u'> >> dsl::code_point_id<4>); + return dsl::quoted(c, escape); + }(); + }; + + struct AssignmentStatement { + static constexpr auto rule = dsl::p<Identifier> >> + (dsl::equal_sign >> + (dsl::p<Identifier> | dsl::p<StringExpression> | dsl::recurse_branch<StatementListBlock>) | + dsl::else_ >> dsl::return_); + }; + + struct StatementListBlock { + static constexpr auto rule = + dsl::curly_bracketed.open() >> + dsl::opt(dsl::list(dsl::p<AssignmentStatement>)) + dsl::opt(dsl::semicolon) + + dsl::curly_bracketed.close(); + }; + + struct File { + // Allow arbitrary spaces between individual tokens. + static constexpr auto whitespace = whitespace_specifier | comment_specifier; + + static constexpr auto rule = dsl::terminator(dsl::eof).list(dsl::p<AssignmentStatement>); + }; +} diff --git a/tools/linux.py b/tools/linux.py new file mode 100644 index 0000000..0a2c35c --- /dev/null +++ b/tools/linux.py @@ -0,0 +1,35 @@ +# Copied from https://github.com/godotengine/godot-cpp/blob/2bf983e6382f5236948f7740faf130a3568f9dd0/tools/linux.py +from SCons.Variables import * +from SCons.Tool import clang, clangxx + + +def options(opts): + opts.Add(BoolVariable("use_llvm", "Use the LLVM compiler - only effective when targeting Linux", False)) + + +def exists(env): + return True + + +def generate(env): + if env["use_llvm"]: + clang.generate(env) + clangxx.generate(env) + + env.Append(CCFLAGS=["-fPIC", "-Wwrite-strings"]) + env.Append(LINKFLAGS=["-Wl,-R,'$$ORIGIN'"]) + + if env["arch"] == "x86_64": + # -m64 and -m32 are x86-specific already, but it doesn't hurt to + # be clear and also specify -march=x86-64. Similar with 32-bit. + env.Append(CCFLAGS=["-m64", "-march=x86-64"]) + env.Append(LINKFLAGS=["-m64", "-march=x86-64"]) + elif env["arch"] == "x86_32": + env.Append(CCFLAGS=["-m32", "-march=i686"]) + env.Append(LINKFLAGS=["-m32", "-march=i686"]) + elif env["arch"] == "arm64": + env.Append(CCFLAGS=["-march=armv8-a"]) + env.Append(LINKFLAGS=["-march=armv8-a"]) + elif env["arch"] == "rv64": + env.Append(CCFLAGS=["-march=rv64gc"]) + env.Append(LINKFLAGS=["-march=rv64gc"]) diff --git a/tools/macos.py b/tools/macos.py new file mode 100644 index 0000000..f0fb81a --- /dev/null +++ b/tools/macos.py @@ -0,0 +1,51 @@ +# Copied from https://github.com/godotengine/godot-cpp/blob/2bf983e6382f5236948f7740faf130a3568f9dd0/tools/macos.py +import os +import sys +import macos_osxcross + + +def options(opts): + opts.Add("macos_deployment_target", "macOS deployment target", "default") + opts.Add("macos_sdk_path", "macOS SDK path", "") + macos_osxcross.options(opts) + + +def exists(env): + return sys.platform == "darwin" or macos_osxcross.exists(env) + + +def generate(env): + if env["arch"] not in ("universal", "arm64", "x86_64"): + print("Only universal, arm64, and x86_64 are supported on macOS. Exiting.") + Exit() + + if sys.platform == "darwin": + # Use clang on macOS by default + env["CXX"] = "clang++" + env["CC"] = "clang" + else: + # Use osxcross + macos_osxcross.generate(env) + + if env["arch"] == "universal": + env.Append(LINKFLAGS=["-arch", "x86_64", "-arch", "arm64"]) + env.Append(CCFLAGS=["-arch", "x86_64", "-arch", "arm64"]) + else: + env.Append(LINKFLAGS=["-arch", env["arch"]]) + env.Append(CCFLAGS=["-arch", env["arch"]]) + + if env["macos_deployment_target"] != "default": + env.Append(CCFLAGS=["-mmacosx-version-min=" + env["macos_deployment_target"]]) + env.Append(LINKFLAGS=["-mmacosx-version-min=" + env["macos_deployment_target"]]) + + if env["macos_sdk_path"]: + env.Append(CCFLAGS=["-isysroot", env["macos_sdk_path"]]) + env.Append(LINKFLAGS=["-isysroot", env["macos_sdk_path"]]) + + env.Append( + LINKFLAGS=[ + "-framework", + "Cocoa", + "-Wl,-undefined,dynamic_lookup", + ] + ) diff --git a/tools/macos_osxcross.py b/tools/macos_osxcross.py new file mode 100644 index 0000000..8ed9a5d --- /dev/null +++ b/tools/macos_osxcross.py @@ -0,0 +1,29 @@ +# Copied from https://github.com/godotengine/godot-cpp/blob/0ee980abae91c481009152cdccab8e61c9625303/tools/macos_osxcross.py +import os + + +def options(opts): + opts.Add("osxcross_sdk", "OSXCross SDK version", "darwin16") + + +def exists(env): + return "OSXCROSS_ROOT" in os.environ + + +def generate(env): + root = os.environ.get("OSXCROSS_ROOT", "") + if env["arch"] == "arm64": + basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-" + else: + basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-" + + env["CC"] = basecmd + "clang" + env["CXX"] = basecmd + "clang++" + env["AR"] = basecmd + "ar" + env["RANLIB"] = basecmd + "ranlib" + env["AS"] = basecmd + "as" + + binpath = os.path.join(root, "target", "bin") + if binpath not in env["ENV"]["PATH"]: + # Add OSXCROSS bin folder to PATH (required for linking). + env["ENV"]["PATH"] = "%s:%s" % (binpath, env["ENV"]["PATH"]) diff --git a/tools/my_spawn.py b/tools/my_spawn.py new file mode 100644 index 0000000..915f972 --- /dev/null +++ b/tools/my_spawn.py @@ -0,0 +1,53 @@ +# Copied from https://github.com/godotengine/godot-cpp/blob/93f2091185ff4390ca8fc8901ebc68ebc35a218f/tools/my_spawn.py +import os + + +def exists(env): + return os.name == "nt" + + +# Workaround for MinGW. See: +# http://www.scons.org/wiki/LongCmdLinesOnWin32 +def configure(env): + import subprocess + + def mySubProcess(cmdline, env): + # print "SPAWNED : " + cmdline + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + proc = subprocess.Popen( + cmdline, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=startupinfo, + shell=False, + env=env, + ) + data, err = proc.communicate() + rv = proc.wait() + if rv: + print("=====") + print(err.decode("utf-8")) + print("=====") + return rv + + def mySpawn(sh, escape, cmd, args, env): + + newargs = " ".join(args[1:]) + cmdline = cmd + " " + newargs + + rv = 0 + if len(cmdline) > 32000 and cmd.endswith("ar"): + cmdline = cmd + " " + args[1] + " " + args[2] + " " + for i in range(3, len(args)): + rv = mySubProcess(cmdline + args[i], env) + if rv: + break + else: + rv = mySubProcess(cmdline, env) + + return rv + + env["SPAWN"] = mySpawn + env.Replace(ARFLAGS=["q"]) diff --git a/tools/targets.py b/tools/targets.py new file mode 100644 index 0000000..5c33555 --- /dev/null +++ b/tools/targets.py @@ -0,0 +1,93 @@ +# Copied from https://github.com/godotengine/godot-cpp/blob/edf02f83194b58408ca241459c986e32c52fd9c7/tools/targets.py +import os +import sys +from SCons.Script import ARGUMENTS +from SCons.Variables import * +from SCons.Variables.BoolVariable import _text2bool + + +def get_cmdline_bool(option, default): + """We use `ARGUMENTS.get()` to check if options were manually overridden on the command line, + and SCons' _text2bool helper to convert them to booleans, otherwise they're handled as strings. + """ + cmdline_val = ARGUMENTS.get(option) + if cmdline_val is not None: + return _text2bool(cmdline_val) + else: + return default + + +def options(opts): + opts.Add( + EnumVariable( + "optimize", + "The desired optimization flags", + "speed_trace", + ("none", "custom", "debug", "speed", "speed_trace", "size"), + ) + ) + opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", True)) + opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) + + +def exists(env): + return True + + +def generate(env): + env.dev_build = env["dev_build"] + env.debug_features = env["target"] in ["editor", "template_debug"] + env.editor_build = env["target"] == "editor" + + if env.editor_build: + env.AppendUnique(CPPDEFINES=["TOOLS_ENABLED"]) + + if env.debug_features: + env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_METHODS_ENABLED"]) + + if env.dev_build: + opt_level = "none" + env.AppendUnique(CPPDEFINES=["DEV_ENABLED"]) + elif env.debug_features: + opt_level = "speed_trace" + else: # Release + opt_level = "speed" + + env["optimize"] = ARGUMENTS.get("optimize", opt_level) + env["debug_symbols"] = get_cmdline_bool("debug_symbols", env.dev_build) + + if env.get("is_msvc", False): + if env["debug_symbols"]: + env.Append(CCFLAGS=["/Zi", "/FS"]) + env.Append(LINKFLAGS=["/DEBUG:FULL"]) + + if env["optimize"] == "speed" or env["optimize"] == "speed_trace": + env.Append(CCFLAGS=["/O2"]) + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "size": + env.Append(CCFLAGS=["/O1"]) + env.Append(LINKFLAGS=["/OPT:REF"]) + + if env["optimize"] == "debug" or env["optimize"] == "none": + env.Append(CCFLAGS=["/MDd", "/Od"]) + else: + env.Append(CCFLAGS=["/MD"]) + + else: + if env["debug_symbols"]: + if env.dev_build: + env.Append(CCFLAGS=["-g3"]) + else: + env.Append(CCFLAGS=["-g2"]) + + if env["optimize"] == "speed": + env.Append(CCFLAGS=["-O3"]) + # `-O2` is friendlier to debuggers than `-O3`, leading to better crash backtraces. + elif env["optimize"] == "speed_trace": + env.Append(CCFLAGS=["-O2"]) + elif env["optimize"] == "size": + env.Append(CCFLAGS=["-Os"]) + elif env["optimize"] == "debug": + env.Append(CCFLAGS=["-Og"]) + elif env["optimize"] == "none": + env.Append(CCFLAGS=["-O0"]) diff --git a/tools/windows.py b/tools/windows.py new file mode 100644 index 0000000..0fd86a6 --- /dev/null +++ b/tools/windows.py @@ -0,0 +1,73 @@ +# Copied from https://github.com/godotengine/godot-cpp/blob/edf02f83194b58408ca241459c986e32c52fd9c7/tools/windows.py +import sys + +import my_spawn + +from SCons.Tool import msvc, mingw +from SCons.Variables import * + + +def options(opts): + opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False)) + opts.Add(BoolVariable("use_clang_cl", "Use the clang driver instead of MSVC - only effective on Windows", False)) + + +def exists(env): + return True + + +def generate(env): + base = None + if not env["use_mingw"] and msvc.exists(env): + if env["arch"] == "x86_64": + env["TARGET_ARCH"] = "amd64" + elif env["arch"] == "x86_32": + env["TARGET_ARCH"] = "x86" + env["is_msvc"] = True + + # MSVC, linker, and archiver. + msvc.generate(env) + env.Tool("mslib") + env.Tool("mslink") + + env.Append(CPPDEFINES=["TYPED_METHOD_BIND", "NOMINMAX"]) + env.Append(CCFLAGS=["/EHsc", "/utf-8"]) + env.Append(LINKFLAGS=["/WX"]) + + if env["use_clang_cl"]: + env["CC"] = "clang-cl" + env["CXX"] = "clang-cl" + + elif sys.platform == "win32" or sys.platform == "msys": + env["use_mingw"] = True + mingw.generate(env) + # Don't want lib prefixes + env["IMPLIBPREFIX"] = "" + env["SHLIBPREFIX"] = "" + # Want dll suffix + env["SHLIBSUFFIX"] = ".dll" + # Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other). + my_spawn.configure(env) + + else: + env["use_mingw"] = True + # Cross-compilation using MinGW + prefix = "i686" if env["arch"] == "x86_32" else env["arch"] + env["CXX"] = prefix + "-w64-mingw32-g++" + env["CC"] = prefix + "-w64-mingw32-gcc" + env["AR"] = prefix + "-w64-mingw32-ar" + env["RANLIB"] = prefix + "-w64-mingw32-ranlib" + env["LINK"] = prefix + "-w64-mingw32-g++" + # Want dll suffix + env["SHLIBSUFFIX"] = ".dll" + + # These options are for a release build even using target=debug + env.Append(CCFLAGS=["-O3", "-Wwrite-strings"]) + env.Append( + LINKFLAGS=[ + "--static", + "-Wl,--no-undefined", + "-static-libgcc", + "-static-libstdc++", + ] + ) |