muon-v0.5.0/0002755000175000017500000000000015041717053011645 5ustar buildbuildmuon-v0.5.0/meson.build0000644000175000017500000001142515041716357014016 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-FileCopyrightText: Simon Zeni # SPDX-License-Identifier: GPL-3.0-only project( 'muon', 'c', version: '0.5.0', license: 'GPL-3.0-only', meson_version: '>=1.3.0', default_options: { 'warning_level': '3', 'buildtype': 'debug', 'default_library': 'static', 'cpp_std': 'c++11', 'force_fallback_for': [ 'glad', 'glfw3', 'imgui', ], }, ) fs = import('fs') # version information git = find_program('git', required: false) if git.found() and fs.is_dir('.git') rev = run_command(git, 'rev-parse', '--short', 'HEAD', check: true) git_sha = rev.stdout().strip() else git_sha = '' endif version_info = configuration_data() version_info.set('version', meson.project_version()) version_info.set('vcs_tag', git_sha) version_info.set('meson_compat', '1.7') configure_file( configuration: version_info, input: 'tools/ci/version.sh.in', output: 'version.sh', ) # platform platform = host_machine.system() if platform != 'windows' # Assume everything that is not windows is posix. This will likely need to # change in the future. platform = 'posix' endif # compiler setup c_args = [ '-DMUON_PLATFORM_' + platform, '-DMUON_ENDIAN=@0@'.format({'big': 1, 'little': 0}[host_machine.endian()]), ] link_args = [] if get_option('static') c_args += '-DMUON_STATIC' link_args += '-static' endif cc = meson.get_compiler('c') if cc.get_argument_syntax() == 'msvc' add_project_arguments( cc.get_supported_arguments( [ '/we4027', # -Wstrict-prototypes '/we4056', # -Woverflow '/we4013', # function undefined; assuming extern returning int '/wd4100', # -Wno-unused-parameter '/wd4706', # assignment within conditional expression '/wd4267', # conversion from x to y, possible loss of data '/wd4244', # conversion from x to y, possible loss of data '/wd4456', # declaration of identifier hides previous local declaration '/wd4457', # declaration of 'identifier' hides function parameter # Lots of false positivies due to not understanding UNREACHABLE '/wd4701', # potentially uninitialized local variable name used '/wd4703', # potentially uninitialized local variable name used '/wd4702', # unreachable code # msvc complaining about flexible array member '/wd4200', # nonstandard extension used: zero-sized array in struct/union '/std:c11', # Occurs spuriously due to /std:c11 enabling a # standards-conformant preprocessor '/wd5105', # macro expansion producing 'defined' has undefined behavior ], ), language: 'c', ) else add_project_arguments( cc.get_supported_arguments( [ '-Wendif-labels', '-Wimplicit-fallthrough=2', '-Winit-self', '-Wlogical-op', '-Wmissing-include-dirs', '-Wno-missing-braces', '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-Wold-style-definition', '-Woverflow', '-Wstrict-aliasing=2', '-Wstrict-prototypes', '-Wundef', '-Wvla', '-fstrict-aliasing', '-std=c99', ], ), language: 'c', ) endif if platform == 'windows' add_project_arguments( ['-D_CRT_SECURE_NO_WARNINGS', '-D_CRT_NONSTDC_NO_DEPRECATE'], language: 'c', ) endif add_project_arguments('-DMUON_BOOTSTRAPPED', language: 'c') include_dir = [include_directories('include')] subdir('tools') subdir('src') # tracy tracy_dep = dependency('tracy', required: get_option('tracy')) if tracy_dep.found() add_languages('cpp') c_args += ['-DTRACY_ENABLE'] deps += tracy_dep endif # meson-docs project python3 = find_program('python3', required: get_option('meson-docs')) if python3.found() and not get_option('meson-docs').disabled() meson_docs_proj = subproject('meson-docs', required: get_option('meson-docs')) if meson_docs_proj.found() src += meson_docs_proj.get_variable('meson_docs_h') c_args += ['-DHAVE_MESON_DOCS_H'] endif else meson_docs_proj = dependency('', required: false) endif muon = executable( 'muon', src, dependencies: deps, include_directories: include_dir, link_args: link_args, c_args: c_args, cpp_args: c_args, install: true, ) subdir('tests') subdir('doc') muon-v0.5.0/bootstrap.bat0000644000175000017500000000134415041716357014360 0ustar buildbuild:: SPDX-FileCopyrightText: Stone Tickle :: SPDX-License-Identifier: GPL-3.0-only @echo off setlocal cd /D "%~dp0" if "%~1" == "" goto :usage where cl >nul 2>nul if %ERRORLEVEL%==0 goto :build if "%~2" == "" (set arch=amd64) else (set arch=%~2) :: Attempt to run vcvarsall if cl was not found. for /f "tokens=*" %%g in ( '"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath' ) do (set installation_path=%%g) call "%installation_path%\VC\Auxiliary\Build\vcvarsall" %arch% :build set dir=%1 if not exist "%dir%" mkdir "%dir%" call cl /nologo /Zi /std:c11 /Iinclude src/amalgam.c /link /out:"%dir%/muon-bootstrap.exe" goto :eof :usage echo usage: %0 build_dir muon-v0.5.0/include/0002755000175000017500000000000015041716357013276 5ustar buildbuildmuon-v0.5.0/include/embedded.h0000644000175000017500000000064415041716357015202 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EMBEDDED_H #define MUON_EMBEDDED_H #include #include #include "lang/source.h" struct embedded_file { const char *name; struct source src; }; bool embedded_get(const char *name, struct source *src); const struct embedded_file *embedded_file_list(uint32_t *len); #endif muon-v0.5.0/include/tracy.h0000644000175000017500000000217615041716357014575 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifdef TRACY_ENABLE #include "tracy/TracyC.h" #define TracyCZoneAutoS TracyCZoneN(tctx_func, __func__, true) #define TracyCZoneAutoE TracyCZoneEnd(tctx_func) #else #define TracyCZoneAutoS #define TracyCZoneAutoE #define TracyCZone(c, x) #define TracyCZoneN(c, x, y) #define TracyCZoneC(c, x, y) #define TracyCZoneNC(c, x, y, z) #define TracyCZoneEnd(c) #define TracyCZoneText(c, x, y) #define TracyCZoneName(c, x, y) #define TracyCZoneValue(c, x) #define TracyCAlloc(x, y) #define TracyCFree(x) #define TracyCSecureAlloc(x, y) #define TracyCSecureFree(x) #define TracyCFrameMark #define TracyCFrameMarkNamed(x) #define TracyCFrameMarkStart(x) #define TracyCFrameMarkEnd(x) #define TracyCFrameImage(x, y, z, w, a) #define TracyCPlot(x, y) #define TracyCMessage(x, y) #define TracyCMessageL(log_misc, x) #define TracyCMessageC(x, y, z) #define TracyCMessageLC(x, y) #define TracyCAppInfo(x, y) #define TracyCZoneS(x, y, z) #define TracyCZoneNS(x, y, z, w) #define TracyCZoneCS(x, y, z, w) #define TracyCZoneNCS(x, y, z, w, a) #endif muon-v0.5.0/include/memmem.h0000644000175000017500000000037015041716357014722 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_MEMMEM_H #define MUON_MEMMEM_H #include void *memmem(const void *h0, size_t k, const void *n0, size_t l); #endif muon-v0.5.0/include/sha_256.h0000644000175000017500000000050515041716357014614 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_SHA_256_H #define MUON_SHA_256_H #include #include void calc_sha_256(uint8_t hash[32], const void *input, size_t len); void sha256_to_str(uint8_t hash[32], char str[65]); #endif muon-v0.5.0/include/args.h0000644000175000017500000000316415041716357014405 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ARGS_H #define MUON_ARGS_H #include "lang/workspace.h" struct args { const char **args; uint32_t len; }; void shell_escape(struct workspace *wk, struct tstr *sb, const char *str); void shell_escape_cmd(struct workspace *wk, struct tstr *sb, const char *str); void shell_escape_custom(struct workspace *wk, struct tstr *sb, const char *str, const char *escape_inner, const char *need_escaping); void ninja_escape(struct workspace *wk, struct tstr *sb, const char *str); void pkgconf_escape(struct workspace *wk, struct tstr *sb, const char *str); void push_args(struct workspace *wk, obj arr, const struct args *args); void push_args_null_terminated(struct workspace *wk, obj arr, char *const *argv); obj join_args_plain(struct workspace *wk, obj arr); obj join_args_shell(struct workspace *wk, obj arr); obj join_args_ninja(struct workspace *wk, obj arr); obj join_args_shell_ninja(struct workspace *wk, obj arr); obj join_args_pkgconf(struct workspace *wk, obj arr); enum arr_to_args_flags { arr_to_args_build_target = 1 << 0, arr_to_args_custom_target = 1 << 1, arr_to_args_external_program = 1 << 2, arr_to_args_alias_target = 1 << 3, arr_to_args_relativize_paths = 1 << 4, }; bool arr_to_args(struct workspace *wk, enum arr_to_args_flags mode, obj arr, obj *res); void join_args_argstr(struct workspace *wk, const char **res, uint32_t *argc, obj arr); void env_to_envstr(struct workspace *wk, const char **res, uint32_t *envc, obj val); obj make_shell_escaped_str(struct workspace *wk, const char *s); #endif muon-v0.5.0/include/install.h0000644000175000017500000000102415041716357015110 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_INSTALL_H #define MUON_INSTALL_H #include "lang/workspace.h" struct obj_install_target *push_install_target(struct workspace *wk, obj src, obj dest, obj mode); bool push_install_target_install_dir(struct workspace *wk, obj src, obj install_dir, obj mode); bool push_install_targets(struct workspace *wk, uint32_t err_node, obj filenames, obj install_dirs, obj install_mode, bool preserve_path); #endif muon-v0.5.0/include/iterator.h0000644000175000017500000000140215041716357015273 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ITERATORS_H #define MUON_ITERATORS_H enum iteration_result { ir_err, ir_cont, ir_done, }; typedef enum iteration_result (*iterator_func)(void *ctx, void *val); #define for_iter_(__ctx, __iter_type, __val, __iter) \ struct iter_##__iter_type __iter = { 0 }; \ for (__val = iter_next_##__iter_type(__ctx, &__iter); iter_has_next_##__iter_type(__ctx, &__iter); \ __val = iter_next_##__iter_type(__ctx, &__iter)) #define for_iter(__iter_type, __ctx, __val) for_iter_(__ctx, __iter_type, __val, CONCAT(__iter, __LINE__)) #endif muon-v0.5.0/include/buf_size.h0000644000175000017500000000066115041716357015256 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BUF_SIZE_H #define MUON_BUF_SIZE_H #define BUF_SIZE_S 255 #define BUF_SIZE_1k 1024 #define BUF_SIZE_2k 2048 #define BUF_SIZE_4k 4096 #define BUF_SIZE_16k (BUF_SIZE_1k * 16) #define BUF_SIZE_32k (BUF_SIZE_1k * 32) #define BUF_SIZE_1m 1048576ul #define ARRAY_LEN(array) (sizeof(array) / sizeof(*array)) #endif muon-v0.5.0/include/cmd_install.h0000644000175000017500000000046315041716357015741 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_CMD_INSTALL_H #define MUON_CMD_INSTALL_H #include struct install_options { const char *destdir; bool dry_run; }; bool install_run(struct install_options *opts); #endif muon-v0.5.0/include/platform/0002755000175000017500000000000015041716357015122 5ustar buildbuildmuon-v0.5.0/include/platform/os.h0000644000175000017500000000176015041716357015716 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_OS_H #define MUON_PLATFORM_OS_H #include #include #ifdef _WIN32 #ifndef S_IRUSR #define S_IRUSR 0400 #endif #ifndef S_IWUSR #define S_IWUSR 0200 #endif #ifndef S_ISDIR #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #ifndef S_ISREG #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif extern char *optarg; extern int opterr, optind, optopt; #else #include #endif #include "lang/object.h" int os_getopt(int argc, char *const argv[], const char *optstring); // Returns the number of jobs to spawn. This number should be slightly larger // than the number of cpus. uint32_t os_parallel_job_count(void); void os_set_env(const struct str *k, const struct str *v); const char *os_get_env(const char *k); bool os_is_debugger_attached(void); int32_t os_get_pid(void); #endif muon-v0.5.0/include/platform/mem.h0000644000175000017500000000057715041716357016060 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_MEM_H #define MUON_PLATFORM_MEM_H #include #include void *z_calloc(size_t nmemb, size_t size); void *z_malloc(size_t size); void *z_realloc(void *ptr, size_t size); void z_free(void *ptr); uint32_t bswap_32(uint32_t x); #endif muon-v0.5.0/include/platform/init.h0000644000175000017500000000042415041716357016234 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_INIT_H #define MUON_PLATFORM_INIT_H void platform_init(void); void platform_set_abort_handler(void((*handler)(void *ctx)), void *ctx); #endif muon-v0.5.0/include/platform/windows/0002755000175000017500000000000015041716357016614 5ustar buildbuildmuon-v0.5.0/include/platform/windows/win32_getopt.h0000644000175000017500000000054615041716357021314 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_WINDOWS_GETOPT_H #define MUON_PLATFORM_WINDOWS_GETOPT_H int getopt(int, char *const[], const char *); extern char *optarg; extern int optind, opterr, optopt; #endif muon-v0.5.0/include/platform/windows/win32_error.h0000644000175000017500000000060715041716357021141 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_WINDOWS_WIN32_ERROR_H #define MUON_PLATFORM_WINDOWS_WIN32_ERROR_H #include "compat.h" const char *win32_error(void); void win32_fatal(const char *fmt, ...) MUON_ATTR_FORMAT(printf, 1, 2); #endif muon-v0.5.0/include/platform/windows/log.h0000644000175000017500000000042415041716357017544 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_WINDOWS_LOG_H #define MUON_PLATFORM_WINDOWS_LOG_H extern bool tty_is_pty; #endif muon-v0.5.0/include/platform/timer.h0000644000175000017500000000131515041716357016411 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_TIMER_H #define MUON_PLATFORM_TIMER_H #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #else #include #endif struct timer { #if defined(_WIN32) LARGE_INTEGER freq; LARGE_INTEGER start; #elif defined(CLOCK_MONOTONIC) struct timespec start; #else struct timeval start; #endif }; void timer_start(struct timer *t); float timer_read(struct timer *t); void timer_sleep(uint64_t nanoseconds); #define SLEEP_TIME 10000000 // 10ms #endif muon-v0.5.0/include/platform/run_cmd.h0000644000175000017500000000537315041716357016730 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_RUN_CMD_H #define MUON_PLATFORM_RUN_CMD_H #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #else #include #endif #include "lang/string.h" enum run_cmd_state { run_cmd_running, run_cmd_finished, run_cmd_error, }; enum run_cmd_ctx_flags { run_cmd_ctx_flag_async = 1 << 0, run_cmd_ctx_flag_dont_capture = 1 << 1, run_cmd_ctx_flag_tee = 1 << 2, }; #ifdef _WIN32 struct win_pipe_inst { OVERLAPPED overlapped; HANDLE handle; HANDLE child_handle; char overlapped_buf[4 << 10]; bool is_reading, is_eof; }; #endif struct run_cmd_ctx { struct tstr err, out; const char *err_msg; // set on error const char *chdir; // set by caller const char *stdin_path; // set by caller int status; enum run_cmd_ctx_flags flags; #ifdef _WIN32 HANDLE process, ioport; bool close_pipes; struct win_pipe_inst pipe_out, pipe_err; struct tstr env; uint32_t cnt_open; #else int pipefd_out[2], pipefd_err[2]; int input_fd; int pid; bool input_fd_open; bool pipefd_out_open[2], pipefd_err_open[2]; #endif }; void push_argv_single(const char **argv, uint32_t *len, uint32_t max, const char *arg); void argstr_pushall(const char *argstr, uint32_t argc, const char **argv, uint32_t *argi, uint32_t max); /* * argstr is a NUL delimited array of strings * envstr is like argstr, every two strings is considered a key/value pair */ uint32_t argstr_to_argv(const char *argstr, uint32_t argc, const char *prepend, char *const **res); struct source; bool run_cmd_determine_interpreter(struct source *src, const char *path, const char **err_msg, const char **new_argv0, const char **new_argv1); #define ARGV(...) (char *const *)(const char *const []){ __VA_ARGS__, 0 } bool run_cmd(struct run_cmd_ctx *ctx, const char *argstr, uint32_t argc, const char *envstr, uint32_t envc); bool run_cmd_argv(struct run_cmd_ctx *ctx, char *const *argv, const char *envstr, uint32_t envc); enum run_cmd_state run_cmd_collect(struct run_cmd_ctx *ctx); void run_cmd_ctx_destroy(struct run_cmd_ctx *ctx); bool run_cmd_kill(struct run_cmd_ctx *ctx, bool force); // runs a command by passing a single string containing both the command and // arguments. // currently only used by samurai on windows bool run_cmd_unsplit(struct run_cmd_ctx *ctx, char *cmd, const char *envstr, uint32_t envc); bool run_cmd_checked(struct run_cmd_ctx *ctx, const char *argstr, uint32_t argc, const char *envstr, uint32_t envc); bool run_cmd_argv_checked(struct run_cmd_ctx *ctx, char *const *argv, const char *envstr, uint32_t envc); void run_cmd_print_error(struct run_cmd_ctx *ctx, enum log_level lvl); #endif muon-v0.5.0/include/platform/assert.h0000644000175000017500000000065615041716357016601 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ASSERT_H #define MUON_ASSERT_H #ifdef _WIN32 #include #define assert(x) ((void)((x) || (win_assert_fail(#x, __FILE__, __LINE__, __func__), 0))) __declspec(noreturn) void win_assert_fail(const char *msg, const char *file, uint32_t line, const char *func); #else #include #endif #endif muon-v0.5.0/include/platform/rpath_fixer.h0000644000175000017500000000044615041716357017610 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_RPATH_FIXER_H #define MUON_PLATFORM_RPATH_FIXER_H #include #include bool fix_rpaths(const char *elf_path, const char *build_root); #endif muon-v0.5.0/include/platform/uname.h0000644000175000017500000000057415041716357016404 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_UNAME_H #define MUON_PLATFORM_UNAME_H #include enum endianness { endianness_uninitialized, big_endian, little_endian, }; const char *uname_sysname(void); const char *uname_machine(void); enum endianness uname_endian(void); #endif muon-v0.5.0/include/platform/log.h0000644000175000017500000000042315041716357016051 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_LOG_H #define MUON_PLATFORM_LOG_H #include #include void print_colorized(FILE *out, const char *s, bool strip); #endif muon-v0.5.0/include/platform/path.h0000644000175000017500000000332415041716357016227 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_PATH_H #define MUON_PLATFORM_PATH_H #include #include #define PATH_SEP '/' #ifdef _WIN32 #define ENV_PATH_SEP ';' #else #define ENV_PATH_SEP ':' #endif #ifdef _WIN32 #define ENV_PATH_SEP_STR ";" #else #define ENV_PATH_SEP_STR ":" #endif struct workspace; struct tstr; void path_init(void); void path_deinit(void); bool path_chdir(const char *path); void path_copy(struct workspace *wk, struct tstr *sb, const char *path); const char *path_cwd(void); void path_copy_cwd(struct workspace *wk, struct tstr *sb); bool path_is_absolute(const char *path); bool path_is_basename(const char *path); bool path_is_subpath(const char *base, const char *sub); void path_push(struct workspace *wk, struct tstr *sb, const char *b); void path_join(struct workspace *wk, struct tstr *sb, const char *a, const char *b); // like path_join but won't discard a if b is an absolute path void path_join_absolute(struct workspace *wk, struct tstr *sb, const char *a, const char *b); void path_make_absolute(struct workspace *wk, struct tstr *buf, const char *path); void path_relative_to(struct workspace *wk, struct tstr *buf, const char *base_raw, const char *path_raw); void path_without_ext(struct workspace *wk, struct tstr *buf, const char *path); void path_basename(struct workspace *wk, struct tstr *buf, const char *path); void path_dirname(struct workspace *wk, struct tstr *buf, const char *path); void path_executable(struct workspace *wk, struct tstr *buf, const char *path); void _path_normalize(struct workspace *wk, struct tstr *buf, bool optimize); void path_to_posix(char *path); #endif muon-v0.5.0/include/platform/filesystem.h0000644000175000017500000000552515041716357017464 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_FILESYSTEM_H #define MUON_PLATFORM_FILESYSTEM_H #include #include #include #include "iterator.h" #include "lang/source.h" struct workspace; struct tstr; bool fs_stat(const char *path, struct stat *sb); enum fs_mtime_result { fs_mtime_result_ok, fs_mtime_result_not_found, fs_mtime_result_err }; enum fs_mtime_result fs_mtime(const char *path, int64_t *mtime); bool fs_exists(const char *path); bool fs_file_exists(const char *path); bool fs_symlink_exists(const char *path); bool fs_exe_exists(const char *path); bool fs_dir_exists(const char *path); bool fs_mkdir(const char *path, bool exist_ok); bool fs_mkdir_p(const char *path); bool fs_rmdir(const char *path, bool force); bool fs_rmdir_recursive(const char *path, bool force); bool fs_read_entire_file(const char *path, struct source *src); bool fs_fsize(FILE *file, uint64_t *ret); bool fs_fclose(FILE *file); FILE *fs_fopen(const char *path, const char *mode); bool fs_fwrite(const void *ptr, size_t size, FILE *f); bool fs_fread(void *ptr, size_t size, FILE *f); int32_t fs_read(int fd, void *buf, uint32_t buf_len); bool fs_write(const char *path, const uint8_t *buf, uint64_t buf_len); bool fs_find_cmd(struct workspace *wk, struct tstr *buf, const char *cmd); bool fs_has_cmd(const char *cmd); void fs_source_destroy(struct source *src); void fs_source_dup(const struct source *src, struct source *dup); bool fs_copy_file(const char *src, const char *dest, bool force); struct fs_copy_dir_ctx { void (*file_cb)(void *usr_ctx, const char *src, const char *dest); void *usr_ctx; const char *src_base, *dest_base; bool force; }; bool fs_copy_dir_ctx(struct fs_copy_dir_ctx *ctx); bool fs_copy_dir(const char *src_base, const char *dest_base, bool force); enum iteration_result fs_copy_dir_iter(void *_ctx, const char *path); bool fs_fileno(FILE *f, int *ret); bool fs_make_symlink(const char *target, const char *path, bool force); bool fs_fseek(FILE *file, size_t off); bool fs_ftell(FILE *file, uint64_t *res); const char *fs_user_home(void); bool fs_is_a_tty_from_fd(int fd); bool fs_is_a_tty(FILE *f); bool fs_chmod(const char *path, uint32_t mode); bool fs_copy_metadata(const char *src, const char *dest); bool fs_remove(const char *path); bool fs_has_extension(const char *path, const char *ext); FILE *fs_make_tmp_file(const char *name, const char *suffix, char *buf, uint32_t len); bool fs_make_writeable_if_exists(const char *path); bool fs_wait_for_input(int fd); typedef enum iteration_result((*fs_dir_foreach_cb)(void *_ctx, const char *path)); bool fs_dir_foreach(const char *path, void *_ctx, fs_dir_foreach_cb cb); #ifndef S_ISGID #define S_ISGID 0 #endif #ifndef S_ISUID #define S_ISUID 0 #endif #ifndef S_ISVTX #define S_ISVTX 0 #endif #endif muon-v0.5.0/include/platform/term.h0000644000175000017500000000042715041716357016243 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_TERM_H #define MUON_PLATFORM_TERM_H #include #include bool term_winsize(int fd, uint32_t *height, uint32_t *width); #endif muon-v0.5.0/include/functions/0002755000175000017500000000000015041716357015306 5ustar buildbuildmuon-v0.5.0/include/functions/environment.h0000644000175000017500000000122315041716357020017 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_ENVIRONMENT_H #define MUON_FUNCTIONS_ENVIRONMENT_H #include "lang/func_lookup.h" enum environment_set_mode { environment_set_mode_set, environment_set_mode_append, environment_set_mode_prepend, }; bool environment_set(struct workspace *wk, obj env, enum environment_set_mode mode, obj key, obj vals, obj sep); bool environment_to_dict(struct workspace *wk, obj env, obj *res); void set_default_environment_vars(struct workspace *wk, obj env, bool set_subdir); extern const struct func_impl impl_tbl_environment[]; #endif muon-v0.5.0/include/functions/boolean.h0000644000175000017500000000041015041716357017067 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_BOOLEAN_H #define MUON_FUNCTIONS_BOOLEAN_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_boolean[]; #endif muon-v0.5.0/include/functions/dependency.h0000644000175000017500000000042115041716357017570 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_DEPENDENCY_H #define MUON_FUNCTIONS_DEPENDENCY_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_dependency[]; #endif muon-v0.5.0/include/functions/kernel.h0000644000175000017500000000143415041716357016737 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_H #define MUON_FUNCTIONS_KERNEL_H #include "lang/func_lookup.h" #include "coerce.h" extern const struct func_impl impl_tbl_kernel[]; extern const struct func_impl impl_tbl_kernel_internal[]; extern const struct func_impl impl_tbl_kernel_opts[]; struct find_program_ctx { struct args_kw *default_options; obj *res; uint32_t node, version_node; obj version, version_argument; obj dirs; enum requirement_type requirement; enum machine_kind machine; bool found; }; bool find_program(struct workspace *wk, struct find_program_ctx *ctx, obj prog); bool find_program_check_override(struct workspace *wk, struct find_program_ctx *ctx, obj prog); #endif muon-v0.5.0/include/functions/modules/0002755000175000017500000000000015041716357016756 5ustar buildbuildmuon-v0.5.0/include/functions/modules/curl.h0000644000175000017500000000042615041716357020074 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_CURL_H #define MUON_FUNCTIONS_MODULES_CURL_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_module_curl[]; #endif muon-v0.5.0/include/functions/modules/pkgconfig.h0000644000175000017500000000044515041716357021077 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_PKGCONFIG_H #define MUON_FUNCTIONS_MODULES_PKGCONFIG_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_module_pkgconfig[]; #endif muon-v0.5.0/include/functions/modules/json.h0000644000175000017500000000042615041716357020100 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_JSON_H #define MUON_FUNCTIONS_MODULES_JSON_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_module_json[]; #endif muon-v0.5.0/include/functions/modules/getopt.h0000644000175000017500000000043415041716357020430 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_GETOPT_H #define MUON_FUNCTIONS_MODULES_GETOPT_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_module_getopt[]; #endif muon-v0.5.0/include/functions/modules/sourceset.h0000644000175000017500000000044515041716357021144 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_SOURCESET_H #define MUON_FUNCTIONS_MODULES_SOURCESET_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_module_sourceset[]; #endif muon-v0.5.0/include/functions/modules/fs.h0000644000175000017500000000051515041716357017536 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_FS_H #define MUON_FUNCTIONS_MODULES_FS_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_module_fs[]; extern const struct func_impl impl_tbl_module_fs_internal[]; #endif muon-v0.5.0/include/functions/modules/toolchain.h0000644000175000017500000000044515041716357021110 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_TOOLCHAIN_H #define MUON_FUNCTIONS_MODULES_TOOLCHAIN_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_module_toolchain[]; #endif muon-v0.5.0/include/functions/modules/keyval.h0000644000175000017500000000043415041716357020421 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_KEYVAL_H #define MUON_FUNCTIONS_MODULES_KEYVAL_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_module_keyval[]; #endif muon-v0.5.0/include/functions/modules/subprojects.h0000644000175000017500000000132115041716357021465 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_SUBPROJECTS_H #define MUON_FUNCTIONS_MODULES_SUBPROJECTS_H #include "lang/func_lookup.h" #include "platform/timer.h" struct subprojects_common_ctx { struct arr handlers; struct timer duration; uint32_t failed; bool force, print; obj *res; }; typedef enum iteration_result ( *subprojects_foreach_cb)(struct workspace *wk, struct subprojects_common_ctx *ctx, const char *name); bool subprojects_foreach(struct workspace *wk, obj list, struct subprojects_common_ctx *usr_ctx, subprojects_foreach_cb cb); extern const struct func_impl impl_tbl_module_subprojects[]; #endif muon-v0.5.0/include/functions/modules/python.h0000644000175000017500000000066015041716357020450 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_PYTHON_H #define MUON_FUNCTIONS_MODULES_PYTHON_H #include "lang/func_lookup.h" void python_build_impl_tbl(void); extern const struct func_impl impl_tbl_module_python[]; extern const struct func_impl impl_tbl_module_python3[]; extern struct func_impl impl_tbl_python_installation[]; #endif muon-v0.5.0/include/functions/custom_target.h0000644000175000017500000000053115041716357020334 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_CUSTOM_TARGET_H #define MUON_FUNCTIONS_CUSTOM_TARGET_H #include "lang/func_lookup.h" bool custom_target_is_linkable(struct workspace *wk, obj ct); extern const struct func_impl impl_tbl_custom_target[]; #endif muon-v0.5.0/include/functions/string.h0000644000175000017500000000150215041716357016761 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_STRING_H #define MUON_FUNCTIONS_STRING_H #include "lang/func_lookup.h" enum format_cb_result { format_cb_found, format_cb_not_found, format_cb_error, format_cb_skip, }; bool version_compare(const struct str *ver1, const struct str *_ver2); bool version_compare_list(struct workspace *wk, const struct str *ver, obj cmp_arr); typedef enum format_cb_result( (*string_format_cb)(struct workspace *wk, uint32_t node, void *ctx, const struct str *key, uint32_t *elem)); bool string_format(struct workspace *wk, uint32_t err_node, obj str, obj *res, void *ctx, string_format_cb cb); extern const struct func_impl impl_tbl_string[]; extern const struct func_impl impl_tbl_string_internal[]; #endif muon-v0.5.0/include/functions/build_target.h0000644000175000017500000000130515041716357020121 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_BUILD_TARGET_H #define MUON_FUNCTIONS_BUILD_TARGET_H #include "lang/func_lookup.h" bool tgt_src_to_object_path(struct workspace *wk, const struct obj_build_target *tgt, enum compiler_language lang, obj src_file, bool relative, struct tstr *res); bool tgt_src_to_pch_path(struct workspace *wk, const struct obj_build_target *tgt, enum compiler_language lang, obj src_file, struct tstr *res); bool build_target_extract_all_objects(struct workspace *wk, uint32_t ip, obj self, obj *res, bool recursive); extern const struct func_impl impl_tbl_build_target[8]; #endif muon-v0.5.0/include/functions/modules.h0000644000175000017500000000125115041716357017124 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_H #define MUON_FUNCTIONS_MODULES_H #include "lang/func_lookup.h" struct module_info { const char *name; const char *path; bool implemented; }; extern const struct module_info module_info[module_count]; extern const struct func_impl impl_tbl_module[]; extern struct func_impl_group module_func_impl_groups[module_count][language_mode_count]; bool module_import(struct workspace *wk, const char *name, bool encapsulate, obj *res); bool module_func_lookup(struct workspace *wk, const char *name, enum module mod, uint32_t *idx); #endif muon-v0.5.0/include/functions/machine.h0000644000175000017500000000050315041716357017057 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MACHINE_H #define MUON_FUNCTIONS_MACHINE_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_machine[]; extern const struct func_impl impl_tbl_machine_internal[]; #endif muon-v0.5.0/include/functions/compiler.h0000644000175000017500000000321615041716357017271 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_COMPILER_H #define MUON_FUNCTIONS_COMPILER_H #include "lang/func_lookup.h" #include "platform/run_cmd.h" enum find_library_flag { find_library_flag_only_static = 1 << 0, find_library_flag_prefer_static = 1 << 0, }; enum find_library_found_location { find_library_found_location_system_dirs, find_library_found_location_extra_dirs, find_library_found_location_link_arg, }; struct find_library_result { obj found; enum find_library_found_location location; }; #define COMPILER_DYNAMIC_LIB_EXTS ".so", ".dylib", ".dll.a", ".dll" #define COMPILER_STATIC_LIB_EXTS ".a", ".lib" struct find_library_result find_library(struct workspace *wk, obj compiler, const char *libname, obj extra_dirs, enum find_library_flag flags); void find_library_result_to_dependency(struct workspace *wk, struct find_library_result find_result, obj compiler, obj d); enum compiler_check_mode { compiler_check_mode_preprocess, compiler_check_mode_compile, compiler_check_mode_link, compiler_check_mode_run, }; struct compiler_check_opts { struct run_cmd_ctx cmd_ctx; enum compiler_check_mode mode; obj comp_id; struct args_kw *deps, *inc, *required, *werror; obj args; bool skip_run_check; bool src_is_path; bool keep_cmd_ctx; const char *output_path; bool from_cache; obj cache_key, cache_val; }; bool compiler_check(struct workspace *wk, struct compiler_check_opts *opts, const char *src, uint32_t err_node, bool *res); extern const struct func_impl impl_tbl_compiler[]; extern const struct func_impl impl_tbl_compiler_internal[]; #endif muon-v0.5.0/include/functions/both_libs.h0000644000175000017500000000053615041716357017426 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef FUNCTIONS_BOTH_LIBS_H #define FUNCTIONS_BOTH_LIBS_H #include "lang/func_lookup.h" obj decay_both_libs(struct workspace *wk, obj both_libs); void both_libs_build_impl_tbl(void); extern struct func_impl impl_tbl_both_libs[]; #endif muon-v0.5.0/include/functions/run_result.h0000644000175000017500000000042115041716357017654 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_RUN_RESULT_H #define MUON_FUNCTIONS_RUN_RESULT_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_run_result[]; #endif muon-v0.5.0/include/functions/dict.h0000644000175000017500000000046715041716357016407 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_DICT_H #define MUON_FUNCTIONS_DICT_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_dict[]; extern const struct func_impl impl_tbl_dict_internal[]; #endif muon-v0.5.0/include/functions/subproject.h0000644000175000017500000000060715041716357017640 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_SUBPROJECT_H #define MUON_FUNCTIONS_SUBPROJECT_H #include "lang/func_lookup.h" bool subproject_get_variable(struct workspace *wk, uint32_t node, obj name_id, obj fallback, obj subproj, obj *res); extern const struct func_impl impl_tbl_subproject[]; #endif muon-v0.5.0/include/functions/source_configuration.h0000644000175000017500000000044515041716357021707 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef FUNCTIONS_SOURCE_CONFIGURATION_H #define FUNCTIONS_SOURCE_CONFIGURATION_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_source_configuration[]; #endif muon-v0.5.0/include/functions/meson.h0000644000175000017500000000047315041716357016602 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MESON_H #define MUON_FUNCTIONS_MESON_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_meson[]; extern const struct func_impl impl_tbl_meson_internal[]; #endif muon-v0.5.0/include/functions/array.h0000644000175000017500000000047315041716357016577 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_ARRAY_H #define MUON_FUNCTIONS_ARRAY_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_array[]; extern const struct func_impl impl_tbl_array_internal[]; #endif muon-v0.5.0/include/functions/kernel/0002755000175000017500000000000015041716357016566 5ustar buildbuildmuon-v0.5.0/include/functions/kernel/dependency.h0000644000175000017500000000421115041716357021051 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_DEPENDENCY_H #define MUON_FUNCTIONS_KERNEL_DEPENDENCY_H #include "lang/workspace.h" enum build_dep_merge_flag { build_dep_merge_flag_merge_all = 1 << 0, }; void build_dep_merge(struct workspace *wk, struct build_dep *dest, const struct build_dep *src, enum build_dep_merge_flag flags); void dep_process_deps(struct workspace *wk, obj deps, struct build_dep *dest); void dep_process_includes(struct workspace *wk, obj arr, enum include_type include_type, struct build_dep *dep); void build_dep_init(struct workspace *wk, struct build_dep *dep); bool func_dependency(struct workspace *wk, obj self, obj *res); bool func_declare_dependency(struct workspace *wk, obj _, obj *res); enum dependency_lookup_method { // Auto means to use whatever dependency checking mechanisms in whatever order meson thinks is best. dependency_lookup_method_auto, dependency_lookup_method_pkgconfig, // The dependency is provided by the standard library and does not need to be linked dependency_lookup_method_builtin, // Just specify the standard link arguments, assuming the operating system provides the library. dependency_lookup_method_system, // This is only supported on OSX - search the frameworks directory by name. dependency_lookup_method_extraframework, // Detect using the sysconfig module. dependency_lookup_method_sysconfig, // Specify using a "program"-config style tool dependency_lookup_method_config_tool, // Misc dependency_lookup_method_dub, dependency_lookup_method_cmake, }; bool dependency_lookup_method_from_s(const struct str *s, enum dependency_lookup_method *lookup_method); const char *dependency_lookup_method_to_s(enum dependency_lookup_method method); bool deps_check_machine_matches(struct workspace *wk, obj tgt_name, enum machine_kind tgt_machine, obj link_with, obj link_whole, obj deps); obj dependency_dup(struct workspace *wk, obj dep, enum build_dep_flag flags); bool dependency_create(struct workspace *wk, const struct build_dep_raw *raw, struct build_dep *dep, enum build_dep_flag flags); #endif muon-v0.5.0/include/functions/kernel/custom_target.h0000644000175000017500000000247615041716357021626 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_CUSTOM_TARGET_H #define MUON_FUNCTIONS_KERNEL_CUSTOM_TARGET_H #include "lang/workspace.h" struct make_custom_target_opts { obj name; uint32_t input_node; uint32_t output_node; uint32_t command_node; obj input_orig; obj output_orig; const char *output_dir, *build_dir; obj command_orig; obj depfile_orig; obj extra_args; bool capture; bool feed; bool extra_args_valid, extra_args_used; }; bool make_custom_target(struct workspace *wk, struct make_custom_target_opts *opts, obj *res); bool install_custom_target(struct workspace *wk, struct obj_custom_target *tgt, const struct args_kw *kw_install, const struct args_kw *kw_build_by_default, obj install_dir, obj install_mode); struct process_custom_target_commandline_opts { uint32_t err_node; bool relativize; obj name; obj input; obj output; obj depfile; obj depends; obj extra_args; const char *build_dir; bool extra_args_valid, extra_args_used; }; bool process_custom_target_commandline(struct workspace *wk, struct process_custom_target_commandline_opts *opts, obj arr, obj *res); bool func_custom_target(struct workspace *wk, obj _, obj *res); bool func_vcs_tag(struct workspace *wk, obj _, obj *res); #endif muon-v0.5.0/include/functions/kernel/install.h0000644000175000017500000000114715041716357020406 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_INSTALL_H #define MUON_FUNCTIONS_KERNEL_INSTALL_H #include "lang/workspace.h" bool func_install_subdir(struct workspace *wk, obj _, obj *ret); bool func_install_man(struct workspace *wk, obj _, obj *ret); bool func_install_symlink(struct workspace *wk, obj _, obj *ret); bool func_install_emptydir(struct workspace *wk, obj _, obj *ret); bool func_install_data(struct workspace *wk, obj _, obj *res); bool func_install_headers(struct workspace *wk, obj _, obj *ret); #endif muon-v0.5.0/include/functions/kernel/build_target.h0000644000175000017500000000124515041716357021404 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_BUILD_TARGET_H #define MUON_FUNCTIONS_KERNEL_BUILD_TARGET_H #include "lang/workspace.h" bool func_both_libraries(struct workspace *wk, obj _, obj *res); bool func_build_target(struct workspace *wk, obj _, obj *res); bool func_executable(struct workspace *wk, obj _, obj *res); bool func_library(struct workspace *wk, obj _, obj *res); bool func_shared_library(struct workspace *wk, obj _, obj *res); bool func_static_library(struct workspace *wk, obj _, obj *res); bool func_shared_module(struct workspace *wk, obj _, obj *res); #endif muon-v0.5.0/include/functions/kernel/options.h0000644000175000017500000000053715041716357020435 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_OPTIONS_H #define MUON_FUNCTIONS_KERNEL_OPTIONS_H #include "lang/workspace.h" bool func_option(struct workspace *wk, obj self, obj *res); bool func_get_option(struct workspace *wk, obj self, obj *res); #endif muon-v0.5.0/include/functions/kernel/configure_file.h0000644000175000017500000000046115041716357021716 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_CONFIGURE_FILE_H #define MUON_FUNCTIONS_KERNEL_CONFIGURE_FILE_H #include "lang/workspace.h" bool func_configure_file(struct workspace *wk, obj _, obj *res); #endif muon-v0.5.0/include/functions/kernel/subproject.h0000644000175000017500000000072015041716357021114 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_SUBPROJECT_H #define MUON_FUNCTIONS_KERNEL_SUBPROJECT_H #include "coerce.h" #include "lang/workspace.h" bool subproject(struct workspace *wk, obj name, enum requirement_type req, struct args_kw *default_options, struct args_kw *versions, obj *res); bool func_subproject(struct workspace *wk, obj _, obj *res); #endif muon-v0.5.0/include/functions/number.h0000644000175000017500000000040515041716357016744 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_NUMBER_H #define MUON_FUNCTIONS_NUMBER_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_number[]; #endif muon-v0.5.0/include/functions/external_program.h0000644000175000017500000000061315041716357021026 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_EXTERNAL_PROGRAM_H #define MUON_FUNCTIONS_EXTERNAL_PROGRAM_H #include "lang/func_lookup.h" void find_program_guess_version(struct workspace *wk, obj cmd_array, obj version_argument, obj *ver); extern const struct func_impl impl_tbl_external_program[5]; #endif muon-v0.5.0/include/functions/file.h0000644000175000017500000000065215041716357016377 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_FILE_H #define MUON_FUNCTIONS_FILE_H #include "lang/func_lookup.h" bool file_is_dynamic_lib(struct workspace *wk, obj file); bool file_is_static_lib(struct workspace *wk, obj file); bool file_is_linkable(struct workspace *wk, obj file); extern const struct func_impl impl_tbl_file[]; #endif muon-v0.5.0/include/functions/generator.h0000644000175000017500000000061515041716357017445 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_GENERATOR_H #define MUON_FUNCTIONS_GENERATOR_H #include "lang/func_lookup.h" bool generated_list_process_for_target(struct workspace *wk, uint32_t err_node, obj gl, obj tgt, bool add_targets, obj *res); extern const struct func_impl impl_tbl_generator[]; #endif muon-v0.5.0/include/functions/configuration_data.h0000644000175000017500000000045115041716357021315 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_CONFIGURATION_DATA_H #define MUON_FUNCTIONS_CONFIGURATION_DATA_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_configuration_data[]; #endif muon-v0.5.0/include/functions/source_set.h0000644000175000017500000000040715041716357017631 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef FUNCTIONS_SOURCE_SET_H #define FUNCTIONS_SOURCE_SET_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_source_set[]; #endif muon-v0.5.0/include/functions/template.h0000644000175000017500000000036215041716357017271 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef FUNCTIONS_xxx_H #define FUNCTIONS_xxx_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_xxx[]; #endif muon-v0.5.0/include/functions/feature_opt.h0000644000175000017500000000042415041716357017772 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_FEATURE_OPT_H #define MUON_FUNCTIONS_FEATURE_OPT_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_feature_opt[]; #endif muon-v0.5.0/include/functions/disabler.h0000644000175000017500000000041315041716357017240 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_DISABLER_H #define MUON_FUNCTIONS_DISABLER_H #include "lang/func_lookup.h" extern const struct func_impl impl_tbl_disabler[]; #endif muon-v0.5.0/include/cmd_subprojects.h0000644000175000017500000000046215041716357016635 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_CMD_SUBPROJECTS_H #define MUON_CMD_SUBPROJECTS_H #include #include bool cmd_subprojects(void *_ctx, uint32_t argc, uint32_t argi, char *const argv[]); #endif muon-v0.5.0/include/preprocessor_helpers.h0000644000175000017500000000066515041716357017724 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PREPROCESSOR_HELPERS_H #define MUON_PREPROCESSOR_HELPERS_H #define STRINGIZE(x) STRINGIZE2(x) #define STRINGIZE2(x) #x #define LINE_STRING STRINGIZE(__LINE__) #ifdef CONCAT #undef CONCAT #endif #define CONCAT(first, second) CONCAT_SIMPLE(first, second) #define CONCAT_SIMPLE(first, second) first##second #endif muon-v0.5.0/include/lang/0002755000175000017500000000000015041716357014217 5ustar buildbuildmuon-v0.5.0/include/lang/parser.h0000644000175000017500000000345615041716357015672 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_PARSER_H #define MUON_LANG_PARSER_H #include #include "lang/compiler.h" #include "lang/lexer.h" enum node_type { node_type_stmt, node_type_group, node_type_bool, node_type_null, node_type_id, node_type_maybe_id, node_type_id_lit, node_type_number, node_type_string, node_type_continue, node_type_break, node_type_args, node_type_def_args, node_type_dict, node_type_array, node_type_list, node_type_kw, node_type_or, node_type_and, node_type_eq, node_type_neq, node_type_lt, node_type_leq, node_type_gt, node_type_geq, node_type_in, node_type_not_in, node_type_add, node_type_sub, node_type_div, node_type_mul, node_type_mod, node_type_not, node_type_index, node_type_member, node_type_call, node_type_assign, node_type_foreach, node_type_foreach_args, node_type_if, node_type_negate, node_type_ternary, node_type_stringify, node_type_func_def, node_type_return, }; struct node_fmt { obj ws; }; enum node_flag { node_flag_breakpoint = 1 << 0, }; struct node { union literal_data data; struct node *l, *r; struct source_location location; struct { struct node_fmt pre, post; } fmt; uint16_t type; uint16_t flags; }; void print_ast(struct workspace *wk, struct node *root); void print_fmt_ast(struct workspace *wk, struct node *root); struct node *parse(struct workspace *wk, const struct source *src, enum vm_compile_mode mode); struct node *parse_fmt(struct workspace *wk, const struct source *src, enum vm_compile_mode mode, obj *raw_blocks); const char *node_type_to_s(enum node_type t); const char *node_to_s(struct workspace *wk, const struct node *n); struct node *cm_parse(struct workspace *wk, const struct source *src); #endif muon-v0.5.0/include/lang/string.h0000644000175000017500000001657415041716357015711 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_STRING_H #define MUON_LANG_STRING_H #include "compat.h" #include #include "lang/object.h" struct workspace; /* STR and STRL are both conveniences for creating temporary string objects. * These are useful for passing to str_ functions without needing to make a * heap allocation. STR() is compile time constant but it requires a * compile-time known string STRL() uses strlen() so it may be used with any * string. */ #define STR(__str) (struct str){ .s = "" __str "", .len = sizeof(__str) - 1 } #define STR_static(__str) { .s = "" __str "", .len = sizeof(__str) - 1 } #define STRL(__str) \ (struct str) \ { \ .s = __str, .len = (uint32_t)strlen(__str) \ } /* tstr - Temporary STRing * * tstrs should almost always be created using the TSTR macro. This creates a * tstr struct as well as allocating a buffer for it on the stack. * * The tstr_ family of functions will use this temporary buffer until if * overflows. If it overflows it will become an obj_str unless it has the flag * tstr_flag_overflow_alloc (e.g. from TSTR_manual) which will cause it to * overflow by allocating directly with malloc. * * A TSTR must be freed if and only if it has the flag * tstr_flag_overflow_alloc, if it overflows without this flag then the * workspace manages its memory. * * Conversion of a TSTR to a string should be done with tstr_into_str which * reuses the underlying string object if it has already been created. */ enum tstr_flags { tstr_flag_overflown = 1 << 0, tstr_flag_overflow_obj_str = 0 << 1, // the default tstr_flag_overflow_alloc = 1 << 1, tstr_flag_overflow_error = 1 << 2, tstr_flag_write = 1 << 3, tstr_flag_string_exposed = 1 << 4, }; #define TSTR_CUSTOM(name, static_len, flags) \ struct tstr name; \ char tstr_static_buf_##name[static_len]; \ tstr_init(&name, tstr_static_buf_##name, static_len, (enum tstr_flags)flags); #define TSTR(name) TSTR_CUSTOM(name, 1024, 0) #define TSTR_manual(name) TSTR_CUSTOM(name, 1024, tstr_flag_overflow_alloc) #define TSTR_FILE(__name, __f) struct tstr __name = { .flags = tstr_flag_write, .buf = (void *)__f }; #define TSTR_STR(__s) (struct str) { .s = (__s)->buf, .len = (__s)->len } struct tstr { char *buf; uint32_t len, cap; enum tstr_flags flags; obj s; }; void tstr_init(struct tstr *sb, char *initial_buffer, uint32_t initial_buffer_cap, enum tstr_flags flags); void tstr_destroy(struct tstr *sb); void tstr_clear(struct tstr *sb); void tstr_grow(struct workspace *wk, struct tstr *sb, uint32_t inc); void tstr_push(struct workspace *wk, struct tstr *sb, char s); void tstr_pushn(struct workspace *wk, struct tstr *sb, const char *s, uint32_t n); void tstr_pushs(struct workspace *wk, struct tstr *sb, const char *s); void tstr_pushf(struct workspace *wk, struct tstr *sb, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); void tstr_vpushf(struct workspace *wk, struct tstr *sb, const char *fmt, va_list args); void tstr_push_json_escaped(struct workspace *wk, struct tstr *buf, const char *str, uint32_t len); void tstr_push_json_escaped_quoted(struct workspace *wk, struct tstr *buf, const struct str *str); void tstr_trim_trailing_newline(struct tstr *sb); obj tstr_into_str(struct workspace *wk, struct tstr *sb); /* str - strings * * struct str a.k.a. obj_string a.k.a. string objects are the primary * representation for strings. They are counted strings and generally may * contain 0 bytes. They are also guaranteed to include a 0 terminator for * convenience. Typically they are constructed with the `make_str*` functions. * * String objects have the same lifetime as all objects: the lifetime of the * workspace they were created with. */ void str_escape(struct workspace *wk, struct tstr *sb, const struct str *ss, bool escape_printable); void str_escape_json(struct workspace *wk, struct tstr *sb, const struct str *ss); bool str_has_null(const struct str *ss); const char *get_cstr(struct workspace *wk, obj s); obj make_str(struct workspace *wk, const char *str); obj make_strn(struct workspace *wk, const char *str, uint32_t n); obj make_strf(struct workspace *wk, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 2, 3); obj make_strfv(struct workspace *wk, const char *fmt, va_list args); obj make_str_enum(struct workspace *wk, const char *str, obj values); obj mark_typeinfo_as_enum(struct workspace *wk, obj ti, obj values); obj make_strn_enum(struct workspace *wk, const char *str, uint32_t n, obj values); bool check_str_enum(struct workspace *wk, obj l, enum obj_type l_t, obj r, enum obj_type r_t); bool str_enum_add_type(struct workspace *wk, uint32_t id, obj *res); void str_enum_add_type_value(struct workspace *wk, obj type, const char *value); obj str_enum_get(struct workspace *wk, obj type, const char *name); void str_app(struct workspace *wk, obj *s, const char *str); void str_appf(struct workspace *wk, obj *s, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); void str_appn(struct workspace *wk, obj *s, const char *str, uint32_t n); void str_apps(struct workspace *wk, obj *s, obj s_id); obj str_clone(struct workspace *wk_src, struct workspace *wk_dest, obj val); obj str_clone_mutable(struct workspace *wk, obj val); bool str_eql(const struct str *ss1, const struct str *ss2); bool str_eql_glob(const struct str *ss1, const struct str *ss2); bool str_eqli(const struct str *ss1, const struct str *ss2); bool str_startswith(const struct str *ss, const struct str *pre); bool str_startswithi(const struct str *ss, const struct str *pre); bool str_endswith(const struct str *ss, const struct str *suf); bool str_endswithi(const struct str *ss, const struct str *suf); bool str_contains(const struct str *str, const struct str *substr); bool str_containsi(const struct str *str, const struct str *substr); obj str_join(struct workspace *wk, obj s1, obj s2); bool str_to_i(const struct str *ss, int64_t *res, bool strip); bool str_to_i_base(const struct str *ss, int64_t *res, bool strip, uint32_t base); obj str_split(struct workspace *wk, const struct str *ss, const struct str *split); obj str_splitlines(struct workspace *wk, const struct str *ss); enum str_strip_flag { str_strip_flag_right_only = 1 << 1, }; obj str_strip(struct workspace *wk, const struct str *ss, const struct str *strip, enum str_strip_flag flags); obj str_split_strip(struct workspace *wk, const struct str *ss, const struct str *split, const struct str *strip); bool str_split_in_two(const struct str *s, struct str *l, struct str *r, char split); void str_to_lower(struct str *str); void cstr_copy_(char *dest, const struct str *src, uint32_t dest_len); #define cstr_copy(__dest, __src) cstr_copy_(__dest, __src, ARRAY_LEN(__dest)); bool is_whitespace(char c); bool is_whitespace_except_newline(char c); void snprintf_append_(char *buf, uint32_t buf_len, uint32_t *buf_i, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 4, 5); #define snprintf_append(__buf, __buf_i, __fmt, ...) snprintf_append_(__buf, sizeof(__buf), __buf_i, __fmt, __VA_ARGS__) enum shell_type { shell_type_posix, shell_type_cmd, }; enum shell_type shell_type_for_host_machine(void); obj str_shell_split(struct workspace *wk, const struct str *str, enum shell_type shell); bool str_fuzzy_match(const struct str *input, const struct str *guess, int32_t *dist); #endif muon-v0.5.0/include/lang/serial.h0000644000175000017500000000062515041716357015650 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_SERIAL_H #define MUON_LANG_SERIAL_H #include "lang/workspace.h" bool serial_dump(struct workspace *wk_src, obj o, FILE *f); bool serial_load(struct workspace *wk, obj *res, FILE *f); bool serial_load_from_private_dir(struct workspace *wk, obj *res, const char *file); #endif muon-v0.5.0/include/lang/lsp.h0000644000175000017500000000036415041716357015167 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_LSP_H #define MUON_LANG_LSP_H #include struct az_opts; bool analyze_server(struct az_opts *opts); #endif muon-v0.5.0/include/lang/func_lookup.h0000644000175000017500000000367015041716357016720 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_FUNC_LOOKUP_H #define MUON_LANG_FUNC_LOOKUP_H #include "lang/workspace.h" typedef bool (*func_native_impl)(struct workspace *wk, obj self, obj *res); typedef obj (*func_impl_self_transform)(struct workspace *wk, obj self); enum func_impl_flag { func_impl_flag_sandbox_disable = 1 << 0, func_impl_flag_extension = 1 << 1, func_impl_flag_throws_error = 1 << 2, }; struct func_impl { const char *name; func_native_impl func; type_tag return_type; bool pure; enum func_impl_flag flags; func_impl_self_transform self_transform; const char *desc; }; struct func_impl_group { const struct func_impl *impls; uint32_t off, len; }; extern struct func_impl_group func_impl_groups[obj_type_count][language_mode_count]; extern struct func_impl native_funcs[]; void build_func_impl_tables(void); bool func_lookup(struct workspace *wk, obj self, const char *name, uint32_t *idx, obj *func); bool func_lookup_for_group(const struct func_impl_group impl_group[], enum language_mode mode, const char *name, uint32_t *idx); void func_kwargs_lookup(struct workspace *wk, obj self, const char *name, struct arr *kwargs_arr); void kwargs_arr_destroy(struct workspace *wk, struct arr *arr); void kwargs_arr_push(struct workspace *wk, struct arr *arr, const struct args_kw *kw); void kwargs_arr_del(struct workspace *wk, struct arr *arr, const char *name); struct args_kw *kwargs_arr_get(struct workspace *wk, struct arr *arr, const char *name); void dump_function_signatures(struct workspace *wk); void dump_function_docs(struct workspace *wk); obj dump_function_native(struct workspace *wk, enum obj_type t, const struct func_impl *impl); obj dump_module_function_native(struct workspace *wk, enum module module, const struct func_impl *impl); obj dump_module_function_capture(struct workspace *wk, const char *module, obj name, obj o); #endif muon-v0.5.0/include/lang/types.h0000644000175000017500000000230015041716357015525 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_TYPES_H #define MUON_LANG_TYPES_H #include #include typedef uint32_t obj; typedef uint64_t type_tag; struct args_norm { type_tag type; const char *name; const char *desc; obj val; uint32_t node; bool set, optional; }; struct args_kw { const char *key; type_tag type; const char *desc; obj val; uint32_t node; bool set; bool required; bool extension; }; enum language_mode { language_external, language_internal, language_opts, language_mode_count, language_extended, }; enum build_language { build_language_meson, build_language_cmake, }; enum log_level { log_quiet, log_error, log_warn, log_note, log_info, log_debug, log_level_count, }; enum toolchain_component { toolchain_component_compiler, toolchain_component_linker, toolchain_component_static_linker, }; #define toolchain_component_count 3 // Keep in sync with above union obj_dict_big_dict_value { uint64_t u64; struct { obj key, val; } val; }; enum error_message_flag { error_message_flag_no_source = 1 << 0, error_message_flag_coalesce = 1 << 1, }; #endif muon-v0.5.0/include/lang/workspace.h0000644000175000017500000000705315041716357016371 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_WORKSPACE_H #define MUON_LANG_WORKSPACE_H #include "datastructures/arr.h" #include "datastructures/stack.h" #include "lang/eval.h" #include "lang/string.h" #include "lang/vm.h" struct project { /* array of dicts */ obj scope_stack; obj toolchains[machine_kind_count]; obj args[machine_kind_count], link_args[machine_kind_count], include_dirs[machine_kind_count], link_with[machine_kind_count]; obj source_root, build_root, cwd, build_dir, subproject_name; obj opts, targets, tests, test_setups, summary; struct { obj static_deps[machine_kind_count], shared_deps[machine_kind_count]; obj frameworks[machine_kind_count]; } dep_cache; obj wrap_provides_deps, wrap_provides_exes; // string obj rule_prefix; obj subprojects_dir; obj module_dir; struct { obj name; obj version; obj license; obj license_files; bool no_version; } cfg; bool not_ok; // set by failed subprojects bool initialized; // ninja-specific obj generic_rules[machine_kind_count]; }; struct workspace { const char *argv0, *source_root, *build_root, *muon_private; struct { uint32_t argc; char *const *argv; } original_commandline; /* Global objects * These should probably be cleaned up into a separate struct. * ----------------- */ obj toolchains[machine_kind_count]; obj global_args[machine_kind_count], global_link_args[machine_kind_count]; /* overridden dependencies dict */ obj dep_overrides_static[machine_kind_count], dep_overrides_dynamic[machine_kind_count]; /* overridden find_program dict */ obj find_program_overrides[machine_kind_count]; /* dict[str] */ obj machine_properties[machine_kind_count]; /* TODO host machine dict */ obj host_machine; /* TODO binaries dict */ obj binaries; /* obj_array that tracks files for build regeneration */ obj regenerate_deps; obj exclude_regenerate_deps; obj install; obj install_scripts; obj postconf_scripts; obj subprojects; /* global options */ obj global_opts; /* dict[sha_512 -> [bool, any]] */ obj compiler_check_cache; /* dict -> dict[method -> capture] */ obj dependency_handlers; /* list[str], used for error reporting */ obj backend_output_stack; /* ----------------- */ struct vm vm; struct stack stack; struct arr projects; struct arr option_overrides; uint32_t cur_project; #ifdef TRACY_ENABLE struct { bool is_master_workspace; } tracy; #endif }; void workspace_init_bare(struct workspace *wk); void workspace_init_runtime(struct workspace *wk); void workspace_init_startup_files(struct workspace *wk); void workspace_init(struct workspace *wk); void workspace_destroy_bare(struct workspace *wk); void workspace_destroy(struct workspace *wk); void workspace_setup_paths(struct workspace *wk, const char *build, const char *argv0, uint32_t argc, char *const argv[]); void workspace_add_exclude_regenerate_dep(struct workspace *wk, obj v); void workspace_add_regenerate_dep(struct workspace *wk, obj v); void workspace_add_regenerate_deps(struct workspace *wk, obj obj_or_arr); struct project * make_project(struct workspace *wk, uint32_t *id, const char *subproject_name, const char *cwd, const char *build_dir); struct project *current_project(struct workspace *wk); const char *workspace_build_dir(struct workspace *wk); const char *workspace_cwd(struct workspace *wk); void workspace_print_summaries(struct workspace *wk, FILE *out); bool workspace_do_setup(struct workspace *wk, const char *build, const char *argv0, uint32_t argc, char *const argv[]); #endif muon-v0.5.0/include/lang/compiler.h0000644000175000017500000000151515041716357016202 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_COMPILER_H #define MUON_LANG_COMPILER_H #include #include struct source; struct workspace; enum vm_compile_mode { vm_compile_mode_fmt = 1 << 1, vm_compile_mode_quiet = 1 << 2, vm_compile_mode_language_extended = 1 << 3, vm_compile_mode_expr = 1 << 4, vm_compile_mode_return_after_project = 1 << 5, vm_compile_mode_relaxed_parse = 1 << 6, }; void vm_compile_state_reset(struct workspace *wk); void vm_compile_initial_code_segment(struct workspace *wk); struct node; bool vm_compile_ast(struct workspace *wk, struct node *n, enum vm_compile_mode mode, uint32_t *entry); bool vm_compile(struct workspace *wk, const struct source *src, enum vm_compile_mode mode, uint32_t *entry); #endif muon-v0.5.0/include/lang/vm.h0000644000175000017500000002132315041716357015011 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_VM_H #define MUON_LANG_VM_H #include "datastructures/arr.h" #include "datastructures/bucket_arr.h" #include "datastructures/hash.h" #include "lang/compiler.h" #include "lang/eval.h" #include "lang/object.h" #include "lang/source.h" #include "lang/types.h" enum op { op_constant = 1, op_constant_list, op_constant_dict, op_constant_func, op_add, op_sub, op_mul, op_div, op_mod, op_not, op_eq, op_in, op_gt, op_lt, op_negate, op_stringify, op_store, op_load, op_try_load, op_return, op_return_end, op_call, op_call_native, op_member, op_index, op_iterator, op_iterator_next, op_jmp, op_jmp_if_true, op_jmp_if_false, op_jmp_if_disabler, op_jmp_if_disabler_keep, op_pop, op_dup, op_swap, op_typecheck, op_dbg_break, // Analyzer only ops op_az_branch, op_az_merge, op_count, }; extern const uint32_t op_operands[op_count]; extern const uint32_t op_operand_size; #define OP_WIDTH(op) (1 + op_operand_size * op_operands[op]) struct workspace; enum op_store_flags { op_store_flag_add_store = 1 << 0, op_store_flag_member = 2 << 0, }; enum variable_assignment_mode { assign_local, assign_reassign, }; enum compile_time_constant_objects { /* obj_null = 0, */ /* obj_disabler = 1, */ /* obj_meson = 2, */ obj_bool_true = 3, obj_bool_false = 4, compile_time_constant_objects_end, }; struct obj_stack_entry { obj o; uint32_t ip; }; struct object_stack { struct bucket_arr ba; struct obj_stack_entry *page; uint32_t i, bucket; }; struct source_location_mapping { struct source_location loc; uint32_t src_idx, ip; }; enum call_frame_type { call_frame_type_eval, call_frame_type_func, }; struct call_frame { type_tag expected_return_type; enum call_frame_type type; obj scope_stack; uint32_t return_ip, call_stack_base; enum language_mode lang_mode; struct obj_func *func; }; struct vm_compiler_state { struct bucket_arr nodes; struct arr node_stack; struct arr loop_jmp_stack, if_jmp_stack; uint32_t loop_depth; obj breakpoints; enum vm_compile_mode mode; bool err; }; struct vm_dbg_state { void (*break_cb)(struct workspace *wk); void *usr_ctx; struct source_location prev_source_location; obj watched; obj breakpoints; obj root_eval_trace; obj eval_trace; uint32_t icount; uint32_t break_after; bool dbg, stepping; bool eval_trace_subdir; }; struct vm_behavior { void((*assign_variable)(struct workspace *wk, const char *name, obj o, uint32_t n_id, enum variable_assignment_mode mode)); void((*unassign_variable)(struct workspace *wk, const char *name)); void((*push_local_scope)(struct workspace *wk)); void((*pop_local_scope)(struct workspace *wk)); obj((*scope_stack_dup)(struct workspace *wk, obj scope_stack)); bool((*get_variable)(struct workspace *wk, const char *name, obj *res)); bool((*eval_project_file)(struct workspace *wk, const char *path, enum build_language lang, enum eval_project_file_flags flags, obj *res)); bool((*native_func_dispatch)(struct workspace *wk, uint32_t func_idx, obj self, obj *res)); bool((*pop_args)(struct workspace *wk, struct args_norm an[], struct args_kw akw[])); bool((*func_lookup)(struct workspace *wk, obj self, const char *name, uint32_t *idx, obj *func)); void((*execute_loop)(struct workspace *wk)); }; struct vm_objects { struct bucket_arr chrs; struct bucket_arr objs; struct bucket_arr dict_elems, dict_hashes, array_elems; struct bucket_arr obj_aos[obj_type_count - _obj_aos_start]; struct hash obj_hash, str_hash, dedup_str_hash; struct { obj values; obj types; } enums; obj complex_types; bool obj_clear_mark_set; }; typedef void((*vm_op_fn)(struct workspace *wk)); struct vm_ops { vm_op_fn ops[op_count]; }; struct vm_type_registry { obj structs; obj enums; obj docs; obj top_level_docs; }; struct vm { struct object_stack stack; struct arr call_stack, locations, code, src; uint32_t ip, nargs, nkwargs; obj scope_stack, default_scope_stack; obj modules; struct vm_ops ops; struct vm_objects objects; struct vm_behavior behavior; struct vm_compiler_state compiler_state; struct vm_dbg_state dbg_state; struct vm_type_registry types; enum language_mode lang_mode; bool run; bool saw_disabler; bool in_analyzer; // When true, disable functions with the .fuzz_unsafe attribute set to true. // This is useful when running `muon internal eval` on randomly generated // files, where you don't want to accidentally execute `run_command('rm', // '-rf', '/')` for example bool disable_fuzz_unsafe_functions; bool error; }; obj object_stack_pop(struct object_stack *s); void object_stack_push(struct workspace *wk, obj o); obj object_stack_peek(struct object_stack *s, uint32_t off); struct obj_stack_entry *object_stack_peek_entry(struct object_stack *s, uint32_t off); struct obj_stack_entry *object_stack_pop_entry(struct object_stack *s); void object_stack_discard(struct object_stack *s, uint32_t n); void object_stack_print(struct workspace *wk, struct object_stack *s); obj vm_get_constant(uint8_t *code, uint32_t *ip); uint32_t vm_constant_host_to_bc(uint32_t n); obj vm_execute(struct workspace *wk); bool vm_eval_capture(struct workspace *wk, obj capture, const struct args_norm an[], const struct args_kw akw[], obj *res); void vm_push_call_stack_frame(struct workspace *wk, struct call_frame *frame); void vm_lookup_inst_location_src_idx(struct vm *vm, uint32_t ip, struct source_location *loc, uint32_t *src_idx); void vm_lookup_inst_location(struct vm *vm, uint32_t ip, struct source_location *loc, struct source **src); obj vm_inst_location_str(struct workspace *wk, uint32_t ip); obj vm_callstack(struct workspace *wk); void vm_dis(struct workspace *wk); const char *vm_dis_inst(struct workspace *wk, uint8_t *code, uint32_t base_ip); void vm_init(struct workspace *wk); void vm_init_objects(struct workspace *wk); void vm_destroy(struct workspace *wk); void vm_destroy_objects(struct workspace *wk); bool pop_args(struct workspace *wk, struct args_norm an[], struct args_kw akw[]); bool vm_pop_args(struct workspace *wk, struct args_norm an[], struct args_kw akw[]); void vm_op_return(struct workspace *wk); MUON_ATTR_FORMAT(printf, 5, 6) void vm_diagnostic(struct workspace *wk, uint32_t ip, enum log_level lvl, enum error_message_flag flags, const char *fmt, ...); MUON_ATTR_FORMAT(printf, 3, 4) void vm_error_at(struct workspace *wk, uint32_t ip, const char *fmt, ...); MUON_ATTR_FORMAT(printf, 2, 3) void vm_error(struct workspace *wk, const char *fmt, ...); MUON_ATTR_FORMAT(printf, 3, 4) void vm_warning_at(struct workspace *wk, uint32_t ip, const char *fmt, ...); MUON_ATTR_FORMAT(printf, 2, 3) void vm_warning(struct workspace *wk, const char *fmt, ...); void vm_dbg_push_breakpoint(struct workspace *wk, obj file, uint32_t line, uint32_t col); bool vm_dbg_push_breakpoint_str(struct workspace *wk, const char *bp); void vm_dbg_unpack_breakpoint(struct workspace *wk, obj v, uint32_t *line, uint32_t *col); /* The below functions may be used to facilitate converting meson dicts to * native c structs. First a struct must be registered with vm_struct, and all * of its members that will be exposed with vm_struct_member. */ enum vm_struct_type { vm_struct_type_bool, vm_struct_type_str, vm_struct_type_obj, vm_struct_type_struct_, vm_struct_type_enum_, }; #define vm_struct_type_mask 7 #define vm_struct_type_shift 3 enum vm_struct_type vm_make_struct_type(struct workspace *wk, enum vm_struct_type base_t, const char *name); bool vm_enum_(struct workspace *wk, const char *name); #define vm_enum(__wk, __e) vm_enum_(__wk, #__e) void vm_enum_value_(struct workspace *wk, const char *name, const char *member, uint32_t value); #define vm_enum_value(__wk, __e, __m) vm_enum_value_(__wk, #__e, #__m, __m) #define vm_enum_value_prefixed(__wk, __e, __m) vm_enum_value_(__wk, #__e, #__m, __e ## _ ## __m) bool vm_obj_to_enum_(struct workspace *wk, const char *name, obj o, void *s); #define vm_obj_to_enum(__wk, __e, __o, __d) vm_obj_to_enum_(__wk, #__e, __o, __d) #define vm_struct_type_enum(__wk, __e) vm_make_struct_type(__wk, vm_struct_type_enum_, #__e) bool vm_struct_(struct workspace *wk, const char *name); #define vm_struct(__wk, __s) vm_struct_(__wk, #__s) void vm_struct_member_(struct workspace *wk, const char *name, const char *member, uint32_t offset, enum vm_struct_type t); #define vm_struct_member(__wk, __s, __m, __t) vm_struct_member_(__wk, #__s, #__m, offsetof(struct __s, __m), __t) bool vm_obj_to_struct_(struct workspace *wk, const char *name, obj o, void *s); #define vm_obj_to_struct(__wk, __s, __o, __d) vm_obj_to_struct_(__wk, #__s, __o, __d) const char *vm_struct_docs_(struct workspace *wk, const char *name, const char *fmt); #define vm_struct_docs(__wk, __s, __f) vm_struct_docs_(__wk, #__s, __f) #endif muon-v0.5.0/include/lang/analyze.h0000644000175000017500000000430715041716357016035 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_ANALYZE_H #define MUON_LANG_ANALYZE_H #include "error.h" #include "workspace.h" struct ast; enum az_diagnostic { az_diagnostic_unused_variable = 1 << 0, az_diagnostic_reassign_to_conflicting_type = 1 << 1, az_diagnostic_dead_code = 1 << 2, }; struct az_opts { bool subdir_error; bool eval_trace; bool analyze_project_call_only; bool relaxed_parse; bool auto_chdir_root; enum error_diagnostic_store_replay_opts replay_opts; enum language_mode lang_mode; const char *single_file; uint64_t enabled_diagnostics; obj file_override; struct arr file_override_src; struct { bool debug_log, wait_for_debugger; } lsp; }; enum az_branch_element_flag { az_branch_element_flag_pop = 1 << 0, }; union az_branch_element { int64_t i64; struct az_branch_element_data { uint32_t ip; uint32_t flags; } data; }; enum az_branch_type { az_branch_type_normal, az_branch_type_loop, }; obj make_typeinfo(struct workspace *wk, type_tag t); obj make_az_branch_element(struct workspace *wk, uint32_t ip, uint32_t flags); bool az_diagnostic_name_to_enum(const char *name, enum az_diagnostic *ret); void az_print_diagnostic_names(void); void az_check_dead_code(struct workspace *wk, struct ast *ast); void az_set_error(void); struct az_assignment { const char *name; obj o; bool accessed, default_var; struct source_location location; uint32_t ip, src_idx; uint32_t ep_stacks_i; uint32_t ep_stack_len; }; struct az_assignment *az_assign_lookup(struct workspace *wk, const char *name); uint32_t az_dict_member_location_lookup_str(struct workspace *wk, obj dict, const char *key); extern struct func_impl_group az_func_impl_group; void analyze_opts_init(struct workspace *wk, struct az_opts *opts); void analyze_opts_destroy(struct workspace *wk, struct az_opts *opts); bool analyze_opts_push_override(struct workspace *wk, struct az_opts *opts, const char *override, const char *content_path, const struct str *content); bool do_analyze(struct workspace *wk, struct az_opts *opts); void eval_trace_print(struct workspace *wk, obj trace); bool analyze_project_call(struct workspace *wk); #endif muon-v0.5.0/include/lang/source.h0000644000175000017500000000073015041716357015666 0ustar buildbuild#ifndef MUON_LANG_SOURCE_H /* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #define MUON_LANG_SOURCE_H #include #include enum source_type { source_type_unknown, source_type_file, source_type_embedded, }; struct source { const char *label; const char *src; uint64_t len; enum source_type type; bool is_weak_reference; }; struct source_location { uint32_t off, len; }; #endif muon-v0.5.0/include/lang/lexer.h0000644000175000017500000000644415041716357015515 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_LEXER_H #define MUON_LANG_LEXER_H #include #include "datastructures/stack.h" #include "lang/source.h" #include "lang/string.h" enum token_type { token_type_error = -1, token_type_eof, token_type_eol, token_type_lparen = '(', token_type_rparen = ')', token_type_lbrack = '[', token_type_rbrack = ']', token_type_lcurl = '{', token_type_rcurl = '}', token_type_dot = '.', token_type_comma = ',', token_type_colon = ':', token_type_question_mark = '?', /* math */ token_type_plus = '+', token_type_minus = '-', token_type_star = '*', token_type_slash = '/', token_type_modulo = '%', /* comparison single char */ token_type_gt = '>', token_type_lt = '<', /* special single char */ token_type_bitor = '|', /* assign */ token_type_assign = '=', token_type_plus_assign = 256, /* comparison multi char */ token_type_eq, token_type_neq, token_type_geq, token_type_leq, /* keywords */ token_type_if, token_type_else, token_type_elif, token_type_endif, token_type_and, token_type_or, token_type_not, token_type_foreach, token_type_endforeach, token_type_in, token_type_not_in, token_type_continue, token_type_break, /* literals */ token_type_identifier, token_type_string, token_type_fstring, token_type_number, token_type_true, token_type_false, /* functions */ token_type_func, token_type_endfunc, token_type_return, token_type_returntype, token_type_doc_comment, token_type_null, }; // Keep in sync with above #define token_type_count (token_type_null + 1) enum cm_token_subtype { cm_token_subtype_none, cm_token_subtype_comp_str, cm_token_subtype_comp_ver, cm_token_subtype_comp_path, cm_token_subtype_comp_regex, }; union literal_data { obj literal; obj str; int64_t num; uint64_t type; struct { uint32_t args, kwargs; } len; }; struct token { enum token_type type; union literal_data data; struct source_location location; }; enum lexer_mode { lexer_mode_fmt = 1 << 0, lexer_mode_functions = 1 << 1, lexer_mode_cmake = 1 << 2, lexer_mode_bom_error = 1 << 3, }; enum cm_lexer_mode { cm_lexer_mode_default, cm_lexer_mode_command, cm_lexer_mode_conditional, }; struct lexer_fmt { obj raw_blocks; uint32_t raw_block_start; bool in_raw_block; }; struct lexer { struct workspace *wk; const struct source *source; const char *src; struct stack stack; struct lexer_fmt fmt; uint32_t i, ws_start, ws_end; enum lexer_mode mode; enum cm_lexer_mode cm_mode; uint8_t enclosed_state; }; bool is_valid_inside_of_identifier(const char c); bool is_valid_start_of_identifier(const char c); bool is_hex_digit(const char c); bool lex_string_escape_utf8(struct workspace *wk, struct tstr *buf, uint32_t val); void lexer_init(struct lexer *lexer, struct workspace *wk, const struct source *src, enum lexer_mode mode); void lexer_destroy(struct lexer *lexer); void lexer_next(struct lexer *lexer, struct token *token); obj lexer_get_preceeding_whitespace(struct lexer *lexer); bool lexer_is_fmt_comment(const struct str *comment, bool *fmt_on); const char *token_type_to_s(enum token_type type); const char *token_to_s(struct workspace *wk, struct token *token); void cm_lexer_next(struct lexer *lexer, struct token *token); #endif muon-v0.5.0/include/lang/typecheck.h0000644000175000017500000001753515041716357016360 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_TYPECHECK_H #define MUON_LANG_TYPECHECK_H #include "lang/object.h" enum complex_type { complex_type_preset, complex_type_or, complex_type_nested, complex_type_enum, }; #define ARG_TYPE_NULL (obj_type_count + 1) // clang-format off #define TYPE_TAG_ALLOW_NULL (((type_tag)1) << 59) #define TYPE_TAG_COMPLEX (((type_tag)1) << 60) #define TYPE_TAG_GLOB (((type_tag)1) << 61) #define TYPE_TAG_LISTIFY (((type_tag)1) << 62) #define obj_typechecking_type_tag (((type_tag)1) << 63) // clang-format on #define TYPE_TAG_MASK \ (TYPE_TAG_ALLOW_NULL | TYPE_TAG_COMPLEX | TYPE_TAG_GLOB | TYPE_TAG_LISTIFY \ | obj_typechecking_type_tag) /* complex types look like this: * * 32 bits -> index into obj_typeinfo bucket array * 16 bits -> unused * 8 bits -> enum complex_type type (e.g. complex_type_or or complex_type_nested) * 4 bits -> tags (should be ARG_TYPE_COMPLEX | * obj_typechecking_type_tag (and potentially also * TYPE_TAG_GLOB/TYPE_TAG_LISTIFY)) */ #define COMPLEX_TYPE(index, t) \ (((uint64_t)index) | (((uint64_t)t) << 48) | TYPE_TAG_COMPLEX | obj_typechecking_type_tag) #define COMPLEX_TYPE_INDEX(t) (t & 0xffffffff) #define COMPLEX_TYPE_TYPE(t) ((t >> 48) & 0xff) #define COMPLEX_TYPE_PRESET(__i) COMPLEX_TYPE(__i, complex_type_preset) // clang-format off #define tc_disabler (obj_typechecking_type_tag | (((type_tag)1) << 0)) #define tc_meson (obj_typechecking_type_tag | (((type_tag)1) << 1)) #define tc_bool (obj_typechecking_type_tag | (((type_tag)1) << 2)) #define tc_file (obj_typechecking_type_tag | (((type_tag)1) << 3)) #define tc_feature_opt (obj_typechecking_type_tag | (((type_tag)1) << 4)) #define tc_machine (obj_typechecking_type_tag | (((type_tag)1) << 5)) #define tc_number (obj_typechecking_type_tag | (((type_tag)1) << 6)) #define tc_string (obj_typechecking_type_tag | (((type_tag)1) << 7)) #define tc_array (obj_typechecking_type_tag | (((type_tag)1) << 8)) #define tc_dict (obj_typechecking_type_tag | (((type_tag)1) << 9)) #define tc_compiler (obj_typechecking_type_tag | (((type_tag)1) << 10)) #define tc_build_target (obj_typechecking_type_tag | (((type_tag)1) << 11)) #define tc_custom_target (obj_typechecking_type_tag | (((type_tag)1) << 12)) #define tc_subproject (obj_typechecking_type_tag | (((type_tag)1) << 13)) #define tc_dependency (obj_typechecking_type_tag | (((type_tag)1) << 14)) #define tc_external_program (obj_typechecking_type_tag | (((type_tag)1) << 15)) #define tc_python_installation (obj_typechecking_type_tag | (((type_tag)1) << 16)) #define tc_run_result (obj_typechecking_type_tag | (((type_tag)1) << 17)) #define tc_configuration_data (obj_typechecking_type_tag | (((type_tag)1) << 18)) #define tc_test (obj_typechecking_type_tag | (((type_tag)1) << 19)) #define tc_module (obj_typechecking_type_tag | (((type_tag)1) << 20)) #define tc_install_target (obj_typechecking_type_tag | (((type_tag)1) << 21)) #define tc_environment (obj_typechecking_type_tag | (((type_tag)1) << 22)) #define tc_include_directory (obj_typechecking_type_tag | (((type_tag)1) << 23)) #define tc_option (obj_typechecking_type_tag | (((type_tag)1) << 24)) #define tc_generator (obj_typechecking_type_tag | (((type_tag)1) << 25)) #define tc_generated_list (obj_typechecking_type_tag | (((type_tag)1) << 26)) #define tc_alias_target (obj_typechecking_type_tag | (((type_tag)1) << 27)) #define tc_both_libs (obj_typechecking_type_tag | (((type_tag)1) << 28)) #define tc_source_set (obj_typechecking_type_tag | (((type_tag)1) << 29)) #define tc_source_configuration (obj_typechecking_type_tag | (((type_tag)1) << 30)) #define tc_iterator (obj_typechecking_type_tag | (((type_tag)1) << 31)) #define tc_func (obj_typechecking_type_tag | (((type_tag)1) << 32)) #define tc_capture (obj_typechecking_type_tag | (((type_tag)1) << 33)) #define tc_typeinfo (obj_typechecking_type_tag | (((type_tag)1) << 34)) #define tc_type_count 35 #define tc_any (tc_bool | tc_file | tc_number | tc_string | tc_array | tc_dict \ | tc_compiler | tc_build_target | tc_custom_target \ | tc_subproject | tc_dependency | tc_feature_opt \ | tc_external_program | tc_python_installation | tc_run_result \ | tc_configuration_data | tc_test | tc_module \ | tc_install_target | tc_environment | tc_include_directory \ | tc_option | tc_generator | tc_generated_list \ | tc_alias_target | tc_both_libs | tc_disabler \ | tc_meson | tc_machine | tc_source_set | tc_source_configuration | tc_func \ | tc_iterator | tc_capture \ ) #define tc_exe (tc_string | tc_file | tc_external_program | tc_python_installation \ | tc_build_target | tc_custom_target | tc_both_libs) #define tc_coercible_env (tc_environment | tc_string | tc_array | tc_dict) #define tc_coercible_files (tc_string | tc_custom_target | tc_build_target | tc_file | tc_both_libs) #define tc_coercible_inc (tc_string | tc_include_directory) #define tc_command_array (TYPE_TAG_LISTIFY | tc_exe) #define tc_depends_kw (TYPE_TAG_LISTIFY | tc_build_target | tc_custom_target | tc_both_libs | tc_file) #define tc_install_mode_kw (TYPE_TAG_LISTIFY | tc_string | tc_number | tc_bool) #define tc_required_kw (tc_bool | tc_feature_opt) /* XXX: tc_file should not really be in tc_link_with_kw, however this is * how muon represents custom_target outputs, which are valid link_with * arguments... */ #define tc_link_with_kw (TYPE_TAG_LISTIFY | tc_build_target | tc_custom_target | tc_file | tc_both_libs) #define tc_message (TYPE_TAG_GLOB | tc_feature_opt | tc_string | tc_bool | tc_number | tc_array | tc_dict | tc_file) // doesn't handle nested types // clang-format on struct obj_typechecking_type_to_obj_type { enum obj_type type; type_tag tc; }; type_tag get_obj_typechecking_type(struct workspace *wk, obj got_obj); bool typecheck(struct workspace *wk, uint32_t ip, obj obj_id, type_tag type); bool typecheck_custom(struct workspace *wk, uint32_t ip, obj obj_id, type_tag type, const char *fmt); bool typecheck_simple_err(struct workspace *wk, obj o, type_tag type); obj typechecking_type_to_str(struct workspace *wk, type_tag t); const char *typechecking_type_to_s(struct workspace *wk, type_tag t); obj typechecking_type_to_arr(struct workspace *wk, type_tag t); type_tag make_complex_type(struct workspace *wk, enum complex_type t, type_tag type, type_tag subtype); bool typecheck_typeinfo(struct workspace *wk, obj v, type_tag t); type_tag obj_type_to_tc_type(enum obj_type t); obj obj_type_to_typestr(struct workspace *wk, obj o); const char *obj_typestr(struct workspace *wk, obj o); bool bounds_adjust(uint32_t len, int64_t *i); bool boundscheck(struct workspace *wk, uint32_t ip, uint32_t len, int64_t *i); bool rangecheck(struct workspace *wk, uint32_t ip, int64_t min, int64_t max, int64_t n); bool type_tags_eql(struct workspace *wk, type_tag a, type_tag b); type_tag flatten_type(struct workspace *wk, type_tag t); enum complex_type_preset { tc_cx_options_dict_or_list = 1, tc_cx_options_deprecated_kw, tc_cx_enum_machine_system, tc_cx_enum_machine_subsystem, tc_cx_enum_machine_endian, tc_cx_enum_shell, tc_cx_list_of_number, tc_cx_dict_of_str, }; type_tag complex_type_preset_get(struct workspace *wk, enum complex_type_preset t); obj complex_type_enum_get(struct workspace *wk, enum complex_type_preset t); #endif muon-v0.5.0/include/lang/eval.h0000644000175000017500000000256215041716357015322 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_EVAL_H #define MUON_LANG_EVAL_H #include #include "lang/types.h" struct workspace; struct source; enum eval_mode { eval_mode_repl = 1 << 0, eval_mode_first = 1 << 1, eval_mode_return_after_project = 1 << 2, eval_mode_relaxed_parse = 1 << 3, }; enum eval_project_file_flags { eval_project_file_flag_first = 1 << 0, eval_project_file_flag_return_after_project = 1 << 1, eval_project_file_flag_relaxed_parse = 1 << 2, }; bool eval_project(struct workspace *wk, const char *subproject_name, const char *cwd, const char *build_dir, uint32_t *proj_id); bool eval_project_file(struct workspace *wk, const char *path, enum build_language lang, enum eval_project_file_flags flags, obj *res); bool eval(struct workspace *wk, const struct source *src, enum build_language lang, enum eval_mode mode, obj *res); bool eval_str(struct workspace *wk, const char *str, enum eval_mode mode, obj *res); bool eval_str_label(struct workspace *wk, const char *label, const char *str, enum eval_mode mode, obj *res); void repl(struct workspace *wk, bool dbg); const char *determine_project_root(struct workspace *wk, const char *path); const char *determine_build_file(struct workspace *wk, const char *cwd, enum build_language *out_lang, bool quiet); #endif muon-v0.5.0/include/lang/fmt.h0000644000175000017500000000170615041716357015160 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_FMT_H #define MUON_LANG_FMT_H #include #include #include "lang/source.h" enum fmt_indent_style { fmt_indent_style_space, fmt_indent_style_tab, }; enum fmt_end_of_line { fmt_end_of_line_lf, fmt_end_of_line_crlf, fmt_end_of_line_cr, }; struct fmt_opts { bool space_array, kwargs_force_multiline, wide_colon, no_single_comma_function, insert_final_newline, sort_files, group_arg_value, simplify_string_literals, sticky_parens, continuation_indent; uint32_t max_line_len; uint32_t indent_style; // enum fmt_indent_style uint32_t indent_size; uint32_t tab_width; uint32_t end_of_line; // enum fmt_end_of_line const char *indent_before_comments; bool use_editor_config; // ignored for now }; bool fmt(struct source *src, FILE *out, const char *cfg_path, bool check_only, bool editorconfig); #endif muon-v0.5.0/include/lang/object.h0000644000175000017500000004654415041716357015651 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_OBJECT_H #define MUON_LANG_OBJECT_H #include "compat.h" #include #include "compilers.h" #include "datastructures/bucket_arr.h" #include "iterator.h" #include "lang/types.h" #include "machines.h" enum obj_type { /* singleton object types */ obj_null = 0, obj_disabler = 1, obj_meson = 2, obj_bool, // obj_bool_true, obj_bool_false /* simple object types */ obj_file, obj_feature_opt, obj_machine, /* complex object types */ _obj_aos_start, obj_number = _obj_aos_start, obj_string, obj_array, obj_dict, obj_compiler, obj_build_target, obj_custom_target, obj_subproject, obj_dependency, obj_external_program, obj_python_installation, obj_run_result, obj_configuration_data, obj_test, obj_module, obj_install_target, obj_environment, obj_include_directory, obj_option, obj_generator, obj_generated_list, obj_alias_target, obj_both_libs, obj_source_set, obj_source_configuration, obj_iterator, /* muon-specific objects */ obj_func, obj_capture, obj_typeinfo, obj_type_count, }; /* start of object structs */ struct obj_typeinfo { type_tag type, subtype; }; struct obj_func { const char *name, *desc; enum language_mode lang_mode; uint32_t nargs, nkwargs; type_tag return_type; uint32_t entry; struct args_norm an[32]; struct args_kw akw[64]; }; struct obj_capture { struct obj_func *func; obj scope_stack, defargs, self; uint32_t native_func; }; enum tgt_type { tgt_executable = 1 << 0, tgt_static_library = 1 << 1, tgt_dynamic_library = 1 << 2, tgt_shared_module = 1 << 3, }; enum tgt_type_count { tgt_type_count = 4, }; // keep in sync enum feature_opt_state { feature_opt_auto, feature_opt_enabled, feature_opt_disabled, }; #define FOREACH_BUILTIN_MODULE(_) \ _(fs, "public/fs", true) \ _(keyval, "public/keyval", true) \ _(pkgconfig, "public/pkgconfig", true) \ _(python, "public/python", true) \ _(python3, "public/python3", true) \ _(sourceset, "public/sourceset", true) \ _(toolchain, "private/toolchain", true) \ _(subprojects, "private/subprojects", true) \ _(getopt, "private/getopt", true) \ _(curl, "private/curl", true) \ _(json, "private/json", true) \ _(cmake, "public/cmake", false) \ _(dlang, "public/dlang", false) \ _(gnome, "public/gnome", false) \ _(hotdoc, "public/hotdoc", false) \ _(i18n, "public/i18n", false) \ _(java, "public/java", false) \ _(modtest, "public/modtest", false) \ _(qt, "public/qt", false) \ _(qt4, "public/qt4", false) \ _(qt5, "public/qt5", false) \ _(qt6, "public/qt6", false) \ _(unstable_cuda, "public/unstable-cuda", false) \ _(unstable_external_project, "public/unstable-external_project", false) \ _(unstable_icestorm, "public/unstable-icestorm", false) \ _(unstable_rust, "public/unstable-rust", false) \ _(unstable_simd, "public/unstable-simd", false) \ _(unstable_wayland, "public/unstable-wayland", false) \ _(windows, "public/windows", false) #define MODULE_ENUM(mod, path, implemented) module_##mod, enum module { FOREACH_BUILTIN_MODULE(MODULE_ENUM) module_count, }; #undef MODULE_ENUM enum str_flags { str_flag_big = 1 << 0, str_flag_mutable = 1 << 1, }; struct str { const char *s; uint32_t len; enum str_flags flags; }; struct obj_internal { enum obj_type t; uint32_t val; }; struct obj_subproject { uint32_t id; bool found; }; struct obj_module { enum module module; bool found, has_impl; obj exports; }; struct obj_array_elem { uint32_t next; obj val; }; enum obj_array_flags { obj_array_flag_cow = 1 << 3, }; struct obj_array { uint32_t head, tail, len; enum obj_array_flags flags; }; enum obj_dict_flags { obj_dict_flag_big = 1 << 0, obj_dict_flag_int_key = 1 << 1, obj_dict_flag_dont_expand = 1 << 2, obj_dict_flag_cow = 1 << 3, }; struct obj_dict_elem { uint32_t next; obj key, val; }; struct obj_dict { uint32_t data, len; obj tail; enum obj_dict_flags flags; }; enum build_tgt_flags { build_tgt_flag_export_dynamic = 1 << 0, build_tgt_flag_pic = 1 << 1, build_tgt_generated_include = 1 << 2, build_tgt_flag_build_by_default = 1 << 3, build_tgt_flag_visibility = 1 << 4, build_tgt_flag_installed = 1 << 5, build_tgt_flag_pie = 1 << 6, }; enum build_dep_flag { build_dep_flag_recursive = 1 << 0, build_dep_flag_both_libs_static = 1 << 1, build_dep_flag_both_libs_shared = 1 << 2, build_dep_flag_include_system = 1 << 3, build_dep_flag_include_non_system = 1 << 4, build_dep_flag_as_link_whole = 1 << 5, build_dep_flag_partial = 1 << 6, build_dep_flag_part_compile_args = 1 << 7, build_dep_flag_part_includes = 1 << 8, build_dep_flag_part_link_args = 1 << 9, build_dep_flag_part_links = 1 << 10, build_dep_flag_part_sources = 1 << 11, }; struct build_dep { enum compiler_language link_language; obj frameworks; // not in raw obj compile_args; obj include_directories; obj link_args; obj link_whole; obj link_with; obj link_with_not_found; obj objects; obj order_deps; obj rpath; obj sources; struct build_dep_raw { enum build_dep_flag flags; obj compile_args; obj include_directories; obj link_args; obj link_whole; obj link_with; obj link_with_not_found; obj objects; obj order_deps; obj rpath; obj sources; obj deps; } raw; }; struct obj_build_target { obj name; // obj_string obj build_name; // obj_string obj build_path; // obj_string obj private_path; // obj_string obj cwd; // obj_string obj build_dir; // obj_string obj soname; // obj_string obj implib; // obj_string obj src; // obj_array obj objects; // obj_array obj args; // obj_dict obj processed_args_pch; // obj_dict obj processed_args; // obj_dict obj link_depends; // obj_array obj generated_pc; // obj_string obj override_options; // obj_array obj required_compilers; // obj_dict obj extra_files; // obj_array obj pch; // obj_dict obj callstack; // obj_array struct build_dep dep; struct build_dep dep_internal; enum compiler_visibility_type visibility; enum build_tgt_flags flags; enum tgt_type type; enum machine_kind machine; }; enum default_both_libraries { default_both_libraries_auto, default_both_libraries_static, default_both_libraries_shared, }; struct obj_both_libs { enum default_both_libraries default_both_libraries; obj static_lib; // obj_build_target obj dynamic_lib; // obj_build_target }; enum custom_target_flags { custom_target_capture = 1 << 0, custom_target_build_always_stale = 1 << 1, custom_target_build_by_default = 1 << 2, custom_target_feed = 1 << 3, custom_target_console = 1 << 4, }; struct obj_custom_target { obj name; // obj_string obj args; // obj_array obj input; // obj_array obj output; // obj_array obj depends; // obj_array obj private_path; // obj_string obj env; // str | list[str] | dict[str] | env obj depfile; // str obj callstack; // obj_array enum custom_target_flags flags; }; struct obj_alias_target { obj name; // obj_string obj depends; // obj_array }; enum dependency_type { dependency_type_declared, dependency_type_pkgconf, dependency_type_threads, dependency_type_external_library, dependency_type_system, dependency_type_not_found, }; enum dependency_public_type { dependency_public_type_unset, dependency_public_type_internal, dependency_public_type_pkgconfig, dependency_public_type_system, dependency_public_type_library, dependency_public_type_not_found, }; enum dep_flags { dep_flag_found = 1 << 0, }; enum include_type { include_type_preserve, include_type_system, include_type_non_system, }; struct obj_dependency { obj name; // obj_string obj version; // obj_string obj variables; // obj_dict struct build_dep dep; enum dep_flags flags; enum dependency_type type; enum dependency_public_type public_type; enum include_type include_type; enum machine_kind machine; }; struct obj_external_program { bool found, guessed_ver; obj cmd_array; obj ver; }; struct obj_python_installation { obj prog; bool pure; obj language_version; obj sysconfig_paths; obj sysconfig_vars; obj install_paths; }; enum run_result_flags { run_result_flag_from_compile = 1 << 0, run_result_flag_compile_ok = 1 << 1, }; struct obj_run_result { obj out; obj err; int32_t status; enum run_result_flags flags; }; struct obj_configuration_data { obj dict; // obj_dict }; enum test_category { test_category_test, test_category_benchmark, }; enum test_protocol { test_protocol_exitcode, test_protocol_tap, test_protocol_gtest, test_protocol_rust, }; struct obj_test { obj name; // obj_string obj exe; // obj_string obj args; // obj_array obj env; // obj_array obj suites; // obj_array obj workdir; // obj_string obj depends; // obj_array of obj_string obj timeout; // obj_number obj priority; // obj_number bool should_fail, is_parallel, verbose; enum test_category category; enum test_protocol protocol; }; struct obj_compiler { obj cmd_arr[toolchain_component_count]; obj overrides[toolchain_component_count]; uint32_t type[toolchain_component_count]; obj ver; obj libdirs; obj fwdirs; enum compiler_language lang; enum machine_kind machine; }; enum install_target_type { install_target_default, install_target_subdir, install_target_symlink, install_target_emptydir, }; struct obj_install_target { obj src; obj dest; bool has_perm; uint32_t perm; obj exclude_directories; // obj_array of obj_string obj exclude_files; // obj_array of obj_string enum install_target_type type; bool build_target; }; struct obj_environment { obj actions; // array }; struct obj_include_directory { obj path; bool is_system; bool is_idirafter; }; enum build_option_type { op_string, op_boolean, op_combo, op_integer, op_array, op_feature, op_shell_array, build_option_type_count, }; enum build_option_kind { build_option_kind_default, build_option_kind_prefixed_dir, }; enum option_value_source { option_value_source_unset, option_value_source_default, option_value_source_environment, option_value_source_yield, option_value_source_default_options, option_value_source_subproject_default_options, option_value_source_override_options, option_value_source_deprecated_rename, option_value_source_commandline, }; struct obj_option { obj name; obj val; obj choices; obj max; obj min; obj deprecated; obj description; uint32_t ip; enum option_value_source source; enum build_option_type type; enum build_option_kind kind; bool yield, builtin; }; struct obj_generator { obj output; obj raw_command; obj depfile; obj depends; bool capture; bool feed; }; struct obj_generated_list { obj generator; // obj_generator obj input; // obj_array of obj_file obj extra_arguments; // obj_array of obj_string obj preserve_path_from; // obj_string obj env; }; struct obj_source_set { obj rules; bool frozen; }; struct obj_source_configuration { obj sources, dependencies; }; enum obj_iterator_type { obj_iterator_type_array, obj_iterator_type_dict_small, obj_iterator_type_dict_big, obj_iterator_type_range, obj_iterator_type_typeinfo, }; struct range_params { uint32_t start, stop, step, i; }; struct obj_iterator { enum obj_iterator_type type; union { struct obj_array_elem *array; struct obj_dict_elem *dict_small; struct { struct hash *h; uint32_t i; } dict_big; struct range_params range; struct { enum obj_type type; uint32_t i; } typeinfo; } data; }; /* end of object structs */ struct obj_clear_mark { uint32_t obji; struct bucket_arr_save objs, chrs; struct bucket_arr_save obj_aos[obj_type_count - _obj_aos_start]; }; obj make_obj(struct workspace *wk, enum obj_type type); enum obj_type get_obj_type(struct workspace *wk, obj id); void make_default_objects(struct workspace *wk); void obj_set_clear_mark(struct workspace *wk, struct obj_clear_mark *mk); void obj_clear(struct workspace *wk, const struct obj_clear_mark *mk); bool get_obj_bool(struct workspace *wk, obj o); obj make_obj_bool(struct workspace *wk, bool v); obj get_obj_bool_with_default(struct workspace *wk, obj o, bool def); obj make_number(struct workspace *wk, int64_t n); int64_t get_obj_number(struct workspace *wk, obj o); void set_obj_number(struct workspace *wk, obj o, int64_t v); obj *get_obj_file(struct workspace *wk, obj o); const char *get_file_path(struct workspace *wk, obj o); const struct str *get_str(struct workspace *wk, obj s); enum feature_opt_state get_obj_feature_opt(struct workspace *wk, obj fo); void set_obj_feature_opt(struct workspace *wk, obj fo, enum feature_opt_state state); enum machine_kind get_obj_machine(struct workspace *wk, obj o); void set_obj_machine(struct workspace *wk, obj o, enum machine_kind kind); #define OBJ_GETTER(type) struct type *get_##type(struct workspace *wk, obj o) OBJ_GETTER(obj_array); OBJ_GETTER(obj_dict); OBJ_GETTER(obj_compiler); OBJ_GETTER(obj_build_target); OBJ_GETTER(obj_custom_target); OBJ_GETTER(obj_subproject); OBJ_GETTER(obj_dependency); OBJ_GETTER(obj_external_program); OBJ_GETTER(obj_python_installation); OBJ_GETTER(obj_run_result); OBJ_GETTER(obj_configuration_data); OBJ_GETTER(obj_test); OBJ_GETTER(obj_module); OBJ_GETTER(obj_install_target); OBJ_GETTER(obj_environment); OBJ_GETTER(obj_include_directory); OBJ_GETTER(obj_option); OBJ_GETTER(obj_generator); OBJ_GETTER(obj_generated_list); OBJ_GETTER(obj_alias_target); OBJ_GETTER(obj_both_libs); OBJ_GETTER(obj_typeinfo); OBJ_GETTER(obj_func); OBJ_GETTER(obj_capture); OBJ_GETTER(obj_source_set); OBJ_GETTER(obj_source_configuration); OBJ_GETTER(obj_iterator); #undef OBJ_GETTER struct tstr; const char *obj_type_to_s(enum obj_type t); bool s_to_type_tag(const char *s, type_tag *t); void obj_to_s(struct workspace *wk, obj o, struct tstr *sb); bool obj_to_json(struct workspace *wk, obj o, struct tstr *sb); bool obj_equal(struct workspace *wk, obj left, obj right); bool obj_clone(struct workspace *wk_src, struct workspace *wk_dest, obj val, obj *ret); #define LO(...) \ { \ log_print(false, log_debug, "%s", ""); \ obj_lprintf(wk, log_debug, __VA_ARGS__); \ } #define LOBJ(object_id) LO("%s: %o\n", #object_id, object_id) bool obj_lprintf(struct workspace *wk, enum log_level lvl, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); bool obj_fprintf(struct workspace *wk, FILE *f, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); bool obj_printf(struct workspace *wk, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 2, 3); bool obj_vasprintf(struct workspace *wk, struct tstr *sb, const char *fmt, va_list ap); uint32_t obj_asprintf(struct workspace *wk, struct tstr *buf, const char *fmt, ...); uint32_t obj_vsnprintf(struct workspace *wk, char *buf, uint32_t len, const char *fmt, va_list ap); uint32_t obj_snprintf(struct workspace *wk, char *buf, uint32_t len, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 4, 5); void obj_inspect(struct workspace *wk, obj val); typedef enum iteration_result (*obj_array_iterator)(struct workspace *wk, void *ctx, obj val); void obj_array_push(struct workspace *wk, obj arr, obj child); void obj_array_prepend(struct workspace *wk, obj *arr, obj val); bool obj_array_foreach(struct workspace *wk, obj arr, void *ctx, obj_array_iterator cb); bool obj_array_foreach_flat(struct workspace *wk, obj arr, void *usr_ctx, obj_array_iterator cb); bool obj_array_in(struct workspace *wk, obj arr, obj val); bool obj_array_index_of(struct workspace *wk, obj arr, obj val, uint32_t *idx); obj *obj_array_index_pointer(struct workspace *wk, obj arr, int64_t i); obj obj_array_index(struct workspace *wk, obj arr, int64_t i); void obj_array_extend(struct workspace *wk, obj arr, obj arr2); void obj_array_extend_nodup(struct workspace *wk, obj arr, obj arr2); void obj_array_dup(struct workspace *wk, obj arr, obj *res); obj obj_array_dup_light(struct workspace *wk, obj src); bool obj_array_join(struct workspace *wk, bool flat, obj arr, obj join, obj *res); void obj_array_tail(struct workspace *wk, obj arr, obj *res); void obj_array_set(struct workspace *wk, obj arr, int64_t i, obj v); void obj_array_del(struct workspace *wk, obj arr, int64_t i); void obj_array_dedup(struct workspace *wk, obj arr, obj *res); void obj_array_dedup_in_place(struct workspace *wk, obj *arr); bool obj_array_flatten_one(struct workspace *wk, obj val, obj *res); typedef int32_t (*obj_array_sort_func)(struct workspace *wk, void *_ctx, obj a, obj b); int32_t obj_array_sort_by_str(struct workspace *wk, void *_ctx, obj a, obj b); void obj_array_sort(struct workspace *wk, void *usr_ctx, obj arr, obj_array_sort_func func, obj *res); obj obj_array_slice(struct workspace *wk, obj arr, int64_t i0, int64_t i1); obj obj_array_get_tail(struct workspace *wk, obj arr); obj obj_array_get_head(struct workspace *wk, obj arr); obj obj_array_pop(struct workspace *wk, obj arr); void obj_array_clear(struct workspace *wk, obj arr); typedef enum iteration_result (*obj_dict_iterator)(struct workspace *wk, void *ctx, obj key, obj val); bool obj_dict_foreach(struct workspace *wk, obj dict, void *ctx, obj_dict_iterator cb); bool obj_dict_in(struct workspace *wk, obj dict, obj key); bool obj_dict_index(struct workspace *wk, obj dict, obj key, obj *res); bool obj_dict_index_strn(struct workspace *wk, obj dict, const char *str, uint32_t len, obj *res); obj *obj_dict_index_strn_pointer(struct workspace *wk, obj dict, const char *str, uint32_t len); bool obj_dict_index_str(struct workspace *wk, obj dict, const char *str, obj *res); void obj_dict_set(struct workspace *wk, obj dict, obj key, obj val); void obj_dict_dup(struct workspace *wk, obj dict, obj *res); void obj_dict_dup_light(struct workspace *wk, obj dict, obj *res); void obj_dict_merge(struct workspace *wk, obj dict, obj dict2, obj *res); void obj_dict_merge_nodup(struct workspace *wk, obj dict, obj dict2); void obj_dict_seti(struct workspace *wk, obj dict, uint32_t key, obj val); bool obj_dict_geti(struct workspace *wk, obj dict, uint32_t key, obj *val); void obj_dict_del(struct workspace *wk, obj dict, obj key); void obj_dict_del_str(struct workspace *wk, obj dict, const char *str); void obj_dict_del_strn(struct workspace *wk, obj dict, const char *str, uint32_t len); const struct str *obj_dict_index_as_str(struct workspace *wk, obj dict, const char *s); bool obj_dict_index_as_bool(struct workspace *wk, obj dict, const char *s); int64_t obj_dict_index_as_number(struct workspace *wk, obj dict, const char *s); obj obj_dict_index_as_obj(struct workspace *wk, obj dict, const char *s); bool obj_iterable_foreach(struct workspace *wk, obj dict_or_array, void *ctx, obj_dict_iterator cb); #endif muon-v0.5.0/include/lang/object_iterators.h0000644000175000017500000001333715041716357017737 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef LANG_OBJECT_ITERATORS_H #define LANG_OBJECT_ITERATORS_H #include #include "lang/types.h" #include "preprocessor_helpers.h" /****************************************************************************** * obj_array_for ******************************************************************************/ struct obj_array_for_helper { const struct obj_array *a; struct obj_array_elem *e; uint32_t i, len; }; #define obj_array_for_array_(__wk, __arr, __val, __iter) \ struct obj_array_for_helper __iter = { \ .a = __arr, \ }; \ __iter.len = __iter.a->len; \ for (__iter.e = __iter.a->len ? (struct obj_array_elem *)bucket_arr_get(&(__wk)->vm.objects.array_elems, __iter.a->head) : 0,\ __val = __iter.e ? __iter.e->val : 0; __iter.i < __iter.len; \ __iter.e = __iter.e->next ? (struct obj_array_elem *)bucket_arr_get(&(__wk)->vm.objects.array_elems, __iter.e->next) : 0, \ __val = __iter.e ? __iter.e->val : 0, \ ++__iter.i) #define obj_array_for_array(__wk, __arr, __val) \ obj_array_for_array_((__wk), __arr, __val, CONCAT(__iter, __LINE__)) #define obj_array_for_(__wk, __arr, __val, __iter) obj_array_for_array_(__wk, get_obj_array(__wk, __arr), __val, __iter) #define obj_array_for(__wk, __arr, __val) obj_array_for_(__wk, __arr, __val, CONCAT(__iter, __LINE__)) /****************************************************************************** * obj_dict_for ******************************************************************************/ struct obj_dict_for_helper { struct obj_dict *d; struct hash *h; struct obj_dict_elem *e; void *k; union obj_dict_big_dict_value v; uint32_t i; bool big; }; #define obj_dict_for_get_kv_big(__iter, __key, __val) \ __iter.k = arr_get(&__iter.h->keys, __iter.i), __iter.v.u64 = *hash_get(__iter.h, __iter.k), \ __key = __iter.v.val.key, __val = __iter.v.val.val #define obj_dict_for_get_kv(__iter, __key, __val) __key = __iter.e->key, __val = __iter.e->val #define obj_dict_for_dict_(__wk, __dict, __key, __val, __iter) \ struct obj_dict_for_helper __iter = { \ .d = __dict, \ }; \ for (__key = 0, \ __val = 0, \ __iter.big = __iter.d->flags & obj_dict_flag_big, \ __iter.h = __iter.big ? (struct hash *)bucket_arr_get(&__wk->vm.objects.dict_hashes, __iter.d->data) : 0, \ __iter.e = __iter.big ? 0 : \ __iter.d->len ? (struct obj_dict_elem *)bucket_arr_get( \ &__wk->vm.objects.dict_elems, __iter.d->data) : \ 0, \ __iter.big ? (__iter.i < __iter.h->keys.len ? (obj_dict_for_get_kv_big(__iter, __key, __val)) : 0) : \ (__iter.e ? (obj_dict_for_get_kv(__iter, __key, __val)) : 0); \ __iter.big ? __iter.i < __iter.h->keys.len : !!__iter.e; \ __iter.big ? (++__iter.i, \ (__iter.i < __iter.h->keys.len ? (obj_dict_for_get_kv_big(__iter, __key, __val)) : 0)) : \ (__iter.e = __iter.e->next ? (struct obj_dict_elem *)bucket_arr_get( \ &__wk->vm.objects.dict_elems, __iter.e->next) : \ 0, \ (__iter.e ? (obj_dict_for_get_kv(__iter, __key, __val)) : 0))) #define obj_dict_for_dict(__wk, __dict, __key, __val) \ obj_dict_for_dict_((__wk), __dict, __key, __val, CONCAT(__iter, __LINE__)) #define obj_dict_for_(__wk, __dict, __key, __val, __iter) \ obj_dict_for_dict_((__wk), get_obj_dict(__wk, __dict), __key, __val, __iter) #define obj_dict_for(__wk, __dict, __key, __val) obj_dict_for_((__wk), __dict, __key, __val, CONCAT(__iter, __LINE__)) /****************************************************************************** * obj_array_flat_for ******************************************************************************/ struct obj_array_flat_iter_ctx { struct obj_array_elem *e; uint32_t pushed; bool init; }; #define obj_array_flat_for_(__wk, __arr, __val, __iter) \ struct obj_array_flat_iter_ctx __iter = { 0 }; \ for (__val = obj_array_flat_iter_next(__wk, __arr, &__iter); __val; \ __val = obj_array_flat_iter_next(__wk, __arr, &__iter)) #define obj_array_flat_for(__wk, __arr, __val) obj_array_flat_for_(__wk, __arr, __val, CONCAT(__iter, __LINE__)) struct workspace; obj obj_array_flat_iter_next(struct workspace *wk, obj arr, struct obj_array_flat_iter_ctx *ctx); void obj_array_flat_iter_end(struct workspace *wk, struct obj_array_flat_iter_ctx *ctx); #endif muon-v0.5.0/include/wrap.h0000644000175000017500000000565715041716357014433 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_WRAP_H #define MUON_WRAP_H #include "compat.h" #include #include #include "buf_size.h" #include "lang/string.h" #include "platform/filesystem.h" #include "platform/run_cmd.h" #include "platform/timer.h" struct workspace; enum wrap_fields { // wrap wf_directory, wf_patch_url, wf_patch_fallback_url, wf_patch_filename, wf_patch_hash, wf_patch_directory, wf_diff_files, wf_method, // wrap-file wf_source_url, wf_source_fallback_url, wf_source_filename, wf_source_hash, wf_lead_directory_missing, // wrap-git wf_url, wf_revision, wf_depth, wf_push_url, wf_clone_recursive, wf_wrapdb_version, // ?? undocumented wrap_fields_count, }; enum wrap_type { wrap_type_file, wrap_type_git, wrap_provide, wrap_type_count, }; struct wrap { struct source src; enum wrap_type type; bool has_provides; const char *fields[wrap_fields_count]; char *buf; char dest_dir_buf[BUF_SIZE_1k], name_buf[BUF_SIZE_1k]; struct tstr dest_dir, name; bool dirty, outdated, updated; }; enum wrap_provides_key { wrap_provides_key_override_dependencies, wrap_provides_key_override_executables, wrap_provides_key_dependency_variables, }; enum wrap_handle_mode { wrap_handle_mode_default, wrap_handle_mode_check_dirty, wrap_handle_mode_update, }; struct wrap_opts { const char *subprojects; bool allow_download, force_update, fail_if_update_skipped; enum wrap_handle_mode mode; bool block; }; enum wrap_handle_sub_state { wrap_handle_sub_state_pending, wrap_handle_sub_state_running, wrap_handle_sub_state_running_cmd, wrap_handle_sub_state_fetching, wrap_handle_sub_state_extracting, wrap_handle_sub_state_complete, wrap_handle_sub_state_collected, }; struct wrap_handle_ctx { struct wrap_opts opts; struct wrap wrap; struct timer duration; uint32_t prev_state, state; enum wrap_handle_sub_state sub_state; const char *path; struct { int64_t depth; char depth_str[16]; } git; struct { int32_t handle; uint8_t *buf; uint64_t len; int64_t downloaded, total; const char *hash, *dest_dir, *filename; } fetch_ctx; struct run_cmd_ctx cmd_ctx; struct { char cmdstr[1024]; // For error reporting struct tstr *out; bool allow_failure; } run_cmd_opts; char tstr_buf[2][1024]; struct tstr bufs[2]; bool ok; }; void wrap_destroy(struct wrap *wrap); bool wrap_parse(struct workspace *wk, const char *subprojects, const char *wrap_file, struct wrap *wrap); bool wrap_handle(struct workspace *wk, const char *wrap_file, struct wrap_handle_ctx *ctx); void wrap_handle_async_start(struct workspace *wk); void wrap_handle_async_end(struct workspace *wk); bool wrap_handle_async(struct workspace *wk, const char *wrap_file, struct wrap_handle_ctx *ctx); bool wrap_load_all_provides(struct workspace *wk, const char *subprojects); const char *wrap_handle_state_to_s(uint32_t state); #endif muon-v0.5.0/include/cmd_test.h0000644000175000017500000000153615041716357015254 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_CMD_TEST_H #define MUON_CMD_TEST_H #include #include #include "lang/object.h" enum test_flag { test_flag_should_fail = 1 << 0, }; #define MAX_CMDLINE_TEST_SUITES 64 enum test_display { test_display_auto, test_display_dots, test_display_bar, }; enum test_output { test_output_term, test_output_html, test_output_json, }; struct test_options { const char *suites[MAX_CMDLINE_TEST_SUITES]; char *const *tests; const char *setup; uint32_t suites_len, tests_len, jobs, verbosity; enum test_display display; enum test_output output; bool fail_fast, print_summary, no_rebuild, list, include_subprojects; enum test_category cat; }; bool tests_run(struct test_options *opts, const char *argv0); #endif muon-v0.5.0/include/guess.h0000644000175000017500000000040115041716357014566 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_GUESS_H #define MUON_GUESS_H #include "lang/workspace.h" bool guess_version(struct workspace *wk, const char *src, obj *res); #endif muon-v0.5.0/include/datastructures/0002755000175000017500000000000015041716357016353 5ustar buildbuildmuon-v0.5.0/include/datastructures/arr.h0000644000175000017500000000216215041716357017307 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_DATA_ARR_H #define MUON_DATA_ARR_H #include #include enum arr_flag { arr_flag_zero_memory = 1 << 0, }; struct arr { uint32_t len; uint32_t cap; uint32_t item_size; uint32_t flags; uint8_t *e; }; void arr_init_flags(struct arr *arr, uint32_t initial, uint32_t item_size, uint32_t flags); void arr_init(struct arr *arr, uint32_t initial, uint32_t item_size); void arr_destroy(struct arr *arr); uint32_t arr_push(struct arr *arr, const void *item); void *arr_pop(struct arr *arr); void *arr_peek(struct arr *arr, uint32_t i); void *arr_get(const struct arr *arr, uint32_t i); void arr_del(struct arr *arr, uint32_t i); void arr_clear(struct arr *arr); void arr_grow_by(struct arr *arr, uint32_t size); void arr_grow_to(struct arr *arr, uint32_t size); typedef int32_t (*sort_func)(const void *a, const void *b, void *ctx); void arr_sort_range(struct arr *arr, uint32_t start, uint32_t end, void *ctx, sort_func func); void arr_sort(struct arr *arr, void *ctx, sort_func func); #endif muon-v0.5.0/include/datastructures/hash.h0000644000175000017500000000226215041716357017447 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_DATA_HASH_H #define MUON_DATA_HASH_H #include "datastructures/arr.h" #include "iterator.h" struct hash; typedef bool((*hash_keycmp)(const struct hash *h, const void *a, const void *b)); typedef uint64_t((*hash_fn)(const struct hash *h, const void *k)); struct hash { struct arr meta, e, keys; uint32_t cap, len, load, max_load, capm; hash_keycmp keycmp; hash_fn hash_func; }; typedef enum iteration_result((*hash_with_keys_iterator_func)(void *ctx, const void *key, uint64_t val)); void hash_init(struct hash *h, uint32_t cap, uint32_t keysize); void hash_init_str(struct hash *h, uint32_t cap); void hash_destroy(struct hash *h); uint64_t *hash_get(const struct hash *h, const void *key); uint64_t *hash_get_strn(const struct hash *h, const char *str, uint64_t len); void hash_set(struct hash *h, const void *key, uint64_t val); void hash_set_strn(struct hash *h, const char *key, uint64_t len, uint64_t val); void hash_unset(struct hash *h, const void *key); void hash_unset_strn(struct hash *h, const char *s, uint64_t len); void hash_clear(struct hash *h); #endif muon-v0.5.0/include/datastructures/bucket_arr.h0000644000175000017500000000230515041716357020643 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_DATA_BUCKET_ARR_H #define MUON_DATA_BUCKET_ARR_H #include "datastructures/arr.h" struct bucket_arr_save { uint32_t tail_bucket, tail_bucket_len; }; struct bucket_arr { struct arr buckets; uint32_t item_size; uint32_t bucket_size; uint32_t len, tail_bucket; }; struct bucket { uint8_t *mem; uint32_t len; }; void init_bucket(struct bucket_arr *ba, struct bucket *b); uint64_t bucket_arr_size(struct bucket_arr *ba); void bucket_arr_init(struct bucket_arr *ba, uint32_t bucket_size, uint32_t item_size); void *bucket_arr_push(struct bucket_arr *ba, const void *item); void *bucket_arr_pushn(struct bucket_arr *ba, const void *data, uint32_t data_len, uint32_t reserve); void *bucket_arr_get(const struct bucket_arr *ba, uint32_t i); void bucket_arr_clear(struct bucket_arr *ba); void bucket_arr_save(const struct bucket_arr *ba, struct bucket_arr_save *save); void bucket_arr_restore(struct bucket_arr *ba, const struct bucket_arr_save *save); void bucket_arr_destroy(struct bucket_arr *ba); bool bucket_arr_lookup_pointer(struct bucket_arr *ba, const uint8_t *p, uint64_t *ret); #endif muon-v0.5.0/include/datastructures/stack.h0000644000175000017500000000221215041716357017624 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_DATASTRUCTURES_STACK_H #define MUON_DATASTRUCTURES_STACK_H #include #include #include "preprocessor_helpers.h" struct stack_tag; typedef void (*stack_print_cb)(void *ctx, void *mem, struct stack_tag *tag); struct stack { char *mem; uint32_t len, cap; }; void stack_init(struct stack *stack, uint32_t cap); void stack_destroy(struct stack *stack); void stack_print(struct stack *_stack); void stack_push_sized(struct stack *stack, const void *mem, uint32_t size, const char *name); void stack_pop_sized(struct stack *stack, void *mem, uint32_t size); void stack_peek_sized(struct stack *stack, void *mem, uint32_t size); #define stack_push(__stack, __it, __nv) \ stack_push_sized((__stack), &(__it), (sizeof(__it)), __FILE__ ":" LINE_STRING " " #__it); \ __it = __nv #define stack_pop(__stack, __it) stack_pop_sized((__stack), &(__it), (sizeof(__it))) #define stack_peek(__stack, __it) stack_peek_sized((__stack), &(__it), (sizeof(__it))) #endif muon-v0.5.0/include/coerce.h0000644000175000017500000000322715041716357014711 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_COERCE_H #define MUON_COERCE_H #include "lang/workspace.h" enum requirement_type { requirement_skip, requirement_required, requirement_auto, }; bool coerce_environment_from_kwarg(struct workspace *wk, struct args_kw *kw, bool set_subdir, obj *res); bool coerce_key_value_dict(struct workspace *wk, uint32_t err_node, obj val, obj *res); bool coerce_include_type(struct workspace *wk, const struct str *str, uint32_t err_node, enum include_type *res); bool coerce_string_to_file(struct workspace *wk, const char *dir, obj string, obj *res); bool coerce_string(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_string_array(struct workspace *wk, uint32_t node, obj arr, obj *res); bool coerce_num_to_string(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_executable(struct workspace *wk, uint32_t node, obj val, obj *res, obj *args); bool coerce_requirement(struct workspace *wk, struct args_kw *kw_required, enum requirement_type *requirement); bool coerce_files(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_file(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_dirs(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_output_files(struct workspace *wk, uint32_t node, obj val, const char *output_dir, obj *res); bool coerce_include_dirs(struct workspace *wk, uint32_t node, obj val, bool is_system, obj *res); enum machine_kind coerce_machine_kind(struct workspace *wk, struct args_kw *native_kw); bool coerce_truthiness(struct workspace *wk, obj o); #endif muon-v0.5.0/include/version.h0000644000175000017500000000055715041716357015141 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Eli Schwartz * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_VERSION_H #define MUON_VERSION_H struct muon_version { const char *const version, *const vcs_tag, *const meson_compat; }; extern const struct muon_version muon_version; #endif muon-v0.5.0/include/formats/0002755000175000017500000000000015041716357014751 5ustar buildbuildmuon-v0.5.0/include/formats/xml.h0000644000175000017500000000214015041716357015715 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_XML_H #define MUON_FORMATS_XML_H #include #include "lang/types.h" #include "datastructures/bucket_arr.h" enum xml_writer_style { xml_writer_style_space_around_attributes = 1 << 0, xml_writer_style_single_line_attributes = 1 << 1, xml_writer_style_single_line_element = 1 << 2, }; struct xml_writer { struct workspace *wk; struct bucket_arr nodes; enum xml_writer_style _style; uint32_t indent; }; void xml_writer_init(struct workspace *wk, struct xml_writer *w); void xml_writer_destroy(struct xml_writer *w); obj xml_node_new(struct xml_writer *w, const char *name); obj xml_node_new_styled(struct xml_writer *w, const char *name, enum xml_writer_style style, const char *element); void xml_node_push_attr(struct xml_writer *w, obj idx, const char *key, obj v); void xml_node_push_child(struct xml_writer *w, obj idx, obj child); void xml_write(struct xml_writer *w, obj root, FILE *out); #endif muon-v0.5.0/include/formats/lines.h0000644000175000017500000000112715041716357016233 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_LINES_H #define MUON_FORMATS_LINES_H #include #include #include "iterator.h" typedef enum iteration_result((*each_line_callback)(void *ctx, char *line, size_t len)); typedef enum iteration_result((*each_line_const_callback)(void *ctx, const char *line, size_t len)); void each_line(char *buf, uint64_t len, void *ctx, each_line_callback cb); void each_line_const(const char *buf, uint64_t len, void *ctx, each_line_const_callback cb); #endif muon-v0.5.0/include/formats/json.h0000644000175000017500000000056015041716357016072 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Seedo Paul * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_JSON_H #define MUON_FORMATS_JSON_H #include #include "lang/workspace.h" bool muon_json_to_obj(struct workspace *wk, const struct str *js, obj *res); #endif muon-v0.5.0/include/formats/editorconfig.h0000644000175000017500000000057515041716357017603 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_EDITORCONFIG_H #define MUON_FORMATS_EDITORCONFIG_H #include "lang/source.h" struct fmt_opts; bool editorconfig_pattern_match(const char *pattern, const char *string); void try_parse_editorconfig(struct source *src, struct fmt_opts *opts); #endif muon-v0.5.0/include/formats/ini.h0000644000175000017500000000122415041716357015676 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_INI_H #define MUON_FORMATS_INI_H #include #include #include "lang/source.h" typedef bool((*inihcb)(void *ctx, struct source *src, const char *sect, const char *k, const char *v, struct source_location location)); bool ini_parse(const char *path, struct source *src, char **buf, inihcb cb, void *octx); bool ini_reparse(const char *path, const struct source *src, char *buf, inihcb cb, void *octx); bool keyval_parse(const char *path, struct source *src, char **buf, inihcb cb, void *octx); #endif muon-v0.5.0/include/formats/tap.h0000644000175000017500000000060715041716357015707 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_TAP_H #define MUON_FORMATS_TAP_H #include #include struct tap_parse_result { uint32_t total, pass, fail, skip; bool have_plan; bool all_ok; }; void tap_parse(const char *buf, uint64_t buf_len, struct tap_parse_result *res); #endif muon-v0.5.0/include/options.h0000644000175000017500000000461615041716357015147 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_OPTIONS_H #define MUON_OPTIONS_H #include "lang/workspace.h" extern bool initializing_builtin_options; extern const char *build_option_type_to_s[build_option_type_count]; extern const char *toolchain_component_option_name[compiler_language_count][toolchain_component_count]; struct option_override { // strings obj proj, name, val; enum option_value_source source; bool obj_value; }; bool create_option(struct workspace *wk, obj opts, obj opt, obj val); bool set_option(struct workspace *wk, obj opt, obj new_val, enum option_value_source source, bool coerce); bool get_option(struct workspace *wk, const struct project *proj, const struct str *name, obj *res); bool get_option_overridable(struct workspace *wk, const struct project *proj, obj overrides, const struct str *name, obj *res); void get_option_value(struct workspace *wk, const struct project *proj, const char *name, obj *res); void get_option_value_overridable(struct workspace *wk, const struct project *proj, obj overrides, const char *name, obj *res); bool check_invalid_option_overrides(struct workspace *wk); bool check_invalid_subproject_option(struct workspace *wk); bool prefix_dir_opts(struct workspace *wk); bool setup_project_options(struct workspace *wk, const char *cwd); bool init_global_options(struct workspace *wk); bool parse_and_set_cmdline_option(struct workspace *wk, char *lhs); bool parse_and_set_default_options(struct workspace *wk, uint32_t err_node, obj arr, obj project_name, bool for_subproject); bool parse_and_set_override_options(struct workspace *wk, uint32_t err_node, obj arr, obj *res); enum wrap_mode { wrap_mode_nopromote, wrap_mode_nodownload, wrap_mode_nofallback, wrap_mode_forcefallback, }; enum wrap_mode get_option_wrap_mode(struct workspace *wk); enum tgt_type get_option_default_library(struct workspace *wk); enum default_both_libraries get_option_default_both_libraries(struct workspace *wk, const struct project *proj, obj overrides); bool get_option_bool(struct workspace *wk, obj overrides, const char *name, bool fallback); enum backend { backend_ninja, backend_xcode, }; enum backend get_option_backend(struct workspace *wk); struct list_options_opts { bool list_all, only_modified; bool list_subprojects; }; bool list_options(const struct list_options_opts *list_opts); #endif muon-v0.5.0/include/machine_file.h0000644000175000017500000000042115041716357016045 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_MACHINE_FILE_H #define MUON_MACHINE_FILE_H #include "lang/workspace.h" bool machine_file_parse(struct workspace *dest_wk, const char *path); #endif muon-v0.5.0/include/machines.h0000644000175000017500000000472315041716357015242 0ustar buildbuild#ifndef MUON_MACHINE_H /* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #define MUON_MACHINE_H #include #include #include "platform/uname.h" /* * Work around non-standard gcc behavior. * * When gcc is invoked without a -std flag, it defines several nonstandard * macros, linux being one of them, which messes up our macro expansion. * The proper fix for this is to always pass -std=c99, but if we fix it here * then we can preserve the ability to compile the amalgam using gcc without * any additional flags. * * Reference: * - https://gcc.gnu.org/onlinedocs/gcc-5.3.0/cpp/System-specific-Predefined-Macros.html * - https://stackoverflow.com/a/19214007 */ #ifdef linux #undef linux #endif #define FOREACH_MACHINE_SYSTEM(_) \ _(unknown) \ _(dragonfly) \ _(freebsd) \ _(gnu) \ _(haiku) \ _(linux) \ _(netbsd) \ _(openbsd) \ _(sunos) \ _(android) \ _(emscripten) \ _(windows) \ _(cygwin) \ _(msys2) \ _(darwin) \ enum machine_system { machine_system_uninitialized = 0, #define MACHINE_ENUM(id) machine_system_##id, FOREACH_MACHINE_SYSTEM(MACHINE_ENUM) #undef MACHINE_ENUM }; #define FOREACH_MACHINE_SUBSYSTEM(_) \ FOREACH_MACHINE_SYSTEM(_) \ _(macos) \ _(ios) \ _(tvos) \ _(visionos) \ enum machine_subsystem { machine_subsystem_uninitialized = 0, #define MACHINE_ENUM(id) machine_subsystem_##id, FOREACH_MACHINE_SUBSYSTEM(MACHINE_ENUM) #undef MACHINE_ENUM }; enum machine_kind { machine_kind_build, machine_kind_host, machine_kind_either, }; #define machine_kind_count 2 // Keep in sync with above struct machine_definition { enum machine_kind kind; enum machine_system sys; enum machine_subsystem subsystem; enum endianness endianness; uint32_t address_bits; char cpu[128]; char cpu_family[128]; bool is_windows; }; extern struct machine_definition build_machine, host_machine; extern const struct machine_definition *machine_definitions[machine_kind_count]; const char *machine_kind_to_s(enum machine_kind kind); const char *machine_system_to_s(enum machine_system sys); const char *machine_subsystem_to_s(enum machine_subsystem sys); const char *machine_system_to_kernel_name(enum machine_system sys); void machine_parse_and_apply_triplet(struct machine_definition *m, const char *s); void machine_init(void); bool machine_matches(enum machine_kind a, enum machine_kind b); bool machine_definitions_eql(struct machine_definition *a, struct machine_definition *b); #endif muon-v0.5.0/include/log.h0000644000175000017500000000576615041716357014244 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LOG_H #define MUON_LOG_H #include "compat.h" #include #include #include #include #include "lang/types.h" #include "preprocessor_helpers.h" extern const char *log_level_clr[log_level_count]; extern const char *log_level_name[log_level_count]; extern const char *log_level_shortname[log_level_count]; #define c_bold 1 #define c_underline 4 #define c_black 30 #define c_red 31 #define c_green 32 #define c_yellow 33 #define c_blue 34 #define c_magenta 35 #define c_cyan 36 #define c_white 37 #define CLR1(x) "\033[" STRINGIZE(x) "m" #define CLR2(x, y) "\033[" STRINGIZE(x) ";" STRINGIZE(y) "m" #define GET_CLR_MACRO(_1,_2,NAME,...) NAME #define CLR(...) GET_CLR_MACRO(__VA_ARGS__, CLR2, CLR1,)(__VA_ARGS__) #define L(...) log_print(true, log_debug, __VA_ARGS__) #define LOG_N(...) log_print(true, log_note, __VA_ARGS__) #define LOG_I(...) log_print(true, log_info, __VA_ARGS__) #define LOG_W(...) log_print(true, log_warn, __VA_ARGS__) #define LOG_E(...) log_print(true, log_error, __VA_ARGS__) #define LL(...) log_print(false, log_debug, __VA_ARGS__) #define LLOG_I(...) log_print(false, log_info, __VA_ARGS__) #define LLOG_W(...) log_print(false, log_warn, __VA_ARGS__) #define LLOG_E(...) log_print(false, log_error, __VA_ARGS__) void log_set_file(FILE *log_file); void log_set_debug_file(FILE *log_file); struct tstr; void log_set_buffer(struct tstr *buf); void log_set_lvl(enum log_level lvl); void log_set_prefix(int32_t n); void log_progress_enable(void); void log_progress_disable(void); bool log_is_progress_bar_enabled(void); void log_progress_push_level(double start, double end); void log_progress_pop_level(void); struct workspace; void log_progress_push_state(struct workspace *wk); void log_progress_pop_state(struct workspace *wk); void log_progress_inc(struct workspace *wk); void log_progress(struct workspace *wk, double val); void log_progress_subval(struct workspace *wk, double val, double sub_val); struct log_progress_style { const char *name; void (*decorate)(void *usr_ctx, uint32_t w); void *usr_ctx; double rate_limit; uint32_t name_pad; bool show_count; bool dont_disable_on_error; }; void log_progress_set_style(const struct log_progress_style *style); void log_printn(enum log_level lvl, const char *buf, uint32_t len); void log_printv(enum log_level lvl, const char *fmt, va_list ap); void log_print(bool nl, enum log_level lvl, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); void log_plain(enum log_level lvl, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 2, 3); void log_raw(const char *fmt, ...) MUON_ATTR_FORMAT(printf, 1, 2); void log_rawv(const char *fmt, va_list ap); bool log_should_print(enum log_level lvl); void log_plain_version_string(enum log_level lvl, const char *version); const char *bool_to_yn(bool v); // You should probably not use this. Prefer to go through one of the above functions FILE *_log_file(void); #endif muon-v0.5.0/include/error.h0000644000175000017500000000457115041716357014605 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ERROR_H #define MUON_ERROR_H #include "compat.h" #include "lang/types.h" #include "log.h" #include "platform/filesystem.h" #define UNREACHABLE assert(false && "unreachable") #define UNREACHABLE_RETURN \ do { \ assert(false && "unreachable"); \ return 0; \ } while (0) enum error_diagnostic_store_replay_opts { error_diagnostic_store_replay_errors_only = 1 << 0, error_diagnostic_store_replay_dont_include_sources = 1 << 1, error_diagnostic_store_replay_werror = 1 << 2, error_diagnostic_store_replay_prepare_only = 1 << 3, }; struct error_diagnostic_message { struct source_location location; enum log_level lvl; const char *msg; uint32_t src_idx; }; void error_unrecoverable(const char *fmt, ...) MUON_ATTR_FORMAT(printf, 1, 2); void error_message(const struct source *src, struct source_location location, enum log_level lvl, enum error_message_flag flags, const char *msg); void error_message_flush_coalesced_message(void); void error_messagev(const struct source *src, struct source_location location, enum log_level lvl, const char *fmt, va_list args); void error_messagef(const struct source *src, struct source_location location, enum log_level lvl, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 4, 5); void error_diagnostic_store_init(struct workspace *wk); void error_diagnostic_store_destroy(struct workspace *wk); struct arr *error_diagnostic_store_get(void); bool error_diagnostic_store_replay(struct workspace *wk, enum error_diagnostic_store_replay_opts opts); void error_diagnostic_store_push(uint32_t src_idx, struct source_location location, enum log_level lvl, const char *msg); void list_line_range(const struct source *src, struct source_location location, uint32_t context); void reopen_source(const struct source *src, struct source *src_reopened, bool *destroy_source); struct detailed_source_location { struct source_location loc; uint32_t line, col, start_of_line, end_line, end_col; }; enum get_detailed_source_location_flag { get_detailed_source_location_flag_multiline = 1 << 0, }; void get_detailed_source_location(const struct source *src, struct source_location loc, struct detailed_source_location *dloc, enum get_detailed_source_location_flag flags); #endif muon-v0.5.0/include/ui.h0000644000175000017500000000033215041716357014060 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_UI_H #define MUON_UI_H #include extern bool have_ui; bool ui_main(void); #endif muon-v0.5.0/include/meson_opts.h0000644000175000017500000000054615041716357015640 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_MESON_OPTS_H #define MUON_MESON_OPTS_H #include "lang/workspace.h" bool translate_meson_opts(struct workspace *wk, uint32_t argc, uint32_t argi, char *argv[], uint32_t *new_argc, uint32_t *new_argi, char **new_argv[]); #endif muon-v0.5.0/include/compat.h0000644000175000017500000000064215041716357014732 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #if !defined(_WIN32) #if !defined(_POSIX_C_SOURCE) #define _POSIX_C_SOURCE 200809L #endif #endif #if defined(__GNUC__) #define MUON_ATTR_FORMAT(type, start, end) __attribute__((format(type, start, end))) #else #define MUON_ATTR_FORMAT(type, start, end) #endif #ifdef __OpenBSD__ #include #endif muon-v0.5.0/include/compilers.h0000644000175000017500000003045715041716357015453 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Owen Rafferty * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_COMPILERS_H #define MUON_COMPILERS_H #include #include #include "lang/types.h" #include "machines.h" struct workspace; struct obj_compiler; #define FOREACH_TOOLCHAIN_COMPILER_TYPE(_) \ _(posix, "posix", "posix") \ _(gcc, "gcc", "gcc") \ _(clang, "clang", "clang") \ _(apple_clang, "clang", "clang-apple") \ _(clang_llvm_ir, "clang", "clang-llvm-ir") \ _(clang_cl, "clang-cl", "clang-cl") \ _(msvc, "msvc", "msvc") \ _(nasm, "nasm", "nasm") \ _(yasm, "yasm", "yasm") #define FOREACH_TOOLCHAIN_LINKER_TYPE(_) \ _(posix, "posix", "posix") \ _(ld, "ld", "ld") \ _(clang, "lld", "lld") \ _(apple, "ld", "ld-apple") \ _(lld_link, "lld-link", "lld-link") \ _(msvc, "link", "link") #define FOREACH_TOOLCHAIN_STATIC_LINKER_TYPE(_) \ _(ar_posix, "posix", "posix") \ _(ar_gcc, "ar", "ar") \ _(msvc, "lib", "lib") #define TOOLCHAIN_ENUM(id, strid, _) compiler_##id, enum compiler_type { FOREACH_TOOLCHAIN_COMPILER_TYPE(TOOLCHAIN_ENUM) compiler_type_count, }; #undef TOOLCHAIN_ENUM #define TOOLCHAIN_ENUM(id, strid, _) linker_##id, enum linker_type { FOREACH_TOOLCHAIN_LINKER_TYPE(TOOLCHAIN_ENUM) linker_type_count, }; #undef TOOLCHAIN_ENUM #define TOOLCHAIN_ENUM(id, strid, _) static_linker_##id, enum static_linker_type { FOREACH_TOOLCHAIN_STATIC_LINKER_TYPE(TOOLCHAIN_ENUM) static_linker_type_count, }; #undef TOOLCHAIN_ENUM #define FOREACH_COMPILER_EXPOSED_LANGUAGE(_) \ _(c) \ _(cpp) \ _(objc) \ _(objcpp) \ _(assembly) \ _(llvm_ir) \ _(nasm) #define TOOLCHAIN_ENUM(lang) compiler_language_##lang, enum compiler_language { compiler_language_null, FOREACH_COMPILER_EXPOSED_LANGUAGE(TOOLCHAIN_ENUM) compiler_language_c_hdr, compiler_language_cpp_hdr, compiler_language_objc_hdr, compiler_language_objcpp_hdr, compiler_language_c_obj, compiler_language_count, }; #undef TOOLCHAIN_ENUM enum compiler_optimization_lvl { compiler_optimization_lvl_none, compiler_optimization_lvl_0, compiler_optimization_lvl_1, compiler_optimization_lvl_2, compiler_optimization_lvl_3, compiler_optimization_lvl_g, compiler_optimization_lvl_s, }; enum compiler_pgo_stage { compiler_pgo_generate, compiler_pgo_use, }; enum compiler_warning_lvl { compiler_warning_lvl_0, compiler_warning_lvl_1, compiler_warning_lvl_2, compiler_warning_lvl_3, compiler_warning_lvl_everything, }; enum compiler_visibility_type { compiler_visibility_default, compiler_visibility_hidden, compiler_visibility_internal, compiler_visibility_protected, compiler_visibility_inlineshidden, }; enum toolchain_arg_arity { toolchain_arg_arity_0, toolchain_arg_arity_1i, toolchain_arg_arity_1s, toolchain_arg_arity_2s, toolchain_arg_arity_1s1b, toolchain_arg_arity_ns, }; struct toolchain_arg_handler { const char *name; enum toolchain_arg_arity arity; }; #define TOOLCHAIN_TRUE ((void *)1) #define TOOLCHAIN_FALSE 0 #define TOOLCHAIN_PARAMS_BASE struct workspace *wk, struct obj_compiler *comp #define TOOLCHAIN_PARAM_NAMES_BASE wk, comp #define TOOLCHAIN_SIG_0 TOOLCHAIN_PARAMS_BASE #define TOOLCHAIN_SIG_1i TOOLCHAIN_PARAMS_BASE, uint32_t i1 #define TOOLCHAIN_SIG_1s TOOLCHAIN_PARAMS_BASE, const char *s1 #define TOOLCHAIN_SIG_2s TOOLCHAIN_PARAMS_BASE, const char *s1, const char *s2 #define TOOLCHAIN_SIG_1s1b TOOLCHAIN_PARAMS_BASE, const char *s1, bool b1 #define TOOLCHAIN_SIG_ns TOOLCHAIN_PARAMS_BASE, const struct args *n1 #define TOOLCHAIN_PARAMS_0 0, (TOOLCHAIN_SIG_0), (TOOLCHAIN_PARAM_NAMES_BASE) #define TOOLCHAIN_PARAMS_1i 1i, (TOOLCHAIN_SIG_1i), (TOOLCHAIN_PARAM_NAMES_BASE, i1) #define TOOLCHAIN_PARAMS_1s 1s, (TOOLCHAIN_SIG_1s), (TOOLCHAIN_PARAM_NAMES_BASE, s1) #define TOOLCHAIN_PARAMS_2s 2s, (TOOLCHAIN_SIG_2s), (TOOLCHAIN_PARAM_NAMES_BASE, s1, s2) #define TOOLCHAIN_PARAMS_1s1b 1s1b, (TOOLCHAIN_SIG_1s1b), (TOOLCHAIN_PARAM_NAMES_BASE, s1, b1) #define TOOLCHAIN_PARAMS_ns ns, (TOOLCHAIN_SIG_ns), (TOOLCHAIN_PARAM_NAMES_BASE, n1) typedef const struct args *((*compiler_get_arg_func_0)(TOOLCHAIN_SIG_0)); typedef const struct args *((*compiler_get_arg_func_1i)(TOOLCHAIN_SIG_1i)); typedef const struct args *((*compiler_get_arg_func_1s)(TOOLCHAIN_SIG_1s)); typedef const struct args *((*compiler_get_arg_func_2s)(TOOLCHAIN_SIG_2s)); typedef const struct args *((*compiler_get_arg_func_1s1b)(TOOLCHAIN_SIG_1s1b)); typedef const struct args *((*compiler_get_arg_func_ns)(TOOLCHAIN_SIG_ns)); #define TOOLCHAIN_ARG_MEMBER_(name, type, params, names) compiler_get_arg_func_##type name; #define TOOLCHAIN_ARG_MEMBER(name, comp, type) TOOLCHAIN_ARG_MEMBER_(name, type) #define FOREACH_COMPILER_ARG(_) \ _(do_linker_passthrough, compiler, TOOLCHAIN_PARAMS_0) \ _(linker_passthrough, compiler, TOOLCHAIN_PARAMS_ns) \ _(deps, compiler, TOOLCHAIN_PARAMS_2s) \ _(compile_only, compiler, TOOLCHAIN_PARAMS_0) \ _(preprocess_only, compiler, TOOLCHAIN_PARAMS_0) \ _(output, compiler, TOOLCHAIN_PARAMS_1s) \ _(optimization, compiler, TOOLCHAIN_PARAMS_1i) \ _(debug, compiler, TOOLCHAIN_PARAMS_0) \ _(warning_lvl, compiler, TOOLCHAIN_PARAMS_1i) \ _(warn_everything, compiler, TOOLCHAIN_PARAMS_0) \ _(werror, compiler, TOOLCHAIN_PARAMS_0) \ _(set_std, compiler, TOOLCHAIN_PARAMS_1s) \ _(include, compiler, TOOLCHAIN_PARAMS_1s) \ _(include_system, compiler, TOOLCHAIN_PARAMS_1s) \ _(include_dirafter, compiler, TOOLCHAIN_PARAMS_1s) \ _(pgo, compiler, TOOLCHAIN_PARAMS_1i) \ _(pic, compiler, TOOLCHAIN_PARAMS_0) \ _(pie, compiler, TOOLCHAIN_PARAMS_0) \ _(sanitize, compiler, TOOLCHAIN_PARAMS_1s) \ _(define, compiler, TOOLCHAIN_PARAMS_1s) \ _(visibility, compiler, TOOLCHAIN_PARAMS_1i) \ _(specify_lang, compiler, TOOLCHAIN_PARAMS_1s) \ _(color_output, compiler, TOOLCHAIN_PARAMS_1s) \ _(enable_lto, compiler, TOOLCHAIN_PARAMS_0) \ _(always, compiler, TOOLCHAIN_PARAMS_0) \ _(crt, compiler, TOOLCHAIN_PARAMS_1s1b) \ _(debugfile, compiler, TOOLCHAIN_PARAMS_1s) \ _(object_ext, compiler, TOOLCHAIN_PARAMS_0) \ _(pch_ext, compiler, TOOLCHAIN_PARAMS_0) \ _(force_language, compiler, TOOLCHAIN_PARAMS_1i) \ _(deps_type, compiler, TOOLCHAIN_PARAMS_0) \ _(coverage, compiler, TOOLCHAIN_PARAMS_0) \ _(std_supported, compiler, TOOLCHAIN_PARAMS_1s) \ _(permissive, compiler, TOOLCHAIN_PARAMS_0) \ _(include_pch, compiler, TOOLCHAIN_PARAMS_1s) \ _(emit_pch, compiler, TOOLCHAIN_PARAMS_0) \ _(winvalid_pch, compiler, TOOLCHAIN_PARAMS_0) #define FOREACH_LINKER_ARG(_) \ _(lib, linker, TOOLCHAIN_PARAMS_1s) \ _(debug, linker, TOOLCHAIN_PARAMS_0) \ _(as_needed, linker, TOOLCHAIN_PARAMS_0) \ _(no_undefined, linker, TOOLCHAIN_PARAMS_0) \ _(start_group, linker, TOOLCHAIN_PARAMS_0) \ _(end_group, linker, TOOLCHAIN_PARAMS_0) \ _(shared, linker, TOOLCHAIN_PARAMS_0) \ _(soname, linker, TOOLCHAIN_PARAMS_1s) \ _(rpath, linker, TOOLCHAIN_PARAMS_1s) \ _(pgo, linker, TOOLCHAIN_PARAMS_1i) \ _(sanitize, linker, TOOLCHAIN_PARAMS_1s) \ _(allow_shlib_undefined, linker, TOOLCHAIN_PARAMS_0) \ _(shared_module, linker, TOOLCHAIN_PARAMS_0) \ _(export_dynamic, linker, TOOLCHAIN_PARAMS_0) \ _(fatal_warnings, linker, TOOLCHAIN_PARAMS_0) \ _(whole_archive, linker, TOOLCHAIN_PARAMS_1s) \ _(enable_lto, linker, TOOLCHAIN_PARAMS_0) \ _(input_output, linker, TOOLCHAIN_PARAMS_2s) \ _(always, linker, TOOLCHAIN_PARAMS_0) \ _(coverage, linker, TOOLCHAIN_PARAMS_0) \ _(implib, linker, TOOLCHAIN_PARAMS_1s) #define FOREACH_STATIC_LINKER_ARG(_) \ _(base, static_linker, TOOLCHAIN_PARAMS_0) \ _(input_output, static_linker, TOOLCHAIN_PARAMS_2s) \ _(always, static_linker, TOOLCHAIN_PARAMS_0) struct language { bool is_header; bool is_linkable; }; struct compiler { struct { FOREACH_COMPILER_ARG(TOOLCHAIN_ARG_MEMBER) } args; enum linker_type default_linker; enum static_linker_type default_static_linker; }; struct linker { struct { FOREACH_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) } args; }; struct static_linker { struct { FOREACH_STATIC_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) } args; }; #undef TOOLCHAIN_ARG_MEMBER #undef TOOLCHAIN_ARG_MEMBER_ enum toolchain_arg { #define TOOLCHAIN_ARG_MEMBER_(comp, _name) toolchain_arg_##comp##_name, #define TOOLCHAIN_ARG_MEMBER(name, comp, type) TOOLCHAIN_ARG_MEMBER_(comp, _##name) FOREACH_COMPILER_ARG(TOOLCHAIN_ARG_MEMBER) FOREACH_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) FOREACH_STATIC_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) #undef TOOLCHAIN_ARG_MEMBER #undef TOOLCHAIN_ARG_MEMBER_ }; struct toolchain_id { const char *public_id; const char *id; }; extern const struct toolchain_id compiler_type_name[]; extern const struct toolchain_id linker_type_name[]; extern const struct toolchain_id static_linker_type_name[]; extern struct compiler compilers[]; extern struct linker linkers[]; extern struct static_linker static_linkers[]; extern const struct language languages[]; struct compiler_check_cache_key { struct obj_compiler *comp; const char *argstr; const char *src; uint32_t argc; }; struct compiler_check_cache_value { obj value; bool success; }; obj compiler_check_cache_key(struct workspace *wk, const struct compiler_check_cache_key *key); bool compiler_check_cache_get(struct workspace *wk, obj key, struct compiler_check_cache_value *val); void compiler_check_cache_set(struct workspace *wk, obj key, const struct compiler_check_cache_value *val); const struct toolchain_id *toolchain_component_type_to_s(enum toolchain_component comp, uint32_t val); const char *toolchain_component_to_s(enum toolchain_component comp); bool toolchain_component_from_s(const char *name, uint32_t *res); const char *compiler_type_to_s(enum compiler_type t); bool compiler_type_from_s(const char *name, uint32_t *res); enum compiler_language compiler_language_to_hdr(enum compiler_language lang); const char *linker_type_to_s(enum linker_type t); bool linker_type_from_s(const char *name, uint32_t *res); bool static_linker_type_from_s(const char *name, uint32_t *res); const char *compiler_language_to_s(enum compiler_language l); bool s_to_compiler_language(const char *s, enum compiler_language *l); bool filename_to_compiler_language(const char *str, enum compiler_language *l); const char *compiler_language_extension(enum compiler_language l); enum compiler_language coalesce_link_languages(enum compiler_language cur, enum compiler_language new_lang); bool toolchain_detect(struct workspace *wk, obj *comp, enum machine_kind machine, enum compiler_language lang); void compilers_init(void); const struct toolchain_arg_handler *get_toolchain_arg_handler_info(enum toolchain_component component, const char *name); void toolchain_arg_arity_to_sig(enum toolchain_arg_arity arity, type_tag signature[2], uint32_t *len); struct toolchain_dump_opts { const char *s1, *s2; bool b1; uint32_t i1; const struct args *n1; }; void toolchain_dump(struct workspace *wk, struct obj_compiler *comp, struct toolchain_dump_opts *opts); #define TOOLCHAIN_ARG_MEMBER_(name, _name, component, _type, params, names) \ const struct args *toolchain_##component##_name params; #define TOOLCHAIN_ARG_MEMBER(name, comp, type) TOOLCHAIN_ARG_MEMBER_(name, _##name, comp, type) FOREACH_COMPILER_ARG(TOOLCHAIN_ARG_MEMBER) FOREACH_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) FOREACH_STATIC_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) #undef TOOLCHAIN_ARG_MEMBER #undef TOOLCHAIN_ARG_MEMBER_ const char *compiler_log_prefix(enum compiler_language lang, enum machine_kind machine); #endif muon-v0.5.0/include/vsenv.h0000644000175000017500000000035215041716357014606 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_VSENV_H #define MUON_VSENV_H #include bool vsenv_setup(const char *cache_path, bool force); #endif muon-v0.5.0/include/opts.h0000644000175000017500000000520715041716357014436 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_OPTS_H #define MUON_OPTS_H #include #include #include /* OPTSTART should be pretty self-explanatory. You just pass it the optstring * that you would pass to getopt(). "h" is added to this optstring for you. * * OPTEND is a little bit more involved, the first 4 arguments are used to * construct the help message, while the 5th argument should be the number of * required operands for this subcommand, or -1 which disables the check. */ #define OPTSTART(optstring) \ signed char opt; \ optind = 1; \ while ((opt = os_getopt(argc - argi, &argv[argi], optstring "h")) != -1) { \ switch (opt) { #define OPTEND_CUSTOM(usage_pre, usage_post, usage_opts, commands, operands, extra_help) \ case 'h': \ print_usage(stdout, commands, usage_pre, usage_opts, usage_post); \ extra_help; \ exit(0); \ break; \ default: print_usage(stderr, commands, usage_pre, usage_opts, usage_post); return false; \ } \ } \ if (!check_operands(argc, (argi + optind), operands)) { \ print_usage(stderr, commands, usage_pre, usage_opts, usage_post); \ return false; \ } \ argi += optind; #define OPTEND(usage_pre, usage_post, usage_opts, commands, operands) \ OPTEND_CUSTOM(usage_pre, usage_post, usage_opts, commands, operands, {}) typedef bool (*cmd_func)(void *ctx, uint32_t argc, uint32_t argi, char *const[]); struct command { const char *name; cmd_func cmd; const char *desc; }; void print_usage(FILE *f, const struct command *commands, const char *pre, const char *opts, const char *post); bool find_cmd(const struct command *commands, uint32_t *ret, uint32_t argc, uint32_t argi, char *const argv[], bool optional); bool check_operands(uint32_t argc, uint32_t argi, int32_t expected); #endif muon-v0.5.0/include/external/0002755000175000017500000000000015041716357015120 5ustar buildbuildmuon-v0.5.0/include/external/samurai/0002755000175000017500000000000015041716357016561 5ustar buildbuildmuon-v0.5.0/include/external/samurai/ctx.h0000644000175000017500000001062615041716357017533 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_CTX_H #define MUON_EXTERNAL_SAMU_CTX_H #include #include #include #include "platform/filesystem.h" #include "platform/timer.h" struct samu_buffer { char *data; size_t len, cap; }; struct samu_string { size_t n; char s[]; }; /* an unevaluated string */ struct samu_evalstring { char *var; struct samu_string *str; struct samu_evalstring *next; }; struct samu_hashtablekey { uint64_t hash; const char *str; size_t len; }; struct samu_buildoptions { size_t maxjobs, maxfail; _Bool verbose, explain, keepdepfile, keeprsp, dryrun; const char *statusfmt; }; struct samu_parseoptions { _Bool dupbuildwarn; }; enum samu_token { SAMU_BUILD, SAMU_DEFAULT, SAMU_INCLUDE, SAMU_POOL, SAMU_RULE, SAMU_SUBNINJA, SAMU_VARIABLE, }; struct samu_scanner { struct source src; const char *path; int chr, line, col; uint32_t src_i; }; struct samu_node { /* shellpath is the escaped shell path, and is populated as needed by nodepath */ struct samu_string *path, *shellpath; /* modification time of file (in nanoseconds) and build log entry (in seconds) */ int64_t mtime, logmtime; /* generating edge and dependent edges */ struct samu_edge *gen, **use; size_t nuse; /* command hash used to build this output, read from build log */ uint64_t hash; /* ID for .ninja_deps. -1 if not present in log. */ int32_t id; /* does the node need to be rebuilt */ _Bool dirty; }; /* build rule, i.e., edge between inputs and outputs */ struct samu_edge { struct samu_rule *rule; struct samu_pool *pool; struct samu_environment *env; /* input and output nodes */ struct samu_node **out, **in; size_t nout, nin; /* index of first implicit output */ size_t outimpidx; /* index of first implicit and order-only input */ size_t inimpidx, inorderidx; /* command hash */ uint64_t hash; /* how many inputs need to be rebuilt or pruned before this edge is ready */ size_t nblock; /* how many inputs need to be pruned before all outputs can be pruned */ size_t nprune; enum { FLAG_WORK = 1 << 0, /* scheduled for build */ FLAG_HASH = 1 << 1, /* calculated the command hash */ FLAG_DIRTY_IN = 1 << 3, /* dirty input */ FLAG_DIRTY_OUT = 1 << 4, /* missing or outdated output */ FLAG_DIRTY = FLAG_DIRTY_IN | FLAG_DIRTY_OUT, FLAG_CYCLE = 1 << 5, /* used for cycle detection */ FLAG_DEPS = 1 << 6, /* dependencies loaded */ } flags; /* used to coordinate ready work in build() */ struct samu_edge *worknext; /* used for alledges linked list */ struct samu_edge *allnext; }; struct samu_nodearray { struct samu_node **node; size_t len; }; struct samu_entry { struct samu_node *node; struct samu_nodearray deps; int64_t mtime; }; struct samu_rule { char *name; struct samu_treenode *bindings; }; struct samu_pool { char *name; int numjobs, maxjobs; /* a queue of ready edges blocked by the pool's capacity */ struct samu_edge *work; }; struct samu_build_ctx { struct samu_edge *work; size_t nstarted, nfinished, ntotal; bool consoleused; struct timer timer; }; struct samu_deps_ctx { FILE *depsfile; struct samu_entry *entries; size_t entrieslen, entriescap; struct samu_buffer buf; struct samu_nodearray deps; size_t depscap; }; struct samu_env_ctx { struct samu_environment *rootenv; struct samu_treenode *pools; struct samu_environment *allenvs; }; struct samu_graph_ctx { struct samu_hashtable *allnodes; struct samu_edge *alledges; }; struct samu_log_ctx { FILE *logfile; }; struct samu_parse_ctx { struct samu_node **deftarg; size_t ndeftarg; }; struct samu_scan_ctx { struct samu_evalstring **paths; size_t npaths; size_t paths_max; struct samu_buffer buf; }; struct samu_arena { size_t blocks_len, i, allocd, filled; char **blocks; }; struct samu_ctx { struct samu_buildoptions buildopts; struct samu_parseoptions parseopts; struct samu_build_ctx build; struct samu_deps_ctx deps; struct samu_env_ctx env; struct samu_graph_ctx graph; struct samu_log_ctx log; struct samu_parse_ctx parse; struct samu_scan_ctx scan; const char *argv0; struct samu_rule phonyrule; struct samu_pool consolepool; struct samu_arena arena; FILE *out; }; struct samu_tool { const char *name; int (*run)(struct samu_ctx *, int, char *[]); }; #endif muon-v0.5.0/include/external/samurai/tool.h0000644000175000017500000000051715041716357017710 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_TOOL_H #define MUON_EXTERNAL_SAMU_TOOL_H const struct samu_tool *samu_toolget(const char *); void samu_toollist(struct samu_ctx *ctx); #endif muon-v0.5.0/include/external/samurai/deps.h0000644000175000017500000000104315041716357017661 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_DEPS_H #define MUON_EXTERNAL_SAMU_DEPS_H struct samu_edge; void samu_depsinit(struct samu_ctx *ctx, const char *builddir); void samu_depsclose(struct samu_ctx *ctx); void samu_depsload(struct samu_ctx *ctx, struct samu_edge *e); void samu_depsrecord(struct samu_ctx *ctx, struct tstr *output, const char **filtered_output, struct samu_edge *e); #endif muon-v0.5.0/include/external/samurai/env.h0000644000175000017500000000376715041716357017535 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_ENV_H #define MUON_EXTERNAL_SAMU_ENV_H struct samu_edge; struct samu_evalstring; struct samu_string; void samu_envinit(struct samu_ctx *ctx); /* create a new environment with an optional parent */ struct samu_environment *samu_mkenv(struct samu_ctx *ctx, struct samu_environment *); /* search environment and its parents for a variable, returning the value or NULL if not found */ struct samu_string *samu_envvar(struct samu_environment *, char *); /* add to environment a variable and its value, replacing the old value if there is one */ void samu_envaddvar(struct samu_ctx *ctx, struct samu_environment *env, char *var, struct samu_string *val); /* evaluate an unevaluated string within an environment, returning the result */ struct samu_string *samu_enveval(struct samu_ctx *ctx, struct samu_environment *, struct samu_evalstring *); /* search an environment and its parents for a rule, returning the rule or NULL if not found */ struct samu_rule *samu_envrule(struct samu_environment *env, char *name); /* add a rule to an environment, or fail if the rule already exists */ void samu_envaddrule(struct samu_ctx *ctx, struct samu_environment *env, struct samu_rule *r); /* create a new rule with the given name */ struct samu_rule *samu_mkrule(struct samu_ctx *ctx, char *); /* add to rule a variable and its value */ void samu_ruleaddvar(struct samu_ctx *ctx, struct samu_rule *r, char *var, struct samu_evalstring *val); /* create a new pool with the given name */ struct samu_pool *samu_mkpool(struct samu_ctx *ctx, char *name); /* lookup a pool by name, or fail if it does not exist */ struct samu_pool *samu_poolget(struct samu_ctx *ctx, char *name); /* evaluate and return an edge's variable, optionally shell-escaped */ struct samu_string *samu_edgevar(struct samu_ctx *ctx, struct samu_edge *e, char *var, bool escape); #endif muon-v0.5.0/include/external/samurai/scan.h0000644000175000017500000000165115041716357017657 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_SCAN_H #define MUON_EXTERNAL_SAMU_SCAN_H void samu_scaninit(struct samu_scanner *, const char *); void samu_scanclose(struct samu_scanner *); void samu_scanerror(struct samu_scanner *, const char *, ...) MUON_ATTR_FORMAT(printf, 2, 3); int samu_scankeyword(struct samu_ctx *ctx, struct samu_scanner *s, char **var); char *samu_scanname(struct samu_ctx *ctx, struct samu_scanner *s); struct samu_evalstring *samu_scanstring(struct samu_ctx *ctx, struct samu_scanner *s, bool path); void samu_scanpaths(struct samu_ctx *ctx, struct samu_scanner *s); void samu_scanchar(struct samu_scanner *, int); int samu_scanpipe(struct samu_scanner *, int); _Bool samu_scanindent(struct samu_scanner *); void samu_scannewline(struct samu_scanner *); #endif muon-v0.5.0/include/external/samurai/parse.h0000644000175000017500000000106415041716357020043 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_PARSE_H #define MUON_EXTERNAL_SAMU_PARSE_H struct samu_environment; struct samu_node; void samu_parseinit(struct samu_ctx *ctx); void samu_parse(struct samu_ctx *ctx, const char *name, struct samu_environment *env); /* execute a function with all default nodes */ void samu_defaultnodes(struct samu_ctx *ctx, void fn(struct samu_ctx *ctx, struct samu_node *)); #endif muon-v0.5.0/include/external/samurai/htab.h0000644000175000017500000000120615041716357017645 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_HTAB_H #define MUON_EXTERNAL_SAMU_HTAB_H #include /* for uint64_t */ void samu_htabkey(struct samu_hashtablekey *, const char *, size_t); struct samu_hashtable *samu_mkhtab(struct samu_arena *a, size_t cap); void **samu_htabput(struct samu_arena *a, struct samu_hashtable *h, struct samu_hashtablekey *k); void *samu_htabget(struct samu_hashtable *, struct samu_hashtablekey *); uint64_t samu_murmurhash64a(const void *, size_t); #endif muon-v0.5.0/include/external/samurai/log.h0000644000175000017500000000064315041716357017514 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_LOG_H #define MUON_EXTERNAL_SAMU_LOG_H struct samu_node; void samu_loginit(struct samu_ctx *ctx, const char *); void samu_logclose(struct samu_ctx *ctx); void samu_logrecord(struct samu_ctx *ctx, struct samu_node *); #endif muon-v0.5.0/include/external/samurai/arg.h0000644000175000017500000000125515041716357017504 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_ARG_H #define MUON_EXTERNAL_SAMU_ARG_H #define SAMU_ARGBEGIN \ for (;;) { \ if (argc > 0) \ ++argv, --argc; \ if (argc == 0 || (*argv)[0] != '-') \ break; \ if ((*argv)[1] == '-' && !(*argv)[2]) { \ ++argv, --argc; \ break; \ } \ for (char *opt_ = &(*argv)[1], done_ = 0; !done_ && *opt_; ++opt_) { \ switch (*opt_) #define SAMU_ARGEND \ } \ } #define SAMU_EARGF(x) \ (done_ = 1, *++opt_ ? opt_ : argv[1] ? --argc, *++argv : ((x), abort(), (char *)0)) #endif muon-v0.5.0/include/external/samurai/build.h0000644000175000017500000000105515041716357020030 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_BUILD_H #define MUON_EXTERNAL_SAMU_BUILD_H struct samu_node; /* reset state, so a new build can be executed */ void samu_buildreset(struct samu_ctx *ctx); /* schedule a particular target to be built */ void samu_buildadd(struct samu_ctx *ctx, struct samu_node *n); /* execute rules to build the scheduled targets */ void samu_build(struct samu_ctx *ctx); #endif muon-v0.5.0/include/external/samurai/graph.h0000644000175000017500000000312415041716357020031 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_GRAPH_H #define MUON_EXTERNAL_SAMU_GRAPH_H #include /* for uint64_t */ struct samu_string; /* set in the tv_nsec field of a node's mtime */ enum { /* we haven't stat the file yet */ SAMU_MTIME_UNKNOWN = 1, /* the file does not exist */ SAMU_MTIME_MISSING = 2, }; void samu_graphinit(struct samu_ctx *ctx); /* create a new node or return existing node */ struct samu_node *samu_mknode(struct samu_ctx *ctx, struct samu_string *path); /* lookup a node by name; returns NULL if it does not exist */ struct samu_node *samu_nodeget(struct samu_ctx *ctx, const char *path, size_t len); /* update the mtime field of a node */ void samu_nodestat(struct samu_node *); /* get a node's path, possibly escaped for the shell */ struct samu_string *samu_nodepath(struct samu_ctx *ctx, struct samu_node *n, bool escape); /* record the usage of a node by an edge */ void samu_nodeuse(struct samu_ctx *ctx, struct samu_node *n, struct samu_edge *e); /* create a new edge with the given parent environment */ struct samu_edge *samu_mkedge(struct samu_ctx *ctx, struct samu_environment *parent); /* compute the murmurhash64a of an edge command and store it in the hash field */ void samu_edgehash(struct samu_ctx *ctx, struct samu_edge *e); /* add dependencies from $depfile or .ninja_deps as implicit inputs */ void samu_edgeadddeps(struct samu_ctx *ctx, struct samu_edge *e, struct samu_node **deps, size_t ndeps); #endif muon-v0.5.0/include/external/samurai/tree.h0000644000175000017500000000141015041716357017663 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_TREE_H #define MUON_EXTERNAL_SAMU_TREE_H /* binary tree node, such that keys are sorted lexicographically for fast lookup */ struct samu_treenode { char *key; void *value; struct samu_treenode *child[2]; int height; }; /* search a binary tree for a key, return the key's value or NULL */ struct samu_treenode *samu_treefind(struct samu_treenode *, const char *); /* insert into a binary tree a key and a value, replace and return the old value if the key already exists */ void *samu_treeinsert(struct samu_ctx *ctx, struct samu_treenode **rootp, char *key, void *value); #endif muon-v0.5.0/include/external/samurai/util.h0000644000175000017500000000351415041716357017710 0ustar buildbuild/* * SPDX-FileCopyrightText: Michael Forney * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: MIT */ #ifndef MUON_EXTERNAL_SAMU_UTIL_H #define MUON_EXTERNAL_SAMU_UTIL_H void samu_warn(const char *, ...) MUON_ATTR_FORMAT(printf, 1, 2); void samu_fatal(const char *, ...) MUON_ATTR_FORMAT(printf, 1, 2); int samu_vprintf(struct samu_ctx *ctx, const char *fmt, va_list ap); int samu_printf(struct samu_ctx *ctx, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 2, 3); void samu_puts(struct samu_ctx *ctx, const char *str); void samu_puts_no_newline(struct samu_ctx *ctx, const char *str); void samu_putchar(struct samu_ctx *ctx, const char c); void samu_arena_init(struct samu_arena *a); void samu_arena_destroy(struct samu_arena *a); void *samu_arena_alloc(struct samu_arena *a, size_t size); void *samu_arena_realloc(struct samu_arena *a, void *p, size_t old, size_t new); void *samu_xmalloc(struct samu_arena *a, size_t); void *samu_xreallocarray(struct samu_arena *a, void *, size_t old, size_t new, size_t item_size); char *samu_xmemdup(struct samu_arena *a, const char *, size_t); int samu_xasprintf(struct samu_arena *a, char **, const char *, ...); /* append a byte to a buffer */ void samu_bufadd(struct samu_arena *a, struct samu_buffer *buf, char c); /* allocates a new string with length n. n + 1 bytes are allocated for * s, but not initialized. */ struct samu_string *samu_mkstr(struct samu_arena *a, size_t n); /* canonicalizes the given path by removing duplicate slashes, and * folding '/.' and 'foo/..' */ void samu_canonpath(struct samu_string *); /* make a directory (or parent directory of a file) recursively */ int samu_makedirs(struct samu_string *, _Bool); /* write a new file with the given name and contents */ int samu_writefile(const char *, struct samu_string *); #endif muon-v0.5.0/include/external/pkgconfig.h0000644000175000017500000000263415041716357017243 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_LIBPKGCONF_H #define MUON_EXTERNAL_LIBPKGCONF_H #include "lang/workspace.h" #define MAX_VERSION_LEN 32 struct pkgconfig_info { char version[MAX_VERSION_LEN + 1]; obj includes, libs, not_found_libs, link_args, compile_args; }; enum pkgconfig_impl_type { pkgconfig_impl_type_null, pkgconfig_impl_type_exec, pkgconfig_impl_type_libpkgconf, }; #define pkgconfig_impl_type_count 3 struct pkgconfig_impl { bool (*lookup)(struct workspace *wk, obj compiler, obj name, bool is_static, struct pkgconfig_info *info); bool (*get_variable)(struct workspace *wk, obj pkg_name, obj var_name, obj defines, obj *res); }; extern const struct pkgconfig_impl pkgconfig_impl_null; extern const struct pkgconfig_impl pkgconfig_impl_exec; extern const struct pkgconfig_impl pkgconfig_impl_libpkgconf; extern struct pkgconfig_impl pkgconfig_impls[pkgconfig_impl_type_count]; void muon_pkgconfig_init(struct workspace *wk); bool muon_pkgconfig_set_impl_type(struct workspace *wk, enum pkgconfig_impl_type t); const char *muon_pkgconfig_impl_type_to_s(enum pkgconfig_impl_type t); bool muon_pkgconfig_lookup(struct workspace *wk, obj compiler, obj name, bool is_static, struct pkgconfig_info *info); bool muon_pkgconfig_get_variable(struct workspace *wk, obj pkg_name, obj var_name, obj defines, obj *res); #endif muon-v0.5.0/include/external/libcurl.h0000644000175000017500000000140615041716357016724 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_LIBCURL_H #define MUON_EXTERNAL_LIBCURL_H #include #include extern const bool have_libcurl; enum mc_fetch_collect_result { mc_fetch_collect_result_pending, mc_fetch_collect_result_done, mc_fetch_collect_result_error, }; enum mc_fetch_flag { mc_fetch_flag_verbose = 1 << 0, }; struct mc_fetch_stats { int64_t downloaded; int64_t total; }; void mc_init(void); void mc_deinit(void); int32_t mc_fetch_begin(const char *url, uint8_t **buf, uint64_t *len, enum mc_fetch_flag flags); enum mc_fetch_collect_result mc_fetch_collect(int32_t i, struct mc_fetch_stats *stats); bool mc_wait(uint32_t timeout_ms); #endif muon-v0.5.0/include/external/readline.h0000644000175000017500000000047115041716357017054 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_READLINE_H #define MUON_EXTERNAL_READLINE_H char *muon_readline(const char *prompt); int muon_readline_history_add(const char *line); void muon_readline_history_free(void); #endif muon-v0.5.0/include/external/samurai.h0000644000175000017500000000071415041716357016732 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_SAMURAI_H #define MUON_EXTERNAL_SAMURAI_H #include #include #include struct samu_opts { FILE *out; }; /* supported ninja version */ enum { samu_ninjamajor = 1, samu_ninjaminor = 9, }; extern const bool have_samurai; bool samu_main(int argc, char *argv[], struct samu_opts *opts); #endif muon-v0.5.0/include/external/libarchive.h0000644000175000017500000000053215041716357017377 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_LIBARCHIVE_H #define MUON_EXTERNAL_LIBARCHIVE_H #include #include extern const bool have_libarchive; bool muon_archive_extract(const char *buf, size_t size, const char *dest_path); #endif muon-v0.5.0/include/ui/0002755000175000017500000000000015041716357013713 5ustar buildbuildmuon-v0.5.0/include/ui/imconfig.h0000644000175000017500000000015515041716357015656 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ muon-v0.5.0/include/util.h0000644000175000017500000000045715041716357014430 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_UTIL_H #define MUON_UTIL_H #ifdef MIN #undef MIN #endif #ifdef MAX #undef MAX #endif #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif muon-v0.5.0/include/backend/0002755000175000017500000000000015041716357014665 5ustar buildbuildmuon-v0.5.0/include/backend/ninja.h0000644000175000017500000000105715041716357016136 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_H #define MUON_BACKEND_NINJA_H #include "lang/workspace.h" struct write_tgt_ctx { FILE *out; const struct project *proj; bool wrote_default; }; enum ninja_run_flag { ninja_run_flag_ignore_errors = 1 << 0, ninja_run_flag_require_ninja = 1 << 1, }; bool ninja_write_all(struct workspace *wk); bool ninja_run(struct workspace *wk, obj args, const char *chdir, const char *capture, enum ninja_run_flag flags); #endif muon-v0.5.0/include/backend/common_args.h0000644000175000017500000000271415041716357017344 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_COMMON_ARGS_H #define MUON_BACKEND_COMMON_ARGS_H #include "lang/workspace.h" void ca_get_std_args(struct workspace *wk, struct obj_compiler *comp, const struct project *proj, const struct obj_build_target *tgt, obj args); void ca_get_option_compile_args(struct workspace *wk, struct obj_compiler *comp, const struct project *proj, const struct obj_build_target *tgt, obj args); void ca_get_option_link_args(struct workspace *wk, struct obj_compiler *comp, const struct project *proj, const struct obj_build_target *tgt, obj args); obj ca_build_target_joined_args(struct workspace *wk, obj processed_args); bool ca_prepare_target_linker_args(struct workspace *wk, struct obj_compiler *comp, const struct project *proj, struct obj_build_target *tgt, bool relativize); bool ca_prepare_all_targets(struct workspace *wk); void ca_setup_compiler_args_includes(struct workspace *wk, obj compiler, obj include_dirs, obj args, bool relativize); void ca_relativize_paths(struct workspace *wk, obj arr, bool relativize_strings, obj *res); void ca_relativize_path(struct workspace *wk, obj path, bool relativize_strings, obj *res); void ca_relativize_path_push(struct workspace *wk, obj path, obj arr); obj ca_regenerate_build_command(struct workspace *wk, bool opts_only); obj ca_backend_tgt_name(struct workspace *wk, obj tgt_id); #endif muon-v0.5.0/include/backend/xcode.h0000644000175000017500000000037215041716357016140 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_XCODE_H #define MUON_BACKEND_XCODE_H #include "lang/workspace.h" bool xcode_write_all(struct workspace *wk); #endif muon-v0.5.0/include/backend/introspect.h0000644000175000017500000000040715041716357017227 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_INTROSPECT_H #define MUON_BACKEND_INTROSPECT_H #include "lang/workspace.h" bool introspect_write_all(struct workspace *wk); #endif muon-v0.5.0/include/backend/backend.h0000644000175000017500000000037315041716357016426 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_BACKEND_H #define MUON_BACKEND_BACKEND_H #include "lang/workspace.h" bool backend_output(struct workspace *wk); #endif muon-v0.5.0/include/backend/ninja/0002755000175000017500000000000015041716357015764 5ustar buildbuildmuon-v0.5.0/include/backend/ninja/custom_target.h0000644000175000017500000000053015041716357021011 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_CUSTOM_TARGET_H #define MUON_BACKEND_NINJA_CUSTOM_TARGET_H #include "lang/workspace.h" struct write_tgt_ctx; bool ninja_write_custom_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *ctx); #endif muon-v0.5.0/include/backend/ninja/rules.h0000644000175000017500000000052415041716357017266 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_RULES_H #define MUON_BACKEND_NINJA_RULES_H #include "lang/workspace.h" bool ninja_write_rules(FILE *out, struct workspace *wk, struct project *main_proj, bool need_phony, obj compiler_rule_arr); #endif muon-v0.5.0/include/backend/ninja/build_target.h0000644000175000017500000000052515041716357020602 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_BUILD_TARGET_H #define MUON_BACKEND_NINJA_BUILD_TARGET_H #include "lang/workspace.h" struct write_tgt_ctx; bool ninja_write_build_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *ctx); #endif muon-v0.5.0/include/backend/ninja/alias_target.h0000644000175000017500000000056615041716357020601 0ustar buildbuild/* * SPDX-FileCopyrightText: dffdff2423 * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_ALIAS_TARGET_H #define MUON_BACKEND_NINJA_ALIAS_TARGET_H #include "lang/types.h" struct workspace; struct project; struct write_tgt_ctx; bool ninja_write_alias_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *ctx); #endif muon-v0.5.0/include/backend/ninja/coverage.h0000644000175000017500000000055015041716357017726 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_COVERAGE_H #define MUON_BACKEND_NINJA_COVERAGE_H #include "lang/workspace.h" bool ninja_coverage_is_enabled_and_available(struct workspace *wk); void ninja_coverage_write_targets(struct workspace *wk, FILE *out); #endif muon-v0.5.0/include/backend/output.h0000644000175000017500000000176615041716357016406 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_OUTPUT_H #define MUON_BACKEND_OUTPUT_H #include "lang/workspace.h" struct output_path { const char *private_dir, *summary, *tests, *install, *compiler_check_cache, *option_info, *introspect_dir, *meson_private_dir, *debug_log; struct { const char *projectinfo; const char *targets; const char *benchmarks; const char *buildoptions; const char *buildsystem_files; const char *compilers; const char *dependencies; const char *scan_dependencies; const char *installed; const char *install_plan; const char *machines; const char *tests; } introspect_file; }; extern const struct output_path output_path; typedef bool((*with_open_callback)(struct workspace *wk, void *ctx, FILE *out)); FILE *output_open(const char *dir, const char *name); bool with_open(const char *dir, const char *name, struct workspace *wk, void *ctx, with_open_callback cb); #endif muon-v0.5.0/include/rpmvercmp.h0000644000175000017500000000037515041716357015465 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_RPMVERCMP_H #define MUON_RPMVERCMP_H #include "lang/object.h" int8_t rpmvercmp(const struct str *a, const struct str *b); #endif muon-v0.5.0/.gitignore0000644000175000017500000000050515041716357013641 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only .meson-subproject-wrap-hash.txt subprojects/glad subprojects/glfw3 subprojects/imgui subprojects/libarchive-3.7.9 subprojects/meson-docs subprojects/meson-tests subprojects/packagecache subprojects/pkgconf subprojects/tracy muon-v0.5.0/.clang-format0000644000175000017500000000614215041716357014227 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only --- AccessModifierOffset: -4 AlignAfterOpenBracket: DontAlign AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: AllDefinitions AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: true AfterNamespace: true AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: All BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakStringLiterals: false ColumnLimit: 120 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false ForEachMacros: - 'OPTSTART' - 'obj_array_flat_for_' - 'obj_array_flat_for' - 'obj_array_for_array' - 'obj_array_for_array_' - 'obj_array_for' - 'obj_dict_for' IncludeBlocks: Preserve IncludeCategories: - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: false IndentPPDirectives: None IndentWidth: 8 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 8 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true # Taken from git's rules PenaltyBreakAssignment: 10 PenaltyBreakBeforeFirstCallParameter: 30 PenaltyBreakComment: 10 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 10 PenaltyExcessCharacter: 100 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: false SortIncludes: true SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatementsExceptForEachMacros SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp03 TabWidth: 8 UseTab: Always muon-v0.5.0/tools/0002755000175000017500000000000015041716357013013 5ustar buildbuildmuon-v0.5.0/tools/meson.build0000644000175000017500000000040615041716357015153 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only embedder = executable( 'embedder', 'embedder.c', include_directories: include_dir, c_args: c_args, link_args: link_args, native: true, ) muon-v0.5.0/tools/update_tests.sh0000755000175000017500000000771015041716357016061 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eu cmp_() { cmp "$@" 2>/dev/null 1>&2 } remove_test_case_blocks_() { awk ' BEGIN { skip=0 } /testcase expect_error/ { skip += 1 } /endtestcase/ { skip -= 1 } { if (skip > 0 || /endtestcase/) { printf "# " } print $0 } ' } format_() { remove_test_case_blocks_ < "$1" | $muon fmt - } copy_() { src_file="$1" dest="$2" meson_source="$3" printf "%s \033[32m->\033[0m %s\n" "$src_file" "$dest" 1>&2 if [ "$meson_source" ]; then format_ "$src_file" > "$dest" else cp "$src_file" "$dest" fi } mkdir_dest_parent_() { dest_dir="$1" relative="$2" dirname="${relative%/*}" if [ "$dirname" = "$relative" ]; then mkdir -p "$dest_dir" else mkdir -p "$dest_dir/$dirname" fi } handle_file_mod_() { d="$1" status="${d%% *}" rename_dest="" file="${d#* }" if [ "${status##R}" != "$status" ]; then status="R" a="${file% *}" b="${d##* }" file="$b" rename_dest="$a" src_file="$meson_root/$rename_dest" basename="${rename_dest##*/}" old_relative="${file##"$src_dir"/}" old_file="$dest_dir/$old_relative" relative="${rename_dest##"$src_dir"/}" dest="$dest_dir/$relative" else src_file="$meson_root/$file" basename="${file##*/}" relative="${file##"$src_dir"/}" dest="$dest_dir/$relative" old_relative="$relative" old_file="$dest_dir/$old_relative" fi meson_source="" if [ "$basename" = "meson.build" ] || [ "$basename" = "meson_options.txt" ]; then meson_source=1 case "$relative" in "179 escape and unicode/meson.build") meson_source="" ;; esac fi if [ ! -f "$dest" ] && [ "$status" != "R" ]; then if [ "$dryrun" ]; then printf "\033[32mnew\033[0m %s\n" "$relative" else mkdir_dest_parent_ "$dest_dir" "$relative" copy_ "$src_file" "$dest" "$meson_source" fi else changed=1 if [ "$meson_source" ]; then if format_ "$src_file" | cmp_ "$old_file"; then changed="" fi else if cmp_ "$src_file" "$old_file"; then changed="" fi fi if [ "$changed" ] || [ "$status" = "R" ]; then if [ "$dryrun" ]; then msg="" if [ "$changed" ]; then msg="\033[35mmodified\033[0m " fi if [ "$status" = "R" ]; then msg="$msg\033[36mrenamed\033[0m " printf "${msg}%s -> %s\n" "$old_relative" "$relative" else printf "${msg}%s\n" "$relative" fi else if [ "$status" = "R" ]; then rm "$old_file" mkdir_dest_parent_ "$dest_dir" "$relative" fi copy_ "$src_file" "$dest" "$meson_source" fi fi fi } copy_tests_() { src_dir="$1" dest_dir="$tests_root/$2" git -C "$meson_root" ls-files -- "$src_dir" | while IFS="" read -r d; do handle_file_mod_ "A $d" done } copy_tests_from_rev_() { src_dir="$1" dest_dir="$tests_root/$2" rev="$3" git -C "$meson_root" diff --name-status HEAD "$rev" -- "$src_dir" | while IFS="" read -r d; do handle_file_mod_ "$d" done } rev="1.5.1" usage() { cat < opts: -f - perform the copy (the default is dryrun only) -c - copy all tests, not just the ones modified since $rev -h - show this message EOF } muon=muon dryrun=1 copy_fun=copy_tests_from_rev_ if [ $# -ge 1 ]; then while getopts "fch" opt; do case "$opt" in f) dryrun="" ;; c) copy_fun="copy_tests_" ;; h) usage exit ;; ?) die "invalid option" ;; esac done shift $((OPTIND-1)) fi if [ $# -lt 1 ]; then usage exit 1 fi meson_root="$1" tests_root="$2" "$copy_fun" "test cases/common" "common" "$rev" "$copy_fun" "test cases/frameworks/6 gettext" "frameworks/6 gettext" "$rev" "$copy_fun" "test cases/frameworks/7 gnome" "frameworks/7 gnome" "$rev" "$copy_fun" "test cases/keyval" "keyval" "$rev" "$copy_fun" "test cases/nasm" "nasm" "$rev" "$copy_fun" "test cases/native" "native" "$rev" "$copy_fun" "test cases/python" "python" "$rev" "$copy_fun" "test cases/objc" "objc" "$rev" "$copy_fun" "test cases/objcpp" "objcpp" "$rev" muon-v0.5.0/tools/embedder.c0000644000175000017500000000226115041716357014725 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include static bool embed(const char *path, const char *embedded_name) { int c; uint32_t i = 0; FILE *f; if (!(f = fopen(path, "rb"))) { fprintf(stderr, "couldn't open '%s' for reading\n", path); return false; } printf("{ .name = \"%s\", .src = { .label = \"%s\", .type = source_type_embedded, .src = (char []){\n", embedded_name, embedded_name); while ((c = fgetc(f)) != EOF) { // output signed char printf("%hhd, ", (signed char)c); if ((i % 14) == 0) { fputs("\n", stdout); } ++i; } fprintf(stdout, "0x0\n}, .len = %d } },\n", i); return fclose(f) == 0; } int main(int argc, char *const argv[]) { assert(argc >= 1); assert(((argc - 1) & 1) == 0 && "you must pass an even number of arguments"); printf("uint32_t embedded_len = %d;\n" "\n" "static struct embedded_file embedded[] = {\n", (argc - 1) / 2); uint32_t i; for (i = 1; i < (uint32_t)argc; i += 2) { if (!embed(argv[i], argv[i + 1])) { return 1; } } printf("};\n"); } muon-v0.5.0/tools/gh-pr.meson0000644000175000017500000000174215041716357015075 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only curl = import('curl') fs = import('fs') func parse_opts() -> dict[any] opts = {} handlers = {} handlers.p = { 'required': true, 'desc': 'Specify the github PR #', 'action': func(arg str) opts.pr = arg endfunc, } rest = import('getopt').getopt(argv, handlers) assert(rest.length() == 0, 'No trailing arguments permitted') return opts endfunc func main() opts = parse_opts() pr = opts.pr url = f'https://github.com/muon-build/muon/pull/@pr@.patch' patch = curl.fetch(url) sep = '---\n' parts = patch.split(sep) assert(parts.length() >= 2, 'result does not contain separator') parts[0] += f'closes #@pr@\n' patch = sep.join(parts) file = f'gh-@pr@.patch' fs.write(file, patch) run_command( ['git', 'am', '-3', file], check: true, ) endfunc main() muon-v0.5.0/tools/ci/0002755000175000017500000000000015041716357013406 5ustar buildbuildmuon-v0.5.0/tools/ci/deploy.sh0000755000175000017500000000047415041716357015244 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux if [ ! -d ~/.ssh ]; then exit 0 elif [ "${GH_DEPLOY:-}" ]; then : fi sshopts="-o StrictHostKeyChecking=no -p 2975" dest=$1 shift rsync --rsh="ssh $sshopts" "$@" deploy@mochiro.moe:muon"$dest" muon-v0.5.0/tools/ci/prepare_tarball.sh0000755000175000017500000000160615041716357017105 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eu setup_tmp_dirs_() { rm -rf "$tmp_dir" mkdir -p "$tmp_dir" "$tmp_dir/i" } git_archive_() { dir="$1" out="$2" rm -f "$out" git -C "$dir" archive --format=tar -o "$out" HEAD } archive_and_merge_() { i=0 for dir in "$@"; do dest="$tmp_dir/i/$i.tar" git_archive_ "$dir" "$dest" mkdir -p "$tmp_dir/$final/$dir" tar x -C "$tmp_dir/$final/$dir" -f "$dest" done } create_final_archive_() { final_out="$tmp_dir/$final.tar.gz" rm -f "$tmp_dir/$final.tar" "$final_out" tar c -C "$tmp_dir" -f "$tmp_dir/$final.tar" "$final" gzip "$tmp_dir/$final.tar" mv "$final_out" . echo "wrote $final.tar.gz" } tmp_dir="$PWD/tarball_tmp" final="$1" setup_tmp_dirs_ archive_and_merge_ "." "subprojects/meson-tests" "subprojects/meson-docs" create_final_archive_ rm -rf "$tmp_dir" muon-v0.5.0/tools/ci/version.sh.in0000644000175000017500000000017115041716357016031 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only version="v@version@" muon-v0.5.0/tools/ci/push_to_gh_mirror.sh0000755000175000017500000000171115041716357017474 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux if [ ! -d ~/.ssh ]; then exit 0 fi cat >> ~/.ssh/config <> branches_to_push.txt fi done git remote add github git@github.com:muon-build/muon.git # We don't want to use --mirror here because that implies --prune which will # remove any branches that might have been created on the gh repo only. # shellcheck disable=SC2046 # Intended splitting of branches git push --force --tags github $(cat branches_to_push.txt) muon-v0.5.0/tools/ci/bootstrap.sh0000755000175000017500000000036315041716357015762 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux build="$1" shift ./bootstrap.sh "$build" build/muon-bootstrap setup "$@" "$build" build/muon-bootstrap -C "$build" samu muon-v0.5.0/tools/ci/prepare_release_docs.sh0000755000175000017500000000047515041716357020117 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux build=$1 cd "$build/doc" mkdir man cp meson.build.5 muon.1 man tar cvf man.tar man/* gzip man.tar mkdir docs mv reference.html man.tar.gz docs rm -r website/version_info.py website/__pycache__ muon-v0.5.0/tools/ci/ref_is_release_branch.sh0000755000175000017500000000041215041716357020224 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eu ref="$1" if [ "$ref" = "refs/heads/master" ]; then exit 0 elif echo "$ref" | grep -q '^refs/heads/[0-9]\+\.[0-9]\+$'; then exit 0 else exit 1 fi muon-v0.5.0/tools/ci/release.sh0000755000175000017500000000206315041716357015364 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eu build="$1" build_small="$2" version="" # shellcheck disable=SC1091 . "$build/version.sh" branch_name="$GIT_REF" echo "version: $version, branch_name: '$branch_name'" tools/ci/prepare_binary.sh "$build" "$version-amd64-linux-static" tools/ci/prepare_binary.sh "$build_small" "$version-amd64-linux-static-small" tools/ci/prepare_release_docs.sh "$build" tools/ci/prepare_tarball.sh "muon-$version" # only allow master and release branches through if tools/ci/ref_is_release_branch.sh "$branch_name"; then : else exit 0 fi tools/ci/deploy.sh "/releases/$version" -r --delete build/doc/docs tools/ci/deploy.sh "/releases/$version" \ "build/muon-$version-amd64-linux-static" \ "build-small/muon-$version-amd64-linux-static-small" \ "muon-$version.tar.gz" # only allow master through if [ "$branch_name" = "refs/heads/master" ]; then : else exit 0 fi tools/ci/deploy.sh / -r --delete build/doc/website tools/ci/deploy.sh / -r --delete build/doc/book muon-v0.5.0/tools/ci/alpine.sh0000755000175000017500000001575215041716357015225 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eu die_() { printf "error: %s\n" "$@" >&2 exit 1 } sudo_() { if [ "$cfg_sudo" ]; then sudo "$@" else "$@" fi } ################################################################################ # setup ################################################################################ queue_package_() { packages="$packages $1" } add_repository_() { printf "%s\n" "$1" | sudo_ tee -a /etc/apk/repositories } setup_repositories_() { if [ "$cfg_tcc" ]; then add_repository_ "http://dl-cdn.alpinelinux.org/alpine/edge/community" fi if [ "$cfg_website" ]; then add_repository_ "http://dl-cdn.alpinelinux.org/alpine/v${alpine_version}/community" fi } install_packages_() { packages="" queue_package_ build-base queue_package_ openssh queue_package_ curl-dev queue_package_ libarchive-dev queue_package_ pkgconf-dev queue_package_ python3 # for meson-tests, meson-docs, and parts of the website queue_package_ linux-headers # used in a few project tests queue_package_ py3-yaml # for meson-docs if [ "$cfg_website" ]; then queue_package_ scdoc # for meson.build.5 and muon.1 queue_package_ mandoc # for html man pages queue_package_ mdbook # for book fi if [ "$cfg_reuse" ]; then queue_package_ reuse # for licensing compliance fi # alternative c compilers if [ "$cfg_clang" ]; then queue_package_ clang fi if [ "$cfg_tcc" ]; then queue_package_ tcc queue_package_ tcc-libs-static # tcc 0.9.27_git20220323-r1 is broken without this fi # for static builds queue_package_ acl-static queue_package_ brotli-static queue_package_ bzip2-static queue_package_ curl-static queue_package_ expat-static queue_package_ libarchive-static queue_package_ lz4-static queue_package_ nghttp2-static queue_package_ openssl-libs-static queue_package_ xz-static queue_package_ zlib-static queue_package_ zstd-static queue_package_ libidn2-static queue_package_ libunistring-static queue_package_ libpsl-static # for releases queue_package_ rsync sudo_ apk add $packages } ################################################################################ # steps ################################################################################ step_reuse_lint_() { reuse lint } step_push_to_gh_mirror_() { tools/ci/push_to_gh_mirror.sh } step_trigger_solaris_ci_() { if [ -d ~/.ssh ]; then tools/ci/solaris11.sh submit fi } enabled_if_() { if [ "$1" ]; then printf "enabled" else printf "disabled" fi } step_build_gcc_() { CC=gcc tools/ci/bootstrap.sh build \ -Dbuildtype=release \ -Dwerror=true \ -Dlibarchive=enabled \ -Dlibcurl=enabled \ -Dlibpkgconf=enabled \ -Dmeson-docs=enabled \ -Dmeson-tests=enabled \ -Dman-pages="$(enabled_if_ "$cfg_website")" \ -Dwebsite="$(enabled_if_ "$cfg_website")" \ -Dstatic=true } step_build_small_() { CC=gcc build/muon setup \ -Dbuildtype=minsize \ -Dstatic=true \ -Dlibarchive=disabled \ -Dlibcurl=disabled \ -Dlibpkgconf=disabled \ build-small build/muon -C build-small samu } step_build_tcc_() { CC=tcc tools/ci/bootstrap.sh build-tcc } step_test_gcc_() { CC=gcc build/muon -C build test -d dots } step_test_clang_() { CC=clang build/muon -C build test -d dots } step_prepare_release_() { # shellcheck disable=SC1091 . "build/version.sh" printf "preparing release for version: %s, branch_name: '%s'\n" "$version" "$branch_name" tools/ci/prepare_binary.sh build "$version-$arch-linux" tools/ci/prepare_binary.sh build-small "$version-$arch-linux-small" if [ "$cfg_website" ]; then tools/ci/prepare_release_docs.sh "build" fi tools/ci/prepare_tarball.sh "muon-$version" } step_deploy_artifacts_() { tools/ci/deploy.sh "/releases/$version" \ "build/muon-$version-$arch-linux" \ "build-small/muon-$version-$arch-linux-small" \ "muon-$version.tar.gz" tools/ci/deploy.sh "/releases/$version" -r --delete build/doc/docs tools/ci/deploy.sh "/releases/$version" } step_deploy_website_() { tools/ci/deploy.sh / -r --delete build/doc/website tools/ci/deploy.sh / -r --delete build/doc/book } step_copy_container_results_() { cp \ "build/version.sh" \ "build/muon-$version-$arch-linux" \ "build-small/muon-$version-$arch-linux-small" \ "/home/output" } ################################################################################ # main ################################################################################ queue_step_() { steps="$steps step_${1}_" } run_steps_() { for step in "$@"; do printf "###################################\n" printf "# %-31s #\n" "$step" printf "###################################\n" "$step" done } container_() { rm -rf "build-docker" exec docker run \ --rm \ -i \ -e "MUON_RUNNER_CONTAINER=1" \ -v "$PWD:/home/muon-src:ro" \ -v "$PWD/build-docker:/home/output" \ -w "/home/muon" \ "alpine:$alpine_version" \ "/home/muon-src/tools/ci/alpine.sh" } alpine_version="3.21" version="" arch="$(arch)" if [ "$arch" = "x86_64" ]; then arch=amd64 fi runner="" branch_name="" cfg_sudo="" cfg_tcc="" cfg_clang="" cfg_website="" cfg_reuse="" cfg_git_mirror="" cfg_trigger_solaris_ci="" cfg_deploy="" if [ "${JOB_ID:-}" ]; then runner="builds.sr.ht" branch_name="$GIT_REF" cfg_sudo=1 cfg_tcc=1 cfg_clang=1 cfg_website=1 cfg_reuse=1 cfg_git_mirror=1 cfg_trigger_solaris_ci=1 cfg_deploy=1 elif [ "${MUON_RUNNER_CONTAINER:-}" ]; then runner="container" branch_name="" apk add git git config --global --add safe.directory /home/muon/../muon-src/.git git config --global init.defaultBranch master git clone ../muon-src . fi if [ $# -ge 1 ]; then while getopts "hc" opt; do case "$opt" in h) usage_ ;; c) container_ ;; ?) die "invalid option" ;; esac done shift $((OPTIND-1)) fi printf "\033[34mrunner: %s, arch: %s, branch_name: %s\033[0m\n" "$runner" "$arch" "$branch_name" setup_repositories_ install_packages_ steps="" # Since we are building static binaries, set this in the environment so muon # doesn't complain export PKG_CONFIG_PATH=/usr/lib/pkgconfig # housekeeping steps ##################### if [ "$cfg_reuse" ]; then queue_step_ "reuse_lint" fi if [ "$cfg_git_mirror" ]; then queue_step_ "push_to_gh_mirror" fi if [ "$cfg_trigger_solaris_ci" ]; then queue_step_ "trigger_solaris_ci" fi # building steps ##################### queue_step_ "build_gcc" if [ "$cfg_tcc" ]; then queue_step_ "build_tcc" fi queue_step_ "build_small" # testing steps ##################### queue_step_ "test_gcc" if [ "$cfg_clang" ]; then queue_step_ "test_clang" fi # release steps ##################### queue_step_ "prepare_release" if [ "$cfg_deploy" ]; then # only allow master and release branches through if tools/ci/ref_is_release_branch.sh "$branch_name"; then : queue_step_ "deploy_artifacts" fi if [ "$cfg_website" ] && [ "$branch_name" = "refs/heads/master" ]; then : queue_step_ "deploy_website" fi fi if [ "$runner" = "container" ]; then queue_step_ "copy_container_results" fi run_steps_ $steps muon-v0.5.0/tools/ci/solaris11.sh0000755000175000017500000000216715041716357015567 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eu build_log="$HOME/build_log.txt" send_status() { echo "$1" > status rsync "$build_log" status deploy@mochiro.moe:muon/ci/solaris11/ } build() { date uname -a set -x git clone https://git.sr.ht/~lattis/muon "$tmp" cd "$tmp" git checkout "$1" export CC=gcc export CFLAGS="-D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__" ./bootstrap.sh build build/muon-bootstrap setup build build/muon-bootstrap -C build samu build/muon-bootstrap -C build test -d dots -s lang } submit() { cat tools/ci/solaris11.sh | ssh \ -oPubkeyAcceptedKeyTypes=+ssh-rsa \ -oStrictHostKeyChecking=no \ -oHostKeyAlgorithms=ssh-rsa \ lattis@gcc211.fsffrance.org \ nohup /bin/sh -s receive "$(git rev-parse @)" } _receive() { echo "build $1 received, logging to $build_log" exec >"$build_log" exec 2>&1 send_status pending tmp="ci/muon/$(date +%s)" mkdir -p "$tmp" if build "$1"; then send_status ok else send_status failed fi cd rm -rf "$tmp" } receive() { _receive "$@"& } command="$1" shift $command "$@" muon-v0.5.0/tools/ci/prepare_binary.sh0000755000175000017500000000027415041716357016750 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux build=$1 suffix=$2 cd "$build" strip muon mv muon "muon-$suffix" muon-v0.5.0/tools/generate_test_check_script.py0000755000175000017500000000224215041716357020740 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only #!/usr/bin/env python3 import glob import json import os import shlex import stat import sys def die(msg): print(msg) sys.exit(1) def write_check_script(dat, path): s = "#!/bin/sh\nset -eux\n" for e in dat: if e["type"] == "dir": test = "-d" elif e["type"] == "file": test = "-f" else: # TODO: handle other file types continue file = shlex.quote(e["file"]) s += f'test {test} "$DESTDIR"/{file}\n' with open(path, "w") as f: f.write(s) os.chmod(path, 0o755) def main(argc, argv): if argc < 2: die(f"usage: {argv[0]} ") for d in glob.glob(f"{argv[1]}/*"): test_json = d + "/test.json" check_script = d + "/check.sh" if not os.access(test_json, os.F_OK): continue with open(test_json) as f: dat = json.load(f) if "installed" in dat: write_check_script(dat["installed"], check_script) if __name__ == "__main__": main(len(sys.argv), sys.argv) muon-v0.5.0/tools/bootstrap_ninja.sh0000755000175000017500000000030315041716357016540 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only echo "This tool is no longer necessary, muon comes with a bundled samu by default." muon-v0.5.0/LICENSES/0002755000175000017500000000000015041716357013060 5ustar buildbuildmuon-v0.5.0/LICENSES/Python-2.0.txt0000644000175000017500000002230315041716357015375 0ustar buildbuildPYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. CNRI OPEN SOURCE LICENSE AGREEMENT (for Python 1.6b1) IMPORTANT: PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY. BY CLICKING ON "ACCEPT" WHERE INDICATED BELOW, OR BY COPYING, INSTALLING OR OTHERWISE USING PYTHON 1.6, beta 1 SOFTWARE, YOU ARE DEEMED TO HAVE AGREED TO THE TERMS AND CONDITIONS OF THIS LICENSE AGREEMENT. 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6, beta 1 software in source or binary form and its associated documentation, as released at the www.python.org Internet site on August 4, 2000 ("Python 1.6b1"). 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6b1 alone or in any derivative version, provided, however, that CNRIs License Agreement is retained in Python 1.6b1, alone or in any derivative version prepared by Licensee. Alternately, in lieu of CNRIs License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6, beta 1, is made available subject to the terms and conditions in CNRIs License Agreement. This Agreement may be located on the Internet using the following unique, persistent identifier (known as a handle): 1895.22/1011. This Agreement may also be obtained from a proxy server on the Internet using the URL:http://hdl.handle.net/1895.22/1011". 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6b1 or any part thereof, and wants to make the derivative work available to the public as provided herein, then Licensee hereby agrees to indicate in any such work the nature of the modifications made to Python 1.6b1. 4. CNRI is making Python 1.6b1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6b1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING PYTHON 1.6b1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. This License Agreement shall be governed by and interpreted in all respects by the law of the State of Virginia, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6b1, Licensee agrees to be bound by the terms and conditions of this License Agreement. ACCEPT CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. muon-v0.5.0/LICENSES/MIT.txt0000644000175000017500000000206615041716357014254 0ustar buildbuildMIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. muon-v0.5.0/LICENSES/Unlicense.txt0000644000175000017500000000227315041716357015550 0ustar buildbuildThis is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to muon-v0.5.0/LICENSES/GPL-3.0-only.txt0000644000175000017500000010356215041716357015525 0ustar buildbuildGNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License†refers to version 3 of the GNU General Public License. “Copyright†also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program†refers to any copyrightable work licensed under this License. Each licensee is addressed as “youâ€. “Licensees†and “recipients†may be individuals or organizations. To “modify†a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version†of the earlier work or a work “based on†the earlier work. A “covered work†means either the unmodified Program or a work based on the Program. To “propagate†a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey†a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices†to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code†for a work means the preferred form of the work for making modifications to it. “Object code†means any non-source form of a work. A “Standard Interface†means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries†of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Componentâ€, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source†for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all noticesâ€. c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate†if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product†is either (1) a “consumer productâ€, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used†refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information†for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions†are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions†within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction†is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor†is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor versionâ€. A contributor's “essential patent claims†are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control†includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license†is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant†such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying†means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory†if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS†WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright†line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about boxâ€. You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer†for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . muon-v0.5.0/LICENSES/Apache-2.0.txt0000644000175000017500000002405015041716357015276 0ustar buildbuildApache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. muon-v0.5.0/src/0002755000175000017500000000000015041716357012442 5ustar buildbuildmuon-v0.5.0/src/compilers.c0000644000175000017500000016233515041716357014613 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Owen Rafferty * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "buf_size.h" #include "compilers.h" #include "error.h" #include "functions/string.h" #include "guess.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "lang/workspace.h" #include "log.h" #include "machines.h" #include "options.h" #include "platform/assert.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "sha_256.h" obj compiler_check_cache_key(struct workspace *wk, const struct compiler_check_cache_key *key) { uint32_t argstr_len; { uint32_t i = 0; const char *p = key->argstr; for (;; ++p) { if (!p[0]) { if (++i >= key->argc) { break; } } } argstr_len = p - key->argstr; } enum { sha_idx_argstr = 0, sha_idx_ver = sha_idx_argstr + 32, sha_idx_src = sha_idx_ver + 32, sha_data_len = sha_idx_src + 32 }; uint8_t sha_data[sha_data_len] = { 0 }; calc_sha_256(&sha_data[sha_idx_argstr], key->argstr, argstr_len); if (key->comp && key->comp->ver) { const struct str *ver = get_str(wk, key->comp->ver); calc_sha_256(&sha_data[sha_idx_ver], ver->s, ver->len); } if (key->src) { calc_sha_256(&sha_data[sha_idx_src], key->src, strlen(key->src)); } uint8_t sha[32]; calc_sha_256(sha, sha_data, sha_data_len); /* LLOG_I("sha: "); */ /* uint32_t i; */ /* for (i = 0; i < 32; ++i) { */ /* log_plain("%02x", sha_res[i]); */ /* } */ /* log_plain("\n"); */ return make_strn(wk, (const char *)sha, 32); } bool compiler_check_cache_get(struct workspace *wk, obj key, struct compiler_check_cache_value *val) { obj arr; if (obj_dict_index(wk, wk->compiler_check_cache, key, &arr)) { obj cache_res; cache_res = obj_array_index(wk, arr, 0); val->success = get_obj_bool(wk, cache_res); val->value = obj_array_index(wk, arr, 1); return true; } else { return false; } } void compiler_check_cache_set(struct workspace *wk, obj key, const struct compiler_check_cache_value *val) { if (!key) { return; } obj arr; if (obj_dict_index(wk, wk->compiler_check_cache, key, &arr)) { obj_array_set(wk, arr, 0, make_obj_bool(wk, val->success)); obj_array_set(wk, arr, 1, val->value); } else { arr = make_obj(wk, obj_array); obj_array_push(wk, arr, make_obj_bool(wk, val->success)); obj_array_push(wk, arr, val->value); obj_dict_set(wk, wk->compiler_check_cache, key, arr); } } #define TOOLCHAIN_NAME(_, public_id, id) { public_id, id }, const struct toolchain_id compiler_type_name[] = { FOREACH_TOOLCHAIN_COMPILER_TYPE(TOOLCHAIN_NAME) }; const struct toolchain_id linker_type_name[] = { FOREACH_TOOLCHAIN_LINKER_TYPE(TOOLCHAIN_NAME) }; const struct toolchain_id static_linker_type_name[] = { FOREACH_TOOLCHAIN_STATIC_LINKER_TYPE(TOOLCHAIN_NAME) }; #undef TOOLCHAIN_CASE static const struct toolchain_id toolchain_component_name[] = { [toolchain_component_compiler] = { "compiler", "compiler" }, [toolchain_component_linker] = { "linker", "linker" }, [toolchain_component_static_linker] = { "static_linker", "static_linker" }, }; static bool toolchain_id_lookup(const char *name, const struct toolchain_id names[], uint32_t len, uint32_t *res) { uint32_t i; for (i = 0; i < len; ++i) { if (strcmp(name, names[i].id) == 0) { *res = i; return true; } } return false; } const char * compiler_type_to_s(enum compiler_type t) { return compiler_type_name[t].public_id; } bool compiler_type_from_s(const char *name, uint32_t *res) { return toolchain_id_lookup(name, compiler_type_name, ARRAY_LEN(compiler_type_name), res); } const char * linker_type_to_s(enum linker_type t) { return linker_type_name[t].public_id; } bool linker_type_from_s(const char *name, uint32_t *res) { return toolchain_id_lookup(name, linker_type_name, ARRAY_LEN(linker_type_name), res); } static const char * static_linker_type_to_s(enum static_linker_type t) { return static_linker_type_name[t].public_id; } bool static_linker_type_from_s(const char *name, uint32_t *res) { return toolchain_id_lookup(name, static_linker_type_name, ARRAY_LEN(static_linker_type_name), res); } const char * toolchain_component_to_s(enum toolchain_component comp) { return toolchain_component_name[comp].public_id; } bool toolchain_component_from_s(const char *name, uint32_t *res) { return toolchain_id_lookup(name, toolchain_component_name, ARRAY_LEN(toolchain_component_name), res); } const struct toolchain_id * toolchain_component_type_to_s(enum toolchain_component comp, uint32_t val) { const struct toolchain_id *ids = 0; switch (comp) { case toolchain_component_compiler: ids = compiler_type_name; break; case toolchain_component_linker: ids = linker_type_name; break; case toolchain_component_static_linker: ids = static_linker_type_name; break; } return &ids[val]; } static const char *compiler_language_names[compiler_language_count] = { [compiler_language_null] = "null", [compiler_language_c] = "c", [compiler_language_c_hdr] = "c_hdr", [compiler_language_cpp] = "cpp", [compiler_language_cpp_hdr] = "cpp_hdr", [compiler_language_objc_hdr] = "objc_hdr", [compiler_language_objcpp_hdr] = "objcpp_hdr", [compiler_language_c_obj] = "c_obj", [compiler_language_objc] = "objc", [compiler_language_objcpp] = "objcpp", [compiler_language_assembly] = "assembly", [compiler_language_llvm_ir] = "llvm_ir", [compiler_language_nasm] = "nasm", }; static const char *compiler_language_gcc_names[compiler_language_count] = { [compiler_language_c] = "c", [compiler_language_c_hdr] = "c-header", [compiler_language_cpp] = "c++", [compiler_language_cpp_hdr] = "c++-header", [compiler_language_objc] = "objective-c", [compiler_language_objc_hdr] = "objective-c-header", [compiler_language_objcpp] = "objective-c++", [compiler_language_objcpp_hdr] = "objective-c++-header", }; const char * compiler_language_to_s(enum compiler_language l) { assert(l < compiler_language_count); return compiler_language_names[l]; } bool s_to_compiler_language(const char *_s, enum compiler_language *l) { const struct str s = STRL(_s); uint32_t i; for (i = 0; i < compiler_language_count; ++i) { if (str_eqli(&s, &STRL(compiler_language_names[i]))) { *l = i; return true; } } return false; } enum compiler_language compiler_language_to_hdr(enum compiler_language lang) { switch (lang) { case compiler_language_c: return compiler_language_c_hdr; case compiler_language_cpp: return compiler_language_cpp_hdr; case compiler_language_objc: return compiler_language_objc_hdr; case compiler_language_objcpp: return compiler_language_objcpp_hdr; default: UNREACHABLE_RETURN; } } static const char *compiler_language_exts[compiler_language_count][10] = { [compiler_language_c] = { "c" }, [compiler_language_c_hdr] = { "h" }, [compiler_language_cpp] = { "cc", "cpp", "cxx", "C" }, [compiler_language_cpp_hdr] = { "hh", "hpp", "hxx" }, [compiler_language_c_obj] = { "o", "obj" }, [compiler_language_objc] = { "m", "M" }, [compiler_language_objcpp] = { "mm" }, [compiler_language_assembly] = { "S", "s" }, [compiler_language_llvm_ir] = { "ll" }, [compiler_language_nasm] = { "asm" }, }; bool filename_to_compiler_language(const char *str, enum compiler_language *l) { uint32_t i, j; const char *ext; if (!(ext = strrchr(str, '.'))) { return false; } ++ext; for (i = 0; i < compiler_language_count; ++i) { for (j = 0; compiler_language_exts[i][j]; ++j) { if (strcmp(ext, compiler_language_exts[i][j]) == 0) { *l = i; return true; } } } return false; } const char * compiler_language_extension(enum compiler_language l) { return compiler_language_exts[l][0]; } enum compiler_language coalesce_link_languages(enum compiler_language cur, enum compiler_language new) { switch (new) { case compiler_language_null: case compiler_language_c_hdr: case compiler_language_cpp_hdr: case compiler_language_objc_hdr: case compiler_language_objcpp_hdr: case compiler_language_llvm_ir: break; case compiler_language_assembly: if (!cur) { return compiler_language_assembly; } break; case compiler_language_nasm: case compiler_language_c: case compiler_language_c_obj: case compiler_language_objc: if (!cur) { return compiler_language_c; } break; case compiler_language_cpp: case compiler_language_objcpp: if (!cur || cur == compiler_language_c || cur == compiler_language_assembly) { return compiler_language_cpp; } break; case compiler_language_count: UNREACHABLE; } return cur; } static bool run_cmd_arr(struct workspace *wk, struct run_cmd_ctx *cmd_ctx, obj cmd_arr, const char *arg) { obj args; obj_array_dup(wk, cmd_arr, &args); obj_array_push(wk, args, make_str(wk, arg)); const char *argstr; uint32_t argc; join_args_argstr(wk, &argstr, &argc, args); obj cache_key = compiler_check_cache_key(wk, &(struct compiler_check_cache_key){ .argstr = argstr, .argc = argc, }); struct compiler_check_cache_value cache_val = { 0 }; if (compiler_check_cache_get(wk, cache_key, &cache_val)) { if (!cache_val.success) { return false; } obj status, err, out; status = obj_array_index(wk, cache_val.value, 0); out = obj_array_index(wk, cache_val.value, 1); err = obj_array_index(wk, cache_val.value, 2); cmd_ctx->status = get_obj_number(wk, status); const struct str *err_str = get_str(wk, err), *out_str = get_str(wk, out); cmd_ctx->out = (struct tstr){ .buf = (char *)out_str->s, .len = out_str->len, .cap = out_str->len, .flags = tstr_flag_overflown, .s = out, }; cmd_ctx->err = (struct tstr){ .buf = (char *)err_str->s, .len = err_str->len, .cap = err_str->len, .flags = tstr_flag_overflown, .s = err, }; return true; } bool success = true; if (!run_cmd(cmd_ctx, argstr, argc, NULL, 0)) { L("failed to run command %s", argstr); run_cmd_print_error(cmd_ctx, log_debug); run_cmd_ctx_destroy(cmd_ctx); return false; } cache_val.success = success; cache_val.value = make_obj(wk, obj_array); obj status; status = make_obj(wk, obj_number); set_obj_number(wk, status, cmd_ctx->status); obj_array_push(wk, cache_val.value, status); obj_array_push(wk, cache_val.value, make_strn(wk, cmd_ctx->out.buf, cmd_ctx->out.len)); obj_array_push(wk, cache_val.value, make_strn(wk, cmd_ctx->err.buf, cmd_ctx->err.len)); compiler_check_cache_set(wk, cache_key, &cache_val); return success; } static const char * guess_version_arg(struct workspace *wk, bool msvc_like) { if (msvc_like) { return "/?"; } else { return "--version"; } } static bool compiler_detect_c_or_cpp(struct workspace *wk, obj cmd_arr, obj comp_id) { bool msvc_like = obj_array_in(wk, cmd_arr, make_str(wk, "cl")); // helpful: mesonbuild/compilers/detect.py:350 struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, cmd_arr, guess_version_arg(wk, msvc_like))) { run_cmd_ctx_destroy(&cmd_ctx); return false; } enum compiler_type type; bool unknown = true; obj ver; if (cmd_ctx.status != 0) { goto detection_over; } if (str_containsi(&TSTR_STR(&cmd_ctx.out), &STR("clang"))) { if (str_contains(&TSTR_STR(&cmd_ctx.out), &STR("Apple"))) { type = compiler_apple_clang; } else if (strstr(cmd_ctx.out.buf, "CL.EXE COMPATIBILITY")) { type = compiler_clang_cl; } else { type = compiler_clang; } } else if (strstr(cmd_ctx.out.buf, "Free Software Foundation")) { type = compiler_gcc; } else if (strstr(cmd_ctx.out.buf, "Microsoft") || strstr(cmd_ctx.err.buf, "Microsoft")) { type = compiler_msvc; } else { goto detection_over; } if (!guess_version(wk, (type == compiler_msvc) ? cmd_ctx.err.buf : cmd_ctx.out.buf, &ver)) { ver = make_str(wk, "unknown"); } unknown = false; detection_over: if (unknown) { LOG_W("unable to detect compiler type, falling back on posix compiler"); type = compiler_posix; ver = make_str(wk, "unknown"); } struct obj_compiler *comp = get_obj_compiler(wk, comp_id); comp->cmd_arr[toolchain_component_compiler] = cmd_arr; comp->type[toolchain_component_compiler] = type; comp->ver = ver; run_cmd_ctx_destroy(&cmd_ctx); return true; } static bool compiler_detect_nasm(struct workspace *wk, obj cmd_arr, obj comp_id) { struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, cmd_arr, "--version") || strstr(cmd_ctx.err.buf, "nasm: error: unable to find utility")) { run_cmd_ctx_destroy(&cmd_ctx); return false; } enum compiler_type type; obj ver; if (strstr(cmd_ctx.out.buf, "NASM")) { type = compiler_nasm; } else if (strstr(cmd_ctx.out.buf, "yasm")) { type = compiler_yasm; } else { // Just assume it is nasm type = compiler_nasm; } if (!guess_version(wk, cmd_ctx.out.buf, &ver)) { ver = make_str(wk, "unknown"); } obj new_cmd; obj_array_dup(wk, cmd_arr, &new_cmd); { uint32_t addr_bits = host_machine.address_bits; const char *plat; TSTR(define); if (host_machine.is_windows) { plat = "win"; tstr_pushf(wk, &define, "WIN%d", addr_bits); } else if (host_machine.sys == machine_system_darwin) { plat = "macho"; tstr_pushs(wk, &define, "MACHO"); } else { plat = "elf"; tstr_pushs(wk, &define, "ELF"); } obj_array_push(wk, new_cmd, make_strf(wk, "-f%s%d", plat, addr_bits)); obj_array_push(wk, new_cmd, make_strf(wk, "-D%s", define.buf)); if (addr_bits == 64) { obj_array_push(wk, new_cmd, make_str(wk, "-D__x86_64__")); } } struct obj_compiler *comp = get_obj_compiler(wk, comp_id); comp->cmd_arr[toolchain_component_compiler] = new_cmd; comp->type[toolchain_component_compiler] = type; comp->ver = ver; comp->lang = compiler_language_nasm; run_cmd_ctx_destroy(&cmd_ctx); return true; } static bool compiler_get_libdirs(struct workspace *wk, struct obj_compiler *comp) { struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, comp->cmd_arr[toolchain_component_compiler], "-print-search-dirs") || cmd_ctx.status) { goto done; } const char *key = "libraries: "; char *s, *e; bool beginning_of_line = true; for (s = cmd_ctx.out.buf; *s; ++s) { if (beginning_of_line && strncmp(s, key, strlen(key)) == 0) { s += strlen(key); if (*s == '=') { ++s; } e = strchr(s, '\n'); struct str str = { .s = s, .len = e ? (uint32_t)(e - s) : strlen(s), }; comp->libdirs = str_split(wk, &str, &STR(ENV_PATH_SEP_STR)); goto done; } beginning_of_line = *s == '\n'; } done: run_cmd_ctx_destroy(&cmd_ctx); if (!comp->libdirs) { const char *libdirs[] = { "/usr/lib", "/usr/local/lib", "/lib", NULL }; comp->libdirs = make_obj(wk, obj_array); uint32_t i; for (i = 0; libdirs[i]; ++i) { obj_array_push(wk, comp->libdirs, make_str(wk, libdirs[i])); } } return true; } static void compiler_refine_host_machine(struct workspace *wk, struct obj_compiler *comp) { struct run_cmd_ctx cmd_ctx = { 0 }; if (run_cmd_arr(wk, &cmd_ctx, comp->cmd_arr[toolchain_component_compiler], "-dumpmachine") && cmd_ctx.status == 0) { machine_parse_and_apply_triplet(&host_machine, cmd_ctx.out.buf); } run_cmd_ctx_destroy(&cmd_ctx); // TODO: check for macros like ILP32 and x86_64 } static bool compiler_detect_cmd_arr(struct workspace *wk, obj comp, enum compiler_language lang, obj cmd_arr) { obj_lprintf(wk, log_debug, "checking compiler %o\n", cmd_arr); switch (lang) { case compiler_language_c: case compiler_language_cpp: case compiler_language_objc: case compiler_language_objcpp: if (!compiler_detect_c_or_cpp(wk, cmd_arr, comp)) { return false; } struct obj_compiler *compiler = get_obj_compiler(wk, comp); compiler_get_libdirs(wk, compiler); compiler_refine_host_machine(wk, compiler); compiler->lang = lang; return true; case compiler_language_nasm: if (!compiler_detect_nasm(wk, cmd_arr, comp)) { return false; } return true; default: LOG_E("tried to get a compiler for unsupported language '%s'", compiler_language_to_s(lang)); return false; } } static bool static_linker_detect(struct workspace *wk, obj comp, enum compiler_language lang, obj cmd_arr) { struct obj_compiler *compiler = get_obj_compiler(wk, comp); obj_lprintf(wk, log_debug, "checking static linker %o\n", cmd_arr); struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, cmd_arr, guess_version_arg(wk, compiler->type[toolchain_component_compiler] == compiler_msvc))) { run_cmd_ctx_destroy(&cmd_ctx); return false; } enum static_linker_type type = compiler->type[toolchain_component_compiler] == compiler_msvc ? static_linker_msvc : static_linker_ar_posix; if (cmd_ctx.status == 0 && strstr(cmd_ctx.out.buf, "Free Software Foundation")) { type = static_linker_ar_gcc; } run_cmd_ctx_destroy(&cmd_ctx); get_obj_compiler(wk, comp)->cmd_arr[toolchain_component_static_linker] = cmd_arr; get_obj_compiler(wk, comp)->type[toolchain_component_static_linker] = type; return true; } static bool linker_detect(struct workspace *wk, obj comp, enum compiler_language lang, obj cmd_arr) { struct obj_compiler *compiler = get_obj_compiler(wk, comp); bool msvc_like = compiler->type[toolchain_component_compiler] == compiler_msvc; enum linker_type type = compilers[compiler->type[toolchain_component_compiler]].default_linker; if (host_machine.sys == machine_system_windows) { if (compiler->type[toolchain_component_compiler] == compiler_clang) { type = linker_lld_link; msvc_like = true; } } else if (host_machine.sys == machine_system_darwin) { if (compiler->type[toolchain_component_compiler] == compiler_clang) { type = linker_apple; } } obj_lprintf(wk, log_debug, "checking linker %o\n", cmd_arr); struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, cmd_arr, guess_version_arg(wk, msvc_like))) { run_cmd_ctx_destroy(&cmd_ctx); return false; } // TODO: do something with command output? run_cmd_ctx_destroy(&cmd_ctx); get_obj_compiler(wk, comp)->cmd_arr[toolchain_component_linker] = cmd_arr; get_obj_compiler(wk, comp)->type[toolchain_component_linker] = type; return true; } typedef bool((*toolchain_detect_cmd_arr_cb)(struct workspace *wk, obj comp, enum compiler_language lang, obj cmd_arr)); bool toolchain_exe_detect(struct workspace *wk, const char *toolchain_exe_option_name, const char *exe_list[], obj comp, enum compiler_language lang, toolchain_detect_cmd_arr_cb cb) { if (!toolchain_exe_option_name) { return false; } obj cmd_arr_opt; get_option(wk, NULL, &STRL(toolchain_exe_option_name), &cmd_arr_opt); struct obj_option *cmd_arr = get_obj_option(wk, cmd_arr_opt); if (cmd_arr->source > option_value_source_default) { return cb(wk, comp, lang, cmd_arr->val); } uint32_t i; for (i = 0; exe_list[i]; ++i) { obj cmd_arr; cmd_arr = make_obj(wk, obj_array); obj_array_push(wk, cmd_arr, make_str(wk, exe_list[i])); if (cb(wk, comp, lang, cmd_arr)) { return true; } } return false; } static bool toolchain_linker_detect(struct workspace *wk, obj comp, enum compiler_language lang) { const char **exe_list = NULL; struct obj_compiler *compiler = get_obj_compiler(wk, comp); if (host_machine.sys == machine_system_windows) { if (compiler->type[toolchain_component_compiler] == compiler_clang) { static const char *clang_list[] = { "lld-link", NULL }; exe_list = clang_list; } else { static const char *msvc_list[] = { "link", NULL }; exe_list = msvc_list; } } else { static const char *default_list[] = { "lld", "ld", NULL }; exe_list = default_list; } return toolchain_exe_detect(wk, toolchain_component_option_name[lang][toolchain_component_linker], exe_list, comp, lang, linker_detect); } static bool toolchain_static_linker_detect(struct workspace *wk, obj comp, enum compiler_language lang) { const char **exe_list = NULL; struct obj_compiler *compiler = get_obj_compiler(wk, comp); if (compiler->type[toolchain_component_compiler] == compiler_msvc) { static const char *msvc_list[] = { "lib", NULL }; exe_list = msvc_list; } else { static const char *default_list[] = { "ar", "llvm-ar", NULL }; exe_list = default_list; } return toolchain_exe_detect(wk, "env.AR", exe_list, comp, lang, static_linker_detect); } static bool toolchain_compiler_detect(struct workspace *wk, obj comp, enum compiler_language lang) { const char **exe_list = NULL; if (host_machine.sys == machine_system_windows) { static const char *default_executables[][compiler_language_count] = { [compiler_language_c] = { "cl", "cc", "gcc", "clang", "clang-cl", NULL }, [compiler_language_cpp] = { "cl", "c++", "g++", "clang++", "clang-cl", NULL }, [compiler_language_objc] = { "cc", "gcc", NULL }, [compiler_language_objcpp] = { "c++", "g++", "clang++", "clang-cl", NULL }, [compiler_language_nasm] = { "nasm", "yasm", NULL }, }; exe_list = default_executables[lang]; } else { static const char *default_executables[][compiler_language_count] = { [compiler_language_c] = { "cc", "gcc", "clang", NULL }, [compiler_language_cpp] = { "c++", "g++", "clang++", NULL }, [compiler_language_objc] = { "cc", "gcc", "clang", NULL }, [compiler_language_objcpp] = { "c++", "g++", "clang++", NULL }, [compiler_language_nasm] = { "nasm", "yasm", NULL }, }; exe_list = default_executables[lang]; } return toolchain_exe_detect(wk, toolchain_component_option_name[lang][toolchain_component_compiler], exe_list, comp, lang, compiler_detect_cmd_arr); } bool toolchain_detect(struct workspace *wk, obj *comp, enum machine_kind machine, enum compiler_language lang) { if (obj_dict_geti(wk, wk->toolchains[machine], lang, comp)) { return true; } *comp = make_obj(wk, obj_compiler); if (!toolchain_compiler_detect(wk, *comp, lang)) { LOG_W("failed to detect compiler for %s", compiler_language_to_s(lang)); return false; } if (!toolchain_linker_detect(wk, *comp, lang)) { LOG_W("failed to detect linker for %s", compiler_language_to_s(lang)); return false; } if (!toolchain_static_linker_detect(wk, *comp, lang)) { LOG_W("failed to detect static linker for %s", compiler_language_to_s(lang)); return false; } obj_dict_seti(wk, wk->toolchains[machine], lang, *comp); struct obj_compiler *compiler = get_obj_compiler(wk, *comp); LLOG_I("%s: detected %s ", compiler_log_prefix(lang, machine), compiler_type_to_s(compiler->type[toolchain_component_compiler])); obj_lprintf(wk, log_info, "%o (%o), linker: %s (%o), static_linker: %s (%o)\n", compiler->ver, compiler->cmd_arr[toolchain_component_compiler], linker_type_to_s(compiler->type[toolchain_component_linker]), compiler->cmd_arr[toolchain_component_linker], static_linker_type_to_s(compiler->type[toolchain_component_static_linker]), compiler->cmd_arr[toolchain_component_static_linker]); return true; } /******************************************************************************* * Toolchain args ******************************************************************************/ #define TOOLCHAIN_ARGS(...) \ static const char *argv[] = __VA_ARGS__; \ static struct args args = { .args = argv, .len = ARRAY_LEN(argv) }; #define TOOLCHAIN_ARGS_RETURN static const struct args * #define TOOLCHAIN_PROTO_0(name) TOOLCHAIN_ARGS_RETURN name(TOOLCHAIN_SIG_0) #define TOOLCHAIN_PROTO_1i(name) TOOLCHAIN_ARGS_RETURN name(TOOLCHAIN_SIG_1i) #define TOOLCHAIN_PROTO_1s(name) TOOLCHAIN_ARGS_RETURN name(TOOLCHAIN_SIG_1s) #define TOOLCHAIN_PROTO_2s(name) TOOLCHAIN_ARGS_RETURN name(TOOLCHAIN_SIG_2s) #define TOOLCHAIN_PROTO_1s1b(name) TOOLCHAIN_ARGS_RETURN name(TOOLCHAIN_SIG_1s1b) #define TOOLCHAIN_PROTO_ns(name) TOOLCHAIN_ARGS_RETURN name(TOOLCHAIN_SIG_ns) /* empty functions */ TOOLCHAIN_PROTO_0(toolchain_arg_empty_0) { TOOLCHAIN_ARGS({ NULL }); args.len = 0; return &args; } TOOLCHAIN_PROTO_1i(toolchain_arg_empty_1i) { TOOLCHAIN_ARGS({ NULL }); args.len = 0; return &args; } TOOLCHAIN_PROTO_1s(toolchain_arg_empty_1s) { TOOLCHAIN_ARGS({ NULL }); args.len = 0; return &args; } TOOLCHAIN_PROTO_2s(toolchain_arg_empty_2s) { TOOLCHAIN_ARGS({ NULL }); args.len = 0; return &args; } TOOLCHAIN_PROTO_1s1b(toolchain_arg_empty_1s1b) { TOOLCHAIN_ARGS({ NULL }); args.len = 0; return &args; } TOOLCHAIN_PROTO_ns(toolchain_arg_empty_ns) { return n1; } /* posix compilers */ TOOLCHAIN_PROTO_0(compiler_posix_args_object_extension) { TOOLCHAIN_ARGS({ ".o" }); return &args; } TOOLCHAIN_PROTO_0(compiler_posix_args_compile_only) { TOOLCHAIN_ARGS({ "-c" }); return &args; } TOOLCHAIN_PROTO_0(compiler_posix_args_preprocess_only) { TOOLCHAIN_ARGS({ "-E" }); return &args; } TOOLCHAIN_PROTO_1s(compiler_posix_args_output) { TOOLCHAIN_ARGS({ "-o", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_1i(compiler_posix_args_optimization) { TOOLCHAIN_ARGS({ NULL }); switch ((enum compiler_optimization_lvl)i1) { case compiler_optimization_lvl_0: argv[0] = "-O0"; break; case compiler_optimization_lvl_1: case compiler_optimization_lvl_2: case compiler_optimization_lvl_3: argv[0] = "-O1"; break; case compiler_optimization_lvl_none: case compiler_optimization_lvl_g: case compiler_optimization_lvl_s: args.len = 0; break; } return &args; } TOOLCHAIN_PROTO_0(compiler_posix_args_debug) { TOOLCHAIN_ARGS({ "-g" }); return &args; } TOOLCHAIN_PROTO_1s(compiler_posix_args_include) { TOOLCHAIN_ARGS({ "-I", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_1s(compiler_posix_args_define) { TOOLCHAIN_ARGS({ "-D", NULL }); argv[1] = s1; return &args; } /* gcc compilers */ TOOLCHAIN_PROTO_ns(linker_args_passthrough) { static char buf[BUF_SIZE_S], buf2[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf, NULL, buf2 }); if (n1->len == 0) { return n1; } else if (n1->len == 1) { const char *passthrough_blacklist[] = { "-shared", "-bundle", "-dynamiclib", }; uint32_t i; for (i = 0; i < ARRAY_LEN(passthrough_blacklist); ++i) { if (strcmp(passthrough_blacklist[i], n1->args[0]) == 0) { return n1; } } snprintf(buf, BUF_SIZE_S, "-Wl,%s", n1->args[0]); args.len = 1; } else if (n1->len == 2) { if (strcmp(n1->args[0], "-l") == 0) { // "-Wl,-l,dl" errors out on some compilers - use "-ldl" form snprintf(buf, BUF_SIZE_S, "-l%s", n1->args[1]); } else { snprintf(buf, BUF_SIZE_S, "-Wl,%s,%s", n1->args[0], n1->args[1]); } args.len = 1; } else if (n1->len == 3) { snprintf(buf, BUF_SIZE_S, "-Wl,%s", n1->args[0]); argv[1] = n1->args[1]; snprintf(buf2, BUF_SIZE_S, "-Wl,%s", n1->args[2]); args.len = 3; } else { UNREACHABLE; } return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_preprocess_only) { TOOLCHAIN_ARGS({ "-E", "-P" }); return &args; } TOOLCHAIN_PROTO_1s(compiler_gcc_args_include_system) { TOOLCHAIN_ARGS({ "-isystem", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_1s(compiler_gcc_args_include_dirafter) { TOOLCHAIN_ARGS({ "-idirafter", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_2s(compiler_gcc_args_deps) { TOOLCHAIN_ARGS({ "-MD", "-MQ", NULL, "-MF", NULL }); argv[2] = s1; argv[4] = s2; return &args; } TOOLCHAIN_PROTO_1i(compiler_gcc_args_optimization) { TOOLCHAIN_ARGS({ NULL }); switch ((enum compiler_optimization_lvl)i1) { case compiler_optimization_lvl_none: args.len = 0; break; case compiler_optimization_lvl_0: argv[0] = "-O0"; break; case compiler_optimization_lvl_1: argv[0] = "-O1"; break; case compiler_optimization_lvl_2: argv[0] = "-O2"; break; case compiler_optimization_lvl_3: argv[0] = "-O3"; break; case compiler_optimization_lvl_g: argv[0] = "-Og"; break; case compiler_optimization_lvl_s: argv[0] = "-Os"; break; } return &args; } TOOLCHAIN_PROTO_1i(compiler_gcc_args_warning_lvl) { TOOLCHAIN_ARGS({ NULL, NULL, NULL }); args.len = 0; switch ((enum compiler_warning_lvl)i1) { case compiler_warning_lvl_everything: UNREACHABLE; break; case compiler_warning_lvl_3: argv[args.len] = "-pedantic"; ++args.len; /* fallthrough */ case compiler_warning_lvl_2: argv[args.len] = "-Wextra"; ++args.len; /* fallthrough */ case compiler_warning_lvl_1: argv[args.len] = "-Wall"; ++args.len; /* fallthrough */ case compiler_warning_lvl_0: break; } return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_werror) { TOOLCHAIN_ARGS({ "-Werror" }); return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_winvalid_pch) { TOOLCHAIN_ARGS({ "-Winvalid-pch" }); return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_warn_everything) { TOOLCHAIN_ARGS({ "-Wall", "-Winvalid-pch", "-Wextra", "-pedantic", "-Wcast-qual", "-Wconversion", "-Wfloat-equal", "-Wformat=2", "-Winline", "-Wmissing-declarations", "-Wredundant-decls", "-Wshadow", "-Wundef", "-Wuninitialized", "-Wwrite-strings", "-Wdisabled-optimization", "-Wpacked", "-Wpadded", "-Wmultichar", "-Wswitch-default", "-Wswitch-enum", "-Wunused-macros", "-Wmissing-include-dirs", "-Wunsafe-loop-optimizations", "-Wstack-protector", "-Wstrict-overflow=5", "-Warray-bounds=2", "-Wlogical-op", "-Wstrict-aliasing=3", "-Wvla", "-Wdouble-promotion", "-Wsuggest-attribute=const", "-Wsuggest-attribute=noreturn", "-Wsuggest-attribute=pure", "-Wtrampolines", "-Wvector-operation-performance", "-Wsuggest-attribute=format", "-Wdate-time", "-Wformat-signedness", "-Wnormalized=nfc", "-Wduplicated-cond", "-Wnull-dereference", "-Wshift-negative-value", "-Wshift-overflow=2", "-Wunused-const-variable=2", "-Walloca", "-Walloc-zero", "-Wformat-overflow=2", "-Wformat-truncation=2", "-Wstringop-overflow=3", "-Wduplicated-branches", "-Wattribute-alias=2", "-Wcast-align=strict", "-Wsuggest-attribute=cold", "-Wsuggest-attribute=malloc", "-Wanalyzer-too-complex", "-Warith-conversion", "-Wbidi-chars=ucn", "-Wopenacc-parallelism", "-Wtrivial-auto-var-init", "-Wbad-function-cast", "-Wmissing-prototypes", "-Wnested-externs", "-Wstrict-prototypes", "-Wold-style-definition", "-Winit-self", "-Wc++-compat", "-Wunsuffixed-float-constants" }); return &args; } TOOLCHAIN_PROTO_1i(compiler_gcc_args_force_language) { TOOLCHAIN_ARGS({ "-x", 0 }); if (compiler_language_gcc_names[i1]) { args.args[1] = compiler_language_gcc_names[i1]; args.len = 2; } else { args.len = 0; } return &args; } TOOLCHAIN_PROTO_0(compiler_clang_args_warn_everything) { TOOLCHAIN_ARGS({ "-Weverything" }); return &args; } TOOLCHAIN_PROTO_1s(compiler_clang_args_include_pch) { TOOLCHAIN_ARGS({ "-include-pch", 0 }); args.args[1] = s1; return &args; } TOOLCHAIN_PROTO_0(compiler_clang_args_emit_pch) { TOOLCHAIN_ARGS({ "-Xclang", "-emit-pch" }); return &args; } TOOLCHAIN_PROTO_0(compiler_clang_args_pch_extension) { TOOLCHAIN_ARGS({ ".pch" }) return &args; } TOOLCHAIN_PROTO_1s(compiler_gcc_args_set_std) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-std=%s", s1); return &args; } TOOLCHAIN_PROTO_1i(compiler_gcc_args_pgo) { TOOLCHAIN_ARGS({ NULL, NULL }); args.len = 1; switch ((enum compiler_pgo_stage)i1) { case compiler_pgo_generate: argv[0] = "-fprofile-generate"; break; case compiler_pgo_use: argv[1] = "-fprofile-correction"; ++args.len; argv[0] = "-fprofile-use"; break; } return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_pic) { TOOLCHAIN_ARGS({ "-fPIC" }); if (host_machine.is_windows) { args.len = 0; } else { args.len = 1; } return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_pie) { TOOLCHAIN_ARGS({ "-fPIE" }); return &args; } TOOLCHAIN_PROTO_1s(compiler_gcc_args_sanitize) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-fsanitize=%s", s1); return &args; } TOOLCHAIN_PROTO_1i(compiler_gcc_args_visibility) { TOOLCHAIN_ARGS({ NULL, NULL }); args.len = 1; switch ((enum compiler_visibility_type)i1) { case compiler_visibility_default: argv[0] = "-fvisibility=default"; break; case compiler_visibility_internal: argv[0] = "-fvisibility=internal"; break; case compiler_visibility_protected: argv[0] = "-fvisibility=protected"; break; case compiler_visibility_inlineshidden: argv[1] = "-fvisibility-inlines-hidden"; ++args.len; // fallthrough case compiler_visibility_hidden: argv[0] = "-fvisibility=hidden"; break; default: assert(false && "unreachable"); } return &args; } TOOLCHAIN_PROTO_1s(compiler_gcc_args_specify_lang) { TOOLCHAIN_ARGS({ "-x", NULL, }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_1s(compiler_gcc_args_color_output) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); if (comp->type[toolchain_component_compiler] == compiler_gcc && (!comp->ver || version_compare(get_str(wk, comp->ver), &STR("<4.9.0")))) { args.len = 0; return &args; } snprintf(buf, BUF_SIZE_S, "-fdiagnostics-color=%s", s1); return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_lto) { TOOLCHAIN_ARGS({ "-flto" }); return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_coverage) { TOOLCHAIN_ARGS({ "--coverage" }) return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_permissive) { TOOLCHAIN_ARGS({ "-fpermissive" }) return &args; } TOOLCHAIN_PROTO_1s(compiler_gcc_args_include_pch) { static char a[BUF_SIZE_S], b[BUF_SIZE_S]; TOOLCHAIN_ARGS({ "-I", a, "-include", b }) TSTR(buf); path_dirname(wk, &buf, s1); assert(buf.len + 1 < sizeof(a)); memcpy(a, buf.buf, buf.len + 1); path_basename(wk, &buf, s1); assert(buf.len + 1 < sizeof(a)); memcpy(b, buf.buf, buf.len + 1); b[strlen(b) - 4] = 0; return &args; } TOOLCHAIN_PROTO_0(compiler_gcc_args_pch_extension) { TOOLCHAIN_ARGS({ ".gch" }) return &args; } /* cl compilers * see mesonbuild/compilers/mixins/visualstudio.py for reference */ TOOLCHAIN_PROTO_0(compiler_cl_args_always) { TOOLCHAIN_ARGS({ "/nologo" }); return &args; } TOOLCHAIN_PROTO_1s1b(compiler_cl_args_crt) { TOOLCHAIN_ARGS({ NULL }); if (strcmp(s1, "from_buildtype") == 0) { argv[0] = b1 ? "/MDd" : "/MD"; } else if (strcmp(s1, "static_from_buildtype") == 0) { argv[0] = b1 ? "/MTd" : "/MT"; } else { argv[0] = s1; } return &args; } TOOLCHAIN_PROTO_1s(compiler_cl_args_debugfile) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "/Fd%s.pdb", s1); return &args; } TOOLCHAIN_PROTO_2s(compiler_cl_args_deps) { TOOLCHAIN_ARGS({ "/showIncludes" }); return &args; } TOOLCHAIN_PROTO_0(compiler_cl_args_compile_only) { TOOLCHAIN_ARGS({ "/c" }); return &args; } TOOLCHAIN_PROTO_0(compiler_cl_args_preprocess_only) { TOOLCHAIN_ARGS({ "/EP" }); return &args; } TOOLCHAIN_PROTO_1s(compiler_cl_args_output) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); if (str_endswithi(&STRL(s1), &STR(".exe"))) { snprintf(buf, BUF_SIZE_S, "/Fe%s", s1); } else { snprintf(buf, BUF_SIZE_S, "/Fo%s", s1); } return &args; } TOOLCHAIN_PROTO_1i(compiler_cl_args_optimization) { TOOLCHAIN_ARGS({ NULL, NULL }); switch ((enum compiler_optimization_lvl)i1) { case compiler_optimization_lvl_none: case compiler_optimization_lvl_g: args.len = 0; break; case compiler_optimization_lvl_0: args.len = 1; argv[0] = "/Od"; break; case compiler_optimization_lvl_1: args.len = 1; argv[0] = "/O1"; break; case compiler_optimization_lvl_2: args.len = 1; argv[0] = "/O2"; break; case compiler_optimization_lvl_3: args.len = 2; argv[0] = "/O2"; argv[1] = "/Gw"; break; case compiler_optimization_lvl_s: args.len = 2; argv[0] = "/O1"; argv[1] = "/Gw"; break; } return &args; } TOOLCHAIN_PROTO_0(compiler_cl_args_debug) { TOOLCHAIN_ARGS({ "/Zi" }); return &args; } TOOLCHAIN_PROTO_1i(compiler_cl_args_warning_lvl) { /* meson uses nothing instead of /W0, but it's the same warning level * see: https://mesonbuild.com/Builtin-options.html#details-for-warning_level */ TOOLCHAIN_ARGS({ NULL }); switch ((enum compiler_warning_lvl)i1) { case compiler_warning_lvl_0: args.len = 0; break; case compiler_warning_lvl_1: argv[0] = "/W2"; break; case compiler_warning_lvl_2: argv[0] = "/W3"; break; case compiler_warning_lvl_everything: case compiler_warning_lvl_3: argv[0] = "/W4"; break; } return &args; } TOOLCHAIN_PROTO_0(compiler_cl_args_warn_everything) { TOOLCHAIN_ARGS({ "/Wall" }); return &args; } TOOLCHAIN_PROTO_0(compiler_cl_args_werror) { TOOLCHAIN_ARGS({ "/WX" }); return &args; } TOOLCHAIN_PROTO_1s(compiler_cl_args_set_std) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); if (strcmp(s1, "c11") == 0) { memcpy(buf, "/std:c11", sizeof("/std:c11")); } else if (strcmp(s1, "c17") == 0 || strcmp(s1, "c18") == 0) { memcpy(buf, "/std:c17", sizeof("/std:c17")); } else { args.len = 0; } snprintf(buf, BUF_SIZE_S, "/std:%s", s1); return &args; } TOOLCHAIN_PROTO_1s(compiler_cl_args_include) { TOOLCHAIN_ARGS({ "/I", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_1s(compiler_cl_args_sanitize) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-fsanitize=%s", s1); return &args; } TOOLCHAIN_PROTO_1s(compiler_cl_args_define) { TOOLCHAIN_ARGS({ "/D", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_0(compiler_cl_args_object_extension) { TOOLCHAIN_ARGS({ ".obj" }); return &args; } TOOLCHAIN_PROTO_1s(compiler_cl_args_std_supported) { const char *supported[] = { "c++14", "c++17", "c++20", "c++latest", "c11", "c17", "clatest", }; uint32_t i; for (i = 0; i < ARRAY_LEN(supported); ++i) { if (strcmp(s1, supported[i]) == 0) { return TOOLCHAIN_TRUE; } } return TOOLCHAIN_FALSE; } TOOLCHAIN_PROTO_1s(compiler_clang_cl_args_color_output) { TOOLCHAIN_ARGS({ "-fcolor-diagnostics" }); return &args; (void)s1; } TOOLCHAIN_PROTO_0(compiler_clang_cl_args_lto) { TOOLCHAIN_ARGS({ "-flto" }); return &args; } TOOLCHAIN_PROTO_0(compiler_cl_args_do_linker_passthrough) { return TOOLCHAIN_FALSE; } TOOLCHAIN_PROTO_0(compiler_deps_gcc) { TOOLCHAIN_ARGS({ "gcc" }); return &args; } TOOLCHAIN_PROTO_0(compiler_deps_msvc) { TOOLCHAIN_ARGS({ "msvc" }); return &args; } TOOLCHAIN_PROTO_1s(linker_posix_args_lib) { TOOLCHAIN_ARGS({ "-l", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_2s(linker_posix_args_input_output) { TOOLCHAIN_ARGS({ NULL, NULL }); argv[0] = s2; argv[1] = s1; return &args; } /* technically not a posix linker argument, but include it here since it is so * common */ TOOLCHAIN_PROTO_0(linker_posix_args_shared) { TOOLCHAIN_ARGS({ "-shared" }); return &args; } TOOLCHAIN_PROTO_0(linker_ld_args_as_needed) { TOOLCHAIN_ARGS({ "--as-needed" }); return &args; } TOOLCHAIN_PROTO_0(linker_ld_args_no_undefined) { TOOLCHAIN_ARGS({ "--no-undefined" }); return &args; } TOOLCHAIN_PROTO_0(linker_ld_args_start_group) { TOOLCHAIN_ARGS({ "--start-group" }); return &args; } TOOLCHAIN_PROTO_0(linker_ld_args_end_group) { TOOLCHAIN_ARGS({ "--end-group" }); return &args; } TOOLCHAIN_PROTO_1s(linker_ld_args_soname) { TOOLCHAIN_ARGS({ "-soname", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_1s(linker_ld_args_rpath) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-rpath,%s", s1); return &args; } TOOLCHAIN_PROTO_0(linker_ld_args_allow_shlib_undefined) { TOOLCHAIN_ARGS({ "--allow-shlib-undefined" }); return &args; } TOOLCHAIN_PROTO_0(linker_ld_args_export_dynamic) { TOOLCHAIN_ARGS({ "-export-dynamic" }); return &args; } TOOLCHAIN_PROTO_0(linker_ld_args_fatal_warnings) { TOOLCHAIN_ARGS({ "--fatal-warnings" }); return &args; } TOOLCHAIN_PROTO_1s(linker_ld_args_whole_archive) { TOOLCHAIN_ARGS({ "--whole-archive", NULL, "--no-whole-archive" }); argv[1] = s1; return &args; } /* cl linkers */ TOOLCHAIN_PROTO_1s(linker_link_args_lib) { TOOLCHAIN_ARGS({ NULL }); argv[0] = s1; return &args; } TOOLCHAIN_PROTO_0(linker_link_args_debug) { TOOLCHAIN_ARGS({ "/DEBUG" }); return &args; } TOOLCHAIN_PROTO_0(linker_link_args_shared) { TOOLCHAIN_ARGS({ "/DLL" }); return &args; } TOOLCHAIN_PROTO_1s(linker_link_args_soname) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "/OUT:%s", s1); return &args; } TOOLCHAIN_PROTO_2s(linker_link_args_input_output) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf, NULL }); snprintf(buf, BUF_SIZE_S, "/out:%s", s2); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_1s(linker_link_args_implib) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "/IMPLIB:%s", s1); return &args; } TOOLCHAIN_PROTO_0(linker_link_args_always) { TOOLCHAIN_ARGS({ "/NOLOGO", NULL }); argv[1] = host_machine.address_bits == 64 ? "/MACHINE:X64" : "/MACHINE:X86"; return &args; } TOOLCHAIN_PROTO_1s(linker_link_args_whole_archive) { static char buf[BUF_SIZE_S]; TOOLCHAIN_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "/WHOLEARCHIVE:%s", s1); return &args; } /* apple linker */ TOOLCHAIN_PROTO_1s(linker_apple_args_whole_archive) { TOOLCHAIN_ARGS({ "-force_load", NULL }); argv[1] = s1; return &args; } TOOLCHAIN_PROTO_0(linker_apple_args_allow_shlib_undefined) { TOOLCHAIN_ARGS({ "-undefined", "dynamic_lookup" }); return &args; } TOOLCHAIN_PROTO_0(linker_apple_args_shared_module) { TOOLCHAIN_ARGS({ "-bundle" }); return &args; } /* static linkers */ TOOLCHAIN_PROTO_0(static_linker_ar_posix_args_base) { TOOLCHAIN_ARGS({ "csr" }); return &args; } TOOLCHAIN_PROTO_0(static_linker_ar_gcc_args_base) { TOOLCHAIN_ARGS({ "csrD" }); return &args; } struct compiler compilers[compiler_type_count]; struct linker linkers[linker_type_count]; struct static_linker static_linkers[static_linker_type_count]; const struct language languages[compiler_language_count] = { [compiler_language_null] = { 0 }, [compiler_language_c] = { .is_header = false }, [compiler_language_c_hdr] = { .is_header = true }, [compiler_language_cpp] = { .is_header = false }, [compiler_language_cpp_hdr] = { .is_header = true }, [compiler_language_c_obj] = { .is_linkable = true }, [compiler_language_assembly] = { 0 }, [compiler_language_llvm_ir] = { 0 }, }; #define TOOLCHAIN_ARG_MEMBER_(name, __type, params, names) .name = toolchain_arg_empty_##__type, #define TOOLCHAIN_ARG_MEMBER(name, comp, type) TOOLCHAIN_ARG_MEMBER_(name, type) static void build_compilers(void) { struct compiler empty = { .args = { FOREACH_COMPILER_ARG(TOOLCHAIN_ARG_MEMBER) }, }; empty.args.object_ext = compiler_posix_args_object_extension; struct compiler clang_llvm_ir = empty; clang_llvm_ir.args.compile_only = compiler_posix_args_compile_only; clang_llvm_ir.args.output = compiler_posix_args_output; struct compiler posix = empty; posix.args.compile_only = compiler_posix_args_compile_only; posix.args.preprocess_only = compiler_posix_args_preprocess_only; posix.args.output = compiler_posix_args_output; posix.args.optimization = compiler_posix_args_optimization; posix.args.debug = compiler_posix_args_debug; posix.args.include = compiler_posix_args_include; posix.args.include_system = compiler_posix_args_include; posix.args.define = compiler_posix_args_define; posix.args.linker_passthrough = linker_args_passthrough; posix.args.pic = compiler_gcc_args_pic; posix.args.specify_lang = compiler_gcc_args_specify_lang; posix.args.werror = compiler_gcc_args_werror; posix.default_linker = linker_posix; posix.default_static_linker = static_linker_ar_posix; struct compiler gcc = posix; gcc.args.linker_passthrough = linker_args_passthrough; gcc.args.preprocess_only = compiler_gcc_args_preprocess_only; gcc.args.deps = compiler_gcc_args_deps; gcc.args.optimization = compiler_gcc_args_optimization; gcc.args.warning_lvl = compiler_gcc_args_warning_lvl; gcc.args.warn_everything = compiler_gcc_args_warn_everything; gcc.args.werror = compiler_gcc_args_werror; gcc.args.winvalid_pch = compiler_gcc_args_winvalid_pch; gcc.args.set_std = compiler_gcc_args_set_std; gcc.args.include_system = compiler_gcc_args_include_system; gcc.args.include_dirafter = compiler_gcc_args_include_dirafter; gcc.args.pgo = compiler_gcc_args_pgo; gcc.args.pic = compiler_gcc_args_pic; gcc.args.pie = compiler_gcc_args_pie; gcc.args.sanitize = compiler_gcc_args_sanitize; gcc.args.visibility = compiler_gcc_args_visibility; gcc.args.specify_lang = compiler_gcc_args_specify_lang; gcc.args.color_output = compiler_gcc_args_color_output; gcc.args.enable_lto = compiler_gcc_args_lto; gcc.args.deps_type = compiler_deps_gcc; gcc.args.coverage = compiler_gcc_args_coverage; gcc.args.permissive = compiler_gcc_args_permissive; gcc.args.include_pch = compiler_gcc_args_include_pch; gcc.args.pch_ext = compiler_gcc_args_pch_extension; gcc.args.force_language = compiler_gcc_args_force_language; gcc.default_linker = linker_ld; gcc.default_static_linker = static_linker_ar_gcc; struct compiler clang = gcc; clang.args.warn_everything = compiler_clang_args_warn_everything; clang.args.include_pch = compiler_clang_args_include_pch; clang.args.emit_pch = compiler_clang_args_emit_pch; clang.args.pch_ext = compiler_clang_args_pch_extension; clang.default_linker = linker_clang; struct compiler apple_clang = clang; apple_clang.default_linker = linker_apple; apple_clang.default_static_linker = static_linker_ar_posix; struct compiler msvc = empty; msvc.args.deps = compiler_cl_args_deps; msvc.args.compile_only = compiler_cl_args_compile_only; msvc.args.preprocess_only = compiler_cl_args_preprocess_only; msvc.args.output = compiler_cl_args_output; msvc.args.optimization = compiler_cl_args_optimization; msvc.args.debug = compiler_cl_args_debug; msvc.args.warning_lvl = compiler_cl_args_warning_lvl; msvc.args.warn_everything = compiler_cl_args_warn_everything; msvc.args.werror = compiler_cl_args_werror; msvc.args.set_std = compiler_cl_args_set_std; msvc.args.include = compiler_cl_args_include; msvc.args.sanitize = compiler_cl_args_sanitize; msvc.args.define = compiler_cl_args_define; msvc.args.always = compiler_cl_args_always; msvc.args.crt = compiler_cl_args_crt; msvc.args.debugfile = compiler_cl_args_debugfile; msvc.args.object_ext = compiler_cl_args_object_extension; msvc.args.deps_type = compiler_deps_msvc; msvc.args.std_supported = compiler_cl_args_std_supported; msvc.args.do_linker_passthrough = compiler_cl_args_do_linker_passthrough; msvc.default_linker = linker_msvc; msvc.default_static_linker = static_linker_msvc; struct compiler clang_cl = msvc; clang_cl.args.color_output = compiler_clang_cl_args_color_output; clang_cl.args.enable_lto = compiler_clang_cl_args_lto; clang_cl.default_linker = linker_lld_link; compilers[compiler_posix] = posix; compilers[compiler_gcc] = gcc; compilers[compiler_clang] = clang; compilers[compiler_apple_clang] = apple_clang; compilers[compiler_clang_llvm_ir] = clang_llvm_ir; compilers[compiler_clang_cl] = clang_cl; compilers[compiler_msvc] = msvc; struct compiler nasm = empty; nasm.args.output = compiler_posix_args_output; nasm.args.optimization = compiler_posix_args_optimization; nasm.args.debug = compiler_posix_args_debug; nasm.args.include = compiler_posix_args_include; nasm.args.include_system = compiler_posix_args_include; nasm.args.define = compiler_posix_args_define; nasm.default_linker = linker_posix; nasm.default_static_linker = static_linker_ar_posix; compilers[compiler_nasm] = nasm; compilers[compiler_yasm] = nasm; } static void build_linkers(void) { /* linkers */ struct linker empty = { .args = { FOREACH_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) } }; struct linker posix = empty; posix.args.lib = linker_posix_args_lib; posix.args.shared = linker_posix_args_shared; posix.args.input_output = linker_posix_args_input_output; struct linker ld = posix; ld.args.as_needed = linker_ld_args_as_needed; ld.args.no_undefined = linker_ld_args_no_undefined; ld.args.start_group = linker_ld_args_start_group; ld.args.end_group = linker_ld_args_end_group; ld.args.soname = linker_ld_args_soname; ld.args.rpath = linker_ld_args_rpath; ld.args.pgo = compiler_gcc_args_pgo; ld.args.sanitize = compiler_gcc_args_sanitize; ld.args.allow_shlib_undefined = linker_ld_args_allow_shlib_undefined; ld.args.shared_module = linker_posix_args_shared; ld.args.export_dynamic = linker_ld_args_export_dynamic; ld.args.fatal_warnings = linker_ld_args_fatal_warnings; ld.args.whole_archive = linker_ld_args_whole_archive; ld.args.enable_lto = compiler_gcc_args_lto; ld.args.coverage = compiler_gcc_args_coverage; struct linker lld = ld; struct linker apple = posix; posix.args.shared = linker_posix_args_shared; apple.args.sanitize = compiler_gcc_args_sanitize; apple.args.enable_lto = compiler_gcc_args_lto; apple.args.allow_shlib_undefined = linker_apple_args_allow_shlib_undefined; apple.args.shared_module = linker_apple_args_shared_module; apple.args.whole_archive = linker_apple_args_whole_archive; apple.args.rpath = linker_ld_args_rpath; struct linker link = empty; link.args.lib = linker_link_args_lib; link.args.debug = linker_link_args_debug; link.args.shared = linker_link_args_shared; link.args.soname = linker_link_args_soname; link.args.input_output = linker_link_args_input_output; link.args.always = linker_link_args_always; link.args.whole_archive = linker_link_args_whole_archive; link.args.implib = linker_link_args_implib; struct linker lld_link = link; lld_link.args.lib = linker_posix_args_lib; lld_link.args.always = toolchain_arg_empty_0; linkers[linker_posix] = posix; linkers[linker_ld] = ld; linkers[linker_clang] = lld; linkers[linker_apple] = apple; linkers[linker_lld_link] = lld_link; linkers[linker_msvc] = link; } static void build_static_linkers(void) { struct static_linker empty = { .args = { FOREACH_STATIC_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) } }; struct static_linker posix = empty; posix.args.base = static_linker_ar_posix_args_base; posix.args.input_output = linker_posix_args_input_output; struct static_linker gcc = posix; gcc.args.base = static_linker_ar_gcc_args_base; struct static_linker msvc = empty; msvc.args.input_output = linker_link_args_input_output; msvc.args.always = linker_link_args_always; static_linkers[static_linker_ar_posix] = posix; static_linkers[static_linker_ar_gcc] = gcc; static_linkers[static_linker_msvc] = msvc; } #undef TOOLCHAIN_ARG_MEMBER #undef TOOLCHAIN_ARG_MEMBER_ void compilers_init(void) { build_compilers(); build_linkers(); build_static_linkers(); } #define TOOLCHAIN_ARG_MEMBER_(name, comp, __type, params, names) { #name, toolchain_arg_arity_##__type }, #define TOOLCHAIN_ARG_MEMBER(name, comp, type) TOOLCHAIN_ARG_MEMBER_(name, comp, type) static struct toolchain_arg_handler toolchain_compiler_arg_handlers[] = { FOREACH_COMPILER_ARG(TOOLCHAIN_ARG_MEMBER) }; static struct toolchain_arg_handler toolchain_linker_arg_handlers[] = { FOREACH_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) }; static struct toolchain_arg_handler toolchain_static_linker_arg_handlers[] = { FOREACH_STATIC_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) }; #undef TOOLCHAIN_ARG_MEMBER #undef TOOLCHAIN_ARG_MEMBER_ static struct { struct toolchain_arg_handler *handlers; uint32_t len; } toolchain_arg_handlers[] = { [toolchain_component_compiler] = { toolchain_compiler_arg_handlers, ARRAY_LEN(toolchain_compiler_arg_handlers) }, [toolchain_component_linker] = { toolchain_linker_arg_handlers, ARRAY_LEN(toolchain_linker_arg_handlers) }, [toolchain_component_static_linker] = { toolchain_static_linker_arg_handlers, ARRAY_LEN(toolchain_static_linker_arg_handlers) }, }; const struct toolchain_arg_handler * get_toolchain_arg_handler_info(enum toolchain_component component, const char *name) { uint32_t i; for (i = 0; i < toolchain_arg_handlers[component].len; ++i) { if (strcmp(toolchain_arg_handlers[component].handlers[i].name, name) == 0) { return &toolchain_arg_handlers[component].handlers[i]; } } return 0; } void toolchain_arg_arity_to_sig(enum toolchain_arg_arity arity, type_tag signature[2], uint32_t *len) { switch (arity) { case toolchain_arg_arity_0: { *len = 0; break; } case toolchain_arg_arity_1i: { signature[0] = tc_number; *len = 1; break; } case toolchain_arg_arity_1s: { signature[0] = tc_string; *len = 1; break; } case toolchain_arg_arity_2s: { signature[0] = tc_string; signature[1] = tc_string; *len = 2; break; } case toolchain_arg_arity_1s1b: { signature[0] = tc_string; signature[1] = tc_bool; *len = 2; break; } case toolchain_arg_arity_ns: { signature[0] = TYPE_TAG_GLOB | tc_string; *len = 1; break; } } } static obj lookup_toolchain_arg_override(struct workspace *wk, struct obj_compiler *c, enum toolchain_component component, uint32_t arg) { obj overrides = c->overrides[component], override; if (overrides && obj_dict_index_str(wk, overrides, toolchain_arg_handlers[component].handlers[arg].name, &override)) { return override; } return 0; } enum toolchain_arg_by_component { #define TOOLCHAIN_ARG_MEMBER_(comp, _name) toolchain_arg_by_component_##comp##_name, #define TOOLCHAIN_ARG_MEMBER(name, comp, type) TOOLCHAIN_ARG_MEMBER_(comp, _##name) FOREACH_COMPILER_ARG(TOOLCHAIN_ARG_MEMBER) toolchain_arg_by_component_reset_0 = -1, FOREACH_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) toolchain_arg_by_component_reset_1 = -1, FOREACH_STATIC_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) #undef TOOLCHAIN_ARG_MEMBER #undef TOOLCHAIN_ARG_MEMBER_ }; static obj handle_toolchain_arg_override; static const struct args * handle_toolchain_arg_override_convert_to_args(struct workspace *wk, obj list) { static const char *argv[32]; static struct args args = { .args = argv, .len = 0 }; obj v; obj_array_for(wk, list, v) { assert(args.len < ARRAY_LEN(argv) && "increase size of argv"); ++args.len; argv[args.len] = get_cstr(wk, v); } return &args; } static const struct args * handle_toolchain_arg_override_check_const(struct workspace *wk) { if (get_obj_type(wk, handle_toolchain_arg_override) == obj_array) { return handle_toolchain_arg_override_convert_to_args(wk, handle_toolchain_arg_override); } return 0; } #define constant_override_check() \ { \ const struct args *args = handle_toolchain_arg_override_check_const(wk); \ if (args) { \ return args; \ } \ } static const struct args * handle_toolchain_arg_override_0(TOOLCHAIN_SIG_0) { constant_override_check(); return 0; } static const struct args * handle_toolchain_arg_override_1i(TOOLCHAIN_SIG_1i) { constant_override_check(); return 0; } static const struct args * handle_toolchain_arg_override_1s(TOOLCHAIN_SIG_1s) { constant_override_check(); return 0; } static const struct args * handle_toolchain_arg_override_2s(TOOLCHAIN_SIG_2s) { constant_override_check(); return 0; } static const struct args * handle_toolchain_arg_override_1s1b(TOOLCHAIN_SIG_1s1b) { constant_override_check(); return 0; } static const struct args * handle_toolchain_arg_override_ns(TOOLCHAIN_SIG_ns) { constant_override_check(); return 0; } #define TOOLCHAIN_ARG_MEMBER_(name, _name, component, _type, params, names) \ const struct args *toolchain_##component##_name params \ { \ handle_toolchain_arg_override = lookup_toolchain_arg_override( \ wk, comp, toolchain_component_##component, toolchain_arg_by_component_##component##_name); \ if (handle_toolchain_arg_override) { \ return handle_toolchain_arg_override_##_type names; \ } \ \ return component##s[comp->type[toolchain_component_##component]].args.name names; \ } #define TOOLCHAIN_ARG_MEMBER(name, comp, type) TOOLCHAIN_ARG_MEMBER_(name, _##name, comp, type) FOREACH_COMPILER_ARG(TOOLCHAIN_ARG_MEMBER) FOREACH_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) FOREACH_STATIC_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) #undef TOOLCHAIN_ARG_MEMBER #undef TOOLCHAIN_ARG_MEMBER_ static void toolchain_dump_args(struct workspace *wk, const char *component, const char *name, const char *type, const struct args *args) { printf("%-13s %-25s %-4s ", component, name, type); if (args) { printf("{"); for (uint32_t i = 0; i < args->len; ++i) { printf("\"%s\"", args->args[i]); if (i + 1 < args->len) { printf(", "); } } printf("}"); } else { printf("false"); } printf("\n"); } void toolchain_dump(struct workspace *wk, struct obj_compiler *comp, struct toolchain_dump_opts *opts) { const char *s1 = opts->s1, *s2 = opts->s2; const bool b1 = opts->b1; const uint32_t i1 = opts->i1; const struct args *n1 = opts->n1; printf("%-13s %-25s %-4s %s\n", "component", "name", "sig", "args"); printf("%-13s %-25s %-4s %s\n", "---", "---", "---", "---"); #define TOOLCHAIN_ARG_MEMBER_(name, _name, component, _type, params, names) \ toolchain_dump_args(wk, #component, #name, #_type, toolchain_##component##_name names); #define TOOLCHAIN_ARG_MEMBER(name, comp, type) TOOLCHAIN_ARG_MEMBER_(name, _##name, comp, type) FOREACH_COMPILER_ARG(TOOLCHAIN_ARG_MEMBER) FOREACH_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) FOREACH_STATIC_LINKER_ARG(TOOLCHAIN_ARG_MEMBER) #undef TOOLCHAIN_ARG_MEMBER #undef TOOLCHAIN_ARG_MEMBER_ } const char * compiler_log_prefix(enum compiler_language lang, enum machine_kind machine) { static char buf[256]; if (machine == machine_kind_build) { snprintf(buf, sizeof(buf), "%s %s machine compiler", compiler_language_to_s(lang), machine_kind_to_s(machine)); } else { snprintf(buf, sizeof(buf), "%s compiler", compiler_language_to_s(lang)); } return buf; } muon-v0.5.0/src/meson.build0000644000175000017500000000612115041716357014602 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only src = files( 'backend/backend.c', 'backend/common_args.c', 'backend/introspect.c', 'backend/ninja.c', 'backend/ninja/alias_target.c', 'backend/ninja/build_target.c', 'backend/ninja/coverage.c', 'backend/ninja/custom_target.c', 'backend/ninja/rules.c', 'backend/output.c', 'backend/xcode.c', 'datastructures/arr.c', 'datastructures/bucket_arr.c', 'datastructures/hash.c', 'datastructures/stack.c', 'formats/editorconfig.c', 'formats/ini.c', 'formats/json.c', 'formats/lines.c', 'formats/tap.c', 'formats/xml.c', 'functions/array.c', 'functions/boolean.c', 'functions/both_libs.c', 'functions/build_target.c', 'functions/compiler.c', 'functions/configuration_data.c', 'functions/custom_target.c', 'functions/dependency.c', 'functions/dict.c', 'functions/disabler.c', 'functions/environment.c', 'functions/external_program.c', 'functions/feature_opt.c', 'functions/file.c', 'functions/generator.c', 'functions/kernel.c', 'functions/kernel/build_target.c', 'functions/kernel/configure_file.c', 'functions/kernel/custom_target.c', 'functions/kernel/dependency.c', 'functions/kernel/install.c', 'functions/kernel/options.c', 'functions/kernel/subproject.c', 'functions/machine.c', 'functions/meson.c', 'functions/modules.c', 'functions/modules/curl.c', 'functions/modules/fs.c', 'functions/modules/getopt.c', 'functions/modules/json.c', 'functions/modules/keyval.c', 'functions/modules/pkgconfig.c', 'functions/modules/python.c', 'functions/modules/sourceset.c', 'functions/modules/subprojects.c', 'functions/modules/toolchain.c', 'functions/number.c', 'functions/run_result.c', 'functions/source_configuration.c', 'functions/source_set.c', 'functions/string.c', 'functions/subproject.c', 'lang/analyze.c', 'lang/compiler.c', 'lang/eval.c', 'lang/fmt.c', 'lang/func_lookup.c', 'lang/lexer.c', 'lang/lsp.c', 'lang/object.c', 'lang/object_iterators.c', 'lang/parser.c', 'lang/serial.c', 'lang/string.c', 'lang/typecheck.c', 'lang/vm.c', 'lang/workspace.c', 'args.c', 'cmd_install.c', 'cmd_subprojects.c', 'cmd_test.c', 'coerce.c', 'compilers.c', 'embedded.c', 'error.c', 'guess.c', 'install.c', 'log.c', 'machine_file.c', 'machines.c', 'main.c', 'memmem.c', 'meson_opts.c', 'options.c', 'opts.c', 'rpmvercmp.c', 'sha_256.c', 'vsenv.c', 'wrap.c', ) deps = [] subdir('platform') src += platform_sources # version information src += configure_file( configuration: version_info, input: 'version.c.in', output: 'version.c', ) # embedded scripts subdir('script') src += script_sources # dependencies subdir('external') src += dep_sources deps += external_deps if get_option('ui').enabled() subdir('ui') else src += files('ui_null.c') endif muon-v0.5.0/src/amalgam.c0000644000175000017500000001150115041716357014201 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifdef __sun #define __EXTENSIONS__ #endif #include "args.c" #include "backend/backend.c" #include "backend/common_args.c" #include "backend/introspect.c" #include "backend/ninja.c" #include "backend/ninja/alias_target.c" #include "backend/ninja/build_target.c" #include "backend/ninja/coverage.c" #include "backend/ninja/custom_target.c" #include "backend/ninja/rules.c" #include "backend/output.c" #include "backend/xcode.c" #include "cmd_install.c" #include "cmd_subprojects.c" #include "cmd_test.c" #include "coerce.c" #include "compilers.c" #include "datastructures/arr.c" #include "datastructures/bucket_arr.c" #include "datastructures/hash.c" #include "datastructures/stack.c" #include "embedded.c" #include "error.c" #include "external/libarchive_null.c" #include "external/libcurl_null.c" #include "external/readline_builtin.c" #include "formats/editorconfig.c" #include "formats/ini.c" #include "formats/json.c" #include "formats/lines.c" #include "formats/tap.c" #include "formats/xml.c" #include "functions/array.c" #include "functions/boolean.c" #include "functions/both_libs.c" #include "functions/build_target.c" #include "functions/compiler.c" #include "functions/configuration_data.c" #include "functions/custom_target.c" #include "functions/dependency.c" #include "functions/dict.c" #include "functions/disabler.c" #include "functions/environment.c" #include "functions/external_program.c" #include "functions/feature_opt.c" #include "functions/file.c" #include "functions/generator.c" #include "functions/kernel.c" #include "functions/kernel/build_target.c" #include "functions/kernel/configure_file.c" #include "functions/kernel/custom_target.c" #include "functions/kernel/dependency.c" #include "functions/kernel/install.c" #include "functions/kernel/options.c" #include "functions/kernel/subproject.c" #include "functions/machine.c" #include "functions/meson.c" #include "functions/modules.c" #include "functions/modules/curl.c" #include "functions/modules/fs.c" #include "functions/modules/getopt.c" #include "functions/modules/json.c" #include "functions/modules/keyval.c" #include "functions/modules/pkgconfig.c" #include "functions/modules/python.c" #include "functions/modules/sourceset.c" #include "functions/modules/subprojects.c" #include "functions/modules/toolchain.c" #include "functions/number.c" #include "functions/run_result.c" #include "functions/source_configuration.c" #include "functions/source_set.c" #include "functions/string.c" #include "functions/subproject.c" #include "guess.c" #include "install.c" #include "lang/analyze.c" #include "lang/compiler.c" #include "lang/eval.c" #include "lang/fmt.c" #include "lang/func_lookup.c" #include "lang/lexer.c" #include "lang/lsp.c" #include "lang/object.c" #include "lang/object_iterators.c" #include "lang/parser.c" #include "lang/serial.c" #include "lang/string.c" #include "lang/typecheck.c" #include "lang/vm.c" #include "lang/workspace.c" #include "log.c" #include "machine_file.c" #include "machines.c" #include "main.c" #include "memmem.c" #include "meson_opts.c" #include "options.c" #include "opts.c" #include "platform/assert.c" #include "platform/filesystem.c" #include "platform/mem.c" #include "platform/os.c" #include "platform/path.c" #include "platform/run_cmd.c" #include "platform/uname.c" #include "rpmvercmp.c" #include "sha_256.c" #include "ui_null.c" #include "version.c.in" #include "vsenv.c" #include "wrap.c" #ifdef _WIN32 #include "platform/windows/filesystem.c" #include "platform/windows/init.c" #include "platform/windows/log.c" #include "platform/windows/os.c" #include "platform/windows/path.c" #include "platform/windows/rpath_fixer.c" #include "platform/windows/run_cmd.c" #include "platform/windows/term.c" #include "platform/windows/timer.c" #include "platform/windows/uname.c" #include "platform/windows/win32_error.c" #else #include "platform/null/rpath_fixer.c" #include "platform/posix/filesystem.c" #include "platform/posix/init.c" #include "platform/posix/log.c" #include "platform/posix/os.c" #include "platform/posix/path.c" #include "platform/posix/run_cmd.c" #include "platform/posix/term.c" #include "platform/posix/timer.c" #include "platform/posix/uname.c" #endif #include "external/pkgconfig.c" #include "external/pkgconfig_exec.c" #ifdef BOOTSTRAP_NO_SAMU #include "external/samurai_null.c" #else #include "external/samurai.c" #include "external/samurai/build.c" #include "external/samurai/deps.c" #include "external/samurai/env.c" #include "external/samurai/graph.c" #include "external/samurai/htab.c" #include "external/samurai/log.c" #include "external/samurai/parse.c" #include "external/samurai/samu.c" #include "external/samurai/scan.c" #include "external/samurai/tool.c" #include "external/samurai/tree.c" #include "external/samurai/util.c" #endif muon-v0.5.0/src/cmd_install.c0000644000175000017500000001767515041716357015115 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "backend/output.h" #include "cmd_install.h" #include "functions/environment.h" #include "lang/serial.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/rpath_fixer.h" #include "platform/run_cmd.h" struct copy_subdir_ctx { obj exclude_directories; obj exclude_files; bool has_perm; uint32_t perm; const char *src_base, *dest_base; const char *src_root; struct workspace *wk; }; static enum iteration_result copy_subdir_iter(void *_ctx, const char *path) { struct copy_subdir_ctx *ctx = _ctx; TSTR(src); TSTR(dest); path_join(ctx->wk, &src, ctx->src_base, path); path_join(ctx->wk, &dest, ctx->dest_base, path); TSTR(rel); path_relative_to(ctx->wk, &rel, ctx->src_root, src.buf); obj rel_str = tstr_into_str(ctx->wk, &rel); if (fs_dir_exists(src.buf)) { if (ctx->exclude_directories && obj_array_in(ctx->wk, ctx->exclude_directories, rel_str)) { LOG_I("skipping dir '%s'", src.buf); return ir_cont; } LOG_I("make dir '%s'", dest.buf); if (!fs_mkdir(dest.buf, true)) { return ir_err; } struct copy_subdir_ctx new_ctx = { .exclude_directories = ctx->exclude_directories, .exclude_files = ctx->exclude_files, .has_perm = ctx->has_perm, .perm = ctx->perm, .src_root = ctx->src_root, .src_base = src.buf, .dest_base = dest.buf, .wk = ctx->wk, }; if (!fs_dir_foreach(src.buf, &new_ctx, copy_subdir_iter)) { return ir_err; } } else if (fs_symlink_exists(src.buf) || fs_file_exists(src.buf)) { if (ctx->exclude_files && obj_array_in(ctx->wk, ctx->exclude_files, rel_str)) { LOG_I("skipping file '%s'", src.buf); return ir_cont; } LOG_I("install '%s' -> '%s'", src.buf, dest.buf); if (!fs_copy_file(src.buf, dest.buf, 0)) { return ir_err; } } else { LOG_E("unhandled file type '%s'", path); return ir_err; } if (ctx->has_perm && !fs_chmod(dest.buf, ctx->perm)) { return ir_err; } return ir_cont; } struct install_ctx { struct install_options *opts; obj prefix; obj full_prefix; obj destdir; }; static enum iteration_result install_iter(struct workspace *wk, void *_ctx, obj v_id) { struct install_ctx *ctx = _ctx; struct obj_install_target *in = get_obj_install_target(wk, v_id); TSTR(dest_dirname); const char *dest = get_cstr(wk, in->dest), *src = get_cstr(wk, in->src); assert(in->type == install_target_symlink || in->type == install_target_emptydir || path_is_absolute(src)); TSTR(full_dest_dir); if (ctx->destdir) { path_join_absolute(wk, &full_dest_dir, get_cstr(wk, ctx->destdir), dest); dest = full_dest_dir.buf; } switch (in->type) { case install_target_default: LOG_I("install '%s' -> '%s'", src, dest); break; case install_target_subdir: LOG_I("install subdir '%s' -> '%s'", src, dest); break; case install_target_symlink: LOG_I("install symlink '%s' -> '%s'", dest, src); break; case install_target_emptydir: LOG_I("install emptydir '%s'", dest); break; default: abort(); } if (ctx->opts->dry_run) { return ir_cont; } switch (in->type) { case install_target_default: case install_target_symlink: path_dirname(wk, &dest_dirname, dest); if (fs_exists(dest_dirname.buf) && !fs_dir_exists(dest_dirname.buf)) { LOG_E("dest '%s' exists and is not a directory", dest_dirname.buf); return ir_err; } if (!fs_mkdir_p(dest_dirname.buf)) { return ir_err; } if (in->type == install_target_default) { if (fs_dir_exists(src)) { if (!fs_copy_dir(src, dest, true)) { return ir_err; } } else { if (!fs_copy_file(src, dest, true)) { return ir_err; } } if (in->build_target) { if (!fix_rpaths(dest, wk->build_root)) { return ir_err; } } } else { if (!fs_make_symlink(src, dest, true)) { return ir_err; } } break; case install_target_subdir: if (!fs_mkdir_p(dest)) { return ir_err; } struct copy_subdir_ctx ctx = { .exclude_directories = in->exclude_directories, .exclude_files = in->exclude_files, .has_perm = in->has_perm, .perm = in->perm, .src_root = src, .src_base = src, .dest_base = dest, .wk = wk, }; if (!fs_dir_foreach(src, &ctx, copy_subdir_iter)) { return ir_err; } break; case install_target_emptydir: if (!fs_mkdir_p(dest)) { return ir_err; } break; default: abort(); } if (in->has_perm && !fs_chmod(dest, in->perm)) { return ir_err; } return ir_cont; } static enum iteration_result install_scripts_iter(struct workspace *wk, void *_ctx, obj install_script) { struct install_ctx *ctx = _ctx; obj install_script_skip_if_destdir, install_script_dry_run, install_script_cmdline; install_script_skip_if_destdir = obj_array_index(wk, install_script, 0); install_script_dry_run = obj_array_index(wk, install_script, 1); install_script_cmdline = obj_array_index(wk, install_script, 2); bool script_skip_if_destdir = get_obj_bool(wk, install_script_skip_if_destdir); bool script_can_dry_run = get_obj_bool(wk, install_script_dry_run); obj env; env = make_obj(wk, obj_dict); if (ctx->destdir) { obj_dict_set(wk, env, make_str(wk, "DESTDIR"), ctx->destdir); } obj_dict_set(wk, env, make_str(wk, "MESON_INSTALL_PREFIX"), ctx->prefix); obj_dict_set(wk, env, make_str(wk, "MESON_INSTALL_DESTDIR_PREFIX"), ctx->full_prefix); if (ctx->opts->dry_run && script_can_dry_run) { obj_dict_set(wk, env, make_str(wk, "MESON_INSTALL_DRY_RUN"), make_str(wk, "1")); } set_default_environment_vars(wk, env, false); const char *argstr, *envstr; uint32_t argc, envc; env_to_envstr(wk, &envstr, &envc, env); join_args_argstr(wk, &argstr, &argc, install_script_cmdline); if (ctx->destdir && script_skip_if_destdir) { LOG_I("skipping install script because DESTDIR is set '%s'", argstr); return ir_cont; } LOG_I("running install script '%s'", argstr); if (ctx->opts->dry_run && !script_can_dry_run) { return ir_cont; } struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd(&cmd_ctx, argstr, argc, envstr, envc)) { LOG_E("failed to run install script: %s", cmd_ctx.err_msg); goto err; } if (cmd_ctx.status != 0) { LOG_E("install script failed"); LOG_E("stdout: %s", cmd_ctx.out.buf); LOG_E("stderr: %s", cmd_ctx.err.buf); goto err; } run_cmd_ctx_destroy(&cmd_ctx); return ir_cont; err: run_cmd_ctx_destroy(&cmd_ctx); return ir_err; } bool install_run(struct install_options *opts) { bool ret = true; TSTR_manual(install_src); path_join(NULL, &install_src, output_path.private_dir, output_path.install); FILE *f; f = fs_fopen(install_src.buf, "rb"); tstr_destroy(&install_src); if (!f) { return false; } struct workspace wk; workspace_init_bare(&wk); obj install; if (!serial_load(&wk, &install, f)) { LOG_E("failed to load %s", output_path.install); goto ret; } else if (!fs_fclose(f)) { goto ret; } struct install_ctx ctx = { .opts = opts, }; obj install_targets, install_scripts, source_root; install_targets = obj_array_index(&wk, install, 0); install_scripts = obj_array_index(&wk, install, 1); source_root = obj_array_index(&wk, install, 2); ctx.prefix = obj_array_index(&wk, install, 3); TSTR(build_root); path_copy_cwd(&wk, &build_root); wk.build_root = get_cstr(&wk, tstr_into_str(&wk, &build_root)); wk.source_root = get_cstr(&wk, source_root); if ((opts->destdir)) { TSTR(full_prefix); TSTR(abs_destdir); path_make_absolute(&wk, &abs_destdir, opts->destdir); path_join_absolute(&wk, &full_prefix, abs_destdir.buf, get_cstr(&wk, ctx.prefix)); ctx.full_prefix = tstr_into_str(&wk, &full_prefix); ctx.destdir = tstr_into_str(&wk, &abs_destdir); } else { ctx.full_prefix = ctx.prefix; } obj_array_foreach(&wk, install_targets, &ctx, install_iter); obj_array_foreach(&wk, install_scripts, &ctx, install_scripts_iter); ret = true; ret: workspace_destroy_bare(&wk); return ret; } muon-v0.5.0/src/vsenv.c0000644000175000017500000001213315041716357013745 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "lang/string.h" #include "log.h" #include "machines.h" #include "platform/filesystem.h" #include "platform/os.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "vsenv.h" static const char *vsenv_list_sep = "---SPLIT---"; static void vsenv_set_vars(const char *vars, uint32_t len) { bool sep_seen = false; uint32_t i; for (i = 0; i < len;) { const char *line = vars + i, *p = strstr(line, "\r\n"); if (!p) { break; } uint32_t line_len = p - line; struct str l = { line, line_len }, k, v; if (!sep_seen) { if (str_eql(&l, &STRL(vsenv_list_sep))) { sep_seen = true; } } else if (str_split_in_two(&l, &k, &v, '=')) { os_set_env(&k, &v); } i += line_len + 2; } } bool vsenv_setup(const char *cache_path, bool force) { if (os_get_env("VSINSTALLDIR")) { return true; } else if (fs_has_cmd("cl.exe")) { return true; } if (cache_path && fs_file_exists(cache_path)) { struct source src; if (fs_read_entire_file(cache_path, &src)) { vsenv_set_vars(src.src, src.len); fs_source_destroy(&src); return true; } } if (!force) { const char *comps[] = { "cc", "gcc", "clang", "clang-cl", }; uint32_t i; for (i = 0; i < ARRAY_LEN(comps); ++i) { if (fs_has_cmd(comps[i])) { return true; } } } bool res = false; char tmp_path[512] = { 0 }; TSTR_manual(path); TSTR_manual(ver); const char *program_files; struct run_cmd_ctx vswhere_cmd_ctx = { 0 }, bat_cmd_ctx = { 0 }; uint32_t i; if (!(program_files = os_get_env("ProgramFiles(x86)"))) { LOG_E("vsenv: unable to get value of 'ProgramFiles(x86)' env var"); goto ret; } path_join(0, &path, program_files, "Microsoft Visual Studio/Installer/vswhere.exe"); if (!fs_file_exists(path.buf)) { LOG_E("vsenv: vswhere.exe not found @ %s", path.buf); goto ret; } if (!run_cmd_argv(&vswhere_cmd_ctx, (char *const[]){ path.buf, "-latest", "-prerelease", "-requiresAny", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-requires", "Microsoft.VisualStudio.Workload.WDExpress", "-products", "*", "-utf8", 0, }, 0, 0)) { LOG_E("vsenv: failed to execute vswhere"); goto ret; } if (vswhere_cmd_ctx.status != 0) { LOG_E("vsenv: exited with error status %d", vswhere_cmd_ctx.status); goto ret; } tstr_clear(&path); struct str installation_path = { 0 }, installation_name = { 0 }; struct { struct str label, *dest; } keys[] = { { STR("installationPath: "), &installation_path }, { STR("installationName: "), &installation_name }, }; for (i = 0; i < vswhere_cmd_ctx.out.len;) { const char *line = vswhere_cmd_ctx.out.buf + i, *p = strstr(line, "\r\n"); if (!p) { break; } uint32_t line_len = p - line; struct str l = { line, line_len }; for (uint32_t i = 0; i < ARRAY_LEN(keys); ++i) { if (str_startswith(&l, &keys[i].label)) { l.s += keys[i].label.len; l.len -= keys[i].label.len; *keys[i].dest = l; break; } } if (installation_name.len && installation_path.len) { break; } i += line_len + 2; } if (!(installation_name.len && installation_path.len)) { LOG_E("vsenv: failed to parse vswhere output"); goto ret; } tstr_clear(&path); tstr_pushn(0, &path, installation_path.s, installation_path.len); if (strcmp(build_machine.cpu, "arm64") == 0) { path_push(0, &path, "VC/Auxiliary/Build/vcvarsarm64.bat"); if (!fs_file_exists(path.buf)) { tstr_clear(&path); tstr_pushn(0, &path, installation_path.s, installation_path.len); path_push(0, &path, "VC/Auxiliary/Build/vcvarsx86_arm64.bat"); } } else { path_push(0, &path, "VC/Auxiliary/Build/vcvars64.bat"); // if VS is not found try VS Express if (!fs_file_exists(path.buf)) { tstr_clear(&path); tstr_pushn(0, &path, installation_path.s, installation_path.len); path_push(0, &path, "VC/Auxiliary/Build/vcvarsx86_amd64.bat"); } } if (!fs_file_exists(path.buf)) { LOG_E("vsenv: failed to locate vcvars @ %s", path.buf); goto ret; } L("vsenv: loading %.*s @ %s", installation_name.len, installation_name.s, path.buf); FILE *tmp = fs_make_tmp_file("vsenv_activate", ".bat", tmp_path, sizeof(tmp_path)); if (!tmp) { LOG_E("vsenv: failed to create temporary file"); goto ret; } fprintf(tmp, "@ECHO OFF\r\n" "call \"%s\"\r\n" "ECHO %s\r\n" "SET\r\n", path.buf, vsenv_list_sep); fs_fclose(tmp); if (!run_cmd_argv(&bat_cmd_ctx, (char *const[]){ tmp_path, 0 }, 0, 0)) { LOG_E("vsenv: failed to execute %s", tmp_path); goto ret; } vsenv_set_vars(bat_cmd_ctx.out.buf, bat_cmd_ctx.out.len); if (cache_path) { L("writing %s", cache_path); if (!fs_write(cache_path, (const uint8_t *)bat_cmd_ctx.out.buf, bat_cmd_ctx.out.len)) { goto ret; } } res = true; ret: tstr_destroy(&path); tstr_destroy(&ver); run_cmd_ctx_destroy(&vswhere_cmd_ctx); run_cmd_ctx_destroy(&bat_cmd_ctx); if (*tmp_path) { fs_remove(tmp_path); } return res; } muon-v0.5.0/src/platform/0002755000175000017500000000000015041716357014266 5ustar buildbuildmuon-v0.5.0/src/platform/posix/0002755000175000017500000000000015041716357015430 5ustar buildbuildmuon-v0.5.0/src/platform/posix/path.c0000644000175000017500000000063215041716357016527 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "lang/string.h" #include "platform/path.h" bool path_is_absolute(const char *path) { return *path == PATH_SEP; } bool path_is_basename(const char *path) { return strchr(path, PATH_SEP) == NULL; } void path_to_posix(char *path) { (void)path; } muon-v0.5.0/src/platform/posix/rpath_fixer.c0000644000175000017500000001600615041716357020110 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifdef __sun #include /* Elf32_Dyn and similar on Solaris */ #endif #include #include #include "buf_size.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/rpath_fixer.h" #include "platform/uname.h" #ifndef DT_MIPS_RLD_MAP_REL #define DT_MIPS_RLD_MAP_REL 1879048245 #endif enum elf_class { elf_class_32, elf_class_64 }; struct elf { enum elf_class class; enum endianness endian; uint64_t shoff; uint16_t shentsize, shnum; }; struct elf_section { uint64_t off, size; uint32_t type; uint32_t entsize; uint32_t len; bool found; }; struct elf_dynstr { uint64_t off; uint32_t tag; uint32_t index; bool found; }; union elf_hdrbuf { Elf64_Ehdr e64; Elf32_Ehdr e32; Elf64_Shdr s64; Elf32_Shdr s32; Elf64_Dyn d64; Elf32_Dyn d32; char bytes[BUF_SIZE_2k]; }; #define EHDR(BUF, CL, FLD) CL == elf_class_32 ? BUF.e32.FLD : BUF.e64.FLD #define SHDR(BUF, CL, FLD) CL == elf_class_32 ? BUF.s32.FLD : BUF.s64.FLD #define DHDR(BUF, CL, FLD) CL == elf_class_32 ? BUF.d32.FLD : BUF.d64.FLD static bool parse_elf(FILE *f, struct elf *elf) { uint8_t ident[EI_NIDENT]; size_t r = fread(ident, 1, EI_NIDENT, f); if (r != EI_NIDENT) { return false; } const uint8_t magic[4] = { ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3 }; if (memcmp(ident, magic, 4) != 0) { return false; } uint32_t hdr_size; switch (ident[EI_CLASS]) { case ELFCLASS32: hdr_size = sizeof(Elf32_Ehdr); elf->class = elf_class_32; break; case ELFCLASS64: hdr_size = sizeof(Elf64_Ehdr); elf->class = elf_class_64; break; case ELFCLASSNONE: default: return false; } switch (ident[EI_DATA]) { case ELFDATA2LSB: elf->endian = little_endian; break; case ELFDATA2MSB: elf->endian = big_endian; break; case ELFDATANONE: default: return false; } union elf_hdrbuf buf = { 0 }; assert(hdr_size <= sizeof(buf)); r = fread(&buf.bytes[EI_NIDENT], 1, hdr_size - EI_NIDENT, f); if (r != hdr_size - EI_NIDENT) { return false; } elf->shoff = EHDR(buf, elf->class, e_shoff); elf->shentsize = EHDR(buf, elf->class, e_shentsize); elf->shnum = EHDR(buf, elf->class, e_shnum); return true; } bool parse_elf_sections(FILE *f, struct elf *elf, struct elf_section *sections[]) { uint32_t i, j; union elf_hdrbuf buf; assert(elf->shentsize <= sizeof(buf)); struct elf_section tmp; if (!fs_fseek(f, elf->shoff)) { return false; } for (i = 0; i < elf->shnum; ++i) { if (!fs_fread(buf.bytes, elf->shentsize, f)) { return false; } tmp.type = SHDR(buf, elf->class, sh_type); tmp.off = SHDR(buf, elf->class, sh_offset); tmp.entsize = SHDR(buf, elf->class, sh_entsize); tmp.size = SHDR(buf, elf->class, sh_size); tmp.len = tmp.entsize ? tmp.size / tmp.entsize : 0; for (j = 0; sections[j]; ++j) { if (tmp.type != sections[j]->type) { continue; } *sections[j] = tmp; sections[j]->found = true; break; } bool all_found = true; for (j = 0; sections[j]; ++j) { all_found &= sections[j]->found; } if (all_found) { return true; } } return false; } static bool parse_elf_dynamic(FILE *f, struct elf *elf, struct elf_section *s_dynamic, struct elf_dynstr *strs[]) { uint32_t i, j; union elf_hdrbuf buf; assert(s_dynamic->entsize <= sizeof(buf)); struct elf_dynstr tmp; if (!fs_fseek(f, s_dynamic->off)) { return false; } for (i = 0; i < s_dynamic->len; ++i) { if (!fs_fread(buf.bytes, s_dynamic->entsize, f)) { return false; } tmp.tag = DHDR(buf, elf->class, d_tag); tmp.off = DHDR(buf, elf->class, d_un.d_val); for (j = 0; strs[j]; ++j) { if (tmp.tag != strs[j]->tag) { continue; } *strs[j] = tmp; strs[j]->found = true; strs[j]->index = i; break; } } return true; } static bool remove_paths(FILE *f, struct elf_section *s_dynstr, struct elf_dynstr *str, const char *build_root, bool *cleared) { char rpath[BUF_SIZE_4k]; uint32_t rpath_len = 0, cur_off = s_dynstr->off + str->off, copy_to = cur_off; bool modified = false, preserve_separator = false; *cleared = true; if (!fs_fseek(f, cur_off)) { return false; } for (;; ++cur_off) { char c; if (!fs_fread(&c, 1, f)) { return false; } if (!c || c == ':') { assert(rpath_len <= ARRAY_LEN(rpath)); rpath[rpath_len] = 0; if (path_is_subpath(build_root, rpath) || rpath_len == 0) { modified = true; } else { if (modified) { if (!fs_fseek(f, copy_to + (preserve_separator ? 1 : 0))) { return false; } else if (!fs_fwrite(rpath, rpath_len, f)) { return false; } else if (!fs_fwrite(&c, 1, f)) { return false; } else if (!fs_fseek(f, cur_off + 1)) { return false; } } copy_to += rpath_len; preserve_separator = c == ':'; *cleared = false; } rpath_len = 0; if (!c) { break; } else if (c == ':') { continue; } } assert(rpath_len < ARRAY_LEN(rpath)); rpath[rpath_len] = c; ++rpath_len; } if (!fs_fseek(f, copy_to)) { return false; } else if (!fs_fwrite((char[]){ 0 }, 1, f)) { return false; } return true; } static bool remove_path_entry(FILE *f, struct elf *elf, struct elf_section *s_dynamic, struct elf_dynstr *entry) { char buf[BUF_SIZE_2k]; assert(s_dynamic->entsize <= ARRAY_LEN(buf)); uint32_t i; for (i = entry->index + 1; i < s_dynamic->len; ++i) { if (!fs_fseek(f, s_dynamic->off + (s_dynamic->entsize * i))) { return false; } if (!fs_fread(buf, s_dynamic->entsize, f)) { return false; } if (!fs_fseek(f, s_dynamic->off + (s_dynamic->entsize * (i - 1)))) { return false; } if (!fs_fwrite(buf, s_dynamic->entsize, f)) { return false; } } struct elf_dynstr mips_rld_map_rel = { .tag = DT_MIPS_RLD_MAP_REL }; if (!parse_elf_dynamic(f, elf, s_dynamic, (struct elf_dynstr *[]){ &mips_rld_map_rel, NULL })) { return false; } if (mips_rld_map_rel.found) { LOG_W("TODO: fix mips_rld_map_rel"); } return true; } bool fix_rpaths(const char *elf_path, const char *build_root) { bool ret = false; FILE *f = NULL; if (!(f = fs_fopen(elf_path, "r+"))) { return false; } struct elf elf; if (!parse_elf(f, &elf)) { // the file is not an elf file ret = true; goto ret; } struct elf_section s_dynamic = { .type = SHT_DYNAMIC }, s_dynstr = { .type = SHT_STRTAB }; if (!parse_elf_sections(f, &elf, (struct elf_section *[]){ &s_dynamic, &s_dynstr, NULL })) { goto ret; } struct elf_dynstr rpaths[] = { { .tag = DT_RPATH }, { .tag = DT_RUNPATH } }; if (!parse_elf_dynamic(f, &elf, &s_dynamic, (struct elf_dynstr *[]){ &rpaths[0], &rpaths[1], NULL })) { goto ret; } uint32_t i; for (i = 0; i < ARRAY_LEN(rpaths); ++i) { if (!rpaths[i].found) { continue; } bool cleared; if (!remove_paths(f, &s_dynstr, &rpaths[i], build_root, &cleared)) { goto ret; } if (cleared) { if (!remove_path_entry(f, &elf, &s_dynamic, &rpaths[i])) { goto ret; } } } ret = true; ret: if (f) { if (!fs_fclose(f)) { return false; } } return ret; } muon-v0.5.0/src/platform/posix/filesystem.c0000644000175000017500000002265415041716357017767 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include #include #include #include #include "buf_size.h" #include "lang/string.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/os.h" #include "platform/path.h" static bool fs_lstat(const char *path, struct stat *sb) { if (lstat(path, sb) != 0) { LOG_E("failed lstat(%s): %s", path, strerror(errno)); return false; } return true; } enum fs_mtime_result fs_mtime(const char *path, int64_t *mtime) { struct stat st; if (stat(path, &st) < 0) { if (errno != ENOENT) { LOG_E("failed stat(%s): %s", path, strerror(errno)); return fs_mtime_result_err; } return fs_mtime_result_not_found; } else { #ifdef __APPLE__ *mtime = (int64_t)st.st_mtime * 1000000000 + st.st_mtimensec; /* Illumos hides the members of st_mtim when you define _POSIX_C_SOURCE since it has not been updated to support POSIX.1-2008: https://www.illumos.org/issues/13327 */ #elif defined(__sun) && !defined(__EXTENSIONS__) *mtime = (int64_t)st.st_mtim.__tv_sec * 1000000000 + st.st_mtim.__tv_nsec; #else *mtime = (int64_t)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec; #endif return fs_mtime_result_ok; } } bool fs_exists(const char *path) { return access(path, F_OK) == 0; } bool fs_symlink_exists(const char *path) { struct stat sb; // use lstat here instead of fs_lstat because we want to ignore errors return lstat(path, &sb) == 0 && S_ISLNK(sb.st_mode); } static bool fs_lexists(const char *path) { return fs_exists(path) || fs_symlink_exists(path); } bool fs_file_exists(const char *path) { struct stat sb; if (access(path, F_OK) != 0) { return false; } else if (!fs_stat(path, &sb)) { return false; } else if (!S_ISREG(sb.st_mode)) { return false; } return true; } bool fs_exe_exists(const char *path) { struct stat sb; if (access(path, X_OK) != 0) { return false; } else if (!fs_stat(path, &sb)) { return false; } else if (!S_ISREG(sb.st_mode)) { return false; } return true; } bool fs_dir_exists(const char *path) { struct stat sb; if (access(path, F_OK) != 0) { return false; } else if (!fs_stat(path, &sb)) { return false; } else if (!S_ISDIR(sb.st_mode)) { return false; } return true; } bool fs_mkdir(const char *path, bool exist_ok) { if (mkdir(path, 0755) == -1) { if (exist_ok && errno == EEXIST) { return true; } LOG_E("failed to create directory %s: %s", path, strerror(errno)); return false; } return true; } bool fs_rmdir(const char *path, bool force) { if (rmdir(path) == -1) { if (force) { return true; } LOG_E("failed to remove directory %s: %s", path, strerror(errno)); return false; } return true; } static bool fs_copy_link(const char *src, const char *dest) { bool res = false; ssize_t n; char *buf; struct stat st; if (!fs_lstat(src, &st)) { return false; } if (!S_ISLNK(st.st_mode)) { return false; } // TODO: allow pseudo-files? assert(st.st_size > 0); buf = z_malloc(st.st_size + 1); n = readlink(src, buf, st.st_size); if (n == -1) { LOG_E("readlink('%s') failed: %s", src, strerror(errno)); goto ret; } buf[n] = '\0'; res = fs_make_symlink(buf, dest, true); ret: z_free(buf); return res; } static bool fs_close(int fd) { if (close(fd) == -1) { LOG_E("failed close(): %s", strerror(errno)); return false; } return true; } static bool fs_write_fd(int fd, const void *buf_v, size_t len) { const char *buf = buf_v; while (len > 0) { ssize_t w = write(fd, buf, len); if (w < 0) { if (errno == EINTR) { continue; } LOG_E("failed write(): %s", strerror(errno)); return false; } buf += w; len -= w; } return true; } // NOTE: better to also return the dirfd, so that it can use renameat() // instead of rename(). but path_dirname needs a workspace, we don't have one. static int fs_maketmp(const char *file, char **out_tmpname) { char suffix[] = "-XXXXXX"; size_t l = strlen(file) + sizeof(suffix); char *s = *out_tmpname = z_malloc(l); snprintf(s, l, "%s%s", file, suffix); int fd = mkstemp(s); if (fd < 0) { *out_tmpname = NULL; z_free(s); } return fd; } bool fs_copy_file(const char *src, const char *dest, bool force) { bool res = false; FILE *f_src = NULL; int f_dest = 0; char *f_dest_tmpname = NULL; struct stat st; if (!fs_lstat(src, &st)) { goto ret; } if (S_ISLNK(st.st_mode)) { return fs_copy_link(src, dest); } else if (!S_ISREG(st.st_mode)) { LOG_E("unhandled file type"); goto ret; } if (force) { fs_make_writeable_if_exists(dest); } if (!(f_src = fs_fopen(src, "r"))) { goto ret; } if ((f_dest = fs_maketmp(dest, &f_dest_tmpname)) < 0) { LOG_E("failed to create temp destination file %s: %s", dest, strerror(errno)); goto ret; } if (!fs_chmod(f_dest_tmpname, st.st_mode)) { goto ret; } assert(f_dest != 0); size_t r; char buf[BUF_SIZE_32k]; while ((r = fread(buf, 1, BUF_SIZE_32k, f_src)) > 0) { errno = 0; // to ensure that we get the error from write() only if (!fs_write_fd(f_dest, buf, r)) { goto ret; } } if (!feof(f_src)) { LOG_E("incomplete read: %s", strerror(errno)); goto ret; } if (!fs_close(f_dest)) { f_dest = 0; goto ret; } f_dest = 0; // now time to atomically rename the tmpfile into the proper place if (rename(f_dest_tmpname, dest) < 0) { LOG_E("failed rename(): %s", strerror(errno)); goto ret; } { // Attempt to copy file attributes. If this fails it isn't fatal. struct timeval times[2] = { { .tv_sec = st.st_atime, }, { .tv_sec = st.st_mtime, }, }; if (utimes(dest, times) == -1) { L("failed utimes(): %s", strerror(errno)); } } res = true; ret: if (f_dest_tmpname) { if (!res) { unlink(f_dest_tmpname); } z_free(f_dest_tmpname); } if (f_src) { if (!fs_fclose(f_src)) { res = false; } } if (f_dest > 0) { if (!fs_close(f_dest)) { res = false; } } return res; } bool fs_dir_foreach(const char *path, void *_ctx, fs_dir_foreach_cb cb) { DIR *d; struct dirent *ent; if (!(d = opendir(path))) { LOG_E("failed opendir(%s): %s", path, strerror(errno)); return false; } bool loop = true, res = true; while (loop && (ent = readdir(d))) { if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { continue; } switch (cb(_ctx, ent->d_name)) { case ir_cont: break; case ir_done: loop = false; break; case ir_err: loop = false; res = false; break; } } if (closedir(d) != 0) { LOG_E("failed closedir(): %s", strerror(errno)); return false; } return res; } bool fs_remove(const char *path) { if (remove(path) != 0) { LOG_E("failed remove(\"%s\"): %s", path, strerror(errno)); return false; } return true; } bool fs_make_symlink(const char *target, const char *path, bool force) { if (force && fs_lexists(path)) { if (!fs_remove(path)) { return false; } } if (symlink(target, path) != 0) { LOG_E("failed symlink(\"%s\", \"%s\"): %s", target, path, strerror(errno)); return false; } return true; } const char * fs_user_home(void) { return os_get_env("HOME"); } bool fs_is_a_tty_from_fd(int fd) { errno = 0; if (isatty(fd) == 1) { return true; } else { return false; } } bool fs_chmod(const char *path, uint32_t mode) { if (mode & S_ISVTX) { struct stat sb; if (!fs_stat(path, &sb)) { return false; } if (!S_ISDIR(sb.st_mode)) { LOG_E("attempt to set sticky bit on regular file: %s", path); return false; } } if (fs_symlink_exists(path)) { #ifdef AT_FDCWD if (fchmodat(AT_FDCWD, path, (mode_t)mode, AT_SYMLINK_NOFOLLOW) == -1) { #else if (chmod(path, (mode_t)mode)) { #endif if (errno == EOPNOTSUPP) { LOG_W("changing permissions of symlinks not supported"); return true; } LOG_E("failed fchmodat(AT_FCWD, %s, %o, AT_SYMLINK_NOFOLLOW): %s", path, mode, strerror(errno)); return false; } } else if (chmod(path, (mode_t)mode) == -1) { LOG_E("failed chmod(%s, %o): %s", path, mode, strerror(errno)); return false; } return true; } bool fs_find_cmd(struct workspace *wk, struct tstr *buf, const char *cmd) { assert(*cmd); uint32_t len; const char *env_path, *base_start; tstr_clear(buf); if (!path_is_basename(cmd)) { path_make_absolute(wk, buf, cmd); if (fs_exe_exists(buf->buf)) { return true; } else { return false; } } if (!(env_path = os_get_env("PATH"))) { LOG_E("failed to get the value of PATH"); return false; } base_start = env_path; while (true) { if (!*env_path || *env_path == ENV_PATH_SEP) { len = env_path - base_start; tstr_clear(buf); tstr_pushn(wk, buf, base_start, len); base_start = env_path + 1; path_push(wk, buf, cmd); if (fs_exe_exists(buf->buf)) { return true; } if (!*env_path) { break; } } ++env_path; } return false; } bool fs_has_extension(const char *path, const char *ext) { char *s; s = strrchr(path, '.'); if (!s) { return false; } return strcmp(s, ext) == 0; } FILE * fs_make_tmp_file(const char *name, const char *suffix, char *buf, uint32_t len) { return 0; } bool fs_wait_for_input(int fd) { while (true) { struct pollfd fds = { .fd = fd, .events = POLLIN, }; if (poll(&fds, 1, -1) == -1) { LOG_E("poll: %s", strerror(errno)); return false; } if (fds.revents & POLLIN) { break; } } return true; } muon-v0.5.0/src/platform/posix/uname.c0000644000175000017500000000214215041716357016676 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "buf_size.h" #include "error.h" #include "platform/assert.h" #include "platform/uname.h" static struct { struct utsname uname; char machine[BUF_SIZE_2k + 1], sysname[BUF_SIZE_2k + 1]; bool init; } uname_info; static void strncpy_lowered(char *dest, const char *src, uint32_t len) { uint32_t i; char c; for (i = 0; i < len && src[i]; ++i) { c = src[i]; if ('A' <= c && c <= 'Z') { c = (c - 'A') + 'a'; } dest[i] = c; } } static void uname_init(void) { if (uname_info.init) { return; } if (uname(&uname_info.uname) == -1) { UNREACHABLE; } strncpy_lowered(uname_info.machine, uname_info.uname.machine, BUF_SIZE_2k); strncpy_lowered(uname_info.sysname, uname_info.uname.sysname, BUF_SIZE_2k); uname_info.init = true; } const char * uname_sysname(void) { uname_init(); return uname_info.sysname; } const char * uname_machine(void) { uname_init(); return uname_info.machine; } muon-v0.5.0/src/platform/posix/log.c0000644000175000017500000000131115041716357016347 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "platform/log.h" void print_colorized(FILE *out, const char *s, bool strip) { if (!strip) { fwrite(s, 1, strlen(s), out); return; } bool parsing_esc = false; const char *start = s; uint32_t len = 0; for (; *s; ++s) { if (*s == '\033') { if (len) { fwrite(start, 1, len, out); len = 0; } parsing_esc = true; } else if (parsing_esc) { if (*s == 'm') { parsing_esc = false; start = s + 1; } } else { ++len; } } if (len) { fwrite(start, 1, len, out); } } muon-v0.5.0/src/platform/posix/os.c0000644000175000017500000000435615041716357016223 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #if defined(__APPLE__) && defined(MUON_BOOTSTRAPPED) #undef _POSIX_C_SOURCE // for sysctl #include #include #endif #include "lang/string.h" #include "log.h" #include "platform/assert.h" #include "platform/os.h" bool os_chdir(const char *path) { return chdir(path) == 0; } char * os_getcwd(char *buf, size_t size) { return getcwd(buf, size); } int os_getopt(int argc, char *const argv[], const char *optstring) { return getopt(argc, argv, optstring); } int32_t os_ncpus(void) { #if defined(_SC_NPROCESSORS_ONLN) return sysconf(_SC_NPROCESSORS_ONLN); #elif defined(__APPLE__) && defined(MUON_BOOTSTRAPPED) int64_t res; size_t size = sizeof(res); int r = sysctlbyname("hw.activecpu", &res, &size, NULL, 0); if (r == -1) { return -1; } else { return res; } #else return -1; #endif } void os_set_env(const struct str *k, const struct str *v) { TSTR_manual(buf_k); TSTR_manual(buf_v); tstr_pushn(0, &buf_k, k->s, k->len); tstr_push(0, &buf_k, 0); tstr_pushn(0, &buf_v, v->s, v->len); tstr_push(0, &buf_v, 0); setenv(buf_k.buf, buf_v.buf, true); } bool os_is_debugger_attached(void) { #if defined(__APPLE__) && defined(MUON_BOOTSTRAPPED) // From Apple Technical Q&A QA1361 // https://developer.apple.com/library/archive/qa/qa1361/_index.html int junk; int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); assert(junk == 0); // We're being debugged if the P_TRACED flag is set. return ((info.kp_proc.p_flag & P_TRACED) != 0); #else return false; #endif } int32_t os_get_pid(void) { return getpid(); } muon-v0.5.0/src/platform/posix/init.c0000644000175000017500000000144415041716357016540 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "platform/init.h" static struct { void((*handler)(void *ctx)); void *ctx; } platform_sigabrt_handler_ctx; void platform_sigabrt_handler(int signo, siginfo_t *info, void *ctx) { if (platform_sigabrt_handler_ctx.handler) { platform_sigabrt_handler_ctx.handler(platform_sigabrt_handler_ctx.ctx); } } void platform_init(void) { { struct sigaction act = { .sa_flags = SA_SIGINFO, .sa_sigaction = platform_sigabrt_handler, }; sigaction(SIGABRT, &act, 0); } } void platform_set_abort_handler(void((*handler)(void *ctx)), void *ctx) { platform_sigabrt_handler_ctx.handler = handler; platform_sigabrt_handler_ctx.ctx = ctx; } muon-v0.5.0/src/platform/posix/timer.c0000644000175000017500000000252715041716357016720 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "log.h" #include "platform/timer.h" void timer_start(struct timer *t) { #ifdef CLOCK_MONOTONIC if (clock_gettime(CLOCK_MONOTONIC, &t->start) == -1) { LOG_E("clock_gettime: %s", strerror(errno)); } #else if (gettimeofday(&t->start, NULL) == -1) { LOG_E("gettimeofday: %s", strerror(errno)); } #endif } float timer_read(struct timer *t) { #ifdef CLOCK_MONOTONIC struct timespec end; if (clock_gettime(CLOCK_MONOTONIC, &end) == -1) { LOG_E("clock_gettime: %s", strerror(errno)); return 0.0f; } double secs = (double)end.tv_sec - (double)t->start.tv_sec; double ns = ((secs * 1000000000.0) + end.tv_nsec) - t->start.tv_nsec; return (float)ns / 1000000000.0f; #else struct timeval end; if (gettimeofday(&end, NULL) == -1) { LOG_E("gettimeofday: %s", strerror(errno)); return 0.0f; } double secs = (double)end.tv_sec - (double)t->start.tv_sec; double us = ((secs * 1000000.0) + end.tv_usec) - t->start.tv_usec; return (float)us / 1000000.0f; #endif } void timer_sleep(uint64_t nanoseconds) { struct timespec req = { .tv_nsec = nanoseconds, }; nanosleep(&req, NULL); } muon-v0.5.0/src/platform/posix/term.c0000644000175000017500000000130415041716357016537 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifdef __sun /* for struct winsize on Solaris */ #define __EXTENSIONS__ #include #include #endif #include #include #include #include "platform/filesystem.h" #include "platform/term.h" bool term_winsize(int fd, uint32_t *height, uint32_t *width) { *height = 24; *width = 80; if (!fs_is_a_tty_from_fd(fd)) { return true; } struct winsize w = { 0 }; if (ioctl(fd, TIOCGWINSZ, &w) == -1) { return false; } if (w.ws_row) { *height = w.ws_row; } if (w.ws_col) { *width = w.ws_col; } return true; } muon-v0.5.0/src/platform/posix/run_cmd.c0000644000175000017500000002465715041716357017237 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include #include #include "error.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "platform/run_cmd.h" extern char **environ; static const int32_t fail_to_exec_exit_code = 163; enum copy_pipe_result { copy_pipe_result_finished, copy_pipe_result_waiting, copy_pipe_result_failed, }; static enum copy_pipe_result copy_pipe(int pipe, struct tstr *tstr, FILE *tee_out) { ssize_t b; char buf[4096]; while (true) { b = read(pipe, buf, sizeof(buf)); if (b == -1) { if (errno == EAGAIN) { return copy_pipe_result_waiting; } else { return copy_pipe_result_failed; } } else if (b == 0) { return copy_pipe_result_finished; } tstr_pushn(0, tstr, buf, b); if (tee_out) { fwrite(buf, b, 1, tee_out); } } } static enum copy_pipe_result copy_pipes(struct run_cmd_ctx *ctx) { enum copy_pipe_result res; bool tee = ctx->flags & run_cmd_ctx_flag_tee; if ((res = copy_pipe(ctx->pipefd_out[0], &ctx->out, tee ? stdout : 0)) == copy_pipe_result_failed) { return res; } switch (copy_pipe(ctx->pipefd_err[0], &ctx->err, tee ? stderr : 0)) { case copy_pipe_result_waiting: return copy_pipe_result_waiting; case copy_pipe_result_finished: return res; case copy_pipe_result_failed: return copy_pipe_result_failed; default: UNREACHABLE_RETURN; } } static void run_cmd_ctx_close_fds(struct run_cmd_ctx *ctx) { if (ctx->pipefd_err_open[0] && close(ctx->pipefd_err[0]) == -1) { LOG_E("failed to close: %s", strerror(errno)); } ctx->pipefd_err_open[0] = false; if (ctx->pipefd_err_open[1] && close(ctx->pipefd_err[1]) == -1) { LOG_E("failed to close: %s", strerror(errno)); } ctx->pipefd_err_open[1] = false; if (ctx->pipefd_out_open[0] && close(ctx->pipefd_out[0]) == -1) { LOG_E("failed to close: %s", strerror(errno)); } ctx->pipefd_out_open[0] = false; if (ctx->pipefd_out_open[1] && close(ctx->pipefd_out[1]) == -1) { LOG_E("failed to close: %s", strerror(errno)); } ctx->pipefd_out_open[1] = false; if (ctx->input_fd_open && close(ctx->input_fd) == -1) { LOG_E("failed to close: %s", strerror(errno)); } ctx->input_fd_open = false; } enum run_cmd_state run_cmd_collect(struct run_cmd_ctx *ctx) { int status; int r; enum copy_pipe_result pipe_res = 0; while (true) { if (!(ctx->flags & run_cmd_ctx_flag_dont_capture)) { if ((pipe_res = copy_pipes(ctx)) == copy_pipe_result_failed) { return run_cmd_error; } } if ((r = waitpid(ctx->pid, &status, WNOHANG)) == -1) { return run_cmd_error; } else if (r == 0) { if (ctx->flags & run_cmd_ctx_flag_async) { return run_cmd_running; } else { // sleep here for 1ms to give the process some // time to complete struct timespec req = { .tv_nsec = 1000000, }; nanosleep(&req, NULL); } } else { break; } } assert(r == ctx->pid); if (!(ctx->flags & run_cmd_ctx_flag_dont_capture)) { while (pipe_res != copy_pipe_result_finished) { if ((pipe_res = copy_pipes(ctx)) == copy_pipe_result_failed) { return run_cmd_error; } } } run_cmd_ctx_close_fds(ctx); if (WIFEXITED(status)) { ctx->status = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { // TODO: it may be helpful to communicate the signal that // caused the command to terminate, this is available with // `WTERMSIG(status)` ctx->err_msg = "command terminated due to signal"; return run_cmd_error; } else { ctx->err_msg = "command exited abnormally"; return run_cmd_error; } if (ctx->status == fail_to_exec_exit_code) { ctx->err_msg = "command failed to execute"; return run_cmd_error; } return run_cmd_finished; } static bool open_run_cmd_pipe(int fds[2], bool fds_open[2]) { if (pipe(fds) == -1) { LOG_E("failed to create pipe: %s", strerror(errno)); return false; } fds_open[0] = true; fds_open[1] = true; int flags; if ((flags = fcntl(fds[0], F_GETFL)) == -1) { LOG_E("failed to get pipe flags: %s", strerror(errno)); return false; } else if (fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) == -1) { LOG_E("failed to set pipe flag O_NONBLOCK: %s", strerror(errno)); return false; } return true; } static bool run_cmd_internal(struct run_cmd_ctx *ctx, const char *_cmd, char *const *argv, const char *envstr, uint32_t envc) { const char *p; TSTR_manual(cmd); if (!fs_find_cmd(NULL, &cmd, _cmd)) { L("failed to run command '%s': not found", _cmd); ctx->err_msg = "command not found"; return false; } { LL("executing %s:", cmd.buf); char *const *ap; for (ap = argv; *ap; ++ap) { log_plain(log_debug, " '%s'", *ap); } log_plain(log_debug, "\n"); if (envstr) { const char *k; uint32_t i = 0; LL("env:"); p = k = envstr; for (; envc; ++p) { if (!p[0]) { if (!k) { k = p + 1; } else { log_plain(log_debug, " %s='%s'", k, p + 1); k = NULL; if (++i >= envc) { break; } } } } log_plain(log_debug, "\n"); } } if (!ctx->stdin_path) { ctx->stdin_path = "/dev/null"; } ctx->input_fd = open(ctx->stdin_path, O_RDONLY); if (ctx->input_fd == -1) { LOG_E("failed to open %s: %s", ctx->stdin_path, strerror(errno)); goto err; } ctx->input_fd_open = true; if (!(ctx->flags & run_cmd_ctx_flag_dont_capture)) { tstr_init(&ctx->out, 0, 0, tstr_flag_overflow_alloc); tstr_init(&ctx->err, 0, 0, tstr_flag_overflow_alloc); if (!open_run_cmd_pipe(ctx->pipefd_out, ctx->pipefd_out_open)) { goto err; } else if (!open_run_cmd_pipe(ctx->pipefd_err, ctx->pipefd_err_open)) { goto err; } } if ((ctx->pid = fork()) == -1) { goto err; } else if (ctx->pid == 0 /* child */) { if (ctx->chdir) { if (chdir(ctx->chdir) == -1) { LOG_E("failed to chdir to %s: %s", ctx->chdir, strerror(errno)); exit(fail_to_exec_exit_code); } } if (ctx->stdin_path) { if (dup2(ctx->input_fd, 0) == -1) { LOG_E("failed to dup stdin: %s", strerror(errno)); exit(fail_to_exec_exit_code); } } if (!(ctx->flags & run_cmd_ctx_flag_dont_capture)) { if (dup2(ctx->pipefd_out[1], 1) == -1) { LOG_E("failed to dup stdout: %s", strerror(errno)); exit(fail_to_exec_exit_code); } if (dup2(ctx->pipefd_err[1], 2) == -1) { LOG_E("failed to dup stderr: %s", strerror(errno)); exit(fail_to_exec_exit_code); } } if (envstr) { const char *k; uint32_t i = 0; p = k = envstr; for (; envc; ++p) { if (!p[0]) { if (!k) { k = p + 1; } else { int err; if ((err = setenv(k, p + 1, 1)) != 0) { LOG_E("failed to set environment var %s='%s': %s", k, p + 1, strerror(err)); exit(fail_to_exec_exit_code); } k = NULL; if (++i >= envc) { break; } } } } } if (execve(cmd.buf, (char *const *)argv, environ) == -1) { LOG_E("%s: %s", cmd.buf, strerror(errno)); exit(fail_to_exec_exit_code); } abort(); } /* parent */ tstr_destroy(&cmd); if (ctx->pipefd_err_open[1] && close(ctx->pipefd_err[1]) == -1) { LOG_E("failed to close: %s", strerror(errno)); } ctx->pipefd_err_open[1] = false; if (ctx->pipefd_out_open[1] && close(ctx->pipefd_out[1]) == -1) { LOG_E("failed to close: %s", strerror(errno)); } ctx->pipefd_out_open[1] = false; if (ctx->flags & run_cmd_ctx_flag_async) { return true; } return run_cmd_collect(ctx) == run_cmd_finished; err: return false; } static bool build_argv(struct run_cmd_ctx *ctx, struct source *src, const char *argstr, uint32_t argstr_argc, char *const *old_argv, struct tstr *cmd, const char ***argv) { const char *argv0, *new_argv0 = NULL, *new_argv1 = NULL; const char **new_argv; uint32_t argc = 0, argi = 0; if (argstr) { argv0 = argstr; argc = argstr_argc; } else { assert(old_argv); argv0 = old_argv[0]; for (; old_argv[argc]; ++argc) { } } assert(*argv0 && "argv0 cannot be empty"); tstr_clear(cmd); tstr_pushs(NULL, cmd, argv0); if (!path_is_basename(cmd->buf)) { path_make_absolute(NULL, cmd, argv0); if (!fs_exe_exists(cmd->buf)) { if (!run_cmd_determine_interpreter(src, cmd->buf, &ctx->err_msg, &new_argv0, &new_argv1)) { return false; } argc += new_argv1 ? 2 : 1; path_copy(NULL, cmd, new_argv0); } } assert(cmd->len); if (!new_argv0 && old_argv) { *argv = NULL; return true; } new_argv = z_calloc(argc + 1, sizeof(const char *)); argi = 0; if (new_argv0) { push_argv_single(new_argv, &argi, argc, new_argv0); if (new_argv1) { push_argv_single(new_argv, &argi, argc, new_argv1); } } if (argstr) { argstr_pushall(argstr, argstr_argc, new_argv, &argi, argc); } else { uint32_t i; for (i = 0; old_argv[i]; ++i) { push_argv_single(new_argv, &argi, argc, old_argv[i]); } } *argv = new_argv; return true; } bool run_cmd_argv(struct run_cmd_ctx *ctx, char *const *argv, const char *envstr, uint32_t envc) { bool ret = false; struct source src = { 0 }; const char **new_argv = NULL; TSTR_manual(cmd); if (!build_argv(ctx, &src, NULL, 0, argv, &cmd, &new_argv)) { goto err; } if (new_argv) { argv = (char **)new_argv; } ret = run_cmd_internal(ctx, cmd.buf, (char *const *)argv, envstr, envc); err: fs_source_destroy(&src); if (new_argv) { z_free((void *)new_argv); } tstr_destroy(&cmd); return ret; } bool run_cmd(struct run_cmd_ctx *ctx, const char *argstr, uint32_t argc, const char *envstr, uint32_t envc) { bool ret = false; struct source src = { 0 }; const char **argv = NULL; TSTR_manual(cmd); if (!build_argv(ctx, &src, argstr, argc, NULL, &cmd, &argv)) { goto err; } ret = run_cmd_internal(ctx, cmd.buf, (char *const *)argv, envstr, envc); err: fs_source_destroy(&src); if (argv) { z_free((void *)argv); } tstr_destroy(&cmd); return ret; } void run_cmd_ctx_destroy(struct run_cmd_ctx *ctx) { run_cmd_ctx_close_fds(ctx); tstr_destroy(&ctx->out); tstr_destroy(&ctx->err); } bool run_cmd_kill(struct run_cmd_ctx *ctx, bool force) { int r; if (force) { r = kill(ctx->pid, SIGKILL); } else { r = kill(ctx->pid, SIGTERM); } if (r != 0) { LOG_E("error killing process %d: %s", ctx->pid, strerror(errno)); return false; } return true; } bool run_cmd_unsplit(struct run_cmd_ctx *ctx, char *cmd, const char *envstr, uint32_t envc) { assert(false && "this function should only be called under windows"); } muon-v0.5.0/src/platform/meson.build0000644000175000017500000000157415041716357016435 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only # common platform sources platform_sources = files( 'assert.c', 'filesystem.c', 'mem.c', 'os.c', 'path.c', 'run_cmd.c', 'uname.c', ) foreach f : [ 'filesystem.c', 'init.c', 'log.c', 'os.c', 'path.c', 'run_cmd.c', 'term.c', 'timer.c', 'uname.c', ] platform_sources += files(platform / f) endforeach if host_machine.system() == 'windows' include_dir += include_directories('../../include/platform/windows') platform_sources += files( 'windows/rpath_fixer.c', 'windows/win32_error.c', ) endif if platform == 'posix' if host_machine.system() == 'darwin' platform_sources += files('null/rpath_fixer.c') else platform_sources += files('posix/rpath_fixer.c') endif endif muon-v0.5.0/src/platform/path.c0000644000175000017500000002141415041716357015366 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "buf_size.h" #include "error.h" #include "lang/string.h" #include "log.h" #include "platform/assert.h" #include "platform/os.h" #include "platform/path.h" static struct { char cwd_buf[BUF_SIZE_2k], tmp1_buf[BUF_SIZE_2k], tmp2_buf[BUF_SIZE_2k]; struct tstr cwd, tmp1, tmp2; } path_ctx; // These functions are defined in platform//os.c // // They should only be called indirectly through path_chdir and path_cwd though // so the prototypes were removed from os.h. bool os_chdir(const char *path); char *os_getcwd(char *buf, size_t size); static void path_getcwd(void) { tstr_clear(&path_ctx.cwd); while (!os_getcwd(path_ctx.cwd.buf, path_ctx.cwd.cap)) { if (errno == ERANGE) { tstr_grow(NULL, &path_ctx.cwd, path_ctx.cwd.cap); } else { error_unrecoverable("getcwd failed: %s", strerror(errno)); } } } void path_init(void) { tstr_init(&path_ctx.cwd, path_ctx.cwd_buf, ARRAY_LEN(path_ctx.cwd_buf), tstr_flag_overflow_alloc); tstr_init(&path_ctx.tmp1, path_ctx.tmp1_buf, ARRAY_LEN(path_ctx.tmp1_buf), tstr_flag_overflow_alloc); tstr_init(&path_ctx.tmp2, path_ctx.tmp2_buf, ARRAY_LEN(path_ctx.tmp2_buf), tstr_flag_overflow_alloc); path_getcwd(); } void path_deinit(void) { tstr_destroy(&path_ctx.cwd); tstr_destroy(&path_ctx.tmp1); tstr_destroy(&path_ctx.tmp2); } void _path_normalize(struct workspace *wk, struct tstr *buf, bool optimize) { uint32_t parents = 0; char *part, *sep; uint32_t part_len, i, slen = buf->len, blen = 0, sep_len; bool loop = true, skip_part; if (!buf->len) { return; } path_to_posix(buf->buf); part = buf->buf; if (*part == PATH_SEP) { ++part; --slen; ++blen; } while (*part && loop) { if (!(sep = strchr(part, PATH_SEP))) { sep = &part[strlen(part)]; loop = false; } sep_len = *sep ? 1 : 0; part_len = sep - part; skip_part = false; if (!part_len || (part_len == 1 && *part == '.')) { // eliminate empty elements (a//b -> a/b) and // current-dir elements (a/./b -> a/b) skip_part = true; } else if (optimize && part_len == 2 && part[0] == '.' && part[1] == '.') { // convert something like a/../b into b if (parents) { for (i = (part - 2) - buf->buf; i > 0; --i) { if (buf->buf[i] == PATH_SEP) { break; } } if (buf->buf[i] == PATH_SEP) { ++i; } part = &buf->buf[i]; skip_part = true; blen -= (sep - &buf->buf[i]) - part_len; --parents; } else { part = sep + sep_len; } } else { ++parents; part = sep + sep_len; } if (skip_part) { memmove(part, sep + sep_len, slen); } else { blen += part_len + sep_len; } slen -= part_len + sep_len; } if (!blen) { buf->buf[0] = '.'; buf->len = 1; } else if (blen > 1 && buf->buf[blen - 1] == PATH_SEP) { buf->buf[blen - 1] = 0; buf->len = blen - 1; } else { buf->len = blen; } } void path_copy(struct workspace *wk, struct tstr *sb, const char *path) { tstr_clear(sb); tstr_pushs(wk, sb, path); _path_normalize(wk, sb, false); } bool path_chdir(const char *path) { if (!os_chdir(path)) { LOG_E("failed chdir(%s): %s", path, strerror(errno)); return false; } path_getcwd(); return true; } void path_copy_cwd(struct workspace *wk, struct tstr *sb) { path_copy(wk, sb, path_ctx.cwd.buf); } const char * path_cwd(void) { return path_ctx.cwd.buf; } void path_join_absolute(struct workspace *wk, struct tstr *sb, const char *a, const char *b) { path_copy(wk, sb, a); tstr_push(wk, sb, PATH_SEP); tstr_pushs(wk, sb, b); _path_normalize(wk, sb, false); } void path_push(struct workspace *wk, struct tstr *sb, const char *b) { if (!*b) { /* Special-case path_1 / '' to mean "append a / to the current * path". */ _path_normalize(wk, sb, false); tstr_push(wk, sb, PATH_SEP); return; } uint32_t b_len = strlen(b); if (path_is_absolute(b) || !sb->len) { path_copy(wk, sb, b); } else { tstr_push(wk, sb, PATH_SEP); tstr_pushn(wk, sb, b, b_len); _path_normalize(wk, sb, false); } if (sb->buf[sb->len - 1] != PATH_SEP && b[b_len - 1] == PATH_SEP) { tstr_push(wk, sb, PATH_SEP); } } void path_join(struct workspace *wk, struct tstr *sb, const char *a, const char *b) { tstr_clear(sb); path_push(wk, sb, a); path_push(wk, sb, b); } void path_make_absolute(struct workspace *wk, struct tstr *buf, const char *path) { if (path_is_absolute(path)) { path_copy(wk, buf, path); } else { path_join(wk, buf, path_ctx.cwd.buf, path); } } void path_relative_to(struct workspace *wk, struct tstr *buf, const char *base_raw, const char *path_raw) { /* * input: base="/path/to/build/" * path="/path/to/build/tgt/dir/libfoo.a" * output: "tgt/dir/libfoo.a" * * input: base="/path/to/build" * path="/path/to/build/libfoo.a" * output: "libfoo.a" * * input: base="/path/to/build" * path="/path/to/src/asd.c" * output: "../src/asd.c" */ tstr_clear(buf); tstr_clear(&path_ctx.tmp1); tstr_pushs(wk, &path_ctx.tmp1, base_raw); _path_normalize(wk, &path_ctx.tmp1, true); tstr_clear(&path_ctx.tmp2); tstr_pushs(wk, &path_ctx.tmp2, path_raw); _path_normalize(wk, &path_ctx.tmp2, true); const char *base = path_ctx.tmp1.buf, *path = path_ctx.tmp2.buf; if (!path_is_absolute(base)) { LOG_E("base path '%s' is not absolute", base); assert(false); } else if (!path_is_absolute(path)) { LOG_E("path '%s' is not absolute", path); assert(false); } uint32_t i = 0, common_end = 0; if (strcmp(base, path) == 0) { tstr_push(wk, buf, '.'); return; } while (base[i] && path[i] && base[i] == path[i]) { if (base[i] == PATH_SEP) { common_end = i; } ++i; } if ((!base[i] && path[i] == PATH_SEP)) { common_end = i; } else if (!path[i] && base[i] == PATH_SEP) { common_end = i; } if (i <= 1) { /* -> base and path match only at root, so take path */ path_copy(wk, buf, path); return; } if (base[common_end] && base[common_end + 1]) { bool have_part = true; i = common_end + 1; do { if (have_part) { tstr_pushs(wk, buf, ".."); tstr_push(wk, buf, PATH_SEP); have_part = false; } if (base[i] == PATH_SEP) { have_part = true; } ++i; } while (base[i]); } if (path[common_end]) { tstr_pushs(wk, buf, &path[common_end + 1]); } _path_normalize(wk, buf, false); } void path_without_ext(struct workspace *wk, struct tstr *buf, const char *path) { int32_t i; tstr_clear(buf); if (!*path) { return; } bool have_ext = false; TSTR_manual(tmp); path_copy(NULL, &tmp, path); path = tmp.buf; for (i = strlen(path) - 1; i >= 0; --i) { if (path[i] == '.') { have_ext = true; break; } else if (path[i] == PATH_SEP) { break; } } if (have_ext) { tstr_pushn(wk, buf, path, i); } else { path_copy(wk, buf, path); } _path_normalize(wk, buf, false); tstr_destroy(&tmp); } void path_basename(struct workspace *wk, struct tstr *buf, const char *path) { int32_t i; tstr_clear(buf); if (!*path) { return; } TSTR_manual(tmp); path_copy(NULL, &tmp, path); path = tmp.buf; for (i = strlen(path) - 1; i >= 0; --i) { if (path[i] == PATH_SEP) { ++i; break; } } if (i < 0) { i = 0; } tstr_pushs(wk, buf, &path[i]); _path_normalize(wk, buf, false); tstr_destroy(&tmp); } void path_dirname(struct workspace *wk, struct tstr *buf, const char *path) { int32_t i; tstr_clear(buf); if (!*path) { goto return_dot; } TSTR_manual(tmp); path_copy(NULL, &tmp, path); path = tmp.buf; for (i = strlen(path) - 1; i >= 0; --i) { if (path[i] == PATH_SEP) { if (i == 0) { /* make dirname of '/path' be '/', not '' */ tstr_pushn(wk, buf, path, 1); } else { tstr_pushn(wk, buf, path, i); } _path_normalize(wk, buf, false); tstr_destroy(&tmp); return; } } tstr_destroy(&tmp); return_dot: tstr_pushs(wk, buf, "."); } bool path_is_subpath(const char *base, const char *sub) { if (!*base) { return false; } TSTR_manual(base_tmp); TSTR_manual(sub_tmp); path_copy(NULL, &base_tmp, base); base = base_tmp.buf; path_copy(NULL, &sub_tmp, sub); sub = sub_tmp.buf; uint32_t i = 0; while (true) { if (!base[i]) { assert(i); if (sub[i] == PATH_SEP || sub[i - 1] == PATH_SEP) { tstr_destroy(&sub_tmp); tstr_destroy(&base_tmp); return true; } } if (base[i] == sub[i]) { if (!base[i]) { tstr_destroy(&sub_tmp); tstr_destroy(&base_tmp); return true; } } else { tstr_destroy(&sub_tmp); tstr_destroy(&base_tmp); return false; } assert(base[i] && sub[i]); ++i; } } void path_executable(struct workspace *wk, struct tstr *buf, const char *path) { if (path_is_basename(path)) { tstr_clear(buf); tstr_push(wk, buf, '.'); tstr_push(wk, buf, PATH_SEP); tstr_pushs(wk, buf, path); } else { path_copy(wk, buf, path); } } muon-v0.5.0/src/platform/windows/0002755000175000017500000000000015041716357015760 5ustar buildbuildmuon-v0.5.0/src/platform/windows/path.c0000644000175000017500000000226515041716357017063 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "lang/string.h" #include "log.h" /* * reference: * https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#paths */ bool path_is_absolute(const char *path) { size_t length; if (!path || !*path) { return false; } /* \file.txt or \directory, rarely used */ if (*path == '\\') { return true; } // Handle unix paths as well if (*path == '/') { return true; } length = strlen(path); if (length < 3) { return false; } /* c:/ or c:\ case insensitive*/ return (((path[0] >= 'a') && (path[0] <= 'z')) || ((path[0] >= 'A') && (path[0] <= 'Z'))) && (path[1] == ':') && ((path[2] == '/') || (path[2] == '\\')); } bool path_is_basename(const char *path) { const char *iter = path; while (*iter) { if ((*iter == '/') || (*iter == '\\')) { return false; } iter++; } return true; } void path_to_posix(char *path) { char *iter = path; while (*iter) { if (*iter == '\\') { *iter = '/'; } iter++; } } muon-v0.5.0/src/platform/windows/rpath_fixer.c0000644000175000017500000000046315041716357020440 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "platform/rpath_fixer.h" bool fix_rpaths(const char *elf_path, const char *build_root) { return true; } muon-v0.5.0/src/platform/windows/filesystem.c0000644000175000017500000002621515041716357020314 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include "lang/string.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/os.h" #include "platform/path.h" #include "platform/windows/log.h" #include "platform/windows/win32_error.h" bool tty_is_pty = true; bool fs_exists(const char *path) { HANDLE h; h = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE | FILE_FLAG_BACKUP_SEMANTICS, NULL); if (h == INVALID_HANDLE_VALUE) { return false; } CloseHandle(h); return true; } bool fs_symlink_exists(const char *path) { return false; (void)path; } bool fs_file_exists(const char *path) { BY_HANDLE_FILE_INFORMATION fi; HANDLE h; h = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); if (h == INVALID_HANDLE_VALUE) { return false; } if (!GetFileInformationByHandle(h, &fi)) { return false; } CloseHandle(h); return (fi.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) == FILE_ATTRIBUTE_ARCHIVE; } bool fs_exe_exists(const char *path) { HANDLE h; HANDLE fm; unsigned char *base; unsigned char *iter; int64_t size; DWORD size_high; DWORD size_low; DWORD offset; bool ret = false; h = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) { return ret; } size_low = GetFileSize(h, &size_high); if ((size_low == INVALID_FILE_SIZE) && (GetLastError() != NO_ERROR)) { /* LOG_I("can not get the size of file %s", path); */ goto close_file; } size = size_low | ((int64_t)size_high << 32); /* * PE file is organized as followed: * 1) MS-DOS header (60 bytes) (beginning with bytes 'M' then 'Z') * 2) offset of PE signature (4 bytes)" * 2) PE signature (4 bytes) : "PE\0\0" * 3) COFF File Header (20 bytes) * the rest is useless for us. */ if (size < 64) { /* LOG_I("file %s is too small", path); */ goto close_file; } fm = CreateFileMapping(h, NULL, PAGE_READONLY, 0UL, 0UL, NULL); if (!fm) { /* LOG_I("Can not map file: %s", win32_error()); */ goto close_file; } base = MapViewOfFile(fm, FILE_MAP_READ, 0, 0, 0); if (!base) { /* LOG_I("Can not view map: %s", win32_error()); */ goto close_fm; } iter = base; if (*((WORD *)iter) != 0x5a4d) { /* LOG_I("file %s is not a MS-DOS file", path); */ goto unmap; } offset = *((DWORD *)(iter + 0x3c)); if (size < offset + 24) { /* LOG_I("file %s is too small", path); */ goto unmap; } iter += offset; if ((iter[0] != 'P') && (iter[1] != 'E') && (iter[2] != '\0') && (iter[3] != '\0')) { /* LOG_I("file %s is not a PE file", path); */ goto unmap; } iter += 22; if ((!((*((WORD *)iter)) & 0x0002)) || ((*((WORD *)iter)) & 0x2000)) { /* LOG_I("file %s is not a binary file", path); */ goto unmap; } ret = true; unmap: UnmapViewOfFile(base); close_fm: CloseHandle(fm); close_file: CloseHandle(h); return ret; } bool fs_dir_exists(const char *path) { BY_HANDLE_FILE_INFORMATION fi; HANDLE h; h = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (h == INVALID_HANDLE_VALUE) { return false; } if (!GetFileInformationByHandle(h, &fi)) { return false; } CloseHandle(h); return (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY; } bool fs_mkdir(const char *path, bool exist_ok) { if (!CreateDirectory(path, NULL)) { if (exist_ok && GetLastError() == ERROR_ALREADY_EXISTS) { return true; } LOG_E("failed to create directory \"%s\": %s", path, win32_error()); return false; } return true; } bool fs_rmdir(const char *path, bool force) { if (!RemoveDirectory(path)) { if (force) { return true; } LOG_E("failed to remove directory %s: %s\n", path, win32_error()); return false; } return true; } bool fs_copy_file(const char *src, const char *dest, bool force) { if (force) { fs_make_writeable_if_exists(dest); } if (!CopyFile(src, dest, FALSE)) { LOG_E("failed to copy file %s: %s", src, win32_error()); return false; } return true; } bool fs_dir_foreach(const char *path, void *_ctx, fs_dir_foreach_cb cb) { HANDLE h; char *filter; WIN32_FIND_DATA fd; size_t len; bool loop = true, res = true; if (!path || !*path) { return false; } len = strlen(path); if ((path[len - 1] == '/') || (path[len - 1] == '\\')) { len--; } filter = (char *)z_malloc(len + 3); CopyMemory(filter, path, len); filter[len] = '\\'; filter[len + 1] = '*'; filter[len + 2] = '\0'; h = FindFirstFileEx(filter, FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0); if (h == INVALID_HANDLE_VALUE) { LOG_E("failed to open directory %s: %s", path, win32_error()); res = false; goto free_filter; } do { if (fd.cFileName[0] == '.') { if (fd.cFileName[1] == '\0') { continue; } else { if ((fd.cFileName[1] == '.') && (fd.cFileName[2] == '\0')) { continue; } } } switch (cb(_ctx, fd.cFileName)) { case ir_cont: break; case ir_done: loop = false; break; case ir_err: loop = false; res = false; break; } } while (loop && FindNextFile(h, &fd)); if (!FindClose(h)) { LOG_E("failed to close handle: %s", win32_error()); res = false; } free_filter: z_free(filter); return res; } bool fs_make_symlink(const char *target, const char *path, bool force) { return false; (void)target; (void)path; (void)force; } const char * fs_user_home(void) { return os_get_env("USERPROFILE"); } static inline bool _is_wprefix(const WCHAR *s, const WCHAR *prefix, uint32_t n) { return wcsncmp(s, prefix, n) == 0; } #define is_wprefix(__s, __p) _is_wprefix(__s, __p, sizeof(__p) / sizeof(WCHAR) - 1) bool fs_is_a_tty_from_fd(int fd) { HANDLE h; DWORD mode; bool ret = false; h = (HANDLE *)_get_osfhandle(fd); if (h == INVALID_HANDLE_VALUE) { return ret; } /* first, check if the handle is assocoated with a conpty-based terminal */ { HMODULE mod; mod = LoadLibrary("kernel32.dll"); if (mod) { if (GetProcAddress(mod, "ClosePseudoConsole")) { if (GetConsoleMode(h, &mode)) { // ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT mode |= 0x4 | 0x1; if (SetConsoleMode(h, mode)) { FreeLibrary(mod); tty_is_pty = true; return true; } } } FreeLibrary(mod); } } /* * test if the stream is associated to a mintty-based terminal * based on: * https://fossies.org/linux/vim/src/iscygpty.c * https://sourceforge.net/p/mingw-w64/mailman/message/35589741/ * it means mintty without conpty */ { if (GetFileType(h) == FILE_TYPE_PIPE) { FILE_NAME_INFO *fni; WCHAR *p = NULL; size_t size; size_t l; size = sizeof(FILE_NAME_INFO) + sizeof(WCHAR) * (MAX_PATH - 1); fni = z_malloc(size + sizeof(WCHAR)); if (GetFileInformationByHandleEx(h, FileNameInfo, fni, size)) { fni->FileName[fni->FileNameLength / sizeof(WCHAR)] = L'\0'; /* * Check the name of the pipe: * '\{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master' */ p = fni->FileName; if (is_wprefix(p, L"\\cygwin-")) { p += 8; } else if (is_wprefix(p, L"\\msys-")) { p += 6; } else { p = NULL; } if (p) { /* Skip 16-digit hexadecimal. */ if (wcsspn(p, L"0123456789abcdefABCDEF") == 16) { p += 16; } else { p = NULL; } } if (p) { if (is_wprefix(p, L"-pty")) { p += 4; } else { p = NULL; } } if (p) { /* Skip pty number. */ l = wcsspn(p, L"0123456789"); if (l >= 1 && l <= 4) { p += l; } else { p = NULL; } if (p) { if (is_wprefix(p, L"-from-master")) { //p += 12; } else if (is_wprefix(p, L"-to-master")) { //p += 10; } else { p = NULL; } } } } z_free(fni); if (p) { tty_is_pty = true; return true; } } } /* * last case: cmd without conpty */ if (GetConsoleMode(h, &mode)) { tty_is_pty = false; return true; } return false; } bool fs_chmod(const char *path, uint32_t mode) { int mask = _S_IREAD; if (mode & _S_IWRITE) { mask |= _S_IWRITE; } if (_chmod(path, mask) == -1) { LOG_E("failed chmod(%s, %o): %s", path, mode, strerror(errno)); return false; } return true; } bool fs_has_extension(const char *path, const char *ext) { char *s; s = strrchr(path, '.'); if (!s) { return false; } return lstrcmpi(s, ext) == 0; } bool fs_find_cmd(struct workspace *wk, struct tstr *buf, const char *cmd) { assert(*cmd); uint32_t len; const char *env_path, *base_start; tstr_clear(buf); if (!path_is_basename(cmd)) { path_make_absolute(wk, buf, cmd); if (fs_exe_exists(buf->buf)) { return true; } if (!fs_has_extension(buf->buf, ".exe")) { tstr_pushs(wk, buf, ".exe"); if (fs_exe_exists(buf->buf)) { return true; } } return false; } if (strcmp(cmd, "cmd") == 0 || strcmp(cmd, "cmd.exe") == 0) { tstr_pushs(wk, buf, "cmd.exe"); return true; } if (!(env_path = os_get_env("PATH"))) { LOG_E("failed to get the value of PATH"); return false; } base_start = env_path; while (true) { if (!*env_path || *env_path == ';') { len = env_path - base_start; tstr_clear(buf); tstr_pushn(wk, buf, base_start, len); base_start = env_path + 1; path_push(wk, buf, cmd); if (fs_exe_exists(buf->buf)) { return true; } else if (!fs_has_extension(buf->buf, ".exe")) { tstr_pushs(wk, buf, ".exe"); if (fs_exe_exists(buf->buf)) { return true; } } if (!*env_path) { break; } } ++env_path; } return false; } enum fs_mtime_result fs_mtime(const char *path, int64_t *mtime) { WIN32_FILE_ATTRIBUTE_DATA d; ULARGE_INTEGER t; if (!GetFileAttributesEx(path, GetFileExInfoStandard, &d)) { return fs_mtime_result_not_found; } t.LowPart = d.ftLastWriteTime.dwLowDateTime; t.HighPart = d.ftLastWriteTime.dwHighDateTime; *mtime = t.QuadPart / 100; return fs_mtime_result_ok; } bool fs_remove(const char *path) { if (!DeleteFileA(path)) { LOG_E("failed DeleteFile(\"%s\"): %s", path, win32_error()); return false; } return true; } FILE * fs_make_tmp_file(const char *name, const char *suffix, char *buf, uint32_t len) { static uint32_t unique = 0; ++unique; char tmp_dir[MAX_PATH + 1]; DWORD result = GetTempPath(sizeof(tmp_dir), tmp_dir); if (result == 0) { // Use '.' (current dir) as tmp dir if GetTempPath fails tmp_dir[0] = '.'; tmp_dir[1] = 0; } snprintf(buf, len, "%s\\__muon_tmp_%d_%s.%s", tmp_dir, unique, name, suffix); return fs_fopen(buf, "w+b"); } bool fs_wait_for_input(int fd) { intptr_t _h = _get_osfhandle(fd); if (_h == -2 || (HANDLE)_h == INVALID_HANDLE_VALUE) { LOG_E("failed _get_osfhandle(): %s", win32_error()); return false; } HANDLE h = (HANDLE)_h; if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0) { LOG_E("failed WaitForSingleObject(0x%p): %s", h, win32_error()); return false; } return true; } muon-v0.5.0/src/platform/windows/uname.c0000644000175000017500000000151515041716357017231 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include "platform/uname.h" const char * uname_sysname(void) { return "windows"; } const char * uname_machine(void) { SYSTEM_INFO si; GetSystemInfo(&si); switch (si.wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_AMD64: return "x86_64"; case PROCESSOR_ARCHITECTURE_ARM: return "arm"; case PROCESSOR_ARCHITECTURE_ARM64: return "aarch64"; case PROCESSOR_ARCHITECTURE_IA64: return "ia64"; case PROCESSOR_ARCHITECTURE_INTEL: return "i686"; case PROCESSOR_ARCHITECTURE_UNKNOWN: /* fall through */ default: return "unknown"; } } muon-v0.5.0/src/platform/windows/log.c0000644000175000017500000000405515041716357016707 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include "buf_size.h" #include "platform/assert.h" #include "platform/log.h" #include "platform/windows/log.h" static const WORD color_map[] = { [1] = FOREGROUND_INTENSITY, [31] = FOREGROUND_RED, [32] = FOREGROUND_GREEN, [33] = FOREGROUND_GREEN | FOREGROUND_RED, [34] = FOREGROUND_BLUE, [35] = FOREGROUND_BLUE | FOREGROUND_RED, [36] = FOREGROUND_BLUE | FOREGROUND_GREEN, [37] = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED }; void print_colorized(FILE *out, const char *s, bool strip) { if (tty_is_pty) { fwrite(s, 1, strlen(s), out); } else { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO consoleInfo; WORD saved_attributes; /* Save current attributes */ GetConsoleScreenBufferInfo(hConsole, &consoleInfo); saved_attributes = consoleInfo.wAttributes; bool parsing_esc = false; const char *start = s; uint32_t len = 0; uint32_t esc_num = 0; for (; *s; ++s) { WORD attr = 0; if (*s == '\033') { attr = 0; if (len) { fwrite(start, 1, len, out); len = 0; } parsing_esc = true; esc_num = 0; } else if (parsing_esc) { if (*s == 'm' || *s == ';') { if (*s == 'm') { parsing_esc = false; start = s + 1; } if (!strip) { assert(esc_num < ARRAY_LEN(color_map) && "esc_num out of range"); attr = esc_num ? attr | color_map[esc_num] : saved_attributes; SetConsoleTextAttribute(hConsole, attr); } esc_num = 0; } else if ('0' <= *s && *s <= '9') { esc_num *= 10; esc_num += (*s - '0'); } else if (*s == '[') { // nothing } else { assert(false && "invalid character"); } } else { ++len; } } if (len) { fwrite(start, 1, len, out); } } } muon-v0.5.0/src/platform/windows/os.c0000644000175000017500000001045115041716357016544 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "lang/string.h" #include "platform/os.h" bool os_chdir(const char *path) { BOOL res; res = SetCurrentDirectory(path); if (!res) { if (GetLastError() == ERROR_FILE_NOT_FOUND) { errno = ENOENT; } else if (GetLastError() == ERROR_PATH_NOT_FOUND) { errno = ENOTDIR; } else if (GetLastError() == ERROR_FILENAME_EXCED_RANGE) { errno = ENAMETOOLONG; } else { errno = EIO; } } return res; } char * os_getcwd(char *buf, size_t size) { DWORD len; /* set errno to ERANGE for crossplatform usage of getcwd() in path.c */ len = GetCurrentDirectory(0UL, NULL); if (size < len) { errno = ERANGE; return NULL; } len = GetCurrentDirectory(size, buf); if (!len) { errno = EPERM; return NULL; } return buf; } /* * getopt ported from musl libc */ char *optarg; int optind = 1, opterr = 1, optopt, __optpos, __optreset = 0; #define optpos __optpos int os_getopt(int argc, char *const argv[], const char *optstring) { int i; char c, d; if (!optind || __optreset) { __optreset = 0; __optpos = 0; optind = 1; } if (optind >= argc || !argv[optind]) { return -1; } if (argv[optind][0] != '-') { if (optstring[0] == '-') { optarg = argv[optind++]; return 1; } return -1; } if (!argv[optind][1]) { return -1; } if (argv[optind][1] == '-' && !argv[optind][2]) { return optind++, -1; } if (!optpos) { optpos++; } c = argv[optind][optpos]; ++optpos; if (!argv[optind][optpos]) { optind++; optpos = 0; } if (optstring[0] == '-' || optstring[0] == '+') { optstring++; } i = 0; do { d = optstring[i]; i++; } while (d != c); if (d != c || c == ':') { optopt = c; if (optstring[0] != ':' && opterr) { fprintf(stderr, "%s: unrecognized option: %c\n", argv[0], c); } return '?'; } if (optstring[i] == ':') { optarg = 0; if (optstring[i + 1] != ':' || optpos) { optarg = argv[optind++] + optpos; optpos = 0; } if (optind > argc) { optopt = c; if (optstring[0] == ':') { return ':'; } if (opterr) { fprintf(stderr, "%s: option requires an argument: %c\n", argv[0], c); } return '?'; } } return c; } static uint32_t count_bits(ULONG_PTR bit_mask) { DWORD lshift; uint32_t bit_count = 0; ULONG_PTR bit_test; DWORD i; lshift = sizeof(ULONG_PTR) * 8 - 1; bit_test = (ULONG_PTR)1 << lshift; for (i = 0; i <= lshift; i++) { bit_count += ((bit_mask & bit_test) ? 1 : 0); bit_test /= 2; } return bit_count; } int32_t os_ncpus(void) { PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION iter; uint32_t ncpus; DWORD length; DWORD byte_offset; BOOL ret; buffer = NULL; length = 0UL; ret = GetLogicalProcessorInformation(buffer, &length); /* * buffer and length values make this function failing * with error being ERROR_INSUFFICIENT_BUFFER. * Error not being ERROR_INSUFFICIENT_BUFFER is very unlikely. */ if (!ret) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { return -1; } /* * Otherwise length is the size in bytes to allocate */ } buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(length); if (!buffer) { return -1; } ret = GetLogicalProcessorInformation(buffer, &length); /* * Should not fail as buffer and length have the correct values, * but anyway, we check the returned value. */ if (!ret) { free(buffer); return -1; } iter = buffer; byte_offset = 0; ncpus = 0; while (byte_offset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= length) { switch (iter->Relationship) { case RelationProcessorCore: ncpus += count_bits(iter->ProcessorMask); break; default: break; } byte_offset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); iter++; } free(buffer); return ncpus; } void os_set_env(const struct str *k, const struct str *v) { TSTR_manual(buf_kv); tstr_pushn(0, &buf_kv, k->s, k->len); tstr_push(0, &buf_kv, '='); tstr_pushn(0, &buf_kv, v->s, v->len); tstr_push(0, &buf_kv, 0); putenv(buf_kv.buf); } bool os_is_debugger_attached(void) { return IsDebuggerPresent() == TRUE; } int32_t os_get_pid(void) { return GetCurrentProcessId(); } muon-v0.5.0/src/platform/windows/win32_error.c0000644000175000017500000000247415041716357020304 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #define STRSAFE_NO_CB_FUNCTIONS #include #include #include "lang/string.h" #include "log.h" #include "platform/windows/win32_error.h" const char * win32_error(void) { static char _msg[4096]; LPTSTR msg; DWORD err; err = GetLastError(); if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0UL, (LPTSTR)&msg, 0UL, NULL)) { snprintf(_msg, sizeof(_msg), "FormatMessage() failed with error Id %ld", GetLastError()); return _msg; } // strip trailing newlines from the error message char *end = &msg[strlen(msg) - 1]; while (end > msg && is_whitespace(*end)) { *end = 0; --end; } snprintf(_msg, sizeof(_msg), "%s (%lu)", msg, err); LocalFree(msg); return _msg; } void win32_fatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); log_printv(log_error, fmt, ap); va_end(ap); if (fmt[strlen(fmt) - 1] == ':') { log_plain(log_error, " %s", win32_error()); } log_plain(log_error, "\n"); exit(1); } muon-v0.5.0/src/platform/windows/init.c0000644000175000017500000000071715041716357017072 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "platform/init.h" void platform_init(void) { setmode(fileno(stdin), O_BINARY); setmode(fileno(stdout), O_BINARY); setmode(fileno(stderr), O_BINARY); setvbuf(stderr, 0, _IOFBF, 2048); } void platform_set_abort_handler(void((*handler)(void *ctx)), void *ctx) { } muon-v0.5.0/src/platform/windows/timer.c0000644000175000017500000000111315041716357017236 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "platform/timer.h" void timer_start(struct timer *t) { QueryPerformanceFrequency(&t->freq); QueryPerformanceCounter(&t->start); } float timer_read(struct timer *t) { LARGE_INTEGER end; QueryPerformanceCounter(&end); return (float)(end.QuadPart - t->start.QuadPart) / (float)t->freq.QuadPart; } void timer_sleep(uint64_t nanoseconds) { Sleep(nanoseconds / 1000000ULL); } muon-v0.5.0/src/platform/windows/term.c0000644000175000017500000000157415041716357017100 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include bool term_winsize(int fd, uint32_t *height, uint32_t *width) { CONSOLE_SCREEN_BUFFER_INFO csbi; HANDLE h; DWORD mode; *height = 24; *width = 80; /* if not a console, or a terminal using conpty, use default values */ h = (HANDLE *)_get_osfhandle(fd); if (h == INVALID_HANDLE_VALUE) { return true; } if (!GetConsoleMode(h, &mode)) { return true; } /* otherwise, retrieve the geometry */ if (GetConsoleScreenBufferInfo(h, &csbi)) { *height = csbi.dwSize.Y; *width = csbi.dwSize.X; return true; } return false; } muon-v0.5.0/src/platform/windows/run_cmd.c0000644000175000017500000003454115041716357017560 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #define STRSAFE_NO_CB_FUNCTIONS #include #include "args.h" #include "buf_size.h" #include "error.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "platform/timer.h" #include "platform/windows/win32_error.h" #define record_handle(__h, v) _record_handle(ctx, __h, v, #__h) static bool _record_handle(struct run_cmd_ctx *ctx, HANDLE *h, HANDLE v, const char *desc) { if (!v || v == INVALID_HANDLE_VALUE) { return false; } ++ctx->cnt_open; *h = v; return true; } #define close_handle(__h) _close_handle(ctx, __h, #__h) static bool _close_handle(struct run_cmd_ctx *ctx, HANDLE *h, const char *desc) { if (!*h || *h == INVALID_HANDLE_VALUE) { return true; } assert(ctx->cnt_open); if (!CloseHandle(*h)) { LOG_E("failed to close handle %s:%p: %s", desc, *h, win32_error()); return false; } --ctx->cnt_open; *h = INVALID_HANDLE_VALUE; return true; } enum copy_pipe_result { copy_pipe_result_finished, copy_pipe_result_waiting, copy_pipe_result_failed, }; static enum copy_pipe_result copy_pipe(struct run_cmd_ctx *ctx, struct win_pipe_inst *pipe, struct tstr *tstr) { if (pipe->is_eof) { return copy_pipe_result_finished; } DWORD bytes_read; if (!GetOverlappedResult(pipe->handle, &pipe->overlapped, &bytes_read, TRUE)) { if (GetLastError() == ERROR_BROKEN_PIPE) { pipe->is_eof = true; if (!close_handle(&pipe->handle)) { return copy_pipe_result_failed; } return copy_pipe_result_finished; } win32_fatal("GetOverlappedResult:"); } if (pipe->is_reading && bytes_read) { tstr_pushn(0, tstr, pipe->overlapped_buf, bytes_read); } memset(&pipe->overlapped, 0, sizeof(pipe->overlapped)); pipe->is_reading = true; if (!ReadFile( pipe->handle, pipe->overlapped_buf, sizeof(pipe->overlapped_buf), &bytes_read, &pipe->overlapped)) { if (GetLastError() == ERROR_BROKEN_PIPE) { pipe->is_eof = true; if (!close_handle(&pipe->handle)) { return copy_pipe_result_failed; } return copy_pipe_result_finished; } if (GetLastError() != ERROR_IO_PENDING) { win32_fatal("ReadFile:"); } } return copy_pipe_result_waiting; } static enum copy_pipe_result copy_pipes(struct run_cmd_ctx *ctx) { DWORD bytes_read; struct win_pipe_inst *pipe; OVERLAPPED *overlapped; if (!GetQueuedCompletionStatus(ctx->ioport, &bytes_read, (PULONG_PTR)&pipe, &overlapped, 100)) { if (GetLastError() == WAIT_TIMEOUT) { return copy_pipe_result_waiting; } else if (GetLastError() != ERROR_BROKEN_PIPE) { win32_fatal("GetQueuedCompletionStatus:"); } } struct tstr *tstr = 0; if (pipe == &ctx->pipe_out) { tstr = &ctx->out; } else if (pipe == &ctx->pipe_err) { tstr = &ctx->err; } else { /* return copy_pipe_result_waiting; */ UNREACHABLE; } return copy_pipe(ctx, pipe, tstr); } static void run_cmd_ctx_close_pipes(struct run_cmd_ctx *ctx) { if (!ctx->close_pipes) { return; } close_handle(&ctx->pipe_err.handle); close_handle(&ctx->pipe_out.handle); close_handle(&ctx->ioport); // TODO stdin #if 0 if (ctx->input_fd_open && close(ctx->input_fd) == -1) { LOG_E("failed to close: %s", win32_error()); } ctx->input_fd_open = false; #endif } enum run_cmd_state run_cmd_collect(struct run_cmd_ctx *ctx) { DWORD res; DWORD status; enum copy_pipe_result pipe_res = 0; bool loop = true; while (loop) { if (!(ctx->flags & run_cmd_ctx_flag_dont_capture) && !(ctx->pipe_out.is_eof && ctx->pipe_err.is_eof)) { if ((pipe_res = copy_pipes(ctx)) == copy_pipe_result_failed) { return run_cmd_error; } } res = WaitForSingleObject(ctx->process, 0); switch (res) { case WAIT_TIMEOUT: if (ctx->flags & run_cmd_ctx_flag_async) { return run_cmd_running; } break; case WAIT_OBJECT_0: // State is signalled loop = false; break; case WAIT_FAILED: ctx->err_msg = win32_error(); return run_cmd_error; case WAIT_ABANDONED: ctx->err_msg = "child exited abnormally (WAIT_ABANDONED)"; return run_cmd_error; } } if (!GetExitCodeProcess(ctx->process, &status)) { ctx->err_msg = "can not get process exit code"; return run_cmd_error; } ctx->status = (int)status; if (!(ctx->flags & run_cmd_ctx_flag_dont_capture)) { while (!(ctx->pipe_out.is_eof && ctx->pipe_err.is_eof)) { if (copy_pipes(ctx) == copy_pipe_result_failed) { return run_cmd_error; } } } return run_cmd_finished; } static bool open_pipes(struct run_cmd_ctx *ctx, struct win_pipe_inst *pipe, const char *name) { static uint64_t uniq = 0; char pipe_name[256]; snprintf(pipe_name, ARRAY_LEN(pipe_name), "\\\\.\\pipe\\muon_run_cmd_pid%lu_%llu_%s", GetCurrentProcessId(), uniq, name); ++uniq; memset(&pipe->overlapped, 0, sizeof(pipe->overlapped)); if (!record_handle(&pipe->handle, CreateNamedPipeA(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, INFINITE, NULL))) { win32_fatal("CreateNamedPipe:"); return false; } if (!CreateIoCompletionPort(pipe->handle, ctx->ioport, (ULONG_PTR)pipe, 0)) { win32_fatal("CreateIoCompletionPort"); } if (!ConnectNamedPipe(pipe->handle, &pipe->overlapped) && GetLastError() != ERROR_IO_PENDING) { win32_fatal("ConnectNamedPipe:"); return false; } HANDLE output_write_child; { // Get the write end of the pipe as a handle inheritable across processes. HANDLE output_write_handle, dup; if (!record_handle(&output_write_handle, CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL))) { win32_fatal("CreateFile:"); return false; } if (!DuplicateHandle(GetCurrentProcess(), output_write_handle, GetCurrentProcess(), &dup, 0, TRUE, DUPLICATE_SAME_ACCESS)) { win32_fatal("DuplicateHandle:"); return false; } if (!record_handle(&output_write_child, dup)) { return false; } else if (!close_handle(&output_write_handle)) { return false; } } pipe->child_handle = output_write_child; return true; } static bool open_run_cmd_pipe(struct run_cmd_ctx *ctx) { if (ctx->flags & run_cmd_ctx_flag_dont_capture) { return true; } assert(ctx->ioport == 0); if (!record_handle(&ctx->ioport, CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1))) { win32_fatal("CreateIoCompletionPort:"); } tstr_init(&ctx->out, 0, 0, tstr_flag_overflow_alloc); tstr_init(&ctx->err, 0, 0, tstr_flag_overflow_alloc); if (!open_pipes(ctx, &ctx->pipe_out, "out")) { return false; } else if (!open_pipes(ctx, &ctx->pipe_err, "err")) { return false; } ctx->close_pipes = true; return true; } static bool run_cmd_internal(struct run_cmd_ctx *ctx, char *command_line, const char *envstr, uint32_t envc) { const char *p; BOOL res; ctx->process = INVALID_HANDLE_VALUE; LL("executing: "); log_plain(log_debug, "%s\n", command_line); if (envstr) { LPSTR oldenv = GetEnvironmentStrings(); const char *k; uint32_t i = 0; LL("env:"); p = k = envstr; for (; envc; ++p) { if (!p[0]) { if (!k) { k = p + 1; } else { assert(*k); log_plain(log_debug, " %s='%s'", k, p + 1); if (!SetEnvironmentVariable(k, p + 1)) { LOG_E("failed to set environment var %s='%s': %s", k, p + 1, win32_error()); FreeEnvironmentStrings(oldenv); return false; } k = NULL; if (++i >= envc) { break; } } } } log_plain(log_debug, "\n"); LPSTR newenv = GetEnvironmentStrings(); // Clear out current env based on newenv char *var; for (var = newenv; *var;) { size_t len = strlen(var); char *split = strchr(var, '='); *split = 0; SetEnvironmentVariable(var, 0); *split = '='; var += len + 1; } // Copy newenv into ctx->env tstr_init(&ctx->env, 0, 0, tstr_flag_overflow_alloc); tstr_pushn(0, &ctx->env, newenv, var - newenv); tstr_push(0, &ctx->env, 0); FreeEnvironmentStrings(newenv); // Reset env based on oldenv for (var = oldenv; *var;) { size_t len = strlen(var); char *split = strchr(var, '='); *split = 0; SetEnvironmentVariable(var, split + 1); *split = '='; var += len + 1; } FreeEnvironmentStrings(oldenv); } // TODO stdin #if 0 if (ctx->stdin_path) { ctx->input_fd = open(ctx->stdin_path, O_RDONLY); if (ctx->input_fd == -1) { LOG_E("failed to open %s: %s", ctx->stdin_path, strerror(errno)); goto err; } ctx->input_fd_open = true; } #endif if (!open_run_cmd_pipe(ctx)) { return false; } SECURITY_ATTRIBUTES security_attributes; memset(&security_attributes, 0, sizeof(SECURITY_ATTRIBUTES)); security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); security_attributes.bInheritHandle = TRUE; // Must be inheritable so subprocesses can dup to children. // TODO: delete when stdin support added HANDLE nul; if (!record_handle(&nul, CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &security_attributes, OPEN_EXISTING, 0, NULL))) { error_unrecoverable("couldn't open nul"); } STARTUPINFOA startup_info; memset(&startup_info, 0, sizeof(startup_info)); startup_info.cb = sizeof(STARTUPINFO); if (!(ctx->flags & run_cmd_ctx_flag_dont_capture)) { startup_info.dwFlags = STARTF_USESTDHANDLES; startup_info.hStdInput = nul; startup_info.hStdOutput = ctx->pipe_out.child_handle; startup_info.hStdError = ctx->pipe_err.child_handle; } PROCESS_INFORMATION process_info; memset(&process_info, 0, sizeof(process_info)); if (ctx->chdir) { if (!fs_dir_exists(ctx->chdir)) { LOG_E("directory %s does not exist: %s", ctx->chdir, win32_error()); exit(1); } } DWORD process_flags = 0; if (strlen(command_line) >= 32767) { LOG_E("command too long"); } res = CreateProcessA(NULL, command_line, NULL, NULL, /* inherit handles */ TRUE, process_flags, ctx->env.buf, ctx->chdir, &startup_info, &process_info); if (!res) { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) { } LOG_E("CreateProcess() failed: %s", win32_error()); ctx->err_msg = "failed to create process"; } close_handle(&ctx->pipe_out.child_handle); close_handle(&ctx->pipe_err.child_handle); close_handle(&nul); if (!res) { return false; } record_handle(&ctx->process, process_info.hProcess); CloseHandle(process_info.hThread); if (ctx->flags & run_cmd_ctx_flag_async) { return true; } return run_cmd_collect(ctx) == run_cmd_finished; } static void run_cmd_push_argv(struct tstr *cmd, struct tstr *arg_buf, const char *arg, bool first) { tstr_clear(arg_buf); shell_escape_cmd(0, arg_buf, arg); tstr_pushf(0, cmd, "%s%s", first ? "" : " ", arg_buf->buf); } static void run_cmd_push_arg(struct tstr *cmd, struct tstr *arg_buf, const char *arg) { run_cmd_push_argv(cmd, arg_buf, arg, false); } static bool run_cmd_push_arg0(struct run_cmd_ctx *ctx, struct tstr *cmd, struct tstr *arg_buf, const char *arg) { TSTR_manual(found_cmd); if (!fs_find_cmd(0, &found_cmd, arg)) { ctx->err_msg = "command not found"; tstr_destroy(&found_cmd); return false; } run_cmd_push_argv(cmd, arg_buf, found_cmd.buf, true); tstr_destroy(&found_cmd); return true; } static bool argv_to_command_line(struct run_cmd_ctx *ctx, struct source *src, const char *argstr, char *const *argv, uint32_t argstr_argc, struct tstr *cmd) { bool res = false; TSTR_manual(arg_buf); const char *argv0 = argstr ? argstr : argv[0]; tstr_clear(cmd); bool have_arg0 = false; if (fs_has_extension(argv0, ".bat")) { if (!run_cmd_push_arg0(ctx, cmd, &arg_buf, "cmd.exe")) { goto ret; } run_cmd_push_arg(cmd, &arg_buf, "/c"); run_cmd_push_arg(cmd, &arg_buf, argv0); have_arg0 = true; } else if (fs_exists(argv0)) { DWORD _binary_type; if (!GetBinaryType(argv0, &_binary_type)) { const char *new_argv0 = 0, *new_argv1 = 0; if (!run_cmd_determine_interpreter(src, argv0, &ctx->err_msg, &new_argv0, &new_argv1)) { return false; } /* ignore /usr/bin/env on Windows */ if (strcmp(new_argv0, "/usr/bin/env") == 0 && new_argv1) { new_argv0 = new_argv1; new_argv1 = 0; } if (!run_cmd_push_arg0(ctx, cmd, &arg_buf, new_argv0)) { goto ret; } if (new_argv1) { run_cmd_push_arg(cmd, &arg_buf, new_argv1); } run_cmd_push_arg(cmd, &arg_buf, argv0); have_arg0 = true; } } if (!have_arg0) { if (!run_cmd_push_arg0(ctx, cmd, &arg_buf, argv0)) { goto ret; } } if (argstr) { const char *p, *arg; uint32_t i = 0; arg = p = argstr; for (;; ++p) { if (!p[0]) { if (i > 0) { run_cmd_push_arg(cmd, &arg_buf, arg); } if (++i >= argstr_argc) { break; } arg = p + 1; } } } else { uint32_t i; for (i = 1; argv[i]; ++i) { run_cmd_push_arg(cmd, &arg_buf, argv[i]); } } res = true; ret: tstr_destroy(&arg_buf); return res; } bool run_cmd_unsplit(struct run_cmd_ctx *ctx, char *cmd, const char *envstr, uint32_t envc) { return run_cmd_internal(ctx, cmd, envstr, envc); } bool run_cmd_argv(struct run_cmd_ctx *ctx, char *const *argv, const char *envstr, uint32_t envc) { bool ret = false; struct source src = { 0 }; TSTR_manual(cmd); if (!argv_to_command_line(ctx, &src, NULL, argv, 0, &cmd)) { goto err; } ret = run_cmd_internal(ctx, cmd.buf, envstr, envc); err: fs_source_destroy(&src); tstr_destroy(&cmd); return ret; } bool run_cmd(struct run_cmd_ctx *ctx, const char *argstr, uint32_t argc, const char *envstr, uint32_t envc) { bool ret = false; struct source src = { 0 }; TSTR_manual(cmd); if (!argv_to_command_line(ctx, &src, argstr, NULL, argc, &cmd)) { goto err; } ret = run_cmd_internal(ctx, cmd.buf, envstr, envc); err: fs_source_destroy(&src); tstr_destroy(&cmd); return ret; } void run_cmd_ctx_destroy(struct run_cmd_ctx *ctx) { close_handle(&ctx->process); run_cmd_ctx_close_pipes(ctx); tstr_destroy(&ctx->out); tstr_destroy(&ctx->err); tstr_destroy(&ctx->env); assert(ctx->cnt_open == 0); } bool run_cmd_kill(struct run_cmd_ctx *ctx, bool force) { BOOL r; if (force) { r = TerminateProcess(ctx->process, 1); } else { // FIXME r = TerminateProcess(ctx->process, 1); } if (!r) { LOG_E("error killing process 0x%p: %s", ctx->process, win32_error()); return false; } return true; } muon-v0.5.0/src/platform/filesystem.c0000644000175000017500000002225515041716357016622 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Eli Schwartz * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifdef __sun /* for symlinkat() and fchmodat(), as _POSIX_C_SOURCE does not enable them * (yet) */ #define __EXTENSIONS__ #endif #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #endif #include "buf_size.h" #include "lang/string.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/os.h" #include "platform/path.h" bool fs_stat(const char *path, struct stat *sb) { if (stat(path, sb) != 0) { LOG_E("failed stat(%s): %s", path, strerror(errno)); return false; } return true; } bool fs_mkdir_p(const char *path) { bool res = false; uint32_t i, len = strlen(path); TSTR_manual(buf); path_copy(NULL, &buf, path); assert(len >= 1); i = 0; if (path_is_absolute(buf.buf)) { char *p; if ((p = strchr(buf.buf, PATH_SEP))) { i = (p - buf.buf) + 1; } } for (; i < len; ++i) { if (buf.buf[i] == PATH_SEP) { buf.buf[i] = 0; if (!fs_mkdir(buf.buf, true)) { goto ret; } buf.buf[i] = PATH_SEP; } } if (!fs_mkdir(path, true)) { goto ret; } res = true; ret: tstr_destroy(&buf); return res; } FILE * fs_fopen(const char *path, const char *mode) { FILE *f; if (!(f = fopen(path, mode))) { LOG_E("failed to open '%s': %s", path, strerror(errno)); return NULL; } return f; } bool fs_fclose(FILE *file) { if (fclose(file) != 0) { LOG_E("failed fclose: %s", strerror(errno)); return false; } return true; } bool fs_fseek(FILE *file, size_t off) { if (fseek(file, off, 0) == -1) { LOG_E("failed fseek: %s", strerror(errno)); return false; } return true; } bool fs_ftell(FILE *file, uint64_t *res) { int64_t pos; if ((pos = ftell(file)) == -1) { LOG_E("failed ftell: %s", strerror(errno)); return false; } assert(pos >= 0); *res = pos; return true; } bool fs_fsize(FILE *file, uint64_t *ret) { if (fseek(file, 0, SEEK_END) == -1) { LOG_E("failed fseek: %s", strerror(errno)); return false; } else if (!fs_ftell(file, ret)) { return false; } rewind(file); return true; } static bool fs_is_seekable(FILE *file, bool *res) { int fd; if (!fs_fileno(file, &fd)) { return false; } errno = 0; if (lseek(fd, 0, SEEK_CUR) == -1) { if (errno == ESPIPE) { *res = false; return true; } LOG_E("lseek returned an unexpected error"); return false; } *res = true; return true; } bool fs_read_entire_file(const char *path, struct source *src) { FILE *f; bool opened = false; size_t read; char *buf = NULL; *src = (struct source){ .label = path, .type = source_type_file }; if (strcmp(path, "-") == 0) { f = stdin; } else { if (!fs_file_exists(path)) { LOG_E("'%s' is not a file", path); goto err; } if (!(f = fs_fopen(path, "rb"))) { goto err; } opened = true; } /* If the file is seekable (i.e. not a pipe), then we can get the size * and read it all at once. Otherwise, read it in chunks. */ bool seekable; if (!fs_is_seekable(f, &seekable)) { goto err; } if (seekable) { if (!fs_fsize(f, &src->len)) { goto err; } buf = z_calloc(src->len + 1, 1); read = fread(buf, 1, src->len, f); if (read != src->len) { LOG_E("failed to read entire file, only read %" PRIu64 "/%" PRId64 "bytes", (uint64_t)read, src->len); goto err; } } else { uint32_t buf_size = BUF_SIZE_4k; buf = z_calloc(buf_size + 1, 1); while ((read = fread(&buf[src->len], 1, buf_size - src->len, f))) { src->len += read; if (src->len >= buf_size) { buf_size *= 2; buf = z_realloc(buf, buf_size); memset(&buf[src->len], 0, buf_size - src->len); } } assert(src->len < buf_size && buf[src->len] == 0); if (!feof(f)) { LOG_E("failed to read entire file, only read %" PRId64 "bytes", src->len); goto err; } } if (opened) { if (!fs_fclose(f)) { goto err; } } src->src = buf; return true; err: if (opened) { fs_fclose(f); } if (buf) { z_free(buf); } return false; } void fs_source_dup(const struct source *src, struct source *dup) { uint32_t label_len = strlen(src->label); char *buf = z_calloc(src->len + label_len + 1, 1); dup->label = &buf[src->len]; dup->src = buf; dup->len = src->len; dup->type = src->type; memcpy(buf, src->src, src->len); memcpy(&buf[src->len], src->label, label_len); } void fs_source_destroy(struct source *src) { if (!src->is_weak_reference) { if (src->src) { z_free((char *)src->src); } } src->src = 0; src->len = 0; } bool fs_fwrite(const void *ptr, size_t size, FILE *f) { size_t r; int err; if (!size) { return true; } r = fwrite(ptr, 1, size, f); assert(r <= size); if (r == size) { return true; } else { if ((err = ferror(f))) { LOG_E("fwrite failed: %s", strerror(err)); } else { LOG_E("fwrite failed: unknown"); } return false; } } bool fs_fread(void *ptr, size_t size, FILE *f) { size_t r; int err; if (!size) { return true; } r = fread(ptr, 1, size, f); assert(r <= size); if (r == size) { return true; } else { if (feof(f)) { LOG_E("fread got EOF"); } else if ((err = ferror(f))) { LOG_E("fread failed: %s", strerror(err)); } else { LOG_E("fread failed: unknown"); } return false; } } int32_t fs_read(int fd, void *buf, uint32_t buf_len) { int32_t res = read(fd, buf, buf_len); if (res < 0) { LOG_E("read: %s", strerror(errno)); } return res; } bool fs_write(const char *path, const uint8_t *buf, uint64_t buf_len) { FILE *f; if (!(f = fs_fopen(path, "wb"))) { return false; } if (!fs_fwrite(buf, buf_len, f)) { LOG_E("failed to write entire file"); fs_fclose(f); return false; } if (!fs_fclose(f)) { return false; } return true; } bool fs_has_cmd(const char *cmd) { TSTR_manual(buf); bool res = fs_find_cmd(NULL, &buf, cmd); tstr_destroy(&buf); return res; } bool fs_fileno(FILE *f, int *ret) { int v; if ((v = fileno(f)) == -1) { LOG_E("failed fileno: %s", strerror(errno)); return false; } *ret = v; return true; } bool fs_make_writeable_if_exists(const char *path) { struct stat sb; if (stat(path, &sb) == 0) { if (!(sb.st_mode & S_IWUSR)) { if (!fs_chmod(path, sb.st_mode | S_IWUSR)) { return false; } } } return true; } enum iteration_result fs_copy_dir_iter(void *_ctx, const char *path) { enum iteration_result res = ir_err; struct fs_copy_dir_ctx *ctx = _ctx; struct stat sb; TSTR_manual(src); TSTR_manual(dest); path_join(NULL, &src, ctx->src_base, path); path_join(NULL, &dest, ctx->dest_base, path); if (!fs_stat(src.buf, &sb)) { goto ret; } if (S_ISDIR(sb.st_mode)) { if (!fs_mkdir(dest.buf, ctx->force)) { goto ret; } struct fs_copy_dir_ctx sub_ctx = *ctx; sub_ctx.src_base = src.buf; sub_ctx.dest_base = dest.buf; if (!fs_copy_dir_ctx(&sub_ctx)) { goto ret; } } else if (S_ISREG(sb.st_mode)) { if (ctx->file_cb) { ctx->file_cb(ctx->usr_ctx, src.buf, dest.buf); } if (!fs_copy_file(src.buf, dest.buf, ctx->force)) { goto ret; } } else { LOG_E("unhandled file type '%s'", path); goto ret; } res = ir_cont; ret: tstr_destroy(&src); tstr_destroy(&dest); return res; } bool fs_copy_dir_ctx(struct fs_copy_dir_ctx *ctx) { if (!fs_mkdir(ctx->dest_base, true)) { return ir_err; } return fs_dir_foreach(ctx->src_base, ctx, fs_copy_dir_iter); } bool fs_copy_dir(const char *src_base, const char *dest_base, bool force) { struct fs_copy_dir_ctx ctx = { .src_base = src_base, .dest_base = dest_base, .force = force, }; return fs_copy_dir_ctx(&ctx); } struct fs_rmdir_ctx { const char *base_dir; bool force; }; static enum iteration_result fs_rmdir_iter(void *_ctx, const char *path) { enum iteration_result ir_res = ir_err; struct fs_rmdir_ctx *ctx = _ctx; struct stat sb; TSTR(name); path_join(NULL, &name, ctx->base_dir, path); if (fs_symlink_exists(name.buf)) { if (!fs_remove(name.buf)) { goto ret; } ir_res = ir_cont; goto ret; } else if (stat(name.buf, &sb) != 0) { if (ctx->force) { ir_res = ir_cont; } else { LOG_E("failed stat(%s): %s", path, strerror(errno)); } goto ret; } if (S_ISDIR(sb.st_mode)) { if (!fs_rmdir_recursive(name.buf, ctx->force)) { goto ret; } if (!fs_rmdir(name.buf, ctx->force)) { goto ret; } } else if (S_ISREG(sb.st_mode)) { if (!fs_remove(name.buf)) { goto ret; } } else { LOG_E("unhandled file type: %s", name.buf); goto ret; } ir_res = ir_cont; ret: tstr_destroy(&name); return ir_res; } bool fs_rmdir_recursive(const char *path, bool force) { struct fs_rmdir_ctx ctx = { .base_dir = path, .force = force, }; return fs_dir_foreach(path, &ctx, fs_rmdir_iter); } bool fs_is_a_tty(FILE *f) { int fd; if (!fs_fileno(f, &fd)) { return false; } return fs_is_a_tty_from_fd(fd); } bool fs_copy_metadata(const char *src, const char *dest) { struct stat sb; if (!fs_stat(src, &sb)) { return false; } if (!fs_chmod(dest, sb.st_mode)) { return false; } return true; } muon-v0.5.0/src/platform/uname.c0000644000175000017500000000051215041716357015533 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "platform/uname.h" #include enum endianness uname_endian(void) { const uint32_t x = 1; if (((char *)&x)[0] == 1) { return little_endian; } else { return big_endian; } } muon-v0.5.0/src/platform/assert.c0000644000175000017500000000063615041716357015736 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifdef _WIN32 #include #include "log.h" #endif #include "platform/assert.h" #ifdef _WIN32 __declspec(noreturn) void win_assert_fail(const char *msg, const char *file, uint32_t line, const char *func) { LOG_E("%s:%d %s: %s", file, line, func, msg); abort(); } #endif muon-v0.5.0/src/platform/os.c0000644000175000017500000000064715041716357015060 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "platform/os.h" int32_t os_ncpus(void); uint32_t os_parallel_job_count(void) { int32_t n = os_ncpus(); if (n == -1) { return 4; } else if (n < 2) { return 2; } else { return n + 2; } } const char * os_get_env(const char *k) { return getenv(k); } muon-v0.5.0/src/platform/mem.c0000644000175000017500000000263315041716357015212 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "error.h" #include "platform/assert.h" #include "platform/mem.h" #include "tracy.h" #ifdef TRACY_ENABLE #include #define PlotRSS \ struct rusage usage; \ getrusage(RUSAGE_SELF, &usage); \ TracyCPlot("maxrss", ((double)usage.ru_maxrss / 1024.0)); #else #define PlotRSS #endif void * z_calloc(size_t nmemb, size_t size) { assert(size); void *ret; ret = calloc(nmemb, size); if (!ret) { error_unrecoverable("calloc failed: %s", strerror(errno)); } TracyCAlloc(ret, size * nmemb); /* PlotRSS; */ return ret; } void * z_malloc(size_t size) { assert(size); void *ret; ret = malloc(size); if (!ret) { error_unrecoverable("malloc failed: %s", strerror(errno)); } TracyCAlloc(ret, size); /* PlotRSS; */ return ret; } void * z_realloc(void *ptr, size_t size) { assert(size); void *ret; TracyCFree(ptr); ret = realloc(ptr, size); if (!ret) { error_unrecoverable("realloc failed: %s", strerror(errno)); } TracyCAlloc(ret, size); /* PlotRSS; */ return ret; } void z_free(void *ptr) { assert(ptr); TracyCFree(ptr); free(ptr); /* PlotRSS; */ } uint32_t bswap_32(uint32_t x) { return x >> 24 | (x >> 8 & 0xff00) | (x << 8 & 0xff0000) | x << 24; } muon-v0.5.0/src/platform/null/0002755000175000017500000000000015041716357015240 5ustar buildbuildmuon-v0.5.0/src/platform/null/rpath_fixer.c0000644000175000017500000000036615041716357017722 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "platform/rpath_fixer.h" bool fix_rpaths(const char *elf_path, const char *build_root) { return true; } muon-v0.5.0/src/platform/run_cmd.c0000644000175000017500000000711415041716357016062 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifdef __sun /* for signals */ #define __EXTENSIONS__ #endif #include #include #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/run_cmd.h" void push_argv_single(const char **argv, uint32_t *len, uint32_t max, const char *arg) { assert(*len < max && "too many arguments"); argv[*len] = arg; ++(*len); } void argstr_pushall(const char *argstr, uint32_t argc, const char **argv, uint32_t *argi, uint32_t max) { if (!argc) { return; } const char *p, *arg; uint32_t i = 0; arg = p = argstr; for (;; ++p) { if (!p[0]) { push_argv_single(argv, argi, max, arg); if (++i >= argc) { break; } arg = p + 1; } } } uint32_t argstr_to_argv(const char *argstr, uint32_t argc, const char *prepend, char *const **res) { uint32_t argi = 0, max = argc; if (prepend) { max += 1; } const char **new_argv = z_calloc(max + 1, sizeof(const char *)); if (prepend) { push_argv_single(new_argv, &argi, max, prepend); } argstr_pushall(argstr, argc, new_argv, &argi, max); *res = (char *const *)new_argv; return argi; } static bool run_cmd_determine_interpreter_skip_whitespace(char **p, bool invert) { bool char_found; while (**p) { char_found = is_whitespace_except_newline(**p); if (invert) { if (char_found) { return true; } } else { if (!char_found) { return true; } } ++*p; } return false; } bool run_cmd_determine_interpreter(struct source *src, const char *path, const char **err_msg, const char **new_argv0, const char **new_argv1) { if (!fs_read_entire_file(path, src)) { *err_msg = "error determining command interpreter: failed to read file"; return false; } if (strncmp(src->src, "#!", 2) != 0) { *err_msg = "error determining command interpreter: missing #!"; return false; } char *p, *q; p = (char *)&src->src[2]; for (q = p; *q; ++q) { if (*q == '\n' || *q == '\r') { *q = 0; break; } } // skip over all whitespace characters before the next token if (!run_cmd_determine_interpreter_skip_whitespace(&p, false)) { *err_msg = "error determining command interpreter: no interpreter specified after #!"; return false; } *new_argv0 = p; *new_argv1 = 0; // skip over all non-whitespace characters if (!run_cmd_determine_interpreter_skip_whitespace(&p, true)) { return true; } *p = 0; ++p; // skip over all whitespace characters before the next token if (!run_cmd_determine_interpreter_skip_whitespace(&p, false)) { return true; } *new_argv1 = p; return true; } void run_cmd_print_error(struct run_cmd_ctx *ctx, enum log_level lvl) { if (ctx->err_msg) { log_print(true, lvl, "%s", ctx->err_msg); } if (ctx->out.len) { log_print(false, lvl, "stdout:\n%s", ctx->out.buf); } if (ctx->err.len) { log_print(false, lvl, "stderr:\n%s", ctx->err.buf); } } bool run_cmd_checked(struct run_cmd_ctx *ctx, const char *argstr, uint32_t argc, const char *envstr, uint32_t envc) { if (!run_cmd(ctx, argstr, argc, envstr, envc) || ctx->status != 0) { run_cmd_print_error(ctx, log_error); run_cmd_ctx_destroy(ctx); return false; } return true; } bool run_cmd_argv_checked(struct run_cmd_ctx *ctx, char *const *argv, const char *envstr, uint32_t envc) { if (!run_cmd_argv(ctx, argv, envstr, envc) || ctx->status != 0) { run_cmd_print_error(ctx, log_error); run_cmd_ctx_destroy(ctx); return false; } return true; } muon-v0.5.0/src/rpmvercmp.c0000644000175000017500000000730015041716357014617 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "lang/string.h" #include "platform/assert.h" #include "rpmvercmp.h" /* * rpmvercmp has been adapted from the rpm source located at rpmio/rpmvercmp.c * It was most recently updated against rpm version 4.17.0. Modifications have * been made to make it more consistent with the muon coding style and to * remove the unneeded temporary buffers. */ /** * Compare alpha and numeric segments of two versions. * return 1: a is newer than b * 0: a and b are the same version * -1: b is newer than a */ int8_t rpmvercmp(const struct str *a, const struct str *b) { int rc; bool isnum; uint32_t ai = 0, bi = 0, ap = 0, bp = 0, al, bl; /* easy comparison to see if versions are identical */ if (str_eql(a, b)) { return 0; } /* loop through each version segment of str1 and str2 and compare them */ while (ai < a->len && bi < b->len) { while (ai < a->len && !isalnum((int)a->s[ai])) { ++ai; } while (bi < b->len && !isalnum((int)b->s[bi])) { ++bi; } /* If we ran to the end of either, we are finished with the loop */ if (!(ai < a->len && bi < b->len)) { break; } ap = ai; bp = bi; /* grab first completely alpha or completely numeric segment */ /* leave one and two pointing to the start of the alpha or numeric */ /* segment and walk ptr1 and ptr2 to end of segment */ if (isdigit((int)a->s[ap])) { while (ap < a->len && isdigit((int)a->s[ap])) { ++ap; } while (bp < b->len && isdigit((int)b->s[bp])) { ++bp; } isnum = true; } else { while (ap < a->len && isalpha((int)a->s[ap])) { ++ap; } while (bp < b->len && isalpha((int)b->s[bp])) { ++bp; } isnum = false; } /* this cannot happen, as we previously tested to make sure that */ /* the first string has a non-null segment */ assert(ai != ap); /* take care of the case where the two version segments are */ /* different types: one numeric, the other alpha (i.e. empty) */ /* numeric segments are always newer than alpha segments */ /* XXX See patch #60884 (and details) from bugzilla #50977. */ if (bi == bp) { return isnum ? 1 : -1; } if (isnum) { /* this used to be done by converting the digit segments */ /* to ints using atoi() - it's changed because long */ /* digit segments can overflow an int - this should fix that. */ /* throw away any leading zeros - it's a number, right? */ while (a->s[ai] == '0') { ++ai; } while (b->s[bi] == '0') { ++bi; } /* whichever number has more digits wins */ if (ap - ai > bp - bi) { return 1; } if (bp - bi > ap - ai) { return -1; } } /* memcmp will return which one is greater - even if the two */ /* segments are alpha or if they are numeric. don't return */ /* if they are equal because there might be more segments to */ /* compare */ al = ap - ai; bl = bp - bi; rc = memcmp(&a->s[ai], &b->s[bi], al < bl ? al : bl); if (rc) { return rc < 1 ? -1 : 1; } else if (al != bl) { // emulate strcmp where iff the comparison is ==, the // longer string wins. return al > bl ? 1 : -1; } /* restore character that was replaced by null above */ /* *ptr1 = oldch1; */ /* one = ptr1; */ ai = ap; /* *ptr2 = oldch2; */ /* two = ptr2; */ bi = bp; } /* this catches the case where all numeric and alpha segments have */ /* compared identically but the segment separating characters were */ /* different */ if (!(ai < a->len) && !(bi < b->len)) { return 0; } /* whichever version still has characters left over wins */ return !(ai < a->len) ? -1 : 1; } muon-v0.5.0/src/functions/0002755000175000017500000000000015041716357014452 5ustar buildbuildmuon-v0.5.0/src/functions/machine.c0000644000175000017500000001252615041716357016226 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "buf_size.h" #include "error.h" #include "functions/machine.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "log.h" #include "machines.h" #include "platform/assert.h" static struct machine_definition * get_machine_for_self(struct workspace *wk, obj self) { switch (get_obj_machine(wk, self)) { case machine_kind_build: return &build_machine; case machine_kind_host: return &host_machine; default: break; } UNREACHABLE_RETURN; } static bool func_machine_system(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct machine_definition *m = get_machine_for_self(wk, self); *res = str_enum_get(wk, complex_type_enum_get(wk, tc_cx_enum_machine_system), machine_system_to_s(m->sys)); return true; } static bool func_machine_subsystem(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct machine_definition *m = get_machine_for_self(wk, self); if (!m->subsystem) { vm_error(wk, "subsystem is undefined"); return false; } *res = str_enum_get( wk, complex_type_enum_get(wk, tc_cx_enum_machine_subsystem), machine_subsystem_to_s(m->subsystem)); return true; } static bool func_machine_endian(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct machine_definition *m = get_machine_for_self(wk, self); const char *s = NULL; switch (m->endianness) { case endianness_uninitialized: UNREACHABLE; break; case little_endian: s = "little"; break; case big_endian: s = "big"; break; } *res = str_enum_get(wk, complex_type_enum_get(wk, tc_cx_enum_machine_endian), s); return true; } static bool func_machine_cpu_family(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct machine_definition *m = get_machine_for_self(wk, self); *res = make_str(wk, m->cpu_family); return true; } static bool func_machine_cpu(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct machine_definition *m = get_machine_for_self(wk, self); *res = make_str(wk, m->cpu); return true; } static bool func_machine_kernel(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct machine_definition *m = get_machine_for_self(wk, self); *res = make_str(wk, machine_system_to_kernel_name(m->sys)); return true; } const struct func_impl impl_tbl_machine[] = { { "cpu", func_machine_cpu, tc_string }, { "cpu_family", func_machine_cpu_family, tc_string }, { "endian", func_machine_endian, COMPLEX_TYPE_PRESET(tc_cx_enum_machine_endian) }, { "system", func_machine_system, COMPLEX_TYPE_PRESET(tc_cx_enum_machine_system) }, { "kernel", func_machine_kernel, tc_string }, { "subsystem", func_machine_subsystem, COMPLEX_TYPE_PRESET(tc_cx_enum_machine_subsystem) }, { NULL, NULL }, }; struct machine_props { enum machine_system system; enum machine_subsystem subsystem; enum endianness endian; const struct str *cpu; const struct str *cpu_family; }; static bool func_machine_set_props(struct workspace *wk, obj self, obj *res) { if (vm_enum(wk, endianness)) { vm_enum_value(wk, endianness, big_endian); vm_enum_value(wk, endianness, little_endian); }; if (vm_enum(wk, machine_system)) { #define MACHINE_ENUM(id) vm_enum_value_prefixed(wk, machine_system, id); FOREACH_MACHINE_SYSTEM(MACHINE_ENUM) #undef MACHINE_ENUM } if (vm_enum(wk, machine_subsystem)) { #define MACHINE_ENUM(id) vm_enum_value_prefixed(wk, machine_subsystem, id); FOREACH_MACHINE_SUBSYSTEM(MACHINE_ENUM) #undef MACHINE_ENUM } if (vm_struct(wk, machine_props)) { vm_struct_member(wk, machine_props, cpu, vm_struct_type_str); vm_struct_member(wk, machine_props, cpu_family, vm_struct_type_str); vm_struct_member(wk, machine_props, endian, vm_struct_type_enum(wk, endianness)); vm_struct_member(wk, machine_props, system, vm_struct_type_enum(wk, machine_system)); vm_struct_member(wk, machine_props, subsystem, vm_struct_type_enum(wk, machine_subsystem)); } struct args_norm an[] = { { obj_dict, .desc = vm_struct_docs(wk, machine_props, "accepted properties:\n```\n%s\n```") }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } struct machine_props props = { 0 }; if (!vm_obj_to_struct(wk, machine_props, an[0].val, &props)) { return false; } struct machine_definition *m = get_machine_for_self(wk, self); if (props.cpu) { cstr_copy(m->cpu, props.cpu); } if (props.cpu_family) { cstr_copy(m->cpu_family, props.cpu_family); } if (props.system) { m->sys = props.system; } m->subsystem = props.subsystem; if (props.endian) { m->endianness = props.endian; } return true; } const struct func_impl impl_tbl_machine_internal[] = { { "cpu", func_machine_cpu, tc_string }, { "cpu_family", func_machine_cpu_family, tc_string }, { "endian", func_machine_endian, COMPLEX_TYPE_PRESET(tc_cx_enum_machine_endian) }, { "system", func_machine_system, COMPLEX_TYPE_PRESET(tc_cx_enum_machine_system) }, { "kernel", func_machine_kernel, tc_string }, { "subsystem", func_machine_subsystem, COMPLEX_TYPE_PRESET(tc_cx_enum_machine_subsystem) }, { "set_props", func_machine_set_props }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/0002755000175000017500000000000015041716357016122 5ustar buildbuildmuon-v0.5.0/src/functions/modules/keyval.c0000644000175000017500000000305315041716357017560 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "error.h" #include "formats/ini.h" #include "functions/modules/keyval.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/mem.h" struct keyval_parse_ctx { struct workspace *wk; obj dict; }; static bool keyval_parse_cb(void *_ctx, struct source *src, const char *sect, const char *k, const char *v, struct source_location location) { struct keyval_parse_ctx *ctx = _ctx; obj_dict_set(ctx->wk, ctx->dict, make_str(ctx->wk, k), make_str(ctx->wk, v)); return true; } static bool func_module_keyval_load(struct workspace *wk, obj self, obj *res) { bool ret = false; struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const char *path = NULL; switch (get_obj_type(wk, an[0].val)) { case obj_file: path = get_file_path(wk, an[0].val); break; case obj_string: path = get_cstr(wk, an[0].val); break; default: UNREACHABLE; } *res = make_obj(wk, obj_dict); struct keyval_parse_ctx ctx = { .wk = wk, .dict = *res, }; struct source src = { 0 }; char *buf = NULL; if (!keyval_parse(path, &src, &buf, keyval_parse_cb, &ctx)) { goto ret; } ret = true; ret: fs_source_destroy(&src); if (buf) { z_free(buf); } return ret; } const struct func_impl impl_tbl_module_keyval[] = { { "load", func_module_keyval_load, tc_dict, }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/curl.c0000644000175000017500000000243515041716357017235 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "external/libcurl.h" #include "functions/modules/curl.h" #include "lang/typecheck.h" #include "log.h" #include "platform/mem.h" static bool func_module_curl_fetch(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string, .desc = "the url to fetch" }, ARG_TYPE_NULL, }; if (!pop_args(wk, an, NULL)) { return false; } mc_init(); uint8_t *buf; uint64_t len; enum mc_fetch_flag flags = 0; struct mc_fetch_stats stats; int32_t handle = mc_fetch_begin(get_cstr(wk, an[0].val), &buf, &len, flags); if (handle == -1) { return false; } bool ok = true; while (true) { switch (mc_fetch_collect(handle, &stats)) { case mc_fetch_collect_result_pending: break; case mc_fetch_collect_result_done: { *res = make_strn(wk, (char *)buf, len); goto done; } case mc_fetch_collect_result_error: { ok = false; goto done; } } mc_wait(1000); } done: z_free(buf); mc_deinit(); return ok; } const struct func_impl impl_tbl_module_curl[] = { { "fetch", func_module_curl_fetch, tc_string, .desc = "Begin fetching a url using libcurl. Only available if libcurl support is enabeld." }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/pkgconfig.c0000644000175000017500000006263315041716357020245 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "buf_size.h" #include "error.h" #include "functions/both_libs.h" #include "functions/custom_target.h" #include "functions/file.h" #include "install.h" #include "lang/typecheck.h" #include "options.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" enum pkgconf_visibility { pkgconf_visibility_pub, pkgconf_visibility_priv, }; struct pkgconf_file { // strings obj name, description, url, version; // arrays of string obj cflags, conflicts; obj builtin_dir_variables, variables; obj reqs[2], libs[2]; obj exclude; bool libs_contains_internal[2]; bool dataonly; }; enum module_pkgconf_diropt { module_pkgconf_diropt_prefix, module_pkgconf_diropt_bindir, module_pkgconf_diropt_datadir, module_pkgconf_diropt_includedir, module_pkgconf_diropt_infodir, module_pkgconf_diropt_libdir, module_pkgconf_diropt_libexecdir, module_pkgconf_diropt_localedir, module_pkgconf_diropt_localstatedir, module_pkgconf_diropt_mandir, module_pkgconf_diropt_sbindir, module_pkgconf_diropt_sharedstatedir, module_pkgconf_diropt_sysconfdir, }; static struct { const char *const name, *const optname; bool refd, added; } module_pkgconf_diropts[] = { [module_pkgconf_diropt_prefix] = { "${prefix}", "prefix" }, [module_pkgconf_diropt_bindir] = { "${bindir}", "bindir" }, [module_pkgconf_diropt_datadir] = { "${datadir}", "datadir" }, [module_pkgconf_diropt_includedir] = { "${includedir}", "includedir" }, [module_pkgconf_diropt_infodir] = { "${infodir}", "infodir" }, [module_pkgconf_diropt_libdir] = { "${libdir}", "libdir" }, [module_pkgconf_diropt_libexecdir] = { "${libexecdir}", "libexecdir" }, [module_pkgconf_diropt_localedir] = { "${localedir}", "localedir" }, [module_pkgconf_diropt_localstatedir] = { "${localstatedir}", "localstatedir" }, [module_pkgconf_diropt_mandir] = { "${mandir}", "mandir" }, [module_pkgconf_diropt_sbindir] = { "${sbindir}", "sbindir" }, [module_pkgconf_diropt_sharedstatedir] = { "${sharedstatedir}", "sharedstatedir" }, [module_pkgconf_diropt_sysconfdir] = { "${sysconfdir}", "sysconfdir" }, }; static enum iteration_result add_subdirs_includes_iter(struct workspace *wk, void *_ctx, obj val) { obj *cflags = _ctx; if (str_eql(get_str(wk, val), &STR("."))) { obj_array_push(wk, *cflags, make_str(wk, "-I${includedir}")); } else { TSTR(path); path_join(wk, &path, "-I${includedir}", get_cstr(wk, val)); obj_array_push(wk, *cflags, tstr_into_str(wk, &path)); } return ir_cont; } static bool module_pkgconf_lib_to_lname(struct workspace *wk, obj lib, obj *res) { TSTR(basename); const char *str; switch (get_obj_type(wk, lib)) { case obj_string: str = get_cstr(wk, lib); break; case obj_file: { path_basename(wk, &basename, get_file_path(wk, lib)); char *dot; if ((dot = strrchr(basename.buf, '.'))) { *dot = '\0'; } str = basename.buf; break; } default: UNREACHABLE; } if (str[0] == '-') { *res = make_str(wk, str); return true; } struct str s = STRL(str); if (str_startswith(&s, &STR("-l"))) { s.len -= 2; s.s += 2; } else if (str_startswith(&s, &STR("lib"))) { s.len -= 3; s.s += 3; } *res = make_strf(wk, "-l%.*s", s.len, s.s); return true; } struct module_pkgconf_process_reqs_iter_ctx { uint32_t err_node; obj dest; }; static enum iteration_result module_pkgconf_process_reqs_iter(struct workspace *wk, void *_ctx, obj val) { struct module_pkgconf_process_reqs_iter_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_string: obj_array_push(wk, ctx->dest, val); break; case obj_both_libs: val = decay_both_libs(wk, val); /* fallthrough */ case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, val); if (!tgt->generated_pc) { vm_error_at(wk, ctx->err_node, "build target has no associated pc file"); return ir_err; } obj_array_push(wk, ctx->dest, tgt->generated_pc); break; } case obj_dependency: { struct obj_dependency *dep = get_obj_dependency(wk, val); if (!(dep->flags & dep_flag_found) || (dep->type == dependency_type_threads)) { return ir_cont; } if (dep->type != dependency_type_pkgconf) { vm_error_at(wk, ctx->err_node, "dependency not from pkgconf"); return ir_err; } obj_array_push(wk, ctx->dest, dep->name); // TODO: handle version req break; } default: vm_error_at( wk, ctx->err_node, "invalid type for pkgconf require %s", obj_type_to_s(get_obj_type(wk, val))); return ir_err; } return ir_cont; } static bool module_pkgconf_process_reqs(struct workspace *wk, uint32_t err_node, obj reqs, obj dest) { struct module_pkgconf_process_reqs_iter_ctx ctx = { .err_node = err_node, .dest = dest, }; if (!obj_array_foreach(wk, reqs, &ctx, module_pkgconf_process_reqs_iter)) { return false; } return true; } struct module_pkgconf_process_libs_iter_ctx { uint32_t err_node; struct pkgconf_file *pc; enum pkgconf_visibility vis; bool link_whole; }; static bool module_pkgconf_process_libs(struct workspace *wk, uint32_t err_node, obj src, struct pkgconf_file *pc, enum pkgconf_visibility vis, bool link_whole); static enum iteration_result str_to_file_iter(struct workspace *wk, void *_ctx, obj v) { obj *arr = _ctx; obj f; f = make_obj(wk, obj_file); *get_obj_file(wk, f) = v; obj_array_push(wk, *arr, f); return ir_cont; } static enum iteration_result module_pkgconf_process_libs_iter(struct workspace *wk, void *_ctx, obj val) { struct module_pkgconf_process_libs_iter_ctx *ctx = _ctx; /* obj_lprintf(wk, "processing lib %o\n", val); */ switch (get_obj_type(wk, val)) { case obj_string: { obj lib; if (!module_pkgconf_lib_to_lname(wk, val, &lib)) { return ir_err; } obj_array_push(wk, ctx->pc->libs[ctx->vis], lib); break; } case obj_file: { if (!file_is_linkable(wk, val)) { vm_error_at(wk, ctx->err_node, "non linkable file %o among libraries", val); return ir_err; } if (path_is_subpath(wk->source_root, get_file_path(wk, val)) || path_is_subpath(wk->build_root, get_file_path(wk, val))) { ctx->pc->libs_contains_internal[ctx->vis] = true; } obj lib; if (!module_pkgconf_lib_to_lname(wk, val, &lib)) { return ir_err; } obj_array_push(wk, ctx->pc->libs[ctx->vis], lib); break; } case obj_both_libs: val = decay_both_libs(wk, val); /* fallthrough */ case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, val); if (tgt->generated_pc) { obj_array_push(wk, ctx->pc->reqs[ctx->vis], tgt->generated_pc); } else { if (tgt->type == tgt_executable) { vm_error_at(wk, ctx->err_node, "invalid build_target type"); return ir_err; } if (tgt->dep.raw.deps) { if (!module_pkgconf_process_libs(wk, ctx->err_node, tgt->dep.raw.deps, ctx->pc, pkgconf_visibility_pub, false)) { return ir_err; } } const enum pkgconf_visibility link_vis = tgt->type == tgt_static_library ? pkgconf_visibility_pub : pkgconf_visibility_priv; if (tgt->dep.raw.link_with) { if (!module_pkgconf_process_libs( wk, ctx->err_node, tgt->dep.raw.link_with, ctx->pc, link_vis, false)) { return ir_err; } } if (tgt->dep.raw.link_whole) { if (!module_pkgconf_process_libs( wk, ctx->err_node, tgt->dep.raw.link_whole, ctx->pc, link_vis, true)) { return ir_err; } } obj lib; if (!module_pkgconf_lib_to_lname(wk, tgt->name, &lib)) { return ir_err; } if (ctx->link_whole) { obj_array_push(wk, ctx->pc->exclude, lib); return ir_cont; } else if ((tgt->type == tgt_static_library && !(tgt->flags & build_tgt_flag_installed))) { return ir_cont; } ctx->pc->libs_contains_internal[ctx->vis] = true; obj_array_push(wk, ctx->pc->libs[ctx->vis], lib); } break; } case obj_dependency: { struct obj_dependency *dep = get_obj_dependency(wk, val); if (!(dep->flags & dep_flag_found)) { return ir_cont; } switch (dep->type) { case dependency_type_system: case dependency_type_declared: { // TODO: I'm pretty sure this doesn't obey partial // dependency semantics if this is a sub dependency of // a partial dep with compile_args: false if (dep->dep.compile_args) { obj_array_extend(wk, ctx->pc->cflags, dep->dep.compile_args); } if (dep->dep.raw.link_with) { if (!module_pkgconf_process_libs( wk, ctx->err_node, dep->dep.raw.link_with, ctx->pc, ctx->vis, false)) { return ir_err; } } if (dep->dep.raw.link_whole) { if (!module_pkgconf_process_libs( wk, ctx->err_node, dep->dep.raw.link_whole, ctx->pc, ctx->vis, true)) { return ir_err; } } if (dep->dep.raw.deps) { if (!module_pkgconf_process_libs(wk, ctx->err_node, dep->dep.raw.deps, ctx->pc, pkgconf_visibility_priv, false)) { return ir_err; } } break; } case dependency_type_pkgconf: obj_array_push(wk, ctx->pc->reqs[ctx->vis], dep->name); // TODO: handle version req break; case dependency_type_threads: obj_array_push(wk, ctx->pc->libs[pkgconf_visibility_priv], make_str(wk, "-pthread")); break; case dependency_type_external_library: { obj link_with_files; link_with_files = make_obj(wk, obj_array); if (dep->dep.link_with) { obj_array_foreach(wk, dep->dep.link_with, &link_with_files, str_to_file_iter); if (!module_pkgconf_process_libs( wk, ctx->err_node, link_with_files, ctx->pc, ctx->vis, false)) { return ir_err; } } if (dep->dep.link_with_not_found) { if (!module_pkgconf_process_libs( wk, ctx->err_node, dep->dep.link_with_not_found, ctx->pc, ctx->vis, false)) { return ir_err; } } break; } case dependency_type_not_found: break; } break; } case obj_custom_target: { if (!custom_target_is_linkable(wk, val)) { vm_error_at(wk, ctx->err_node, "non linkable custom target %o among libraries", val); return ir_err; } struct obj_custom_target *tgt = get_obj_custom_target(wk, val); obj out; out = obj_array_index(wk, tgt->output, 0); if (str_endswith(get_str(wk, *get_obj_file(wk, out)), &STR(".a"))) { return ir_cont; } obj lib; if (!module_pkgconf_lib_to_lname(wk, out, &lib)) { return ir_err; } ctx->pc->libs_contains_internal[ctx->vis] = true; obj_array_push(wk, ctx->pc->libs[ctx->vis], lib); break; } default: vm_error_at( wk, ctx->err_node, "invalid type for pkgconf library %s", obj_type_to_s(get_obj_type(wk, val))); return ir_err; } return ir_cont; } static bool module_pkgconf_process_libs(struct workspace *wk, uint32_t err_node, obj src, struct pkgconf_file *pc, enum pkgconf_visibility vis, bool link_whole) { struct module_pkgconf_process_libs_iter_ctx ctx = { .pc = pc, .err_node = err_node, .vis = vis, .link_whole = link_whole, }; if (get_obj_type(wk, src) == obj_array) { if (!obj_array_foreach(wk, src, &ctx, module_pkgconf_process_libs_iter)) { return false; } } else { if (module_pkgconf_process_libs_iter(wk, &ctx, src) == ir_err) { return false; } } return true; } static bool module_pkgconf_declare_var(struct workspace *wk, uint32_t err_node, bool escape, bool skip_reserved_check, const struct str *key, const struct str *val, obj dest) { if (!skip_reserved_check) { const char *reserved[] = { "prefix", "libdir", "includedir", NULL }; uint32_t i; for (i = 0; reserved[i]; ++i) { if (str_eql(key, &STRL(reserved[i]))) { vm_error_at(wk, err_node, "variable %s is reserved", reserved[i]); return false; } } for (i = 0; i < ARRAY_LEN(module_pkgconf_diropts); ++i) { if (str_eql(key, &STRL(module_pkgconf_diropts[i].optname))) { module_pkgconf_diropts[i].added = true; } if (module_pkgconf_diropts[i].refd) { continue; } if (str_startswith(val, &STRL(module_pkgconf_diropts[i].name))) { module_pkgconf_diropts[module_pkgconf_diropt_prefix].refd = true; // prefix module_pkgconf_diropts[i].refd = true; } } } TSTR(esc); const char *esc_val; if (escape) { pkgconf_escape(wk, &esc, val->s); esc_val = esc.buf; } else { esc_val = val->s; } obj_array_push(wk, dest, make_strf(wk, "%.*s=%s", key->len, key->s, esc_val)); return true; } static void module_pkgconf_declare_builtin_dir_var(struct workspace *wk, const char *opt, obj dest) { obj val, valstr; get_option_value(wk, current_project(wk), opt, &val); if (strcmp(opt, "prefix") == 0) { valstr = val; } else { valstr = make_strf(wk, "${prefix}/%s", get_cstr(wk, val)); } module_pkgconf_declare_var(wk, 0, true, true, &STRL(opt), get_str(wk, valstr), dest); } struct module_pkgconf_process_vars_ctx { uint32_t err_node; bool escape, dataonly; obj dest; }; static enum iteration_result module_pkgconf_process_vars_array_iter(struct workspace *wk, void *_ctx, obj v) { struct module_pkgconf_process_vars_ctx *ctx = _ctx; const struct str *src = get_str(wk, v); const char *sep; if (!(sep = strchr(src->s, '='))) { vm_error_at(wk, ctx->err_node, "invalid variable string, missing '='"); return ir_err; } struct str key = { .s = src->s, .len = sep - src->s }; struct str val = { .s = src->s + (key.len + 1), .len = src->len - (key.len + 1), }; if (!module_pkgconf_declare_var(wk, ctx->err_node, ctx->escape, ctx->dataonly, &key, &val, ctx->dest)) { return ir_err; } return ir_cont; } static enum iteration_result module_pkgconf_process_vars_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct module_pkgconf_process_vars_ctx *ctx = _ctx; if (!module_pkgconf_declare_var( wk, ctx->err_node, ctx->escape, ctx->dataonly, get_str(wk, key), get_str(wk, val), ctx->dest)) { return ir_err; } return ir_cont; } static bool module_pkgconf_process_vars(struct workspace *wk, uint32_t err_node, bool escape, bool dataonly, obj vars, obj dest) { struct module_pkgconf_process_vars_ctx ctx = { .err_node = err_node, .escape = escape, .dataonly = dataonly, .dest = dest, }; switch (get_obj_type(wk, vars)) { case obj_string: if (module_pkgconf_process_vars_array_iter(wk, &ctx, vars) == ir_err) { return false; } break; case obj_array: if (!obj_array_foreach(wk, vars, &ctx, module_pkgconf_process_vars_array_iter)) { return false; } break; case obj_dict: if (!obj_dict_foreach(wk, vars, &ctx, module_pkgconf_process_vars_dict_iter)) { return false; } break; default: vm_error_at(wk, err_node, "invalid type for variables, expected array or dict"); return false; } return true; } static bool module_pkgconf_prepend_libdir(struct workspace *wk, struct args_kw *install_dir_opt, obj *libs) { obj libdir; const char *path; if (install_dir_opt->set) { TSTR(rel); obj pre; get_option_value(wk, current_project(wk), "prefix", &pre); const char *install_dir = get_cstr(wk, install_dir_opt->val), *prefix = get_cstr(wk, pre); if (path_is_subpath(prefix, install_dir)) { path_relative_to(wk, &rel, prefix, install_dir); path = rel.buf; } else if (path_is_absolute(install_dir)) { vm_error_at(wk, install_dir_opt->val, "absolute install dir path not a subdir of prefix"); return false; } else { path = install_dir; } libdir = make_strf(wk, "-L${prefix}/%s", path); } else { libdir = make_strf(wk, "-L${libdir}"); } obj arr; arr = make_obj(wk, obj_array); obj_array_push(wk, arr, libdir); obj_array_extend_nodup(wk, arr, *libs); *libs = arr; return true; } struct module_pkgconf_remove_dups_ctx { obj exclude; obj res; }; enum iteration_result module_pkgconf_remove_dups_iter(struct workspace *wk, void *_ctx, obj val) { struct module_pkgconf_remove_dups_ctx *ctx = _ctx; if (obj_array_in(wk, ctx->exclude, val)) { return ir_cont; } obj_array_push(wk, ctx->exclude, val); obj_array_push(wk, ctx->res, val); return ir_cont; } static void module_pkgconf_remove_dups(struct workspace *wk, obj *list, obj exclude) { obj arr; arr = make_obj(wk, obj_array); struct module_pkgconf_remove_dups_ctx ctx = { .exclude = exclude, .res = arr, }; obj_array_foreach(wk, *list, &ctx, module_pkgconf_remove_dups_iter); *list = ctx.res; } static bool module_pkgconf_write(struct workspace *wk, const char *path, struct pkgconf_file *pc) { FILE *f; if (!(f = fs_fopen(path, "wb"))) { return false; } if (get_obj_array(wk, pc->builtin_dir_variables)->len) { obj str; obj_array_join(wk, false, pc->builtin_dir_variables, make_str(wk, "\n"), &str); fputs(get_cstr(wk, str), f); fputc('\n', f); } if (get_obj_array(wk, pc->variables)->len) { fputc('\n', f); obj str; obj_array_join(wk, false, pc->variables, make_str(wk, "\n"), &str); fputs(get_cstr(wk, str), f); fputc('\n', f); } fputc('\n', f); fprintf(f, "Name: %s\n", get_cstr(wk, pc->name)); fprintf(f, "Description: %s\n", get_cstr(wk, pc->description)); if (pc->url) { fprintf(f, "URL: %s\n", get_cstr(wk, pc->url)); } fprintf(f, "Version: %s\n", get_cstr(wk, pc->version)); if (get_obj_array(wk, pc->reqs[pkgconf_visibility_pub])->len) { obj str; obj_array_join(wk, false, pc->reqs[pkgconf_visibility_pub], make_str(wk, ", "), &str); fprintf(f, "Requires: %s\n", get_cstr(wk, str)); } if (get_obj_array(wk, pc->reqs[pkgconf_visibility_priv])->len) { obj str; obj_array_join(wk, false, pc->reqs[pkgconf_visibility_priv], make_str(wk, ", "), &str); fprintf(f, "Requires.private: %s\n", get_cstr(wk, str)); } if (get_obj_array(wk, pc->libs[pkgconf_visibility_pub])->len) { obj str; obj_array_join(wk, false, pc->libs[pkgconf_visibility_pub], make_str(wk, " "), &str); fprintf(f, "Libs: %s\n", get_cstr(wk, str)); } if (get_obj_array(wk, pc->libs[pkgconf_visibility_priv])->len) { obj str; obj_array_join(wk, false, pc->libs[pkgconf_visibility_priv], make_str(wk, " "), &str); fprintf(f, "Libs.private: %s\n", get_cstr(wk, str)); } if (!pc->dataonly && get_obj_array(wk, pc->cflags)->len) { fprintf(f, "Cflags: %s\n", get_cstr(wk, join_args_pkgconf(wk, pc->cflags))); } if (!fs_fclose(f)) { return false; } return true; } static bool func_module_pkgconfig_generate(struct workspace *wk, obj self, obj *res) { uint32_t i; struct args_norm an[] = { { tc_both_libs | tc_build_target, .optional = true }, ARG_TYPE_NULL }; enum kwargs { kw_name, kw_description, kw_extra_cflags, kw_filebase, kw_install_dir, kw_libraries, kw_libraries_private, kw_subdirs, kw_requires, kw_requires_private, kw_url, kw_variables, kw_unescaped_variables, kw_uninstalled_variables, // TODO kw_unescaped_uninstalled_variables, // TODO kw_version, kw_dataonly, kw_conflicts, }; const type_tag tc_library = tc_string | tc_file | tc_build_target | tc_dependency | tc_custom_target | tc_both_libs, tc_requires = tc_string | tc_build_target | tc_dependency | tc_both_libs; struct args_kw akw[] = { [kw_name] = { "name", obj_string }, [kw_description] = { "description", obj_string }, [kw_extra_cflags] = { "extra_cflags", TYPE_TAG_LISTIFY | obj_string }, [kw_filebase] = { "filebase", obj_string }, [kw_install_dir] = { "install_dir", obj_string }, [kw_libraries] = { "libraries", TYPE_TAG_LISTIFY | tc_library }, [kw_libraries_private] = { "libraries_private", TYPE_TAG_LISTIFY | tc_library }, [kw_subdirs] = { "subdirs", TYPE_TAG_LISTIFY | obj_string }, [kw_requires] = { "requires", TYPE_TAG_LISTIFY | tc_requires }, [kw_requires_private] = { "requires_private", TYPE_TAG_LISTIFY | tc_requires }, [kw_url] = { "url", obj_string }, [kw_variables] = { "variables", tc_string | tc_array | tc_dict }, [kw_unescaped_variables] = { "unescaped_variables", tc_string | tc_array | tc_dict }, [kw_uninstalled_variables] = { "uninstalled_variables", tc_string | tc_array | tc_dict }, [kw_unescaped_uninstalled_variables] = { "unescaped_uninstalled_variables", tc_string | tc_array | tc_dict }, [kw_version] = { "version", obj_string }, [kw_dataonly] = { "dataonly", obj_bool }, [kw_conflicts] = { "conflicts", TYPE_TAG_LISTIFY | obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (!an[0].set && !akw[kw_name].set) { vm_error(wk, "you must either pass a library, or the name keyword"); return false; } struct pkgconf_file pc = { .url = akw[kw_url].val, .conflicts = akw[kw_conflicts].val, .dataonly = akw[kw_dataonly].set ? get_obj_bool(wk, akw[kw_dataonly].val) : false, }; for (i = 0; i < 2; ++i) { pc.libs[i] = make_obj(wk, obj_array); pc.reqs[i] = make_obj(wk, obj_array); } pc.cflags = make_obj(wk, obj_array); pc.variables = make_obj(wk, obj_array); pc.builtin_dir_variables = make_obj(wk, obj_array); pc.exclude = make_obj(wk, obj_array); obj mainlib = 0; if (an[0].set) { switch (get_obj_type(wk, an[0].val)) { case obj_both_libs: { mainlib = decay_both_libs(wk, an[0].val); break; } case obj_build_target: mainlib = an[0].val; break; default: assert(false && "unreachable"); } } if (akw[kw_name].set) { pc.name = akw[kw_name].val; } else if (an[0].set) { pc.name = get_obj_build_target(wk, mainlib)->name; } if (akw[kw_description].set) { pc.description = akw[kw_description].val; } else if (mainlib) { pc.description = make_strf(wk, "%s: %s", get_cstr(wk, current_project(wk)->cfg.name), get_cstr(wk, pc.name)); } if (akw[kw_version].set) { pc.version = akw[kw_version].val; } else { pc.version = current_project(wk)->cfg.version; } /* cflags include dirs */ if (akw[kw_subdirs].set) { if (!obj_array_foreach(wk, akw[kw_subdirs].val, &pc.cflags, add_subdirs_includes_iter)) { return false; } } else { obj_array_push(wk, pc.cflags, make_str(wk, "-I${includedir}")); } if (mainlib) { if (!module_pkgconf_process_libs(wk, an[0].node, mainlib, &pc, pkgconf_visibility_pub, false)) { return false; } } if (akw[kw_libraries].set) { if (!module_pkgconf_process_libs( wk, akw[kw_libraries].node, akw[kw_libraries].val, &pc, pkgconf_visibility_pub, false)) { return false; } } if (akw[kw_libraries_private].set) { if (!module_pkgconf_process_libs(wk, akw[kw_libraries_private].node, akw[kw_libraries_private].val, &pc, pkgconf_visibility_priv, false)) { return false; } } module_pkgconf_remove_dups(wk, &pc.reqs[pkgconf_visibility_pub], pc.exclude); module_pkgconf_remove_dups(wk, &pc.libs[pkgconf_visibility_pub], pc.exclude); module_pkgconf_remove_dups(wk, &pc.reqs[pkgconf_visibility_priv], pc.exclude); module_pkgconf_remove_dups(wk, &pc.libs[pkgconf_visibility_priv], pc.exclude); for (i = 0; i < 2; ++i) { if (get_obj_array(wk, pc.libs[i])->len && pc.libs_contains_internal[i]) { if (!module_pkgconf_prepend_libdir(wk, &akw[kw_install_dir], &pc.libs[i])) { return false; } } } if (akw[kw_requires].set) { if (!module_pkgconf_process_reqs( wk, akw[kw_requires].node, akw[kw_requires].val, pc.reqs[pkgconf_visibility_pub])) { return false; } } if (akw[kw_requires_private].set) { if (!module_pkgconf_process_reqs(wk, akw[kw_requires_private].node, akw[kw_requires_private].val, pc.reqs[pkgconf_visibility_priv])) { return false; } } if (akw[kw_extra_cflags].set) { obj_array_extend(wk, pc.cflags, akw[kw_extra_cflags].val); } { // variables for (i = 0; i < ARRAY_LEN(module_pkgconf_diropts); ++i) { module_pkgconf_diropts[i].refd = false; } if (!pc.dataonly) { module_pkgconf_diropts[module_pkgconf_diropt_prefix].refd = true; module_pkgconf_diropts[module_pkgconf_diropt_includedir].refd = true; if (get_obj_array(wk, pc.libs[0])->len || get_obj_array(wk, pc.libs[1])->len) { module_pkgconf_diropts[module_pkgconf_diropt_libdir].refd = true; } } if (akw[kw_variables].set) { if (!module_pkgconf_process_vars( wk, akw[kw_variables].node, true, pc.dataonly, akw[kw_variables].val, pc.variables)) { return false; } } if (akw[kw_unescaped_variables].set) { if (!module_pkgconf_process_vars(wk, akw[kw_unescaped_variables].node, false, pc.dataonly, akw[kw_unescaped_variables].val, pc.variables)) { return false; } } for (i = 0; i < ARRAY_LEN(module_pkgconf_diropts); ++i) { if (module_pkgconf_diropts[i].refd && !module_pkgconf_diropts[i].added) { module_pkgconf_declare_builtin_dir_var( wk, module_pkgconf_diropts[i].optname, pc.builtin_dir_variables); } } } obj filebase = pc.name; if (akw[kw_filebase].set) { filebase = akw[kw_filebase].val; } TSTR(path); path_join(wk, &path, wk->muon_private, get_cstr(wk, filebase)); tstr_pushs(wk, &path, ".pc"); if (!module_pkgconf_write(wk, path.buf, &pc)) { return false; } if (mainlib) { get_obj_build_target(wk, mainlib)->generated_pc = filebase; } *res = make_obj(wk, obj_file); *get_obj_file(wk, *res) = tstr_into_str(wk, &path); { TSTR(install_dir_buf); const char *install_dir; if (akw[kw_install_dir].set) { install_dir = get_cstr(wk, akw[kw_install_dir].val); } else { obj install_base; if (pc.dataonly) { get_option_value(wk, current_project(wk), "datadir", &install_base); } else { get_option_value(wk, current_project(wk), "libdir", &install_base); } path_join(wk, &install_dir_buf, get_cstr(wk, install_base), "pkgconfig"); install_dir = install_dir_buf.buf; } TSTR(dest); path_join(wk, &dest, install_dir, get_cstr(wk, filebase)); tstr_pushs(wk, &dest, ".pc"); push_install_target(wk, *get_obj_file(wk, *res), tstr_into_str(wk, &dest), 0); } return true; } const struct func_impl impl_tbl_module_pkgconfig[] = { { "generate", func_module_pkgconfig_generate, tc_file }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/sourceset.c0000644000175000017500000000120715041716357020300 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/modules/sourceset.h" #include "lang/typecheck.h" static bool func_module_sourceset_source_set(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj(wk, obj_source_set); struct obj_source_set *ss = get_obj_source_set(wk, *res); ss->rules = make_obj(wk, obj_array); return true; } const struct func_impl impl_tbl_module_sourceset[] = { { "source_set", func_module_sourceset_source_set, tc_source_set, }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/python.c0000644000175000017500000003744415041716357017621 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "coerce.h" #include "embedded.h" #include "formats/json.h" #include "functions/external_program.h" #include "functions/modules/python.h" #include "install.h" #include "lang/object.h" #include "lang/typecheck.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/run_cmd.h" static bool introspect_python_interpreter(struct workspace *wk, const char *path, struct obj_python_installation *python) { struct source src; if (!embedded_get("python/python_info.py", &src)) { return false; } struct run_cmd_ctx cmd_ctx = { 0 }; char *const var_args[] = { (char *)path, "-c", (char *)src.src, 0 }; if (!run_cmd_argv_checked(&cmd_ctx, var_args, NULL, 0)) { return false; } obj res_introspect; { bool success = muon_json_to_obj(wk, &TSTR_STR(&cmd_ctx.out), &res_introspect); run_cmd_ctx_destroy(&cmd_ctx); if (!success) { return false; } } if (get_obj_type(wk, res_introspect) != obj_dict) { LOG_E("introspection object is not a dictionary"); return false; } struct { const char *key; obj *dest; } key_to_dest[] = { { "version", &python->language_version }, { "sysconfig_paths", &python->sysconfig_paths }, { "variables", &python->sysconfig_vars }, { "install_paths", &python->install_paths }, }; uint32_t i; for (i = 0; i < ARRAY_LEN(key_to_dest); ++i) { if (!obj_dict_index_str(wk, res_introspect, key_to_dest[i].key, key_to_dest[i].dest)) { LOG_E("introspection object missing key '%s'", key_to_dest[i].key); return false; } } return true; } static bool python_module_present(struct workspace *wk, const char *pythonpath, const char *mod) { struct run_cmd_ctx cmd_ctx = { 0 }; TSTR(importstr); tstr_pushf(wk, &importstr, "import %s", mod); char *const *args = (char *const[]){ (char *)pythonpath, "-c", importstr.buf, 0 }; bool present = run_cmd_argv(&cmd_ctx, args, NULL, 0) && cmd_ctx.status == 0; run_cmd_ctx_destroy(&cmd_ctx); return present; } struct iter_mod_ctx { const char *pythonpath; uint32_t node; enum requirement_type requirement; }; static enum iteration_result iterate_required_module_list(struct workspace *wk, void *ctx, obj val) { struct iter_mod_ctx *_ctx = ctx; const char *mod = get_cstr(wk, val); if (python_module_present(wk, _ctx->pythonpath, mod)) { return ir_cont; } if (_ctx->requirement == requirement_required) { vm_error_at(wk, _ctx->node, "python: required module '%s' not found", mod); } return ir_err; } static bool build_python_installation(struct workspace *wk, obj self, obj *res, struct tstr cmd_path, bool found, bool pure) { *res = make_obj(wk, obj_python_installation); struct obj_python_installation *python = get_obj_python_installation(wk, *res); python->pure = pure; python->prog = make_obj(wk, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, python->prog); ep->found = found; ep->cmd_array = make_obj(wk, obj_array); obj_array_push(wk, ep->cmd_array, tstr_into_str(wk, &cmd_path)); if (found && !introspect_python_interpreter(wk, cmd_path.buf, python)) { vm_error(wk, "failed to introspect python"); return false; } return true; } static bool func_module_python_find_installation(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string, .optional = true }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_disabler, kw_modules, kw_pure, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_disabler] = { "disabler", obj_bool }, [kw_modules] = { "modules", TYPE_TAG_LISTIFY | obj_string }, [kw_pure] = { "pure", obj_bool }, 0 }; if (!pop_args(wk, an, akw)) { return false; } bool pure = false; if (akw[kw_pure].set) { pure = get_obj_bool(wk, akw[kw_pure].val); } enum requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } bool disabler = akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val); const char *cmd = "python3"; if (an[0].set) { const char *pycmd = get_cstr(wk, an[0].val); if (pycmd && *pycmd) { cmd = pycmd; } } TSTR(cmd_path); bool found = fs_find_cmd(wk, &cmd_path, cmd); if (!found && (requirement == requirement_required)) { vm_error(wk, "%s not found", cmd); return false; } if (!found && disabler) { *res = obj_disabler; return true; } if (!found) { return build_python_installation(wk, self, res, cmd_path, found, pure); } if (akw[kw_modules].set && found) { bool all_present = obj_array_foreach(wk, akw[kw_modules].val, &(struct iter_mod_ctx){ .pythonpath = cmd_path.buf, .node = akw[kw_modules].node, .requirement = requirement, }, iterate_required_module_list); if (!all_present) { if (requirement == requirement_required) { return false; } if (disabler) { *res = obj_disabler; return true; } /* Return a not-found object. */ found = false; } } return build_python_installation(wk, self, res, cmd_path, found, pure); } static bool func_python_installation_language_version(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_python_installation(wk, self)->language_version; return true; } static bool func_module_python3_find_python(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const char *cmd = "python3"; if (an[0].set) { cmd = get_cstr(wk, an[0].val); } TSTR(cmd_path); if (!fs_find_cmd(wk, &cmd_path, cmd)) { vm_error(wk, "python3 not found"); return false; } *res = make_obj(wk, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *res); ep->found = true; ep->cmd_array = make_obj(wk, obj_array); obj_array_push(wk, ep->cmd_array, tstr_into_str(wk, &cmd_path)); return true; } static bool func_python_installation_get_path(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj path = an[0].val; obj sysconfig_paths = get_obj_python_installation(wk, self)->sysconfig_paths; if (obj_dict_index(wk, sysconfig_paths, path, res)) { return true; } if (!an[1].set) { vm_error(wk, "path '%o' not found, no default specified", path); return false; } *res = an[1].val; return true; } static bool func_python_installation_get_var(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj var = an[0].val; obj sysconfig_vars = get_obj_python_installation(wk, self)->sysconfig_vars; if (obj_dict_index(wk, sysconfig_vars, var, res)) { return true; } if (!an[1].set) { vm_error(wk, "variable '%o' not found, no default specified", var); return false; } *res = an[1].val; return true; } static bool func_python_installation_has_path(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj sysconfig_paths = get_obj_python_installation(wk, self)->sysconfig_paths; bool found = obj_dict_in(wk, sysconfig_paths, an[0].val); *res = make_obj_bool(wk, found); return true; } static bool func_python_installation_has_var(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj sysconfig_vars = get_obj_python_installation(wk, self)->sysconfig_vars; bool found = obj_dict_in(wk, sysconfig_vars, an[0].val); *res = make_obj_bool(wk, found); return true; } static bool get_install_dir(struct workspace *wk, obj self, bool pure, const char *subdir, obj *res) { TSTR(installdir); obj prefix; get_option_value(wk, current_project(wk), "prefix", &prefix); struct obj_python_installation *py = get_obj_python_installation(wk, self); if (pure) { obj puredir; get_option_value(wk, current_project(wk), "python.purelibdir", &puredir); if (!str_eql(get_str(wk, puredir), &STR(""))) { path_push(wk, &installdir, get_cstr(wk, puredir)); } else { if (!obj_dict_index_str(wk, py->install_paths, "purelib", &puredir)) { return false; } path_join_absolute(wk, &installdir, get_cstr(wk, prefix), get_cstr(wk, puredir)); } } else { obj platdir; get_option_value(wk, current_project(wk), "python.platlibdir", &platdir); if (!str_eql(get_str(wk, platdir), &STR(""))) { path_push(wk, &installdir, get_cstr(wk, platdir)); } else { if (!obj_dict_index_str(wk, py->install_paths, "platlib", &platdir)) { return false; } path_join_absolute(wk, &installdir, get_cstr(wk, prefix), get_cstr(wk, platdir)); } } if (subdir) { path_push(wk, &installdir, subdir); } *res = tstr_into_str(wk, &installdir); return true; } static bool func_python_installation_get_install_dir(struct workspace *wk, obj self, obj *res) { enum kwargs { kw_pure, kw_subdir, }; struct args_kw akw[] = { [kw_pure] = { "pure", obj_bool }, [kw_subdir] = { "subdir", obj_string }, 0, }; if (!pop_args(wk, NULL, akw)) { return false; } struct obj_python_installation *py = get_obj_python_installation(wk, self); bool pure = py->pure; if (akw[kw_pure].set) { pure = get_obj_bool(wk, akw[kw_pure].val); } const char *subdir = NULL; if (akw[kw_subdir].set) { subdir = get_cstr(wk, akw[kw_subdir].val); } return get_install_dir(wk, self, pure, subdir, res); } struct py_install_data_rename_ctx { obj rename; obj mode; obj dest; uint32_t i; uint32_t node; }; static enum iteration_result py_install_data_rename_iter(struct workspace *wk, void *_ctx, obj val) { struct py_install_data_rename_ctx *ctx = _ctx; obj src = *get_obj_file(wk, val); obj dest; obj rename; rename = obj_array_index(wk, ctx->rename, ctx->i); TSTR(d); path_join(wk, &d, get_cstr(wk, ctx->dest), get_cstr(wk, rename)); dest = tstr_into_str(wk, &d); push_install_target(wk, src, dest, ctx->mode); ++ctx->i; return ir_cont; } static bool func_python_installation_install_sources(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_file | tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_follow_symlinks, kw_install_dir, kw_install_mode, kw_install_tag, kw_rename, kw_sources, kw_preserve_path, kw_pure, kw_subdir, }; struct args_kw akw[] = { [kw_follow_symlinks] = { "follow_symlinks", obj_bool }, // TODO [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_rename] = { "rename", TYPE_TAG_LISTIFY | obj_string }, [kw_sources] = { "sources", TYPE_TAG_LISTIFY | tc_file | tc_string }, [kw_preserve_path] = { "preserve_path", obj_bool }, [kw_pure] = { "pure", obj_bool }, [kw_subdir] = { "subdir", obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (akw[kw_rename].set && akw[kw_preserve_path].set) { vm_error(wk, "rename keyword conflicts with preserve_path"); return false; } struct obj_python_installation *py = get_obj_python_installation(wk, self); bool pure = py->pure; if (akw[kw_pure].set) { pure = get_obj_bool(wk, akw[kw_pure].val); } const char *subdir = NULL; if (akw[kw_subdir].set) { subdir = get_cstr(wk, akw[kw_subdir].val); } obj install_dir; if (akw[kw_install_dir].set) { install_dir = akw[kw_install_dir].val; } else { get_install_dir(wk, self, pure, subdir, &install_dir); } obj sources = an[0].val; uint32_t err_node = an[0].node; if (akw[kw_sources].set) { obj_array_extend_nodup(wk, sources, akw[kw_sources].val); err_node = akw[kw_sources].node; } if (akw[kw_rename].set) { if (get_obj_array(wk, akw[kw_rename].val)->len != get_obj_array(wk, sources)->len) { vm_error(wk, "number of elements in rename != number of sources"); return false; } struct py_install_data_rename_ctx ctx = { .node = err_node, .mode = akw[kw_install_mode].val, .rename = akw[kw_rename].val, .dest = install_dir, }; obj coerced; if (!coerce_files(wk, err_node, sources, &coerced)) { return false; } return obj_array_foreach(wk, coerced, &ctx, py_install_data_rename_iter); } bool preserve_path = akw[kw_preserve_path].set && get_obj_bool(wk, akw[kw_preserve_path].val); return push_install_targets(wk, err_node, sources, install_dir, akw[kw_install_mode].val, preserve_path); } static bool func_python_installation_interpreter_path(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct obj_python_installation *py = get_obj_python_installation(wk, self); struct obj_external_program *ep = get_obj_external_program(wk, py->prog); if (get_obj_array(wk, ep->cmd_array)->len > 1) { vm_error(wk, "cannot return the full_path() of an external program with multiple elements (have: %o)\n", ep->cmd_array); return false; } *res = obj_array_index(wk, get_obj_external_program(wk, self)->cmd_array, 0); return true; } static bool func_python_installation_dependency(struct workspace *wk, obj self, obj *res) { struct arr kwargs; func_kwargs_lookup(wk, 0, "dependency", &kwargs); kwargs_arr_push(wk, &kwargs, &(struct args_kw){ "embed", obj_bool }); if (!pop_args(wk, 0, (struct args_kw *)kwargs.e)) { kwargs_arr_destroy(wk, &kwargs); return false; } kwargs_arr_destroy(wk, &kwargs); vm_error(wk, "unimplemented"); return false; } static bool func_python_installation_extension_module(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { TYPE_TAG_GLOB | tc_coercible_files | tc_generated_list }, ARG_TYPE_NULL, }; struct arr kwargs; func_kwargs_lookup(wk, 0, "shared_module", &kwargs); kwargs_arr_del(wk, &kwargs, "name_suffix"); kwargs_arr_del(wk, &kwargs, "name_prefix"); kwargs_arr_push(wk, &kwargs, &(struct args_kw){ "subdir", obj_string }); kwargs_arr_push(wk, &kwargs, &(struct args_kw){ "limited_api", obj_string }); if (!pop_args(wk, an, (struct args_kw *)kwargs.e)) { kwargs_arr_destroy(wk, &kwargs); return false; } kwargs_arr_destroy(wk, &kwargs); vm_error(wk, "unimplemented"); return false; } static obj python_self_transform(struct workspace *wk, obj self) { return get_obj_python_installation(wk, self)->prog; } void python_build_impl_tbl(void) { uint32_t i; for (i = 0; impl_tbl_external_program[i].name; ++i) { struct func_impl tmp = impl_tbl_external_program[i]; tmp.self_transform = python_self_transform; impl_tbl_python_installation[i] = tmp; } } const struct func_impl impl_tbl_module_python[] = { { "find_installation", func_module_python_find_installation, tc_python_installation }, { NULL, NULL }, }; const struct func_impl impl_tbl_module_python3[] = { { "find_python", func_module_python3_find_python, tc_external_program }, { NULL, NULL }, }; struct func_impl impl_tbl_python_installation[] = { [ARRAY_LEN(impl_tbl_external_program) - 1] = { "get_path", func_python_installation_get_path, tc_string }, { "get_install_dir", func_python_installation_get_install_dir, tc_string }, { "get_variable", func_python_installation_get_var, tc_string }, { "has_path", func_python_installation_has_path, tc_bool }, { "has_variable", func_python_installation_has_var, tc_bool }, { "install_sources", func_python_installation_install_sources }, { "language_version", func_python_installation_language_version, tc_string }, { "path", func_python_installation_interpreter_path, tc_string }, { "dependency", func_python_installation_dependency, tc_dependency }, { "extension_module", func_python_installation_extension_module, tc_build_target }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/toolchain.c0000644000175000017500000000624315041716357020251 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "buf_size.h" #include "functions/modules/toolchain.h" #include "lang/typecheck.h" static bool func_module_toolchain_create(struct workspace *wk, obj self, obj *res) { enum kwargs { kw_inherit, kw_inherit_compiler, kw_inherit_linker, kw_inherit_static_linker, }; struct args_kw akw[] = { [kw_inherit] = { "inherit", tc_compiler, .desc = "A toolchain to inherit from" }, [kw_inherit_compiler] = { "inherit_compiler", tc_string | tc_compiler, .desc = "The compiler component to inherit from. Can be either a compiler object or compiler type name." }, [kw_inherit_linker] = { "inherit_linker", tc_string | tc_compiler, .desc = "The linker component to inherit from. Can be either a compiler object or linker type name." }, [kw_inherit_static_linker] = { "inherit_static_linker", tc_string | tc_compiler, .desc = "The static linker component to inherit from. Can be either a compiler object or static linker type name." }, 0, }; if (!pop_args(wk, NULL, akw)) { return false; } *res = make_obj(wk, obj_compiler); struct obj_compiler *c = get_obj_compiler(wk, *res); c->ver = make_str(wk, "unknown"); c->libdirs = make_obj(wk, obj_array); { const struct { const char *name; uint32_t kw; bool (*lookup_name)(const char *, uint32_t *); } toolchain_elem[] = { { "compiler", kw_inherit_compiler, compiler_type_from_s }, { "linker", kw_inherit_linker, linker_type_from_s }, { "static_linker", kw_inherit_static_linker, static_linker_type_from_s }, }; uint32_t i; for (i = 0; i < ARRAY_LEN(toolchain_elem); ++i) { if (!akw[toolchain_elem[i].kw].set) { if (akw[kw_inherit].set) { akw[toolchain_elem[i].kw].val = akw[kw_inherit].val; akw[toolchain_elem[i].kw].node = akw[kw_inherit].node; } else { continue; } } uint32_t type; obj override = 0; obj cmd_array = 0; if (get_obj_type(wk, akw[toolchain_elem[i].kw].val) == obj_string) { uint32_t compiler_type; if (!toolchain_elem[i].lookup_name( get_cstr(wk, akw[toolchain_elem[i].kw].val), &compiler_type)) { vm_error_at(wk, akw[toolchain_elem[i].kw].node, "unknown %s type: %o", toolchain_elem[i].name, akw[toolchain_elem[i].kw].val); return false; } type = compiler_type; } else { const struct obj_compiler *base = get_obj_compiler(wk, akw[toolchain_elem[i].kw].val); type = base->type[i]; override = base->overrides[i]; cmd_array = base->cmd_arr[i]; } c->type[i] = type; c->overrides[i] = override; c->cmd_arr[i] = cmd_array; } } return true; } const struct func_impl impl_tbl_module_toolchain[] = { { "create", func_module_toolchain_create, tc_compiler, .desc = "Creates a new compiler object that can be passed in to the `toolchain` keyword on `add_languages` or inherited from when coreating a new toolchain. The toolchain object is reffered to as a `compiler` for historical reasons, although it also contains other information required to compile programs such as linker metadata." }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/json.c0000644000175000017500000000317515041716357017243 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "formats/json.h" #include "functions/modules/json.h" #include "lang/typecheck.h" #include "log.h" static bool func_module_json_parse(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string, .desc = "the json to parse" }, ARG_TYPE_NULL, }; enum kwargs { kw_check, }; struct args_kw akw[] = { [kw_check] = { "check", obj_bool }, 0 }; if (!pop_args(wk, an, akw)) { return false; } bool check = get_obj_bool_with_default(wk, akw[kw_check].val, true); obj r; bool ok = muon_json_to_obj(wk, get_str(wk, an[0].val), &r); if (check) { if (!ok) { vm_error(wk, "failed to parse json: %s", get_str(wk, r)->s); return false; } *res = r; } else { *res = make_obj(wk, obj_dict); obj_dict_set(wk, *res, make_str(wk, "ok"), make_obj_bool(wk, ok)); obj_dict_set(wk, *res, make_str(wk, "result"), r); } return true; } static bool func_module_json_stringify(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_array | tc_dict, .desc = "the object to stringify" }, ARG_TYPE_NULL, }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(buf); if (!obj_to_json(wk, an[0].val, &buf)) { return false; } *res = tstr_into_str(wk, &buf); return true; } const struct func_impl impl_tbl_module_json[] = { { "parse", func_module_json_parse, tc_array | tc_dict, .desc = "Parse a json string into an object" }, { "stringify", func_module_json_stringify, tc_any, .desc = "Convert an object into a json string" }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/fs.c0000644000175000017500000006170215041716357016702 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Eli Schwartz * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "error.h" #include "formats/editorconfig.h" #include "functions/both_libs.h" #include "functions/kernel/custom_target.h" #include "functions/modules/fs.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/os.h" #include "platform/path.h" #include "sha_256.h" enum fix_file_path_opts { fix_file_path_noexpanduser = 1 << 0, fix_file_path_noabs = 1 << 1, }; static const char * fs_coerce_file_path(struct workspace *wk, uint32_t node, obj o, bool abs_build_target) { enum obj_type t = get_obj_type(wk, o); const struct str *ss; switch (t) { case obj_string: ss = get_str(wk, o); break; case obj_file: ss = get_str(wk, *get_obj_file(wk, o)); break; case obj_both_libs: o = decay_both_libs(wk, o); /* fallthrough */ case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, o); const char *name = get_cstr(wk, tgt->build_name); if (!abs_build_target) { return name; } TSTR(joined); path_join(wk, &joined, get_cstr(wk, tgt->build_dir), name); return get_cstr(wk, tstr_into_str(wk, &joined)); } case obj_custom_target: { o = get_obj_custom_target(wk, o)->output; obj res; if (!obj_array_flatten_one(wk, o, &res)) { vm_error_at(wk, node, "couldn't get path for custom target with multiple outputs"); return false; } return get_file_path(wk, res); } default: UNREACHABLE; } if (str_has_null(ss)) { vm_error_at(wk, node, "path cannot contain null bytes"); return 0; } else if (!ss->len) { vm_error_at(wk, node, "path cannot be empty"); return 0; } return ss->s; } static bool fix_file_path(struct workspace *wk, uint32_t err_node, obj path, enum fix_file_path_opts opts, struct tstr *buf) { const char *s = fs_coerce_file_path(wk, err_node, path, false); if (!s) { return false; } if (path_is_absolute(s)) { path_copy(wk, buf, s); } else { if (!(opts & fix_file_path_noexpanduser) && s[0] == '~') { const char *home; if (!(home = fs_user_home())) { vm_error_at(wk, err_node, "failed to get user home directory"); return false; } if (s[1] == '/' && s[2]) { path_join(wk, buf, home, &s[2]); } else { path_copy(wk, buf, home); } } else if (opts & fix_file_path_noabs) { path_copy(wk, buf, s); } else { path_join(wk, buf, workspace_cwd(wk), s); } } _path_normalize(wk, buf, true); return true; } typedef bool((*fs_lookup_func)(const char *)); static bool func_module_fs_lookup_common(struct workspace *wk, obj *res, fs_lookup_func lookup, enum fix_file_path_opts opts, bool allow_file) { type_tag t = tc_string; if (allow_file) { t |= tc_file; } struct args_norm an[] = { { t }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, opts, &path)) { return false; } *res = make_obj_bool(wk, lookup(path.buf)); return true; } static bool func_module_fs_exists(struct workspace *wk, obj self, obj *res) { return func_module_fs_lookup_common(wk, res, fs_exists, 0, false); } static bool func_module_fs_is_file(struct workspace *wk, obj self, obj *res) { return func_module_fs_lookup_common(wk, res, fs_file_exists, 0, false); } static bool func_module_fs_is_dir(struct workspace *wk, obj self, obj *res) { return func_module_fs_lookup_common(wk, res, fs_dir_exists, 0, false); } static bool func_module_fs_is_symlink(struct workspace *wk, obj self, obj *res) { return func_module_fs_lookup_common(wk, res, fs_symlink_exists, 0, true); } static bool func_module_fs_parent(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_coercible_files }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_noabs, &path)) { return false; } TSTR(buf); path_dirname(wk, &buf, path.buf); *res = tstr_into_str(wk, &buf); return true; } static bool func_module_fs_read(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; enum { kw_encoding, }; struct args_kw akw[] = { [kw_encoding] = { "encoding", obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (akw[kw_encoding].set) { if (!str_eql(get_str(wk, akw[kw_encoding].val), &STR("utf-8"))) { vm_error_at(wk, akw[kw_encoding].node, "only 'utf-8' supported"); } } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path)) { return false; } struct source src = { 0 }; if (!fs_read_entire_file(path.buf, &src)) { return false; } *res = make_strn(wk, src.src, src.len); fs_source_destroy(&src); return true; } static bool func_module_fs_is_absolute(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } // TODO: Handle this *res = make_obj_bool(wk, path_is_absolute(get_cstr(wk, an[0].val))); return true; } static bool func_module_fs_expanduser(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path)) { return false; } *res = tstr_into_str(wk, &path); return true; } static bool func_module_fs_name(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_coercible_files }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_noexpanduser, &path)) { return false; } TSTR(basename); path_basename(wk, &basename, path.buf); *res = tstr_into_str(wk, &basename); return true; } static bool func_module_fs_stem(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_coercible_files }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_noexpanduser, &path)) { return false; } TSTR(basename); path_basename(wk, &basename, path.buf); char *dot; if ((dot = strrchr(basename.buf, '.'))) { *dot = 0; basename.len = strlen(basename.buf); } *res = tstr_into_str(wk, &basename); return true; } static bool func_module_fs_as_posix(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const char *path = get_cstr(wk, an[0].val), *p; TSTR(buf); for (p = path; *p; ++p) { if (*p == '\\') { tstr_push(wk, &buf, '/'); if (*(p + 1) == '\\') { ++p; } } else { tstr_push(wk, &buf, *p); } } *res = tstr_into_str(wk, &buf); return true; } static bool func_module_fs_replace_suffix(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_coercible_files }, { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_noabs, &path)) { return false; } char *base = strrchr(path.buf, '/'); char *dot; if ((dot = strrchr(path.buf, '.')) && dot > base) { *dot = 0; path.len = strlen(path.buf); } tstr_pushs(wk, &path, get_cstr(wk, an[1].val)); *res = tstr_into_str(wk, &path); return true; } static bool func_module_fs_hash(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } if (!str_eql(get_str(wk, an[1].val), &STR("sha256"))) { vm_error_at(wk, an[1].node, "only sha256 is supported"); return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path)) { return false; } struct source src = { 0 }; if (!fs_read_entire_file(path.buf, &src)) { return false; } uint8_t hash[32] = { 0 }; calc_sha_256(hash, src.src, src.len); // TODO: other hash algos char buf[65] = { 0 }; sha256_to_str(hash, buf); *res = make_str(wk, buf); fs_source_destroy(&src); return true; } static bool func_module_fs_size(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path)) { return false; } uint64_t size; FILE *f; if (!(f = fs_fopen(path.buf, "rb"))) { return false; } else if (!fs_fsize(f, &size)) { return false; } else if (!fs_fclose(f)) { return false; } assert(size < INT64_MAX); *res = make_obj(wk, obj_number); set_obj_number(wk, *res, size); return true; } static bool func_module_fs_is_samepath(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { tc_string | tc_file }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path1); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path1)) { return false; } TSTR(path2); if (!fix_file_path(wk, an[1].node, an[1].val, 0, &path2)) { return false; } // TODO: handle symlinks *res = make_obj_bool(wk, strcmp(path1.buf, path2.buf) == 0); return true; } static bool func_module_fs_copyfile(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { tc_string, .optional = true }, ARG_TYPE_NULL }; enum { kw_install, kw_install_dir, kw_install_tag, kw_install_mode, }; struct args_kw akw[] = { [kw_install] = { "install", obj_bool }, [kw_install_dir] = { "install_dir", TYPE_TAG_LISTIFY | tc_string | tc_bool }, [kw_install_tag] = { "install_tag", tc_string }, // TODO [kw_install_mode] = { "install_mode", tc_install_mode_kw }, 0, }; if (!pop_args(wk, an, akw)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path)) { return false; } obj output; if (an[1].set) { output = an[1].val; } else { TSTR(dest); path_basename(wk, &dest, path.buf); output = tstr_into_str(wk, &dest); } obj command; command = make_obj(wk, obj_array); push_args_null_terminated(wk, command, (char *const[]){ (char *)wk->argv0, "internal", "eval", "-e", "commands/copyfile.meson", "@INPUT@", "@OUTPUT@", NULL, }); struct make_custom_target_opts opts = { .name = make_str(wk, "copyfile"), .input_node = an[0].node, .output_node = an[1].node, .input_orig = an[0].val, .output_orig = output, .output_dir = get_cstr(wk, current_project(wk)->build_dir), .command_orig = command, }; if (!make_custom_target(wk, &opts, res)) { return false; } obj_array_push(wk, current_project(wk)->targets, *res); struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); if (!install_custom_target(wk, tgt, &akw[kw_install], 0, akw[kw_install_dir].val, 0)) { return false; } return true; } static bool func_module_fs_relative_to(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_coercible_files }, { tc_coercible_files }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const char *p1, *p2; if (!(p1 = fs_coerce_file_path(wk, an[0].node, an[0].val, true))) { return false; } else if (!(p2 = fs_coerce_file_path(wk, an[1].node, an[1].val, true))) { return false; } TSTR(b1); TSTR(b2); if (path_is_absolute(p1)) { path_copy(wk, &b1, p1); } else { path_join(wk, &b1, workspace_cwd(wk), p1); } if (path_is_absolute(p2)) { path_copy(wk, &b2, p2); } else { path_join(wk, &b2, workspace_cwd(wk), p2); } TSTR(path); path_relative_to(wk, &path, b2.buf, b1.buf); *res = tstr_into_str(wk, &path); return true; } const struct func_impl impl_tbl_module_fs[] = { { "as_posix", func_module_fs_as_posix, tc_string, true, .desc = "Convert backslashes to `/`" }, { "copyfile", func_module_fs_copyfile, tc_custom_target, .desc = "Creates a custom target that copies a file at build time" }, { "exists", func_module_fs_exists, tc_bool, .desc = "Check if a file or directory exists" }, { "expanduser", func_module_fs_expanduser, tc_string, .desc = "Expand a path that starts with a ~ into an absolute path" }, { "hash", func_module_fs_hash, tc_string, .desc = "Calculate the content hash of a file" }, { "is_absolute", func_module_fs_is_absolute, tc_bool, true, .desc = "Check if a path is absolute" }, { "is_dir", func_module_fs_is_dir, tc_bool, .desc = "Check if a directory exists" }, { "is_file", func_module_fs_is_file, tc_bool, .desc = "Check if a file exists" }, { "is_samepath", func_module_fs_is_samepath, tc_bool, .desc = "Check if two paths point to the same object" }, { "is_symlink", func_module_fs_is_symlink, tc_bool, .desc = "Check if a symlink exists" }, { "name", func_module_fs_name, tc_string, true, .desc = "Get the basename of a path" }, { "parent", func_module_fs_parent, tc_string, true, .desc = "Get the dirname of a path" }, { "read", func_module_fs_read, tc_string, .desc = "Read a file from disk" }, { "relative_to", func_module_fs_relative_to, tc_string, true, .desc = "Get a path relative to another path" }, { "replace_suffix", func_module_fs_replace_suffix, tc_string, true, .desc = "Replace a path's suffix" }, { "size", func_module_fs_size, tc_number, .desc = "Get a file's size in bytes" }, { "stem", func_module_fs_stem, tc_string, true, .desc = "Get the basename of a path with its extension removed" }, { NULL, NULL }, }; static bool func_module_fs_write(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path)) { return false; } const struct str *ss = get_str(wk, an[1].val); if (!fs_write(path.buf, (uint8_t *)ss->s, ss->len)) { return false; } return true; } static bool func_module_fs_copy(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_force, }; struct args_kw akw[] = { [kw_force] = { "force", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } bool force = akw[kw_force].set ? get_obj_bool(wk, akw[kw_force].val) : false; TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path)) { return false; } if (!fs_copy_file(path.buf, get_cstr(wk, an[1].val), force)) { return false; } return true; } static bool func_module_fs_cwd(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } TSTR(cwd); path_copy_cwd(wk, &cwd); *res = tstr_into_str(wk, &cwd); return true; } static bool func_module_fs_make_absolute(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); path_make_absolute(wk, &path, get_cstr(wk, an[0].val)); *res = tstr_into_str(wk, &path); return true; } static bool func_module_fs_mkdir(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_make_parents }; struct args_kw akw[] = { [kw_make_parents] = { "make_parents", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (akw[kw_make_parents].set && get_obj_bool(wk, akw[kw_make_parents].val)) { return fs_mkdir_p(get_cstr(wk, an[0].val)); } else { return fs_mkdir(get_cstr(wk, an[0].val), true); } } static bool func_module_fs_rmdir(struct workspace *wk, obj rcvr, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_recursive, kw_force, }; struct args_kw akw[] = { [kw_recursive] = { "recursive", obj_bool }, [kw_force] = { "force", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } bool recursive = akw[kw_recursive].set ? get_obj_bool(wk, akw[kw_recursive].val) : false; bool force = akw[kw_force].set ? get_obj_bool(wk, akw[kw_force].val) : false; if (recursive) { return fs_rmdir_recursive(get_cstr(wk, an[0].val), force); } else { return fs_rmdir(get_cstr(wk, an[0].val), force); } } static bool func_module_fs_is_basename(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj_bool(wk, path_is_basename(get_cstr(wk, an[0].val))); return true; } static bool func_module_fs_without_ext(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); path_without_ext(wk, &path, get_cstr(wk, an[0].val)); *res = tstr_into_str(wk, &path); return true; } static bool func_module_fs_is_subpath(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj_bool(wk, path_is_subpath(get_cstr(wk, an[0].val), get_cstr(wk, an[1].val))); return true; } static bool func_module_fs_add_suffix(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); path_copy(wk, &path, get_cstr(wk, an[0].val)); tstr_pushs(wk, &path, get_cstr(wk, an[1].val)); *res = tstr_into_str(wk, &path); return true; } static bool func_module_fs_executable(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); path_executable(wk, &path, get_cstr(wk, an[0].val)); *res = tstr_into_str(wk, &path); return true; } struct func_module_fs_glob_ctx { struct workspace *wk; const char *pat, *prefix, *matchprefix; enum { fs_glob_mode_normal, fs_glob_mode_recurse, } mode; obj res; }; static enum iteration_result func_module_fs_glob_cb(void *_ctx, const char *name) { const struct func_module_fs_glob_ctx *ctx = _ctx; struct workspace *wk = ctx->wk; struct func_module_fs_glob_ctx subctx = *ctx; if (ctx->mode == fs_glob_mode_recurse) { goto recurse_all_mode; } struct str pat = { .s = ctx->pat }; { uint32_t i; for (i = 0; pat.s[i] && pat.s[i] != '/'; ++i) { ++pat.len; } } /* L("path: %s, pat: %.*s", name, pat.len, pat.s); */ if (str_eql(&pat, &STR("**"))) { subctx.mode = fs_glob_mode_recurse; subctx.matchprefix = "/"; goto recurse_all_mode; } if (str_eql_glob(&pat, &STRL(name))) { TSTR(path); path_join(wk, &path, ctx->prefix, name); subctx.prefix = path.buf; subctx.pat += pat.len; if (!*subctx.pat) { obj_array_push(wk, ctx->res, tstr_into_str(wk, &path)); } else { ++subctx.pat; if (fs_dir_exists(path.buf)) { if (!fs_dir_foreach(path.buf, &subctx, func_module_fs_glob_cb)) { return ir_err; } } } } return ir_cont; recurse_all_mode: { TSTR(path); path_join(wk, &path, subctx.matchprefix, name); TSTR(full_path); path_join(wk, &full_path, subctx.prefix, name); subctx.prefix = full_path.buf; subctx.matchprefix = path.buf; /* L("recurse: path: %s, pat: %s", path.buf, ctx->pat); */ if (editorconfig_pattern_match(ctx->pat, path.buf)) { obj_array_push(wk, ctx->res, tstr_into_str(wk, &full_path)); } if (fs_dir_exists(full_path.buf)) { if (!fs_dir_foreach(full_path.buf, &subctx, func_module_fs_glob_cb)) { return ir_err; } } return ir_cont; } } static bool func_module_fs_glob(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(pat); { const struct str *_pat = get_str(wk, an[0].val); if (str_has_null(_pat)) { vm_error(wk, "null byte not allowed in pattern"); return false; } path_join(wk, &pat, workspace_cwd(wk), _pat->s); } TSTR(prefix); { uint32_t i, prefix_len = 0; for (i = 0; pat.buf[i]; ++i) { if (pat.buf[i] == '\\') { ++i; continue; } if (pat.buf[i] == '*') { break; } else if (pat.buf[i] == '/') { prefix_len = i; } } tstr_pushn(wk, &prefix, pat.buf, prefix_len); } if (prefix.len) { pat.buf += prefix.len + 1; pat.len -= prefix.len + 1; } else { path_copy(wk, &prefix, "."); } /* L("prefix: %.*s, pat: %s", prefix.len, prefix.buf, pat.buf); */ *res = make_obj(wk, obj_array); if (!fs_dir_exists(prefix.buf)) { vm_error(wk, "Path \"%s\" does not exist", prefix.buf); return false; } struct func_module_fs_glob_ctx ctx = { .wk = wk, .pat = pat.buf, .prefix = prefix.buf, .res = *res, }; return fs_dir_foreach(prefix.buf, &ctx, func_module_fs_glob_cb); } struct delete_suffixes_ctx { const char *base_dir; const char *suffix; }; static enum iteration_result delete_suffix_recursive(void *_ctx, const char *path) { enum iteration_result ir_res = ir_err; struct delete_suffixes_ctx *ctx = _ctx; struct stat sb; TSTR(name); path_join(NULL, &name, ctx->base_dir, path); // Check for existence first, before doing a stat. It's possible that // another process has deleted this file in the time since the iteration // machinery found this file. In this case, the file no longer exists, so // just continue. if (!fs_exists(name.buf)) { tstr_destroy(&name); return ir_cont; } if (!fs_stat(name.buf, &sb)) { goto ret; } if (S_ISDIR(sb.st_mode)) { struct delete_suffixes_ctx new_ctx = *ctx; new_ctx.base_dir = name.buf; if (!fs_dir_foreach(new_ctx.base_dir, &new_ctx, delete_suffix_recursive)) { goto ret; } } else if (S_ISREG(sb.st_mode)) { if (fs_has_extension(name.buf, ctx->suffix) && !fs_remove(name.buf)) { goto ret; } } else { LOG_E("unhandled file type: %s", name.buf); goto ret; } ir_res = ir_cont; ret: tstr_destroy(&name); return ir_res; } static bool func_module_fs_delete_with_suffix(struct workspace *wk, obj rcrv, obj *res) { struct args_norm an[] = { { tc_string }, { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const char *base_dir = get_cstr(wk, an[0].val); struct delete_suffixes_ctx ctx = { .base_dir = base_dir, .suffix = get_cstr(wk, an[1].val), }; return fs_dir_foreach(base_dir, &ctx, delete_suffix_recursive); } static bool func_module_fs_canonicalize(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_noexpanduser, &path)) { return false; } *res = tstr_into_str(wk, &path); return true; } const struct func_impl impl_tbl_module_fs_internal[] = { { "as_posix", func_module_fs_as_posix, tc_string, true }, { "exists", func_module_fs_exists, tc_bool }, { "expanduser", func_module_fs_expanduser, tc_string }, { "hash", func_module_fs_hash, tc_string }, { "is_absolute", func_module_fs_is_absolute, tc_bool, true }, { "is_dir", func_module_fs_is_dir, tc_bool }, { "is_file", func_module_fs_is_file, tc_bool }, { "is_samepath", func_module_fs_is_samepath, tc_bool }, { "is_symlink", func_module_fs_is_symlink, tc_bool }, { "name", func_module_fs_name, tc_string, true }, { "parent", func_module_fs_parent, tc_string, true }, { "read", func_module_fs_read, tc_string }, { "relative_to", func_module_fs_relative_to, tc_string, true }, { "replace_suffix", func_module_fs_replace_suffix, tc_string, true }, { "size", func_module_fs_size, tc_number }, { "stem", func_module_fs_stem, tc_string, true }, // non-standard muon extensions { "add_suffix", func_module_fs_add_suffix, tc_string, true, .desc = "Append a suffix to a path" }, { "copy", func_module_fs_copy, .flags = func_impl_flag_sandbox_disable, .desc = "Copy a file to a destination at configure time" }, { "cwd", func_module_fs_cwd, tc_string, .desc = "Return the muon's current working directory" }, { "executable", func_module_fs_executable, tc_string, true, .desc = "Prepend ./ to a path if it is a basename" }, { "glob", func_module_fs_glob, tc_array, .desc = "Gather a list of files matching a glob expression" }, { "is_basename", func_module_fs_is_basename, tc_bool, true, .desc = "Check if a path is a basename" }, { "is_subpath", func_module_fs_is_subpath, tc_bool, true, .desc = "Check if a path is a subpath of another path" }, { "make_absolute", func_module_fs_make_absolute, tc_string, .desc = "Make a path absolute by prepending the current working directory" }, { "mkdir", func_module_fs_mkdir, .flags = func_impl_flag_sandbox_disable, .desc = "Create a directory" }, { "rmdir", func_module_fs_rmdir, .flags = func_impl_flag_sandbox_disable, .desc = "Remove a directory" }, { "without_ext", func_module_fs_without_ext, tc_string, true, .desc = "Get a path with its extension removed" }, { "write", func_module_fs_write, .flags = func_impl_flag_sandbox_disable, .desc = "Write a file" }, { "delete_with_suffix", func_module_fs_delete_with_suffix, .desc = "Recursively delete all files with a matching suffix" }, { "canonicalize", func_module_fs_canonicalize, tc_string, true, .desc = "Make a path absolute and replace .." }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/getopt.c0000644000175000017500000001346515041716357017577 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "args.h" #include "functions/modules/getopt.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "platform/assert.h" #include "platform/mem.h" #include "platform/os.h" #include "platform/run_cmd.h" struct getopt_handler { bool required; bool seen; obj action; const struct str *desc; }; static bool getopt_handler_requires_optarg(struct workspace *wk, const struct getopt_handler *handler) { const struct obj_capture *capture = get_obj_capture(wk, handler->action); return capture->func->nargs == 1; } static void func_module_getopt_usage(struct workspace *wk, const char *argv0, obj handlers, int exitcode) { obj handler; if (obj_dict_index_strn(wk, handlers, "h", 1, &handler)) { obj capture_res; obj action = 0; if (!vm_eval_capture(wk, action, 0, 0, &capture_res)) { exitcode = 1; } } else { printf("usage: %s [options]\n", argv0); printf("options:\n"); obj k, v; obj_dict_for(wk, handlers, k, v) { struct getopt_handler handler = { 0 }; vm_obj_to_struct(wk, getopt_handler, v, &handler); printf(" -%s%s - %s%s\n", get_cstr(wk, k), getopt_handler_requires_optarg(wk, &handler) ? " " : "", handler.desc->s, handler.required ? " (required)" : ""); } printf(" -h - show this message\n"); } exit(exitcode); } static bool func_module_getopt_getopt(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_LISTIFY | tc_string, .desc = "the array of arguments to parse" }, { make_complex_type(wk, complex_type_nested, tc_dict, tc_dict), .desc = "A dict of `opt` -> `handler`.\n\n" "- `opt` must be a single character.\n" "- `handler` is a dict that may contain the following keys:\n" "\n" " - `required` - defaults to false, causes this option to be required\n" " - `action` - required, a function that will be called to handle this option\n" "\n" " If the function accepts a single argument then the option will be required to supply a value\n" " - `desc` - required, a string to show in the help message.\n" }, ARG_TYPE_NULL, }; if (!pop_args(wk, an, NULL)) { return false; } if (vm_struct(wk, getopt_handler)) { vm_struct_member(wk, getopt_handler, required, vm_struct_type_bool); vm_struct_member(wk, getopt_handler, seen, vm_struct_type_bool); vm_struct_member(wk, getopt_handler, action, vm_struct_type_obj); vm_struct_member(wk, getopt_handler, desc, vm_struct_type_str); } bool ret = false; obj handlers = an[1].val; char optstring[256] = { 0 }; { const uint32_t optstring_max = sizeof(optstring) - 3; uint32_t optstring_i = 0; obj k, v; obj_dict_for(wk, handlers, k, v) { if (optstring_i >= optstring_max) { vm_error(wk, "too many options"); return false; } const struct str *s = get_str(wk, k); if (s->len != 1) { vm_error(wk, "option %o invalid, must be a single character", k); return false; } struct getopt_handler handler = { 0 }; if (!vm_obj_to_struct(wk, getopt_handler, v, &handler)) { LOG_E("option %s has an invalid handler", get_cstr(wk, k)); return false; } if (!typecheck_custom(wk, 0, handler.action, tc_capture, 0)) { vm_error(wk, "action for %o is not a function", k); return false; } struct obj_capture *capture = get_obj_capture(wk, handler.action); if (capture->func->nkwargs) { vm_error(wk, "handler for %o must not accept kwargs", k); return false; } else if (capture->func->nargs > 1) { vm_error(wk, "handler for %o can only accept at most 1 posarg", k); return false; } optstring[optstring_i] = *s->s; ++optstring_i; if (getopt_handler_requires_optarg(wk, &handler)) { optstring[optstring_i] = ':'; ++optstring_i; } } if (!strchr(optstring, 'h')) { optstring[optstring_i] = 'h'; } } char *const *argv; uint32_t argc; { const char *joined; join_args_argstr(wk, &joined, &argc, an[0].val); argstr_to_argv(joined, argc, 0, &argv); } signed char opt; opterr = 1; optind = 1; optarg = 0; while ((opt = os_getopt(argc, argv, optstring)) != -1) { struct getopt_handler handler = { 0 }; { obj v; char opt_as_str[] = { opt, 0 }; if (!obj_dict_index_strn(wk, handlers, opt_as_str, 1, &v)) { if (opt == '?' || opt == 'h') { func_module_getopt_usage(wk, argv[0], handlers, opt == '?' ? 1 : 0); } vm_error(wk, "no handler defined for -%s", opt_as_str); goto ret; } vm_obj_to_struct(wk, getopt_handler, v, &handler); if (handler.required) { obj_dict_set(wk, v, make_str(wk, "seen"), obj_bool_true); } } { obj capture_res; struct args_norm capture_an[] = { { ARG_TYPE_NULL }, ARG_TYPE_NULL }; if (optarg) { capture_an[0].node = 0; capture_an[0].type = tc_string; capture_an[0].val = make_str(wk, optarg); } if (!vm_eval_capture(wk, handler.action, capture_an, 0, &capture_res)) { goto ret; } } optarg = 0; } *res = make_obj(wk, obj_array); for (uint32_t i = optind; i < argc; ++i) { obj_array_push(wk, *res, make_str(wk, argv[i])); } { obj k, v; obj_dict_for(wk, handlers, k, v) { struct getopt_handler handler = { 0 }; vm_obj_to_struct(wk, getopt_handler, v, &handler); if (handler.required && !handler.seen) { obj_lprintf(wk, log_info, "missing required option %o\n", k); func_module_getopt_usage(wk, argv[0], handlers, 1); } } } ret = true; ret: z_free((void *)argv); return ret; } const struct func_impl impl_tbl_module_getopt[] = { { "getopt", func_module_getopt_getopt, tc_array, .desc = "Parse command line arguments using getopt. Returns the array of trailing positional args." }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules/subprojects.c0000644000175000017500000003650515041716357020640 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "coerce.h" #include "functions/modules/subprojects.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "platform/path.h" #include "platform/timer.h" #include "tracy.h" #include "util.h" #include "wrap.h" struct subprojects_foreach_ctx { subprojects_foreach_cb cb; struct subprojects_common_ctx *usr_ctx; struct workspace *wk; const char *subprojects_dir; }; static const char * subprojects_dir(struct workspace *wk) { TSTR(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->source_root), get_cstr(wk, current_project(wk)->subprojects_dir)); return get_str(wk, tstr_into_str(wk, &path))->s; } static enum iteration_result subprojects_foreach_iter(void *_ctx, const char *name) { struct subprojects_foreach_ctx *ctx = _ctx; uint32_t len = strlen(name); TSTR(path); if (len <= 5 || strcmp(&name[len - 5], ".wrap") != 0) { return ir_cont; } path_join(ctx->wk, &path, subprojects_dir(ctx->wk), name); if (!fs_file_exists(path.buf)) { return ir_cont; } return ctx->cb(ctx->wk, ctx->usr_ctx, path.buf); } bool subprojects_foreach(struct workspace *wk, obj list, struct subprojects_common_ctx *usr_ctx, subprojects_foreach_cb cb) { if (list) { bool res = true; TSTR(wrap_file); obj v; obj_array_for(wk, list, v) { const char *name = get_cstr(wk, v); path_join(wk, &wrap_file, subprojects_dir(wk), name); tstr_pushs(wk, &wrap_file, ".wrap"); if (!fs_file_exists(wrap_file.buf)) { LOG_E("wrap file for '%s' not found", name); res = false; break; } if (cb(wk, usr_ctx, wrap_file.buf) == ir_err) { res = false; break; } } return res; } else if (fs_dir_exists(subprojects_dir(wk))) { struct subprojects_foreach_ctx ctx = { .cb = cb, .usr_ctx = usr_ctx, .wk = wk, }; return fs_dir_foreach(subprojects_dir(wk), &ctx, subprojects_foreach_iter); } return true; } static enum iteration_result subprojects_gather_iter(struct workspace *wk, struct subprojects_common_ctx *ctx, const char *path) { struct wrap_handle_ctx wrap_ctx = { .path = get_str(wk, make_str(wk, path))->s, }; arr_push(&ctx->handlers, &wrap_ctx); return ir_cont; } struct subprojects_process_opts { uint32_t job_count; enum wrap_handle_mode wrap_mode; const char *subprojects_dir; obj *res; bool single_file; bool progress_bar; bool fail_if_update_skipped; }; struct subprojects_process_progress_decorate_ctx { uint32_t prev_list_len; struct subprojects_common_ctx *ctx; }; struct subprojects_process_progress_decorate_elem { struct wrap_handle_ctx *wrap_ctx; float dur; }; static int32_t subprojects_process_progress_decorate_sort_compare(const void *_a, const void *_b) { const struct subprojects_process_progress_decorate_elem *a = _a, *b = _b; return a->dur > b->dur ? -1 : 1; } static void subprojects_process_progress_decorate(void *_ctx, uint32_t width) { struct subprojects_process_progress_decorate_ctx *decorate_ctx = _ctx; struct subprojects_common_ctx *ctx = decorate_ctx->ctx; float elapsed = timer_read(&ctx->duration); if (elapsed < 1) { log_raw("\r"); return; } log_raw("\n"); struct wrap_handle_ctx *wrap_ctx; struct subprojects_process_progress_decorate_elem list[32] = { 0 }; uint32_t list_len = 0; uint32_t i; for (i = 0; i < ctx->handlers.len; ++i) { wrap_ctx = arr_get(&ctx->handlers, i); if (wrap_ctx->sub_state == wrap_handle_sub_state_pending) { continue; } else if (wrap_ctx->sub_state == wrap_handle_sub_state_collected) { continue; } list[list_len].wrap_ctx = wrap_ctx; list[list_len].dur = timer_read(&wrap_ctx->duration); ++list_len; if (list_len >= ARRAY_LEN(list)) { break; } } qsort(list, list_len, sizeof(struct subprojects_process_progress_decorate_elem), subprojects_process_progress_decorate_sort_compare); for (i = 0; i < list_len; ++i) { char buf[512] = { 0 }; uint32_t buf_i = 0; snprintf_append(buf, &buf_i, "%6.2fs " CLR(c_magenta, c_bold) "%-20.20s" CLR(0) " " CLR(c_blue) "%-9s" CLR(0), list[i].dur, list[i].wrap_ctx->wrap.name.buf, wrap_handle_state_to_s(list[i].wrap_ctx->prev_state)); switch (list[i].wrap_ctx->sub_state) { case wrap_handle_sub_state_fetching: { snprintf_append(buf, &buf_i, "%s", " fetching"); const int64_t total = list[i].wrap_ctx->fetch_ctx.total, dl = list[i].wrap_ctx->fetch_ctx.downloaded; if (total && dl && dl <= total) { snprintf_append(buf, &buf_i, " %3.0f%%", 100.0 * (double)dl / (double)total); } break; } case wrap_handle_sub_state_extracting: { snprintf_append(buf, &buf_i, "%s", " extracting"); break; } case wrap_handle_sub_state_running_cmd: { struct tstr *out = &list[i].wrap_ctx->cmd_ctx.err; int32_t j, end; if (out->len) { j = out->len - 1; if (out->buf[j] == '\r' || out->buf[j] == '\n') { j--; } end = j; for (; j >= 1; --j) { if (out->buf[j] == '\r' || out->buf[j] == '\n') { ++j; break; } } int32_t len = end - j; snprintf_append(buf, &buf_i, " %.*s", len, out->buf + j); } break; } default: break; } for (uint32_t j = 0, w = 0; j < buf_i; ++j) { if (buf[j] == '\033') { while (j < buf_i && buf[j] != 'm') { ++j; } continue; } ++w; if (w >= width) { buf[j] = 0; break; } } log_raw("%s\033[K\n", buf); } for (; i < decorate_ctx->prev_list_len; ++i) { log_raw("\033[K\n"); } log_raw("\033[%dA", MAX(list_len, decorate_ctx->prev_list_len) + 1); decorate_ctx->prev_list_len = list_len; } static obj wrap_to_obj(struct workspace *wk, struct wrap *wrap) { char *t = "file"; if (wrap->type == wrap_type_git) { t = "git "; } obj d = make_obj(wk, obj_dict); obj_dict_set(wk, d, make_str(wk, "name"), make_str(wk, wrap->name.buf)); obj_dict_set(wk, d, make_str(wk, "type"), make_str(wk, t)); obj_dict_set(wk, d, make_str(wk, "path"), tstr_into_str(wk, &wrap->dest_dir)); return d; } static bool subprojects_process(struct workspace *wk, obj list, struct subprojects_process_opts *opts) { // Init ctx struct subprojects_common_ctx ctx = { .res = opts->res }; arr_init(&ctx.handlers, 8, sizeof(struct wrap_handle_ctx)); *ctx.res = make_obj(wk, obj_array); // Gather subprojects if (opts->single_file) { struct wrap_handle_ctx wrap_ctx = { .path = get_str(wk, list)->s, }; arr_push(&ctx.handlers, &wrap_ctx); } else { subprojects_foreach(wk, list, &ctx, subprojects_gather_iter); } // Progress bar setup log_progress_push_state(wk); if (opts->progress_bar) { log_progress_enable(); log_progress_push_level(0, ctx.handlers.len); } struct subprojects_process_progress_decorate_ctx decorate_ctx = { .ctx = &ctx }; struct log_progress_style log_progress_style = { .show_count = true, .decorate = subprojects_process_progress_decorate, .usr_ctx = &decorate_ctx, .dont_disable_on_error = true, }; log_progress_set_style(&log_progress_style); uint32_t i; uint32_t cnt_complete = 0, cnt_failed = 0, cnt_running; struct wrap_handle_ctx *wrap_ctx; for (i = 0; i < ctx.handlers.len; ++i) { wrap_ctx = arr_get(&ctx.handlers, i); wrap_ctx->opts = (struct wrap_opts){ .mode = opts->wrap_mode, .allow_download = true, .subprojects = opts->subprojects_dir ? opts->subprojects_dir : subprojects_dir(wk), .fail_if_update_skipped = opts->fail_if_update_skipped, }; } wrap_handle_async_start(wk); timer_start(&ctx.duration); while (cnt_complete < ctx.handlers.len) { float loop_start = timer_read(&ctx.duration); cnt_running = 0; for (i = 0; i < ctx.handlers.len; ++i) { wrap_ctx = arr_get(&ctx.handlers, i); if (wrap_ctx->sub_state == wrap_handle_sub_state_complete) { ++cnt_complete; if (opts->wrap_mode == wrap_handle_mode_update && wrap_ctx->wrap.updated) { LOG_I(CLR(c_green) "[%3d/%3d] updated" CLR(0) " %s", cnt_complete, ctx.handlers.len, wrap_ctx->wrap.name.buf); } wrap_ctx->sub_state = wrap_handle_sub_state_collected; } if (wrap_ctx->sub_state == wrap_handle_sub_state_collected) { continue; } wrap_ctx->ok = true; ++cnt_running; if (cnt_running > opts->job_count) { break; } TracyCZoneN(tctx_1, "wrap_handle_async", true); if (!wrap_handle_async(wk, wrap_ctx->path, wrap_ctx)) { wrap_ctx->sub_state = wrap_handle_sub_state_collected; if (wrap_ctx->wrap.name.len) { LOG_I(CLR(c_red) "failed" CLR(0) " %s", wrap_ctx->wrap.name.buf); } wrap_ctx->ok = false; ++cnt_failed; ++cnt_complete; } TracyCZoneEnd(tctx_1); } log_progress_subval(wk, cnt_complete, cnt_complete + cnt_running); float loop_dur_ns = (timer_read(&ctx.duration) - loop_start) * 1e9; if (loop_dur_ns < ((double)SLEEP_TIME / 10.0)) { timer_sleep(((double)SLEEP_TIME / 10.0) - loop_dur_ns); } } for (i = 0; i < ctx.handlers.len; ++i) { wrap_ctx = arr_get(&ctx.handlers, i); if (!wrap_ctx->ok) { continue; } obj d = wrap_to_obj(wk, &wrap_ctx->wrap); switch (opts->wrap_mode) { case wrap_handle_mode_check_dirty: { obj_dict_set(wk, d, make_str(wk, "outdated"), make_obj_bool(wk, wrap_ctx->wrap.outdated)); obj_dict_set(wk, d, make_str(wk, "dirty"), make_obj_bool(wk, wrap_ctx->wrap.dirty)); break; } default: break; } obj_array_push(wk, *opts->res, d); wrap_destroy(&wrap_ctx->wrap); } wrap_handle_async_end(wk); arr_destroy(&ctx.handlers); if (opts->progress_bar) { log_progress_disable(); } log_progress_pop_state(wk); return cnt_failed == 0; } static bool func_subprojects_update(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_LISTIFY | tc_string, .optional = true, .desc = "A list of subprojects to operate on." }, ARG_TYPE_NULL, }; enum kwargs { kw_required, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw, .desc = "Fail if any update would have been skipped (e.g. if the destination is dirty)." }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum requirement_type req = requirement_auto; if (akw[kw_required].set) { if (!coerce_requirement(wk, &akw[kw_required], &req)) { return false; } } return subprojects_process(wk, an[0].val, &(struct subprojects_process_opts){ .wrap_mode = wrap_handle_mode_update, .job_count = 8, .progress_bar = true, .res = res, .fail_if_update_skipped = req == requirement_required, }); } static int32_t subprojects_array_sort_func(struct workspace *wk, void *_ctx, obj a, obj b) { const struct str *a_name = obj_dict_index_as_str(wk, a, "name"); const struct str *b_name = obj_dict_index_as_str(wk, b, "name"); return strcmp(a_name->s, b_name->s); } static bool func_subprojects_list(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_LISTIFY | tc_string, .optional = true, .desc = "A list of subprojects to operate on." }, ARG_TYPE_NULL, }; enum kwargs { kw_print, }; struct args_kw akw[] = { [kw_print] = { "print", tc_bool, .desc = "Print out a formatted list of subprojects as well as returning it." }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (!subprojects_process(wk, an[0].val, &(struct subprojects_process_opts){ .wrap_mode = wrap_handle_mode_check_dirty, .job_count = 4, .res = res, })) { return false; } { obj sorted; obj_array_sort(wk, 0, *res, subprojects_array_sort_func, &sorted); *res = sorted; } if (get_obj_bool_with_default(wk, akw[kw_print].val, false)) { obj d; obj_array_for(wk, *res, d) { const struct str *name = obj_dict_index_as_str(wk, d, "name"); const struct str *type = obj_dict_index_as_str(wk, d, "type"); const char *t_clr = CLR(c_blue); if (str_eql(type, &STR("git"))) { t_clr = CLR(c_magenta); } LLOG_I("[%s%s%s] %s ", t_clr, type->s, CLR(0), name->s); if (obj_dict_index_as_bool(wk, d, "outdated")) { log_plain(log_info, CLR(c_green) "U" CLR(0)); } if (obj_dict_index_as_bool(wk, d, "dirty")) { log_plain(log_info, "*"); } log_plain(log_info, "\n"); } } return true; } static enum iteration_result subprojects_clean_iter(struct workspace *wk, struct subprojects_common_ctx *ctx, const char *path) { struct wrap wrap = { 0 }; if (!wrap_parse(wk, subprojects_dir(wk), path, &wrap)) { goto cont; } bool can_clean = wrap.type == wrap_type_git || (wrap.type == wrap_type_file && wrap.fields[wf_source_url]); if (!can_clean) { goto cont; } if (!fs_dir_exists(wrap.dest_dir.buf)) { goto cont; } if (ctx->force) { LOG_I("removing %s", wrap.dest_dir.buf); fs_rmdir_recursive(wrap.dest_dir.buf, true); fs_rmdir(wrap.dest_dir.buf, true); obj_array_push(wk, *ctx->res, make_str(wk, wrap.name.buf)); } else { LOG_I("would remove %s", wrap.dest_dir.buf); } wrap_destroy(&wrap); cont: return ir_cont; } static bool func_subprojects_clean(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_LISTIFY | tc_string, .optional = true, .desc = "A list of subprojects to operate on." }, ARG_TYPE_NULL, }; enum kwargs { kw_force, }; struct args_kw akw[] = { [kw_force] = { "force", tc_bool, .desc = "Force the operation." }, 0, }; if (!pop_args(wk, an, akw)) { return false; } *res = make_obj(wk, obj_array); struct subprojects_common_ctx ctx = { .force = get_obj_bool_with_default(wk, akw[kw_force].val, false), .print = true, .res = res, }; return subprojects_foreach(wk, an[0].val, &ctx, subprojects_clean_iter); } static bool func_subprojects_fetch(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string, .desc = "The wrap file to fetch." }, { tc_string, .optional = true, .desc = "The directory to fetch into" }, ARG_TYPE_NULL, }; enum kwargs { kw_force, }; struct args_kw akw[] = { [kw_force] = { "force", tc_bool, .desc = "Force the operation." }, 0, }; if (!pop_args(wk, an, akw)) { return false; } return subprojects_process(wk, an[0].val, &(struct subprojects_process_opts){ .wrap_mode = wrap_handle_mode_update, .job_count = 1, .progress_bar = true, .subprojects_dir = an[1].set ? get_cstr(wk, an[1].val) : path_cwd(), .single_file = true, .res = res, }); } static bool func_subprojects_load_wrap(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string, .desc = "The wrap file to load." }, ARG_TYPE_NULL, }; if (!pop_args(wk, an, 0)) { return false; } struct wrap wrap = { 0 }; if (!wrap_parse(wk, ".", get_cstr(wk, an[0].val), &wrap)) { return false; } *res = wrap_to_obj(wk, &wrap); wrap_destroy(&wrap); return true; } const struct func_impl impl_tbl_module_subprojects[] = { { "update", func_subprojects_update, .flags = func_impl_flag_sandbox_disable, .desc = "Update subprojects with .wrap files" }, { "list", func_subprojects_list, tc_array, .flags = func_impl_flag_sandbox_disable, .desc = "List subprojects with .wrap files and their status." }, { "clean", func_subprojects_clean, .flags = func_impl_flag_sandbox_disable, .desc = "Clean subprojects with .wrap files" }, { "fetch", func_subprojects_fetch, .flags = func_impl_flag_sandbox_disable, .desc = "Fetch a project using a .wrap file" }, { "load_wrap", func_subprojects_load_wrap, .flags = func_impl_flag_sandbox_disable, .desc = "Load a wrap file into a dict" }, { NULL, NULL }, }; muon-v0.5.0/src/functions/array.c0000644000175000017500000000714015041716357015734 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/array.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "util.h" static bool func_array_length(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj(wk, obj_number); set_obj_number(wk, *res, get_obj_array(wk, self)->len); return true; } static bool func_array_get(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_number }, { tc_any, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } int64_t i = get_obj_number(wk, an[0].val); if (!bounds_adjust(get_obj_array(wk, self)->len, &i)) { if (an[1].set) { *res = an[1].val; } else { vm_error_at(wk, an[0].node, "index out of bounds"); return false; } } else { *res = obj_array_index(wk, self, i); } return true; } struct array_contains_ctx { obj item; bool found; }; static enum iteration_result array_contains_iter(struct workspace *wk, void *_ctx, obj val) { struct array_contains_ctx *ctx = _ctx; if (get_obj_type(wk, val) == obj_array) { obj_array_foreach(wk, val, ctx, array_contains_iter); if (ctx->found) { return ir_done; } } if (obj_equal(wk, val, ctx->item)) { ctx->found = true; return ir_done; } return ir_cont; } static bool func_array_contains(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } struct array_contains_ctx ctx = { .item = an[0].val }; obj_array_foreach(wk, self, &ctx, array_contains_iter); *res = make_obj_bool(wk, ctx.found); return true; } static bool func_array_delete(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_number }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } int64_t idx = get_obj_number(wk, an[0].val); if (!boundscheck(wk, an[0].node, get_obj_array(wk, self)->len, &idx)) { return false; } obj_array_del(wk, self, idx); return true; } static bool func_array_slice(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_number, .optional = true }, { obj_number, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const struct obj_array *a = get_obj_array(wk, self); int64_t start = 0, end = a->len; if (an[0].set) { start = get_obj_number(wk, an[0].val); } if (an[1].set) { end = get_obj_number(wk, an[1].val); } bounds_adjust(a->len, &start); bounds_adjust(a->len, &end); end = MIN(end, a->len); *res = obj_array_slice(wk, self, start, end); return true; } static bool func_array_clear(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, 0, 0)) { return false; } obj_array_clear(wk, self); return true; } static bool func_array_dedup(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, 0, 0)) { return false; } obj_array_dedup(wk, self, res); return true; } const struct func_impl impl_tbl_array[] = { { "length", func_array_length, tc_number, true }, { "get", func_array_get, tc_any, true }, { "contains", func_array_contains, tc_bool, true }, { NULL, NULL }, }; const struct func_impl impl_tbl_array_internal[] = { { "length", func_array_length, tc_number, true }, { "get", func_array_get, tc_any, true }, { "contains", func_array_contains, tc_bool, true }, { "delete", func_array_delete }, { "slice", func_array_slice, tc_array, true }, { "clear", func_array_clear }, { "dedup", func_array_dedup, tc_array, true }, { NULL, NULL }, }; muon-v0.5.0/src/functions/dict.c0000644000175000017500000000434515041716357015545 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/func_lookup.h" #include "functions/dict.h" #include "lang/typecheck.h" static enum iteration_result dict_keys_iter(struct workspace *wk, void *_ctx, obj k, obj v) { obj *arr = _ctx; obj_array_push(wk, *arr, k); return ir_cont; } static bool func_dict_keys(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj(wk, obj_array); obj_dict_foreach(wk, self, res, dict_keys_iter); return true; } static bool func_dict_has_key(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj_bool(wk, obj_dict_in(wk, self, an[0].val)); return true; } static bool func_dict_get(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { tc_any, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } if (!obj_dict_index(wk, self, an[0].val, res)) { if (an[1].set) { *res = an[1].val; } else { vm_error_at(wk, an[0].node, "key not in dictionary: '%s'", get_cstr(wk, an[0].val)); return false; } } return true; } const struct func_impl impl_tbl_dict[] = { { "keys", func_dict_keys, tc_array, true }, { "has_key", func_dict_has_key, tc_bool, true }, { "get", func_dict_get, tc_any, true }, { NULL, NULL }, }; static bool func_dict_delete(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj_dict_del(wk, self, an[0].val); return true; } static bool func_dict_set(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string }, { tc_any }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj_dict_set(wk, self, an[0].val, an[1].val); return true; } const struct func_impl impl_tbl_dict_internal[] = { { "keys", func_dict_keys, tc_array, true }, { "has_key", func_dict_has_key, tc_bool, true }, { "get", func_dict_get, tc_any, true }, { "delete", func_dict_delete }, { "set", func_dict_set }, { NULL, NULL }, }; muon-v0.5.0/src/functions/modules.c0000644000175000017500000002041315041716357016264 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "embedded.h" #include "functions/modules.h" #include "functions/modules/curl.h" #include "functions/modules/fs.h" #include "functions/modules/getopt.h" #include "functions/modules/json.h" #include "functions/modules/keyval.h" #include "functions/modules/pkgconfig.h" #include "functions/modules/python.h" #include "functions/modules/sourceset.h" #include "functions/modules/subprojects.h" #include "functions/modules/toolchain.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" #define MODULE_INFO(mod, _path, _implemented) \ { .name = #mod, .path = _path, .implemented = _implemented }, const struct module_info module_info[module_count] = { FOREACH_BUILTIN_MODULE(MODULE_INFO) }; #undef MODULE_INFO static bool module_lookup_builtin(const char *name, enum module *res, bool *has_impl) { enum module i; for (i = 0; i < module_count; ++i) { if (strcmp(name, module_info[i].path) == 0) { *res = i; *has_impl = module_info[i].implemented; return true; } } return false; } struct module_lookup_script_opts { bool embedded; bool encapsulate; }; static bool module_lookup_script(struct workspace *wk, struct tstr *path, struct obj_module *m, const struct module_lookup_script_opts *opts) { bool ret = false; bool stack_popped = false; stack_push(&wk->stack, wk->vm.lang_mode, language_extended); stack_push(&wk->stack, wk->vm.scope_stack, wk->vm.behavior.scope_stack_dup(wk, wk->vm.default_scope_stack)); obj res; if (opts->embedded) { struct source src; if (!(embedded_get(path->buf, &src))) { goto ret; } src.label = get_cstr(wk, tstr_into_str(wk, path)); src.len = strlen(src.src); if (!eval(wk, &src, build_language_meson, 0, &res)) { goto ret; } } else { if (!wk->vm.behavior.eval_project_file(wk, path->buf, build_language_meson, 0, &res)) { goto ret; } } if (!typecheck_custom(wk, 0, res, make_complex_type(wk, complex_type_nested, tc_dict, tc_capture), "expected %s, got %s for module return type")) { goto ret; } if (opts->encapsulate) { m->found = true; m->has_impl = true; m->exports = res; } else { stack_pop(&wk->stack, wk->vm.scope_stack); stack_pop(&wk->stack, wk->vm.lang_mode); stack_popped = true; obj k, v; obj_dict_for(wk, res, k, v) { wk->vm.behavior.assign_variable(wk, get_cstr(wk, k), v, 0, assign_local); } } ret = true; ret: if (!stack_popped) { stack_pop(&wk->stack, wk->vm.scope_stack); stack_pop(&wk->stack, wk->vm.lang_mode); } return ret; } const char *module_paths[] = { [language_external] = "embedded:modules/%.meson;builtin:public/%", [language_internal] = "embedded:lib/%.meson;builtin:private/%;builtin:public/%", [language_opts] = "", [language_extended] = "embedded:lib/%.meson;builtin:private/%;builtin:public/%", }; bool module_import(struct workspace *wk, const char *name, bool encapsulate, obj *res) { struct obj_module *m = 0; { enum { schema_type_file, schema_type_embedded, schema_type_builtin, } schema; const char *schema_type_str[] = { [schema_type_file] = "file", [schema_type_embedded] = "embedded", [schema_type_builtin] = "builtin", }; bool loop = true; struct str path; TSTR(path_interpolated); TSTR(module_path); const char *p, *sep; { struct project *proj; if (wk->vm.lang_mode == language_external && (proj = current_project(wk)) && proj->module_dir) { tstr_pushs(wk, &module_path, module_paths[wk->vm.lang_mode]); tstr_pushs(wk, &module_path, ";file:"); TSTR(new_module_path); path_push(wk, &new_module_path, get_cstr(wk, proj->source_root)); path_push(wk, &new_module_path, get_cstr(wk, proj->module_dir)); path_push(wk, &new_module_path, "%.meson"); tstr_pushn(wk, &module_path, new_module_path.buf, new_module_path.len); p = module_path.buf; } else { p = module_paths[wk->vm.lang_mode]; } } while (loop) { path.s = p; if ((sep = strchr(path.s, ';'))) { path.len = sep - path.s; p = sep + 1; } else { path.len = strlen(path.s); loop = false; } { // Parse schema if given if ((sep = memchr(path.s, ':', path.len))) { const struct str schema_str = { path.s, sep - path.s }; for (schema = 0; (uint32_t)schema < ARRAY_LEN(schema_type_str); ++schema) { if (schema_type_str[schema] && str_eql(&STRL(schema_type_str[schema]), &schema_str)) { break; } } if (schema == ARRAY_LEN(schema_type_str)) { goto missing_schema; } path.s = sep + 1; path.len -= (schema_str.len + 1); } else { missing_schema: vm_error(wk, "missing or invalid schema in module path: %.*s", path.len, path.s); return false; } } { // Interpolate path tstr_clear(&path_interpolated); uint32_t i; for (i = 0; i < path.len; ++i) { if (path.s[i] == '%') { tstr_pushs(wk, &path_interpolated, name); } else { tstr_push(wk, &path_interpolated, path.s[i]); } } } switch (schema) { case schema_type_file: case schema_type_embedded: { struct module_lookup_script_opts opts = { .encapsulate = encapsulate, .embedded = schema == schema_type_embedded, }; if (encapsulate) { *res = make_obj(wk, obj_module); m = get_obj_module(wk, *res); if (obj_dict_index_strn(wk, wk->vm.modules, path_interpolated.buf, path_interpolated.len, res)) { return true; } } if (module_lookup_script(wk, &path_interpolated, m, &opts)) { if (encapsulate) { obj_dict_set(wk, wk->vm.modules, tstr_into_str(wk, &path_interpolated), *res); } if (schema == schema_type_file) { workspace_add_regenerate_dep(wk, tstr_into_str(wk, &path_interpolated)); } if (wk->vm.error) { return false; } return true; } break; } case schema_type_builtin: { enum module mod_type; bool has_impl = false; if (module_lookup_builtin(path_interpolated.buf, &mod_type, &has_impl)) { if (!encapsulate) { vm_error(wk, "builtin modules cannot be imported into the current scope"); return false; } *res = make_obj(wk, obj_module); m = get_obj_module(wk, *res); m->module = mod_type; m->found = has_impl; m->has_impl = has_impl; return true; } break; } } } } return false; } static bool func_module_found(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, get_obj_module(wk, self)->found); return true; } // clang-format off struct func_impl_group module_func_impl_groups[module_count][language_mode_count] = { [module_fs] = { { impl_tbl_module_fs }, { impl_tbl_module_fs_internal } }, [module_keyval] = { { impl_tbl_module_keyval }, { 0 } }, [module_pkgconfig] = { { impl_tbl_module_pkgconfig }, { 0 } }, [module_python3] = { { impl_tbl_module_python3 }, { 0 } }, [module_python] = { { impl_tbl_module_python }, { 0 } }, [module_sourceset] = { { impl_tbl_module_sourceset }, { 0 } }, [module_toolchain] = { { 0 }, { impl_tbl_module_toolchain } }, [module_subprojects] = { { 0 }, { impl_tbl_module_subprojects } }, [module_getopt] = { { 0 }, { impl_tbl_module_getopt } }, [module_curl] = { { 0 }, { impl_tbl_module_curl } }, [module_json] = { { 0 }, { impl_tbl_module_json } }, }; const struct func_impl impl_tbl_module[] = { { "found", func_module_found, tc_bool, }, { 0 }, }; // clang-format on bool module_func_lookup(struct workspace *wk, const char *name, enum module mod, uint32_t *idx) { return func_lookup_for_group(module_func_impl_groups[mod], wk->vm.lang_mode, name, idx); } muon-v0.5.0/src/functions/configuration_data.c0000644000175000017500000001135615041716357020462 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "lang/func_lookup.h" #include "functions/configuration_data.h" #include "lang/typecheck.h" static bool func_configuration_data_set_quoted(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_description, // TODO }; struct args_kw akw[] = { [kw_description] = { "description", obj_string, }, 0 }; if (!pop_args(wk, an, akw)) { return false; } obj dict = get_obj_configuration_data(wk, self)->dict; const char *s = get_cstr(wk, an[1].val); obj str = make_str(wk, "\""); for (; *s; ++s) { if (*s == '"') { str_app(wk, &str, "\\"); } str_appn(wk, &str, s, 1); } str_app(wk, &str, "\""); obj_dict_set(wk, dict, an[0].val, str); return true; } static bool func_configuration_data_set(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { tc_string | tc_number | tc_bool }, ARG_TYPE_NULL }; enum kwargs { kw_description, // ingnored }; struct args_kw akw[] = { [kw_description] = { "description", obj_string, }, 0 }; if (!pop_args(wk, an, akw)) { return false; } obj dict = get_obj_configuration_data(wk, self)->dict; obj_dict_set(wk, dict, an[0].val, an[1].val); return true; } static bool func_configuration_data_set10(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { obj_bool }, ARG_TYPE_NULL }; enum kwargs { kw_description, // ignored }; struct args_kw akw[] = { [kw_description] = { "description", obj_string, }, 0 }; if (!pop_args(wk, an, akw)) { return false; } obj dict = get_obj_configuration_data(wk, self)->dict; obj n; n = make_obj(wk, obj_number); set_obj_number(wk, n, get_obj_bool(wk, an[1].val) ? 1 : 0); obj_dict_set(wk, dict, an[0].val, n); return true; } static bool configuration_data_get(struct workspace *wk, uint32_t err_node, obj conf, obj key, obj def, obj *res) { obj dict = get_obj_configuration_data(wk, conf)->dict; if (!obj_dict_index(wk, dict, key, res)) { if (def) { *res = def; } else { vm_error_at(wk, err_node, "key '%s' not found", get_cstr(wk, key)); return false; } } return true; } static bool func_configuration_data_get(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { tc_any, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } return configuration_data_get(wk, an[0].node, self, an[0].val, an[1].val, res); } static bool func_configuration_data_get_unquoted(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { tc_any, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj v; if (!configuration_data_get(wk, an[0].node, self, an[0].val, an[1].val, &v)) { return false; } const char *s = get_cstr(wk, v); uint32_t l = strlen(s); if (l >= 2 && s[0] == '"' && s[l - 1] == '"') { *res = make_strn(wk, &s[1], l - 2); } else { *res = v; } return true; } static enum iteration_result obj_dict_keys_iter(struct workspace *wk, void *_ctx, obj k, obj _v) { obj *res = _ctx; obj_array_push(wk, *res, k); return ir_cont; } static bool func_configuration_data_keys(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } obj dict = get_obj_configuration_data(wk, self)->dict; *res = make_obj(wk, obj_array); obj_dict_foreach(wk, dict, res, obj_dict_keys_iter); return true; } static bool func_configuration_data_has(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj _, dict = get_obj_configuration_data(wk, self)->dict; *res = make_obj_bool(wk, obj_dict_index(wk, dict, an[0].val, &_)); return true; } static bool func_configuration_data_merge_from(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_configuration_data }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj_dict_merge_nodup( wk, get_obj_configuration_data(wk, self)->dict, get_obj_configuration_data(wk, an[0].val)->dict); return true; } const struct func_impl impl_tbl_configuration_data[] = { { "get", func_configuration_data_get, tc_any }, { "get_unquoted", func_configuration_data_get_unquoted, tc_any }, { "has", func_configuration_data_has, tc_bool }, { "keys", func_configuration_data_keys, tc_array }, { "merge_from", func_configuration_data_merge_from }, { "set", func_configuration_data_set }, { "set10", func_configuration_data_set10 }, { "set_quoted", func_configuration_data_set_quoted }, { NULL, NULL }, }; muon-v0.5.0/src/functions/source_set.c0000644000175000017500000002061115041716357016767 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "error.h" #include "functions/source_set.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "platform/assert.h" static enum iteration_result source_set_freeze_nested_iter(struct workspace *wk, void *_ctx, obj v) { if (get_obj_type(wk, v) == obj_source_set) { get_obj_source_set(wk, v)->frozen = true; } return ir_cont; } static bool source_set_add_rule(struct workspace *wk, obj self, struct args_norm *posargs, struct args_kw *kw_when, struct args_kw *kw_if_true, struct args_kw *kw_if_false) { obj when = 0, if_true, if_false = 0; if (get_obj_array(wk, posargs->val)->len) { if (kw_when->set || kw_if_true->set || (kw_if_false && kw_if_false->set)) { vm_error_at(wk, posargs->node, "posargs not allowed when kwargs are used"); return false; } if_true = posargs->val; } else { when = kw_when->val; if_true = kw_if_true->val; if (kw_if_false) { if_false = kw_if_false->val; } } if (if_true) { obj_array_foreach(wk, if_true, NULL, source_set_freeze_nested_iter); } obj rule; rule = make_obj(wk, obj_array); obj_array_push(wk, rule, when); obj_array_push(wk, rule, if_true); obj_array_push(wk, rule, if_false); obj_array_push(wk, get_obj_source_set(wk, self)->rules, rule); return true; } static bool source_set_check_not_frozen(struct workspace *wk, obj self) { if (get_obj_source_set(wk, self)->frozen) { vm_error(wk, "cannot modify frozen source set"); return false; } return true; } static bool func_source_set_add(struct workspace *wk, obj self, obj *res) { const type_tag tc_ss_sources = tc_string | tc_file | tc_custom_target | tc_generated_list; struct args_norm an[] = { { TYPE_TAG_GLOB | tc_ss_sources | tc_dependency }, ARG_TYPE_NULL }; enum kwargs { kw_when, kw_if_true, kw_if_false, }; struct args_kw akw[] = { [kw_when] = { "when", TYPE_TAG_LISTIFY | tc_string | tc_dependency }, [kw_if_true] = { "if_true", TYPE_TAG_LISTIFY | tc_ss_sources | tc_dependency }, [kw_if_false] = { "if_false", TYPE_TAG_LISTIFY | tc_ss_sources }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (!source_set_check_not_frozen(wk, self)) { return false; } return source_set_add_rule(wk, self, &an[0], &akw[kw_when], &akw[kw_if_true], &akw[kw_if_false]); } static bool func_source_set_add_all(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_source_set }, ARG_TYPE_NULL }; enum kwargs { kw_when, kw_if_true, }; struct args_kw akw[] = { [kw_when] = { "when", TYPE_TAG_LISTIFY | tc_string | tc_dependency }, [kw_if_true] = { "if_true", TYPE_TAG_LISTIFY | tc_source_set }, 0 }; if (!pop_args(wk, an, akw)) { return false; } if (!source_set_check_not_frozen(wk, self)) { return false; } return source_set_add_rule(wk, self, &an[0], &akw[kw_when], &akw[kw_if_true], NULL); } enum source_set_collect_mode { source_set_collect_sources, source_set_collect_dependencies, }; struct source_set_collect_ctx { enum source_set_collect_mode mode; bool strict; obj conf; obj res; // for rule_match_iter uint32_t err_node; bool match; }; static enum iteration_result source_set_collect_rules_iter(struct workspace *wk, void *_ctx, obj v); static enum iteration_result source_set_collect_iter(struct workspace *wk, void *_ctx, obj v) { struct source_set_collect_ctx *ctx = _ctx; switch (get_obj_type(wk, v)) { case obj_string: case obj_file: case obj_custom_target: case obj_generated_list: if (ctx->mode == source_set_collect_sources) { obj_array_push(wk, ctx->res, v); } break; case obj_dependency: if (ctx->mode == source_set_collect_dependencies) { obj_array_push(wk, ctx->res, v); } break; case obj_source_set: if (!obj_array_foreach(wk, get_obj_source_set(wk, v)->rules, ctx, source_set_collect_rules_iter)) { return ir_err; } break; default: UNREACHABLE; } return ir_cont; } static enum iteration_result source_set_rule_match_iter(struct workspace *wk, void *_ctx, obj v) { struct source_set_collect_ctx *ctx = _ctx; enum obj_type t = get_obj_type(wk, v); if (!ctx->conf && t != obj_dependency) { return ir_cont; } switch (t) { case obj_dependency: if (!(get_obj_dependency(wk, v)->flags & dep_flag_found)) { ctx->match = false; return ir_done; } break; case obj_string: { obj idx; if (!obj_dict_index(wk, ctx->conf, v, &idx)) { if (ctx->strict) { vm_error_at(wk, ctx->err_node, "key %o not in configuration", v); return ir_err; } ctx->match = false; return ir_done; } bool bv = false; switch (get_obj_type(wk, idx)) { case obj_bool: bv = get_obj_bool(wk, idx); break; case obj_string: bv = get_str(wk, idx)->len > 0; break; case obj_number: bv = get_obj_number(wk, idx) > 0; break; default: UNREACHABLE; } if (!bv) { ctx->match = false; return ir_done; } break; } default: UNREACHABLE; } return ir_cont; } static enum iteration_result source_set_collect_when_deps_iter(struct workspace *wk, void *_ctx, obj v) { struct source_set_collect_ctx *ctx = _ctx; if (get_obj_type(wk, v) == obj_dependency) { obj_array_push(wk, ctx->res, v); } return ir_cont; } static enum iteration_result source_set_collect_rules_iter(struct workspace *wk, void *_ctx, obj v) { struct source_set_collect_ctx *ctx = _ctx; obj when, if_true, if_false; when = obj_array_index(wk, v, 0); if_true = obj_array_index(wk, v, 1); if_false = obj_array_index(wk, v, 2); ctx->match = true; if (when && !obj_array_foreach_flat(wk, when, ctx, source_set_rule_match_iter)) { return ir_err; } if (ctx->match && if_true) { if (when && ctx->mode == source_set_collect_dependencies) { obj_array_foreach_flat(wk, when, ctx, source_set_collect_when_deps_iter); } obj_array_foreach_flat(wk, if_true, ctx, source_set_collect_iter); } if ((!ctx->conf || !ctx->match) && if_false) { obj_array_foreach_flat(wk, if_false, ctx, source_set_collect_iter); } return ir_cont; } static bool source_set_collect(struct workspace *wk, uint32_t err_node, obj self, obj conf, enum source_set_collect_mode mode, bool strict, obj *res) { obj arr; arr = make_obj(wk, obj_array); struct source_set_collect_ctx ctx = { .mode = mode, .conf = conf, .strict = strict, .res = arr, }; struct obj_source_set *ss = get_obj_source_set(wk, self); if (!obj_array_foreach(wk, ss->rules, &ctx, source_set_collect_rules_iter)) { return false; } obj_array_dedup(wk, arr, res); return true; } static bool func_source_set_all_sources(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } return source_set_collect(wk, 0, self, 0, source_set_collect_sources, true, res); } static bool func_source_set_all_dependencies(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } return source_set_collect(wk, 0, self, 0, source_set_collect_dependencies, true, res); } static bool func_source_set_apply(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_configuration_data | tc_dict }, ARG_TYPE_NULL }; enum kwargs { kw_strict, }; struct args_kw akw[] = { [kw_strict] = { "strict", tc_bool }, 0 }; if (!pop_args(wk, an, akw)) { return false; } struct obj_source_set *ss = get_obj_source_set(wk, self); ss->frozen = true; obj dict = 0; switch (get_obj_type(wk, an[0].val)) { case obj_dict: dict = an[0].val; break; case obj_configuration_data: dict = get_obj_configuration_data(wk, an[0].val)->dict; break; default: UNREACHABLE; } bool strict = akw[kw_strict].set ? get_obj_bool(wk, akw[kw_strict].val) : true; *res = make_obj(wk, obj_source_configuration); struct obj_source_configuration *sc = get_obj_source_configuration(wk, *res); if (!source_set_collect( wk, akw[kw_strict].node, self, dict, source_set_collect_sources, strict, &sc->sources)) { return false; } if (!source_set_collect( wk, akw[kw_strict].node, self, dict, source_set_collect_dependencies, strict, &sc->dependencies)) { return false; } return true; } const struct func_impl impl_tbl_source_set[] = { { "add", func_source_set_add, 0, true }, { "add_all", func_source_set_add_all, 0, true }, { "all_sources", func_source_set_all_sources, tc_array, true }, { "all_dependencies", func_source_set_all_dependencies, tc_array, true }, { "apply", func_source_set_apply, tc_source_configuration, true }, { NULL, NULL }, }; muon-v0.5.0/src/functions/disabler.c0000644000175000017500000000100215041716357016372 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/func_lookup.h" #include "functions/disabler.h" #include "lang/typecheck.h" static bool func_disabler_found(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, false); return true; } const struct func_impl impl_tbl_disabler[] = { { "found", func_disabler_found, tc_bool }, { NULL, NULL }, }; muon-v0.5.0/src/functions/both_libs.c0000644000175000017500000000365615041716357016573 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "buf_size.h" #include "error.h" #include "functions/both_libs.h" #include "functions/build_target.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "options.h" #include "platform/assert.h" obj decay_both_libs(struct workspace *wk, obj both_libs) { struct obj_both_libs *b = get_obj_both_libs(wk, both_libs); enum default_both_libraries def_both_libs = b->default_both_libraries; if (def_both_libs == default_both_libraries_auto) { def_both_libs = get_option_default_both_libraries(wk, 0, 0); } switch(def_both_libs) { case default_both_libraries_auto: return b->dynamic_lib; case default_both_libraries_static: return b->static_lib; case default_both_libraries_shared: return b->dynamic_lib; } UNREACHABLE_RETURN; } static bool func_both_libs_get_shared_lib(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_both_libs(wk, self)->dynamic_lib; return true; } static bool func_both_libs_get_static_lib(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_both_libs(wk, self)->static_lib; return true; } static obj both_libs_self_transform(struct workspace *wk, obj self) { return decay_both_libs(wk, self); } void both_libs_build_impl_tbl(void) { uint32_t i; for (i = 0; impl_tbl_build_target[i].name; ++i) { struct func_impl tmp = impl_tbl_build_target[i]; tmp.self_transform = both_libs_self_transform; impl_tbl_both_libs[i] = tmp; } } struct func_impl impl_tbl_both_libs[] = { [ARRAY_LEN(impl_tbl_build_target) - 1] = { "get_shared_lib", func_both_libs_get_shared_lib, tc_build_target }, { "get_static_lib", func_both_libs_get_static_lib, tc_build_target }, { NULL, NULL }, }; muon-v0.5.0/src/functions/source_configuration.c0000644000175000017500000000160715041716357021047 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/func_lookup.h" #include "functions/source_configuration.h" #include "lang/typecheck.h" static bool func_source_configuration_sources(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_source_configuration(wk, self)->sources; return true; } static bool func_source_configuration_dependencies(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_source_configuration(wk, self)->dependencies; return true; } const struct func_impl impl_tbl_source_configuration[] = { { "sources", func_source_configuration_sources, tc_array, true }, { "dependencies", func_source_configuration_dependencies, tc_array, true }, { NULL, NULL }, }; muon-v0.5.0/src/functions/kernel.c0000644000175000017500000016606715041716357016114 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: dffdff2423 * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "buf_size.h" #include "coerce.h" #include "error.h" #include "external/samurai.h" #include "functions/environment.h" #include "functions/external_program.h" #include "functions/kernel.h" #include "functions/kernel/build_target.h" #include "functions/kernel/configure_file.h" #include "functions/kernel/custom_target.h" #include "functions/kernel/dependency.h" #include "functions/kernel/install.h" #include "functions/kernel/options.h" #include "functions/kernel/subproject.h" #include "functions/modules.h" #include "functions/string.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/serial.h" #include "lang/typecheck.h" #include "log.h" #include "options.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "version.h" #include "wrap.h" static bool project_add_language(struct workspace *wk, uint32_t err_node, obj str, obj compiler, enum machine_kind machine, enum requirement_type req, bool *found) { if (req == requirement_skip) { return true; } obj comp_id; obj res; enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, str), &l)) { if (req == requirement_required) { vm_error_at(wk, err_node, "%o is not a valid language", str); return false; } else { return true; } } if (obj_dict_geti(wk, current_project(wk)->toolchains[machine], l, &res)) { *found = true; return true; } if (compiler) { comp_id = make_obj(wk, obj_compiler); struct obj_compiler *c = get_obj_compiler(wk, comp_id); *c = *get_obj_compiler(wk, compiler); c->lang = l; enum toolchain_component component; for (component = 0; component < toolchain_component_count; ++component) { if (!c->cmd_arr[component]) { vm_error(wk, "compiler %s is not configured", toolchain_component_to_s(component)); } } obj_dict_seti(wk, wk->toolchains[machine], l, comp_id); } else { if (!toolchain_detect(wk, &comp_id, machine, l)) { if (req == requirement_required) { vm_error_at(wk, err_node, "unable to detect %s compiler", get_cstr(wk, str)); return false; } else { return true; } } } struct obj_compiler *comp = get_obj_compiler(wk, comp_id); comp->machine = machine; obj_dict_seti(wk, current_project(wk)->toolchains[machine], l, comp_id); /* if we just added a c or cpp compiler, set the assembly compiler to that */ if (l == compiler_language_c || l == compiler_language_cpp) { obj_dict_seti(wk, current_project(wk)->toolchains[machine], compiler_language_assembly, comp_id); struct obj_compiler *comp = get_obj_compiler(wk, comp_id); // TODO: make this overrideable const enum compiler_type type = comp->type[toolchain_component_compiler]; if (type == compiler_clang || type == compiler_apple_clang) { obj llvm_ir_compiler; llvm_ir_compiler = make_obj(wk, obj_compiler); struct obj_compiler *c = get_obj_compiler(wk, llvm_ir_compiler); *c = *comp; c->type[toolchain_component_compiler] = compiler_clang_llvm_ir; c->lang = compiler_language_llvm_ir; obj_dict_seti(wk, current_project(wk)->toolchains[machine], compiler_language_llvm_ir, llvm_ir_compiler); } } switch (l) { case compiler_language_assembly: case compiler_language_nasm: case compiler_language_objc: { obj c_compiler; if (!obj_dict_geti(wk, current_project(wk)->toolchains[machine], compiler_language_c, &c_compiler) && !obj_dict_geti( wk, current_project(wk)->toolchains[machine], compiler_language_cpp, &c_compiler)) { bool c_found; if (!project_add_language(wk, err_node, make_str(wk, "c"), compiler, machine, req, &c_found)) { return false; } } } break; case compiler_language_objcpp: { obj cpp_compiler; if (!obj_dict_geti( wk, current_project(wk)->toolchains[machine], compiler_language_cpp, &cpp_compiler)) { bool cpp_found; if (!project_add_language( wk, err_node, make_str(wk, "cpp"), compiler, machine, req, &cpp_found)) { return false; } } } break; default: break; } *found = true; return true; } static bool func_project(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, { TYPE_TAG_GLOB | tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_default_options, kw_license, kw_license_files, kw_meson_version, kw_subproject_dir, kw_module_dir, kw_version, }; struct args_kw akw[] = { [kw_default_options] = { "default_options", COMPLEX_TYPE_PRESET(tc_cx_options_dict_or_list) }, [kw_license] = { "license", TYPE_TAG_LISTIFY | obj_string }, [kw_license_files] = { "license_files", TYPE_TAG_LISTIFY | obj_string }, [kw_meson_version] = { "meson_version", obj_string }, [kw_subproject_dir] = { "subproject_dir", obj_string }, [kw_module_dir] = { "module_dir", obj_string, .desc = "Specify a directory to search for .meson files in when import()-ing modules", .extension = true }, [kw_version] = { "version", tc_string | tc_file }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (current_project(wk)->initialized) { vm_error(wk, "project may only be called once"); return false; } if (akw[kw_subproject_dir].set) { current_project(wk)->subprojects_dir = akw[kw_subproject_dir].val; } if (akw[kw_module_dir].set) { current_project(wk)->module_dir = akw[kw_module_dir].val; } current_project(wk)->cfg.name = an[0].val; if (wk->vm.in_analyzer) { return true; } #ifndef MUON_BOOTSTRAPPED if (wk->cur_project == 0 && !str_eql(get_str(wk, an[0].val), &STR("muon"))) { vm_error_at(wk, an[0].node, "This muon has not been fully bootstrapped. It can only be used to setup muon itself."); return false; } #endif #ifdef MUON_BOOTSTRAPPED if (akw[kw_meson_version].set) { if (!version_compare(&STRL(muon_version.meson_compat), get_str(wk, akw[kw_meson_version].val))) { vm_error_at(wk, akw[kw_meson_version].node, "meson compatibility version %s does not meet requirement: %o", muon_version.meson_compat, akw[kw_meson_version].val); return false; } } #endif obj val; obj_array_for(wk, an[1].val, val) { bool _found; if (!project_add_language(wk, an[1].node, val, 0, machine_kind_host, requirement_required, &_found)) { return false; } if (!project_add_language(wk, an[1].node, val, 0, machine_kind_build, requirement_auto, &_found)) { return false; } } current_project(wk)->cfg.license = akw[kw_license].val; current_project(wk)->cfg.license_files = akw[kw_license_files].val; if (akw[kw_version].set) { if (get_obj_type(wk, akw[kw_version].val) == obj_string) { current_project(wk)->cfg.version = akw[kw_version].val; } else { struct source ver_src = { 0 }; if (!fs_read_entire_file(get_file_path(wk, akw[kw_version].val), &ver_src)) { vm_error_at(wk, akw[kw_version].node, "failed to read version file"); return false; } const char *str_ver = ver_src.src; uint32_t i; for (i = 0; ver_src.src[i]; ++i) { if (ver_src.src[i] == '\n') { if (ver_src.src[i + 1]) { vm_error_at(wk, akw[kw_version].node, "version file is more than one line long"); return false; } break; } } current_project(wk)->cfg.version = make_strn(wk, str_ver, i); fs_source_destroy(&ver_src); } } else { current_project(wk)->cfg.version = make_str(wk, "undefined"); current_project(wk)->cfg.no_version = true; } if (akw[kw_default_options].set) { if (!parse_and_set_default_options( wk, akw[kw_default_options].node, akw[kw_default_options].val, 0, false)) { return false; } } if (wk->cur_project == 0) { if (!prefix_dir_opts(wk)) { return false; } } { // subprojects TSTR(subprojects_path); path_join(wk, &subprojects_path, get_cstr(wk, current_project(wk)->source_root), get_cstr(wk, current_project(wk)->subprojects_dir)); if (!wrap_load_all_provides(wk, subprojects_path.buf)) { LOG_E("failed loading wrap provides"); return false; } } LLOG_I(CLR(c_bold, c_magenta) "%s" CLR(0), get_cstr(wk, current_project(wk)->cfg.name)); log_plain_version_string(log_info, get_cstr(wk, current_project(wk)->cfg.version)); log_plain(log_info, "\n"); current_project(wk)->initialized = true; return true; } static obj get_project_argument_array(struct workspace *wk, obj dict, enum compiler_language l) { obj arg_arr; if (!obj_dict_geti(wk, dict, l, &arg_arr)) { arg_arr = make_obj(wk, obj_array); obj_dict_seti(wk, dict, l, arg_arr); } return arg_arr; } struct add_arguments_ctx { uint32_t lang_node; uint32_t args_node; obj args_dict; obj args_to_add; obj arg_arr; }; static enum iteration_result add_arguments_language_iter(struct workspace *wk, void *_ctx, obj val_id) { struct add_arguments_ctx *ctx = _ctx; if (!typecheck(wk, ctx->args_node, val_id, obj_string)) { return ir_err; } obj_array_push(wk, ctx->arg_arr, val_id); return ir_cont; } static enum iteration_result add_arguments_iter(struct workspace *wk, void *_ctx, obj val) { struct add_arguments_ctx *ctx = _ctx; enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, val), &l)) { vm_error_at(wk, ctx->lang_node, "unknown language '%s'", get_cstr(wk, val)); return ir_err; } ctx->arg_arr = get_project_argument_array(wk, ctx->args_dict, l); if (!obj_array_foreach_flat(wk, ctx->args_to_add, ctx, add_arguments_language_iter)) { return ir_err; } return ir_cont; } static bool add_arguments_common(struct workspace *wk, obj args_dict[machine_kind_count], obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_language, kw_native, }; struct args_kw akw[] = { [kw_language] = { "language", TYPE_TAG_LISTIFY | obj_string, .required = true }, [kw_native] = { "native", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } struct add_arguments_ctx ctx = { .lang_node = akw[kw_language].node, .args_node = an[0].node, .args_dict = args_dict[coerce_machine_kind(wk, &akw[kw_native])], .args_to_add = an[0].val, }; return obj_array_foreach(wk, akw[kw_language].val, &ctx, add_arguments_iter); } static bool func_add_project_arguments(struct workspace *wk, obj _, obj *res) { return add_arguments_common(wk, current_project(wk)->args, res); } static bool func_add_global_arguments(struct workspace *wk, obj _, obj *res) { if (wk->cur_project != 0) { vm_error(wk, "add_global_arguments cannot be called from a subproject"); return false; } return add_arguments_common(wk, wk->global_args, res); } static bool func_add_project_link_arguments(struct workspace *wk, obj _, obj *res) { return add_arguments_common(wk, current_project(wk)->link_args, res); } static bool func_add_global_link_arguments(struct workspace *wk, obj _, obj *res) { if (wk->cur_project != 0) { vm_error(wk, "add_global_link_arguments cannot be called from a subproject"); return false; } return add_arguments_common(wk, wk->global_link_args, res); } static bool func_add_project_dependencies(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_dependency }, ARG_TYPE_NULL }; enum kwargs { kw_language, kw_native, }; struct args_kw akw[] = { [kw_language] = { "language", TYPE_TAG_LISTIFY | obj_string, .required = true }, [kw_native] = { "native", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum machine_kind machine = coerce_machine_kind(wk, &akw[kw_native]); struct build_dep d = { 0 }; dep_process_deps(wk, an[0].val, &d); obj lang; obj_array_for(wk, akw[kw_language].val, lang) { enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, lang), &l)) { vm_error_at(wk, akw[kw_language].node, "unknown language '%s'", get_cstr(wk, lang)); return false; } obj res; if (!obj_dict_geti(wk, current_project(wk)->toolchains[machine], l, &res)) { // NOTE: Its a little weird that the other add_project_xxx // functions don't check this and this function does, but that // is how meson does it. vm_error_at(wk, akw[kw_language].node, "undeclared language '%s'", get_cstr(wk, lang)); return false; } obj_array_extend( wk, get_project_argument_array(wk, current_project(wk)->args[machine], l), d.compile_args); obj_array_extend( wk, get_project_argument_array(wk, current_project(wk)->link_args[machine], l), d.link_args); obj_array_extend(wk, get_project_argument_array(wk, current_project(wk)->include_dirs[machine], l), d.include_directories); obj_array_extend( wk, get_project_argument_array(wk, current_project(wk)->link_with[machine], l), d.link_with); } return true; } static bool add_languages(struct workspace *wk, uint32_t node, obj langs, obj compiler, enum machine_kind machine, enum requirement_type required, bool *missing) { obj val; obj_array_for(wk, langs, val) { bool found = false; if (!project_add_language(wk, node, val, compiler, machine, required, &found)) { return false; } if (!found) { *missing = true; } } return true; } static bool func_add_languages(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_native, kw_toolchain, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_native] = { "native", obj_bool }, [kw_toolchain] = { "toolchain", tc_compiler, .desc = "Instead of detecting a toolchain, use the compiler `toolchain` for this language.", .extension = true }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum requirement_type required; if (!coerce_requirement(wk, &akw[kw_required], &required)) { return false; } enum machine_kind machine = coerce_machine_kind(wk, &akw[kw_native]); bool missing = false; if (!add_languages(wk, an[0].node, an[0].val, akw[kw_toolchain].val, machine, required, &missing)) { return false; } if (!akw[kw_native].set) { bool _missing = false; if (!add_languages(wk, an[0].node, an[0].val, akw[kw_toolchain].val, machine_kind_build, requirement_auto, &_missing)) { return false; } } *res = make_obj_bool(wk, !missing); return true; } static bool func_files(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } return coerce_files(wk, 0, an[0].val, res); } struct find_program_custom_dir_ctx { const char *prog; struct tstr *buf; bool found; }; static enum iteration_result find_program_custom_dir_iter(struct workspace *wk, void *_ctx, obj val) { struct find_program_custom_dir_ctx *ctx = _ctx; path_join(wk, ctx->buf, get_cstr(wk, val), ctx->prog); if (fs_file_exists(ctx->buf->buf)) { ctx->found = true; return ir_done; } return ir_cont; } bool find_program_check_override(struct workspace *wk, struct find_program_ctx *ctx, obj prog) { obj override; if (!obj_dict_index(wk, wk->find_program_overrides[ctx->machine], prog, &override)) { return true; } obj override_version = 0, op; switch (get_obj_type(wk, override)) { case obj_array: op = obj_array_index(wk, override, 0); override_version = obj_array_index(wk, override, 1); break; case obj_python_installation: case obj_external_program: op = override; struct obj_external_program *ep = get_obj_external_program(wk, op); if (!ep->found) { return true; } if (ctx->version) { find_program_guess_version(wk, ep->cmd_array, ctx->version_argument, &override_version); } break; default: UNREACHABLE; } if (ctx->version && override_version) { if (!version_compare_list(wk, get_str(wk, override_version), ctx->version)) { return true; } } if (get_obj_type(wk, op) == obj_file) { obj newres; newres = make_obj(wk, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, newres); ep->found = true; ep->cmd_array = make_obj(wk, obj_array); obj_array_push(wk, ep->cmd_array, *get_obj_file(wk, op)); op = newres; } ctx->found = true; *ctx->res = op; return true; } static bool find_program_check_fallback(struct workspace *wk, struct find_program_ctx *ctx, obj prog) { obj fallback_arr, subproj_name; if (obj_dict_index(wk, current_project(wk)->wrap_provides_exes, prog, &fallback_arr)) { obj_array_flatten_one(wk, fallback_arr, &subproj_name); obj subproj; if (!(subproject(wk, subproj_name, requirement_auto, ctx->default_options, NULL, &subproj) && get_obj_subproject(wk, subproj)->found)) { return true; } if (!find_program_check_override(wk, ctx, prog)) { return false; } else if (!ctx->found) { obj _; if (!obj_dict_index(wk, wk->find_program_overrides[ctx->machine], prog, &_)) { vm_warning_at(wk, 0, "subproject %o claims to provide %o for the %s machine, but did not override it", subproj_name, prog, machine_kind_to_s(ctx->machine)); } } } return true; } bool find_program_check_version(struct workspace *wk, struct find_program_ctx *ctx, obj ver) { if (!ctx->version) { return true; } if (!ver) { return true; // no version to check against } if (!version_compare_list(wk, get_str(wk, ver), ctx->version)) { LO("version %o does not meet requirement: %o\n", ver, ctx->version); return false; } return true; } bool find_program(struct workspace *wk, struct find_program_ctx *ctx, obj prog) { const char *str; obj ver = 0; bool guessed_ver = false; obj cmd_array = 0; type_tag tc_allowed = tc_file | tc_string | tc_external_program | tc_python_installation; if (!typecheck(wk, ctx->node, prog, tc_allowed)) { return false; } enum obj_type t = get_obj_type(wk, prog); switch (t) { case obj_file: str = get_file_path(wk, prog); break; case obj_string: str = get_cstr(wk, prog); break; case obj_python_installation: prog = get_obj_python_installation(wk, prog)->prog; /* fallthrough */ case obj_external_program: if (get_obj_external_program(wk, prog)->found) { *ctx->res = prog; ctx->found = true; } return true; default: UNREACHABLE_RETURN; } const char *path = 0; TSTR(buf); struct find_program_custom_dir_ctx dir_ctx = { .buf = &buf, .prog = str, }; enum wrap_mode wrap_mode = 0; /* 0. Special case overrides, not skippable */ if (t == obj_string) { const bool is_meson = strcmp(str, "meson") == 0; const bool is_muon = !is_meson && strcmp(str, "muon") == 0; if (is_meson || is_muon) { const char *argv0_resolved; TSTR(argv0); if (fs_find_cmd(wk, &argv0, wk->argv0)) { argv0_resolved = argv0.buf; } else { argv0_resolved = wk->argv0; } obj ver = 0; cmd_array = make_obj(wk, obj_array); obj_array_push(wk, cmd_array, make_str(wk, argv0_resolved)); if (is_meson) { obj_array_push(wk, cmd_array, make_str(wk, "meson")); ver = make_str(wk, muon_version.meson_compat); } else { ver = make_str(wk, muon_version.version); } if (!find_program_check_version(wk, ctx, ver)) { return true; } *ctx->res = make_obj(wk, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *ctx->res); ep->found = true; ep->cmd_array = cmd_array; ep->ver = ver; ep->guessed_ver = true; ctx->found = true; return true; } } if (wk->vm.lang_mode == language_internal) { goto find_program_step_4; } /* 1. Program overrides set via meson.override_find_program() */ if (t == obj_string) { if (!find_program_check_override(wk, ctx, prog)) { return false; } if (ctx->found) { return true; } } /* 2. [provide] sections in subproject wrap files, if wrap_mode is set to forcefallback */ wrap_mode = get_option_wrap_mode(wk); if (t == obj_string && wrap_mode == wrap_mode_forcefallback) { if (!find_program_check_fallback(wk, ctx, prog)) { return false; } if (ctx->found) { return true; } } /* TODO: 3. [binaries] section in your machine files */ find_program_step_4: /* 4. Directories provided using the dirs: kwarg */ if (ctx->dirs) { obj_array_foreach(wk, ctx->dirs, &dir_ctx, find_program_custom_dir_iter); if (dir_ctx.found) { path = buf.buf; goto found; } } /* 5. Project's source tree relative to the current subdir */ /* If you use the return value of configure_file(), the current subdir inside the build tree is used instead */ path_join(wk, &buf, workspace_cwd(wk), str); if (fs_file_exists(buf.buf)) { path = buf.buf; goto found; } /* 6. PATH environment variable, and program specific environment variables */ { // TODO: avoid duplicating this // TODO: should this apply to compiler envvars, e.g. env.CC? const char *program_specific_envvar = 0; if (strcmp(str, "ninja") == 0) { program_specific_envvar = "env.NINJA"; } else if (strcmp(str, "ar") == 0) { program_specific_envvar = "env.AR"; } else if (strcmp(str, "pkg-config") == 0 || strcmp(str, "pkgconf") == 0) { program_specific_envvar = "env.PKG_CONFIG"; } if (program_specific_envvar) { get_option_value(wk, NULL, program_specific_envvar, &cmd_array); } } if (fs_find_cmd(wk, &buf, str)) { path = buf.buf; goto found; } if (wk->vm.lang_mode == language_internal) { goto find_program_step_8; } /* 7. [provide] sections in subproject wrap files, if wrap_mode is set to anything other than nofallback */ if (t == obj_string && wrap_mode != wrap_mode_nofallback && ctx->requirement == requirement_required) { if (!find_program_check_fallback(wk, ctx, prog)) { return false; } if (ctx->found) { return true; } } find_program_step_8: /* 8. Special cases, only if the binary was not found by regular means */ if (t == obj_string) { if (have_samurai && (strcmp(str, "ninja") == 0 || strcmp(str, "samu") == 0)) { obj ver = make_strf(wk, "%d.%d.0", samu_ninjamajor, samu_ninjaminor); if (!find_program_check_version(wk, ctx, ver)) { return true; } *ctx->res = make_obj(wk, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *ctx->res); ep->found = true; ep->cmd_array = make_obj(wk, obj_array); obj_array_push(wk, ep->cmd_array, make_str(wk, wk->argv0)); obj_array_push(wk, ep->cmd_array, make_str(wk, "samu")); ep->guessed_ver = true; ep->ver = ver; ctx->found = true; return true; } } return true; found: { if (!cmd_array) { cmd_array = make_obj(wk, obj_array); obj_array_push(wk, cmd_array, make_str(wk, path)); } if (ctx->version) { find_program_guess_version(wk, cmd_array, ctx->version_argument, &ver); guessed_ver = true; if (!find_program_check_version(wk, ctx, ver)) { return true; } } *ctx->res = make_obj(wk, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *ctx->res); ep->found = true; ep->cmd_array = cmd_array; ep->guessed_ver = guessed_ver; ep->ver = ver; ctx->found = true; return true; } } static bool func_find_program(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_string | tc_file }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_native, kw_disabler, kw_dirs, kw_version, kw_version_argument, kw_default_options, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_native] = { "native", obj_bool }, [kw_disabler] = { "disabler", obj_bool }, [kw_dirs] = { "dirs", TYPE_TAG_LISTIFY | obj_string }, [kw_version] = { "version", TYPE_TAG_LISTIFY | obj_string }, [kw_version_argument] = { "version_argument", obj_string }, [kw_default_options] = { "default_options", COMPLEX_TYPE_PRESET(tc_cx_options_dict_or_list) }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } if (requirement == requirement_skip) { if (akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val)) { *res = obj_disabler; } else { *res = make_obj(wk, obj_external_program); get_obj_external_program(wk, *res)->found = false; } return true; } struct find_program_ctx ctx = { .node = an[0].node, .version = akw[kw_version].val, .version_argument = akw[kw_version_argument].val, .dirs = akw[kw_dirs].val, .res = res, .requirement = requirement, .default_options = &akw[kw_default_options], .machine = coerce_machine_kind(wk, &akw[kw_native]), }; { obj val; obj_array_flat_for_(wk, an[0].val, val, flat_iter) { if (!find_program(wk, &ctx, val)) { obj_array_flat_iter_end(wk, &flat_iter); break; } else if (ctx.found) { obj_array_flat_iter_end(wk, &flat_iter); break; } } } if (!ctx.found) { if (requirement == requirement_required) { vm_error_at(wk, an[0].node, "program not found"); return false; } if (akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val)) { *res = obj_disabler; } else { *res = make_obj(wk, obj_external_program); get_obj_external_program(wk, *res)->found = false; } } return true; } static bool func_include_directories(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_coercible_inc }, ARG_TYPE_NULL }; enum kwargs { kw_is_system, }; struct args_kw akw[] = { [kw_is_system] = { "is_system", obj_bool }, 0 }; if (!pop_args(wk, an, akw)) { return false; } bool is_system = akw[kw_is_system].set ? get_obj_bool(wk, akw[kw_is_system].val) : false; if (!coerce_include_dirs(wk, an[0].node, an[0].val, is_system, res)) { return false; } return true; } static bool func_generator(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_exe }, ARG_TYPE_NULL }; enum kwargs { kw_output, kw_arguments, kw_capture, kw_depfile, kw_depends, }; struct args_kw akw[] = { [kw_output] = { "output", TYPE_TAG_LISTIFY | obj_string, .required = true }, [kw_arguments] = { "arguments", obj_array, .required = true }, [kw_capture] = { "capture", obj_bool }, [kw_depfile] = { "depfile", obj_string }, [kw_depends] = { "depends", tc_depends_kw }, 0 }; if (!pop_args(wk, an, akw)) { return false; } obj command; command = make_obj(wk, obj_array); obj_array_push(wk, command, an[0].val); obj_array_extend(wk, command, akw[kw_arguments].val); *res = make_obj(wk, obj_generator); struct obj_generator *gen = get_obj_generator(wk, *res); gen->output = akw[kw_output].val; gen->raw_command = command; gen->depfile = akw[kw_depfile].val; gen->capture = akw[kw_capture].set && get_obj_bool(wk, akw[kw_capture].val); if (akw[kw_depends].set) { obj depends; if (!coerce_files(wk, akw[kw_depends].node, akw[kw_depends].val, &depends)) { return false; } gen->depends = depends; } return true; } static bool func_assert(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_bool }, { obj_string, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = 0; if (!get_obj_bool(wk, an[0].val)) { if (an[1].set) { LOG_E("%s", get_cstr(wk, an[1].val)); } else { vm_error(wk, "assertion failed"); } return false; } return true; } static bool func_log_common(struct workspace *wk, enum log_level lvl) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } if (lvl == log_error) { struct source *src = 0; struct source_location loc = { 0 }; vm_lookup_inst_location(&wk->vm, wk->vm.ip - 1, &loc, &src); struct detailed_source_location dloc = { 0 }; get_detailed_source_location(src, loc, &dloc, get_detailed_source_location_flag_multiline); log_plain(lvl, "%s:%d:%d: ", src->label, dloc.line, dloc.col); } log_print(false, lvl, "%s", ""); obj val; obj_array_for(wk, an[0].val, val) { obj_lprintf(wk, lvl, "%#o ", val); } log_plain(lvl, "\n"); return true; } static bool func_debug(struct workspace *wk, obj _, obj *res) { return func_log_common(wk, log_debug); } static bool func_message(struct workspace *wk, obj _, obj *res) { return func_log_common(wk, log_note); } static bool func_error(struct workspace *wk, obj _, obj *res) { func_log_common(wk, log_error); return false; } static bool func_warning(struct workspace *wk, obj _, obj *res) { return func_log_common(wk, log_warn); } static bool func_print(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } log_plain(log_info, "%s", get_cstr(wk, an[0].val)); *res = 0; return true; } static bool func_run_command(struct workspace *wk, obj _, obj *res) { type_tag tc_allowed_an = tc_string | tc_file | tc_external_program | tc_compiler | tc_python_installation; struct args_norm an[] = { { TYPE_TAG_GLOB | tc_allowed_an }, ARG_TYPE_NULL }; enum kwargs { kw_check, kw_env, kw_capture, }; struct args_kw akw[] = { [kw_check] = { "check", obj_bool }, [kw_env] = { "env", tc_coercible_env }, [kw_capture] = { "capture", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } const char *argstr, *envstr; uint32_t argc, envc; { obj arg0; obj cmd_file = 0; struct find_program_ctx find_program_ctx = { .node = an[0].node, .res = &cmd_file, .requirement = requirement_auto, .machine = coerce_machine_kind(wk, 0), }; if (!get_obj_array(wk, an[0].val)->len) { vm_error(wk, "missing command"); return false; } arg0 = obj_array_index(wk, an[0].val, 0); if (get_obj_type(wk, arg0) == obj_compiler) { obj cmd_arr = get_obj_compiler(wk, arg0)->cmd_arr[toolchain_component_compiler]; obj tail; obj_array_tail(wk, an[0].val, &tail); obj_array_dup(wk, cmd_arr, &an[0].val); obj_array_extend_nodup(wk, an[0].val, tail); } else { if (!find_program(wk, &find_program_ctx, arg0)) { return false; } else if (!find_program_ctx.found) { vm_error(wk, "unable to find program %o", arg0); return false; } obj_array_set(wk, an[0].val, 0, cmd_file); } obj args; if (!arr_to_args(wk, arr_to_args_external_program, an[0].val, &args)) { return false; } if (wk->vm.lang_mode != language_internal) { workspace_add_regenerate_deps(wk, args); } join_args_argstr(wk, &argstr, &argc, args); } { obj env; if (!coerce_environment_from_kwarg(wk, &akw[kw_env], true, &env)) { return false; } env_to_envstr(wk, &envstr, &envc, env); } bool ret = false; struct run_cmd_ctx cmd_ctx = { .chdir = current_project(wk) ? get_cstr(wk, current_project(wk)->cwd) : 0, }; if (!run_cmd(&cmd_ctx, argstr, argc, envstr, envc)) { vm_error(wk, "%s", cmd_ctx.err_msg); if (cmd_ctx.out.len) { log_plain(log_info, "stdout:\n%s", cmd_ctx.out.buf); } if (cmd_ctx.err.len) { log_plain(log_info, "stderr:\n%s", cmd_ctx.err.buf); } goto ret; } if (akw[kw_check].set && get_obj_bool(wk, akw[kw_check].val) && cmd_ctx.status != 0) { vm_error(wk, "command failed"); if (cmd_ctx.out.len) { log_plain(log_info, "stdout:\n%s", cmd_ctx.out.buf); } if (cmd_ctx.err.len) { log_plain(log_info, "stderr:\n%s", cmd_ctx.err.buf); } goto ret; } *res = make_obj(wk, obj_run_result); struct obj_run_result *run_result = get_obj_run_result(wk, *res); run_result->status = cmd_ctx.status; if (akw[kw_capture].set && !get_obj_bool(wk, akw[kw_capture].val)) { run_result->out = make_str(wk, ""); run_result->err = make_str(wk, ""); } else { run_result->out = tstr_into_str(wk, &cmd_ctx.out); run_result->err = tstr_into_str(wk, &cmd_ctx.err); } ret = true; ret: run_cmd_ctx_destroy(&cmd_ctx); return ret; } static bool func_run_target(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_command, kw_depends, kw_env, }; struct args_kw akw[] = { [kw_command] = { "command", tc_command_array, .required = true }, [kw_depends] = { "depends", tc_depends_kw }, [kw_env] = { "env", tc_coercible_env }, 0 }; if (!pop_args(wk, an, akw)) { return false; } struct make_custom_target_opts opts = { .name = an[0].val, .command_node = akw[kw_command].node, .command_orig = akw[kw_command].val, }; if (!make_custom_target(wk, &opts, res)) { return false; } struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); tgt->flags |= custom_target_console; if (akw[kw_depends].set) { obj depends; if (!coerce_files(wk, akw[kw_depends].node, akw[kw_depends].val, &depends)) { return false; } obj_array_extend_nodup(wk, tgt->depends, depends); } if (!coerce_environment_from_kwarg(wk, &akw[kw_env], true, &tgt->env)) { return false; } L("adding run target '%s'", get_cstr(wk, tgt->name)); obj_array_push(wk, current_project(wk)->targets, *res); return true; } static enum iteration_result subdir_if_found_iter(struct workspace *wk, void *_ctx, obj v) { struct obj_dependency *dep = get_obj_dependency(wk, v); bool *all_found = _ctx; if (!(dep->flags & dep_flag_found)) { *all_found = false; return ir_done; } return ir_cont; } static bool func_subdir(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_if_found, }; struct args_kw akw[] = { [kw_if_found] = { "if_found", TYPE_TAG_LISTIFY | tc_dependency }, 0 }; if (!pop_args(wk, an, akw)) { return false; } if (akw[kw_if_found].set && !wk->vm.in_analyzer) { bool all_found = true; obj_array_foreach(wk, akw[kw_if_found].val, &all_found, subdir_if_found_iter); if (!all_found) { return true; } } TSTR(build_dir); obj old_cwd = current_project(wk)->cwd; obj old_build_dir = current_project(wk)->build_dir; TSTR(new_cwd); path_join(wk, &new_cwd, get_cstr(wk, old_cwd), get_cstr(wk, an[0].val)); current_project(wk)->cwd = tstr_into_str(wk, &new_cwd); path_join(wk, &build_dir, get_cstr(wk, old_build_dir), get_cstr(wk, an[0].val)); current_project(wk)->build_dir = tstr_into_str(wk, &build_dir); bool ret = false; if (!wk->vm.in_analyzer) { if (!fs_mkdir_p(build_dir.buf)) { goto ret; } } wk->vm.dbg_state.eval_trace_subdir = true; { enum build_language lang; const char *build_file = determine_build_file(wk, new_cwd.buf, &lang, false); if (!build_file) { goto ret; } ret = wk->vm.behavior.eval_project_file(wk, build_file, lang, 0, 0); } ret: current_project(wk)->cwd = old_cwd; current_project(wk)->build_dir = old_build_dir; return ret; } static bool func_configuration_data(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_dict, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj(wk, obj_configuration_data); if (an[0].set) { get_obj_configuration_data(wk, *res)->dict = an[0].val; } else { obj dict; dict = make_obj(wk, obj_dict); get_obj_configuration_data(wk, *res)->dict = dict; } return true; } static bool func_add_test_setup(struct workspace *wk, obj _, obj *ret) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_env, kw_exclude_suites, kw_exe_wrapper, kw_gdb, kw_is_default, kw_timeout_multiplier, }; struct args_kw akw[] = { [kw_env] = { "env", tc_coercible_env, }, [kw_exclude_suites] = { "exclude_suites", TYPE_TAG_LISTIFY | obj_string }, [kw_exe_wrapper] = { "exe_wrapper", tc_command_array }, [kw_gdb] = { "gdb", obj_bool }, [kw_is_default] = { "is_default", obj_bool }, [kw_timeout_multiplier] = { "timeout_multiplier", obj_number }, 0 }; if (!pop_args(wk, an, akw)) { return false; } obj test_setup; test_setup = make_obj(wk, obj_array); obj env = 0; if (akw[kw_env].set && !coerce_environment_from_kwarg(wk, &akw[kw_env], false, &env)) { return false; } obj exe_wrapper = 0; if (akw[kw_exe_wrapper].set && !arr_to_args(wk, arr_to_args_build_target | arr_to_args_custom_target | arr_to_args_external_program, akw[kw_exe_wrapper].val, &exe_wrapper)) { return false; } /* [name, env, exclude_suites, exe_wrapper, is_default, timeout_multiplier] */ obj_array_push(wk, test_setup, an[0].val); obj_array_push(wk, test_setup, env); obj_array_push(wk, test_setup, akw[kw_exclude_suites].val); obj_array_push(wk, test_setup, exe_wrapper); obj_array_push(wk, test_setup, akw[kw_is_default].val); obj_array_push(wk, test_setup, akw[kw_timeout_multiplier].val); if (!current_project(wk)->test_setups) { current_project(wk)->test_setups = make_obj(wk, obj_array); } obj_array_push(wk, current_project(wk)->test_setups, test_setup); return true; } struct add_test_depends_ctx { struct obj_test *t; bool from_custom_tgt; }; static enum iteration_result add_test_depends_iter(struct workspace *wk, void *_ctx, obj val) { TSTR(rel); struct add_test_depends_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_string: case obj_external_program: case obj_python_installation: break; case obj_file: if (!ctx->from_custom_tgt) { break; } path_relative_to(wk, &rel, wk->build_root, get_file_path(wk, val)); obj_array_push(wk, ctx->t->depends, tstr_into_str(wk, &rel)); break; case obj_both_libs: { add_test_depends_iter(wk, ctx, get_obj_both_libs(wk, val)->dynamic_lib); add_test_depends_iter(wk, ctx, get_obj_both_libs(wk, val)->static_lib); break; } case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, val); path_relative_to(wk, &rel, wk->build_root, get_cstr(wk, tgt->build_path)); obj_array_push(wk, ctx->t->depends, tstr_into_str(wk, &rel)); break; } case obj_custom_target: ctx->from_custom_tgt = true; if (!obj_array_foreach(wk, get_obj_custom_target(wk, val)->output, ctx, add_test_depends_iter)) { return ir_err; } ctx->from_custom_tgt = false; break; default: UNREACHABLE; } return ir_cont; } static bool add_test_common(struct workspace *wk, enum test_category cat) { type_tag tc_allowed_an = tc_build_target | tc_external_program | tc_file | tc_python_installation | tc_custom_target; struct args_norm an[] = { { obj_string }, { tc_allowed_an }, ARG_TYPE_NULL }; enum kwargs { kw_args, kw_workdir, kw_depends, kw_should_fail, kw_env, kw_suite, kw_priority, kw_timeout, kw_protocol, kw_is_parallel, kw_verbose, }; struct args_kw akw[] = { [kw_args] = { "args", tc_command_array, }, [kw_workdir] = { "workdir", obj_string, }, [kw_depends] = { "depends", tc_depends_kw, }, [kw_should_fail] = { "should_fail", obj_bool, }, [kw_env] = { "env", tc_coercible_env, }, [kw_suite] = { "suite", TYPE_TAG_LISTIFY | obj_string }, [kw_priority] = { "priority", obj_number, }, [kw_timeout] = { "timeout", obj_number, }, [kw_protocol] = { "protocol", obj_string, }, [kw_is_parallel] = { 0 }, [kw_verbose] = { "verbose", obj_bool }, 0 }; if (cat == test_category_test) { akw[kw_is_parallel] = (struct args_kw){ "is_parallel", obj_bool, }; } if (!pop_args(wk, an, akw)) { return false; } enum test_protocol protocol = test_protocol_exitcode; if (akw[kw_protocol].set) { const char *protocol_names[] = { [test_protocol_exitcode] = "exitcode", [test_protocol_tap] = "tap", [test_protocol_gtest] = "gtest", [test_protocol_rust] = "rust", }; for (protocol = 0; (uint32_t)protocol < ARRAY_LEN(protocol_names); ++protocol) { if (str_eql(get_str(wk, akw[kw_protocol].val), &STRL(protocol_names[protocol]))) { break; } } if (protocol == ARRAY_LEN(protocol_names)) { vm_error_at(wk, akw[kw_protocol].node, "invalid protocol %o", akw[kw_protocol].val); return false; } if (protocol == test_protocol_gtest || protocol == test_protocol_rust) { vm_warning_at(wk, akw[kw_protocol].node, "unsupported protocol %o, falling back to 'exitcode'", akw[kw_protocol].val); protocol = test_protocol_exitcode; } } obj exe, exe_args = 0; if (!coerce_executable(wk, an[1].node, an[1].val, &exe, &exe_args)) { return false; } obj args = exe_args; if (akw[kw_args].set) { if (!arr_to_args(wk, arr_to_args_build_target | arr_to_args_custom_target | arr_to_args_external_program, akw[kw_args].val, &args)) { return false; } if (exe_args) { obj_array_extend_nodup(wk, exe_args, args); args = exe_args; } } obj test; test = make_obj(wk, obj_test); struct obj_test *t = get_obj_test(wk, test); if (!coerce_environment_from_kwarg(wk, &akw[kw_env], false, &t->env)) { return false; } t->name = an[0].val; t->exe = exe; t->args = args; t->should_fail = akw[kw_should_fail].set && get_obj_bool(wk, akw[kw_should_fail].val); t->suites = akw[kw_suite].val; t->workdir = akw[kw_workdir].val; t->timeout = akw[kw_timeout].val; t->priority = akw[kw_priority].val; t->category = cat; t->protocol = protocol; t->verbose = akw[kw_verbose].set && get_obj_bool(wk, akw[kw_verbose].val); if (akw[kw_is_parallel].key) { t->is_parallel = akw[kw_is_parallel].set ? get_obj_bool(wk, akw[kw_is_parallel].val) : true; } struct add_test_depends_ctx deps_ctx = { .t = t }; t->depends = make_obj(wk, obj_array); add_test_depends_iter(wk, &deps_ctx, an[1].val); if (akw[kw_depends].set) { obj_array_foreach(wk, akw[kw_depends].val, &deps_ctx, add_test_depends_iter); } if (akw[kw_args].set) { obj_array_foreach(wk, akw[kw_args].val, &deps_ctx, add_test_depends_iter); } obj_array_push(wk, current_project(wk)->tests, test); return true; } static bool func_test(struct workspace *wk, obj _, obj *ret) { return add_test_common(wk, test_category_test); } static bool func_benchmark(struct workspace *wk, obj _, obj *ret) { return add_test_common(wk, test_category_benchmark); } struct join_paths_ctx { struct tstr *buf; }; static enum iteration_result join_paths_iter(struct workspace *wk, void *_ctx, obj val) { struct join_paths_ctx *ctx = _ctx; if (!typecheck(wk, 0, val, obj_string)) { return ir_err; } path_push(wk, ctx->buf, get_cstr(wk, val)); return ir_cont; } static bool func_join_paths(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } TSTR(join_paths_buf); struct join_paths_ctx ctx = { .buf = &join_paths_buf, }; if (!obj_array_foreach_flat(wk, an[0].val, &ctx, join_paths_iter)) { return false; } *res = tstr_into_str(wk, ctx.buf); return true; } static bool func_environment(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { make_complex_type(wk, complex_type_or, make_complex_type(wk, complex_type_or, tc_string, make_complex_type(wk, complex_type_nested, tc_array, tc_string)), make_complex_type(wk, complex_type_nested, tc_dict, tc_string)), .optional = true }, ARG_TYPE_NULL }; enum kwargs { kw_method, kw_separator, }; struct args_kw akw[] = { [kw_method] = { "method", tc_string }, [kw_separator] = { "separator", tc_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum environment_set_mode mode = environment_set_mode_set; if (akw[kw_method].set) { const struct str modes[] = { [environment_set_mode_set] = STR("set"), [environment_set_mode_append] = STR("append"), [environment_set_mode_prepend] = STR("prepend"), }, *method = get_str(wk, akw[kw_method].val); uint32_t i; for (i = 0; i < ARRAY_LEN(modes); ++i) { if (str_eql(method, &modes[i])) { break; } } if (i >= ARRAY_LEN(modes)) { vm_error_at(wk, akw[kw_method].node, "invalid method: %o", akw[kw_method].val); return false; } mode = i; } *res = make_obj(wk, obj_environment); struct obj_environment *d = get_obj_environment(wk, *res); d->actions = make_obj(wk, obj_array); if (an[0].set) { obj dict; if (!coerce_key_value_dict(wk, an[0].node, an[0].val, &dict)) { return false; } obj key, val; obj_dict_for(wk, dict, key, val) { if (!environment_set(wk, *res, mode, key, val, akw[kw_separator].val)) { return false; } } } return true; } static bool func_import(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_disabler, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_disabler] = { "disabler", obj_bool }, 0 }; if (!pop_args(wk, an, akw)) { return false; } if (wk->vm.in_analyzer) { // If we are in the analyzer, don't create a disabler here so // that the custom not found module logic can be used akw[kw_disabler].set = false; } enum requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } bool found = false; if (requirement == requirement_skip) { *res = make_obj(wk, obj_module); } else { if (module_import(wk, get_cstr(wk, an[0].val), true, res)) { found = true; } else if (requirement == requirement_required) { vm_error_at(wk, an[0].node, "module not found"); return false; } } struct obj_module *m = get_obj_module(wk, *res); if (!m->has_impl) { if (requirement != requirement_required || wk->vm.in_analyzer) { found = false; } else { LOG_W("importing unimplemented module '%s'", get_cstr(wk, an[0].val)); } } if (!found && akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val)) { *res = obj_disabler; return true; } return true; } static bool func_is_disabler(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } UNREACHABLE_RETURN; } static bool func_disabler(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } UNREACHABLE_RETURN; } static bool func_set_variable(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, { tc_any }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } UNREACHABLE_RETURN; } static bool func_unset_variable(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const char *varname = get_cstr(wk, an[0].val); obj _val; if (wk->vm.behavior.get_variable(wk, varname, &_val)) { wk->vm.behavior.unassign_variable(wk, varname); } else { vm_error_at(wk, an[0].node, "cannot unset undefined variable: %o", an[0].val); return false; } return true; } static bool func_get_variable(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_any }, { tc_any, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } UNREACHABLE_RETURN; } static bool func_is_variable(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj dont_care; *res = make_obj_bool(wk, wk->vm.behavior.get_variable(wk, get_cstr(wk, an[0].val), &dont_care)); return true; } static bool func_subdir_done(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } UNREACHABLE_RETURN; } static void summary_push_kv(struct workspace *wk, obj dest, obj k, obj v, obj attr) { obj wrapped_v = make_obj(wk, obj_array); obj_array_push(wk, wrapped_v, attr); obj_array_push(wk, wrapped_v, v); obj_dict_set(wk, dest, k, wrapped_v); } static bool func_summary(struct workspace *wk, obj _, obj *res) { type_tag value_base_type = tc_number | tc_bool | tc_string | tc_external_program | tc_dependency | tc_feature_opt; type_tag value_type = make_complex_type(wk, complex_type_or, value_base_type, make_complex_type(wk, complex_type_nested, tc_array, value_base_type)); type_tag dict_type = make_complex_type(wk, complex_type_nested, tc_dict, value_type); struct args_norm an[] = { { make_complex_type(wk, complex_type_or, tc_string, dict_type) }, { value_type, .optional = true }, ARG_TYPE_NULL }; enum kwargs { kw_section, kw_bool_yn, kw_list_sep, }; struct args_kw akw[] = { [kw_section] = { "section", obj_string, }, [kw_bool_yn] = { "bool_yn", obj_bool, }, [kw_list_sep] = { "list_sep", obj_string, }, 0 }; if (!pop_args(wk, an, akw)) { return false; } obj attr = 0; if (akw[kw_list_sep].val) { if (!attr) { attr = make_obj(wk, obj_dict); } obj_dict_set(wk, attr, make_str(wk, "list_sep"), akw[kw_list_sep].val); } if (akw[kw_bool_yn].val) { if (!attr) { attr = make_obj(wk, obj_dict); } obj_dict_set(wk, attr, make_str(wk, "bool_yn"), akw[kw_bool_yn].val); } obj section = akw[kw_section].set ? akw[kw_section].val : make_str(wk, ""); obj dest; if (!obj_dict_index(wk, current_project(wk)->summary, section, &dest)) { dest = make_obj(wk, obj_dict); obj_dict_set(wk, current_project(wk)->summary, section, dest); } if (an[1].set) { if (!typecheck(wk, an[0].node, an[0].val, tc_string)) { return false; } summary_push_kv(wk, dest, an[0].val, an[1].val, attr); } else { if (!typecheck(wk, an[0].node, an[0].val, dict_type)) { return false; } obj k, v; obj_dict_for(wk, an[0].val, k, v) { summary_push_kv(wk, dest, k, v, attr); } } return true; } static obj make_alias_target(struct workspace *wk, obj name, obj deps) { assert(get_obj_type(wk, name) == obj_string && "Alias target name must be a string."); assert(get_obj_type(wk, deps) == obj_array && "Alias target list must be an array."); obj id; id = make_obj(wk, obj_alias_target); struct obj_alias_target *alias_tgt = get_obj_alias_target(wk, id); alias_tgt->name = name; alias_tgt->depends = deps; return id; } struct alias_target_iter_ctx { obj deps; }; static enum iteration_result push_alias_target_deps_iter(struct workspace *wk, void *_ctx, obj val) { struct alias_target_iter_ctx *ctx = _ctx; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_both_libs: { push_alias_target_deps_iter(wk, ctx, get_obj_both_libs(wk, val)->dynamic_lib); push_alias_target_deps_iter(wk, ctx, get_obj_both_libs(wk, val)->static_lib); break; } case obj_alias_target: case obj_build_target: case obj_custom_target: obj_array_push(wk, ctx->deps, val); break; default: vm_error_at(wk, val, "expected target but got: %s", obj_type_to_s(t)); return ir_err; } return ir_cont; } static bool func_alias_target(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, { TYPE_TAG_GLOB | tc_build_target | tc_custom_target | tc_alias_target | tc_both_libs }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } L("adding alias target '%s'", get_cstr(wk, an[0].val)); obj deps_id; deps_id = make_obj(wk, obj_array); struct alias_target_iter_ctx iter_ctx = { .deps = deps_id, }; if (!obj_array_foreach_flat(wk, an[1].val, &iter_ctx, push_alias_target_deps_iter)) { return false; } *res = make_alias_target(wk, an[0].val, deps_id); obj_array_push(wk, current_project(wk)->targets, *res); return true; } static bool func_range(struct workspace *wk, obj _, obj *res) { struct range_params params; struct args_norm an[] = { { obj_number }, { obj_number, .optional = true }, { obj_number, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, 0)) { return false; } int64_t n = get_obj_number(wk, an[0].val); if (!rangecheck(wk, an[0].node, 0, UINT32_MAX, n)) { return false; } params.start = n; if (an[1].set) { int64_t n = get_obj_number(wk, an[1].val); if (!rangecheck(wk, an[1].node, params.start, UINT32_MAX, n)) { return false; } params.stop = n; } else { params.stop = params.start; params.start = 0; } if (an[2].set) { int64_t n = get_obj_number(wk, an[2].val); if (!rangecheck(wk, an[2].node, 1, UINT32_MAX, n)) { return false; } params.step = n; } else { params.step = 1; } *res = make_obj(wk, obj_iterator); struct obj_iterator *iter = get_obj_iterator(wk, *res); iter->type = obj_iterator_type_range; iter->data.range = params; return true; } /* * muon extension funcitons */ static bool func_p(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_any | TYPE_TAG_ALLOW_NULL }, ARG_TYPE_NULL }; enum kwargs { kw_inspect, kw_pretty, }; struct args_kw akw[] = { [kw_inspect] = { "inspect", tc_bool }, [kw_pretty] = { "pretty", tc_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (akw[kw_inspect].set && get_obj_bool(wk, akw[kw_inspect].val)) { obj_inspect(wk, an[0].val); } else { const char *fmt = akw[kw_pretty].set && get_obj_bool(wk, akw[kw_pretty].val) ? "%#o\n" : "%o\n"; obj_lprintf(wk, log_info, fmt, an[0].val); } *res = an[0].val; return true; } static bool func_serial_load(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj str; coerce_string(wk, an[0].node, an[0].val, &str); FILE *f; if (str_eql(get_str(wk, str), &STR("-"))) { f = stdin; } else if (!(f = fs_fopen(get_cstr(wk, str), "rb"))) { return false; } bool ret = false; if (!serial_load(wk, res, f)) { goto ret; } if (!fs_fclose(f)) { goto ret; } ret = true; ret: return ret; } static bool func_serial_dump(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { tc_any }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj str; coerce_string(wk, an[0].node, an[0].val, &str); FILE *f; if (!(f = fs_fopen(get_cstr(wk, str), "wb"))) { return false; } bool ret = false; if (!serial_dump(wk, an[1].val, f)) { goto ret; } if (!fs_fclose(f)) { goto ret; } ret = true; ret: return ret; } static bool func_is_null(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_ALLOW_NULL | tc_any }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } if (get_obj_type(wk, an[0].val) == obj_null) { *res = obj_bool_true; } else { *res = obj_bool_false; } return true; } static bool func_typeof(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_ALLOW_NULL | tc_any }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_str(wk, obj_type_to_s(get_obj_type(wk, an[0].val))); return true; } static bool func_exit(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_number }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } exit(get_obj_number(wk, an[0].val)); return true; } static bool func_create_enum(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_string, .desc = "The value for this enum" }, { TYPE_TAG_LISTIFY | tc_string, "The list of possible values for this enum" }, ARG_TYPE_NULL, }; if (!pop_args(wk, an, NULL)) { return false; } const struct str *s = get_str(wk, an[0].val); if (!obj_array_in(wk, an[1].val, an[0].val)) { vm_error_at(wk, an[0].node, "value %o not in list of values", an[0].val); return false; } *res = make_strn_enum(wk, s->s, s->len, an[1].val); return true; } // clang-format off const struct func_impl impl_tbl_kernel[] = { { "add_global_arguments", func_add_global_arguments }, { "add_global_link_arguments", func_add_global_link_arguments }, { "add_languages", func_add_languages, tc_bool }, { "add_project_arguments", func_add_project_arguments }, { "add_project_dependencies", func_add_project_dependencies }, { "add_project_link_arguments", func_add_project_link_arguments }, { "add_test_setup", func_add_test_setup }, { "alias_target", func_alias_target, tc_alias_target }, { "assert", func_assert, .flags = func_impl_flag_throws_error }, { "benchmark", func_benchmark }, { "both_libraries", func_both_libraries, tc_both_libs }, { "build_target", func_build_target, tc_build_target | tc_both_libs }, { "configuration_data", func_configuration_data, tc_configuration_data }, { "configure_file", func_configure_file, tc_file }, { "custom_target", func_custom_target, tc_custom_target }, { "debug", func_debug }, { "declare_dependency", func_declare_dependency, tc_dependency }, { "dependency", func_dependency, tc_dependency, true }, { "disabler", func_disabler, tc_disabler }, { "environment", func_environment, tc_environment }, { "error", func_error, .flags = func_impl_flag_throws_error }, { "executable", func_executable, tc_build_target }, { "files", func_files, tc_array }, { "find_program", func_find_program, tc_external_program }, { "generator", func_generator, tc_generator }, { "get_option", func_get_option, tc_string | tc_number | tc_bool | tc_feature_opt | tc_array, true, }, { "get_variable", func_get_variable, tc_any, true }, { "import", func_import, tc_module, true }, { "include_directories", func_include_directories, tc_array }, { "install_data", func_install_data }, { "install_emptydir", func_install_emptydir }, { "install_headers", func_install_headers }, { "install_man", func_install_man }, { "install_subdir", func_install_subdir }, { "install_symlink", func_install_symlink }, { "is_disabler", func_is_disabler, tc_bool, true }, { "is_variable", func_is_variable, tc_bool, true }, { "join_paths", func_join_paths, tc_string, true }, { "library", func_library, tc_build_target | tc_both_libs }, { "message", func_message }, { "project", func_project, 0, true }, // Not really pure but partially runs { "range", func_range, tc_array, true }, { "run_command", func_run_command, tc_run_result }, { "run_target", func_run_target, tc_custom_target }, { "set_variable", func_set_variable, 0, true }, { "shared_library", func_shared_library, tc_build_target }, { "shared_module", func_shared_module, tc_build_target }, { "static_library", func_static_library, tc_build_target }, { "subdir", func_subdir, 0, true }, { "subdir_done", func_subdir_done }, { "subproject", func_subproject, tc_subproject, true }, // Not really pure but partially runs { "summary", func_summary }, { "test", func_test }, { "unset_variable", func_unset_variable, 0, true }, { "vcs_tag", func_vcs_tag, tc_custom_target }, { "warning", func_warning }, // non-standard muon extensions { "p", func_p, tc_any, true, .flags = func_impl_flag_extension }, { NULL, NULL }, }; const struct func_impl impl_tbl_kernel_internal[] = { { "assert", func_assert, .flags = func_impl_flag_throws_error }, { "configure_file", func_configure_file, tc_file }, { "configuration_data", func_configuration_data, tc_configuration_data }, { "disabler", func_disabler, tc_disabler }, { "environment", func_environment, tc_environment }, { "error", func_error, .flags = func_impl_flag_throws_error }, { "files", func_files, tc_array }, { "find_program", func_find_program, tc_external_program }, { "get_variable", func_get_variable, tc_any, true }, { "import", func_import, tc_module, true }, { "is_disabler", func_is_disabler, tc_bool, true }, { "is_variable", func_is_variable, tc_bool, true }, { "join_paths", func_join_paths, tc_string, true }, { "message", func_message }, { "range", func_range, tc_array, true }, { "run_command", func_run_command, tc_run_result, .flags = func_impl_flag_sandbox_disable }, { "set_variable", func_set_variable, 0, true }, { "unset_variable", func_unset_variable, 0, true }, { "warning", func_warning }, // non-standard muon extensions { "p", func_p, tc_any, true }, { "print", func_print, tc_any }, { "serial_load", func_serial_load, tc_any }, { "serial_dump", func_serial_dump, .flags = func_impl_flag_sandbox_disable }, { "is_null", func_is_null, tc_bool, true }, { "typeof", func_typeof, tc_string, true }, { "exit", func_exit }, { "create_enum", func_create_enum, tc_string, true, .desc = "Create a string enum. The resulting string will warn if it is compared against a value that it can never contain." }, { NULL, NULL }, }; const struct func_impl impl_tbl_kernel_opts[] = { { "option", func_option, 0, true }, // non-standard muon extensions { "p", func_p, tc_any }, { NULL, NULL }, }; muon-v0.5.0/src/functions/subproject.c0000644000175000017500000000342015041716357016773 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/subproject.h" #include "lang/analyze.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" bool subproject_get_variable(struct workspace *wk, uint32_t node, obj name_id, obj fallback, obj subproj, obj *res) { const char *name = get_cstr(wk, name_id); struct obj_subproject *sub = get_obj_subproject(wk, subproj); if (!sub->found) { if (wk->vm.in_analyzer) { *res = make_typeinfo(wk, tc_any); return true; } else { vm_error_at(wk, node, "subproject was not found"); } return false; } bool ok = true; stack_push(&wk->stack, wk->vm.scope_stack, ((struct project *)arr_get(&wk->projects, sub->id))->scope_stack); if (!wk->vm.behavior.get_variable(wk, name, res)) { if (!fallback) { ok = false; } else { *res = fallback; } } stack_pop(&wk->stack, wk->vm.scope_stack); return ok; } static bool func_subproject_get_variable(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { tc_any, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } if (!subproject_get_variable(wk, an[0].node, an[0].val, an[1].val, self, res)) { vm_error_at(wk, an[0].node, "subproject does not define '%s'", get_cstr(wk, an[0].val)); return false; } return true; } static bool func_subproject_found(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, get_obj_subproject(wk, self)->found); return true; } const struct func_impl impl_tbl_subproject[] = { { "found", func_subproject_found, tc_bool }, { "get_variable", func_subproject_get_variable, tc_any, true }, { NULL, NULL }, }; muon-v0.5.0/src/functions/run_result.c0000644000175000017500000000406615041716357017024 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/func_lookup.h" #include "functions/run_result.h" #include "lang/typecheck.h" static bool ensure_valid_run_result(struct workspace *wk, obj self) { struct obj_run_result *rr = get_obj_run_result(wk, self); if ((rr->flags & run_result_flag_from_compile) && !(rr->flags & run_result_flag_compile_ok)) { vm_error(wk, "this run_result was not run because its source could not be compiled"); return false; } return true; } static bool func_run_result_returncode(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } if (!ensure_valid_run_result(wk, self)) { return false; } *res = make_obj(wk, obj_number); set_obj_number(wk, *res, get_obj_run_result(wk, self)->status); return true; } static bool func_run_result_stdout(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } if (!ensure_valid_run_result(wk, self)) { return false; } *res = get_obj_run_result(wk, self)->out; return true; } static bool func_run_result_stderr(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } if (!ensure_valid_run_result(wk, self)) { return false; } *res = get_obj_run_result(wk, self)->err; return true; } static bool func_run_result_compiled(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct obj_run_result *rr = get_obj_run_result(wk, self); if (!(rr->flags & run_result_flag_from_compile)) { vm_error(wk, "this run_result is not from a compiler.run() call"); return false; } *res = make_obj_bool(wk, rr->flags & run_result_flag_compile_ok); return true; } const struct func_impl impl_tbl_run_result[] = { { "compiled", func_run_result_compiled, tc_bool }, { "returncode", func_run_result_returncode, tc_number }, { "stderr", func_run_result_stderr, tc_string }, { "stdout", func_run_result_stdout, tc_string }, { NULL, NULL }, }; muon-v0.5.0/src/functions/string.c0000644000175000017500000003173215041716357016130 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Harley Swick * SPDX-FileCopyrightText: Eli Schwartz * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "buf_size.h" #include "coerce.h" #include "error.h" #include "functions/string.h" #include "lang/func_lookup.h" #include "lang/lexer.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "platform/assert.h" #include "rpmvercmp.h" #include "util.h" static bool func_strip(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = str_strip(wk, get_str(wk, self), an[0].set ? get_str(wk, an[0].val) : NULL, 0); return true; } static bool func_to_upper(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = str_clone_mutable(wk, self); const struct str *ss = get_str(wk, *res); uint32_t i; for (i = 0; i < ss->len; ++i) { if ('a' <= ss->s[i] && ss->s[i] <= 'z') { ((char *)ss->s)[i] -= 32; } } return true; } static bool func_to_lower(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = str_clone_mutable(wk, self); const struct str *ss = get_str(wk, *res); uint32_t i; for (i = 0; i < ss->len; ++i) { if ('A' <= ss->s[i] && ss->s[i] <= 'Z') { ((char *)ss->s)[i] += 32; } } return true; } bool string_format(struct workspace *wk, uint32_t err_node, obj str, obj *res, void *ctx, string_format_cb cb) { const struct str *ss_in = get_str(wk, str); struct str key, text = { .s = ss_in->s }; obj elem; uint32_t i; bool reading_id = false; *res = make_str(wk, ""); for (i = 0; i < ss_in->len; ++i) { if (reading_id) { key.len = &ss_in->s[i] - key.s; if (ss_in->s[i] == '@') { switch (cb(wk, err_node, ctx, &key, &elem)) { case format_cb_not_found: { vm_error(wk, "key '%.*s' not found", key.len, key.s); return false; } case format_cb_error: return false; case format_cb_found: { obj coerced; if (!coerce_string(wk, err_node, elem, &coerced)) { return false; } str_apps(wk, res, coerced); text.s = &ss_in->s[i + 1]; break; } case format_cb_skip: { str_appn(wk, res, key.s - 1, key.len + 1); text.s = &ss_in->s[i]; --i; break; } } reading_id = false; } else if (!is_valid_inside_of_identifier(ss_in->s[i])) { str_appn(wk, res, key.s - 1, key.len + 1); text.s = &ss_in->s[i]; reading_id = false; } } else if (ss_in->s[i] == '@' && is_valid_inside_of_identifier(ss_in->s[i + 1])) { text.len = &ss_in->s[i] - text.s; str_appn(wk, res, text.s, text.len); text.s = &ss_in->s[i]; reading_id = true; key.s = &ss_in->s[i + 1]; } else if (ss_in->s[i] == '\\' && ss_in->s[i + 1] == '@') { text.len = &ss_in->s[i] - text.s; str_appn(wk, res, text.s, text.len); text.s = &ss_in->s[i + 1]; ++i; } } text.len = &ss_in->s[i] - text.s; str_appn(wk, res, text.s, text.len); if (reading_id) { vm_warning(wk, "unclosed @"); } return true; } struct func_format_ctx { obj arr; }; static enum format_cb_result func_format_cb(struct workspace *wk, uint32_t node, void *_ctx, const struct str *key, uint32_t *elem) { struct func_format_ctx *ctx = _ctx; int64_t i; if (!str_to_i(key, &i, false)) { return format_cb_skip; } if (!boundscheck(wk, node, get_obj_array(wk, ctx->arr)->len, &i)) { return format_cb_error; } *elem = obj_array_index(wk, ctx->arr, i); return format_cb_found; } static bool func_format(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } struct func_format_ctx ctx = { .arr = an[0].val, }; obj str; if (!string_format(wk, an[0].node, self, &str, &ctx, func_format_cb)) { return false; } *res = str; return true; } static bool func_underscorify(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = str_clone_mutable(wk, self); const struct str *ss = get_str(wk, *res); uint32_t i; for (i = 0; i < ss->len; ++i) { if (!(('a' <= ss->s[i] && ss->s[i] <= 'z') || ('A' <= ss->s[i] && ss->s[i] <= 'Z') || ('0' <= ss->s[i] && ss->s[i] <= '9'))) { ((char *)ss->s)[i] = '_'; } } return true; } static bool func_split(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const struct str *split = an[0].set ? get_str(wk, an[0].val) : NULL, *ss = get_str(wk, self); *res = str_split(wk, ss, split); return true; } static bool func_splitlines(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, 0, 0)) { return false; } *res = str_splitlines(wk, get_str(wk, self)); return true; } static bool func_join(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } return obj_array_join(wk, true, an[0].val, self, res); } bool version_compare(const struct str *ver1, const struct str *_ver2) { struct str ver2 = *_ver2; enum op_type { op_ge, op_gt, op_eq, op_ne, op_le, op_lt, }; enum op_type op = op_eq; struct { const struct str name; enum op_type op; } ops[] = { { STR(">="), op_ge }, { STR(">"), op_gt }, { STR("=="), op_eq }, { STR("!="), op_ne }, { STR("<="), op_le }, { STR("<"), op_lt }, { STR("="), op_eq }, }; uint32_t i; for (i = 0; i < ARRAY_LEN(ops); ++i) { if (str_startswith(&ver2, &ops[i].name)) { op = ops[i].op; ver2.s += ops[i].name.len; ver2.len -= ops[i].name.len; break; } } int8_t cmp = rpmvercmp(ver1, &ver2); switch (op) { case op_eq: return cmp == 0; break; case op_ne: return cmp != 0; break; case op_gt: return cmp == 1; break; case op_ge: return cmp >= 0; break; case op_lt: return cmp == -1; break; case op_le: return cmp <= 0; break; default: UNREACHABLE_RETURN; } } bool version_compare_list(struct workspace *wk, const struct str *ver, obj cmp_arr) { obj o; obj_array_for(wk, cmp_arr, o) { if (!version_compare(ver, get_str(wk, o))) { return false; } } return true; } static bool func_version_compare(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } bool matches = version_compare(get_str(wk, self), get_str(wk, an[0].val)); *res = make_obj_bool(wk, matches); return true; } static bool func_string_to_int(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } const struct str *ss = get_str(wk, self); int64_t n; if (!str_to_i(ss, &n, true)) { vm_error(wk, "unable to parse %o", self); return false; } *res = make_obj(wk, obj_number); set_obj_number(wk, *res, n); return true; } static bool func_string_startswith(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj_bool(wk, str_startswith(get_str(wk, self), get_str(wk, an[0].val))); return true; } static bool func_string_endswith(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj_bool(wk, str_endswith(get_str(wk, self), get_str(wk, an[0].val))); return true; } static bool func_string_substring(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_number, .optional = true }, { obj_number, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const struct str *s = get_str(wk, self); int64_t start = 0, end = s->len; if (an[0].set) { start = get_obj_number(wk, an[0].val); } if (an[1].set) { end = get_obj_number(wk, an[1].val); } if (start < 0) { start = s->len + start; } if (end < 0) { end = s->len + end; } if (end < start) { end = start; } start = MAX(0, start); if (start > end || start > s->len) { *res = make_str(wk, ""); return true; } *res = make_strn(wk, &s->s[start], MIN(end - start, s->len - start)); return true; } static bool func_string_replace(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const struct str *s = get_str(wk, self); const struct str *find = get_str(wk, an[0].val); const struct str *replace = get_str(wk, an[1].val); struct str tmp, pre = { .s = s->s, .len = 0, }; *res = make_str(wk, ""); uint32_t i; for (i = 0; i < s->len; ++i) { tmp = (struct str){ .s = &s->s[i], .len = s->len - i, }; if (str_startswith(&tmp, find)) { str_appn(wk, res, pre.s, pre.len); str_appn(wk, res, replace->s, replace->len); i += find->len; pre.s = &s->s[i]; pre.len = 0; --i; } else { ++pre.len; } } str_appn(wk, res, pre.s, pre.len); return true; } static bool func_string_contains(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } const struct str *s = get_str(wk, self); const struct str *find = get_str(wk, an[0].val); struct str tmp; bool found = false; uint32_t i; for (i = 0; i < s->len; ++i) { tmp = (struct str){ .s = &s->s[i], .len = s->len - i, }; if (str_startswith(&tmp, find)) { found = true; break; } } *res = make_obj_bool(wk, found); return true; } const struct func_impl impl_tbl_string[] = { { "contains", func_string_contains, tc_bool, true }, { "endswith", func_string_endswith, tc_bool, true }, { "format", func_format, tc_string, true }, { "join", func_join, tc_string, true }, { "replace", func_string_replace, tc_string, true }, { "split", func_split, tc_array, true }, { "splitlines", func_splitlines, tc_array, true }, { "startswith", func_string_startswith, tc_bool, true }, { "strip", func_strip, tc_string, true }, { "substring", func_string_substring, tc_string, true }, { "to_int", func_string_to_int, tc_number, true }, { "to_lower", func_to_lower, tc_string, true }, { "to_upper", func_to_upper, tc_string, true }, { "underscorify", func_underscorify, tc_string, true }, { "version_compare", func_version_compare, tc_bool, true }, { NULL, NULL }, }; static bool func_string_length(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, 0, 0)) { return false; } *res = make_number(wk, get_str(wk, self)->len); return true; } static bool string_shell_common(struct workspace *wk, enum shell_type *shell) { if (vm_enum(wk, shell_type)) { vm_enum_value_prefixed(wk, shell_type, posix); vm_enum_value_prefixed(wk, shell_type, cmd); } enum kwargs { kw_shell }; struct args_kw akw[] = { [kw_shell] = { "shell", complex_type_preset_get(wk, tc_cx_enum_shell) }, 0 }; if (!pop_args(wk, 0, akw)) { return false; } *shell = shell_type_posix; if (akw[kw_shell].set && !vm_obj_to_enum(wk, shell_type, akw[kw_shell].val, shell)) { return false; } return true; } static bool func_string_shell_split(struct workspace *wk, obj self, obj *res) { enum shell_type shell; if (!string_shell_common(wk, &shell)) { return false; } *res = str_shell_split(wk, get_str(wk, self), shell); return true; } static bool func_string_shell_quote(struct workspace *wk, obj self, obj *res) { enum shell_type shell; if (!string_shell_common(wk, &shell)) { return false; } void (*escape_func)(struct workspace *wk, struct tstr *sb, const char *str) = 0; switch (shell) { case shell_type_posix: escape_func = shell_escape; break; case shell_type_cmd: escape_func = shell_escape_cmd; break; } TSTR(buf); escape_func(wk, &buf, get_str(wk, self)->s); *res = tstr_into_str(wk, &buf); return true; } const struct func_impl impl_tbl_string_internal[] = { { "contains", func_string_contains, tc_bool, true }, { "endswith", func_string_endswith, tc_bool, true }, { "format", func_format, tc_string, true }, { "join", func_join, tc_string, true }, { "replace", func_string_replace, tc_string, true }, { "split", func_split, tc_array, true }, { "splitlines", func_splitlines, tc_array, true }, { "startswith", func_string_startswith, tc_bool, true }, { "strip", func_strip, tc_string, true }, { "substring", func_string_substring, tc_string, true }, { "to_int", func_string_to_int, tc_number, true }, { "to_lower", func_to_lower, tc_string, true }, { "to_upper", func_to_upper, tc_string, true }, { "underscorify", func_underscorify, tc_string, true }, { "version_compare", func_version_compare, tc_bool, true }, { "length", func_string_length, tc_number, true }, { "shell_split", func_string_shell_split, tc_array, true }, { "shell_quote", func_string_shell_quote, tc_string, true }, { NULL, NULL }, }; muon-v0.5.0/src/functions/external_program.c0000644000175000017500000000435615041716357020175 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "args.h" #include "functions/external_program.h" #include "guess.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "log.h" #include "platform/run_cmd.h" void find_program_guess_version(struct workspace *wk, obj cmd_array, obj version_argument, obj *ver) { *ver = 0; struct run_cmd_ctx cmd_ctx = { 0 }; obj args; obj_array_dup(wk, cmd_array, &args); obj_array_push(wk, args, version_argument ? version_argument : make_str(wk, "--version")); const char *argstr; uint32_t argc; join_args_argstr(wk, &argstr, &argc, args); if (run_cmd(&cmd_ctx, argstr, argc, NULL, 0) && cmd_ctx.status == 0) { if (!guess_version(wk, cmd_ctx.out.buf, ver)) { *ver = make_str(wk, "unknown"); } } run_cmd_ctx_destroy(&cmd_ctx); } static bool func_external_program_found(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, get_obj_external_program(wk, self)->found); return true; } static bool func_external_program_path(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct obj_external_program *ep = get_obj_external_program(wk, self); if (get_obj_array(wk, ep->cmd_array)->len > 1) { vm_error(wk, "cannot return the full_path() of an external program with multiple elements (have: %o)\n", ep->cmd_array); return false; } *res = obj_array_index(wk, get_obj_external_program(wk, self)->cmd_array, 0); return true; } static bool func_external_program_version(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct obj_external_program *prog = get_obj_external_program(wk, self); if (!prog->guessed_ver) { find_program_guess_version(wk, prog->cmd_array, 0, &prog->ver); prog->guessed_ver = true; } *res = prog->ver; return true; } const struct func_impl impl_tbl_external_program[] = { { "found", func_external_program_found, tc_bool }, { "path", func_external_program_path, tc_string }, { "full_path", func_external_program_path, tc_string }, { "version", func_external_program_version, tc_string }, { NULL, NULL }, }; muon-v0.5.0/src/functions/number.c0000644000175000017500000000261515041716357016110 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "functions/number.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" static bool func_number_is_odd(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, (get_obj_number(wk, self) & 1) != 0); return true; } static bool func_number_is_even(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, (get_obj_number(wk, self) & 1) == 0); return true; } static bool func_number_to_string(struct workspace *wk, obj self, obj *res) { enum kwargs { kw_fill }; struct args_kw akw[] = { [kw_fill] = { "fill", tc_number }, 0 }; if (!pop_args(wk, NULL, akw)) { return false; } char fmt[32]; int64_t fill = akw[kw_fill].set ? get_obj_number(wk, akw[kw_fill].val) : 0; if (fill > 0) { snprintf(fmt, sizeof(fmt), "%%0%" PRId64 PRId64, get_obj_number(wk, akw[kw_fill].val)); } else { snprintf(fmt, sizeof(fmt), "%%" PRId64); } *res = make_strf(wk, fmt, get_obj_number(wk, self)); return true; } const struct func_impl impl_tbl_number[] = { { "to_string", func_number_to_string, tc_string }, { "is_even", func_number_is_even, tc_bool }, { "is_odd", func_number_is_odd, tc_bool }, { NULL, NULL }, }; muon-v0.5.0/src/functions/generator.c0000644000175000017500000001336215041716357016607 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "coerce.h" #include "error.h" #include "functions/generator.h" #include "functions/kernel/custom_target.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "platform/assert.h" #include "platform/path.h" bool generated_list_process_file(struct workspace *wk, uint32_t node, struct obj_generator *g, struct obj_generated_list *gl, const char *dir, bool add_targets, obj val, obj *res, bool *generated_include) { TSTR(path); const char *output_dir = dir; if (gl->preserve_path_from) { const char *src = get_file_path(wk, val), *base = get_cstr(wk, gl->preserve_path_from); assert(path_is_subpath(base, src)); TSTR(dest_dir); path_relative_to(wk, &path, base, src); path_dirname(wk, &dest_dir, path.buf); path_join(wk, &path, dir, dest_dir.buf); output_dir = path.buf; } struct make_custom_target_opts opts = { .input_node = node, .output_node = node, .command_node = node, .input_orig = val, .output_orig = g->output, .output_dir = output_dir, .build_dir = dir, .command_orig = g->raw_command, .depfile_orig = g->depfile, .capture = g->capture, .feed = g->feed, .extra_args = gl->extra_arguments, .extra_args_valid = true, }; obj tgt; if (!make_custom_target(wk, &opts, &tgt)) { return false; } struct obj_custom_target *t = get_obj_custom_target(wk, tgt); t->env = gl->env; obj name; if (add_targets) { name = make_str(wk, ""); } { obj tmp_arr, file; tmp_arr = make_obj(wk, obj_array); obj_array_for(wk, t->output, file) { obj_array_push(wk, tmp_arr, file); if (add_targets) { const char *generated_path = get_cstr(wk, *get_obj_file(wk, file)); enum compiler_language l; if (!*generated_include && filename_to_compiler_language(generated_path, &l) && languages[l].is_header) { *generated_include = true; } TSTR(rel); path_relative_to(wk, &rel, wk->build_root, generated_path); str_app(wk, &name, " "); str_app(wk, &name, rel.buf); } } obj_array_extend_nodup(wk, *res, tmp_arr); } if (add_targets) { t->name = make_strf(wk, "", get_cstr(wk, name)); if (g->depends) { obj_array_extend(wk, t->depends, g->depends); } obj_array_push(wk, current_project(wk)->targets, tgt); } return true; } bool generated_list_process_for_target(struct workspace *wk, uint32_t node, obj generated_list, obj build_target, bool add_targets, obj *res) { struct obj_generated_list *gl = get_obj_generated_list(wk, generated_list); struct obj_generator *g = get_obj_generator(wk, gl->generator); enum obj_type t = get_obj_type(wk, build_target); const char *dir; switch (t) { case obj_build_target: dir = get_cstr(wk, get_obj_build_target(wk, build_target)->private_path); break; case obj_custom_target: { dir = get_cstr(wk, get_obj_custom_target(wk, build_target)->private_path); break; } default: UNREACHABLE; } *res = make_obj(wk, obj_array); bool generated_include = false; obj val; obj_array_for(wk, gl->input, val) { if (get_obj_type(wk, val) == obj_generated_list) { obj sub_res; if (!generated_list_process_for_target(wk, node, val, build_target, add_targets, &sub_res)) { return false; } obj file; obj_array_for(wk, sub_res, file) { if (!generated_list_process_file( wk, node, g, gl, dir, add_targets, file, res, &generated_include)) { return false; } } continue; } if (!generated_list_process_file(wk, node, g, gl, dir, add_targets, val, res, &generated_include)) { return false; } } if (add_targets && t == obj_build_target && generated_include) { get_obj_build_target(wk, build_target)->flags |= build_tgt_generated_include; } return true; } static bool func_generator_process(struct workspace *wk, obj gen, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_coercible_files | tc_generated_list }, ARG_TYPE_NULL }; enum kwargs { kw_extra_args, kw_preserve_path_from, kw_env, }; struct args_kw akw[] = { [kw_extra_args] = { "extra_args", TYPE_TAG_LISTIFY | obj_string }, [kw_preserve_path_from] = { "preserve_path_from", obj_string }, [kw_env] = { "env", tc_coercible_env }, 0, }; if (!pop_args(wk, an, akw)) { return false; } *res = make_obj(wk, obj_generated_list); struct obj_generated_list *gl = get_obj_generated_list(wk, *res); gl->generator = gen; gl->extra_arguments = akw[kw_extra_args].val; gl->preserve_path_from = akw[kw_preserve_path_from].val; if (!coerce_environment_from_kwarg(wk, &akw[kw_env], true, &gl->env)) { return false; } gl->input = make_obj(wk, obj_array); { obj v, coercible_files; coercible_files = make_obj(wk, obj_array); obj_array_for(wk, an[0].val, v) { obj_array_push(wk, get_obj_type(wk, v) == obj_generated_list ? gl->input : coercible_files, v); } obj files; if (!coerce_files(wk, an[0].node, coercible_files, &files)) { return false; } obj_array_extend_nodup(wk, gl->input, files); } if (gl->preserve_path_from) { if (!path_is_absolute(get_cstr(wk, gl->preserve_path_from))) { vm_error_at(wk, akw[kw_preserve_path_from].node, "preserve_path_from must be an absolute path"); return false; } obj f; obj_array_for(wk, gl->input, f) { const char *src = get_file_path(wk, f), *base = get_cstr(wk, gl->preserve_path_from); if (!path_is_subpath(base, src)) { vm_error_at(wk, akw[kw_preserve_path_from].node, "source file '%s' is not a subdir of preserve_path_from path '%s'", src, base); return false; } } } return true; } const struct func_impl impl_tbl_generator[] = { { "process", func_generator_process, tc_generated_list }, { NULL, NULL }, }; muon-v0.5.0/src/functions/custom_target.c0000644000175000017500000000241215041716357017473 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/func_lookup.h" #include "functions/custom_target.h" #include "functions/file.h" #include "lang/typecheck.h" bool custom_target_is_linkable(struct workspace *wk, obj ct) { struct obj_custom_target *tgt = get_obj_custom_target(wk, ct); if (get_obj_array(wk, tgt->output)->len == 1) { obj out = obj_array_index(wk, tgt->output, 0); return file_is_linkable(wk, out); } return false; } static bool func_custom_target_to_list(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_custom_target(wk, self)->output; return true; } static bool func_custom_target_full_path(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } obj elem; if (!obj_array_flatten_one(wk, get_obj_custom_target(wk, self)->output, &elem)) { vm_error(wk, "this custom_target has multiple outputs"); return false; } *res = *get_obj_file(wk, elem); return true; } const struct func_impl impl_tbl_custom_target[] = { { "full_path", func_custom_target_full_path, tc_string }, { "to_list", func_custom_target_to_list, tc_array }, { NULL, NULL }, }; muon-v0.5.0/src/functions/build_target.c0000644000175000017500000002310215041716357017257 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "coerce.h" #include "error.h" #include "functions/build_target.h" #include "functions/generator.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "platform/assert.h" #include "platform/path.h" struct tgt_src_to_compiled_path_opts { bool relative; const char *default_ext; compiler_get_arg_func_0 get_ext; enum compiler_language lang; }; static bool tgt_src_to_compiled_path(struct workspace *wk, const struct obj_build_target *tgt, struct tgt_src_to_compiled_path_opts *opts, obj src_file, struct tstr *res) { obj src = *get_obj_file(wk, src_file); TSTR(private_path_rel); TSTR(rel); const char *base, *private_path = get_cstr(wk, tgt->private_path); if (opts->relative) { path_relative_to(wk, &private_path_rel, wk->build_root, private_path); private_path = private_path_rel.buf; } if (path_is_subpath(get_cstr(wk, tgt->private_path), get_cstr(wk, src))) { // file is a source from a generated list base = get_cstr(wk, tgt->private_path); } else if (path_is_subpath(get_cstr(wk, tgt->build_dir), get_cstr(wk, src))) { // file is a generated source from custom_target / configure_file base = get_cstr(wk, tgt->build_dir); } else if (path_is_subpath(get_cstr(wk, tgt->cwd), get_cstr(wk, src))) { // file is in target cwd base = get_cstr(wk, tgt->cwd); } else if (path_is_subpath(wk->source_root, get_cstr(wk, src))) { // file is in source root base = wk->source_root; } else { // outside the source root base = NULL; } if (base) { path_relative_to(wk, &rel, base, get_cstr(wk, src)); } else { path_copy(wk, &rel, get_cstr(wk, src)); uint32_t i; for (i = 0; i < rel.len; ++i) { if (rel.buf[i] == PATH_SEP || rel.buf[i] == ':') { rel.buf[i] = '_'; } } } path_join(wk, res, private_path, rel.buf); const char *ext = opts->default_ext; { obj comp_id; if (obj_dict_geti(wk, current_project(wk)->toolchains[tgt->machine], opts->lang, &comp_id)) { ext = opts->get_ext(wk, get_obj_compiler(wk, comp_id))->args[0]; } } tstr_pushs(wk, res, ext); return true; } bool tgt_src_to_pch_path(struct workspace *wk, const struct obj_build_target *tgt, enum compiler_language lang, obj src_file, struct tstr *res) { struct tgt_src_to_compiled_path_opts opts = { .relative = true, .get_ext = toolchain_compiler_pch_ext, .lang = lang, }; if (get_obj_type(wk, src_file) == obj_build_target) { tgt = get_obj_build_target(wk, src_file); obj tgt_pch = tgt->pch; if (!obj_dict_geti(wk, tgt_pch, lang, &src_file)) { UNREACHABLE; } } return tgt_src_to_compiled_path(wk, tgt, &opts, src_file, res); } bool tgt_src_to_object_path(struct workspace *wk, const struct obj_build_target *tgt, enum compiler_language lang, obj src_file, bool relative, struct tstr *res) { struct tgt_src_to_compiled_path_opts opts = { .relative = relative, .default_ext = ".o", .get_ext = toolchain_compiler_object_ext, .lang = lang, }; return tgt_src_to_compiled_path(wk, tgt, &opts, src_file, res); } static bool func_build_target_name(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_build_target(wk, self)->name; return true; } static bool func_build_target_full_path(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct obj_build_target *tgt = get_obj_build_target(wk, self); *res = tgt->build_path; return true; } struct build_target_extract_objects_ctx { struct obj_build_target *tgt; obj tgt_id; obj *res; bool verify_exists; }; static bool build_target_extract_object(struct workspace *wk, struct build_target_extract_objects_ctx *ctx, obj val) { obj file; enum obj_type t = get_obj_type(wk, val); if (!typecheck(wk, 0, val, tc_file | tc_string | tc_custom_target | tc_generated_list)) { return false; } switch (t) { case obj_string: { if (!coerce_string_to_file(wk, get_cstr(wk, ctx->tgt->cwd), val, &file)) { return false; } break; } case obj_file: file = val; break; case obj_custom_target: { struct obj_custom_target *tgt = get_obj_custom_target(wk, val); obj v; obj_array_for(wk, tgt->output, v) { if (!build_target_extract_object(wk, ctx, v)) { return false; } } return true; } case obj_generated_list: { obj processed; if (!generated_list_process_for_target(wk, 0, val, ctx->tgt_id, false, &processed)) { return false; } obj v; obj_array_for(wk, processed, v) { if (!build_target_extract_object(wk, ctx, v)) { return false; } } return true; } default: UNREACHABLE_RETURN; } enum compiler_language l; if (!filename_to_compiler_language(get_file_path(wk, file), &l)) { return false; } switch (l) { case compiler_language_c_hdr: case compiler_language_cpp_hdr: case compiler_language_objc_hdr: case compiler_language_objcpp_hdr: case compiler_language_c_obj: // skip non-compileable sources return true; case compiler_language_assembly: case compiler_language_nasm: case compiler_language_c: case compiler_language_cpp: case compiler_language_llvm_ir: case compiler_language_objcpp: case compiler_language_objc: break; case compiler_language_null: case compiler_language_count: UNREACHABLE; } if (ctx->verify_exists && !obj_array_in(wk, ctx->tgt->src, file)) { vm_error_at(wk, 0, "%o is not in target sources (%o)", file, ctx->tgt->src); return false; } TSTR(dest_path); if (!tgt_src_to_object_path(wk, ctx->tgt, l, file, false, &dest_path)) { return false; } obj new_file; new_file = make_obj(wk, obj_file); *get_obj_file(wk, new_file) = tstr_into_str(wk, &dest_path); obj_array_push(wk, *ctx->res, new_file); return ir_cont; } static bool func_build_target_extract_objects(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_string | tc_file | tc_custom_target | tc_generated_list }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj(wk, obj_array); struct build_target_extract_objects_ctx ctx = { .res = res, .tgt = get_obj_build_target(wk, self), .tgt_id = self, .verify_exists = true, }; obj v; obj_array_for(wk, an[0].val, v) { if (!build_target_extract_object(wk, &ctx, v)) { return false; } } return true; } static bool build_target_extract_all_objects_impl(struct workspace *wk, obj self, obj *res, bool recursive); static bool build_targets_extract_all_objects(struct workspace *wk, obj arr, obj *res) { if (!arr) { return true; } obj v; obj_array_for(wk, arr, v) { if (get_obj_type(wk, v) != obj_build_target) { continue; } if (!build_target_extract_all_objects_impl(wk, v, res, true)) { return false; } } return true; } static bool build_target_extract_all_objects_recurse(struct workspace *wk, struct build_dep *dep, obj *res) { if (!build_targets_extract_all_objects(wk, dep->raw.link_with, res)) { return false; } if (!build_targets_extract_all_objects(wk, dep->raw.link_whole, res)) { return false; } if (dep->raw.deps) { obj v; obj_array_for(wk, dep->raw.deps, v) { struct obj_dependency *dep = get_obj_dependency(wk, v); if (!build_target_extract_all_objects_recurse(wk, &dep->dep, res)) { return false; } } } return true; } static bool build_target_extract_all_objects_impl(struct workspace *wk, obj self, obj *res, bool recursive) { struct build_target_extract_objects_ctx ctx = { .res = res, .tgt = get_obj_build_target(wk, self), .tgt_id = self, }; obj v; obj_array_for(wk, ctx.tgt->src, v) { if (!build_target_extract_object(wk, &ctx, v)) { return false; } } if (recursive) { obj_array_extend(wk, *res, ctx.tgt->objects); if (!build_target_extract_all_objects_recurse(wk, &ctx.tgt->dep_internal, res)) { return false; } } return true; } bool build_target_extract_all_objects(struct workspace *wk, uint32_t ip, obj self, obj *res, bool recursive) { *res = make_obj(wk, obj_array); return build_target_extract_all_objects_impl(wk, self, res, recursive); } static bool func_build_target_extract_all_objects(struct workspace *wk, obj self, obj *res) { enum kwargs { kw_recursive, }; struct args_kw akw[] = { [kw_recursive] = { "recursive", obj_bool }, 0 }; if (!pop_args(wk, NULL, akw)) { return false; } bool recursive = akw[kw_recursive].set ? get_obj_bool(wk, akw[kw_recursive].val) : false; return build_target_extract_all_objects(wk, 0, self, res, recursive); } static bool func_build_target_private_dir_include(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj(wk, obj_include_directory); struct obj_include_directory *inc = get_obj_include_directory(wk, *res); inc->path = get_obj_build_target(wk, self)->private_path; return true; } static bool func_build_target_found(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, true); return true; } const struct func_impl impl_tbl_build_target[] = { { "extract_all_objects", func_build_target_extract_all_objects, tc_array }, { "extract_objects", func_build_target_extract_objects, tc_array }, { "found", func_build_target_found, tc_bool }, { "full_path", func_build_target_full_path, tc_string }, { "name", func_build_target_name, tc_string }, { "path", func_build_target_full_path, tc_string }, { "private_dir_include", func_build_target_private_dir_include, tc_string }, { NULL, NULL }, }; muon-v0.5.0/src/functions/dependency.c0000644000175000017500000002572315041716357016743 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Harley Swick * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "coerce.h" #include "error.h" #include "external/pkgconfig.h" #include "functions/dependency.h" #include "functions/kernel/dependency.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "platform/assert.h" #include "platform/path.h" static bool func_dependency_found(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, (get_obj_dependency(wk, self)->flags & dep_flag_found) == dep_flag_found); return true; } static bool dep_get_pkgconfig_variable(struct workspace *wk, obj dep, uint32_t node, obj var, obj defines, obj *res) { struct obj_dependency *d = get_obj_dependency(wk, dep); if (d->type != dependency_type_pkgconf) { vm_error_at(wk, node, "dependency not from pkgconf"); return false; } if (!muon_pkgconfig_get_variable(wk, d->name, var, defines, res)) { return false; } return true; } static bool func_dependency_get_pkgconfig_variable(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_default, }; struct args_kw akw[] = { [kw_default] = { "default", obj_string }, 0 }; if (!pop_args(wk, an, akw)) { return false; } if (!dep_get_pkgconfig_variable(wk, self, an[0].node, an[0].val, 0, res)) { if (akw[kw_default].set) { *res = akw[kw_default].val; } else { vm_error_at(wk, an[0].node, "undefined pkg_config variable"); return false; } } return true; } static bool func_dependency_get_configtool_variable(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, 0)) { return false; } vm_error_at(wk, 0, "get_configtool_variable not implemented"); return false; } static bool dep_pkgconfig_define(struct workspace *wk, obj dep, uint32_t node, obj var, obj *res) { struct obj_array *arr = get_obj_array(wk, var); if (arr->len % 2 != 0) { vm_error_at(wk, node, "non-even number of arguments in list"); return false; } *res = make_obj(wk, obj_dict); obj key = 0, val = 0; obj_array_for_array_(wk, arr, val, iter) { if (!(iter.i & 1)) { key = val; continue; } obj_dict_set(wk, *res, key, val); } return true; } static enum dependency_public_type dependency_type_to_public_type(struct obj_dependency *d) { if (d->public_type != dependency_public_type_unset) { return d->public_type; } switch (d->type) { case dependency_type_pkgconf: return dependency_public_type_pkgconfig; break; case dependency_type_declared: return dependency_public_type_internal; break; case dependency_type_system: case dependency_type_threads: return dependency_public_type_system; break; case dependency_type_external_library: return dependency_public_type_library; break; case dependency_type_not_found: return dependency_public_type_not_found; break; default: UNREACHABLE_RETURN; } } static bool func_dependency_get_variable(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string, .optional = true }, ARG_TYPE_NULL }; enum kwargs { kw_pkgconfig, kw_pkgconfig_define, kw_internal, kw_system, kw_default_value, }; struct args_kw akw[] = { [kw_pkgconfig] = { "pkgconfig", obj_string }, [kw_pkgconfig_define] = { "pkgconfig_define", TYPE_TAG_LISTIFY | obj_string }, [kw_internal] = { "internal", obj_string }, [kw_system] = { "system", obj_string }, [kw_default_value] = { "default_value", obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } uint32_t type_kwargs[] = { kw_pkgconfig, kw_internal, kw_system }; if (an[0].set) { uint32_t i; for (i = 0; i < ARRAY_LEN(type_kwargs); ++i) { uint32_t kw = type_kwargs[i]; if (!akw[kw].set) { akw[kw].set = true; akw[kw].node = an[0].node; akw[kw].val = an[0].val; } } } struct obj_dependency *dep = get_obj_dependency(wk, self); switch (dependency_type_to_public_type(dep)) { case dependency_public_type_pkgconfig: { obj defines = 0; if (akw[kw_pkgconfig_define].set) { if (!dep_pkgconfig_define( wk, self, akw[kw_pkgconfig_define].node, akw[kw_pkgconfig_define].val, &defines)) { return false; } } if (akw[kw_pkgconfig].set) { if (dep_get_pkgconfig_variable( wk, self, akw[kw_pkgconfig].node, akw[kw_pkgconfig].val, defines, res)) { return true; } } break; } case dependency_public_type_internal: { if (dep->variables && akw[kw_internal].set) { if (obj_dict_index(wk, dep->variables, akw[kw_internal].val, res)) { return true; } } break; } case dependency_public_type_system: { if (dep->variables && akw[kw_system].set) { if (obj_dict_index(wk, dep->variables, akw[kw_system].val, res)) { return true; } } break; } case dependency_public_type_library: break; case dependency_public_type_not_found: break; case dependency_public_type_unset: break; } if (akw[kw_default_value].set) { *res = akw[kw_default_value].val; return true; } else { vm_error(wk, "dependency has no such variable"); return false; } } static bool func_dependency_version(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } obj version = get_obj_dependency(wk, self)->version; if (version) { *res = version; } else { *res = make_str(wk, "unknown"); } return true; } static bool func_dependency_type_name(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct obj_dependency *dep = get_obj_dependency(wk, self); if (!(dep->flags & dep_flag_found)) { *res = make_str(wk, "not-found"); return true; } const char *n = NULL; switch (dependency_type_to_public_type(dep)) { case dependency_public_type_pkgconfig: n = "pkgconfig"; break; case dependency_public_type_internal: n = "internal"; break; case dependency_public_type_system: n = "system"; break; case dependency_public_type_library: n = "library"; break; case dependency_public_type_not_found: n = "not-found"; break; case dependency_public_type_unset: UNREACHABLE; break; } *res = make_str(wk, n); return true; } static bool func_dependency_name(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct obj_dependency *dep = get_obj_dependency(wk, self); if (!dep->name) { *res = make_str(wk, "internal"); } else { *res = dep->name; } return true; } static bool func_dependency_partial_dependency(struct workspace *wk, obj self, obj *res) { enum kwargs { kw_compile_args, kw_includes, kw_link_args, kw_links, kw_sources, }; struct args_kw akw[] = { [kw_compile_args] = { "compile_args", obj_bool }, [kw_includes] = { "includes", obj_bool }, [kw_link_args] = { "link_args", obj_bool }, [kw_links] = { "links", obj_bool }, [kw_sources] = { "sources", obj_bool }, 0 }; if (!pop_args(wk, NULL, akw)) { return false; } enum build_dep_flag flags = build_dep_flag_recursive | build_dep_flag_partial; if (akw[kw_compile_args].set && get_obj_bool(wk, akw[kw_compile_args].val)) { flags |= build_dep_flag_part_compile_args; } if (akw[kw_includes].set && get_obj_bool(wk, akw[kw_includes].val)) { flags |= build_dep_flag_part_includes; } if (akw[kw_link_args].set && get_obj_bool(wk, akw[kw_link_args].val)) { flags |= build_dep_flag_part_link_args; } if (akw[kw_links].set && get_obj_bool(wk, akw[kw_links].val)) { flags |= build_dep_flag_part_links; } if (akw[kw_sources].set && get_obj_bool(wk, akw[kw_sources].val)) { flags |= build_dep_flag_part_sources; } if (!(*res = dependency_dup(wk, self, flags))) { return false; } return true; } static bool func_dependency_as_system(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } enum include_type inc_type = include_type_system; if (an[0].set) { if (!coerce_include_type(wk, get_str(wk, an[0].val), an[0].node, &inc_type)) { return false; } } enum build_dep_flag flags = 0; switch (inc_type) { case include_type_preserve: break; case include_type_system: flags |= build_dep_flag_include_system; break; case include_type_non_system: flags |= build_dep_flag_include_non_system; break; } if (!(*res = dependency_dup(wk, self, flags))) { return false; } get_obj_dependency(wk, *res)->include_type = inc_type; return true; } static bool func_dependency_as_link_whole(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, 0, 0)) { return false; } if (!(*res = dependency_dup(wk, self, build_dep_flag_as_link_whole))) { return false; } return true; } static bool func_dependency_both_libs_common(struct workspace *wk, obj self, obj *res, enum build_dep_flag flags) { enum kwargs { kw_recursive, }; struct args_kw akw[] = { [kw_recursive] = { "recursive", obj_bool }, 0, }; if (!pop_args(wk, 0, akw)) { return false; } if (get_obj_bool_with_default(wk, akw[kw_recursive].val, false)) { flags |= build_dep_flag_recursive; } if (!(*res = dependency_dup(wk, self, flags))) { return false; } return true; } static bool func_dependency_as_shared(struct workspace *wk, obj self, obj *res) { return func_dependency_both_libs_common(wk, self, res, build_dep_flag_both_libs_shared); } static bool func_dependency_as_static(struct workspace *wk, obj self, obj *res) { return func_dependency_both_libs_common(wk, self, res, build_dep_flag_both_libs_static); } static bool func_dependency_include_type(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } const char *s = NULL; switch (get_obj_dependency(wk, self)->include_type) { case include_type_preserve: s = "preserve"; break; case include_type_system: s = "system"; break; case include_type_non_system: s = "non-system"; break; default: assert(false && "unreachable"); break; } *res = make_str(wk, s); return true; } const struct func_impl impl_tbl_dependency[] = { { "as_link_whole", func_dependency_as_link_whole, tc_dependency }, { "as_shared", func_dependency_as_shared, tc_dependency }, { "as_static", func_dependency_as_static, tc_dependency }, { "as_system", func_dependency_as_system, tc_dependency }, { "found", func_dependency_found, tc_bool }, { "get_configtool_variable", func_dependency_get_configtool_variable, tc_string }, { "get_pkgconfig_variable", func_dependency_get_pkgconfig_variable, tc_string }, { "get_variable", func_dependency_get_variable, tc_string }, { "include_type", func_dependency_include_type, tc_string }, { "name", func_dependency_name, tc_string }, { "partial_dependency", func_dependency_partial_dependency, tc_dependency }, { "type_name", func_dependency_type_name, tc_string }, { "version", func_dependency_version, tc_string }, { NULL, NULL }, }; muon-v0.5.0/src/functions/environment.c0000644000175000017500000001275715041716357017174 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "error.h" #include "functions/environment.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "platform/assert.h" #include "platform/os.h" #include "platform/path.h" static enum iteration_result evironment_to_dict_iter(struct workspace *wk, void *_ctx, obj action) { obj env = *(obj *)_ctx, mode_num, key, val, sep; mode_num = obj_array_index(wk, action, 0); key = obj_array_index(wk, action, 1); val = obj_array_index(wk, action, 2); sep = obj_array_index(wk, action, 3); enum environment_set_mode mode = get_obj_number(wk, mode_num); if (mode == environment_set_mode_set) { obj_dict_set(wk, env, key, val); return ir_cont; } const char *oval; obj v; if (obj_dict_index(wk, env, key, &v)) { oval = get_cstr(wk, v); } else { if (!(oval = os_get_env(get_cstr(wk, key)))) { obj_dict_set(wk, env, key, val); return ir_cont; } } obj str; switch (mode) { case environment_set_mode_append: str = make_strf(wk, "%s%s%s", oval, get_cstr(wk, sep), get_cstr(wk, val)); break; case environment_set_mode_prepend: str = make_strf(wk, "%s%s%s", get_cstr(wk, val), get_cstr(wk, sep), oval); break; default: UNREACHABLE; } obj_dict_set(wk, env, key, str); return ir_cont; } bool environment_to_dict(struct workspace *wk, obj env, obj *res) { if (get_obj_type(wk, env) == obj_dict) { *res = env; return true; } *res = make_obj(wk, obj_dict); return obj_array_foreach(wk, get_obj_environment(wk, env)->actions, res, evironment_to_dict_iter); } static void environment_or_dict_set(struct workspace *wk, obj env, const char *key, const char *val) { switch (get_obj_type(wk, env)) { case obj_dict: obj_dict_set(wk, env, make_str(wk, key), make_str(wk, val)); break; case obj_environment: environment_set(wk, env, environment_set_mode_set, make_str(wk, key), make_str(wk, val), 0); break; default: UNREACHABLE; } } void set_default_environment_vars(struct workspace *wk, obj env, bool set_subdir) { if (wk->vm.lang_mode == language_internal) { return; } if (wk->argv0) { // argv0 may not be set, e.g. during `muon install` environment_or_dict_set(wk, env, "MUON_PATH", wk->argv0); obj introspect = make_obj(wk, obj_array); obj_array_push(wk, introspect, make_str(wk, wk->argv0)); obj_array_push(wk, introspect, make_str(wk, "meson")); obj_array_push(wk, introspect, make_str(wk, "introspect")); environment_or_dict_set(wk, env, "MESONINTROSPECT", get_str(wk, join_args_shell(wk, introspect))->s); } environment_or_dict_set(wk, env, "MESON_BUILD_ROOT", wk->build_root); environment_or_dict_set(wk, env, "MESON_SOURCE_ROOT", wk->source_root); if (set_subdir) { TSTR(subdir); path_relative_to(wk, &subdir, wk->source_root, get_cstr(wk, current_project(wk)->cwd)); environment_or_dict_set(wk, env, "MESON_SUBDIR", subdir.buf); } } bool environment_set(struct workspace *wk, obj env, enum environment_set_mode mode, obj key, obj vals, obj sep) { if (!sep) { sep = make_str(wk, ENV_PATH_SEP_STR); } obj joined; if (get_obj_type(wk, vals) == obj_string) { joined = vals; } else { if (!obj_array_join(wk, false, vals, sep, &joined)) { return false; } } obj elem, mode_num; mode_num = make_obj(wk, obj_number); set_obj_number(wk, mode_num, mode); elem = make_obj(wk, obj_array); obj_array_push(wk, elem, mode_num); obj_array_push(wk, elem, key); obj_array_push(wk, elem, joined); obj_array_push(wk, elem, sep); obj_array_push(wk, get_obj_environment(wk, env)->actions, elem); return true; } static bool func_environment_set_common(struct workspace *wk, obj self, enum environment_set_mode mode) { struct args_norm an[] = { { obj_string }, { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_separator, }; struct args_kw akw[] = { [kw_separator] = { "separator", obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (!get_obj_array(wk, an[1].val)->len) { vm_error_at(wk, an[1].node, "you must pass at least one value"); return false; } return environment_set(wk, self, mode, an[0].val, an[1].val, akw[kw_separator].val); } static bool func_environment_set(struct workspace *wk, obj self, obj *res) { return func_environment_set_common(wk, self, environment_set_mode_set); } static bool func_environment_append(struct workspace *wk, obj self, obj *res) { return func_environment_set_common(wk, self, environment_set_mode_append); } static bool func_environment_prepend(struct workspace *wk, obj self, obj *res) { return func_environment_set_common(wk, self, environment_set_mode_prepend); } static bool func_environment_unset(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string, .desc = "The name to unset" }, ARG_TYPE_NULL, }; if (!pop_args(wk, an, 0)) { return false; } obj to_delete, action, actions = get_obj_environment(wk, self)->actions; to_delete = make_obj(wk, obj_array); uint32_t i = 0; obj_array_for(wk, actions, action) { if (obj_equal(wk, action, an[0].val)) { obj_array_push(wk, to_delete, i + 1); } ++i; } obj_array_for(wk, to_delete, i) { obj_array_del(wk, actions, i - 1); } return true; } const struct func_impl impl_tbl_environment[] = { { "set", func_environment_set }, { "append", func_environment_append }, { "prepend", func_environment_prepend }, { "unset", func_environment_unset }, { NULL, NULL }, }; muon-v0.5.0/src/functions/compiler.c0000644000175000017500000021025415041716357016432 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti SPDX-FileCopyrightText: Luke Drummond * SPDX-FileCopyrightText: Eli Schwartz * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include "args.h" #include "backend/common_args.h" #include "buf_size.h" #include "coerce.h" #include "compilers.h" #include "error.h" #include "functions/compiler.h" #include "functions/kernel/custom_target.h" #include "functions/kernel/dependency.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "options.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/run_cmd.h" MUON_ATTR_FORMAT(printf, 3, 4) static void compiler_log(struct workspace *wk, obj compiler, const char *fmt, ...) { va_list args; va_start(args, fmt); struct obj_compiler *comp = get_obj_compiler(wk, compiler); LLOG_I("%s: ", compiler_log_prefix(comp->lang, comp->machine)); log_printv(log_info, fmt, args); log_plain(log_info, "\n"); va_end(args); } MUON_ATTR_FORMAT(printf, 3, 4) static void compiler_check_log(struct workspace *wk, struct compiler_check_opts *opts, const char *fmt, ...) { va_list args; va_start(args, fmt); struct obj_compiler *comp = get_obj_compiler(wk, opts->comp_id); LLOG_I("%s: ", compiler_log_prefix(comp->lang, comp->machine)); log_printv(log_info, fmt, args); if (opts->from_cache) { log_plain(log_info, " " CLR(c_cyan) "cached" CLR(0)); } log_plain(log_info, "\n"); va_end(args); } static bool add_include_directory_args(struct workspace *wk, struct args_kw *inc, struct build_dep *dep, obj comp_id, obj compiler_args) { obj include_dirs; include_dirs = make_obj(wk, obj_array); if (inc && inc->set) { obj includes; if (!coerce_include_dirs(wk, inc->node, inc->val, false, &includes)) { return false; } obj_array_extend_nodup(wk, include_dirs, includes); } if (dep) { obj_array_extend_nodup(wk, include_dirs, dep->include_directories); } ca_setup_compiler_args_includes(wk, comp_id, include_dirs, compiler_args, false); return true; } bool compiler_check(struct workspace *wk, struct compiler_check_opts *opts, const char *src, uint32_t err_node, bool *res) { enum requirement_type req = requirement_auto; if (opts->required && opts->required->set) { if (!coerce_requirement(wk, opts->required, &req)) { return false; } } if (req == requirement_skip) { *res = false; return true; } struct obj_compiler *comp = get_obj_compiler(wk, opts->comp_id); obj compiler_args; compiler_args = make_obj(wk, obj_array); obj_array_extend(wk, compiler_args, comp->cmd_arr[toolchain_component_compiler]); push_args(wk, compiler_args, toolchain_compiler_always(wk, comp)); ca_get_std_args(wk, comp, current_project(wk), NULL, compiler_args); if (comp->lang == compiler_language_cpp) { push_args(wk, compiler_args, toolchain_compiler_permissive(wk, comp)); } if (opts->werror && opts->werror->set && get_obj_bool(wk, opts->werror->val)) { push_args(wk, compiler_args, toolchain_compiler_werror(wk, comp)); } switch (opts->mode) { case compiler_check_mode_run: case compiler_check_mode_link: ca_get_option_link_args(wk, comp, current_project(wk), NULL, compiler_args); /* fallthrough */ case compiler_check_mode_compile: ca_get_option_compile_args(wk, comp, current_project(wk), NULL, compiler_args); /* fallthrough */ case compiler_check_mode_preprocess: break; } bool have_dep = false; struct build_dep dep = { 0 }; if (opts->deps && opts->deps->set) { have_dep = true; dep_process_deps(wk, opts->deps->val, &dep); obj_array_extend_nodup(wk, compiler_args, dep.compile_args); } if (!add_include_directory_args(wk, opts->inc, have_dep ? &dep : NULL, opts->comp_id, compiler_args)) { return false; } switch (opts->mode) { case compiler_check_mode_preprocess: push_args(wk, compiler_args, toolchain_compiler_preprocess_only(wk, comp)); break; case compiler_check_mode_compile: push_args(wk, compiler_args, toolchain_compiler_compile_only(wk, comp)); break; case compiler_check_mode_run: break; case compiler_check_mode_link: { push_args(wk, compiler_args, toolchain_compiler_linker_passthrough(wk, comp, toolchain_linker_fatal_warnings(wk, comp))); break; } } obj source_path; if (opts->src_is_path) { source_path = make_str(wk, src); } else { TSTR(test_source_path); path_join(wk, &test_source_path, wk->muon_private, "test."); tstr_pushs(wk, &test_source_path, compiler_language_extension(comp->lang)); source_path = tstr_into_str(wk, &test_source_path); } obj_array_push(wk, compiler_args, source_path); TSTR(test_output_path); const char *output_path; if (opts->output_path) { output_path = opts->output_path; } else if (opts->mode == compiler_check_mode_run) { path_join(wk, &test_output_path, wk->muon_private, "compiler_check_exe"); if (machine_definitions[comp->machine]->is_windows) { tstr_pushs(wk, &test_output_path, ".exe"); } output_path = test_output_path.buf; } else { path_join(wk, &test_output_path, wk->muon_private, "test."); tstr_pushs(wk, &test_output_path, compiler_language_extension(comp->lang)); tstr_pushs(wk, &test_output_path, toolchain_compiler_object_ext(wk, comp)->args[0]); output_path = test_output_path.buf; } push_args(wk, compiler_args, toolchain_compiler_output(wk, comp, output_path)); if (have_dep) { struct obj_build_target tgt = { .dep_internal = dep, }; ca_prepare_target_linker_args(wk, comp, 0, &tgt, false); obj_array_extend_nodup(wk, compiler_args, dep.link_args); } if (opts->args) { obj_array_extend(wk, compiler_args, opts->args); } bool ret = false; struct run_cmd_ctx cmd_ctx = { 0 }; const char *argstr; uint32_t argc; join_args_argstr(wk, &argstr, &argc, compiler_args); opts->cache_key = compiler_check_cache_key(wk, &(struct compiler_check_cache_key){ .comp = comp, .argstr = argstr, .argc = argc, .src = src, }); struct compiler_check_cache_value cache_value = { 0 }; if (compiler_check_cache_get(wk, opts->cache_key, &cache_value)) { *res = cache_value.success; opts->cache_val = cache_value.value; opts->from_cache = true; return true; } if (!opts->src_is_path) { L("compiling: '%s'", src); if (!fs_write(get_cstr(wk, source_path), (const uint8_t *)src, strlen(src))) { return false; } } else { L("compiling: '%s'", get_cstr(wk, source_path)); } if (!run_cmd(&cmd_ctx, argstr, argc, NULL, 0)) { vm_error_at(wk, err_node, "error: %s", cmd_ctx.err_msg); goto ret; } L("compiler stdout: '%s'", cmd_ctx.out.buf); L("compiler stderr: '%s'", cmd_ctx.err.buf); if (opts->mode == compiler_check_mode_run) { if (cmd_ctx.status != 0) { if (opts->skip_run_check) { *res = false; ret = true; goto ret; } else { LOG_W("failed to compile test, rerun with -v to see compiler invocation"); goto ret; } } if (!run_cmd_argv(&opts->cmd_ctx, (char *const[]){ (char *)output_path, NULL }, NULL, 0)) { LOG_W("compiled binary failed to run: %s", opts->cmd_ctx.err_msg); run_cmd_ctx_destroy(&opts->cmd_ctx); goto ret; } else if (!opts->skip_run_check && opts->cmd_ctx.status != 0) { LOG_W("compiled binary returned an error (exit code %d)", opts->cmd_ctx.status); run_cmd_ctx_destroy(&opts->cmd_ctx); goto ret; } *res = true; } else { *res = cmd_ctx.status == 0; } // store wether or not the check suceeded in the cache, the caller is // responsible for storing the actual value compiler_check_cache_set(wk, opts->cache_key, &(struct compiler_check_cache_value){ .success = *res }); ret = true; ret: if (ret && opts->keep_cmd_ctx) { opts->cmd_ctx = cmd_ctx; } else { run_cmd_ctx_destroy(&cmd_ctx); } if (!*res && req == requirement_required) { assert(opts->required); vm_error_at(wk, opts->required->node, "a required compiler check failed"); return false; } return ret; } static int64_t compiler_check_parse_output_int(struct compiler_check_opts *opts) { char *endptr; int64_t size; size = strtoll(opts->cmd_ctx.out.buf, &endptr, 10); if (*endptr) { LOG_W("compiler check binary had malformed output '%s'", opts->cmd_ctx.out.buf); return -1; } return size; } enum cc_kwargs { cc_kw_args, cc_kw_dependencies, cc_kw_prefix, cc_kw_required, cc_kw_include_directories, cc_kw_name, cc_kw_guess, cc_kw_high, cc_kw_low, cc_kw_werror, cc_kwargs_count, cm_kw_args = 1 << 0, cm_kw_dependencies = 1 << 1, cm_kw_prefix = 1 << 2, cm_kw_required = 1 << 3, cm_kw_include_directories = 1 << 4, cm_kw_name = 1 << 5, cm_kw_guess = 1 << 6, cm_kw_high = 1 << 7, cm_kw_low = 1 << 8, cm_kw_werror = 1 << 9, }; static void compiler_opts_init(obj self, struct args_kw *akw, struct compiler_check_opts *opts) { opts->comp_id = self; if (akw[cc_kw_dependencies].set) { opts->deps = &akw[cc_kw_dependencies]; } if (akw[cc_kw_args].set) { opts->args = akw[cc_kw_args].val; } if (akw[cc_kw_include_directories].set) { opts->inc = &akw[cc_kw_include_directories]; } if (akw[cc_kw_required].set) { opts->required = &akw[cc_kw_required]; } if (akw[cc_kw_werror].set) { opts->werror = &akw[cc_kw_werror]; } } static bool func_compiler_check_args_common(struct workspace *wk, obj self, struct args_norm *an, struct args_kw **kw_res, struct compiler_check_opts *opts, enum cc_kwargs args_mask) { static struct args_kw akw[cc_kwargs_count + 1] = { 0 }; struct args_kw akw_base[] = { [cc_kw_args] = { "args", TYPE_TAG_LISTIFY | obj_string }, [cc_kw_dependencies] = { "dependencies", TYPE_TAG_LISTIFY | tc_dependency }, [cc_kw_prefix] = { "prefix", TYPE_TAG_LISTIFY | obj_string }, [cc_kw_required] = { "required", tc_required_kw }, [cc_kw_include_directories] = { "include_directories", TYPE_TAG_LISTIFY | tc_coercible_inc }, [cc_kw_name] = { "name", obj_string }, [cc_kw_guess] = { "guess", obj_number, }, [cc_kw_high] = { "high", obj_number, }, [cc_kw_low] = { "low", obj_number, }, [cc_kw_werror] = { "werror", obj_bool }, 0 }; memcpy(akw, akw_base, sizeof(struct args_kw) * cc_kwargs_count); struct args_kw *use_akw; if (kw_res && args_mask) { *kw_res = akw; use_akw = akw; } else { use_akw = NULL; } if (!pop_args(wk, an, use_akw)) { return false; } if (use_akw) { uint32_t i; for (i = 0; i < cc_kwargs_count; ++i) { if ((args_mask & (1 << i))) { continue; } else if (akw[i].set) { vm_error_at(wk, akw[i].node, "invalid keyword '%s'", akw[i].key); return false; } } } compiler_opts_init(self, akw, opts); return true; } static const char * compiler_check_prefix(struct workspace *wk, struct args_kw *akw) { if (akw[cc_kw_prefix].set) { if (get_obj_type(wk, akw[cc_kw_prefix].val) == obj_array) { obj joined; obj_array_join(wk, true, akw[cc_kw_prefix].val, make_str(wk, "\n"), &joined); akw[cc_kw_prefix].val = joined; } return get_cstr(wk, akw[cc_kw_prefix].val); } else { return ""; } } #define compiler_handle_has_required_kw_setup(__requirement, kw) \ enum requirement_type __requirement; \ if (!akw[kw].set) { \ __requirement = requirement_auto; \ } else if (!coerce_requirement(wk, &akw[kw], &__requirement)) { \ return false; \ } \ if (__requirement == requirement_skip) { \ *res = make_obj_bool(wk, false); \ return true; \ } #define compiler_handle_has_required_kw(__requirement, __result) \ if (__requirement == requirement_required && !__result) { \ vm_error(wk, "required compiler check failed"); \ return false; \ } static bool func_compiler_sizeof(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compiler_check_mode_run, .skip_run_check = true, }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories)) { return false; } char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "#include \n" "%s\n" "int main(void) { printf(\"%%ld\", (long)(sizeof(%s))); return 0; }\n", compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val)); bool ok; if (compiler_check(wk, &opts, src, an[0].node, &ok) && ok) { if (!opts.from_cache) { *res = make_obj(wk, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); } } else { if (!opts.from_cache) { *res = make_obj(wk, obj_number); set_obj_number(wk, *res, -1); } } if (opts.from_cache) { *res = opts.cache_val; } else { run_cmd_ctx_destroy(&opts.cmd_ctx); compiler_check_cache_set( wk, opts.cache_key, &(struct compiler_check_cache_value){ .success = true, .value = *res }); } compiler_check_log(wk, &opts, "sizeof %s: %" PRId64, get_cstr(wk, an[0].val), get_obj_number(wk, *res)); return true; } static bool func_compiler_alignment(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compiler_check_mode_run, }; if (!func_compiler_check_args_common( wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix)) { return false; } char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "#include \n" "#include \n" "%s\n" "struct tmp { char c; %s target; };\n" "int main(void) { printf(\"%%d\", (int)(offsetof(struct tmp, target))); return 0; }\n", compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val)); bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok) || !ok) { return false; } if (opts.from_cache) { *res = opts.cache_val; } else { *res = make_obj(wk, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); run_cmd_ctx_destroy(&opts.cmd_ctx); compiler_check_cache_set( wk, opts.cache_key, &(struct compiler_check_cache_value){ .success = true, .value = *res }); } compiler_check_log(wk, &opts, "alignment of %s: %" PRId64, get_cstr(wk, an[0].val), get_obj_number(wk, *res)); return true; } static bool func_compiler_compute_int(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compiler_check_mode_run, }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories | cm_kw_guess | cm_kw_high | cm_kw_low)) { return false; } char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "#include \n" "%s\n" "int main(void) {\n" "printf(\"%%ld\", (long)(%s));\n" "}\n", compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val)); bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok) || !ok) { return false; } if (opts.from_cache) { *res = opts.cache_val; } else { *res = make_obj(wk, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); run_cmd_ctx_destroy(&opts.cmd_ctx); compiler_check_cache_set( wk, opts.cache_key, &(struct compiler_check_cache_value){ .success = true, .value = *res }); } compiler_check_log(wk, &opts, "%s computed to %" PRId64, get_cstr(wk, an[0].val), get_obj_number(wk, *res)); return true; } static bool get_has_function_attribute_test(const struct str *name, const char **res) { /* These functions are based on the following code: * https://git.savannah.gnu.org/gitweb/?p=autoconf-archive.git;a=blob_plain;f=m4/ax_gcc_func_attribute.m4, * which is licensed under the following terms: * * Copyright (c) 2013 Gabriele Svelto * * Copying and distribution of this file, with or without modification, are * permitted in any medium without royalty provided the copyright notice * and this notice are preserved. This file is offered as-is, without any * warranty. */ struct { const char *name, *src; } tests[] = { { "alias", "#ifdef __cplusplus\n" "extern \"C\" {\n" "#endif\n" "int foo(void) { return 0; }\n" "int bar(void) __attribute__((alias(\"foo\")));\n" "#ifdef __cplusplus\n" "}\n" "#endif\n" }, { "aligned", "int foo(void) __attribute__((aligned(32)));\n" }, { "alloc_size", "void *foo(int a) __attribute__((alloc_size(1)));\n" }, { "always_inline", "inline __attribute__((always_inline)) int foo(void) { return 0; }\n" }, { "artificial", "inline __attribute__((artificial)) int foo(void) { return 0; }\n" }, { "cold", "int foo(void) __attribute__((cold));\n" }, { "const", "int foo(void) __attribute__((const));\n" }, { "constructor", "int foo(void) __attribute__((constructor));\n" }, { "constructor_priority", "int foo( void ) __attribute__((__constructor__(65535/2)));\n" }, { "deprecated", "int foo(void) __attribute__((deprecated(\"\")));\n" }, { "destructor", "int foo(void) __attribute__((destructor));\n" }, { "dllexport", "__declspec(dllexport) int foo(void) { return 0; }\n" }, { "dllimport", "__declspec(dllimport) int foo(void);\n" }, { "error", "int foo(void) __attribute__((error(\"\")));\n" }, { "externally_visible", "int foo(void) __attribute__((externally_visible));\n" }, { "fallthrough", "int foo( void ) {\n" " switch (0) {\n" " case 1: __attribute__((fallthrough));\n" " case 2: break;\n" " }\n" " return 0;\n" "};\n" }, { "flatten", "int foo(void) __attribute__((flatten));\n" }, { "format", "int foo(const char * p, ...) __attribute__((format(printf, 1, 2)));\n" }, { "format_arg", "char * foo(const char * p) __attribute__((format_arg(1)));\n" }, { "force_align_arg_pointer", "__attribute__((force_align_arg_pointer)) int foo(void) { return 0; }\n" }, { "gnu_inline", "inline __attribute__((gnu_inline)) int foo(void) { return 0; }\n" }, { "hot", "int foo(void) __attribute__((hot));\n" }, { "ifunc", "('int my_foo(void) { return 0; }'\n" " static int (*resolve_foo(void))(void) { return my_foo; }'\n" " int foo(void) __attribute__((ifunc(\"resolve_foo\")));'),\n" }, { "leaf", "__attribute__((leaf)) int foo(void) { return 0; }\n" }, { "malloc", "int *foo(void) __attribute__((malloc));\n" }, { "noclone", "int foo(void) __attribute__((noclone));\n" }, { "noinline", "__attribute__((noinline)) int foo(void) { return 0; }\n" }, { "nonnull", "int foo(char * p) __attribute__((nonnull(1)));\n" }, { "noreturn", "int foo(void) __attribute__((noreturn));\n" }, { "nothrow", "int foo(void) __attribute__((nothrow));\n" }, { "null_terminated_string_arg", "int foo(const char * p) __attribute__((null_terminated_string_arg(1)));\n" }, { "optimize", "__attribute__((optimize(3))) int foo(void) { return 0; }\n" }, { "packed", "struct __attribute__((packed)) foo { int bar; };\n" }, { "pure", "int foo(void) __attribute__((pure));\n" }, { "returns_nonnull", "int *foo(void) __attribute__((returns_nonnull));\n" }, { "section", "#if defined(__APPLE__) && defined(__MACH__)\n" " extern int foo __attribute__((section(\"__BAR,__bar\")));\n" "#else\n" " extern int foo __attribute__((section(\".bar\")));\n" "#endif\n" }, { "sentinel", "int foo(const char *bar, ...) __attribute__((sentinel));" }, { "unused", "int foo(void) __attribute__((unused));\n" }, { "used", "int foo(void) __attribute__((used));\n" }, { "vector_size", "__attribute__((vector_size(32))); int foo(void) { return 0; }\n" }, { "visibility", "int foo_def(void) __attribute__((visibility(\"default\")));\n" "int foo_hid(void) __attribute__((visibility(\"hidden\")));\n" "int foo_int(void) __attribute__((visibility(\"internal\")));\n" }, { "visibility:default", "int foo(void) __attribute__((visibility(\"default\")));\n" }, { "visibility:hidden", "int foo(void) __attribute__((visibility(\"hidden\")));\n" }, { "visibility:internal", "int foo(void) __attribute__((visibility(\"internal\")));\n" }, { "visibility:protected", "int foo(void) __attribute__((visibility(\"protected\")));\n" }, { "warning", "int foo(void) __attribute__((warning(\"\")));\n" }, { "warn_unused_result", "int foo(void) __attribute__((warn_unused_result));\n" }, { "weak", "int foo(void) __attribute__((weak));\n" }, { "weakref", "static int foo(void) { return 0; }\n" "static int var(void) __attribute__((weakref(\"foo\")));\n" }, { 0 } }; uint32_t i; for (i = 0; tests[i].name; ++i) { if (str_eql(name, &STRL(tests[i].name))) { *res = tests[i].src; return true; } } return false; } static bool compiler_has_function_attribute(struct workspace *wk, obj comp_id, uint32_t err_node, obj arg, bool *has_fattr) { struct compiler_check_opts opts = { .mode = compiler_check_mode_compile, .comp_id = comp_id, }; const char *src; if (!get_has_function_attribute_test(get_str(wk, arg), &src)) { vm_error_at(wk, err_node, "unknown attribute '%s'", get_cstr(wk, arg)); return false; } if (!compiler_check(wk, &opts, src, err_node, has_fattr)) { return false; } compiler_check_log(wk, &opts, "has attribute %s: %s", get_cstr(wk, arg), bool_to_yn(*has_fattr)); return true; } static bool func_compiler_has_function_attribute(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, 0 }; if (!pop_args(wk, an, akw)) { return false; } compiler_handle_has_required_kw_setup(requirement, kw_required); bool has_fattr; if (!compiler_has_function_attribute(wk, self, an[0].node, an[0].val, &has_fattr)) { return false; } compiler_handle_has_required_kw(requirement, has_fattr); *res = make_obj_bool(wk, has_fattr); return true; } struct func_compiler_get_supported_function_attributes_iter_ctx { uint32_t node; obj arr, compiler; }; static enum iteration_result func_compiler_get_supported_function_attributes_iter(struct workspace *wk, void *_ctx, obj val_id) { struct func_compiler_get_supported_function_attributes_iter_ctx *ctx = _ctx; bool has_fattr; if (!compiler_has_function_attribute(wk, ctx->compiler, ctx->node, val_id, &has_fattr)) { return ir_err; } if (has_fattr) { obj_array_push(wk, ctx->arr, val_id); } return ir_cont; } static bool func_compiler_get_supported_function_attributes(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj(wk, obj_array); return obj_array_foreach_flat(wk, an[0].val, &(struct func_compiler_get_supported_function_attributes_iter_ctx){ .compiler = self, .arr = *res, .node = an[0].node, }, func_compiler_get_supported_function_attributes_iter); } static bool func_compiler_has_function(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compiler_check_mode_link, }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories | cm_kw_required)) { return false; } compiler_handle_has_required_kw_setup(requirement, cc_kw_required); const char *prefix = compiler_check_prefix(wk, akw), *func = get_cstr(wk, an[0].val); bool prefix_contains_include = strstr(prefix, "#include") != NULL; char src[BUF_SIZE_4k]; if (prefix_contains_include) { snprintf(src, BUF_SIZE_4k, "%s\n" "#include \n" "#if defined __stub_%s || defined __stub___%s\n" "fail fail fail this function is not going to work\n" "#endif\n" "int main(void) {\n" "void *a = (void*) &%s;\n" "long long b = (long long) a;\n" "return (int) b;\n" "}\n", prefix, func, func, func); } else { snprintf(src, BUF_SIZE_4k, "#define %s muon_disable_define_of_%s\n" "%s\n" "#include \n" "#undef %s\n" "#ifdef __cplusplus\n" "extern \"C\"\n" "#endif\n" "char %s (void);\n" "#if defined __stub_%s || defined __stub___%s\n" "fail fail fail this function is not going to work\n" "#endif\n" "int main(void) { return %s(); }\n", func, func, prefix, func, func, func, func, func); } bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } if (!ok) { bool is_builtin = str_startswith(get_str(wk, an[0].val), &STR("__builtin_")); const char *__builtin_ = is_builtin ? "" : "__builtin_"; /* With some toolchains (MSYS2/mingw for example) the compiler * provides various builtins which are not really implemented and * fall back to the stdlib where they aren't provided and fail at * build/link time. In case the user provides a header, including * the header didn't lead to the function being defined, and the * function we are checking isn't a builtin itself we assume the * builtin is not functional and we just error out. */ snprintf(src, BUF_SIZE_4k, "%s\n" "int main(void) {\n" "#if !%d && !defined(%s) && !%d\n" " #error \"No definition for %s%s found in the prefix\"\n" "#endif\n" "#ifdef __has_builtin\n" " #if !__has_builtin(%s%s)\n" " #error \"%s%s not found\"\n" " #endif\n" "#elif ! defined(%s)\n" " %s%s;\n" "#endif\n" "return 0;\n" "}\n", prefix, !prefix_contains_include, func, is_builtin, __builtin_, func, __builtin_, func, __builtin_, func, func, __builtin_, func); if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } } compiler_handle_has_required_kw(requirement, ok); *res = make_obj_bool(wk, ok); compiler_check_log(wk, &opts, "has function %s: %s", get_cstr(wk, an[0].val), bool_to_yn(ok)); return true; } static bool compiler_has_header_symbol_c(struct workspace *wk, uint32_t node, struct compiler_check_opts *opts, const char *prefix, obj header, obj symbol, bool *res) { char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "#include <%s>\n" "int main(void) {\n" " /* If it's not defined as a macro, try to use as a symbol */\n" " #ifndef %s\n" " %s;\n" " #endif\n" " return 0;\n" "}\n", prefix, get_cstr(wk, header), get_cstr(wk, symbol), get_cstr(wk, symbol)); if (!compiler_check(wk, opts, src, node, res)) { return false; } return true; } static bool compiler_has_header_symbol_cpp(struct workspace *wk, uint32_t node, struct compiler_check_opts *opts, const char *prefix, obj header, obj symbol, bool *res) { char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "#include <%s>\n" "using %s;\n" "int main(void) {\n" " return 0;\n" "}\n", prefix, get_cstr(wk, header), get_cstr(wk, symbol)); if (!compiler_check(wk, opts, src, node, res)) { return false; } return true; } static bool func_compiler_has_header_symbol(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compiler_check_mode_compile, }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_required | cm_kw_include_directories)) { return false; } compiler_handle_has_required_kw_setup(required, cc_kw_required); bool ok; switch (get_obj_compiler(wk, self)->lang) { case compiler_language_c: if (!compiler_has_header_symbol_c( wk, an[0].node, &opts, compiler_check_prefix(wk, akw), an[0].val, an[1].val, &ok)) { return false; } break; case compiler_language_cpp: if (!compiler_has_header_symbol_c( wk, an[0].node, &opts, compiler_check_prefix(wk, akw), an[0].val, an[1].val, &ok)) { return false; } if (!ok) { if (!compiler_has_header_symbol_cpp( wk, an[0].node, &opts, compiler_check_prefix(wk, akw), an[0].val, an[1].val, &ok)) { return false; } } break; default: UNREACHABLE; } compiler_handle_has_required_kw(required, ok); *res = make_obj_bool(wk, ok); compiler_check_log(wk, &opts, "header %s has symbol %s: %s", get_cstr(wk, an[0].val), get_cstr(wk, an[1].val), bool_to_yn(ok)); return true; } static bool compiler_get_define(struct workspace *wk, uint32_t err_node, struct compiler_check_opts *opts, bool check_only, const char *prefix, const char *def, obj *res) { TSTR(output_path); path_join(wk, &output_path, wk->muon_private, "get_define_output"); opts->output_path = output_path.buf; opts->mode = compiler_check_mode_preprocess; char src[BUF_SIZE_4k]; const struct str delim_start = STR("\"MUON_GET_DEFINE_DELIMITER_START\"\n"), delim_end = STR("\n\"MUON_GET_DEFINE_DELIMITER_END\""), delim_sentinel = STR("\"MUON_GET_DEFINE_UNDEFINED_SENTINEL\""); snprintf(src, BUF_SIZE_4k, "%s\n" "#ifndef %s\n" "#define %s %s\n" "#endif \n" "%s%s%s\n", prefix, def, def, delim_sentinel.s, delim_start.s, def, delim_end.s); struct source output = { 0 }; bool ok; if (!compiler_check(wk, opts, src, err_node, &ok)) { return false; } else if (!ok) { goto failed; } if (opts->from_cache) { *res = opts->cache_val; goto done; } if (!fs_read_entire_file(output_path.buf, &output)) { return false; } *res = 0; bool started = false; bool in_quotes = false; bool esc = false; bool joining = false; uint32_t i; for (i = 0; i < output.len; ++i) { struct str output_s = { &output.src[i], output.len - i }; if (!started && str_startswith(&output_s, &delim_start)) { started = true; *res = make_str(wk, ""); // Check for delim_end right after delim_start. If there is no // value they will share a newline so we need to do the check here. i += delim_start.len - 1; output_s = (struct str){ &output.src[i], output.len - i }; if (str_startswith(&output_s, &delim_end)) { break; } ++i; output_s = (struct str){ &output.src[i], output.len - i }; if (i >= output.len) { break; } } if (!started) { continue; } if (str_startswith(&output_s, &delim_end)) { break; } switch (output.src[i]) { case '"': if (esc) { esc = false; } else { in_quotes = !in_quotes; if (!in_quotes || joining) { uint32_t start = i; ++i; for (; i < output.len; ++i) { if (!strchr("\t ", output.src[i])) { break; } } if (output.src[i] == '"') { joining = true; ++i; } else { i = start; } } } break; case '\\': esc = true; break; } if (output.src[i] == '\n') { break; } if (started) { str_appn(wk, res, &output.src[i], 1); } } fs_source_destroy(&output); if (*res && str_eql(get_str(wk, *res), &delim_sentinel)) { *res = 0; } compiler_check_cache_set( wk, opts->cache_key, &(struct compiler_check_cache_value){ .success = true, .value = *res }); done: if (check_only) { compiler_check_log(wk, opts, "defines %s %s", def, bool_to_yn(!!*res)); } else { if (!*res) { *res = make_str(wk, ""); } compiler_check_log(wk, opts, "defines %s as '%s'", def, get_cstr(wk, *res)); } return true; failed: fs_source_destroy(&output); vm_error_at(wk, err_node, "failed to %s define: '%s'", check_only ? "check" : "get", def); return false; } static bool func_compiler_get_define(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { 0 }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories)) { return false; } if (!compiler_get_define( wk, an[0].node, &opts, false, compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val), res)) { return false; } return true; } static bool func_compiler_has_define(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { 0 }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories | cm_kw_required)) { return false; } compiler_handle_has_required_kw_setup(required, cc_kw_required); if (!compiler_get_define( wk, an[0].node, &opts, true, compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val), res)) { return false; } compiler_handle_has_required_kw(required, !!*res); obj b; b = make_obj_bool(wk, !!*res); *res = b; return true; } static bool func_compiler_symbols_have_underscore_prefix(struct workspace *wk, obj self, obj *res) { struct compiler_check_opts opts = { .comp_id = self }; if (!pop_args(wk, NULL, NULL)) { return false; } obj pre; if (!compiler_get_define(wk, 0, &opts, false, "", "__USER_LABEL_PREFIX__", &pre)) { return false; } *res = make_obj_bool(wk, str_eql(get_str(wk, pre), &STR("_"))); return true; } static bool func_compiler_check_common(struct workspace *wk, obj self, obj *res, enum compiler_check_mode mode) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = mode, }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_name | cm_kw_include_directories | cm_kw_werror | cm_kw_required)) { return false; } compiler_handle_has_required_kw_setup(requirement, cc_kw_required); enum obj_type t = get_obj_type(wk, an[0].val); const char *src; switch (t) { case obj_string: src = get_cstr(wk, an[0].val); break; case obj_file: { src = get_file_path(wk, an[0].val); opts.src_is_path = true; break; } default: vm_error_at(wk, an[0].node, "expected file or string, got %s", obj_type_to_s(t)); return false; } bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } if (akw[cc_kw_name].set) { const char *mode_s = NULL; switch (mode) { case compiler_check_mode_run: mode_s = "runs"; break; case compiler_check_mode_link: mode_s = "links"; break; case compiler_check_mode_compile: mode_s = "compiles"; break; case compiler_check_mode_preprocess: mode_s = "preprocesses"; break; } compiler_check_log(wk, &opts, "%s %s: %s", get_cstr(wk, akw[cc_kw_name].val), mode_s, bool_to_yn(ok)); } compiler_handle_has_required_kw(requirement, ok); *res = make_obj_bool(wk, ok); return true; } static bool func_compiler_compiles(struct workspace *wk, obj self, obj *res) { return func_compiler_check_common(wk, self, res, compiler_check_mode_compile); } static bool func_compiler_links(struct workspace *wk, obj self, obj *res) { return func_compiler_check_common(wk, self, res, compiler_check_mode_link); } static bool compiler_check_header(struct workspace *wk, uint32_t err_node, struct compiler_check_opts *opts, const char *prefix, const char *hdr, enum requirement_type required, obj *res) { char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "#include <%s>\n" "int main(void) {}\n", prefix, hdr); bool ok; if (!compiler_check(wk, opts, src, err_node, &ok)) { return false; } const char *mode_s = NULL; switch (opts->mode) { case compiler_check_mode_compile: mode_s = "is usable"; break; case compiler_check_mode_preprocess: mode_s = "found"; break; default: abort(); } compiler_handle_has_required_kw(required, ok); *res = make_obj_bool(wk, ok); compiler_check_log(wk, opts, "header %s %s: %s", hdr, mode_s, bool_to_yn(ok)); return true; } static bool compiler_check_header_common(struct workspace *wk, obj self, obj *res, enum compiler_check_mode mode) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = mode, }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_required | cm_kw_include_directories)) { return false; } compiler_handle_has_required_kw_setup(required, cc_kw_required); return compiler_check_header( wk, an[0].node, &opts, compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val), required, res); } static bool func_compiler_has_header(struct workspace *wk, obj self, obj *res) { return compiler_check_header_common(wk, self, res, compiler_check_mode_preprocess); } static bool func_compiler_check_header(struct workspace *wk, obj self, obj *res) { return compiler_check_header_common(wk, self, res, compiler_check_mode_compile); } static bool func_compiler_has_type(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compiler_check_mode_compile, }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories | cm_kw_required)) { return false; } compiler_handle_has_required_kw_setup(required, cc_kw_required); char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "void bar(void) { sizeof(%s); }\n", compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val)); bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } compiler_handle_has_required_kw(required, ok); *res = make_obj_bool(wk, ok); compiler_check_log(wk, &opts, "has type %s: %s", get_cstr(wk, an[0].val), bool_to_yn(ok)); return true; } static bool compiler_has_member(struct workspace *wk, struct compiler_check_opts *opts, uint32_t err_node, const char *prefix, obj target, obj member, bool *res) { opts->mode = compiler_check_mode_compile; char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "void bar(void) {\n" "%s foo;\n" "foo.%s;\n" "}\n", prefix, get_cstr(wk, target), get_cstr(wk, member)); if (!compiler_check(wk, opts, src, err_node, res)) { return false; } compiler_check_log( wk, opts, "struct %s has member %s: %s", get_cstr(wk, target), get_cstr(wk, member), bool_to_yn(*res)); return true; } static bool func_compiler_has_member(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { 0 }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories | cm_kw_required)) { return false; } compiler_handle_has_required_kw_setup(required, cc_kw_required); bool ok; if (!compiler_has_member(wk, &opts, an[0].node, compiler_check_prefix(wk, akw), an[0].val, an[1].val, &ok)) { return false; } compiler_handle_has_required_kw(required, ok); *res = make_obj_bool(wk, ok); return true; } struct compiler_has_members_ctx { struct compiler_check_opts *opts; uint32_t node; const char *prefix; obj target; bool ok; }; static enum iteration_result compiler_has_members_iter(struct workspace *wk, void *_ctx, obj val) { struct compiler_has_members_ctx *ctx = _ctx; if (!typecheck(wk, ctx->node, val, obj_string)) { return ir_err; } bool ok; if (!compiler_has_member(wk, ctx->opts, ctx->node, ctx->prefix, ctx->target, val, &ok)) { return ir_err; } if (!ok) { ctx->ok = false; return ir_done; } return ir_cont; } static bool func_compiler_has_members(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { 0 }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories | cm_kw_required)) { return false; } compiler_handle_has_required_kw_setup(required, cc_kw_required); if (!get_obj_array(wk, an[1].val)->len) { vm_error_at(wk, an[1].node, "missing member arguments"); return false; } struct compiler_has_members_ctx ctx = { .opts = &opts, .node = an[0].node, .prefix = compiler_check_prefix(wk, akw), .target = an[0].val, .ok = true, }; if (!obj_array_foreach_flat(wk, an[1].val, &ctx, compiler_has_members_iter)) { return false; } compiler_handle_has_required_kw(required, ctx.ok); *res = make_obj_bool(wk, ctx.ok); return true; } static bool func_compiler_run(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compiler_check_mode_run, .skip_run_check = true, }; if (!func_compiler_check_args_common(wk, self, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_name | cm_kw_werror | cm_kw_required)) { return false; } obj o; if (!obj_array_flatten_one(wk, an[0].val, &o)) { vm_error_at(wk, an[0].node, "could not flatten argument"); } enum obj_type t = get_obj_type(wk, an[0].val); const char *src; switch (t) { case obj_string: src = get_cstr(wk, an[0].val); break; case obj_file: { src = get_file_path(wk, an[0].val); opts.src_is_path = true; break; } default: vm_error_at(wk, an[0].node, "expected file or string, got %s", obj_type_to_s(t)); return false; } enum requirement_type requirement; { if (!akw[cc_kw_required].set) { requirement = requirement_auto; } else if (!coerce_requirement(wk, &akw[cc_kw_required], &requirement)) { return false; } if (requirement == requirement_skip) { *res = make_obj(wk, obj_run_result); struct obj_run_result *rr = get_obj_run_result(wk, *res); rr->flags |= run_result_flag_from_compile; return true; } } bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } if (akw[cc_kw_name].set) { compiler_check_log(wk, &opts, "runs %s: %s", get_cstr(wk, akw[cc_kw_name].val), bool_to_yn(ok)); } compiler_handle_has_required_kw(requirement, ok); if (opts.from_cache) { *res = opts.cache_val; } else { *res = make_obj(wk, obj_run_result); struct obj_run_result *rr = get_obj_run_result(wk, *res); rr->flags |= run_result_flag_from_compile; if (ok) { rr->flags |= run_result_flag_compile_ok; rr->out = make_strn(wk, opts.cmd_ctx.out.buf, opts.cmd_ctx.out.len); rr->err = make_strn(wk, opts.cmd_ctx.err.buf, opts.cmd_ctx.err.len); rr->status = opts.cmd_ctx.status; } compiler_check_cache_set( wk, opts.cache_key, &(struct compiler_check_cache_value){ .success = ok, .value = *res }); run_cmd_ctx_destroy(&opts.cmd_ctx); } return true; } static bool compiler_has_argument(struct workspace *wk, obj comp_id, uint32_t err_node, obj arg, bool *has_argument, enum compiler_check_mode mode) { struct obj_compiler *comp = get_obj_compiler(wk, comp_id); obj args; args = make_obj(wk, obj_array); push_args(wk, args, toolchain_compiler_werror(wk, comp)); if (get_obj_type(wk, arg) == obj_string) { obj_array_push(wk, args, arg); } else { obj_array_extend(wk, args, arg); obj str; obj_array_join(wk, true, arg, make_str(wk, " "), &str); arg = str; } struct compiler_check_opts opts = { .mode = mode, .comp_id = comp_id, .args = args, .keep_cmd_ctx = true, }; const char *src = "int main(void){}\n"; if (!compiler_check(wk, &opts, src, err_node, has_argument)) { return false; } if (!opts.from_cache) { if (comp->type[toolchain_component_compiler] == compiler_msvc) { // Check for msvc command line warning D9002 : ignoring unknown option if (opts.cmd_ctx.err.len && strstr(opts.cmd_ctx.err.buf, "D9002")) { *has_argument = false; compiler_check_cache_set( wk, opts.cache_key, &(struct compiler_check_cache_value){ .success = *has_argument }); } } run_cmd_ctx_destroy(&opts.cmd_ctx); } compiler_check_log(wk, &opts, "supports argument '%s': %s", get_cstr(wk, arg), bool_to_yn(*has_argument)); return true; } struct func_compiler_get_supported_arguments_iter_ctx { uint32_t node; obj arr, compiler; enum compiler_check_mode mode; }; static enum iteration_result func_compiler_get_supported_arguments_iter(struct workspace *wk, void *_ctx, obj val_id) { struct func_compiler_get_supported_arguments_iter_ctx *ctx = _ctx; bool has_argument; if (!compiler_has_argument(wk, ctx->compiler, ctx->node, val_id, &has_argument, ctx->mode)) { return false; } if (has_argument) { obj_array_push(wk, ctx->arr, val_id); } return ir_cont; } static bool compiler_has_argument_common(struct workspace *wk, obj self, type_tag glob, obj *res, enum compiler_check_mode mode) { struct args_norm an[] = { { glob | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, 0 }; if (!pop_args(wk, an, akw)) { return false; } compiler_handle_has_required_kw_setup(requirement, kw_required); bool has_argument; if (!compiler_has_argument(wk, self, an[0].node, an[0].val, &has_argument, mode)) { return false; } compiler_handle_has_required_kw(requirement, has_argument); *res = make_obj_bool(wk, has_argument); return true; } static bool func_compiler_has_argument(struct workspace *wk, obj self, obj *res) { return compiler_has_argument_common(wk, self, 0, res, compiler_check_mode_compile); } static bool func_compiler_has_link_argument(struct workspace *wk, obj self, obj *res) { return compiler_has_argument_common(wk, self, 0, res, compiler_check_mode_link); } static bool func_compiler_has_multi_arguments(struct workspace *wk, obj self, obj *res) { return compiler_has_argument_common(wk, self, TYPE_TAG_GLOB, res, compiler_check_mode_compile); } static bool func_compiler_has_multi_link_arguments(struct workspace *wk, obj self, obj *res) { return compiler_has_argument_common(wk, self, TYPE_TAG_GLOB, res, compiler_check_mode_link); } static bool compiler_get_supported_arguments(struct workspace *wk, obj self, obj *res, enum compiler_check_mode mode) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj(wk, obj_array); return obj_array_foreach_flat(wk, an[0].val, &(struct func_compiler_get_supported_arguments_iter_ctx){ .compiler = self, .arr = *res, .node = an[0].node, .mode = mode, }, func_compiler_get_supported_arguments_iter); } static bool func_compiler_get_supported_arguments(struct workspace *wk, obj self, obj *res) { return compiler_get_supported_arguments(wk, self, res, compiler_check_mode_compile); } static bool func_compiler_get_supported_link_arguments(struct workspace *wk, obj self, obj *res) { return compiler_get_supported_arguments(wk, self, res, compiler_check_mode_link); } static enum iteration_result func_compiler_first_supported_argument_iter(struct workspace *wk, void *_ctx, obj val_id) { struct func_compiler_get_supported_arguments_iter_ctx *ctx = _ctx; bool has_argument; if (!compiler_has_argument(wk, ctx->compiler, ctx->node, val_id, &has_argument, ctx->mode)) { return false; } if (has_argument) { compiler_log(wk, ctx->compiler, "first supported argument: '%s'", get_cstr(wk, val_id)); obj_array_push(wk, ctx->arr, val_id); return ir_done; } return ir_cont; } static bool compiler_first_supported_argument(struct workspace *wk, obj self, obj *res, enum compiler_check_mode mode) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } *res = make_obj(wk, obj_array); return obj_array_foreach_flat(wk, an[0].val, &(struct func_compiler_get_supported_arguments_iter_ctx){ .compiler = self, .arr = *res, .node = an[0].node, .mode = mode, }, func_compiler_first_supported_argument_iter); } static bool func_compiler_first_supported_argument(struct workspace *wk, obj self, obj *res) { return compiler_first_supported_argument(wk, self, res, compiler_check_mode_compile); } static bool func_compiler_first_supported_link_argument(struct workspace *wk, obj self, obj *res) { return compiler_first_supported_argument(wk, self, res, compiler_check_mode_link); } static bool func_compiler_get_id(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_str(wk, compiler_type_to_s(get_obj_compiler(wk, self)->type[toolchain_component_compiler])); return true; } static bool func_compiler_get_linker_id(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_str(wk, linker_type_to_s(get_obj_compiler(wk, self)->type[toolchain_component_linker])); return true; } static bool func_compiler_get_argument_syntax(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } const char *syntax; enum compiler_type type = get_obj_compiler(wk, self)->type[toolchain_component_compiler]; switch (type) { case compiler_gcc: case compiler_clang: case compiler_apple_clang: syntax = "gcc"; break; case compiler_clang_cl: case compiler_msvc: syntax = "msvc"; break; case compiler_posix: default: syntax = "other"; break; } *res = make_str(wk, syntax); return true; } static obj find_library_check_dirs(struct workspace *wk, const char *libname, obj libdirs, const char **exts, uint32_t exts_len) { static const char *pref[] = { "", "lib" }; TSTR(path); TSTR(lib); uint32_t i, j; obj libdir; obj_array_for(wk, libdirs, libdir) { for (i = 0; i < exts_len; ++i) { for (j = 0; j < ARRAY_LEN(pref); ++j) { tstr_clear(&lib); tstr_pushf(wk, &lib, "%s%s%s", pref[j], libname, exts[i]); path_join(wk, &path, get_cstr(wk, libdir), lib.buf); if (fs_file_exists(path.buf)) { return tstr_into_str(wk, &path); } } } } return 0; } struct find_library_result find_library(struct workspace *wk, obj compiler, const char *libname, obj extra_dirs, enum find_library_flag flags) { static const char *ext_order_static_preferred[] = { COMPILER_STATIC_LIB_EXTS, COMPILER_DYNAMIC_LIB_EXTS }, *ext_order_static_only[] = { COMPILER_STATIC_LIB_EXTS }, *ext_order_dynamic_preferred[] = { COMPILER_DYNAMIC_LIB_EXTS, COMPILER_STATIC_LIB_EXTS }; const char **ext_order; uint32_t ext_order_len; if (flags & find_library_flag_only_static) { ext_order = ext_order_static_only; ext_order_len = ARRAY_LEN(ext_order_static_only); } else if (flags & find_library_flag_prefer_static) { ext_order = ext_order_static_preferred; ext_order_len = ARRAY_LEN(ext_order_static_preferred); } else { ext_order = ext_order_dynamic_preferred; ext_order_len = ARRAY_LEN(ext_order_dynamic_preferred); } obj found = 0; // First check in dirs if the kw is set. if (extra_dirs) { if ((found = find_library_check_dirs(wk, libname, extra_dirs, ext_order, ext_order_len))) { return (struct find_library_result){ found, find_library_found_location_extra_dirs }; } } if (!compiler) { // If we don't have a compiler then just assume that -l $libname works return (struct find_library_result){ make_str(wk, libname), find_library_found_location_link_arg }; } struct obj_compiler *comp = get_obj_compiler(wk, compiler); // Next, check system libdirs if (!found) { if ((found = find_library_check_dirs(wk, libname, comp->libdirs, ext_order, ext_order_len))) { return (struct find_library_result){ found, find_library_found_location_system_dirs }; } } // Finally, just attempt to pass the libname on the linker command line if (!found) { bool ok = false; struct compiler_check_opts opts = { .mode = compiler_check_mode_link, .comp_id = compiler }; opts.args = make_obj(wk, obj_array); push_args(wk, opts.args, toolchain_linker_lib(wk, comp, libname)); const char *src = "int main(void) { return 0; }\n"; if (!compiler_check(wk, &opts, src, 0, &ok)) { return (struct find_library_result){ 0 }; } if (ok) { return (struct find_library_result){ make_str(wk, libname), find_library_found_location_link_arg }; } } return (struct find_library_result){ 0 }; } void find_library_result_to_dependency(struct workspace *wk, struct find_library_result find_result, obj compiler, obj d) { struct obj_compiler *comp = get_obj_compiler(wk, compiler); struct obj_dependency *dep = get_obj_dependency(wk, d); dep->name = find_result.found; dep->type = dependency_type_external_library; dep->flags |= dep_flag_found; dep->machine = comp->machine; struct build_dep_raw raw = { 0 }; if (find_result.location == find_library_found_location_link_arg) { raw.link_with_not_found = make_obj(wk, obj_array); obj_array_push(wk, raw.link_with_not_found, find_result.found); } else { raw.link_with = make_obj(wk, obj_array); obj_array_push(wk, raw.link_with, find_result.found); if (find_result.location == find_library_found_location_extra_dirs) { raw.rpath = make_obj(wk, obj_array); TSTR(dirname); path_dirname(wk, &dirname, get_cstr(wk, find_result.found)); obj_array_push(wk, raw.rpath, tstr_into_str(wk, &dirname)); } } if (!dependency_create(wk, &raw, &dep->dep, 0)) { UNREACHABLE; } dep->dep.link_language = comp->lang; } struct compiler_find_library_check_headers_ctx { uint32_t err_node; struct compiler_check_opts *opts; const char *prefix; bool ok; }; static enum iteration_result compiler_find_library_check_headers_iter(struct workspace *wk, void *_ctx, obj hdr) { struct compiler_find_library_check_headers_ctx *ctx = _ctx; obj res; if (!compiler_check_header( wk, ctx->err_node, ctx->opts, ctx->prefix, get_cstr(wk, hdr), requirement_auto, &res)) { return ir_err; } ctx->ok &= get_obj_bool(wk, res); return ir_cont; } static bool func_compiler_find_library(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_static, kw_disabler, kw_dirs, // has_headers kw_has_headers, kw_header_required, kw_header_args, kw_header_dependencies, kw_header_include_directories, kw_header_no_builtin_args, // TODO kw_header_prefix, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_static] = { "static", obj_bool }, [kw_disabler] = { "disabler", obj_bool }, [kw_dirs] = { "dirs", TYPE_TAG_LISTIFY | obj_string }, // has_headers [kw_has_headers] = { "has_headers", TYPE_TAG_LISTIFY | obj_string }, [kw_header_required] = { "header_required", obj_bool }, [kw_header_args] = { "header_args", TYPE_TAG_LISTIFY | obj_string }, [kw_header_dependencies] = { "header_dependencies", TYPE_TAG_LISTIFY | tc_dependency }, [kw_header_include_directories] = { "header_include_directories", TYPE_TAG_LISTIFY | tc_coercible_inc }, [kw_header_no_builtin_args] = { "header_no_builtin_args", }, [kw_header_prefix] = { "header_prefix", }, 0 }; if (!pop_args(wk, an, akw)) { return false; } *res = make_obj(wk, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *res); dep->type = dependency_type_external_library; // Validate args if (!akw[kw_has_headers].set) { uint32_t i; for (i = kw_header_required; i <= kw_header_prefix; ++i) { if (akw[i].set) { vm_error_at(wk, akw[i].node, "header_ keywords are invalid without " "also specifying the has_headers keyword"); return false; } } } enum requirement_type requirement; { // Handle disabled libs if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } if (requirement == requirement_skip) { return true; } } // Determine requested lib type enum find_library_flag flags = 0; { if (!akw[kw_static].set) { get_option_value(wk, current_project(wk), "prefer_static", &akw[kw_static].val); } if (get_obj_bool(wk, akw[kw_static].val)) { flags |= find_library_flag_only_static; } } struct find_library_result find_result = find_library(wk, self, get_cstr(wk, an[0].val), akw[kw_dirs].val, flags); bool found = find_result.found != 0; if (found && akw[kw_has_headers].set) { struct compiler_check_opts opts = { 0 }; struct args_kw header_kwargs[cc_kwargs_count + 1] = { [cc_kw_args] = akw[kw_header_args], [cc_kw_dependencies] = akw[kw_header_dependencies], [cc_kw_prefix] = akw[kw_header_prefix], [cc_kw_required] = akw[kw_header_required], [cc_kw_include_directories] = akw[kw_header_include_directories], }; compiler_opts_init(self, header_kwargs, &opts); struct compiler_find_library_check_headers_ctx check_headers_ctx = { .ok = true, .err_node = akw[kw_has_headers].node, .opts = &opts, .prefix = compiler_check_prefix(wk, header_kwargs), }; obj_array_foreach( wk, akw[kw_has_headers].val, &check_headers_ctx, compiler_find_library_check_headers_iter); if (!check_headers_ctx.ok) { found = false; } } compiler_log(wk, self, "library %s found: %s", get_cstr(wk, an[0].val), bool_to_yn(found)); if (found) { obj_lprintf(wk, log_debug, "library resolved to %#o\n", find_result.found); find_library_result_to_dependency(wk, find_result, self, *res); } else { if (requirement == requirement_required) { vm_error_at(wk, an[0].node, "required library not found"); return false; } if (akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val)) { *res = obj_disabler; } } return true; } static bool func_compiler_preprocess(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_string | tc_file | tc_custom_target | tc_generated_list }, ARG_TYPE_NULL }; enum kwargs { kw_compile_args, kw_include_directories, kw_output, kw_dependencies, kw_depends, }; struct args_kw akw[] = { [kw_compile_args] = { "compile_args", TYPE_TAG_LISTIFY | tc_string }, [kw_include_directories] = { "include_directories", TYPE_TAG_LISTIFY | tc_coercible_inc }, [kw_output] = { "output", tc_string }, [kw_dependencies] = { "dependencies", TYPE_TAG_LISTIFY | tc_dependency }, [kw_depends] = { "depends", tc_depends_kw }, 0, }; if (!pop_args(wk, an, akw)) { return false; } struct obj_compiler *comp = get_obj_compiler(wk, self); obj depends = 0; if (akw[kw_depends].set) { if (!coerce_files(wk, akw[kw_depends].node, akw[kw_depends].val, &depends)) { return false; } } obj base_cmd; obj_array_dup(wk, comp->cmd_arr[toolchain_component_compiler], &base_cmd); push_args(wk, base_cmd, toolchain_compiler_preprocess_only(wk, comp)); const char *lang = 0; switch (comp->lang) { case compiler_language_c: lang = "c"; break; case compiler_language_cpp: lang = "c++"; break; case compiler_language_objc: lang = "objective-c"; break; default: vm_error(wk, "compiler for language %s does not support preprocess()", compiler_language_to_s(comp->lang)); return false; } push_args(wk, base_cmd, toolchain_compiler_specify_lang(wk, comp, lang)); ca_get_std_args(wk, comp, current_project(wk), NULL, base_cmd); ca_get_option_compile_args(wk, comp, current_project(wk), NULL, base_cmd); bool have_dep = false; struct build_dep dep = { 0 }; if (akw[kw_dependencies].set) { have_dep = true; dep_process_deps(wk, akw[kw_dependencies].val, &dep); obj_array_extend_nodup(wk, base_cmd, dep.compile_args); } push_args(wk, base_cmd, toolchain_compiler_include(wk, comp, "@OUTDIR@")); push_args(wk, base_cmd, toolchain_compiler_include(wk, comp, "@CURRENT_SOURCE_DIR@")); if (!add_include_directory_args(wk, &akw[kw_include_directories], have_dep ? &dep : 0, self, base_cmd)) { return false; } if (akw[kw_compile_args].set) { obj_array_extend(wk, base_cmd, akw[kw_compile_args].val); } *res = make_obj(wk, obj_array); TSTR(output_dir); tstr_pushs(wk, &output_dir, get_cstr(wk, current_project(wk)->build_dir)); path_push(wk, &output_dir, "preprocess.p"); if (!fs_mkdir_p(output_dir.buf)) { return false; } obj output; if (akw[kw_output].set) { output = akw[kw_output].val; } else { output = make_str(wk, "@PLAINNAME@.i"); } obj v; obj_array_for(wk, an[0].val, v) { obj cmd; obj_array_dup(wk, base_cmd, &cmd); push_args(wk, cmd, toolchain_compiler_output(wk, comp, "@OUTPUT@")); obj_array_push(wk, cmd, make_str(wk, "@INPUT@")); struct make_custom_target_opts opts = { .input_node = an[0].node, .input_orig = v, .output_orig = output, .output_dir = output_dir.buf, .command_orig = cmd, .extra_args_valid = true, }; obj tgt; if (!make_custom_target(wk, &opts, &tgt)) { return false; } struct obj_custom_target *t = get_obj_custom_target(wk, tgt); obj output; if (!obj_array_flatten_one(wk, t->output, &output)) { UNREACHABLE; } t->name = make_strf(wk, "", get_file_path(wk, output)); if (depends) { obj_array_extend_nodup(wk, t->depends, depends); } obj_array_push(wk, current_project(wk)->targets, tgt); obj_array_push(wk, *res, output); } return true; } static bool func_compiler_cmd_array(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_compiler(wk, self)->cmd_arr[toolchain_component_compiler]; return true; } static bool func_compiler_version(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = get_obj_compiler(wk, self)->ver; return true; } const struct func_impl impl_tbl_compiler[] = { { "alignment", func_compiler_alignment, tc_number }, { "check_header", func_compiler_check_header, tc_bool }, { "cmd_array", func_compiler_cmd_array, tc_array }, { "compiles", func_compiler_compiles, tc_bool }, { "compute_int", func_compiler_compute_int, tc_number }, { "find_library", func_compiler_find_library, tc_dependency }, { "first_supported_argument", func_compiler_first_supported_argument, tc_array }, { "first_supported_link_argument", func_compiler_first_supported_link_argument, tc_array }, { "get_argument_syntax", func_compiler_get_argument_syntax, tc_string }, { "get_define", func_compiler_get_define, tc_string }, { "get_id", func_compiler_get_id, tc_string }, { "get_linker_id", func_compiler_get_linker_id, tc_string }, { "get_supported_arguments", func_compiler_get_supported_arguments, tc_array }, { "get_supported_function_attributes", func_compiler_get_supported_function_attributes, tc_array }, { "get_supported_link_arguments", func_compiler_get_supported_link_arguments, tc_array }, { "has_argument", func_compiler_has_argument, tc_bool }, { "has_define", func_compiler_has_define, tc_bool }, { "has_function", func_compiler_has_function, tc_bool }, { "has_function_attribute", func_compiler_has_function_attribute, tc_bool }, { "has_header", func_compiler_has_header, tc_bool }, { "has_header_symbol", func_compiler_has_header_symbol, tc_bool }, { "has_link_argument", func_compiler_has_link_argument, tc_bool }, { "has_member", func_compiler_has_member, tc_bool }, { "has_members", func_compiler_has_members, tc_bool }, { "has_multi_arguments", func_compiler_has_multi_arguments, tc_bool }, { "has_multi_link_arguments", func_compiler_has_multi_link_arguments, tc_bool }, { "has_type", func_compiler_has_type, tc_bool }, { "links", func_compiler_links, tc_bool }, { "preprocess", func_compiler_preprocess, tc_array }, { "run", func_compiler_run, tc_run_result }, { "sizeof", func_compiler_sizeof, tc_number }, { "symbols_have_underscore_prefix", func_compiler_symbols_have_underscore_prefix, tc_bool }, { "version", func_compiler_version, tc_string }, { NULL, NULL }, }; static bool validate_toolchain_handlers(struct workspace *wk, obj handlers, enum toolchain_component component) { const struct toolchain_arg_handler *handler; obj k, v; obj_dict_for(wk, handlers, k, v) { if (!(handler = get_toolchain_arg_handler_info(component, get_cstr(wk, k)))) { vm_error(wk, "unknown toolchain function %o", k); return false; } if (get_obj_type(wk, v) != obj_capture) { continue; } struct obj_func *f = get_obj_capture(wk, v)->func; if (f->nkwargs) { vm_error(wk, "toolchain function %o has an invalid signature: accepts kwargs", k); return false; } else if (!type_tags_eql(wk, f->return_type, make_complex_type(wk, complex_type_nested, tc_array, tc_string))) { vm_error( wk, "toolchain function %o has an invalid signature: return type must be list[str]", k); return false; } type_tag expected_sig[2]; uint32_t expected_sig_len; toolchain_arg_arity_to_sig(handler->arity, expected_sig, &expected_sig_len); bool sig_valid; switch (f->nargs) { case 0: { sig_valid = expected_sig_len == 0; break; } case 1: { sig_valid = expected_sig_len == 1 && expected_sig[0] == f->an[0].type; break; } case 2: { sig_valid = expected_sig_len == 2 && expected_sig[0] == f->an[0].type && expected_sig[1] == f->an[1].type; break; } default: sig_valid = false; } if (!sig_valid) { obj expected = make_str(wk, "("); uint32_t i; for (i = 0; i < expected_sig_len; ++i) { str_app(wk, &expected, typechecking_type_to_s(wk, expected_sig[i])); if (i + 1 < expected_sig_len) { str_app(wk, &expected, ", "); } } str_app(wk, &expected, ")"); vm_error(wk, "toolchain function %o has an invalid signature: expected signature: %#o", k, expected); return false; } } return true; } static bool func_compiler_configure(struct workspace *wk, obj self, obj *res) { type_tag override_type = make_complex_type(wk, complex_type_nested, tc_dict, make_complex_type(wk, complex_type_or, tc_capture, make_complex_type(wk, complex_type_nested, tc_array, tc_string))); struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_overwrite, kw_cmd_array, kw_handlers, kw_libdirs, kw_version, }; struct args_kw akw[] = { [kw_overwrite] = { "overwrite", tc_bool }, [kw_cmd_array] = { "cmd_array", TYPE_TAG_LISTIFY | tc_string }, [kw_handlers] = { "handlers", override_type }, [kw_libdirs] = { "libdirs", TYPE_TAG_LISTIFY | tc_string }, [kw_version] = { "version", tc_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum toolchain_component component; if (!toolchain_component_from_s(get_cstr(wk, an[0].val), &component)) { vm_error(wk, "unknown toolchain component %o", an[0].val); return false; } struct obj_compiler *c = get_obj_compiler(wk, self); if (akw[kw_handlers].set) { obj *overrides = &c->overrides[component]; bool overwrite = akw[kw_overwrite].set ? get_obj_bool(wk, akw[kw_overwrite].val) : *overrides == 0; if (!validate_toolchain_handlers(wk, akw[kw_handlers].val, component)) { return false; } if (overwrite) { *overrides = akw[kw_handlers].val; } else if (!*overrides) { vm_error(wk, "unable to merge overrides: there are no existing overrides"); return false; } else { obj_dict_merge_nodup(wk, *overrides, akw[kw_handlers].val); } } if (akw[kw_cmd_array].set) { c->cmd_arr[component] = akw[kw_cmd_array].val; } if (akw[kw_libdirs].set) { if (component != toolchain_component_compiler) { vm_error(wk, "libdirs only configurable for compiler"); return false; } c->libdirs = akw[kw_libdirs].val; } if (akw[kw_version].set) { if (component != toolchain_component_compiler) { vm_error(wk, "version only configurable for compiler"); return false; } c->ver = akw[kw_version].val; } return true; } static bool func_compiler_get_internal_id(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_str(wk, compiler_type_name[get_obj_compiler(wk, self)->type[toolchain_component_compiler]].id); return true; } static bool func_compiler_get_internal_linker_id(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_str(wk, linker_type_name[get_obj_compiler(wk, self)->type[toolchain_component_linker]].id); return true; } static bool func_compiler_get_internal_static_linker_id(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_str( wk, static_linker_type_name[get_obj_compiler(wk, self)->type[toolchain_component_static_linker]].id); return true; } const struct func_impl impl_tbl_compiler_internal[] = { { "configure", func_compiler_configure }, { "get_internal_id", func_compiler_get_internal_id }, { "get_internal_linker_id", func_compiler_get_internal_linker_id }, { "get_internal_static_linker_id", func_compiler_get_internal_static_linker_id }, { NULL, NULL }, }; muon-v0.5.0/src/functions/meson.c0000644000175000017500000004770415041716357015751 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Eli Schwartz * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "args.h" #include "backend/common_args.h" #include "backend/output.h" #include "buf_size.h" #include "coerce.h" #include "compilers.h" #include "error.h" #include "functions/kernel.h" #include "functions/kernel/dependency.h" #include "functions/meson.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "options.h" #include "platform/assert.h" #include "platform/path.h" #include "version.h" static bool func_meson_get_compiler(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_native, }; struct args_kw akw[] = { [kw_native] = { "native", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, an[0].val), &l) || !obj_dict_geti( wk, current_project(wk)->toolchains[coerce_machine_kind(wk, &akw[kw_native])], l, res)) { vm_error_at(wk, an[0].node, "no compiler found for '%s'", get_cstr(wk, an[0].val)); return false; } return true; } static bool func_meson_project_name(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = current_project(wk)->cfg.name; return true; } static bool func_meson_project_license(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = current_project(wk)->cfg.license; if (!*res) { *res = make_obj(wk, obj_array); } return true; } static bool func_meson_project_license_files(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = current_project(wk)->cfg.license_files; if (!*res) { *res = make_obj(wk, obj_array); } return true; } static bool func_meson_project_version(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = current_project(wk)->cfg.version; return true; } static bool func_meson_version(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_str(wk, muon_version.meson_compat); return true; } static bool func_meson_current_source_dir(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = current_project(wk)->cwd; return true; } static bool func_meson_current_build_dir(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = current_project(wk)->build_dir; return true; } static bool func_meson_project_source_root(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = current_project(wk)->source_root; return true; } static bool func_meson_project_build_root(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = current_project(wk)->build_root; return true; } static bool func_meson_global_source_root(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_str(wk, wk->source_root); return true; } static bool func_meson_global_build_root(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_str(wk, wk->build_root); return true; } static bool func_meson_build_options(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } obj options = ca_regenerate_build_command(wk, true); // remove the build directory from options obj_array_pop(wk, options); *res = join_args_shell(wk, options); return true; } static bool func_meson_is_subproject(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, wk->cur_project != 0); return true; } static bool func_meson_backend(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } switch (get_option_backend(wk)) { case backend_ninja: *res = make_str(wk, "ninja"); break; case backend_xcode: *res = make_str(wk, "xcode"); break; } return true; } static bool is_cross_build(void) { return !machine_definitions_eql(&build_machine, &host_machine); } static bool func_meson_is_cross_build(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, is_cross_build()); return true; } static bool func_meson_is_unity(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, false); return true; } static bool func_meson_override_dependency(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, { obj_dependency }, ARG_TYPE_NULL }; enum kwargs { kw_static, kw_native, }; struct args_kw akw[] = { [kw_static] = { "static", obj_bool }, [kw_native] = { "native", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum machine_kind machine = coerce_machine_kind(wk, &akw[kw_native]); obj override_dict; if (akw[kw_static].set) { if (get_obj_bool(wk, akw[kw_static].val)) { override_dict = wk->dep_overrides_static[machine]; } else { override_dict = wk->dep_overrides_dynamic[machine]; } } else { switch (get_option_default_library(wk)) { case tgt_static_library: override_dict = wk->dep_overrides_static[machine]; break; default: override_dict = wk->dep_overrides_dynamic[machine]; break; } } obj d = make_obj(wk, obj_dependency); { // Clone this dependency and set its name to the name of the override struct obj_dependency *dep = get_obj_dependency(wk, d); *dep = *get_obj_dependency(wk, an[1].val); dep->name = an[0].val; } obj_dict_set(wk, override_dict, an[0].val, d); return true; } static bool func_meson_override_find_program(struct workspace *wk, obj _, obj *res) { type_tag tc_allowed = tc_file | tc_external_program | tc_build_target | tc_custom_target | tc_python_installation; struct args_norm an[] = { { obj_string }, { tc_allowed }, ARG_TYPE_NULL }; // TODO: why does override_find_program not accept a native keyword? enum machine_kind machine = coerce_machine_kind(wk, 0); if (!pop_args(wk, an, NULL)) { return false; } obj override; switch (get_obj_type(wk, an[1].val)) { case obj_build_target: case obj_custom_target: case obj_file: override = make_obj(wk, obj_array); obj_array_push(wk, override, an[1].val); obj ver = 0; if (!current_project(wk)->cfg.no_version) { ver = current_project(wk)->cfg.version; } obj_array_push(wk, override, ver); break; case obj_external_program: case obj_python_installation: override = an[1].val; break; default: UNREACHABLE; } obj_dict_set(wk, wk->find_program_overrides[machine], an[0].val, override); return true; } struct process_script_commandline_ctx { uint32_t node; obj arr; bool allow_not_built; bool make_deps_default; }; static bool process_script_commandline(struct workspace *wk, struct process_script_commandline_ctx *ctx, obj val) { enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_string: { if (get_obj_array(wk, ctx->arr)->len) { obj_array_push(wk, ctx->arr, val); } else { obj found_prog; struct find_program_ctx find_program_ctx = { .node = ctx->node, .res = &found_prog, .requirement = requirement_required, .machine = machine_kind_build, }; if (!find_program(wk, &find_program_ctx, val)) { return false; } obj_array_extend(wk, ctx->arr, get_obj_external_program(wk, found_prog)->cmd_array); } break; } case obj_custom_target: { if (!ctx->allow_not_built) { goto type_error; } struct obj_custom_target *o = get_obj_custom_target(wk, val); if (ctx->make_deps_default) { o->flags |= custom_target_build_by_default; } obj val; obj_array_for(wk, o->output, val) { obj_array_push(wk, ctx->arr, *get_obj_file(wk, val)); } break; } case obj_build_target: { if (!ctx->allow_not_built) { goto type_error; } struct obj_build_target *o = get_obj_build_target(wk, val); if (ctx->make_deps_default) { o->flags |= build_tgt_flag_build_by_default; } } //fallthrough case obj_external_program: case obj_python_installation: case obj_file: { obj str, args; if (!coerce_executable(wk, ctx->node, val, &str, &args)) { return false; } obj_array_push(wk, ctx->arr, str); if (args) { obj_array_extend_nodup(wk, ctx->arr, args); } break; } default: type_error: vm_error_at(wk, ctx->node, "invalid type for script commandline '%s'", obj_type_to_s(t)); return false; } return true; } static bool func_meson_add_install_script(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_exe }, ARG_TYPE_NULL }; enum kwargs { kw_install_tag, // ignored kw_skip_if_destdir, kw_dry_run, }; struct args_kw akw[] = { [kw_install_tag] = { "install_tag", obj_string }, [kw_skip_if_destdir] = { "skip_if_destdir", obj_bool }, [kw_dry_run] = { "dry_run", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } struct process_script_commandline_ctx ctx = { .node = an[0].node, .allow_not_built = true, .make_deps_default = true, }; ctx.arr = make_obj(wk, obj_array); obj v; obj_array_flat_for_(wk, an[0].val, v, iter) { if (!process_script_commandline(wk, &ctx, v)) { obj_array_flat_iter_end(wk, &iter); return false; } } if (!akw[kw_skip_if_destdir].set) { akw[kw_skip_if_destdir].val = make_obj_bool(wk, false); } if (!akw[kw_dry_run].set) { akw[kw_dry_run].val = make_obj_bool(wk, false); } obj install_script; install_script = make_obj(wk, obj_array); obj_array_push(wk, install_script, akw[kw_skip_if_destdir].val); obj_array_push(wk, install_script, akw[kw_dry_run].val); obj_array_push(wk, install_script, ctx.arr); obj_array_push(wk, wk->install_scripts, install_script); return true; } static bool func_meson_add_postconf_script(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_exe }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } struct process_script_commandline_ctx ctx = { .node = an[0].node, }; ctx.arr = make_obj(wk, obj_array); obj v; obj_array_flat_for_(wk, an[0].val, v, iter) { if (!process_script_commandline(wk, &ctx, v)) { obj_array_flat_iter_end(wk, &iter); return false; } } obj_array_push(wk, wk->postconf_scripts, ctx.arr); return true; } static bool func_meson_add_dist_script(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_exe }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } struct process_script_commandline_ctx ctx = { .node = an[0].node, .allow_not_built = true, }; ctx.arr = make_obj(wk, obj_array); obj v; obj_array_flat_for_(wk, an[0].val, v, iter) { if (!process_script_commandline(wk, &ctx, v)) { obj_array_flat_iter_end(wk, &iter); return false; } } // TODO: uncomment when muon dist is implemented /* obj_array_push(wk, wk->dist_scripts, ctx.arr); */ return true; } static bool meson_get_property(struct workspace *wk, obj dict, obj key, obj fallback, obj *res) { if (obj_dict_index(wk, dict, key, res)) { return true; } if (fallback) { *res = fallback; return true; } vm_error(wk,"unknown property %o", key); return false; } static bool func_meson_get_cross_property(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, { tc_any, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } return meson_get_property(wk, wk->machine_properties[machine_kind_host], an[0].val, an[1].val, res); } static bool func_meson_get_external_property(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, { tc_any, .optional = true }, ARG_TYPE_NULL }; enum kwargs { kw_native, }; struct args_kw akw[] = { [kw_native] = { "native", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } return meson_get_property( wk, wk->machine_properties[coerce_machine_kind(wk, &akw[kw_native])], an[0].val, an[1].val, res); } static bool func_meson_can_run_host_binaries(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } // TODO: This could actually still be true even when cross compiling if an // exe wrapper is defined. But muon doesn't support that yet. *res = make_obj_bool(wk, !is_cross_build()); return true; } static bool func_meson_add_devenv(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } return true; } const struct func_impl impl_tbl_meson[] = { { "add_devenv", func_meson_add_devenv }, { "add_dist_script", func_meson_add_dist_script }, { "add_install_script", func_meson_add_install_script }, { "add_postconf_script", func_meson_add_postconf_script }, { "backend", func_meson_backend, tc_string }, { "build_options", func_meson_build_options, tc_string }, { "build_root", func_meson_global_build_root, tc_string }, { "can_run_host_binaries", func_meson_can_run_host_binaries, tc_bool }, { "current_build_dir", func_meson_current_build_dir, tc_string }, { "current_source_dir", func_meson_current_source_dir, tc_string }, { "get_compiler", func_meson_get_compiler, tc_compiler }, { "get_cross_property", func_meson_get_cross_property, tc_any }, { "get_external_property", func_meson_get_external_property, tc_any }, { "global_build_root", func_meson_global_build_root, tc_string }, { "global_source_root", func_meson_global_source_root, tc_string }, { "has_exe_wrapper", func_meson_can_run_host_binaries, tc_bool }, { "is_cross_build", func_meson_is_cross_build, tc_bool }, { "is_subproject", func_meson_is_subproject, tc_bool }, { "is_unity", func_meson_is_unity, tc_bool }, { "override_dependency", func_meson_override_dependency }, { "override_find_program", func_meson_override_find_program }, { "project_build_root", func_meson_project_build_root, tc_string }, { "project_license", func_meson_project_license, tc_string }, { "project_license_files", func_meson_project_license_files, tc_string }, { "project_name", func_meson_project_name, tc_string }, { "project_source_root", func_meson_project_source_root, tc_string }, { "project_version", func_meson_project_version, tc_string }, { "source_root", func_meson_global_source_root, tc_string }, { "version", func_meson_version, tc_string, true }, { NULL, NULL }, }; static obj compiler_dict_to_str_dict(struct workspace *wk, obj d[machine_kind_count]) { obj res; res = make_obj(wk, obj_dict); for (enum machine_kind machine = 0; machine < machine_kind_count; ++machine) { obj r; r = make_obj(wk, obj_dict); obj k, v; obj_dict_for(wk, d[machine], k, v) { obj_dict_set(wk, r, make_str(wk, compiler_language_to_s(k)), v); } obj_dict_set(wk, res, make_str(wk, machine_kind_to_s(machine)), r); } return res; } static bool func_meson_project(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } struct project *proj = current_project(wk); *res = make_obj(wk, obj_dict); if (!proj) { return true; } obj_dict_set(wk, *res, make_str(wk, "opts"), proj->opts); obj_dict_set(wk, *res, make_str(wk, "toolchains"), compiler_dict_to_str_dict(wk, proj->toolchains)); obj_dict_set(wk, *res, make_str(wk, "args"), compiler_dict_to_str_dict(wk, proj->args)); obj_dict_set(wk, *res, make_str(wk, "link_args"), compiler_dict_to_str_dict(wk, proj->link_args)); return true; } static bool func_meson_register_dependency_handler(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL, }; enum kwargs { kw_pkgconfig, kw_builtin, kw_system, kw_config_tool, kw_order, }; struct args_kw akw[] = { [kw_pkgconfig] = { "pkgconfig", tc_capture }, [kw_builtin] = { "builtin", tc_capture }, [kw_system] = { "system", tc_capture }, [kw_config_tool] = { "config_tool", tc_capture }, [kw_order] = { "order", TYPE_TAG_LISTIFY | tc_string }, 0, }; const struct { enum kwargs kw; enum dependency_lookup_method method; } kwarg_to_method[] = { { kw_pkgconfig, dependency_lookup_method_pkgconfig }, { kw_builtin, dependency_lookup_method_builtin }, { kw_system, dependency_lookup_method_system }, { kw_config_tool, dependency_lookup_method_config_tool }, }; if (!pop_args(wk, an, akw)) { return false; } obj handler_dict; handler_dict = make_obj(wk, obj_dict); bool set_any = false; if (akw[kw_order].set) { obj method; obj_array_for(wk, akw[kw_order].val, method) { enum dependency_lookup_method m; if (!dependency_lookup_method_from_s(get_str(wk, method), &m)) { vm_error_at(wk, akw[kw_order].node, "invalid dependency method %o", method); return false; } uint32_t i; enum kwargs kw = 0; for (i = 0; i < ARRAY_LEN(kwarg_to_method); ++i) { if (m == kwarg_to_method[i].method) { kw = kwarg_to_method[i].kw; break; } } obj v = akw[i].val; if (i == ARRAY_LEN(kwarg_to_method) || !akw[kw].set) { v = obj_bool_true; } obj_dict_seti(wk, handler_dict, m, v); set_any = true; } } else { uint32_t i; for (i = 0; i < ARRAY_LEN(kwarg_to_method); ++i) { enum kwargs kw = kwarg_to_method[i].kw; if (!akw[kw].set) { continue; } obj_dict_seti(wk, handler_dict, kwarg_to_method[i].method, akw[i].val); set_any = true; } } if (!set_any) { vm_error(wk, "No handlers defined."); } obj_dict_set(wk, wk->dependency_handlers, an[0].val, handler_dict); return true; } static bool func_meson_argv0(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, 0, 0)) { return false; } *res = make_str(wk, wk->argv0); return true; } static bool func_meson_private_dir(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, 0, 0)) { return false; } *res = make_str(wk, output_path.private_dir); return true; } static bool func_meson_has_compiler(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_native, }; struct args_kw akw[] = { [kw_native] = { "native", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, an[0].val), &l)) { vm_error_at(wk, an[0].node, "unknown compiler language: '%s'", get_cstr(wk, an[0].val)); return false; } *res = make_obj_bool(wk, obj_dict_geti(wk, current_project(wk)->toolchains[coerce_machine_kind(wk, &akw[kw_native])], l, &_)); return true; } static bool func_meson_set_external_properties(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { COMPLEX_TYPE_PRESET(tc_cx_dict_of_str) }, ARG_TYPE_NULL }; enum kwargs { kw_native, }; struct args_kw akw[] = { [kw_native] = { "native", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } obj *dest = &wk->machine_properties[coerce_machine_kind(wk, &akw[kw_native])]; obj merged; obj_dict_merge(wk, *dest, an[0].val, &merged); *dest = merged; return true; } const struct func_impl impl_tbl_meson_internal[] = { { "project", func_meson_project, tc_dict, .desc = "return a dict containing read-only properties of the current project" }, { "register_dependency_handler", func_meson_register_dependency_handler, .desc = "register custom callbacks to run when a specific dependency lookup is invoked" }, { "argv0", func_meson_argv0, tc_string, .desc = "returns the argv[0] that was used to invoke muon itself" }, { "private_dir", func_meson_private_dir, tc_string, .desc = "returns the path to muon's private directory in the build folder" }, { "has_compiler", func_meson_has_compiler, tc_bool, .desc = "returns whether or not a compiler for the given language has been configured" }, { "set_external_properties", func_meson_set_external_properties, .desc = "set properties to be accessed by meson.get_cross_property() and meson.get_external_property()" }, { NULL, NULL }, }; muon-v0.5.0/src/functions/file.c0000644000175000017500000000253215041716357015535 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "functions/compiler.h" #include "functions/file.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" static bool file_ends_with_suffix(struct workspace *wk, obj file, const char *suffixes[], uint32_t len) { const struct str *s = get_str(wk, *get_obj_file(wk, file)); uint32_t i; for (i = 0; i < len; ++i) { if (str_endswith(s, &STRL(suffixes[i]))) { return true; } } return false; } bool file_is_dynamic_lib(struct workspace *wk, obj file) { const char *exts[] = { COMPILER_DYNAMIC_LIB_EXTS }; return file_ends_with_suffix(wk, file, exts, ARRAY_LEN(exts)); } bool file_is_static_lib(struct workspace *wk, obj file) { const char *exts[] = { COMPILER_STATIC_LIB_EXTS }; return file_ends_with_suffix(wk, file, exts, ARRAY_LEN(exts)); } bool file_is_linkable(struct workspace *wk, obj file) { return file_is_static_lib(wk, file) || file_is_dynamic_lib(wk, file); } static bool func_file_full_path(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = *get_obj_file(wk, self); return true; } const struct func_impl impl_tbl_file[] = { { "full_path", func_file_full_path, tc_string }, { NULL, NULL }, }; muon-v0.5.0/src/functions/boolean.c0000644000175000017500000000207415041716357016236 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "functions/boolean.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" static bool func_boolean_to_string(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string, .optional = true }, { obj_string, .optional = true }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } if (get_obj_bool(wk, self)) { *res = an[0].set ? an[0].val : make_str(wk, "true"); } else { *res = an[1].set ? an[1].val : make_str(wk, "false"); } return true; } static bool func_boolean_to_int(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } int32_t val = get_obj_bool(wk, self) ? 1 : 0; *res = make_obj(wk, obj_number); set_obj_number(wk, *res, val); return true; } const struct func_impl impl_tbl_boolean[] = { { "to_int", func_boolean_to_int, tc_number }, { "to_string", func_boolean_to_string, tc_string }, { NULL, NULL }, }; muon-v0.5.0/src/functions/template.c0000644000175000017500000000051415041716357016427 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/func_lookup.h" #include "functions/xxx.h" static bool func_(struct workspace *wk, obj self, obj *res) { } const struct func_impl impl_tbl_xxx[] = { { "", func_ }, { NULL, NULL }, }; muon-v0.5.0/src/functions/kernel/0002755000175000017500000000000015041716357015732 5ustar buildbuildmuon-v0.5.0/src/functions/kernel/subproject.c0000644000175000017500000001407715041716357020265 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/kernel/subproject.h" #include "functions/string.h" #include "functions/subproject.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" #include "wrap.h" static bool subproject_prepare(struct workspace *wk, struct tstr *cwd_buf, const char **cwd, struct tstr *build_dir_buf, const char **build_dir, bool required, bool *found) { TSTR(wrap_path); tstr_pushf(wk, &wrap_path, "%s.wrap", *cwd); if (!wk->vm.in_analyzer && fs_file_exists(wrap_path.buf)) { bool wrap_ok = false; TSTR(base_path); path_dirname(wk, &base_path, *cwd); struct wrap_handle_ctx wrap_ctx = { .opts = { .allow_download = get_option_wrap_mode(wk) != wrap_mode_nodownload, .subprojects = base_path.buf, } }; if (!wrap_handle(wk, wrap_path.buf, &wrap_ctx)) { goto wrap_cleanup; } if (wrap_ctx.wrap.fields[wf_directory]) { path_join(wk, cwd_buf, base_path.buf, wrap_ctx.wrap.fields[wf_directory]); path_dirname(wk, &base_path, *build_dir); path_join(wk, build_dir_buf, base_path.buf, wrap_ctx.wrap.fields[wf_directory]); *cwd = cwd_buf->buf; *build_dir = build_dir_buf->buf; } wrap_ok = true; wrap_cleanup: wrap_destroy(&wrap_ctx.wrap); if (!wrap_ok) { if (required) { LOG_E("project %s wrap error", *cwd); return false; } else { *found = false; return true; } } } TSTR(src); path_join(wk, &src, *cwd, "meson.build"); if (!fs_file_exists(src.buf)) { if (required) { LOG_E("project %s does not contain a meson.build", *cwd); return false; } else { *found = false; return true; } } *found = true; return true; } bool subproject(struct workspace *wk, obj name, enum requirement_type req, struct args_kw *default_options, struct args_kw *versions, obj *res) { // don't re-evaluate the same subproject if (obj_dict_index(wk, wk->subprojects, name, res)) { return true; } *res = make_obj(wk, obj_subproject); if (req == requirement_skip) { return true; } const char *subproj_name = get_cstr(wk, name); TSTR(cwd); TSTR(build_dir); path_join(wk, &cwd, get_cstr(wk, current_project(wk)->source_root), get_cstr(wk, current_project(wk)->subprojects_dir)); path_push(wk, &cwd, subproj_name); path_join(wk, &build_dir, get_cstr(wk, current_project(wk)->build_root), get_cstr(wk, current_project(wk)->subprojects_dir)); path_push(wk, &build_dir, subproj_name); uint32_t subproject_id = 0; bool found; const char *sp_cwd = cwd.buf, *sp_build_dir = build_dir.buf; TSTR(sp_cwd_buf); TSTR(sp_build_dir_buf); if (!subproject_prepare( wk, &sp_cwd_buf, &sp_cwd, &sp_build_dir_buf, &sp_build_dir, req == requirement_required, &found)) { return false; } if (!found) { return true; } if (default_options && default_options->set) { if (!parse_and_set_default_options(wk, default_options->node, default_options->val, name, true)) { return false; } } if (!eval_project(wk, subproj_name, sp_cwd, sp_build_dir, &subproject_id)) { goto not_found; } if (versions && versions->set) { struct project *subp = arr_get(&wk->projects, subproject_id); if (!version_compare_list(wk, get_str(wk, subp->cfg.version), versions->val)) { if (req == requirement_required) { vm_error_at(wk, versions->node, "subproject version mismatch; wanted %o, got %o", versions->val, subp->cfg.version); goto not_found; } } } *res = make_obj(wk, obj_subproject); struct obj_subproject *sub = get_obj_subproject(wk, *res); sub->id = subproject_id; sub->found = true; obj_dict_set(wk, wk->subprojects, name, *res); obj k, v; obj_dict_for(wk, current_project(wk)->wrap_provides_deps, k, v) { if (get_obj_array(wk, v)->len < 2) { continue; } obj sub_name, var_name, dep; sub_name = obj_array_index(wk, v, 0); var_name = obj_array_index(wk, v, 1); if (!obj_equal(wk, sub_name, name)) { continue; } if (!subproject_get_variable(wk, 0, var_name, 0, *res, &dep)) { vm_error(wk, "subproject dependency variable %o is not defined in %o", var_name, name); return false; } obj _dep; bool override_set = obj_dict_index(wk, wk->dep_overrides_dynamic[machine_kind_build], k, &_dep) || obj_dict_index(wk, wk->dep_overrides_static[machine_kind_build], k, &_dep) || obj_dict_index(wk, wk->dep_overrides_dynamic[machine_kind_host], k, &_dep) || obj_dict_index(wk, wk->dep_overrides_static[machine_kind_host], k, &_dep); if (override_set) { continue; } L("setting override for dependency '%s'", get_cstr(wk, k)); obj_dict_set(wk, wk->dep_overrides_dynamic[machine_kind_build], k, dep); obj_dict_set(wk, wk->dep_overrides_static[machine_kind_build], k, dep); obj_dict_set(wk, wk->dep_overrides_dynamic[machine_kind_host], k, dep); obj_dict_set(wk, wk->dep_overrides_static[machine_kind_host], k, dep); } if (fs_dir_exists(wk->build_root)) { if (!fs_mkdir_p(build_dir.buf)) { return false; } } return true; not_found: if (subproject_id) { struct project *proj = arr_get(&wk->projects, subproject_id); proj->not_ok = true; } return req != requirement_required; } bool func_subproject(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_default_options, kw_required, kw_version, }; struct args_kw akw[] = { [kw_default_options] = { "default_options", COMPLEX_TYPE_PRESET(tc_cx_options_dict_or_list) }, [kw_required] = { "required", tc_required_kw }, [kw_version] = { "version", TYPE_TAG_LISTIFY | obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (wk->vm.in_analyzer) { subproject(wk, an[0].val, requirement_auto, 0, 0, res); return true; } enum requirement_type req; if (!coerce_requirement(wk, &akw[kw_required], &req)) { return false; } if (!subproject(wk, an[0].val, req, &akw[kw_default_options], &akw[kw_version], res)) { return false; } return true; } muon-v0.5.0/src/functions/kernel/custom_target.c0000644000175000017500000005702615041716357020766 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: dffdff2423 * SPDX-FileCopyrightText: Eli Schwartz * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "coerce.h" #include "error.h" #include "functions/generator.h" #include "functions/kernel.h" #include "functions/kernel/custom_target.h" #include "functions/string.h" #include "install.h" #include "lang/typecheck.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" struct custom_target_cmd_fmt_ctx { struct process_custom_target_commandline_opts *opts; uint32_t i; obj *res; bool skip_depends; }; static bool prefix_plus_index(const struct str *ss, const char *prefix, int64_t *index) { uint32_t len = strlen(prefix); if (str_startswith(ss, &STRL(prefix))) { return str_to_i(&(struct str){ .s = &ss->s[len], .len = ss->len - len }, index, false); } return false; } static void str_relative_to_build_root(struct workspace *wk, struct custom_target_cmd_fmt_ctx *ctx, const char *path_orig, obj *res) { TSTR(rel); const char *path = path_orig; if (!ctx->opts->relativize) { *res = make_str(wk, path); return; } if (!path_is_absolute(path)) { *res = make_str(wk, path); return; } path_relative_to(wk, &rel, wk->build_root, path); if (ctx->i == 0) { // prefix relative argv0 with ./ so that executables are looked // up properly if they reside in the build root. Without this, // an executable in the build root will be called without any // path elements, and will be assumed to be on PATH, which // either results in the wrong executable being run, or a // command not found error. TSTR(exe); path_executable(wk, &exe, rel.buf); *res = tstr_into_str(wk, &exe); } else { *res = tstr_into_str(wk, &rel); } } static enum format_cb_result format_cmd_arg_cb(struct workspace *wk, uint32_t node, void *_ctx, const struct str *strkey, obj *elem) { struct custom_target_cmd_fmt_ctx *ctx = _ctx; enum cmd_arg_fmt_key { key_input, key_output, key_outdir, key_depfile, key_plainname, key_basename, key_private_dir, key_source_root, key_build_root, key_build_dir, key_current_source_dir, cmd_arg_fmt_key_count, }; const struct { char *key; bool valid; bool needs_name; } key_names[cmd_arg_fmt_key_count] = { [key_input ] = { "INPUT", ctx->opts->input }, [key_output ] = { "OUTPUT", ctx->opts->output }, [key_outdir ] = { "OUTDIR", ctx->opts->output }, [key_depfile ] = { "DEPFILE", ctx->opts->depfile }, [key_plainname ] = { "PLAINNAME", ctx->opts->input }, [key_basename ] = { "BASENAME", ctx->opts->input }, [key_private_dir ] = { "PRIVATE_DIR", ctx->opts->output, true, }, [key_source_root ] = { "SOURCE_ROOT", true }, [key_build_root ] = { "BUILD_ROOT", true }, [key_build_dir ] = { "BUILD_DIR", ctx->opts->build_dir }, [key_current_source_dir] = { "CURRENT_SOURCE_DIR", true }, }; enum cmd_arg_fmt_key key; for (key = 0; key < cmd_arg_fmt_key_count; ++key) { if (!str_eql(strkey, &STRL(key_names[key].key))) { continue; } if (!key_names[key].valid || (key_names[key].needs_name && !ctx->opts->name)) { return format_cb_not_found; } break; } obj e; switch (key) { case key_input: case key_output: { obj arr = key == key_input ? ctx->opts->input : ctx->opts->output; int64_t index = 0; if (!boundscheck(wk, ctx->opts->err_node, get_obj_array(wk, arr)->len, &index)) { return format_cb_error; } e = obj_array_index(wk, arr, 0); str_relative_to_build_root(wk, ctx, get_file_path(wk, e), elem); return format_cb_found; } case key_outdir: /* @OUTDIR@: the full path to the directory where the output(s) * must be written */ str_relative_to_build_root(wk, ctx, get_cstr(wk, current_project(wk)->build_dir), elem); return format_cb_found; case key_current_source_dir: /* @CURRENT_SOURCE_DIR@: this is the directory where the * currently processed meson.build is located in. Depending on * the backend, this may be an absolute or a relative to * current workdir path. */ str_relative_to_build_root(wk, ctx, workspace_cwd(wk), elem); return format_cb_found; case key_private_dir: { /* @PRIVATE_DIR@ (since 0.50.1): path to a directory where the * custom target must store all its intermediate files. */ TSTR(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->build_dir), get_cstr(wk, ctx->opts->name)); tstr_pushs(wk, &path, ".p"); if (!fs_mkdir_p(path.buf)) { return format_cb_error; } str_relative_to_build_root(wk, ctx, path.buf, elem); return format_cb_found; } case key_depfile: /* @DEPFILE@: the full path to the dependency file passed to * depfile */ str_relative_to_build_root(wk, ctx, get_file_path(wk, ctx->opts->depfile), elem); return format_cb_found; case key_source_root: /* @SOURCE_ROOT@: the path to the root of the source tree. * Depending on the backend, this may be an absolute or a * relative to current workdir path. */ str_relative_to_build_root(wk, ctx, wk->source_root, elem); return format_cb_found; case key_build_root: /* @BUILD_ROOT@: the path to the root of the build tree. * Depending on the backend, this may be an absolute or a * relative to current workdir path. */ str_relative_to_build_root(wk, ctx, wk->build_root, elem); return format_cb_found; case key_build_dir: // only for generators str_relative_to_build_root(wk, ctx, ctx->opts->build_dir, elem); return format_cb_found; case key_plainname: /* @PLAINNAME@: the input filename, without a path */ case key_basename: { /* @BASENAME@: the input filename, with extension removed */ struct obj_array *in = get_obj_array(wk, ctx->opts->input); if (in->len != 1) { vm_error_at(wk, ctx->opts->err_node, "to use @PLAINNAME@ and @BASENAME@ in a custom " "target command, there must be exactly one input"); return format_cb_error; } obj in0 = obj_array_index(wk, ctx->opts->input, 0); const struct str *orig_str = get_str(wk, *get_obj_file(wk, in0)); TSTR(plainname); path_basename(wk, &plainname, orig_str->s); if (key == key_basename) { TSTR(basename); path_without_ext(wk, &basename, plainname.buf); str_relative_to_build_root(wk, ctx, basename.buf, elem); } else { str_relative_to_build_root(wk, ctx, plainname.buf, elem); } return format_cb_found; } default: break; } int64_t index; obj arr; if (prefix_plus_index(strkey, "INPUT", &index)) { arr = ctx->opts->input; } else if (prefix_plus_index(strkey, "OUTPUT", &index)) { arr = ctx->opts->output; } else { if (ctx->opts->err_node) { vm_warning_at(wk, ctx->opts->err_node, "not substituting unknown key '%.*s' in commandline", strkey->len, strkey->s); } return format_cb_skip; } if (!boundscheck(wk, ctx->opts->err_node, get_obj_array(wk, arr)->len, &index)) { return format_cb_error; } e = obj_array_index(wk, arr, index); str_relative_to_build_root(wk, ctx, get_file_path(wk, e), elem); return format_cb_found; } static enum iteration_result custom_target_cmd_fmt_iter(struct workspace *wk, void *_ctx, obj val) { struct custom_target_cmd_fmt_ctx *ctx = _ctx; obj ss; enum obj_type t = get_obj_type(wk, val); obj extra_args = 0; switch (t) { case obj_both_libs: case obj_build_target: case obj_external_program: case obj_python_installation: case obj_file: { obj str; if (!coerce_executable(wk, ctx->opts->err_node, val, &str, &extra_args)) { return ir_err; } str_relative_to_build_root(wk, ctx, get_cstr(wk, str), &ss); if (!ctx->skip_depends) { obj_array_push(wk, ctx->opts->depends, ss); } break; } case obj_string: { if (ctx->opts->input && str_eql(get_str(wk, val), &STR("@INPUT@"))) { ctx->skip_depends = true; if (!obj_array_foreach(wk, ctx->opts->input, ctx, custom_target_cmd_fmt_iter)) { return ir_err; } ctx->skip_depends = false; return ir_cont; } else if (ctx->opts->output && str_eql(get_str(wk, val), &STR("@OUTPUT@"))) { ctx->skip_depends = true; if (!obj_array_foreach(wk, ctx->opts->output, ctx, custom_target_cmd_fmt_iter)) { return ir_err; } ctx->skip_depends = false; goto cont; } else if (ctx->opts->extra_args_valid && str_eql(get_str(wk, val), &STR("@EXTRA_ARGS@"))) { if (ctx->opts->extra_args) { obj_array_extend(wk, *ctx->res, ctx->opts->extra_args); ctx->opts->extra_args_used = true; } goto cont; } obj s; if (!string_format(wk, ctx->opts->err_node, val, &s, ctx, format_cmd_arg_cb)) { return ir_err; } ss = s; if (ctx->i == 0 && !path_is_absolute(get_str(wk, ss)->s)) { obj argv0 = 0; struct find_program_ctx find_program_ctx = { .res = &argv0, .requirement = requirement_required, .machine = coerce_machine_kind(wk, 0), }; if (!find_program(wk, &find_program_ctx, ss)) { return false; } if (!find_program_ctx.found) { vm_error_at(wk, ctx->opts->err_node, "program %o not found", ss); return false; } return custom_target_cmd_fmt_iter(wk, ctx, argv0); } break; } case obj_custom_target: { obj output = get_obj_custom_target(wk, val)->output; if (!obj_array_foreach(wk, output, ctx, custom_target_cmd_fmt_iter)) { return ir_err; } goto cont; } case obj_compiler: { obj cmd_array = get_obj_compiler(wk, val)->cmd_arr[toolchain_component_compiler]; if (!obj_array_foreach(wk, cmd_array, ctx, custom_target_cmd_fmt_iter)) { return ir_err; } goto cont; } default: vm_error_at(wk, ctx->opts->err_node, "unable to coerce %o to string", val); return ir_err; } assert(get_obj_type(wk, ss) == obj_string); obj_array_push(wk, *ctx->res, ss); if (extra_args) { obj_array_extend_nodup(wk, *ctx->res, extra_args); } cont: ++ctx->i; return ir_cont; } bool process_custom_target_commandline(struct workspace *wk, struct process_custom_target_commandline_opts *opts, obj arr, obj *res) { *res = make_obj(wk, obj_array); struct custom_target_cmd_fmt_ctx ctx = { .opts = opts, .res = res, }; if (!obj_array_foreach_flat(wk, arr, &ctx, custom_target_cmd_fmt_iter)) { return false; } if (!get_obj_array(wk, *res)->len) { vm_error_at(wk, opts->err_node, "cmd cannot be empty"); return false; } return true; } static enum format_cb_result format_cmd_output_cb(struct workspace *wk, uint32_t node, void *_ctx, const struct str *strkey, obj *elem) { struct custom_target_cmd_fmt_ctx *ctx = _ctx; enum cmd_output_fmt_key { key_plainname, key_basename, cmd_output_fmt_key_count }; const char *key_names[cmd_output_fmt_key_count] = { [key_plainname] = "PLAINNAME", [key_basename] = "BASENAME", }; enum cmd_output_fmt_key key; for (key = 0; key < cmd_output_fmt_key_count; ++key) { if (str_eql(strkey, &STRL(key_names[key]))) { break; } } if (key >= cmd_output_fmt_key_count) { return format_cb_not_found; } struct obj_array *in = get_obj_array(wk, ctx->opts->input); if (in->len != 1) { vm_error_at(wk, ctx->opts->err_node, "to use @PLAINNAME@ and @BASENAME@ in a custom " "target output, there must be exactly one input"); return format_cb_error; } obj in0; in0 = obj_array_index(wk, ctx->opts->input, 0); const struct str *ss = get_str(wk, *get_obj_file(wk, in0)); TSTR(buf); switch (key) { case key_plainname: path_basename(wk, &buf, ss->s); break; case key_basename: { TSTR(basename); path_basename(wk, &basename, ss->s); path_without_ext(wk, &buf, basename.buf); break; } default: assert(false && "unreachable"); return format_cb_error; } *elem = tstr_into_str(wk, &buf); return format_cb_found; } static enum iteration_result custom_command_output_format_iter(struct workspace *wk, void *_ctx, obj v) { struct custom_target_cmd_fmt_ctx *ctx = _ctx; obj file = *get_obj_file(wk, v); obj s; if (!string_format(wk, ctx->opts->err_node, file, &s, ctx, format_cmd_output_cb)) { return ir_err; } obj f; f = make_obj(wk, obj_file); *get_obj_file(wk, f) = s; obj_array_push(wk, ctx->opts->output, f); return ir_cont; } struct process_custom_tgt_sources_ctx { uint32_t err_node; obj tgt_id; obj res; }; static enum iteration_result process_custom_tgt_sources_iter(struct workspace *wk, void *_ctx, obj val) { obj res; struct process_custom_tgt_sources_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_generated_list: if (!generated_list_process_for_target(wk, ctx->err_node, val, ctx->tgt_id, true, &res)) { return ir_err; } break; default: { if (!coerce_files(wk, ctx->err_node, val, &res)) { return ir_err; } break; } } obj_array_extend_nodup(wk, ctx->res, res); return ir_cont; } bool make_custom_target(struct workspace *wk, struct make_custom_target_opts *opts, obj *res) { obj input, raw_output, output, args; *res = make_obj(wk, obj_custom_target); struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); tgt->name = opts->name; tgt->callstack = vm_callstack(wk); // A custom_target won't have a name if it is from a generator if (opts->name) { /* private path */ TSTR(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->build_dir), get_cstr(wk, opts->name)); tstr_pushs(wk, &path, ".p"); tgt->private_path = tstr_into_str(wk, &path); } if (opts->input_orig) { input = make_obj(wk, obj_array); struct process_custom_tgt_sources_ctx ctx = { .err_node = opts->input_node, .res = input, .tgt_id = *res, }; if (get_obj_type(wk, opts->input_orig) != obj_array) { obj arr_input; arr_input = make_obj(wk, obj_array); obj_array_push(wk, arr_input, opts->input_orig); opts->input_orig = arr_input; } if (!obj_array_foreach_flat(wk, opts->input_orig, &ctx, process_custom_tgt_sources_iter)) { return false; } } else { input = 0; } if (opts->output_orig) { if (!coerce_output_files(wk, opts->output_node, opts->output_orig, opts->output_dir, &raw_output)) { return false; } else if (!get_obj_array(wk, raw_output)->len) { vm_error_at(wk, opts->output_node, "output cannot be empty"); return false; } output = make_obj(wk, obj_array); struct custom_target_cmd_fmt_ctx ctx = { .opts = &(struct process_custom_target_commandline_opts) { .err_node = opts->output_node, .input = input, .output = output, .name = opts->name, }, }; if (!obj_array_foreach(wk, raw_output, &ctx, custom_command_output_format_iter)) { return false; } } else { output = 0; } obj depfile = 0; if (opts->depfile_orig) { obj raw_depfiles; if (!coerce_output_files(wk, 0, opts->depfile_orig, opts->output_dir, &raw_depfiles)) { return false; } if (!obj_array_flatten_one(wk, raw_depfiles, &depfile)) { UNREACHABLE; } struct custom_target_cmd_fmt_ctx ctx = { .opts = &(struct process_custom_target_commandline_opts) { .input = input, }, }; obj depfile_formatted; if (!string_format(wk, 0, *get_obj_file(wk, depfile), &depfile_formatted, &ctx, format_cmd_output_cb)) { return ir_err; } *get_obj_file(wk, depfile) = depfile_formatted; } struct process_custom_target_commandline_opts cmdline_opts = { .err_node = opts->command_node, .relativize = true, .name = opts->name, .input = input, .output = output, .depfile = depfile, .build_dir = opts->build_dir, .extra_args = opts->extra_args, .extra_args_valid = opts->extra_args_valid, }; cmdline_opts.depends = make_obj(wk, obj_array); if (!process_custom_target_commandline(wk, &cmdline_opts, opts->command_orig, &args)) { return false; } if (opts->extra_args && !cmdline_opts.extra_args_used) { vm_warning_at(wk, opts->command_node, "extra args passed, but no @EXTRA_ARGS@ key found to substitute"); } if (opts->capture) { tgt->flags |= custom_target_capture; } if (opts->feed) { tgt->flags |= custom_target_feed; } tgt->args = args; tgt->input = input; tgt->output = output; tgt->depfile = depfile; tgt->depends = cmdline_opts.depends; return true; } bool install_custom_target(struct workspace *wk, struct obj_custom_target *tgt, const struct args_kw *kw_install, const struct args_kw *kw_build_by_default, obj install_dir, obj install_mode) { bool should_install = get_obj_bool_with_default(wk, kw_install->val, false) || (!kw_install->set && install_dir); if (!should_install) { return true; } if (!kw_build_by_default || !kw_build_by_default->set) { tgt->flags |= custom_target_build_by_default; } if (!install_dir || !get_obj_array(wk, install_dir)->len) { vm_error(wk, "custom target installation requires install_dir"); return false; } if (get_obj_array(wk, install_dir)->len == 1) { obj i0; i0 = obj_array_index(wk, install_dir, 0); install_dir = i0; } return push_install_targets(wk, 0, tgt->output, install_dir, install_mode, false); } bool func_custom_target(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { obj_string, .optional = true }, ARG_TYPE_NULL }; enum kwargs { kw_input, kw_output, kw_command, kw_capture, kw_install, kw_install_dir, kw_install_mode, kw_install_tag, kw_build_by_default, kw_depfile, kw_depend_files, kw_depends, kw_build_always_stale, kw_build_always, kw_env, kw_feed, kw_console, }; struct args_kw akw[] = { [kw_input] = { "input", TYPE_TAG_LISTIFY | tc_coercible_files | tc_generated_list, }, [kw_output] = { "output", TYPE_TAG_LISTIFY | tc_string, .required = true }, [kw_command] = { "command", tc_command_array | tc_both_libs, .required = true }, [kw_capture] = { "capture", obj_bool }, [kw_install] = { "install", obj_bool }, [kw_install_dir] = { "install_dir", TYPE_TAG_LISTIFY | tc_string | tc_bool }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", tc_string }, // TODO [kw_build_by_default] = { "build_by_default", obj_bool }, [kw_depfile] = { "depfile", obj_string }, [kw_depend_files] = { "depend_files", TYPE_TAG_LISTIFY | tc_string | tc_file }, [kw_depends] = { "depends", tc_depends_kw }, [kw_build_always_stale] = { "build_always_stale", obj_bool }, [kw_build_always] = { "build_always", obj_bool }, [kw_env] = { "env", tc_coercible_env }, [kw_feed] = { "feed", obj_bool }, [kw_console] = { "console", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } obj name; if (an[0].set) { name = an[0].val; } else { if (!get_obj_array(wk, akw[kw_output].val)->len) { vm_error_at(wk, akw[kw_output].node, "output cannot be empty"); return false; } obj v; v = obj_array_index(wk, akw[kw_output].val, 0); name = v; } struct make_custom_target_opts opts = { .name = name, .input_node = akw[kw_input].node, .output_node = akw[kw_output].node, .command_node = akw[kw_command].node, .input_orig = akw[kw_input].val, .output_orig = akw[kw_output].val, .output_dir = get_cstr(wk, current_project(wk)->build_dir), .command_orig = akw[kw_command].val, .depfile_orig = akw[kw_depfile].val, .capture = akw[kw_capture].set && get_obj_bool(wk, akw[kw_capture].val), .feed = akw[kw_feed].set && get_obj_bool(wk, akw[kw_feed].val), }; if (!make_custom_target(wk, &opts, res)) { return false; } struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); if (akw[kw_depend_files].set) { obj depend_files; if (!coerce_files(wk, akw[kw_depend_files].node, akw[kw_depend_files].val, &depend_files)) { return false; } obj_array_extend_nodup(wk, tgt->depends, depend_files); } if (akw[kw_depends].set) { obj depends; if (!coerce_files(wk, akw[kw_depends].node, akw[kw_depends].val, &depends)) { return false; } obj_array_extend_nodup(wk, tgt->depends, depends); } if (akw[kw_build_always_stale].set && get_obj_bool(wk, akw[kw_build_always_stale].val)) { tgt->flags |= custom_target_build_always_stale; } if (akw[kw_build_by_default].set && get_obj_bool(wk, akw[kw_build_by_default].val)) { tgt->flags |= custom_target_build_by_default; } if (akw[kw_build_always].set && get_obj_bool(wk, akw[kw_build_always].val)) { tgt->flags |= custom_target_build_always_stale | custom_target_build_by_default; } if (akw[kw_console].set && get_obj_bool(wk, akw[kw_console].val)) { if (opts.capture) { vm_error_at(wk, akw[kw_console].node, "console and capture cannot both be set to true"); return false; } tgt->flags |= custom_target_console; } if (!install_custom_target(wk, tgt, &akw[kw_install], &akw[kw_build_by_default], akw[kw_install_dir].val, akw[kw_install_mode].val)) { return false; } if (!coerce_environment_from_kwarg(wk, &akw[kw_env], false, &tgt->env)) { return false; } L("adding custom target '%s'", get_cstr(wk, tgt->name)); obj_array_push(wk, current_project(wk)->targets, *res); return true; } bool func_vcs_tag(struct workspace *wk, obj _, obj *res) { enum kwargs { kw_input, kw_output, kw_command, kw_fallback, kw_replace_string, kw_install, kw_install_dir, kw_install_mode, kw_install_tag, }; struct args_kw akw[] = { [kw_input] = { "input", TYPE_TAG_LISTIFY | tc_coercible_files, .required = true }, [kw_output] = { "output", obj_string, .required = true }, [kw_command] = { "command", tc_command_array | tc_both_libs }, [kw_fallback] = { "fallback", obj_string }, [kw_replace_string] = { "replace_string", obj_string }, [kw_install] = { "install", obj_bool }, [kw_install_dir] = { "install_dir", TYPE_TAG_LISTIFY | tc_string | tc_bool }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", tc_string }, // TODO 0 }; if (!pop_args(wk, NULL, akw)) { return false; } obj replace_string = akw[kw_replace_string].set ? akw[kw_replace_string].val : make_str(wk, "\\@VCS_TAG\\@"); obj fallback; if (akw[kw_fallback].set) { fallback = akw[kw_fallback].val; } else { fallback = current_project(wk)->cfg.version; } obj command; command = make_obj(wk, obj_array); push_args_null_terminated(wk, command, (char *const[]){ (char *)wk->argv0, "internal", "eval", "-e", "commands/vcs_tagger.meson", NULL, }); obj input; { obj input_arr; if (!coerce_files(wk, akw[kw_input].node, akw[kw_input].val, &input_arr)) { return false; } if (!obj_array_flatten_one(wk, input_arr, &input)) { vm_error_at(wk, akw[kw_input].node, "expected exactly one input"); return false; } } obj_array_push(wk, command, make_str(wk, "-s")); obj_array_push(wk, command, input); obj_array_push(wk, command, make_str(wk, "-d")); obj_array_push(wk, command, make_str(wk, "@OUTPUT@")); obj_array_push(wk, command, make_str(wk, "-p")); obj_array_push(wk, command, replace_string); obj_array_push(wk, command, make_str(wk, "-f")); obj_array_push(wk, command, fallback); obj_array_push(wk, command, make_str(wk, "-r")); obj_array_push(wk, command, make_str(wk, wk->source_root)); if (akw[kw_command].set) { obj_array_push(wk, command, make_str(wk, "--")); obj_array_extend(wk, command, akw[kw_command].val); } struct make_custom_target_opts opts = { .name = make_str(wk, "vcs_tag"), .input_node = akw[kw_input].node, .output_node = akw[kw_output].node, .input_orig = akw[kw_input].val, .output_orig = akw[kw_output].val, .output_dir = get_cstr(wk, current_project(wk)->build_dir), .command_orig = command, }; if (!make_custom_target(wk, &opts, res)) { return false; } struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); tgt->flags |= custom_target_build_always_stale; if (!install_custom_target(wk, tgt, &akw[kw_install], 0, akw[kw_install_dir].val, akw[kw_install_mode].val)) { return false; } obj_array_push(wk, current_project(wk)->targets, *res); return true; } muon-v0.5.0/src/functions/kernel/build_target.c0000644000175000017500000010137315041716357020546 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "coerce.h" #include "functions/build_target.h" #include "functions/file.h" #include "functions/generator.h" #include "functions/kernel/build_target.h" #include "functions/kernel/dependency.h" #include "install.h" #include "lang/typecheck.h" #include "log.h" #include "machines.h" #include "options.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" enum build_target_kwargs { bt_kw_sources, bt_kw_include_directories, bt_kw_implicit_include_directories, bt_kw_dependencies, bt_kw_install, bt_kw_install_dir, bt_kw_install_mode, bt_kw_install_tag, bt_kw_link_with, bt_kw_link_whole, bt_kw_version, bt_kw_build_by_default, bt_kw_extra_files, bt_kw_target_type, bt_kw_name_prefix, bt_kw_name_suffix, bt_kw_soversion, bt_kw_link_depends, bt_kw_objects, bt_kw_pic, bt_kw_pie, bt_kw_build_rpath, bt_kw_install_rpath, bt_kw_export_dynamic, bt_kw_vs_module_defs, // TODO bt_kw_gnu_symbol_visibility, bt_kw_native, bt_kw_darwin_versions, // TODO bt_kw_implib, // TODO bt_kw_gui_app, // TODO bt_kw_link_language, // TODO bt_kw_win_subsystem, // TODO bt_kw_override_options, bt_kw_link_args, #define E(lang, s) bt_kw_##lang##s #define TOOLCHAIN_ENUM(lang) E(lang, _args), E(lang, _static_args), E(lang, _shared_args), E(lang, _pch), FOREACH_COMPILER_EXPOSED_LANGUAGE(TOOLCHAIN_ENUM) #undef TOOLCHAIN_ENUM #undef E bt_kwargs_count, }; static enum iteration_result determine_linker_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_build_target *tgt = _ctx; enum compiler_language fl; if (!filename_to_compiler_language(get_file_path(wk, val), &fl)) { /* LOG_E("unable to determine language for '%s'", get_cstr(wk, src->dat.file)); */ return ir_cont; } tgt->dep_internal.link_language = coalesce_link_languages(tgt->dep_internal.link_language, fl); return ir_cont; } static enum iteration_result determine_linker_from_objects_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_build_target *tgt = _ctx; enum compiler_language fl; /* * Try to see if the file looks like * path/to/object.language.object_extension * * This means we expect two extensions, the first one will be stripped, * and then the second will be used to determine the language of the * file. */ const struct str *o = get_str(wk, *get_obj_file(wk, val)); TSTR(path); path_basename(wk, &path, o->s); const char *first_dot = strrchr(path.buf, '.'); path.len = first_dot - path.buf; path.buf[path.len] = 0; if (!strrchr(path.buf, '.')) { return ir_cont; } if (!filename_to_compiler_language(path.buf, &fl)) { /* LOG_E("unable to determine language for '%s'", get_cstr(wk, src->dat.file)); */ return ir_cont; } tgt->dep_internal.link_language = coalesce_link_languages(tgt->dep_internal.link_language, fl); return ir_cont; } static bool build_tgt_determine_linker(struct workspace *wk, uint32_t err_node, struct obj_build_target *tgt) { if (!obj_array_foreach(wk, tgt->src, tgt, determine_linker_iter)) { return ir_err; } if (!obj_array_foreach(wk, tgt->objects, tgt, determine_linker_from_objects_iter)) { return ir_err; } if (!tgt->dep_internal.link_language) { enum compiler_language clink_langs[] = { compiler_language_c, compiler_language_cpp, }; obj comp; uint32_t i; for (i = 0; i < ARRAY_LEN(clink_langs); ++i) { if (obj_dict_geti(wk, current_project(wk)->toolchains[tgt->machine], clink_langs[i], &comp)) { tgt->dep_internal.link_language = clink_langs[i]; break; } } } if (!tgt->dep_internal.link_language) { vm_error_at(wk, err_node, "unable to determine linker for target"); return false; } return true; } struct process_build_tgt_sources_ctx { uint32_t err_node; obj tgt_id; obj res; obj prepend_include_directories; bool implicit_include_directories; }; static bool process_source_include(struct workspace *wk, struct process_build_tgt_sources_ctx *ctx, obj val) { const char *src = get_file_path(wk, val); if (!path_is_subpath(wk->build_root, src)) { return true; } TSTR(dir); TSTR(path); path_relative_to(wk, &path, wk->build_root, src); struct obj_build_target *tgt = get_obj_build_target(wk, ctx->tgt_id); obj_array_push(wk, tgt->dep_internal.order_deps, tstr_into_str(wk, &path)); if (!tgt->dep_internal.raw.order_deps) { tgt->dep_internal.raw.order_deps = make_obj(wk, obj_array); } obj_array_push(wk, tgt->dep_internal.raw.order_deps, val); if (!ctx->implicit_include_directories) { return true; } path_dirname(wk, &dir, src); obj inc; inc = make_obj(wk, obj_include_directory); struct obj_include_directory *d = get_obj_include_directory(wk, inc); d->path = tstr_into_str(wk, &dir); obj_array_push(wk, ctx->prepend_include_directories, inc); // mkdir so that the include dir doesn't get pruned later on if (!fs_mkdir_p(dir.buf)) { return false; } return true; } static void build_tgt_inc_required_compiler(struct workspace *wk, struct obj_build_target *tgt, enum compiler_language lang) { obj n; if (obj_dict_geti(wk, tgt->required_compilers, lang, &n)) { obj_dict_seti(wk, tgt->required_compilers, lang, n + 1); } else { obj_dict_seti(wk, tgt->required_compilers, lang, 1); } } static enum iteration_result build_tgt_push_source_files_iter(struct workspace *wk, void *_ctx, obj val) { struct process_build_tgt_sources_ctx *ctx = _ctx; struct obj_build_target *tgt = get_obj_build_target(wk, ctx->tgt_id); if (file_is_linkable(wk, val)) { obj_array_push(wk, tgt->dep_internal.link_with, val); return ir_cont; } enum compiler_language lang; if (!filename_to_compiler_language(get_file_path(wk, val), &lang) || languages[lang].is_header) { obj_array_push(wk, tgt->extra_files, val); // process every file that is either a header, or isn't // recognized, as a header if (!process_source_include(wk, ctx, val)) { return ir_err; } return ir_cont; } else if (languages[lang].is_linkable) { obj_array_push(wk, tgt->objects, val); return ir_cont; } build_tgt_inc_required_compiler(wk, tgt, lang); obj_array_push(wk, ctx->res, val); return ir_cont; } static enum iteration_result process_build_tgt_sources_iter(struct workspace *wk, void *_ctx, obj val) { obj res; struct process_build_tgt_sources_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_generated_list: if (!generated_list_process_for_target(wk, ctx->err_node, val, ctx->tgt_id, true, &res)) { return ir_err; } break; default: { if (!coerce_files(wk, ctx->err_node, val, &res)) { return ir_err; } break; } } obj_array_foreach(wk, res, ctx, build_tgt_push_source_files_iter); return ir_cont; } static bool type_from_kw(struct workspace *wk, uint32_t node, obj t, enum tgt_type *res) { const char *tgt_type = get_cstr(wk, t); struct { char *name; enum tgt_type type; } tgt_tbl[] = { { "executable", tgt_executable, }, { "shared_library", tgt_dynamic_library, }, { "shared_module", tgt_shared_module, }, { "static_library", tgt_static_library, }, { "both_libraries", tgt_dynamic_library | tgt_static_library, }, { "library", get_option_default_library(wk), }, { 0 }, }; uint32_t i; for (i = 0; tgt_tbl[i].name; ++i) { if (strcmp(tgt_type, tgt_tbl[i].name) == 0) { *res = tgt_tbl[i].type; break; } } if (!tgt_tbl[i].name) { vm_error_at(wk, node, "unsupported target type '%s'", tgt_type); return false; } return true; } static void setup_soname(struct workspace *wk, struct obj_build_target *tgt, const char *plain_name, obj sover, obj ver) { char soversion[BUF_SIZE_1k] = { "." }; bool have_soversion = false; if (sover) { have_soversion = true; strncpy(&soversion[1], get_cstr(wk, sover), BUF_SIZE_1k - 2); } else if (ver) { have_soversion = true; strncpy(&soversion[1], get_cstr(wk, ver), BUF_SIZE_1k - 2); char *p; if ((p = strchr(&soversion[1], '.'))) { *p = 0; } } tgt->soname = make_strf(wk, "%s%s", plain_name, have_soversion ? soversion : ""); } static void setup_dllname(struct workspace *wk, struct obj_build_target *tgt, const char *plain_name, obj dllver, obj ver) { if (dllver) { tgt->soname = make_strf(wk, "%s-%s.dll", plain_name, get_cstr(wk, dllver)); } else if (ver) { char buf[BUF_SIZE_1k]; strncpy(buf, get_cstr(wk, ver), sizeof(buf) - 1); char *tmp = strchr(buf, '.'); if (tmp) { *tmp = '\0'; tgt->soname = make_strf(wk, "%s-%s.dll", plain_name, buf); } } if (!tgt->soname) { tgt->soname = make_strf(wk, "%s.dll", plain_name); } if (tgt->type == tgt_dynamic_library) { TSTR(implib); tstr_pushf(wk, &implib, "%s-implib.lib", plain_name); TSTR(path); path_join(wk, &path, get_cstr(wk, tgt->build_dir), implib.buf); tgt->implib = tstr_into_str(wk, &path); } } static bool setup_shared_object_symlinks(struct workspace *wk, struct obj_build_target *tgt, const char *plain_name, obj *plain_name_install, obj *soname_install) { TSTR(soname_symlink); TSTR(plain_name_symlink); if (!fs_mkdir_p(get_cstr(wk, tgt->build_dir))) { return false; } if (!str_eql(get_str(wk, tgt->build_name), get_str(wk, tgt->soname))) { path_join(wk, &soname_symlink, get_cstr(wk, tgt->build_dir), get_cstr(wk, tgt->soname)); if (!fs_make_symlink(get_cstr(wk, tgt->build_name), soname_symlink.buf, true)) { return false; } *soname_install = tstr_into_str(wk, &soname_symlink); } if (!str_eql(&STRL(plain_name), get_str(wk, tgt->soname)) && !str_eql(&STRL(plain_name), get_str(wk, tgt->build_name))) { path_join(wk, &plain_name_symlink, get_cstr(wk, tgt->build_dir), plain_name); if (!fs_make_symlink(get_cstr(wk, tgt->soname), plain_name_symlink.buf, true)) { return false; } *plain_name_install = tstr_into_str(wk, &plain_name_symlink); } return true; } static bool determine_target_build_name(struct workspace *wk, struct obj_build_target *tgt, obj sover, obj ver, obj name_pre, obj name_suff, char plain_name[BUF_SIZE_2k]) { char ver_dll[BUF_SIZE_1k]; const char *pref, *suff, *ver_suff = NULL; *ver_dll = '\0'; switch (tgt->type) { case tgt_executable: pref = ""; if (host_machine.is_windows) { suff = "exe"; } else { suff = NULL; } break; case tgt_static_library: if (host_machine.sys == machine_system_cygwin) { pref = "cyg"; } else { pref = "lib"; } suff = "a"; break; case tgt_shared_module: case tgt_dynamic_library: if (host_machine.sys == machine_system_cygwin) { pref = "cyg"; } else if (host_machine.sys == machine_system_windows) { pref = ""; } else { pref = "lib"; } if (host_machine.is_windows) { suff = "dll"; if (sover) { strncpy(ver_dll, get_cstr(wk, sover), sizeof(ver_dll) - 1); } else if (ver) { strncpy(ver_dll, get_cstr(wk, ver), sizeof(ver_dll) - 1); char *ver_tmp = strchr(ver_dll, '.'); if (ver_tmp) { *ver_tmp = 0; } else { *ver_dll = 0; } } } else if (host_machine.sys == machine_system_darwin) { suff = "dylib"; } else { suff = "so"; if (ver) { ver_suff = get_cstr(wk, ver); } else if (sover) { ver_suff = get_cstr(wk, sover); } } break; default: assert(false && "unreachable"); return false; } if (name_pre) { pref = get_cstr(wk, name_pre); } if (name_suff) { suff = get_cstr(wk, name_suff); } if (host_machine.is_windows) { snprintf(plain_name, BUF_SIZE_2k, "%s%s", pref, get_cstr(wk, tgt->name)); tgt->build_name = make_strf(wk, "%s%s%s%s%s", plain_name, *ver_dll ? "-" : "", *ver_dll ? ver_dll : "", suff ? "." : "", suff ? suff : ""); } else { snprintf(plain_name, BUF_SIZE_2k, "%s%s%s%s", pref, get_cstr(wk, tgt->name), suff ? "." : "", suff ? suff : ""); tgt->build_name = make_strf(wk, "%s%s%s", plain_name, ver_suff ? "." : "", ver_suff ? ver_suff : ""); } return true; } static bool create_target(struct workspace *wk, struct args_norm *an, struct args_kw *akw, enum tgt_type type, bool ignore_sources, obj *res) { char plain_name[BUF_SIZE_2k + 1] = { 0 }; *res = make_obj(wk, obj_build_target); struct obj_build_target *tgt = get_obj_build_target(wk, *res); tgt->type = type; tgt->name = an[0].val; tgt->cwd = current_project(wk)->cwd; tgt->build_dir = current_project(wk)->build_dir; tgt->machine = coerce_machine_kind(wk, &akw[bt_kw_native]); tgt->callstack = vm_callstack(wk); tgt->args = make_obj(wk, obj_dict); tgt->src = make_obj(wk, obj_array); tgt->required_compilers = make_obj(wk, obj_dict); tgt->extra_files = make_obj(wk, obj_array); { // dep internal setup enum build_dep_flag flags = 0; if (get_option_default_both_libraries(wk, 0, 0) == default_both_libraries_auto) { if (tgt->type & tgt_static_library) { flags |= build_dep_flag_both_libs_static; flags |= build_dep_flag_recursive; } else if (tgt->type & tgt_dynamic_library) { flags |= build_dep_flag_both_libs_shared; flags |= build_dep_flag_recursive; } } obj rpath = make_obj(wk, obj_array); if (akw[bt_kw_build_rpath].set) { obj_array_push(wk, rpath, akw[bt_kw_build_rpath].val); } if (akw[bt_kw_install_rpath].set) { obj_array_push(wk, rpath, akw[bt_kw_install_rpath].val); } struct build_dep_raw raw = { .link_with = akw[bt_kw_link_with].val, .link_whole = akw[bt_kw_link_whole].val, .deps = akw[bt_kw_dependencies].val, .rpath = rpath, }; if (!dependency_create(wk, &raw, &tgt->dep_internal, flags)) { return false; } } if (!deps_check_machine_matches(wk, tgt->name, tgt->machine, akw[bt_kw_link_with].val, akw[bt_kw_link_whole].val, akw[bt_kw_dependencies].val)) { return false; } if (akw[bt_kw_override_options].set) { if (!parse_and_set_override_options(wk, akw[bt_kw_override_options].node, akw[bt_kw_override_options].val, &tgt->override_options)) { return false; } } { // build target flags { // pic bool pic = false; if (akw[bt_kw_pic].set) { pic = get_obj_bool(wk, akw[bt_kw_pic].val); if (!pic && tgt->type & (tgt_dynamic_library | tgt_shared_module)) { vm_error_at( wk, akw[bt_kw_pic].node, "shared libraries must be compiled as pic"); return false; } } else { bool staticpic = get_option_bool(wk, tgt->override_options, "b_staticpic", true); if (tgt->type & tgt_static_library) { pic = staticpic; } else if (tgt->type & (tgt_dynamic_library | tgt_shared_module)) { pic = true; } } if (pic) { tgt->flags |= build_tgt_flag_pic; } } { // pie bool pie = false; if (akw[bt_kw_pie].set) { pie = get_obj_bool(wk, akw[bt_kw_pie].val); if (pie && (tgt->type & tgt_executable) != tgt_executable) { vm_error_at(wk, akw[bt_kw_pie].node, "pie cannot be set for non-executables"); return false; } } else if ((tgt->type & tgt_executable) == tgt_executable) { pie = get_option_bool(wk, tgt->override_options, "b_pie", false); } if (pie) { tgt->flags |= build_tgt_flag_pie; } } if (akw[bt_kw_export_dynamic].set && get_obj_bool(wk, akw[bt_kw_export_dynamic].val)) { tgt->flags |= build_tgt_flag_export_dynamic; } if (!akw[bt_kw_build_by_default].set || get_obj_bool(wk, akw[bt_kw_build_by_default].val)) { tgt->flags |= build_tgt_flag_build_by_default; } struct args_kw *vis = &akw[bt_kw_gnu_symbol_visibility]; if (vis->set && get_str(wk, vis->val)->len) { const struct str *str = get_str(wk, vis->val); if (str_eql(str, &STR("default"))) { tgt->visibility = compiler_visibility_default; } else if (str_eql(str, &STR("hidden"))) { tgt->visibility = compiler_visibility_hidden; } else if (str_eql(str, &STR("internal"))) { tgt->visibility = compiler_visibility_internal; } else if (str_eql(str, &STR("protected"))) { tgt->visibility = compiler_visibility_protected; } else if (str_eql(str, &STR("inlineshidden"))) { tgt->visibility = compiler_visibility_inlineshidden; } else { vm_error_at(wk, vis->node, "unknown visibility '%s'", get_cstr(wk, vis->val)); return false; } tgt->flags |= build_tgt_flag_visibility; } } obj sover = 0; if (akw[bt_kw_soversion].set) { if (!coerce_num_to_string(wk, akw[bt_kw_soversion].node, akw[bt_kw_soversion].val, &sover)) { return false; } } if (!determine_target_build_name(wk, tgt, sover, akw[bt_kw_version].val, akw[bt_kw_name_prefix].val, akw[bt_kw_name_suffix].val, plain_name)) { return false; } { /* tgt_build_path */ TSTR(path); path_join(wk, &path, get_cstr(wk, tgt->build_dir), get_cstr(wk, tgt->build_name)); tgt->build_path = make_str(wk, path.buf); tstr_pushs(wk, &path, ".p"); tgt->private_path = tstr_into_str(wk, &path); } bool implicit_include_directories = akw[bt_kw_implicit_include_directories].set ? get_obj_bool(wk, akw[bt_kw_implicit_include_directories].val) : true; obj prepend_include_directories = make_obj(wk, obj_array); { // sources if (akw[bt_kw_objects].set) { if (!coerce_files(wk, akw[bt_kw_objects].node, akw[bt_kw_objects].val, &tgt->objects)) { return false; } obj deduped; obj_array_dedup(wk, tgt->objects, &deduped); tgt->objects = deduped; } else { tgt->objects = make_obj(wk, obj_array); } obj_array_extend(wk, tgt->objects, tgt->dep_internal.objects); obj_array_dedup_in_place(wk, &tgt->objects); if (!ignore_sources) { obj sources = an[1].val; if (akw[bt_kw_sources].set) { obj_array_extend(wk, sources, akw[bt_kw_sources].val); } obj_array_extend(wk, sources, tgt->dep_internal.sources); struct process_build_tgt_sources_ctx ctx = { .err_node = an[1].node, .res = tgt->src, .tgt_id = *res, .prepend_include_directories = prepend_include_directories, .implicit_include_directories = implicit_include_directories, }; if (!obj_array_foreach_flat(wk, sources, &ctx, process_build_tgt_sources_iter)) { return false; } obj deduped; obj_array_dedup(wk, tgt->src, &deduped); tgt->src = deduped; } if (!get_obj_array(wk, tgt->src)->len && !get_obj_array(wk, tgt->objects)->len && !akw[bt_kw_link_whole].set && tgt->type != tgt_static_library) { uint32_t node = akw[bt_kw_sources].set ? akw[bt_kw_sources].node : an[1].node; vm_error_at(wk, node, "target declared with no linkable sources"); return false; } if (akw[bt_kw_extra_files].set) { obj_array_extend(wk, tgt->extra_files, akw[bt_kw_extra_files].val); } } { // include directories uint32_t node = an[0].node; // TODO: not a very informative error node obj include_directories = make_obj(wk, obj_array); if (implicit_include_directories) { obj_array_push(wk, include_directories, current_project(wk)->cwd); } if (akw[bt_kw_include_directories].set) { node = akw[bt_kw_include_directories].node; obj_array_extend(wk, include_directories, akw[bt_kw_include_directories].val); } obj_array_extend_nodup(wk, include_directories, prepend_include_directories); obj coerced; if (!coerce_include_dirs(wk, node, include_directories, false, &coerced)) { return false; } obj_array_extend_nodup(wk, coerced, tgt->dep_internal.include_directories); tgt->dep_internal.include_directories = coerced; } { // compiler args static struct { enum build_target_kwargs kw; enum compiler_language l; bool static_only, shared_only; } lang_args[] = { #define E(lang, s, st, sh) { bt_kw_##lang##s, compiler_language_##lang, st, sh } #define TOOLCHAIN_ENUM(lang) \ E(lang, _args, false, false), E(lang, _static_args, true, false), E(lang, _shared_args, false, true), FOREACH_COMPILER_EXPOSED_LANGUAGE(TOOLCHAIN_ENUM) #undef TOOLCHAIN_ENUM #undef E }; { // copy c or cpp args to assembly args if (akw[bt_kw_c_args].set) { obj_dict_seti(wk, tgt->args, compiler_language_assembly, akw[bt_kw_c_args].val); } else if (akw[bt_kw_cpp_args].set) { obj_dict_seti(wk, tgt->args, compiler_language_assembly, akw[bt_kw_cpp_args].val); } } uint32_t i; for (i = 0; i < ARRAY_LEN(lang_args); ++i) { if (!akw[lang_args[i].kw].set) { continue; } else if (lang_args[i].static_only && type != tgt_static_library) { continue; } else if (lang_args[i].shared_only && type != tgt_dynamic_library) { continue; } obj_dict_seti(wk, tgt->args, lang_args[i].l, akw[lang_args[i].kw].val); } } { // pch struct { struct args_kw *kw; enum compiler_language l; } pch_args[] = { #define E(lang, s) { &akw[bt_kw_##lang##s], compiler_language_##lang }, #define TOOLCHAIN_ENUM(lang) E(lang, _pch) FOREACH_COMPILER_EXPOSED_LANGUAGE(TOOLCHAIN_ENUM) #undef TOOLCHAIN_ENUM #undef E }; uint32_t i; for (i = 0; i < ARRAY_LEN(pch_args); ++i) { if (!pch_args[i].kw->set) { continue; } obj pch; if (get_obj_type(wk, pch_args[i].kw->val) == obj_build_target) { struct obj_build_target *pch_tgt = get_obj_build_target(wk, pch_args[i].kw->val); if ((pch_tgt->type & tgt_static_library) != tgt_static_library ) { vm_error_at(wk, pch_args[i].kw->node, "this build target must be a static library to be used as a pch"); return false; } obj pch_tgt_pch; if (!obj_dict_geti(wk, pch_tgt->pch, pch_args[i].l, &pch_tgt_pch)) { vm_error_at(wk, pch_args[i].kw->node, "this build target does not define a pch for %s", compiler_language_to_s(pch_args[i].l)); return false; } if (get_obj_type(wk, pch_tgt_pch) == obj_build_target) { vm_error_at(wk, pch_args[i].kw->node, "cannot use a build target for pch which itself uses a build target for pch"); return false; } pch = pch_args[i].kw->val; } else { if (!coerce_file(wk, pch_args[i].kw->node, pch_args[i].kw->val, &pch)) { return false; } } if (!tgt->pch) { tgt->pch = make_obj(wk, obj_dict); } obj_dict_seti(wk, tgt->pch, pch_args[i].l, pch); build_tgt_inc_required_compiler(wk, tgt, pch_args[i].l); } } obj soname_install = 0, plain_name_install = 0; // soname handling if (type & (tgt_dynamic_library | tgt_shared_module)) { if (host_machine.is_windows) { setup_dllname(wk, tgt, plain_name, sover, akw[bt_kw_version].val); } else { setup_soname(wk, tgt, plain_name, sover, akw[bt_kw_version].val); if (type == tgt_dynamic_library) { if (!setup_shared_object_symlinks( wk, tgt, plain_name, &plain_name_install, &soname_install)) { return false; } } } } // link depends if (akw[bt_kw_link_depends].set) { obj depends; if (!coerce_files(wk, akw[bt_kw_link_depends].node, akw[bt_kw_link_depends].val, &depends)) { return false; } tgt->link_depends = depends; } if (akw[bt_kw_install].set && get_obj_bool(wk, akw[bt_kw_install].val)) { tgt->flags |= build_tgt_flag_installed; obj install_dir = 0; if (akw[bt_kw_install_dir].set) { install_dir = akw[bt_kw_install_dir].val; } else { switch (type) { case tgt_executable: get_option_value(wk, current_project(wk), "bindir", &install_dir); break; case tgt_static_library: get_option_value(wk, current_project(wk), "libdir", &install_dir); break; case tgt_dynamic_library: case tgt_shared_module: { if (host_machine.is_windows) { get_option_value(wk, current_project(wk), "bindir", &install_dir); } else { get_option_value(wk, current_project(wk), "libdir", &install_dir); } break; } default: assert(false && "unreachable"); break; } } TSTR(install_src); path_join(wk, &install_src, get_cstr(wk, tgt->build_dir), get_cstr(wk, tgt->build_name)); TSTR(install_dest); path_join(wk, &install_dest, get_cstr(wk, install_dir), get_cstr(wk, tgt->build_name)); struct obj_install_target *install_tgt; if (!(install_tgt = push_install_target(wk, tstr_into_str(wk, &install_src), tstr_into_str(wk, &install_dest), akw[bt_kw_install_mode].val))) { return false; } install_tgt->build_target = true; if (soname_install) { push_install_target_install_dir(wk, soname_install, install_dir, akw[bt_kw_install_mode].val); } if (plain_name_install) { push_install_target_install_dir( wk, plain_name_install, install_dir, akw[bt_kw_install_mode].val); } } if (!build_tgt_determine_linker(wk, an[0].node, tgt)) { return false; } tgt->dep = (struct build_dep){ .link_language = tgt->dep_internal.link_language, .include_directories = tgt->dep_internal.include_directories, .order_deps = tgt->dep_internal.order_deps, .rpath = tgt->dep_internal.rpath, .raw = tgt->dep_internal.raw, }; if (tgt->type == tgt_static_library) { tgt->dep.link_whole = tgt->dep_internal.link_whole; tgt->dep.link_with = tgt->dep_internal.link_with; tgt->dep.link_with_not_found = tgt->dep_internal.link_with_not_found; tgt->dep.frameworks = tgt->dep_internal.frameworks; obj_array_dup(wk, tgt->dep_internal.link_args, &tgt->dep.link_args); } if (akw[bt_kw_link_args].set) { obj_array_extend(wk, tgt->dep_internal.link_args, akw[bt_kw_link_args].val); } if (tgt->flags & build_tgt_generated_include) { const char *private_path = get_cstr(wk, tgt->private_path); // mkdir so that the include dir doesn't get pruned later on if (!fs_mkdir_p(private_path)) { return false; } } L("adding build target %s", get_cstr(wk, tgt->build_name)); obj_array_push(wk, current_project(wk)->targets, *res); return true; } static bool typecheck_string_or_empty_array(struct workspace *wk, struct args_kw *kw) { if (!kw->set) { return true; } enum obj_type t = get_obj_type(wk, kw->val); if (t == obj_string) { return true; } else if (t == obj_array && get_obj_array(wk, kw->val)->len == 0) { kw->set = false; kw->val = 0; return true; } else { vm_error_at(wk, kw->node, "expected string or [], got %s", obj_type_to_s(t)); return false; } } static bool both_libs_can_reuse_objects(struct workspace *wk, struct args_kw *akw) { bool lib_specific_args[] = { #define E(lang, s) akw[bt_kw_##lang##s].set #define TOOLCHAIN_ENUM(lang) E(lang, _static_args), E(lang, _shared_args), FOREACH_COMPILER_EXPOSED_LANGUAGE(TOOLCHAIN_ENUM) #undef TOOLCHAIN_ENUM #undef E }; uint32_t i; for (i = 0; i < ARRAY_LEN(lib_specific_args); ++i) { if (lib_specific_args[i]) { return false; } } return true; } static bool tgt_common(struct workspace *wk, obj *res, enum tgt_type type, enum tgt_type argtype, bool tgt_type_from_kw) { struct args_norm an[] = { { obj_string }, { TYPE_TAG_GLOB | tc_coercible_files | tc_generated_list }, ARG_TYPE_NULL }; struct args_kw akw[bt_kwargs_count + 1] = { [bt_kw_sources] = { "sources", TYPE_TAG_LISTIFY | tc_coercible_files | tc_generated_list }, [bt_kw_include_directories] = { "include_directories", TYPE_TAG_LISTIFY | tc_coercible_inc }, [bt_kw_implicit_include_directories] = { "implicit_include_directories", obj_bool }, [bt_kw_dependencies] = { "dependencies", TYPE_TAG_LISTIFY | tc_dependency }, [bt_kw_install] = { "install", obj_bool }, [bt_kw_install_dir] = { "install_dir", obj_string }, [bt_kw_install_mode] = { "install_mode", tc_install_mode_kw }, [bt_kw_install_tag] = { "install_tag", tc_string }, // TODO [bt_kw_link_with] = { "link_with", tc_link_with_kw }, [bt_kw_link_whole] = { "link_whole", tc_link_with_kw }, [bt_kw_version] = { "version", obj_string }, [bt_kw_build_by_default] = { "build_by_default", obj_bool }, [bt_kw_extra_files] = { "extra_files", TYPE_TAG_LISTIFY | tc_coercible_files }, [bt_kw_target_type] = { "target_type", obj_string }, [bt_kw_name_prefix] = { "name_prefix", tc_string | tc_array }, [bt_kw_name_suffix] = { "name_suffix", tc_string | tc_array }, [bt_kw_soversion] = { "soversion", tc_number | tc_string }, [bt_kw_link_depends] = { "link_depends", TYPE_TAG_LISTIFY | tc_string | tc_file | tc_custom_target | tc_build_target | tc_build_target }, [bt_kw_objects] = { "objects", TYPE_TAG_LISTIFY | tc_file | tc_string }, [bt_kw_pic] = { "pic", obj_bool }, [bt_kw_pie] = { "pie", obj_bool }, [bt_kw_build_rpath] = { "build_rpath", obj_string }, [bt_kw_install_rpath] = { "install_rpath", obj_string }, [bt_kw_export_dynamic] = { "export_dynamic", obj_bool }, [bt_kw_vs_module_defs] = { "vs_module_defs", tc_string | tc_file | tc_custom_target }, [bt_kw_gnu_symbol_visibility] = { "gnu_symbol_visibility", obj_string }, [bt_kw_native] = { "native", obj_bool }, [bt_kw_darwin_versions] = { "darwin_versions", TYPE_TAG_LISTIFY | tc_string | tc_number }, [bt_kw_implib] = { "implib", tc_bool | tc_string }, [bt_kw_gui_app] = { "gui_app", obj_bool }, [bt_kw_link_language] = { "link_language", obj_string }, [bt_kw_win_subsystem] = { "win_subsystem", obj_string }, [bt_kw_override_options] = { "override_options", COMPLEX_TYPE_PRESET(tc_cx_options_dict_or_list) }, [bt_kw_link_args] = { "link_args", TYPE_TAG_LISTIFY | obj_string }, #define E(lang, s, t) [bt_kw_##lang##s] = { #lang #s, t } #define TOOLCHAIN_ENUM(lang) \ E(lang, _args, TYPE_TAG_LISTIFY | obj_string), E(lang, _static_args, TYPE_TAG_LISTIFY | obj_string), \ E(lang, _shared_args, TYPE_TAG_LISTIFY | obj_string), E(lang, _pch, tc_string | tc_file | tc_build_target), FOREACH_COMPILER_EXPOSED_LANGUAGE(TOOLCHAIN_ENUM) #undef TOOLCHAIN_ENUM #undef E }; if (!pop_args(wk, an, akw)) { return false; } if (tgt_type_from_kw) { if (!akw[bt_kw_target_type].set) { vm_error(wk, "missing required kwarg: %s", akw[bt_kw_target_type].key); return false; } if (!type_from_kw(wk, akw[bt_kw_target_type].node, akw[bt_kw_target_type].val, &type)) { return false; } } else { if (akw[bt_kw_target_type].set) { vm_error_at(wk, akw[bt_kw_target_type].node, "invalid kwarg"); return false; } } static const enum tgt_type keyword_validity[bt_kwargs_count] = { [bt_kw_version] = tgt_dynamic_library, [bt_kw_soversion] = tgt_dynamic_library, }; uint32_t i; for (i = 0; i < bt_kwargs_count; ++i) { if (keyword_validity[i] && akw[i].set && !(keyword_validity[i] & argtype)) { vm_error_at(wk, akw[i].node, "invalid kwarg"); return false; } } if (!typecheck_string_or_empty_array(wk, &akw[bt_kw_name_suffix])) { return false; } else if (!typecheck_string_or_empty_array(wk, &akw[bt_kw_name_prefix])) { return false; } if (type == (tgt_static_library | tgt_dynamic_library) && !akw[bt_kw_pic].set) { akw[bt_kw_pic].val = make_obj_bool(wk, true); akw[bt_kw_pic].set = true; } bool multi_target = false; obj tgt = 0; for (i = 0; i <= tgt_type_count; ++i) { enum tgt_type t = 1 << i; if (!(type & t)) { continue; } bool ignore_sources = false; if (tgt && !multi_target) { multi_target = true; *res = make_obj(wk, obj_array); obj_array_push(wk, *res, tgt); if (both_libs_can_reuse_objects(wk, akw)) { // If this target is a multi-target (both_libraries), // set the objects argument with objects from the // previous target obj objects; if (!build_target_extract_all_objects(wk, an[0].node, tgt, &objects, true)) { return false; } if (akw[bt_kw_objects].set) { obj_array_extend(wk, akw[bt_kw_objects].val, objects); } else { akw[bt_kw_objects].set = true; akw[bt_kw_objects].val = objects; akw[bt_kw_objects].node = an[0].node; } ignore_sources = true; } } if (!create_target(wk, an, akw, t, ignore_sources, &tgt)) { return false; } if (multi_target) { obj_array_push(wk, *res, tgt); } else { *res = tgt; } } if (multi_target) { obj val; val = make_obj(wk, obj_both_libs); struct obj_both_libs *both = get_obj_both_libs(wk, val); both->static_lib = obj_array_index(wk, *res, 0); both->dynamic_lib = obj_array_index(wk, *res, 1); *res = val; assert(get_obj_build_target(wk, both->static_lib)->type == tgt_static_library); assert(get_obj_build_target(wk, both->dynamic_lib)->type == tgt_dynamic_library); } return true; } bool func_executable(struct workspace *wk, obj _, obj *res) { return tgt_common(wk, res, tgt_executable, tgt_executable, false); } bool func_static_library(struct workspace *wk, obj _, obj *res) { return tgt_common(wk, res, tgt_static_library, tgt_static_library, false); } bool func_shared_library(struct workspace *wk, obj _, obj *res) { return tgt_common(wk, res, tgt_dynamic_library, tgt_dynamic_library, false); } bool func_both_libraries(struct workspace *wk, obj _, obj *res) { return tgt_common( wk, res, tgt_static_library | tgt_dynamic_library, tgt_static_library | tgt_dynamic_library, false); } bool func_library(struct workspace *wk, obj _, obj *res) { return tgt_common(wk, res, get_option_default_library(wk), tgt_static_library | tgt_dynamic_library, false); } bool func_shared_module(struct workspace *wk, obj _, obj *res) { return tgt_common(wk, res, tgt_shared_module, tgt_shared_module, false); } bool func_build_target(struct workspace *wk, obj _, obj *res) { return tgt_common(wk, res, 0, tgt_executable | tgt_static_library | tgt_dynamic_library, true); } muon-v0.5.0/src/functions/kernel/dependency.c0000644000175000017500000015665415041716357020233 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "backend/common_args.h" #include "buf_size.h" #include "coerce.h" #include "external/pkgconfig.h" #include "functions/both_libs.h" #include "functions/compiler.h" #include "functions/file.h" #include "functions/kernel/dependency.h" #include "functions/kernel/subproject.h" #include "functions/string.h" #include "functions/subproject.h" #include "lang/analyze.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "machines.h" #include "options.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" #include "tracy.h" static const struct { const char *name; enum dependency_lookup_method method; } dependency_lookup_method_names[] = { { "auto", dependency_lookup_method_auto }, { "builtin", dependency_lookup_method_builtin }, { "cmake", dependency_lookup_method_cmake }, { "config-tool", dependency_lookup_method_config_tool }, { "dub", dependency_lookup_method_dub }, { "extraframework", dependency_lookup_method_extraframework }, { "pkg-config", dependency_lookup_method_pkgconfig }, { "sysconfig", dependency_lookup_method_sysconfig }, { "system", dependency_lookup_method_system }, // For backwards compatibility { "sdlconfig", dependency_lookup_method_config_tool }, { "cups-config", dependency_lookup_method_config_tool }, { "pcap-config", dependency_lookup_method_config_tool }, { "libwmf-config", dependency_lookup_method_config_tool }, { "qmake", dependency_lookup_method_config_tool }, }; bool dependency_lookup_method_from_s(const struct str *s, enum dependency_lookup_method *lookup_method) { uint32_t i; for (i = 0; i < ARRAY_LEN(dependency_lookup_method_names); ++i) { if (str_eql(s, &STRL(dependency_lookup_method_names[i].name))) { *lookup_method = dependency_lookup_method_names[i].method; break; } } if (i == ARRAY_LEN(dependency_lookup_method_names)) { return false; } return true; } const char * dependency_lookup_method_to_s(enum dependency_lookup_method method) { uint32_t i; for (i = 0; i < ARRAY_LEN(dependency_lookup_method_names); ++i) { if (dependency_lookup_method_names[i].method == method) { return dependency_lookup_method_names[i].name; } } UNREACHABLE; } enum dep_lib_mode { dep_lib_mode_default, dep_lib_mode_static, dep_lib_mode_shared, }; struct dep_lookup_ctx { obj *res; struct args_kw *default_options, *versions, *handler_kwargs; enum requirement_type requirement; enum machine_kind machine; uint32_t err_node; uint32_t fallback_node; obj name; obj names; obj fallback; obj not_found_message; obj modules; enum dep_lib_mode lib_mode; enum dependency_lookup_method lookup_method; bool disabler; bool from_cache; bool from_override; bool found; }; static obj get_dependency_c_compiler(struct workspace *wk, enum machine_kind machine) { struct project *proj = current_project(wk); obj compiler; if (obj_dict_geti(wk, proj->toolchains[machine], compiler_language_c, &compiler)) { return compiler; } else if (obj_dict_geti(wk, proj->toolchains[machine], compiler_language_cpp, &compiler)) { return compiler; } else if (obj_dict_geti(wk, proj->toolchains[machine], compiler_language_objc, &compiler)) { return compiler; } else if (obj_dict_geti(wk, proj->toolchains[machine], compiler_language_objcpp, &compiler)) { return compiler; } else { return 0; } } static bool check_dependency_override_for_machine(struct workspace *wk, const struct dep_lookup_ctx *ctx, enum machine_kind machine, enum dep_lib_mode *found_lib_mode) { bool found = false; obj n; obj_array_for(wk, ctx->names, n) { if (ctx->lib_mode != dep_lib_mode_shared) { if (obj_dict_index(wk, wk->dep_overrides_static[machine], n, ctx->res)) { *found_lib_mode = dep_lib_mode_static; found = true; break; } } if (ctx->lib_mode != dep_lib_mode_static) { if (obj_dict_index(wk, wk->dep_overrides_dynamic[machine], n, ctx->res)) { *found_lib_mode = dep_lib_mode_shared; found = true; break; } } } return found; } static bool check_dependency_override(struct workspace *wk, struct dep_lookup_ctx *ctx) { ctx->found = check_dependency_override_for_machine(wk, ctx, ctx->machine, &ctx->lib_mode); if (ctx->found) { LO("found %o in override\n", ctx->name); ctx->from_override = true; } return ctx->found; } static bool check_dependency_cache(struct workspace *wk, struct dep_lookup_ctx *ctx, obj *res) { if (ctx->lib_mode != dep_lib_mode_shared) { if (obj_dict_index(wk, current_project(wk)->dep_cache.static_deps[ctx->machine], ctx->name, res)) { ctx->lib_mode = dep_lib_mode_static; return true; } } if (ctx->lib_mode != dep_lib_mode_static) { if (obj_dict_index(wk, current_project(wk)->dep_cache.shared_deps[ctx->machine], ctx->name, res)) { ctx->lib_mode = dep_lib_mode_shared; return true; } } return false; } static bool check_dependency_version(struct workspace *wk, obj dep_ver_str, obj ver) { if (!ver) { return true; } if (!dep_ver_str) { return false; } return version_compare_list(wk, get_str(wk, dep_ver_str), ver); } static bool handle_dependency_fallback(struct workspace *wk, struct dep_lookup_ctx *ctx, bool *found) { if (get_option_wrap_mode(wk) == wrap_mode_nofallback) { return true; } obj subproj_name, subproj_dep = 0, subproj; switch (get_obj_array(wk, ctx->fallback)->len) { case 2: subproj_dep = obj_array_index(wk, ctx->fallback, 1); /* FALLTHROUGH */ case 1: subproj_name = obj_array_index(wk, ctx->fallback, 0); break; default: vm_error_at(wk, ctx->err_node, "expected array of length 1-2 for fallback"); return false; } if (ctx->lib_mode != dep_lib_mode_default) { obj libopt; if (ctx->lib_mode == dep_lib_mode_static) { libopt = make_str(wk, "default_library=static"); } else { libopt = make_str(wk, "default_library=shared"); } if (ctx->default_options->set) { if (!obj_array_in(wk, ctx->default_options->val, libopt)) { obj newopts; obj_array_dup(wk, ctx->default_options->val, &newopts); obj_array_push(wk, newopts, libopt); ctx->default_options->val = newopts; } } else { ctx->default_options->val = make_obj(wk, obj_array); obj_array_push(wk, ctx->default_options->val, libopt); ctx->default_options->set = true; } } if (!subproject(wk, subproj_name, ctx->requirement, ctx->default_options, ctx->versions, &subproj)) { goto not_found; } if (!get_obj_subproject(wk, subproj)->found) { goto not_found; } if (!check_dependency_override(wk, ctx)) { if (subproj_dep) { if (!subproject_get_variable(wk, ctx->fallback_node, subproj_dep, 0, subproj, ctx->res)) { vm_warning_at(wk, ctx->fallback_node, "subproject dependency variable %o is not defined", subproj_dep); goto not_found; } } else { vm_warning_at(wk, ctx->fallback_node, "subproject does not override dependency %o for %s machine", ctx->name, machine_kind_to_s(ctx->machine)); goto not_found; } } if (get_obj_type(wk, *ctx->res) != obj_dependency) { vm_warning_at(wk, ctx->fallback_node, "overridden dependency is not a dependency object"); goto not_found; } struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); if (!machine_matches(dep->machine, ctx->machine)) { vm_warning_at(wk, ctx->fallback_node, "overridden dependency is for the %s machine, but a dependency for the %s machine was requested", machine_kind_to_s(dep->machine), machine_kind_to_s(ctx->machine)); goto not_found; } *found = true; return true; not_found: obj_lprintf(wk, log_info, "fallback %o failed for %o\n", ctx->fallback, ctx->name); *ctx->res = 0; *found = false; return true; } static bool get_dependency_pkgconfig(struct workspace *wk, struct dep_lookup_ctx *ctx, bool *found) { struct pkgconfig_info info = { 0 }; *found = false; if (!muon_pkgconfig_lookup(wk, get_dependency_c_compiler(wk, ctx->machine), ctx->name, ctx->lib_mode == dep_lib_mode_static, &info)) { return true; } obj ver_str = make_str(wk, info.version); *ctx->res = make_obj(wk, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); dep->name = ctx->name; dep->version = ver_str; dep->flags |= dep_flag_found; dep->type = dependency_type_pkgconf; struct build_dep_raw raw = { .link_with = info.libs, .link_with_not_found = info.not_found_libs, .include_directories = info.includes, .compile_args = info.compile_args, .link_args = info.link_args, }; if (!dependency_create(wk, &raw, &dep->dep, 0)) { return false; } *found = true; return true; } struct get_dependency_extraframework_scan_path_ctx { struct workspace *wk; const struct str *fw; obj res; }; static enum iteration_result get_dependency_extraframework_scan_path_cb(void *_ctx, const char *path) { struct get_dependency_extraframework_scan_path_ctx *ctx = _ctx; struct str p = STRL(path); const struct str suffix = STR(".framework"); if (str_endswith(&p, &suffix)) { p.len -= suffix.len; if (str_eqli(&p, ctx->fw)) { ctx->res = make_strn(ctx->wk, p.s, p.len); return ir_done; } } return ir_cont; } static bool get_dependency_extraframework(struct workspace *wk, struct dep_lookup_ctx *ctx, bool *found) { if (machine_definitions[ctx->machine]->sys != machine_system_darwin) { L("skipping extraframework dependency lookup: %s machine system is not darwin", machine_kind_to_s(ctx->machine)); return true; } obj compiler = get_dependency_c_compiler(wk, ctx->machine); if (!compiler) { L("skipping extraframework dependency lookup: no compiler defined"); return true; } struct obj_compiler *comp = get_obj_compiler(wk, compiler); if (comp->type[toolchain_component_compiler] != compiler_apple_clang) { L("skipping extraframework dependency lookup: compiler type is not apple clang"); return true; } if (!comp->fwdirs) { obj cmd; obj_array_dup(wk, comp->cmd_arr[toolchain_component_compiler], &cmd); obj_array_push(wk, cmd, make_str(wk, "-v")); obj_array_push(wk, cmd, make_str(wk, "-E")); obj_array_push(wk, cmd, make_str(wk, "-")); push_args(wk, cmd, toolchain_compiler_always(wk, comp)); ca_get_option_compile_args(wk, comp, current_project(wk), 0, cmd); const char *argstr; uint32_t argc; join_args_argstr(wk, &argstr, &argc, cmd); struct compiler_check_cache_key key = { comp, argstr, "", argc }; obj k = compiler_check_cache_key(wk, &key); struct compiler_check_cache_value val = { 0 }; if (compiler_check_cache_get(wk, k, &val)) { comp->fwdirs = val.value; } else { comp->fwdirs = make_obj(wk, obj_array); struct run_cmd_ctx cmd_ctx = { 0 }; if (run_cmd(&cmd_ctx, argstr, argc, 0, 0) && cmd_ctx.status == 0) { char *e, *s = cmd_ctx.err.buf, *eof = s + cmd_ctx.err.len; const struct str fw_suffix = STR("(framework directory)"); while (s < eof) { if (!(e = strchr(s, '\n'))) { e = eof; } struct str line = { s, e - s }; if (str_contains(&line, &fw_suffix)) { line.s += 1; line.len -= 2 + fw_suffix.len; obj_array_push(wk, comp->fwdirs, make_strn(wk, line.s, line.len)); } s = e + 1; } } else { run_cmd_print_error(&cmd_ctx, log_debug); L("failed to determine compiler framework directories"); // Add a dummy framework directory so we can still utilize it // when building the cache key. obj_array_push(wk, comp->fwdirs, make_str(wk, "unknown://")); } run_cmd_ctx_destroy(&cmd_ctx); val.success = true; val.value = comp->fwdirs; compiler_check_cache_set(wk, k, &val); } LO("detected compiler framework directories: %o\n", comp->fwdirs); } obj modules; bool handle_as_appleframeworks = false; if (strcmp(get_cstr(wk, ctx->name), "appleframeworks") == 0) { if (!ctx->modules) { vm_error_at(wk, ctx->err_node, "'appleframeworks' dependency requires the modules keyword"); return false; } handle_as_appleframeworks = true; modules = ctx->modules; } else { modules = make_obj(wk, obj_array); obj_array_push(wk, modules, ctx->name); } obj found_frameworks = make_obj(wk, obj_array); obj fw, fw_dir; obj_array_for(wk, modules, fw) { obj dep = 0; obj_array_for(wk, comp->fwdirs, fw_dir) { const char *fw_dir_str = get_str(wk, fw_dir)->s; if (fs_dir_exists(fw_dir_str)) { struct get_dependency_extraframework_scan_path_ctx scan_path_ctx = { .wk = wk, .fw = get_str(wk, fw), }; fs_dir_foreach(get_str(wk, fw_dir)->s, &scan_path_ctx, get_dependency_extraframework_scan_path_cb); if (!scan_path_ctx.res) { continue; } fw = scan_path_ctx.res; } obj fw_path; TSTR(fw_path_buf); { path_join(wk, &fw_path_buf, get_str(wk, fw_dir)->s, get_str(wk, fw)->s); tstr_pushs(wk, &fw_path_buf, ".framework"); fw_path = tstr_into_str(wk, &fw_path_buf); } if (obj_dict_index( wk, current_project(wk)->dep_cache.frameworks[ctx->machine], fw_path, &dep)) { break; } struct compiler_check_opts opts = { .mode = compiler_check_mode_link, .comp_id = compiler, }; obj compile_args = make_obj(wk, obj_array); // TODO: add -F when adding support for the "paths" kwarg. We // don't need to add -F ever right now since the fw must come from // a system path. // obj_array_push(wk, compile_args, make_str(wk, "-F")); // obj_array_push(wk, compile_args, fw_dir); obj link_args = make_obj(wk, obj_array); obj_array_push(wk, link_args, make_str(wk, "-framework")); obj_array_push(wk, link_args, fw); opts.args = make_obj(wk, obj_array); obj_array_extend(wk, opts.args, compile_args); obj_array_extend(wk, opts.args, link_args); bool ok; const char *src = "int main(void) { return 0; }\n"; if (!compiler_check(wk, &opts, src, 0, &ok)) { return false; } if (!ok) { continue; } obj include_directories = 0; if (!handle_as_appleframeworks) { const char *header_paths[] = { "Headers", "Versions/Current/Headers", }; for (uint32_t i = 0; i < ARRAY_LEN(header_paths); ++i) { TSTR(dir); path_join(wk, &dir, fw_path_buf.buf, header_paths[i]); if (fs_dir_exists(dir.buf)) { include_directories = make_obj(wk, obj_array); obj inc = make_obj(wk, obj_include_directory); struct obj_include_directory *i = get_obj_include_directory(wk, inc); i->path = tstr_into_str(wk, &dir); i->is_idirafter = true; obj_array_push(wk, include_directories, inc); break; } } } dep = make_obj(wk, obj_dependency); struct obj_dependency *d = get_obj_dependency(wk, dep); d->name = fw_path; d->flags |= dep_flag_found; d->type = dependency_type_pkgconf; d->machine = ctx->machine; struct build_dep_raw raw = { .include_directories = include_directories, .compile_args = compile_args, .link_args = link_args, }; if (!dependency_create(wk, &raw, &d->dep, 0)) { return false; } obj_dict_set(wk, current_project(wk)->dep_cache.frameworks[ctx->machine], fw_path, dep); break; } if (!dep) { // Missing one module means the entire dependency is not found return true; } obj_array_push(wk, found_frameworks, dep); } *found = true; if (obj_array_flatten_one(wk, found_frameworks, ctx->res)) { return true; } *ctx->res = make_obj(wk, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); char name[512]; obj_snprintf(wk, name, sizeof(name), "frameworks:%o", modules); dep->name = make_str(wk, name); dep->flags |= dep_flag_found; dep->type = dependency_type_declared; struct build_dep_raw raw = { .deps = found_frameworks, }; return dependency_create(wk, &raw, &dep->dep, 0); } static bool get_dependency_system(struct workspace *wk, struct dep_lookup_ctx *ctx, bool *found) { obj compiler = get_dependency_c_compiler(wk, ctx->machine); if (!compiler) { return true; } enum find_library_flag flags = 0; if (ctx->lib_mode == dep_lib_mode_static) { flags = find_library_flag_only_static; } struct find_library_result find_result = find_library(wk, compiler, get_cstr(wk, ctx->name), 0, flags); if (!find_result.found) { return true; } *found = true; *ctx->res = make_obj(wk, obj_dependency); find_library_result_to_dependency(wk, find_result, compiler, *ctx->res); return true; } typedef bool (*native_dependency_lookup_handler)(struct workspace *, struct dep_lookup_ctx *, bool *); static const native_dependency_lookup_handler dependency_lookup_handlers[] = { [dependency_lookup_method_pkgconfig] = get_dependency_pkgconfig, [dependency_lookup_method_builtin] = 0, [dependency_lookup_method_system] = get_dependency_system, [dependency_lookup_method_extraframework] = get_dependency_extraframework, }; struct dependency_lookup_handler { enum dependency_lookup_handler_type { dependency_lookup_handler_type_native, dependency_lookup_handler_type_capture, } type; enum dependency_lookup_method m; union { native_dependency_lookup_handler native; obj capture; } handler; }; struct dependency_lookup_handlers { struct dependency_lookup_handler e[4]; uint32_t len; }; static void dependency_lookup_handler_push_native(struct dependency_lookup_handlers *handlers, enum dependency_lookup_method m) { assert(handlers->len < ARRAY_LEN(handlers->e)); handlers->e[handlers->len].type = dependency_lookup_handler_type_native; handlers->e[handlers->len].m = m; handlers->e[handlers->len].handler.native = dependency_lookup_handlers[m]; ++handlers->len; } static void dependency_lookup_handler_push_capture(struct dependency_lookup_handlers *handlers, enum dependency_lookup_method m, obj o) { if (o == obj_bool_true) { dependency_lookup_handler_push_native(handlers, m); } else { assert(handlers->len < ARRAY_LEN(handlers->e)); handlers->e[handlers->len].type = dependency_lookup_handler_type_capture; handlers->e[handlers->len].m = m; handlers->e[handlers->len].handler.capture = o; ++handlers->len; } } static bool dependency_is_resolving_from_capture = false; static bool build_lookup_handler_list(struct workspace *wk, struct dep_lookup_ctx *ctx, struct dependency_lookup_handlers *handlers) { obj handler_dict = 0; if (!dependency_is_resolving_from_capture && obj_dict_index(wk, wk->dependency_handlers, ctx->name, &handler_dict)) { obj handler; if (ctx->lookup_method != dependency_lookup_method_auto) { if (!obj_dict_geti(wk, handler_dict, ctx->lookup_method, &handler)) { vm_error(wk, "Lookup method %s not supported for %o", dependency_lookup_method_to_s(ctx->lookup_method), ctx->name); return false; } dependency_lookup_handler_push_capture(handlers, ctx->lookup_method, handler); } else { obj method; obj_dict_for(wk, handler_dict, method, handler) { dependency_lookup_handler_push_capture(handlers, method, handler); } } } else { if (ctx->lookup_method == dependency_lookup_method_auto) { dependency_lookup_handler_push_native(handlers, dependency_lookup_method_pkgconfig); dependency_lookup_handler_push_native(handlers, dependency_lookup_method_extraframework); } else { if (!dependency_lookup_handlers[ctx->lookup_method]) { vm_error(wk, "Lookup method %s not supported for %o", dependency_lookup_method_to_s(ctx->lookup_method), ctx->name); return false; } dependency_lookup_handler_push_native(handlers, ctx->lookup_method); } } return true; } static obj get_dependency_fallback_name(struct workspace *wk, struct dep_lookup_ctx *ctx) { if (ctx->fallback) { return ctx->fallback; } obj provided_fallback; if (obj_dict_index(wk, current_project(wk)->wrap_provides_deps, ctx->name, &provided_fallback)) { return provided_fallback; } // implicitly fallback on a subproject named the same as this dependency obj res; res = make_obj(wk, obj_array); obj_array_push(wk, res, ctx->name); return res; } static bool is_dependency_fallback_forced(struct workspace *wk, struct dep_lookup_ctx *ctx) { obj force_fallback_for, subproj_name; get_option_value(wk, current_project(wk), "force_fallback_for", &force_fallback_for); subproj_name = obj_array_index(wk, get_dependency_fallback_name(wk, ctx), 0); enum wrap_mode wrap_mode = get_option_wrap_mode(wk); return wrap_mode == wrap_mode_forcefallback || obj_array_in(wk, force_fallback_for, ctx->name) || obj_dict_in(wk, wk->subprojects, subproj_name); } static bool get_dependency(struct workspace *wk, struct dep_lookup_ctx *ctx) { { obj cached_dep; if (check_dependency_cache(wk, ctx, &cached_dep)) { if (ctx->found) { LO("found %o in cache\n", ctx->name); } struct obj_dependency *dep = get_obj_dependency(wk, cached_dep); if (!check_dependency_version(wk, dep->version, ctx->versions->val)) { return true; } *ctx->res = cached_dep; ctx->found = true; ctx->from_cache = true; return true; } } if (check_dependency_override(wk, ctx)) { return true; } if (is_dependency_fallback_forced(wk, ctx)) { // This dependency is forced to fall-back, handle it later. return true; } struct dependency_lookup_handlers handlers = { 0 }; build_lookup_handler_list(wk, ctx, &handlers); uint32_t i; for (i = 0; i < handlers.len; ++i) { switch (handlers.e[i].type) { case dependency_lookup_handler_type_native: { TracyCZoneN(tctx_1, "dependency_lookup_handler_type_native", true); bool ok = handlers.e[i].handler.native(wk, ctx, &ctx->found); TracyCZoneEnd(tctx_1); if (!ok) { return false; } break; } case dependency_lookup_handler_type_capture: { stack_push(&wk->stack, dependency_is_resolving_from_capture, true); TracyCZoneN(tctx_1, "dependency_lookup_handler_type_capture", true); bool ok = vm_eval_capture(wk, handlers.e[i].handler.capture, 0, ctx->handler_kwargs, ctx->res); TracyCZoneEnd(tctx_1); stack_pop(&wk->stack, dependency_is_resolving_from_capture); if (ok) { struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); dep->name = ctx->name; switch (handlers.e[i].m) { case dependency_lookup_method_pkgconfig: { dep->public_type = dependency_public_type_pkgconfig; break; } case dependency_lookup_method_builtin: { case dependency_lookup_method_system: case dependency_lookup_method_extraframework: dep->public_type = dependency_public_type_system; break; } case dependency_lookup_method_auto: break; case dependency_lookup_method_sysconfig: break; case dependency_lookup_method_config_tool: break; case dependency_lookup_method_dub: break; case dependency_lookup_method_cmake: break; } if (dep->flags & dep_flag_found) { ctx->found = true; } } else { if (ctx->requirement == requirement_required) { return false; } } break; } } if (ctx->found) { struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); if (!check_dependency_version(wk, dep->version, ctx->versions->val)) { obj_lprintf(wk, log_note, "found dependency %o, but the version %s does not match the requested version %o\n", ctx->name, dep->version ? get_str(wk, dep->version)->s : "'unknown'", ctx->versions->val); ctx->found = false; *ctx->res = 0; continue; } break; } } if (!ctx->found && ctx->fallback) { if (!handle_dependency_fallback(wk, ctx, &ctx->found)) { return false; } } return true; } enum handle_special_dependency_result { handle_special_dependency_result_error, handle_special_dependency_result_continue, handle_special_dependency_result_stop, }; static enum handle_special_dependency_result handle_special_dependency(struct workspace *wk, struct dep_lookup_ctx *ctx) { if (strcmp(get_cstr(wk, ctx->name), "threads") == 0) { *ctx->res = make_obj(wk, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); dep->name = ctx->name; dep->flags |= dep_flag_found; dep->type = dependency_type_threads; dep->dep.compile_args = make_obj(wk, obj_array); obj_array_push(wk, dep->dep.compile_args, make_str(wk, "-pthread")); dep->dep.link_args = make_obj(wk, obj_array); obj_array_push(wk, dep->dep.link_args, make_str(wk, "-pthread")); ctx->found = true; } else if (strcmp(get_cstr(wk, ctx->name), "curses") == 0) { // TODO: this is stupid ctx->name = make_str(wk, "ncurses"); if (!get_dependency(wk, ctx)) { return handle_special_dependency_result_error; } } else if (strcmp(get_cstr(wk, ctx->name), "appleframeworks") == 0) { get_dependency_extraframework(wk, ctx, &ctx->found); } else if (strcmp(get_cstr(wk, ctx->name), "") == 0) { if (ctx->requirement == requirement_required) { vm_error_at(wk, ctx->err_node, "dependency '' cannot be required"); return handle_special_dependency_result_error; } *ctx->res = make_obj(wk, obj_dependency); ctx->found = true; } else { return handle_special_dependency_result_continue; } return handle_special_dependency_result_stop; } bool func_dependency(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_native, kw_version, kw_static, kw_modules, kw_optional_modules, // ignored kw_components, // ignored kw_main, // ignored kw_language, // ignored kw_fallback, kw_allow_fallback, kw_default_options, kw_not_found_message, kw_disabler, kw_method, kw_include_type, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_native] = { "native", obj_bool }, [kw_version] = { "version", TYPE_TAG_LISTIFY | obj_string }, [kw_static] = { "static", obj_bool }, [kw_modules] = { "modules", TYPE_TAG_LISTIFY | obj_string, .desc = "A list of sub-dependencies for this dependency. Only supported by certain dependencies.", }, [kw_main] = { "main", tc_bool, .desc = "Ignored" }, [kw_optional_modules] = { "optional_modules", TYPE_TAG_LISTIFY | obj_string, .desc = "Ignored" }, [kw_components] = { "components", TYPE_TAG_LISTIFY | obj_string, .desc = "Ignored" }, [kw_language] = { "language", tc_string, .desc = "Ignored" }, [kw_fallback] = { "fallback", TYPE_TAG_LISTIFY | obj_string }, [kw_allow_fallback] = { "allow_fallback", obj_bool }, [kw_default_options] = { "default_options", COMPLEX_TYPE_PRESET(tc_cx_options_dict_or_list) }, [kw_not_found_message] = { "not_found_message", obj_string }, [kw_disabler] = { "disabler", obj_bool }, [kw_method] = { "method", obj_string }, [kw_include_type] = { "include_type", obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (!get_obj_array(wk, an[0].val)->len) { vm_error_at(wk, an[0].node, "no dependency names specified"); return false; } if (wk->vm.in_analyzer) { if (wk->vm.lang_mode == language_external) { // TODO: check fallback keyword? obj name, _subproj; obj_array_for(wk, an[0].val, name) { if (get_str(wk, name)->len) { subproject(wk, name, requirement_auto, 0, 0, &_subproj); } } } *res = make_typeinfo(wk, tc_dependency); return true; } enum requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } if (requirement == requirement_skip) { *res = make_obj(wk, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *res); dep->name = obj_array_index(wk, an[0].val, 0); return true; } if (!current_project(wk)) { vm_error(wk, "This function cannot be called before project()"); return false; } enum include_type inc_type = include_type_preserve; if (akw[kw_include_type].set) { if (!coerce_include_type( wk, get_str(wk, akw[kw_include_type].val), akw[kw_include_type].node, &inc_type)) { return false; } } enum dependency_lookup_method lookup_method = dependency_lookup_method_auto; if (akw[kw_method].set) { if (!dependency_lookup_method_from_s(get_str(wk, akw[kw_method].val), &lookup_method)) { vm_error_at(wk, akw[kw_method].node, "invalid dependency method %o", akw[kw_method].val); return false; } if (!(lookup_method == dependency_lookup_method_auto || lookup_method == dependency_lookup_method_extraframework || lookup_method == dependency_lookup_method_pkgconfig || lookup_method == dependency_lookup_method_builtin || lookup_method == dependency_lookup_method_system)) { vm_warning_at(wk, akw[kw_method].node, "unsupported dependency method %o, falling back to 'auto'", akw[kw_method].val); lookup_method = dependency_lookup_method_auto; } } enum dep_lib_mode lib_mode = dep_lib_mode_default; if (akw[kw_static].set) { if (get_obj_bool(wk, akw[kw_static].val)) { lib_mode = dep_lib_mode_static; } else { lib_mode = dep_lib_mode_shared; } } else { obj prefer_static; get_option_value(wk, current_project(wk), "prefer_static", &prefer_static); if (get_obj_bool(wk, prefer_static)) { lib_mode = dep_lib_mode_static; } } /* A fallback is allowed if */ bool fallback_allowed = false; if (akw[kw_allow_fallback].set) { /* - allow_fallback: true */ fallback_allowed = get_obj_bool(wk, akw[kw_allow_fallback].val); } else { /* - allow_fallback is not specified and the requirement is required */ fallback_allowed = requirement == requirement_required /* - allow_fallback is not specified and the fallback keyword is * specified with at least one value (i.e. not an empty array) */ || (akw[kw_fallback].set && get_obj_array(wk, akw[kw_fallback].val)->len); } uint32_t fallback_err_node = 0; obj fallback = 0; if (fallback_allowed) { if (akw[kw_fallback].set) { fallback_err_node = akw[kw_fallback].node; fallback = akw[kw_fallback].val; } else if (akw[kw_allow_fallback].set) { fallback_err_node = akw[kw_allow_fallback].node; } else { fallback_err_node = an[0].node; } } enum machine_kind machine = coerce_machine_kind(wk, &akw[kw_native]); struct args_kw handler_kwargs[] = { { "static", .val = lib_mode == dep_lib_mode_static ? obj_bool_true : obj_bool_false }, { "native", .val = akw[kw_native].set ? akw[kw_native].val : obj_bool_false }, { "modules", .val = akw[kw_modules].val }, { "main", .val = akw[kw_main].val }, { "required", .val = akw[kw_required].val }, 0, }; struct dep_lookup_ctx ctx = { .res = res, .handler_kwargs = handler_kwargs, .names = an[0].val, .requirement = requirement, .machine = machine, .versions = &akw[kw_version], .err_node = an[0].node, .fallback_node = fallback_err_node, .fallback = fallback, .default_options = &akw[kw_default_options], .not_found_message = akw[kw_not_found_message].val, .lib_mode = lib_mode, .disabler = akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val), .modules = akw[kw_modules].val, .lookup_method = lookup_method, }; obj name; obj_array_for(wk, an[0].val, name) { struct dep_lookup_ctx sub_ctx = ctx; ctx.name = sub_ctx.name = name; switch (handle_special_dependency(wk, &sub_ctx)) { case handle_special_dependency_result_error: return false; case handle_special_dependency_result_stop: { fallback_allowed = false; break; } case handle_special_dependency_result_continue: if (!sub_ctx.found) { TracyCZoneN(tctx_1, "get_dependency", true); bool ok = get_dependency(wk, &sub_ctx); TracyCZoneEnd(tctx_1); if (!ok) { return false; } } break; } if (sub_ctx.found) { ctx.lib_mode = sub_ctx.lib_mode; ctx.from_cache = sub_ctx.from_cache; ctx.from_override = sub_ctx.from_override; ctx.found = true; break; } } if (!ctx.found && fallback_allowed) { obj_array_for(wk, an[0].val, name) { struct dep_lookup_ctx sub_ctx = ctx; ctx.name = sub_ctx.name = name; sub_ctx.fallback = get_dependency_fallback_name(wk, &sub_ctx); if (!handle_dependency_fallback(wk, &sub_ctx, &sub_ctx.found)) { return false; } if (sub_ctx.found) { ctx.lib_mode = sub_ctx.lib_mode; ctx.from_cache = sub_ctx.from_cache; ctx.from_override = sub_ctx.from_override; ctx.found = true; break; } } } #if 0 if (!ctx.found) { if (ctx.requirement == requirement_required) { LLOG_E("required "); } else { LLOG_W("%s", ""); } obj joined; obj_array_join(wk, false, an[0].val, make_str(wk, ", "), &joined); obj_lprintf(wk, "dependency %#o", joined); obj_lprintf(wk, " for the %s machine", machine_kind_to_s(ctx.machine)); if (ctx.not_found_message) { obj_lprintf(wk, ", %#o", ctx.not_found_message); } log_plain("\n"); #endif struct obj_dependency *dep = 0; if (get_obj_type(wk, *res) == obj_dependency) { dep = get_obj_dependency(wk, *res); } if (!str_eql(get_str(wk, ctx.name), &STR("")) && !ctx.from_cache && !ctx.from_override) { LLOG_I("dependency "); if (!ctx.found) { obj joined; obj_array_join(wk, false, an[0].val, make_str(wk, ", "), &joined); obj_lprintf(wk, log_info, "%#o", joined); } else if (dep->type == dependency_type_declared) { obj_lprintf(wk, log_info, "%#o", ctx.name); } else { log_plain(log_info, "%s", get_cstr(wk, dep->name)); } if (!ctx.found) { // TODO: print version searched for } else if (dep->version) { log_plain_version_string(log_info, get_cstr(wk, dep->version)); } if (ctx.lib_mode == dep_lib_mode_static) { log_plain(log_info, " static"); } if (machine != machine_kind_host) { log_plain(log_info, " (%s)", machine_kind_to_s(machine)); } log_plain(log_info, " found: %s", bool_to_yn(ctx.found)); log_plain(log_info, "\n"); } if (dep) { if (ctx.from_cache) { obj dup; dup = make_obj(wk, obj_dependency); struct obj_dependency *newdep = get_obj_dependency(wk, dup); *newdep = *dep; dep = newdep; *res = dup; } // set the include type and machine if the return value is not a disabler dep->include_type = inc_type; dep->machine = machine; if (dep->flags & dep_flag_found && !ctx.from_cache) { obj name; obj_array_for(wk, ctx.names, name) { if (ctx.lib_mode != dep_lib_mode_shared) { obj_dict_set(wk, current_project(wk)->dep_cache.static_deps[machine], name, *ctx.res); } if (ctx.lib_mode != dep_lib_mode_static) { obj_dict_set(wk, current_project(wk)->dep_cache.shared_deps[machine], name, *ctx.res); } } } } else { { enum machine_kind other_machine = ctx.machine == machine_kind_build ? machine_kind_host : machine_kind_build; enum dep_lib_mode _lib_mode; if (check_dependency_override_for_machine(wk, &ctx, other_machine, &_lib_mode)) { LOG_N("a dependency with the same name is available for the %s machine\n", machine_kind_to_s(other_machine)); } } if (ctx.requirement == requirement_required) { vm_error_at(wk, ctx.err_node, "required dependency not found"); return false; } else { if (ctx.disabler) { *ctx.res = obj_disabler; } else { *ctx.res = make_obj(wk, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *ctx.res); dep->name = ctx.name; dep->type = dependency_type_not_found; } } } return true; } struct process_dependency_sources_ctx { obj res; }; static enum iteration_result coerce_dependency_sources_iter(struct workspace *wk, void *_ctx, obj val) { struct process_dependency_sources_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_generated_list: obj_array_push(wk, ctx->res, val); break; default: { obj res; if (!coerce_files(wk, 0, val, &res)) { return ir_err; } obj_array_extend_nodup(wk, ctx->res, res); } } return ir_cont; } static bool deps_determine_machine_list(struct workspace *wk, obj list, enum machine_kind *m) { if (!list) { return false; } obj l; obj_array_for(wk, list, l) { switch (get_obj_type(wk, l)) { case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, l); *m = tgt->machine; return true; } case obj_dependency: { struct obj_dependency *dep = get_obj_dependency(wk, l); if (!(dep->flags & dep_flag_found)) { continue; } switch (dep->type) { case dependency_type_pkgconf: case dependency_type_external_library: case dependency_type_system: { *m = dep->machine; return true; } case dependency_type_declared: { if (deps_determine_machine_list(wk, dep->dep.raw.deps, m)) { return true; } else if (deps_determine_machine_list(wk, dep->dep.raw.link_with, m)) { return true; } else if (deps_determine_machine_list(wk, dep->dep.raw.link_whole, m)) { return true; } break; } default: break; } break; } default: break; } } return false; } static enum machine_kind deps_determine_machine(struct workspace *wk, obj link_with, obj link_whole, obj deps) { enum machine_kind m = machine_kind_host; if (deps_determine_machine_list(wk, link_with, &m)) { return m; } if (deps_determine_machine_list(wk, link_whole, &m)) { return m; } if (deps_determine_machine_list(wk, deps, &m)) { return m; } return machine_kind_either; } static bool deps_check_machine_matches_list(struct workspace *wk, obj tgt_name, enum machine_kind tgt_machine, obj list) { if (!list) { return true; } enum machine_kind machine; obj l; obj_array_for(wk, list, l) { switch (get_obj_type(wk, l)) { case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, l); machine = tgt->machine; break; } case obj_dependency: { struct obj_dependency *dep = get_obj_dependency(wk, l); if (!(dep->flags & dep_flag_found)) { continue; } switch (dep->type) { case dependency_type_pkgconf: case dependency_type_external_library: case dependency_type_system: break; case dependency_type_declared: { if (dep->machine == machine_kind_either) { break; } if (!deps_check_machine_matches(wk, tgt_name, tgt_machine, dep->dep.raw.link_with, dep->dep.raw.link_whole, dep->dep.raw.deps)) { return false; } continue; } default: continue; } machine = dep->machine; break; } default: continue; } if (!machine_matches(machine, tgt_machine)) { vm_error(wk, "target %o is built for the %s machine while its dependency %o is built for the %s machine", tgt_name, machine_kind_to_s(tgt_machine), l, machine_kind_to_s(machine)); return false; } } return true; } bool deps_check_machine_matches(struct workspace *wk, obj tgt_name, enum machine_kind tgt_machine, obj link_with, obj link_whole, obj deps) { return deps_check_machine_matches_list(wk, tgt_name, tgt_machine, link_with) && deps_check_machine_matches_list(wk, tgt_name, tgt_machine, link_whole) && deps_check_machine_matches_list(wk, tgt_name, tgt_machine, deps); } bool func_declare_dependency(struct workspace *wk, obj _, obj *res) { enum kwargs { kw_sources, kw_link_with, kw_link_whole, kw_link_args, kw_dependencies, kw_version, kw_include_directories, kw_variables, kw_compile_args, kw_objects, kw_extra_files, }; struct args_kw akw[] = { [kw_sources] = { "sources", TYPE_TAG_LISTIFY | tc_coercible_files | tc_generated_list }, [kw_link_with] = { "link_with", tc_link_with_kw }, [kw_link_whole] = { "link_whole", tc_link_with_kw }, [kw_link_args] = { "link_args", TYPE_TAG_LISTIFY | obj_string }, [kw_dependencies] = { "dependencies", TYPE_TAG_LISTIFY | tc_dependency }, [kw_version] = { "version", obj_string }, [kw_include_directories] = { "include_directories", TYPE_TAG_LISTIFY | tc_coercible_inc }, [kw_variables] = { "variables", tc_string | tc_array | tc_dict }, [kw_compile_args] = { "compile_args", TYPE_TAG_LISTIFY | obj_string }, [kw_objects] = { "objects", TYPE_TAG_LISTIFY | tc_file | tc_string }, [kw_extra_files] = { "extra_files", TYPE_TAG_LISTIFY | tc_coercible_files }, // ignored 0, }; if (!pop_args(wk, NULL, akw)) { return false; } if (akw[kw_include_directories].set) { obj inc_dirs; if (!coerce_include_dirs( wk, akw[kw_include_directories].node, akw[kw_include_directories].val, false, &inc_dirs)) { return false; } akw[kw_include_directories].val = inc_dirs; } struct obj_dependency *dep; *res = make_obj(wk, obj_dependency); dep = get_obj_dependency(wk, *res); dep->name = make_strf(wk, "%s declared:%s", get_cstr(wk, current_project(wk)->cfg.name), get_str(wk, vm_inst_location_str(wk, wk->vm.ip - 1))->s); dep->flags |= dep_flag_found; dep->type = dependency_type_declared; struct build_dep_raw raw = { .deps = akw[kw_dependencies].val, .link_with = akw[kw_link_with].val, .link_whole = akw[kw_link_whole].val, .link_args = akw[kw_link_args].val, .compile_args = akw[kw_compile_args].val, .include_directories = akw[kw_include_directories].val, .objects = akw[kw_objects].val, .sources = akw[kw_sources].val, }; if (!dependency_create(wk, &raw, &dep->dep, 0)) { return false; } if (akw[kw_variables].set && !coerce_key_value_dict(wk, akw[kw_variables].node, akw[kw_variables].val, &dep->variables)) { return false; } if (akw[kw_version].set) { dep->version = akw[kw_version].val; } else { dep->version = current_project(wk)->cfg.version; } dep->machine = deps_determine_machine(wk, akw[kw_link_with].val, akw[kw_link_whole].val, akw[kw_dependencies].val); if (!deps_check_machine_matches(wk, dep->name, dep->machine, akw[kw_link_with].val, akw[kw_link_whole].val, akw[kw_dependencies].val)) { return false; } return true; } /* */ static bool skip_if_present(struct workspace *wk, obj arr, obj val) { if (hash_get(&wk->vm.objects.obj_hash, &val)) { return true; } hash_set(&wk->vm.objects.obj_hash, &val, true); return false; } struct dep_process_includes_ctx { struct build_dep *dep; enum include_type include_type; }; static enum iteration_result dep_process_includes_iter(struct workspace *wk, void *_ctx, obj inc_id) { struct dep_process_includes_ctx *ctx = _ctx; struct obj_include_directory *inc = get_obj_include_directory(wk, inc_id); bool new_is_system = inc->is_system; switch (ctx->include_type) { case include_type_preserve: break; case include_type_system: new_is_system = true; break; case include_type_non_system: new_is_system = false; break; } if (inc->is_system != new_is_system) { inc_id = make_obj(wk, obj_include_directory); struct obj_include_directory *new_inc = get_obj_include_directory(wk, inc_id); *new_inc = *inc; new_inc->is_system = new_is_system; } obj_array_push(wk, ctx->dep->include_directories, inc_id); return ir_cont; } void dep_process_includes(struct workspace *wk, obj arr, enum include_type include_type, struct build_dep *dep) { dep->raw.include_directories = arr; obj_array_foreach_flat(wk, arr, &(struct dep_process_includes_ctx){ .include_type = include_type, .dep = dep, }, dep_process_includes_iter); } void build_dep_init(struct workspace *wk, struct build_dep *dep) { if (!dep->include_directories) { dep->include_directories = make_obj(wk, obj_array); } if (!dep->link_with) { dep->link_with = make_obj(wk, obj_array); } if (!dep->link_whole) { dep->link_whole = make_obj(wk, obj_array); } if (!dep->link_with_not_found) { dep->link_with_not_found = make_obj(wk, obj_array); } if (!dep->frameworks) { dep->frameworks = make_obj(wk, obj_array); } if (!dep->link_args) { dep->link_args = make_obj(wk, obj_array); } if (!dep->compile_args) { dep->compile_args = make_obj(wk, obj_array); } if (!dep->order_deps) { dep->order_deps = make_obj(wk, obj_array); } if (!dep->rpath) { dep->rpath = make_obj(wk, obj_array); } if (!dep->sources) { dep->sources = make_obj(wk, obj_array); } if (!dep->objects) { dep->objects = make_obj(wk, obj_array); } } void build_dep_merge(struct workspace *wk, struct build_dep *dest, const struct build_dep *src, enum build_dep_merge_flag flags) { bool merge_all = flags & build_dep_merge_flag_merge_all; build_dep_init(wk, dest); dest->link_language = coalesce_link_languages(src->link_language, dest->link_language); if (src->link_with) { obj_array_extend(wk, dest->link_with, src->link_with); } if (src->link_with_not_found) { obj_array_extend(wk, dest->link_with_not_found, src->link_with_not_found); } if (src->link_whole) { obj_array_extend(wk, dest->link_whole, src->link_whole); } if (merge_all && src->include_directories) { obj_array_extend(wk, dest->include_directories, src->include_directories); } if (src->link_args) { obj_array_extend(wk, dest->link_args, src->link_args); } if (src->frameworks) { obj_array_extend(wk, dest->frameworks, src->frameworks); } if (merge_all && src->compile_args) { obj_array_extend(wk, dest->compile_args, src->compile_args); } if (src->rpath) { obj_array_extend(wk, dest->rpath, src->rpath); } if (src->order_deps) { obj_array_extend(wk, dest->order_deps, src->order_deps); } if (merge_all && src->sources) { obj_array_extend(wk, dest->sources, src->sources); } if (merge_all && src->objects) { obj_array_extend(wk, dest->objects, src->objects); } } static bool obj_array_pair_in(struct workspace *wk, obj arr, obj a, obj b) { obj prev = 0, v; obj_array_for(wk, arr, v) { if (prev) { if (obj_equal(wk, a, prev) && obj_equal(wk, b, v)) { return true; } } prev = v; } return false; } static bool is_joined_arg(const struct str *s, const struct str *prefix) { return s->len > prefix->len && str_startswith(s, prefix); } static bool is_any_joined_arg(const struct str *s, const struct str *prefixes, uint32_t len) { for (uint32_t i = 0; i < len; ++i) { if (is_joined_arg(s, &prefixes[i])) { return true; } } return false; } static bool is_any_str(const struct str *s, const struct str *strs, uint32_t len) { for (uint32_t i = 0; i < len; ++i) { if (str_eql(s, &strs[i])) { return true; } } return false; } static void dedup_build_dep(struct workspace *wk, struct build_dep *dep) { TracyCZoneAutoS; obj_array_dedup_in_place(wk, &dep->link_with); obj_array_dedup_in_place(wk, &dep->link_with_not_found); obj_array_dedup_in_place(wk, &dep->link_whole); obj_array_dedup_in_place(wk, &dep->frameworks); obj_array_dedup_in_place(wk, &dep->raw.deps); obj_array_dedup_in_place(wk, &dep->raw.order_deps); obj_array_dedup_in_place(wk, &dep->raw.link_with); obj_array_dedup_in_place(wk, &dep->raw.link_whole); obj_array_dedup_in_place(wk, &dep->include_directories); obj_array_dedup_in_place(wk, &dep->rpath); obj_array_dedup_in_place(wk, &dep->order_deps); obj_array_dedup_in_place(wk, &dep->sources); obj_array_dedup_in_place(wk, &dep->objects); { obj arg, new_args = make_obj(wk, obj_array); obj prev = 0; const struct str *prev_s = 0; obj_array_for(wk, dep->link_args, arg) { bool add = true; const struct str *s = get_str(wk, arg); if (str_eql(s, &STR("-pthread"))) { if (obj_array_in(wk, new_args, arg)) { add = false; } } else if (prev_s && str_eql(prev_s, &STR("-framework"))) { if (obj_array_pair_in(wk, new_args, prev, arg)) { obj_array_pop(wk, new_args); add = false; } } if (add) { obj_array_push(wk, new_args, arg); } prev = arg; prev_s = s; } dep->link_args = new_args; } { obj arg, new_args = make_obj(wk, obj_array); obj prev = 0; const struct str *prev_s = 0; const struct str to_dedup[] = { STR("-W"), STR("-D"), STR("-I"), }; obj_array_for(wk, dep->compile_args, arg) { bool add = true; const struct str *s = get_str(wk, arg); if (str_eql(s, &STR("-pthread")) || is_any_joined_arg(s, to_dedup, ARRAY_LEN(to_dedup))) { if (obj_array_in(wk, new_args, arg)) { add = false; } } else if (prev_s && is_any_str(s, to_dedup, ARRAY_LEN(to_dedup))) { if (obj_array_pair_in(wk, new_args, prev, arg)) { obj_array_pop(wk, new_args); add = false; } } if (add) { obj_array_push(wk, new_args, arg); } prev = arg; prev_s = s; } dep->compile_args = new_args; } TracyCZoneAutoE; } struct dep_process_link_with_ctx { struct build_dep *dest; bool link_whole; enum build_dep_flag flags; }; static bool dep_process_link_with_lib(struct workspace *wk, struct dep_process_link_with_ctx *ctx, obj val) { if (skip_if_present(wk, ctx->dest->raw.link_with, val)) { return true; } enum obj_type t = get_obj_type(wk, val); /* obj_lprintf(wk, "link_with: %o\n", val); */ obj dest_link_with; if (ctx->link_whole) { dest_link_with = ctx->dest->link_whole; } else { dest_link_with = ctx->dest->link_with; } switch (t) { case obj_both_libs: { enum default_both_libraries def_both_libs = default_both_libraries_auto; if (ctx->flags & build_dep_flag_both_libs_shared) { def_both_libs = default_both_libraries_shared; } else if (ctx->flags & build_dep_flag_both_libs_static) { def_both_libs = default_both_libraries_static; } if (def_both_libs != default_both_libraries_auto) { struct obj_both_libs *b = get_obj_both_libs(wk, val); switch (def_both_libs) { case default_both_libraries_auto: val = b->dynamic_lib; break; case default_both_libraries_static: val = b->static_lib; break; case default_both_libraries_shared: val = b->dynamic_lib; break; } } else if (ctx->link_whole) { struct obj_both_libs *b = get_obj_both_libs(wk, val); val = b->static_lib; } else { val = decay_both_libs(wk, val); } } /* fallthrough */ case obj_build_target: { const struct obj_build_target *tgt = get_obj_build_target(wk, val); obj link_to = tgt->build_path; if (tgt->implib) { link_to = tgt->implib; } const char *path = get_cstr(wk, link_to); if (ctx->link_whole && tgt->type != tgt_static_library) { vm_error(wk, "link whole only accepts static libraries"); return false; } if (tgt->type != tgt_executable) { obj_array_push(wk, dest_link_with, make_str(wk, path)); } // calculate rpath for this target // we always want an absolute path here, regardles of // ctx->relativize if (tgt->type != tgt_static_library) { TSTR(abs); TSTR(dir); const char *p; path_dirname(wk, &dir, path); if (path_is_absolute(dir.buf)) { p = dir.buf; } else { path_join(wk, &abs, wk->build_root, dir.buf); p = abs.buf; } obj s = make_str(wk, p); if (!obj_array_in(wk, ctx->dest->rpath, s)) { obj_array_push(wk, ctx->dest->rpath, s); } } build_dep_merge(wk, ctx->dest, &tgt->dep, 0); break; } case obj_custom_target: { obj v; obj_array_for(wk, get_obj_custom_target(wk, val)->output, v) { if (!dep_process_link_with_lib(wk, ctx, v)) { return false; } } break; } case obj_file: { obj_array_push(wk, dest_link_with, *get_obj_file(wk, val)); if (file_is_dynamic_lib(wk, val)) { TSTR(dir); path_dirname(wk, &dir, get_file_path(wk, val)); obj_array_push(wk, ctx->dest->rpath, tstr_into_str(wk, &dir)); } break; } case obj_string: obj_array_push(wk, dest_link_with, val); break; default: { vm_error(wk, "invalid type for link_with: '%s'", obj_type_to_s(t)); return false; } } return true; } static bool dep_process_link_with(struct workspace *wk, obj arr, struct build_dep *dest, enum build_dep_flag flags, bool link_whole) { if (link_whole) { dest->raw.link_whole = arr; } else { dest->raw.link_with = arr; } build_dep_init(wk, dest); hash_clear(&wk->vm.objects.obj_hash); struct dep_process_link_with_ctx ctx = { .dest = dest, .flags = flags, .link_whole = link_whole, }; obj v; obj_array_flat_for_(wk, arr, v, iter) { if (!dep_process_link_with_lib(wk, &ctx, v)) { obj_array_flat_iter_end(wk, &iter); return false; } } return true; } void dep_process_deps(struct workspace *wk, obj deps, struct build_dep *dest) { TracyCZoneAutoS; build_dep_init(wk, dest); dest->raw.deps = deps; hash_clear(&wk->vm.objects.obj_hash); obj val; obj_array_for(wk, deps, val) { if (skip_if_present(wk, dest->raw.deps, val)) { continue; } const struct obj_dependency *dep = get_obj_dependency(wk, val); if (!(dep->flags & dep_flag_found)) { continue; } build_dep_merge(wk, dest, &dep->dep, build_dep_merge_flag_merge_all); } dedup_build_dep(wk, dest); TracyCZoneAutoE; } static const enum build_dep_flag build_dep_flag_partial_mask = build_dep_flag_partial | build_dep_flag_part_compile_args | build_dep_flag_part_includes | build_dep_flag_part_link_args | build_dep_flag_part_links | build_dep_flag_part_sources; #define IS_INCLUDED(__part) (!(flags & build_dep_flag_partial) || (flags & build_dep_flag_part_##__part)) obj dependency_dup(struct workspace *wk, obj dep, enum build_dep_flag flags) { TracyCZoneAutoS; const struct obj_dependency *src = get_obj_dependency(wk, dep); obj res = make_obj(wk, obj_dependency); struct obj_dependency *d = get_obj_dependency(wk, res); *d = *src; d->dep = (struct build_dep){ 0 }; // Copy everything over that can't be recreated from the raw field if (IS_INCLUDED(links)) { d->dep.link_language = src->dep.link_language; if (src->dep.frameworks) { obj_array_dup(wk, src->dep.frameworks, &d->dep.frameworks); } } if (!dependency_create(wk, &src->dep.raw, &d->dep, flags)) { return 0; } d->machine = src->machine; if (flags & build_dep_flag_partial) { const enum build_dep_flag partial_flags = flags & build_dep_flag_partial_mask; const enum build_dep_flag either_machine_parts = build_dep_flag_partial | build_dep_flag_part_includes; if ((partial_flags & ~either_machine_parts) == 0) { d->machine = machine_kind_either; } } TracyCZoneAutoE; return res; } bool dependency_create(struct workspace *wk, const struct build_dep_raw *raw, struct build_dep *dep, enum build_dep_flag flags) { TracyCZoneAutoS; const enum build_dep_flag both_libs_mask = build_dep_flag_both_libs_static | build_dep_flag_both_libs_shared; if (raw->flags & both_libs_mask) { flags &= ~both_libs_mask; flags |= (raw->flags & both_libs_mask); } flags |= (raw->flags & build_dep_flag_partial_mask); dep->raw = *raw; dep->raw.flags = flags; build_dep_init(wk, dep); bool partial = flags & build_dep_flag_partial; if (raw->objects && !partial) { obj_array_extend_nodup(wk, dep->objects, raw->objects); } if (raw->link_args && IS_INCLUDED(link_args)) { obj_array_extend_nodup(wk, dep->link_args, raw->link_args); } if (raw->compile_args && IS_INCLUDED(compile_args)) { obj_array_extend_nodup(wk, dep->compile_args, raw->compile_args); } if (raw->sources && IS_INCLUDED(sources)) { struct process_dependency_sources_ctx ctx = { .res = dep->sources, }; if (!obj_array_foreach_flat(wk, raw->sources, &ctx, coerce_dependency_sources_iter)) { return false; } } if (raw->link_with && IS_INCLUDED(links)) { bool link_whole = flags & build_dep_flag_as_link_whole; if (!dep_process_link_with(wk, raw->link_with, dep, flags, link_whole)) { return false; } } if (raw->link_whole && IS_INCLUDED(links)) { if (!dep_process_link_with(wk, raw->link_whole, dep, 0, true)) { return false; } } if (raw->link_with_not_found && IS_INCLUDED(links)) { obj_array_extend_nodup(wk, dep->link_with_not_found, raw->link_with_not_found); } if (raw->rpath && IS_INCLUDED(links)) { obj_array_extend_nodup(wk, dep->rpath, raw->rpath); } if (raw->include_directories && IS_INCLUDED(includes)) { enum include_type inc_type = include_type_preserve; if (flags & build_dep_flag_include_system) { inc_type = include_type_system; } else if (flags & build_dep_flag_include_non_system) { inc_type = include_type_non_system; } dep_process_includes(wk, raw->include_directories, inc_type, dep); } if (raw->deps) { obj raw_deps = raw->deps; if (flags & build_dep_flag_recursive) { obj recreated_deps = make_obj(wk, obj_array); obj val, dup; obj_array_for(wk, raw->deps, val) { if (!(dup = dependency_dup(wk, val, flags))) { return false; } obj_array_push(wk, recreated_deps, dup); } raw_deps = recreated_deps; } dep_process_deps(wk, raw_deps, dep); } TracyCZoneAutoE; return true; } #undef IS_INCLUDED muon-v0.5.0/src/functions/kernel/options.c0000644000175000017500000001474615041716357017603 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "error.h" #include "functions/kernel/options.h" #include "lang/analyze.h" #include "lang/typecheck.h" #include "options.h" #include "platform/assert.h" static bool build_option_type_from_s(struct workspace *wk, uint32_t node, uint32_t name, enum build_option_type *res) { static const char *build_option_type_name[] = { [op_string] = "string", [op_boolean] = "boolean", [op_combo] = "combo", [op_integer] = "integer", [op_array] = "array", [op_feature] = "feature", [op_shell_array] = "shell_array", }; enum build_option_type type; for (type = 0; type < build_option_type_count; ++type) { if (type == op_shell_array && !initializing_builtin_options) { continue; } if (strcmp(build_option_type_name[type], get_cstr(wk, name)) == 0) { *res = type; return true; } } vm_error_at(wk, node, "invalid option type '%s'", get_cstr(wk, name)); return false; } static bool validate_option_name(struct workspace *wk, uint32_t err_node, obj name) { uint32_t i; const struct str *s = get_str(wk, name); for (i = 0; i < s->len; ++i) { if (('a' <= s->s[i] && s->s[i] <= 'z') || ('A' <= s->s[i] && s->s[i] <= 'Z') || ('0' <= s->s[i] && s->s[i] <= '9') || (s->s[i] == '-') || (s->s[i] == '_')) { continue; } vm_error_at(wk, err_node, "option name may not contain '%c'", s->s[i]); return false; } return true; } bool func_option(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_type, kw_value, kw_description, kw_choices, kw_max, kw_min, kw_yield, kw_deprecated, kwargs_count, kw_kind = kwargs_count, }; struct args_kw akw[] = { [kw_type] = { "type", obj_string }, [kw_value] = { "value", tc_any }, [kw_description] = { "description", obj_string }, [kw_choices] = { "choices", obj_array }, [kw_max] = { "max", obj_number }, [kw_min] = { "min", obj_number }, [kw_yield] = { "yield", obj_bool }, [kw_deprecated] = { "deprecated", COMPLEX_TYPE_PRESET(tc_cx_options_deprecated_kw) }, [kw_kind] = { 0 }, 0 }; if (initializing_builtin_options) { akw[kw_kind] = (struct args_kw){ "kind", tc_string }; } if (!pop_args(wk, an, akw)) { return false; } if (!akw[kw_type].set) { vm_error(wk, "missing required keyword 'type'"); return false; } enum build_option_type type; if (!build_option_type_from_s(wk, akw[kw_type].node, akw[kw_type].val, &type)) { return false; } enum keyword_req { kw_opt, // optional kw_req, // required kw_inv, // invalid }; static const enum keyword_req keyword_validity[build_option_type_count][kwargs_count] = { /* kw_type, kw_value, kw_description, kw_choices, kw_max, kw_min, kw_yield, kw_deprecated */ [op_string] = { kw_req, kw_opt, kw_opt, kw_inv, kw_inv, kw_inv, kw_opt, kw_opt, }, [op_boolean] = { kw_req, kw_opt, kw_opt, kw_inv, kw_inv, kw_inv, kw_opt, kw_opt, }, [op_combo] = { kw_req, kw_opt, kw_opt, kw_req, kw_inv, kw_inv, kw_opt, kw_opt, }, [op_integer] = { kw_req, kw_req, kw_opt, kw_inv, kw_opt, kw_opt, kw_opt, kw_opt, }, [op_array] = { kw_req, kw_opt, kw_opt, kw_opt, kw_inv, kw_inv, kw_opt, kw_opt, }, [op_feature] = { kw_req, kw_opt, kw_opt, kw_inv, kw_inv, kw_inv, kw_opt, kw_opt, }, }; uint32_t i; for (i = 0; i < kwargs_count; ++i) { switch (keyword_validity[type][i]) { case kw_opt: break; case kw_inv: if (akw[i].set) { vm_error_at(wk, akw[i].node, "invalid keyword for option type"); return false; } break; case kw_req: if (!akw[i].set) { vm_error(wk, "missing keyword '%s' for option type", akw[i].key); return false; } break; default: assert(false && "unreachable"); } } obj val = 0; if (akw[kw_value].set) { val = akw[kw_value].val; } else { switch (type) { case op_string: val = make_str(wk, ""); break; case op_boolean: val = make_obj_bool(wk, true); break; case op_combo: if (!get_obj_array(wk, akw[kw_choices].val)->len) { vm_error_at(wk, akw[kw_choices].node, "combo option with no choices"); return false; } val = obj_array_index(wk, akw[kw_choices].val, 0); break; case op_array: case op_shell_array: if (akw[kw_choices].set) { val = akw[kw_choices].val; } else { val = make_obj(wk, obj_array); } break; case op_feature: val = make_obj(wk, obj_feature_opt); set_obj_feature_opt(wk, val, feature_opt_auto); break; default: UNREACHABLE_RETURN; } } obj opt; opt = make_obj(wk, obj_option); struct obj_option *o = get_obj_option(wk, opt); o->name = an[0].val; o->type = type; o->min = akw[kw_min].val; o->max = akw[kw_max].val; o->choices = akw[kw_choices].val; o->yield = akw[kw_yield].set && get_obj_bool(wk, akw[kw_yield].val); o->description = akw[kw_description].val; o->deprecated = akw[kw_deprecated].val; o->ip = wk->vm.ip - 1; if (akw[kw_kind].set) { if (str_eql(&STR("default"), get_str(wk, akw[kw_kind].val))) { o->kind = build_option_kind_default; } else if (str_eql(&STR("prefixed_dir"), get_str(wk, akw[kw_kind].val))) { o->kind = build_option_kind_prefixed_dir; } else { vm_error_at(wk, akw[kw_kind].node, "invalid kind: %o", akw[kw_kind].val); return false; } } obj opts; if (wk->projects.len) { if (!validate_option_name(wk, an[0].node, an[0].val)) { return false; } opts = current_project(wk)->opts; } else { opts = wk->global_opts; } if (!create_option(wk, opts, opt, val)) { return false; } return true; } bool func_get_option(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } obj opt; if (!get_option(wk, current_project(wk), get_str(wk, an[0].val), &opt)) { vm_error_at(wk, an[0].node, "undefined option"); return false; } struct obj_option *o = get_obj_option(wk, opt); if (wk->vm.in_analyzer) { type_tag t = 0; switch (o->type) { case op_string: t = tc_string; break; case op_boolean: t = tc_bool; break; case op_combo: t = COMPLEX_TYPE(o->choices, complex_type_enum); break; case op_integer: t = tc_number; break; case op_shell_array: t = tc_array; break; case op_array: t = tc_array; break; case op_feature: t = tc_feature_opt; break; default: UNREACHABLE; } *res = make_typeinfo(wk, t); } else { *res = o->val; } return true; } muon-v0.5.0/src/functions/kernel/configure_file.c0000644000175000017500000005627215041716357021070 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "buf_size.h" #include "coerce.h" #include "error.h" #include "functions/environment.h" #include "functions/kernel/configure_file.h" #include "functions/kernel/custom_target.h" #include "install.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/run_cmd.h" enum configure_file_output_format { configure_file_output_format_c, configure_file_output_format_nasm, configure_file_output_format_json, }; static bool file_exists_with_content(struct workspace *wk, const char *dest, const char *out_buf, uint32_t out_len) { if (fs_file_exists(dest)) { struct source src = { 0 }; if (fs_read_entire_file(dest, &src)) { bool eql = out_len == src.len && memcmp(out_buf, src.src, src.len) == 0; fs_source_destroy(&src); return eql; } } return false; } static uint32_t configure_var_len(const char *p) { uint32_t i = 0; // Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define while (p[i] && (('a' <= p[i] && p[i] <= 'z') || ('A' <= p[i] && p[i] <= 'Z') || ('0' <= p[i] && p[i] <= '9') || '_' == p[i] || '-' == p[i])) { ++i; } return i; } enum configure_file_syntax { configure_file_syntax_mesondefine = 0 << 0, configure_file_syntax_cmakedefine = 1 << 0, configure_file_syntax_mesonvar = 1 << 1, configure_file_syntax_cmakevar = 1 << 2, }; struct configure_file_context { const char *name; obj dict; const struct tstr *in; enum configure_file_syntax syntax; }; struct configure_file_var_patterns { struct { struct str start, end; enum configure_file_syntax type; } pats[2]; uint32_t len; }; static void configure_file_var_pattern_push(struct configure_file_var_patterns *var_patterns, enum configure_file_syntax syntax, enum configure_file_syntax enable_flag, const struct str *start, const struct str *end) { if (!(syntax & enable_flag)) { return; } assert(var_patterns->len < ARRAY_LEN(var_patterns->pats)); var_patterns->pats[var_patterns->len].start = *start; var_patterns->pats[var_patterns->len].end = *end; var_patterns->pats[var_patterns->len].type = enable_flag; ++var_patterns->len; } MUON_ATTR_FORMAT(printf, 4, 5) static void configure_file_log(struct configure_file_context *ctx, struct source_location loc, enum log_level lvl, const char *fmt, ...) { va_list args; va_start(args, fmt); struct source src = { .src = ctx->in->buf, .len = ctx->in->len, .label = ctx->name, }; error_messagev(&src, loc, lvl, fmt, args); va_end(args); } static bool configure_file_var_patterns_match(struct configure_file_var_patterns *var_patterns, const struct tstr *in, uint32_t off, uint32_t *match_idx) { const struct str s = { in->buf + off, in->len - off }; for (uint32_t i = 0; i < var_patterns->len; ++i) { if (str_startswith(&s, &var_patterns->pats[i].start)) { *match_idx = i; return true; } } return false; } static bool substitute_config_variables(struct workspace *wk, struct configure_file_context *ctx, struct tstr *out) { const struct tstr *in = ctx->in; struct configure_file_var_patterns var_patterns = { 0 }; configure_file_var_pattern_push( &var_patterns, ctx->syntax, configure_file_syntax_cmakevar, &STR("${"), &STR("}")); configure_file_var_pattern_push( &var_patterns, ctx->syntax, configure_file_syntax_mesonvar, &STR("@"), &STR("@")); struct source_location location = { 0, 1 }, id_location; uint32_t i, id_start; uint32_t match_idx = 0; obj elem; for (i = 0; i < in->len; ++i) { location.off = i; struct str src_str = { in->buf + i, in->len - i }; if (in->buf[i] == '\\') { /* cope with weird config file escaping rules :( * * - Backslashes not directly preceeding a format character are not * modified. * - The number of backslashes preceding varstart in the output is * equal to the number of backslashes in the input divided by * two, rounding down. * - If mode is cmake and the number of backslashes is even, don't * escape the variable, otherwise always escape the variable. */ uint32_t j, output_backslashes; bool output_format_char = false; for (j = 1; in->buf[i + j] == '\\'; ++j) { } if (configure_file_var_patterns_match(&var_patterns, in, i + j, &match_idx)) { output_backslashes = j / 2; if (var_patterns.pats[match_idx].type == configure_file_syntax_mesonvar) { output_format_char = true; i += j; } else { if ((j & 1) != 0) { output_format_char = true; i += j; } else { i += j - 1; } } } else { i += j - 1; output_backslashes = j; } for (j = 0; j < output_backslashes; ++j) { tstr_pushn(wk, out, "\\", 1); } if (output_format_char) { tstr_pushs(wk, out, var_patterns.pats[match_idx].start.s); i += var_patterns.pats[match_idx].start.len - 1; } } else if (configure_file_var_patterns_match(&var_patterns, in, i, &match_idx)) { i += var_patterns.pats[match_idx].start.len; id_start = i; id_location = location; i += configure_var_len(&in->buf[id_start]); src_str = (struct str){ in->buf + i, in->len - i }; if (!str_startswith(&src_str, &var_patterns.pats[match_idx].end)) { i = id_start - 1; tstr_pushs(wk, out, var_patterns.pats[match_idx].start.s); continue; } if (i <= id_start) { // This means we got a key of length zero tstr_pushs(wk, out, "@@"); continue; } else if (!obj_dict_index_strn(wk, ctx->dict, &in->buf[id_start], i - id_start, &elem)) { configure_file_log(ctx, id_location, log_error, "key %.*s not found in configuration data", i - id_start, &in->buf[id_start]); return false; } obj sub; if (!coerce_string(wk, 0, elem, &sub)) { configure_file_log(ctx, id_location, log_error, "unable to substitute value"); return false; } const struct str *ss = get_str(wk, sub); tstr_pushn(wk, out, ss->s, ss->len); } else { tstr_pushn(wk, out, &in->buf[i], 1); } } return true; } static bool substitute_config_defines(struct workspace *wk, struct configure_file_context *ctx, struct tstr *out) { struct str define = { 0 }; if (ctx->syntax & configure_file_syntax_cmakedefine) { define = STR("#cmakedefine"); } else { define = STR("#mesondefine"); } struct source_location location = { 0, 1 }; obj elem; const char *e, *s = ctx->in->buf, *eof = s + ctx->in->len; while (s < eof) { if (!(e = strchr(s, '\n'))) { e = eof; } location.off = s - ctx->in->buf; struct str line = { s, e - s }; if (str_startswith(&line, &define)) { obj arr = str_split(wk, &line, 0); if (ctx->syntax & configure_file_syntax_cmakedefine) { bool cmake_bool = str_startswith(&line, &STR("#cmakedefine01")); if (get_obj_array(wk, arr)->len < 2) { configure_file_log( ctx, location, log_error, "#cmakedefine does not contain >= 2 tokens"); return false; } obj varname = obj_array_index(wk, arr, 1); if (!obj_dict_index(wk, ctx->dict, varname, &elem)) { if (cmake_bool) { tstr_pushf(wk, out, "#define %s 0\n", get_str(wk, varname)->s); } else { tstr_pushf(wk, out, "/* #undef %s */\n", get_str(wk, varname)->s); } } else { if (!cmake_bool && !coerce_truthiness(wk, elem)) { tstr_pushf(wk, out, "/* #undef %s */\n", get_str(wk, varname)->s); } else { tstr_pushf(wk, out, "#define %s", get_str(wk, varname)->s); obj v; obj_array_for_(wk, arr, v, iter) { if (iter.i < 2) { continue; } if (obj_dict_index(wk, ctx->dict, v, &elem)) { obj str; if (!coerce_string(wk, 0, v, &str)) { return false; } tstr_pushf(wk, out, " %s", get_cstr(wk, str)); } else { tstr_pushf(wk, out, " %s", get_str(wk, v)->s); } } tstr_push(wk, out, '\n'); } } } else { if (get_obj_array(wk, arr)->len != 2) { configure_file_log(ctx, location, log_error, "#mesondefine does not contain exactly two tokens"); return false; } obj varname = obj_array_index(wk, arr, 1); if (!obj_dict_index(wk, ctx->dict, varname, &elem)) { tstr_pushf(wk, out, "/* #undef %s */\n", get_str(wk, varname)->s); } else { if (!typecheck(wk, 0, elem, tc_string | tc_bool | tc_number)) { return false; } switch (get_obj_type(wk, elem)) { case obj_string: { tstr_pushf(wk, out, "#define %s %s\n", get_str(wk, varname)->s, get_cstr(wk, elem)); break; } case obj_bool: { if (get_obj_bool(wk, elem)) { tstr_pushf(wk, out, "#define %s\n", get_str(wk, varname)->s); } else { tstr_pushf(wk, out, "#undef %s\n", get_str(wk, varname)->s); } break; } case obj_number: { tstr_pushf(wk, out, "#define %s %" PRId64 "\n", get_str(wk, varname)->s, get_obj_number(wk, elem)); break; } default: UNREACHABLE; } } } } else { tstr_pushn(wk, out, line.s, line.len); tstr_push(wk, out, '\n'); } s = e + 1; } return true; } static bool substitute_config(struct workspace *wk, obj dict, const char *input_path, const char *output_path, enum configure_file_syntax syntax) { bool ret = true; struct source src = { 0 }; if (!fs_read_entire_file(input_path, &src)) { ret = false; goto cleanup; } const struct tstr src_tstr = { (char *)src.src, src.len }; struct configure_file_context ctx = { .name = input_path, .dict = dict, .in = &src_tstr, .syntax = syntax, }; TSTR(out1); if (!substitute_config_defines(wk, &ctx, &out1)) { ret = false; goto cleanup; } ctx.in = &out1; TSTR(out2); if (!substitute_config_variables(wk, &ctx, &out2)) { ret = false; goto cleanup; } if (file_exists_with_content(wk, output_path, out2.buf, out2.len)) { goto cleanup; } if (!fs_write(output_path, (uint8_t *)out2.buf, out2.len)) { ret = false; goto cleanup; } if (!fs_copy_metadata(input_path, output_path)) { ret = false; goto cleanup; } cleanup: fs_source_destroy(&src); return ret; } static bool generate_config(struct workspace *wk, enum configure_file_output_format format, obj macro_name, obj dict, uint32_t node, obj out_path) { TSTR_manual(out_buf); if (macro_name) { tstr_pushf( wk, &out_buf, "#ifndef %s\n#define %s\n", get_cstr(wk, macro_name), get_cstr(wk, macro_name)); } else if (format == configure_file_output_format_json) { tstr_push(wk, &out_buf, '{'); } bool ret = false, first = true; obj key, val; obj_dict_for(wk, dict, key, val) { enum obj_type t = get_obj_type(wk, val); char define_prefix = (char[]){ [configure_file_output_format_c] = '#', [configure_file_output_format_nasm] = '%', [configure_file_output_format_json] = 0, }[format]; if (format == configure_file_output_format_json) { if (!first) { tstr_push(wk, &out_buf, ','); } first = false; tstr_push_json_escaped_quoted(wk, &out_buf, get_str(wk, key)); tstr_push(wk, &out_buf, ':'); } switch (t) { case obj_string: /* conf_data.set('FOO', '"string"') => #define FOO "string" */ /* conf_data.set('FOO', 'a_token') => #define FOO a_token */ switch (format) { case configure_file_output_format_c: case configure_file_output_format_nasm: tstr_pushf(wk, &out_buf, "%cdefine %s %s\n", define_prefix, get_cstr(wk, key), get_cstr(wk, val)); break; case configure_file_output_format_json: { tstr_push_json_escaped_quoted(wk, &out_buf, get_str(wk, val)); break; } } break; case obj_bool: /* conf_data.set('FOO', true) => #define FOO */ /* conf_data.set('FOO', false) => #undef FOO */ switch (format) { case configure_file_output_format_c: case configure_file_output_format_nasm: if (get_obj_bool(wk, val)) { tstr_pushf(wk, &out_buf, "%cdefine %s\n", define_prefix, get_cstr(wk, key)); } else { tstr_pushf(wk, &out_buf, "%cundef %s\n", define_prefix, get_cstr(wk, key)); } break; case configure_file_output_format_json: tstr_pushs(wk, &out_buf, get_obj_bool(wk, val) ? "true" : "false"); break; } break; case obj_number: /* conf_data.set('FOO', 1) => #define FOO 1 */ /* conf_data.set('FOO', 0) => #define FOO 0 */ switch (format) { case configure_file_output_format_c: case configure_file_output_format_nasm: tstr_pushf(wk, &out_buf, "%cdefine %s %" PRId64 "\n", define_prefix, get_cstr(wk, key), get_obj_number(wk, val)); break; case configure_file_output_format_json: { char buf[32] = { 0 }; snprintf(buf, sizeof(buf), "%" PRId64, get_obj_number(wk, val)); tstr_pushs(wk, &out_buf, buf); break; } } break; default: vm_error_at(wk, node, "invalid type for config data value: '%s'", obj_type_to_s(t)); goto ret; } } if (macro_name) { tstr_pushf(wk, &out_buf, "#endif\n"); } else if (format == configure_file_output_format_json) { tstr_pushs(wk, &out_buf, "}\n"); } if (!file_exists_with_content(wk, get_cstr(wk, out_path), out_buf.buf, out_buf.len)) { if (!fs_write(get_cstr(wk, out_path), (uint8_t *)out_buf.buf, out_buf.len)) { goto ret; } } ret = true; ret: tstr_destroy(&out_buf); return ret; } static bool configure_file_with_command(struct workspace *wk, uint32_t node, obj command, obj input, obj out_path, obj depfile, bool capture) { obj args, output_arr; { obj f; f = make_obj(wk, obj_file); *get_obj_file(wk, f) = out_path; output_arr = make_obj(wk, obj_array); obj_array_push(wk, output_arr, f); } { // XXX: depfile for configure_file is not supported, this is // only here to make the types align obj f; f = make_obj(wk, obj_file); *get_obj_file(wk, f) = depfile; depfile = f; } struct process_custom_target_commandline_opts opts = { .err_node = node, .input = input, .output = output_arr, .depfile = depfile, }; opts.depends = make_obj(wk, obj_array); if (!process_custom_target_commandline(wk, &opts, command, &args)) { return false; } bool ret = false; struct run_cmd_ctx cmd_ctx = { 0 }; const char *argstr, *envstr; uint32_t argc, envc; if (!path_chdir(workspace_build_dir(wk))) { return false; } obj env; env = make_obj(wk, obj_dict); set_default_environment_vars(wk, env, true); join_args_argstr(wk, &argstr, &argc, args); env_to_envstr(wk, &envstr, &envc, env); if (!run_cmd(&cmd_ctx, argstr, argc, envstr, envc)) { vm_error_at(wk, node, "error running command: %s", cmd_ctx.err_msg); goto ret; } if (cmd_ctx.status != 0) { vm_error_at(wk, node, "error running command: %s", cmd_ctx.err.buf); goto ret; } if (capture) { if (file_exists_with_content(wk, get_cstr(wk, out_path), cmd_ctx.out.buf, cmd_ctx.out.len)) { ret = true; } else { ret = fs_write(get_cstr(wk, out_path), (uint8_t *)cmd_ctx.out.buf, cmd_ctx.out.len); } } else { ret = true; } ret: if (!path_chdir(wk->source_root)) { return false; } run_cmd_ctx_destroy(&cmd_ctx); return ret; } static bool array_to_elem_or_err(struct workspace *wk, uint32_t node, uint32_t arr, uint32_t *res) { if (!typecheck(wk, node, arr, obj_array)) { return false; } if (get_obj_array(wk, arr)->len != 1) { vm_error_at(wk, node, "expected an array of length 1"); return false; } *res = obj_array_index(wk, arr, 0); return true; } static bool is_substr(const char *s, const char *sub, uint32_t *len) { *len = strlen(sub); return strncmp(s, sub, *len) == 0; } static bool perform_output_string_substitutions(struct workspace *wk, uint32_t node, uint32_t src, uint32_t input_arr, uint32_t *res) { const char *s = get_cstr(wk, src); uint32_t len; obj str = make_str(wk, ""), e = 0; for (; *s; ++s) { if (is_substr(s, "@BASENAME@", &len)) { if (!array_to_elem_or_err(wk, node, input_arr, &e)) { return false; } assert(e); TSTR(buf); char *c; path_basename(wk, &buf, get_file_path(wk, e)); if ((c = strrchr(buf.buf, '.'))) { *c = 0; buf.len = strlen(buf.buf); } str_app(wk, &str, buf.buf); s += len - 1; } else if (is_substr(s, "@PLAINNAME@", &len)) { if (!array_to_elem_or_err(wk, node, input_arr, &e)) { return false; } TSTR(buf); path_basename(wk, &buf, get_file_path(wk, e)); str_app(wk, &str, buf.buf); s += len - 1; } else { str_appn(wk, &str, s, 1); } } *res = str; return true; } static bool exclusive_or(bool *vals, uint32_t len) { uint32_t i; bool found = false; for (i = 0; i < len; ++i) { if (vals[i]) { if (found) { return false; } else { found = true; } } } return found; } bool func_configure_file(struct workspace *wk, obj _, obj *res) { obj input_arr = 0, output_str; enum kwargs { kw_configuration, kw_input, kw_output, kw_command, kw_capture, kw_install, kw_install_dir, kw_install_mode, kw_install_tag, kw_copy, kw_format, kw_output_format, kw_encoding, // TODO: ignored kw_depfile, // TODO: ignored kw_macro_name, }; struct args_kw akw[] = { [kw_configuration] = { "configuration", tc_configuration_data | tc_dict }, [kw_input] = { "input", TYPE_TAG_LISTIFY | tc_coercible_files, }, [kw_output] = { "output", obj_string, .required = true }, [kw_command] = { "command", obj_array }, [kw_capture] = { "capture", obj_bool }, [kw_install] = { "install", obj_bool }, [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_copy] = { "copy", obj_bool }, [kw_format] = { "format", obj_string }, [kw_output_format] = { "output_format", obj_string }, [kw_encoding] = { "encoding", obj_string }, [kw_depfile] = { "depfile", obj_string }, [kw_macro_name] = { "macro_name", obj_string }, 0, }; if (!pop_args(wk, NULL, akw)) { return false; } enum configure_file_output_format output_format = configure_file_output_format_c; if (akw[kw_output_format].set) { const struct str *output_format_str = get_str(wk, akw[kw_output_format].val); if (str_eql(output_format_str, &STR("c"))) { output_format = configure_file_output_format_c; } else if (str_eql(output_format_str, &STR("nasm"))) { output_format = configure_file_output_format_nasm; } else if (str_eql(output_format_str, &STR("json"))) { output_format = configure_file_output_format_json; } else { vm_error_at( wk, akw[kw_output_format].node, "invalid output format %o", akw[kw_output_format].val); return false; } } if (akw[kw_input].set) { if (!coerce_files(wk, akw[kw_input].node, akw[kw_input].val, &input_arr)) { return false; } } { /* setup out file */ obj subd; if (!perform_output_string_substitutions( wk, akw[kw_output].node, akw[kw_output].val, input_arr, &subd)) { return false; } const char *out = get_cstr(wk, subd); TSTR(out_path); if (!path_is_basename(out)) { if (wk->vm.lang_mode == language_external) { vm_error_at(wk, akw[kw_output].node, "config file output '%s' contains path separator", out); return false; } TSTR(dir); path_dirname(wk, &dir, out); if (!fs_mkdir_p(dir.buf)) { return false; } } if (!fs_mkdir_p(workspace_build_dir(wk))) { return false; } path_join(wk, &out_path, workspace_build_dir(wk), out); LOG_I("configuring '%s'", out_path.buf); output_str = tstr_into_str(wk, &out_path); *res = make_obj(wk, obj_file); *get_obj_file(wk, *res) = output_str; } if (!exclusive_or((bool[]){ akw[kw_command].set, akw[kw_configuration].set, akw[kw_copy].set }, 3)) { vm_error(wk, "you must pass either command:, configuration:, or copy:"); return false; } if (akw[kw_command].set) { bool capture = akw[kw_capture].set && get_obj_bool(wk, akw[kw_capture].val); if (!configure_file_with_command(wk, akw[kw_command].node, akw[kw_command].val, input_arr, output_str, akw[kw_depfile].val, capture)) { return false; } } else if (akw[kw_copy].set) { obj input; bool copy_res = false; if (!array_to_elem_or_err(wk, akw[kw_input].node, input_arr, &input)) { return false; } workspace_add_regenerate_deps(wk, *get_obj_file(wk, input)); struct source src = { 0 }; if (!fs_read_entire_file(get_file_path(wk, input), &src)) { return false; } if (!file_exists_with_content(wk, get_cstr(wk, output_str), src.src, src.len)) { if (!fs_write(get_cstr(wk, output_str), (uint8_t *)src.src, src.len)) { goto copy_err; } if (!fs_copy_metadata(get_file_path(wk, input), get_cstr(wk, output_str))) { goto copy_err; } } copy_res = true; copy_err: fs_source_destroy(&src); if (!copy_res) { return false; } } else { obj dict, conf = akw[kw_configuration].val; enum obj_type t = get_obj_type(wk, conf); switch (t) { case obj_dict: dict = conf; break; case obj_configuration_data: dict = get_obj_configuration_data(wk, conf)->dict; break; default: vm_error_at(wk, akw[kw_configuration].node, "invalid type for configuration data '%s'", obj_type_to_s(t)); return false; } if (akw[kw_input].set) { obj input; /* NOTE: when meson gets an empty array as the input argument * to configure file, it acts like the input keyword wasn't set. * We throw an error. */ if (!array_to_elem_or_err(wk, akw[kw_input].node, input_arr, &input)) { return false; } workspace_add_regenerate_deps(wk, *get_obj_file(wk, input)); const char *path = get_file_path(wk, input); enum configure_file_syntax syntax = configure_file_syntax_mesondefine | configure_file_syntax_mesonvar; if (akw[kw_format].set) { const struct str *fmt = get_str(wk, akw[kw_format].val); if (str_eql(fmt, &STR("meson"))) { syntax = configure_file_syntax_mesondefine | configure_file_syntax_mesonvar; } else if (str_eql(fmt, &STR("cmake"))) { syntax = configure_file_syntax_cmakedefine | configure_file_syntax_cmakevar | configure_file_syntax_mesonvar; } else if (str_eql(fmt, &STR("cmake@"))) { syntax = configure_file_syntax_cmakedefine | configure_file_syntax_mesonvar; } else { vm_error_at( wk, akw[kw_format].node, "invalid format type %o", akw[kw_format].val); return false; } } if (!substitute_config(wk, dict, path, get_cstr(wk, output_str), syntax)) { return false; } } else { if (akw[kw_macro_name].set && output_format != configure_file_output_format_c) { vm_error_at( wk, akw[kw_macro_name].node, "macro_name specified with a non c output format"); return false; } if (!generate_config(wk, output_format, akw[kw_macro_name].val, dict, akw[kw_configuration].node, output_str)) { return false; } } } if ((akw[kw_install].set && get_obj_bool(wk, akw[kw_install].val)) || (!akw[kw_install].set && akw[kw_install_dir].set)) { if (!akw[kw_install_dir].set) { vm_error_at(wk, akw[kw_install].node, "configure_file installation requires install_dir"); return false; } push_install_target_install_dir(wk, output_str, akw[kw_install_dir].val, akw[kw_install_mode].val); } return true; } muon-v0.5.0/src/functions/kernel/install.c0000644000175000017500000002530015041716357017542 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "coerce.h" #include "functions/kernel/install.h" #include "install.h" #include "lang/typecheck.h" #include "log.h" #include "options.h" #include "platform/assert.h" #include "platform/path.h" #define install_follow_symlinks_check() \ if (akw[kw_follow_symlinks].set && !get_obj_bool(wk, akw[kw_follow_symlinks].val)) { \ LOG_W("follow_symlinks: false is not supported"); \ } bool func_install_subdir(struct workspace *wk, obj _, obj *ret) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_mode, kw_install_tag, kw_exclude_directories, kw_exclude_files, kw_strip_directory, kw_follow_symlinks, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string, .required = true }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_exclude_directories] = { "exclude_directories", TYPE_TAG_LISTIFY | obj_string }, [kw_exclude_files] = { "exclude_files", TYPE_TAG_LISTIFY | obj_string }, [kw_strip_directory] = { "strip_directory", obj_bool }, [kw_follow_symlinks] = { "follow_symlinks", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } install_follow_symlinks_check(); bool strip_directory = akw[kw_strip_directory].set ? get_obj_bool(wk, akw[kw_strip_directory].val) : false; obj dest = akw[kw_install_dir].val; if (!strip_directory) { TSTR(path); TSTR(name); char *sep; const char *name_tail; tstr_pushs(wk, &name, get_cstr(wk, an[0].val)); name_tail = name.buf; // strip the first part of the name if ((sep = strchr(name.buf, PATH_SEP))) { *sep = 0; name_tail = sep + 1; } path_join(wk, &path, get_cstr(wk, dest), name_tail); dest = tstr_into_str(wk, &path); } TSTR(path); path_join(wk, &path, workspace_cwd(wk), get_cstr(wk, an[0].val)); obj src = tstr_into_str(wk, &path); struct obj_install_target *tgt; if (!(tgt = push_install_target(wk, src, dest, akw[kw_install_mode].val))) { return false; } tgt->exclude_directories = akw[kw_exclude_directories].val; tgt->exclude_files = akw[kw_exclude_files].val; tgt->type = install_target_subdir; return true; } struct install_man_ctx { obj mode; obj install_dir; obj locale; uint32_t err_node; bool default_install_dir; }; static enum iteration_result install_man_iter(struct workspace *wk, void *_ctx, obj val) { struct install_man_ctx *ctx = _ctx; obj src = *get_obj_file(wk, val); TSTR(man); path_basename(wk, &man, get_cstr(wk, src)); size_t len = man.len; assert(len > 0); --len; if (len <= 1 || man.buf[len - 1] != '.' || man.buf[len] < '0' || man.buf[len] > '9') { vm_error_at(wk, ctx->err_node, "invalid path to man page"); return ir_err; } obj install_dir; if (ctx->default_install_dir) { install_dir = make_strf(wk, "%s/man%c", get_cstr(wk, ctx->install_dir), man.buf[len]); } else { install_dir = ctx->install_dir; } const char *basename = man.buf; if (ctx->locale) { char *dot = strchr(man.buf, '.'); assert(dot); if (str_startswith(&STRL(dot + 1), get_str(wk, ctx->locale))) { *dot = '\0'; obj new_man = make_strf(wk, "%s.%c", man.buf, man.buf[len]); basename = get_cstr(wk, new_man); } } TSTR(path); path_join(wk, &path, get_cstr(wk, install_dir), basename); obj dest = tstr_into_str(wk, &path); if (!push_install_target(wk, src, dest, ctx->mode)) { return ir_err; } return ir_cont; } bool func_install_man(struct workspace *wk, obj _, obj *ret) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_coercible_files }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_mode, kw_locale, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_locale] = { "locale", obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } struct install_man_ctx ctx = { .err_node = an[0].node, .mode = akw[kw_install_mode].val, .install_dir = akw[kw_install_dir].val, .default_install_dir = false, }; if (!akw[kw_install_dir].set) { obj mandir; get_option_value(wk, current_project(wk), "mandir", &mandir); if (akw[kw_locale].set) { TSTR(path); path_join(wk, &path, get_cstr(wk, mandir), get_cstr(wk, akw[kw_locale].val)); ctx.install_dir = tstr_into_str(wk, &path); ctx.locale = akw[kw_locale].val; } else { ctx.install_dir = mandir; } ctx.default_install_dir = true; } obj manpages; if (!coerce_files(wk, an[0].node, an[0].val, &manpages)) { return false; } return obj_array_foreach(wk, manpages, &ctx, install_man_iter); } bool func_install_symlink(struct workspace *wk, obj _, obj *ret) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_tag, kw_pointing_to, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string, .required = true }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_pointing_to] = { "pointing_to", obj_string, .required = true }, 0, }; if (!pop_args(wk, an, akw)) { return false; } TSTR(path); path_join(wk, &path, get_cstr(wk, akw[kw_install_dir].val), get_cstr(wk, an[0].val)); struct obj_install_target *tgt; if (!(tgt = push_install_target(wk, akw[kw_pointing_to].val, tstr_into_str(wk, &path), 0))) { return false; } tgt->type = install_target_symlink; return true; } struct install_emptydir_ctx { obj mode; }; static enum iteration_result install_emptydir_iter(struct workspace *wk, void *_ctx, obj val) { struct install_emptydir_ctx *ctx = _ctx; struct obj_install_target *tgt; if (!(tgt = push_install_target(wk, make_str(wk, ""), val, ctx->mode))) { return ir_err; } tgt->type = install_target_emptydir; return ir_cont; } bool func_install_emptydir(struct workspace *wk, obj _, obj *ret) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_mode, kw_install_tag, }; struct args_kw akw[] = { [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO 0 }; if (!pop_args(wk, an, akw)) { return false; } struct install_emptydir_ctx ctx = { .mode = akw[kw_install_mode].val, }; return obj_array_foreach(wk, an[0].val, &ctx, install_emptydir_iter); } struct install_data_rename_ctx { obj rename; obj mode; obj dest; uint32_t i; uint32_t node; }; static enum iteration_result install_data_rename_iter(struct workspace *wk, void *_ctx, obj val) { struct install_data_rename_ctx *ctx = _ctx; obj src = *get_obj_file(wk, val); obj dest; obj rename; rename = obj_array_index(wk, ctx->rename, ctx->i); TSTR(d); path_join(wk, &d, get_cstr(wk, ctx->dest), get_cstr(wk, rename)); dest = tstr_into_str(wk, &d); push_install_target(wk, src, dest, ctx->mode); ++ctx->i; return ir_cont; } bool func_install_data(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_file | tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_mode, kw_install_tag, kw_rename, kw_sources, kw_preserve_path, kw_follow_symlinks, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_rename] = { "rename", TYPE_TAG_LISTIFY | obj_string }, [kw_sources] = { "sources", TYPE_TAG_LISTIFY | tc_file | tc_string }, [kw_preserve_path] = { "preserve_path", obj_bool }, [kw_follow_symlinks] = { "follow_symlinks", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } install_follow_symlinks_check(); if (akw[kw_rename].set && akw[kw_preserve_path].set) { vm_error_at(wk, akw[kw_preserve_path].node, "rename keyword conflicts with preserve_path"); return false; } obj install_dir; if (akw[kw_install_dir].set) { install_dir = akw[kw_install_dir].val; } else { obj install_dir_base; get_option_value(wk, current_project(wk), "datadir", &install_dir_base); TSTR(buf); path_join(wk, &buf, get_cstr(wk, install_dir_base), get_cstr(wk, current_project(wk)->cfg.name)); install_dir = tstr_into_str(wk, &buf); } obj sources = an[0].val; uint32_t err_node = an[0].node; if (akw[kw_sources].set) { obj_array_extend_nodup(wk, sources, akw[kw_sources].val); err_node = akw[kw_sources].node; } if (akw[kw_rename].set) { if (get_obj_array(wk, akw[kw_rename].val)->len != get_obj_array(wk, sources)->len) { vm_error_at(wk, akw[kw_rename].node, "number of elements in rename != number of sources"); return false; } struct install_data_rename_ctx ctx = { .node = err_node, .mode = akw[kw_install_mode].val, .rename = akw[kw_rename].val, .dest = install_dir, }; obj coerced; if (!coerce_files(wk, err_node, sources, &coerced)) { return false; } return obj_array_foreach(wk, coerced, &ctx, install_data_rename_iter); } else { bool preserve_path = akw[kw_preserve_path].set && get_obj_bool(wk, akw[kw_preserve_path].val); return push_install_targets( wk, err_node, sources, install_dir, akw[kw_install_mode].val, preserve_path); } } bool func_install_headers(struct workspace *wk, obj _, obj *ret) { struct args_norm an[] = { { TYPE_TAG_GLOB | tc_file | tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_mode, kw_subdir, kw_preserve_path, kw_follow_symlinks, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_subdir] = { "subdir", obj_string }, [kw_preserve_path] = { "preserve_path", obj_bool }, [kw_follow_symlinks] = { "follow_symlinks", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } install_follow_symlinks_check(); if (akw[kw_install_dir].set && akw[kw_subdir].set) { vm_error_at(wk, akw[kw_subdir].node, "subdir may not be set if install_dir is set"); return false; } obj install_dir_base; if (akw[kw_install_dir].set) { install_dir_base = akw[kw_install_dir].val; } else { get_option_value(wk, current_project(wk), "includedir", &install_dir_base); } obj install_dir; if (akw[kw_subdir].set) { TSTR(buf); path_join(wk, &buf, get_cstr(wk, install_dir_base), get_cstr(wk, akw[kw_subdir].val)); install_dir = tstr_into_str(wk, &buf); } else { install_dir = install_dir_base; } bool preserve_path = akw[kw_preserve_path].set && get_obj_bool(wk, akw[kw_preserve_path].val); return push_install_targets(wk, an[0].node, an[0].val, install_dir, akw[kw_install_mode].val, preserve_path); } muon-v0.5.0/src/functions/feature_opt.c0000644000175000017500000001244215041716357017134 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/func_lookup.h" #include "functions/feature_opt.h" #include "lang/typecheck.h" static bool feature_opt_common(struct workspace *wk, obj self, obj *res, enum feature_opt_state state) { if (!pop_args(wk, NULL, NULL)) { return false; } *res = make_obj_bool(wk, get_obj_feature_opt(wk, self) == state); return true; } static bool func_feature_opt_auto(struct workspace *wk, obj self, obj *res) { return feature_opt_common(wk, self, res, feature_opt_auto); } static bool func_feature_opt_disabled(struct workspace *wk, obj self, obj *res) { return feature_opt_common(wk, self, res, feature_opt_disabled); } static bool func_feature_opt_enabled(struct workspace *wk, obj self, obj *res) { return feature_opt_common(wk, self, res, feature_opt_enabled); } static bool func_feature_opt_allowed(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, self); *res = make_obj_bool(wk, state == feature_opt_auto || state == feature_opt_enabled); return true; } static bool func_feature_opt_disable_auto_if(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_bool }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, self); if (!get_obj_bool(wk, an[0].val)) { *res = self; return true; } else if (state == feature_opt_disabled || state == feature_opt_enabled) { *res = self; return true; } else { *res = make_obj(wk, obj_feature_opt); set_obj_feature_opt(wk, *res, feature_opt_disabled); return true; } } static bool func_feature_opt_enable_auto_if(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_bool }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, self); if (!get_obj_bool(wk, an[0].val)) { *res = self; return true; } else if (state == feature_opt_disabled || state == feature_opt_enabled) { *res = self; return true; } else { *res = make_obj(wk, obj_feature_opt); set_obj_feature_opt(wk, *res, feature_opt_enabled); return true; } } static bool func_feature_opt_enable_if(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_bool }, ARG_TYPE_NULL }; enum kwargs { kw_error_message, }; struct args_kw akw[] = { [kw_error_message] = { "error_message", obj_string }, 0 }; if (!pop_args(wk, an, akw)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, self); if (!get_obj_bool(wk, an[0].val)) { *res = self; return true; } else if (state == feature_opt_disabled) { const char *err_msg = akw[kw_error_message].set ? get_cstr(wk, akw[kw_error_message].set) : "requirement not met"; vm_error_at(wk, an[0].node, "%s", err_msg); return false; } else { *res = make_obj(wk, obj_feature_opt); set_obj_feature_opt(wk, *res, feature_opt_enabled); return true; } return true; } static bool func_feature_opt_disable_if(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_bool }, ARG_TYPE_NULL }; enum kwargs { kw_error_message, }; struct args_kw akw[] = { [kw_error_message] = { "error_message", obj_string }, 0 }; if (!pop_args(wk, an, akw)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, self); if (!get_obj_bool(wk, an[0].val)) { *res = self; return true; } else if (state == feature_opt_enabled) { const char *err_msg = akw[kw_error_message].set ? get_cstr(wk, akw[kw_error_message].set) : "requirement not met"; vm_error_at(wk, an[0].node, "%s", err_msg); return false; } else { *res = make_obj(wk, obj_feature_opt); set_obj_feature_opt(wk, *res, feature_opt_disabled); return true; } return true; } static bool func_feature_opt_require(struct workspace *wk, obj self, obj *res) { struct args_norm an[] = { { tc_bool }, ARG_TYPE_NULL }; enum kwargs { kw_error_message, }; struct args_kw akw[] = { [kw_error_message] = { "error_message", obj_string }, 0 }; if (!pop_args(wk, an, akw)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, self); if (!get_obj_bool(wk, an[0].val)) { if (state == feature_opt_enabled) { vm_error_at(wk, an[0].node, "%s", akw[kw_error_message].set ? get_cstr(wk, akw[kw_error_message].set) : "requirement not met"); return false; } else { *res = make_obj(wk, obj_feature_opt); set_obj_feature_opt(wk, *res, feature_opt_disabled); } } else { *res = self; } return true; } const struct func_impl impl_tbl_feature_opt[] = { { "allowed", func_feature_opt_allowed, tc_bool, true }, { "auto", func_feature_opt_auto, tc_bool, true }, { "disable_auto_if", func_feature_opt_disable_auto_if, tc_feature_opt, true }, { "disable_if", func_feature_opt_disable_if, tc_feature_opt, true }, { "disabled", func_feature_opt_disabled, tc_bool, true }, { "enable_auto_if", func_feature_opt_enable_auto_if, tc_feature_opt, true }, { "enable_if", func_feature_opt_enable_if, tc_feature_opt, true }, { "enabled", func_feature_opt_enabled, tc_bool, true }, { "require", func_feature_opt_require, tc_feature_opt, true }, { NULL, NULL }, }; muon-v0.5.0/src/lang/0002755000175000017500000000000015041716357013363 5ustar buildbuildmuon-v0.5.0/src/lang/workspace.c0000644000175000017500000002775115041716357015537 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "backend/backend.h" #include "backend/output.h" #include "buf_size.h" #include "embedded.h" #include "error.h" #include "lang/object_iterators.h" #include "lang/serial.h" #include "lang/workspace.h" #include "log.h" #include "options.h" #include "platform/assert.h" #include "platform/path.h" #include "tracy.h" #include "version.h" struct project * make_project(struct workspace *wk, uint32_t *id, const char *subproject_name, const char *cwd, const char *build_dir) { *id = arr_push(&wk->projects, &(struct project){ 0 }); struct project *proj = arr_get(&wk->projects, *id); proj->opts = make_obj(wk, obj_dict); proj->summary = make_obj(wk, obj_dict); proj->targets = make_obj(wk, obj_array); proj->tests = make_obj(wk, obj_array); proj->wrap_provides_deps = make_obj(wk, obj_dict); proj->wrap_provides_exes = make_obj(wk, obj_dict); for (uint32_t i = 0; i < machine_kind_count; ++i) { proj->toolchains[i] = make_obj(wk, obj_dict); proj->args[i] = make_obj(wk, obj_dict); proj->link_args[i] = make_obj(wk, obj_dict); proj->link_with[i] = make_obj(wk, obj_dict); proj->include_dirs[i] = make_obj(wk, obj_dict); proj->dep_cache.static_deps[i] = make_obj(wk, obj_dict); proj->dep_cache.shared_deps[i] = make_obj(wk, obj_dict); proj->dep_cache.frameworks[i] = make_obj(wk, obj_dict); } proj->subprojects_dir = make_str(wk, "subprojects"); if (subproject_name) { proj->subproject_name = make_str(wk, subproject_name); } else { proj->subproject_name = 0; } proj->cwd = make_str(wk, cwd); proj->source_root = proj->cwd; proj->build_dir = make_str(wk, build_dir); proj->build_root = proj->build_dir; proj->scope_stack = wk->vm.behavior.scope_stack_dup(wk, wk->vm.default_scope_stack); return proj; } struct project * current_project(struct workspace *wk) { return wk->projects.len ? arr_get(&wk->projects, wk->cur_project) : 0; } void workspace_init_bare(struct workspace *wk) { *wk = (struct workspace){ 0 }; vm_init(wk); stack_init(&wk->stack, 4096); { #ifdef TRACY_ENABLE static bool first = true; if (first) { wk->tracy.is_master_workspace = true; } first = false; #endif } } static bool workspace_eval_startup_file(struct workspace *wk, const char *script) { obj _; bool ret; struct source src; if (!embedded_get(script, &src)) { LOG_E("embedded script %s not found", script); return false; } stack_push(&wk->stack, wk->vm.lang_mode, language_extended); stack_push(&wk->stack, wk->vm.scope_stack, wk->vm.behavior.scope_stack_dup(wk, wk->vm.default_scope_stack)); ret = eval(wk, &src, build_language_meson, 0, &_); stack_pop(&wk->stack, wk->vm.scope_stack); stack_pop(&wk->stack, wk->vm.lang_mode); return ret; } void workspace_init_runtime(struct workspace *wk) { wk->argv0 = "dummy"; wk->build_root = "dummy"; TSTR(source_root); path_copy_cwd(wk, &source_root); wk->source_root = get_cstr(wk, tstr_into_str(wk, &source_root)); arr_init(&wk->projects, 16, sizeof(struct project)); arr_init(&wk->option_overrides, 32, sizeof(struct option_override)); wk->binaries = make_obj(wk, obj_dict); wk->host_machine = make_obj(wk, obj_dict); wk->regenerate_deps = make_obj(wk, obj_array); wk->exclude_regenerate_deps = make_obj(wk, obj_array); wk->install = make_obj(wk, obj_array); wk->install_scripts = make_obj(wk, obj_array); wk->postconf_scripts = make_obj(wk, obj_array); wk->subprojects = make_obj(wk, obj_dict); wk->global_opts = make_obj(wk, obj_dict); wk->compiler_check_cache = make_obj(wk, obj_dict); wk->dependency_handlers = make_obj(wk, obj_dict); for (uint32_t i = 0; i < machine_kind_count; ++i) { wk->toolchains[i] = make_obj(wk, obj_dict); wk->global_args[i] = make_obj(wk, obj_dict); wk->global_link_args[i] = make_obj(wk, obj_dict); wk->dep_overrides_static[i] = make_obj(wk, obj_dict); wk->dep_overrides_dynamic[i] = make_obj(wk, obj_dict); wk->find_program_overrides[i] = make_obj(wk, obj_dict); wk->machine_properties[i] = make_obj(wk, obj_dict); } } void workspace_init_startup_files(struct workspace *wk) { if (!init_global_options(wk)) { UNREACHABLE; } const char *startup_files[] = { "runtime/dependencies.meson", }; for (uint32_t i = 0; i < ARRAY_LEN(startup_files); ++i) { if (!workspace_eval_startup_file(wk, startup_files[i])) { LOG_W("script %s failed to load", startup_files[i]); } } } void workspace_init(struct workspace *wk) { workspace_init_bare(wk); workspace_init_runtime(wk); workspace_init_startup_files(wk); } void workspace_destroy_bare(struct workspace *wk) { vm_destroy(wk); stack_destroy(&wk->stack); } void workspace_destroy(struct workspace *wk) { TracyCZoneAutoS; arr_destroy(&wk->projects); arr_destroy(&wk->option_overrides); workspace_destroy_bare(wk); TracyCZoneAutoE; } void workspace_setup_paths(struct workspace *wk, const char *build, const char *argv0, uint32_t argc, char *const argv[]) { TSTR(build_root); path_make_absolute(wk, &build_root, build); wk->build_root = get_cstr(wk, tstr_into_str(wk, &build_root)); TSTR(argv0_resolved); if (fs_find_cmd(wk, &argv0_resolved, argv0)) { wk->argv0 = get_cstr(wk, tstr_into_str(wk, &argv0_resolved)); } else { wk->argv0 = get_cstr(wk, make_str(wk, argv0)); } wk->original_commandline.argc = argc; wk->original_commandline.argv = argv; TSTR(muon_private); path_join(wk, &muon_private, wk->build_root, output_path.private_dir); wk->muon_private = get_cstr(wk, tstr_into_str(wk, &muon_private)); } static bool workspace_create_build_dir(struct workspace *wk) { if (!fs_mkdir_p(wk->muon_private)) { return false; } TSTR(path); { const struct str *gitignore_src = &STR("*\n"); path_join(wk, &path, wk->build_root, ".gitignore"); if (!fs_write(path.buf, (const uint8_t *)gitignore_src->s, gitignore_src->len)) { return false; } } { const struct str *hgignore_src = &STR("syntax: glob\n**/*\n"); path_join(wk, &path, wk->build_root, ".hgignore"); if (!fs_write(path.buf, (const uint8_t *)hgignore_src->s, hgignore_src->len)) { return false; } } return true; } static obj summary_bool_to_s(struct workspace *wk, bool v, bool bool_yn) { if (bool_yn) { return make_strf(wk, "%s", bool_to_yn(v)); } else { return make_strf(wk, "%s%s" CLR(0), v ? CLR(c_green) : CLR(c_red), v ? "true" : "false"); } } static obj summary_item_to_s(struct workspace *wk, obj v, bool bool_yn) { switch (get_obj_type(wk, v)) { case obj_dependency: { bool found = get_obj_dependency(wk, v)->flags & dep_flag_found; return summary_bool_to_s(wk, found, bool_yn); } case obj_external_program: { bool found = get_obj_external_program(wk, v)->found; return summary_bool_to_s(wk, found, bool_yn); } case obj_bool: { return summary_bool_to_s(wk, get_obj_bool(wk, v), bool_yn); } default: { TSTR(buf); obj_asprintf(wk, &buf, CLR(c_green) "%#o" CLR(0), v); return tstr_into_str(wk, &buf); } } } void workspace_print_summaries(struct workspace *wk, FILE *out) { if (!out) { return; } FILE *old_log_file = _log_file(); log_set_file(out); uint32_t i; struct project *proj; for (i = 0; i < wk->projects.len; ++i) { proj = arr_get(&wk->projects, i); if (proj->not_ok) { continue; } struct obj_dict *d = get_obj_dict(wk, proj->summary); if (!d->len) { continue; } LOG_I(CLR(c_magenta) "%s" CLR(0) " %s", get_cstr(wk, proj->cfg.name), get_cstr(wk, proj->cfg.version)); obj section, values; obj_dict_for(wk, proj->summary, section, values) { if (get_str(wk, section)->len) { obj_lprintf(wk, log_info, " %#o\n", section); } obj k, v; obj_dict_for(wk, values, k, v) { obj attr = obj_array_index(wk, v, 0); bool bool_yn = false; obj list_sep = 0; if (attr) { obj bool_yn_val; if (attr && obj_dict_index(wk, attr, make_str(wk, "bool_yn"), &bool_yn_val)) { bool_yn = get_obj_bool(wk, bool_yn_val); } obj_dict_index(wk, attr, make_str(wk, "list_sep"), &list_sep); } v = obj_array_index(wk, v, 1); obj_lprintf(wk, log_info, " %#o: ", k); if (get_obj_type(wk, v) == obj_array) { obj sub_v; obj to_join = list_sep ? make_obj(wk, obj_array) : 0; if (!to_join) { log_plain(log_info, "\n"); } obj_array_for(wk, v, sub_v) { sub_v = summary_item_to_s(wk, sub_v, bool_yn); if (to_join) { obj_array_push(wk, to_join, sub_v); } else { log_plain(log_info, " "); const struct str *s = get_str(wk, sub_v); log_plain(log_info, "%s", s->s); log_plain(log_info, "\n"); } } if (to_join) { obj joined; obj_array_join(wk, false, to_join, list_sep, &joined); const struct str *s = get_str(wk, joined); log_plain(log_info, "%s", s->s); } } else { const struct str *s = get_str(wk, summary_item_to_s(wk, v, bool_yn)); log_plain(log_info, "%s", s->s); } log_plain(log_info, "\n"); } } } log_set_file(old_log_file); } static obj make_str_path_absolute(struct workspace *wk, obj v) { TSTR(path); const char *s = get_cstr(wk, v); if (!path_is_absolute(s)) { path_join(wk, &path, workspace_cwd(wk), s); return tstr_into_str(wk, &path); } return v; } void workspace_add_exclude_regenerate_dep(struct workspace *wk, obj v) { v = make_str_path_absolute(wk, v); if (obj_array_in(wk, wk->exclude_regenerate_deps, v)) { return; } obj_array_push(wk, wk->exclude_regenerate_deps, v); } void workspace_add_regenerate_dep(struct workspace *wk, obj v) { if (!wk->regenerate_deps) { return; } v = make_str_path_absolute(wk, v); const char *s = get_cstr(wk, v); if (obj_array_in(wk, wk->exclude_regenerate_deps, v)) { return; } else if (path_is_subpath(wk->build_root, s)) { return; } else if (!fs_file_exists(s)) { return; } obj_array_push(wk, wk->regenerate_deps, v); } void workspace_add_regenerate_deps(struct workspace *wk, obj obj_or_arr) { if (get_obj_type(wk, obj_or_arr) == obj_array) { obj v; obj_array_for(wk, obj_or_arr, v) { workspace_add_regenerate_dep(wk, v); } } else { workspace_add_regenerate_dep(wk, obj_or_arr); } } const char * workspace_cwd(struct workspace *wk) { if (wk->vm.lang_mode == language_internal) { return path_cwd(); } else { return get_cstr(wk, current_project(wk)->cwd); } } const char * workspace_build_dir(struct workspace *wk) { if (wk->vm.lang_mode == language_internal) { return wk->build_root; } else { return get_cstr(wk, current_project(wk)->build_dir); } } bool workspace_do_setup(struct workspace *wk, const char *build, const char *argv0, uint32_t argc, char *const argv[]) { FILE *debug_file = 0; bool res = false; bool progress = log_is_progress_bar_enabled(); log_progress_disable(); workspace_setup_paths(wk, build, argv0, argc, argv); if (!workspace_create_build_dir(wk)) { goto ret; } { TSTR(path); path_join(wk, &path, wk->muon_private, output_path.debug_log); if (!(debug_file = fs_fopen(path.buf, "wb"))) { return false; } log_set_debug_file(debug_file); } workspace_init_startup_files(wk); { TSTR(path); path_join(wk, &path, wk->muon_private, output_path.compiler_check_cache); if (fs_file_exists(path.buf)) { FILE *f; if ((f = fs_fopen(path.buf, "rb"))) { if (!serial_load(wk, &wk->compiler_check_cache, f)) { LOG_E("failed to load compiler check cache"); } fs_fclose(f); } } } LOG_I("muon %s%s%s", muon_version.version, *muon_version.vcs_tag ? "-" : "", muon_version.vcs_tag); if (progress) { log_progress_enable(); log_progress_set_style(&(struct log_progress_style) { .rate_limit = 64, .name_pad = 20 }); } uint32_t project_id; if (!eval_project(wk, NULL, wk->source_root, wk->build_root, &project_id)) { goto ret; } if (log_is_progress_bar_enabled()) { log_progress_disable(); } else { log_plain(log_info, "\n"); } if (!backend_output(wk)) { goto ret; } workspace_print_summaries(wk, _log_file()); LOG_I("setup complete"); res = true; ret: if (debug_file) { fs_fclose(debug_file); log_set_debug_file(0); } return res; } muon-v0.5.0/src/lang/fmt.c0000644000175000017500000012551715041716357014326 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Ailin Nemui * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "buf_size.h" #include "error.h" #include "formats/editorconfig.h" #include "formats/ini.h" #include "lang/fmt.h" #include "lang/object_iterators.h" #include "lang/parser.h" #include "lang/string.h" #include "lang/typecheck.h" #include "lang/workspace.h" #include "log.h" #include "platform/assert.h" #include "platform/mem.h" enum fmt_frag_flag { fmt_frag_flag_add_trailing_comma = 1 << 1, fmt_frag_flag_enclosing_space = 1 << 2, fmt_frag_flag_has_comment_trailing = 1 << 3, fmt_frag_flag_fmt_off = 1 << 4, fmt_frag_flag_fmt_on = 1 << 5, fmt_frag_flag_stick_left = 1 << 6, fmt_frag_flag_stick_left_unless_enclosed = 1 << 7, fmt_frag_flag_stick_right = 1 << 8, fmt_frag_flag_stick_line_left = 1 << 9, fmt_frag_flag_stick_line_right = 1 << 10, fmt_frag_flag_stick_line_left_unless_enclosed = 1 << 11, fmt_frag_flag_force_single_line = 1 << 12, fmt_frag_flag_enclosed_extra_indent = 1 << 13, }; enum fmt_frag_type { fmt_frag_type_expr, fmt_frag_type_line, fmt_frag_type_block, fmt_frag_type_lines, // if returned from fmt_node, will be merged into current block fmt_frag_type_ws_newline, fmt_frag_type_ws_comment, fmt_frag_type_ws_comment_trailing, }; struct fmt_frag { obj str; enum fmt_frag_type type; enum node_type node_type; bool force_ml; // TODO: should this be a flag? const char *enclosing; struct fmt_frag *next, *child, *pre_ws, *post_ws; uint32_t flags; }; struct fmt_out_block { obj str; bool raw; }; struct fmt_ctx { struct workspace *wk; struct tstr *out_buf; obj raw_blocks; uint32_t indent, raw_block_idx, measured_len; bool trailing_comment, fmt_on, measuring, line_has_content, is_enclosed; struct bucket_arr frags; struct arr out_blocks; struct arr list_tmp; struct fmt_opts opts; }; static struct fmt_frag * fmt_frag(struct fmt_ctx *f, enum fmt_frag_type type) { return bucket_arr_push(&f->frags, &(struct fmt_frag){ .type = type }); } static struct fmt_frag * fmt_frag_s(struct fmt_ctx *f, const char *s) { return bucket_arr_push(&f->frags, &(struct fmt_frag){ .str = make_str(f->wk, s) }); } static struct fmt_frag * fmt_frag_o(struct fmt_ctx *f, obj s) { return bucket_arr_push(&f->frags, &(struct fmt_frag){ .str = s }); } static struct fmt_frag * fmt_frag_sibling(struct fmt_frag *fr, struct fmt_frag *sibling) { for (; fr->next; fr = fr->next) { } fr->next = sibling; return sibling; } static struct fmt_frag * fmt_frag_child(struct fmt_frag **dest, struct fmt_frag *child) { if (!*dest) { return *dest = child; } return fmt_frag_sibling(*dest, child); } static struct fmt_frag * fmt_frag_last_child(struct fmt_frag *fr) { for (fr = fr->child; fr->next; fr = fr->next) { } return fr; } static void fmt_frag_broadcast_flag(struct fmt_frag *fr, enum fmt_frag_flag flag) { for (; fr; fr = fr->next) { fr->flags |= flag; } } static obj fmt_obj_as_simple_str(struct fmt_ctx *f, obj s) { struct node *n_str_stmt, *n_str; const struct str *str = get_str(f->wk, s); n_str_stmt = parse(f->wk, &(struct source){ .src = str->s, .len = str->len }, 0); assert(n_str_stmt && n_str_stmt->type == node_type_stmt && n_str_stmt->l); n_str = n_str_stmt->l; if (n_str->type != node_type_string) { return 0; } return n_str->data.str; } static obj fmt_frag_as_simple_str(struct fmt_ctx *f, const struct fmt_frag *fr) { if (!fr || fr->node_type != node_type_string) { return 0; } return fmt_obj_as_simple_str(f, fr->str); } static void fmt_frag_move_ws(struct fmt_frag *dst, struct fmt_frag *src) { uint32_t flag_mask = fmt_frag_flag_has_comment_trailing, flags; flags = src->flags & flag_mask; src->flags &= ~flags; dst->flags |= flags; dst->pre_ws = src->pre_ws; dst->post_ws = src->post_ws; src->pre_ws = 0; src->post_ws = 0; } /******************************************************************************* * debug printing ******************************************************************************/ struct tree_indent { uint32_t indent, bars; uint32_t i, len; }; static void tree_indent_print(const struct tree_indent *ti) { uint32_t i; for (i = 0; i < ti->indent; ++i) { if (i < ti->indent - 1) { if (ti->bars & (1 << i)) { log_raw("│ "); } else { log_raw(" "); } } else if (!ti->len || ti->i == ti->len - 1) { log_raw("└── "); } else { log_raw("├── "); } } } static uint32_t fmt_frag_dbg_len(const struct fmt_frag *p) { uint32_t len = 0; struct fmt_frag *fr; for (fr = p->pre_ws; fr; fr = fr->next) { ++len; } for (fr = p->child; fr; fr = fr->next) { ++len; } for (fr = p->post_ws; fr; fr = fr->next) { ++len; } return len; } static void fmt_write_frag_set_dbg_ws(struct fmt_ctx *f, const struct fmt_frag *pws, struct tree_indent *sub_ti, const char *label) { const struct fmt_frag *ws; for (ws = pws; ws; ws = ws->next) { tree_indent_print(sub_ti); log_raw("%s: ", label); if (ws->type == fmt_frag_type_ws_newline) { log_raw("newline"); } else { obj_lprintf(f->wk, log_info, "# %o", ws->str); if (ws->type == fmt_frag_type_ws_comment) { log_raw(" comment"); } else if (ws->type == fmt_frag_type_ws_comment_trailing) { log_raw(" comment_trailing"); } else { UNREACHABLE; } } log_raw("\n"); ++sub_ti->i; } } static uint32_t fmt_measure_frag(struct fmt_ctx *f, struct fmt_frag *p); static void fmt_write_frag_set_dbg(struct fmt_ctx *f, struct fmt_frag *p, const struct tree_indent *ti, const struct fmt_frag *hl) { if (!log_should_print(log_debug)) { return; } struct fmt_frag *fr; struct tree_indent sub_ti; if (p == hl) { log_raw("\033[34m"); } tree_indent_print(ti); if (p->str) { obj_lprintf(f->wk, log_debug, "%o", p->str); } else if (p->type == fmt_frag_type_block) { obj_lprintf(f->wk, log_debug, "block"); } else if (p->type == fmt_frag_type_line) { obj_lprintf(f->wk, log_debug, "line"); } else if (p->enclosing) { obj_lprintf(f->wk, log_debug, "%s", p->enclosing); } else { obj_lprintf(f->wk, log_debug, "?"); } if (p->flags) { obj flags; flags = make_obj(f->wk, obj_array); struct { uint32_t flag; const char *label; } names[] = { #define FF(v) { fmt_frag_flag_##v, #v } FF(add_trailing_comma), FF(enclosing_space), FF(has_comment_trailing), FF(fmt_off), FF(fmt_on), FF(stick_left), FF(stick_left_unless_enclosed), FF(stick_right), FF(stick_line_left), FF(stick_line_right), FF(stick_line_left_unless_enclosed), FF(force_single_line), FF(enclosed_extra_indent), #undef FF }; uint32_t i; for (i = 0; i < ARRAY_LEN(names); ++i) { if (p->flags & names[i].flag) { obj_array_push(f->wk, flags, make_str(f->wk, names[i].label)); } } obj joined; obj_array_join(f->wk, false, flags, make_str(f->wk, ","), &joined); log_raw(" <%s>", get_cstr(f->wk, joined)); } if (p == hl) { log_raw("\033[0m"); } log_raw(" - %d", fmt_measure_frag(f, p)); log_raw("\n"); sub_ti = (struct tree_indent){ .len = fmt_frag_dbg_len(p), .indent = ti->indent + 1, .bars = ti->bars, }; if (ti->i < ti->len - 1) { sub_ti.bars |= (1 << (ti->indent - 1)); } fmt_write_frag_set_dbg_ws(f, p->pre_ws, &sub_ti, "pre_ws"); uint32_t i; for (fr = p->child, i = 0; fr; fr = fr->next, ++i) { fmt_write_frag_set_dbg(f, fr, &sub_ti, hl); ++sub_ti.i; } fmt_write_frag_set_dbg_ws(f, p->post_ws, &sub_ti, "post_ws"); } /******************************************************************************* * fmt_write ******************************************************************************/ static void fmt_push_out_block(struct fmt_ctx *f) { arr_push(&f->out_blocks, &(struct fmt_out_block){ .str = tstr_into_str(f->wk, f->out_buf), }); *f->out_buf = (struct tstr){ 0 }; tstr_init(f->out_buf, 0, 0, 0); } static void fmt_write_nl(struct fmt_ctx *f) { if (f->measuring) { if (f->is_enclosed) { f->measured_len += f->opts.max_line_len + 1; } return; } else if (!f->fmt_on) { return; } f->line_has_content = false; tstr_push(f->wk, f->out_buf, '\n'); } static void fmt_write(struct fmt_ctx *f, const char *s, uint32_t n) { if (f->measuring) { if (strchr(s, '\n')) { f->measured_len += f->opts.max_line_len + 1; } else { f->measured_len += n; } return; } else if (!f->fmt_on) { return; } if (!f->line_has_content) { uint32_t i, j; for (i = 1; i < f->indent; ++i) { switch (f->opts.indent_style) { case fmt_indent_style_space: { for (j = 0; j < f->opts.indent_size; ++j) { tstr_push(f->wk, f->out_buf, ' '); } break; } case fmt_indent_style_tab: { tstr_push(f->wk, f->out_buf, '\t'); break; } } } } f->line_has_content = true; tstr_pushn(f->wk, f->out_buf, s, n); } static void fmt_writestr(struct fmt_ctx *f, const struct str *s) { fmt_write(f, s->s, s->len); } static void fmt_writes(struct fmt_ctx *f, const char *s) { fmt_write(f, s, strlen(s)); } static void fmt_write_frag_comment(struct fmt_ctx *f, struct fmt_frag *comment) { const struct str *str = get_str(f->wk, comment->str); if (f->measuring) { f->measured_len += f->opts.max_line_len + 1; return; } if (comment->flags & fmt_frag_flag_fmt_on) { f->fmt_on = true; obj raw_block; raw_block = obj_array_index(f->wk, f->raw_blocks, f->raw_block_idx); arr_push(&f->out_blocks, &(struct fmt_out_block){ .str = raw_block, .raw = true, }); ++f->raw_block_idx; } fmt_writes(f, "#"); if (str->len) { fmt_writestr(f, str); } if (comment->flags & fmt_frag_flag_fmt_off) { fmt_push_out_block(f); f->fmt_on = false; } } static void fmt_write_frag_trailing_comment(struct fmt_ctx *f, struct fmt_frag *ws) { struct fmt_frag *fr; for (fr = ws; fr; fr = fr->next) { if (fr->type != fmt_frag_type_ws_comment_trailing) { continue; } if (f->line_has_content) { fmt_writes(f, f->opts.indent_before_comments); } fmt_write_frag_comment(f, fr); } } enum fmt_write_frag_ws_mode { fmt_write_frag_ws_mode_pre, fmt_write_frag_ws_mode_post, fmt_write_frag_ws_mode_empty_line, }; static void fmt_write_frag_ws(struct fmt_ctx *f, struct fmt_frag *ws, enum fmt_write_frag_ws_mode mode) { uint32_t newlines = 0, consecutive_newlines = 0; struct fmt_frag *fr; for (fr = ws; fr; fr = fr->next) { switch (fr->type) { case fmt_frag_type_ws_comment_trailing: { ++newlines; break; } case fmt_frag_type_ws_comment: { consecutive_newlines = 0; if (mode == fmt_write_frag_ws_mode_post) { fmt_write_nl(f); } fmt_write_frag_comment(f, fr); if (mode == fmt_write_frag_ws_mode_empty_line) { if (fr->next) { fmt_write_nl(f); } } if (mode == fmt_write_frag_ws_mode_pre) { fmt_write_nl(f); } ++newlines; break; } case fmt_frag_type_ws_newline: { if (f->is_enclosed) { if (newlines < 1) { // Skip the first newline ++newlines; continue; } else if (consecutive_newlines > 0) { // After that only print 1 consecutive newline continue; } } else { if (newlines < 1) { // Skip the first newline ++newlines; continue; } else if (consecutive_newlines >= 1) { continue; } } fmt_write_nl(f); ++newlines; ++consecutive_newlines; break; } default: UNREACHABLE; } } } static void fmt_write_frag(struct fmt_ctx *f, struct fmt_frag *p); static uint32_t fmt_measure_frag(struct fmt_ctx *f, struct fmt_frag *p) { f->measured_len = 0; f->measuring = true; fmt_write_frag(f, p); f->measuring = false; return f->measured_len; } static void fmt_write_frag(struct fmt_ctx *f, struct fmt_frag *p) { struct fmt_frag *fr; uint32_t base_len = f->opts.indent_size * f->indent; bool ml = f->measuring ? false : base_len + fmt_measure_frag(f, p) > f->opts.max_line_len; if (f->measuring) { if (p->force_ml) { f->measured_len += f->opts.max_line_len + 1; return; } else if (p->flags & fmt_frag_flag_force_single_line) { f->measured_len = 0; return; } } // Write preceeding whitespace if (p->pre_ws) { enum fmt_write_frag_ws_mode mode = fmt_write_frag_ws_mode_pre; if (p->type == fmt_frag_type_line && !p->child) { // Handle empty lines a bit differently mode = fmt_write_frag_ws_mode_empty_line; } fmt_write_frag_ws(f, p->pre_ws, mode); } // Write enclosing characters and indent if necessary if (p->enclosing) { fmt_write(f, &p->enclosing[0], 1); stack_push(&f->wk->stack, f->is_enclosed, true); if (ml) { ++f->indent; if (p->flags & fmt_frag_flag_enclosed_extra_indent) { ++f->indent; } } if (p->child && (p->child->flags & fmt_frag_flag_stick_left || p->child->flags & fmt_frag_flag_stick_line_left)) { // } else { if (ml) { fmt_write_nl(f); } else if (p->flags & fmt_frag_flag_enclosing_space) { fmt_writes(f, " "); } } } else if (p->type == fmt_frag_type_block) { stack_push(&f->wk->stack, f->is_enclosed, false); ++f->indent; } if (p->child && p->child->pre_ws && p->child->pre_ws->flags & fmt_frag_flag_has_comment_trailing) { // If our first child has a preceeding trailing comment, write // it here fmt_write_frag_trailing_comment(f, p->child->pre_ws); fmt_write_nl(f); } // If we have children, write them out for (fr = p->child; fr; fr = fr->next) { fmt_write_frag(f, fr); if (fr->next) { // If the next child has a trailing comment, write it before the newline if (fr->next->pre_ws) { fmt_write_frag_trailing_comment(f, fr->next->pre_ws); } // Write the child separator if ((fr->next->flags & fmt_frag_flag_stick_left) || (fr->flags & fmt_frag_flag_stick_right)) { } else { bool stick_line = (fr->flags & fmt_frag_flag_stick_line_right) || (fr->next->flags & fmt_frag_flag_stick_line_left) || (!f->is_enclosed && (fr->next->flags & fmt_frag_flag_stick_line_left_unless_enclosed || fr->next->flags & fmt_frag_flag_stick_left_unless_enclosed)); if (ml && !stick_line) { fmt_write_nl(f); } else if (fr->next->flags & fmt_frag_flag_stick_left_unless_enclosed) { } else { fmt_writes(f, " "); } } } } // Write out the current fragment's string if (p->str) { fmt_writestr(f, get_str(f->wk, p->str)); } // Write enclosing characters and dedent if necessary if (p->enclosing) { if (ml) { --f->indent; } if (p->child && (fmt_frag_last_child(p)->flags & fmt_frag_flag_stick_right)) { // } else { if (ml) { if ((p->flags & fmt_frag_flag_add_trailing_comma)) { fmt_writes(f, ","); } fmt_write_nl(f); } else if (p->flags & fmt_frag_flag_enclosing_space) { fmt_writes(f, " "); } } fmt_write(f, &p->enclosing[1], 1); stack_pop(&f->wk->stack, f->is_enclosed); if (ml && p->flags & fmt_frag_flag_enclosed_extra_indent) { --f->indent; } } else if (p->type == fmt_frag_type_block) { stack_pop(&f->wk->stack, f->is_enclosed); --f->indent; } if (p->post_ws) { fmt_write_frag_trailing_comment(f, p->post_ws); fmt_write_frag_ws(f, p->post_ws, fmt_write_frag_ws_mode_post); } } static void fmt_write_block(struct fmt_ctx *f, struct fmt_frag *block) { if (!block) { return; } fmt_write_frag_set_dbg(f, block, &(struct tree_indent){ .indent = 1, .len = 1 }, 0); fmt_write_frag(f, block); } /******************************************************************************* * formatter ******************************************************************************/ static void fmt_node_ws(struct fmt_ctx *f, struct node *n, obj ws, struct fmt_frag **dest) { const struct str *s = get_str(f->wk, ws); struct str comment; struct fmt_frag *child; bool trailing_comment = true; uint32_t i, cs, ce; for (i = 0; i < s->len; ++i) { if (s->s[i] == '\n') { trailing_comment = false; fmt_frag_child(dest, fmt_frag(f, fmt_frag_type_ws_newline)); } else if (s->s[i] == '#') { ++i; cs = ce = i; for (; s->s[i] != '\n' && i < s->len; ++i) { ++ce; } comment = (struct str){ .s = &s->s[cs], .len = ce - cs }; child = fmt_frag_child(dest, fmt_frag_o(f, make_strn(f->wk, comment.s, comment.len))); if (trailing_comment) { child->type = fmt_frag_type_ws_comment_trailing; (*dest)->flags |= fmt_frag_flag_has_comment_trailing; } else { child->type |= fmt_frag_type_ws_comment; } obj stripped_comment = str_strip(f->wk, &comment, 0, 0); bool fmt_on; if (lexer_is_fmt_comment(get_str(f->wk, stripped_comment), &fmt_on)) { child->flags |= fmt_on ? fmt_frag_flag_fmt_on : fmt_frag_flag_fmt_off; } trailing_comment = false; } } } static const char * fmt_node_to_token(enum node_type type) { switch (type) { case node_type_continue: return "continue"; case node_type_break: return "break"; case node_type_return: return "return"; case node_type_assign: return "="; case node_type_or: return "or"; case node_type_and: return "and"; case node_type_add: return "+"; case node_type_sub: return "-"; case node_type_mul: return "*"; case node_type_div: return "/"; case node_type_mod: return "%"; case node_type_not: return "not"; case node_type_eq: return "=="; case node_type_neq: return "!="; case node_type_in: return "in"; case node_type_not_in: return "not in"; case node_type_lt: return "<"; case node_type_gt: return ">"; case node_type_leq: return "<="; case node_type_geq: return ">="; default: UNREACHABLE_RETURN; } } static int32_t fmt_files_args_sort_cmp(const void *_a, const void *_b, void *_ctx) { struct fmt_ctx *f = _ctx; const struct fmt_frag *a = *(const struct fmt_frag **)_a, *b = *(const struct fmt_frag **)_b; obj sa = fmt_frag_as_simple_str(f, a->child), sb = fmt_frag_as_simple_str(f, b->child); const char *s1 = get_cstr(f->wk, sa), *s2 = get_cstr(f->wk, sb); bool s1_hasdir = strchr(s1, '/') != NULL, s2_hasdir = strchr(s2, '/') != NULL; if ((s1_hasdir && s2_hasdir) || (!s1_hasdir && !s2_hasdir)) { return strcmp(s1, s2); } else if (s1_hasdir) { return -1; } else { return 1; } } static struct fmt_frag *fmt_block(struct fmt_ctx *f, struct node *n); static struct fmt_frag *fmt_node(struct fmt_ctx *f, struct node *n); enum fmt_list_flag { fmt_list_flag_sort_files = 1 << 0, fmt_list_flag_func_args = 1 << 1, }; static void fmt_list(struct fmt_ctx *f, struct node *n, struct fmt_frag *fr, enum fmt_list_flag flags) { struct fmt_frag *child = 0, *next, *prev; obj str; enum node_type type = n->type; uint32_t list_tmp_base = f->list_tmp.len, len = 0; bool saw_trailing_comma = false; bool is_func_with_single_arg = false; while (true) { if (n->l) { prev = child; child = fmt_frag(f, fmt_frag_type_expr); arr_push(&f->list_tmp, &child); if (n->l->type == node_type_kw) { if (f->opts.kwargs_force_multiline) { fr->force_ml = true; } next = fmt_frag_child(&child->child, fmt_node(f, n->l->r)); // Move the child's ws up one level and grab // post_ws if we have it. fmt_frag_move_ws(child, child->child); if (n->l->fmt.post.ws) { fmt_node_ws(f, n->l, n->l->fmt.post.ws, &child->post_ws); } if (type == node_type_def_args && n->l->r->l->data.type) { next = fmt_frag_child(&child->child, fmt_frag_s(f, typechecking_type_to_s(f->wk, n->l->r->l->data.type))); next->flags |= fmt_frag_flag_stick_line_left; } next = fmt_frag_child(&child->child, fmt_frag_s(f, ":")); if (!f->opts.wide_colon) { next->flags |= fmt_frag_flag_stick_left; } else { next->flags |= fmt_frag_flag_stick_line_left; } // node_type_list is a placeholder for keys with no value if (n->l->l->type != node_type_list) { next = fmt_frag_child(&child->child, fmt_frag(f, fmt_frag_type_expr)); next->flags |= fmt_frag_flag_stick_line_left; fmt_frag_child(&next->child, fmt_node(f, n->l->l)); } } else { fmt_frag_child(&child->child, fmt_node(f, n->l)); // Move the child's ws up one level fmt_frag_move_ws(child, child->child); // If the previous list element is a plain // string that looks like a flag, mark the // current element as stick_line_left. This is // so that things like ['-o', value] remain // together when the list is multilined. if (prev && (str = fmt_frag_as_simple_str(f, prev->child))) { obj cur = fmt_frag_as_simple_str(f, child->child); const struct str *s = get_str(f->wk, str); bool prev_is_arg_like = str_startswith(s, &STR("-")) && !strchr(s->s, '='), cur_is_arg_like = cur && str_startswith(get_str(f->wk, cur), &STR("-")); if (prev_is_arg_like && !cur_is_arg_like && !(child->pre_ws && child->pre_ws->type == fmt_frag_type_ws_comment_trailing)) { child->flags |= fmt_frag_flag_stick_line_left; } } if (type == node_type_def_args && n->l->l->data.type) { next = fmt_frag_sibling(child->child, fmt_frag_s(f, typechecking_type_to_s(f->wk, n->l->l->data.type))); next->flags |= fmt_frag_flag_stick_line_left; } } ++len; } if (!n->r) { break; } if (n->r->fmt.pre.ws) { // this means we got whitespace before the , // Add it to the trailing whitespace for the current child fmt_node_ws(f, n, n->r->fmt.pre.ws, &child->post_ws); } assert(!child->str); child->str = make_str(f->wk, ","); n = n->r; if (!n->r && !n->l) { // trailing comma fr->force_ml = true; saw_trailing_comma = true; } } if (!saw_trailing_comma) { is_func_with_single_arg = (flags & fmt_list_flag_func_args) && len == 1; if (is_func_with_single_arg) { if (n->l->type == node_type_string && str_startswith(get_str(f->wk, n->l->data.str), &STR("'''"))) { fr->flags |= fmt_frag_flag_force_single_line; } } if (f->opts.no_single_comma_function && is_func_with_single_arg) { } else { fr->flags |= fmt_frag_flag_add_trailing_comma; } } if (flags & fmt_list_flag_sort_files) { arr_sort_range(&f->list_tmp, list_tmp_base, f->list_tmp.len, f, fmt_files_args_sort_cmp); // Fix-up commas that might have gotten messed up by the above sorting uint32_t i; for (i = list_tmp_base; i < f->list_tmp.len; ++i) { child = *(struct fmt_frag **)arr_get(&f->list_tmp, i); if (i == f->list_tmp.len - 1) { if (saw_trailing_comma) { if (!child->str) { child->str = make_str(f->wk, ","); } } else { child->str = 0; } } else if (!child->str) { child->str = make_str(f->wk, ","); } } } uint32_t i; for (i = list_tmp_base; i < f->list_tmp.len; ++i) { child = *(struct fmt_frag **)arr_get(&f->list_tmp, i); fmt_frag_child(&fr->child, child); } f->list_tmp.len = list_tmp_base; } static struct fmt_frag * fmt_node(struct fmt_ctx *f, struct node *n) { assert(n->type != node_type_stmt); struct fmt_frag *fr, *res, *next; res = fr = fmt_frag(f, fmt_frag_type_expr); fr->node_type = n->type; if (n->fmt.pre.ws) { fmt_node_ws(f, n, n->fmt.pre.ws, &fr->pre_ws); } if (n->fmt.post.ws) { fmt_node_ws(f, n, n->fmt.post.ws, &res->post_ws); } /* L("formatting %p:%s", (void *)n, node_to_s(f->wk, n)); */ switch (n->type) { case node_type_maybe_id: case node_type_stringify: case node_type_stmt: UNREACHABLE; case node_type_args: case node_type_def_args: case node_type_kw: case node_type_list: case node_type_foreach_args: // Skipped break; case node_type_continue: case node_type_break: { fr->str = make_str(f->wk, fmt_node_to_token(n->type)); break; } case node_type_return: { fr->str = make_str(f->wk, fmt_node_to_token(n->type)); if (n->l) { next = fmt_frag_sibling(fr, fmt_node(f, n->l)); next->flags |= fmt_frag_flag_stick_line_left; } break; } case node_type_id: case node_type_id_lit: case node_type_number: { fr->str = n->data.str; break; } case node_type_string: { obj str; if (f->opts.simplify_string_literals && (str = fmt_obj_as_simple_str(f, n->data.str)) && !str_contains(get_str(f->wk, str), &STR("\n")) && !str_contains(get_str(f->wk, str), &STR("'"))) { struct str newstr = *get_str(f->wk, n->data.str); if (str_startswith(&newstr, &STR("f"))) { ++newstr.s; --newstr.len; } if (str_startswith(&newstr, &STR("'''"))) { newstr.s += 2; newstr.len -= 4; } n->data.str = make_strn(f->wk, newstr.s, newstr.len); } fr->str = n->data.str; break; } case node_type_bool: { fr->str = make_str(f->wk, n->data.num ? "true" : "false"); break; } case node_type_null: { fr->str = make_str(f->wk, "null"); break; } case node_type_negate: { fr->str = make_str(f->wk, "-"); fr->flags |= fmt_frag_flag_stick_right; next = fmt_frag_sibling(fr, fmt_node(f, n->l)); break; } case node_type_not: { fr->str = make_str(f->wk, "not"); fr->flags |= fmt_frag_flag_stick_line_right; next = fmt_frag_sibling(fr, fmt_node(f, n->l)); break; } case node_type_assign: case node_type_or: case node_type_and: case node_type_add: case node_type_sub: case node_type_mul: case node_type_div: case node_type_mod: case node_type_eq: case node_type_neq: case node_type_in: case node_type_not_in: case node_type_lt: case node_type_gt: case node_type_leq: case node_type_geq: { const char *token; if (n->type == node_type_assign && (n->data.type & op_store_flag_add_store)) { token = "+="; } else { token = fmt_node_to_token(n->type); } bool is_member_assign = n->type == node_type_assign && (n->data.type & op_store_flag_member); struct node *rhs; if (is_member_assign) { res = fr = fmt_node(f, n->r->l); if (n->l->type == node_type_id_lit) { next = fmt_frag_sibling(fr, fmt_frag_s(f, ".")); next->flags |= fmt_frag_flag_stick_left; next = fmt_frag_sibling(fr, fmt_node(f, n->l)); next->flags |= fmt_frag_flag_stick_left; } else { next = fmt_frag_sibling(fr, fmt_frag(f, fmt_frag_type_expr)); next->enclosing = "[]"; next->flags |= fmt_frag_flag_stick_left; fmt_frag_child(&next->child, fmt_node(f, n->l)); } rhs = n->r->r; } else { res = fr = fmt_node(f, n->l); rhs = n->r; } next = fmt_frag_sibling(fr, fmt_frag_s(f, token)); switch (n->type) { case node_type_add: case node_type_or: case node_type_and: { next->flags |= fmt_frag_flag_stick_line_left_unless_enclosed; break; } default: { next->flags |= fmt_frag_flag_stick_line_left; break; } } next = fmt_frag_sibling(fr, fmt_node(f, rhs)); next->flags |= fmt_frag_flag_stick_line_left; break; } case node_type_index: { res = fr = fmt_node(f, n->l); next = fmt_frag_sibling(fr, fmt_frag(f, fmt_frag_type_expr)); next->enclosing = "[]"; next->flags |= fmt_frag_flag_stick_left; fmt_frag_child(&next->child, fmt_node(f, n->r)); break; } case node_type_group: { fr->enclosing = "()"; fmt_frag_child(&fr->child, fmt_node(f, n->l)); if (f->opts.sticky_parens) { fr->child->flags |= fmt_frag_flag_stick_left; fmt_frag_last_child(fr)->flags |= fmt_frag_flag_stick_right; } break; } case node_type_dict: { fr->enclosing = "{}"; fmt_list(f, n, fr, 0); break; } case node_type_array: { fr->enclosing = "[]"; if (f->opts.space_array) { fr->flags |= fmt_frag_flag_enclosing_space; } fmt_list(f, n, fr, 0); break; } case node_type_member: { res = fr = fmt_node(f, n->l); next = fmt_frag_sibling(fr, fmt_frag_s(f, ".")); next->flags |= fmt_frag_flag_stick_left; next = fmt_frag_sibling(fr, fmt_node(f, n->r)); next->flags |= fmt_frag_flag_stick_left; /* fmt_list(f, n->l->l, next, fmt_list_flag_func_args); */ /* next->enclosing = "()"; */ /* next->flags |= fmt_frag_flag_stick_left; */ break; } case node_type_call: { res = fr = fmt_node(f, n->r); enum fmt_list_flag flags = fmt_list_flag_func_args; { enum fmt_special_function { fmt_special_function_unknown, fmt_special_function_files, }; enum fmt_special_function function = fmt_special_function_unknown; if (n->r->type == node_type_id_lit) { if (str_eql(get_str(f->wk, n->r->data.str), &STR("files"))) { function = fmt_special_function_files; } } switch (function) { case fmt_special_function_unknown: break; case fmt_special_function_files: { struct node *args = n->l, *arr; if (!f->opts.sort_files) { goto fmt_special_function_done; } // If files() gets a single argument of type // array, un-nest it. if (!args->r && args->l && args->l->type == node_type_array) { arr = args->l; args->l = arr->l; args->r = arr->r; } bool all_elements_are_simple_strings = true; while (true) { if (args->l) { if (args->l->type != node_type_string || str_startswith( get_str(f->wk, args->l->data.str), &STR("f"))) { all_elements_are_simple_strings = false; break; } } if (!args->r) { break; } args = args->r; } if (all_elements_are_simple_strings) { flags |= fmt_list_flag_sort_files; } break; } } } fmt_special_function_done: next = fmt_frag_sibling(fr, fmt_frag(f, fmt_frag_type_expr)); fmt_list(f, n->l, next, flags); next->enclosing = "()"; next->flags |= fmt_frag_flag_stick_left; break; } case node_type_ternary: { res = fr = fmt_node(f, n->l); next = fmt_frag_sibling(fr, fmt_frag_s(f, "?")); next->flags |= fmt_frag_flag_stick_line_left; next = fmt_frag_sibling(fr, fmt_node(f, n->r->l)); next->flags |= fmt_frag_flag_stick_line_left; next = fmt_frag_sibling(fr, fmt_frag_s(f, ":")); next->flags |= fmt_frag_flag_stick_line_left; next = fmt_frag_sibling(fr, fmt_node(f, n->r->r)); next->flags |= fmt_frag_flag_stick_line_left; break; } case node_type_foreach: { res->type = fmt_frag_type_lines; fr = fmt_frag_child(&res->child, fmt_frag(f, fmt_frag_type_line)); fr = fmt_frag_child(&fr->child, fmt_frag_s(f, "foreach")); next = fmt_frag_sibling(fr, fmt_node(f, n->l->l->l)); next->flags |= fmt_frag_flag_stick_line_left; if (n->l->l->r) { str_app(f->wk, &next->str, ","); next = fmt_frag_sibling(fr, fmt_node(f, n->l->l->r)); next->flags |= fmt_frag_flag_stick_line_left; } next = fmt_frag_sibling(fr, fmt_frag_s(f, ":")); next->flags |= fmt_frag_flag_stick_line_left; next = fmt_frag_sibling(fr, fmt_node(f, n->l->r)); next->flags |= fmt_frag_flag_stick_line_left; if (n->r) { fmt_frag_child(&res->child, fmt_block(f, n->r)); } fr = fmt_frag_child(&res->child, fmt_frag(f, fmt_frag_type_line)); fmt_frag_child(&fr->child, fmt_frag_s(f, "endforeach")); break; } case node_type_if: { res->type = fmt_frag_type_lines; bool first = true; while (n) { fr = fmt_frag_child(&res->child, fmt_frag(f, fmt_frag_type_line)); fr = fmt_frag_child(&fr->child, fmt_frag_s(f, first ? "if" : n->l->l ? "elif" : "else")); if (n->l->l) { next = fmt_frag_sibling(fr, fmt_node(f, n->l->l)); next->flags |= fmt_frag_flag_stick_line_left; if (f->opts.continuation_indent) { fmt_frag_broadcast_flag(next, fmt_frag_flag_enclosed_extra_indent); } } if (n->l->r) { fmt_frag_child(&res->child, fmt_block(f, n->l->r)); } first = false; n = n->r; } fr = fmt_frag_child(&res->child, fmt_frag(f, fmt_frag_type_line)); fmt_frag_child(&fr->child, fmt_frag_s(f, "endif")); break; } case node_type_func_def: { res->type = fmt_frag_type_lines; fr = fmt_frag_child(&res->child, fmt_frag(f, fmt_frag_type_line)); fr = fmt_frag_child(&fr->child, fmt_frag_s(f, "func")); if (n->l->l->l) { next = fmt_frag_sibling(fr, fmt_node(f, n->l->l->l)); next->flags |= fmt_frag_flag_stick_line_left; } next = fmt_frag_sibling(fr, fmt_frag(f, fmt_frag_type_expr)); fmt_list(f, n->l->r, next, 0); next->enclosing = "()"; next->flags |= fmt_frag_flag_stick_left; if (n->data.type) { next = fmt_frag_sibling(fr, fmt_frag_s(f, "->")); next->flags |= fmt_frag_flag_stick_line_left; next = fmt_frag_sibling(fr, fmt_frag_s(f, typechecking_type_to_s(f->wk, n->data.type))); next->flags |= fmt_frag_flag_stick_line_left; } if (n->r) { fmt_frag_child(&res->child, fmt_block(f, n->r)); } fr = fmt_frag_child(&res->child, fmt_frag(f, fmt_frag_type_line)); fmt_frag_child(&fr->child, fmt_frag_s(f, "endfunc")); break; } } return res; } static struct fmt_frag * fmt_block(struct fmt_ctx *f, struct node *n) { struct fmt_frag *block, *line, *child; block = fmt_frag(f, fmt_frag_type_block); block->force_ml = true; while (true) { assert(n->type == node_type_stmt); line = fmt_frag(f, fmt_frag_type_line); if (n->fmt.pre.ws) { fmt_node_ws(f, n, n->fmt.pre.ws, &line->pre_ws); } if (n->fmt.post.ws) { fmt_node_ws(f, n, n->fmt.post.ws, &line->post_ws); } if (n->l) { child = fmt_node(f, n->l); if (child->type == fmt_frag_type_lines) { child->child->pre_ws = line->pre_ws; fmt_frag_last_child(child)->post_ws = line->post_ws; fmt_frag_child(&block->child, child->child); } else { fmt_frag_child(&line->child, child); fmt_frag_child(&block->child, line); } } else { // This is an empty line, only add empty lines if they have pre_ws if (line->pre_ws) { // Convert all trailing comments on empty lines into regular comments if (line->pre_ws->flags & fmt_frag_flag_has_comment_trailing) { for (child = line->pre_ws; child; child = child->next) { if (child->type == fmt_frag_type_ws_comment_trailing) { child->type = fmt_frag_type_ws_comment; } } line->pre_ws->flags &= ~fmt_frag_flag_has_comment_trailing; } fmt_frag_child(&block->child, line); } } if (!n->r) { break; } n = n->r; } if (!block->child) { // If we don't have any children, then disregard this block return 0; } return block; } /******************************************************************************* * config parsing ******************************************************************************/ static void fmt_cfg_parse_indent_by(struct fmt_ctx *f, void *val) { const char *indent_by = *(const char **)val; if (!*indent_by) { f->opts.indent_size = 0; return; } if (*indent_by == ' ') { f->opts.indent_style = fmt_indent_style_space; for (; *indent_by; ++indent_by) { ++f->opts.indent_size; } } else if (*indent_by == '\t') { f->opts.indent_style = fmt_indent_style_tab; } } static bool fmt_cfg_parse_cb(void *_ctx, struct source *src, const char *sect, const char *k, const char *v, struct source_location location) { struct fmt_ctx *ctx = _ctx; enum val_type { type_uint, type_str, type_bool, type_enum, }; struct fmt_cfg_enum { const char *name; uint32_t val; }; struct fmt_cfg_enum indent_style_tbl[] = { { "tab", fmt_indent_style_tab }, { "space", fmt_indent_style_space }, 0 }; struct fmt_cfg_enum end_of_line_tbl[] = { { "lf", fmt_end_of_line_lf }, { "cr", fmt_end_of_line_cr }, { "crlf", fmt_end_of_line_crlf }, 0 }; const struct { const char *name; enum val_type type; uint32_t off; bool deprecated; void((*deprecated_action)(struct fmt_ctx *f, void *val)); struct fmt_cfg_enum *enum_tbl; } keys[] = { { "max_line_len", type_uint, offsetof(struct fmt_opts, max_line_len) }, { "space_array", type_bool, offsetof(struct fmt_opts, space_array) }, { "kwargs_force_multiline", type_bool, offsetof(struct fmt_opts, kwargs_force_multiline) }, { "wide_colon", type_bool, offsetof(struct fmt_opts, wide_colon) }, { "no_single_comma_function", type_bool, offsetof(struct fmt_opts, no_single_comma_function) }, { "insert_final_newline", type_bool, offsetof(struct fmt_opts, insert_final_newline) }, { "sort_files", type_bool, offsetof(struct fmt_opts, sort_files) }, { "group_arg_value", type_bool, offsetof(struct fmt_opts, group_arg_value) }, { "simplify_string_literals", type_bool, offsetof(struct fmt_opts, simplify_string_literals) }, { "use_editor_config", type_bool, offsetof(struct fmt_opts, use_editor_config) }, { "indent_before_comments", type_str, offsetof(struct fmt_opts, indent_before_comments) }, { "indent_size", type_uint, offsetof(struct fmt_opts, indent_size) }, { "tab_width", type_uint, offsetof(struct fmt_opts, tab_width) }, { "indent_style", type_enum, offsetof(struct fmt_opts, indent_style), .enum_tbl = indent_style_tbl }, { "end_of_line", type_enum, offsetof(struct fmt_opts, end_of_line), .enum_tbl = end_of_line_tbl }, { "sticky_parens", type_bool, offsetof(struct fmt_opts, sticky_parens) }, { "continuation_indent", type_bool, offsetof(struct fmt_opts, continuation_indent) }, // deprecated options { "indent_by", type_str, .deprecated = true, .deprecated_action = fmt_cfg_parse_indent_by }, { "kwa_ml", type_bool, offsetof(struct fmt_opts, kwargs_force_multiline), .deprecated = true }, 0, }; if (!k || !*k) { error_messagef(src, location, log_error, "missing key"); return false; } else if (!v || !*v) { error_messagef(src, location, log_error, "missing value"); return false; } else if (sect) { error_messagef(src, location, log_error, "invalid section"); return false; } uint32_t i; for (i = 0; keys[i].name; ++i) { if (strcmp(k, keys[i].name) != 0) { continue; } void *val_dest = (((uint8_t *)(&ctx->opts)) + keys[i].off); if (keys[i].deprecated) { error_messagef(src, location, log_warn, "option %s is deprecated", keys[i].name); } switch (keys[i].type) { case type_uint: { char *endptr = NULL; long long lval = strtoll(v, &endptr, 10); if (*endptr) { error_messagef(src, location, log_error, "unable to parse integer"); return false; } else if (lval < 0 || lval > (long long)UINT32_MAX) { error_messagef(src, location, log_error, "integer outside of range 0-%u", UINT32_MAX); return false; } uint32_t val = lval; if (keys[i].deprecated_action) { keys[i].deprecated_action(ctx, &val); } else { memcpy(val_dest, &val, sizeof(uint32_t)); } break; } case type_str: { char *start, *end; start = strchr(v, '\''); end = strrchr(v, '\''); if (!start || !end || start == end) { error_messagef(src, location, log_error, "expected single-quoted string"); return false; } *end = 0; ++start; if (keys[i].deprecated_action) { keys[i].deprecated_action(ctx, &start); } else { memcpy(val_dest, &start, sizeof(char *)); } break; } case type_bool: { bool val; if (strcmp(v, "true") == 0) { val = true; } else if (strcmp(v, "false") == 0) { val = false; } else { error_messagef(src, location, log_error, "invalid value for bool, expected true/false"); return false; } if (keys[i].deprecated_action) { keys[i].deprecated_action(ctx, &val); } else { memcpy(val_dest, &val, sizeof(bool)); } break; } case type_enum: { assert(keys[i].enum_tbl); uint32_t j, val = 0; for (j = 0; keys[i].enum_tbl[j].name; ++j) { if (strcmp(v, keys[i].enum_tbl[j].name) == 0) { val = keys[i].enum_tbl[j].val; break; } } if (!keys[i].enum_tbl[j].name) { error_messagef(src, location, log_error, "invalid value for %s: %s", keys[i].name, v); return false; } if (keys[i].deprecated_action) { keys[i].deprecated_action(ctx, &val); } else { memcpy(val_dest, &val, sizeof(uint32_t)); } } } break; } if (!keys[i].name) { error_messagef(src, location, log_error, "unknown config key: %s", k); return false; } return true; } static enum fmt_end_of_line fmt_guess_line_endings(struct source *src) { uint32_t i; for (i = 0; i < src->len; ++i) { if (strncmp(&src->src[i], "\r\n", 2) == 0) { return fmt_end_of_line_crlf; } else if (src->src[i] == '\n') { return fmt_end_of_line_lf; } } return fmt_end_of_line_lf; } /******************************************************************************* * entrypoint ******************************************************************************/ void fmt_assemble_out_blocks(struct fmt_ctx *f) { uint32_t i; struct fmt_out_block *blocks = (struct fmt_out_block *)f->out_blocks.e; const struct str *s; const char *line, *line_end; struct str lstr; obj l; bool end_of_block; L("fmt output: "); const char *end_of_line = 0; switch ((enum fmt_end_of_line)f->opts.end_of_line) { case fmt_end_of_line_lf: end_of_line = "\n"; break; case fmt_end_of_line_crlf: end_of_line = "\r\n"; break; case fmt_end_of_line_cr: end_of_line = "\r"; break; default: UNREACHABLE; } for (i = 0; i < f->out_blocks.len; ++i) { s = get_str(f->wk, blocks[i].str); if (blocks[i].raw) { tstr_pushn(f->wk, f->out_buf, s->s, s->len); continue; } for (line = s->s; *line;) { lstr.s = line; line_end = strchr(line, '\n'); end_of_block = false; if (line_end == line) { tstr_pushs(f->wk, f->out_buf, end_of_line); ++line; continue; } else if (line_end) { lstr.len = line_end - line; } else { lstr.len = strlen(line); } end_of_block = !*(line + lstr.len); l = str_strip(f->wk, &lstr, 0, str_strip_flag_right_only); s = get_str(f->wk, l); tstr_pushn(f->wk, f->out_buf, s->s, s->len); bool last_line = end_of_block && i == f->out_blocks.len - 1; if (last_line) { if (f->opts.insert_final_newline) { tstr_pushs(f->wk, f->out_buf, end_of_line); } } else if (!end_of_block) { tstr_pushs(f->wk, f->out_buf, end_of_line); } if (!line_end) { break; } line = line_end + 1; } } } bool fmt(struct source *src, FILE *out, const char *cfg_path, bool check_only, bool editorconfig) { bool ret = false; struct tstr out_buf; struct workspace wk = { 0 }; workspace_init_bare(&wk); struct fmt_ctx f = { .wk = &wk, .out_buf = &out_buf, .fmt_on = true, .opts = { .max_line_len = 80, .indent_style = fmt_indent_style_space, .indent_size = 4, .tab_width = 8, .space_array = false, .kwargs_force_multiline = false, .wide_colon = false, .no_single_comma_function = false, .insert_final_newline = true, .end_of_line = fmt_guess_line_endings(src), .sort_files = true, .group_arg_value = true, .simplify_string_literals = false, .indent_before_comments = " ", .use_editor_config = true, .sticky_parens = false, .continuation_indent = false, }, }; bucket_arr_init(&f.frags, 1024, sizeof(struct fmt_frag)); arr_init(&f.out_blocks, 64, sizeof(struct fmt_out_block)); arr_init(&f.list_tmp, 64, sizeof(struct fmt_frag *)); if (editorconfig) { try_parse_editorconfig(src, &f.opts); } char *cfg_buf = NULL; struct source cfg_src = { 0 }; if (cfg_path) { if (!ini_parse(cfg_path, &cfg_src, &cfg_buf, fmt_cfg_parse_cb, &f)) { goto ret; } } enum vm_compile_mode compile_mode = vm_compile_mode_fmt; if (str_endswith(&STRL(src->label), &STR(".meson"))) { compile_mode |= vm_compile_mode_language_extended; } struct node *n; if (!(n = parse_fmt(&wk, src, compile_mode, &f.raw_blocks))) { goto ret; } tstr_init(&out_buf, 0, 0, 0); fmt_write_block(&f, fmt_block(&f, n)); fmt_push_out_block(&f); if (!check_only) { out_buf.flags = tstr_flag_write; out_buf.buf = (void *)out; } fmt_assemble_out_blocks(&f); if (check_only) { if (src->len != out_buf.len) { goto ret; } else if (memcmp(src->src, out_buf.buf, src->len)) { goto ret; } } ret = true; ret: workspace_destroy_bare(&wk); if (cfg_buf) { z_free(cfg_buf); } fs_source_destroy(&cfg_src); bucket_arr_destroy(&f.frags); arr_destroy(&f.out_blocks); arr_destroy(&f.list_tmp); return ret; } muon-v0.5.0/src/lang/object_iterators.c0000644000175000017500000000347515041716357017100 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/object_iterators.h" #include "lang/workspace.h" obj obj_array_flat_iter_next(struct workspace *wk, obj arr, struct obj_array_flat_iter_ctx *ctx) { obj v = 0; if (!ctx->init) { struct obj_array *a = get_obj_array(wk, arr); ctx->e = a->len ? bucket_arr_get(&wk->vm.objects.array_elems, a->head) : 0; ctx->pushed = 0; ctx->init = true; } while (ctx->e && !v) { v = ctx->e->val; while (get_obj_type(wk, v) == obj_array) { struct obj_array *a = get_obj_array(wk, v); struct obj_array_elem *e = 0; if (!a->len) { v = 0; goto skip; } e = bucket_arr_get(&wk->vm.objects.array_elems, a->head); v = e->val; stack_push(&wk->stack, ctx->e, e); ++ctx->pushed; } #if 0 Note: Below was taken from the original implementation of obj_array_foreach_flat. May want to integrate the typeinfo handling at some point. if (get_obj_type(wk, val) == obj_array) { if (!obj_array_foreach(wk, val, ctx, obj_array_foreach_flat_iter)) { return ir_err; } else { return ir_cont; } } else if (get_obj_type(wk, val) == obj_typeinfo && get_obj_typeinfo(wk, val)->type == tc_array) { // skip typeinfo arrays as they wouldn't be yielded if they // were real arrays return ir_cont; } else { return ctx->cb(wk, ctx->usr_ctx, val); } #endif skip: while (!ctx->e->next && ctx->pushed) { stack_pop(&wk->stack, ctx->e); --ctx->pushed; } if (ctx->e->next) { ctx->e = bucket_arr_get(&wk->vm.objects.array_elems, ctx->e->next); } else { ctx->e = 0; } } return v; } void obj_array_flat_iter_end(struct workspace *wk, struct obj_array_flat_iter_ctx *ctx) { while (ctx->pushed) { stack_pop(&wk->stack, ctx->e); --ctx->pushed; } } muon-v0.5.0/src/lang/string.c0000644000175000017500000010024015041716357015030 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "buf_size.h" #include "error.h" #include "lang/object.h" #include "lang/object_iterators.h" #include "lang/string.h" #include "lang/typecheck.h" #include "lang/workspace.h" #include "log.h" #include "memmem.h" #include "platform/assert.h" #include "platform/mem.h" void str_escape(struct workspace *wk, struct tstr *sb, const struct str *ss, bool escape_printable) { bool esc; uint32_t i; for (i = 0; i < ss->len; ++i) { esc = ss->s[i] < 32 || ss->s[i] == '\'' || ss->s[i] == '\\'; if (!escape_printable && strchr("\t\n\r'", ss->s[i])) { esc = false; } if (esc) { if (ss->s[i] == '\'') { tstr_pushf(wk, sb, "\\'"); } else if (ss->s[i] == '\\') { tstr_pushf(wk, sb, "\\\\"); } else if (7 <= ss->s[i] && ss->s[i] <= 13) { tstr_pushf(wk, sb, "\\%c", "abtnvfr"[ss->s[i] - 7]); } else { tstr_pushf(wk, sb, "\\%d", ss->s[i]); } } else { tstr_push(wk, sb, ss->s[i]); } } } void str_escape_json(struct workspace *wk, struct tstr *sb, const struct str *ss) { uint32_t i; const char *esc = "\"\\"; for (i = 0; i < ss->len; ++i) { if (strchr(esc, ss->s[i])) { tstr_pushf(wk, sb, "\\\""); } else if (ss->s[i] < 32 || ss->s[i] > 126) { if (8 <= ss->s[i] && ss->s[i] <= 13 && ss->s[i] != 11) { tstr_pushf(wk, sb, "\\%c", "btn_fr"[ss->s[i] - 8]); } else { tstr_pushf(wk, sb, "\\u%04x", ss->s[i]); } } else { tstr_push(wk, sb, ss->s[i]); } } } bool str_has_null(const struct str *ss) { uint32_t i; for (i = 0; i < ss->len; ++i) { if (!ss->s[i]) { return true; } } return false; } const char * get_cstr(struct workspace *wk, obj s) { if (!s) { return NULL; } const struct str *ss = get_str(wk, s); if (str_has_null(ss)) { error_unrecoverable("cstr can not contain null bytes"); } return ss->s; } static struct str * reserve_str(struct workspace *wk, obj *s, uint32_t len) { enum str_flags f = 0; const char *p; uint32_t new_len = len + 1; if (new_len > wk->vm.objects.chrs.bucket_size) { f |= str_flag_big; p = z_calloc(new_len, 1); } else { p = bucket_arr_pushn(&wk->vm.objects.chrs, NULL, 0, new_len); } *s = make_obj(wk, obj_string); struct str *str = (struct str *)get_str(wk, *s); *str = (struct str){ .s = p, .len = len, .flags = f, }; return str; } static struct str * grow_str(struct workspace *wk, obj *s, uint32_t grow_by, bool alloc_nul) { assert(s); struct str *ss = (struct str *)get_str(wk, *s); uint32_t new_len = ss->len + grow_by; if (!(ss->flags & str_flag_mutable)) { struct str *newstr = reserve_str(wk, s, new_len); newstr->flags |= str_flag_mutable; newstr->len = ss->len; memcpy((void *)newstr->s, ss->s, ss->len); return newstr; } if (alloc_nul) { new_len += 1; } if (ss->flags & str_flag_big) { ss->s = z_realloc((void *)ss->s, new_len); memset((void *)&ss->s[ss->len], 0, new_len - ss->len); } else if (new_len >= wk->vm.objects.chrs.bucket_size) { ss->flags |= str_flag_big; char *np = z_calloc(new_len, 1); memcpy(np, ss->s, ss->len); ss->s = np; } else { char *np = bucket_arr_pushn(&wk->vm.objects.chrs, ss->s, ss->len, new_len); ss->s = np; } return ss; } #define SMALL_STR_LEN 64 static obj _make_str(struct workspace *wk, const char *p, uint32_t len, enum str_flags flags, bool hash) { obj s; if (!p) { return 0; } uint64_t *v; if (hash && len <= SMALL_STR_LEN && (v = hash_get_strn(&wk->vm.objects.str_hash, p, len))) { s = *v; return s; } struct str *str = reserve_str(wk, &s, len); memcpy((void *)str->s, p, len); str->flags |= flags; if (hash && !wk->vm.objects.obj_clear_mark_set && len <= SMALL_STR_LEN) { hash_set_strn(&wk->vm.objects.str_hash, str->s, str->len, s); } return s; } bool str_enum_add_type(struct workspace *wk, uint32_t id, obj *res) { if (!obj_dict_geti(wk, wk->vm.objects.enums.types, id, res)) { *res = make_obj(wk, obj_dict); obj_dict_set(wk, *res, make_str(wk, ""), make_obj(wk, obj_array)); obj_dict_seti(wk, wk->vm.objects.enums.types, id, *res); return true; } return false; } void str_enum_add_type_value(struct workspace *wk, obj type, const char *value) { obj values; if (!obj_dict_index_str(wk, type, "", &values)) { UNREACHABLE; } obj v = make_str_enum(wk, value, values); obj_array_push(wk, values, v); obj_dict_set(wk, type, v, v); } obj str_enum_get(struct workspace *wk, obj type, const char *name) { obj res; if (!obj_dict_index_str(wk, type, name, &res)) { UNREACHABLE; } return res; } obj mark_typeinfo_as_enum(struct workspace *wk, obj ti, obj values) { obj_dict_seti(wk, wk->vm.objects.enums.values, ti, values); return ti; } obj make_strn_enum(struct workspace *wk, const char *str, uint32_t n, obj values) { obj s = _make_str(wk, str, n, 0, false); obj_dict_seti(wk, wk->vm.objects.enums.values, s, values); return s; } obj make_str_enum(struct workspace *wk, const char *str, obj values) { return make_strn_enum(wk, str, strlen(str), values); } bool check_str_enum(struct workspace *wk, obj l, enum obj_type l_t, obj r, enum obj_type r_t) { // Only run this check in the analyzer? if (!wk->vm.in_analyzer) { return true; } enum obj_type c_t; obj values = 0, c = 0; if (l_t == obj_typeinfo) { type_tag t = get_obj_typeinfo(wk, l)->type; if (!(t & TYPE_TAG_COMPLEX)) { return true; } if (COMPLEX_TYPE_TYPE(t) == complex_type_preset) { t = complex_type_preset_get(wk, COMPLEX_TYPE_INDEX(t)); } if (COMPLEX_TYPE_TYPE(t) != complex_type_enum) { return true; } c = r; c_t = r_t; values = COMPLEX_TYPE_INDEX(t); } else if (obj_dict_geti(wk, wk->vm.objects.enums.values, l, &values)) { c = r; c_t = r_t; } else if (r_t == obj_string && obj_dict_geti(wk, wk->vm.objects.enums.values, r, &values)) { c = l; c_t = obj_string; } else { return true; } if (c_t == obj_string) { if (!obj_array_in(wk, values, c)) { vm_warning(wk, "%o is not one of %o", c, values); return false; } } else if (c_t == obj_array) { obj v; obj_array_for(wk, c, v) { if (!obj_array_in(wk, values, v)) { vm_warning(wk, "%o is not one of %o", v, values); return false; } } } else if (c_t == obj_dict) { obj k, _v; obj_dict_for(wk, c, k, _v) { (void)_v; if (!obj_array_in(wk, values, k)) { vm_warning(wk, "%o is not one of %o", k, values); return false; } } } return true; } obj make_strn(struct workspace *wk, const char *str, uint32_t n) { return _make_str(wk, str, n, 0, true); } obj make_str(struct workspace *wk, const char *str) { return _make_str(wk, str, strlen(str), 0, true); } obj make_strfv(struct workspace *wk, const char *fmt, va_list args) { uint32_t len; va_list args_copy; va_copy(args_copy, args); len = vsnprintf(NULL, 0, fmt, args_copy); va_end(args_copy); obj s; struct str *ss = reserve_str(wk, &s, len); // TODO: the buffer size is too small here because the object expansion // isn't taken in to account by vsnprintf above. Need to make it // possible to pass NULL to obj_vsnprintf to get a reliable buffer // length /* obj_vsnprintf(wk, (char *)ss->s, len + 1, fmt, args); */ vsnprintf((char *)ss->s, len + 1, fmt, args); return s; } obj make_strf(struct workspace *wk, const char *fmt, ...) { va_list args; va_start(args, fmt); obj s = make_strfv(wk, fmt, args); va_end(args); return s; } void str_appn(struct workspace *wk, obj *s, const char *str, uint32_t n) { struct str *ss = grow_str(wk, s, n, true); memcpy((char *)&ss->s[ss->len], str, n); ss->len += n; } void str_apps(struct workspace *wk, obj *s, obj s_id) { const struct str *str = get_str(wk, s_id); str_appn(wk, s, str->s, str->len); } void str_app(struct workspace *wk, obj *s, const char *str) { str_appn(wk, s, str, strlen(str)); } void str_appf(struct workspace *wk, obj *s, const char *fmt, ...) { uint32_t len; va_list args, args_copy; va_start(args, fmt); va_copy(args_copy, args); len = vsnprintf(NULL, 0, fmt, args_copy); uint32_t olen = get_str(wk, *s)->len; struct str *ss = grow_str(wk, s, len, true); /* obj_vsnprintf(wk, (char *)ss->s, len + 1, fmt, args); */ vsnprintf((char *)&ss->s[olen], len + 1, fmt, args); ss->len += len; va_end(args_copy); va_end(args); } obj str_clone_mutable(struct workspace *wk, obj val) { const struct str *ss = get_str(wk, val); return _make_str(wk, ss->s, ss->len, str_flag_mutable, false); } obj str_clone(struct workspace *wk_src, struct workspace *wk_dest, obj val) { const struct str *ss = get_str(wk_src, val); return _make_str(wk_dest, ss->s, ss->len, 0, true); } bool str_eql(const struct str *ss1, const struct str *ss2) { return ss1->len == ss2->len && memcmp(ss1->s, ss2->s, ss1->len) == 0; } static bool str_eql_glob_impl(const struct str *ss1, const struct str *ss2, uint32_t *match_len, bool top) { if (!ss1->len && !top) { *match_len = ss2->len; return true; } if (!ss2->len) { *match_len = 0; return true; } *match_len = 0; uint32_t i1 = 0, i2 = 0; for (i1 = 0; i1 < ss1->len; ++i1) { if (ss1->s[i1] == '*') { struct str sub1 = { .s = ss1->s + (i1 + 1), .len = ss1->len - (i1 + 1) }, sub2 = { .s = ss2->s + i2, .len = ss2->len - i2 }; uint32_t sub_match_len, sub_match_consumed = 0; while (!str_eql_glob_impl(&sub1, &sub2, &sub_match_len, false)) { ++i2; ++sub_match_consumed; sub2 = (struct str){ .s = ss2->s + i2, .len = ss2->len - i2 }; } if (sub_match_len) { sub_match_len += sub_match_consumed; } *match_len += sub_match_len; i1 += sub_match_len; } else if (ss1->s[i1] == ss2->s[i2]) { ++*match_len; ++i2; } else { return false; } } return true; } bool str_eql_glob(const struct str *ss1, const struct str *ss2) { uint32_t match_len; return str_eql_glob_impl(ss1, ss2, &match_len, true) && match_len == ss2->len; } static uint8_t str_char_to_lower(uint8_t c) { if ('A' <= c && c <= 'Z') { return c + 32; } return c; } void str_to_lower(struct str *str) { uint32_t i; char *s = (char *)str->s; for (i = 0; i < str->len; ++i) { s[i] = str_char_to_lower(str->s[i]); } } bool str_eqli(const struct str *ss1, const struct str *ss2) { if (ss1->len != ss2->len) { return false; } uint32_t i; for (i = 0; i < ss1->len; ++i) { if (str_char_to_lower(ss1->s[i]) != str_char_to_lower(ss2->s[i])) { return false; } } return true; } bool str_startswith(const struct str *ss, const struct str *pre) { if (ss->len < pre->len) { return false; } return memcmp(ss->s, pre->s, pre->len) == 0; } bool str_startswithi(const struct str *ss, const struct str *pre) { if (ss->len < pre->len) { return false; } uint32_t i; for (i = 0; i < pre->len; ++i) { if (str_char_to_lower(ss->s[i]) != str_char_to_lower(pre->s[i])) { return false; } } return true; } bool str_endswith(const struct str *ss, const struct str *suf) { if (ss->len < suf->len) { return false; } return memcmp(&ss->s[ss->len - suf->len], suf->s, suf->len) == 0; } bool str_endswithi(const struct str *ss, const struct str *suf) { if (ss->len < suf->len) { return false; } uint32_t i; for (i = 0; i < suf->len; ++i) { if (str_char_to_lower(ss->s[ss->len - i - 1]) != str_char_to_lower(suf->s[suf->len - i - 1])) { return false; } } return true; } bool str_contains(const struct str *str, const struct str *substr) { return !!memmem(str->s, str->len, substr->s, substr->len); } bool str_containsi(const struct str *str, const struct str *substr) { if (substr->len > str->len) { return false; } else if (substr->len == str->len) { return str_eqli(str, substr); } uint32_t i; for (i = 0; i < str->len - substr->len; ++i) { struct str a = { .s = str->s + i, .len = substr->len }; if (str_eqli(&a, substr)) { return true; } } return false; } obj str_join(struct workspace *wk, obj s1, obj s2) { obj res; const struct str *ss1 = get_str(wk, s1), *ss2 = get_str(wk, s2); struct str *ss = reserve_str(wk, &res, ss1->len + ss2->len); memcpy((char *)ss->s, ss1->s, ss1->len); memcpy((char *)&ss->s[ss1->len], ss2->s, ss2->len); return res; } bool is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } bool is_whitespace_except_newline(char c) { return c == ' ' || c == '\t' || c == '\r'; } bool str_to_i_base(const struct str *ss, int64_t *res, bool strip, uint32_t base) { char *endptr = NULL; const char *start = ss->s; // HACK: casting away const-ness char *end = (char *)ss->s + ss->len; if (strip) { while (is_whitespace(*start)) { ++start; } } // We need ss to be null terminated. For example given the string // "this%20file" and a ss pointing at the 20 in the middle, we want to be // able to convert that 20 alone and not have the trailing f count as a 3rd // digit (hexadecimal). char old_end = *end; *end = 0; *res = strtoll(start, &endptr, base); *end = old_end; if (strip) { while (is_whitespace(*endptr)) { ++endptr; } } if ((uint32_t)(endptr - ss->s) != ss->len) { return false; } return true; } bool str_to_i(const struct str *ss, int64_t *res, bool strip) { return str_to_i_base(ss, res, strip, 10); } obj str_split(struct workspace *wk, const struct str *ss, const struct str *split) { obj res; res = make_obj(wk, obj_array); uint32_t i, start = 0; obj s; for (i = 0; i < ss->len; ++i) { if (split) { struct str slice = { .s = &ss->s[i], .len = ss->len - i }; if (str_startswith(&slice, split)) { s = make_strn(wk, &ss->s[start], i - start); obj_array_push(wk, res, s); start = i + split->len; i += split->len - 1; } } else { start = i; while (start < ss->len && is_whitespace(ss->s[start])) { ++start; } uint32_t end = start; while (end < ss->len && !is_whitespace(ss->s[end])) { ++end; } if (end > start) { obj_array_push(wk, res, make_strn(wk, &ss->s[start], end - start)); } i = end - 1; } } if (split) { s = make_strn(wk, &ss->s[start], i - start); obj_array_push(wk, res, s); } return res; } obj str_splitlines(struct workspace *wk, const struct str *ss) { const struct str seps[] = { STR("\n"), STR("\r\n"), STR("\r") }; const struct str *split; obj res; res = make_obj(wk, obj_array); if (!ss->len) { return res; } uint32_t i, j, start = 0; obj s; for (i = 0; i < ss->len; ++i) { struct str slice = { .s = &ss->s[i], .len = ss->len - i }; for (j = 0; j < ARRAY_LEN(seps); ++j) { split = &seps[j]; if (str_startswith(&slice, split)) { s = make_strn(wk, &ss->s[start], i - start); obj_array_push(wk, res, s); start = i + split->len; i += split->len - 1; break; } } } // Only push the final element if not empty if (i != start) { s = make_strn(wk, &ss->s[start], i - start); obj_array_push(wk, res, s); } return res; } bool str_split_in_two(const struct str *s, struct str *l, struct str *r, char split) { const char *p; if (!(p = memchr(s->s, split, s->len))) { return false; } *l = (struct str){ s->s, p - s->s }; *r = (struct str){ s->s + l->len + 1, s->len - (l->len + 1) }; return true; } static bool str_has_chr(char c, const struct str *ss) { uint32_t i; for (i = 0; i < ss->len; ++i) { if (ss->s[i] == c) { return true; } } return false; } obj str_strip(struct workspace *wk, const struct str *ss, const struct str *strip, enum str_strip_flag flags) { const struct str *defstrip = &STR(" \r\n\t"); if (!strip) { strip = defstrip; } uint32_t i = 0; int32_t len; if (!(flags & str_strip_flag_right_only)) { for (; i < ss->len; ++i) { if (!str_has_chr(ss->s[i], strip)) { break; } } } for (len = ss->len - 1; len >= 0; --len) { if (len < (int64_t)i) { break; } if (!str_has_chr(ss->s[len], strip)) { break; } } ++len; assert((int64_t)len >= (int64_t)i); return make_strn(wk, &ss->s[i], len - i); } struct str_split_strip_ctx { const struct str *strip; obj res; }; static enum iteration_result str_split_strip_iter(struct workspace *wk, void *_ctx, obj v) { struct str_split_strip_ctx *ctx = _ctx; obj_array_push(wk, ctx->res, str_strip(wk, get_str(wk, v), ctx->strip, 0)); return ir_cont; } obj str_split_strip(struct workspace *wk, const struct str *ss, const struct str *split, const struct str *strip) { struct str_split_strip_ctx ctx = { .strip = strip, }; ctx.res = make_obj(wk, obj_array); obj_array_foreach(wk, str_split(wk, ss, split), &ctx, str_split_strip_iter); return ctx.res; } /* tstr */ void tstr_init(struct tstr *sb, char *initial_buffer, uint32_t initial_buffer_cap, enum tstr_flags flags) { // If we don't get passed an initial buffer, initial_buffer_cap must be // zero so that the first write to this buf triggers an allocation. As // a convenience, ensure the buf points to a valid empty string so that // callers don't have to always check len before trying to read buf. if (!initial_buffer) { assert(initial_buffer_cap == 0); initial_buffer = ""; } if (initial_buffer_cap) { initial_buffer[0] = 0; } *sb = (struct tstr){ .flags = flags, .buf = initial_buffer, .cap = initial_buffer_cap, }; } void tstr_destroy(struct tstr *sb) { if ((sb->flags & tstr_flag_overflown) && (sb->flags & tstr_flag_overflow_alloc)) { if (sb->buf) { z_free(sb->buf); sb->buf = 0; } } } void tstr_clear(struct tstr *sb) { if ((sb->flags & tstr_flag_write)) { return; } memset(sb->buf, 0, sb->len); sb->len = 0; } void tstr_grow(struct workspace *wk, struct tstr *sb, uint32_t inc) { uint32_t newcap, newlen = sb->len + inc; if (newlen < sb->cap) { return; } newcap = sb->cap; if (!newcap) { newcap = 1024; } do { newcap *= 2; } while (newcap < newlen); if (sb->flags & tstr_flag_overflown) { if (sb->flags & tstr_flag_overflow_alloc) { sb->buf = z_realloc(sb->buf, newcap); memset((void *)&sb->buf[sb->len], 0, newcap - sb->cap); } else { grow_str(wk, &sb->s, newcap - sb->cap, false); struct str *ss = (struct str *)get_str(wk, sb->s); sb->buf = (char *)ss->s; ss->len = newcap; } } else { if (sb->flags & tstr_flag_overflow_error) { error_unrecoverable("unhandled tstr overflow: " "capacity: %d, length: %d, " "trying to push %d bytes", sb->cap, sb->len, inc); } sb->flags |= tstr_flag_overflown; char *obuf = sb->buf; if (sb->flags & tstr_flag_overflow_alloc) { sb->buf = z_calloc(newcap, 1); } else { reserve_str(wk, &sb->s, newcap); struct str *ss = (struct str *)get_str(wk, sb->s); ss->flags |= str_flag_mutable; sb->buf = (char *)ss->s; assert(ss->len == newcap); } if (obuf) { memcpy(sb->buf, obuf, sb->len); } } sb->cap = newcap; } void tstr_push(struct workspace *wk, struct tstr *sb, char s) { if (sb->flags & tstr_flag_write) { FILE *out = (FILE *)sb->buf; if (fputc(s, out) == EOF) { error_unrecoverable("failed to write output to file"); } return; } tstr_grow(wk, sb, 2); sb->buf[sb->len] = s; sb->buf[sb->len + 1] = 0; ++sb->len; } void tstr_pushn(struct workspace *wk, struct tstr *sb, const char *s, uint32_t n) { if (sb->flags & tstr_flag_write) { FILE *out = (FILE *)sb->buf; if (!fs_fwrite(s, n, out)) { error_unrecoverable("failed to write output to file"); } return; } if (!n) { return; } tstr_grow(wk, sb, n + 1); memcpy(&sb->buf[sb->len], s, n); sb->buf[sb->len + n] = 0; sb->len += n; } void tstr_pushs(struct workspace *wk, struct tstr *sb, const char *s) { if (sb->flags & tstr_flag_write) { FILE *out = (FILE *)sb->buf; if (fputs(s, out) == EOF) { error_unrecoverable("failed to write output to file"); } return; } uint32_t n = strlen(s) + 1; if (n < 2) { return; } tstr_grow(wk, sb, n); memcpy(&sb->buf[sb->len], s, n); sb->len += n - 1; } void tstr_vpushf(struct workspace *wk, struct tstr *sb, const char *fmt, va_list args) { uint32_t len; if (sb->flags & tstr_flag_write) { if (vfprintf((FILE *)sb->buf, fmt, args) < 0) { error_unrecoverable("failed to write output to file"); } return; } va_list args_copy; va_copy(args_copy, args); len = vsnprintf(NULL, 0, fmt, args_copy); tstr_grow(wk, sb, len); vsnprintf(&sb->buf[sb->len], len + 1, fmt, args); sb->len += len; va_end(args_copy); } void tstr_pushf(struct workspace *wk, struct tstr *sb, const char *fmt, ...) { va_list args; va_start(args, fmt); tstr_vpushf(wk, sb, fmt, args); va_end(args); } void tstr_push_json_escaped(struct workspace *wk, struct tstr *buf, const char *str, uint32_t len) { uint32_t i; for (i = 0; i < len; ++i) { switch (str[i]) { case '\b': tstr_pushs(wk, buf, "\\b"); break; case '\f': tstr_pushs(wk, buf, "\\f"); break; case '\n': tstr_pushs(wk, buf, "\\n"); break; case '\r': tstr_pushs(wk, buf, "\\r"); break; case '\t': tstr_pushs(wk, buf, "\\t"); break; case '"': tstr_pushs(wk, buf, "\\\""); break; case '\\': tstr_pushs(wk, buf, "\\\\"); break; default: { if (str[i] < ' ') { tstr_pushf(wk, buf, "\\u%04x", str[i]); } else { tstr_push(wk, buf, str[i]); } break; } } } } void tstr_push_json_escaped_quoted(struct workspace *wk, struct tstr *buf, const struct str *str) { tstr_push(wk, buf, '"'); tstr_push_json_escaped(wk, buf, str->s, str->len); tstr_push(wk, buf, '"'); } obj tstr_into_str(struct workspace *wk, struct tstr *sb) { assert(!(sb->flags & tstr_flag_string_exposed)); if (!(sb->flags & tstr_flag_overflow_alloc) && sb->flags & tstr_flag_overflown) { sb->flags |= tstr_flag_string_exposed; struct str *ss = (struct str *)get_str(wk, sb->s); assert(strlen(sb->buf) == sb->len); ss->len = sb->len; return sb->s; } else if (!sb->len) { return make_str(wk, ""); } else { return make_strn(wk, sb->buf, sb->len); } } void tstr_trim_trailing_newline(struct tstr *sb) { if (sb->buf[sb->len - 1] == '\n') { --sb->len; sb->buf[sb->len] = 0; } if (sb->len) { if (sb->buf[sb->len - 1] == '\r') { --sb->len; sb->buf[sb->len] = 0; } } } void cstr_copy_(char *dest, const struct str *src, uint32_t dest_len) { uint32_t src_len = src->len + 1; assert(src_len <= dest_len); memcpy(dest, src->s, src_len); } void snprintf_append_(char *buf, uint32_t buf_len, uint32_t *buf_i, const char *fmt, ...) { va_list args; if (*buf_i >= buf_len) { return; } va_start(args, fmt); *buf_i += vsnprintf(buf + *buf_i, buf_len - *buf_i, fmt, args); va_end(args); } /* Shlex-like string splitting * * Reference: * - https://docs.python.org/3/library/shlex.html * - https://github.com/python/cpython/blob/main/Lib/shlex.py * - https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html * for cmd: * - https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments * */ struct shlex_ctx { const struct str str; uint32_t i; char c; }; static void shlex_advance(struct workspace *wk, struct shlex_ctx *ctx) { if (ctx->i > ctx->str.len) { return; } ++ctx->i; ctx->c = ctx->str.s[ctx->i]; } static obj shlex_cmd_next(struct workspace *wk, struct shlex_ctx *ctx) { // Arguments are delimited by whitespace characters, which are either spaces or tabs. while (is_whitespace(ctx->c)) { shlex_advance(wk, ctx); } if (!ctx->c) { return 0; } TSTR(tok); char quote = 0; uint32_t i, slashes; if (ctx->c == '"') { quote = '"'; shlex_advance(wk, ctx); } while (ctx->c) { // A double quote mark preceded by a backslash (\") is interpreted // as a literal double quote mark ("). // // Backslashes are interpreted literally, unless they immediately // precede a double quote mark. slashes = 0; while (ctx->c == '\\') { ++slashes; shlex_advance(wk, ctx); } if (slashes) { if (ctx->c != '"') { for (i = 0; i < slashes; ++i) { tstr_push(wk, &tok, '\\'); } } } if (!ctx->c) { goto done; } switch (ctx->c) { case ' ': case '\t': { if (quote) { tstr_push(wk, &tok, ctx->c); } else { goto done; } break; } case '"': { // A string surrounded by double quote marks is interpreted as a // single argument, whether it contains whitespace characters or // not. A quoted string can be embedded in an argument. The caret // (^) isn't recognized as an escape character or delimiter. Within // a quoted string, a pair of double quote marks is interpreted as // a single escaped double quote mark. If the command line ends // before a closing double quote mark is found, then all the // characters read so far are output as the last argument. if (slashes && !(slashes & 1)) { // If an even number of backslashes is followed by a double // quote mark, then one backslash (\) is placed in the argv // array for every pair of backslashes (\\), and the double // quote mark (") is interpreted as a string delimiter. for (i = 0; i < slashes / 2; ++i) { tstr_push(wk, &tok, '\\'); } if (is_whitespace(ctx->str.s[ctx->i + 1])) { shlex_advance(wk, ctx); goto done; } else { quote = '"'; } } else if (slashes && (slashes & 1)) { // If an odd number of backslashes is followed by a double // quote mark, then one backslash (\) is placed in the argv // array for every pair of backslashes (\\). The double quote // mark is interpreted as an escape sequence by the remaining // backslash, causing a literal double quote mark (") to be // placed in argv. for (i = 0; i < slashes / 2; ++i) { tstr_push(wk, &tok, '\\'); } tstr_push(wk, &tok, '"'); } else if (ctx->str.s[ctx->i + 1] == '"') { shlex_advance(wk, ctx); tstr_push(wk, &tok, '"'); } else if (quote) { shlex_advance(wk, ctx); goto done; } break; } default: { tstr_push(wk, &tok, ctx->c); break; } } shlex_advance(wk, ctx); } done: return tstr_into_str(wk, &tok); } static obj shlex_posix_next(struct workspace *wk, struct shlex_ctx *ctx) { while (is_whitespace(ctx->c)) { shlex_advance(wk, ctx); } if (!ctx->c) { return 0; } TSTR(tok); char quote = 0; while (ctx->c) { switch (ctx->c) { case '#': { // If the current character is a '#', it and all subsequent // characters up to, but excluding, the next shall be // discarded as a comment. The that ends the line is not // considered part of the comment. if (quote) { tstr_push(wk, &tok, ctx->c); } else { while (ctx->c && ctx->c != '\n') { shlex_advance(wk, ctx); } continue; } break; } case ' ': case '\t': case '\n': { if (quote) { tstr_push(wk, &tok, ctx->c); } else { goto done; } break; } case '\\': { shlex_advance(wk, ctx); // 2.2.1 Escape Character (Backslash) // A that is not quoted shall preserve the literal // value of the following character, with the exception of a // . if (quote == '\'') { tstr_push(wk, &tok, '\\'); tstr_push(wk, &tok, ctx->c); } else if (quote == '"') { // Outside of "$(...)" and "${...}" the shall // retain its special meaning as an escape character (see 2.2.1 // Escape Character (Backslash)) only when immediately followed // by one of the following characters: // $ ` \ // or by a double-quote character that would otherwise be // considered special (see 2.6.4 Arithmetic Expansion and 2.7.4 // Here-Document). if (strchr("$`\\\n\"", ctx->c)) { tstr_push(wk, &tok, ctx->c); } else { tstr_push(wk, &tok, '\\'); tstr_push(wk, &tok, ctx->c); } } else if (ctx->c == '\n') { // If a immediately follows the , // the shell shall interpret this as line continuation. The // and shall be removed before splitting the // input into tokens. } else { tstr_push(wk, &tok, ctx->c); } break; } case '\'': { // 2.2.2 Single-Quotes // Enclosing characters in single-quotes ('') shall preserve the // literal value of each character within the single-quotes. A // single-quote cannot occur within single-quotes. if (!quote) { quote = '\''; } else if (quote == '\'') { quote = 0; } else if (quote == '\"') { tstr_push(wk, &tok, ctx->c); } break; } case '"': { // 2.2.3 Double-Quotes // Enclosing characters in double-quotes ("") shall preserve the // literal value of all characters within the double-quotes, with // the exception of the characters backquote, , and // , as follows: if (!quote) { quote = '\"'; } else if (quote == '\"') { quote = 0; } else if (quote == '\'') { tstr_push(wk, &tok, ctx->c); } break; } default: { tstr_push(wk, &tok, ctx->c); break; } } shlex_advance(wk, ctx); } done: return tstr_into_str(wk, &tok); } enum shell_type shell_type_for_host_machine(void) { if (host_machine.is_windows) { return shell_type_cmd; } else { return shell_type_posix; } } obj str_shell_split(struct workspace *wk, const struct str *str, enum shell_type shell) { obj tok, res = make_obj(wk, obj_array); struct shlex_ctx ctx = { .str = *str, .c = str->s[0] }; obj (*lex_func)(struct workspace *wk, struct shlex_ctx *ctx) = 0; switch (shell) { case shell_type_posix: lex_func = shlex_posix_next; break; case shell_type_cmd: lex_func = shlex_cmd_next; break; } while ((tok = lex_func(wk, &ctx))) { obj_array_push(wk, res, tok); } return res; } static int32_t min3(int32_t a, int32_t b, int32_t c) { int32_t min = a; if (b < min) { min = b; } if (c < min) { min = c; } return min; } #define LEVENSHTEIN_MAX_COMPARE_LEN 256 static int32_t str_levenshtein_distance(const struct str *a, const struct str *b) { int32_t *v0, *v1, _v0[LEVENSHTEIN_MAX_COMPARE_LEN] = { 0 }, _v1[LEVENSHTEIN_MAX_COMPARE_LEN] = { 0 }; v0 = _v0; v1 = _v1; int32_t m = a->len + 1, n = b->len + 1; if (m > LEVENSHTEIN_MAX_COMPARE_LEN) { m = LEVENSHTEIN_MAX_COMPARE_LEN; } if (n > LEVENSHTEIN_MAX_COMPARE_LEN) { n = LEVENSHTEIN_MAX_COMPARE_LEN; } for (int32_t i = 0; i < n; ++i) { v0[i] = i; } for (int32_t i = 0; i < m - 1; ++i) { v1[0] = i + 1; for (int32_t j = 0; j < n - 1; ++j) { int32_t deletionCost = v0[j + 1] + 1; int32_t insertionCost = v1[j] + 1; int32_t substitutionCost; if (str_char_to_lower(a->s[i]) == str_char_to_lower(b->s[j])) { substitutionCost = v0[j]; } else { substitutionCost = v0[j] + 1; } v1[j + 1] = min3(deletionCost, insertionCost, substitutionCost); } if (v0 == _v0) { v0 = _v1; v1 = _v0; } else { v0 = _v0; v1 = _v1; } } return v0[n - 1]; } #define JARO_WINKLER_MAX_COMPARE_LEN 64 static double str_jaro_winkler_distance(const struct str *s1, const struct str *s2) { if (s1->len > s2->len) { const struct str *s3 = s1; s1 = s2; s2 = s3; } int32_t length1 = s1->len, length2 = s2->len; if (length1 > JARO_WINKLER_MAX_COMPARE_LEN) { length1 = JARO_WINKLER_MAX_COMPARE_LEN; } if (length2 > JARO_WINKLER_MAX_COMPARE_LEN) { length2 = JARO_WINKLER_MAX_COMPARE_LEN; } double m = 0, t = 0; int32_t range = length1 > 3 ? length2 / 2 - 1 : 0; uint64_t flags1 = 0, flags2 = 0; for (int32_t i = 0; i < length1; ++i) { int32_t last = i + range; for (int32_t j = i >= range ? i - range : 0; j < last; ++j) { if (!(flags2 & (1 << j)) && str_char_to_lower(s1->s[i]) == str_char_to_lower(s2->s[j])) { flags1 |= 1 << i; flags2 |= 1 << j; m += 1; break; } } } if (m == 0.0) { return m; } int32_t k = 0; for (int32_t i = 0; i < length1; ++i) { if ((flags1 & (1 << i))) { int32_t index = k; for (int32_t j = k; j < length2; ++j) { index = j; if ((flags2 & (1 << j))) { k = j + 1; break; } } if (str_char_to_lower(s1->s[i]) != str_char_to_lower(s2->s[index])) { t += 0.5; } } } double dist = m == 0 ? 0 : (m / length1 + m / length2 + (m - t) / m) / 3; if (dist > 0.7) { double prefix_bonus = 0.0; for (int32_t i = 0; i < length1 && i < 4; ++i) { if (str_char_to_lower(s1->s[i]) == str_char_to_lower(s2->s[i])) { prefix_bonus += 0.25; } else { break; } } dist += prefix_bonus * (1 - dist); } return dist; } bool str_fuzzy_match(const struct str *input, const struct str *guess, int32_t *dist) { { double threshold = input->len > 3 ? 0.834 : 0.77; if (str_jaro_winkler_distance(input, guess) < threshold) { return false; } } { int32_t threshold = (input->len * 0.25) + 0.5; if ((*dist = str_levenshtein_distance(input, guess)) > threshold) { return false; } } return true; } muon-v0.5.0/src/lang/eval.c0000644000175000017500000003244115041716357014460 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "error.h" #include "external/readline.h" #include "functions/modules.h" #include "lang/compiler.h" #include "lang/eval.h" #include "lang/parser.h" #include "log.h" #include "options.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/path.h" #include "tracy.h" #include "wrap.h" bool eval_project(struct workspace *wk, const char *subproject_name, const char *cwd, const char *build_dir, uint32_t *proj_id) { bool ret = false; uint32_t parent_project = wk->cur_project; make_project(wk, &wk->cur_project, subproject_name, cwd, build_dir); *proj_id = wk->cur_project; stack_push(&wk->stack, wk->vm.scope_stack, current_project(wk)->scope_stack); obj parent_eval_trace = wk->vm.dbg_state.eval_trace; if (wk->cur_project > 0) { log_set_prefix(2); } if (subproject_name && !wk->vm.in_analyzer) { L("entering subproject '%s'", subproject_name); } enum build_language lang; const char *build_file = determine_build_file(wk, cwd, &lang, false); if (!build_file) { goto cleanup; } if (!setup_project_options(wk, cwd)) { goto cleanup; } wk->vm.dbg_state.eval_trace_subdir = true; if (!wk->vm.behavior.eval_project_file(wk, build_file, lang, eval_project_file_flag_first, 0)) { goto cleanup; } if (wk->cur_project == 0 && !check_invalid_subproject_option(wk)) { goto cleanup; } ret = true; cleanup: wk->vm.dbg_state.eval_trace = parent_eval_trace; if (wk->cur_project > 0) { L("leaving subproject '%s'", subproject_name); log_set_prefix(-2); } wk->cur_project = parent_project; stack_pop(&wk->stack, wk->vm.scope_stack); return ret; } static bool ensure_project_is_first_statement(struct workspace *wk, const struct source *src, struct node *n, bool check_only) { bool first_statement_is_a_call_to_project = n->type == node_type_stmt && n->l && n->l->type == node_type_call && n->l->r && n->l->r->type == node_type_id_lit && str_eql(get_str(wk, n->l->r->data.str), &STR("project")); if (!first_statement_is_a_call_to_project) { if (!check_only) { error_message(src, n->location, log_error, 0, "first statement is not a call to project()"); } return false; } return true; } bool eval(struct workspace *wk, const struct source *src, enum build_language lang, enum eval_mode mode, obj *res) { TracyCZoneAutoS; TracyCMessage(src->label, strlen(src->label)); obj res_ignored; if (!res) { res = &res_ignored; } if (lang == build_language_cmake && mode == eval_mode_first) { stack_push(&wk->stack, wk->vm.lang_mode, language_extended); obj _; bool ok = module_import(wk, "cmake_prelude", false, &_); stack_pop(&wk->stack, wk->vm.lang_mode); assert(ok); } arr_push(&wk->vm.src, src); src = arr_peek(&wk->vm.src, 1); enum vm_compile_mode compile_mode = (wk->vm.lang_mode == language_extended || wk->vm.lang_mode == language_internal) ? vm_compile_mode_language_extended : 0; if (mode & eval_mode_repl) { compile_mode |= vm_compile_mode_expr; } if (mode & eval_mode_return_after_project) { compile_mode |= vm_compile_mode_return_after_project; } if (mode & eval_mode_relaxed_parse) { compile_mode |= vm_compile_mode_relaxed_parse; } uint32_t entry; { struct node *n = 0; vm_compile_state_reset(wk); switch (lang) { case build_language_meson: n = parse(wk, src, compile_mode); break; case build_language_cmake: n = cm_parse(wk, src); break; } if (!n) { TracyCZoneAutoE; return false; } bool first = lang == build_language_meson && (mode & eval_mode_first); if (first) { if (!ensure_project_is_first_statement(wk, src, n, false)) { TracyCZoneAutoE; return false; } } if (!vm_compile_ast(wk, n, compile_mode, &entry)) { TracyCZoneAutoE; return false; } } log_progress_push_level(entry, wk->vm.code.len); if (wk->vm.dbg_state.eval_trace) { obj_array_push(wk, wk->vm.dbg_state.eval_trace, make_strf(wk, "%s%s", src->type == source_type_embedded ? "[embedded] " : "", src->label)); bool trace_subdir = wk->vm.dbg_state.eval_trace_subdir; if (trace_subdir) { obj subdir_eval_trace; subdir_eval_trace = make_obj(wk, obj_array); obj_array_push(wk, wk->vm.dbg_state.eval_trace, subdir_eval_trace); stack_push(&wk->stack, wk->vm.dbg_state.eval_trace, subdir_eval_trace); } stack_push(&wk->stack, wk->vm.dbg_state.eval_trace_subdir, false); } uint32_t call_stack_base = wk->vm.call_stack.len; struct call_frame eval_frame = { .type = call_frame_type_eval, .return_ip = wk->vm.ip, }; vm_push_call_stack_frame(wk, &eval_frame); wk->vm.ip = entry; *res = vm_execute(wk); assert(call_stack_base == wk->vm.call_stack.len); if (wk->vm.dbg_state.eval_trace) { stack_pop(&wk->stack, wk->vm.dbg_state.eval_trace_subdir); if (wk->vm.dbg_state.eval_trace_subdir) { stack_pop(&wk->stack, wk->vm.dbg_state.eval_trace); } } log_progress_pop_level(); bool ok = !wk->vm.error; wk->vm.error = false; TracyCZoneAutoE; return ok; } bool eval_str_label(struct workspace *wk, const char *label, const char *str, enum eval_mode mode, obj *res) { struct source src = { .label = get_cstr(wk, make_str(wk, label)), .src = str, .len = strlen(str) }; return eval(wk, &src, build_language_meson, mode, res); } bool eval_str(struct workspace *wk, const char *str, enum eval_mode mode, obj *res) { return eval_str_label(wk, "", str, mode, res); } bool eval_project_file(struct workspace *wk, const char *path, enum build_language lang, enum eval_project_file_flags flags, obj *res) { bool ret = false; obj path_str = make_str(wk, path); workspace_add_regenerate_deps(wk, path_str); struct source src = { 0 }; if (!fs_read_entire_file(get_str(wk, path_str)->s, &src)) { return false; } enum eval_mode eval_mode = 0; if (flags & eval_project_file_flag_first) { eval_mode |= eval_mode_first; } if (flags & eval_project_file_flag_return_after_project) { eval_mode |= eval_mode_return_after_project; } if (flags & eval_project_file_flag_relaxed_parse) { eval_mode |= eval_mode_relaxed_parse; } if (!eval(wk, &src, lang, eval_mode, res)) { goto ret; } ret = true; ret: return ret; } static bool repl_eval_str(struct workspace *wk, const char *str, obj *repl_res) { stack_push(&wk->stack, wk->vm.dbg_state.stepping, false); bool ret = eval_str(wk, str, eval_mode_repl, repl_res); stack_pop(&wk->stack, wk->vm.dbg_state.stepping); return ret; } void repl(struct workspace *wk, bool dbg) { bool loop = true; obj repl_res = 0; char *line; FILE *out = _log_file(); enum repl_cmd { repl_cmd_noop, repl_cmd_exit, repl_cmd_abort, repl_cmd_step, repl_cmd_list, repl_cmd_inspect, repl_cmd_watch, repl_cmd_unwatch, repl_cmd_eval, repl_cmd_breakpoint, repl_cmd_backtrace, repl_cmd_help, }; static enum repl_cmd cmd = repl_cmd_noop; struct { const char *name[5]; enum repl_cmd cmd; bool valid, has_arg; const char *help_text; } repl_cmds[] = { { { "abort", 0 }, repl_cmd_abort, dbg }, { { "c", "continue", 0 }, repl_cmd_exit, dbg }, { { "exit", 0 }, repl_cmd_exit, !dbg }, { { "h", "help", 0 }, repl_cmd_help, true }, { { "i", "inspect", 0 }, repl_cmd_inspect, true, true }, { { "l", "list", 0 }, repl_cmd_list, dbg }, { { "s", "step", 0 }, repl_cmd_step, dbg }, { { "w", "watch", 0 }, repl_cmd_watch, dbg, true }, { { "uw", "unwatch", 0 }, repl_cmd_unwatch, dbg, true }, { { "e", "p", "eval", "print", 0 }, repl_cmd_eval, true, true }, { { "br", "breakpoint", 0 }, repl_cmd_breakpoint, dbg, true }, { { "bt", "backtrace", 0 }, repl_cmd_backtrace, dbg }, 0 }; if (dbg) { struct source_location loc; uint32_t src_idx; vm_lookup_inst_location_src_idx(&wk->vm, wk->vm.ip, &loc, &src_idx); list_line_range(arr_get(&wk->vm.src, src_idx), loc, 1); } const char *prompt = "> "; char *arg = NULL; while (loop && (line = muon_readline(prompt))) { if (!*line) { goto cmd_found; } muon_readline_history_add(line); if ((arg = strchr(line, ' '))) { *arg = 0; ++arg; } uint32_t i, j; for (i = 0; *repl_cmds[i].name; ++i) { if (repl_cmds[i].valid) { for (j = 0; repl_cmds[i].name[j]; ++j) { if (strcmp(line, repl_cmds[i].name[j]) == 0) { if (repl_cmds[i].has_arg) { if (!arg) { fprintf(out, "missing argument\n"); continue; } } else { if (arg) { fprintf(out, "this command does not take an argument\n"); continue; } } cmd = repl_cmds[i].cmd; goto cmd_found; } } } } fprintf(out, "unknown repl command '%s'\n", line); continue; cmd_found: switch (cmd) { case repl_cmd_abort: exit(1); break; case repl_cmd_exit: { wk->vm.dbg_state.stepping = false; loop = false; break; } case repl_cmd_help: fprintf(out, "repl commands:\n"); for (i = 0; *repl_cmds[i].name; ++i) { if (!repl_cmds[i].valid) { continue; } fprintf(out, " - "); for (j = 0; repl_cmds[i].name[j]; ++j) { fprintf(out, "%s", repl_cmds[i].name[j]); if (repl_cmds[i].name[j + 1]) { fprintf(out, ", "); } } if (repl_cmds[i].help_text) { fprintf(out, " - %s", repl_cmds[i].help_text); } fprintf(out, "\n"); } break; case repl_cmd_list: { struct source_location loc; uint32_t src_idx; vm_lookup_inst_location_src_idx(&wk->vm, wk->vm.ip, &loc, &src_idx); list_line_range(arr_get(&wk->vm.src, src_idx), loc, 11); break; } case repl_cmd_step: { wk->vm.dbg_state.stepping = true; loop = false; break; } case repl_cmd_inspect: if (!repl_eval_str(wk, arg, &repl_res)) { break; } obj_inspect(wk, repl_res); break; case repl_cmd_watch: if (!wk->vm.dbg_state.watched) { wk->vm.dbg_state.watched = make_obj(wk, obj_array); } obj_array_push(wk, wk->vm.dbg_state.watched, make_str(wk, arg)); break; case repl_cmd_unwatch: if (wk->vm.dbg_state.watched) { uint32_t idx; if (obj_array_index_of(wk, wk->vm.dbg_state.watched, make_str(wk, arg), &idx)) { obj_array_del(wk, wk->vm.dbg_state.watched, idx); } } break; case repl_cmd_eval: { if (!repl_eval_str(wk, arg, &repl_res)) { continue; } if (repl_res) { obj_fprintf(wk, out, "%o\n", repl_res); wk->vm.behavior.assign_variable(wk, "_", repl_res, 0, assign_local); } break; } case repl_cmd_breakpoint: { vm_dbg_push_breakpoint_str(wk, arg); break; } case repl_cmd_backtrace: { uint32_t i; struct call_frame frame; struct source *src; struct source_location loc; for (i = 1; i < wk->vm.call_stack.len + 1; ++i) { if (i == wk->vm.call_stack.len) { frame = (struct call_frame){ .return_ip = wk->vm.ip - 1, }; } else { frame = *(struct call_frame *)arr_get(&wk->vm.call_stack, i); } vm_lookup_inst_location(&wk->vm, frame.return_ip, &loc, &src); error_message(src, loc, log_info, 0, ""); } break; } case repl_cmd_noop: break; } } muon_readline_history_free(); } const char * determine_project_root(struct workspace *wk, const char *path) { TSTR(tmp); TSTR(new_path); path_make_absolute(wk, &new_path, path); path_basename(wk, &tmp, new_path.buf); if (strcmp(tmp.buf, "meson.build") != 0) { path_dirname(wk, &tmp, new_path.buf); path_join(wk, &new_path, tmp.buf, "meson.build"); } path = new_path.buf; while (true) { if (!fs_file_exists(path)) { goto cont; } struct node *n; struct source src = { 0 }; if (!fs_read_entire_file(path, &src)) { goto cont; } else if (!(n = parse(wk, &src, vm_compile_mode_quiet | vm_compile_mode_relaxed_parse))) { // If we failed to parse this file, try just searching for the string project( struct str src_str = { src.src, src.len }; if (str_startswith(&src_str, &STR("project(")) || str_contains(&src_str, &STR("\nproject("))) { goto found; } goto cont; } if (ensure_project_is_first_statement(wk, 0, n, true)) { found: // found path_dirname(wk, &tmp, path); obj s = tstr_into_str(wk, &tmp); fs_source_destroy(&src); return get_cstr(wk, s); } cont: fs_source_destroy(&src); path_dirname(wk, &tmp, path); path_dirname(wk, &new_path, tmp.buf); if (strcmp(new_path.buf, tmp.buf) == 0) { return NULL; } path_push(wk, &new_path, "meson.build"); path = new_path.buf; } } const char * determine_build_file(struct workspace *wk, const char *cwd, enum build_language *out_lang, bool quiet) { const struct { const char *name; enum build_language lang; } names[] = { { "meson.build", build_language_meson }, // { "CMakeLists.txt", build_language_cmake }, }; TSTR(name); bool found = false; uint32_t i; for (i = 0; i < ARRAY_LEN(names); ++i) { path_join(wk, &name, cwd, names[i].name); if (fs_file_exists(name.buf)) { found = true; break; } } if (!quiet && !found) { TSTR(name_buf); for (i = 0; i < ARRAY_LEN(names); ++i) { tstr_pushf(wk, &name_buf, "%s%s", names[i].name, i + 1 == ARRAY_LEN(names) ? "" : ", "); } vm_error_at(wk, -1, "no build file found in %s (tried %s)", cwd, name_buf.buf); } if (!found) { return 0; } *out_lang = names[i].lang; if (!quiet && *out_lang == build_language_cmake) { vm_warning_at(wk, -1, "using experimental cmake compat mode, this will probably break"); } return get_cstr(wk, tstr_into_str(wk, &name)); } muon-v0.5.0/src/lang/analyze.c0000644000175000017500000012755415041716357015206 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "lang/analyze.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "lang/workspace.h" #include "log.h" #include "options.h" #include "platform/assert.h" #include "platform/mem.h" #include "platform/os.h" #include "platform/path.h" #include "tracy.h" static struct { const struct az_opts *opts; struct obj_func *fp; uint32_t impure_loop_depth; bool error; struct vm_ops unpatched_ops; struct obj_typeinfo az_injected_native_func_return; struct hash branch_map; struct hash dict_locations; struct arr visited_ops; } analyzer; struct az_file_entrypoint { bool is_root, has_diagnostic; enum log_level lvl; struct source_location location; uint32_t src_idx; }; static struct arr az_entrypoint_stack, az_entrypoint_stacks; enum branch_map_type { branch_map_type_normal, branch_map_type_ternary, }; union branch_map { struct branch_map_data { uint8_t taken, not_taken, impure, type; } data; uint64_t u64; }; struct bucket_arr assignments; void az_set_error(void) { analyzer.error = true; } /****************************************************************************** * utilities ******************************************************************************/ struct obj_tainted_by_typeinfo_opts { bool allow_tainted_dict_values; }; static bool obj_tainted_by_typeinfo(struct workspace *wk, obj o, struct obj_tainted_by_typeinfo_opts *opts) { if (!o) { return true; } switch (get_obj_type(wk, o)) { case obj_typeinfo: { return true; } case obj_array: { obj v; obj_array_for(wk, o, v) { if (obj_tainted_by_typeinfo(wk, v, opts)) { return true; } } break; } case obj_dict: { const bool disallow_tainted_values = opts && !opts->allow_tainted_dict_values; obj k, v; obj_dict_for(wk, o, k, v) { if (obj_tainted_by_typeinfo(wk, k, 0)) { return true; } else if (disallow_tainted_values && obj_tainted_by_typeinfo(wk, v, 0)) { return true; } } break; } default: break; } return false; } static void copy_az_entrypoint_stack(uint32_t *ep_stacks_i, uint32_t *ep_stack_len) { if (az_entrypoint_stack.len) { *ep_stacks_i = az_entrypoint_stacks.len; arr_grow_by(&az_entrypoint_stacks, az_entrypoint_stack.len); *ep_stack_len = az_entrypoint_stack.len; memcpy(arr_get(&az_entrypoint_stacks, *ep_stacks_i), az_entrypoint_stack.e, sizeof(struct az_file_entrypoint) * az_entrypoint_stack.len); } else { *ep_stacks_i = 0; *ep_stack_len = 0; } } static void mark_az_entrypoint_as_containing_diagnostic(uint32_t ep_stacks_i, enum log_level lvl) { struct az_file_entrypoint *ep = arr_get(&az_entrypoint_stacks, ep_stacks_i); ep->has_diagnostic = true; ep->lvl = lvl; } static bool az_diagnostic_enabled(enum az_diagnostic d) { return analyzer.opts->enabled_diagnostics & d; } /*static*/ const char * inspect_typeinfo(struct workspace *wk, obj t) { if (get_obj_type(wk, t) == obj_typeinfo) { struct obj_typeinfo *ti = get_obj_typeinfo(wk, t); return typechecking_type_to_s(wk, ti->type); } else { return obj_type_to_s(get_obj_type(wk, t)); } } obj make_typeinfo(struct workspace *wk, type_tag t) { obj res; res = make_obj(wk, obj_typeinfo); struct obj_typeinfo *type = get_obj_typeinfo(wk, res); type->type = t; return res; } obj make_az_branch_element(struct workspace *wk, uint32_t ip, uint32_t flags) { union az_branch_element elem = { .data = { .ip = ip, .flags = flags, }, }; return make_number(wk, elem.i64); } static type_tag coerce_type_tag(struct workspace *wk, obj r) { type_tag t = get_obj_type(wk, r); if (t == obj_typeinfo) { return get_obj_typeinfo(wk, r)->type; } else { return obj_type_to_tc_type(t); } } static void merge_types(struct workspace *wk, struct obj_typeinfo *a, obj r) { a->type = flatten_type(wk, a->type) | flatten_type(wk, coerce_type_tag(wk, r)); } uint32_t az_dict_member_location_lookup_str(struct workspace *wk, obj dict, const char *key) { uint64_t *hv; if ((hv = hash_get(&analyzer.dict_locations, &dict))) { obj ip; if (obj_dict_index_str(wk, *hv, key, &ip)) { return ip; } } return 0; } /****************************************************************************** * scope handling ******************************************************************************/ /* * Variable assignment and retrieval is handled with the following functions. * The additional complexity is required due to variables that are * conditionally assigned, e.g. assigned in an if-block, or foreach loop. * * When a conditional block is started, a "scope group" is created, and then * every branch of the if statement gets its own sub-scope. At the end of the * if statement, all the sub scopes are merged (conflicting object types get * merged) into the parent scope, and the scope group is popped. * * The scope stack, rather than simply an array of dicts as in normal vm * execution, is an array of "scope groups". The first group is a plain dict, * which represents the root scope for this level in the scope stack. When * entering a branch, additional groups are pushed onto this list each * consisting of a separate dict per sub-scope. */ void print_scope_stack(struct workspace *wk) { L("scope stack:"); obj local_scope, scope_group, scope, k, v; obj_array_for(wk, wk->vm.scope_stack, local_scope) { L(" local scope:"); uint32_t i = 0; obj_array_for(wk, local_scope, scope_group) { if (i == 0) { L(" root scope:"); obj_dict_for(wk, scope_group, k, v) { struct az_assignment *assign = bucket_arr_get(&assignments, v); LO(" %o: %s %o\n", k, assign->accessed ? "a" : "_", assign->o); } } else { obj_array_for(wk, scope_group, scope) { struct az_assignment *assign = bucket_arr_get(&assignments, v); L(" scope group:"); obj_dict_for(wk, scope, k, v) { LO(" %o: %s %o\n", k, assign->accessed ? "a" : "_", assign->o); } } } ++i; } } } // example scope_stack: [[{a: 1}, [{b: 2}, {b: 3}], [{c: 4}]]] // example local_scope: [{a: 1}, [{b: 2}, {b: 3}], [{c: 4}]] static bool assign_lookup_with_scope(struct workspace *wk, const char *name, obj *scope, obj *res) { TracyCZoneAutoS; bool found = false; obj local_scope; obj_array_for(wk, wk->vm.scope_stack, local_scope) { obj base = obj_array_index(wk, local_scope, 0); uint32_t local_scope_len = get_obj_array(wk, local_scope)->len; if (local_scope_len > 1) { int32_t i; // example: {a: 1}, -- skip // example: [{b: 2}, {b: 3}], -- take last // example: [{c: 4}] -- take last for (i = local_scope_len - 1; i >= 1; --i) { obj scope_group; scope_group = obj_array_index(wk, local_scope, i); *scope = obj_array_get_tail(wk, scope_group); if (obj_dict_index_str(wk, *scope, name, res)) { found = true; break; } } } if (!found) { if (obj_dict_index_str(wk, base, name, res)) { *scope = base; found = true; } } } TracyCZoneAutoE; return found; } struct az_assignment * az_assign_lookup(struct workspace *wk, const char *name) { obj res, _; if (assign_lookup_with_scope(wk, name, &_, &res)) { return bucket_arr_get(&assignments, res); } return 0; } static obj assign_lookup_scope(struct workspace *wk, const char *name) { obj _, scope; if (assign_lookup_with_scope(wk, name, &scope, &_)) { return scope; } return 0; } static void az_unassign(struct workspace *wk, const char *name) { obj scope = assign_lookup_scope(wk, name); if (scope) { obj_dict_del_strn(wk, scope, name, strlen(name)); } } static void check_reassign_to_different_type(struct workspace *wk, struct az_assignment *a, obj new_val, struct az_assignment *new_a, uint32_t n_id) { if (!az_diagnostic_enabled(az_diagnostic_reassign_to_conflicting_type)) { return; } type_tag t1 = coerce_type_tag(wk, a->o), t2 = coerce_type_tag(wk, new_val); if ((t1 & t2) != t2) { char buf[BUF_SIZE_2k] = { 0 }; snprintf(buf, BUF_SIZE_2k, "reassignment of variable %s with type %s to conflicting type %s", a->name, typechecking_type_to_s(wk, t1), typechecking_type_to_s(wk, t2)); if (new_a) { error_diagnostic_store_push(new_a->src_idx, new_a->location, log_warn, buf); } else { vm_warning_at(wk, n_id, "%s", buf); } } } static obj push_assignment(struct workspace *wk, const char *name, obj o, uint32_t ip) { // initialize source location to 0 since some variables don't have // anything to put there, like builtin variables uint32_t src_idx, ep_stack_len = 0, ep_stacks_i = 0; copy_az_entrypoint_stack(&ep_stacks_i, &ep_stack_len); struct source_location loc; vm_lookup_inst_location_src_idx(&wk->vm, ip, &loc, &src_idx); // Add the new assignment to the current scope and return its index as // an obj (for storage in the scope dict) obj v = assignments.len; bucket_arr_push(&assignments, &(struct az_assignment){ .name = name, .o = o, .ip = ip, .location = loc, .src_idx = src_idx, .ep_stacks_i = ep_stacks_i, .ep_stack_len = ep_stack_len, }); return v; } static struct az_assignment * scope_assign(struct workspace *wk, const char *name, obj o, uint32_t ip, enum variable_assignment_mode mode) { TracyCZoneAutoS; obj scope = 0; bool accessed = false; if (mode == assign_reassign) { mode = assign_local; accessed = true; } if (mode == assign_local) { obj local_scope = obj_array_get_tail(wk, wk->vm.scope_stack); if (get_obj_array(wk, local_scope)->len == 1) { scope = obj_array_get_tail(wk, local_scope); } else { obj scope_group = obj_array_get_tail(wk, local_scope); scope = obj_array_get_tail(wk, scope_group); } } assert(scope); struct az_assignment *a = 0; if (analyzer.impure_loop_depth && (a = az_assign_lookup(wk, name))) { // When overwriting a variable in an impure loop turn it into a // typeinfo so that it gets marked as impure. enum obj_type new_type = get_obj_type(wk, o); if (new_type != obj_typeinfo && !obj_equal(wk, a->o, o)) { o = make_typeinfo(wk, obj_type_to_tc_type(new_type)); } } obj aid; if (obj_dict_index(wk, scope, make_str(wk, name), &aid)) { // The assignment was found so just reassign it here a = bucket_arr_get(&assignments, aid); check_reassign_to_different_type(wk, a, o, NULL, ip); a->o = o; a->ip = ip; TracyCZoneAutoE; return a; } aid = push_assignment(wk, name, o, ip); obj_dict_set(wk, scope, make_str(wk, name), aid); struct az_assignment *assign = bucket_arr_get(&assignments, aid); assign->accessed = accessed; TracyCZoneAutoE; return assign; } static void push_scope_group(struct workspace *wk) { obj local_scope = obj_array_get_tail(wk, wk->vm.scope_stack); obj scope_group; scope_group = make_obj(wk, obj_array); obj_array_push(wk, local_scope, scope_group); } static void push_scope_group_scope(struct workspace *wk) { obj local_scope = obj_array_get_tail(wk, wk->vm.scope_stack); obj scope_group = obj_array_get_tail(wk, local_scope); obj scope; scope = make_obj(wk, obj_dict); obj_array_push(wk, scope_group, scope); } static void merge_objects(struct workspace *wk, struct az_assignment *dest, struct az_assignment *src) { type_tag dest_type = get_obj_type(wk, dest->o); type_tag src_type = get_obj_type(wk, src->o); src->accessed = true; if (dest_type != obj_typeinfo) { dest->o = make_typeinfo(wk, obj_type_to_tc_type(dest_type)); } if (src_type != obj_typeinfo) { src->o = make_typeinfo(wk, obj_type_to_tc_type(src_type)); } check_reassign_to_different_type(wk, dest, src->o, src, 0); merge_types(wk, get_obj_typeinfo(wk, dest->o), src->o); assert(get_obj_type(wk, dest->o) == obj_typeinfo); assert(get_obj_type(wk, src->o) == obj_typeinfo); src->o = 0; } static void pop_scope_group(struct workspace *wk) { obj local_scope = obj_array_get_tail(wk, wk->vm.scope_stack); if (get_obj_array(wk, local_scope)->len == 1) { return; } obj scope_group = obj_array_pop(wk, local_scope); obj merged = obj_array_index(wk, scope_group, 0); { // First, merge all scopes other than the root scope into `merged` bool first = true; obj scope; obj_array_for(wk, scope_group, scope) { if (first) { first = false; continue; } obj k, aid; obj_dict_for(wk, scope, k, aid) { obj bid; struct az_assignment *b, *a = bucket_arr_get(&assignments, aid); if (obj_dict_index(wk, merged, k, &bid)) { b = bucket_arr_get(&assignments, bid); merge_objects(wk, b, a); } else { obj_dict_set(wk, merged, k, aid); } } } } { // Now, merge `merged` into base obj k, aid; obj_dict_for(wk, merged, k, aid) { (void)k; struct az_assignment *a = bucket_arr_get(&assignments, aid), *b; if ((b = az_assign_lookup(wk, a->name))) { merge_objects(wk, b, a); } else { if (false) { // This code makes all assignments within scope groups become // impure on pop. I'm not sure why I added this because it messes up code like this: // // if use_i18n // i18n = import('i18n') // endif // // With the below block enabled i18n becomes impure afterward // so we can't typecheck any of its methods. type_tag type = get_obj_type(wk, a->o); if (type != obj_typeinfo) { a->o = make_typeinfo(wk, obj_type_to_tc_type(type)); } } b = scope_assign(wk, a->name, a->o, a->ip, assign_local); b->accessed = a->accessed; b->location = a->location; b->src_idx = a->src_idx; a->accessed = true; } } } } /****************************************************************************** * analyzer ops ******************************************************************************/ struct az_branch_group { enum az_branch_type type; bool loop_impure; uint32_t merge_point; // TODO: remove this, it is unused struct az_branch_element_data branch; struct branch_map_data result; }; static struct az_branch_group cur_branch_group; static void az_op_az_branch(struct workspace *wk) { enum az_branch_type branch_type = vm_get_constant(wk->vm.code.e, &wk->vm.ip); uint32_t merge_point = vm_get_constant(wk->vm.code.e, &wk->vm.ip); uint32_t branches = vm_get_constant(wk->vm.code.e, &wk->vm.ip); { struct az_branch_group new_branch_group = { .merge_point = merge_point, .type = branch_type, }; stack_push(&wk->stack, cur_branch_group, new_branch_group); } if (branch_type == az_branch_type_loop) { obj it = object_stack_peek(&wk->vm.stack, 1); if (get_obj_iterator(wk, it)->type == obj_iterator_type_typeinfo) { ++analyzer.impure_loop_depth; cur_branch_group.loop_impure = true; } return; } push_scope_group(wk); // L("---> branching, merging @ %03x", cur_branch_group.merge_point); bool pure = true; obj branch, expr_result = 0; obj_array_for(wk, branches, branch) { // L("--> branch"); cur_branch_group.branch = (union az_branch_element){ .i64 = get_obj_number(wk, branch) }.data; cur_branch_group.result = (struct branch_map_data){ 0 }; wk->vm.ip = cur_branch_group.branch.ip; vm_push_call_stack_frame(wk, &(struct call_frame){ .type = call_frame_type_eval }); push_scope_group_scope(wk); vm_execute(wk); if (cur_branch_group.result.impure) { pure = false; } if (pure && cur_branch_group.result.taken) { break; } if ((cur_branch_group.branch.flags & az_branch_element_flag_pop) && (cur_branch_group.result.taken || !pure)) { struct obj_stack_entry *entry = object_stack_pop_entry(&wk->vm.stack); type_tag entry_type = coerce_type_tag(wk, entry->o); if (!expr_result) { expr_result = make_typeinfo(wk, entry_type); } else { merge_types(wk, get_obj_typeinfo(wk, expr_result), entry->o); } } } // L("<--- all branches merged %03x <---", cur_branch_group.merge_point); pop_scope_group(wk); stack_pop(&wk->stack, cur_branch_group); if (expr_result && !pure) { object_stack_push(wk, expr_result); } } static void az_op_az_merge(struct workspace *wk) { /* L("<--- joining branch %03x, %03x", cur_branch_group.merge_point, wk->vm.ip - 1); */ if (cur_branch_group.type == az_branch_type_loop) { if (cur_branch_group.loop_impure) { --analyzer.impure_loop_depth; } stack_pop(&wk->stack, cur_branch_group); return; } struct call_frame *frame = arr_pop(&wk->vm.call_stack); assert(cur_branch_group.merge_point == wk->vm.ip - 1); assert(frame->type == call_frame_type_eval); object_stack_push(wk, 0); wk->vm.run = false; } static void az_jmp_if_cond_matches(struct workspace *wk, bool cond) { struct obj_stack_entry *entry = object_stack_pop_entry(&wk->vm.stack); vm_get_constant(wk->vm.code.e, &wk->vm.ip); typecheck(wk, entry->ip, entry->o, obj_bool); union branch_map *map; { uint32_t ip = wk->vm.ip - 1; if (!(map = (union branch_map *)hash_get(&analyzer.branch_map, &ip))) { hash_set(&analyzer.branch_map, &ip, 0); map = (union branch_map *)hash_get(&analyzer.branch_map, &ip); } } if (cur_branch_group.branch.flags & az_branch_element_flag_pop) { map->data.type = branch_map_type_ternary; } if (get_obj_type(wk, entry->o) == obj_bool) { if (cond == get_obj_bool(wk, entry->o)) { map->data.not_taken = cur_branch_group.result.not_taken = true; wk->vm.ip = cur_branch_group.merge_point; } else { map->data.taken = cur_branch_group.result.taken = true; } } else { map->data.impure = cur_branch_group.result.impure = true; } } static void az_op_jmp_if_false(struct workspace *wk) { az_jmp_if_cond_matches(wk, false); } static void az_op_jmp_if_true(struct workspace *wk) { az_jmp_if_cond_matches(wk, true); } struct az_func_context { struct obj_capture *capture; }; static struct az_func_context cur_func_context; static void az_op_return(struct workspace *wk) { if (cur_func_context.capture) { obj v = object_stack_peek(&wk->vm.stack, 1); typecheck_custom( wk, 0, v, cur_func_context.capture->func->return_type, "expected return type %s, got %s"); } } static void az_op_return_end(struct workspace *wk) { struct call_frame *frame = arr_peek(&wk->vm.call_stack, 1); if (frame->type == call_frame_type_func) { object_stack_pop(&wk->vm.stack); object_stack_push(wk, make_typeinfo(wk, flatten_type(wk, cur_func_context.capture->func->return_type))); } analyzer.unpatched_ops.ops[op_return_end](wk); } /****************************************************************************** * analyzer behaviors ******************************************************************************/ static void az_assign_wrapper(struct workspace *wk, const char *name, obj o, uint32_t n_id, enum variable_assignment_mode mode) { scope_assign(wk, name, o, n_id, mode); } static bool az_lookup_wrapper(struct workspace *wk, const char *name, obj *res) { struct az_assignment *a = az_assign_lookup(wk, name); if (a) { a->accessed = true; *res = a->o; return true; } else { return false; } } static void az_push_local_scope(struct workspace *wk) { obj scope_group; scope_group = make_obj(wk, obj_array); obj scope; scope = make_obj(wk, obj_dict); obj_array_push(wk, scope_group, scope); obj_array_push(wk, wk->vm.scope_stack, scope_group); } static void az_pop_local_scope(struct workspace *wk) { obj scope_group = obj_array_pop(wk, wk->vm.scope_stack); assert(get_obj_array(wk, scope_group)->len == 1); } static obj az_scope_stack_dup(struct workspace *wk, obj scope_stack) { obj dup, local_scope, scope_group, scope; obj local_scope_dup, scope_group_dup, scope_dup; dup = make_obj(wk, obj_array); obj_array_for(wk, scope_stack, local_scope) { local_scope_dup = make_obj(wk, obj_array); uint32_t i = 0; obj_array_for(wk, local_scope, scope_group) { if (i == 0) { obj_dict_dup(wk, scope_group, &scope_dup); obj_array_push(wk, local_scope_dup, scope_dup); } else { scope_group_dup = make_obj(wk, obj_array); obj_array_for(wk, scope_group, scope) { obj_dict_dup(wk, scope, &scope_dup); obj_array_push(wk, scope_group_dup, scope_dup); } obj_array_push(wk, local_scope_dup, scope_group_dup); } ++i; } obj_array_push(wk, dup, local_scope_dup); } return dup; } static bool az_eval_project_file(struct workspace *wk, const char *path, enum build_language lang, enum eval_project_file_flags flags, obj *res) { obj override; if (analyzer.opts->file_override && obj_dict_index_str(wk, analyzer.opts->file_override, path, &override)) { enum eval_mode eval_mode = 0; if (flags & eval_project_file_flag_first) { eval_mode |= eval_mode_first; } if (analyzer.opts->relaxed_parse) { eval_mode |= eval_mode_relaxed_parse; } const struct source *src = arr_get(&analyzer.opts->file_override_src, override); struct source weak_src = *src; weak_src.is_weak_reference = true; return eval(wk, &weak_src, lang, eval_mode, res); } else { if (analyzer.opts->analyze_project_call_only) { flags |= eval_project_file_flag_return_after_project; } if (analyzer.opts->relaxed_parse) { flags |= eval_project_file_flag_relaxed_parse; } return eval_project_file(wk, path, lang, flags, res); } } static void az_execute_loop(struct workspace *wk) { arr_grow_to(&analyzer.visited_ops, wk->vm.code.len); uint32_t cip; while (wk->vm.run) { // LL("%-50s", vm_dis_inst(wk, wk->vm.code.e, wk->vm.ip)); // object_stack_print(wk, &wk->vm.stack); cip = wk->vm.ip; ++wk->vm.ip; analyzer.visited_ops.e[cip] = 1; wk->vm.ops.ops[wk->vm.code.e[cip]](wk); } } /****************************************************************************** * analyzer behaviors -- function lookup and dispatch ******************************************************************************/ struct az_pop_args_ctx { uint32_t id; bool do_analyze; bool pure_function; bool encountered_error; bool allow_impure_args; bool allow_impure_args_except_first; // set to true for set_variable and subdir } pop_args_ctx; static bool az_injected_native_func(struct workspace *wk, obj self, obj *res) { pop_args_ctx.encountered_error = false; // discard all arguments object_stack_discard(&wk->vm.stack, wk->vm.nargs + wk->vm.nkwargs * 2); *res = make_typeinfo(wk, analyzer.az_injected_native_func_return.type); return true; } static const struct func_impl az_func_impls[] = { { "az_injected_native_func", az_injected_native_func }, { 0 }, }; struct func_impl_group az_func_impl_group = { .impls = az_func_impls, }; static bool az_func_lookup(struct workspace *wk, obj self, const char *name, uint32_t *idx, obj *func) { // If self isn't typeinfo just proceed with standard func_lookup if (get_obj_type(wk, self) != obj_typeinfo) { return func_lookup(wk, self, name, idx, func); } struct obj_typeinfo res_t = { 0 }, *ti = get_obj_typeinfo(wk, self); struct { uint32_t idx; uint32_t matches; } lookup_res = { 0 }; uint32_t i; type_tag t = flatten_type(wk, ti->type); // Strip disabler from type list if ((t & tc_disabler) == tc_disabler) { t &= ~tc_disabler; t |= obj_typechecking_type_tag; } // If type includes dict, then we could potentially respond to anything if ((t & tc_dict & ~TYPE_TAG_MASK)) { return false; } // If type includes module, then we can't be sure what methods it might // respond to if ((t & tc_module & ~TYPE_TAG_MASK)) { res_t.type = tc_any; goto return_injected_native_func; } for (i = 1; i <= tc_type_count; ++i) { type_tag tc = obj_type_to_tc_type(i); if ((t & tc) != tc) { continue; } // TODO: add a warning if not all candidates match, e.g. // calling to_string on something that is potentially a string? if (func_lookup_for_group(func_impl_groups[i], wk->vm.lang_mode, name, &lookup_res.idx)) { ++lookup_res.matches; res_t.type |= native_funcs[lookup_res.idx].return_type; } } if (lookup_res.matches < 1) { // No matches found return false; } else if (lookup_res.matches > 1) { // Multiple matches found, return the index of az_injected_native_func // and wire it up to return our merged return type. goto return_injected_native_func; } else { // Single match found, return it *idx = lookup_res.idx; } return true; return_injected_native_func: analyzer.az_injected_native_func_return = res_t; *idx = az_func_impl_group.off; *func = 0; return true; } static bool az_pop_args(struct workspace *wk, struct args_norm an[], struct args_kw akw[]) { if (!vm_pop_args(wk, an, akw)) { return false; } pop_args_ctx.encountered_error = false; if (!pop_args_ctx.do_analyze) { return true; } bool typeinfo_among_args = false; { // Check if any argument value is tainted uint32_t i; for (i = 0; an && an[i].type != ARG_TYPE_NULL; ++i) { if (!an[i].set) { continue; } if (pop_args_ctx.allow_impure_args) { continue; } else if (pop_args_ctx.allow_impure_args_except_first && i > 0) { continue; } if (obj_tainted_by_typeinfo(wk, an[i].val, 0)) { typeinfo_among_args = true; break; } } if (!typeinfo_among_args && akw) { for (i = 0; akw[i].key; ++i) { if (!akw[i].set) { continue; } if (pop_args_ctx.allow_impure_args || pop_args_ctx.allow_impure_args_except_first) { continue; } if (obj_tainted_by_typeinfo(wk, akw[i].val, 0)) { typeinfo_among_args = true; break; } } } } if (typeinfo_among_args) { pop_args_ctx.pure_function = false; } if (pop_args_ctx.pure_function) { return true; } // now return false to halt the function return false; } static bool az_native_func_dispatch(struct workspace *wk, uint32_t func_idx, obj self, obj *res) { static uint32_t id = 0; struct az_pop_args_ctx _ctx = { .id = id }; ++id; stack_push(&wk->stack, pop_args_ctx, _ctx); *res = 0; bool pure = native_funcs[func_idx].pure; if (self && obj_tainted_by_typeinfo( wk, self, &(struct obj_tainted_by_typeinfo_opts){ .allow_tainted_dict_values = true })) { pure = false; } bool is_subdir = false; if (!self) { is_subdir = strcmp(native_funcs[func_idx].name, "subdir") == 0; if (is_subdir || strcmp(native_funcs[func_idx].name, "subproject") == 0 || strcmp(native_funcs[func_idx].name, "dependency") == 0) { pop_args_ctx.allow_impure_args_except_first = true; } else if (strcmp(native_funcs[func_idx].name, "p") == 0) { pop_args_ctx.allow_impure_args = true; } } pop_args_ctx.do_analyze = true; // pure_function can be set to false even if it was true in the case // that any of its arguments are of type obj_typeinfo pop_args_ctx.pure_function = pure; pop_args_ctx.encountered_error = true; bool func_ok = native_funcs[func_idx].func(wk, self, res); pure = pop_args_ctx.pure_function; bool args_ok = !pop_args_ctx.encountered_error; stack_pop(&wk->stack, pop_args_ctx); if (pure) { return func_ok; } if (is_subdir) { vm_warning(wk, "Unable to analyze this subdir call. Diagnostics will be incomplete"); } if (func_idx == az_func_impl_group.off) { // This means the native function we called was // az_injected_native_func and we should respect the return // type. } else { *res = make_typeinfo(wk, native_funcs[func_idx].return_type); } return args_ok; } /* */ static void az_op_constant_func(struct workspace *wk) { analyzer.unpatched_ops.ops[op_constant_func](wk); obj c = object_stack_peek(&wk->vm.stack, 1); struct obj_capture *capture = get_obj_capture(wk, c); struct args_norm an[ARRAY_LEN(capture->func->an)] = { 0 }; struct args_kw akw[ARRAY_LEN(capture->func->akw)] = { 0 }; { uint32_t i; for (i = 0; i < capture->func->nargs; ++i) { an[i].val = make_typeinfo(wk, flatten_type(wk, capture->func->an[i].type)); an[i].node = wk->vm.ip - 1; } an[i].type = ARG_TYPE_NULL; for (i = 0; i < capture->func->nkwargs; ++i) { akw[i].key = capture->func->akw[i].key; akw[i].val = make_typeinfo(wk, flatten_type(wk, capture->func->akw[i].type)); akw[i].node = wk->vm.ip - 1; } akw[i].key = 0; } { obj res; struct az_func_context new_func_context = { .capture = capture, }; stack_push(&wk->stack, pop_args_ctx, (struct az_pop_args_ctx){ 0 }); stack_push(&wk->stack, cur_func_context, new_func_context); vm_eval_capture(wk, c, an, akw, &res); stack_pop(&wk->stack, cur_func_context); stack_pop(&wk->stack, pop_args_ctx); } } static void az_op_call(struct workspace *wk) { obj c = object_stack_peek(&wk->vm.stack, 1); bool pop_args_error; const struct az_pop_args_ctx new_pop_args_ctx = { .encountered_error = true, }; stack_push(&wk->stack, pop_args_ctx, new_pop_args_ctx); analyzer.unpatched_ops.ops[op_call](wk); pop_args_error = pop_args_ctx.encountered_error; stack_pop(&wk->stack, pop_args_ctx); if (get_obj_type(wk, c) == obj_capture && !pop_args_error) { struct obj_capture *capture = get_obj_capture(wk, c); { // TODO: only if there were no errors! // // op_call just set some variables for function args // that we will never use but we don't want an unused // variable warning so mark them all accessed. obj local_scope = obj_array_get_tail(wk, wk->vm.scope_stack), root_scope = obj_array_get_tail(wk, local_scope); obj _k, aid; obj_dict_for(wk, root_scope, _k, aid) { (void)_k; struct az_assignment *assign = bucket_arr_get(&assignments, aid); assign->accessed = true; } } object_stack_push(wk, make_typeinfo(wk, flatten_type(wk, capture->func->return_type))); analyzer.unpatched_ops.ops[op_return](wk); } } static void az_op_constant_dict(struct workspace *wk) { obj b; uint32_t ip = wk->vm.ip; uint32_t i, len = vm_get_constant(wk->vm.code.e, &ip); b = make_obj(wk, obj_dict); for (i = 0; i < len; ++i) { obj key = object_stack_peek(&wk->vm.stack, (len - i) * 2 - 1); if (wk->vm.in_analyzer && get_obj_type(wk, key) == obj_typeinfo) { continue; } obj_dict_set(wk, b, key, object_stack_peek_entry(&wk->vm.stack, (len - i) * 2)->ip); } analyzer.unpatched_ops.ops[op_constant_dict](wk); obj dict = object_stack_peek(&wk->vm.stack, 1); hash_set(&analyzer.dict_locations, &dict, b); } static void az_dict_locations_merge(struct workspace *wk, obj a, obj b, obj tgt) { uint64_t *la, *lb; if (!(la = hash_get(&analyzer.dict_locations, &a))) { UNREACHABLE; } else if (!(lb = hash_get(&analyzer.dict_locations, &b))) { UNREACHABLE; } obj merged; obj_dict_merge(wk, *la, *lb, &merged); hash_set(&analyzer.dict_locations, &tgt, merged); } static void az_op_add(struct workspace *wk) { obj b = object_stack_peek(&wk->vm.stack, 1); obj a = object_stack_peek(&wk->vm.stack, 2); analyzer.unpatched_ops.ops[op_add](wk); obj c = object_stack_peek(&wk->vm.stack, 1); if (get_obj_type(wk, c) == obj_dict && get_obj_type(wk, b) == obj_dict && get_obj_type(wk, a) == obj_dict) { az_dict_locations_merge(wk, a, b, c); } } static void az_op_store(struct workspace *wk) { obj dup = 0; enum op_store_flags flags; { uint32_t ip = wk->vm.ip; flags = vm_get_constant(wk->vm.code.e, &ip); } if (flags & op_store_flag_member) { obj tgt, key; tgt = object_stack_peek(&wk->vm.stack, 2); key = object_stack_peek(&wk->vm.stack, 3); if (get_obj_type(wk, tgt) == obj_dict && get_obj_type(wk, key) == obj_string) { const struct obj_stack_entry *e; e = object_stack_peek_entry(&wk->vm.stack, 1); uint64_t *hv; if (!(hv = hash_get(&analyzer.dict_locations, &tgt))) { hash_set(&analyzer.dict_locations, &tgt, make_obj(wk, obj_dict)); hv = hash_get(&analyzer.dict_locations, &tgt); } obj_dict_set(wk, *hv, key, e->ip); } } else { obj tgt = object_stack_peek(&wk->vm.stack, 2); if (get_obj_type(wk, tgt) == obj_dict) { if (flags & op_store_flag_add_store) { obj val = object_stack_peek(&wk->vm.stack, 1); if (get_obj_type(wk, val) == obj_dict) { az_dict_locations_merge(wk, tgt, val, tgt); } } else { uint64_t *hv; if (!(hv = hash_get(&analyzer.dict_locations, &tgt))) { UNREACHABLE; } obj_dict_dup_light(wk, *hv, &dup); } } } analyzer.unpatched_ops.ops[op_store](wk); if (dup) { obj tgt = object_stack_peek(&wk->vm.stack, 1); hash_set(&analyzer.dict_locations, &tgt, dup); } } /****************************************************************************** * eval trace helpers ******************************************************************************/ struct eval_trace_print_ctx { uint32_t indent, len, i; uint64_t bars; }; static uint32_t eval_trace_arr_len(struct workspace *wk, obj arr) { uint32_t cnt = 0; obj v; obj_array_for(wk, arr, v) { if (get_obj_type(wk, v) != obj_array) { ++cnt; } } return cnt; } static void eval_trace_print_level(struct workspace *wk, struct eval_trace_print_ctx *ctx, obj v) { switch (get_obj_type(wk, v)) { case obj_array: { struct eval_trace_print_ctx subctx = { .indent = ctx->indent + 1, .bars = ctx->bars, .len = eval_trace_arr_len(wk, v), }; if (ctx->i <= ctx->len - 1) { subctx.bars |= (1 << (ctx->indent - 1)); } obj sub_v; obj_array_for(wk, v, sub_v) { eval_trace_print_level(wk, &subctx, sub_v); } break; } case obj_string: { uint32_t i; for (i = 0; i < ctx->indent; ++i) { if (i < ctx->indent - 1) { if (ctx->bars & (1 << i)) { printf("│ "); } else { printf(" "); } } else if (ctx->i == ctx->len - 1) { printf("└── "); } else { printf("├── "); } } TSTR(rel); if (path_is_absolute(get_cstr(wk, v))) { TSTR(cwd); path_copy_cwd(wk, &cwd); path_relative_to(wk, &rel, cwd.buf, get_cstr(wk, v)); printf("%s\n", rel.buf); } else { printf("%s\n", get_cstr(wk, v)); } ++ctx->i; break; } default: UNREACHABLE; } } void eval_trace_print(struct workspace *wk, obj trace) { struct eval_trace_print_ctx ctx = { .indent = 1, .len = eval_trace_arr_len(wk, trace), }; obj v; obj_array_for(wk, trace, v) { eval_trace_print_level(wk, &ctx, v); } } /****************************************************************************** * analyzer options ******************************************************************************/ static const struct { const char *name; enum az_diagnostic d; } az_diagnostic_names[] = { { "unused-variable", az_diagnostic_unused_variable }, { "reassign-to-conflicting-type", az_diagnostic_reassign_to_conflicting_type }, { "dead-code", az_diagnostic_dead_code }, }; bool az_diagnostic_name_to_enum(const char *name, enum az_diagnostic *ret) { uint32_t i; for (i = 0; i < ARRAY_LEN(az_diagnostic_names); ++i) { if (strcmp(az_diagnostic_names[i].name, name) == 0) { *ret = az_diagnostic_names[i].d; return true; } } return false; } void az_print_diagnostic_names(void) { uint32_t i; for (i = 0; i < ARRAY_LEN(az_diagnostic_names); ++i) { printf("%s\n", az_diagnostic_names[i].name); } } /****************************************************************************** * diagnostic helpers ******************************************************************************/ static void az_warn_dead_code(struct workspace *wk, uint32_t src_idx, struct source_location *start_loc, struct source_location *end_loc) { if (src_idx == UINT32_MAX) { return; } // TOOD: how can this occur? if (end_loc->off < start_loc->off) { return; } struct source_location merged = { .off = start_loc->off, .len = (end_loc->off - start_loc->off) + end_loc->len }; struct source *src = arr_get(&wk->vm.src, src_idx); error_message(src, merged, log_warn, 0, "dead code"); } void analyze_opts_init(struct workspace *wk, struct az_opts *opts) { *opts = (struct az_opts){ 0 }; opts->file_override = make_obj(wk, obj_dict); arr_init(&opts->file_override_src, 8, sizeof(struct source)); } void analyze_opts_destroy(struct workspace *wk, struct az_opts *opts) { TracyCZoneAutoS; uint32_t i; for (i = 0; i < opts->file_override_src.len; ++i) { struct source *src = arr_get(&opts->file_override_src, i); fs_source_destroy(src); } arr_destroy(&opts->file_override_src); TracyCZoneAutoE; } bool analyze_opts_push_override(struct workspace *wk, struct az_opts *opts, const char *override, const char *content_path, const struct str *content) { TracyCZoneAutoS; uint32_t idx; struct source *src = 0; TSTR(abs); path_make_absolute(wk, &abs, override); obj path = tstr_into_str(wk, &abs); if (obj_dict_index(wk, opts->file_override, path, &idx)) { src = arr_get(&opts->file_override_src, idx); fs_source_destroy(src); } if (!content && !content_path) { obj_dict_del(wk, opts->file_override, path); TracyCZoneAutoE; return true; } if (!src) { idx = opts->file_override_src.len; arr_push(&opts->file_override_src, &(struct source){ 0 }); src = arr_peek(&opts->file_override_src, 1); obj_dict_set(wk, opts->file_override, path, idx); } assert(!src->src); assert(!src->len); if (content) { void *buf = z_calloc(content->len + 1, 1); memcpy(buf, content->s, content->len); *src = (struct source){ .type = source_type_file, .len = content->len, .src = buf, }; } else { if (!fs_read_entire_file(content_path, src)) { TracyCZoneAutoE; return false; } } src->label = get_cstr(wk, path); TracyCZoneAutoE; return true; } /****************************************************************************** * entrypoint ******************************************************************************/ bool do_analyze(struct workspace *wk, struct az_opts *opts) { TracyCZoneAutoS; bool res = false; analyzer.opts = opts; bucket_arr_init(&assignments, 512, sizeof(struct az_assignment)); hash_init(&analyzer.branch_map, 1024, sizeof(uint32_t)); hash_init(&analyzer.dict_locations, 1024, sizeof(obj)); arr_init_flags(&analyzer.visited_ops, 1024, 1, arr_flag_zero_memory); { /* re-initialize the default scope */ obj original_scope, scope_group, scope; original_scope = obj_array_index(wk, wk->vm.default_scope_stack, 0); wk->vm.default_scope_stack = make_obj(wk, obj_array); scope_group = make_obj(wk, obj_array); scope = make_obj(wk, obj_dict); obj_array_push(wk, scope_group, scope); obj_array_push(wk, wk->vm.default_scope_stack, scope_group); obj k, v; obj_dict_for(wk, original_scope, k, v) { obj aid = push_assignment(wk, get_cstr(wk, k), v, 0); struct az_assignment *a = bucket_arr_get(&assignments, aid); a->default_var = true; obj_dict_set(wk, scope, k, aid); } wk->vm.scope_stack = az_scope_stack_dup(wk, wk->vm.default_scope_stack); } wk->vm.behavior.assign_variable = az_assign_wrapper; wk->vm.behavior.unassign_variable = az_unassign; wk->vm.behavior.push_local_scope = az_push_local_scope; wk->vm.behavior.pop_local_scope = az_pop_local_scope; wk->vm.behavior.get_variable = az_lookup_wrapper; wk->vm.behavior.scope_stack_dup = az_scope_stack_dup; wk->vm.behavior.eval_project_file = az_eval_project_file; wk->vm.behavior.native_func_dispatch = az_native_func_dispatch; wk->vm.behavior.pop_args = az_pop_args; wk->vm.behavior.func_lookup = az_func_lookup; wk->vm.behavior.execute_loop = az_execute_loop; wk->vm.in_analyzer = true; analyzer.unpatched_ops = wk->vm.ops; wk->vm.ops.ops[op_az_branch] = az_op_az_branch; wk->vm.ops.ops[op_az_merge] = az_op_az_merge; wk->vm.ops.ops[op_jmp_if_false] = az_op_jmp_if_false; wk->vm.ops.ops[op_jmp_if_true] = az_op_jmp_if_true; wk->vm.ops.ops[op_constant_func] = az_op_constant_func; wk->vm.ops.ops[op_return] = az_op_return; wk->vm.ops.ops[op_return_end] = az_op_return_end; wk->vm.ops.ops[op_call] = az_op_call; wk->vm.ops.ops[op_store] = az_op_store; wk->vm.ops.ops[op_constant_dict] = az_op_constant_dict; wk->vm.ops.ops[op_add] = az_op_add; error_diagnostic_store_init(wk); arr_init(&az_entrypoint_stack, 32, sizeof(struct az_file_entrypoint)); arr_init(&az_entrypoint_stacks, 32, sizeof(struct az_file_entrypoint)); if (analyzer.opts->eval_trace) { wk->vm.dbg_state.eval_trace = make_obj(wk, obj_array); } if (analyzer.opts->auto_chdir_root) { obj first_override = 0; if (analyzer.opts->file_override) { obj _v; obj_dict_for(wk, analyzer.opts->file_override, first_override, _v) { (void)_v; break; } } if (first_override) { const char *root = determine_project_root(wk, get_cstr(wk, first_override)); if (root) { TSTR(cwd); path_copy_cwd(wk, &cwd); if (strcmp(cwd.buf, root) != 0) { path_chdir(root); wk->source_root = root; } } } } wk->vm.lang_mode = opts->lang_mode; if (wk->vm.lang_mode == language_internal) { struct az_assignment *a = scope_assign(wk, "argv", make_typeinfo(wk, tc_array), 0, assign_local); a->default_var = true; } else { workspace_init_runtime(wk); workspace_init_startup_files(wk); { obj wrap_mode; get_option(wk, 0, &STR("wrap_mode"), &wrap_mode); set_option( wk, wrap_mode, make_str(wk, "forcefallback"), option_value_source_commandline, false); } } if (opts->single_file) { struct source src; if (!fs_read_entire_file(opts->single_file, &src)) { res = false; } else { obj _v; res = eval(wk, &src, build_language_meson, 0, &_v); } } else { uint32_t project_id; res = eval_project(wk, NULL, wk->source_root, wk->build_root, &project_id); } if (az_diagnostic_enabled(az_diagnostic_unused_variable)) { uint32_t i; for (i = 0; i < assignments.len; ++i) { struct az_assignment *a = bucket_arr_get(&assignments, i); if (!a->default_var && !a->accessed && *a->name != '_') { const char *msg = get_cstr(wk, make_strf(wk, "unused variable %s", a->name)); enum log_level lvl = log_warn; error_diagnostic_store_push(a->src_idx, a->location, lvl, msg); if (analyzer.opts->subdir_error && a->ep_stack_len) { mark_az_entrypoint_as_containing_diagnostic(a->ep_stacks_i, lvl); } } } } if (az_diagnostic_enabled(az_diagnostic_dead_code)) { uint32_t i, *ip; for (i = 0; i < analyzer.branch_map.keys.len; ++i) { ip = arr_get(&analyzer.branch_map.keys, i); const union branch_map *map = (union branch_map *)hash_get(&analyzer.branch_map, ip); if (!map->data.impure) { if (!map->data.taken && map->data.not_taken) { switch (map->data.type) { case branch_map_type_normal: vm_warning_at(wk, *ip, "branch never taken"); break; case branch_map_type_ternary: vm_warning_at(wk, *ip, "true branch never evaluated"); break; } } else if (map->data.taken && !map->data.not_taken) { switch (map->data.type) { case branch_map_type_normal: vm_warning_at(wk, *ip, "branch always taken"); break; case branch_map_type_ternary: vm_warning_at(wk, *ip, "false branch never evaluated"); break; } } } } // If this isn't true then we probaly failed to parse a file if (wk->vm.code.len == analyzer.visited_ops.len) { bool in_dead_code = false; uint32_t start_src_idx, src_idx; struct source_location start_loc, loc; for (i = 5; i < wk->vm.code.len; i += OP_WIDTH(wk->vm.code.e[i])) { if (!analyzer.visited_ops.e[i]) { vm_lookup_inst_location_src_idx(&wk->vm, i, &loc, &src_idx); if (!in_dead_code) { in_dead_code = true; start_loc = loc; start_src_idx = src_idx; } assert(src_idx == start_src_idx); } else if (in_dead_code) { az_warn_dead_code(wk, src_idx, &start_loc, &loc); in_dead_code = false; } } } } uint32_t i; for (i = 0; i < az_entrypoint_stacks.len;) { uint32_t j; struct az_file_entrypoint *ep_stack = arr_get(&az_entrypoint_stacks, i); assert(ep_stack->is_root); uint32_t len = 1; for (j = 1; j + i < az_entrypoint_stacks.len && !ep_stack[j].is_root; ++j) { ++len; } if (ep_stack->has_diagnostic) { enum log_level lvl = ep_stack->lvl; for (j = 0; j < len; ++j) { error_diagnostic_store_push( ep_stack[j].src_idx, ep_stack[j].location, lvl, "errors in subdir"); } } i += len; } if (analyzer.opts->eval_trace) { eval_trace_print(wk, wk->vm.dbg_state.eval_trace); } else { bool saw_error; saw_error = error_diagnostic_store_replay(wk, analyzer.opts->replay_opts); if (saw_error || analyzer.error) { res = false; } } bucket_arr_destroy(&assignments); arr_destroy(&az_entrypoint_stack); arr_destroy(&az_entrypoint_stacks); arr_destroy(&analyzer.visited_ops); hash_destroy(&analyzer.branch_map); hash_destroy(&analyzer.dict_locations); TracyCZoneAutoE; return res; } bool analyze_project_call(struct workspace *wk) { struct az_opts opts = { .replay_opts = error_diagnostic_store_replay_errors_only, .analyze_project_call_only = true, }; workspace_init_bare(wk); return do_analyze(wk, &opts); } muon-v0.5.0/src/lang/serial.c0000644000175000017500000002333115041716357015006 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "backend/output.h" #include "lang/serial.h" #include "lang/workspace.h" #include "log.h" #include "platform/assert.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #define SERIAL_MAGIC_LEN 9 static const char serial_magic[SERIAL_MAGIC_LEN + 1] = "muondump"; static const uint32_t serial_version = 9; static bool corrupted_dump(void) { LOG_E("unable to load corrupted serial dump"); return false; } static bool dump_uint32(uint32_t v, FILE *f) { return fs_fwrite(&v, sizeof(uint32_t), f); } static bool dump_uint64(uint64_t v, FILE *f) { return fs_fwrite(&v, sizeof(uint64_t), f); } static bool load_uint32(uint32_t *v, FILE *f) { return fs_fread(v, sizeof(uint32_t), f); } static bool load_uint64(uint64_t *v, FILE *f) { return fs_fread(v, sizeof(uint64_t), f); } static bool dump_bucket_arr(const struct bucket_arr *ba, FILE *f) { uint32_t i; if (!dump_uint32(ba->buckets.len, f)) { return false; } for (i = 0; i < ba->buckets.len; ++i) { struct bucket *b = arr_get(&ba->buckets, i); if (!dump_uint32(b->len, f)) { return false; } if (!fs_fwrite(b->mem, ba->item_size * b->len, f)) { return false; } } return true; } static bool load_bucket_arr(struct bucket_arr *ba, FILE *f) { uint32_t buckets_len; uint32_t i; struct bucket b = { 0 }; assert(ba->len == 0); if (!load_uint32(&buckets_len, f)) { return false; } z_free(((struct bucket *)arr_get(&ba->buckets, 0))->mem); arr_clear(&ba->buckets); for (i = 0; i < buckets_len; ++i) { init_bucket(ba, &b); if (!load_uint32(&b.len, f)) { goto done; } if (b.len > ba->bucket_size) { corrupted_dump(); goto done; } ba->len += b.len; if (!fs_fread(b.mem, ba->item_size * b.len, f)) { goto done; } arr_push(&ba->buckets, &b); continue; done: z_free(b.mem); return corrupted_dump(); } return true; } static bool dump_serial_header(FILE *f) { return fs_fwrite(serial_magic, SERIAL_MAGIC_LEN, f) && dump_uint32(serial_version, f); } static bool load_serial_header(FILE *f) { char buf[SERIAL_MAGIC_LEN] = { 0 }; if (!fs_fread(buf, SERIAL_MAGIC_LEN, f)) { return false; } if (memcmp(buf, serial_magic, SERIAL_MAGIC_LEN) != 0) { LOG_E("invalid file (missing magic)"); return false; } uint32_t v; if (!load_uint32(&v, f)) { return false; } if (v != serial_version) { LOG_E("unable to load data file created by a different version of muon (%d != %d)", v, serial_version); return false; } return true; } static bool dump_big_strings(struct workspace *wk, struct arr *offsets, FILE *f) { uint64_t len = 0; uint64_t start, end; // dump a placeholder at the start if (!fs_ftell(f, &start)) { return false; } else if (!dump_uint64(0, f)) { return false; } uint32_t i; struct bucket_arr *str_ba = &wk->vm.objects.obj_aos[obj_string - _obj_aos_start]; for (i = 0; i < str_ba->len; ++i) { struct str *ss = bucket_arr_get(str_ba, i); if (!(ss->flags & str_flag_big)) { continue; } if (!fs_fwrite(ss->s, ss->len + 1, f)) { return false; } arr_push(offsets, &len); len += ss->len + 1; } // jump back to the beginning, write the length, and then return if (!fs_ftell(f, &end)) { return false; } else if (!fs_fseek(f, start)) { return false; } else if (!dump_uint64(len, f)) { return false; } else if (!fs_fseek(f, end)) { return false; } return true; } struct big_string_table { uint8_t *data; uint64_t len; }; static bool load_big_strings(struct workspace *wk, struct big_string_table *bst, FILE *f) { uint64_t len; uint8_t *buf = NULL; if (!load_uint64(&len, f)) { return false; } if (len) { if (len > UINT32_MAX) { return corrupted_dump(); } buf = z_calloc(1, len); if (!fs_fread(buf, len, f)) { return false; } } *bst = (struct big_string_table){ .data = buf, .len = len, }; return true; } // store flags as a u64 to prevent padding in this struct, which makes memsan // upset when we fwrite it out struct serial_str { uint64_t s, len, flags; }; static bool get_big_string(struct workspace *wk, const struct big_string_table *bst, struct serial_str *src, struct str *res) { assert(src->flags & str_flag_big); if (src->s >= bst->len) { return corrupted_dump(); } char *buf = z_calloc(1, src->len + 1); memcpy(buf, &bst->data[src->s], src->len); *res = (struct str){ .flags = src->flags, .len = src->len, .s = buf, }; return true; } static bool dump_objs(struct workspace *wk, struct arr *big_string_offsets, FILE *f) { if (!dump_uint32(wk->vm.objects.objs.len - compile_time_constant_objects_end, f)) { return false; } struct serial_str ser_s = { 0 }; void *data; size_t len; uint8_t type_tag; assert(obj_type_count < UINT8_MAX && "increase size of type tag"); uint32_t i, big_string_i = 0; for (i = compile_time_constant_objects_end; i < wk->vm.objects.objs.len; ++i) { struct obj_internal *o = bucket_arr_get(&wk->vm.objects.objs, i); type_tag = o->t; if (!fs_fwrite(&type_tag, sizeof(uint8_t), f)) { return false; } if (o->t == obj_string) { const struct str *ss = bucket_arr_get(&wk->vm.objects.obj_aos[obj_string - _obj_aos_start], o->val); ser_s = (struct serial_str){ .len = ss->len, .flags = ss->flags, }; if (ss->flags & str_flag_big) { ser_s.s = *(uint64_t *)arr_get(big_string_offsets, big_string_i); ++big_string_i; } else { if (!bucket_arr_lookup_pointer(&wk->vm.objects.chrs, (uint8_t *)ss->s, &ser_s.s)) { assert(false && "pointer not found"); } } data = &ser_s; len = sizeof(struct serial_str); } else if (o->t < _obj_aos_start) { data = &o->val; len = sizeof(uint32_t); } else { struct bucket_arr *ba = &wk->vm.objects.obj_aos[o->t - _obj_aos_start]; data = bucket_arr_get(ba, o->val); len = ba->item_size; } if (!fs_fwrite(data, len, f)) { return false; } } return true; } static bool load_objs(struct workspace *wk, const struct big_string_table *bst, FILE *f) { uint32_t len; if (!load_uint32(&len, f)) { return false; } if (!len) { return true; } uint8_t type_tag; struct serial_str ser_s; struct bucket_arr *ba; uint32_t i; for (i = 0; i < len; ++i) { if (!fs_fread(&type_tag, sizeof(uint8_t), f)) { return false; } bucket_arr_pushn(&wk->vm.objects.objs, NULL, 0, 1); struct obj_internal *o = bucket_arr_get(&wk->vm.objects.objs, wk->vm.objects.objs.len - 1); *o = (struct obj_internal){ .t = type_tag, }; if (type_tag < _obj_aos_start) { if (!fs_fread(&o->val, sizeof(uint32_t), f)) { return false; } continue; } if (type_tag >= obj_type_count) { return corrupted_dump(); } ba = &wk->vm.objects.obj_aos[type_tag - _obj_aos_start]; o->val = ba->len; bucket_arr_pushn(ba, NULL, 0, 1); if (type_tag == obj_string) { if (!fs_fread(&ser_s, sizeof(struct serial_str), f)) { return false; } struct str *ss = bucket_arr_get(ba, o->val); if (ser_s.flags & str_flag_big) { if (!get_big_string(wk, bst, &ser_s, ss)) { return false; } } else { uint32_t bucket_i = ser_s.s % wk->vm.objects.chrs.bucket_size, buckets_i = ser_s.s / wk->vm.objects.chrs.bucket_size; if (buckets_i > wk->vm.objects.chrs.buckets.len || bucket_i > ((struct bucket *)(arr_get( &wk->vm.objects.chrs.buckets, buckets_i))) ->len) { return corrupted_dump(); } *ss = (struct str){ .s = bucket_arr_get(&wk->vm.objects.chrs, ser_s.s), .len = ser_s.len, .flags = ser_s.flags, }; } } else { if (!fs_fread(bucket_arr_get(ba, o->val), ba->item_size, f)) { return false; } } } return true; } bool serial_dump(struct workspace *wk_src, obj o, FILE *f) { bool ret = false; struct workspace wk_dest = { 0 }; vm_init_objects(&wk_dest); struct arr big_string_offsets; arr_init(&big_string_offsets, 32, sizeof(uint64_t)); obj obj_dest; if (!obj_clone(wk_src, &wk_dest, o, &obj_dest)) { goto ret; } /* obj_lprintf(&wk_dest, "saving %o\n", obj_dest); */ if (!(dump_serial_header(f) && dump_uint32(obj_dest, f) && dump_bucket_arr(&wk_dest.vm.objects.chrs, f) && dump_big_strings(&wk_dest, &big_string_offsets, f) && dump_objs(&wk_dest, &big_string_offsets, f) && dump_bucket_arr(&wk_dest.vm.objects.dict_elems, f) && dump_bucket_arr(&wk_dest.vm.objects.array_elems, f))) { goto ret; } ret = true; ret: vm_destroy_objects(&wk_dest); arr_destroy(&big_string_offsets); return ret; } bool serial_load(struct workspace *wk, obj *res, FILE *f) { bool ret = false; struct workspace wk_src = { 0 }; vm_init_objects(&wk_src); // remove null elems bucket_arr_clear(&wk_src.vm.objects.dict_elems); bucket_arr_clear(&wk_src.vm.objects.array_elems); struct big_string_table bst = { 0 }; obj obj_src; if (!(load_serial_header(f) && load_uint32(&obj_src, f) && load_bucket_arr(&wk_src.vm.objects.chrs, f) && load_big_strings(&wk_src, &bst, f) && load_objs(&wk_src, &bst, f) && load_bucket_arr(&wk_src.vm.objects.dict_elems, f) && load_bucket_arr(&wk_src.vm.objects.array_elems, f))) { goto ret; } /* obj_lprintf(&wk_src, "loaded %o\n", obj_src); */ if (!obj_clone(&wk_src, wk, obj_src, res)) { corrupted_dump(); goto ret; } ret = true; ret: if (bst.data) { z_free(bst.data); } vm_destroy_objects(&wk_src); return ret; } bool serial_load_from_private_dir(struct workspace *wk, obj *res, const char *file) { TSTR(path); path_join(wk, &path, output_path.private_dir, file); if (!fs_file_exists(path.buf)) { return false; } FILE *f; if (!(f = fs_fopen(path.buf, "rb"))) { return false; } bool ret = serial_load(wk, res, f); if (!fs_fclose(f)) { ret = false; } return ret; } muon-v0.5.0/src/lang/lexer.c0000644000175000017500000006343115041716357014653 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include "buf_size.h" #include "error.h" #include "lang/lexer.h" #include "platform/assert.h" /****************************************************************************** * token printing ******************************************************************************/ const char * token_type_to_s(enum token_type type) { switch (type) { case token_type_error: return "error"; case token_type_eof: return "end of file"; case token_type_eol: return "end of line"; case token_type_lparen: return "("; case token_type_rparen: return ")"; case token_type_lbrack: return "["; case token_type_rbrack: return "]"; case token_type_lcurl: return "{"; case token_type_rcurl: return "}"; case token_type_dot: return "."; case token_type_comma: return ","; case token_type_colon: return ":"; case token_type_assign: return "="; case token_type_plus: return "+"; case token_type_minus: return "-"; case token_type_star: return "*"; case token_type_slash: return "/"; case token_type_modulo: return "%"; case token_type_plus_assign: return "+="; case token_type_eq: return "=="; case token_type_neq: return "!="; case token_type_gt: return ">"; case token_type_geq: return ">="; case token_type_lt: return "<"; case token_type_leq: return "<="; case token_type_true: return "true"; case token_type_false: return "false"; case token_type_if: return "if"; case token_type_else: return "else"; case token_type_elif: return "elif"; case token_type_endif: return "endif"; case token_type_and: return "and"; case token_type_or: return "or"; case token_type_not: return "not"; case token_type_not_in: return "not in"; case token_type_foreach: return "foreach"; case token_type_endforeach: return "endforeach"; case token_type_in: return "in"; case token_type_continue: return "continue"; case token_type_break: return "break"; case token_type_identifier: return "identifier"; case token_type_string: return "string"; case token_type_fstring: return "fstring"; case token_type_number: return "number"; case token_type_question_mark: return "?"; case token_type_func: return "func"; case token_type_endfunc: return "endfunc"; case token_type_return: return "return"; case token_type_bitor: return "|"; case token_type_returntype: return "->"; case token_type_doc_comment: return "doc comment"; case token_type_null: return "null"; } UNREACHABLE_RETURN; } const char * token_to_s(struct workspace *wk, struct token *token) { assert(token); static char buf[BUF_SIZE_S + 1]; uint32_t i; i = snprintf(buf, BUF_SIZE_S, "%s", token_type_to_s(token->type)); if (token->type == token_type_string || token->type == token_type_fstring || token->type == token_type_identifier || token->type == token_type_error) { i += obj_snprintf(wk, &buf[i], BUF_SIZE_S - i, ":%o", token->data.str); } else if (token->type == token_type_number) { i += snprintf(&buf[i], BUF_SIZE_S - i, ":%" PRIi64, token->data.num); } /* i += snprintf(&buf[i], BUF_SIZE_S - i, " off %d, len: %d", token->location.off, token->location.len); */ return buf; } /****************************************************************************** * char types ******************************************************************************/ bool is_valid_start_of_identifier(const char c) { return c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); } static bool is_digit(const char c) { return '0' <= c && c <= '9'; } bool is_hex_digit(const char c) { return is_digit(c) || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'); } bool is_valid_inside_of_identifier(const char c) { return is_valid_start_of_identifier(c) || is_digit(c); } static bool is_skipchar(const char c) { return c == '\r' || c == ' ' || c == '\t' || c == '#'; } /****************************************************************************** * lexer utils ******************************************************************************/ #define lexer_str(__len) \ (struct str) \ { \ &lexer->src[lexer->i], lexer->i + __len > lexer->source->len ? lexer->source->len - lexer->i : __len \ } static void lex_advance(struct lexer *lexer) { if (lexer->i >= lexer->source->len) { return; } ++lexer->i; } static void lex_advance_n(struct lexer *lexer, uint32_t n) { uint32_t i; for (i = 0; i < n; ++i) { lex_advance(lexer); } } struct lex_str_token_table { struct str str; int32_t token_type; int32_t token_subtype; }; static bool lex_str_token_lookup(struct lexer *lexer, struct token *token, const struct lex_str_token_table *table, uint32_t table_len, struct str *str) { uint32_t i; for (i = 0; i < table_len; ++i) { if (str_eql(&table[i].str, str)) { token->type = table[i].token_type; token->location.len = table[i].str.len; token->data.type = table[i].token_subtype; return true; } } return false; } static void lex_copy_str(struct lexer *lexer, struct token *token, uint32_t start, uint32_t end) { token->data.str = make_strn(lexer->wk, &lexer->src[start], end - start); token->location.len = end - start; } static void MUON_ATTR_FORMAT(printf, 3, 4) lex_error_token(struct lexer *lexer, struct token *token, const char *fmt, ...) { token->type = token_type_error; va_list args; va_start(args, fmt); token->data.str = make_strfv(lexer->wk, fmt, args); va_end(args); } /****************************************************************************** * lexer ******************************************************************************/ static const struct lex_str_token_table lex_2chr_tokens[] = { { STR_static("!="), token_type_neq }, { STR_static("+="), token_type_plus_assign }, { STR_static("<="), token_type_leq }, { STR_static("=="), token_type_eq }, { STR_static(">="), token_type_geq }, }; static const struct lex_str_token_table lex_2chr_tokens_func[] = { { STR_static("->"), token_type_returntype }, }; static const struct lex_str_token_table lex_keyword_tokens[] = { { STR_static("and"), token_type_and }, { STR_static("break"), token_type_break }, { STR_static("continue"), token_type_continue }, { STR_static("elif"), token_type_elif }, { STR_static("else"), token_type_else }, { STR_static("endforeach"), token_type_endforeach }, { STR_static("endif"), token_type_endif }, { STR_static("false"), token_type_false }, { STR_static("foreach"), token_type_foreach }, { STR_static("if"), token_type_if }, { STR_static("in"), token_type_in }, { STR_static("not"), token_type_not }, { STR_static("or"), token_type_or }, { STR_static("true"), token_type_true }, }; static const struct lex_str_token_table lex_keyword_tokens_func[] = { { STR_static("endfunc"), token_type_endfunc }, { STR_static("func"), token_type_func }, { STR_static("return"), token_type_return }, { STR_static("null"), token_type_null }, }; static void lex_number(struct lexer *lexer, struct token *token) { token->type = token_type_number; uint32_t base = 10; uint32_t start = lexer->i; if (lexer->src[lexer->i] == '0') { switch (lexer->src[lexer->i + 1]) { case 'X': case 'x': base = 16; lexer->i += 2; break; case 'B': case 'b': base = 2; lexer->i += 2; break; case 'O': case 'o': base = 8; lexer->i += 2; break; default: lex_advance(lexer); if (lexer->mode & lexer_mode_fmt) { lex_copy_str(lexer, token, start, lexer->i); } else { token->data.num = 0; } return; } } char *endptr = 0; errno = 0; int64_t val = strtoll(&lexer->src[lexer->i], &endptr, base); assert(endptr); if (endptr == &lexer->src[lexer->i]) { ++lexer->i; lex_error_token(lexer, token, "invalid number"); return; } lexer->i += endptr - &lexer->src[lexer->i]; if (errno == ERANGE) { lex_error_token(lexer, token, "number out of representable range [%" PRId64 ",%" PRId64 "]", INT64_MIN, INT64_MAX); return; } if (lexer->mode & lexer_mode_fmt) { lex_copy_str(lexer, token, start, lexer->i); } else { token->data.num = val; } } bool lex_string_escape_utf8(struct workspace *wk, struct tstr *buf, uint32_t val) { uint8_t pre, b, pre_len; uint32_t len, i; /* From: https://en.wikipedia.org/wiki/UTF-8#Encoding * U+0000 - U+007F 0x00 0xxxxxxx * U+0080 - U+07FF 0xc0 110xxxxx 10xxxxxx * U+0800 - U+FFFF 0xe0 1110xxxx 10xxxxxx 10xxxxxx * U+10000 - U+10FFFF 0xf0 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx * */ if (val <= 0x7f) { tstr_push(wk, buf, val); return true; } else if (val <= 0x07ff) { len = 2; pre_len = 5; pre = 0xc0; b = 11; } else if (val <= 0xffff) { len = 3; pre_len = 4; pre = 0xe0; b = 16; } else if (val <= 0x10ffff) { len = 4; pre_len = 3; pre = 0xf0; b = 21; } else { return false; } tstr_push(wk, buf, pre | (val >> (b - pre_len))); for (i = 1; i < len; ++i) { tstr_push(wk, buf, 0x80 | ((val >> (b - pre_len - (6 * i))) & 0x3f)); } return true; } static bool lex_string_escape(struct lexer *lexer, struct token *token, struct tstr *buf) { switch (lexer->src[lexer->i + 1]) { case '\\': case '\'': lex_advance(lexer); tstr_push(lexer->wk, buf, lexer->src[lexer->i]); return true; case 'a': lex_advance(lexer); tstr_push(lexer->wk, buf, '\a'); return true; case 'b': lex_advance(lexer); tstr_push(lexer->wk, buf, '\b'); return true; case 'f': lex_advance(lexer); tstr_push(lexer->wk, buf, '\f'); return true; case 'r': lex_advance(lexer); tstr_push(lexer->wk, buf, '\r'); return true; case 't': lex_advance(lexer); tstr_push(lexer->wk, buf, '\t'); return true; case 'v': lex_advance(lexer); tstr_push(lexer->wk, buf, '\v'); return true; case 'n': lex_advance(lexer); tstr_push(lexer->wk, buf, '\n'); return true; case 'x': case 'u': case 'U': { uint32_t len = 0; switch (lexer->src[lexer->i + 1]) { case 'x': len = 2; break; case 'u': len = 4; break; case 'U': len = 8; break; } lex_advance(lexer); char num[9] = { 0 }; uint32_t i; for (i = 0; i < len; ++i) { num[i] = lexer->src[lexer->i + 1]; if (!is_hex_digit(num[i])) { lex_error_token(lexer, token, "unterminated hex escape"); return false; } lex_advance(lexer); } uint32_t val = strtol(num, 0, 16); if (!lex_string_escape_utf8(lexer->wk, buf, val)) { lex_error_token(lexer, token, "invalid utf-8 escape 0x%x", val); return false; } return true; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { char num[4] = { 0 }; uint32_t i; for (i = 0; i < 3; ++i) { num[i] = lexer->src[lexer->i + 1]; if (!is_digit(num[i])) { break; } lex_advance(lexer); } tstr_push(lexer->wk, buf, strtol(num, 0, 8)); return true; } default: tstr_push(lexer->wk, buf, lexer->src[lexer->i]); lex_advance(lexer); tstr_push(lexer->wk, buf, lexer->src[lexer->i]); return true; case 0: lex_error_token(lexer, token, "unterminated hex escape"); return false; } UNREACHABLE_RETURN; } typedef bool(lex_string_escape_fun)(struct lexer *lexer, struct token *token, struct tstr *buf); static void lex_basic_string(struct lexer *lexer, struct token *token, struct tstr *buf, char end, lex_string_escape_fun escape) { lex_advance(lexer); for (; lexer->i < lexer->source->len && lexer->src[lexer->i] != end; lex_advance(lexer)) { switch (lexer->src[lexer->i]) { case 0: case '\n': goto unterminated_string; case '\\': { if (!lex_string_escape(lexer, token, buf)) { return; } break; } default: tstr_push(lexer->wk, buf, lexer->src[lexer->i]); break; } } if (lexer->src[lexer->i] != end) { unterminated_string: lex_error_token(lexer, token, "unterminated string"); return; } lex_advance(lexer); token->data.str = tstr_into_str(lexer->wk, buf); } static void lex_string(struct lexer *lexer, struct token *token) { const struct str multiline_terminator = STR("'''"); TSTR(buf); if (str_eql(&lexer_str(multiline_terminator.len), &multiline_terminator)) { lex_advance_n(lexer, multiline_terminator.len); while (lexer->source->len - lexer->i >= multiline_terminator.len && !str_eql(&lexer_str(multiline_terminator.len), &multiline_terminator)) { if (lexer->src[lexer->i] != '\r') { tstr_push(lexer->wk, &buf, lexer->src[lexer->i]); } lex_advance(lexer); } if (str_eql(&lexer_str(multiline_terminator.len), &multiline_terminator)) { lex_advance_n(lexer, 3); token->data.str = tstr_into_str(lexer->wk, &buf); } else { lex_error_token(lexer, token, "unterminated multiline string"); } return; } lex_basic_string(lexer, token, &buf, '\'', lex_string_escape); } enum lexer_enclosed_state { lexer_enclosed_state_none = 0, lexer_enclosed_state_enclosed = 1, }; static void lexer_push_pop_enclosed_state(struct lexer *lexer, enum token_type type) { bool pop = false; uint8_t enclosed_state; switch (type) { case token_type_func: { enclosed_state = lexer_enclosed_state_none; break; } case token_type_endfunc: { pop = true; break; } case '(': case '[': case '{': { enclosed_state = lexer_enclosed_state_enclosed; break; } case ')': case ']': case '}': { pop = true; break; } default: return; } if (pop) { if (lexer->stack.len) { stack_pop(&lexer->stack, lexer->enclosed_state); } } else { stack_push(&lexer->stack, lexer->enclosed_state, enclosed_state); } } void lexer_next(struct lexer *lexer, struct token *token) { uint32_t start; lexer->ws_start = lexer->i, lexer->ws_end = lexer->i; restart: *token = (struct token){ .location = (struct source_location){ .off = lexer->i, .len = 1 }, }; if (lexer->i >= lexer->source->len) { token->type = token_type_eof; return; } if (lexer->mode & lexer_mode_bom_error) { lex_error_token(lexer, token, "Unexpected utf-8 BOM. File must be in utf-8 with no BOM."); lexer->mode &= ~lexer_mode_bom_error; lexer->i += 3; return; } while (is_skipchar(lexer->src[lexer->i])) { if (lexer->src[lexer->i] == '#') { lex_advance(lexer); obj doc_comment = 0; if (!(lexer->mode & lexer_mode_fmt) && (lexer->mode & lexer_mode_functions) && lexer->src[lexer->i] == '#') { lex_advance(lexer); if (strchr(" \t", lexer->src[lexer->i])) { lex_advance(lexer); } doc_comment = make_str(lexer->wk, ""); } start = lexer->i; while (lexer->src[lexer->i]) { if (doc_comment) { if (lexer->src[lexer->i] == '\n') { uint32_t skip = 1; while (strchr(" \t", lexer->src[lexer->i + skip])) { ++skip; } if (str_startswith(&STRL(&lexer->src[lexer->i + skip]), &STR("##"))) { skip += 2; uint32_t i; for (i = 0; i < skip; ++i) { lex_advance(lexer); } str_app(lexer->wk, &doc_comment, "\n"); if (strchr(" \t", lexer->src[lexer->i])) { lex_advance(lexer); } continue; } else { break; } } else { str_appn(lexer->wk, &doc_comment, &lexer->src[lexer->i], 1); } } else if (lexer->src[lexer->i] == '\n') { break; } lex_advance(lexer); } if (lexer->mode & lexer_mode_fmt) { bool fmt_on; obj s; s = make_strn(lexer->wk, &lexer->src[start], lexer->i - start); s = str_strip(lexer->wk, get_str(lexer->wk, s), 0, 0); if (lexer_is_fmt_comment(get_str(lexer->wk, s), &fmt_on)) { if (fmt_on) { if (lexer->fmt.in_raw_block) { s = make_strn(lexer->wk, &lexer->src[lexer->fmt.raw_block_start], (start - 1) - lexer->fmt.raw_block_start); obj_array_push(lexer->wk, lexer->fmt.raw_blocks, s); lexer->fmt.in_raw_block = false; } } else { if (!lexer->fmt.in_raw_block) { lexer->fmt.raw_block_start = lexer->i; lexer->fmt.in_raw_block = true; } } } } else if (doc_comment) { token->location.off = start; token->type = token_type_doc_comment; token->data.str = doc_comment; return; } } else { lex_advance(lexer); } } if (str_eql(&lexer_str(2), &STR("\\\n"))) { lex_advance_n(lexer, 2); goto restart; } else if (str_eql(&lexer_str(3), &STR("\\\r\n"))) { lex_advance_n(lexer, 3); goto restart; } lexer->ws_end = lexer->i; token->location.off = lexer->i; struct str lexer_str_2chr = lexer_str(2); if (lex_str_token_lookup(lexer, token, lex_2chr_tokens, ARRAY_LEN(lex_2chr_tokens), &lexer_str_2chr) || ((lexer->mode & lexer_mode_functions) && lex_str_token_lookup( lexer, token, lex_2chr_tokens_func, ARRAY_LEN(lex_2chr_tokens_func), &lexer_str_2chr))) { lex_advance_n(lexer, 2); return; } if (str_eql(&lexer_str(2), &STR("f\'"))) { start = lexer->i; lex_advance(lexer); token->type = token_type_fstring; lex_string(lexer, token); token->location.len = lexer->i - token->location.off; if (lexer->mode & lexer_mode_fmt && token->type != token_type_error) { token->type = token_type_string; lex_copy_str(lexer, token, start, lexer->i); } return; } else if (is_valid_start_of_identifier(lexer->src[lexer->i])) { start = lexer->i; struct str str = { &lexer->src[lexer->i] }; while (is_valid_inside_of_identifier(lexer->src[lexer->i])) { lex_advance(lexer); ++str.len; } if (lex_str_token_lookup(lexer, token, lex_keyword_tokens, ARRAY_LEN(lex_keyword_tokens), &str) || ((lexer->mode & lexer_mode_functions) && lex_str_token_lookup(lexer, token, lex_keyword_tokens_func, ARRAY_LEN(lex_keyword_tokens_func), &str))) { lexer_push_pop_enclosed_state(lexer, token->type); return; } else { token->type = token_type_identifier; lex_copy_str(lexer, token, start, lexer->i); } return; } else if (is_digit(lexer->src[lexer->i])) { lex_number(lexer, token); token->location.len = lexer->i - token->location.off; return; } switch (lexer->src[lexer->i]) { case '\n': lex_advance(lexer); if (lexer->enclosed_state) { goto restart; } else { token->type = token_type_eol; } return; case '\'': start = lexer->i; token->type = token_type_string; lex_string(lexer, token); token->location.len = lexer->i - token->location.off; if (lexer->mode & lexer_mode_fmt && token->type != token_type_error) { lex_copy_str(lexer, token, start, lexer->i); } return; case '(': case '[': case '{': token->type = lexer->src[lexer->i]; lexer_push_pop_enclosed_state(lexer, token->type); break; case ')': case ']': case '}': token->type = lexer->src[lexer->i]; lexer_push_pop_enclosed_state(lexer, token->type); break; case '.': case ',': case ':': case '?': case '+': case '-': case '*': case '/': case '%': case '=': case '>': case '<': token->type = lexer->src[lexer->i]; break; case '|': if (!(lexer->mode & lexer_mode_functions)) { goto unexpected_character; } token->type = lexer->src[lexer->i]; break; case '\0': if (lexer->i != lexer->source->len) { goto unexpected_character; } token->type = token_type_eof; break; default: unexpected_character: lex_error_token(lexer, token, "unexpected character: '%c'", lexer->src[lexer->i]); break; } lex_advance(lexer); return; } void lexer_init(struct lexer *lexer, struct workspace *wk, const struct source *src, enum lexer_mode mode) { *lexer = (struct lexer){ .wk = wk, .source = src, .src = src->src, .mode = mode, }; if (src->len >= 3 && memcmp(src->src, (uint8_t []){ 0xef, 0xbb, 0xbf }, 3) == 0) { lexer->mode |= lexer_mode_bom_error; } stack_init(&lexer->stack, 2048); if (lexer->mode & lexer_mode_fmt) { lexer->fmt.raw_blocks = make_obj(lexer->wk, obj_array); } } void lexer_destroy(struct lexer *lexer) { stack_destroy(&lexer->stack); } /****************************************************************************** * fmt related ******************************************************************************/ obj lexer_get_preceeding_whitespace(struct lexer *lexer) { return make_strn(lexer->wk, &lexer->src[lexer->ws_start], lexer->ws_end - lexer->ws_start); } bool lexer_is_fmt_comment(const struct str *comment, bool *fmt_on) { if (str_eql(comment, &STR("fmt:off")) || str_eql(comment, &STR("fmt: off"))) { *fmt_on = false; return true; } else if (str_eql(comment, &STR("fmt:on")) || str_eql(comment, &STR("fmt: on"))) { *fmt_on = true; return true; } return false; } /****************************************************************************** * cmake ******************************************************************************/ static const struct lex_str_token_table cm_lex_keyword_tokens_command[] = { { STR_static("else"), token_type_else }, { STR_static("elseif"), token_type_elif }, { STR_static("endif"), token_type_endif }, { STR_static("if"), token_type_if }, }; static const struct lex_str_token_table cm_lex_keyword_tokens_conditional[] = { { STR_static("NOT"), token_type_not }, { STR_static("AND"), token_type_and }, { STR_static("OR"), token_type_or }, { STR_static("EQUAL"), token_type_eq }, { STR_static("LESS"), '<' }, { STR_static("LESS_EQUAL"), token_type_leq }, { STR_static("GREATER"), '>' }, { STR_static("GREATER_EQUAL"), token_type_geq }, { STR_static("STR_EQUAL"), token_type_eq, cm_token_subtype_comp_str }, { STR_static("STR_LESS"), '<', cm_token_subtype_comp_str }, { STR_static("STR_LESS_EQUAL"), token_type_leq, cm_token_subtype_comp_str }, { STR_static("STR_GREATER"), '>', cm_token_subtype_comp_str }, { STR_static("STR_GREATER_EQUAL"), token_type_geq, cm_token_subtype_comp_str }, { STR_static("VERSION_EQUAL"), token_type_eq, cm_token_subtype_comp_ver }, { STR_static("VERSION_LESS"), '<', cm_token_subtype_comp_ver }, { STR_static("VERSION_LESS_EQUAL"), token_type_leq, cm_token_subtype_comp_ver }, { STR_static("VERSION_GREATER"), '>', cm_token_subtype_comp_ver }, { STR_static("VERSION_GREATER_EQUAL"), token_type_geq, cm_token_subtype_comp_ver }, { STR_static("PATH_EQUAL"), token_type_eq, cm_token_subtype_comp_path }, { STR_static("MATCHES"), token_type_eq, cm_token_subtype_comp_regex }, }; void cm_lexer_next(struct lexer *lexer, struct token *token) { uint32_t start; restart: *token = (struct token){ .location = (struct source_location){ .off = lexer->i, .len = 1 }, }; if (lexer->i >= lexer->source->len) { token->type = token_type_eof; return; } while (is_skipchar(lexer->src[lexer->i])) { if (lexer->src[lexer->i] == '#') { lex_advance(lexer); start = lexer->i; while (lexer->src[lexer->i] && lexer->src[lexer->i] != '\n') { lex_advance(lexer); } if (lexer->mode & lexer_mode_fmt) { bool fmt_on; obj s; s = make_strn(lexer->wk, &lexer->src[start], lexer->i - start); s = str_strip(lexer->wk, get_str(lexer->wk, s), 0, 0); if (lexer_is_fmt_comment(get_str(lexer->wk, s), &fmt_on)) { if (fmt_on) { if (lexer->fmt.in_raw_block) { s = make_strn(lexer->wk, &lexer->src[lexer->fmt.raw_block_start], (start - 1) - lexer->fmt.raw_block_start); obj_array_push(lexer->wk, lexer->fmt.raw_blocks, s); lexer->fmt.in_raw_block = false; } } else { if (!lexer->fmt.in_raw_block) { lexer->fmt.raw_block_start = lexer->i; lexer->fmt.in_raw_block = true; } } } } } else { lex_advance(lexer); } } token->location.off = lexer->i; /* if (is_valid_start_of_identifier(lexer->src[lexer->i])) { */ /* start = lexer->i; */ /* struct str str = { &lexer->src[lexer->i] }; */ /* while (is_valid_inside_of_identifier(lexer->src[lexer->i])) { */ /* lex_advance(lexer); */ /* ++str.len; */ /* } */ /* token->type = token_type_identifier; */ /* lex_copy_str(lexer, token, start, lexer->i); */ /* return; */ /* } */ if (!strchr("()#\" \r\n\t", lexer->src[lexer->i])) { start = lexer->i; struct str str = { &lexer->src[lexer->i] }; while (!strchr("()#\" \r\n\t", lexer->src[lexer->i])) { lex_advance(lexer); ++str.len; } token->type = token_type_string; if (is_valid_start_of_identifier(lexer->src[start])) { uint32_t i; for (i = 1; i < str.len; ++i) { if (!is_valid_inside_of_identifier(lexer->src[start + i])) { break; } } if (i == str.len) { token->type = token_type_identifier; } } if (token->type == token_type_identifier) { const struct lex_str_token_table *token_table = 0; uint32_t token_table_len = 0; switch (lexer->cm_mode) { case cm_lexer_mode_default: break; case cm_lexer_mode_command: token_table = cm_lex_keyword_tokens_command; token_table_len = ARRAY_LEN(cm_lex_keyword_tokens_command); break; case cm_lexer_mode_conditional: token_table = cm_lex_keyword_tokens_conditional; token_table_len = ARRAY_LEN(cm_lex_keyword_tokens_conditional); break; } if (token_table && lex_str_token_lookup(lexer, token, token_table, token_table_len, &str)) { return; } } lex_copy_str(lexer, token, start, lexer->i); return; } switch (lexer->src[lexer->i]) { case '\n': lex_advance(lexer); if (lexer->enclosed_state) { goto restart; } else { token->type = token_type_eol; } return; case '"': { start = lexer->i; token->type = token_type_string; TSTR(buf); lex_basic_string(lexer, token, &buf, '"', lex_string_escape); token->location.len = lexer->i - token->location.off; return; } case '(': token->type = lexer->src[lexer->i]; lexer_push_pop_enclosed_state(lexer, token->type); break; case ')': token->type = lexer->src[lexer->i]; lexer_push_pop_enclosed_state(lexer, token->type); break; case '\0': if (lexer->i != lexer->source->len) { goto unexpected_character; } token->type = token_type_eof; break; default: unexpected_character: lex_error_token(lexer, token, "unexpected character: '%c'", lexer->src[lexer->i]); break; } lex_advance(lexer); return; } muon-v0.5.0/src/lang/vm.c0000644000175000017500000024634615041716357014166 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "buf_size.h" #include "coerce.h" #include "error.h" #include "lang/analyze.h" #include "lang/compiler.h" #include "lang/func_lookup.h" #include "lang/object_iterators.h" #include "lang/parser.h" #include "lang/typecheck.h" #include "lang/vm.h" #include "lang/workspace.h" #include "log.h" #include "platform/assert.h" #include "platform/init.h" #include "platform/mem.h" #include "platform/path.h" #include "tracy.h" const uint32_t op_operands[op_count] = { [op_iterator] = 1, [op_iterator_next] = 1, [op_store] = 1, [op_constant] = 1, [op_constant_list] = 1, [op_constant_dict] = 1, [op_constant_func] = 1, [op_call] = 2, [op_member] = 1, [op_call_native] = 3, [op_jmp_if_true] = 1, [op_jmp_if_false] = 1, [op_jmp_if_disabler] = 1, [op_jmp_if_disabler_keep] = 1, [op_jmp] = 1, [op_typecheck] = 1, [op_az_branch] = 3, }; const uint32_t op_operand_size = 3; /****************************************************************************** * object stack ******************************************************************************/ enum { object_stack_page_size = 1024 / sizeof(struct obj_stack_entry) }; static void object_stack_alloc_page(struct object_stack *s) { bucket_arr_pushn(&s->ba, 0, 0, object_stack_page_size); s->ba.len -= object_stack_page_size; ++s->bucket; s->page = (struct obj_stack_entry *)((struct bucket *)s->ba.buckets.e)[s->bucket].mem; ((struct bucket *)s->ba.buckets.e)[s->bucket].len = object_stack_page_size; s->i = 0; } static void object_stack_init(struct object_stack *s) { bucket_arr_init(&s->ba, object_stack_page_size, sizeof(struct obj_stack_entry)); s->page = (struct obj_stack_entry *)((struct bucket *)s->ba.buckets.e)[0].mem; ((struct bucket *)s->ba.buckets.e)[0].len = object_stack_page_size; } static void object_stack_push_ip(struct workspace *wk, obj o, uint32_t ip) { if (wk->vm.stack.i >= object_stack_page_size) { object_stack_alloc_page(&wk->vm.stack); } wk->vm.stack.page[wk->vm.stack.i] = (struct obj_stack_entry){ .o = o, .ip = ip }; ++wk->vm.stack.i; ++wk->vm.stack.ba.len; } void object_stack_push(struct workspace *wk, obj o) { object_stack_push_ip(wk, o, wk->vm.ip - 1); } struct obj_stack_entry * object_stack_pop_entry(struct object_stack *s) { if (!s->i) { assert(s->bucket); --s->bucket; s->page = (struct obj_stack_entry *)((struct bucket *)s->ba.buckets.e)[s->bucket].mem; s->i = object_stack_page_size; } --s->i; --s->ba.len; return &s->page[s->i]; } obj object_stack_pop(struct object_stack *s) { return object_stack_pop_entry(s)->o; } struct obj_stack_entry * object_stack_peek_entry(struct object_stack *s, uint32_t off) { return bucket_arr_get(&s->ba, s->ba.len - off); } obj object_stack_peek(struct object_stack *s, uint32_t off) { return ((struct obj_stack_entry *)bucket_arr_get(&s->ba, s->ba.len - off))->o; } void object_stack_discard(struct object_stack *s, uint32_t n) { assert(s->ba.len >= n); s->ba.len -= n; s->bucket = (s->ba.len ? s->ba.len - 1 : 0) / s->ba.bucket_size; s->page = (struct obj_stack_entry *)((struct bucket *)s->ba.buckets.e)[s->bucket].mem; s->i = s->ba.len - (s->bucket * s->ba.bucket_size); } void object_stack_print(struct workspace *wk, struct object_stack *s) { for (int32_t i = s->ba.len - 1; i >= 0; --i) { struct obj_stack_entry *e = bucket_arr_get(&s->ba, i); obj_lprintf(wk, log_debug, "%o%s", e->o, i > 0 ? ", " : ""); } log_plain(log_debug, "\n"); } /****************************************************************************** * vm errors ******************************************************************************/ static uint32_t vm_lookup_source_location_mapping_idx(struct vm *vm, uint32_t ip) { struct source_location_mapping *locations = (struct source_location_mapping *)vm->locations.e; uint32_t i; for (i = 0; i < vm->locations.len; ++i) { if (locations[i].ip > ip) { if (i) { --i; } break; } } if (i == vm->locations.len) { --i; } return i; } void vm_lookup_inst_location_src_idx(struct vm *vm, uint32_t ip, struct source_location *loc, uint32_t *src_idx) { struct source_location_mapping *locations = (struct source_location_mapping *)vm->locations.e; uint32_t i = vm_lookup_source_location_mapping_idx(vm, ip); *loc = locations[i].loc; *src_idx = locations[i].src_idx; } void vm_lookup_inst_location(struct vm *vm, uint32_t ip, struct source_location *loc, struct source **src) { uint32_t src_idx; vm_lookup_inst_location_src_idx(vm, ip, loc, &src_idx); if (src_idx == UINT32_MAX) { static struct source null_src = { 0 }; *src = &null_src; } else { *src = arr_get(&vm->src, src_idx); } } static obj vm_inst_location_obj(struct workspace *wk, uint32_t ip) { struct source_location loc; struct source *src; vm_lookup_inst_location(&wk->vm, ip, &loc, &src); struct detailed_source_location dloc; get_detailed_source_location(src, loc, &dloc, (enum get_detailed_source_location_flag)0); obj res; res = make_obj(wk, obj_array); obj_array_push( wk, res, make_strf(wk, "%s%s", src->type == source_type_embedded ? "[embedded] " : "", src->label)); obj_array_push(wk, res, make_number(wk, dloc.line)); obj_array_push(wk, res, make_number(wk, dloc.col)); return res; } obj vm_inst_location_str(struct workspace *wk, uint32_t ip) { obj a, src, line, col; a = vm_inst_location_obj(wk, ip); src = obj_array_index(wk, a, 0); line = obj_array_index(wk, a, 1); col = obj_array_index(wk, a, 2); return make_strf(wk, "%s:%d:%d", get_str(wk, src)->s, (uint32_t)get_obj_number(wk, line), (uint32_t)get_obj_number(wk, col)); } obj vm_callstack(struct workspace *wk) { obj res; res = make_obj(wk, obj_array); obj_array_push(wk, res, vm_inst_location_obj(wk, wk->vm.ip - 1)); int32_t i; struct call_frame *frame; for (i = wk->vm.call_stack.len - 1; i >= 0; --i) { frame = arr_get(&wk->vm.call_stack, i); if (frame->return_ip) { obj_array_push(wk, res, vm_inst_location_obj(wk, frame->return_ip - 1)); } } return res; } static void vm_trigger_error(struct workspace *wk) { if (wk->vm.in_analyzer) { az_set_error(); } else { wk->vm.error = true; wk->vm.run = false; } } static void vm_diagnostic_v(struct workspace *wk, uint32_t ip, enum log_level lvl, enum error_message_flag flags, const char *fmt, va_list args) { static char buf[1024]; obj_vsnprintf(wk, buf, ARRAY_LEN(buf), fmt, args); if (!ip) { ip = wk->vm.ip - 1; } else if (ip == UINT32_MAX) { ip = 0; } struct source_location loc = { 0 }; struct source *src = 0; if (ip) { vm_lookup_inst_location(&wk->vm, ip, &loc, &src); } error_message(src, loc, lvl, flags, buf); if (lvl == log_error) { vm_trigger_error(wk); } } void vm_diagnostic(struct workspace *wk, uint32_t ip, enum log_level lvl, enum error_message_flag flags, const char *fmt, ...) { va_list args; va_start(args, fmt); vm_diagnostic_v(wk, ip, lvl, flags, fmt, args); va_end(args); } void vm_error_at(struct workspace *wk, uint32_t ip, const char *fmt, ...) { va_list args; va_start(args, fmt); vm_diagnostic_v(wk, ip, log_error, 0, fmt, args); va_end(args); } void vm_warning(struct workspace *wk, const char *fmt, ...) { va_list args; va_start(args, fmt); vm_diagnostic_v(wk, 0, log_warn, 0, fmt, args); va_end(args); } void vm_warning_at(struct workspace *wk, uint32_t ip, const char *fmt, ...) { va_list args; va_start(args, fmt); vm_diagnostic_v(wk, ip, log_warn, 0, fmt, args); va_end(args); } void vm_error(struct workspace *wk, const char *fmt, ...) { va_list args; va_start(args, fmt); vm_diagnostic_v(wk, 0, log_error, 0, fmt, args); va_end(args); } /****************************************************************************** * pop_args ******************************************************************************/ static bool typecheck_function_arg(struct workspace *wk, uint32_t ip, obj val, type_tag type) { bool listify = (type & TYPE_TAG_LISTIFY) == TYPE_TAG_LISTIFY; type &= ~TYPE_TAG_LISTIFY; enum obj_type t = get_obj_type(wk, val); obj v; if (listify && t == obj_array) { obj_array_flat_for_(wk, val, v, flat_iter) { if (typecheck_typeinfo(wk, v, tc_array)) { continue; } else if (!typecheck(wk, ip, v, type)) { obj_array_flat_iter_end(wk, &flat_iter); return false; } } return true; } else if (listify && typecheck_typeinfo(wk, val, tc_array)) { return true; } return typecheck(wk, ip, val, type); } static void vm_function_arg_type_error(struct workspace *wk, uint32_t ip, obj obj_id, type_tag type, const char *name) { vm_error_at(wk, ip, "expected type %s, got %s for argument%s%s", typechecking_type_to_s(wk, type), get_cstr(wk, obj_type_to_typestr(wk, obj_id)), name ? " " : "", name ? name : ""); } static bool typecheck_and_mutate_function_arg(struct workspace *wk, uint32_t ip, obj *val, type_tag type, const char *name) { bool listify = (type & TYPE_TAG_LISTIFY) == TYPE_TAG_LISTIFY; type &= ~TYPE_TAG_LISTIFY; enum obj_type t = get_obj_type(wk, *val); // If obj_file or tc_file is requested, and the argument is an array of // length 1, try to unpack it. if (!listify && (type == obj_file || (type & tc_file) == tc_file)) { if (t == obj_array && get_obj_array(wk, *val)->len == 1) { obj i0; i0 = obj_array_index(wk, *val, 0); if (get_obj_type(wk, i0) == obj_file) { *val = i0; } } else if (t == obj_typeinfo && typecheck_typeinfo(wk, *val, tc_array)) { return true; } } if (listify) { obj v, arr; arr = make_obj(wk, obj_array); if (t == obj_array) { obj_array_flat_for_(wk, *val, v, flat_iter) { if (v == obj_disabler) { wk->vm.saw_disabler = true; } else if (typecheck_typeinfo(wk, v, tc_array)) { // Consider a function with signature: // func f(a listify[str]) // Now, imagine it gets called like this: // f(['a', ['b'], , 'd']) // When flattening this array, it is // impossible to flatten the typeinfo // array because it lacks any // information about what might be // inside it, or even if it is empty or // not. // // Also, consider this: // f(['a', ['b'], , 'd']) // // Now, the typeinfo could be anything // (including an array) so we still // don't know whether to flatten it. // // For now, just let these values // through unscathed. } else if (!typecheck_custom(wk, ip, v, type, 0)) { vm_function_arg_type_error(wk, ip, v, type, name); obj_array_flat_iter_end(wk, &flat_iter); return false; } obj_array_push(wk, arr, v); } } else if (t == obj_typeinfo && typecheck_typeinfo(wk, *val, tc_array)) { return true; } else { if (*val == obj_disabler) { wk->vm.saw_disabler = true; } else if (!typecheck_custom(wk, ip, *val, type, 0)) { vm_function_arg_type_error(wk, ip, *val, type, name); return false; } obj_array_push(wk, arr, *val); } *val = arr; return true; } if (!typecheck_custom(wk, ip, *val, type, 0)) { vm_function_arg_type_error(wk, ip, *val, type, name); return false; } return true; } static bool handle_kwarg(struct workspace *wk, struct args_kw akw[], const char *kw, uint32_t kw_ip, obj v, uint32_t v_ip) { uint32_t i; bool glob = false; for (i = 0; akw[i].key; ++i) { if (akw[i].type & TYPE_TAG_GLOB) { glob = true; break; } else if (strcmp(kw, akw[i].key) == 0) { break; } } if (!akw[i].key) { vm_error_at(wk, kw_ip, "unknown kwarg %s", kw); return false; } else if (akw[i].set && !glob) { vm_error_at(wk, kw_ip, "keyword argument '%s' set twice", kw); return false; } if (!typecheck_and_mutate_function_arg(wk, v_ip, &v, akw[i].type & ~TYPE_TAG_GLOB, akw[i].key)) { return false; } if (glob) { obj_dict_set(wk, akw[i].val, make_str(wk, kw), v); akw[i].node = v_ip; akw[i].set = true; } else { akw[i].val = v; akw[i].node = v_ip; akw[i].set = true; } return true; } bool vm_pop_args(struct workspace *wk, struct args_norm an[], struct args_kw akw[]) { const char *kw; struct obj_stack_entry *entry; uint32_t i, j, argi; uint32_t args_popped = 0; bool got_kwargs_typeinfo = false; if (akw) { for (i = 0; akw[i].key; ++i) { akw[i].set = false; akw[i].val = 0; if (akw[i].type & TYPE_TAG_GLOB) { akw[i].val = make_obj(wk, obj_dict); akw[i].set = true; } } } else if (wk->vm.nkwargs) { vm_error(wk, "this function does not accept kwargs"); goto err; } for (i = 0; i < wk->vm.nkwargs; ++i) { entry = object_stack_pop_entry(&wk->vm.stack); ++args_popped; kw = get_str(wk, entry->o)->s; if (strcmp(kw, "kwargs") == 0) { entry = object_stack_pop_entry(&wk->vm.stack); ++args_popped; if (entry->o == obj_disabler) { wk->vm.saw_disabler = true; continue; } if (!typecheck(wk, entry->ip, entry->o, obj_dict)) { goto err; } if (get_obj_type(wk, entry->o) == obj_typeinfo) { // If we get kwargs as typeinfo, we can't do // anything useful got_kwargs_typeinfo = true; continue; } obj k, v; obj_dict_for(wk, entry->o, k, v) { if (!handle_kwarg(wk, akw, get_cstr(wk, k), entry->ip, v, entry->ip)) { goto err; } wk->vm.saw_disabler |= v == obj_disabler; } } else { uint32_t kw_ip = entry->ip; entry = object_stack_pop_entry(&wk->vm.stack); ++args_popped; if (!handle_kwarg(wk, akw, kw, kw_ip, entry->o, entry->ip)) { goto err; } wk->vm.saw_disabler |= entry->o == obj_disabler; } } if (akw && !got_kwargs_typeinfo) { for (i = 0; akw[i].key; ++i) { if (akw[i].required && !akw[i].set) { vm_error(wk, "missing required keyword argument: %s", akw[i].key); goto err; } } } argi = 0; for (i = 0; an && an[i].type != ARG_TYPE_NULL; ++i) { an[i].set = false; type_tag type = an[i].type; if (type & TYPE_TAG_GLOB) { type &= ~TYPE_TAG_GLOB; type |= TYPE_TAG_LISTIFY; an[i].set = true; an[i].val = make_obj(wk, obj_array); for (j = i; j < wk->vm.nargs; ++j) { entry = object_stack_peek_entry(&wk->vm.stack, wk->vm.nargs - argi); wk->vm.saw_disabler |= entry->o == obj_disabler; obj_array_push(wk, an[i].val, entry->o); an[i].node = entry->ip; ++argi; if (!typecheck_function_arg(wk, entry->ip, entry->o, type)) { goto err; } } } else { if (argi >= wk->vm.nargs) { if (!an[i].optional) { vm_error(wk, "missing positional argument%s%s", an[i].name ? ": " : "", an[i].name ? an[i].name : ""); goto err; } else { break; } } entry = object_stack_peek_entry(&wk->vm.stack, wk->vm.nargs - argi); wk->vm.saw_disabler |= entry->o == obj_disabler; an[i].val = entry->o; an[i].node = entry->ip; an[i].set = true; ++argi; } if (!typecheck_and_mutate_function_arg(wk, an[i].node, &an[i].val, type, 0)) { goto err; } } if (wk->vm.nargs > argi) { vm_error(wk, "too many args, got %d, expected %d", wk->vm.nargs, argi); goto err; } object_stack_discard(&wk->vm.stack, argi); args_popped += argi; if (wk->vm.saw_disabler) { goto err; } return true; err: object_stack_discard(&wk->vm.stack, (wk->vm.nargs + wk->vm.nkwargs * 2) - args_popped); return false; } bool pop_args(struct workspace *wk, struct args_norm an[], struct args_kw akw[]) { return wk->vm.behavior.pop_args(wk, an, akw); } /****************************************************************************** * utility functions ******************************************************************************/ uint32_t vm_constant_host_to_bc(uint32_t n) { #if !defined(MUON_ENDIAN) union { int i; char c; } u = { 1 }; return u.c ? n : bswap_32(n) >> 8; #elif MUON_ENDIAN == 1 return bswap_32(n) >> 8; #elif MUON_ENDIAN == 0 return n; #endif } static uint32_t vm_constant_bc_to_host(uint32_t n) { #if !defined(MUON_ENDIAN) union { int i; char c; } u = { 1 }; return u.c ? n : bswap_32(n << 8); #elif MUON_ENDIAN == 1 return bswap_32(n << 8); #elif MUON_ENDIAN == 0 return n; #endif } obj vm_get_constant(uint8_t *code, uint32_t *ip) { obj r = (code[*ip + 0] << 16) | (code[*ip + 1] << 8) | code[*ip + 2]; r = vm_constant_bc_to_host(r); *ip += 3; return r; } /****************************************************************************** * disassembler ******************************************************************************/ const char * vm_dis_inst(struct workspace *wk, uint8_t *code, uint32_t base_ip) { uint32_t i = 0; static char buf[2048]; buf[0] = 0; #define buf_push(...) i += obj_snprintf(wk, &buf[i], sizeof(buf) - i, __VA_ARGS__); #define op_case(__op) \ case __op: buf_push(#__op); uint32_t ip = base_ip; buf_push("%04x ", ip); uint32_t op = code[ip], constants[3]; { ++ip; uint32_t j; for (j = 0; j < op_operands[op]; ++j) { constants[j] = vm_get_constant(code, &ip); } } // clang-format off switch (op) { op_case(op_pop) break; op_case(op_dup) break; op_case(op_swap) break; op_case(op_stringify) break; op_case(op_index) break; op_case(op_add) break; op_case(op_sub) break; op_case(op_mul) break; op_case(op_div) break; op_case(op_mod) break; op_case(op_eq) break; op_case(op_in) break; op_case(op_gt) break; op_case(op_lt) break; op_case(op_not) break; op_case(op_negate) break; op_case(op_return) break; op_case(op_return_end) break; op_case(op_try_load) break; op_case(op_load) break; op_case(op_store) { buf_push(":%04x:", constants[0]); if (constants[0] & op_store_flag_member) { buf_push("member"); } if (constants[0] & op_store_flag_add_store) { buf_push("+="); } break; } op_case(op_iterator) buf_push(":%d", constants[0]); break; op_case(op_iterator_next) buf_push(":%04x", constants[0]); break; op_case(op_constant) buf_push(":%o", constants[0]); break; op_case(op_constant_list) buf_push(":len:%d", constants[0]); break; op_case(op_constant_dict) buf_push(":len:%d", constants[0]); break; op_case(op_constant_func) buf_push(":%d", constants[0]); break; op_case(op_call) buf_push(":%d,%d", constants[0], constants[1]); break; op_case(op_member) { uint32_t a; a = constants[0]; buf_push(":%o", a); break; } op_case(op_call_native) buf_push(":"); buf_push("%d,%d,", constants[0], constants[1]); uint32_t id = constants[2]; buf_push("%s", native_funcs[id].name); break; op_case(op_jmp_if_true) buf_push(":%04x", constants[0]); break; op_case(op_jmp_if_false) buf_push(":%04x", constants[0]); break; op_case(op_jmp_if_disabler) buf_push(":%04x", constants[0]); break; op_case(op_jmp_if_disabler_keep) buf_push(":%04x", constants[0]); break; op_case(op_jmp) buf_push(":%04x", constants[0]); break; op_case(op_typecheck) buf_push(":%s", obj_type_to_s(constants[0])); break; op_case(op_az_branch) buf_push(":%d", constants[0]); buf_push(", obj:%d, %d", constants[1], constants[2]); break; op_case(op_az_merge) break; op_case(op_dbg_break) break; case op_count: UNREACHABLE; } // clang-format on #undef buf_push assert(ip - base_ip == OP_WIDTH(code[base_ip])); return buf; } void vm_dis(struct workspace *wk) { uint32_t w = 60; char loc_buf[256]; for (uint32_t i = 0; i < wk->vm.code.len;) { uint8_t op = wk->vm.code.e[i]; const char *dis = vm_dis_inst(wk, wk->vm.code.e, i); struct source_location loc; struct source *src; vm_lookup_inst_location(&wk->vm, i, &loc, &src); struct detailed_source_location dloc = { 0 }; if (src) { get_detailed_source_location(src, loc, &dloc, (enum get_detailed_source_location_flag)0); } snprintf(loc_buf, sizeof(loc_buf), "%s:%3d:%02d-[%3d:%02d]", src ? src->label : 0, dloc.line, dloc.col, dloc.end_line, dloc.end_col); log_plain(log_info, "%-*s%s\n", w, dis, loc_buf); /* if (src) { */ /* list_line_range(src, loc, 0); */ /* } */ i += OP_WIDTH(op); } } /****************************************************************************** * vm ops ******************************************************************************/ void vm_push_call_stack_frame(struct workspace *wk, struct call_frame *frame) { arr_push(&wk->vm.call_stack, frame); } struct call_frame * vm_pop_call_stack_frame(struct workspace *wk) { struct call_frame *frame = arr_pop(&wk->vm.call_stack); switch (frame->type) { case call_frame_type_eval: break; case call_frame_type_func: wk->vm.behavior.pop_local_scope(wk); wk->vm.scope_stack = frame->scope_stack; wk->vm.lang_mode = frame->lang_mode; break; } return frame; } static void vm_push_dummy(struct workspace *wk) { // Used when an op encounters an error but still needs to put something on the stack object_stack_push(wk, make_typeinfo(wk, tc_any)); } static void vm_execute_capture(struct workspace *wk, obj a) { uint32_t i; struct obj_capture *capture; capture = get_obj_capture(wk, a); stack_push(&wk->stack, wk->vm.saw_disabler, false); bool ok = pop_args(wk, capture->func->an, capture->func->akw); bool saw_disabler = wk->vm.saw_disabler; stack_pop(&wk->stack, wk->vm.saw_disabler); if (!ok) { if (saw_disabler) { object_stack_push(wk, obj_disabler); } else { object_stack_push(wk, make_typeinfo(wk, flatten_type(wk, capture->func->return_type))); } return; } vm_push_call_stack_frame(wk, &(struct call_frame){ .type = call_frame_type_func, .return_ip = wk->vm.ip, .scope_stack = wk->vm.scope_stack, .expected_return_type = capture->func->return_type, .lang_mode = wk->vm.lang_mode, .func = capture->func, }); wk->vm.lang_mode = capture->func->lang_mode; wk->vm.scope_stack = capture->scope_stack; wk->vm.behavior.push_local_scope(wk); for (i = 0; capture->func->an[i].type != ARG_TYPE_NULL; ++i) { wk->vm.behavior.assign_variable(wk, capture->func->an[i].name, capture->func->an[i].val, capture->func->an[i].node, assign_local); } for (i = 0; capture->func->akw[i].key; ++i) { obj val = 0; if (capture->func->akw[i].set) { val = capture->func->akw[i].val; } else if (capture->defargs) { const struct str s = STRL(capture->func->akw[i].key); obj_dict_index_strn(wk, capture->defargs, s.s, s.len, &val); } wk->vm.behavior.assign_variable( wk, capture->func->akw[i].key, val, capture->func->akw[i].node, assign_local); } wk->vm.ip = capture->func->entry; return; } static void vm_execute_native(struct workspace *wk, uint32_t func_idx, obj self) { obj res = 0; stack_push(&wk->stack, wk->vm.saw_disabler, false); bool ok; { #ifdef TRACY_ENABLE TracyCZoneC(tctx_func, 0xff5000, true); char func_name[1024]; snprintf(func_name, sizeof(func_name), "%s", native_funcs[func_idx].name); TracyCZoneName(tctx_func, func_name, strlen(func_name)); #endif ok = wk->vm.behavior.native_func_dispatch(wk, func_idx, self, &res); TracyCZoneEnd(tctx_func); } bool saw_disabler = wk->vm.saw_disabler; stack_pop(&wk->stack, wk->vm.saw_disabler); if (!ok) { if (saw_disabler) { res = obj_disabler; } else { if (native_funcs[func_idx].flags & func_impl_flag_throws_error) { vm_trigger_error(wk); } else { vm_error(wk, "in function '%s'", native_funcs[func_idx].name); } vm_push_dummy(wk); return; } } object_stack_push(wk, res); } #define binop_disabler_check(a, b) \ if (a == obj_disabler || b == obj_disabler) { \ object_stack_push(wk, obj_disabler); \ return; \ } #define unop_disabler_check(a) \ if (a == obj_disabler) { \ object_stack_push(wk, obj_disabler); \ return; \ } #define vm_pop(__it) entry = object_stack_pop_entry(&wk->vm.stack), __it = entry->o, __it##_ip = entry->ip #define vm_peek(__it, __i) entry = object_stack_peek_entry(&wk->vm.stack, __i), __it = entry->o, __it##_ip = entry->ip static void vm_op_constant(struct workspace *wk) { obj a; a = vm_get_constant(wk->vm.code.e, &wk->vm.ip); object_stack_push(wk, a); } static void vm_op_constant_list(struct workspace *wk) { obj b; uint32_t i, len = vm_get_constant(wk->vm.code.e, &wk->vm.ip); b = make_obj(wk, obj_array); for (i = 0; i < len; ++i) { obj_array_push(wk, b, object_stack_peek(&wk->vm.stack, len - i)); } object_stack_discard(&wk->vm.stack, len); object_stack_push(wk, b); } static void vm_op_constant_dict(struct workspace *wk) { obj b; uint32_t i, len = vm_get_constant(wk->vm.code.e, &wk->vm.ip); b = make_obj(wk, obj_dict); for (i = 0; i < len; ++i) { obj key = object_stack_peek(&wk->vm.stack, (len - i) * 2 - 1); if (wk->vm.in_analyzer && get_obj_type(wk, key) == obj_typeinfo) { object_stack_discard(&wk->vm.stack, len * 2); object_stack_push(wk, make_typeinfo(wk, tc_dict)); return; } obj_dict_set(wk, b, key, object_stack_peek(&wk->vm.stack, (len - i) * 2)); } object_stack_discard(&wk->vm.stack, len * 2); object_stack_push(wk, b); } static void vm_op_constant_func(struct workspace *wk) { obj a, c, defargs; struct obj_capture *capture; defargs = object_stack_pop(&wk->vm.stack); a = vm_get_constant(wk->vm.code.e, &wk->vm.ip); c = make_obj(wk, obj_capture); capture = get_obj_capture(wk, c); capture->func = get_obj_func(wk, a); capture->scope_stack = wk->vm.behavior.scope_stack_dup(wk, wk->vm.scope_stack); capture->defargs = defargs; object_stack_push(wk, c); } #define typecheck_operand(__o, __o_type, __expect_type, __expect_tc_type, __result_type) \ if (__o_type == obj_typeinfo) { \ if (!typecheck_typeinfo(wk, __o, __expect_tc_type)) { \ goto type_err; \ } else { \ res = make_typeinfo(wk, __result_type); \ break; \ } \ } else if (__expect_type != 0 && __o_type != __expect_type) { \ goto type_err; \ } struct check_obj_typeinfo_map { type_tag expect, result; }; static bool typecheck_typeinfo_operands(struct workspace *wk, obj a, obj b, obj *res, struct check_obj_typeinfo_map map[obj_type_count]) { type_tag ot, tc, a_t = get_obj_typeinfo(wk, a)->type, result = 0; uint32_t matches = 0; for (ot = 1; ot <= tc_type_count; ++ot) { tc = obj_type_to_tc_type(ot); if ((a_t & tc) != tc) { continue; } else if (!map[ot].expect) { continue; } if (typecheck_custom(wk, 0, b, map[ot].expect, 0)) { result |= map[ot].result; ++matches; } } if (!matches) { return false; } *res = make_typeinfo(wk, result); return true; } static void vm_op_add(struct workspace *wk) { obj a, b; b = object_stack_pop(&wk->vm.stack); a = object_stack_pop(&wk->vm.stack); binop_disabler_check(a, b); enum obj_type a_t = get_obj_type(wk, a), b_t = get_obj_type(wk, b); obj res = 0; switch (a_t) { case obj_number: { typecheck_operand(b, b_t, obj_number, tc_number, tc_number); res = make_obj(wk, obj_number); set_obj_number(wk, res, get_obj_number(wk, a) + get_obj_number(wk, b)); break; } case obj_string: { typecheck_operand(b, b_t, obj_string, tc_string, tc_string); res = str_join(wk, a, b); break; } case obj_array: { obj_array_dup(wk, a, &res); if (b_t == obj_array) { obj_array_extend(wk, res, b); } else if (b_t == obj_typeinfo) { type_tag t = get_obj_typeinfo(wk, b)->type; t &= ~(tc_array | obj_typechecking_type_tag); // Only push b if it has a type other than list. This is because // if we push b with type tc_list then it looks like we just made a // nested list which [] + [] is never supposed to do. if (t) { obj_array_push(wk, res, b); } } else { obj_array_push(wk, res, b); } break; } case obj_dict: { typecheck_operand(b, b_t, obj_dict, tc_dict, tc_dict); obj_dict_merge(wk, a, b, &res); break; } case obj_typeinfo: { struct check_obj_typeinfo_map map[obj_type_count] = { [obj_number] = { tc_number, tc_number }, [obj_string] = { tc_string, tc_string }, [obj_dict] = { tc_dict, tc_dict }, [obj_array] = { tc_any, tc_array }, }; if (!typecheck_typeinfo_operands(wk, a, b, &res, map)) { goto type_err; } break; } default: type_err: vm_error(wk, "+ not defined for %s and %s", obj_typestr(wk, a), obj_typestr(wk, b)); vm_push_dummy(wk); return; } object_stack_push(wk, res); } #define vm_simple_integer_op_body(__op, __strop) \ obj a, b; \ b = object_stack_pop(&wk->vm.stack); \ a = object_stack_pop(&wk->vm.stack); \ binop_disabler_check(a, b); \ \ enum obj_type a_t = get_obj_type(wk, a), b_t = get_obj_type(wk, b); \ obj res; \ \ switch (a_t) { \ case obj_number: { \ typecheck_operand(b, b_t, obj_number, tc_number, tc_number); \ \ res = make_obj(wk, obj_number); \ set_obj_number(wk, res, get_obj_number(wk, a) __op get_obj_number(wk, b)); \ break; \ } \ case obj_typeinfo: { \ struct check_obj_typeinfo_map map[obj_type_count] = { \ [obj_number] = { tc_number, tc_number }, \ }; \ if (!typecheck_typeinfo_operands(wk, a, b, &res, map)) { \ goto type_err; \ } \ break; \ } \ default: \ type_err: \ vm_error(wk, __strop " not defined for %s and %s", obj_typestr(wk, a), obj_typestr(wk, b)); \ vm_push_dummy(wk); \ return; \ } \ \ object_stack_push(wk, res); static void vm_op_sub(struct workspace *wk) { vm_simple_integer_op_body(-, "-"); } static void vm_op_mul(struct workspace *wk) { vm_simple_integer_op_body(*, "*"); } static void vm_op_mod(struct workspace *wk) { vm_simple_integer_op_body(%, "%%"); } static void vm_op_div(struct workspace *wk) { obj a, b; b = object_stack_pop(&wk->vm.stack); a = object_stack_pop(&wk->vm.stack); binop_disabler_check(a, b); enum obj_type a_t = get_obj_type(wk, a), b_t = get_obj_type(wk, b); obj res = 0; switch (a_t) { case obj_number: typecheck_operand(b, b_t, obj_number, tc_number, tc_number); res = make_obj(wk, obj_number); set_obj_number(wk, res, get_obj_number(wk, a) / get_obj_number(wk, b)); break; case obj_string: { typecheck_operand(b, b_t, obj_string, tc_string, tc_string); const struct str *ss1 = get_str(wk, a), *ss2 = get_str(wk, b); if (str_has_null(ss1)) { vm_error(wk, "%o is an invalid path", a); vm_push_dummy(wk); return; } else if (str_has_null(ss2)) { vm_error(wk, "%o is an invalid path", b); vm_push_dummy(wk); return; } TSTR(buf); path_join(wk, &buf, ss1->s, ss2->s); res = tstr_into_str(wk, &buf); break; } case obj_typeinfo: { struct check_obj_typeinfo_map map[obj_type_count] = { [obj_number] = { tc_number, tc_number }, [obj_string] = { tc_string, tc_string }, }; if (!typecheck_typeinfo_operands(wk, a, b, &res, map)) { goto type_err; } break; } default: type_err: vm_error(wk, "/ not defined for %s and %s", obj_typestr(wk, a), obj_typestr(wk, b)); vm_push_dummy(wk); return; } object_stack_push(wk, res); } #define vm_simple_comparison_op_body(__op, __strop) \ obj a, b; \ b = object_stack_pop(&wk->vm.stack); \ a = object_stack_pop(&wk->vm.stack); \ binop_disabler_check(a, b); \ \ enum obj_type a_t = get_obj_type(wk, a), b_t = get_obj_type(wk, b); \ obj res = 0; \ \ switch (a_t) { \ case obj_number: { \ typecheck_operand(b, b_t, obj_number, tc_number, tc_number); \ \ res = get_obj_number(wk, a) __op get_obj_number(wk, b) ? obj_bool_true : obj_bool_false; \ break; \ } \ case obj_typeinfo: { \ struct check_obj_typeinfo_map map[obj_type_count] = { \ [obj_number] = { tc_number, tc_bool }, \ }; \ if (!typecheck_typeinfo_operands(wk, a, b, &res, map)) { \ goto type_err; \ } \ break; \ } \ default: \ type_err: \ vm_error(wk, __strop " not defined for %s and %s", obj_typestr(wk, a), obj_typestr(wk, b)); \ vm_push_dummy(wk); \ return; \ } \ \ object_stack_push(wk, res); static void vm_op_lt(struct workspace *wk) { vm_simple_comparison_op_body(<, "<"); } static void vm_op_gt(struct workspace *wk) { vm_simple_comparison_op_body(>, ">"); } static void vm_op_in(struct workspace *wk) { obj a, b; b = object_stack_pop(&wk->vm.stack); a = object_stack_pop(&wk->vm.stack); binop_disabler_check(a, b); enum obj_type a_t = get_obj_type(wk, a), b_t = get_obj_type(wk, b); obj res = 0; switch (b_t) { case obj_array: { typecheck_operand(a, a_t, 0, tc_any, tc_bool); res = obj_array_in(wk, b, a) ? obj_bool_true : obj_bool_false; if (a_t == obj_string) { check_str_enum(wk, a, a_t, b, b_t); } break; } case obj_dict: typecheck_operand(a, a_t, obj_string, tc_string, tc_bool); res = obj_dict_in(wk, b, a) ? obj_bool_true : obj_bool_false; if (res == obj_bool_false) { check_str_enum(wk, a, a_t, b, b_t); } break; case obj_string: typecheck_operand(a, a_t, obj_string, tc_string, tc_bool); const struct str *r = get_str(wk, b), *l = get_str(wk, a); res = str_contains(r, l) ? obj_bool_true : obj_bool_false; break; case obj_typeinfo: { struct check_obj_typeinfo_map map[obj_type_count] = { [obj_array] = { tc_any, tc_bool }, [obj_dict] = { tc_string, tc_bool }, [obj_string] = { tc_string, tc_bool }, }; if (!typecheck_typeinfo_operands(wk, b, a, &res, map)) { goto type_err; } break; } default: { type_err: vm_error(wk, "'in' not supported for %s and %s", obj_typestr(wk, a), obj_typestr(wk, b)); vm_push_dummy(wk); return; } } object_stack_push(wk, res); } static void vm_op_eq(struct workspace *wk) { obj a, b; b = object_stack_pop(&wk->vm.stack); a = object_stack_pop(&wk->vm.stack); binop_disabler_check(a, b); enum obj_type a_t = get_obj_type(wk, a), b_t = get_obj_type(wk, b); obj res = 0; if (a_t == obj_typeinfo || b_t == obj_typeinfo) { if (a_t == obj_string) { check_str_enum(wk, b, b_t, a, a_t); } else if (b_t == obj_string) { check_str_enum(wk, a, a_t, b, b_t); } res = make_typeinfo(wk, tc_bool); } else { res = obj_equal(wk, a, b) ? obj_bool_true : obj_bool_false; if (a_t == obj_string && b_t == obj_string && res == obj_bool_false) { check_str_enum(wk, a, a_t, b, b_t); } } object_stack_push(wk, res); } static void vm_op_not(struct workspace *wk) { obj a; a = object_stack_pop(&wk->vm.stack); unop_disabler_check(a); enum obj_type a_t = get_obj_type(wk, a); obj res = 0; switch (a_t) { case obj_bool: { res = get_obj_bool(wk, a) ? obj_bool_false : obj_bool_true; break; } case obj_typeinfo: { if (!typecheck_typeinfo(wk, a, tc_bool)) { goto type_err; } res = make_typeinfo(wk, tc_bool); break; } default: type_err: vm_error(wk, "'not' not supported for %s", obj_typestr(wk, a)); object_stack_push(wk, make_typeinfo(wk, tc_bool)); return; } object_stack_push(wk, res); } static void vm_op_negate(struct workspace *wk) { obj a; a = object_stack_pop(&wk->vm.stack); unop_disabler_check(a); enum obj_type a_t = get_obj_type(wk, a); obj res = 0; switch (a_t) { case obj_number: { res = make_obj(wk, obj_number); set_obj_number(wk, res, get_obj_number(wk, a) * -1); break; } case obj_typeinfo: { if (!typecheck_typeinfo(wk, a, tc_number)) { goto type_err; } res = make_typeinfo(wk, tc_number); break; } default: type_err: vm_error(wk, "unary - not supported for %s", obj_typestr(wk, a)); object_stack_push(wk, make_typeinfo(wk, tc_number)); return; } object_stack_push(wk, res); } static void vm_op_stringify(struct workspace *wk) { obj a; a = object_stack_pop(&wk->vm.stack); obj res = 0; if (get_obj_type(wk, a) == obj_typeinfo) { if (!typecheck_typeinfo(wk, a, tc_bool | tc_file | tc_number | tc_string | tc_feature_opt)) { vm_error(wk, "unable to coerce %s to string", obj_typestr(wk, a)); } res = make_typeinfo(wk, tc_string); } else if (!coerce_string(wk, wk->vm.ip - 1, a, &res)) { vm_push_dummy(wk); return; } object_stack_push(wk, res); } static bool vm_op_store_member_target(struct workspace *wk, uint32_t ip, obj target_container, obj id, enum op_store_flags flags, obj **member_target) { obj res; enum obj_type target_container_type = get_obj_type(wk, target_container), id_type = get_obj_type(wk, id); switch (target_container_type) { case obj_array: { typecheck_operand(id, id_type, obj_number, tc_number, tc_any); int64_t i; i = get_obj_number(wk, id); if (!boundscheck(wk, ip, get_obj_array(wk, target_container)->len, &i)) { return false; } *member_target = obj_array_index_pointer(wk, target_container, i); return true; } case obj_dict: { typecheck_operand(id, id_type, obj_string, tc_string, tc_any); const struct str *s = get_str(wk, id); *member_target = obj_dict_index_strn_pointer(wk, target_container, s->s, s->len); if (!*member_target) { if (flags & op_store_flag_add_store) { vm_error_at(wk, ip, "member %o not found on %s", id, obj_typestr(wk, target_container)); return false; } obj_dict_set(wk, target_container, id, 0); *member_target = obj_dict_index_strn_pointer(wk, target_container, s->s, s->len); } return true; } case obj_typeinfo: { struct check_obj_typeinfo_map map[obj_type_count] = { [obj_array] = { tc_number, tc_any }, [obj_dict] = { tc_string, tc_any }, }; if (!typecheck_typeinfo_operands(wk, target_container, id, &res, map)) { goto type_err; } break; } default: type_err: vm_error_at( wk, ip, "unable to index %s with %s", obj_typestr(wk, target_container), obj_typestr(wk, id)); break; } // If we got here it means that the member expression contains a typeinfo or there was a type error. return false; } static void vm_op_store(struct workspace *wk) { enum op_store_flags flags = vm_get_constant(wk->vm.code.e, &wk->vm.ip); struct obj_stack_entry *id_entry; obj id, val, *member_target = 0; /* op store operands come in different order depending on the store type: * regular store: * * member store * */ if (flags & op_store_flag_member) { val = object_stack_pop(&wk->vm.stack); obj target_container = object_stack_pop(&wk->vm.stack); id_entry = object_stack_pop_entry(&wk->vm.stack); id = id_entry->o; if (!vm_op_store_member_target(wk, id_entry->ip, target_container, id, flags, &member_target)) { object_stack_push(wk, val); return; } } else { id_entry = object_stack_pop_entry(&wk->vm.stack); id = id_entry->o; val = object_stack_pop(&wk->vm.stack); } if (get_obj_type(wk, id) == obj_typeinfo) { object_stack_push(wk, val); return; } if (flags & op_store_flag_add_store) { obj source; const struct str *id_str = 0; if (member_target) { source = *member_target; } else { id_str = get_str(wk, id); if (!wk->vm.behavior.get_variable(wk, id_str->s, &source)) { vm_error(wk, "undefined object %o", id); vm_push_dummy(wk); return; } } enum obj_type source_t = get_obj_type(wk, source), val_t = get_obj_type(wk, val); obj res; bool assign = false; if (wk->vm.in_analyzer) { // This is to ensure that array and dict mutations inside branches // cause the mutated object to be marked dirty when the branch // completes. assign = true; } switch (source_t) { case obj_number: { assign = true; typecheck_operand(val, val_t, obj_number, tc_number, tc_number); res = make_obj(wk, obj_number); set_obj_number(wk, res, get_obj_number(wk, source) + get_obj_number(wk, val)); break; } case obj_string: { assign = true; typecheck_operand(val, val_t, obj_string, tc_string, tc_string); // TODO: could use str_appn, but would have to dup on store res = str_join(wk, source, val); break; } case obj_array: { if (val_t == obj_array) { obj_array_extend(wk, source, val); } else { obj_array_push(wk, source, val); } res = source; break; } case obj_dict: { typecheck_operand(val, val_t, obj_dict, tc_dict, tc_dict); obj_dict_merge_nodup(wk, source, val); res = source; break; } case obj_typeinfo: { assign = true; struct check_obj_typeinfo_map map[obj_type_count] = { [obj_number] = { tc_number, tc_number }, [obj_string] = { tc_string, tc_string }, [obj_dict] = { tc_dict, tc_dict }, [obj_array] = { tc_any, tc_array }, }; if (!typecheck_typeinfo_operands(wk, source, val, &res, map)) { goto type_err; } break; } default: type_err: vm_error(wk, "+= not defined for %s and %s", obj_typestr(wk, source), obj_typestr(wk, val)); vm_push_dummy(wk); return; } if (assign) { if (member_target) { *member_target = res; } else { wk->vm.behavior.assign_variable(wk, id_str->s, res, wk->vm.ip - 1, assign_reassign); } } object_stack_push(wk, res); } else { switch (get_obj_type(wk, val)) { case obj_environment: case obj_configuration_data: { // TODO: these objects are just tiny wrappers over dict and array. They could probably use the same logic as below. obj cloned; if (!obj_clone(wk, wk, val, &cloned)) { UNREACHABLE; } val = cloned; break; } case obj_dict: { // TODO: If we could detect if this was the initial storage of a dict literal to a var, then we wouldn't have to dup this. obj dup; obj_dict_dup_light(wk, val, &dup); val = dup; break; } case obj_array: { val = obj_array_dup_light(wk, val); break; } case obj_typeinfo: { obj dup = make_obj(wk, obj_typeinfo); *get_obj_typeinfo(wk, dup) = *get_obj_typeinfo(wk, val); val = dup; break; } case obj_capture: { struct obj_capture *c = get_obj_capture(wk, val); if (c->func && !c->func->name) { c->func->name = get_str(wk, id)->s; } break; } default: break; } if (member_target) { *member_target = val; } else { wk->vm.behavior.assign_variable(wk, get_str(wk, id)->s, val, id_entry->ip, assign_local); } object_stack_push(wk, val); } } static void vm_op_load(struct workspace *wk) { obj a, b; a = object_stack_pop(&wk->vm.stack); // a could be a disabler if this is an inlined get_variable call if (a == obj_disabler) { object_stack_push(wk, obj_disabler); return; } else if (get_obj_type(wk, a) == obj_typeinfo) { vm_push_dummy(wk); return; } if (!wk->vm.behavior.get_variable(wk, get_str(wk, a)->s, &b)) { vm_error(wk, "undefined object %s", get_cstr(wk, a)); vm_push_dummy(wk); return; } /* LO("%o <= %o\n", b, a); */ object_stack_push(wk, b); } static void vm_op_try_load(struct workspace *wk) { obj a, b, res; b = object_stack_pop(&wk->vm.stack); a = object_stack_pop(&wk->vm.stack); if (a == obj_disabler) { object_stack_push(wk, obj_disabler); return; } else if (get_obj_type(wk, a) == obj_typeinfo) { vm_push_dummy(wk); return; } if (!wk->vm.behavior.get_variable(wk, get_str(wk, a)->s, &res)) { res = b; } object_stack_push(wk, res); } static void vm_op_index(struct workspace *wk) { struct obj_stack_entry *entry; uint32_t b_ip; obj a, b; int64_t i; obj res = 0; vm_pop(b); a = object_stack_pop(&wk->vm.stack); binop_disabler_check(a, b); enum obj_type a_t = get_obj_type(wk, a), b_t = get_obj_type(wk, b); switch (a_t) { case obj_array: { typecheck_operand(b, b_t, obj_number, tc_number, tc_any); i = get_obj_number(wk, b); if (!boundscheck(wk, b_ip, get_obj_array(wk, a)->len, &i)) { break; } res = obj_array_index(wk, a, i); break; } case obj_dict: { typecheck_operand(b, b_t, obj_string, tc_string, tc_any); if (!obj_dict_index(wk, a, b, &res)) { vm_error_at(wk, b_ip, "key not in dictionary: %o", b); vm_push_dummy(wk); return; } break; } case obj_custom_target: { typecheck_operand(b, b_t, obj_number, tc_number, tc_file); i = get_obj_number(wk, b); struct obj_custom_target *tgt = get_obj_custom_target(wk, a); struct obj_array *arr = get_obj_array(wk, tgt->output); if (!boundscheck(wk, b_ip, arr->len, &i)) { break; } res = obj_array_index(wk, tgt->output, i); break; } case obj_string: { typecheck_operand(b, b_t, obj_number, tc_number, tc_string); i = get_obj_number(wk, b); const struct str *s = get_str(wk, a); if (!boundscheck(wk, b_ip, s->len, &i)) { break; } res = make_strn(wk, &s->s[i], 1); break; } case obj_iterator: { typecheck_operand(b, b_t, obj_number, tc_number, tc_number); i = get_obj_number(wk, b); struct obj_iterator *iter = get_obj_iterator(wk, a); // It should be impossible to get a different type of iterator // outside of a loop. assert(iter->type == obj_iterator_type_range); double r = (double)iter->data.range.stop - (double)iter->data.range.start; uint32_t steps = (uint32_t)(r / (double)iter->data.range.step + 0.5); if (!boundscheck(wk, b_ip, steps, &i)) { break; } res = make_obj(wk, obj_number); set_obj_number(wk, res, (i * iter->data.range.step) + iter->data.range.start); break; } case obj_typeinfo: { struct check_obj_typeinfo_map map[obj_type_count] = { [obj_array] = { tc_number, tc_any }, [obj_dict] = { tc_string, tc_any }, [obj_custom_target] = { tc_number, tc_file }, [obj_string] = { tc_number, tc_string }, [obj_iterator] = { tc_number, tc_number }, }; if (!typecheck_typeinfo_operands(wk, a, b, &res, map)) { goto type_err; } break; } default: { type_err: vm_error_at(wk, b_ip, "unable to index %s with %s", obj_typestr(wk, a), obj_typestr(wk, b)); vm_push_dummy(wk); return; } } object_stack_push(wk, res); } static void vm_op_member(struct workspace *wk) { obj id, self, f = 0; uint32_t idx; self = object_stack_pop(&wk->vm.stack); id = vm_get_constant(wk->vm.code.e, &wk->vm.ip); if (!wk->vm.behavior.func_lookup(wk, self, get_str(wk, id)->s, &idx, &f)) { if (self == obj_disabler) { object_stack_push(wk, obj_disabler); return; } else if (get_obj_type(wk, self) == obj_dict) { obj res; if (obj_dict_index(wk, self, id, &res)) { object_stack_push(wk, res); return; } } else if (typecheck_typeinfo(wk, self, tc_dict)) { vm_push_dummy(wk); return; } else if (wk->vm.in_analyzer && get_obj_type(wk, self) == obj_module && !get_obj_module(wk, self)->found) { // Don't error on missing functions for not-found modules vm_push_dummy(wk); return; } vm_error(wk, "member %o not found on %#o", id, obj_type_to_typestr(wk, self)); vm_push_dummy(wk); return; } obj res = make_obj(wk, obj_capture); struct obj_capture *c = get_obj_capture(wk, res); if (f) { if (get_obj_type(wk, f) == obj_typeinfo) { vm_push_dummy(wk); return; } *c = *get_obj_capture(wk, f); } else { c->native_func = idx; if (native_funcs[idx].self_transform && get_obj_type(wk, self) != obj_typeinfo) { self = native_funcs[idx].self_transform(wk, self); } } c->self = self; object_stack_push(wk, res); } static void vm_op_call(struct workspace *wk) { wk->vm.nargs = vm_get_constant(wk->vm.code.e, &wk->vm.ip); wk->vm.nkwargs = vm_get_constant(wk->vm.code.e, &wk->vm.ip); obj f = object_stack_pop(&wk->vm.stack); if (f == obj_disabler) { object_stack_discard(&wk->vm.stack, wk->vm.nargs + wk->vm.nkwargs * 2); object_stack_push(wk, obj_disabler); return; } if (wk->vm.in_analyzer && get_obj_type(wk, f) == obj_typeinfo) { object_stack_discard(&wk->vm.stack, wk->vm.nargs + wk->vm.nkwargs * 2); vm_push_dummy(wk); typecheck(wk, 0, f, tc_capture); return; } else if (!typecheck(wk, 0, f, tc_capture)) { object_stack_discard(&wk->vm.stack, wk->vm.nargs + wk->vm.nkwargs * 2); vm_push_dummy(wk); return; } struct obj_capture *c = get_obj_capture(wk, f); if (c->func) { vm_execute_capture(wk, f); } else { vm_execute_native(wk, c->native_func, c->self); } } static void vm_op_call_native(struct workspace *wk) { wk->vm.nargs = vm_get_constant(wk->vm.code.e, &wk->vm.ip); wk->vm.nkwargs = vm_get_constant(wk->vm.code.e, &wk->vm.ip); uint32_t idx = vm_get_constant(wk->vm.code.e, &wk->vm.ip); vm_execute_native(wk, idx, 0); } static void vm_op_iterator(struct workspace *wk) { obj a, iter; uint32_t a_ip; struct obj_iterator *iterator; struct obj_stack_entry *entry; vm_pop(a); enum obj_type a_type = get_obj_type(wk, a); uint32_t args_to_unpack = vm_get_constant(wk->vm.code.e, &wk->vm.ip), expected_args_to_unpack = 0; switch (a_type) { case obj_array: expected_args_to_unpack = 1; if (expected_args_to_unpack != args_to_unpack) { goto args_to_unpack_mismatch_error; } iter = make_obj(wk, obj_iterator); object_stack_push(wk, iter); iterator = get_obj_iterator(wk, iter); iterator->type = obj_iterator_type_array; obj dup = obj_array_dup_light(wk, a); struct obj_array *arr = get_obj_array(wk, dup); iterator->data.array = arr->len ? bucket_arr_get(&wk->vm.objects.array_elems, arr->head) : 0; break; case obj_dict: { expected_args_to_unpack = 2; if (expected_args_to_unpack != args_to_unpack) { goto args_to_unpack_mismatch_error; } iter = make_obj(wk, obj_iterator); object_stack_push(wk, iter); iterator = get_obj_iterator(wk, iter); obj dup; obj_dict_dup_light(wk, a, &dup); struct obj_dict *d = get_obj_dict(wk, dup); if (d->flags & obj_dict_flag_big) { iterator->type = obj_iterator_type_dict_big; iterator->data.dict_big.h = bucket_arr_get(&wk->vm.objects.dict_hashes, d->data); } else { iterator->type = obj_iterator_type_dict_small; if (d->len) { iterator->data.dict_small = bucket_arr_get(&wk->vm.objects.dict_elems, d->data); } } break; } case obj_iterator: { expected_args_to_unpack = 1; if (expected_args_to_unpack != args_to_unpack) { goto args_to_unpack_mismatch_error; } iterator = get_obj_iterator(wk, a); assert(iterator->type == obj_iterator_type_range); object_stack_push(wk, a); iterator->data.range.i = iterator->data.range.start; break; } case obj_typeinfo: { enum obj_type t; if ((get_obj_typechecking_type(wk, a) & (tc_dict | tc_array)) == (tc_dict | tc_array)) { expected_args_to_unpack = args_to_unpack; t = args_to_unpack == 1 ? obj_array : obj_dict; } else if (typecheck_custom(wk, 0, a, tc_dict, 0)) { expected_args_to_unpack = 2; t = obj_dict; } else if (typecheck_custom(wk, 0, a, tc_array, 0)) { expected_args_to_unpack = 1; t = obj_array; } else if (typecheck_custom(wk, 0, a, tc_iterator, 0)) { expected_args_to_unpack = 1; t = obj_iterator; } else { goto type_error; } if (expected_args_to_unpack != args_to_unpack) { goto args_to_unpack_mismatch_error; } iter = make_obj(wk, obj_iterator); object_stack_push(wk, iter); iterator = get_obj_iterator(wk, iter); iterator->type = obj_iterator_type_typeinfo; iterator->data.typeinfo.type = t; break; } default: { type_error: vm_error_at(wk, a_ip, "unable to iterate over object of type %#o", obj_type_to_typestr(wk, a)); goto push_dummy_iterator; break; } } return; args_to_unpack_mismatch_error: vm_error(wk, "%s args to unpack, expected %d for %s", args_to_unpack > expected_args_to_unpack ? "too many" : "not enough", expected_args_to_unpack, obj_typestr(wk, a)); push_dummy_iterator: iter = make_obj(wk, obj_iterator); object_stack_push(wk, iter); iterator = get_obj_iterator(wk, iter); iterator->type = obj_iterator_type_typeinfo; iterator->data.typeinfo.type = args_to_unpack == 2 ? obj_dict : obj_array; } static void vm_op_iterator_next(struct workspace *wk) { bool push_key = false; obj key = 0, val = 0; struct obj_iterator *iterator; uint32_t break_jmp = vm_get_constant(wk->vm.code.e, &wk->vm.ip); iterator = get_obj_iterator(wk, object_stack_peek(&wk->vm.stack, 1)); bool should_break = false; switch (iterator->type) { case obj_iterator_type_array: if (!iterator->data.array) { should_break = true; } else { val = iterator->data.array->val; iterator->data.array = iterator->data.array->next ? bucket_arr_get( &wk->vm.objects.array_elems, iterator->data.array->next) : 0; } break; case obj_iterator_type_range: if (iterator->data.range.i >= iterator->data.range.stop) { should_break = true; } else { val = make_obj(wk, obj_number); set_obj_number(wk, val, iterator->data.range.i); iterator->data.range.i += iterator->data.range.step; } break; case obj_iterator_type_dict_small: if (!iterator->data.dict_small) { should_break = true; } else { push_key = true; key = iterator->data.dict_small->key; val = iterator->data.dict_small->val; if (iterator->data.dict_small->next) { iterator->data.dict_small = bucket_arr_get(&wk->vm.objects.dict_elems, iterator->data.dict_small->next); } else { iterator->data.dict_small = 0; } } break; case obj_iterator_type_dict_big: if (iterator->data.dict_big.i >= iterator->data.dict_big.h->keys.len) { should_break = true; } else { push_key = true; void *k = arr_get(&iterator->data.dict_big.h->keys, iterator->data.dict_big.i); union obj_dict_big_dict_value *uv = (union obj_dict_big_dict_value *)hash_get(iterator->data.dict_big.h, k); key = uv->val.key; val = uv->val.val; ++iterator->data.dict_big.i; } break; case obj_iterator_type_typeinfo: { // Let it loop twice to catch all variables modified in this impure // loop. if (iterator->data.typeinfo.i > 1) { should_break = true; break; } ++iterator->data.typeinfo.i; switch (iterator->data.typeinfo.type) { case obj_dict: { push_key = true; key = make_typeinfo(wk, tc_string); val = make_typeinfo(wk, tc_any); break; } case obj_array: { val = make_typeinfo(wk, tc_any); break; } case obj_iterator: { val = make_typeinfo(wk, tc_number); break; } default: UNREACHABLE; } break; } } if (should_break) { wk->vm.ip = break_jmp; return; } object_stack_push(wk, val); if (push_key) { object_stack_push(wk, key); } } static void vm_op_pop(struct workspace *wk) { object_stack_pop(&wk->vm.stack); } static void vm_op_dup(struct workspace *wk) { obj a; a = object_stack_peek(&wk->vm.stack, 1); object_stack_push(wk, a); } static void vm_op_swap(struct workspace *wk) { obj a, b; a = object_stack_pop(&wk->vm.stack); b = object_stack_pop(&wk->vm.stack); object_stack_push(wk, a); object_stack_push(wk, b); } static void vm_op_jmp_if_disabler(struct workspace *wk) { obj a, b; a = object_stack_peek(&wk->vm.stack, 1); b = vm_get_constant(wk->vm.code.e, &wk->vm.ip); if (a == obj_disabler) { object_stack_discard(&wk->vm.stack, 1); wk->vm.ip = b; } } static void vm_op_jmp_if_disabler_keep(struct workspace *wk) { obj a, b; a = object_stack_peek(&wk->vm.stack, 1); b = vm_get_constant(wk->vm.code.e, &wk->vm.ip); if (a == obj_disabler) { wk->vm.ip = b; } } static void vm_op_jmp_if_true(struct workspace *wk) { struct obj_stack_entry *entry; uint32_t a_ip; obj a, b; vm_pop(a); if (!typecheck(wk, a_ip, a, obj_bool)) { return; } b = vm_get_constant(wk->vm.code.e, &wk->vm.ip); if (get_obj_bool(wk, a)) { wk->vm.ip = b; } } static void vm_op_jmp_if_false(struct workspace *wk) { struct obj_stack_entry *entry; uint32_t a_ip; obj a, b; vm_pop(a); if (!typecheck(wk, a_ip, a, obj_bool)) { return; } b = vm_get_constant(wk->vm.code.e, &wk->vm.ip); if (!get_obj_bool(wk, a)) { wk->vm.ip = b; } } static void vm_op_jmp(struct workspace *wk) { obj a; a = vm_get_constant(wk->vm.code.e, &wk->vm.ip); wk->vm.ip = a; } void vm_op_return(struct workspace *wk) { struct obj_stack_entry *entry; uint32_t a_ip; obj a; struct call_frame *frame = vm_pop_call_stack_frame(wk); wk->vm.ip = frame->return_ip; switch (frame->type) { case call_frame_type_eval: { wk->vm.run = false; break; } case call_frame_type_func: vm_peek(a, 1); typecheck_custom(wk, a_ip, a, frame->expected_return_type, "expected return type %s, got %s"); break; } } static void vm_op_typecheck(struct workspace *wk) { struct obj_stack_entry *entry; entry = object_stack_peek_entry(&wk->vm.stack, 1); typecheck(wk, entry->ip, entry->o, vm_get_constant(wk->vm.code.e, &wk->vm.ip)); } static void vm_op_dbg_break(struct workspace *wk) { if (wk->vm.dbg_state.break_cb) { wk->vm.dbg_state.break_cb(wk); } else { repl(wk, true); } } /****************************************************************************** * vm_execute ******************************************************************************/ static void vm_abort_handler(void *ctx) { struct workspace *wk = ctx; vm_error(wk, "encountered unhandled error"); } bool vm_eval_capture(struct workspace *wk, obj c, const struct args_norm an[], const struct args_kw akw[], obj *res) { bool ok; wk->vm.nargs = 0; if (an) { for (wk->vm.nargs = 0; an[wk->vm.nargs].type != ARG_TYPE_NULL; ++wk->vm.nargs) { object_stack_push_ip(wk, an[wk->vm.nargs].val, an[wk->vm.nargs].node); } } wk->vm.nkwargs = 0; if (akw) { uint32_t i; for (i = 0; akw[i].key; ++i) { if (!akw[i].val) { continue; } object_stack_push_ip(wk, akw[i].val, akw[i].node); object_stack_push(wk, make_str(wk, akw[i].key)); ++wk->vm.nkwargs; } } uint32_t call_stack_base = wk->vm.call_stack.len; vm_push_call_stack_frame(wk, &(struct call_frame){ .type = call_frame_type_eval, .return_ip = wk->vm.ip, }); // Set the vm ip to 0 where vm_compile_initial_code_segment has placed a return statement wk->vm.ip = 0; vm_execute_capture(wk, c); if (wk->vm.error) { object_stack_pop(&wk->vm.stack); vm_pop_call_stack_frame(wk); goto err; } vm_execute(wk); err: assert(call_stack_base == wk->vm.call_stack.len); ok = !wk->vm.error; *res = ok ? object_stack_pop(&wk->vm.stack) : 0; wk->vm.error = false; return ok; } static void vm_unwind_call_stack(struct workspace *wk) { struct call_frame *frame; while (wk->vm.call_stack.len) { frame = vm_pop_call_stack_frame(wk); switch (frame->type) { case call_frame_type_eval: { error_message_flush_coalesced_message(); wk->vm.ip = frame->return_ip; // TODO: this is a little hacky? We need to make sure that // execution can continue even if we emitted errors. wk->vm.run = true; return; } case call_frame_type_func: break; } const char *fmt = frame->func->name ? "in function '%s'" : "in %s"; const char *fname = frame->func->name ? frame->func->name : "anonymous function"; vm_diagnostic(wk, frame->return_ip - 1, log_error, error_message_flag_no_source | error_message_flag_coalesce, fmt, fname); } error_message_flush_coalesced_message(); } obj vm_execute(struct workspace *wk) { uint32_t object_stack_base = wk->vm.stack.ba.len; platform_set_abort_handler(vm_abort_handler, wk); stack_push(&wk->stack, wk->vm.run, true); wk->vm.behavior.execute_loop(wk); stack_pop(&wk->stack, wk->vm.run); if (wk->vm.error) { vm_unwind_call_stack(wk); assert(wk->vm.stack.ba.len >= object_stack_base); object_stack_discard(&wk->vm.stack, wk->vm.stack.ba.len - object_stack_base); return 0; } else { return object_stack_pop(&wk->vm.stack); } } /****************************************************************************** * vm behavior functions ******************************************************************************/ /* muon stores variable scopes as an array of dicts. * * Each element of the array is a block scope. Currently the only way to enter * a new block is inside of a function. * * For example, take the below scope_stack: * * [{'a': 1}, {'b': 2}, {'c': 3}] * * This could be generated by the following code: * * a = 1 * * func f() * b = 2 * func g() * c = 3 # at this point, scope_stack looks like the above. * endfunc * g() * endfunc * * f() * * When looking up variables, scopes are checked from the end of the * scope_stack. */ static bool vm_get_local_variable(struct workspace *wk, const char *name, obj *res, obj *scope) { bool found = false; obj s, idx; obj_array_for(wk, wk->vm.scope_stack, s) { if (obj_dict_index_str(wk, s, name, &idx)) { *res = idx; *scope = s; found = true; } } return found; } static bool vm_get_variable(struct workspace *wk, const char *name, obj *res) { obj o, _scope; if (vm_get_local_variable(wk, name, &o, &_scope)) { *res = o; return true; } else { return false; } } static obj vm_scope_stack_dup(struct workspace *wk, obj scope_stack) { obj r, v; r = make_obj(wk, obj_array); obj_array_for(wk, scope_stack, v) { obj scope; obj_dict_dup(wk, v, &scope); obj_array_push(wk, r, scope); } return r; } static void vm_push_local_scope(struct workspace *wk) { obj scope; scope = make_obj(wk, obj_dict); obj_array_push(wk, wk->vm.scope_stack, scope); } static void vm_pop_local_scope(struct workspace *wk) { obj_array_pop(wk, wk->vm.scope_stack); } static void vm_unassign_variable(struct workspace *wk, const char *name) { obj _, scope; if (!vm_get_local_variable(wk, name, &_, &scope)) { return; } obj_dict_del_str(wk, scope, name); } static void vm_assign_variable(struct workspace *wk, const char *name, obj o, uint32_t ip, enum variable_assignment_mode mode) { obj scope = 0; if (mode == assign_reassign) { obj _; if (!vm_get_local_variable(wk, name, &_, &scope)) { UNREACHABLE; } } else { scope = obj_array_get_tail(wk, wk->vm.scope_stack); } obj_dict_set(wk, scope, make_str(wk, name), o); if (wk->vm.dbg_state.watched && obj_array_in(wk, wk->vm.dbg_state.watched, make_str(wk, name))) { LOG_I("watched variable \"%s\" changed", name); repl(wk, true); } } static bool vm_native_func_dispatch(struct workspace *wk, uint32_t func_idx, obj self, obj *res) { return native_funcs[func_idx].func(wk, self, res); } union vm_breakpoint { struct { uint32_t line, col; } dat; int64_t i; }; static void vm_dgb_enable(struct workspace *wk) { if (wk->vm.dbg_state.dbg) { return; } wk->vm.dbg_state.dbg = true; wk->vm.dbg_state.eval_trace = make_obj(wk, obj_array); wk->vm.dbg_state.root_eval_trace = wk->vm.dbg_state.eval_trace; } void vm_dbg_push_breakpoint(struct workspace *wk, obj file, uint32_t line, uint32_t col) { vm_dgb_enable(wk); union vm_breakpoint breakpoint = { .dat = { line, col } }; if (!wk->vm.dbg_state.breakpoints) { wk->vm.dbg_state.breakpoints = make_obj(wk, obj_dict); } obj file_bp; if (!obj_dict_index(wk, wk->vm.dbg_state.breakpoints, file, &file_bp)) { file_bp = make_obj(wk, obj_array); obj_dict_set(wk, wk->vm.dbg_state.breakpoints, file, file_bp); } L("pushing breakpoint for %s:%d:%d", get_cstr(wk, file), line, col); obj_array_push(wk, file_bp, make_number(wk, breakpoint.i)); } void vm_dbg_unpack_breakpoint(struct workspace *wk, obj v, uint32_t *line, uint32_t *col) { union vm_breakpoint breakpoint = { .i = get_obj_number(wk, v) }; *line = breakpoint.dat.line; *col = breakpoint.dat.col; } bool vm_dbg_push_breakpoint_str(struct workspace *wk, const char *bp) { const char *sep = strchr(bp, ':'); obj name; uint32_t line, col = 0; if (sep) { const char *sep_col = strchr(sep + 1, ':'); struct str l = STRL(sep + 1), c = { 0 }; if (sep_col) { c = STRL(sep_col + 1); l.len -= (c.len + 1); } int64_t i; if (!str_to_i(&l, &i, true)) { LOG_E("invalid line number: %.*s", l.len, l.s); return false; } line = i; if (c.s) { if (!str_to_i(&c, &i, true)) { LOG_E("invalid column: %.*s", c.len, c.s); return false; } col = i; } TSTR(p); path_make_absolute(wk, &p, get_cstr(wk, make_strn(wk, bp, sep - bp))); name = tstr_into_str(wk, &p); } else { name = make_str(wk, bp); line = 0; } vm_dbg_push_breakpoint(wk, name, line, col); return true; } static void vm_check_break(struct workspace *wk, uint32_t ip) { bool should_break = false; struct source *src = 0; if (wk->vm.dbg_state.stepping) { struct source_location loc = { 0 }; vm_lookup_inst_location(&wk->vm, ip, &loc, &src); if (wk->vm.dbg_state.prev_source_location.off != loc.off) { wk->vm.dbg_state.prev_source_location = loc; should_break = true; } } if (!should_break && wk->vm.dbg_state.break_after) { should_break = wk->vm.dbg_state.icount >= wk->vm.dbg_state.break_after; } if (should_break) { if (wk->vm.dbg_state.break_cb) { wk->vm.dbg_state.break_cb(wk); } else { repl(wk, true); } } } static void vm_execute_loop(struct workspace *wk) { uint32_t cip; while (wk->vm.run) { // LL("%-50s", vm_dis_inst(wk, wk->vm.code.e, wk->vm.ip)); // object_stack_print(wk, &wk->vm.stack); log_progress(wk, wk->vm.ip); vm_check_break(wk, wk->vm.ip); cip = wk->vm.ip; ++wk->vm.ip; wk->vm.ops.ops[wk->vm.code.e[cip]](wk); ++wk->vm.dbg_state.icount; } } /****************************************************************************** * struct/type registration ******************************************************************************/ static obj vm_struct_type_dict(struct workspace *wk, enum vm_struct_type base_t) { switch (base_t) { case vm_struct_type_enum_: return wk->vm.types.enums; case vm_struct_type_struct_: return wk->vm.types.structs; default: UNREACHABLE_RETURN; } } enum vm_struct_type vm_make_struct_type(struct workspace *wk, enum vm_struct_type base_t, const char *name) { obj def; if (!obj_dict_index_str(wk, vm_struct_type_dict(wk, base_t), name, &def)) { error_unrecoverable("type %s is not registered", name); } return base_t | (def << vm_struct_type_shift); } static void vm_types_init(struct workspace *wk) { if (!wk->vm.types.structs) { wk->vm.types.structs = make_obj(wk, obj_dict); wk->vm.types.enums = make_obj(wk, obj_dict); wk->vm.types.docs = make_obj(wk, obj_dict); wk->vm.types.top_level_docs = make_obj(wk, obj_dict); } } static bool vm_register_common(struct workspace *wk, obj dict, const char *name) { obj def; if (obj_dict_index_str(wk, dict, name, &def)) { return false; } def = make_obj(wk, obj_dict); obj_dict_set(wk, dict, make_str(wk, name), def); return true; } bool vm_enum_(struct workspace *wk, const char *name) { vm_types_init(wk); return vm_register_common(wk, wk->vm.types.enums, name); } void vm_enum_value_(struct workspace *wk, const char *name, const char *member, uint32_t value) { obj def; if (!obj_dict_index_str(wk, wk->vm.types.enums, name, &def)) { error_unrecoverable("enums %s is not registered", name); } obj_dict_set(wk, def, make_str(wk, member), value); } bool vm_struct_(struct workspace *wk, const char *name) { vm_types_init(wk); return vm_register_common(wk, wk->vm.types.structs, name); } const char * vm_enum_docs_def(struct workspace *wk, obj def) { obj doc; if (obj_dict_geti(wk, wk->vm.types.docs, def, &doc)) { return get_str(wk, doc)->s; } obj arr = make_obj(wk, obj_array); obj k, v; obj_dict_for(wk, def, k, v) { obj_array_push(wk, arr, k); } obj_array_join(wk, false, arr, make_str(wk, "|"), &doc); obj_dict_seti(wk, wk->vm.types.docs, def, doc); return get_str(wk, doc)->s; } const char * vm_struct_docs_def(struct workspace *wk, obj def) { obj doc; if (obj_dict_geti(wk, wk->vm.types.docs, def, &doc)) { return get_str(wk, doc)->s; } obj arr = make_obj(wk, obj_array); obj k, v; obj_dict_for(wk, def, k, v) { obj t = obj_array_index(wk, v, 1); enum vm_struct_type type = t & vm_struct_type_mask; const char *type_str = 0; switch (type) { case vm_struct_type_struct_: type_str = vm_struct_docs_def(wk, t >> vm_struct_type_shift); break; case vm_struct_type_enum_: type_str = vm_enum_docs_def(wk, t >> vm_struct_type_shift); break; case vm_struct_type_bool: type_str = "bool"; break; case vm_struct_type_str: type_str = "str"; break; case vm_struct_type_obj: type_str = "any"; break; } assert(type_str); obj_array_push(wk, arr, make_strf(wk, "%s?: %s", get_str(wk, k)->s, type_str)); } obj joined; obj_array_join(wk, false, arr, make_str(wk, ",\n\t"), &joined); TSTR(buf); tstr_pushf(wk, &buf, "{\n\t%s\n}", get_str(wk, joined)->s); doc = tstr_into_str(wk, &buf); obj_dict_seti(wk, wk->vm.types.docs, def, doc); return get_str(wk, doc)->s; } const char * vm_struct_docs_(struct workspace *wk, const char *name, const char *fmt) { vm_types_init(wk); obj doc; if (obj_dict_index_str(wk, wk->vm.types.top_level_docs, name, &doc)) { return get_str(wk, doc)->s; } obj def; if (!obj_dict_index_str(wk, wk->vm.types.structs, name, &def)) { error_unrecoverable("struct %s is not registered", name); } const char *doc_str = vm_struct_docs_def(wk, def); TSTR(buf); tstr_pushf(wk, &buf, fmt, doc_str); doc = tstr_into_str(wk, &buf); obj_dict_set(wk, wk->vm.types.top_level_docs, make_str(wk, name), doc); return get_str(wk, doc)->s; } void vm_struct_member_(struct workspace *wk, const char *name, const char *member, uint32_t offset, enum vm_struct_type t) { obj def; if (!obj_dict_index_str(wk, wk->vm.types.structs, name, &def)) { error_unrecoverable("struct %s is not registered", name); } obj member_def; member_def = make_obj(wk, obj_array); obj_array_push(wk, member_def, offset); obj_array_push(wk, member_def, t); obj_dict_set(wk, def, make_str(wk, member), member_def); } static bool vm_obj_to_enum_def(struct workspace *wk, obj def, obj o, void *s) { if (!typecheck_custom(wk, 0, o, tc_string, 0)) { vm_error(wk, "expected type %s for enum, got %s", typechecking_type_to_s(wk, tc_string), get_cstr(wk, obj_type_to_typestr(wk, o))); return false; } obj v; if (!obj_dict_index(wk, def, o, &v)) { vm_error(wk, "unknown enum value %s", get_cstr(wk, o)); return false; } *(uint32_t *)s = v; return true; } bool vm_obj_to_enum_(struct workspace *wk, const char *name, obj o, void *s) { obj def; if (!obj_dict_index_str(wk, wk->vm.types.enums, name, &def)) { error_unrecoverable("enums %s is not registered", name); } return vm_obj_to_enum_def(wk, def, o, s); } static bool vm_obj_to_struct_def(struct workspace *wk, obj def, obj o, void *s) { type_tag expected_type; obj k, v, member_def; obj_dict_for(wk, o, k, v) { if (!obj_dict_index(wk, def, k, &member_def)) { vm_error(wk, "unknown key %s", get_cstr(wk, k)); return false; } obj offset, t; offset = obj_array_index(wk, member_def, 0); t = obj_array_index(wk, member_def, 1); char *dest = (char *)s + offset; enum vm_struct_type type = t & vm_struct_type_mask; switch (type) { case vm_struct_type_struct_: if (!vm_obj_to_struct_def(wk, t >> vm_struct_type_shift, v, dest)) { return false; } break; case vm_struct_type_enum_: if (!vm_obj_to_enum_def(wk, t >> vm_struct_type_shift, v, dest)) { return false; } break; case vm_struct_type_bool: expected_type = tc_bool; if (!typecheck_custom(wk, 0, v, expected_type, 0)) { goto type_err; } *(bool *)dest = get_obj_bool(wk, v); break; case vm_struct_type_str: expected_type = tc_string; if (!typecheck_custom(wk, 0, v, expected_type, 0)) { goto type_err; } *(const struct str **)dest = get_str(wk, v); break; case vm_struct_type_obj: *(obj *)dest = v; break; } } return true; type_err: vm_error(wk, "expected type %s for member %s, got %s", typechecking_type_to_s(wk, expected_type), get_cstr(wk, k), get_cstr(wk, obj_type_to_typestr(wk, v))); return false; } bool vm_obj_to_struct_(struct workspace *wk, const char *name, obj o, void *s) { obj def; if (!obj_dict_index_str(wk, wk->vm.types.structs, name, &def)) { error_unrecoverable("struct %s is not registered", name); } return vm_obj_to_struct_def(wk, def, o, s); } /****************************************************************************** * init / destroy ******************************************************************************/ void vm_init_objects(struct workspace *wk) { bucket_arr_init(&wk->vm.objects.chrs, 4096, 1); bucket_arr_init(&wk->vm.objects.objs, 1024, sizeof(struct obj_internal)); bucket_arr_init(&wk->vm.objects.dict_elems, 1024, sizeof(struct obj_dict_elem)); bucket_arr_init(&wk->vm.objects.dict_hashes, 16, sizeof(struct hash)); bucket_arr_init(&wk->vm.objects.array_elems, 1024, sizeof(struct obj_array_elem)); const struct { uint32_t item_size; uint32_t bucket_size; } sizes[] = { [obj_number] = { sizeof(int64_t), 1024 }, [obj_string] = { sizeof(struct str), 1024 }, [obj_compiler] = { sizeof(struct obj_compiler), 4 }, [obj_array] = { sizeof(struct obj_array), 2048 }, [obj_dict] = { sizeof(struct obj_dict), 512 }, [obj_build_target] = { sizeof(struct obj_build_target), 16 }, [obj_custom_target] = { sizeof(struct obj_custom_target), 16 }, [obj_subproject] = { sizeof(struct obj_subproject), 16 }, [obj_dependency] = { sizeof(struct obj_dependency), 16 }, [obj_external_program] = { sizeof(struct obj_external_program), 32 }, [obj_python_installation] = { sizeof(struct obj_python_installation), 32 }, [obj_run_result] = { sizeof(struct obj_run_result), 32 }, [obj_configuration_data] = { sizeof(struct obj_configuration_data), 16 }, [obj_test] = { sizeof(struct obj_test), 64 }, [obj_module] = { sizeof(struct obj_module), 16 }, [obj_install_target] = { sizeof(struct obj_install_target), 128 }, [obj_environment] = { sizeof(struct obj_environment), 4 }, [obj_include_directory] = { sizeof(struct obj_include_directory), 16 }, [obj_option] = { sizeof(struct obj_option), 32 }, [obj_generator] = { sizeof(struct obj_generator), 16 }, [obj_generated_list] = { sizeof(struct obj_generated_list), 16 }, [obj_alias_target] = { sizeof(struct obj_alias_target), 4 }, [obj_both_libs] = { sizeof(struct obj_both_libs), 4 }, [obj_typeinfo] = { sizeof(struct obj_typeinfo), 32 }, [obj_func] = { sizeof(struct obj_func), 32 }, [obj_capture] = { sizeof(struct obj_func), 64 }, [obj_source_set] = { sizeof(struct obj_source_set), 4 }, [obj_source_configuration] = { sizeof(struct obj_source_configuration), 4 }, [obj_iterator] = { sizeof(struct obj_iterator), 32 }, }; uint32_t i; for (i = _obj_aos_start; i < obj_type_count; ++i) { bucket_arr_init(&wk->vm.objects.obj_aos[i - _obj_aos_start], sizes[i].bucket_size, sizes[i].item_size); } // reserve dict_elem 0 and array_elem as a null element bucket_arr_pushn(&wk->vm.objects.dict_elems, 0, 0, 1); bucket_arr_pushn(&wk->vm.objects.array_elems, 0, 0, 1); hash_init(&wk->vm.objects.obj_hash, 128, sizeof(obj)); hash_init_str(&wk->vm.objects.str_hash, 128); hash_init_str(&wk->vm.objects.dedup_str_hash, 128); make_default_objects(wk); } void vm_init(struct workspace *wk) { wk->vm = (struct vm){ 0 }; /* core vm runtime */ object_stack_init(&wk->vm.stack); arr_init(&wk->vm.call_stack, 64, sizeof(struct call_frame)); arr_init(&wk->vm.code, 4 * 1024, 1); arr_init(&wk->vm.src, 64, sizeof(struct source)); arr_init(&wk->vm.locations, 1024, sizeof(struct source_location_mapping)); /* compiler state */ arr_init(&wk->vm.compiler_state.node_stack, 4096, sizeof(struct node *)); arr_init(&wk->vm.compiler_state.if_jmp_stack, 64, sizeof(uint32_t)); arr_init(&wk->vm.compiler_state.loop_jmp_stack, 64, sizeof(uint32_t)); bucket_arr_init(&wk->vm.compiler_state.nodes, 2048, sizeof(struct node)); /* behavior pointers */ wk->vm.behavior = (struct vm_behavior){ .assign_variable = vm_assign_variable, .unassign_variable = vm_unassign_variable, .push_local_scope = vm_push_local_scope, .pop_local_scope = vm_pop_local_scope, .scope_stack_dup = vm_scope_stack_dup, .get_variable = vm_get_variable, .eval_project_file = eval_project_file, .native_func_dispatch = vm_native_func_dispatch, .pop_args = vm_pop_args, .func_lookup = func_lookup, .execute_loop = vm_execute_loop, }; /* ops */ wk->vm.ops = (struct vm_ops){ .ops = { [op_constant] = vm_op_constant, [op_constant_list] = vm_op_constant_list, [op_constant_dict] = vm_op_constant_dict, [op_constant_func] = vm_op_constant_func, [op_add] = vm_op_add, [op_sub] = vm_op_sub, [op_mul] = vm_op_mul, [op_div] = vm_op_div, [op_mod] = vm_op_mod, [op_not] = vm_op_not, [op_eq] = vm_op_eq, [op_in] = vm_op_in, [op_gt] = vm_op_gt, [op_lt] = vm_op_lt, [op_negate] = vm_op_negate, [op_stringify] = vm_op_stringify, [op_store] = vm_op_store, [op_try_load] = vm_op_try_load, [op_load] = vm_op_load, [op_return] = vm_op_return, [op_return_end] = vm_op_return, [op_call] = vm_op_call, [op_member] = vm_op_member, [op_call_native] = vm_op_call_native, [op_index] = vm_op_index, [op_iterator] = vm_op_iterator, [op_iterator_next] = vm_op_iterator_next, [op_jmp_if_false] = vm_op_jmp_if_false, [op_jmp_if_true] = vm_op_jmp_if_true, [op_jmp_if_disabler] = vm_op_jmp_if_disabler, [op_jmp_if_disabler_keep] = vm_op_jmp_if_disabler_keep, [op_jmp] = vm_op_jmp, [op_pop] = vm_op_pop, [op_dup] = vm_op_dup, [op_swap] = vm_op_swap, [op_typecheck] = vm_op_typecheck, [op_dbg_break] = vm_op_dbg_break, } }; /* objects */ vm_init_objects(wk); /* func impl tables */ build_func_impl_tables(); /* default scope */ wk->vm.default_scope_stack = make_obj(wk, obj_array); obj scope; scope = make_obj(wk, obj_dict); obj_array_push(wk, wk->vm.default_scope_stack, scope); obj_dict_set(wk, scope, make_str(wk, "meson"), obj_meson); obj id; id = make_obj(wk, obj_machine); set_obj_machine(wk, id, machine_kind_build); obj_dict_set(wk, scope, make_str(wk, "build_machine"), id); id = make_obj(wk, obj_machine); set_obj_machine(wk, id, machine_kind_host); obj_dict_set(wk, scope, make_str(wk, "host_machine"), id); obj_dict_set(wk, scope, make_str(wk, "target_machine"), id); /* module cache */ wk->vm.modules = make_obj(wk, obj_dict); /* enum types */ wk->vm.objects.enums.values = make_obj(wk, obj_dict); wk->vm.objects.enums.types = make_obj(wk, obj_dict); /* complex type cache */ wk->vm.objects.complex_types = make_obj(wk, obj_dict); /* scope stack */ wk->vm.scope_stack = wk->vm.behavior.scope_stack_dup(wk, wk->vm.default_scope_stack); /* initial code segment */ vm_compile_initial_code_segment(wk); } void vm_destroy_objects(struct workspace *wk) { uint32_t i; struct bucket_arr *ba = &wk->vm.objects.obj_aos[obj_string - _obj_aos_start]; for (i = 0; i < ba->len; ++i) { struct str *s = bucket_arr_get(ba, i); if (s->flags & str_flag_big) { z_free((void *)s->s); } } for (i = _obj_aos_start; i < obj_type_count; ++i) { bucket_arr_destroy(&wk->vm.objects.obj_aos[i - _obj_aos_start]); } for (i = 0; i < wk->vm.objects.dict_hashes.len; ++i) { struct hash *h = bucket_arr_get(&wk->vm.objects.dict_hashes, i); hash_destroy(h); } bucket_arr_destroy(&wk->vm.objects.chrs); bucket_arr_destroy(&wk->vm.objects.objs); bucket_arr_destroy(&wk->vm.objects.dict_elems); bucket_arr_destroy(&wk->vm.objects.dict_hashes); bucket_arr_destroy(&wk->vm.objects.array_elems); hash_destroy(&wk->vm.objects.obj_hash); hash_destroy(&wk->vm.objects.str_hash); hash_destroy(&wk->vm.objects.dedup_str_hash); } void vm_destroy(struct workspace *wk) { vm_destroy_objects(wk); bucket_arr_destroy(&wk->vm.stack.ba); arr_destroy(&wk->vm.call_stack); arr_destroy(&wk->vm.code); for (uint32_t i = 0; i < wk->vm.src.len; ++i) { struct source *src = arr_get(&wk->vm.src, i); if (src->type == source_type_file) { fs_source_destroy(src); } } arr_destroy(&wk->vm.src); arr_destroy(&wk->vm.locations); arr_destroy(&wk->vm.compiler_state.node_stack); arr_destroy(&wk->vm.compiler_state.if_jmp_stack); arr_destroy(&wk->vm.compiler_state.loop_jmp_stack); bucket_arr_destroy(&wk->vm.compiler_state.nodes); } muon-v0.5.0/src/lang/object.c0000644000175000017500000016441315041716357015004 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Simon Zeni * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include "buf_size.h" #include "error.h" #include "lang/object.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "options.h" #include "platform/assert.h" #include "platform/mem.h" #include "tracy.h" static bool making_default_objects = false; static void * get_obj_internal(struct workspace *wk, obj id, enum obj_type type) { struct obj_internal *o = bucket_arr_get(&wk->vm.objects.objs, id); if (o->t != type) { LOG_E("internal type error, expected %s but got %s", obj_type_to_s(type), obj_type_to_s(o->t)); abort(); return NULL; } switch (type) { case obj_null: case obj_disabler: case obj_meson: case obj_bool: if (!making_default_objects) { error_unrecoverable("tried to get singleton object of type %s", obj_type_to_s(type)); } // fallthrough case obj_file: case obj_feature_opt: case obj_machine: { return &o->val; break; } case obj_number: case obj_string: case obj_array: case obj_dict: case obj_compiler: case obj_build_target: case obj_custom_target: case obj_subproject: case obj_dependency: case obj_external_program: case obj_python_installation: case obj_run_result: case obj_configuration_data: case obj_test: case obj_module: case obj_install_target: case obj_environment: case obj_include_directory: case obj_option: case obj_generator: case obj_generated_list: case obj_alias_target: case obj_both_libs: case obj_typeinfo: case obj_source_set: case obj_source_configuration: case obj_iterator: case obj_func: case obj_capture: return bucket_arr_get(&wk->vm.objects.obj_aos[o->t - _obj_aos_start], o->val); default: assert(false && "tried to get invalid object type"); return NULL; } } void make_default_objects(struct workspace *wk) { making_default_objects = true; obj id; id = make_obj(wk, obj_null); assert(id == 0); id = make_obj(wk, obj_disabler); assert(id == obj_disabler); id = make_obj(wk, obj_meson); assert(id == obj_meson); id = make_obj(wk, obj_bool); assert(id == obj_bool_true); *(bool *)get_obj_internal(wk, id, obj_bool) = true; id = make_obj(wk, obj_bool); assert(id == obj_bool_false); *(bool *)get_obj_internal(wk, id, obj_bool) = false; making_default_objects = false; } enum obj_type get_obj_type(struct workspace *wk, obj id) { struct obj_internal *o = bucket_arr_get(&wk->vm.objects.objs, id); return o->t; } bool get_obj_bool(struct workspace *wk, obj o) { if (o == obj_bool_true) { return true; } else if (o == obj_bool_false) { return false; } else { UNREACHABLE; } /* return *(bool *)get_obj_internal(wk, o, obj_bool); */ } obj make_obj_bool(struct workspace *wk, bool v) { return v ? obj_bool_true : obj_bool_false; } obj get_obj_bool_with_default(struct workspace *wk, obj o, bool def) { return o ? get_obj_bool(wk, o) : def; } obj make_number(struct workspace *wk, int64_t n) { obj o; o = make_obj(wk, obj_number); set_obj_number(wk, o, n); return o; } int64_t get_obj_number(struct workspace *wk, obj o) { return *(int64_t *)get_obj_internal(wk, o, obj_number); } void set_obj_number(struct workspace *wk, obj o, int64_t v) { *(int64_t *)get_obj_internal(wk, o, obj_number) = v; } obj * get_obj_file(struct workspace *wk, obj o) { return get_obj_internal(wk, o, obj_file); } const char * get_file_path(struct workspace *wk, obj o) { return get_cstr(wk, *get_obj_file(wk, o)); } const struct str * get_str(struct workspace *wk, obj s) { return get_obj_internal(wk, s, obj_string); } enum feature_opt_state get_obj_feature_opt(struct workspace *wk, obj fo) { enum feature_opt_state state; state = *(enum feature_opt_state *)get_obj_internal(wk, fo, obj_feature_opt); if (state == feature_opt_auto) { obj auto_features_opt; // NOTE: wk->global_opts won't be initialized if we are loading // a serial dump if (wk->global_opts && get_option(wk, NULL, &STR("auto_features"), &auto_features_opt)) { struct obj_option *opt = get_obj_option(wk, auto_features_opt); return *(enum feature_opt_state *)get_obj_internal(wk, opt->val, obj_feature_opt); } else { return state; } } else { return state; } } void set_obj_feature_opt(struct workspace *wk, obj fo, enum feature_opt_state state) { *(enum feature_opt_state *)get_obj_internal(wk, fo, obj_feature_opt) = state; } enum machine_kind get_obj_machine(struct workspace *wk, obj o) { return *(enum machine_kind *)get_obj_internal(wk, o, obj_machine); } void set_obj_machine(struct workspace *wk, obj o, enum machine_kind kind) { *(enum machine_kind *)get_obj_internal(wk, o, obj_machine) = kind; } #define OBJ_GETTER(type) \ struct type *get_##type(struct workspace *wk, obj o) \ { \ return get_obj_internal(wk, o, type); \ } OBJ_GETTER(obj_array) OBJ_GETTER(obj_dict) OBJ_GETTER(obj_compiler) OBJ_GETTER(obj_build_target) OBJ_GETTER(obj_custom_target) OBJ_GETTER(obj_subproject) OBJ_GETTER(obj_dependency) OBJ_GETTER(obj_external_program) OBJ_GETTER(obj_python_installation) OBJ_GETTER(obj_run_result) OBJ_GETTER(obj_configuration_data) OBJ_GETTER(obj_test) OBJ_GETTER(obj_module) OBJ_GETTER(obj_install_target) OBJ_GETTER(obj_environment) OBJ_GETTER(obj_include_directory) OBJ_GETTER(obj_option) OBJ_GETTER(obj_generator) OBJ_GETTER(obj_generated_list) OBJ_GETTER(obj_alias_target) OBJ_GETTER(obj_both_libs) OBJ_GETTER(obj_typeinfo) OBJ_GETTER(obj_func) OBJ_GETTER(obj_capture) OBJ_GETTER(obj_source_set) OBJ_GETTER(obj_source_configuration) OBJ_GETTER(obj_iterator) #undef OBJ_GETTER obj make_obj(struct workspace *wk, enum obj_type type) { uint32_t val; obj res = wk->vm.objects.objs.len; switch (type) { case obj_null: case obj_disabler: case obj_meson: case obj_bool: if (!making_default_objects) { UNREACHABLE; } // fallthrough case obj_file: case obj_machine: case obj_feature_opt: val = 0; break; case obj_number: case obj_string: case obj_array: case obj_dict: case obj_compiler: case obj_build_target: case obj_custom_target: case obj_subproject: case obj_dependency: case obj_external_program: case obj_python_installation: case obj_run_result: case obj_configuration_data: case obj_test: case obj_module: case obj_install_target: case obj_environment: case obj_include_directory: case obj_option: case obj_generator: case obj_generated_list: case obj_alias_target: case obj_both_libs: case obj_source_set: case obj_source_configuration: case obj_iterator: case obj_typeinfo: case obj_func: case obj_capture: { struct bucket_arr *ba = &wk->vm.objects.obj_aos[type - _obj_aos_start]; val = ba->len; bucket_arr_pushn(ba, NULL, 0, 1); break; } default: assert(false && "tried to make invalid object type"); } bucket_arr_push(&wk->vm.objects.objs, &(struct obj_internal){ .t = type, .val = val }); #ifdef TRACY_ENABLE if (wk->tracy.is_master_workspace) { uint64_t mem = 0; mem += bucket_arr_size(&wk->vm.objects.objs); uint32_t i; for (i = 0; i < obj_type_count - _obj_aos_start; ++i) { mem += bucket_arr_size(&wk->vm.objects.obj_aos[i]); } #define MB(b) ((double)b / 1048576.0) TracyCPlot("objects", wk->vm.objects.objs.len); TracyCPlot("object memory (mb)", MB(mem)); TracyCPlot("string memory (mb)", MB(bucket_arr_size(&wk->vm.objects.chrs))); #undef MB } #endif return res; } void obj_set_clear_mark(struct workspace *wk, struct obj_clear_mark *mk) { wk->vm.objects.obj_clear_mark_set = true; mk->obji = wk->vm.objects.objs.len; bucket_arr_save(&wk->vm.objects.chrs, &mk->chrs); bucket_arr_save(&wk->vm.objects.objs, &mk->objs); uint32_t i; for (i = 0; i < obj_type_count - _obj_aos_start; ++i) { bucket_arr_save(&wk->vm.objects.obj_aos[i], &mk->obj_aos[i]); } } void obj_clear(struct workspace *wk, const struct obj_clear_mark *mk) { struct obj_internal *o; struct str *ss; uint32_t i; for (i = mk->obji; i < wk->vm.objects.objs.len; ++i) { o = bucket_arr_get(&wk->vm.objects.objs, i); if (o->t == obj_string) { ss = bucket_arr_get(&wk->vm.objects.obj_aos[obj_string - _obj_aos_start], o->val); if (ss->flags & str_flag_big) { z_free((void *)ss->s); } } } bucket_arr_restore(&wk->vm.objects.objs, &mk->objs); bucket_arr_restore(&wk->vm.objects.chrs, &mk->chrs); for (i = 0; i < obj_type_count - _obj_aos_start; ++i) { bucket_arr_restore(&wk->vm.objects.obj_aos[i], &mk->obj_aos[i]); } } static struct { enum obj_type t; const char *name; } obj_names[obj_type_count] = { { .t = obj_null, .name = "null" }, { .t = obj_compiler, .name = "compiler" }, { .t = obj_dependency, .name = "dep" }, { .t = obj_meson, .name = "meson" }, { .t = obj_string, .name = "str" }, { .t = obj_number, .name = "int" }, { .t = obj_array, .name = "list" }, { .t = obj_dict, .name = "dict" }, { .t = obj_bool, .name = "bool" }, { .t = obj_file, .name = "file" }, { .t = obj_build_target, .name = "build_tgt" }, { .t = obj_subproject, .name = "subproject" }, { .t = obj_machine, .name = "build_machine" }, { .t = obj_feature_opt, .name = "feature" }, { .t = obj_external_program, .name = "external_program" }, { .t = obj_python_installation, .name = "python_installation" }, { .t = obj_run_result, .name = "runresult" }, { .t = obj_configuration_data, .name = "cfg_data" }, { .t = obj_custom_target, .name = "custom_tgt" }, { .t = obj_test, .name = "test" }, { .t = obj_module, .name = "module" }, { .t = obj_install_target, .name = "install_tgt" }, { .t = obj_environment, .name = "env" }, { .t = obj_include_directory, .name = "inc" }, { .t = obj_option, .name = "option" }, { .t = obj_disabler, .name = "disabler" }, { .t = obj_generator, .name = "generator" }, { .t = obj_generated_list, .name = "generated_list" }, { .t = obj_alias_target, .name = "alias_tgt" }, { .t = obj_both_libs, .name = "both_libs" }, { .t = obj_typeinfo, .name = "typeinfo" }, { .t = obj_func, .name = "func_def" }, { .t = obj_capture, .name = "function" }, { .t = obj_source_set, .name = "source_set" }, { .t = obj_source_configuration, .name = "source_configuration" }, { .t = obj_iterator, .name = "iterator" }, }; const char * obj_type_to_s(enum obj_type t) { uint32_t i; for (i = 0; i < obj_type_count; ++i) { if (obj_names[i].t == t) { return obj_names[i].name; } } assert(false && "unreachable"); return NULL; } bool s_to_type_tag(const char *s, type_tag *t) { uint32_t i; for (i = 0; i < obj_type_count; ++i) { if (strcmp(s, obj_names[i].name) == 0) { *t = obj_type_to_tc_type(obj_names[i].t); return true; } } struct { type_tag t; const char *name; } extra_types[] = { { .t = tc_exe, .name = "exe" }, { .t = tc_any, .name = "any" }, { .t = TYPE_TAG_LISTIFY, .name = "listify" }, { .t = TYPE_TAG_GLOB, .name = "glob" }, }; for (i = 0; i < ARRAY_LEN(extra_types); ++i) { if (strcmp(s, extra_types[i].name) == 0) { *t = extra_types[i].t; return true; } } return false; } struct obj_equal_iter_ctx { obj other_container; uint32_t i; }; static enum iteration_result obj_equal_array_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_equal_iter_ctx *ctx = _ctx; obj r; r = obj_array_index(wk, ctx->other_container, ctx->i); if (!obj_equal(wk, val, r)) { return ir_err; } ++ctx->i; return ir_cont; } static enum iteration_result obj_equal_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct obj_equal_iter_ctx *ctx = _ctx; obj r; if (!obj_dict_index(wk, ctx->other_container, key, &r)) { return ir_err; } else if (!obj_equal(wk, val, r)) { return ir_err; } return ir_cont; } bool obj_equal(struct workspace *wk, obj left, obj right) { if (left == right) { return true; } enum obj_type t = get_obj_type(wk, left), right_t = get_obj_type(wk, right); // if we get == swap operands so we only have to // deal with the == case if (right_t == obj_iterator && t == obj_array) { obj s = left; enum obj_type st = t; left = right; t = right_t; right = s; right_t = st; } if (t == obj_iterator) { if (!(right_t == obj_array || right_t == obj_iterator)) { return false; } } else if (t != right_t) { return false; } switch (t) { case obj_string: return str_eql(get_str(wk, left), get_str(wk, right)); case obj_file: return str_eql(get_str(wk, *get_obj_file(wk, left)), get_str(wk, *get_obj_file(wk, right))); case obj_number: return get_obj_number(wk, left) == get_obj_number(wk, right); case obj_bool: return get_obj_bool(wk, left) == get_obj_bool(wk, right); case obj_array: { struct obj_equal_iter_ctx ctx = { .other_container = right, }; struct obj_array *l = get_obj_array(wk, left), *r = get_obj_array(wk, right); return l->len == r->len && obj_array_foreach(wk, left, &ctx, obj_equal_array_iter); } case obj_feature_opt: { return get_obj_feature_opt(wk, left) == get_obj_feature_opt(wk, right); } case obj_include_directory: { struct obj_include_directory *l, *r; l = get_obj_include_directory(wk, left); r = get_obj_include_directory(wk, right); return l->is_system == r->is_system && obj_equal(wk, l->path, r->path); } case obj_dict: { struct obj_equal_iter_ctx ctx = { .other_container = right, }; struct obj_dict *l = get_obj_dict(wk, left), *r = get_obj_dict(wk, right); return l->len == r->len && obj_dict_foreach(wk, left, &ctx, obj_equal_dict_iter); } case obj_iterator: { struct obj_iterator *iter = get_obj_iterator(wk, left); assert(iter->type == obj_iterator_type_range); switch (right_t) { case obj_array: { int64_t a = iter->data.range.start, b; obj v; obj_array_for(wk, right, v) { if (a >= iter->data.range.step) { return false; } if (get_obj_type(wk, v) != obj_number) { return false; } b = get_obj_number(wk, v); if (a != b) { return false; } a += iter->data.range.step; } return true; } case obj_iterator: { struct obj_iterator *riter = get_obj_iterator(wk, right); assert(riter->type == obj_iterator_type_range); return iter->data.range.start == riter->data.range.start && iter->data.range.stop == riter->data.range.stop && iter->data.range.step == riter->data.range.step; } default: UNREACHABLE_RETURN; } break; } default: /* LOG_W("TODO: compare %s", obj_type_to_s(t)); */ return false; } } /******************************************************************************* * arrays ******************************************************************************/ static void obj_array_copy_on_write(struct workspace *wk, struct obj_array *a, obj arr) { struct obj_array cur; if (!(a->flags & obj_array_flag_cow)) { return; } cur = *a; *a = (struct obj_array){ 0 }; obj v; obj_array_for_array(wk, &cur, v) { obj_array_push(wk, arr, v); } } bool obj_array_foreach(struct workspace *wk, obj arr, void *ctx, obj_array_iterator cb) { obj v; obj_array_for(wk, arr, v) { switch (cb(wk, ctx, v)) { case ir_cont: break; case ir_done: return true; case ir_err: return false; } } return true; } bool obj_array_foreach_flat(struct workspace *wk, obj arr, void *usr_ctx, obj_array_iterator cb) { obj v; obj_array_flat_for_(wk, arr, v, iter) { switch (cb(wk, usr_ctx, v)) { case ir_cont: break; case ir_done: { obj_array_flat_iter_end(wk, &iter); return true; } case ir_err: { obj_array_flat_iter_end(wk, &iter); return false; } } } return true; } void obj_array_push(struct workspace *wk, obj arr, obj child) { struct obj_array_elem *e; struct obj_array *a; a = get_obj_array(wk, arr); obj_array_copy_on_write(wk, a, arr); uint32_t next = wk->vm.objects.array_elems.len; if (!a->len) { a->head = next; } bucket_arr_push(&wk->vm.objects.array_elems, &(struct obj_array_elem){ .val = child }); if (a->len) { e = bucket_arr_get(&wk->vm.objects.array_elems, a->tail); e->next = next; } a->tail = next; ++a->len; } void obj_array_prepend(struct workspace *wk, obj *arr, obj val) { obj prepend; prepend = make_obj(wk, obj_array); obj_array_push(wk, prepend, val); obj_array_extend_nodup(wk, prepend, *arr); *arr = prepend; } bool obj_array_index_of(struct workspace *wk, obj arr, obj val, uint32_t *idx) { obj v; uint32_t i = 0; obj_array_for(wk, arr, v) { if (obj_equal(wk, val, v)) { *idx = i; return true; } ++i; } return false; } bool obj_array_in(struct workspace *wk, obj arr, obj val) { uint32_t _; return obj_array_index_of(wk, arr, val, &_); } static obj * obj_array_index_pointer_raw(struct workspace *wk, obj arr, int64_t i) { struct obj_array *a = get_obj_array(wk, arr); if (!a->len) { return 0; } else if (i == 0) { return &((struct obj_array_elem *)bucket_arr_get(&wk->vm.objects.array_elems, a->head))->val; } else if (i == a->len - 1) { return &((struct obj_array_elem *)bucket_arr_get(&wk->vm.objects.array_elems, a->tail))->val; } obj v; int64_t j = 0; obj_array_for_array_(wk, a, v, iter) { (void)v; if (j == i) { return &iter.e->val; } ++j; } return 0; } obj * obj_array_index_pointer(struct workspace *wk, obj arr, int64_t i) { obj_array_copy_on_write(wk, get_obj_array(wk, arr), arr); return obj_array_index_pointer_raw(wk, arr, i); } obj obj_array_index(struct workspace *wk, obj arr, int64_t i) { obj *a = obj_array_index_pointer_raw(wk, arr, i); assert(a); return *a; } obj obj_array_get_tail(struct workspace *wk, obj arr) { uint32_t tail = get_obj_array(wk, arr)->tail; struct obj_array_elem *e = bucket_arr_get(&wk->vm.objects.array_elems, tail); return e->val; } obj obj_array_get_head(struct workspace *wk, obj arr) { uint32_t head = get_obj_array(wk, arr)->head; struct obj_array_elem *e = bucket_arr_get(&wk->vm.objects.array_elems, head); return e->val; } void obj_array_dup(struct workspace *wk, obj arr, obj *res) { *res = make_obj(wk, obj_array); obj v; obj_array_for(wk, arr, v) { obj_array_push(wk, *res, v); } } static void obj_array_dup_into_light(struct workspace *wk, obj src, obj dst) { struct obj_array *a_dst = get_obj_array(wk, dst), *a_src = get_obj_array(wk, src); *a_dst = *a_src; a_dst->flags |= obj_array_flag_cow; a_src->flags |= obj_array_flag_cow; } obj obj_array_dup_light(struct workspace *wk, obj src) { obj res; res = make_obj(wk, obj_array); obj_array_dup_into_light(wk, src, res); return res; } // mutates arr and consumes arr2 void obj_array_extend_nodup(struct workspace *wk, obj arr, obj arr2) { struct obj_array *a, *b; struct obj_array_elem *tail; if (!(b = get_obj_array(wk, arr2))->len) { return; } a = get_obj_array(wk, arr); obj_array_copy_on_write(wk, a, arr); if (!a->len) { obj_array_dup_into_light(wk, arr2, arr); return; } tail = bucket_arr_get(&wk->vm.objects.array_elems, a->tail); assert(!tail->next); tail->next = b->head; a->tail = b->tail; a->len += b->len; } // mutates arr without modifying arr2 void obj_array_extend(struct workspace *wk, obj arr, obj arr2) { obj dup; obj_array_dup(wk, arr2, &dup); obj_array_extend_nodup(wk, arr, dup); } struct obj_array_join_ctx { obj *res; const struct str *join; uint32_t i, len; }; static enum iteration_result obj_array_join_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_array_join_ctx *ctx = _ctx; if (!typecheck_simple_err(wk, val, obj_string)) { return ir_err; } const struct str *ss = get_str(wk, val); str_appn(wk, ctx->res, ss->s, ss->len); if (ctx->i < ctx->len - 1) { str_appn(wk, ctx->res, ctx->join->s, ctx->join->len); } ++ctx->i; return ir_cont; } static enum iteration_result obj_array_flat_len_iter(struct workspace *wk, void *_ctx, obj _) { uint32_t *len = _ctx; ++(*len); return ir_cont; } static uint32_t obj_array_flat_len(struct workspace *wk, obj arr) { uint32_t len = 0; obj_array_foreach_flat(wk, arr, &len, obj_array_flat_len_iter); return len; } bool obj_array_join(struct workspace *wk, bool flat, obj arr, obj join, obj *res) { *res = make_str(wk, ""); if (!typecheck_simple_err(wk, join, obj_string)) { return false; } struct obj_array_join_ctx ctx = { .join = get_str(wk, join), .res = res, }; if (flat) { ctx.len = obj_array_flat_len(wk, arr); return obj_array_foreach_flat(wk, arr, &ctx, obj_array_join_iter); } else { ctx.len = get_obj_array(wk, arr)->len; return obj_array_foreach(wk, arr, &ctx, obj_array_join_iter); } } void obj_array_tail(struct workspace *wk, obj arr, obj *res) { const struct obj_array *a = get_obj_array(wk, arr); *res = make_obj(wk, obj_array); if (a->len > 1) { struct obj_array *n = get_obj_array(wk, *res); struct obj_array_elem *head = bucket_arr_get(&wk->vm.objects.array_elems, a->head); n->head = head->next; n->tail = a->tail; n->len = a->len; } } void obj_array_set(struct workspace *wk, obj arr, int64_t i, obj v) { obj *p = obj_array_index_pointer(wk, arr, i); assert(p); *p = v; } void obj_array_del(struct workspace *wk, obj arr, int64_t i) { struct obj_array *a = get_obj_array(wk, arr); obj_array_copy_on_write(wk, a, arr); struct obj_array_elem *head = bucket_arr_get(&wk->vm.objects.array_elems, a->head), *prev = 0; uint32_t head_idx = a->head, prev_idx = 0; assert(i >= 0 && i < a->len); #if 0 if (i == 0) { if (a->len >= 1) { head = bucket_arr_get(&wk->vm.objects.array_elems, a->head); a->head = head->next; --a->len; } else { *a = (struct obj_array){ 0 }; } return; } #endif int64_t j = 0; while (true) { if (j == i) { break; } prev_idx = head_idx; prev = head; head_idx = head->next; head = bucket_arr_get(&wk->vm.objects.array_elems, head_idx); ++j; } if (i == 0) { a->head = head->next; } else if (i == a->len - 1) { a->tail = prev_idx; prev->next = 0; } else { prev->next = head->next; } --a->len; } obj obj_array_pop(struct workspace *wk, obj arr) { obj t = obj_array_get_tail(wk, arr); obj_array_del(wk, arr, get_obj_array(wk, arr)->len - 1); return t; } void obj_array_clear(struct workspace *wk, obj arr) { struct obj_array *a = get_obj_array(wk, arr); *a = (struct obj_array){ 0 }; } void obj_array_dedup(struct workspace *wk, obj arr, obj *res) { hash_clear(&wk->vm.objects.obj_hash); hash_clear(&wk->vm.objects.dedup_str_hash); *res = make_obj(wk, obj_array); obj val, oval; obj_array_for(wk, arr, val) { oval = val; switch (get_obj_type(wk, val)) { case obj_file: val = *get_obj_file(wk, val); /* fallthrough */ case obj_string: { const struct str *s = get_str(wk, val); if (hash_get_strn(&wk->vm.objects.dedup_str_hash, s->s, s->len)) { continue; } hash_set_strn(&wk->vm.objects.dedup_str_hash, s->s, s->len, true); obj_array_push(wk, *res, oval); break; } default: { if (hash_get(&wk->vm.objects.obj_hash, &val)) { continue; } hash_set(&wk->vm.objects.obj_hash, &val, true); if (!obj_array_in(wk, *res, val)) { obj_array_push(wk, *res, val); } break; } } } } void obj_array_dedup_in_place(struct workspace *wk, obj *arr) { if (!*arr) { return; } obj dedupd; obj_array_dedup(wk, *arr, &dedupd); *arr = dedupd; } bool obj_array_flatten_one(struct workspace *wk, obj val, obj *res) { enum obj_type t = get_obj_type(wk, val); if (t == obj_array) { struct obj_array *v = get_obj_array(wk, val); if (v->len == 1) { *res = obj_array_index(wk, val, 0); } else { return false; } } else { *res = val; } return true; } int32_t obj_array_sort_by_str(struct workspace *wk, void *_ctx, obj a, obj b) { const struct str *sa = get_str(wk, a), *sb = get_str(wk, b); uint32_t min = sa->len > sb->len ? sb->len : sa->len; return memcmp(sa->s, sb->s, min); } static enum iteration_result obj_array_sort_push_to_da_iter(struct workspace *wk, void *_ctx, obj v) { struct arr *da = _ctx; arr_push(da, &v); return ir_cont; } struct obj_array_sort_ctx { struct workspace *wk; void *usr_ctx; obj_array_sort_func func; }; static int32_t obj_array_sort_wrapper(const void *a, const void *b, void *_ctx) { struct obj_array_sort_ctx *ctx = _ctx; return ctx->func(ctx->wk, ctx->usr_ctx, *(obj *)a, *(obj *)b); } void obj_array_sort(struct workspace *wk, void *usr_ctx, obj arr, obj_array_sort_func func, obj *res) { uint32_t len = get_obj_array(wk, arr)->len; if (!len) { *res = arr; return; } struct arr da; arr_init(&da, len, sizeof(obj)); obj_array_foreach(wk, arr, &da, obj_array_sort_push_to_da_iter); struct obj_array_sort_ctx ctx = { .wk = wk, .usr_ctx = usr_ctx, .func = func, }; arr_sort(&da, &ctx, obj_array_sort_wrapper); *res = make_obj(wk, obj_array); uint32_t i; for (i = 0; i < da.len; ++i) { obj_array_push(wk, *res, *(obj *)arr_get(&da, i)); } arr_destroy(&da); } obj obj_array_slice(struct workspace *wk, obj a, int64_t start, int64_t end) { struct obj_array *src = get_obj_array(wk, a); obj res; res = make_obj(wk, obj_array); struct obj_array *dst = get_obj_array(wk, res); if (start == end) { // empty slice return res; } const bool tail_slice = end == src->len; uint32_t prev_elem = src->head; obj v; obj_array_for_array_(wk, src, v, iter) { if (iter.i >= end) { break; } if (iter.i >= start) { if (tail_slice) { src->flags |= obj_array_flag_cow; dst->len = src->len - start; dst->head = prev_elem; dst->tail = src->tail; dst->flags |= obj_array_flag_cow; return res; } obj_array_push(wk, res, v); } prev_elem = iter.e->next; } return res; } /******************************************************************************* * dictionaries ******************************************************************************/ bool obj_dict_foreach(struct workspace *wk, obj dict, void *ctx, obj_dict_iterator cb) { obj k, v; obj_dict_for(wk, dict, k, v) { switch (cb(wk, ctx, k, v)) { case ir_cont: break; case ir_done: return true; case ir_err: return false; } } return true; } void obj_dict_dup(struct workspace *wk, obj dict, obj *res) { *res = make_obj(wk, obj_dict); obj k, v; obj_dict_for(wk, dict, k, v) { obj_dict_set(wk, *res, k, v); } } void obj_dict_dup_light(struct workspace *wk, obj dict, obj *res) { *res = make_obj(wk, obj_dict); struct obj_dict *new = get_obj_dict(wk, *res), *cur = get_obj_dict(wk, dict); *new = *cur; // TODO: We have to set cow on both dicts because we don't know which one // will be modified. // Ideally, we would unset the one dict's cow flag when the other one gets // written to, but that would require refcounting. // // a = {} // b = a // c = a // // Now a, b, and c all point at the same underlying dict. We'd need to // track the references and only remove cow when there is a single reference. cur->flags |= obj_dict_flag_cow; new->flags |= obj_dict_flag_cow; } void obj_dict_merge_nodup(struct workspace *wk, obj dict, obj dict2) { obj k, v; obj_dict_for(wk, dict2, k, v) { obj_dict_set(wk, dict, k, v); } } void obj_dict_merge(struct workspace *wk, obj dict, obj dict2, obj *res) { obj_dict_dup(wk, dict, res); obj_dict_merge_nodup(wk, *res, dict2); } union obj_dict_key_comparison_key { struct str string; uint32_t num; }; /* other is marked uint32_t since it can be used to represent an obj or a number */ typedef bool( (*obj_dict_key_comparison_func)(struct workspace *wk, union obj_dict_key_comparison_key *key, uint32_t other)); static bool obj_dict_key_comparison_func_string(struct workspace *wk, union obj_dict_key_comparison_key *key, obj other) { const struct str *ss_a = get_str(wk, other); return str_eql(ss_a, &key->string); } static bool obj_dict_key_comparison_func_int(struct workspace *wk, union obj_dict_key_comparison_key *key, uint32_t other) { return key->num == other; } static bool _obj_dict_index(struct workspace *wk, obj dict, union obj_dict_key_comparison_key *key, obj_dict_key_comparison_func comp, obj **res) { struct obj_dict *d = get_obj_dict(wk, dict); if (!d->len) { return false; } if (d->flags & obj_dict_flag_big) { struct hash *h = bucket_arr_get(&wk->vm.objects.dict_hashes, d->data); uint64_t *uv; if (d->flags & obj_dict_flag_int_key) { uv = hash_get(h, &key->num); } else { uv = hash_get_strn(h, key->string.s, key->string.len); } if (uv) { union obj_dict_big_dict_value *val = (union obj_dict_big_dict_value *)uv; *res = &val->val.val; return true; } } else { struct obj_dict_elem *e = bucket_arr_get(&wk->vm.objects.dict_elems, d->data); while (true) { if (comp(wk, key, e->key)) { *res = &e->val; return true; } if (!e->next) { break; } e = bucket_arr_get(&wk->vm.objects.dict_elems, e->next); } } return false; } static obj * obj_dict_index_strn_pointer_raw(struct workspace *wk, obj dict, const char *str, uint32_t len) { obj *r = 0; union obj_dict_key_comparison_key key = { .string = { .s = str, .len = len } }; if (!_obj_dict_index(wk, dict, &key, obj_dict_key_comparison_func_string, &r)) { return 0; } return r; } static void obj_dict_copy_on_write(struct workspace *wk, obj dict) { struct obj_dict *d = get_obj_dict(wk, dict), cur; if (!(d->flags & obj_dict_flag_cow)) { return; } cur = *d; *d = (struct obj_dict){ 0 }; obj k, v; obj_dict_for_dict(wk, &cur, k, v) { obj_dict_set(wk, dict, k, v); } } obj * obj_dict_index_strn_pointer(struct workspace *wk, obj dict, const char *str, uint32_t len) { obj_dict_copy_on_write(wk, dict); return obj_dict_index_strn_pointer_raw(wk, dict, str, len); } bool obj_dict_index_strn(struct workspace *wk, obj dict, const char *str, uint32_t len, obj *res) { obj *r = obj_dict_index_strn_pointer_raw(wk, dict, str, len); if (r) { *res = *r; return true; } return false; } bool obj_dict_index_str(struct workspace *wk, obj dict, const char *str, obj *res) { return obj_dict_index_strn(wk, dict, str, strlen(str), res); } bool obj_dict_index(struct workspace *wk, obj dict, obj key, obj *res) { const struct str *k = get_str(wk, key); return obj_dict_index_strn(wk, dict, k->s, k->len, res); } bool obj_dict_in(struct workspace *wk, obj dict, obj key) { obj res; return obj_dict_index(wk, dict, key, &res); } static void obj_dict_set_impl(struct workspace *wk, obj dict, union obj_dict_key_comparison_key *k, obj_dict_key_comparison_func comp, obj key, obj val) { obj_dict_copy_on_write(wk, dict); struct obj_dict *d = get_obj_dict(wk, dict); /* empty dict */ if (!d->len) { uint32_t e_idx = wk->vm.objects.dict_elems.len; bucket_arr_push(&wk->vm.objects.dict_elems, &(struct obj_dict_elem){ .key = key, .val = val }); d->data = e_idx; d->tail = e_idx; ++d->len; return; } if (!(d->flags & obj_dict_flag_dont_expand) && !(d->flags & obj_dict_flag_big) && d->len >= 15) { struct obj_dict_elem *e = bucket_arr_get(&wk->vm.objects.dict_elems, d->data); uint32_t h_idx = wk->vm.objects.dict_hashes.len; struct hash *h = bucket_arr_push(&wk->vm.objects.dict_hashes, &(struct hash){ 0 }); if (d->flags & obj_dict_flag_int_key) { hash_init(h, 16, sizeof(obj)); } else { hash_init_str(h, 16); } d->data = h_idx; d->tail = 0; // unnecessary but nice while (true) { union obj_dict_big_dict_value val = { .val = { .key = e->key, .val = e->val } }; if (d->flags & obj_dict_flag_int_key) { hash_set(h, &e->key, val.u64); } else { const struct str *ss = get_str(wk, e->key); hash_set_strn(h, ss->s, ss->len, val.u64); } if (!e->next) { break; } e = bucket_arr_get(&wk->vm.objects.dict_elems, e->next); } d->flags |= obj_dict_flag_big; } obj *r = 0; if (_obj_dict_index(wk, dict, k, comp, &r)) { *r = val; return; } /* set new value */ if ((d->flags & obj_dict_flag_big)) { struct hash *h = bucket_arr_get(&wk->vm.objects.dict_hashes, d->data); union obj_dict_big_dict_value big_val = { .val = { .key = key, .val = val } }; if (d->flags & obj_dict_flag_int_key) { hash_set(h, &key, big_val.u64); } else { const struct str *ss = get_str(wk, key); hash_set_strn(h, ss->s, ss->len, big_val.u64); } d->len = h->len; } else { uint32_t e_idx = wk->vm.objects.dict_elems.len; bucket_arr_push(&wk->vm.objects.dict_elems, &(struct obj_dict_elem){ .key = key, .val = val, }); struct obj_dict_elem *tail = bucket_arr_get(&wk->vm.objects.dict_elems, d->tail); tail->next = e_idx; d->tail = e_idx; ++d->len; } } void obj_dict_set(struct workspace *wk, obj dict, obj key, obj val) { union obj_dict_key_comparison_key k = { .string = *get_str(wk, key), }; obj_dict_set_impl(wk, dict, &k, obj_dict_key_comparison_func_string, key, val); } static void _obj_dict_del(struct workspace *wk, obj dict, union obj_dict_key_comparison_key *key, obj_dict_key_comparison_func comp) { obj_dict_copy_on_write(wk, dict); struct obj_dict *d = get_obj_dict(wk, dict); if (!d->len) { return; } if (d->flags & obj_dict_flag_big) { struct hash *h = bucket_arr_get(&wk->vm.objects.dict_hashes, d->data); if (d->flags & obj_dict_flag_int_key) { hash_unset(h, &key->num); } else { hash_unset_strn(h, key->string.s, key->string.len); } return; } uint32_t cur_id = d->data, prev_id = 0; bool found = false; struct obj_dict_elem *prev, *e = bucket_arr_get(&wk->vm.objects.dict_elems, d->data); while (true) { if (comp(wk, key, e->key)) { found = true; break; } prev_id = cur_id; if (!e->next) { break; } cur_id = e->next; e = bucket_arr_get(&wk->vm.objects.dict_elems, e->next); } if (!found) { return; } --d->len; if (cur_id == d->data) { d->data = e->next; } else { prev = bucket_arr_get(&wk->vm.objects.dict_elems, prev_id); if (e->next) { prev->next = e->next; } else { d->tail = prev_id; prev->next = 0; } } } void obj_dict_del_strn(struct workspace *wk, obj dict, const char *str, uint32_t len) { union obj_dict_key_comparison_key key = { .string = { .s = str, .len = len, } }; _obj_dict_del(wk, dict, &key, obj_dict_key_comparison_func_string); } void obj_dict_del_str(struct workspace *wk, obj dict, const char *str) { obj_dict_del_strn(wk, dict, str, strlen(str)); } void obj_dict_del(struct workspace *wk, obj dict, obj key) { const struct str *k = get_str(wk, key); obj_dict_del_strn(wk, dict, k->s, k->len); } /* dict convienence functions */ void obj_dict_seti(struct workspace *wk, obj dict, uint32_t key, obj val) { union obj_dict_key_comparison_key k = { .num = key }; struct obj_dict *d = get_obj_dict(wk, dict); d->flags |= obj_dict_flag_int_key; obj_dict_set_impl(wk, dict, &k, obj_dict_key_comparison_func_int, key, val); } bool obj_dict_geti(struct workspace *wk, obj dict, uint32_t key, obj *val) { obj *r = 0; if (_obj_dict_index(wk, dict, &(union obj_dict_key_comparison_key){ .num = key }, obj_dict_key_comparison_func_int, &r)) { *val = *r; return true; } return false; } const struct str * obj_dict_index_as_str(struct workspace *wk, obj dict, const char *s) { obj r; if (!obj_dict_index_str(wk, dict, s, &r)) { return 0; } return get_str(wk, r); } bool obj_dict_index_as_bool(struct workspace *wk, obj dict, const char *s) { obj r; if (!obj_dict_index_str(wk, dict, s, &r)) { return 0; } return get_obj_bool(wk, r); } int64_t obj_dict_index_as_number(struct workspace *wk, obj dict, const char *s) { obj r; if (!obj_dict_index_str(wk, dict, s, &r)) { UNREACHABLE; } return get_obj_number(wk, r); } obj obj_dict_index_as_obj(struct workspace *wk, obj dict, const char *s) { obj r; if (!obj_dict_index_str(wk, dict, s, &r)) { return 0; } return r; } /******************************************************************************* * obj_iterable_foreach ******************************************************************************/ struct obj_iterable_foreach_ctx { void *ctx; obj_dict_iterator cb; }; static enum iteration_result obj_iterable_foreach_array_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_iterable_foreach_ctx *ctx = _ctx; return ctx->cb(wk, ctx->ctx, val, 0); } bool obj_iterable_foreach(struct workspace *wk, obj dict_or_array, void *ctx, obj_dict_iterator cb) { switch (get_obj_type(wk, dict_or_array)) { case obj_dict: { return obj_dict_foreach(wk, dict_or_array, ctx, cb); } case obj_array: { return obj_array_foreach(wk, dict_or_array, &(struct obj_iterable_foreach_ctx){ .ctx = ctx, .cb = cb, }, obj_iterable_foreach_array_iter); } default: UNREACHABLE_RETURN; } } /* */ struct obj_clone_ctx { struct workspace *wk_dest; obj container; }; static enum iteration_result obj_clone_array_iter(struct workspace *wk_src, void *_ctx, obj val) { struct obj_clone_ctx *ctx = _ctx; obj dest_val; if (!obj_clone(wk_src, ctx->wk_dest, val, &dest_val)) { return ir_err; } obj_array_push(ctx->wk_dest, ctx->container, dest_val); return ir_cont; } static enum iteration_result obj_clone_dict_iter(struct workspace *wk_src, void *_ctx, obj key, obj val) { struct obj_clone_ctx *ctx = _ctx; obj dest_key, dest_val; if (!obj_clone(wk_src, ctx->wk_dest, key, &dest_key)) { return ir_err; } else if (!obj_clone(wk_src, ctx->wk_dest, val, &dest_val)) { return ir_err; } obj_dict_set(ctx->wk_dest, ctx->container, dest_key, dest_val); return ir_cont; } bool obj_clone(struct workspace *wk_src, struct workspace *wk_dest, obj val, obj *ret) { if (val >= wk_src->vm.objects.objs.len) { LOG_E("invalid object"); return false; } enum obj_type t = get_obj_type(wk_src, val); /* L("cloning %s", obj_type_to_s(t)); */ switch (t) { case obj_null: *ret = 0; return true; case obj_number: *ret = make_obj(wk_dest, t); set_obj_number(wk_dest, *ret, get_obj_number(wk_src, val)); return true; case obj_disabler: *ret = val; return true; case obj_bool: *ret = val; return true; case obj_string: { *ret = str_clone(wk_src, wk_dest, val); return true; } case obj_file: *ret = make_obj(wk_dest, t); *get_obj_file(wk_dest, *ret) = str_clone(wk_src, wk_dest, *get_obj_file(wk_src, val)); return true; case obj_array: *ret = make_obj(wk_dest, t); return obj_array_foreach(wk_src, val, &(struct obj_clone_ctx){ .container = *ret, .wk_dest = wk_dest }, obj_clone_array_iter); case obj_dict: *ret = make_obj(wk_dest, t); struct obj_dict *d = get_obj_dict(wk_dest, *ret); d->flags |= obj_dict_flag_dont_expand; bool status = obj_dict_foreach(wk_src, val, &(struct obj_clone_ctx){ .container = *ret, .wk_dest = wk_dest }, obj_clone_dict_iter); d->flags &= ~obj_dict_flag_dont_expand; return status; case obj_test: { *ret = make_obj(wk_dest, t); struct obj_test *test = get_obj_test(wk_src, val), *o = get_obj_test(wk_dest, *ret); *o = *test; o->name = str_clone(wk_src, wk_dest, test->name); o->exe = str_clone(wk_src, wk_dest, test->exe); if (test->workdir) { o->workdir = str_clone(wk_src, wk_dest, test->workdir); } if (!obj_clone(wk_src, wk_dest, test->args, &o->args)) { return false; } if (!obj_clone(wk_src, wk_dest, test->env, &o->env)) { return false; } if (!obj_clone(wk_src, wk_dest, test->suites, &o->suites)) { return false; } if (!obj_clone(wk_src, wk_dest, test->depends, &o->depends)) { return false; } if (!obj_clone(wk_src, wk_dest, test->timeout, &o->timeout)) { return false; } if (!obj_clone(wk_src, wk_dest, test->priority, &o->priority)) { return false; } return true; } case obj_install_target: { *ret = make_obj(wk_dest, t); struct obj_install_target *in = get_obj_install_target(wk_src, val), *o = get_obj_install_target(wk_dest, *ret); o->src = str_clone(wk_src, wk_dest, in->src); o->dest = str_clone(wk_src, wk_dest, in->dest); o->build_target = in->build_target; o->type = in->type; o->has_perm = in->has_perm; o->perm = in->perm; if (!obj_clone(wk_src, wk_dest, in->exclude_directories, &o->exclude_directories)) { return false; } if (!obj_clone(wk_src, wk_dest, in->exclude_files, &o->exclude_files)) { return false; } return true; } case obj_environment: { *ret = make_obj(wk_dest, obj_environment); struct obj_environment *env = get_obj_environment(wk_src, val), *o = get_obj_environment(wk_dest, *ret); if (!obj_clone(wk_src, wk_dest, env->actions, &o->actions)) { return false; } return true; } case obj_option: { *ret = make_obj(wk_dest, t); struct obj_option *opt = get_obj_option(wk_src, val), *o = get_obj_option(wk_dest, *ret); o->source = opt->source; o->type = opt->type; o->builtin = opt->builtin; o->yield = opt->yield; if (!obj_clone(wk_src, wk_dest, opt->name, &o->name)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->val, &o->val)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->choices, &o->choices)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->max, &o->max)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->min, &o->min)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->deprecated, &o->deprecated)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->description, &o->description)) { return false; } return true; } case obj_feature_opt: { *ret = make_obj(wk_dest, t); set_obj_feature_opt(wk_dest, *ret, get_obj_feature_opt(wk_src, val)); return true; } case obj_configuration_data: { *ret = make_obj(wk_dest, t); struct obj_configuration_data *conf = get_obj_configuration_data(wk_src, val), *o = get_obj_configuration_data(wk_dest, *ret); if (!obj_clone(wk_src, wk_dest, conf->dict, &o->dict)) { return false; } return true; } case obj_run_result: { *ret = make_obj(wk_dest, t); struct obj_run_result *rr = get_obj_run_result(wk_src, val), *o = get_obj_run_result(wk_dest, *ret); *o = *rr; if (!obj_clone(wk_src, wk_dest, rr->out, &o->out)) { return false; } else if (!obj_clone(wk_src, wk_dest, rr->err, &o->err)) { return false; } return true; } default: LOG_E("unable to clone '%s'", obj_type_to_s(t)); return false; } } struct obj_to_s_opts { bool pretty; uint32_t indent; }; struct obj_to_s_ctx { struct tstr *sb; struct obj_to_s_opts *opts; uint32_t cont_i, cont_len; }; static void obj_to_s_opts(struct workspace *wk, obj o, struct tstr *sb, struct obj_to_s_opts *opts); static void obj_to_s_indent(struct workspace *wk, struct obj_to_s_ctx *ctx) { if (!ctx->opts->pretty) { return; } uint32_t i; for (i = 0; i < ctx->opts->indent; ++i) { tstr_pushs(wk, ctx->sb, " "); } } static void obj_to_s_pretty_newline(struct workspace *wk, struct obj_to_s_ctx *ctx) { if (!ctx->opts->pretty) { return; } tstr_push(wk, ctx->sb, '\n'); obj_to_s_indent(wk, ctx); } static void obj_to_s_pretty_newline_or_space(struct workspace *wk, struct obj_to_s_ctx *ctx) { if (!ctx->opts->pretty) { tstr_push(wk, ctx->sb, ' '); return; } obj_to_s_pretty_newline(wk, ctx); } static enum iteration_result obj_to_s_array_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_to_s_ctx *ctx = _ctx; obj_to_s_opts(wk, val, ctx->sb, ctx->opts); if (ctx->cont_i < ctx->cont_len - 1) { tstr_pushs(wk, ctx->sb, ","); obj_to_s_pretty_newline_or_space(wk, ctx); } ++ctx->cont_i; return ir_cont; } static void obj_to_s_str(struct workspace *wk, struct obj_to_s_ctx *ctx, obj s) { tstr_push(wk, ctx->sb, '\''); str_escape(wk, ctx->sb, get_str(wk, s), true); tstr_push(wk, ctx->sb, '\''); } static void obj_to_s_opts(struct workspace *wk, obj o, struct tstr *sb, struct obj_to_s_opts *opts) { struct obj_to_s_ctx ctx = { .sb = sb, .opts = opts }; enum obj_type t = get_obj_type(wk, o); switch (t) { case obj_include_directory: { struct obj_include_directory *inc = get_obj_include_directory(wk, o); tstr_pushs(wk, sb, "path); tstr_pushs(wk, sb, ">"); break; } case obj_dependency: { struct obj_dependency *dep = get_obj_dependency(wk, o); tstr_pushs(wk, sb, "name) { obj_to_s_str(wk, &ctx, dep->name); } const char *type = 0; switch (dep->type) { case dependency_type_declared: type = "declared"; break; case dependency_type_pkgconf: type = "pkgconf"; break; case dependency_type_threads: type = "threads"; break; case dependency_type_external_library: type = "external_library"; break; case dependency_type_system: type = "system"; break; case dependency_type_not_found: type = "not_found"; break; } const char *found = dep->flags & dep_flag_found ? " found" : ""; tstr_pushf(wk, sb, " %s machine:%s%s>", type, machine_kind_to_s(dep->machine), found); break; } case obj_alias_target: tstr_pushs(wk, sb, "name); tstr_pushs(wk, sb, ">"); break; case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, o); const char *type = NULL; switch (tgt->type) { case tgt_executable: type = "executable"; break; case tgt_static_library: type = "static_library"; break; case tgt_dynamic_library: type = "shared_library"; break; case tgt_shared_module: type = "shared_module"; break; } tstr_pushf(wk, sb, "<%s ", type); obj_to_s_str(wk, &ctx, tgt->name); tstr_pushs(wk, sb, ">"); break; } case obj_feature_opt: switch (get_obj_feature_opt(wk, o)) { case feature_opt_auto: tstr_pushs(wk, sb, "'auto'"); break; case feature_opt_enabled: tstr_pushs(wk, sb, "'enabled'"); break; case feature_opt_disabled: tstr_pushs(wk, sb, "'disabled'"); break; } break; case obj_test: { struct obj_test *test = get_obj_test(wk, o); tstr_pushs(wk, sb, "test("); obj_to_s_str(wk, &ctx, test->name); tstr_pushs(wk, sb, ", "); obj_to_s_str(wk, &ctx, test->exe); if (test->args) { tstr_pushs(wk, sb, ", args: "); obj_to_s_opts(wk, test->args, sb, opts); } if (test->should_fail) { tstr_pushs(wk, sb, ", should_fail: true"); } tstr_pushs(wk, sb, ")"); break; } case obj_file: tstr_pushs(wk, sb, ""); break; case obj_string: { obj_to_s_str(wk, &ctx, o); break; } case obj_number: tstr_pushf(wk, sb, "%" PRId64, get_obj_number(wk, o)); break; case obj_bool: tstr_pushs(wk, sb, get_obj_bool(wk, o) ? "true" : "false"); break; case obj_array: ctx.cont_len = get_obj_array(wk, o)->len; tstr_pushs(wk, sb, "["); ++opts->indent; obj_to_s_pretty_newline(wk, &ctx); obj_array_foreach(wk, o, &ctx, obj_to_s_array_iter); --opts->indent; obj_to_s_pretty_newline(wk, &ctx); tstr_pushs(wk, sb, "]"); break; case obj_dict: ctx.cont_len = get_obj_dict(wk, o)->len; tstr_pushs(wk, sb, "{"); ++opts->indent; obj_to_s_pretty_newline(wk, &ctx); bool int_keys = get_obj_dict(wk, o)->flags & obj_dict_flag_int_key; obj key, val; obj_dict_for(wk, o, key, val) { if (int_keys) { tstr_pushf(wk, ctx.sb, "%d", key); } else { obj_to_s_opts(wk, key, ctx.sb, ctx.opts); } tstr_pushs(wk, ctx.sb, ": "); obj_to_s_opts(wk, val, ctx.sb, ctx.opts); if (ctx.cont_i < ctx.cont_len - 1) { tstr_pushs(wk, ctx.sb, ","); obj_to_s_pretty_newline_or_space(wk, &ctx); } ++ctx.cont_i; } --opts->indent; obj_to_s_pretty_newline(wk, &ctx); tstr_pushs(wk, sb, "}"); break; case obj_python_installation: { struct obj_python_installation *py = get_obj_python_installation(wk, o); tstr_pushf(wk, sb, "<%s prog: ", obj_type_to_s(t)); obj_to_s_opts(wk, py->prog, sb, opts); if (get_obj_external_program(wk, py->prog)->found) { tstr_pushf(wk, sb, ", pure: %s", py->pure ? "true" : "false"); tstr_pushf(wk, sb, ", language_version: %s", get_cstr(wk, py->language_version)); tstr_pushs(wk, sb, ", sysconfig_paths: "); obj_to_s_opts(wk, py->sysconfig_paths, sb, opts); tstr_pushs(wk, sb, ", sysconfig_vars: "); obj_to_s_opts(wk, py->sysconfig_vars, sb, opts); tstr_pushs(wk, sb, ", install_paths: "); obj_to_s_opts(wk, py->install_paths, sb, opts); } tstr_pushs(wk, sb, ">"); break; } case obj_external_program: { struct obj_external_program *prog = get_obj_external_program(wk, o); tstr_pushf(wk, sb, "<%s found: %s", obj_type_to_s(t), prog->found ? "true" : "false"); if (prog->found) { tstr_pushs(wk, sb, ", cmd_array: "); obj_to_s_opts(wk, prog->cmd_array, sb, opts); } tstr_pushs(wk, sb, ">"); break; } case obj_option: { struct obj_option *opt = get_obj_option(wk, o); tstr_pushs(wk, sb, "