muon-v0.3.0/0002755000175000017500000000000014674570635011661 5ustar buildbuildmuon-v0.3.0/include/0002755000175000017500000000000014674562002013271 5ustar buildbuildmuon-v0.3.0/include/sha_256.h0000644000175000017500000000042114674562002014604 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); #endif muon-v0.3.0/include/platform/0002755000175000017500000000000014674562002015115 5ustar buildbuildmuon-v0.3.0/include/platform/os.h0000644000175000017500000000140614674562002015706 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 0 #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 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); #endif muon-v0.3.0/include/platform/init.h0000644000175000017500000000042414674562002016227 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.3.0/include/platform/rpath_fixer.h0000644000175000017500000000044614674562002017603 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.3.0/include/platform/windows/0002755000175000017500000000000014674562002016607 5ustar buildbuildmuon-v0.3.0/include/platform/windows/log.h0000644000175000017500000000042414674562002017537 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.3.0/include/platform/windows/win32_error.h0000644000175000017500000000060714674562002021134 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.3.0/include/platform/windows/win32_getopt.h0000644000175000017500000000054614674562002021307 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.3.0/include/platform/mem.h0000644000175000017500000000057714674562002016053 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.3.0/include/platform/log.h0000644000175000017500000000036114674562002016045 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_LOG_H #define MUON_PLATFORM_LOG_H #include void print_colorized(FILE *out, const char *s); #endif muon-v0.3.0/include/platform/path.h0000644000175000017500000000343714674562002016227 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 sbuf; void path_init(void); void path_deinit(void); bool path_chdir(const char *path); void path_copy(struct workspace *wk, struct sbuf *sb, const char *path); const char *path_cwd(void); void path_copy_cwd(struct workspace *wk, struct sbuf *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 sbuf *sb, const char *b); void path_join(struct workspace *wk, struct sbuf *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 sbuf *sb, const char *a, const char *b); void path_make_absolute(struct workspace *wk, struct sbuf *buf, const char *path); void path_relative_to(struct workspace *wk, struct sbuf *buf, const char *base_raw, const char *path_raw); void path_without_ext(struct workspace *wk, struct sbuf *buf, const char *path); void path_basename(struct workspace *wk, struct sbuf *buf, const char *path); void path_dirname(struct workspace *wk, struct sbuf *buf, const char *path); void path_executable(struct workspace *wk, struct sbuf *buf, const char *path); void _path_normalize(struct workspace *wk, struct sbuf *buf, bool optimize); void path_to_posix(char *path); void shell_escape(struct workspace *wk, struct sbuf *sb, const char *str); #endif muon-v0.3.0/include/platform/uname.h0000644000175000017500000000054114674562002016371 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 { big_endian, little_endian, }; const char *uname_sysname(void); const char *uname_machine(void); enum endianness uname_endian(void); #endif muon-v0.3.0/include/platform/term.h0000644000175000017500000000042714674562002016236 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.3.0/include/platform/filesystem.h0000644000175000017500000000457114674562002017457 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 #include "datastructures/iterator.h" #include "lang/source.h" struct workspace; struct sbuf; 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); bool fs_write(const char *path, const uint8_t *buf, uint64_t buf_len); bool fs_find_cmd(struct workspace *wk, struct sbuf *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 fs_copy_dir(const char *src_base, const char *dest_base); 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); /* Windows only */ bool fs_has_extension(const char *path, const char *ext); 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.3.0/include/platform/assert.h0000644000175000017500000000065114674562002016567 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ASSERT_H # define MUON_ASSERT_H # ifdef _WIN32 # 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.3.0/include/platform/run_cmd.h0000644000175000017500000000455314674562002016722 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; HANDLE event; char overlapped_buf[4 << 10]; bool is_pending, is_eof; }; #endif struct run_cmd_ctx { struct sbuf 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; bool close_pipes; struct win_pipe_inst pipe_out, pipe_err; #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); 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); #endif muon-v0.3.0/include/platform/timer.h0000644000175000017500000000115414674562002016405 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 { #ifdef _WIN32 LARGE_INTEGER freq; LARGE_INTEGER start; #else struct timespec start; #endif }; void timer_start(struct timer *t); float timer_read(struct timer *t); void timer_sleep(uint64_t nanoseconds); #endif muon-v0.3.0/include/coerce.h0000644000175000017500000000301414674562002014676 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_COERCE_H #define MUON_COERCE_H #include "lang/func_lookup.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); #endif muon-v0.3.0/include/datastructures/0002755000175000017500000000000014674562002016346 5ustar buildbuildmuon-v0.3.0/include/datastructures/iterator.h0000644000175000017500000000044414674562002020350 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ITERATOR_H #define MUON_ITERATOR_H enum iteration_result { ir_err, ir_cont, ir_done, }; typedef enum iteration_result (*iterator_func)(void *ctx, void *val); #endif muon-v0.3.0/include/datastructures/stack.h0000644000175000017500000000221214674562002017617 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.3.0/include/datastructures/hash.h0000644000175000017500000000261614674562002017445 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_DATA_HASH_H #define MUON_DATA_HASH_H #include #include #include "datastructures/arr.h" #include "datastructures/iterator.h" struct hash; typedef bool((*hash_keycmp)(const struct hash *h, const void *a, const void *b)); typedef uint64_t((*hash_func)(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_func 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); void hash_for_each(struct hash *h, void *ctx, iterator_func ifnc); void hash_for_each_with_keys(struct hash *h, void *ctx, hash_with_keys_iterator_func ifnc); #endif muon-v0.3.0/include/datastructures/bucket_arr.h0000644000175000017500000000230514674562002020636 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.3.0/include/datastructures/arr.h0000644000175000017500000000220614674562002017301 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 #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.3.0/include/args.h0000644000175000017500000000242114674562002014373 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 ninja_escape(struct workspace *wk, struct sbuf *sb, const char *str); void pkgconf_escape(struct workspace *wk, struct sbuf *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); #endif muon-v0.3.0/include/compilers.h0000644000175000017500000002430014674562002015434 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" 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, "ld64", "ld64") \ _(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_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_component { toolchain_component_compiler, toolchain_component_linker, toolchain_component_static_linker, }; 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_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 a #define TOOLCHAIN_SIG_1s TOOLCHAIN_PARAMS_BASE, const char *a #define TOOLCHAIN_SIG_2s TOOLCHAIN_PARAMS_BASE, const char *a, const char *b #define TOOLCHAIN_SIG_1s1b TOOLCHAIN_PARAMS_BASE, const char *a, bool b #define TOOLCHAIN_SIG_ns TOOLCHAIN_PARAMS_BASE, const struct args *a #define TOOLCHAIN_PARAMS_0 0, (TOOLCHAIN_SIG_0), (TOOLCHAIN_PARAM_NAMES_BASE) #define TOOLCHAIN_PARAMS_1i 1i, (TOOLCHAIN_SIG_1i), (TOOLCHAIN_PARAM_NAMES_BASE, a) #define TOOLCHAIN_PARAMS_1s 1s, (TOOLCHAIN_SIG_1s), (TOOLCHAIN_PARAM_NAMES_BASE, a) #define TOOLCHAIN_PARAMS_2s 2s, (TOOLCHAIN_SIG_2s), (TOOLCHAIN_PARAM_NAMES_BASE, a, b) #define TOOLCHAIN_PARAMS_1s1b 1s1b, (TOOLCHAIN_SIG_1s1b), (TOOLCHAIN_PARAM_NAMES_BASE, a, b) #define TOOLCHAIN_PARAMS_ns ns, (TOOLCHAIN_SIG_ns), (TOOLCHAIN_PARAM_NAMES_BASE, a) 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(_) \ _(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) \ _(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) \ _(deps_type, 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) #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_ }; extern struct compiler compilers[]; extern struct linker linkers[]; extern struct static_linker static_linkers[]; extern const struct language languages[]; const char *compiler_type_to_s(enum compiler_type t); bool compiler_type_from_s(const char *name, uint32_t *res); 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); bool toolchain_detect(struct workspace *wk, obj *comp, 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); #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_ #endif muon-v0.3.0/include/machines.h0000644000175000017500000000223714674562002015233 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" enum machine_system { machine_system_dragonfly, machine_system_freebsd, machine_system_gnu, machine_system_haiku, machine_system_linux, machine_system_netbsd, machine_system_openbsd, machine_system_sunos, machine_system_android, machine_system_emscripten, machine_system_windows, machine_system_cygwin, machine_system_msys2, machine_system_darwin, machine_system_unknown, }; enum machine_kind { machine_kind_build, machine_kind_host, }; struct machine_definition { enum machine_kind kind; enum machine_system sys; 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; const char *machine_system_to_s(enum machine_system 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); #endif muon-v0.3.0/include/util.h0000644000175000017500000000036314674562002014417 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_UTIL_H #define MUON_UTIL_H #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif muon-v0.3.0/include/functions/0002755000175000017500000000000014674562002015301 5ustar buildbuildmuon-v0.3.0/include/functions/external_program.h0000644000175000017500000000061314674562002021021 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.3.0/include/functions/disabler.h0000644000175000017500000000041314674562002017233 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.3.0/include/functions/compiler.h0000644000175000017500000000050714674562002017264 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" extern const struct func_impl impl_tbl_compiler[]; extern const struct func_impl impl_tbl_compiler_internal[]; #endif muon-v0.3.0/include/functions/array.h0000644000175000017500000000047314674562002016572 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.3.0/include/functions/environment.h0000644000175000017500000000122314674562002020012 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.3.0/include/functions/number.h0000644000175000017500000000040514674562002016737 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.3.0/include/functions/machine.h0000644000175000017500000000041014674562002017047 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[]; #endif muon-v0.3.0/include/functions/configuration_data.h0000644000175000017500000000045114674562002021310 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.3.0/include/functions/kernel/0002755000175000017500000000000014674562002016561 5ustar buildbuildmuon-v0.3.0/include/functions/kernel/install.h0000644000175000017500000000115114674562002020374 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/func_lookup.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.3.0/include/functions/kernel/custom_target.h0000644000175000017500000000217114674562002021611 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/func_lookup.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); 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.3.0/include/functions/kernel/options.h0000644000175000017500000000053714674562002020430 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.3.0/include/functions/kernel/subproject.h0000644000175000017500000000075614674562002021120 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/func_lookup.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.3.0/include/functions/kernel/build_target.h0000644000175000017500000000124714674562002021401 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/func_lookup.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.3.0/include/functions/kernel/configure_file.h0000644000175000017500000000046314674562002021713 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/func_lookup.h" bool func_configure_file(struct workspace *wk, obj _, obj *res); #endif muon-v0.3.0/include/functions/kernel/dependency.h0000644000175000017500000000146314674562002021052 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/func_lookup.h" void dep_process_deps(struct workspace *wk, obj deps, struct build_dep *dest); bool dep_process_link_with(struct workspace *wk, uint32_t err_node, obj arr, struct build_dep *dest); bool dep_process_link_whole(struct workspace *wk, uint32_t err_node, obj arr, struct build_dep *dest); void dep_process_includes(struct workspace *wk, obj arr, enum include_type include_type, obj dest); 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); #endif muon-v0.3.0/include/functions/meson.h0000644000175000017500000000047314674562002016575 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.3.0/include/functions/feature_opt.h0000644000175000017500000000042414674562002017765 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.3.0/include/functions/custom_target.h0000644000175000017500000000053114674562002020327 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.3.0/include/functions/run_result.h0000644000175000017500000000042114674562002017647 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.3.0/include/functions/string.h0000644000175000017500000000133214674562002016755 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(struct workspace *wk, uint32_t err_node, const struct str *ver, obj cmp_arr, bool *res); 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[]; #endif muon-v0.3.0/include/functions/modules.h0000644000175000017500000000125114674562002017117 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.3.0/include/functions/dict.h0000644000175000017500000000046714674562002016402 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.3.0/include/functions/kernel.h0000644000175000017500000000056514674562002016736 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" 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[]; #endif muon-v0.3.0/include/functions/both_libs.h0000644000175000017500000000044314674562002017416 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" void both_libs_build_impl_tbl(void); extern struct func_impl impl_tbl_both_libs[]; #endif muon-v0.3.0/include/functions/template.h0000644000175000017500000000036214674562002017264 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.3.0/include/functions/boolean.h0000644000175000017500000000041014674562002017062 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.3.0/include/functions/subproject.h0000644000175000017500000000060714674562002017633 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.3.0/include/functions/source_configuration.h0000644000175000017500000000044514674562002021702 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.3.0/include/functions/build_target.h0000644000175000017500000000102214674562002020110 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, obj src_file, bool relative, struct sbuf *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.3.0/include/functions/source_set.h0000644000175000017500000000040714674562002017624 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.3.0/include/functions/file.h0000644000175000017500000000046714674562002016376 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_linkable(struct workspace *wk, obj file); extern const struct func_impl impl_tbl_file[]; #endif muon-v0.3.0/include/functions/modules/0002755000175000017500000000000014674562002016751 5ustar buildbuildmuon-v0.3.0/include/functions/modules/pkgconfig.h0000644000175000017500000000044514674562002021072 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.3.0/include/functions/modules/fs.h0000644000175000017500000000051514674562002017531 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.3.0/include/functions/modules/toolchain.h0000644000175000017500000000044514674562002021103 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.3.0/include/functions/modules/keyval.h0000644000175000017500000000043414674562002020414 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.3.0/include/functions/modules/python.h0000644000175000017500000000066014674562002020443 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.3.0/include/functions/modules/sourceset.h0000644000175000017500000000044514674562002021137 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.3.0/include/functions/generator.h0000644000175000017500000000061514674562002017440 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.3.0/include/functions/dependency.h0000644000175000017500000000042114674562002017563 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.3.0/include/cmd_test.h0000644000175000017500000000151114674562002015240 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; enum test_category cat; }; bool tests_run(struct test_options *opts, const char *argv0); #endif muon-v0.3.0/include/memmem.h0000644000175000017500000000037014674562002014715 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.3.0/include/install.h0000644000175000017500000000102414674562002015103 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.3.0/include/preprocessor_helpers.h0000644000175000017500000000062214674562002017710 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__) #define CONCAT(first, second) CONCAT_SIMPLE(first, second) #define CONCAT_SIMPLE(first, second) first##second #endif muon-v0.3.0/include/formats/0002755000175000017500000000000014674562002014744 5ustar buildbuildmuon-v0.3.0/include/formats/tap.h0000644000175000017500000000060714674562002015702 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.3.0/include/formats/editorconfig.h0000644000175000017500000000060514674562002017570 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_EDITORCONFIG_H #define MUON_FORMATS_EDITORCONFIG_H #include "platform/filesystem.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.3.0/include/formats/lines.h0000644000175000017500000000114514674562002016226 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 "datastructures/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.3.0/include/formats/ini.h0000644000175000017500000000130214674562002015666 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 #include "error.h" #include "platform/filesystem.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.3.0/include/wrap.h0000644000175000017500000000273414674562002014417 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 "lang/types.h" #include "platform/filesystem.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, // 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 sbuf dest_dir, name; }; enum wrap_provides_key { wrap_provides_key_override_dependencies, wrap_provides_key_override_executables, wrap_provides_key_dependency_variables, }; void wrap_destroy(struct wrap *wrap); bool wrap_parse(const char *wrap_file, struct wrap *wrap); bool wrap_handle(const char *wrap_file, const char *subprojects, struct wrap *wrap, bool download); bool wrap_load_all_provides(struct workspace *wk, const char *subprojects); #endif muon-v0.3.0/include/log.h0000644000175000017500000000271414674562002014225 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 "platform/assert.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 L(...) log_print(true, log_debug, __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_init(void); void log_set_file(FILE *log_file); void log_set_lvl(enum log_level lvl); void log_set_prefix(const char *prefix); const char *log_get_prefix(void); uint32_t log_print_prefix(enum log_level lvl, char *buf, uint32_t size); void log_print(bool nl, enum log_level lvl, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); bool log_clr(void); void log_plain(const char *fmt, ...) MUON_ATTR_FORMAT(printf, 1, 2); void log_plainv(const char *fmt, va_list ap); FILE *log_file(void); bool log_should_print(enum log_level lvl); #endif muon-v0.3.0/include/backend/0002755000175000017500000000000014674562002014660 5ustar buildbuildmuon-v0.3.0/include/backend/backend.h0000644000175000017500000000037314674562002016421 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.3.0/include/backend/ninja/0002755000175000017500000000000014674562002015757 5ustar buildbuildmuon-v0.3.0/include/backend/ninja/rules.h0000644000175000017500000000052414674562002017261 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.3.0/include/backend/ninja/custom_target.h0000644000175000017500000000053014674562002021004 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.3.0/include/backend/ninja/alias_target.h0000644000175000017500000000061214674562002020564 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 #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.3.0/include/backend/ninja/build_target.h0000644000175000017500000000052514674562002020575 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.3.0/include/backend/common_args.h0000644000175000017500000000327214674562002017337 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 get_std_args(struct workspace *wk, struct obj_compiler *comp, const struct project *proj, const struct obj_build_target *tgt, obj args_id); void get_option_compile_args(struct workspace *wk, struct obj_compiler *comp, const struct project *proj, const struct obj_build_target *tgt, obj args_id); void get_option_link_args(struct workspace *wk, struct obj_compiler *comp, const struct project *proj, const struct obj_build_target *tgt, obj args_id); bool setup_compiler_args(struct workspace *wk, const struct obj_build_target *tgt, const struct project *proj, obj include_dirs, obj dep_args, obj *joined_args); bool build_target_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj *joined_args); struct setup_linker_args_ctx { struct obj_compiler *compiler; struct build_dep *args; const struct obj_build_target *tgt; const struct project *proj; }; void setup_linker_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, struct setup_linker_args_ctx *ctx); void setup_compiler_args_includes(struct workspace *wk, obj compiler, obj include_dirs, obj args, bool relativize); void relativize_paths(struct workspace *wk, obj arr, bool relativize_strings, obj *res); void relativize_path(struct workspace *wk, obj path, bool relativize_strings, obj *res); void relativize_path_push(struct workspace *wk, obj path, obj arr); obj regenerate_build_command(struct workspace *wk, bool opts_only); #endif muon-v0.3.0/include/backend/output.h0000644000175000017500000000114314674562002016366 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; }; 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.3.0/include/backend/ninja.h0000644000175000017500000000065214674562002016131 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; }; bool ninja_write_all(struct workspace *wk); bool ninja_run(struct workspace *wk, obj args, const char *chdir, const char *capture); #endif muon-v0.3.0/include/options.h0000644000175000017500000000356614674562002015145 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; 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 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); bool get_option_bool(struct workspace *wk, obj overrides, const char *name, bool fallback); struct list_options_opts { bool list_all, only_modified; }; bool list_options(const struct list_options_opts *list_opts); #endif muon-v0.3.0/include/machine_file.h0000644000175000017500000000042114674562002016040 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.3.0/include/tracy.h0000644000175000017500000000217614674562002014570 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.3.0/include/embedded.h0000644000175000017500000000032114674562002015165 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EMBEDDED_H #define MUON_EMBEDDED_H const char *embedded_get(const char *name); #endif muon-v0.3.0/include/error.h0000644000175000017500000000400714674562002014572 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 #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_include_sources = 1 << 1, error_diagnostic_store_replay_werror = 1 << 2, }; void error_unrecoverable(const char *fmt, ...) MUON_ATTR_FORMAT(printf, 1, 2); void error_message(struct source *src, struct source_location location, enum log_level lvl, const char *msg); void error_messagev(struct source *src, struct source_location location, enum log_level lvl, const char *fmt, va_list args); void error_messagef(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_replay(enum error_diagnostic_store_replay_opts opts, bool *saw_error); void error_diagnostic_store_push(uint32_t src_idx, struct source_location location, enum log_level lvl, const char *msg); void error_diagnostic_store_redirect(struct source *src, struct source_location location); void error_diagnostic_store_redirect_reset(void); void list_line_range(struct source *src, struct source_location location, uint32_t context); 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(struct source *src, struct source_location loc, struct detailed_source_location *dloc, enum get_detailed_source_location_flag flags); #endif muon-v0.3.0/include/cmd_install.h0000644000175000017500000000046314674562002015734 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.3.0/include/meson_opts.h0000644000175000017500000000054614674562002015633 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.3.0/include/guess.h0000644000175000017500000000040114674562002014561 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.3.0/include/external/0002755000175000017500000000000014674562002015113 5ustar buildbuildmuon-v0.3.0/include/external/libarchive.h0000644000175000017500000000053214674562002017372 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.3.0/include/external/libcurl.h0000644000175000017500000000057614674562002016726 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; void muon_curl_init(void); void muon_curl_deinit(void); bool muon_curl_fetch(const char *url, uint8_t **buf, uint64_t *len); #endif muon-v0.3.0/include/external/libpkgconf.h0000644000175000017500000000126514674562002017404 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 pkgconf_info { char version[MAX_VERSION_LEN + 1]; obj includes, libs, not_found_libs, link_args, compile_args; }; extern const bool have_libpkgconf; bool muon_pkgconf_lookup(struct workspace *wk, obj name, bool is_static, struct pkgconf_info *info); bool muon_pkgconf_get_variable(struct workspace *wk, const char *pkg_name, const char *var, obj *res); bool muon_pkgconf_define(struct workspace *wk, const char *key, const char *value); #endif muon-v0.3.0/include/external/samurai.h0000644000175000017500000000056714674562002016733 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; }; extern const bool have_samurai; bool samu_main(int argc, char *argv[], struct samu_opts *opts); #endif muon-v0.3.0/include/external/samurai/0002755000175000017500000000000014674562002016554 5ustar buildbuildmuon-v0.3.0/include/external/samurai/tool.h0000644000175000017500000000044414674562002017702 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 *); #endif muon-v0.3.0/include/external/samurai/tree.h0000644000175000017500000000141014674562002017656 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.3.0/include/external/samurai/util.h0000644000175000017500000000351414674562002017703 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.3.0/include/external/samurai/ctx.h0000644000175000017500000001062614674562002017526 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.3.0/include/external/samurai/scan.h0000644000175000017500000000165114674562002017652 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.3.0/include/external/samurai/parse.h0000644000175000017500000000117714674562002020043 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); /* supported ninja version */ enum { ninjamajor = 1, ninjaminor = 9, }; /* 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.3.0/include/external/samurai/env.h0000644000175000017500000000376714674562002017530 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.3.0/include/external/samurai/htab.h0000644000175000017500000000120614674562002017640 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.3.0/include/external/samurai/log.h0000644000175000017500000000064314674562002017507 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.3.0/include/external/samurai/deps.h0000644000175000017500000000104314674562002017654 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 sbuf *output, const char **filtered_output, struct samu_edge *e); #endif muon-v0.3.0/include/external/samurai/graph.h0000644000175000017500000000312414674562002020024 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.3.0/include/external/samurai/arg.h0000644000175000017500000000125514674562002017477 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.3.0/include/external/samurai/build.h0000644000175000017500000000105514674562002020023 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.3.0/include/external/tinyjson.h0000644000175000017500000000056514674562002017145 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Seedo Paul * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_TINYJSON_H #define MUON_EXTERNAL_TINYJSON_H #include #include "lang/workspace.h" bool muon_json_to_dict(struct workspace *wk, char *json_str, obj *res); #endif muon-v0.3.0/include/external/readline.h0000644000175000017500000000047114674562002017047 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.3.0/include/version.h0000644000175000017500000000055714674562002015134 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.3.0/include/lang/0002755000175000017500000000000014674562002014212 5ustar buildbuildmuon-v0.3.0/include/lang/compiler.h0000644000175000017500000000135114674562002016173 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 #include "lang/workspace.h" 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, }; 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, struct source *src, enum vm_compile_mode mode, uint32_t *entry); #endif muon-v0.3.0/include/lang/source.h0000644000175000017500000000111214674562002015654 0ustar buildbuild#ifndef MUON_LANG_SOURCE_H /* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #define MUON_LANG_SOURCE_H #include enum source_reopen_type { source_reopen_type_none, source_reopen_type_embedded, source_reopen_type_file, }; struct source { const char *label; const char *src; uint64_t len; // only necessary if src is NULL. If so, this source will be re-read // on error to fetch appropriate context lines. enum source_reopen_type reopen_type; }; struct source_location { uint32_t off, len; }; #endif muon-v0.3.0/include/lang/eval.h0000644000175000017500000000173214674562002015313 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 #include #include "lang/types.h" struct workspace; struct source; struct darr; enum eval_mode { eval_mode_default, eval_mode_repl, eval_mode_first, }; 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, bool first); bool eval(struct workspace *wk, struct source *src, 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); #endif muon-v0.3.0/include/lang/workspace.h0000644000175000017500000000600014674562002016353 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_WORKSPACE_H #define MUON_LANG_WORKSPACE_H #include "buf_size.h" #include "datastructures/arr.h" #include "datastructures/bucket_arr.h" #include "datastructures/hash.h" #include "datastructures/stack.h" #include "lang/eval.h" #include "lang/object.h" #include "lang/source.h" #include "lang/string.h" #include "lang/vm.h" struct project { /* array of dicts */ obj scope_stack; obj source_root, build_root, cwd, build_dir, subproject_name; obj opts, compilers, targets, tests, test_setups, summary; obj args, link_args, include_dirs; struct { obj static_deps, shared_deps; } dep_cache; obj wrap_provides_deps, wrap_provides_exes; // string obj rule_prefix; obj subprojects_dir; struct { obj name; obj version; obj license; obj license_files; bool no_version; } cfg; bool not_ok; // set by failed subprojects bool initialized; }; 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_array that tracks files for build regeneration */ obj regenerate_deps; /* TODO host machine dict */ obj host_machine; /* TODO binaries dict */ obj binaries; obj install; obj install_scripts; obj postconf_scripts; obj subprojects; /* args dict for add_global_arguments() */ obj global_args; /* args dict for add_global_link_arguments() */ obj global_link_args; /* overridden dependencies dict */ obj dep_overrides_static, dep_overrides_dynamic; /* overridden find_program dict */ obj find_program_overrides; /* global options */ obj global_opts; /* dict[sha_512 -> [bool, any]] */ obj compiler_check_cache; /* dict -> 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; struct complex_types complex_types; 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); bool workspace_setup_paths(struct workspace *wk, const char *build, const char *argv0, uint32_t argc, char *const argv[]); 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_cwd(struct workspace *wk); void workspace_print_summaries(struct workspace *wk, FILE *out); #endif muon-v0.3.0/include/lang/lexer.h0000644000175000017500000000541714674562002015507 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 #include "datastructures/bucket_arr.h" #include "datastructures/stack.h" #include "error.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, }; // Keep in sync with above #define token_type_count (token_type_returntype + 1) 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, }; struct lexer_fmt { obj raw_blocks; uint32_t raw_block_start; bool in_raw_block; }; struct lexer { struct workspace *wk; struct source *source; const char *src; struct stack stack; struct lexer_fmt fmt; uint32_t i, ws_start, ws_end; enum lexer_mode mode; uint8_t enclosed_state; }; bool is_valid_inside_of_identifier(const char c); bool is_valid_start_of_identifier(const char c); void lexer_init(struct lexer *lexer, struct workspace *wk, 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); #endif muon-v0.3.0/include/lang/func_lookup.h0000644000175000017500000000216614674562002016712 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_impl)(struct workspace *wk, obj self, obj *res); typedef obj (*func_impl_self_transform)(struct workspace *wk, obj self); struct func_impl { const char *name; func_impl func; type_tag return_type; bool pure, fuzz_unsafe, extension; func_impl_self_transform self_transform; }; 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 dump_function_signature(struct workspace *wk, struct args_norm posargs[], struct args_kw kwargs[]); void dump_function_signatures(struct workspace *wk); #endif muon-v0.3.0/include/lang/string.h0000644000175000017500000000775414674562002015704 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; #define WKSTR(cstring) \ (struct str) \ { \ .s = cstring, .len = strlen(cstring) \ } #define WKSTR_STATIC(str) \ { \ str, sizeof(str) - 1 \ } /* sbuf */ enum sbuf_flags { sbuf_flag_overflown = 1 << 0, sbuf_flag_overflow_obj_str = 0 << 1, // the default sbuf_flag_overflow_alloc = 1 << 1, sbuf_flag_overflow_error = 1 << 2, sbuf_flag_write = 1 << 3, sbuf_flag_string_exposed = 1 << 4, }; #define SBUF_CUSTOM(name, static_len, flags) \ struct sbuf name; \ char sbuf_static_buf_##name[static_len]; \ sbuf_init(&name, sbuf_static_buf_##name, static_len, flags); #define SBUF(name) SBUF_CUSTOM(name, 1024, 0) #define SBUF_manual(name) SBUF_CUSTOM(name, 1024, sbuf_flag_overflow_alloc) struct sbuf { char *buf; uint32_t len, cap; enum sbuf_flags flags; obj s; }; void sbuf_init(struct sbuf *sb, char *initial_buffer, uint32_t initial_buffer_cap, enum sbuf_flags flags); void sbuf_destroy(struct sbuf *sb); void sbuf_clear(struct sbuf *sb); void sbuf_grow(struct workspace *wk, struct sbuf *sb, uint32_t inc); void sbuf_push(struct workspace *wk, struct sbuf *sb, char s); void sbuf_pushn(struct workspace *wk, struct sbuf *sb, const char *s, uint32_t n); void sbuf_pushs(struct workspace *wk, struct sbuf *sb, const char *s); void sbuf_pushf(struct workspace *wk, struct sbuf *sb, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); void sbuf_push_json_escaped(struct workspace *wk, struct sbuf *buf, const char *str, uint32_t len); void sbuf_push_json_escaped_quoted(struct workspace *wk, struct sbuf *buf, const struct str *str); obj sbuf_into_str(struct workspace *wk, struct sbuf *sb); void str_unescape(struct workspace *wk, struct sbuf *sb, const struct str *ss, bool escape_whitespace); 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); 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); obj str_join(struct workspace *wk, obj s1, obj s2); bool str_to_i(const struct str *ss, int64_t *res, bool strip); 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); void str_to_lower(struct str *str); bool is_whitespace(char c); #endif muon-v0.3.0/include/lang/object_iterators.h0000644000175000017500000001126514674562002017730 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 #include "lang/types.h" #include "preprocessor_helpers.h" /****************************************************************************** * obj_array_for ******************************************************************************/ struct obj_array_for_helper { struct obj_array *a; }; #define obj_array_for_(__wk, __arr, __val, __iter) \ struct obj_array_for_helper __iter = { \ .a = get_obj_array(__wk, __arr), \ }; \ for (__val = __iter.a->len ? __iter.a->val : 0; __val; \ __iter.a = __iter.a->have_next ? get_obj_array(__wk, __iter.a->next) : 0, \ __val = __iter.a ? __iter.a->val : 0) #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; uint64_t *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 = hash_get(__iter.h, __iter.k), \ __key = *__iter.v >> 32, __val = *__iter.v & 0xffffffff #define obj_dict_for_get_kv(__iter, __key, __val) __key = __iter.e->key, __val = __iter.e->val #define obj_dict_for_(__wk, __dict, __key, __val, __iter) \ struct obj_dict_for_helper __iter = { \ .d = get_obj_dict(__wk, __dict), \ }; \ for (__key = 0, \ __val = 0, \ __iter.big = __iter.d->flags & obj_dict_flag_big, \ __iter.h = __iter.big ? bucket_arr_get(&__wk->vm.objects.dict_hashes, __iter.d->data) : 0, \ __iter.e = __iter.big ? 0 : \ __iter.d->len ? 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 ? \ 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(__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 *a; 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.3.0/include/lang/fmt.h0000644000175000017500000000166114674562002015153 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 "lang/parser.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.3.0/include/lang/analyze.h0000644000175000017500000000253614674562002016032 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" enum az_diagnostic { az_diagnostic_unused_variable = 1 << 0, az_diagnostic_reassign_to_conflicting_type = 1 << 1, az_diagnostic_dead_code = 1 << 2, az_diagnostic_redirect_script_error = 1 << 3, }; struct az_opts { bool subdir_error; bool eval_trace; enum error_diagnostic_store_replay_opts replay_opts; const char *file_override, *internal_file, *get_definition_for; uint64_t enabled_diagnostics; }; 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); extern struct func_impl_group az_func_impl_group; bool do_analyze(struct az_opts *opts); void eval_trace_print(struct workspace *wk, obj trace); #endif muon-v0.3.0/include/lang/vm.h0000644000175000017500000001361314674562002015007 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/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_add_store, op_load, op_try_load, op_return, op_return_end, op_call, op_call_method, op_call_native, 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, // 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 variable_assignment_mode { assign_local, assign_reassign, }; enum compile_time_constant_objects { disabler_id = 1, obj_bool_true = 2, obj_bool_false = 3, }; 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 vm_compiler_state { struct bucket_arr nodes; struct arr node_stack; struct arr loop_jmp_stack, if_jmp_stack; uint32_t loop_depth; bool err; }; struct vm_dbg_state { struct source_location prev_source_location; obj watched; obj breakpoints; obj root_eval_trace; obj eval_trace; bool dbg, stepping, dump_signature; 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, bool first)); 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; struct bucket_arr obj_aos[obj_type_count - _obj_aos_start]; struct hash obj_hash, str_hash; bool obj_clear_mark_set; }; typedef void((*vm_op_fn)(struct workspace *wk)); struct vm_ops { vm_op_fn ops[op_count]; }; 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 module_path; struct vm_ops ops; struct vm_objects objects; struct vm_behavior behavior; struct vm_compiler_state compiler_state; struct vm_dbg_state dbg_state; 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_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); 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, 4, 5) void vm_diagnostic(struct workspace *wk, uint32_t ip, enum log_level lvl, 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, ...); bool vm_dbg_push_breakpoint(struct workspace *wk, const char *bp); #endif muon-v0.3.0/include/lang/serial.h0000644000175000017500000000062514674562002015643 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.3.0/include/lang/parser.h0000644000175000017500000000327714674562002015666 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 #include "datastructures/arr.h" #include "lang/compiler.h" #include "lang/lexer.h" enum node_type { node_type_stmt, node_type_group, node_type_bool, node_type_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_method, node_type_call, node_type_assign, node_type_plusassign, 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; }; struct node { union literal_data data; struct node *l, *r; struct source_location location; struct { struct node_fmt pre, post; } fmt; enum node_type type; }; 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, struct source *src, enum vm_compile_mode mode); struct node *parse_fmt(struct workspace *wk, 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); #endif muon-v0.3.0/include/lang/object.h0000644000175000017500000004054714674562002015641 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 #include "compilers.h" #include "datastructures/bucket_arr.h" #include "datastructures/iterator.h" #include "lang/types.h" #include "machines.h" enum obj_type { /* singleton object types */ obj_null, obj_meson, obj_disabler, /* simple object types */ obj_bool, 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; struct ast *ast; enum language_mode lang_mode; uint32_t args_id, block_id, nargs, nkwargs; obj kwarg_defaults, src, scope_stack; 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; }; 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", true) \ _(keyval, "public", true) \ _(pkgconfig, "public", true) \ _(python, "public", true) \ _(python3, "public", true) \ _(sourceset, "public", true) \ _(toolchain, "private", true) \ _(cmake, "public", false) \ _(dlang, "public", false) \ _(gnome, "public", false) \ _(hotdoc, "public", false) \ _(i18n, "public", false) \ _(java, "public", false) \ _(modtest, "public", false) \ _(qt, "public", false) \ _(qt4, "public", false) \ _(qt5, "public", false) \ _(qt6, "public", false) \ _(unstable_cuda, "public", false) \ _(unstable_external_project, "public", false) \ _(unstable_icestorm, "public", false) \ _(unstable_rust, "public", false) \ _(unstable_simd, "public", false) \ _(unstable_wayland, "public", false) \ _(windows, "public", 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 { obj val; // any obj next; // obj_array obj tail; // obj_array uint32_t len; bool have_next; }; enum obj_dict_flags { obj_dict_flag_big = 1 << 0, obj_dict_flag_int_key = 1 << 1, obj_dict_flag_dont_expand = 1 << 2, }; 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, }; struct build_dep { enum compiler_language link_language; obj link_whole; // obj_array obj link_with; // obj_array obj link_with_not_found; // obj_array obj link_args; // obj_array obj compile_args; // obj_array obj include_directories; // obj_array obj sources; // obj_array obj objects; // obj_array obj order_deps; // obj_array obj rpath; // obj_array struct { obj deps; obj link_with; obj link_whole; } 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 src; // obj_array obj objects; // obj_array obj args; // obj_dict obj link_depends; // obj_array obj generated_pc; // obj_string obj override_options; // obj_array obj required_compilers; // obj_dict struct build_dep dep; struct build_dep dep_internal; enum compiler_visibility_type visibility; enum build_tgt_flags flags; enum tgt_type type; }; struct obj_both_libs { 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 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_appleframeworks, dependency_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 include_type include_type; }; 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; obj linker_cmd_arr; obj static_linker_cmd_arr; obj ver; obj libdirs; obj overrides[3]; uint32_t type[3]; enum compiler_language lang; bool linker_passthrough; }; 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; }; enum build_option_type { op_string, op_boolean, op_combo, op_integer, op_array, op_feature, 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; 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 *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]; }; void make_obj(struct workspace *wk, obj *id, enum obj_type type); enum obj_type get_obj_type(struct workspace *wk, obj id); 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); void set_obj_bool(struct workspace *wk, obj o, bool v); bool get_obj_bool(struct workspace *wk, obj o); void set_obj_bool(struct workspace *wk, obj o, bool v); 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 sbuf; 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 sbuf *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(...) \ if (log_should_print(log_debug)) { \ char buf[4096]; \ log_print_prefix(log_debug, buf, ARRAY_LEN(buf)); \ log_plain("%s", buf); \ obj_fprintf(wk, log_file(), __VA_ARGS__); \ } #define LOBJ(object_id) LO("%s: %o\n", #object_id, object_id) bool obj_vasprintf(struct workspace *wk, struct sbuf *sb, const char *fmt, va_list ap); bool obj_asprintf(struct workspace *wk, struct sbuf *sb, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); bool obj_vfprintf(struct workspace *wk, FILE *f, const char *fmt, va_list ap) MUON_ATTR_FORMAT(printf, 3, 0); 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); 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, FILE *out, 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); void obj_array_index(struct workspace *wk, obj arr, int64_t i, obj *res); 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); 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_pop(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); 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_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); bool obj_iterable_foreach(struct workspace *wk, obj dict_or_array, void *ctx, obj_dict_iterator cb); #endif muon-v0.3.0/include/lang/types.h0000644000175000017500000000136114674562002015526 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; obj val; uint32_t node; bool set, optional; }; struct args_kw { const char *key; type_tag type; obj val; uint32_t node; bool set; bool required; }; enum language_mode { language_external, language_internal, language_opts, language_mode_count, language_extended, }; enum log_level { log_quiet, log_error, log_warn, log_info, log_debug, log_level_count, }; struct complex_types { type_tag options_dict_or_list; }; #endif muon-v0.3.0/include/lang/typecheck.h0000644000175000017500000001642314674562002016346 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_or, complex_type_nested, }; #define ARG_TYPE_NULL (obj_type_count + 1) // clang-format off #define TYPE_TAG_ALLOW_VOID (((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_VOID | 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) // clang-format off #define tc_meson (obj_typechecking_type_tag | (((type_tag)1) << 0)) #define tc_disabler (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); 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); void complex_types_init(struct workspace *wk, struct complex_types *types); #endif muon-v0.3.0/include/opts.h0000644000175000017500000000461514674562002014433 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_OPTS_H #define MUON_OPTS_H #include "lang/workspace.h" #include "platform/os.h" /* 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(usage_pre, usage_post, usage_opts, commands, operands) \ case 'h': \ print_usage(stdout, commands, usage_pre, usage_opts, usage_post); \ 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; typedef bool (*cmd_func)(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, cmd_func *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.3.0/include/rpmvercmp.h0000644000175000017500000000037514674562002015460 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_RPMVERCMP_H #define MUON_RPMVERCMP_H #include "lang/string.h" int8_t rpmvercmp(const struct str *a, const struct str *b); #endif muon-v0.3.0/include/buf_size.h0000644000175000017500000000066114674562002015251 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.3.0/include/compat.h0000644000175000017500000000056314674562002014727 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 muon-v0.3.0/doc/0002755000175000017500000000000014674562002012413 5ustar buildbuildmuon-v0.3.0/doc/features.md0000644000175000017500000000240614674562002014553 0ustar buildbuild # muon - features In addition to trying maintain meson compatibility, muon also has a few features of its own. ### muon analyze muon has a full-featured static analyzer for meson build scripts. It can check for undefined variables, unused variables, variable reassignment to different types, undefined functions, undefined kwargs, missing required args, methods, operator typechecking, and more. ### muon fmt A formatter for meson build scripts. ### debugger muon has an interactive gdb-like debugger. You can trigger it by setting breakpoints with the -b flag on either setup or internal eval subcommands. Inside the debugger you can look around, evaluate expressions, and step through code. ### built-in ninja muon has an embedded copy of samurai (at src/external/samurai) which has been ported to use muon's cross-platform layer and so works on all the platforms muon supports. This means you can just grab a single muon binary and that's all you need to build your project. ### fast muon is orders of magnitude faster than meson when your scripts are interpreter- bound. This means that as long as muon doesn't have to shell out to the compiler it is very fast. muon-v0.3.0/doc/release_notes.md0000644000175000017500000000164714674562002015573 0ustar buildbuild # Release notes ## 0.3.0 - Brand-new interpreter: https://mochiro.moe/posts/10-muon-internals/ - Lots of windows improvements - bootstrap.bat - Simple tests passing - Lots of macOS improvements - All tests passing - Universal binary deployed by CI - Script modules introduced: a way of writing muon modules with mostly normal meson code. - i18n module - gnome module (wip) - Embedded cross-platform samurai implementation - Lots of bugs fixed! - 17 contributors: Andrea Pappacoda (1) Andrew McNulty (11) Eli Schwartz (1) Filipe Laíns (4) JCWasmx86 (1) Michael Forney (2) Michal Sieron (2) Seedo Paul (27) Sertonix (3) Stone Tickle (497) Thomas Adam (1) Tokunori Ikegami (1) Vincent Torri (12) kzc (2) rofl0r (1) sewn (1) torque (1) muon-v0.3.0/doc/meson.build0000644000175000017500000000211514674562002014552 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only scdoc = find_program('scdoc', required: get_option('docs')) if not scdoc.found() subdir_done() endif man_pages = { 'muon': 1, 'meson.build': 5, } man_page_targets = {} foreach page, section : man_pages name = f'@page@.@section@' input = f'@name@.scd' tgt = custom_target( input, input: files(input), output: name, capture: true, feed: true, command: scdoc, install: true, install_dir: get_option('mandir') / 'man@0@'.format(section), ) man_page_targets += {name: tgt} endforeach meson_proj = dependency('', required: false) if python3.found() meson_proj = subproject('meson-docs', required: false) endif if meson_proj.found() man_page_targets += { 'meson-reference.3': meson_proj.get_variable('meson_reference_3'), } else warning('meson-docs not found, meson-reference.3 will not be built') endif if get_option('website') subdir('website') endif summary('docs', true) muon-v0.3.0/doc/misc/0002755000175000017500000000000014674562002013346 5ustar buildbuildmuon-v0.3.0/doc/misc/configuration_data_escaping_rules.md0000644000175000017500000000153414674562002022614 0ustar buildbuild Configuration data escaping rules: 1. You can escape the format character (@) by preceding it with a backslash. 2. Backslashes not directly preceding a format character are not modified. 3. The number of backslashes preceding a @ in the output is equal to the number of backslashes in the input divided by two, rounding down. For example, given the string "\\@" (one backslash), the output will contain no backslashes. Both "\\\\@" and "\\\\\\@" (two and three backslashes) will produce one backslash in the output "\\@". 4. If the configuration format is cmake and the number of backslashes is even, don't escape the variable. Otherwise, always escape the variable. Examples: "\\@" -> "@" "\\\\@" -> "\\@" "\\\\ @" -> "\\\\ @" muon-v0.3.0/doc/misc/dep_flowchart.md0000644000175000017500000000307714674562002016516 0ustar buildbuild ```mermaid flowchart TD start(for each dependency name) start --> loop loop(have another dependency name) loop --yes--> special loop --no--> notfound special(has special handling) special --yes--> found special --no--> cache cache(dependency in cache) cache --yes--> vercheck cache --no--> forcefallback vercheck(dependency version matches) vercheck --yes--> found vercheck --no--> loop forcefallback(fallback forced) forcefallback --yes--> fallback forcefallback --no--> lookup fallback(fallback keyword set) fallback --yes - use fallback from keyword--> handlefallback fallback --no--> fallbackimplicit fallbackimplicit(fallback provided by any wraps?) fallbackimplicit --yes - use fallback from wrap provides--> handlefallback fallbackimplicit --no--> fallbackimplicitname fallbackimplicitname(fallback is allowed) fallbackimplicitname --yes - use fallback based on dependency name--> handlefallback fallbackimplicitname --no--> loop handlefallback(check for any named dependency in subproject) handlefallback --found--> found handlefallback --not found--> loop lookup(lookup dependency with tool, e.g. pkgconf) lookup --found--> found lookup --not found--> fallbackafterlookup fallbackafterlookup(fallback is allowed) fallbackafterlookup --yes--> fallback fallbackafterlookup --no--> loop found(done) notfound(not found) ``` fallback allowed: allow\_fallback is set to true or requirement is required muon-v0.3.0/doc/contributing.md0000644000175000017500000000752314674562002015451 0ustar buildbuild # Contributions Hello, thanks for considering contributing to muon. Please send patches and questions to <~lattis/muon@lists.sr.ht>. Before making any big changes, please send a proposal to the mailing list so I can give you some pointers, and make sure you don't waste your time. # Style Muon uses a style similar to the linux kernel. A few differences are: - the return type of a function goes on the line above its declaration. e.g. ```c int main(void) { return 0; } ``` - it still goes on the same line in a function prototype e.g. ```c int main(void); ``` - never omit braces for single statement if, else, for, while, etc. - avoid function-like macros except in exceptional cases - it is OK (but not great) if your line is more than 80 characters - please use fixed-width integer types (e.g. `uint32_t`, `uint64_t`, `uint8_t`) whenever possible In general, just try to follow the style of surrounding code. # Internals ## Error handling All errors that can be checked, should be checked. If an error is detected, an error message should be printed using `vm_error`, or if there is no source code associated with the error, `LOG_E`. The error should be immediately returned. Most functions returning a `bool` return `false` on error. The most common other type of error returning function has the return type `enum iteration_result`. These functions should return `ir_err` on error. ## Meson functions All meson functions are defined in `functions` with a separate file for each object type on which the function is defined. Functions not defined on an object are in `kernel.c`. If the function implementation is sufficiently large, it may be broken up into a separate file under `functions//.c`. When declaring a new function, you need to add it to the "impl\_tbl" at the bottom of the file. All functions should call `pop_args()` before they do anything, even if they don't take any arguments. `pop_args` takes 3 arrays, conventionally named `an` (args, normal), `ao` (args, optional), and `akw` (args, keyword). Any of these may be NULL. In particular, if they are all NULL then the function takes no arguments, and `pop_args` will ensure this is the case. Arguments should specify what types they accept by bitwise or-ing `tc_`-prefixed types together. `an` and `ao` argument arrays *must* be terminated by an argument of type `ARG_TYPE_NULL`. `TYPE_TAG_GLOB` can be or-d with the type of the last element of `an`, and will store the remaining arguments in an `obj_array`. This is similar to e.g. `def func(a, b, *c):` in python, where `c` is the "glob" argument. You may also bitwise or any type with `TYPE_TAG_LISTIFY`. This "type" will cause `pop_args` to do the following things: 1. coerce single elements to arrays - `'hello' #=> ['hello']` 2. flatten arrays - `['hello', [], [['world']]] #=> ['hello', 'world']` 3. typecheck all elements of the array - given the "type" `TYPE_TAG_LISTIFY | obj_string`, the above examples would pass, but `['a', false]` would not. ## Workspace The workspace is a structure that contains all the data for an entire build setup, including all subprojects, AST, options, objects, strings. Most interpreter related functions take a workspace as one of their arguments. ## Objects Meson objects are created with the `make_obj` function. See `object.h` for more information. Conventionally, all objects are referred to by id of type obj rather than a pointer. ## Memory You may be wondering, what about Garbage collection? Muon's current approach is to cleanup only once at the very end. Meson is not a Turing-complete language, so we don't need to worry about long running programs. muon-v0.3.0/doc/differences.md0000644000175000017500000001135314674562002015213 0ustar buildbuild # Differences between muon and Meson This document describes functional differences between muon and Meson. None of these is a set-in-stone design decision, just a reflection of the current state of affairs. This document is also not exhaustive, but is a best-effort list. Some other small differences may be found by searching the tests/project for "# different than meson" comments. ## nested subproject promotion Meson performs nested subproject promotion. This means that nested subprojects become top-level subprojects, and all subprojects share the same namespace. For example, given the following project structure: ``` . ├── meson.build └── subprojects ├── a │ ├── meson.build │ └── subprojects │ └── b │ └── meson.build └── b └── meson.build ``` The order of `subproject` calls determines which subprojects will be used: ```meson project('main') # This causes all subprojects under subprojects/a/subprojects/ to be "promoted" subproject('a') # This will now use subprojects/a/subprojects/b, instead of subprojects/b subproject('b') ``` muon does not perform subproject promotion. ## malformed escape sequences Meson silently accepts malformed escape sequences and outputs them literally, removing the leading escape character. For example: ```meson '\c' # becomes 'c' '\Uabcdefghi' # becomes 'Uabcdefghi' '\xqr' # becomes 'xqr' ``` In muon, malformed escape sequences are parse errors. ## format strings Format strings in various parts of Meson use `@` as the delimiter. The behavior is inconsistent, `configure_file()` recognizes `\` as an escape character, but format strings in `string.format()` and `custom_target` command arguments do not. Muon will accept `\` as an escape character in `string.format()` and `custom_target`, but this is incompatible with meson. `\` use in `custom_target` command arguments is already incompatible though (see next point). `configure_file()` will also warn you about invalid substitutions, and will remove them in the output, `string.format()` will error on invalid substitutions, and `custom_target` command arguments will be silently treated as literals if they are invalid substitutions (e.g. `@BAZ@`). Because some projects rely on the above custom target command argument behaviour, muon merely adds a warning for invalid substitutions. In all other cases muon will raise error. ## `custom_target` replaces backslashes with slashes in the command arguments In Meson, all backslashes in `custom_target` command line arguments are blindly replaced to forward slashes. This behavior is not present in muon. Reference: ## `build_target()` functions with empty sources Meson allows you to create build targets (`executable()`, `shared_library()`, `static_library()`, etc.) without specifying any sources. In muon this is an error. ## global compiler cache Meson maintains a global compiler cache, which means that all languages added by subprojects are available to the main project and vice-versa. This can hide bugs that will surface if the subproject is built by itself, or subproject calls are rearranged. ## run\_command() cwd Meson executes run\_command() commands in the current subdirectory, while muon executes them in the project root. Neither behaviour should be relied upon however, since the docs say that it runs commands from an unspecified directory. ## backslash escaping in compiler defines Meson replaces `\` with `\\` in compiler defines. This is legacy behavior that prevents you from using things like C escapes (e.g. `\n`) in compiler defines, at the benefit of making it easier to use windows paths. See meson commit aca93df184a32ed7faf3636c0fbe90d05cb67857 for more information: > Jon Turney: > Now that all command-line escaping for ninja is dealt with in the ninja > backend, escape_extra_args() shouldn't need to do anything. > > But tests of existing behaviour rely on all backslashes in defines being > C escaped: This means that Windows-style paths including backslashes can > be safely used, but makes it impossible to have a define containing a C > escape. ## MESONINTROSPECT Since muon does not offer an introspection subcommand, `MESONINTROSPECT` is not set in the environment of run\_command, test, custom\_target, etc. `MUON_PATH` is provided for users who are waiting for and are (ab)using `MESONINTROSPECT` for this purpose. ## default option values A number of builtin options have different default values: - `default_library` is `'static'` - `warning_level` is `'3'` muon-v0.3.0/doc/muon_fmt.ini0000644000175000017500000000100614674562002014733 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only # Example configuration for muon fmt max_line_len = 80 indent_style = space indent_size = 4 space_array = false kwargs_force_multiline = false wide_colon = false no_single_comma_function = false insert_final_newline = true ; end_of_line = (not specifying end_of_line tells muon fmt to retain current line endings) sort_files = true group_arg_value = true simplify_string_literals = false indent_before_comments = ' ' muon-v0.3.0/doc/script_modules.md0000644000175000017500000001465314674562002016000 0ustar buildbuild # Script Modules With muon, you can write modules in an extended form of the meson dsl. This is for a number of reasons: - Many meson modules are effectively reusable chunks of meson code (calls to dependency, custom\_target, etc.), so it is not necessary to access things that a native module has access to like the interpreter state. - Lowering the bar for module contributions (this is important because they often involve specific tools I have no experience with/interest in). - Potential to some day share module code with other meson implementations. That last point is a bit pie in the sky, but at least this makes it technically possible from muon's point of view and proves that it has value. ## Writing a new script module All script modules live at `src/script/modules/.meson`. After adding a new module, you must modify `src/script/meson.build` to register it. muon will embed the module source text as a char[] in the built executable, and when the module is import()ed that source will be interpreted. ## Module structure A script module consists of functions that perform the various module operations, with a return statement at the bottom of the file that exports those functions. ``` func func1() endfunc func func2() endfunc return { 'func1': func1, 'func2': func2, } ``` The final `return` at module scope is what "exports" the functions to module consumers. The returned object must be of type `dict[func]`. ## Functions Function definition is started with the `func` keyword followed by an identifier, and a list of arguments enclosed in parenthesis. If the function returns a value it must specify the type of that value by adding `-> ` after the argument list. Positional and keyword arguments are supported in the argument list. Positional arguments must come first. Keyword arguments are specified by adding a colon (:) to the end of the argument followed by an optional default value. All arguments must also specify their type by following the argument name with a type specifier. Examples: ``` func a() -> int return 1 endfunc func b(arg1 int) -> int return arg1 endfunc func b(arg1 int, kw str:, kw_with_default str: 'default') message(arg1) message(kw) message(kw_with_default) endfunc ``` ## Scope Scope generally works like other interpreted languages. Modules have their own scope and cannot access any external variables (save builtins such as meson, build\_machine, etc.). Functions also get their own scope, but can see variables that have been declared in parent scopes prior to the function definition. Function definitions also capture their scope. Example: ``` func a() -> func i = 0 func b() -> int i += 1 return i endfunc return b endfunc counter = a() counter() #=> 1 counter() #=> 2 new_counter = a() new_counter() #=> 1 new_counter() #=> 2 counter() #=> 3 ``` ## Type specifiers Type specifiers take the form of the ones found in the meson docs. The following types are allowed: - `void` - `compiler` - `dep` - `meson` - `str` - `int` - `list` - `dict` - `bool` - `file` - `build_tgt` - `subproject` - `build_machine` - `feature` - `external_program` - `python_installation` - `runresult` - `cfg_data` - `custom_tgt` - `test` - `module` - `install_tgt` - `env` - `inc` - `option` - `disabler` - `generator` - `generated_list` - `alias_tgt` - `both_libs` - `typeinfo` - `func` - `source_set` - `source_configuration` In addition, a value that can take one of many types can be specified by writing multiple types separated with a `|`. ``` int|str dict|list|str ``` There are a few preset combinations of types: - `any` - all of the above types except `void` - `exe` - `str|file|external_program|python_installation|build_tgt|custom_tgt` `dict` and `list` should also be followed by a sub-type enclosed in square brackets. ``` dict[str] dict[str|int] ``` Finally, you can wrap the entire type in `glob[]` or `listify[]` to get the special argument handling detailed in doc/contributing.md (search for `TYPE_TAG_{GLOB,LISTIFY}`). ## Additional built-in functions Various additional builtin functions are avaliable: - `serial_load(path str) -> any` - load a serialized object from a file - `serial_dump(path str, obj any)` - serialize and save an object to a file - `is_void(arg any) -> bool` - check if arg is the value void. This can be used to check if a kwarg with no default value has been set. - `typeof(arg any) -> str` - returns the type of an object. - `list.delete(index int)` - delete the element from list at the specefied index - `dict.delete(key str)` - delete key from dict - `meson.argv0() -> str` - gets the command used to invoke muon (useful for creating targets which invoke scripts). - `print(str str)` - print text without additional modifications. - `exit(code int)` - call exit() with the given code. The `fs` module also has additonal functions: - `copy(src str|file, dest str)` - copy a file from src to dest - `write(dest str|file, data str)` - write `data` to `dest` - `cwd() -> str` - returns muon's current working directory - `mkdir(path str)` - make the directory at `path` - `rmdir(path str, recursive bool: false, force bool: false)` - delete the directory at `path`. `recursive` defaults to false. If set to true the contents of `path` will be recursively deleted before `path` itself. `force` will ignore errors. - `is_basename(path str) -> bool` - true if str contains no path separators - `is_subpath(base str, sub str) -> bool` - true if `sub` is a subpath of `base` - `add_suffix(path str, suff str) -> str` - add the suffix `suff` to `path` - `make_absolute(path str) -> str` - prepend cwd to `path` if path is relative - `without_ext(path str) -> str` - return path with extension removed to it, otherwise return path - `executable(path str) -> str` - if path has no path separators, prepend './' - `glob(pattern str) -> list[str]` - similar to python's glob.glob ## Semantic differences between regular and extended Meson - In regular meson functions which create targets, the `output` parameter is not allowed to contain file separators. This has the effect that targets must only produce outputs which live in the current build directory. In extended meson file separators are permitted in these cases and so extended meson functions can create targets whose outputs may exist in other build directories. muon-v0.3.0/doc/meson.build.5.scd0000644000175000017500000001457714674562002015504 0ustar buildbuildmeson.build(5) ; SPDX-FileCopyrightText: Stone Tickle ; SPDX-License-Identifier: GPL-3.0-only # NAME meson.build - a build system dsl # DESCRIPTION The meson dsl is a dynamically typed language, similar to other interpreted languages like python and ruby. All objects are immutable. Functions cannot be defined, only built-in functions are available. - *STRUCTURE* - overview of source and project layout - *TYPES* - information on all primitive types - *SYNTAX* - description of syntax and keywords # STRUCTURE All meson projects must have a file called `meson.build` at the project root. This is the root build file. This file must have a call to the `project()` function as its first statement. Additional `meson.build` files may reside in any subdirectories of the project. These build files are then executed with the `subdir()` function. # TYPES The meson build system dsl contains most of the common types that would be expected: - booleans (written as `true` and `false`) - integers - strings - arrays - dictionaries ## Booleans Booleans are either `true` or `false`. *OPERATORS* - `and` - logical and - `or` - logical or - `not` - logical and ## Integers You can specify an integer literal using decimal, hexadecimal, octal, and binary. ``` int_1 = 1 int_42 = 42 int_255 = 0xFF int_493 = 0o755 int_1365 = 0b10101010101 ``` All common arithmetic operations are implemented for integers: addition, subtraction, division, multiplication, and modulo. *OPERATORS* - `\*` - multiplication - `/` - integer division - `%` - modulo (remainder) - `+` - addition - `-` - subtraction - `==` - equal to - `!=` - not equal to - `<` - less than - `>` - greater than - `<=` - less than or equal to - `>=` - greater than or equal to ## Strings Strings in Meson are declared with single quotes. `\` is the escape character. The full list of escape sequences is: - `\\` Backslash - `\'` Single quote - `\a` Bell - `\b` Backspace - `\f` Formfeed - `\n` Newline - `\r` Carriage Return - `\t` Horizontal Tab - `\v` Vertical Tab - `\ooo` Character with octal value ooo, up to 3 digits long - `\xhh` Character with hex value hh - `\uxxxx` Character with 16-bit hex value xxxx - `\Uxxxxxxxx` Character with 32-bit hex value xxxxxxxx - `\N{name}` Character named name in Unicode database Multi-line strings are surrounded by 3 consecutive quotes. These are raw strings that do not support the escape sequences listed above. Format strings are expressed by a placing leading `f` before the first opening quote. Inside of a format string, sequences of the form `@[a-z_]+@` will be substituted with the value of the matching variable. This variable can be of type bool, int, or str. ``` name = 'Alice' # prints "Hello Alice" message(f'Hello @name@') ``` *OPERATORS* - `+` - concatenate two strings - `/` - concatenate two strings as if `join_paths()` was called - `[int]` - access the character at index - ` in ` - check if str is a substring - ` not in ` - check if str is not a substring - `==` - equal to - `!=` - not equal to ## Arrays Arrays are delimited by brackets. An array can contain an arbitrary number of objects of any type. *OPERATORS* - `+` - If the rhs operand is an array, it will be joined to the lhs array. If it is a scalar, it will be appended to the lhs array. - `[int]` - access the object at index - ` in ` - check if object is in `array` - ` not in ` - check if object is not in `array` - `==` - equal to - `!=` - not equal to ## Dictionaries Dictionaries are delimited by curly braces. A dictionary can contain an arbitrary number of key: value pairs. Keys are required to be strings, but values can be objects of any type. Dictionaries are immutable and do not have a guaranteed order. *OPERATORS* - `+` - merge two dictionaries. In case of a conflicting key, the value from the rhs dictionary will be taken. - `[str]` - access the object with key `key` - ` in ` - check if key is in `dict` - ` not in ` - check if key is not in `dict` - `==` - equal to - `!=` - not equal to # SYNTAX A meson build file is composed of statements, which are terminated by newlines. Other than the statement-terminating newline, white space has no syntactic meaning. ## Comments A comment starts with the `#` character and extends until the end of the line. ## Variables A variable can contain a value of any type, and does not need to be predeclared. ``` var1 = 'hello' var2 = 102 ``` One important difference in how variables work in the dsl is that all objects are immutable. When you see an operation which appears like a mutation, actually a new object is created and assigned to the name. ``` var1 = [1, 2, 3] var2 = var1 var2 += [4] # var2 is now [1, 2, 3, 4] # var1 is still [1, 2, 3] ``` ## Function and method calls Builtin functions are called by their name followed by parenthesis containing optional, comma-separated arguments. Arguments are either positional or keyword. Keyword arguments are expressed using the keyword without quotes, followed by a colon. ``` foo() foo('bar') foo('bar', baz: true) ``` Method calls are expressed by a `.` followed by the same function call syntax as above. ``` foo.bar() foo.bar('baz', qux: false) ``` For a complete list of functions and methods, please see `meson-reference(3)`. ## If statements Start an if statement with the `if` keyword followed by a boolean expression. Further conditions can be expressed using the `elif` keyword followed by a boolean expression. The `else` keyword can be used to handle the case when no conditions are matched. An if statement is terminated with the `endif` keyword. ``` if conditon1 ... elif condition2 ... else condition3 ... endif ``` ## Foreach statements To loop over values in an iterable, use the `foreach` keyword followed by a comma separated list of names to assign the values of the iterable to, a colon, and an iterable expression. Only arrays and dictionaries are iterable. A foreach statement is terminated with the `endforeach` keyword. Arrays have one value to assign to. ``` foreach value : array foo(value) endforeach ``` Dictionaries have one two values to assign to. ``` foreach key, value : dictionary foo(key) bar(value) endforeach ``` Inside a `foreach` block you may use the `break` and `continue` keywords. \`break` exits the loop immediately. `continue` skips the rest of the current iteration. # SEE ALSO meson-reference(3) meson(1) muon(1) muon-v0.3.0/doc/website/0002755000175000017500000000000014674562002014055 5ustar buildbuildmuon-v0.3.0/doc/website/man-style.css0000644000175000017500000000126514674562002016502 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ body { max-width: 100ex; font-family: monospace; font-size: 14px; } pre { color: #434241; } @media (prefers-color-scheme: dark) { body { background-color: #282A36; color: #F8F8F2; } a { color: #8BE9FD; } pre { background-color: #191A21; color: #F8F8F2; } } .manual-text { padding: 0 9ex 1ex 4ex; } .head, .foot { width: 100%; color: #999; } .head-vol { text-align: center; } .head-rtitle { text-align: right; } h1 { font-size: 16px; } h2 { font-size: 15px; } .Bd-indent { padding-left: 4ex; } section .Ss { padding-left: 2ex; } muon-v0.3.0/doc/website/version_info.py.in0000644000175000017500000000030714674562002017532 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only class MuonVersion: version = "@version@" vcs_tag = "@vcs_tag@" meson_compat = "@meson_compat@" muon-v0.3.0/doc/website/meson.build0000644000175000017500000000272714674562002016225 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only mandoc = find_program('mandoc') foreach name, tgt : man_page_targets name = f'@name@.html' custom_target( name, input: tgt, output: name, capture: true, command: [mandoc, '-T', 'html', '-O', 'style=man-style.css', '@INPUT@'], build_by_default: true, ) endforeach if meson_proj.found() muon_signatures = custom_target( 'muon_signatures.txt', output: 'muon_signatures.txt', command: [muon, 'internal', 'dump_funcs'], capture: true, ) meson_signatures = meson_proj.get_variable('signatures') configure_file( configuration: version_info, input: 'version_info.py.in', output: 'version_info.py', ) env = environment() env.append('PYTHONPATH', meson.current_build_dir()) custom_target( 'status.html', output: 'status.html', command: [ python3, files('signatures_to_html.py'), muon_signatures, meson_signatures, ], capture: true, build_by_default: true, env: env, ) endif foreach f : [ 'index.html', 'muon_ci.html', 'man-style.css', 'status.css', 'main.css', 'muon_logo.svg', ] configure_file( configuration: version_info, input: f, output: f, ) endforeach summary('website', true) muon-v0.3.0/doc/website/status.css0000644000175000017500000000160114674562002016106 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ .positive { background-color: #50FA7B; color: #21222C; } .negative { background-color: #FF5555; color: #21222C; } .warning { background-color: #FFFD8C; color: #21222C; } tr:nth-child(odd) { background-color: #F8F8F2; } @media (prefers-color-scheme: dark) { body { background-color: #282A36; color: #F8F8F2; } a { color: #8BE9FD; } tr:nth-child(odd) { background-color: #424450; } } body { margin: 0; font-family: sans-serif; } .wrapper { width: auto; } @media screen and (min-width: 769px) { .wrapper { margin-left: 100px; margin-right: 100px; } } table { width: 100%; } .item { width: 100%; } .support_cell { display: flex; flex-direction: row; justify-content: space-between; } muon-v0.3.0/doc/website/muon_logo.svg0000644000175000017500000001553614674562002016604 0ustar buildbuild muon-v0.3.0/doc/website/signatures_to_html.py0000644000175000017500000002614314674562002020345 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only import sys import itertools from version_info import MuonVersion function_support_notes = { "subproject": """ muon does not perform subproject promotion """, } def parse(file): sigs = {} with open(file, "r") as f: argi = { "posargs:": 0, "varargs:": 1, "optargs:": 2, "kwargs:": 3, "returns:": 4, } fname = None args = [[], [], [], [], []] curargs = None extension = False for line in f.read().strip().split("\n"): if line[0] != " ": if fname: sigs[fname] = Sig(fname, *args, extension) fname = None args = [[], [], [], [], []] curargs = None extension = False l = line.split(":") if len(l) > 1 and l[0] == "extension": extension = True fname = l[-1] elif line[2] != " ": curargs = argi[line.strip()] else: v = line.strip() if curargs == 3: # kwargs v = v.split(": ") args[curargs].append(v) if fname: sigs[fname] = Sig(fname, *args, extension) return sigs class Sig: def empty(name): return Sig(name, [], [], [], [], [], False) def __init__(self, name, posargs, varargs, optargs, kwargs, returns, extension): self.name = name self.posargs = posargs self.varargs = varargs self.optargs = optargs self.kwargs = {self.esc(k): v for k, v in self.normalize_kw(kwargs)} if returns: self.returns = returns[0] else: self.returns = returns self.extension = extension def normalize_kw(self, kwargs): applies_to = [ "executable", "library", "static_library", "shared_library", "shared_module", "build_target", "both_libraries", ] if self.name not in applies_to: return kwargs prefixes = ["c_", "cpp_", "objc_"] delete = [] add = {} for kw, v in kwargs: for pre in prefixes: if kw.startswith(pre): delete.append(kw) add["_" + kw[len(pre) :]] = v break add = [[k, v] for k, v in add.items()] return [x for x in kwargs if x[0] not in delete] + add def esc(self, c): return str(c).replace("<", "<").replace(">", ">") def arg_count(self): return ( len(self.posargs) + len(self.varargs) + len(self.optargs) + len(self.kwargs) ) muon = parse(sys.argv[1]) meson = parse(sys.argv[2]) class Table: def __init__(self, thead): self.thead = thead self.rows = [] def __add__(self, other): self.rows.append(other) return self def html(self): return ( "" + self.thead.html() + "" + "".join([r.html() for r in self.rows]) + "
" ) class Row: def __init__(self, *cols): self.cols = cols def html(self): return ( "" + "".join(["" + str(c) + "" for c in self.cols]) + "" ) class FuncInfo: def __init__(self, name): self.name = name self.me = None self.mu = None def _parse_type(t, in_container=False): parsed = [] name = "" i = 0 while i < len(t): c = t[i] if c == "[": (n, sub) = _parse_type(t[i + 1 :], in_container=True) parsed.append( ( name, sub, ) ) name = "" i += n + 1 continue elif c == "]": if name: parsed.append(name) return (i + 1, parsed) elif c == " ": i += 1 continue elif c == "|": if name: parsed.append(name) name = "" else: name += c i += 1 if name: parsed.append(name) return (i, parsed) def parse_type(t): if t is None: return [] (_, parsed) = _parse_type(t) return parsed # Attempt to detect if a signature indicates listification and remove the # duplicate part def normalize_listify(tp): if not tp: return tp nl = [] l = set() for t in tp: if type(t) is tuple: if t[0] == "list": l = t[1] else: return tp else: nl.append(t) if set(nl) == set(l): return [("list", nl)] else: return tp def assemble_type(t): if type(t) is list: def sort_func(v): assert type(v) is not list if type(v) is tuple: return v[0] else: return v t.sort(key=sort_func) return " | ".join(assemble_type(x) for x in t) elif type(t) is tuple: return t[0] + "[" + assemble_type(t[1]) + "]" else: return t def neutral(text): return f'{text}' def positive(text): return f'{text}' def negative(text): return f'{text}' def warning(text): return f'{text}' def tdiff(a, b): s = [] for t in a: if type(t) is tuple: other = None for u in b: if type(u) is tuple and t[0] == u[0]: other = u[1] break if other: s.append(neutral(f"{t[0]}[{tdiff(t[1], other)}]")) else: s.append(positive(f"{t[0]}[{assemble_type(t[1])}]")) else: if t in b: s.append(neutral(t)) else: s.append(positive(t)) return " | ".join(s) def normalize_types(tp): conts = [] ntp = set() for t in tp: if type(t) is tuple: sub = normalize_types(t[1]) if not sub: ntp |= set([t[0]]) else: conts.append((t[0], sub)) else: ntp |= set( { "custom_idx": ["file"], "extracted_obj": ["file"], "void": [], "tgt": ["build_tgt", "custom_tgt", "both_libs"], "lib": ["build_tgt", "both_libs"], "exe": ["build_tgt"], }.get(t, [t]) ) def sort_func(v): assert type(v) is not list if type(v) is tuple: return v[0] else: return v return sorted(list(ntp) + conts, key=sort_func) def typecomp(mu, me): a = normalize_listify(normalize_types(parse_type(mu))) b = normalize_listify(normalize_types(parse_type(me))) return (tdiff(a, b), tdiff(b, a)) def footnote(footnotes, footnote): if footnote not in footnotes: footnotes.append(footnote) index = footnotes.index(footnote) + 1 return f"{index}" func_tbl = Table(Row("function", "status", "muon return", "meson return")) arg_tbls = [] footnotes = [] all_funcs = set(muon.keys()) | set(meson.keys()) methods = set([f for f in all_funcs if "." in f]) kernel = all_funcs - methods for f in sorted(kernel) + sorted(methods): if f in ["custom_idx.full_path", "custom_tgt.[index]"]: continue r = FuncInfo(f) if f in muon: flink = f'{f}' r.mu = muon[f] else: flink = f if f in meson: r.me = meson[f] support = positive("supported") notes = "" if f not in muon: r.mu = Sig.empty(f) support = negative("unsupported") elif f not in meson: r.me = Sig.empty(f) if muon[f].extension: support = "muon extension" else: support = warning("supported") notes = footnote(footnotes, "There is no type information for this function in the meson yaml documentation, therefore a comparison cannot be made.") if f in function_support_notes: support = warning("supported") notes += footnote(footnotes, function_support_notes[f]) if notes: support += '
' + notes + '
' support = f'
{support}
' func_tbl += Row(flink, support, *typecomp(r.mu.returns, r.me.returns)) t = Table(Row("kind", "keyword", "muon type", "meson type")) for k in ["posarg", "vararg", "optarg"]: for a, b in itertools.zip_longest( getattr(r.mu, k + "s"), getattr(r.me, k + "s") ): t += Row(k, "", *typecomp(a, b)) for k in sorted(set(r.me.kwargs.keys()) | set(r.mu.kwargs.keys())): t1 = None t2 = None if k in r.mu.kwargs: t1 = r.mu.kwargs[k] if k in r.me.kwargs: t2 = r.me.kwargs[k] t += Row("kwarg", k, *typecomp(t1, t2)) if f in muon: arg_tbls.append((r, t)) module_tbl = Table(Row("module", "status")) for m in ["fs", "keyval", "pkgconfig", "sourceset", "i18n"]: module_tbl += Row(m, positive("supported")) for m in ["python3", "python"]: module_tbl += Row(m, "partial") for m in [ "cmake", "dlang", "gnome", "hotdoc", "java", "modtest", "qt", "qt4", "qt5", "qt6", "unstable-cuda", "unstable-external_project", "unstable-icestorm", "unstable-rust", "unstable-simd", "unstable-wayland", "windows", ]: module_tbl += Row(m, negative("unsupported")) print( """ muon implementation status
""" ) print('
') print("

Version

") print(f"

muon version {MuonVersion.version}-{MuonVersion.vcs_tag}

") print(f"

meson compat version {MuonVersion.meson_compat}

") print("
") print('
') print(f"

Modules

") print(module_tbl.html()) print(f"

Functions and methods

") print(func_tbl.html()) print("
") for r, at in arg_tbls: print("
") print('
') print(f'

{r.name}

') if at.rows: print(at.html()) else: print("no arguments") print("
") print("
") print('
') print('

Footnotes

') print("
    ") for index in range(len(footnotes)): print(f'
  1. {footnotes[index]}
  2. ') print("
") print('
') print("
") muon-v0.3.0/doc/website/muon_ci.html0000644000175000017500000000763114674562002016401 0ustar buildbuild muon ci

muon ci

os arch status flags
Alpine x86_64
NetBSD amd64 🧪
Debian Stable amd64
Ubuntu amd64
Solaris 11 sun4u ... 🧪
Windows - msys2 x86_64
Windows - vs2019 amd64, amd64_x86
macOS 12 x86_64

flags

  • 🧪 - project tests are disabled
  • ⚠ - all tests are disabled
muon-v0.3.0/doc/website/index.html0000644000175000017500000001275414674562002016061 0ustar buildbuild muon

A meson-compatible build system.

muon-v0.3.0/doc/website/main.css0000644000175000017500000000204714674562002015514 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ body { max-width: 700px; margin: auto; padding: 10px; font-family: sans-serif; } @media (prefers-color-scheme: dark) { body { background-color: #282A36; color: #F8F8F2; } a { color: #8BE9FD; } small { color: #e8e8e2; } } nav div { padding: 10px; padding-left: 20px; display: block; } .logo { display: flex; flex-direction: row; align-items: center; font-size: 40px; } .logo small { font-size: 10px; } .logo_text { display: flex; flex-direction: column; padding: 10px; } .downloads_title { display: flex; flex-direction: row; align-items: center; } .loader { width: 20px; height: 20px; border: 5px solid #FFF; border-bottom-color: transparent; border-radius: 50%; display: inline-block; box-sizing: border-box; animation: rotation 1s linear infinite; } @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } muon-v0.3.0/doc/muon.1.scd0000644000175000017500000003024014674562002014220 0ustar buildbuildmuon(1) ; SPDX-FileCopyrightText: Stone Tickle ; SPDX-License-Identifier: GPL-3.0-only # NAME muon - a meson-compatible build system # SYNOPSIS *muon* [*-vh*] [*-C* ] [] *muon* *setup* [*-D*[subproject*:*]option*=*value...] build ++ *cd* build++ ++ *muon* *test* [options] ++ *muon* *install* [options] # DESCRIPTION *muon* interprets _source files_ written in the _meson dsl_ and produces _buildfiles_ for a backend. Currently the only supported backend is _ninja_. When building *meson* projects with *muon*, you typically first start by running the *setup* command in the project root. This will create _buildfiles_ for the backend in the _build dir_ you specify. You then invoke the backend, e.g. ``` ninja -C ``` If the project defines tests, you may run them with the *test* subcommand, and finally install the project with the *install* subcommand. # OPTIONS - *-v* - enable verbose output - *-C* - chdir to _path_ before executing a command - *-h* - print a help message # COMMANDS *muon* requires a command. All commands accept a *-h* option which prints a brief summary of their usage. ## analyze *muon* *analyze* Run a static analyzer on the current project. *OPTIONS*: - *-l* - optimize output for editor linter plugins. For example, a diagnostic is added for the subdir() callsite, so that the editor can highlight subdirs with errors in them. - *-q* - only report errors - *-O* - read project file with matching path from stdin. This is useful for editor linter plugins that run as you type on files that aren't saved to disk. - *-i* - analyze the single file _path_ in internal mode. This is useful for catching bugs in scripts that will be evaluated with *muon internal eval*. - *-t* - print a tree of all meson source files that are evaluated - *-d* - print the location of the definition of _var_ - *-W* [no-] - enable or disable a particular diagnostic, e.g. unused-variable. - *-W* list - list available diagnostics. - *-W* error - turn all warnings into errors. ## benchmark See documentation for the *test* subcommand. ## check *muon* *check* [*-p*|*-d*] [*-m*] Check if _filename_ parses. *OPTIONS*: - *-p* - print the parsed ast - *-d* - print the dissasembly - *-m* - set the compilation mode. `f` parses for formatting, `x` compiles for the extended language (e.g. user-defined functions) ## fmt *muon* *fmt* [*-i*] [*-q*] [*-c* ] [ [...]] Format a _source file_. *OPTIONS*: - *-q* - exit with 1 if files would be modified by muon fmt - *-i* - format files in-place - *-c* - read configuration from _muon\_fmt.ini_ - *-e* - try to read configuration from .editorconfig. Only indentation related settings are recognized. *CONFIGURATION OPTIONS* [[ *key* :[ *type* :[ *default* :[ *description* | max_line_len : int : 80 : The maximum length of lines before they are split | indent_style : space|tab : space : Wether to use spaces or tabs for indentation | indent_size : int : 4 : For indent_style = space, the number of spaces to indent by per level | insert_final_newline : bool : true : Whether or not to insert a trailing newline at the end of the file | end_of_line : lf|crlf|cr : : The type of line ending to use. If this value is not set then the line ending of the first line in the source file will be used. | space_array : bool : false : Whether to include spaces around array elements (ex. [1] vs [ 1 ]) | kwargs_force_multiline : bool : false : Make every dictionary literal and function call with kwargs expand into multiple lines. | wide_colon : bool : false : Whether to put a space before the colon operator (ex. `key : val`) | no_single_comma_function : bool : false : Don't add a trailing comma to multi-line function calls with only one argument. | sort_files : bool : true : Whether to sort the arguments of files() | group_arg_value : bool : true : Whether to group strings in array literals such that command line arguments that take a value remain on the same line as the value. The heuristic used is that if a string starts with `-` and the next string does not, put both strings on the same line | simplify_string_literals : bool : false : Whether to automatically convert multiline string literals without newlines, as well as f-strings without formatting into single-quoted string literals. | indent_before_comments : str : ' ' : The indent to put before trailing comments | sticky_parens : bool : false : Controls whether parenthesis used for grouping are stuck to what they enclose or are exploded out to be on their own lines. | continuation_indent : bool : false : Controls whether a multiline conditional in an if statement gets an extra indent. ## install *muon* *install* [*-n*] Installs the project. The _DESTDIR_ environment variable is respected and will prefix all installation directories if it is present. *OPTIONS*: - *-n* - dry run - *-d* - set destdir ## internal *muon* *internal* [] Internal contains several subcommands used by *muon* internally. These commands are subject to change at any time and should not be relied upon. *SUBCOMMANDS*: - *eval* - evaluate a _source file_ - *exe* - execute a command - *repl* - start a _meson dsl_ repl - *dump_funcs* - output all supported functions and arguments ## internal eval *muon* *internal* *eval* [*-e*] [*-s*] [] Evaluate a _source file_. The interpreter environment is substantially different from the typical environment during *setup*. Build related functions are unavailable, as well as many other functions including *subdir*. Additionally, the variable *argv* is defined as an array containing the commandline starting at . *OPTIONS*: - *-e* - lookup as an embedded script - *-s* - disable functions that are unsafe to be called at random, particularly `run_command()`. The motivation for this flag is so that automated fuzz testing can be used without accidentally executing something like `run_command('rm', '-rf', '/')`. ## internal exe *muon* *internal* *exe* [*-f* ] [*-c* ] [*-e* ] [*-a* ] [*-R* ] [-- []] Execute with arguments . *OPTIONS*: - *-f* - pass _input file_ as stdin to - *-c* - capture stdout of and write it to _output file_ - *-e* - read and set environment variables from _env.dat_ - *-a* - read and set command from _args.dat_ - *-R* - remove _file_ if it exists before executing the command ## internal repl *muon* *internal* *repl* Start a _meson dsl_ repl. The functions available are limited as with *internal eval*. ## internal dump_funcs *muon* *internal* *dump_funcs* Print all supported functions, methods, and module functions with their arguments, argument types, and return types to stdout. This subcommand is mainly useful for generating https://muon.build/status.html. ## meson \[*muon*\] *meson* ... A compatibility layer that attempts to translate all flags and operands from meson cli syntax to muon cli syntax. For example, the following two commands: ``` muon meson setup build --werror --prefix=/ muon meson test -C build --list ``` Would be translated into the following two muon versions respectively: ``` muon setup -Dwerror=true -Dprefix=/ build muon -C build test -l ``` This compatibility layer is also enabled when muon's executable is named _meson_. For a more detailed usage information you can use the following two commands: ``` muon meson -h muon meson -h ``` NOTE: This is a best-effort translation and does not guarantee or imply full cli compatibility. Many unimplemented flags are ignored and attempting to use an unsupported subcommands will result in an error. ## options *muon* *options* [*-a*] [*-m*] Lists available project options and defaults. This command may either be run from the project root or from a build directory. Running this command from a build directory highlights configured option values in the output, whereas running it from the project root causes the default value for each option to be highlighted. *OPTIONS*: - *-a* - Include builtin global and per-project options in the output. - *-m* - Only display option values that have been modified. ## samu *muon* *samu* [] Executes an embedded copy of *samu*(1). This command requires that muon was compiled with *samu* enabled. ## setup *muon* *setup* [*-D*[subproject*:*]option*=*value...] [*-c* ] [*-b*] Interpret all _source files_ and generate _buildfiles_ in _build dir_. *OPTIONS*: - *-D* [subproject*:*]option*=*value - Set build options. Options are either built in or project-defined. Subproject options can be specified by prepending the subproject's name and a colon to the *option*. This option may be specified multiple times. - *-c* - load compiler check cache dump from path. This is used internally when creating the regeneration command. - *-b* - Break on error. When this option is passed, muon will enter a debugging repl when a fatal error is encountered. From there you can inspect and modify state, and optionally continue setup. ## summary *muon* *summary* Print a previously configured project's summary. ## test *muon* *test* [*-d* ] [*-o* ] [*-e* ] [*-f*] \[*-j* ] [*-l*] [*-R*] [*-s* ] [*-S*] [*-v [*-v*]*] [ \[[...]] Execute tests defined in _source files_. The default is to execute all tests, but you can also specify which tests to execute on the command line. should consist of an optional project name, followed by a colon and then a test name. Either may be omitted. For example, "test name" and ":test name" will match all tests named 'test name' in any project.++ "proj:test name" will match all tests named 'test name' in the project 'proj'.++ "proj:" will match all tests in the project 'proj'. Additionally, the test name may be a glob expression. For example, "proj:long\*" will match all tests with names starting with 'long' in the project 'proj'. *OPTIONS*: - *-d* - Control test progress output. _display mode_ can be one of *auto*, *dots*, or *bar*. *dots* prints a '.' for success and 'E' for error, *bar* prints a progress bar with an error count. The default mode, *auto*, selects *bar* if the output device is a terminal or *dots* otherwise. - *-o* - Control test results output. *term* prints failures and output to the terminal, *html* generates a single-page html report, and *json* outputs test information to a json file. - *-e* - Use test setup _setup_. - *-f* - Fail fast. exit after first test failure is encountered. - *-j* - Set the number of jobs used when running tests. - *-l* - List tests that would be run with the current setup, suites, etc. The format of the output is : - . - *-R* - No rebuild. Disable automatic build system invocation prior to running tests. - *-s* - Only run tests in suite _suite_. This option may be specified multiple times. - *-S* - print a summary of test results, including the duration of each test - *-v* - Increase verbosity. When passed once, print test results as they are completed. When passed twice, the stdout/stderr of tests is not captured. ## version *muon* *version* Print out version information as well as enabled features. # EXTENSIONS *muon* provides some extra functions that may be called within _source files_. They are documented below. - *dbg()* - Begin an interactive debugger. - *p(value)* - Print any value's internal representation. For example, `p('hello')` prints `'hello'`. # SEE ALSO meson.build(5) meson-reference(3) meson(1) # AUTHORS Maintained by Stone Tickle , who is assisted by other open source contributors. For more information about muon development, see . muon-v0.3.0/meson.build0000644000175000017500000001037214674562002014011 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-FileCopyrightText: Simon Zeni # SPDX-License-Identifier: GPL-3.0-only project( 'muon', 'c', version: '0.3.0', license: 'GPL-3.0-only', meson_version: '>=1.3.0', default_options: { 'warning_level': '3', 'buildtype': 'debug', 'default_library': 'static', }, ) 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.5') configure_file( configuration: version_info, input: 'tools/ci/version.txt.in', output: 'version.txt', ) # 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_id() == '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 muon = executable( 'muon', src, dependencies: deps, include_directories: include_dir, link_args: link_args, c_args: c_args, cpp_args: c_args, install: true, ) python3 = find_program('python3', required: false) subdir('tests') subdir('doc') muon-v0.3.0/subprojects/0002755000175000017500000000000014674562002014211 5ustar buildbuildmuon-v0.3.0/subprojects/bestline/0002755000175000017500000000000014674562002016016 5ustar buildbuildmuon-v0.3.0/subprojects/bestline/meson.build0000644000175000017500000000030514674562002020154 0ustar buildbuildproject('bestline', 'c') meson.override_dependency( 'bestline', declare_dependency( include_directories: '.', link_with: static_library('bestline', 'bestline.c'), ), ) muon-v0.3.0/subprojects/bestline/bestline.c0000644000175000017500000041153414674562002017775 0ustar buildbuild/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=4 sts=4 sw=4 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ │ │ Bestline ── Library for interactive pseudoteletypewriter command │ │ sessions using ANSI Standard X3.64 control sequences │ │ │ │ OVERVIEW │ │ │ │ Bestline is a fork of linenoise (a popular readline alternative) │ │ that fixes its bugs and adds the missing features while reducing │ │ binary footprint (surprisingly) by removing bloated dependencies │ │ which means you can finally have a permissively-licensed command │ │ prompt w/ a 30kb footprint that's nearly as good as gnu readline │ │ │ │ EXAMPLE │ │ │ │ main() { │ │ char *line; │ │ while ((line = bestlineWithHistory("IN> ", "foo"))) { │ │ fputs("OUT> ", stdout); │ │ fputs(line, stdout); │ │ fputs("\n", stdout); │ │ free(line); │ │ } │ │ } │ │ │ │ CHANGES │ │ │ │ - Remove bell │ │ - Add kill ring │ │ - Fix flickering │ │ - Add UTF-8 editing │ │ - Add CTRL-R search │ │ - Support unlimited lines │ │ - Add parentheses awareness │ │ - React to terminal resizing │ │ - Don't generate .data section │ │ - Support terminal flow control │ │ - Make history loading 10x faster │ │ - Make multiline mode the only mode │ │ - Accommodate O_NONBLOCK file descriptors │ │ - Restore raw mode on process foregrounding │ │ - Make source code compatible with C++ compilers │ │ - Fix corruption issues by using generalized parsing │ │ - Implement nearly all GNU readline editing shortcuts │ │ - Remove heavyweight dependencies like printf/sprintf │ │ - Remove ISIG→^C→EAGAIN hack and use ephemeral handlers │ │ - Support running on Windows in MinTTY or CMD.EXE on Win10+ │ │ - Support diacratics, русский, Ελληνικά, 中国人, 日本語, 한국인 │ │ │ │ SHORTCUTS │ │ │ │ CTRL-E END │ │ CTRL-A START │ │ CTRL-B BACK │ │ CTRL-F FORWARD │ │ CTRL-L CLEAR │ │ CTRL-H BACKSPACE │ │ CTRL-D DELETE │ │ CTRL-Y YANK │ │ CTRL-D EOF (IF EMPTY) │ │ CTRL-N NEXT HISTORY │ │ CTRL-P PREVIOUS HISTORY │ │ CTRL-R SEARCH HISTORY │ │ CTRL-G CANCEL SEARCH │ │ ALT-< BEGINNING OF HISTORY │ │ ALT-> END OF HISTORY │ │ ALT-F FORWARD WORD │ │ ALT-B BACKWARD WORD │ │ CTRL-ALT-F FORWARD EXPR │ │ CTRL-ALT-B BACKWARD EXPR │ │ ALT-RIGHT FORWARD EXPR │ │ ALT-LEFT BACKWARD EXPR │ │ ALT-SHIFT-B BARF EXPR │ │ ALT-SHIFT-S SLURP EXPR │ │ ALT-SHIFT-R RAISE EXPR │ │ CTRL-K KILL LINE FORWARDS │ │ CTRL-U KILL LINE BACKWARDS │ │ ALT-H KILL WORD BACKWARDS │ │ CTRL-W KILL WORD BACKWARDS │ │ CTRL-ALT-H KILL WORD BACKWARDS │ │ ALT-D KILL WORD FORWARDS │ │ ALT-Y ROTATE KILL RING AND YANK AGAIN │ │ ALT-\ SQUEEZE ADJACENT WHITESPACE │ │ CTRL-T TRANSPOSE │ │ ALT-T TRANSPOSE WORD │ │ ALT-U UPPERCASE WORD │ │ ALT-L LOWERCASE WORD │ │ ALT-C CAPITALIZE WORD │ │ CTRL-Z SUSPEND PROCESS │ │ CTRL-\ QUIT PROCESS │ │ CTRL-S PAUSE OUTPUT │ │ CTRL-Q UNPAUSE OUTPUT (IF PAUSED) │ │ CTRL-Q ESCAPED INSERT │ │ CTRL-SPACE SET MARK │ │ CTRL-X CTRL-X GOTO MARK │ │ PROTIP REMAP CAPS LOCK TO CTRL │ │ │ ╞══════════════════════════════════════════════════════════════════════════════╡ │ │ │ Copyright 2018-2021 Justine Tunney │ │ Copyright 2010-2016 Salvatore Sanfilippo │ │ Copyright 2010-2013 Pieter Noordhuis │ │ │ │ All rights reserved. │ │ │ │ Redistribution and use in source and binary forms, with or without │ │ modification, are permitted provided that the following conditions are │ │ met: │ │ │ │ * Redistributions of source code must retain the above copyright │ │ notice, this list of conditions and the following disclaimer. │ │ │ │ * Redistributions in binary form must reproduce the above copyright │ │ notice, this list of conditions and the following disclaimer in the │ │ documentation and/or other materials provided with the distribution. │ │ │ │ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS │ │ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT │ │ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR │ │ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT │ │ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, │ │ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT │ │ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, │ │ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY │ │ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT │ │ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE │ │ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. │ │ │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "bestline.h" #ifndef __COSMOPOLITAN__ #define _POSIX_C_SOURCE 1 /* so GCC builds in ANSI mode */ #define _XOPEN_SOURCE 700 /* so GCC builds in ANSI mode */ #define _DARWIN_C_SOURCE 1 /* so SIGWINCH / IUTF8 on XNU */ #ifdef __sun #define __EXTENSIONS__ /* for struct winsize on Solaris */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef SIGWINCH #define SIGWINCH 28 /* GNU/Systemd + XNU + FreeBSD + NetBSD + OpenBSD */ #endif #ifndef IUTF8 #define IUTF8 0 #endif #endif __asm__(".ident\t\"\\n\\n\ Bestline (BSD-2)\\n\ Copyright 2018-2020 Justine Tunney \\n\ Copyright 2010-2016 Salvatore Sanfilippo \\n\ Copyright 2010-2013 Pieter Noordhuis \""); #ifndef BESTLINE_MAX_RING #define BESTLINE_MAX_RING 8 #endif #ifndef BESTLINE_MAX_HISTORY #define BESTLINE_MAX_HISTORY 1024 #endif #define BESTLINE_HISTORY_FIRST +BESTLINE_MAX_HISTORY #define BESTLINE_HISTORY_PREV +1 #define BESTLINE_HISTORY_NEXT -1 #define BESTLINE_HISTORY_LAST -BESTLINE_MAX_HISTORY #define Ctrl(C) ((C) ^ 0100) #define Min(X, Y) ((Y) > (X) ? (X) : (Y)) #define Max(X, Y) ((Y) < (X) ? (X) : (Y)) #define Case(X, Y) case X: Y; break #define Read16le(X) \ ((255 & (X)[0]) << 000 | \ (255 & (X)[1]) << 010) #define Read32le(X) \ ((unsigned)(255 & (X)[0]) << 000 | \ (unsigned)(255 & (X)[1]) << 010 | \ (unsigned)(255 & (X)[2]) << 020 | \ (unsigned)(255 & (X)[3]) << 030) struct abuf { char *b; unsigned len; unsigned cap; }; struct rune { unsigned c; unsigned n; }; struct bestlineRing { unsigned i; char *p[BESTLINE_MAX_RING]; }; /* The bestlineState structure represents the state during line editing. * We pass this state to functions implementing specific editing * functionalities. */ struct bestlineState { int ifd; /* terminal stdin file descriptor */ int ofd; /* terminal stdout file descriptor */ struct winsize ws; /* rows and columns in terminal */ char *buf; /* edited line buffer */ const char *prompt; /* prompt to display */ int hindex; /* history index */ int rows; /* rows being used */ int oldpos; /* previous refresh cursor position */ unsigned buflen; /* edited line buffer size */ unsigned pos; /* current buffer index */ unsigned len; /* current edited line length */ unsigned mark; /* saved cursor position */ unsigned yi, yj; /* boundaries of last yank */ char seq[2][16]; /* keystroke history for yanking code */ char final; /* set to true on last update */ char dirty; /* if an update was squashed */ }; static const char *const kUnsupported[] = {"dumb","cons25","emacs"}; static int gotint; static int gotcont; static int gotwinch; static signed char rawmode; static char maskmode; static char ispaused; static char iscapital; static unsigned historylen; static struct bestlineRing ring; static struct sigaction orig_cont; static struct sigaction orig_winch; static struct termios orig_termios; static char *history[BESTLINE_MAX_HISTORY]; static bestlineXlatCallback *xlatCallback; static bestlineHintsCallback *hintsCallback; static bestlineFreeHintsCallback *freeHintsCallback; static bestlineCompletionCallback *completionCallback; static void bestlineAtExit(void); static void bestlineRefreshLine(struct bestlineState *); static void bestlineOnInt(int sig) { gotint = sig; } static void bestlineOnCont(int sig) { gotcont = sig; } static void bestlineOnWinch(int sig) { gotwinch = sig; } static char IsControl(unsigned c) { return c <= 0x1F || (0x7F <= c && c <= 0x9F); } static int GetMonospaceCharacterWidth(unsigned c) { return !IsControl(c) + (c >= 0x1100 && (c <= 0x115f || c == 0x2329 || c == 0x232a || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || (c >= 0xac00 && c <= 0xd7a3) || (c >= 0xf900 && c <= 0xfaff) || (c >= 0xfe10 && c <= 0xfe19) || (c >= 0xfe30 && c <= 0xfe6f) || (c >= 0xff00 && c <= 0xff60) || (c >= 0xffe0 && c <= 0xffe6) || (c >= 0x20000 && c <= 0x2fffd) || (c >= 0x30000 && c <= 0x3fffd))); } /** * Returns nonzero if 𝑐 isn't alphanumeric. * * Line reading interfaces generally define this operation as UNICODE * characters that aren't in the letter category (Lu, Ll, Lt, Lm, Lo) * and aren't in the number categorie (Nd, Nl, No). We also add a few * other things like blocks and emoji (So). */ char bestlineIsSeparator(unsigned c) { int m, l, r, n; if (c < 0200) { return !(('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')); } if (c <= 0xffff) { static const unsigned short kGlyphs[][2] = { {0x00aa, 0x00aa}, /* 1x English */ {0x00b2, 0x00b3}, /* 2x English Arabic */ {0x00b5, 0x00b5}, /* 1x Greek */ {0x00b9, 0x00ba}, /* 2x English Arabic */ {0x00bc, 0x00be}, /* 3x Vulgar English Arabic */ {0x00c0, 0x00d6}, /* 23x Watin */ {0x00d8, 0x00f6}, /* 31x Watin */ {0x0100, 0x02c1}, /* 450x Watin-AB,IPA,Spacemod */ {0x02c6, 0x02d1}, /* 12x Spacemod */ {0x02e0, 0x02e4}, /* 5x Spacemod */ {0x02ec, 0x02ec}, /* 1x Spacemod */ {0x02ee, 0x02ee}, /* 1x Spacemod */ {0x0370, 0x0374}, /* 5x Greek */ {0x0376, 0x0377}, /* 2x Greek */ {0x037a, 0x037d}, /* 4x Greek */ {0x037f, 0x037f}, /* 1x Greek */ {0x0386, 0x0386}, /* 1x Greek */ {0x0388, 0x038a}, /* 3x Greek */ {0x038c, 0x038c}, /* 1x Greek */ {0x038e, 0x03a1}, /* 20x Greek */ {0x03a3, 0x03f5}, /* 83x Greek */ {0x03f7, 0x0481}, /* 139x Greek */ {0x048a, 0x052f}, /* 166x Cyrillic */ {0x0531, 0x0556}, /* 38x Armenian */ {0x0560, 0x0588}, /* 41x Armenian */ {0x05d0, 0x05ea}, /* 27x Hebrew */ {0x0620, 0x064a}, /* 43x Arabic */ {0x0660, 0x0669}, /* 10x Arabic */ {0x0671, 0x06d3}, /* 99x Arabic */ {0x06ee, 0x06fc}, /* 15x Arabic */ {0x0712, 0x072f}, /* 30x Syriac */ {0x074d, 0x07a5}, /* 89x Syriac,Arabic2,Thaana */ {0x07c0, 0x07ea}, /* 43x NKo */ {0x0800, 0x0815}, /* 22x Samaritan */ {0x0840, 0x0858}, /* 25x Mandaic */ {0x0904, 0x0939}, /* 54x Devanagari */ {0x0993, 0x09a8}, /* 22x Bengali */ {0x09e6, 0x09f1}, /* 12x Bengali */ {0x0a13, 0x0a28}, /* 22x Gurmukhi */ {0x0a66, 0x0a6f}, /* 10x Gurmukhi */ {0x0a93, 0x0aa8}, /* 22x Gujarati */ {0x0b13, 0x0b28}, /* 22x Oriya */ {0x0c92, 0x0ca8}, /* 23x Kannada */ {0x0caa, 0x0cb3}, /* 10x Kannada */ {0x0ce6, 0x0cef}, /* 10x Kannada */ {0x0d12, 0x0d3a}, /* 41x Malayalam */ {0x0d85, 0x0d96}, /* 18x Sinhala */ {0x0d9a, 0x0db1}, /* 24x Sinhala */ {0x0de6, 0x0def}, /* 10x Sinhala */ {0x0e01, 0x0e30}, /* 48x Thai */ {0x0e8c, 0x0ea3}, /* 24x Lao */ {0x0f20, 0x0f33}, /* 20x Tibetan */ {0x0f49, 0x0f6c}, /* 36x Tibetan */ {0x109e, 0x10c5}, /* 40x Myanmar,Georgian */ {0x10d0, 0x10fa}, /* 43x Georgian */ {0x10fc, 0x1248}, /* 333x Georgian,Hangul,Ethiopic */ {0x13a0, 0x13f5}, /* 86x Cherokee */ {0x1401, 0x166d}, /* 621x Aboriginal */ {0x16a0, 0x16ea}, /* 75x Runic */ {0x1700, 0x170c}, /* 13x Tagalog */ {0x1780, 0x17b3}, /* 52x Khmer */ {0x1820, 0x1878}, /* 89x Mongolian */ {0x1a00, 0x1a16}, /* 23x Buginese */ {0x1a20, 0x1a54}, /* 53x Tai Tham */ {0x1a80, 0x1a89}, /* 10x Tai Tham */ {0x1a90, 0x1a99}, /* 10x Tai Tham */ {0x1b05, 0x1b33}, /* 47x Balinese */ {0x1b50, 0x1b59}, /* 10x Balinese */ {0x1b83, 0x1ba0}, /* 30x Sundanese */ {0x1bae, 0x1be5}, /* 56x Sundanese */ {0x1c90, 0x1cba}, /* 43x Georgian2 */ {0x1cbd, 0x1cbf}, /* 3x Georgian2 */ {0x1e00, 0x1f15}, /* 278x Watin-C,Greek2 */ {0x2070, 0x2071}, /* 2x Supersub */ {0x2074, 0x2079}, /* 6x Supersub */ {0x207f, 0x2089}, /* 11x Supersub */ {0x2090, 0x209c}, /* 13x Supersub */ {0x2100, 0x2117}, /* 24x Letterlike */ {0x2119, 0x213f}, /* 39x Letterlike */ {0x2145, 0x214a}, /* 6x Letterlike */ {0x214c, 0x218b}, /* 64x Letterlike,Numbery */ {0x21af, 0x21cd}, /* 31x Arrows */ {0x21d5, 0x21f3}, /* 31x Arrows */ {0x230c, 0x231f}, /* 20x Technical */ {0x232b, 0x237b}, /* 81x Technical */ {0x237d, 0x239a}, /* 30x Technical */ {0x23b4, 0x23db}, /* 40x Technical */ {0x23e2, 0x2426}, /* 69x Technical,ControlPictures */ {0x2460, 0x25b6}, /* 343x Enclosed,Boxes,Blocks,Shapes */ {0x25c2, 0x25f7}, /* 54x Shapes */ {0x2600, 0x266e}, /* 111x Symbols */ {0x2670, 0x2767}, /* 248x Symbols,Dingbats */ {0x2776, 0x27bf}, /* 74x Dingbats */ {0x2800, 0x28ff}, /* 256x Braille */ {0x2c00, 0x2c2e}, /* 47x Glagolitic */ {0x2c30, 0x2c5e}, /* 47x Glagolitic */ {0x2c60, 0x2ce4}, /* 133x Watin-D */ {0x2d00, 0x2d25}, /* 38x Georgian2 */ {0x2d30, 0x2d67}, /* 56x Tifinagh */ {0x2d80, 0x2d96}, /* 23x Ethiopic2 */ {0x2e2f, 0x2e2f}, /* 1x Punctuation2 */ {0x3005, 0x3007}, /* 3x CJK Symbols & Punctuation */ {0x3021, 0x3029}, /* 9x CJK Symbols & Punctuation */ {0x3031, 0x3035}, /* 5x CJK Symbols & Punctuation */ {0x3038, 0x303c}, /* 5x CJK Symbols & Punctuation */ {0x3041, 0x3096}, /* 86x Hiragana */ {0x30a1, 0x30fa}, /* 90x Katakana */ {0x3105, 0x312f}, /* 43x Bopomofo */ {0x3131, 0x318e}, /* 94x Hangul Compatibility Jamo */ {0x31a0, 0x31ba}, /* 27x Bopomofo Extended */ {0x31f0, 0x31ff}, /* 16x Katakana Phonetic Extensions */ {0x3220, 0x3229}, /* 10x Enclosed CJK Letters & Months */ {0x3248, 0x324f}, /* 8x Enclosed CJK Letters & Months */ {0x3251, 0x325f}, /* 15x Enclosed CJK Letters & Months */ {0x3280, 0x3289}, /* 10x Enclosed CJK Letters & Months */ {0x32b1, 0x32bf}, /* 15x Enclosed CJK Letters & Months */ {0x3400, 0x4db5}, /* 6582x CJK Unified Ideographs Extension A */ {0x4dc0, 0x9fef}, /* 21040x Yijing Hexagram, CJK Unified Ideographs */ {0xa000, 0xa48c}, /* 1165x Yi Syllables */ {0xa4d0, 0xa4fd}, /* 46x Lisu */ {0xa500, 0xa60c}, /* 269x Vai */ {0xa610, 0xa62b}, /* 28x Vai */ {0xa6a0, 0xa6ef}, /* 80x Bamum */ {0xa80c, 0xa822}, /* 23x Syloti Nagri */ {0xa840, 0xa873}, /* 52x Phags-pa */ {0xa882, 0xa8b3}, /* 50x Saurashtra */ {0xa8d0, 0xa8d9}, /* 10x Saurashtra */ {0xa900, 0xa925}, /* 38x Kayah Li */ {0xa930, 0xa946}, /* 23x Rejang */ {0xa960, 0xa97c}, /* 29x Hangul Jamo Extended-A */ {0xa984, 0xa9b2}, /* 47x Javanese */ {0xa9cf, 0xa9d9}, /* 11x Javanese */ {0xaa00, 0xaa28}, /* 41x Cham */ {0xaa50, 0xaa59}, /* 10x Cham */ {0xabf0, 0xabf9}, /* 10x Meetei Mayek */ {0xac00, 0xd7a3}, /* 11172x Hangul Syllables */ {0xf900, 0xfa6d}, /* 366x CJK Compatibility Ideographs */ {0xfa70, 0xfad9}, /* 106x CJK Compatibility Ideographs */ {0xfb1f, 0xfb28}, /* 10x Alphabetic Presentation Forms */ {0xfb2a, 0xfb36}, /* 13x Alphabetic Presentation Forms */ {0xfb46, 0xfbb1}, /* 108x Alphabetic Presentation Forms */ {0xfbd3, 0xfd3d}, /* 363x Arabic Presentation Forms-A */ {0xfe76, 0xfefc}, /* 135x Arabic Presentation Forms-B */ {0xff10, 0xff19}, /* 10x Dubs */ {0xff21, 0xff3a}, /* 26x Dubs */ {0xff41, 0xff5a}, /* 26x Dubs */ {0xff66, 0xffbe}, /* 89x Dubs */ {0xffc2, 0xffc7}, /* 6x Dubs */ {0xffca, 0xffcf}, /* 6x Dubs */ {0xffd2, 0xffd7}, /* 6x Dubs */ {0xffda, 0xffdc}, /* 3x Dubs */ }; l = 0; r = n = sizeof(kGlyphs) / sizeof(kGlyphs[0]); while (l < r) { m = (l + r) >> 1; if (kGlyphs[m][1] < c) { l = m + 1; } else { r = m; } } return !(l < n && kGlyphs[l][0] <= c && c <= kGlyphs[l][1]); } else { static const unsigned kAstralGlyphs[][2] = { {0x10107, 0x10133}, /* 45x Aegean */ {0x10140, 0x10178}, /* 57x Ancient Greek Numbers */ {0x1018a, 0x1018b}, /* 2x Ancient Greek Numbers */ {0x10280, 0x1029c}, /* 29x Lycian */ {0x102a0, 0x102d0}, /* 49x Carian */ {0x102e1, 0x102fb}, /* 27x Coptic Epact Numbers */ {0x10300, 0x10323}, /* 36x Old Italic */ {0x1032d, 0x1034a}, /* 30x Old Italic, Gothic */ {0x10350, 0x10375}, /* 38x Old Permic */ {0x10380, 0x1039d}, /* 30x Ugaritic */ {0x103a0, 0x103c3}, /* 36x Old Persian */ {0x103c8, 0x103cf}, /* 8x Old Persian */ {0x103d1, 0x103d5}, /* 5x Old Persian */ {0x10400, 0x1049d}, /* 158x Deseret, Shavian, Osmanya */ {0x104b0, 0x104d3}, /* 36x Osage */ {0x104d8, 0x104fb}, /* 36x Osage */ {0x10500, 0x10527}, /* 40x Elbasan */ {0x10530, 0x10563}, /* 52x Caucasian Albanian */ {0x10600, 0x10736}, /* 311x Linear A */ {0x10800, 0x10805}, /* 6x Cypriot Syllabary */ {0x1080a, 0x10835}, /* 44x Cypriot Syllabary */ {0x10837, 0x10838}, /* 2x Cypriot Syllabary */ {0x1083f, 0x1089e}, /* 86x Cypriot,ImperialAramaic,Palmyrene,Nabataean */ {0x108e0, 0x108f2}, /* 19x Hatran */ {0x108f4, 0x108f5}, /* 2x Hatran */ {0x108fb, 0x1091b}, /* 33x Hatran */ {0x10920, 0x10939}, /* 26x Lydian */ {0x10980, 0x109b7}, /* 56x Meroitic Hieroglyphs */ {0x109bc, 0x109cf}, /* 20x Meroitic Cursive */ {0x109d2, 0x10a00}, /* 47x Meroitic Cursive */ {0x10a10, 0x10a13}, /* 4x Kharoshthi */ {0x10a15, 0x10a17}, /* 3x Kharoshthi */ {0x10a19, 0x10a35}, /* 29x Kharoshthi */ {0x10a40, 0x10a48}, /* 9x Kharoshthi */ {0x10a60, 0x10a7e}, /* 31x Old South Arabian */ {0x10a80, 0x10a9f}, /* 32x Old North Arabian */ {0x10ac0, 0x10ac7}, /* 8x Manichaean */ {0x10ac9, 0x10ae4}, /* 28x Manichaean */ {0x10aeb, 0x10aef}, /* 5x Manichaean */ {0x10b00, 0x10b35}, /* 54x Avestan */ {0x10b40, 0x10b55}, /* 22x Inscriptional Parthian */ {0x10b58, 0x10b72}, /* 27x Inscriptional Parthian and Pahlavi */ {0x10b78, 0x10b91}, /* 26x Inscriptional Pahlavi, Psalter Pahlavi */ {0x10c00, 0x10c48}, /* 73x Old Turkic */ {0x10c80, 0x10cb2}, /* 51x Old Hungarian */ {0x10cc0, 0x10cf2}, /* 51x Old Hungarian */ {0x10cfa, 0x10d23}, /* 42x Old Hungarian, Hanifi Rohingya */ {0x10d30, 0x10d39}, /* 10x Hanifi Rohingya */ {0x10e60, 0x10e7e}, /* 31x Rumi Numeral Symbols */ {0x10f00, 0x10f27}, /* 40x Old Sogdian */ {0x10f30, 0x10f45}, /* 22x Sogdian */ {0x10f51, 0x10f54}, /* 4x Sogdian */ {0x10fe0, 0x10ff6}, /* 23x Elymaic */ {0x11003, 0x11037}, /* 53x Brahmi */ {0x11052, 0x1106f}, /* 30x Brahmi */ {0x11083, 0x110af}, /* 45x Kaithi */ {0x110d0, 0x110e8}, /* 25x Sora Sompeng */ {0x110f0, 0x110f9}, /* 10x Sora Sompeng */ {0x11103, 0x11126}, /* 36x Chakma */ {0x11136, 0x1113f}, /* 10x Chakma */ {0x11144, 0x11144}, /* 1x Chakma */ {0x11150, 0x11172}, /* 35x Mahajani */ {0x11176, 0x11176}, /* 1x Mahajani */ {0x11183, 0x111b2}, /* 48x Sharada */ {0x111c1, 0x111c4}, /* 4x Sharada */ {0x111d0, 0x111da}, /* 11x Sharada */ {0x111dc, 0x111dc}, /* 1x Sharada */ {0x111e1, 0x111f4}, /* 20x Sinhala Archaic Numbers */ {0x11200, 0x11211}, /* 18x Khojki */ {0x11213, 0x1122b}, /* 25x Khojki */ {0x11280, 0x11286}, /* 7x Multani */ {0x11288, 0x11288}, /* 1x Multani */ {0x1128a, 0x1128d}, /* 4x Multani */ {0x1128f, 0x1129d}, /* 15x Multani */ {0x1129f, 0x112a8}, /* 10x Multani */ {0x112b0, 0x112de}, /* 47x Khudawadi */ {0x112f0, 0x112f9}, /* 10x Khudawadi */ {0x11305, 0x1130c}, /* 8x Grantha */ {0x1130f, 0x11310}, /* 2x Grantha */ {0x11313, 0x11328}, /* 22x Grantha */ {0x1132a, 0x11330}, /* 7x Grantha */ {0x11332, 0x11333}, /* 2x Grantha */ {0x11335, 0x11339}, /* 5x Grantha */ {0x1133d, 0x1133d}, /* 1x Grantha */ {0x11350, 0x11350}, /* 1x Grantha */ {0x1135d, 0x11361}, /* 5x Grantha */ {0x11400, 0x11434}, /* 53x Newa */ {0x11447, 0x1144a}, /* 4x Newa */ {0x11450, 0x11459}, /* 10x Newa */ {0x1145f, 0x1145f}, /* 1x Newa */ {0x11480, 0x114af}, /* 48x Tirhuta */ {0x114c4, 0x114c5}, /* 2x Tirhuta */ {0x114c7, 0x114c7}, /* 1x Tirhuta */ {0x114d0, 0x114d9}, /* 10x Tirhuta */ {0x11580, 0x115ae}, /* 47x Siddham */ {0x115d8, 0x115db}, /* 4x Siddham */ {0x11600, 0x1162f}, /* 48x Modi */ {0x11644, 0x11644}, /* 1x Modi */ {0x11650, 0x11659}, /* 10x Modi */ {0x11680, 0x116aa}, /* 43x Takri */ {0x116b8, 0x116b8}, /* 1x Takri */ {0x116c0, 0x116c9}, /* 10x Takri */ {0x11700, 0x1171a}, /* 27x Ahom */ {0x11730, 0x1173b}, /* 12x Ahom */ {0x11800, 0x1182b}, /* 44x Dogra */ {0x118a0, 0x118f2}, /* 83x Warang Citi */ {0x118ff, 0x118ff}, /* 1x Warang Citi */ {0x119a0, 0x119a7}, /* 8x Nandinagari */ {0x119aa, 0x119d0}, /* 39x Nandinagari */ {0x119e1, 0x119e1}, /* 1x Nandinagari */ {0x119e3, 0x119e3}, /* 1x Nandinagari */ {0x11a00, 0x11a00}, /* 1x Zanabazar Square */ {0x11a0b, 0x11a32}, /* 40x Zanabazar Square */ {0x11a3a, 0x11a3a}, /* 1x Zanabazar Square */ {0x11a50, 0x11a50}, /* 1x Soyombo */ {0x11a5c, 0x11a89}, /* 46x Soyombo */ {0x11a9d, 0x11a9d}, /* 1x Soyombo */ {0x11ac0, 0x11af8}, /* 57x Pau Cin Hau */ {0x11c00, 0x11c08}, /* 9x Bhaiksuki */ {0x11c0a, 0x11c2e}, /* 37x Bhaiksuki */ {0x11c40, 0x11c40}, /* 1x Bhaiksuki */ {0x11c50, 0x11c6c}, /* 29x Bhaiksuki */ {0x11c72, 0x11c8f}, /* 30x Marchen */ {0x11d00, 0x11d06}, /* 7x Masaram Gondi */ {0x11d08, 0x11d09}, /* 2x Masaram Gondi */ {0x11d0b, 0x11d30}, /* 38x Masaram Gondi */ {0x11d46, 0x11d46}, /* 1x Masaram Gondi */ {0x11d50, 0x11d59}, /* 10x Masaram Gondi */ {0x11d60, 0x11d65}, /* 6x Gunjala Gondi */ {0x11d67, 0x11d68}, /* 2x Gunjala Gondi */ {0x11d6a, 0x11d89}, /* 32x Gunjala Gondi */ {0x11d98, 0x11d98}, /* 1x Gunjala Gondi */ {0x11da0, 0x11da9}, /* 10x Gunjala Gondi */ {0x11ee0, 0x11ef2}, /* 19x Makasar */ {0x11fc0, 0x11fd4}, /* 21x Tamil Supplement */ {0x12000, 0x12399}, /* 922x Cuneiform */ {0x12400, 0x1246e}, /* 111x Cuneiform Numbers & Punctuation */ {0x12480, 0x12543}, /* 196x Early Dynastic Cuneiform */ {0x13000, 0x1342e}, /* 1071x Egyptian Hieroglyphs */ {0x14400, 0x14646}, /* 583x Anatolian Hieroglyphs */ {0x16800, 0x16a38}, /* 569x Bamum Supplement */ {0x16a40, 0x16a5e}, /* 31x Mro */ {0x16a60, 0x16a69}, /* 10x Mro */ {0x16ad0, 0x16aed}, /* 30x Bassa Vah */ {0x16b00, 0x16b2f}, /* 48x Pahawh Hmong */ {0x16b40, 0x16b43}, /* 4x Pahawh Hmong */ {0x16b50, 0x16b59}, /* 10x Pahawh Hmong */ {0x16b5b, 0x16b61}, /* 7x Pahawh Hmong */ {0x16b63, 0x16b77}, /* 21x Pahawh Hmong */ {0x16b7d, 0x16b8f}, /* 19x Pahawh Hmong */ {0x16e40, 0x16e96}, /* 87x Medefaidrin */ {0x16f00, 0x16f4a}, /* 75x Miao */ {0x16f50, 0x16f50}, /* 1x Miao */ {0x16f93, 0x16f9f}, /* 13x Miao */ {0x16fe0, 0x16fe1}, /* 2x Ideographic Symbols & Punctuation */ {0x16fe3, 0x16fe3}, /* 1x Ideographic Symbols & Punctuation */ {0x17000, 0x187f7}, /* 6136x Tangut */ {0x18800, 0x18af2}, /* 755x Tangut Components */ {0x1b000, 0x1b11e}, /* 287x Kana Supplement */ {0x1b150, 0x1b152}, /* 3x Small Kana Extension */ {0x1b164, 0x1b167}, /* 4x Small Kana Extension */ {0x1b170, 0x1b2fb}, /* 396x Nushu */ {0x1bc00, 0x1bc6a}, /* 107x Duployan */ {0x1bc70, 0x1bc7c}, /* 13x Duployan */ {0x1bc80, 0x1bc88}, /* 9x Duployan */ {0x1bc90, 0x1bc99}, /* 10x Duployan */ {0x1d2e0, 0x1d2f3}, /* 20x Mayan Numerals */ {0x1d360, 0x1d378}, /* 25x Counting Rod Numerals */ {0x1d400, 0x1d454}, /* 85x 𝐀..𝑔 Math */ {0x1d456, 0x1d49c}, /* 71x 𝑖..𝒜 Math */ {0x1d49e, 0x1d49f}, /* 2x 𝒞..𝒟 Math */ {0x1d4a2, 0x1d4a2}, /* 1x 𝒢..𝒢 Math */ {0x1d4a5, 0x1d4a6}, /* 2x 𝒥..𝒦 Math */ {0x1d4a9, 0x1d4ac}, /* 4x 𝒩..𝒬 Math */ {0x1d4ae, 0x1d4b9}, /* 12x 𝒮..𝒹 Math */ {0x1d4bb, 0x1d4bb}, /* 1x 𝒻..𝒻 Math */ {0x1d4bd, 0x1d4c3}, /* 7x 𝒽..𝓃 Math */ {0x1d4c5, 0x1d505}, /* 65x 𝓅..𝔅 Math */ {0x1d507, 0x1d50a}, /* 4x 𝔇..𝔊 Math */ {0x1d50d, 0x1d514}, /* 8x 𝔍..𝔔 Math */ {0x1d516, 0x1d51c}, /* 7x 𝔖..𝔜 Math */ {0x1d51e, 0x1d539}, /* 28x 𝔞..𝔹 Math */ {0x1d53b, 0x1d53e}, /* 4x 𝔻..𝔾 Math */ {0x1d540, 0x1d544}, /* 5x 𝕀..𝕄 Math */ {0x1d546, 0x1d546}, /* 1x 𝕆..𝕆 Math */ {0x1d54a, 0x1d550}, /* 7x 𝕊..𝕐 Math */ {0x1d552, 0x1d6a5}, /* 340x 𝕒..𝚥 Math */ {0x1d6a8, 0x1d6c0}, /* 25x 𝚨..𝛀 Math */ {0x1d6c2, 0x1d6da}, /* 25x 𝛂..𝛚 Math */ {0x1d6dc, 0x1d6fa}, /* 31x 𝛜..𝛺 Math */ {0x1d6fc, 0x1d714}, /* 25x 𝛼..𝜔 Math */ {0x1d716, 0x1d734}, /* 31x 𝜖..𝜴 Math */ {0x1d736, 0x1d74e}, /* 25x 𝜶..𝝎 Math */ {0x1d750, 0x1d76e}, /* 31x 𝝐..𝝮 Math */ {0x1d770, 0x1d788}, /* 25x 𝝰..𝞈 Math */ {0x1d78a, 0x1d7a8}, /* 31x 𝞊..𝞨 Math */ {0x1d7aa, 0x1d7c2}, /* 25x 𝞪..𝟂 Math */ {0x1d7c4, 0x1d7cb}, /* 8x 𝟄..𝟋 Math */ {0x1d7ce, 0x1d9ff}, /* 562x Math, Sutton SignWriting */ {0x1f100, 0x1f10c}, /* 13x Enclosed Alphanumeric Supplement */ {0x20000, 0x2a6d6}, /* 42711x CJK Unified Ideographs Extension B */ {0x2a700, 0x2b734}, /* 4149x CJK Unified Ideographs Extension C */ {0x2b740, 0x2b81d}, /* 222x CJK Unified Ideographs Extension D */ {0x2b820, 0x2cea1}, /* 5762x CJK Unified Ideographs Extension E */ {0x2ceb0, 0x2ebe0}, /* 7473x CJK Unified Ideographs Extension F */ {0x2f800, 0x2fa1d}, /* 542x CJK Compatibility Ideographs Supplement */ }; l = 0; r = n = sizeof(kAstralGlyphs) / sizeof(kAstralGlyphs[0]); while (l < r) { m = (l + r) >> 1; if (kAstralGlyphs[m][1] < c) { l = m + 1; } else { r = m; } } return !(l < n && kAstralGlyphs[l][0] <= c && c <= kAstralGlyphs[l][1]); } } unsigned bestlineLowercase(unsigned c) { int m, l, r, n; if (c < 0200) { if ('A' <= c && c <= 'Z') { return c + 32; } else { return c; } } else if (c <= 0xffff) { if ((0x0100 <= c && c <= 0x0176) || /* 60x Ā..ā → ā..ŵ Watin-A */ (0x01de <= c && c <= 0x01ee) || /* 9x Ǟ..Ǯ → ǟ..ǯ Watin-B */ (0x01f8 <= c && c <= 0x021e) || /* 20x Ǹ..Ȟ → ǹ..ȟ Watin-B */ (0x0222 <= c && c <= 0x0232) || /* 9x Ȣ..Ȳ → ȣ..ȳ Watin-B */ (0x1e00 <= c && c <= 0x1eff)) { /*256x Ḁ..Ỿ → ḁ..ỿ Watin-C */ if (c == 0x0130) return c - 199; if (c == 0x1e9e) return c; return c + (~c & 1); } else if (0x01cf <= c && c <= 0x01db) { return c + (c & 1); /* 7x Ǐ..Ǜ → ǐ..ǜ Watin-B */ } else if (0x13a0 <= c && c <= 0x13ef) { return c + 38864; /* 80x Ꭰ ..Ꮿ → ꭰ ..ꮿ Cherokee */ } else { static const struct { unsigned short a; unsigned short b; short d; } kLower[] = { {0x00c0, 0x00d6, +32}, /* 23x À ..Ö → à ..ö Watin */ {0x00d8, 0x00de, +32}, /* 7x Ø ..Þ → ø ..þ Watin */ {0x0178, 0x0178, -121}, /* 1x Ÿ ..Ÿ → ÿ ..ÿ Watin-A */ {0x0179, 0x0179, +1}, /* 1x Ź ..Ź → ź ..ź Watin-A */ {0x017b, 0x017b, +1}, /* 1x Ż ..Ż → ż ..ż Watin-A */ {0x017d, 0x017d, +1}, /* 1x Ž ..Ž → ž ..ž Watin-A */ {0x0181, 0x0181, +210}, /* 1x Ɓ ..Ɓ → ɓ ..ɓ Watin-B */ {0x0182, 0x0182, +1}, /* 1x Ƃ ..Ƃ → ƃ ..ƃ Watin-B */ {0x0184, 0x0184, +1}, /* 1x Ƅ ..Ƅ → ƅ ..ƅ Watin-B */ {0x0186, 0x0186, +206}, /* 1x Ɔ ..Ɔ → ɔ ..ɔ Watin-B */ {0x0187, 0x0187, +1}, /* 1x Ƈ ..Ƈ → ƈ ..ƈ Watin-B */ {0x0189, 0x018a, +205}, /* 2x Ɖ ..Ɗ → ɖ ..ɗ Watin-B */ {0x018b, 0x018b, +1}, /* 1x Ƌ ..Ƌ → ƌ ..ƌ Watin-B */ {0x018e, 0x018e, +79}, /* 1x Ǝ ..Ǝ → ǝ ..ǝ Watin-B */ {0x018f, 0x018f, +202}, /* 1x Ə ..Ə → ə ..ə Watin-B */ {0x0190, 0x0190, +203}, /* 1x Ɛ ..Ɛ → ɛ ..ɛ Watin-B */ {0x0191, 0x0191, +1}, /* 1x Ƒ ..Ƒ → ƒ ..ƒ Watin-B */ {0x0193, 0x0193, +205}, /* 1x Ɠ ..Ɠ → ɠ ..ɠ Watin-B */ {0x0194, 0x0194, +207}, /* 1x Ɣ ..Ɣ → ɣ ..ɣ Watin-B */ {0x0196, 0x0196, +211}, /* 1x Ɩ ..Ɩ → ɩ ..ɩ Watin-B */ {0x0197, 0x0197, +209}, /* 1x Ɨ ..Ɨ → ɨ ..ɨ Watin-B */ {0x0198, 0x0198, +1}, /* 1x Ƙ ..Ƙ → ƙ ..ƙ Watin-B */ {0x019c, 0x019c, +211}, /* 1x Ɯ ..Ɯ → ɯ ..ɯ Watin-B */ {0x019d, 0x019d, +213}, /* 1x Ɲ ..Ɲ → ɲ ..ɲ Watin-B */ {0x019f, 0x019f, +214}, /* 1x Ɵ ..Ɵ → ɵ ..ɵ Watin-B */ {0x01a0, 0x01a0, +1}, /* 1x Ơ ..Ơ → ơ ..ơ Watin-B */ {0x01a2, 0x01a2, +1}, /* 1x Ƣ ..Ƣ → ƣ ..ƣ Watin-B */ {0x01a4, 0x01a4, +1}, /* 1x Ƥ ..Ƥ → ƥ ..ƥ Watin-B */ {0x01a6, 0x01a6, +218}, /* 1x Ʀ ..Ʀ → ʀ ..ʀ Watin-B */ {0x01a7, 0x01a7, +1}, /* 1x Ƨ ..Ƨ → ƨ ..ƨ Watin-B */ {0x01a9, 0x01a9, +218}, /* 1x Ʃ ..Ʃ → ʃ ..ʃ Watin-B */ {0x01ac, 0x01ac, +1}, /* 1x Ƭ ..Ƭ → ƭ ..ƭ Watin-B */ {0x01ae, 0x01ae, +218}, /* 1x Ʈ ..Ʈ → ʈ ..ʈ Watin-B */ {0x01af, 0x01af, +1}, /* 1x Ư ..Ư → ư ..ư Watin-B */ {0x01b1, 0x01b2, +217}, /* 2x Ʊ ..Ʋ → ʊ ..ʋ Watin-B */ {0x01b3, 0x01b3, +1}, /* 1x Ƴ ..Ƴ → ƴ ..ƴ Watin-B */ {0x01b5, 0x01b5, +1}, /* 1x Ƶ ..Ƶ → ƶ ..ƶ Watin-B */ {0x01b7, 0x01b7, +219}, /* 1x Ʒ ..Ʒ → ʒ ..ʒ Watin-B */ {0x01b8, 0x01b8, +1}, /* 1x Ƹ ..Ƹ → ƹ ..ƹ Watin-B */ {0x01bc, 0x01bc, +1}, /* 1x Ƽ ..Ƽ → ƽ ..ƽ Watin-B */ {0x01c4, 0x01c4, +2}, /* 1x DŽ ..DŽ → dž ..dž Watin-B */ {0x01c5, 0x01c5, +1}, /* 1x Dž ..Dž → dž ..dž Watin-B */ {0x01c7, 0x01c7, +2}, /* 1x LJ ..LJ → lj ..lj Watin-B */ {0x01c8, 0x01c8, +1}, /* 1x Lj ..Lj → lj ..lj Watin-B */ {0x01ca, 0x01ca, +2}, /* 1x NJ ..NJ → nj ..nj Watin-B */ {0x01cb, 0x01cb, +1}, /* 1x Nj ..Nj → nj ..nj Watin-B */ {0x01cd, 0x01cd, +1}, /* 1x Ǎ ..Ǎ → ǎ ..ǎ Watin-B */ {0x01f1, 0x01f1, +2}, /* 1x DZ ..DZ → dz ..dz Watin-B */ {0x01f2, 0x01f2, +1}, /* 1x Dz ..Dz → dz ..dz Watin-B */ {0x01f4, 0x01f4, +1}, /* 1x Ǵ ..Ǵ → ǵ ..ǵ Watin-B */ {0x01f6, 0x01f6, -97}, /* 1x Ƕ ..Ƕ → ƕ ..ƕ Watin-B */ {0x01f7, 0x01f7, -56}, /* 1x Ƿ ..Ƿ → ƿ ..ƿ Watin-B */ {0x0220, 0x0220, -130}, /* 1x Ƞ ..Ƞ → ƞ ..ƞ Watin-B */ {0x023b, 0x023b, +1}, /* 1x Ȼ ..Ȼ → ȼ ..ȼ Watin-B */ {0x023d, 0x023d, -163}, /* 1x Ƚ ..Ƚ → ƚ ..ƚ Watin-B */ {0x0241, 0x0241, +1}, /* 1x Ɂ ..Ɂ → ɂ ..ɂ Watin-B */ {0x0243, 0x0243, -195}, /* 1x Ƀ ..Ƀ → ƀ ..ƀ Watin-B */ {0x0244, 0x0244, +69}, /* 1x Ʉ ..Ʉ → ʉ ..ʉ Watin-B */ {0x0245, 0x0245, +71}, /* 1x Ʌ ..Ʌ → ʌ ..ʌ Watin-B */ {0x0246, 0x0246, +1}, /* 1x Ɇ ..Ɇ → ɇ ..ɇ Watin-B */ {0x0248, 0x0248, +1}, /* 1x Ɉ ..Ɉ → ɉ ..ɉ Watin-B */ {0x024a, 0x024a, +1}, /* 1x Ɋ ..Ɋ → ɋ ..ɋ Watin-B */ {0x024c, 0x024c, +1}, /* 1x Ɍ ..Ɍ → ɍ ..ɍ Watin-B */ {0x024e, 0x024e, +1}, /* 1x Ɏ ..Ɏ → ɏ ..ɏ Watin-B */ {0x0386, 0x0386, +38}, /* 1x Ά ..Ά → ά ..ά Greek */ {0x0388, 0x038a, +37}, /* 3x Έ ..Ί → έ ..ί Greek */ {0x038c, 0x038c, +64}, /* 1x Ό ..Ό → ό ..ό Greek */ {0x038e, 0x038f, +63}, /* 2x Ύ ..Ώ → ύ ..ώ Greek */ {0x0391, 0x03a1, +32}, /* 17x Α ..Ρ → α ..ρ Greek */ {0x03a3, 0x03ab, +32}, /* 9x Σ ..Ϋ → σ ..ϋ Greek */ {0x03dc, 0x03dc, +1}, /* 1x Ϝ ..Ϝ → ϝ ..ϝ Greek */ {0x03f4, 0x03f4, -60}, /* 1x ϴ ..ϴ → θ ..θ Greek */ {0x0400, 0x040f, +80}, /* 16x Ѐ ..Џ → ѐ ..џ Cyrillic */ {0x0410, 0x042f, +32}, /* 32x А ..Я → а ..я Cyrillic */ {0x0460, 0x0460, +1}, /* 1x Ѡ ..Ѡ → ѡ ..ѡ Cyrillic */ {0x0462, 0x0462, +1}, /* 1x Ѣ ..Ѣ → ѣ ..ѣ Cyrillic */ {0x0464, 0x0464, +1}, /* 1x Ѥ ..Ѥ → ѥ ..ѥ Cyrillic */ {0x0472, 0x0472, +1}, /* 1x Ѳ ..Ѳ → ѳ ..ѳ Cyrillic */ {0x0490, 0x0490, +1}, /* 1x Ґ ..Ґ → ґ ..ґ Cyrillic */ {0x0498, 0x0498, +1}, /* 1x Ҙ ..Ҙ → ҙ ..ҙ Cyrillic */ {0x049a, 0x049a, +1}, /* 1x Қ ..Қ → қ ..қ Cyrillic */ {0x0531, 0x0556, +48}, /* 38x Ա ..Ֆ → ա ..ֆ Armenian */ {0x10a0, 0x10c5, +7264}, /* 38x Ⴀ ..Ⴥ → ⴀ ..ⴥ Georgian */ {0x10c7, 0x10c7, +7264}, /* 1x Ⴧ ..Ⴧ → ⴧ ..ⴧ Georgian */ {0x10cd, 0x10cd, +7264}, /* 1x Ⴭ ..Ⴭ → ⴭ ..ⴭ Georgian */ {0x13f0, 0x13f5, +8}, /* 6x Ᏸ ..Ᏽ → ᏸ ..ᏽ Cherokee */ {0x1c90, 0x1cba, -3008}, /* 43x Ა ..Ჺ → ა ..ჺ Georgian2 */ {0x1cbd, 0x1cbf, -3008}, /* 3x Ჽ ..Ჿ → ჽ ..ჿ Georgian2 */ {0x1f08, 0x1f0f, -8}, /* 8x Ἀ ..Ἇ → ἀ ..ἇ Greek2 */ {0x1f18, 0x1f1d, -8}, /* 6x Ἐ ..Ἕ → ἐ ..ἕ Greek2 */ {0x1f28, 0x1f2f, -8}, /* 8x Ἠ ..Ἧ → ἠ ..ἧ Greek2 */ {0x1f38, 0x1f3f, -8}, /* 8x Ἰ ..Ἷ → ἰ ..ἷ Greek2 */ {0x1f48, 0x1f4d, -8}, /* 6x Ὀ ..Ὅ → ὀ ..ὅ Greek2 */ {0x1f59, 0x1f59, -8}, /* 1x Ὑ ..Ὑ → ὑ ..ὑ Greek2 */ {0x1f5b, 0x1f5b, -8}, /* 1x Ὓ ..Ὓ → ὓ ..ὓ Greek2 */ {0x1f5d, 0x1f5d, -8}, /* 1x Ὕ ..Ὕ → ὕ ..ὕ Greek2 */ {0x1f5f, 0x1f5f, -8}, /* 1x Ὗ ..Ὗ → ὗ ..ὗ Greek2 */ {0x1f68, 0x1f6f, -8}, /* 8x Ὠ ..Ὧ → ὠ ..ὧ Greek2 */ {0x1f88, 0x1f8f, -8}, /* 8x ᾈ ..ᾏ → ᾀ ..ᾇ Greek2 */ {0x1f98, 0x1f9f, -8}, /* 8x ᾘ ..ᾟ → ᾐ ..ᾗ Greek2 */ {0x1fa8, 0x1faf, -8}, /* 8x ᾨ ..ᾯ → ᾠ ..ᾧ Greek2 */ {0x1fb8, 0x1fb9, -8}, /* 2x Ᾰ ..Ᾱ → ᾰ ..ᾱ Greek2 */ {0x1fba, 0x1fbb, -74}, /* 2x Ὰ ..Ά → ὰ ..ά Greek2 */ {0x1fbc, 0x1fbc, -9}, /* 1x ᾼ ..ᾼ → ᾳ ..ᾳ Greek2 */ {0x1fc8, 0x1fcb, -86}, /* 4x Ὲ ..Ή → ὲ ..ή Greek2 */ {0x1fcc, 0x1fcc, -9}, /* 1x ῌ ..ῌ → ῃ ..ῃ Greek2 */ {0x1fd8, 0x1fd9, -8}, /* 2x Ῐ ..Ῑ → ῐ ..ῑ Greek2 */ {0x1fda, 0x1fdb, -100}, /* 2x Ὶ ..Ί → ὶ ..ί Greek2 */ {0x1fe8, 0x1fe9, -8}, /* 2x Ῠ ..Ῡ → ῠ ..ῡ Greek2 */ {0x1fea, 0x1feb, -112}, /* 2x Ὺ ..Ύ → ὺ ..ύ Greek2 */ {0x1fec, 0x1fec, -7}, /* 1x Ῥ ..Ῥ → ῥ ..ῥ Greek2 */ {0x1ff8, 0x1ff9, -128}, /* 2x Ὸ ..Ό → ὸ ..ό Greek2 */ {0x1ffa, 0x1ffb, -126}, /* 2x Ὼ ..Ώ → ὼ ..ώ Greek2 */ {0x1ffc, 0x1ffc, -9}, /* 1x ῼ ..ῼ → ῳ ..ῳ Greek2 */ {0x2126, 0x2126, -7517}, /* 1x Ω ..Ω → ω ..ω Letterlike */ {0x212a, 0x212a, -8383}, /* 1x K ..K → k ..k Letterlike */ {0x212b, 0x212b, -8262}, /* 1x Å ..Å → å ..å Letterlike */ {0x2132, 0x2132, +28}, /* 1x Ⅎ ..Ⅎ → ⅎ ..ⅎ Letterlike */ {0x2160, 0x216f, +16}, /* 16x Ⅰ ..Ⅿ → ⅰ ..ⅿ Numbery */ {0x2183, 0x2183, +1}, /* 1x Ↄ ..Ↄ → ↄ ..ↄ Numbery */ {0x24b6, 0x24cf, +26}, /* 26x Ⓐ ..Ⓩ → ⓐ ..ⓩ Enclosed */ {0x2c00, 0x2c2e, +48}, /* 47x Ⰰ ..Ⱞ → ⰰ ..ⱞ Glagolitic */ {0xff21, 0xff3a, +32}, /* 26x A..Z → a..z Dubs */ }; l = 0; r = n = sizeof(kLower) / sizeof(kLower[0]); while (l < r) { m = (l + r) >> 1; if (kLower[m].b < c) { l = m + 1; } else { r = m; } } if (l < n && kLower[l].a <= c && c <= kLower[l].b) { return c + kLower[l].d; } else { return c; } } } else { static struct { unsigned a; unsigned b; short d; } kAstralLower[] = { {0x10400, 0x10427, +40}, /* 40x 𐐀 ..𐐧 → 𐐨 ..𐑏 Deseret */ {0x104b0, 0x104d3, +40}, /* 36x 𐒰 ..𐓓 → 𐓘 ..𐓻 Osage */ {0x1d400, 0x1d419, +26}, /* 26x 𝐀 ..𝐙 → 𝐚 ..𝐳 Math */ {0x1d43c, 0x1d44d, +26}, /* 18x 𝐼 ..𝑍 → 𝑖 ..𝑧 Math */ {0x1d468, 0x1d481, +26}, /* 26x 𝑨 ..𝒁 → 𝒂 ..𝒛 Math */ {0x1d4ae, 0x1d4b5, +26}, /* 8x 𝒮 ..𝒵 → 𝓈 ..𝓏 Math */ {0x1d4d0, 0x1d4e9, +26}, /* 26x 𝓐 ..𝓩 → 𝓪 ..𝔃 Math */ {0x1d50d, 0x1d514, +26}, /* 8x 𝔍 ..𝔔 → 𝔧 ..𝔮 Math */ {0x1d56c, 0x1d585, +26}, /* 26x 𝕬 ..𝖅 → 𝖆 ..𝖟 Math */ {0x1d5a0, 0x1d5b9, +26}, /* 26x 𝖠 ..𝖹 → 𝖺 ..𝗓 Math */ {0x1d5d4, 0x1d5ed, +26}, /* 26x 𝗔 ..𝗭 → 𝗮 ..𝘇 Math */ {0x1d608, 0x1d621, +26}, /* 26x 𝘈 ..𝘡 → 𝘢 ..𝘻 Math */ {0x1d63c, 0x1d655, -442}, /* 26x 𝘼 ..𝙕 → 𝒂 ..𝒛 Math */ {0x1d670, 0x1d689, +26}, /* 26x 𝙰 ..𝚉 → 𝚊 ..𝚣 Math */ {0x1d6a8, 0x1d6b8, +26}, /* 17x 𝚨 ..𝚸 → 𝛂 ..𝛒 Math */ {0x1d6e2, 0x1d6f2, +26}, /* 17x 𝛢 ..𝛲 → 𝛼 ..𝜌 Math */ {0x1d71c, 0x1d72c, +26}, /* 17x 𝜜 ..𝜬 → 𝜶 ..𝝆 Math */ {0x1d756, 0x1d766, +26}, /* 17x 𝝖 ..𝝦 → 𝝰 ..𝞀 Math */ {0x1d790, 0x1d7a0, -90}, /* 17x 𝞐 ..𝞠 → 𝜶 ..𝝆 Math */ }; l = 0; r = n = sizeof(kAstralLower) / sizeof(kAstralLower[0]); while (l < r) { m = (l + r) >> 1; if (kAstralLower[m].b < c) { l = m + 1; } else { r = m; } } if (l < n && kAstralLower[l].a <= c && c <= kAstralLower[l].b) { return c + kAstralLower[l].d; } else { return c; } } } unsigned bestlineUppercase(unsigned c) { int m, l, r, n; if (c < 0200) { if ('a' <= c && c <= 'z') { return c - 32; } else { return c; } } else if (c <= 0xffff) { if ((0x0101 <= c && c <= 0x0177) || /* 60x ā..ŵ → Ā..ā Watin-A */ (0x01df <= c && c <= 0x01ef) || /* 9x ǟ..ǯ → Ǟ..Ǯ Watin-B */ (0x01f8 <= c && c <= 0x021e) || /* 20x ǹ..ȟ → Ǹ..Ȟ Watin-B */ (0x0222 <= c && c <= 0x0232) || /* 9x ȣ..ȳ → Ȣ..Ȳ Watin-B */ (0x1e01 <= c && c <= 0x1eff)) { /*256x ḁ..ỿ → Ḁ..Ỿ Watin-C */ if (c == 0x0131) return c + 232; if (c == 0x1e9e) return c; return c - (c & 1); } else if (0x01d0 <= c && c <= 0x01dc) { return c - (~c & 1); /* 7x ǐ..ǜ → Ǐ..Ǜ Watin-B */ } else if (0xab70 <= c && c <= 0xabbf) { return c - 38864; /* 80x ꭰ ..ꮿ → Ꭰ ..Ꮿ Cherokee Supplement */ } else { static const struct { unsigned short a; unsigned short b; short d; } kUpper[] = { {0x00b5, 0x00b5, +743}, /* 1x µ ..µ → Μ ..Μ Watin */ {0x00e0, 0x00f6, -32}, /* 23x à ..ö → À ..Ö Watin */ {0x00f8, 0x00fe, -32}, /* 7x ø ..þ → Ø ..Þ Watin */ {0x00ff, 0x00ff, +121}, /* 1x ÿ ..ÿ → Ÿ ..Ÿ Watin */ {0x017a, 0x017a, -1}, /* 1x ź ..ź → Ź ..Ź Watin-A */ {0x017c, 0x017c, -1}, /* 1x ż ..ż → Ż ..Ż Watin-A */ {0x017e, 0x017e, -1}, /* 1x ž ..ž → Ž ..Ž Watin-A */ {0x017f, 0x017f, -300}, /* 1x ſ ..ſ → S ..S Watin-A */ {0x0180, 0x0180, +195}, /* 1x ƀ ..ƀ → Ƀ ..Ƀ Watin-B */ {0x0183, 0x0183, -1}, /* 1x ƃ ..ƃ → Ƃ ..Ƃ Watin-B */ {0x0185, 0x0185, -1}, /* 1x ƅ ..ƅ → Ƅ ..Ƅ Watin-B */ {0x0188, 0x0188, -1}, /* 1x ƈ ..ƈ → Ƈ ..Ƈ Watin-B */ {0x018c, 0x018c, -1}, /* 1x ƌ ..ƌ → Ƌ ..Ƌ Watin-B */ {0x0192, 0x0192, -1}, /* 1x ƒ ..ƒ → Ƒ ..Ƒ Watin-B */ {0x0195, 0x0195, +97}, /* 1x ƕ ..ƕ → Ƕ ..Ƕ Watin-B */ {0x0199, 0x0199, -1}, /* 1x ƙ ..ƙ → Ƙ ..Ƙ Watin-B */ {0x019a, 0x019a, +163}, /* 1x ƚ ..ƚ → Ƚ ..Ƚ Watin-B */ {0x019e, 0x019e, +130}, /* 1x ƞ ..ƞ → Ƞ ..Ƞ Watin-B */ {0x01a1, 0x01a1, -1}, /* 1x ơ ..ơ → Ơ ..Ơ Watin-B */ {0x01a3, 0x01a3, -1}, /* 1x ƣ ..ƣ → Ƣ ..Ƣ Watin-B */ {0x01a5, 0x01a5, -1}, /* 1x ƥ ..ƥ → Ƥ ..Ƥ Watin-B */ {0x01a8, 0x01a8, -1}, /* 1x ƨ ..ƨ → Ƨ ..Ƨ Watin-B */ {0x01ad, 0x01ad, -1}, /* 1x ƭ ..ƭ → Ƭ ..Ƭ Watin-B */ {0x01b0, 0x01b0, -1}, /* 1x ư ..ư → Ư ..Ư Watin-B */ {0x01b4, 0x01b4, -1}, /* 1x ƴ ..ƴ → Ƴ ..Ƴ Watin-B */ {0x01b6, 0x01b6, -1}, /* 1x ƶ ..ƶ → Ƶ ..Ƶ Watin-B */ {0x01b9, 0x01b9, -1}, /* 1x ƹ ..ƹ → Ƹ ..Ƹ Watin-B */ {0x01bd, 0x01bd, -1}, /* 1x ƽ ..ƽ → Ƽ ..Ƽ Watin-B */ {0x01bf, 0x01bf, +56}, /* 1x ƿ ..ƿ → Ƿ ..Ƿ Watin-B */ {0x01c5, 0x01c5, -1}, /* 1x Dž ..Dž → DŽ ..DŽ Watin-B */ {0x01c6, 0x01c6, -2}, /* 1x dž ..dž → DŽ ..DŽ Watin-B */ {0x01c8, 0x01c8, -1}, /* 1x Lj ..Lj → LJ ..LJ Watin-B */ {0x01c9, 0x01c9, -2}, /* 1x lj ..lj → LJ ..LJ Watin-B */ {0x01cb, 0x01cb, -1}, /* 1x Nj ..Nj → NJ ..NJ Watin-B */ {0x01cc, 0x01cc, -2}, /* 1x nj ..nj → NJ ..NJ Watin-B */ {0x01ce, 0x01ce, -1}, /* 1x ǎ ..ǎ → Ǎ ..Ǎ Watin-B */ {0x01dd, 0x01dd, -79}, /* 1x ǝ ..ǝ → Ǝ ..Ǝ Watin-B */ {0x01f2, 0x01f2, -1}, /* 1x Dz ..Dz → DZ ..DZ Watin-B */ {0x01f3, 0x01f3, -2}, /* 1x dz ..dz → DZ ..DZ Watin-B */ {0x01f5, 0x01f5, -1}, /* 1x ǵ ..ǵ → Ǵ ..Ǵ Watin-B */ {0x023c, 0x023c, -1}, /* 1x ȼ ..ȼ → Ȼ ..Ȼ Watin-B */ {0x023f, 0x0240,+10815}, /* 2x ȿ ..ɀ → Ȿ ..Ɀ Watin-B */ {0x0242, 0x0242, -1}, /* 1x ɂ ..ɂ → Ɂ ..Ɂ Watin-B */ {0x0247, 0x0247, -1}, /* 1x ɇ ..ɇ → Ɇ ..Ɇ Watin-B */ {0x0249, 0x0249, -1}, /* 1x ɉ ..ɉ → Ɉ ..Ɉ Watin-B */ {0x024b, 0x024b, -1}, /* 1x ɋ ..ɋ → Ɋ ..Ɋ Watin-B */ {0x024d, 0x024d, -1}, /* 1x ɍ ..ɍ → Ɍ ..Ɍ Watin-B */ {0x024f, 0x024f, -1}, /* 1x ɏ ..ɏ → Ɏ ..Ɏ Watin-B */ {0x037b, 0x037d, +130}, /* 3x ͻ ..ͽ → Ͻ ..Ͽ Greek */ {0x03ac, 0x03ac, -38}, /* 1x ά ..ά → Ά ..Ά Greek */ {0x03ad, 0x03af, -37}, /* 3x έ ..ί → Έ ..Ί Greek */ {0x03b1, 0x03c1, -32}, /* 17x α ..ρ → Α ..Ρ Greek */ {0x03c2, 0x03c2, -31}, /* 1x ς ..ς → Σ ..Σ Greek */ {0x03c3, 0x03cb, -32}, /* 9x σ ..ϋ → Σ ..Ϋ Greek */ {0x03cc, 0x03cc, -64}, /* 1x ό ..ό → Ό ..Ό Greek */ {0x03cd, 0x03ce, -63}, /* 2x ύ ..ώ → Ύ ..Ώ Greek */ {0x03d0, 0x03d0, -62}, /* 1x ϐ ..ϐ → Β ..Β Greek */ {0x03d1, 0x03d1, -57}, /* 1x ϑ ..ϑ → Θ ..Θ Greek */ {0x03d5, 0x03d5, -47}, /* 1x ϕ ..ϕ → Φ ..Φ Greek */ {0x03d6, 0x03d6, -54}, /* 1x ϖ ..ϖ → Π ..Π Greek */ {0x03dd, 0x03dd, -1}, /* 1x ϝ ..ϝ → Ϝ ..Ϝ Greek */ {0x03f0, 0x03f0, -86}, /* 1x ϰ ..ϰ → Κ ..Κ Greek */ {0x03f1, 0x03f1, -80}, /* 1x ϱ ..ϱ → Ρ ..Ρ Greek */ {0x03f5, 0x03f5, -96}, /* 1x ϵ ..ϵ → Ε ..Ε Greek */ {0x0430, 0x044f, -32}, /* 32x а ..я → А ..Я Cyrillic */ {0x0450, 0x045f, -80}, /* 16x ѐ ..џ → Ѐ ..Џ Cyrillic */ {0x0461, 0x0461, -1}, /* 1x ѡ ..ѡ → Ѡ ..Ѡ Cyrillic */ {0x0463, 0x0463, -1}, /* 1x ѣ ..ѣ → Ѣ ..Ѣ Cyrillic */ {0x0465, 0x0465, -1}, /* 1x ѥ ..ѥ → Ѥ ..Ѥ Cyrillic */ {0x0473, 0x0473, -1}, /* 1x ѳ ..ѳ → Ѳ ..Ѳ Cyrillic */ {0x0491, 0x0491, -1}, /* 1x ґ ..ґ → Ґ ..Ґ Cyrillic */ {0x0499, 0x0499, -1}, /* 1x ҙ ..ҙ → Ҙ ..Ҙ Cyrillic */ {0x049b, 0x049b, -1}, /* 1x қ ..қ → Қ ..Қ Cyrillic */ {0x0561, 0x0586, -48}, /* 38x ա ..ֆ → Ա ..Ֆ Armenian */ {0x10d0, 0x10fa, +3008}, /* 43x ა ..ჺ → Ა ..Ჺ Georgian */ {0x10fd, 0x10ff, +3008}, /* 3x ჽ ..ჿ → Ჽ ..Ჿ Georgian */ {0x13f8, 0x13fd, -8}, /* 6x ᏸ ..ᏽ → Ᏸ ..Ᏽ Cherokee */ {0x214e, 0x214e, -28}, /* 1x ⅎ ..ⅎ → Ⅎ ..Ⅎ Letterlike */ {0x2170, 0x217f, -16}, /* 16x ⅰ ..ⅿ → Ⅰ ..Ⅿ Numbery */ {0x2184, 0x2184, -1}, /* 1x ↄ ..ↄ → Ↄ ..Ↄ Numbery */ {0x24d0, 0x24e9, -26}, /* 26x ⓐ ..ⓩ → Ⓐ ..Ⓩ Enclosed */ {0x2c30, 0x2c5e, -48}, /* 47x ⰰ ..ⱞ → Ⰰ ..Ⱞ Glagolitic */ {0x2d00, 0x2d25, -7264}, /* 38x ⴀ ..ⴥ → Ⴀ ..Ⴥ Georgian2 */ {0x2d27, 0x2d27, -7264}, /* 1x ⴧ ..ⴧ → Ⴧ ..Ⴧ Georgian2 */ {0x2d2d, 0x2d2d, -7264}, /* 1x ⴭ ..ⴭ → Ⴭ ..Ⴭ Georgian2 */ {0xff41, 0xff5a, -32}, /* 26x a..z → A..Z Dubs */ }; l = 0; r = n = sizeof(kUpper) / sizeof(kUpper[0]); while (l < r) { m = (l + r) >> 1; if (kUpper[m].b < c) { l = m + 1; } else { r = m; } } if (l < n && kUpper[l].a <= c && c <= kUpper[l].b) { return c + kUpper[l].d; } else { return c; } } } else { static const struct { unsigned a; unsigned b; short d; } kAstralUpper[] = { {0x10428, 0x1044f, -40}, /* 40x 𐐨..𐑏 → 𐐀..𐐧 Deseret */ {0x104d8, 0x104fb, -40}, /* 36x 𐓘..𐓻 → 𐒰..𐓓 Osage */ {0x1d41a, 0x1d433, -26}, /* 26x 𝐚..𝐳 → 𝐀..𝐙 Math */ {0x1d456, 0x1d467, -26}, /* 18x 𝑖..𝑧 → 𝐼..𝑍 Math */ {0x1d482, 0x1d49b, -26}, /* 26x 𝒂..𝒛 → 𝑨..𝒁 Math */ {0x1d4c8, 0x1d4cf, -26}, /* 8x 𝓈..𝓏 → 𝒮..𝒵 Math */ {0x1d4ea, 0x1d503, -26}, /* 26x 𝓪..𝔃 → 𝓐..𝓩 Math */ {0x1d527, 0x1d52e, -26}, /* 8x 𝔧..𝔮 → 𝔍..𝔔 Math */ {0x1d586, 0x1d59f, -26}, /* 26x 𝖆..𝖟 → 𝕬..𝖅 Math */ {0x1d5ba, 0x1d5d3, -26}, /* 26x 𝖺..𝗓 → 𝖠..𝖹 Math */ {0x1d5ee, 0x1d607, -26}, /* 26x 𝗮..𝘇 → 𝗔..𝗭 Math */ {0x1d622, 0x1d63b, -26}, /* 26x 𝘢..𝘻 → 𝘈..𝘡 Math */ {0x1d68a, 0x1d6a3, +442}, /* 26x 𝒂..𝒛 → 𝘼..𝙕 Math */ {0x1d6c2, 0x1d6d2, -26}, /* 26x 𝚊..𝚣 → 𝙰..𝚉 Math */ {0x1d6fc, 0x1d70c, -26}, /* 17x 𝛂..𝛒 → 𝚨..𝚸 Math */ {0x1d736, 0x1d746, -26}, /* 17x 𝛼..𝜌 → 𝛢..𝛲 Math */ {0x1d770, 0x1d780, -26}, /* 17x 𝜶..𝝆 → 𝜜..𝜬 Math */ {0x1d770, 0x1d756, -26}, /* 17x 𝝰..𝞀 → 𝝖..𝝦 Math */ {0x1d736, 0x1d790, -90}, /* 17x 𝜶..𝝆 → 𝞐..𝞠 Math */ }; l = 0; r = n = sizeof(kAstralUpper) / sizeof(kAstralUpper[0]); while (l < r) { m = (l + r) >> 1; if (kAstralUpper[m].b < c) { l = m + 1; } else { r = m; } } if (l < n && kAstralUpper[l].a <= c && c <= kAstralUpper[l].b) { return c + kAstralUpper[l].d; } else { return c; } } } char bestlineNotSeparator(unsigned c) { return !bestlineIsSeparator(c); } static unsigned GetMirror(const unsigned short A[][2], size_t n, unsigned c) { int l, m, r; l = 0; r = n - 1; while (l <= r) { m = (l + r) >> 1; if (A[m][0] < c) { l = m + 1; } else if (A[m][0] > c) { r = m - 1; } else { return A[m][1]; } } return 0; } unsigned bestlineMirrorLeft(unsigned c) { static const unsigned short kMirrorRight[][2] = { {L')', L'('}, {L']', L'['}, {L'}', L'{'}, {L'⁆', L'⁅'}, {L'⁾', L'⁽'}, {L'₎', L'₍'}, {L'⌉', L'⌈'}, {L'⌋', L'⌊'}, {L'〉', L'〈'}, {L'❩', L'❨'}, {L'❫', L'❪'}, {L'❭', L'❬'}, {L'❯', L'❮'}, {L'❱', L'❰'}, {L'❳', L'❲'}, {L'❵', L'❴'}, {L'⟆', L'⟅'}, {L'⟧', L'⟦'}, {L'⟩', L'⟨'}, {L'⟫', L'⟪'}, {L'⟭', L'⟬'}, {L'⟯', L'⟮'}, {L'⦄', L'⦃'}, {L'⦆', L'⦅'}, {L'⦈', L'⦇'}, {L'⦊', L'⦉'}, {L'⦌', L'⦋'}, {L'⦎', L'⦏'}, {L'⦐', L'⦍'}, {L'⦒', L'⦑'}, {L'⦔', L'⦓'}, {L'⦘', L'⦗'}, {L'⧙', L'⧘'}, {L'⧛', L'⧚'}, {L'⧽', L'⧼'}, {L'﹚', L'﹙'}, {L'﹜', L'﹛'}, {L'﹞', L'﹝'}, {L')', L'('}, {L']', L'['}, {L'}', L'{'}, {L'」', L'「'}, }; return GetMirror(kMirrorRight, sizeof(kMirrorRight) / sizeof(kMirrorRight[0]), c); } unsigned bestlineMirrorRight(unsigned c) { static const unsigned short kMirrorLeft[][2] = { {L'(', L')'}, {L'[', L']'}, {L'{', L'}'}, {L'⁅', L'⁆'}, {L'⁽', L'⁾'}, {L'₍', L'₎'}, {L'⌈', L'⌉'}, {L'⌊', L'⌋'}, {L'〈', L'〉'}, {L'❨', L'❩'}, {L'❪', L'❫'}, {L'❬', L'❭'}, {L'❮', L'❯'}, {L'❰', L'❱'}, {L'❲', L'❳'}, {L'❴', L'❵'}, {L'⟅', L'⟆'}, {L'⟦', L'⟧'}, {L'⟨', L'⟩'}, {L'⟪', L'⟫'}, {L'⟬', L'⟭'}, {L'⟮', L'⟯'}, {L'⦃', L'⦄'}, {L'⦅', L'⦆'}, {L'⦇', L'⦈'}, {L'⦉', L'⦊'}, {L'⦋', L'⦌'}, {L'⦍', L'⦐'}, {L'⦏', L'⦎'}, {L'⦑', L'⦒'}, {L'⦓', L'⦔'}, {L'⦗', L'⦘'}, {L'⧘', L'⧙'}, {L'⧚', L'⧛'}, {L'⧼', L'⧽'}, {L'﹙', L'﹚'}, {L'﹛', L'﹜'}, {L'﹝', L'﹞'}, {L'(', L')'}, {L'[', L']'}, {L'{', L'}'}, {L'「', L'」'}, }; return GetMirror(kMirrorLeft, sizeof(kMirrorLeft) / sizeof(kMirrorLeft[0]), c); } char bestlineIsXeparator(unsigned c) { return (bestlineIsSeparator(c) && !bestlineMirrorLeft(c) && !bestlineMirrorRight(c)); } static unsigned Capitalize(unsigned c) { if (!iscapital) { c = bestlineUppercase(c); iscapital = 1; } return c; } static inline int Bsr(unsigned long long x) { #if defined(__GNUC__) && !defined(__STRICT_ANSI__) int b; b = __builtin_clzll(x); b ^= sizeof(unsigned long long) * CHAR_BIT - 1; return b; #else static const char kDebruijn[64] = { 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63, }; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; x |= x >> 32; return kDebruijn[(x * 0x03f79d71b4cb0a89) >> 58]; #endif } static struct rune DecodeUtf8(int c) { struct rune r; if (c < 252) { r.n = Bsr(255 & ~c); r.c = c & (((1 << r.n) - 1) | 3); r.n = 6 - r.n; } else { r.c = c & 3; r.n = 5; } return r; } static unsigned long long EncodeUtf8(unsigned c) { static const unsigned short kTpEnc[32 - 7] = { 1|0300<<8, 1|0300<<8, 1|0300<<8, 1|0300<<8, 2|0340<<8, 2|0340<<8, 2|0340<<8, 2|0340<<8, 2|0340<<8, 3|0360<<8, 3|0360<<8, 3|0360<<8, 3|0360<<8, 3|0360<<8, 4|0370<<8, 4|0370<<8, 4|0370<<8, 4|0370<<8, 4|0370<<8, 5|0374<<8, 5|0374<<8, 5|0374<<8, 5|0374<<8, 5|0374<<8, 5|0374<<8, }; int e, n; unsigned long long w; if (c < 0200) return c; e = kTpEnc[Bsr(c) - 7]; n = e & 0xff; w = 0; do { w |= 0200 | (c & 077); w <<= 8; c >>= 6; } while (--n); return c | w | e >> 8; } static struct rune GetUtf8(const char *p, size_t n) { struct rune r; if ((r.n = r.c = 0) < n && (r.c = p[r.n++] & 255) >= 0300) { r.c = DecodeUtf8(r.c).c; while (r.n < n && (p[r.n] & 0300) == 0200) { r.c = r.c << 6 | (p[r.n++] & 077); } } return r; } static char *FormatUnsigned(char *p, unsigned x) { char t; size_t i, a, b; i = 0; do { p[i++] = x % 10 + '0'; x = x / 10; } while (x > 0); p[i] = '\0'; if (i) { for (a = 0, b = i - 1; a < b; ++a, --b) { t = p[a]; p[a] = p[b]; p[b] = t; } } return p + i; } static void abInit(struct abuf *a) { a->len = 0; a->cap = 16; a->b = (char *)malloc(a->cap); a->b[0] = 0; } static char abGrow(struct abuf *a, int need) { int cap; char *b; cap = a->cap; do cap += cap / 2; while (cap < need); if (!(b = (char *)realloc(a->b, cap * sizeof(*a->b)))) return 0; a->cap = cap; a->b = b; return 1; } static void abAppendw(struct abuf *a, unsigned long long w) { char *p; if (a->len + 8 > a->cap && !abGrow(a, a->len + 8)) return; p = a->b + a->len; p[0] = (0x00000000000000FF & w) >> 000; p[1] = (0x000000000000FF00 & w) >> 010; p[2] = (0x0000000000FF0000 & w) >> 020; p[3] = (0x00000000FF000000 & w) >> 030; p[4] = (0x000000FF00000000 & w) >> 040; p[5] = (0x0000FF0000000000 & w) >> 050; p[6] = (0x00FF000000000000 & w) >> 060; p[7] = (0xFF00000000000000 & w) >> 070; a->len += w ? (Bsr(w) >> 3) + 1 : 1; } static void abAppend(struct abuf *a, const char *s, int len) { if (a->len + len + 1 > a->cap && !abGrow(a, a->len + len + 1)) return; memcpy(a->b + a->len, s, len); a->b[a->len + len] = 0; a->len += len; } static void abAppends(struct abuf *a, const char *s) { abAppend(a, s, strlen(s)); } static void abAppendu(struct abuf *a, unsigned u) { char b[11]; abAppend(a, b, FormatUnsigned(b, u) - b); } static void abFree(struct abuf *a) { free(a->b); a->b = 0; } static size_t GetFdSize(int fd) { struct stat st; st.st_size = 0; fstat(fd, &st); return st.st_size; } static char IsCharDev(int fd) { struct stat st; st.st_mode = 0; fstat(fd, &st); return (st.st_mode & S_IFMT) == S_IFCHR; } static int WaitUntilReady(int fd, int events) { struct pollfd p[1]; p[0].fd = fd; p[0].events = events; return poll(p, 1, -1); } static char HasPendingInput(int fd) { struct pollfd p[1]; p[0].fd = fd; p[0].events = POLLIN; return poll(p, 1, 0) == 1; } static char *GetLineBlock(FILE *f) { ssize_t rc; char *p = 0; size_t n, c = 0; if ((rc = getdelim(&p, &c, '\n', f)) != EOF) { for (n = rc; n; --n) { if (p[n - 1] == '\r' || p[n - 1] == '\n') { p[n - 1] = 0; } else { break; } } return p; } else { free(p); return 0; } } long bestlineReadCharacter(int fd, char *p, unsigned long n) { int e; size_t i; ssize_t rc; struct rune r; unsigned char c; enum { kAscii, kUtf8, kEsc, kCsi1, kCsi2, kSs, kNf, kStr, kStr2, kDone } t; i = 0; r.c = 0; r.n = 0; e = errno; t = kAscii; if (n) p[0] = 0; do { for (;;) { if (gotint) { errno = EINTR; return -1; } if (n) { rc = read(fd,&c,1); } else { rc = read(fd,0,0); } if (rc == -1 && errno == EINTR) { if (!i) { return -1; } } else if (rc == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { if (WaitUntilReady(fd, POLLIN) == -1) { if (rc == -1 && errno == EINTR) { if (!i) { return -1; } } else { return -1; } } } else if (rc == -1) { return -1; } else if (!rc) { if (!i) { errno = e; return 0; } else { errno = EILSEQ; return -1; } } else { break; } } if (i + 1 < n) { p[i] = c; p[i+1] = 0; } else if (i < n) { p[i] = 0; } ++i; switch (t) { Whoopsie: if (n) p[0] = c; t = kAscii; i = 1; /* fallthrough */ case kAscii: if (c < 0200) { if (c == 033) { t = kEsc; } else { t = kDone; } } else if (c >= 0300) { t = kUtf8; r = DecodeUtf8(c); } else { /* ignore overlong sequences */ } break; case kUtf8: if ((c & 0300) == 0200) { r.c <<= 6; r.c |= c & 077; if (!--r.n) { switch (r.c) { case 033: t = kEsc; /* parsed but not canonicalized */ break; case 0x9b: t = kCsi1; /* unusual but legal */ break; case 0x8e: /* SS2 (Single Shift Two) */ case 0x8f: /* SS3 (Single Shift Three) */ t = kSs; break; case 0x90: /* DCS (Device Control String) */ case 0x98: /* SOS (Start of String) */ case 0x9d: /* OSC (Operating System Command) */ case 0x9e: /* PM (Privacy Message) */ case 0x9f: /* APC (Application Program Command) */ t = kStr; break; default: t = kDone; break; } } } else { goto Whoopsie; /* ignore underlong sequences if not eof */ } break; case kEsc: if (0x20 <= c && c <= 0x2f) { /* Nf */ /* * Almost no one uses ANSI Nf sequences * They overlaps with alt+graphic keystrokes * We care more about being able to type alt-/ */ if (c == ' ' || c == '#') { t = kNf; } else { t = kDone; } } else if (0x30 <= c && c <= 0x3f) { /* Fp */ t = kDone; } else if (0x20 <= c && c <= 0x5F) { /* Fe */ switch (c) { case '[': t = kCsi1; break; case 'N': /* SS2 (Single Shift Two) */ case 'O': /* SS3 (Single Shift Three) */ t = kSs; break; case 'P': /* DCS (Device Control String) */ case 'X': /* SOS (Start of String) */ case ']': /* OSC (Operating System Command) */ case '^': /* PM (Privacy Message) */ case '_': /* APC (Application Program Command) */ t = kStr; break; default: t = kDone; break; } } else if (0x60 <= c && c <= 0x7e) { /* Fs */ t = kDone; } else if (c == 033) { if (i < 3) { /* alt chording */ } else { t = kDone; /* esc mashing */ i = 1; } } else { t = kDone; } break; case kSs: t = kDone; break; case kNf: if (0x30 <= c && c <= 0x7e) { t = kDone; } else if (!(0x20 <= c && c <= 0x2f)) { goto Whoopsie; } break; case kCsi1: if (0x20 <= c && c <= 0x2f) { t = kCsi2; } else if (c == '[' && ((i == 3) || (i == 4 && p[1] == 033))) { /* linux function keys */ } else if (0x40 <= c && c <= 0x7e) { t = kDone; } else if (!(0x30 <= c && c <= 0x3f)) { goto Whoopsie; } break; case kCsi2: if (0x40 <= c && c <= 0x7e) { t = kDone; } else if (!(0x20 <= c && c <= 0x2f)) { goto Whoopsie; } break; case kStr: switch (c) { case '\a': t = kDone; break; case 0033: /* ESC */ case 0302: /* C1 (UTF-8) */ t = kStr2; break; default: break; } break; case kStr2: switch (c) { case '\a': case '\\': /* ST (ASCII) */ case 0234: /* ST (UTF-8) */ t = kDone; break; default: t = kStr; break; } break; default: assert(0); } } while (t != kDone); errno = e; return i; } static char *GetLineChar(int fin, int fout) { size_t got; ssize_t rc; char seq[16]; struct abuf a; struct sigaction sa[3]; abInit(&a); gotint = 0; sigemptyset(&sa->sa_mask); sa->sa_flags = 0; sa->sa_handler = bestlineOnInt; sigaction(SIGINT,sa,sa+1); sigaction(SIGQUIT,sa,sa+2); for (;;) { if (gotint) { rc = -1; break; } if ((rc = bestlineReadCharacter(fin, seq, sizeof(seq))) == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { if (WaitUntilReady(fin, POLLIN) > 0) { continue; } } if (errno == EINTR) { continue; } else { break; } } if (!(got = rc)) { if (a.len) { break; } else { rc = -1; break; } } if (seq[0] == '\r') { if (HasPendingInput(fin)) { if ((rc = bestlineReadCharacter(fin, seq + 1, sizeof(seq) - 1)) > 0) { if (seq[0] == '\n') { break; } } else { rc = -1; break; } } else { write(fout, "\n", 1); break; } } else if (seq[0] == Ctrl('D')) { break; } else if (seq[0] == '\n') { break; } else if (seq[0] == '\b') { while (a.len && (a.b[a.len - 1] & 0300) == 0200) --a.len; if (a.len) --a.len; } if (!IsControl(seq[0])) { abAppend(&a, seq, got); } } sigaction(SIGQUIT,sa+2,0); sigaction(SIGINT,sa+1,0); if (gotint) { abFree(&a); raise(gotint); errno = EINTR; rc = -1; } if (rc != -1) { return a.b; } else { abFree(&a); return 0; } } static char *GetLine(FILE *in, FILE *out) { if (!IsCharDev(fileno(in))) { return GetLineBlock(in); } else { return GetLineChar(fileno(in), fileno(out)); } } static char *Copy(char *d, const char *s, size_t n) { memcpy(d, s, n); return d + n; } static int CompareStrings(const char *a, const char *b) { size_t i; int x, y, c; for (i = 0;; ++i) { x = bestlineLowercase(a[i] & 255); y = bestlineLowercase(b[i] & 255); if ((c = x - y) || !x) { return c; } } } static const char *FindSubstringReverse(const char *p, size_t n, const char *q, size_t m) { size_t i; if (m <= n) { n -= m; do { for (i = 0; i < m; ++i) { if (p[n + i] != q[i]) { break; } } if (i == m) { return p + n; } } while (n--); } return 0; } static int ParseUnsigned(const char *s, void *e) { int c, x; for (x = 0; (c = *s++);) { if ('0' <= c && c <= '9') { x = Min(c - '0' + x * 10, 32767); } else { break; } } if (e) *(const char **)e = s; return x; } /** * Returns UNICODE CJK Monospace Width of string. * * Control codes and ANSI sequences have a width of zero. We only parse * a limited subset of ANSI here since we don't store ANSI codes in the * linenoiseState::buf, but we do encourage CSI color codes in prompts. */ static size_t GetMonospaceWidth(const char *p, size_t n, char *out_haswides) { int c, d; size_t i, w; struct rune r; char haswides; enum { kAscii, kUtf8, kEsc, kCsi1, kCsi2 } t; for (haswides = r.c = r.n = w = i = 0, t = kAscii; i < n; ++i) { c = p[i] & 255; switch (t) { Whoopsie: t = kAscii; /* fallthrough */ case kAscii: if (c < 0200) { if (c == 033) { t = kEsc; } else { ++w; } } else if (c >= 0300) { t = kUtf8; r = DecodeUtf8(c); } break; case kUtf8: if ((c & 0300) == 0200) { r.c <<= 6; r.c |= c & 077; if (!--r.n) { d = GetMonospaceCharacterWidth(r.c); d = Max(0, d); w += d; haswides |= d > 1; t = kAscii; break; } } else { goto Whoopsie; } break; case kEsc: if (c == '[') { t = kCsi1; } else { t = kAscii; } break; case kCsi1: if (0x20 <= c && c <= 0x2f) { t = kCsi2; } else if (0x40 <= c && c <= 0x7e) { t = kAscii; } else if (!(0x30 <= c && c <= 0x3f)) { goto Whoopsie; } break; case kCsi2: if (0x40 <= c && c <= 0x7e) { t = kAscii; } else if (!(0x20 <= c && c <= 0x2f)) { goto Whoopsie; } break; default: assert(0); } } if (out_haswides) { *out_haswides = haswides; } return w; } static int bestlineIsUnsupportedTerm(void) { size_t i; char *term; static char once, res; if (!once) { if ((term = getenv("TERM"))) { for (i = 0; i < sizeof(kUnsupported) / sizeof(*kUnsupported); i++) { if (!CompareStrings(term,kUnsupported[i])) { res = 1; break; } } } once = 1; } return res; } static int enableRawMode(int fd) { struct termios raw; struct sigaction sa; if (tcgetattr(fd,&orig_termios) != -1) { raw = orig_termios; raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); raw.c_oflag &= ~OPOST; raw.c_iflag |= IUTF8; raw.c_cflag |= CS8; raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; if (tcsetattr(fd,TCSANOW,&raw) != -1) { sa.sa_flags = 0; sa.sa_handler = bestlineOnCont; sigemptyset(&sa.sa_mask); sigaction(SIGCONT,&sa,&orig_cont); sa.sa_handler = bestlineOnWinch; sigaction(SIGWINCH,&sa,&orig_winch); rawmode = fd; gotwinch = 0; gotcont = 0; return 0; } } errno = ENOTTY; return -1; } static void bestlineUnpause(int fd) { if (ispaused) { tcflow(fd, TCOON); ispaused = 0; } } void bestlineDisableRawMode(void) { if (rawmode != -1) { bestlineUnpause(rawmode); sigaction(SIGCONT,&orig_cont,0); sigaction(SIGWINCH,&orig_winch,0); tcsetattr(rawmode,TCSANOW,&orig_termios); rawmode = -1; } } static int bestlineWrite(int fd, const void *p, size_t n) { ssize_t rc; size_t wrote; do { for (;;) { if (gotint) { errno = EINTR; return -1; } if (ispaused) { return 0; } rc = write(fd, p, n); if (rc == -1 && errno == EINTR) { continue; } else if (rc == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { if (WaitUntilReady(fd, POLLOUT) == -1) { if (errno == EINTR) { continue; } else { return -1; } } } else { break; } } if (rc != -1) { wrote = rc; n -= wrote; p = (char *)p + wrote; } else { return -1; } } while (n); return 0; } static int bestlineWriteStr(int fd, const char *p) { return bestlineWrite(fd, p, strlen(p)); } static ssize_t bestlineRead(int fd, char *buf, size_t size, struct bestlineState *l) { size_t got; ssize_t rc; int refreshme; do { refreshme = 0; if (gotint) { errno = EINTR; return -1; } if (gotcont && rawmode != -1) { enableRawMode(rawmode); if (l) refreshme = 1; } if (gotwinch && l) { refreshme = 1; } if (refreshme) bestlineRefreshLine(l); rc = bestlineReadCharacter(fd, buf, size); } while (rc == -1 && errno == EINTR); if (rc != -1) { got = rc; if (got > 0 && l) { memcpy(l->seq[1], l->seq[0], sizeof(l->seq[0])); memset(l->seq[0], 0, sizeof(l->seq[0])); memcpy(l->seq[0], buf, Min(Min(size, got), sizeof(l->seq[0]) - 1)); } } return rc; } /** * Returns number of columns in current terminal. * * 1. Checks COLUMNS environment variable (set by Emacs) * 2. Tries asking termios (works for pseudoteletypewriters) * 3. Falls back to inband signalling (works w/ pipe or serial) * 4. Otherwise we conservatively assume 80 columns * * @param ws should be initialized by caller to zero before first call * @param ifd is input file descriptor * @param ofd is output file descriptor * @return window size */ static struct winsize GetTerminalSize(struct winsize ws, int ifd, int ofd) { int x; ssize_t n; char *p, *s, b[16]; ioctl(ofd, TIOCGWINSZ, &ws); if ((!ws.ws_row && (s = getenv("ROWS")) && (x = ParseUnsigned(s, 0)))) { ws.ws_row = x; } if ((!ws.ws_col && (s = getenv("COLUMNS")) && (x = ParseUnsigned(s, 0)))) { ws.ws_col = x; } if (((!ws.ws_col || !ws.ws_row) && bestlineRead(ifd,0,0,0) != -1 && bestlineWriteStr(ofd, "\0337" /* save position */ "\033[9979;9979H" /* move cursor to bottom right corner */ "\033[6n" /* report position */ "\0338") != -1 && /* restore position */ (n = bestlineRead(ifd,b,sizeof(b),0)) != -1 && n && b[0] == 033 && b[1] == '[' && b[n - 1] == 'R')) { p = b+2; if ((x = ParseUnsigned(p,&p))) ws.ws_row = x; if (*p++ == ';' && (x = ParseUnsigned(p,0))) ws.ws_col = x; } if (!ws.ws_col) ws.ws_col = 80; if (!ws.ws_row) ws.ws_row = 24; return ws; } /* Clear the screen. Used to handle ctrl+l */ void bestlineClearScreen(int fd) { bestlineWriteStr(fd, "\033[H" /* move cursor to top left corner */ "\033[2J"); /* erase display */ } static void bestlineBeep(void) { /* THE TERMINAL BELL IS DEAD - HISTORY HAS KILLED IT */ } static char bestlineGrow(struct bestlineState *ls, size_t n) { char *p; size_t m; m = ls->buflen; if (m >= n) return 1; do m += m >> 1; while (m < n); if (!(p = (char *)realloc(ls->buf, m * sizeof(*ls->buf)))) return 0; ls->buf = p; ls->buflen = m; return 1; } /* This is an helper function for bestlineEdit() and is called when the * user types the key in order to complete the string currently in the * input. * * The state of the editing is encapsulated into the pointed bestlineState * structure as described in the structure definition. */ static ssize_t bestlineCompleteLine(struct bestlineState *ls, char *seq, int size) { ssize_t nread; size_t i, n, stop; bestlineCompletions lc; struct bestlineState saved; nread=0; memset(&lc,0,sizeof(lc)); completionCallback(ls->buf,&lc); if (!lc.len) { bestlineBeep(); } else { i = 0; stop = 0; while (!stop) { /* Show completion or original buffer */ if (i < lc.len) { saved = *ls; ls->len = ls->pos = strlen(lc.cvec[i]); ls->buf = lc.cvec[i]; bestlineRefreshLine(ls); ls->len = saved.len; ls->pos = saved.pos; ls->buf = saved.buf; } else { bestlineRefreshLine(ls); } if ((nread = bestlineRead(ls->ifd,seq,size,ls)) <= 0) { bestlineFreeCompletions(&lc); return -1; } switch (seq[0]) { case '\t': i = (i+1) % (lc.len+1); if (i == lc.len) { bestlineBeep(); } break; default: if (i < lc.len) { n = strlen(lc.cvec[i]); if (bestlineGrow(ls, n + 1)) { memcpy(ls->buf, lc.cvec[i], n + 1); ls->len = ls->pos = n; } } stop = 1; break; } } } bestlineFreeCompletions(&lc); return nread; } static void bestlineEditHistoryGoto(struct bestlineState *l, unsigned i) { size_t n; if (historylen <= 1) return; i = Max(Min(i,historylen-1),0); free(history[historylen - 1 - l->hindex]); history[historylen - 1 - l->hindex] = strdup(l->buf); l->hindex = i; n = strlen(history[historylen - 1 - l->hindex]); bestlineGrow(l, n + 1); n = Min(n, l->buflen - 1); memcpy(l->buf, history[historylen - 1 - l->hindex], n); l->buf[n] = 0; l->len = l->pos = n; bestlineRefreshLine(l); } static void bestlineEditHistoryMove(struct bestlineState *l, int dx) { bestlineEditHistoryGoto(l,l->hindex+dx); } static char *bestlineMakeSearchPrompt(struct abuf *ab, int fail, const char *s, int n) { ab->len=0; abAppendw(ab,'('); if (fail) abAppends(ab,"failed "); abAppends(ab,"reverse-i-search `\033[4m"); abAppend(ab,s,n); abAppends(ab,"\033[24m"); abAppends(ab,s+n); abAppendw(ab,Read32le("') ")); return ab->b; } static int bestlineSearch(struct bestlineState *l, char *seq, int size) { char *p; char isstale; struct abuf ab; struct abuf prompt; unsigned i, j, k, matlen; const char *oldprompt, *q; int rc, fail, added, oldpos, oldindex; if (historylen <= 1) return 0; abInit(&ab); abInit(&prompt); oldpos = l->pos; oldprompt = l->prompt; oldindex = l->hindex; for (fail=matlen=0;;) { l->prompt = bestlineMakeSearchPrompt(&prompt,fail,ab.b,matlen); bestlineRefreshLine(l); fail = 1; added = 0; j = l->pos; i = l->hindex; rc = bestlineRead(l->ifd,seq,size,l); if (rc > 0) { if (seq[0] == Ctrl('?') || seq[0] == Ctrl('H')) { if (ab.len) { --ab.len; matlen = Min(matlen, ab.len); } } else if (seq[0] == Ctrl('R')) { if (j) { --j; } else if (i + 1 < historylen) { ++i; j = strlen(history[historylen - 1 - i]); } } else if (seq[0] == Ctrl('G')) { bestlineEditHistoryGoto(l,oldindex); l->pos = oldpos; rc = 0; break; } else if (IsControl(seq[0])) { /* only sees canonical c0 */ break; } else { abAppend(&ab,seq,rc); added = rc; } } else { break; } isstale = 0; while (i < historylen) { p = history[historylen - 1 - i]; k = strlen(p); if (!isstale) { j = Min(k, j + ab.len); } else { isstale = 0; j = k; } if ((q = FindSubstringReverse(p, j, ab.b, ab.len))) { bestlineEditHistoryGoto(l,i); l->pos = q - p; fail = 0; if (added) { matlen += added; added = 0; } break; } else { isstale = 1; ++i; } } } l->prompt = oldprompt; bestlineRefreshLine(l); abFree(&prompt); abFree(&ab); bestlineRefreshLine(l); return rc; } static void bestlineRingFree(void) { size_t i; for (i = 0; i < BESTLINE_MAX_RING; ++i) { if (ring.p[i]) { free(ring.p[i]); ring.p[i] = 0; } } } static void bestlineRingPush(const char *p, size_t n) { char *q; if (!n) return; if (!(q = (char *)malloc(n + 1))) return; ring.i = (ring.i + 1) % BESTLINE_MAX_RING; free(ring.p[ring.i]); ring.p[ring.i] = (char *)memcpy(q, p, n); ring.p[ring.i][n] = 0; } static void bestlineRingRotate(void) { size_t i; for (i = 0; i < BESTLINE_MAX_RING; ++i) { ring.i = (ring.i - 1) % BESTLINE_MAX_RING; if (ring.p[ring.i]) break; } } static char *bestlineRefreshHints(struct bestlineState *l) { char *hint; struct abuf ab; const char *ansi1 = "\033[90m", *ansi2 = "\033[39m"; if (!hintsCallback) return 0; if (!(hint = hintsCallback(l->buf, &ansi1, &ansi2))) return 0; abInit(&ab); if (ansi1) abAppends(&ab, ansi1); abAppends(&ab, hint); if (ansi2) abAppends(&ab, ansi2); if (freeHintsCallback) freeHintsCallback(hint); return ab.b; } static size_t Backward(struct bestlineState *l, size_t pos) { if (pos) { do --pos; while (pos && (l->buf[pos] & 0300) == 0200); } return pos; } static int bestlineEditMirrorLeft(struct bestlineState *l, int res[2]) { unsigned c, pos, left, right, depth, index; if ((pos = Backward(l, l->pos))) { right = GetUtf8(l->buf + pos, l->len - pos).c; if ((left = bestlineMirrorLeft(right))) { depth = 0; index = pos; do { pos = Backward(l, pos); c = GetUtf8(l->buf + pos, l->len - pos).c; if (c == right) { ++depth; } else if (c == left) { if (depth) { --depth; } else { res[0] = pos; res[1] = index; return 0; } } } while (pos); } } return -1; } static int bestlineEditMirrorRight(struct bestlineState *l, int res[2]) { struct rune rune; unsigned pos, left, right, depth, index; pos = l->pos; rune = GetUtf8(l->buf + pos, l->len - pos); left = rune.c; if ((right = bestlineMirrorRight(left))) { depth = 0; index = pos; do { pos += rune.n; rune = GetUtf8(l->buf + pos, l->len - pos); if (rune.c == left) { ++depth; } else if (rune.c == right) { if (depth) { --depth; } else { res[0] = index; res[1] = pos; return 0; } } } while (pos + rune.n < l->len); } return -1; } static int bestlineEditMirror(struct bestlineState *l, int res[2]) { int rc; rc = bestlineEditMirrorLeft(l, res); if (rc == -1) rc = bestlineEditMirrorRight(l, res); return rc; } static void bestlineRefreshLineImpl(struct bestlineState *l, int force) { char *hint; char flipit; char hasflip; char haswides; struct abuf ab; const char *buf; struct rune rune; struct winsize oldsize; int fd, plen, rows, len, pos; unsigned x, xn, yn, width, pwidth; int i, t, cx, cy, tn, resized, flip[2]; /* * synchonize the i/o state */ if (ispaused) { if (force) { bestlineUnpause(l->ofd); } else { return; } } if (!force && HasPendingInput(l->ifd)) { l->dirty = 1; return; } oldsize = l->ws; if ((resized = gotwinch) && rawmode != -1) { gotwinch = 0; l->ws = GetTerminalSize(l->ws, l->ifd, l->ofd); } hasflip = !l->final && !bestlineEditMirror(l, flip); StartOver: fd = l->ofd; buf = l->buf; pos = l->pos; len = l->len; xn = l->ws.ws_col; yn = l->ws.ws_row; plen = strlen(l->prompt); pwidth = GetMonospaceWidth(l->prompt, plen, 0); width = GetMonospaceWidth(buf, len, &haswides); /* * handle the case where the line is larger than the whole display * gnu readline actually isn't able to deal with this situation!!! * we kludge xn to address the edge case of wide chars on the edge */ for (tn = xn - haswides * 2;;) { if (pwidth + width + 1 < tn * yn) break; /* we're fine */ if (!len || width < 2) break; /* we can't do anything */ if (pwidth + 2 > tn * yn) break; /* we can't do anything */ if (pos > len / 2) { /* hide content on the left if we're editing on the right */ rune = GetUtf8(buf, len); buf += rune.n; len -= rune.n; pos -= rune.n; } else { /* hide content on the right if we're editing on left */ t = len; while (len && (buf[len - 1] & 0300) == 0200) --len; if (len) --len; rune = GetUtf8(buf + len, t - len); } if ((t = GetMonospaceCharacterWidth(rune.c)) > 0) { width -= t; } } pos = Max(0, Min(pos, len)); /* * now generate the terminal codes to update the line * * since we support unlimited lines it's important that we don't * clear the screen before we draw the screen. doing that causes * flickering. the key with terminals is to overwrite cells, and * then use \e[K and \e[J to clear everything else. * * we make the assumption that prompts and hints may contain ansi * sequences, but the buffer does not. * * we need to handle the edge case where a wide character like 度 * might be at the edge of the window, when there's one cell left. * so we can't use division based on string width to compute the * coordinates and have to track it as we go. */ cy = -1; cx = -1; rows = 1; abInit(&ab); abAppendw(&ab, '\r'); /* start of line */ if (l->rows - l->oldpos - 1 > 0) { abAppends(&ab, "\033["); abAppendu(&ab, l->rows - l->oldpos - 1); abAppendw(&ab, 'A'); /* cursor up clamped */ } abAppends(&ab, l->prompt); x = pwidth; for (i = 0; i < len; i += rune.n) { rune = GetUtf8(buf + i, len - i); if (x && x + rune.n > xn) { if (cy >= 0) ++cy; if (x < xn) { abAppends(&ab, "\033[K"); /* clear line forward */ } abAppends(&ab, "\r" /* start of line */ "\n"); /* cursor down unclamped */ ++rows; x = 0; } if (i == pos) { cy = 0; cx = x; } if (maskmode) { abAppendw(&ab, '*'); } else { flipit = hasflip && (i == flip[0] || i == flip[1]); if (flipit) abAppends(&ab, "\033[1m"); abAppendw(&ab, EncodeUtf8(rune.c)); if (flipit) abAppends(&ab, "\033[22m"); } t = GetMonospaceCharacterWidth(rune.c); t = Max(0, t); x += t; } if (!l->final && (hint = bestlineRefreshHints(l))) { if (GetMonospaceWidth(hint, strlen(hint), 0) < xn - x) { if (cx < 0) { cx = x; } abAppends(&ab, hint); } free(hint); } abAppendw(&ab, Read32le("\033[J")); /* erase display forwards */ /* * if we are at the very end of the screen with our prompt, we need * to emit a newline and move the prompt to the first column. */ if (pos && pos == len && x >= xn) { abAppendw(&ab, Read32le("\n\r\0")); ++rows; } /* * move cursor to right position */ if (cy > 0) { abAppends(&ab, "\033["); abAppendu(&ab, cy); abAppendw(&ab, 'A'); /* cursor up */ } if (cx > 0) { abAppendw(&ab, Read32le("\r\033[")); abAppendu(&ab, cx); abAppendw(&ab, 'C'); /* cursor right */ } else if (!cx) { abAppendw(&ab, '\r'); /* start */ } /* * now get ready to progress state * we use a mostly correct kludge when the tty resizes */ l->rows = rows; if (resized && oldsize.ws_col > l->ws.ws_col) { resized = 0; abFree(&ab); goto StartOver; } l->dirty = 0; l->oldpos = Max(0, cy); /* * send codes to terminal */ bestlineWrite(fd, ab.b, ab.len); abFree(&ab); } static void bestlineRefreshLine(struct bestlineState *l) { bestlineRefreshLineImpl(l, 0); } static void bestlineRefreshLineForce(struct bestlineState *l) { bestlineRefreshLineImpl(l, 1); } static void bestlineEditInsert(struct bestlineState *l, const char *p, size_t n) { if (!bestlineGrow(l, l->len + n + 1)) return; memmove(l->buf + l->pos + n, l->buf + l->pos, l->len - l->pos); memcpy(l->buf + l->pos, p, n); l->pos += n; l->len += n; l->buf[l->len] = 0; bestlineRefreshLine(l); } static void bestlineEditHome(struct bestlineState *l) { l->pos = 0; bestlineRefreshLine(l); } static void bestlineEditEnd(struct bestlineState *l) { l->pos = l->len; bestlineRefreshLine(l); } static void bestlineEditUp(struct bestlineState *l) { bestlineEditHistoryMove(l,BESTLINE_HISTORY_PREV); } static void bestlineEditDown(struct bestlineState *l) { bestlineEditHistoryMove(l,BESTLINE_HISTORY_NEXT); } static void bestlineEditBof(struct bestlineState *l) { bestlineEditHistoryMove(l,BESTLINE_HISTORY_FIRST); } static void bestlineEditEof(struct bestlineState *l) { bestlineEditHistoryMove(l,BESTLINE_HISTORY_LAST); } static void bestlineEditRefresh(struct bestlineState *l) { bestlineClearScreen(l->ofd); bestlineRefreshLine(l); } static size_t Forward(struct bestlineState *l, size_t pos) { return pos + GetUtf8(l->buf + pos, l->len - pos).n; } static size_t Backwards(struct bestlineState *l, size_t pos, char pred(unsigned)) { size_t i; struct rune r; while (pos) { i = Backward(l, pos); r = GetUtf8(l->buf + i, l->len - i); if (pred(r.c)) { pos = i; } else { break; } } return pos; } static size_t Forwards(struct bestlineState *l, size_t pos, char pred(unsigned)) { struct rune r; while (pos < l->len) { r = GetUtf8(l->buf + pos, l->len - pos); if (pred(r.c)) { pos += r.n; } else { break; } } return pos; } static size_t ForwardWord(struct bestlineState *l, size_t pos) { pos = Forwards(l, pos, bestlineIsSeparator); pos = Forwards(l, pos, bestlineNotSeparator); return pos; } static size_t BackwardWord(struct bestlineState *l, size_t pos) { pos = Backwards(l, pos, bestlineIsSeparator); pos = Backwards(l, pos, bestlineNotSeparator); return pos; } static size_t EscapeWord(struct bestlineState *l, size_t i) { size_t j; struct rune r; for (; i && i < l->len; i += r.n) { if (i < l->len) { r = GetUtf8(l->buf + i, l->len - i); if (bestlineIsSeparator(r.c)) break; } if ((j = i)) { do --j; while (j && (l->buf[j] & 0300) == 0200); r = GetUtf8(l->buf + j, l->len - j); if (bestlineIsSeparator(r.c)) break; } } return i; } static void bestlineEditLeft(struct bestlineState *l) { l->pos = Backward(l, l->pos); bestlineRefreshLine(l); } static void bestlineEditRight(struct bestlineState *l) { if (l->pos == l->len) return; do l->pos++; while (l->pos < l->len && (l->buf[l->pos] & 0300) == 0200); bestlineRefreshLine(l); } static void bestlineEditLeftWord(struct bestlineState *l) { l->pos = BackwardWord(l, l->pos); bestlineRefreshLine(l); } static void bestlineEditRightWord(struct bestlineState *l) { l->pos = ForwardWord(l, l->pos); bestlineRefreshLine(l); } static void bestlineEditLeftExpr(struct bestlineState *l) { int mark[2]; l->pos = Backwards(l, l->pos, bestlineIsXeparator); if (!bestlineEditMirrorLeft(l, mark)) { l->pos = mark[0]; } else { l->pos = Backwards(l, l->pos, bestlineNotSeparator); } bestlineRefreshLine(l); } static void bestlineEditRightExpr(struct bestlineState *l) { int mark[2]; l->pos = Forwards(l, l->pos, bestlineIsXeparator); if (!bestlineEditMirrorRight(l, mark)) { l->pos = Forward(l, mark[1]); } else { l->pos = Forwards(l, l->pos, bestlineNotSeparator); } bestlineRefreshLine(l); } static void bestlineEditDelete(struct bestlineState *l) { size_t i; if (l->pos == l->len) return; i = Forward(l, l->pos); memmove(l->buf+l->pos, l->buf+i, l->len-i+1); l->len -= i - l->pos; bestlineRefreshLine(l); } static void bestlineEditRubout(struct bestlineState *l) { size_t i; if (!l->pos) return; i = Backward(l, l->pos); memmove(l->buf+i, l->buf+l->pos, l->len-l->pos+1); l->len -= l->pos - i; l->pos = i; bestlineRefreshLine(l); } static void bestlineEditDeleteWord(struct bestlineState *l) { size_t i; if (l->pos == l->len) return; i = ForwardWord(l, l->pos); bestlineRingPush(l->buf + l->pos, i - l->pos); memmove(l->buf + l->pos, l->buf + i, l->len - i + 1); l->len -= i - l->pos; bestlineRefreshLine(l); } static void bestlineEditRuboutWord(struct bestlineState *l) { size_t i; if (!l->pos) return; i = BackwardWord(l, l->pos); bestlineRingPush(l->buf + i, l->pos - i); memmove(l->buf + i, l->buf + l->pos, l->len - l->pos + 1); l->len -= l->pos - i; l->pos = i; bestlineRefreshLine(l); } static void bestlineEditXlatWord(struct bestlineState *l, unsigned xlat(unsigned)) { unsigned c; size_t i, j; struct rune r; struct abuf ab; abInit(&ab); i = Forwards(l, l->pos, bestlineIsSeparator); for (j = i; j < l->len; j += r.n) { r = GetUtf8(l->buf + j, l->len - j); if (bestlineIsSeparator(r.c)) break; if ((c = xlat(r.c)) != r.c) { abAppendw(&ab, EncodeUtf8(c)); } else { /* avoid canonicalization */ abAppend(&ab, l->buf + j, r.n); } } if (ab.len && bestlineGrow(l, i + ab.len + l->len - j + 1)) { l->pos = i + ab.len; abAppend(&ab, l->buf + j, l->len - j); l->len = i + ab.len; memcpy(l->buf + i, ab.b, ab.len + 1); bestlineRefreshLine(l); } abFree(&ab); } static void bestlineEditLowercaseWord(struct bestlineState *l) { bestlineEditXlatWord(l, bestlineLowercase); } static void bestlineEditUppercaseWord(struct bestlineState *l) { bestlineEditXlatWord(l, bestlineUppercase); } static void bestlineEditCapitalizeWord(struct bestlineState *l) { iscapital = 0; bestlineEditXlatWord(l, Capitalize); } static void bestlineEditKillLeft(struct bestlineState *l) { size_t diff, old_pos; bestlineRingPush(l->buf, l->pos); old_pos = l->pos; l->pos = 0; diff = old_pos - l->pos; memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); l->len -= diff; bestlineRefreshLine(l); } static void bestlineEditKillRight(struct bestlineState *l) { bestlineRingPush(l->buf + l->pos, l->len - l->pos); l->buf[l->pos] = '\0'; l->len = l->pos; bestlineRefreshLine(l); } static void bestlineEditYank(struct bestlineState *l) { char *p; size_t n; if (!ring.p[ring.i]) return; n = strlen(ring.p[ring.i]); if (!bestlineGrow(l, l->len + n + 1)) return; if (!(p = (char *)malloc(l->len - l->pos + 1))) return; memcpy(p, l->buf + l->pos, l->len - l->pos + 1); memcpy(l->buf + l->pos, ring.p[ring.i], n); memcpy(l->buf + l->pos + n, p, l->len - l->pos + 1); free(p); l->yi = l->pos; l->yj = l->pos + n; l->pos += n; l->len += n; bestlineRefreshLine(l); } static void bestlineEditRotate(struct bestlineState *l) { if ((l->seq[1][0] == Ctrl('Y') || (l->seq[1][0] == 033 && l->seq[1][1] == 'y'))) { if (l->yi < l->len && l->yj <= l->len) { memmove(l->buf + l->yi, l->buf + l->yj, l->len - l->yj + 1); l->len -= l->yj - l->yi; l->pos -= l->yj - l->yi; } bestlineRingRotate(); bestlineEditYank(l); } } static void bestlineEditTranspose(struct bestlineState *l) { char *q, *p; size_t a, b, c; b = l->pos; if (b == l->len) --b; a = Backward(l, b); c = Forward(l, b); if (!(a < b && b < c)) return; p = q = (char *)malloc(c - a); p = Copy(p, l->buf + b, c - b); p = Copy(p, l->buf + a, b - a); assert((size_t)(p - q) == c - a); memcpy(l->buf + a, q, p - q); l->pos = c; free(q); bestlineRefreshLine(l); } static void bestlineEditTransposeWords(struct bestlineState *l) { char *q, *p; size_t i, pi, xi, xj, yi, yj; i = l->pos; if (i == l->len) { i = Backwards(l, i, bestlineIsSeparator); i = Backwards(l, i, bestlineNotSeparator); } pi = EscapeWord(l, i); xj = Backwards(l, pi, bestlineIsSeparator); xi = Backwards(l, xj, bestlineNotSeparator); yi = Forwards(l, pi, bestlineIsSeparator); yj = Forwards(l, yi, bestlineNotSeparator); if (!(xi < xj && xj < yi && yi < yj)) return; p = q = (char *)malloc(yj - xi); p = Copy(p, l->buf + yi, yj - yi); p = Copy(p, l->buf + xj, yi - xj); p = Copy(p, l->buf + xi, xj - xi); assert((size_t)(p - q) == yj - xi); memcpy(l->buf + xi, q, p - q); l->pos = yj; free(q); bestlineRefreshLine(l); } static void bestlineEditSqueeze(struct bestlineState *l) { size_t i, j; i = Backwards(l, l->pos, bestlineIsSeparator); j = Forwards(l, l->pos, bestlineIsSeparator); if (!(i < j)) return; memmove(l->buf + i, l->buf + j, l->len - j + 1); l->len -= j - i; l->pos = i; bestlineRefreshLine(l); } static void bestlineEditMark(struct bestlineState *l) { l->mark = l->pos; } static void bestlineEditGoto(struct bestlineState *l) { if (l->mark > l->len) return; l->pos = Min(l->mark, l->len); bestlineRefreshLine(l); } static size_t bestlineEscape(char *d, const char *s, size_t n) { char *p; size_t i; unsigned c, w, l; for (p = d, l = i = 0; i < n; ++i) { switch ((c = s[i] & 255)) { Case('\a', w = Read16le("\\a")); Case('\b', w = Read16le("\\b")); Case('\t', w = Read16le("\\t")); Case('\n', w = Read16le("\\n")); Case('\v', w = Read16le("\\v")); Case('\f', w = Read16le("\\f")); Case('\r', w = Read16le("\\r")); Case('"', w = Read16le("\\\"")); Case('\'', w = Read16le("\\\'")); Case('\\', w = Read16le("\\\\")); default: if (c <= 0x1F || c == 0x7F || (c == '?' && l == '?')) { w = Read16le("\\x"); w |= "0123456789abcdef"[(c & 0xF0) >> 4] << 020; w |= "0123456789abcdef"[(c & 0x0F) >> 0] << 030; } else { w = c; } break; } p[0] = (w & 0x000000ff) >> 000; p[1] = (w & 0x0000ff00) >> 010; p[2] = (w & 0x00ff0000) >> 020; p[3] = (w & 0xff000000) >> 030; p += (Bsr(w) >> 3) + 1; l = w; } return p - d; } static void bestlineEditInsertEscape(struct bestlineState *l) { size_t m; ssize_t n; char seq[16]; char esc[sizeof(seq) * 4]; if ((n = bestlineRead(l->ifd, seq, sizeof(seq), l)) > 0) { m = bestlineEscape(esc, seq, n); bestlineEditInsert(l, esc, m); } } static void bestlineEditInterrupt(void) { gotint = SIGINT; } static void bestlineEditQuit(void) { gotint = SIGQUIT; } static void bestlineEditSuspend(void) { raise(SIGSTOP); } static void bestlineEditPause(struct bestlineState *l) { tcflow(l->ofd, TCOOFF); ispaused = 1; } static void bestlineEditCtrlq(struct bestlineState *l) { if (ispaused) { bestlineUnpause(l->ofd); bestlineRefreshLineForce(l); } else { bestlineEditInsertEscape(l); } } /** * Moves last item inside current s-expression to outside, e.g. * * (a| b c) * (a| b) c * * The cursor position changes only if a paren is moved before it: * * (a b c |) * (a b) c | * * To accommodate non-LISP languages we connect unspaced outer symbols: * * f(a,| b, g()) * f(a,| b), g() * * Our standard keybinding is ALT-SHIFT-B. */ static void bestlineEditBarf(struct bestlineState *l) { struct rune r; unsigned long w; size_t i, pos, depth = 0; unsigned lhs, rhs, end, *stack = 0; /* go as far right within current s-expr as possible */ for (pos = l->pos;; pos += r.n) { if (pos == l->len) goto Finish; r = GetUtf8(l->buf + pos, l->len - pos); if (depth) { if (r.c == stack[depth - 1]) { --depth; } } else { if ((rhs = bestlineMirrorRight(r.c))) { stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); stack[depth - 1] = rhs; } else if (bestlineMirrorLeft(r.c)) { end = pos; break; } } } /* go back one item */ pos = Backwards(l, pos, bestlineIsXeparator); for (;; pos = i) { if (!pos) goto Finish; i = Backward(l, pos); r = GetUtf8(l->buf + i, l->len - i); if (depth) { if (r.c == stack[depth - 1]) { --depth; } } else { if ((lhs = bestlineMirrorLeft(r.c))) { stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); stack[depth - 1] = lhs; } else if (bestlineIsSeparator(r.c)) { break; } } } pos = Backwards(l, pos, bestlineIsXeparator); /* now move the text */ r = GetUtf8(l->buf + end, l->len - end); memmove(l->buf + pos + r.n, l->buf + pos, end - pos); w = EncodeUtf8(r.c); for (i = 0; i < r.n; ++i) { l->buf[pos + i] = w; w >>= 8; } if (l->pos > pos) { l->pos += r.n; } bestlineRefreshLine(l); Finish: free(stack); } /** * Moves first item outside current s-expression to inside, e.g. * * (a| b) c d * (a| b c) d * * To accommodate non-LISP languages we connect unspaced outer symbols: * * f(a,| b), g() * f(a,| b, g()) * * Our standard keybinding is ALT-SHIFT-S. */ static void bestlineEditSlurp(struct bestlineState *l) { char rp[6]; struct rune r; size_t pos, depth = 0; unsigned rhs, point = 0, start = 0, *stack = 0; /* go to outside edge of current s-expr */ for (pos = l->pos; pos < l->len; pos += r.n) { r = GetUtf8(l->buf + pos, l->len - pos); if (depth) { if (r.c == stack[depth - 1]) { --depth; } } else { if ((rhs = bestlineMirrorRight(r.c))) { stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); stack[depth - 1] = rhs; } else if (bestlineMirrorLeft(r.c)) { point = pos; pos += r.n; start = pos; break; } } } /* go forward one item */ pos = Forwards(l, pos, bestlineIsXeparator); for (; pos < l->len ; pos += r.n) { r = GetUtf8(l->buf + pos, l->len - pos); if (depth) { if (r.c == stack[depth - 1]) { --depth; } } else { if ((rhs = bestlineMirrorRight(r.c))) { stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); stack[depth - 1] = rhs; } else if (bestlineIsSeparator(r.c)) { break; } } } /* now move the text */ memcpy(rp, l->buf + point, start - point); memmove(l->buf + point, l->buf + start, pos - start); memcpy(l->buf + pos - (start - point), rp, start - point); bestlineRefreshLine(l); free(stack); } static void bestlineEditRaise(struct bestlineState *l) { (void)l; } /** * Runs bestline engine. * * This function is the core of the line editing capability of bestline. * It expects 'fd' to be already in "raw mode" so that every key pressed * will be returned ASAP to read(). * * The resulting string is put into 'buf' when the user type enter, or * when ctrl+d is typed. * * Returns chomped character count in buf >=0 or -1 on eof / error */ static ssize_t bestlineEdit(int stdin_fd, int stdout_fd, const char *prompt, char **obuf) { ssize_t rc; size_t nread; struct rune rune; char *p, seq[16]; unsigned long long w; struct bestlineState l; memset(&l,0,sizeof(l)); if (!(l.buf = (char *)malloc((l.buflen = 32)))) return -1; l.buf[0] = 0; l.ifd = stdin_fd; l.ofd = stdout_fd; l.prompt = prompt ? prompt : ""; l.ws = GetTerminalSize(l.ws,l.ifd,l.ofd); bestlineHistoryAdd(""); bestlineWriteStr(l.ofd,l.prompt); while (1) { if (l.dirty) bestlineRefreshLineForce(&l); rc = bestlineRead(l.ifd,seq,sizeof(seq),&l); if (rc > 0) { if (seq[0] == Ctrl('R')) { rc = bestlineSearch(&l,seq,sizeof(seq)); if (!rc) continue; } else if (seq[0] == '\t' && completionCallback) { rc = bestlineCompleteLine(&l,seq,sizeof(seq)); if (!rc) continue; } } if (rc > 0) { nread = rc; } else if (!rc && l.len) { nread = 1; seq[0] = '\r'; seq[1] = 0; } else { free(history[--historylen]); history[historylen] = 0; free(l.buf); return -1; } switch (seq[0]) { Case(Ctrl('P'), bestlineEditUp(&l)); Case(Ctrl('E'), bestlineEditEnd(&l)); Case(Ctrl('N'), bestlineEditDown(&l)); Case(Ctrl('A'), bestlineEditHome(&l)); Case(Ctrl('B'), bestlineEditLeft(&l)); Case(Ctrl('@'), bestlineEditMark(&l)); Case(Ctrl('Y'), bestlineEditYank(&l)); Case(Ctrl('Q'), bestlineEditCtrlq(&l)); Case(Ctrl('F'), bestlineEditRight(&l)); Case(Ctrl('\\'), bestlineEditQuit()); Case(Ctrl('S'), bestlineEditPause(&l)); Case(Ctrl('?'), bestlineEditRubout(&l)); Case(Ctrl('H'), bestlineEditRubout(&l)); Case(Ctrl('L'), bestlineEditRefresh(&l)); Case(Ctrl('Z'), bestlineEditSuspend()); Case(Ctrl('U'), bestlineEditKillLeft(&l)); Case(Ctrl('T'), bestlineEditTranspose(&l)); Case(Ctrl('K'), bestlineEditKillRight(&l)); Case(Ctrl('W'), bestlineEditRuboutWord(&l)); case Ctrl('C'): if (bestlineRead(l.ifd,seq,sizeof(seq),&l) != 1) break; switch (seq[0]) { Case(Ctrl('C'), bestlineEditInterrupt()); Case(Ctrl('B'), bestlineEditBarf(&l)); Case(Ctrl('S'), bestlineEditSlurp(&l)); Case(Ctrl('R'), bestlineEditRaise(&l)); default: break; } break; case Ctrl('X'): if (l.seq[1][0] == Ctrl('X')) { bestlineEditGoto(&l); } break; case Ctrl('D'): if (l.len) { bestlineEditDelete(&l); } else { free(history[--historylen]); history[historylen] = 0; free(l.buf); return -1; } break; case '\r': l.final = 1; free(history[--historylen]); history[historylen] = 0; bestlineEditEnd(&l); bestlineRefreshLineForce(&l); if ((p = (char *)realloc(l.buf, l.len + 1))) l.buf = p; *obuf = l.buf; return l.len; case 033: if (nread < 2) break; switch (seq[1]) { Case('<', bestlineEditBof(&l)); Case('>', bestlineEditEof(&l)); Case('B', bestlineEditBarf(&l)); Case('S', bestlineEditSlurp(&l)); Case('R', bestlineEditRaise(&l)); Case('y', bestlineEditRotate(&l)); Case('\\', bestlineEditSqueeze(&l)); Case('b', bestlineEditLeftWord(&l)); Case('f', bestlineEditRightWord(&l)); Case('h', bestlineEditRuboutWord(&l)); Case('d', bestlineEditDeleteWord(&l)); Case('l', bestlineEditLowercaseWord(&l)); Case('u', bestlineEditUppercaseWord(&l)); Case('c', bestlineEditCapitalizeWord(&l)); Case('t', bestlineEditTransposeWords(&l)); Case(Ctrl('B'), bestlineEditLeftExpr(&l)); Case(Ctrl('F'), bestlineEditRightExpr(&l)); Case(Ctrl('H'), bestlineEditRuboutWord(&l)); case '[': if (nread < 3) break; if (seq[2] >= '0' && seq[2] <= '9') { if (nread < 4) break; if (seq[3] == '~') { switch (seq[2]) { Case('1', bestlineEditHome(&l)); /* \e[1~ */ Case('3', bestlineEditDelete(&l)); /* \e[3~ */ Case('4', bestlineEditEnd(&l)); /* \e[4~ */ default: break; } } } else { switch (seq[2]) { Case('A', bestlineEditUp(&l)); Case('B', bestlineEditDown(&l)); Case('C', bestlineEditRight(&l)); Case('D', bestlineEditLeft(&l)); Case('H', bestlineEditHome(&l)); Case('F', bestlineEditEnd(&l)); default: break; } } break; case 'O': if (nread < 3) break; switch (seq[2]) { Case('A', bestlineEditUp(&l)); Case('B', bestlineEditDown(&l)); Case('C', bestlineEditRight(&l)); Case('D', bestlineEditLeft(&l)); Case('H', bestlineEditHome(&l)); Case('F', bestlineEditEnd(&l)); default: break; } break; case 033: if (nread < 3) break; switch (seq[2]) { case '[': if (nread < 4) break; switch (seq[3]) { Case('C', bestlineEditRightExpr(&l)); /* \e\e[C alt-right */ Case('D', bestlineEditLeftExpr(&l)); /* \e\e[D alt-left */ default: break; } break; case 'O': if (nread < 4) break; switch (seq[3]) { Case('C', bestlineEditRightExpr(&l)); /* \e\eOC alt-right */ Case('D', bestlineEditLeftExpr(&l)); /* \e\eOD alt-left */ default: break; } break; default: break; } break; default: break; } break; default: if (!IsControl(seq[0])) { /* only sees canonical c0 */ if (xlatCallback) { rune = GetUtf8(seq,nread); w = EncodeUtf8(xlatCallback(rune.c)); nread = 0; do { seq[nread++] = w; } while ((w >>= 8)); } bestlineEditInsert(&l,seq,nread); } break; } } } void bestlineFree(void *ptr) { free(ptr); } void bestlineHistoryFree(void) { size_t i; for (i = 0; i < BESTLINE_MAX_HISTORY; i++) { if (history[i]) { free(history[i]); history[i] = 0; } } historylen = 0; } static void bestlineAtExit(void) { bestlineDisableRawMode(); bestlineHistoryFree(); bestlineRingFree(); } int bestlineHistoryAdd(const char *line) { char *linecopy; if (!BESTLINE_MAX_HISTORY) return 0; if (historylen && !strcmp(history[historylen-1], line)) return 0; if (!(linecopy = strdup(line))) return 0; if (historylen == BESTLINE_MAX_HISTORY) { free(history[0]); memmove(history,history+1,sizeof(char*)*(BESTLINE_MAX_HISTORY-1)); historylen--; } history[historylen++] = linecopy; return 1; } /** * Saves line editing history to file. * * @return 0 on success, or -1 w/ errno */ int bestlineHistorySave(const char *filename) { FILE *fp; unsigned j; mode_t old_umask; old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); fp = fopen(filename,"w"); umask(old_umask); if (!fp) return -1; chmod(filename,S_IRUSR|S_IWUSR); for (j = 0; j < historylen; j++) { fputs(history[j],fp); fputc('\n',fp); } fclose(fp); return 0; } /** * Loads history from the specified file. * * If the file doesn't exist, zero is returned and this will do nothing. * If the file does exists and the operation succeeded zero is returned * otherwise on error -1 is returned. * * @return 0 on success, or -1 w/ errno */ int bestlineHistoryLoad(const char *filename) { char **h; int rc, fd, err; size_t i, j, k, n, t; char *m, *e, *p, *q, *f, *s; err = errno, rc = 0; if (!BESTLINE_MAX_HISTORY) return 0; if (!(h = (char**)calloc(2*BESTLINE_MAX_HISTORY,sizeof(char*)))) return -1; if ((fd = open(filename,O_RDONLY)) != -1) { if ((n = GetFdSize(fd))) { if ((m = (char *)mmap(0,n,PROT_READ,MAP_SHARED,fd,0))!=MAP_FAILED) { for (i = 0, e = (p = m) + n; p < e; p = f + 1) { if (!(q = (char *)memchr(p, '\n', e - p))) q = e; for (f = q; q > p; --q) { if (q[-1] != '\n' && q[-1] != '\r') break; } if (q > p) { h[i * 2 + 0] = p; h[i * 2 + 1] = q; i = (i + 1) % BESTLINE_MAX_HISTORY; } } bestlineHistoryFree(); for (j = 0; j < BESTLINE_MAX_HISTORY; ++j) { if (h[(k = (i + j) % BESTLINE_MAX_HISTORY) * 2]) { if ((s = (char *)malloc((t=h[k*2+1]-h[k*2])+1))) { memcpy(s,h[k*2],t),s[t]=0; history[historylen++] = s; } } } munmap(m,n); } else { rc = -1; } } close(fd); } else if (errno == ENOENT) { errno = err; } else { rc = -1; } free(h); return rc; } /** * Reads line interactively. * * This function can be used instead of bestline() in cases where we * know for certain we're dealing with a terminal, which means we can * avoid linking any stdio code. * * @return chomped allocated string of read line or null on eof/error */ char *bestlineRaw(const char *prompt, int infd, int outfd) { char *buf; ssize_t rc; static char once; struct sigaction sa[3]; if (!once) atexit(bestlineAtExit), once = 1; if (enableRawMode(infd) == -1) return 0; buf = 0; gotint = 0; sigemptyset(&sa->sa_mask); sa->sa_flags = 0; sa->sa_handler = bestlineOnInt; sigaction(SIGINT,sa,sa+1); sigaction(SIGQUIT,sa,sa+2); rc = bestlineEdit(infd,outfd,prompt,&buf); bestlineDisableRawMode(); sigaction(SIGQUIT,sa+2,0); sigaction(SIGINT,sa+1,0); if (gotint) { free(buf); buf = 0; raise(gotint); errno = EINTR; rc = -1; } if (rc != -1) { bestlineWriteStr(outfd,"\n"); return buf; } else { free(buf); return 0; } } /** * Reads line intelligently. * * The high level function that is the main API of the bestline library. * This function checks if the terminal has basic capabilities, just checking * for a blacklist of inarticulate terminals, and later either calls the line * editing function or uses dummy fgets() so that you will be able to type * something even in the most desperate of the conditions. * * @param prompt is printed before asking for input if we have a term * and this may be set to empty or null to disable and prompt may * contain ansi escape sequences, color, utf8, etc. * @return chomped allocated string of read line or null on eof/error */ char *bestline(const char *prompt) { if (prompt && *prompt && (strchr(prompt, '\n') || strchr(prompt, '\t') || strchr(prompt + 1, '\r'))) { errno = EINVAL; return 0; } if ((!isatty(fileno(stdin)) || !isatty(fileno(stdout)))) { if (prompt && *prompt && (IsCharDev(fileno(stdin)) && IsCharDev(fileno(stdout)))) { fputs(prompt,stdout); fflush(stdout); } return GetLine(stdin, stdout); } else if (bestlineIsUnsupportedTerm()) { if (prompt && *prompt) { fputs(prompt,stdout); fflush(stdout); } return GetLine(stdin, stdout); } else { fflush(stdout); return bestlineRaw(prompt,fileno(stdin),fileno(stdout)); } } /** * Reads line intelligently w/ history, e.g. * * // see ~/.foo_history * main() { * char *line; * while ((line = bestlineWithHistory("IN> ", "foo"))) { * printf("OUT> %s\n", line); * free(line); * } * } * * @param prompt is printed before asking for input if we have a term * and this may be set to empty or null to disable and prompt may * contain ansi escape sequences, color, utf8, etc. * @param prog is name of your app, used to generate history filename * however if it contains a slash / dot then we'll assume prog is * the history filename which as determined by the caller * @return chomped allocated string of read line or null on eof/error */ char *bestlineWithHistory(const char *prompt, const char *prog) { char *line; struct abuf path; const char *a, *b; abInit(&path); if (prog) { if (strchr(prog, '/') || strchr(prog, '.')) { abAppends(&path, prog); } else { b = ""; if (!(a = getenv("HOME"))) { if (!(a = getenv("HOMEDRIVE")) || !(b = getenv("HOMEPATH"))) { a = ""; } } if (*a) { abAppends(&path, a); abAppends(&path, b); abAppendw(&path, '/'); } abAppendw(&path, '.'); abAppends(&path, prog); abAppends(&path, "_history"); } } if (path.len) { bestlineHistoryLoad(path.b); } line = bestline(prompt); if (path.len && line && *line) { /* history here is inefficient but helpful when the user has multiple * repls open at the same time, so history propagates between them */ bestlineHistoryLoad(path.b); bestlineHistoryAdd(line); bestlineHistorySave(path.b); } abFree(&path); return line; } /** * Registers tab completion callback. */ void bestlineSetCompletionCallback(bestlineCompletionCallback *fn) { completionCallback = fn; } /** * Registers hints callback. * * Register a hits function to be called to show hits to the user at the * right of the prompt. */ void bestlineSetHintsCallback(bestlineHintsCallback *fn) { hintsCallback = fn; } /** * Sets free hints callback. * * This registers a function to free the hints returned by the hints * callback registered with bestlineSetHintsCallback(). */ void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *fn) { freeHintsCallback = fn; } /** * Sets character translation callback. */ void bestlineSetXlatCallback(bestlineXlatCallback *fn) { xlatCallback = fn; } /** * Adds completion. * * This function is used by the callback function registered by the user * in order to add completion options given the input string when the * user typed . See the example.c source code for a very easy to * understand example. */ void bestlineAddCompletion(bestlineCompletions *lc, const char *str) { size_t len; char *copy, **cvec; if ((copy = (char *)malloc((len = strlen(str))+1))) { memcpy(copy,str,len+1); if ((cvec = (char **)realloc(lc->cvec,(lc->len+1)*sizeof(*lc->cvec)))) { lc->cvec = cvec; lc->cvec[lc->len++] = copy; } else { free(copy); } } } /** * Frees list of completion option populated by bestlineAddCompletion(). */ void bestlineFreeCompletions(bestlineCompletions *lc) { size_t i; for (i = 0; i < lc->len; i++) free(lc->cvec[i]); if (lc->cvec) free(lc->cvec); } /** * Enables "mask mode". * * When it is enabled, instead of the input that the user is typing, the * terminal will just display a corresponding number of asterisks, like * "****". This is useful for passwords and other secrets that should * not be displayed. * * @see bestlineMaskModeDisable() */ void bestlineMaskModeEnable(void) { maskmode = 1; } /** * Disables "mask mode". */ void bestlineMaskModeDisable(void) { maskmode = 0; } muon-v0.3.0/subprojects/bestline/.gitignore0000644000175000017500000000016514674562002020006 0ustar buildbuild*.o ape.lds history.txt cosmopolitan.h cosmopolitan.a bestline_example bestline_example.com bestline_example.com.dbg muon-v0.3.0/subprojects/bestline/bestline.h0000644000175000017500000000261014674562002017771 0ustar buildbuild#pragma once typedef struct bestlineCompletions { unsigned long len; char **cvec; } bestlineCompletions; typedef void (bestlineCompletionCallback)(const char *, bestlineCompletions *); typedef char *(bestlineHintsCallback)(const char *, const char **, const char **); typedef void (bestlineFreeHintsCallback)(void *); typedef unsigned (bestlineXlatCallback)(unsigned); void bestlineSetCompletionCallback(bestlineCompletionCallback *); void bestlineSetHintsCallback(bestlineHintsCallback *); void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *); void bestlineAddCompletion(bestlineCompletions *, const char *); void bestlineSetXlatCallback(bestlineXlatCallback *); char *bestline(const char *); char *bestlineRaw(const char *, int, int); char *bestlineWithHistory(const char *, const char *); int bestlineHistoryAdd(const char *); int bestlineHistorySave(const char *); int bestlineHistoryLoad(const char *); void bestlineFreeCompletions(bestlineCompletions *); void bestlineHistoryFree(void); void bestlineClearScreen(int); void bestlineMaskModeEnable(void); void bestlineMaskModeDisable(void); void bestlineDisableRawMode(void); void bestlineFree(void *); char bestlineIsSeparator(unsigned); char bestlineNotSeparator(unsigned); char bestlineIsXeparator(unsigned); unsigned bestlineUppercase(unsigned); unsigned bestlineLowercase(unsigned); long bestlineReadCharacter(int, char *, unsigned long); muon-v0.3.0/subprojects/bestline/README.md0000644000175000017500000002132414674562002017275 0ustar buildbuild# Bestline Library for interactive pseudoteletypewriter command sessions using ANSI Standard X3.64 control sequences ![Bestline Demo Video GIF](bestline.gif) ## Overview This is a single-file no-dependencies C or C++ library that makes it as easy as possible to display a command prompt that asks the user for input. It supports Emacs editing shortcuts, history search, completion / hint callback, and utf-8 editing under a bsd-2 license. Bestline is a fork of linenoise (a popular readline alternative) that fixes its bugs and adds the missing features while reducing binary footprint (surprisingly) by removing bloated dependencies, which means you can finally have a permissively-licensed command prompt w/ a 38kb footprint that's nearly as good as gnu readline. ``` $ CC="cc -s -static -Os -DNDEBUG" make $ ls -hal bestline_example -rwxr-xr-x 1 jart jart 38K Sep 19 21:41 bestline_example ``` # Example This example will save history to `~/.foo_history`. It's 50kb when statically linked with Cosmopolitan Libc. ```c #include #include "bestline.h" main() { char *line; while ((line = bestlineWithHistory("IN> ", "foo"))) { fputs("OUT> ", stdout); fputs(line, stdout); fputs("\n", stdout); free(line); } } ``` ## Shortcuts ``` CTRL-E END CTRL-A START CTRL-B BACK CTRL-F FORWARD CTRL-L CLEAR CTRL-H BACKSPACE CTRL-D DELETE CTRL-Y YANK CTRL-D EOF (IF EMPTY) CTRL-N NEXT HISTORY CTRL-P PREVIOUS HISTORY CTRL-R SEARCH HISTORY CTRL-G CANCEL SEARCH ALT-< BEGINNING OF HISTORY ALT-> END OF HISTORY ALT-F FORWARD WORD ALT-B BACKWARD WORD CTRL-ALT-F FORWARD EXPR CTRL-ALT-B BACKWARD EXPR ALT-RIGHT FORWARD EXPR ALT-LEFT BACKWARD EXPR CTRL-K KILL LINE FORWARDS CTRL-U KILL LINE BACKWARDS ALT-H KILL WORD BACKWARDS CTRL-W KILL WORD BACKWARDS CTRL-ALT-H KILL WORD BACKWARDS ALT-D KILL WORD FORWARDS ALT-Y ROTATE KILL RING AND YANK AGAIN ALT-\ SQUEEZE ADJACENT WHITESPACE CTRL-T TRANSPOSE ALT-T TRANSPOSE WORD ALT-U UPPERCASE WORD ALT-L LOWERCASE WORD ALT-C CAPITALIZE WORD CTRL-C INTERRUPT PROCESS CTRL-Z SUSPEND PROCESS CTRL-\ QUIT PROCESS CTRL-S PAUSE OUTPUT CTRL-Q UNPAUSE OUTPUT (IF PAUSED) CTRL-Q ESCAPED INSERT CTRL-SPACE SET MARK CTRL-X CTRL-X GOTO MARK CTRL-Z SUSPEND PROCESS ``` ### ProTip Remap CAPS LOCK to CTRL. ## Requirements You have to use an ANSI UTF-8 terminal that supports VT100 codes. ## Changes Here's what we've changed compared to [linenoise](https://github.com/antirez/linenoise/): - Remove bell - Add kill ring - Fix flickering - Add UTF-8 editing - Add CTRL-R search - React to terminal resizing - Don't generate .data section - Support terminal flow control - Make history loading 10x faster - Make multiline mode the only mode - Support unlimited input line length - Accommodate O_NONBLOCK file descriptors - Restore raw mode on process foregrounding - Make source code compatible with C++ compilers - Fix corruption issues by using generalized parsing - Implement nearly all GNU readline editing shortcuts - Remove heavyweight dependencies like printf/sprintf - Remove ISIG→^C→EAGAIN hack and catch signals properly - Support running on Windows in MinTTY or CMD.EXE on Win10+ - Support diacritics, русский, Ελληνικά, 漢字, 仮名, 한글 ## Readability This codebase aims to follow in Antirez's tradition of writing beautiful programs, that solve extremely difficult technical problems in the most elegant way possible. The original Linenoise source code is sort of like an old Delorean where it's simple and beautiful, but has a lot of things broken about it that need to be fixed, which gives you plenty of reasons to sit down and fix things to fully appreciate its beauty. There are, however, some differences in style. Antirez generally optimizes for fewer lines of code even if it makes the binary footprint larger and with poor edge case handling and cultural biases presumably to preserve its accessibility and value as an educational tool. For example, one of the biggest issues with Linenoise, was that pressing the wrong key on the keyboard would mess with the state and garble input since it didn't actually parse ansi codes or even multibyte characters. While this project has addressed many of Linenoise's shortcomings, we've sought to do it in a way that carries on the Antirez tradition of simple elegant hackable code. It is our hope that should you find opportunities for improvement in this codebase that you'll find it equally pleasurable to work with. ## Portability Bestline is written in portable ANSI C99 that conforms to POSIX. We recommend using Cosmopolitan Libc since it produces binaries that work on any operating system including Windows. Portability across terminals is achieved because literally everything these days supports VT100 control codes which were standardized by ANSI back in the 1970's. This library ignores platform-specific norms for multibyte encoding and it also ignores antiquated terminal capability databases. Libraries like ncurses were designed to reduce bandwidth on 300 bit per second modems. They're bloated and huge because they needed to implement workarounds to all the "incompatible by design" engineering practices used by terminal platforms in the 70's in 80's. Corporate America has long since moved on to making GUI platforms incompatible instead. Even the Windows command prompt supports VT100 and XTERM sequences these days. Seriously. It's 2021 and everyone in the world finally agrees on UTF-8 and ANSI VT100 style command sequences. That's why bestline is now, for the first time in history, able to offer you a fully featured experience using simple bloat-free code. ## Contributing We'd love to accept your pull requests! Please send an email beforehand to Justine Tunney saying that you intend to assign her the copyright to the changes you contribute to bestline. Please do not contribute changes that have `#ifdef` statements. We don't care if MSVC printed a warning, and we won't accept Windows torture code since Windows compatibility can be abstracted by Cosmopolitan Libc which does C on Windows better than Windows does considering how there's about ten different incompatible libc implementations, provided by the Windows platform, and they have a history of doing things like adding telemetry. ## License Bestline is released under the 2-clause BSD license. You have the freedom to copy the Bestline source code into your codebase, but you have to keep the license notice at the top of the file. You also have the freedom to distribute your app as a closed-source binary, but you have to embed the copyright notice in the executable. We've added an `.ident` assembly directive to the top of the source code file which should automatically take care of binary notice compliance. ``` The BSD-2 License Copyright 2018-2021 Justine Tunney Copyright 2010-2016 Salvatore Sanfilippo Copyright 2010-2013 Pieter Noordhuis All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` ## Donations Please consider tipping the author at since she needs your support in order to keep going building cool tools and libraries that serve the public interest. So if you like what you've seen and want to encourage more, please consider granting recognition. muon-v0.3.0/subprojects/.gitignore0000644000175000017500000000047114674562002016201 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only * !*.wrap !.gitignore !bestline !bestline/README.md !bestline/bestline.c !bestline/bestline.h !bestline/meson.build !tinyjson !tinyjson/tiny-json.c !tinyjson/tiny-json.h !tinyjson/meson.build !tinyjson/README.md muon-v0.3.0/subprojects/tinyjson.wrap0000644000175000017500000000021414674562002016754 0ustar buildbuild; SPDX-FileCopyrightText: Stone Tickle ; SPDX-License-Identifier: GPL-3.0-only [wrap-file] source_filename = tinyjson muon-v0.3.0/subprojects/meson-docs.wrap0000644000175000017500000000050414674562002017150 0ustar buildbuild; SPDX-FileCopyrightText: Stone Tickle ; SPDX-License-Identifier: GPL-3.0-only [wrap-file] source_filename = meson-docs-1.5.1-18-g587869c37.tar.gz source_url = https://mochiro.moe/wrap/meson-docs-1.5.1-18-g587869c37.tar.gz source_hash = 2a781073f8fdbf0f3c9dcea73cf32a37f12714d6cf0e7054d5dba245c3b564df muon-v0.3.0/subprojects/bestline.wrap0000644000175000017500000000025714674562002016713 0ustar buildbuild; SPDX-FileCopyrightText: Stone Tickle ; SPDX-License-Identifier: GPL-3.0-only [wrap-file] source_filename = bestline [provide] dependencies = bestline muon-v0.3.0/subprojects/tinyjson/0002755000175000017500000000000014674562002016066 5ustar buildbuildmuon-v0.3.0/subprojects/tinyjson/tiny-json.h0000644000175000017500000001450214674562002020171 0ustar buildbuild /* Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2016-2018 Rafa Garcia . 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. */ #ifndef _TINY_JSON_H_ #define _TINY_JSON_H_ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #define json_containerOf( ptr, type, member ) \ ((type*)( (char*)ptr - offsetof( type, member ) )) /** @defgroup tinyJson Tiny JSON parser. * @{ */ /** Enumeration of codes of supported JSON properties types. */ typedef enum { JSON_OBJ, JSON_ARRAY, JSON_TEXT, JSON_BOOLEAN, JSON_INTEGER, JSON_REAL, JSON_NULL } jsonType_t; /** Structure to handle JSON properties. */ typedef struct json_s { struct json_s* sibling; char const* name; union { char const* value; struct { struct json_s* child; struct json_s* last_child; } c; } u; jsonType_t type; } json_t; /** Parse a string to get a json. * @param str String pointer with a JSON object. It will be modified. * @param mem Array of json properties to allocate. * @param qty Number of elements of mem. * @retval Null pointer if any was wrong in the parse process. * @retval If the parser process was successfully a valid handler of a json. * This property is always unnamed and its type is JSON_OBJ. */ json_t const* json_create( char* str, json_t mem[], unsigned int qty ); /** Get the name of a json property. * @param json A valid handler of a json property. * @retval Pointer to null-terminated if property has name. * @retval Null pointer if the property is unnamed. */ static inline char const* json_getName( json_t const* json ) { return json->name; } /** Get the value of a json property. * The type of property cannot be JSON_OBJ or JSON_ARRAY. * @param property A valid handler of a json property. * @return Pointer to null-terminated string with the value. */ static inline char const* json_getValue( json_t const* property ) { return property->u.value; } /** Get the type of a json property. * @param json A valid handler of a json property. * @return The code of type.*/ static inline jsonType_t json_getType( json_t const* json ) { return json->type; } /** Get the next sibling of a JSON property that is within a JSON object or array. * @param json A valid handler of a json property. * @retval The handler of the next sibling if found. * @retval Null pointer if the json property is the last one. */ static inline json_t const* json_getSibling( json_t const* json ) { return json->sibling; } /** Search a property by its name in a JSON object. * @param obj A valid handler of a json object. Its type must be JSON_OBJ. * @param property The name of property to get. * @retval The handler of the json property if found. * @retval Null pointer if not found. */ json_t const* json_getProperty( json_t const* obj, char const* property ); /** Search a property by its name in a JSON object and return its value. * @param obj A valid handler of a json object. Its type must be JSON_OBJ. * @param property The name of property to get. * @retval If found a pointer to null-terminated string with the value. * @retval Null pointer if not found or it is an array or an object. */ char const* json_getPropertyValue( json_t const* obj, char const* property ); /** Get the first property of a JSON object or array. * @param json A valid handler of a json property. * Its type must be JSON_OBJ or JSON_ARRAY. * @retval The handler of the first property if there is. * @retval Null pointer if the json object has not properties. */ static inline json_t const* json_getChild( json_t const* json ) { return json->u.c.child; } /** Get the value of a json boolean property. * @param property A valid handler of a json object. Its type must be JSON_BOOLEAN. * @return The value stdbool. */ static inline bool json_getBoolean( json_t const* property ) { return *property->u.value == 't'; } /** Get the value of a json integer property. * @param property A valid handler of a json object. Its type must be JSON_INTEGER. * @return The value stdint. */ static inline int64_t json_getInteger( json_t const* property ) { return strtoll( property->u.value,(char**)NULL, 10); } /** Get the value of a json real property. * @param property A valid handler of a json object. Its type must be JSON_REAL. * @return The value. */ static inline double json_getReal( json_t const* property ) { return strtod( property->u.value,(char**)NULL ); } /** Structure to handle a heap of JSON properties. */ typedef struct jsonPool_s jsonPool_t; struct jsonPool_s { json_t* (*init)( jsonPool_t* pool ); json_t* (*alloc)( jsonPool_t* pool ); }; /** Parse a string to get a json. * @param str String pointer with a JSON object. It will be modified. * @param pool Custom json pool pointer. * @retval Null pointer if any was wrong in the parse process. * @retval If the parser process was successfully a valid handler of a json. * This property is always unnamed and its type is JSON_OBJ. */ json_t const* json_createWithPool( char* str, jsonPool_t* pool ); /** @ } */ #ifdef __cplusplus } #endif #endif /* _TINY_JSON_H_ */ muon-v0.3.0/subprojects/tinyjson/meson.build0000644000175000017500000000031014674562002020220 0ustar buildbuildproject('tiny-json', 'c') meson.override_dependency( 'tinyjson', declare_dependency( include_directories: '.', link_with: static_library('tiny-json', 'tiny-json.c'), ), ) muon-v0.3.0/subprojects/tinyjson/tiny-json.c0000644000175000017500000004137514674562002020174 0ustar buildbuild /* Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2016-2018 Rafa Garcia . 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. */ #include #include #include "tiny-json.h" /** Structure to handle a heap of JSON properties. */ typedef struct jsonStaticPool_s { json_t* mem; /**< Pointer to array of json properties. */ unsigned int qty; /**< Length of the array of json properties. */ unsigned int nextFree; /**< The index of the next free json property. */ jsonPool_t pool; } jsonStaticPool_t; /* Search a property by its name in a JSON object. */ json_t const* json_getProperty( json_t const* obj, char const* property ) { json_t const* sibling; for( sibling = obj->u.c.child; sibling; sibling = sibling->sibling ) if ( sibling->name && !strcmp( sibling->name, property ) ) return sibling; return 0; } /* Search a property by its name in a JSON object and return its value. */ char const* json_getPropertyValue( json_t const* obj, char const* property ) { json_t const* field = json_getProperty( obj, property ); if ( !field ) return 0; jsonType_t type = json_getType( field ); if ( JSON_ARRAY >= type ) return 0; return json_getValue( field ); } /* Internal prototypes: */ static char* goBlank( char* str ); static char* goNum( char* str ); static json_t* poolInit( jsonPool_t* pool ); static json_t* poolAlloc( jsonPool_t* pool ); static char* objValue( char* ptr, json_t* obj, jsonPool_t* pool ); static char* setToNull( char* ch ); static bool isEndOfPrimitive( char ch ); /* Parse a string to get a json. */ json_t const* json_createWithPool( char *str, jsonPool_t *pool ) { char* ptr = goBlank( str ); if ( !ptr || (*ptr != '{' && *ptr != '[') ) return 0; json_t* obj = pool->init( pool ); obj->name = 0; obj->sibling = 0; obj->u.c.child = 0; ptr = objValue( ptr, obj, pool ); if ( !ptr ) return 0; return obj; } /* Parse a string to get a json. */ json_t const* json_create( char* str, json_t mem[], unsigned int qty ) { jsonStaticPool_t spool; spool.mem = mem; spool.qty = qty; spool.pool.init = poolInit; spool.pool.alloc = poolAlloc; return json_createWithPool( str, &spool.pool ); } /** Get a special character with its escape character. Examples: * 'b' -> '\\b', 'n' -> '\\n', 't' -> '\\t' * @param ch The escape character. * @retval The character code. */ static char getEscape( char ch ) { static struct { char ch; char code; } const pair[] = { { '\"', '\"' }, { '\\', '\\' }, { '/', '/' }, { 'b', '\b' }, { 'f', '\f' }, { 'n', '\n' }, { 'r', '\r' }, { 't', '\t' }, }; unsigned int i; for( i = 0; i < sizeof pair / sizeof *pair; ++i ) if ( pair[i].ch == ch ) return pair[i].code; return '\0'; } /** Parse 4 characters. * @param str Pointer to first digit. * @retval '?' If the four characters are hexadecimal digits. * @retval '\0' In other cases. */ static unsigned char getCharFromUnicode( unsigned char const* str ) { unsigned int i; for( i = 0; i < 4; ++i ) if ( !isxdigit( str[i] ) ) return '\0'; return '?'; } /** Parse a string and replace the scape characters by their meaning characters. * This parser stops when finds the character '\"'. Then replaces '\"' by '\0'. * @param str Pointer to first character. * @retval Pointer to first non white space after the string. If success. * @retval Null pointer if any error occur. */ static char* parseString( char* str ) { unsigned char* head = (unsigned char*)str; unsigned char* tail = (unsigned char*)str; for( ; *head; ++head, ++tail ) { if ( *head == '\"' ) { *tail = '\0'; return (char*)++head; } if ( *head == '\\' ) { if ( *++head == 'u' ) { char const ch = getCharFromUnicode( ++head ); if ( ch == '\0' ) return 0; *tail = ch; head += 3; } else { char const esc = getEscape( *head ); if ( esc == '\0' ) return 0; *tail = esc; } } else *tail = *head; } return 0; } /** Parse a string to get the name of a property. * @param ptr Pointer to first character. * @param property The property to assign the name. * @retval Pointer to first of property value. If success. * @retval Null pointer if any error occur. */ static char* propertyName( char* ptr, json_t* property ) { property->name = ++ptr; ptr = parseString( ptr ); if ( !ptr ) return 0; ptr = goBlank( ptr ); if ( !ptr ) return 0; if ( *ptr++ != ':' ) return 0; return goBlank( ptr ); } /** Parse a string to get the value of a property when its type is JSON_TEXT. * @param ptr Pointer to first character ('\"'). * @param property The property to assign the name. * @retval Pointer to first non white space after the string. If success. * @retval Null pointer if any error occur. */ static char* textValue( char* ptr, json_t* property ) { ++property->u.value; ptr = parseString( ++ptr ); if ( !ptr ) return 0; property->type = JSON_TEXT; return ptr; } /** Compare two strings until get the null character in the second one. * @param ptr sub string * @param str main string * @retval Pointer to next character. * @retval Null pointer if any error occur. */ static char* checkStr( char* ptr, char const* str ) { while( *str ) if ( *ptr++ != *str++ ) return 0; return ptr; } /** Parser a string to get a primitive value. * If the first character after the value is different of '}' or ']' is set to '\0'. * @param ptr Pointer to first character. * @param property Property handler to set the value and the type, (true, false or null). * @param value String with the primitive literal. * @param type The code of the type. ( JSON_BOOLEAN or JSON_NULL ) * @retval Pointer to first non white space after the string. If success. * @retval Null pointer if any error occur. */ static char* primitiveValue( char* ptr, json_t* property, char const* value, jsonType_t type ) { ptr = checkStr( ptr, value ); if ( !ptr || !isEndOfPrimitive( *ptr ) ) return 0; ptr = setToNull( ptr ); property->type = type; return ptr; } /** Parser a string to get a true value. * If the first character after the value is different of '}' or ']' is set to '\0'. * @param ptr Pointer to first character. * @param property Property handler to set the value and the type, (true, false or null). * @retval Pointer to first non white space after the string. If success. * @retval Null pointer if any error occur. */ static char* trueValue( char* ptr, json_t* property ) { return primitiveValue( ptr, property, "true", JSON_BOOLEAN ); } /** Parser a string to get a false value. * If the first character after the value is different of '}' or ']' is set to '\0'. * @param ptr Pointer to first character. * @param property Property handler to set the value and the type, (true, false or null). * @retval Pointer to first non white space after the string. If success. * @retval Null pointer if any error occur. */ static char* falseValue( char* ptr, json_t* property ) { return primitiveValue( ptr, property, "false", JSON_BOOLEAN ); } /** Parser a string to get a null value. * If the first character after the value is different of '}' or ']' is set to '\0'. * @param ptr Pointer to first character. * @param property Property handler to set the value and the type, (true, false or null). * @retval Pointer to first non white space after the string. If success. * @retval Null pointer if any error occur. */ static char* nullValue( char* ptr, json_t* property ) { return primitiveValue( ptr, property, "null", JSON_NULL ); } /** Analyze the exponential part of a real number. * @param ptr Pointer to first character. * @retval Pointer to first non numerical after the string. If success. * @retval Null pointer if any error occur. */ static char* expValue( char* ptr ) { if ( *ptr == '-' || *ptr == '+' ) ++ptr; if ( !isdigit( (int)(*ptr) ) ) return 0; ptr = goNum( ++ptr ); return ptr; } /** Analyze the decimal part of a real number. * @param ptr Pointer to first character. * @retval Pointer to first non numerical after the string. If success. * @retval Null pointer if any error occur. */ static char* fraqValue( char* ptr ) { if ( !isdigit( (int)(*ptr) ) ) return 0; ptr = goNum( ++ptr ); if ( !ptr ) return 0; return ptr; } /** Parser a string to get a numerical value. * If the first character after the value is different of '}' or ']' is set to '\0'. * @param ptr Pointer to first character. * @param property Property handler to set the value and the type: JSON_REAL or JSON_INTEGER. * @retval Pointer to first non white space after the string. If success. * @retval Null pointer if any error occur. */ static char* numValue( char* ptr, json_t* property ) { if ( *ptr == '-' ) ++ptr; if ( !isdigit( (int)(*ptr) ) ) return 0; if ( *ptr != '0' ) { ptr = goNum( ptr ); if ( !ptr ) return 0; } else if ( isdigit( (int)(*++ptr) ) ) return 0; property->type = JSON_INTEGER; if ( *ptr == '.' ) { ptr = fraqValue( ++ptr ); if ( !ptr ) return 0; property->type = JSON_REAL; } if ( *ptr == 'e' || *ptr == 'E' ) { ptr = expValue( ++ptr ); if ( !ptr ) return 0; property->type = JSON_REAL; } if ( !isEndOfPrimitive( *ptr ) ) return 0; if ( JSON_INTEGER == property->type ) { char const* value = property->u.value; bool const negative = *value == '-'; static char const min[] = "-9223372036854775808"; static char const max[] = "9223372036854775807"; unsigned int const maxdigits = ( negative? sizeof min: sizeof max ) - 1; unsigned int const len = ( unsigned int const ) ( ptr - value ); if ( len > maxdigits ) return 0; if ( len == maxdigits ) { char const tmp = *ptr; *ptr = '\0'; char const* const threshold = negative ? min: max; if ( 0 > strcmp( threshold, value ) ) return 0; *ptr = tmp; } } ptr = setToNull( ptr ); return ptr; } /** Add a property to a JSON object or array. * @param obj The handler of the JSON object or array. * @param property The handler of the property to be added. */ static void add( json_t* obj, json_t* property ) { property->sibling = 0; if ( !obj->u.c.child ){ obj->u.c.child = property; obj->u.c.last_child = property; } else { obj->u.c.last_child->sibling = property; obj->u.c.last_child = property; } } /** Parser a string to get a json object value. * @param ptr Pointer to first character. * @param obj The handler of the JSON root object or array. * @param pool The handler of a json pool for creating json instances. * @retval Pointer to first character after the value. If success. * @retval Null pointer if any error occur. */ static char* objValue( char* ptr, json_t* obj, jsonPool_t* pool ) { obj->type = *ptr == '{' ? JSON_OBJ : JSON_ARRAY; obj->u.c.child = 0; obj->sibling = 0; ptr++; for(;;) { ptr = goBlank( ptr ); if ( !ptr ) return 0; if ( *ptr == ',' ) { ++ptr; continue; } char const endchar = ( obj->type == JSON_OBJ )? '}': ']'; if ( *ptr == endchar ) { *ptr = '\0'; json_t* parentObj = obj->sibling; if ( !parentObj ) return ++ptr; obj->sibling = 0; obj = parentObj; ++ptr; continue; } json_t* property = pool->alloc( pool ); if ( !property ) return 0; if( obj->type != JSON_ARRAY ) { if ( *ptr != '\"' ) return 0; ptr = propertyName( ptr, property ); if ( !ptr ) return 0; } else property->name = 0; add( obj, property ); property->u.value = ptr; switch( *ptr ) { case '{': property->type = JSON_OBJ; property->u.c.child = 0; property->sibling = obj; obj = property; ++ptr; break; case '[': property->type = JSON_ARRAY; property->u.c.child = 0; property->sibling = obj; obj = property; ++ptr; break; case '\"': ptr = textValue( ptr, property ); break; case 't': ptr = trueValue( ptr, property ); break; case 'f': ptr = falseValue( ptr, property ); break; case 'n': ptr = nullValue( ptr, property ); break; default: ptr = numValue( ptr, property ); break; } if ( !ptr ) return 0; } } /** Initialize a json pool. * @param pool The handler of the pool. * @return a instance of a json. */ static json_t* poolInit( jsonPool_t* pool ) { jsonStaticPool_t *spool = json_containerOf( pool, jsonStaticPool_t, pool ); spool->nextFree = 1; return spool->mem; } /** Create an instance of a json from a pool. * @param pool The handler of the pool. * @retval The handler of the new instance if success. * @retval Null pointer if the pool was empty. */ static json_t* poolAlloc( jsonPool_t* pool ) { jsonStaticPool_t *spool = json_containerOf( pool, jsonStaticPool_t, pool ); if ( spool->nextFree >= spool->qty ) return 0; return spool->mem + spool->nextFree++; } /** Checks whether an character belongs to set. * @param ch Character value to be checked. * @param set Set of characters. It is just a null-terminated string. * @return true or false there is membership or not. */ static bool isOneOfThem( char ch, char const* set ) { while( *set != '\0' ) if ( ch == *set++ ) return true; return false; } /** Increases a pointer while it points to a character that belongs to a set. * @param str The initial pointer value. * @param set Set of characters. It is just a null-terminated string. * @return The final pointer value or null pointer if the null character was found. */ static char* goWhile( char* str, char const* set ) { for(; *str != '\0'; ++str ) { if ( !isOneOfThem( *str, set ) ) return str; } return 0; } /** Set of characters that defines a blank. */ static char const* const blank = " \n\r\t\f"; /** Increases a pointer while it points to a white space character. * @param str The initial pointer value. * @return The final pointer value or null pointer if the null character was found. */ static char* goBlank( char* str ) { return goWhile( str, blank ); } /** Increases a pointer while it points to a decimal digit character. * @param str The initial pointer value. * @return The final pointer value or null pointer if the null character was found. */ static char* goNum( char* str ) { for( ; *str != '\0'; ++str ) { if ( !isdigit( (int)(*str) ) ) return str; } return 0; } /** Set of characters that defines the end of an array or a JSON object. */ static char const* const endofblock = "}]"; /** Set a char to '\0' and increase its pointer if the char is different to '}' or ']'. * @param ch Pointer to character. * @return Final value pointer. */ static char* setToNull( char* ch ) { if ( !isOneOfThem( *ch, endofblock ) ) *ch++ = '\0'; return ch; } /** Indicate if a character is the end of a primitive value. */ static bool isEndOfPrimitive( char ch ) { return ch == ',' || isOneOfThem( ch, blank ) || isOneOfThem( ch, endofblock ); } muon-v0.3.0/subprojects/tinyjson/README.md0000644000175000017500000000707514674562002017354 0ustar buildbuild# tiny-json [![Build Status](https://travis-ci.org/rafagafe/tiny-json.svg?branch=master)](https://travis-ci.org/rafagafe/tiny-json) [![GitHub contributors](https://img.shields.io/github/contributors/rafagafe/tiny-json.svg)](https://github.com/rafagafe/tiny-json/graphs/contributors) tiny-json is a versatile and easy to use json parser written in C and suitable for embedded systems. It is fast, robust and portable. It is not only a tokenizer. You can access json data in string format or get primitive values directly as C type variables without any loss of performance. You can access the JSON fields one on one or get their values by their names. This helps you to save a lot of source code lines and development time. * It does not use recursivity. * It does not use dynamic memory. The memory you use can be reserved statically. * There is no limit for nested levels in arrays or json objects. * The JSON property number limit is determined by the size of a buffer that can be statically reserved. If you need to create JSON strings please visit: https://github.com/rafagafe/json-maker # Philosophy When parsing a JSON text string a tree is created by linking json_t structures. Navigating or querying this tree is very easy using the provided API. To maintain reduced memory usage and fast processing the strings are not copied. When you request the value of a JSON element, a reference to the original JSON string is returned. To facilitate the processing of the data the returned strings are null-terminated. This is achieved by setting the null character to JSON control characters such as commas, brackets, braces, and quotation marks. # API The tiny-json API provides two types. `jsonType_t` is an enumeration for all possible JSON field types. `json_t` is a structure containing internal data which you don't need to know. ```C typedef enum { JSON_OBJ, JSON_ARRAY, JSON_TEXT, JSON_BOOLEAN, JSON_INTEGER, JSON_REAL, JSON_NULL } jsonType_t; ``` To parse a JSON string use `json_create()`. We pass it an array of `json_t` for it to allocate JSON fields. If the JSON string is bad formated or has more fields than the array this function returns a null pointer. ```C enum { MAX_FIELDS = 4 }; json_t pool[ MAX_FIELDS ]; char str[] = "{ \"name\": \"peter\", \"age\": 32 }"; json_t const* parent = json_create( str, pool, MAX_FIELDS ); if ( parent == NULL ) return EXIT_FAILURE; ``` To get a field by its name we use `json_getProperty()`. If the field does not exist the function returns a null pointer. To get the type of a field we use `json_getType()`. ```C json_t const* namefield = json_getProperty( parent, "name" ); if ( namefield == NULL ) return EXIT_FAILURE; if ( json_getType( namefield ) != JSON_TEXT ) return EXIT_FAILURE; ``` To get the value of a field in string format we use `json_getValue()`. It always returns a valid null-teminated string. ```C char const* namevalue = json_getValue( namefield ); printf( "%s%s%s", "Name: '", namevalue, "'.\n" ); ``` For primitive fields we can use a specific function to get the fields value directly as a C type, f.i. `json_getInteger()` or we can use `json_getValue()` to get its value in text format. ```C json_t const* agefield = json_getProperty( parent, "age" ); if ( agefield == NULL ) return EXIT_FAILURE; if ( json_getType( agefield ) != JSON_INTEGER ) return EXIT_FAILURE; int64_t agevalue = json_getInteger( agefield ); printf( "%s%lld%s", "Age: '", agevalue, "'.\n" ); char const* agetxt = json_getValue( agefield ); printf( "%s%s%s", "Age: '", agetxt, "'.\n" ); ``` For an example how to use nested JSON objects and arrays please see example-01.c. muon-v0.3.0/.builds/0002755000175000017500000000000014674562002013206 5ustar buildbuildmuon-v0.3.0/.builds/debian.yml0000644000175000017500000000175014674562002015154 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only image: debian/stable packages: - clang - libclang-rt-dev - llvm - gobjc sources: - https://git.sr.ht/~lattis/muon tasks: - bootstrap: | cd muon tools/ci/bootstrap.sh build - build_asan_ubsan: | cd muon build/muon setup -Db_sanitize=address,undefined build_asan_ubsan build/muon -C build_asan_ubsan samu - build_memsan: | cd muon CC=clang build/muon setup -Db_sanitize=memory build_memsan build/muon -C build_memsan samu - build_fortify_source: | cd muon CC=clang CFLAGS=-D_FORTIFY_SOURCE=3 build/muon setup build_fortify_source build/muon -C build_fortify_source samu - test_asan_ubsan: | cd muon/build_asan_ubsan ./muon test -d dots - test_memsan: | cd muon/build_memsan ./muon test -d dots - test_fortify_source: | cd muon/build_fortify_source ./muon test -d dots muon-v0.3.0/.builds/netbsd.yml0000644000175000017500000000075614674562002015216 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only image: netbsd/latest packages: sources: - https://git.sr.ht/~lattis/muon tasks: - build: | cd muon # TODO remove this when we implement rpaths export LD_LIBRARY_PATH=/usr/pkg/lib tools/ci/bootstrap.sh build - test: | cd muon/build # TODO remove this when we implement rpaths export LD_LIBRARY_PATH=/usr/pkg/lib ./muon test -d dots -s lang muon-v0.3.0/.builds/alpine.yml0000644000175000017500000000615314674562002015204 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only image: alpine/3.17 secrets: - 2fe5fef6-db89-4410-8147-07e314589d18 - 18083346-dfba-4050-bc05-413561f99228 repositories: edge_testing: > http://dl-cdn.alpinelinux.org/alpine/edge/testing https://muon.build/nothing ../../../dev/null community: > http://dl-cdn.alpinelinux.org/alpine/v3.17/community # must be kept in sync with the image's alpine version https://muon.build/nothing ../../../dev/null packages: - curl-dev - libarchive-dev - pkgconf-dev - python3 # for project tests, meson-reference.3, and parts of the website - linux-headers # used in a few project tests - py3-yaml # for meson-reference.3 - scdoc # for meson.build.5 and muon.1 - mandoc # for html man pages - reuse # for licensing compliance # alternative c compilers - clang - tcc - tcc-libs-static # tcc 0.9.27_git20220323-r1 is broken without this # for static builds - acl-static - brotli-static - bzip2-static - curl-static - expat-static - libarchive-static - lz4-static - nghttp2-static - openssl-libs-static - xz-static - zlib-static - zstd-dev # for releases - rsync sources: - https://git.sr.ht/~lattis/muon environment: PKG_CONFIG_PATH: /usr/lib/pkgconfig tasks: - reuse: | cd muon reuse lint - push_to_gh_mirror: | cd muon tools/ci/push_to_gh_mirror.sh - kickoff_custom_ci: | cd muon if [ -d ~/.ssh ]; then tools/ci/solaris11.sh submit fi - build_gcc: | # In order to fetch the meson-docs wrap, we have to build muon twice, # once to bootstrap, and then again when we have libcurl cd muon OPTS="-Dlibpkgconf=enabled -Dlibarchive=enabled -Dlibcurl=enabled -Dbuildtype=release -Dstatic=true -Dwebsite=true -Dwerror=true" CC=gcc tools/ci/bootstrap.sh build $OPTS build/muon setup $OPTS build build/muon -C build samu - build_tcc: | cd muon CC=tcc tools/ci/bootstrap.sh build-tcc - test_gcc: | cd muon/build CC=gcc ./muon test -d dots - test_clang: | cd muon/build CC=clang ./muon test -d dots - build_small: | cd muon CC=gcc build/muon setup \ -Dbuildtype=minsize \ -Dstatic=true \ -Dlibcurl=disabled \ -Dlibarchive=disabled \ build-small build/muon -C build-small samu - release: | cd muon ver="$(cat build/version.txt)" tools/ci/prepare_release_docs.sh build tools/ci/prepare_binary.sh build "$ver-amd64-linux-static" tools/ci/prepare_binary.sh build-small "${ver}-amd64-linux-static-small" tools/ci/prepare_tarball.sh "muon-${ver}" tools/ci/deploy.sh / -r --delete build/doc/website tools/ci/deploy.sh "/releases/${ver}" -r --delete build/doc/docs tools/ci/deploy.sh "/releases/${ver}" \ "build/muon-${ver}-amd64-linux-static" \ "build/muon-${ver}-amd64-linux-static.md5" \ "build-small/muon-${ver}-amd64-linux-static-small" \ "build-small/muon-${ver}-amd64-linux-static-small.md5" \ "muon-${ver}.tar.gz" muon-v0.3.0/.builds/projects.yml0000644000175000017500000000127714674562002015567 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only image: alpine/3.17 packages: - curl-dev - libarchive-dev - pkgconf-dev - linux-headers # assumed by rizin sources: - https://git.sr.ht/~lattis/muon - https://github.com/rizinorg/rizin environment: PKG_CONFIG_PATH: /usr/lib/pkgconfig tasks: - muon: | cd muon OPTS="-Dlibpkgconf=enabled -Dlibarchive=enabled -Dlibcurl=enabled -Dbuildtype=release" CC=gcc tools/ci/bootstrap.sh build $OPTS build/muon setup $OPTS build build/muon -C build samu sudo build/muon -C build install - rizin: | cd rizin muon setup build muon -C build samu muon-v0.3.0/.editorconfig0000644000175000017500000000060114674562002014316 0ustar buildbuild# SPDX-FileCopyrightText: Eli Schwartz # SPDX-FileCopyrightText: dffdff2423 # SPDX-License-Identifier: GPL-3.0-only root = true [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true [{*.c,*.h,*.sh}] indent_style = tab [{meson.build,meson_options.txt}] indent_style = space indent_size = 4 muon-v0.3.0/contrib/0002755000175000017500000000000014674562002013306 5ustar buildbuildmuon-v0.3.0/contrib/muon_fmt.vim0000644000175000017500000000072014674562002015644 0ustar buildbuild" SPDX-FileCopyrightText: Stone Tickle " SPDX-License-Identifier: GPL-3.0-only " Author: lattis " Description: Ale fixer muon fmt for meson files " function! FormatMeson(buffer) abort return { \ 'command': 'muon fmt -' \} endfunction execute ale#fix#registry#Add('muon-fmt', 'FormatMeson', ['meson'], 'muon fmt for meson') " usage: " source /path/to/muon/contrib/muon_fmt.vim " let g:ale_fixers = { " \ 'meson': ['muon-fmt'], " } muon-v0.3.0/contrib/muon.vim0000644000175000017500000000313014674562002014774 0ustar buildbuild" SPDX-FileCopyrightText: Stone Tickle " SPDX-License-Identifier: GPL-3.0-only " Author: lattis " Description: Ale linter muon for meson files " function! ale_linters#meson#muon#GetExecutable(buffer) abort return 'muon' endfunction function! ale_linters#meson#muon#GetCommand(buffer) abort let l:executable = ale_linters#meson#muon#GetExecutable(a:buffer) let l:file = resolve(expand('%:p')) let l:cmd = ale#Escape(l:executable) let l:args = 'analyze -l' if match(l:file, '\.meson$') != -1 let l:args = l:args . 'i-' else let l:args = l:args . 'O' . ale#Escape(l:file) endif return l:cmd . ' ' . l:args endfunction function! ale_linters#meson#muon#Handle(buffer, lines) abort let l:pattern = '\v(^.*):(\d+):(\d+): (warning|error) (.*)$' let l:output = [] let l:cur_file = resolve(expand('%:p')) for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:filename = l:match[1] if l:filename == '-' let l:filename = l:cur_file endif call add(l:output, { \ 'filename': l:filename, \ 'lnum': l:match[2] + 0, \ 'col': l:match[3] + 0, \ 'type': l:match[4] == 'warning' ? 'W' : 'E', \ 'text': l:match[5], \}) endfor return l:output endfunction call ale#linter#Define('meson', { \ 'name': 'muon', \ 'executable': function('ale_linters#meson#muon#GetExecutable'), \ 'command': function('ale_linters#meson#muon#GetCommand'), \ 'callback': 'ale_linters#meson#muon#Handle', \ 'output_stream': 'stderr', \}) " usage: " mkdir -p /path/to/vim/config/ale_linters/meson " cp /path/to/muon/contrib/muon.vim " /path/to/vim/config/ale_linters/meson/muon.vim muon-v0.3.0/bootstrap.bat0000644000175000017500000000053214674562002014351 0ustar buildbuild:: SPDX-FileCopyrightText: Stone Tickle :: SPDX-License-Identifier: GPL-3.0-only @echo off setlocal cd /D "%~dp0" if "%~1" == "" goto :usage 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.3.0/.reuse/0002755000175000017500000000000014674562002013047 5ustar buildbuildmuon-v0.3.0/.reuse/dep50000644000175000017500000000076214674562002013632 0ustar buildbuildFormat: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: muon Upstream-Contact: Stone Tickle Source: https://muon.build Files: tests/project/unit/* Copyright: The Meson development team Stone Tickle License: Apache-2.0 Files: tests/fmt/cases/* Copyright: Stone Tickle License: GPL-3.0-only Files: tools/ci/version.txt.in Copyright: Stone Tickle License: GPL-3.0-only muon-v0.3.0/.github/0002755000175000017500000000000014674562002013206 5ustar buildbuildmuon-v0.3.0/.github/workflows/0002755000175000017500000000000014674562002015243 5ustar buildbuildmuon-v0.3.0/.github/workflows/macos.yml0000644000175000017500000000344414674562002017073 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only name: macos on: push: branches: [ master ] pull_request: branches: [ master ] types: [opened, synchronize, reopened] permissions: contents: read jobs: macos: runs-on: macos-latest environment: master steps: - uses: actions/checkout@v3 - name: deps run: | set -x mkdir -p build arch curl -L -o build/pkgconf-1.9.3.tar.gz https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-1.9.3.tar.gz tar xvf build/pkgconf-1.9.3.tar.gz mv pkgconf-pkgconf-1.9.3 subprojects/pkgconf - name: build run: | ./bootstrap.sh build PATH="build:$PATH" build/muon -v setup build build/muon -C build samu - name: test run: | build/muon -C build test -v -ddots - name: build-x86_64 run: | arch -x86_64 sh -c ' ./bootstrap.sh build-x86_64 &&\ PATH="build-x86_64:$PATH" build-x86_64/muon -v setup build-x86_64 &&\ build-x86_64/muon -C build-x86_64 samu\ ' - name: build-universal-binary run: | ver="$(cat build/version.txt)" lipo -create -output muon build-x86_64/muon build/muon zip "muon-${ver}-universal-macos.zip" muon - name: upload-universal-binary if: github.ref == 'refs/heads/master' env: GH_DEPLOY: 1 run: | ver="$(cat build/version.txt)" echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/deploy chmod 600 ~/.ssh/deploy cat >> ~/.ssh/config < # SPDX-FileCopyrightText: Vincent Torri # SPDX-License-Identifier: GPL-3.0-only name: msys2 on: push: branches: [ master ] pull_request: branches: [ master ] types: [opened, synchronize, reopened] jobs: test: strategy: matrix: config: - msystem: mingw32 install: base-devel git mingw-w64-i686-ninja mingw-w64-i686-toolchain mingw-w64-i686-pkgconf mingw-w64-i686-curl mingw-w64-i686-libarchive - msystem: mingw64 install: base-devel git mingw-w64-x86_64-ninja mingw-w64-x86_64-toolchain mingw-w64-x86_64-pkgconf mingw-w64-x86_64-curl mingw-w64-x86_64-libarchive - msystem: msys install: base-devel git ninja gcc pkgconf libcurl-devel libarchive-devel name: msys2 - ${{ matrix.config.msystem }} runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@master - uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.config.msystem }} update: true install: ${{ matrix.config.install }} - name: bootstrap run: | CC=gcc CFLAGS=-std=c99 ./bootstrap.sh build CC=gcc CFLAGS=-std=c99 ./build/muon.exe setup build ninja -C build - name: test run: | ./build/muon.exe -C build test -v -ddots || true muon-v0.3.0/.github/workflows/vs2019.yml0000644000175000017500000000167314674562002016737 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only name: vs2019 on: push: branches: [master] pull_request: branches: [master] types: [opened, synchronize, reopened] jobs: test: strategy: matrix: arch: - amd64 - amd64_x86 compiler: # TODO: Add clang when bootstrap.bat adds clang support. - msvc name: ${{ matrix.arch }} - ${{ matrix.compiler }} runs-on: windows-2019 defaults: run: shell: cmd steps: - uses: actions/checkout@v4 - uses: ilammy/msvc-dev-cmd@v1 with: arch: ${{ matrix.arch }} - name: bootstrap run: > bootstrap.bat build && build\muon-bootstrap.exe -v setup build && build\muon-bootstrap.exe -vC build samu - name: test run: > build\muon.exe -vC build test || exit 0 muon-v0.3.0/.github/workflows/ubuntu.yml0000644000175000017500000000522414674562002017311 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only name: ubuntu on: push: branches: [ master ] pull_request: branches: [ master ] types: [opened, synchronize, reopened] jobs: with-sanitizers: runs-on: ubuntu-latest steps: # Checkout muon source and install dependencies. - uses: actions/checkout@master with: fetch-depth: 0 # Bootstrap basic build. - name: Bootstrap run: | ./bootstrap.sh build PATH="build:$PATH" build/muon -v setup build build/muon -C build samu # Build muon with ASan and UBSan and run the testsuite. - name: build_asan_ubsan run: | build/muon setup -Db_sanitize=address,undefined build_asan_ubsan build/muon -C build_asan_ubsan samu - name: test_asan_ubsan run: | cd build_asan_ubsan ./muon test -d dots # Build muon FORTIFY_SOURCE=3 and run the testsuite. - name: build_fortify_source run: | CFLAGS=-U_FORTIFY_SOURCE D_FORTIFY_SOURCE=3 build/muon setup build_fortify_source build/muon -C build_fortify_source samu - name: test_fortify_source run: | cd build_fortify_source ./muon test -d dots # Build muon with libpkgconf and run the testsuite. These steps exist # due to the version of libpkgconf-dev that is currently packaged by Debian # (1.8.1) containing a number of memory leaks that, if run with the sanitizers # enabled, will cause a number of test failures. So we build without them # enabled in this case. # TODO: when Debian updates its libpkgconf-dev package, investigate whether # this is still necessary. with-libpkgconf: runs-on: ubuntu-latest env: DEBIAN_FRONTEND: noninteractive steps: # Checkout muon source and install dependencies. - uses: actions/checkout@master with: fetch-depth: 0 # Required for gnome module tests. - name: Install dependencies run: | sudo apt install libpkgconf-dev libglib2.0-0 libglib2.0-dev libgirepository1.0-dev # Bootstrap basic build. - name: Bootstrap run: | ./bootstrap.sh build PATH="build:$PATH" build/muon -v setup build build/muon -C build samu # Build and run testsuite. - name: build_with_libpkgconf run: | build/muon setup -Dlibpkgconf=enabled build_with_libpkgconf build/muon -C build_with_libpkgconf samu - name: test_with_libpkgconf run: | cd build_with_libpkgconf ./muon test -d dots muon-v0.3.0/tools/0002755000175000017500000000000014674562002013006 5ustar buildbuildmuon-v0.3.0/tools/meson.build0000644000175000017500000000040614674562002015146 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.3.0/tools/update_tests.sh0000755000175000017500000000771014674562002016054 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.3.0/tools/bootstrap_ninja.sh0000755000175000017500000000030314674562002016533 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.3.0/tools/generate_test_check_script.py0000755000175000017500000000224214674562002020733 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.3.0/tools/embedder.c0000644000175000017500000000213014674562002014713 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 = (char []){\n", embedded_name); while ((c = fgetc(f)) != EOF) { // output signed char printf("%hhd, ", (signed char)c); if ((i % 14) == 0) { fputs("\n", stdout); } ++i; } fputs("0x0\n} },\n", stdout); 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.3.0/tools/ci/0002755000175000017500000000000014674562002013401 5ustar buildbuildmuon-v0.3.0/tools/ci/push_to_gh_mirror.sh0000755000175000017500000000066214674562002017473 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 < # 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 cp website/status.css docs mv website/status.html man.tar.gz docs rm -r website/version_info.py website/__pycache__ muon-v0.3.0/tools/ci/solaris11.sh0000755000175000017500000000214414674562002015555 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 setup build build/muon -C build samu build/muon -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 dir=ci/muon 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.3.0/tools/ci/prepare_tarball.sh0000755000175000017500000000155714674562002017105 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_ "." "tests/project/meson-tests" create_final_archive_ rm -rf "$tmp_dir" muon-v0.3.0/tools/ci/version.txt.in0000644000175000017500000000001314674562002016224 0ustar buildbuildv@version@ muon-v0.3.0/tools/ci/bootstrap.sh0000755000175000017500000000033714674562002015756 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 setup "$@" "$build" build/muon -C "$build" samu muon-v0.3.0/tools/ci/deploy.sh0000755000175000017500000000061214674562002015231 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 : elif [ "$(git rev-parse master)" != "$(git rev-parse HEAD)" ]; then exit 0 fi sshopts="-o StrictHostKeyChecking=no -p 2975" dest=$1 shift rsync --rsh="ssh $sshopts" "$@" deploy@mochiro.moe:muon"$dest" muon-v0.3.0/tools/ci/prepare_binary.sh0000755000175000017500000000034714674562002016744 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" md5sum "muon-$suffix" > "muon-$suffix.md5" muon-v0.3.0/.clang-format0000644000175000017500000000605514674562002014225 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' - '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.3.0/README.md0000644000175000017500000000752414674562002013133 0ustar buildbuild muon logo # [muon] muon is an implementation of the meson build system in c99 with minimal dependencies. ## [Features] - `muon analyze` - a static analyzer for meson.build files. [demo] - `muon fmt` - a meson.build code formatter - An interactive stepping debugger - A built-in cross platform [ninja implementation] - [fast] ## Status `muon` is close to feature-complete with the core of meson for `c` and `c++`. See the status page for each version for more detailed information. Things missing include: - cross-compilation support - build optimizations like pch and unity - some `b_` options - dependencies with a custom configuration tool - many modules Other differences from meson are described in `doc/differences.md` If you want to contribute, try using `muon` to build your favorite project. Patches and bug reports welcome! Additionally, muon is not fully supported on all platforms yet. The current status may be viewed on muon's [ci dashboard]. Platforms with some or all tests disabled are currently WIP and platforms not tested in CI have unknown status. In general, posix systems should work fine. ## Dependencies Essential: - A c99 compatible toolchain For `pkgconf` support: - `libpkgconf` - `pkgconf` or `pkg-config` For `[wrap-file]` support: - `libcurl` - `libarchive` To build documentation: - `scdoc` for muon.1 and meson.build.5 - `python3` and `py3-yaml` for meson-reference.3 To run most project tests: - `python3` ## Install If you already have meson or muon and are not interested in bootstrapping, you can just do a typical meson configure, build, install: ``` $meson setup build cd build ninja build $meson test $meson install ``` Otherwise, you must bootstrap muon. The bootstrapping process has two stages. The first stage produces a `muon` binary capable of building itself (but not necessarily anything else). The second stage produces the final binary. Stage 1: ``` ./bootstrap.sh build ``` This will by default build a ninja implementation (samu) into the resulting executable. To disable this behavior use `CFLAGS=-DBOOTSTRAP_NO_SAMU`. Stage 2: ``` build/muon setup build build/muon -C build samu build/muon -C build test build/muon -C build install ``` ## Contribute Please refer to the [contributing guide] before sending patches. Send patches on the [mailing list], report issues on the [issue tracker], and discuss in [#muon on libera.chat]. ## License `muon` is licensed under the GPL version 3 (see LICENSE). Tests under `tests/project` were copied from the [meson project tests] and are licensed under [Apache 2.0]. ## Credits Although I had already had the idea to re-implement meson in C, I was initially inspired to actually go out and do it when I saw [boson]. `muon`'s code was originally based on `boson`, though has since been almost completely rewritten. [muon]: https://muon.build [contributing guide]: https://git.sr.ht/~lattis/muon/tree/master/item/doc/contributing.md [mailing list]: https://lists.sr.ht/~lattis/muon/ [issue tracker]: https://todo.sr.ht/~lattis/muon/ [#muon on libera.chat]: ircs://irc.libera.chat/#muon [meson project tests]: https://github.com/mesonbuild/meson/tree/master/test%20cases [Apache 2.0]: https://www.apache.org/licenses/LICENSE-2.0.txt [boson]: https://sr.ht/~bl4ckb0ne/boson/ [Fast]: https://github.com/annacrombie/meson-raytracer#performance [demo]: https://play.muon.build [ninja implementation]: https://git.sr.ht/~lattis/muon/tree/master/item/src/external/samurai/README.md [ci dashboard]: https://muon.build/muon_ci.html [Features]: https://git.sr.ht/~lattis/muon/tree/master/item/doc/features.md muon-v0.3.0/bootstrap.sh0000755000175000017500000000137214674562002014223 0ustar buildbuild#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only # Requirements: # - c99 # - sh # Optional requirements: # - pkgconf or pkg-config # - libpkgconf set -eux dir="$1" mkdir -p "$dir" pkgconf_cmd="" if command -v pkgconf >/dev/null; then pkgconf_cmd=pkgconf elif command -v pkg-config >/dev/null; then pkgconf_cmd=pkg-config fi if [ -n "$pkgconf_cmd" ] && $pkgconf_cmd libpkgconf; then pkgconf_cflags="$($pkgconf_cmd --cflags libpkgconf) -DBOOTSTRAP_HAVE_LIBPKGCONF" pkgconf_libs="$($pkgconf_cmd --libs libpkgconf)" else pkgconf_cflags="" pkgconf_libs="" fi # shellcheck disable=SC2086 ${CC:-c99} ${CFLAGS:-} ${LDFLAGS:-} -Iinclude $pkgconf_cflags "src/amalgam.c" $pkgconf_libs -o "$dir/muon" muon-v0.3.0/src/0002755000175000017500000000000014674562002012435 5ustar buildbuildmuon-v0.3.0/src/platform/0002755000175000017500000000000014674562002014261 5ustar buildbuildmuon-v0.3.0/src/platform/path.c0000644000175000017500000002124014674562002015356 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 sbuf 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) { sbuf_clear(&path_ctx.cwd); while (!os_getcwd(path_ctx.cwd.buf, path_ctx.cwd.cap)) { if (errno == ERANGE) { sbuf_grow(NULL, &path_ctx.cwd, path_ctx.cwd.cap); } else { error_unrecoverable("getcwd failed: %s", strerror(errno)); } } } void path_init(void) { sbuf_init(&path_ctx.cwd, path_ctx.cwd_buf, ARRAY_LEN(path_ctx.cwd_buf), sbuf_flag_overflow_alloc); sbuf_init(&path_ctx.tmp1, path_ctx.tmp1_buf, ARRAY_LEN(path_ctx.tmp1_buf), sbuf_flag_overflow_alloc); sbuf_init(&path_ctx.tmp2, path_ctx.tmp2_buf, ARRAY_LEN(path_ctx.tmp2_buf), sbuf_flag_overflow_alloc); path_getcwd(); } void path_deinit(void) { sbuf_destroy(&path_ctx.cwd); sbuf_destroy(&path_ctx.tmp1); sbuf_destroy(&path_ctx.tmp2); } void _path_normalize(struct workspace *wk, struct sbuf *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; /* assert(slen && "path is empty"); */ 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 sbuf *sb, const char *path) { sbuf_clear(sb); sbuf_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 sbuf *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 sbuf *sb, const char *a, const char *b) { path_copy(wk, sb, a); sbuf_push(wk, sb, PATH_SEP); sbuf_pushs(wk, sb, b); _path_normalize(wk, sb, false); } void path_push(struct workspace *wk, struct sbuf *sb, const char *b) { if (path_is_absolute(b) || !sb->len) { path_copy(wk, sb, b); } else if (!*b) { /* Special-case path_1 / '' to mean "append a / to the current * path". */ _path_normalize(wk, sb, false); sbuf_push(wk, sb, PATH_SEP); } else { sbuf_push(wk, sb, PATH_SEP); sbuf_pushs(wk, sb, b); _path_normalize(wk, sb, false); } } void path_join(struct workspace *wk, struct sbuf *sb, const char *a, const char *b) { sbuf_clear(sb); path_push(wk, sb, a); path_push(wk, sb, b); } void path_make_absolute(struct workspace *wk, struct sbuf *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 sbuf *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" */ sbuf_clear(buf); sbuf_clear(&path_ctx.tmp1); sbuf_pushs(wk, &path_ctx.tmp1, base_raw); _path_normalize(wk, &path_ctx.tmp1, true); sbuf_clear(&path_ctx.tmp2); sbuf_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) { sbuf_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) { sbuf_pushs(wk, buf, ".."); sbuf_push(wk, buf, PATH_SEP); have_part = false; } if (base[i] == PATH_SEP) { have_part = true; } ++i; } while (base[i]); } if (path[common_end]) { sbuf_pushs(wk, buf, &path[common_end + 1]); } _path_normalize(wk, buf, false); } void path_without_ext(struct workspace *wk, struct sbuf *buf, const char *path) { int32_t i; sbuf_clear(buf); if (!*path) { return; } bool have_ext = false; SBUF_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) { sbuf_pushn(wk, buf, path, i); } else { path_copy(wk, buf, path); } _path_normalize(wk, buf, false); sbuf_destroy(&tmp); } void path_basename(struct workspace *wk, struct sbuf *buf, const char *path) { int32_t i; sbuf_clear(buf); if (!*path) { return; } SBUF_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; } sbuf_pushs(wk, buf, &path[i]); _path_normalize(wk, buf, false); sbuf_destroy(&tmp); } void path_dirname(struct workspace *wk, struct sbuf *buf, const char *path) { int32_t i; sbuf_clear(buf); if (!*path) { goto return_dot; } SBUF_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 '' */ sbuf_pushn(wk, buf, path, 1); } else { sbuf_pushn(wk, buf, path, i); } _path_normalize(wk, buf, false); sbuf_destroy(&tmp); return; } } sbuf_destroy(&tmp); return_dot: sbuf_pushs(wk, buf, "."); } bool path_is_subpath(const char *base, const char *sub) { if (!*base) { return false; } SBUF_manual(base_tmp); SBUF_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) { sbuf_destroy(&sub_tmp); sbuf_destroy(&base_tmp); return true; } } if (base[i] == sub[i]) { if (!base[i]) { sbuf_destroy(&sub_tmp); sbuf_destroy(&base_tmp); return true; } } else { sbuf_destroy(&sub_tmp); sbuf_destroy(&base_tmp); return false; } assert(base[i] && sub[i]); ++i; } } void path_executable(struct workspace *wk, struct sbuf *buf, const char *path) { if (path_is_basename(path)) { sbuf_clear(buf); sbuf_push(wk, buf, '.'); sbuf_push(wk, buf, PATH_SEP); sbuf_pushs(wk, buf, path); } else { path_copy(wk, buf, path); } } muon-v0.3.0/src/platform/filesystem.c0000644000175000017500000002046014674562002016611 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/filesystem.h" #include "platform/mem.h" #include "platform/os.h" // For S_ISDIR, S_ISREG on windows #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); SBUF_manual(buf); path_copy(NULL, &buf, path); assert(len >= 1); for (i = 1; 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: sbuf_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, .reopen_type = source_reopen_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->reopen_type = src->reopen_type; memcpy(buf, src->src, src->len); memcpy(&buf[src->len], src->label, label_len); } void fs_source_destroy(struct source *src) { 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; } } 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) { SBUF_manual(buf); bool res = fs_find_cmd(NULL, &buf, cmd); sbuf_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; } struct fs_copy_dir_ctx { const char *src_base, *dest_base; }; static 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; SBUF_manual(src); SBUF_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, true)) { goto ret; } if (!fs_copy_dir(src.buf, dest.buf)) { goto ret; } } else if (S_ISREG(sb.st_mode)) { if (!fs_copy_file(src.buf, dest.buf)) { goto ret; } } else { LOG_E("unhandled file type '%s'", path); goto ret; } res = ir_cont; ret: sbuf_destroy(&src); sbuf_destroy(&dest); return res; } bool fs_copy_dir(const char *src_base, const char *dest_base) { struct fs_copy_dir_ctx ctx = { .src_base = src_base, .dest_base = dest_base, }; if (!fs_mkdir(dest_base, true)) { return ir_err; } return fs_dir_foreach(src_base, &ctx, fs_copy_dir_iter); } 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; SBUF(name); path_join(NULL, &name, ctx->base_dir, path); 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: sbuf_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.3.0/src/platform/null/0002755000175000017500000000000014674562002015233 5ustar buildbuildmuon-v0.3.0/src/platform/null/rpath_fixer.c0000644000175000017500000000036614674562002017715 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.3.0/src/platform/meson.build0000644000175000017500000000157414674562002016430 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.3.0/src/platform/posix/0002755000175000017500000000000014674562002015423 5ustar buildbuildmuon-v0.3.0/src/platform/posix/path.c0000644000175000017500000000170214674562002016521 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; } void shell_escape(struct workspace *wk, struct sbuf *sb, const char *str) { const char *need_escaping = "\"'$ \\><&#()\n"; const char *s; bool do_esc = false; if (!*str) { sbuf_pushs(wk, sb, "''"); return; } for (s = str; *s; ++s) { if (strchr(need_escaping, *s)) { do_esc = true; break; } } if (!do_esc) { sbuf_pushs(wk, sb, str); return; } sbuf_push(wk, sb, '\''); for (s = str; *s; ++s) { if (*s == '\'') { sbuf_pushs(wk, sb, "'\\''"); } else { sbuf_push(wk, sb, *s); } } sbuf_push(wk, sb, '\''); } muon-v0.3.0/src/platform/posix/filesystem.c0000644000175000017500000001670414674562002017761 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include #include #include "buf_size.h" #include "lang/string.h" #include "log.h" #include "platform/filesystem.h" #include "platform/mem.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; } bool fs_copy_file(const char *src, const char *dest) { bool res = false; FILE *f_src = NULL; int f_dest = 0; 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 (!(f_src = fs_fopen(src, "r"))) { goto ret; } if ((f_dest = open(dest, O_CREAT | O_WRONLY | O_TRUNC, st.st_mode)) == -1) { LOG_E("failed to create destination file %s: %s", dest, strerror(errno)); goto ret; } assert(f_dest != 0); size_t r; ssize_t w; 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 ((w = write(f_dest, buf, r)) == -1) { LOG_E("failed write(): %s", strerror(errno)); goto ret; } else { assert(w >= 0); if ((size_t)w < r) { LOG_E("incomplete write: %s", strerror(errno)); goto ret; } } } if (!feof(f_src)) { LOG_E("incomplete read: %s", strerror(errno)); goto ret; } res = true; ret: if (f_src) { if (!fs_fclose(f_src)) { res = false; } } if (f_dest > 0) { if (close(f_dest) == -1) { LOG_E("failed close(): %s", strerror(errno)); 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 (symlinkat(target, 0, path) != 0) { LOG_E("failed symlink(\"%s\", \"%s\"): %s", target, path, strerror(errno)); return false; } return true; } const char * fs_user_home(void) { return getenv("HOME"); } bool fs_is_a_tty_from_fd(int fd) { errno = 0; if (isatty(fd) == 1) { return true; } else { if (errno != ENOTTY) { LOG_W("isatty() failed: %s", strerror(errno)); } 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)) { if (fchmodat(AT_FDCWD, path, (mode_t)mode, AT_SYMLINK_NOFOLLOW) == -1) { 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 sbuf *buf, const char *cmd) { assert(*cmd); uint32_t len; const char *env_path, *base_start; sbuf_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 = getenv("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; sbuf_clear(buf); sbuf_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; } muon-v0.3.0/src/platform/posix/rpath_fixer.c0000644000175000017500000001600614674562002020103 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.3.0/src/platform/posix/log.c0000644000175000017500000000043014674562002016343 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "platform/log.h" void print_colorized(FILE *out, const char *s) { fwrite(s, 1, strlen(s), out); } muon-v0.3.0/src/platform/posix/os.c0000644000175000017500000000200214674562002016200 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 "log.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 } muon-v0.3.0/src/platform/posix/uname.c0000644000175000017500000000210514674562002016670 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/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.3.0/src/platform/posix/term.c0000644000175000017500000000132514674562002016535 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 "log.h" #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.3.0/src/platform/posix/timer.c0000644000175000017500000000165514674562002016714 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/log.h" #include "platform/timer.h" void timer_start(struct timer *t) { if (clock_gettime(CLOCK_MONOTONIC, &t->start) == -1) { LOG_E("clock_gettime: %s", strerror(errno)); } } float timer_read(struct timer *t) { 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; } void timer_sleep(uint64_t nanoseconds) { struct timespec req = { .tv_nsec = nanoseconds, }; nanosleep(&req, NULL); } muon-v0.3.0/src/platform/posix/run_cmd.c0000644000175000017500000002413414674562002017220 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 "args.h" #include "buf_size.h" #include "error.h" #include "log.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "platform/run_cmd.h" extern char **environ; 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 sbuf *sbuf, 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; } sbuf_pushn(0, sbuf, 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; } 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; SBUF_manual(cmd); if (!fs_find_cmd(NULL, &cmd, _cmd)) { ctx->err_msg = "command not found"; return false; } if (log_should_print(log_debug)) { LL("executing %s:", cmd.buf); char *const *ap; for (ap = argv; *ap; ++ap) { log_plain(" '%s'", *ap); } log_plain("\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(" %s='%s'", k, p + 1); k = NULL; if (++i >= envc) { break; } } } } log_plain("\n"); } } 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; } if (!(ctx->flags & run_cmd_ctx_flag_dont_capture)) { sbuf_init(&ctx->out, 0, 0, sbuf_flag_overflow_alloc); sbuf_init(&ctx->err, 0, 0, sbuf_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(1); } } if (ctx->stdin_path) { if (dup2(ctx->input_fd, 0) == -1) { LOG_E("failed to dup stdin: %s", strerror(errno)); exit(1); } } 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(1); } if (dup2(ctx->pipefd_err[1], 2) == -1) { LOG_E("failed to dup stderr: %s", strerror(errno)); exit(1); } } 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(1); } k = NULL; if (++i >= envc) { break; } } } } } if (execve(cmd.buf, (char *const *)argv, environ) == -1) { LOG_E("%s: %s", cmd.buf, strerror(errno)); exit(1); } abort(); } /* parent */ sbuf_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 sbuf *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"); sbuf_clear(cmd); sbuf_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; SBUF_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); } sbuf_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; SBUF_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); } sbuf_destroy(&cmd); return ret; } void run_cmd_ctx_destroy(struct run_cmd_ctx *ctx) { run_cmd_ctx_close_fds(ctx); sbuf_destroy(&ctx->out); sbuf_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.3.0/src/platform/posix/init.c0000644000175000017500000000144414674562002016533 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.3.0/src/platform/windows/0002755000175000017500000000000014674562002015753 5ustar buildbuildmuon-v0.3.0/src/platform/windows/path.c0000644000175000017500000000320514674562002017051 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" /* * 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; } 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++; } } void shell_escape(struct workspace *wk, struct sbuf *sb, const char *str) { const char *need_escaping = "\"'$ \\><&#\n"; const char *s; bool do_esc = false; if (!*str) { sbuf_pushs(wk, sb, "\"\""); return; } for (s = str; *s; ++s) { if (strchr(need_escaping, *s)) { do_esc = true; break; } } if (!do_esc) { sbuf_pushs(wk, sb, str); return; } sbuf_push(wk, sb, '\"'); for (s = str; *s; ++s) { if (*s == '\"') { sbuf_pushs(wk, sb, "\\\""); } else { sbuf_push(wk, sb, *s); } } sbuf_push(wk, sb, '\"'); } muon-v0.3.0/src/platform/windows/filesystem.c0000644000175000017500000002412114674562002020301 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/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) { 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 getenv("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 sbuf *buf, const char *cmd) { assert(*cmd); uint32_t len; const char *env_path, *base_start; sbuf_clear(buf); if (!path_is_basename(cmd)) { path_make_absolute(wk, buf, cmd); if (fs_exe_exists(buf->buf)) { return true; } else { if (!fs_has_extension(buf->buf, ".exe")) { sbuf_pushs(wk, buf, ".exe"); if (fs_exe_exists(buf->buf)) { return true; } } return false; } } if (!(env_path = getenv("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; sbuf_clear(buf); sbuf_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")) { sbuf_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; } muon-v0.3.0/src/platform/windows/rpath_fixer.c0000644000175000017500000000046314674562002020433 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.3.0/src/platform/windows/log.c0000644000175000017500000000400414674562002016674 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) { 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; } 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.3.0/src/platform/windows/os.c0000644000175000017500000000762414674562002016547 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #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; } muon-v0.3.0/src/platform/windows/uname.c0000644000175000017500000000151514674562002017224 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.3.0/src/platform/windows/term.c0000644000175000017500000000157414674562002017073 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.3.0/src/platform/windows/timer.c0000644000175000017500000000111314674562002017231 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.3.0/src/platform/windows/win32_error.c0000644000175000017500000000242014674562002020266 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 "log.h" #include "platform/windows/win32_error.h" const char * win32_error(void) { static char _msg[4096]; LPTSTR msg; DWORD err; *_msg = '\0'; err = GetLastError(); if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0UL, (LPTSTR)&msg, 0UL, NULL)) { StringCchPrintf(_msg, sizeof(_msg), "FormatMessage() failed with error Id %ld", GetLastError()); return _msg; } StringCchCat(_msg, sizeof(_msg), msg); LocalFree(msg); // strip trailing newlines from the error message char *end = &_msg[strlen(_msg) - 1]; while (end > msg && strchr("\t\r\n ", *end)) { *end = 0; --end; } return _msg; } void win32_fatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); log_plainv(fmt, ap); va_end(ap); if (fmt[strlen(fmt) - 1] == ':') { log_plain(" %s", win32_error()); } log_plain("\n"); exit(1); } muon-v0.3.0/src/platform/windows/run_cmd.c0000644000175000017500000003306614674562002017554 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/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" static uint32_t cnt_open = 0; #define record_handle(__h, v) _record_handle(__h, v, #__h) static bool _record_handle(HANDLE *h, HANDLE v, const char *desc) { if (!v || v == INVALID_HANDLE_VALUE) { return false; } ++cnt_open; *h = v; return true; } #define close_handle(__h) _close_handle(__h, #__h) static bool _close_handle(HANDLE *h, const char *desc) { if (!*h || *h == INVALID_HANDLE_VALUE) { return true; } assert(cnt_open); if (!CloseHandle(*h)) { LOG_E("failed to close handle %s:%p: %s", desc, *h, win32_error()); return false; } --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 win_pipe_inst *pipe, struct sbuf *sbuf) { if (pipe->is_eof) { return copy_pipe_result_finished; } DWORD bytes_read; if (pipe->is_pending) { if (!GetOverlappedResult(pipe->handle, &pipe->overlapped, &bytes_read, TRUE)) { switch (GetLastError()) { case ERROR_BROKEN_PIPE: pipe->is_eof = true; if (!close_handle(&pipe->handle)) { return copy_pipe_result_failed; } return copy_pipe_result_finished; default: win32_fatal("GetOverlappedResult:"); return copy_pipe_result_failed; } } else { if (bytes_read) { sbuf_pushn(0, sbuf, pipe->overlapped_buf, bytes_read); pipe->overlapped.Offset = 0; pipe->overlapped.OffsetHigh = 0; memset(pipe->overlapped_buf, 0, sizeof(pipe->overlapped_buf)); } ResetEvent(pipe->overlapped.hEvent); } } if (!ReadFile( pipe->handle, pipe->overlapped_buf, sizeof(pipe->overlapped_buf), &bytes_read, &pipe->overlapped)) { switch (GetLastError()) { case ERROR_BROKEN_PIPE: pipe->is_eof = true; if (!close_handle(&pipe->handle)) { return copy_pipe_result_failed; } return copy_pipe_result_finished; case ERROR_IO_PENDING: pipe->is_pending = true; break; default: win32_fatal("ReadFile:"); return copy_pipe_result_failed; } } else { pipe->is_pending = false; if (bytes_read) { sbuf_pushn(0, sbuf, pipe->overlapped_buf, bytes_read); } } return copy_pipe_result_waiting; } static enum copy_pipe_result copy_pipes(struct run_cmd_ctx *ctx) { struct { struct win_pipe_inst *pipe; struct sbuf *sbuf; } pipes[2]; HANDLE events[2]; uint32_t event_count = 0; #define PUSH_PIPE(__p, __sb) \ if (!(__p)->is_eof) { \ pipes[event_count].pipe = (__p); \ pipes[event_count].sbuf = (__sb); \ events[event_count] = (__p)->event; \ ++event_count; \ } PUSH_PIPE(&ctx->pipe_out, &ctx->out); PUSH_PIPE(&ctx->pipe_err, &ctx->err); #undef PUSH_PIPE if (!event_count) { return copy_pipe_result_finished; } DWORD wait = WaitForMultipleObjects(event_count, events, FALSE, 100); if (wait == WAIT_TIMEOUT) { return copy_pipe_result_waiting; } else if (wait == WAIT_FAILED) { win32_fatal("WaitForMultipleObjects:"); } else if (WAIT_ABANDONED_0 <= wait && wait < WAIT_ABANDONED_0 + event_count) { win32_fatal("WaitForMultipleObjects: abandoned"); } else if (wait >= WAIT_OBJECT_0 + event_count) { win32_fatal("WaitForMultipleObjects: index out of range"); } else { wait -= WAIT_OBJECT_0; } return copy_pipe(pipes[wait].pipe, pipes[wait].sbuf); } 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_err.event); close_handle(&ctx->pipe_out.handle); close_handle(&ctx->pipe_out.event); // 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)) { 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; if (!record_handle(&pipe->event, CreateEvent(NULL, // default security attribute TRUE, // manual-reset event TRUE, // initial state = signaled NULL // unnamed event object ))) { win32_fatal("CreateEvent:"); } memset(&pipe->overlapped, 0, sizeof(pipe->overlapped)); pipe->overlapped.hEvent = pipe->event; 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 (!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; pipe->is_pending = true; return true; } static bool open_run_cmd_pipe(struct run_cmd_ctx *ctx) { if (ctx->flags & run_cmd_ctx_flag_dont_capture) { return true; } sbuf_init(&ctx->out, 0, 0, sbuf_flag_overflow_alloc); sbuf_init(&ctx->err, 0, 0, sbuf_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; if (envstr) { const char *k; uint32_t i = 0; p = k = envstr; for (;; ++p) { if (!p[0]) { if (!k) { k = p + 1; } else { SetEnvironmentVariable(k, p + 1); k = NULL; if (++i >= envc) { break; } } } } } if (log_should_print(log_debug)) { LL("executing : '%s'\n", command_line); if (envstr) { const char *k; uint32_t i = 0; LL("env:"); p = k = envstr; for (;; ++p) { if (!p[0]) { if (!k) { k = p + 1; } else { log_plain(" %s='%s'", k, p + 1); k = NULL; if (++i >= envc) { break; } } } } log_plain("\n"); } } // 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; res = CreateProcess(NULL, command_line, NULL, NULL, /* inherit handles */ TRUE, process_flags, NULL, 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 bool argv_to_command_line(struct run_cmd_ctx *ctx, struct source *src, const char *argstr, char *const *argv, uint32_t argstr_argc, struct sbuf *cmd) { const char *argv0, *new_argv0 = NULL, *new_argv1 = NULL; if (argstr) { argv0 = argstr; } else { argv0 = argv[0]; } if (!argv0 || !*argv0) { LOG_E("missing argv0"); return false; } sbuf_clear(cmd); if (!path_is_basename(argv0)) { if (fs_has_extension(argv0, ".bat")) { sbuf_pushf(0, cmd, "cmd.exe /c \"%s\"", argv0); } else if (fs_exists(argv0)) { DWORD _binary_type; if (!GetBinaryType(argv0, &_binary_type)) { 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) { argv0 = new_argv1; new_argv1 = 0; } else { argv0 = new_argv0; } } sbuf_pushf(0, cmd, "\"%s\"", argv0); } else { sbuf_pushf(0, cmd, "\"%s\"", argv0); } } else { SBUF_manual(found_cmd); if (!fs_find_cmd(0, &found_cmd, argv0)) { ctx->err_msg = "command not found"; sbuf_destroy(&found_cmd); return false; } sbuf_pushf(0, cmd, "\"%s\"", found_cmd.buf); sbuf_destroy(&found_cmd); } if (new_argv1) { sbuf_pushf(0, cmd, " \"%s\"", new_argv1); } if (argstr) { const char *p, *arg; uint32_t i = 0; arg = p = argstr; for (;; ++p) { if (!p[0]) { if (i > 0) { sbuf_pushf(0, cmd, " %s", arg); } if (++i >= argstr_argc) { break; } arg = p + 1; } } } else { uint32_t i; for (i = 1; argv[i]; ++i) { sbuf_pushf(0, cmd, " %s", argv[i]); } } return true; } 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 }; SBUF_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); sbuf_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 }; SBUF_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); sbuf_destroy(&cmd); return ret; } void run_cmd_ctx_destroy(struct run_cmd_ctx *ctx) { close_handle(&ctx->process); run_cmd_ctx_close_pipes(ctx); sbuf_destroy(&ctx->out); sbuf_destroy(&ctx->err); } 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.3.0/src/platform/windows/init.c0000644000175000017500000000065314674562002017064 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); } void platform_set_abort_handler(void((*handler)(void *ctx)), void *ctx) { } muon-v0.3.0/src/platform/os.c0000644000175000017500000000052314674562002015044 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #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; } } muon-v0.3.0/src/platform/uname.c0000644000175000017500000000051214674562002015526 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.3.0/src/platform/run_cmd.c0000644000175000017500000000525714674562002016063 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) { 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_chars(char **p, const char *skip, bool invert) { bool char_found; while (**p) { char_found = strchr(skip, **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; if ((p = strchr(&src->src[2], '\n'))) { *p = 0; } p = (char *)&src->src[2]; // skip over all whitespace characters before the next token if (!run_cmd_determine_interpreter_skip_chars(&p, " \r\t", 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_chars(&p, " \r\t", true)) { return true; } *p = 0; ++p; // skip over all whitespace characters before the next token if (!run_cmd_determine_interpreter_skip_chars(&p, " \r\t", false)) { return true; } *new_argv1 = p; return true; } muon-v0.3.0/src/platform/mem.c0000644000175000017500000000260314674562002015202 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.3.0/src/platform/assert.c0000644000175000017500000000063514674562002015730 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifdef _WIN32 #include #endif #include "log.h" #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.3.0/src/meson_opts.c0000644000175000017500000003477414674562002015004 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "args.h" #include "buf_size.h" #include "datastructures/arr.h" #include "error.h" #include "lang/string.h" #include "lang/workspace.h" #include "log.h" #include "meson_opts.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "version.h" #include #include struct translate_meson_opts_ctx { obj prepend_args; obj stray_args; obj argv; bool help; const char *subcommand; }; struct meson_option_spec { const char *name; bool has_value; const char *corresponding_option; bool ignore, help; uint32_t handle_as; }; typedef bool((*translate_meson_opts_func)(struct workspace *wk, char *argv[], uint32_t argc, struct translate_meson_opts_ctx *ctx)); typedef bool((*translate_meson_opts_callback)(struct workspace *wk, const struct meson_option_spec *spec, const char *val, struct translate_meson_opts_ctx *ctx)); static void translate_meson_opts_help(struct translate_meson_opts_ctx *ctx, const struct meson_option_spec *opts, uint32_t opts_len) { printf("This is the muon meson cli compatibility layer.\n" "usage for subcommand %s:\n", ctx->subcommand); bool any_ignored = false, indent = true; printf("opts:\n"); uint32_t i; for (i = 0; i < opts_len; ++i) { any_ignored |= opts[i].ignore; if (indent) { printf(" "); } printf("%s%s%s%s%s", (opts[i].ignore && log_clr()) ? "\033[31m" : "", opts[i].ignore ? "*" : (indent ? " " : ""), (opts[i].ignore && log_clr()) ? "\033[0m" : "", opts[i].name[1] ? "--" : "-", opts[i].name); if (i + 1 < opts_len && ((opts[i].help && opts[i + 1].help) || (opts[i].handle_as && opts[i + 1].handle_as && opts[i].handle_as == opts[i + 1].handle_as))) { printf(", "); indent = false; continue; } if (opts[i].has_value) { printf(" "); } else if (opts[i].help) { printf(" - show this message"); } if (opts[i].corresponding_option) { printf(" - -D%s=%s", opts[i].corresponding_option, opts[i].has_value ? "" : "true"); } printf("\n"); indent = true; } if (any_ignored) { printf("%s*%s denotes an ignored option.\n", log_clr() ? "\033[31m" : "", log_clr() ? "\033[0m" : ""); } } static bool translate_meson_opts_parser(struct workspace *wk, char *argv[], uint32_t argc, struct translate_meson_opts_ctx *ctx, const struct meson_option_spec *opts, uint32_t opts_len, translate_meson_opts_callback cb) { uint32_t argi = 0, i; bool is_opt, is_longopt, next_is_val = false; const struct meson_option_spec *spec; const char *val; while (argi < argc) { spec = NULL; val = NULL; struct str *arg = &WKSTR(argv[argi]); is_opt = !next_is_val && arg->len > 1 && arg->s[0] == '-'; is_longopt = is_opt && arg->len > 2 && arg->s[1] == '-'; if (is_longopt) { arg->s += 2; arg->len -= 2; char *sep; if ((sep = strchr(argv[argi], '='))) { val = sep + 1; arg->len -= (1 + strlen(val)); } } else if (is_opt) { arg->s += 1; arg->len -= 1; if (arg->len > 1) { val = arg->s + 1; arg->len -= strlen(val); } } if (is_opt) { for (i = 0; i < opts_len; ++i) { const struct str *opt_name = &WKSTR(opts[i].name); if (!str_eql(arg, opt_name)) { continue; } spec = &opts[i]; ++argi; break; } } else { obj_array_push(wk, ctx->stray_args, make_str(wk, argv[argi])); ++argi; continue; } if (!spec) { LOG_E("unknown option '%s'", argv[argi]); return false; } if (spec->has_value && !val) { if (argi + 1 > argc) { LOG_E("option '%s' requires an argument", argv[argi - 1]); return false; } val = argv[argi]; ++argi; } if (spec->ignore) { continue; } else if (spec->corresponding_option) { if (!spec->has_value) { val = "true"; } obj_array_push(wk, ctx->argv, make_strf(wk, "-D%s=%s", spec->corresponding_option, val)); } else if (spec->help) { ctx->help = true; translate_meson_opts_help(ctx, opts, opts_len); } else if (spec->handle_as) { if (!cb(wk, spec, val, ctx)) { return false; } } else { UNREACHABLE; } } return true; } enum meson_opts_test { opt_test_chdir = 1, opt_test_no_rebuild, opt_test_list, opt_test_suite, opt_test_benchmark, opt_test_workers, opt_test_verbose, opt_test_setup, opt_test_fail_fast, }; static bool translate_meson_opts_test_callback(struct workspace *wk, const struct meson_option_spec *spec, const char *val, struct translate_meson_opts_ctx *ctx) { switch ((enum meson_opts_test)(spec->handle_as)) { case opt_test_no_rebuild: obj_array_push(wk, ctx->argv, make_str(wk, "-R")); break; case opt_test_list: obj_array_push(wk, ctx->argv, make_str(wk, "-l")); break; case opt_test_suite: obj_array_push(wk, ctx->argv, make_strf(wk, "-s%s", val)); break; case opt_test_benchmark: obj_array_set(wk, ctx->argv, 0, make_str(wk, "benchmark")); break; case opt_test_workers: obj_array_push(wk, ctx->argv, make_strf(wk, "-j%s", val)); break; case opt_test_verbose: obj_array_push(wk, ctx->argv, make_strf(wk, "-v")); break; case opt_test_setup: obj_array_push(wk, ctx->argv, make_strf(wk, "-e%s", val)); break; case opt_test_chdir: obj_array_push(wk, ctx->prepend_args, make_strf(wk, "-C%s", val)); break; case opt_test_fail_fast: { if (strcmp(val, "1") != 0) { LOG_E("--maxfail only supports 1, value %s unsupported", val); return false; } obj_array_push(wk, ctx->argv, make_strf(wk, "-f")); break; } default: UNREACHABLE; } return true; } static bool translate_meson_opts_test(struct workspace *wk, char *argv[], uint32_t argc, struct translate_meson_opts_ctx *ctx) { struct meson_option_spec opts[] = { { "h", .help = true }, { "help", .help = true }, { "C", true, .handle_as = opt_test_chdir }, { "v", .handle_as = opt_test_verbose }, { "maxfail", true, .handle_as = opt_test_fail_fast }, { "no-rebuild", .handle_as = opt_test_no_rebuild }, { "list", .handle_as = opt_test_list }, { "suite", true, .handle_as = opt_test_suite }, { "benchmark", .handle_as = opt_test_benchmark }, { "num-processes", true, .handle_as = opt_test_workers }, { "setup", true, .handle_as = opt_test_setup }, { "q", .ignore = true, .handle_as = 0xfff0 }, { "quiet", .ignore = true, .handle_as = 0xfff0 }, { "t", true, .ignore = true, .handle_as = 0xfff1 }, { "timeout-multiplier", true, .ignore = true, .handle_as = 0xfff1 }, { "gdb", .ignore = true }, { "gdb-path", true, .ignore = true }, { "repeat", true, .ignore = true }, { "wrapper", true, .ignore = true, }, { "no-suite", .ignore = true }, { "no-stdsplit", .ignore = true }, { "print-errorlogs", .ignore = true }, { "logbase", true, .ignore = true }, { "test-args", true, .ignore = true }, }; obj_array_push(wk, ctx->argv, make_str(wk, "test")); obj_array_push(wk, ctx->argv, make_str(wk, "-v")); return translate_meson_opts_parser( wk, argv, argc, ctx, opts, ARRAY_LEN(opts), translate_meson_opts_test_callback); } enum meson_opts_install { opt_install_dryrun = 1, opt_install_chdir, opt_install_destdir, }; static bool translate_meson_opts_install_callback(struct workspace *wk, const struct meson_option_spec *spec, const char *val, struct translate_meson_opts_ctx *ctx) { switch ((enum meson_opts_install)(spec->handle_as)) { case opt_install_dryrun: { obj_array_push(wk, ctx->argv, make_str(wk, "-n")); break; } case opt_install_chdir: obj_array_push(wk, ctx->prepend_args, make_strf(wk, "-C%s", val)); break; case opt_install_destdir: obj_array_push(wk, ctx->argv, make_strf(wk, "-d%s", val)); break; default: UNREACHABLE; } return true; } static bool translate_meson_opts_install(struct workspace *wk, char *argv[], uint32_t argc, struct translate_meson_opts_ctx *ctx) { struct meson_option_spec opts[] = { { "h", .help = true }, { "help", .help = true }, { "C", true, .handle_as = opt_install_chdir }, { "n", .handle_as = opt_install_dryrun }, { "dryrun", .handle_as = opt_install_dryrun }, { "destdir", true, .handle_as = opt_install_destdir }, { "no-rebuild", .ignore = true }, { "only-changed", .ignore = true }, { "quiet", .ignore = true }, { "skip-subprojects", true, .ignore = true }, { "tags", true, .ignore = true }, { "strip", .ignore = true }, }; obj_array_push(wk, ctx->argv, make_str(wk, "install")); if (!translate_meson_opts_parser( wk, argv, argc, ctx, opts, ARRAY_LEN(opts), translate_meson_opts_install_callback)) { return false; } return true; } enum meson_opts_setup { opt_setup_version = 1, opt_setup_define, }; static bool translate_meson_opts_setup_callback(struct workspace *wk, const struct meson_option_spec *spec, const char *val, struct translate_meson_opts_ctx *ctx) { switch ((enum meson_opts_setup)(spec->handle_as)) { case opt_setup_version: { obj_array_prepend(wk, &ctx->argv, make_str(wk, "version")); break; } case opt_setup_define: obj_array_push(wk, ctx->argv, make_strf(wk, "-D%s", val)); break; default: UNREACHABLE; } return true; } static bool translate_meson_opts_setup(struct workspace *wk, char *argv[], uint32_t argc, struct translate_meson_opts_ctx *ctx) { struct meson_option_spec opts[] = { { "h", .help = true }, { "help", .help = true }, { "v", .handle_as = opt_setup_version }, { "D", true, .handle_as = opt_setup_define }, { "prefix", true, "prefix" }, { "bindir", true, "bindir", }, { "datadir", true, "datadir" }, { "includedir", true, "includedir" }, { "infodir", true, "infodir" }, { "libdir", true, "libdir", }, { "libexecdir", true, "libexecdir" }, { "localedir", true, "localedir" }, { "localstatedir", true, "localstatedir" }, { "mandir", true, "mandir" }, { "sbindir", true, "sbindir" }, { "sharedstatedir", true, "sharedstatedir" }, { "sysconfdir", true, "sysconfdir" }, { "auto-features", true, "auto_features" }, { "backend", true, "backend" }, { "build.cmake-prefix-path", true, "cmake_prefix_path" }, { "build.pkg-config-path", true, "pkg_config_path" }, { "buildtype", true, "buildtype" }, { "cmake-prefix-path", true, "cmake_prefix_path" }, { "cross-file", true, .ignore = true, }, { "debug", false, "debug", }, { "default-library", true, "default_library" }, { "errorlogs", .ignore = true }, { "fatal-meson-warnings", .ignore = true }, { "force-fallback-for", true, "force_fallback_for", }, { "install-umask", true, "insall_umask" }, { "layout", true, "layout", }, { "native-file", true, .ignore = true }, { "optimization", true, "optimization" }, { "pkg-config-path", true, "pkg_config_path", }, { "pkgconfig.relocatable", .ignore = true }, { "prefer-static", false, "prefer_static" }, { "python.install-env", true, .ignore = true }, { "python.bytecompile", true, .ignore = true }, { "python.platlibdir", true }, { "python.purelibdir", true }, { "python.allow_limited_api", true, .ignore = true }, { "stdsplit", false, "stdsplit" }, { "strip", false, "strip" }, { "unity", true, "unity" }, { "unity-size", true, "unity_size" }, { "warnlevel", true, "warning_level" }, { "werror", false, "werror" }, { "wrap-mode", true, "wrap_mode" }, { "reconfigure", .ignore = true }, { "version", .handle_as = opt_setup_version }, { "vsenv", .ignore = true }, { "wipe", .ignore = true }, }; obj_array_push(wk, ctx->argv, make_str(wk, "setup")); if (!translate_meson_opts_parser( wk, argv, argc, ctx, opts, ARRAY_LEN(opts), translate_meson_opts_setup_callback)) { return false; } if (get_obj_array(wk, ctx->stray_args)->len == 2) { // We were passed a source dir and a build dir. This can be // accomplished in muon via -C setup , with the // additional requirement that the build dir be specified // relative to the source dir, or absolute. obj build, src; obj_array_index(wk, ctx->stray_args, 0, &build); obj_array_index(wk, ctx->stray_args, 1, &src); SBUF(build_dir); path_make_absolute(wk, &build_dir, get_cstr(wk, build)); obj_array_push(wk, ctx->prepend_args, make_strf(wk, "-C%s", get_cstr(wk, src))); make_obj(wk, &ctx->stray_args, obj_array); obj_array_push(wk, ctx->stray_args, sbuf_into_str(wk, &build_dir)); } return true; } static const struct { const char *name; translate_meson_opts_func translate_func; } meson_opts_subcommands[] = { { "setup", translate_meson_opts_setup }, { "configure", translate_meson_opts_setup }, { "install", translate_meson_opts_install, }, { "test", translate_meson_opts_test, }, }; static translate_meson_opts_func meson_opts_subcommand(const char *arg) { uint32_t i; for (i = 0; i < ARRAY_LEN(meson_opts_subcommands); ++i) { if (strcmp(arg, meson_opts_subcommands[i].name) == 0) { return meson_opts_subcommands[i].translate_func; } } LOG_E("unknown subcommand '%s'", arg); return 0; } static void print_meson_opts_usage(void) { printf("opts:\n" " -v, --version - print the meson compat version and exit\n" " -h [subcommand] - print this message or show help for a subcommand\n" "commands:\n"); uint32_t i; for (i = 0; i < ARRAY_LEN(meson_opts_subcommands); ++i) { printf(" %s\n", meson_opts_subcommands[i].name); } } 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[]) { if (argc - argi < 1) { print_meson_opts_usage(); return false; } translate_meson_opts_func translate_func = 0; if (strcmp(argv[argi], "-v") == 0 || strcmp(argv[argi], "--version") == 0) { printf("%s\n", muon_version.meson_compat); exit(0); } else if (strcmp(argv[argi], "-h") == 0) { printf("This is the muon meson cli compatibility layer.\n"); print_meson_opts_usage(); exit(0); } if (!(translate_func = meson_opts_subcommand(argv[argi]))) { return false; } struct translate_meson_opts_ctx ctx = { .subcommand = argv[argi] }; make_obj(wk, &ctx.argv, obj_array); make_obj(wk, &ctx.prepend_args, obj_array); make_obj(wk, &ctx.stray_args, obj_array); ++argi; if (!translate_func(wk, argv + argi, argc - argi, &ctx)) { /* LOG_E("failed to translate"); */ return false; } if (ctx.help) { exit(0); } obj_array_prepend(wk, &ctx.prepend_args, make_str(wk, argv[0])); obj_array_extend_nodup(wk, ctx.prepend_args, ctx.argv); ctx.argv = ctx.prepend_args; obj_array_extend_nodup(wk, ctx.argv, ctx.stray_args); obj_fprintf(wk, log_file(), "args: %o\n", ctx.argv); const char *argstr; join_args_argstr(wk, &argstr, new_argc, ctx.argv); argstr_to_argv(argstr, *new_argc, NULL, (char *const **)new_argv); *new_argi = 0; return true; } muon-v0.3.0/src/datastructures/0002755000175000017500000000000014674562002015512 5ustar buildbuildmuon-v0.3.0/src/datastructures/arr.c0000644000175000017500000000626614674562002016452 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include "datastructures/arr.h" #include "log.h" #include "platform/assert.h" #include "platform/mem.h" void arr_init_flags(struct arr *arr, uint32_t initial, uint32_t item_size, uint32_t flags) { assert(item_size > 0); *arr = (struct arr){ .item_size = item_size, .cap = initial, .flags = flags, .e = (flags & arr_flag_zero_memory) ? z_calloc(initial, item_size) : z_malloc(initial * item_size), }; } void arr_init(struct arr *arr, uint32_t initial, uint32_t item_size) { arr_init_flags(arr, initial, item_size, 0); } void arr_destroy(struct arr *arr) { if (arr->e) { z_free(arr->e); arr->e = NULL; } } void arr_clear(struct arr *arr) { arr->len = 0; } static uint8_t * arr_point_at(const struct arr *arr, uint32_t i) { return arr->e + (i * arr->item_size); } static void * arr_get_mem(struct arr *arr) { uint32_t i, newcap; ++arr->len; /* ensure_mem_size(elem, size, ++(*len), cap); */ if (arr->len > arr->cap) { assert(arr->cap); newcap = arr->cap * 2; if (newcap < arr->len) { newcap = arr->len * 2; } arr->e = z_realloc(arr->e, newcap * arr->item_size); if (arr->flags & arr_flag_zero_memory) { memset(arr->e + (arr->cap * arr->item_size), 0, (newcap - arr->cap) * arr->item_size); } arr->cap = newcap; } else { /* NOTE: uncomment the below line to cause a realloc for * _every_ push into a arr. This can help find bugs where you * held a pointer into a arr too long. */ /* arr->e = z_realloc(arr->e, arr->cap * arr->item_size); */ } i = arr->len - 1; return arr_point_at(arr, i); } void arr_grow_by(struct arr *arr, uint32_t size) { arr->len += size - 1; arr_get_mem(arr); } void arr_grow_to(struct arr *arr, uint32_t size) { arr->len = size - 1; arr_get_mem(arr); } uint32_t arr_push(struct arr *arr, const void *item) { memcpy(arr_get_mem(arr), item, arr->item_size); return arr->len - 1; } void * arr_get(const struct arr *arr, uint32_t i) { if (i >= arr->len) { L("index %" PRIu64 " out of bounds (%" PRIu64 ")", (uint64_t)i, (uint64_t)arr->len); } assert(i < arr->len); return arr_point_at(arr, i); } void arr_del(struct arr *arr, uint32_t i) { assert(i < arr->len); arr->len--; if (arr->len > 0 && arr->len != i) { memmove(arr_point_at(arr, i), arr_point_at(arr, arr->len), arr->item_size); } } static struct { void *user_ctx; sort_func func; } arr_sort_ctx; static int32_t arr_sort_compare(const void *a, const void *b) { return arr_sort_ctx.func(a, b, arr_sort_ctx.user_ctx); } void arr_sort_range(struct arr *arr, uint32_t start, uint32_t end, void *ctx, sort_func func) { arr_sort_ctx.user_ctx = ctx; arr_sort_ctx.func = func; qsort(arr_point_at(arr, start), end - start, arr->item_size, arr_sort_compare); } void arr_sort(struct arr *arr, void *ctx, sort_func func) { arr_sort_range(arr, 0, arr->len, ctx, func); } void * arr_pop(struct arr *arr) { assert(arr->len); --arr->len; return arr_point_at(arr, arr->len); } void * arr_peek(struct arr *arr, uint32_t i) { return arr_point_at(arr, arr->len - i); } muon-v0.3.0/src/datastructures/stack.c0000644000175000017500000000457714674562002016776 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "datastructures/stack.h" #include "log.h" #include "platform/assert.h" #include "platform/mem.h" /****************************************************************************** * stack ******************************************************************************/ void stack_init(struct stack *stack, uint32_t cap) { *stack = (struct stack){ .mem = z_malloc(cap), .cap = cap, }; } void stack_destroy(struct stack *stack) { z_free(stack->mem); } struct stack_tag { const char *name; uint32_t size; }; static void stack_push_raw(struct stack *stack, const void *mem, uint32_t size) { assert(stack->len + size < stack->cap); memcpy(stack->mem + stack->len, mem, size); stack->len += size; } static void stack_pop_raw(struct stack *stack, void *mem, uint32_t size) { assert(stack->len >= size); stack->len -= size; memcpy(mem, stack->mem + stack->len, size); } static void stack_peek_raw(struct stack *stack, void *mem, uint32_t size, uint32_t *off) { assert(*off >= size); *off -= size; memcpy(mem, stack->mem + *off, size); } void stack_print(struct stack *_stack) { struct stack_tag tag; struct stack stack = *_stack; while (stack.len) { stack_pop_raw(&stack, &tag, sizeof(tag)); printf(" - %d, %04d - %s", stack.len, tag.size, tag.name); assert(stack.len >= tag.size); stack.len -= tag.size; if (tag.size == 1) { printf(" %02x", *(uint8_t *)(stack.mem + stack.len)); } printf("\n"); } } void stack_push_sized(struct stack *stack, const void *mem, uint32_t size, const char *name) { stack_push_raw(stack, mem, size); stack_push_raw(stack, &(struct stack_tag){ name, size }, sizeof(struct stack_tag)); /* L("\033[33mstack\033[0m %05d pushed %s (%d)", stack->len, name, size); */ } void stack_pop_sized(struct stack *stack, void *mem, uint32_t size) { struct stack_tag tag; stack_pop_raw(stack, &tag, sizeof(tag)); assert(size == tag.size); stack_pop_raw(stack, mem, size); /* L("\033[33mstack\033[0m %05d popped %s (%d)", stack->len, tag.name, tag.size); */ } void stack_peek_sized(struct stack *stack, void *mem, uint32_t size) { uint32_t off = stack->len; struct stack_tag tag; stack_peek_raw(stack, &tag, sizeof(tag), &off); assert(size == tag.size); stack_peek_raw(stack, mem, size, &off); } muon-v0.3.0/src/datastructures/bucket_arr.c0000644000175000017500000000704514674562002020003 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "datastructures/bucket_arr.h" #include "log.h" #include "platform/assert.h" #include "platform/mem.h" void init_bucket(struct bucket_arr *ba, struct bucket *b) { b->mem = z_calloc(ba->item_size, ba->bucket_size); } uint64_t bucket_arr_size(struct bucket_arr *ba) { return ba->buckets.len * ba->item_size * ba->bucket_size; } void bucket_arr_init(struct bucket_arr *ba, uint32_t bucket_size, uint32_t item_size) { assert(item_size > 0); *ba = (struct bucket_arr){ .item_size = item_size, .bucket_size = bucket_size, }; arr_init(&ba->buckets, 1, sizeof(struct bucket)); arr_push(&ba->buckets, &(struct bucket){ 0 }); init_bucket(ba, arr_get(&ba->buckets, 0)); } void bucket_arr_clear(struct bucket_arr *ba) { uint32_t i; struct bucket *b; for (i = 0; i < ba->buckets.len; ++i) { b = arr_get(&ba->buckets, i); b->len = 0; } ba->buckets.len = 1; ba->tail_bucket = 0; ba->len = 0; } void bucket_arr_save(const struct bucket_arr *ba, struct bucket_arr_save *save) { struct bucket *b; b = arr_get(&ba->buckets, ba->tail_bucket); save->tail_bucket = ba->tail_bucket; save->tail_bucket_len = b->len; } void bucket_arr_restore(struct bucket_arr *ba, const struct bucket_arr_save *save) { struct bucket *b; b = arr_get(&ba->buckets, save->tail_bucket); assert(save->tail_bucket_len <= b->len); ba->len -= b->len - save->tail_bucket_len; b->len = save->tail_bucket_len; memset(&b->mem[b->len * ba->item_size], 0, (ba->bucket_size - b->len) * ba->item_size); uint32_t bi; for (bi = save->tail_bucket + 1; bi < ba->buckets.len; ++bi) { b = arr_get(&ba->buckets, bi); memset(b->mem, 0, b->len * ba->item_size); ba->len -= b->len; b->len = 0; } ba->tail_bucket = save->tail_bucket; } void * bucket_arr_pushn(struct bucket_arr *ba, const void *data, uint32_t data_len, uint32_t reserve) { void *dest; struct bucket *b; assert(reserve >= data_len); assert(reserve <= ba->bucket_size); b = arr_get(&ba->buckets, ba->tail_bucket); if (b->len + reserve > ba->bucket_size) { if (ba->tail_bucket >= ba->buckets.len - 1) { arr_push(&ba->buckets, &(struct bucket){ 0 }); ++ba->tail_bucket; b = arr_get(&ba->buckets, ba->tail_bucket); init_bucket(ba, b); } else { ++ba->tail_bucket; b = arr_get(&ba->buckets, ba->tail_bucket); assert(b->mem); assert(b->len == 0); } } dest = b->mem + (b->len * ba->item_size); if (data) { memcpy(dest, data, ba->item_size * data_len); } b->len += reserve; ba->len += reserve; return dest; } void * bucket_arr_push(struct bucket_arr *ba, const void *item) { return bucket_arr_pushn(ba, item, 1, 1); } void * bucket_arr_get(const struct bucket_arr *ba, uint32_t i) { struct bucket *b; uint32_t bucket_i = i % ba->bucket_size; b = arr_get(&ba->buckets, i / ba->bucket_size); assert(bucket_i < b->len); return b->mem + (bucket_i * ba->item_size); } void bucket_arr_destroy(struct bucket_arr *ba) { uint32_t i; struct bucket *b; for (i = 0; i < ba->buckets.len; ++i) { b = arr_get(&ba->buckets, i); z_free(b->mem); } arr_destroy(&ba->buckets); } bool bucket_arr_lookup_pointer(struct bucket_arr *ba, const uint8_t *p, uint64_t *ret) { uint32_t i; for (i = 0; i < ba->buckets.len; ++i) { struct bucket *b = arr_get(&ba->buckets, i); if (b->mem <= p && p < b->mem + (b->len * ba->item_size)) { *ret = i * ba->bucket_size + (p - b->mem) / ba->item_size; return true; } } return false; } muon-v0.3.0/src/datastructures/hash.c0000644000175000017500000001422614674562002016604 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "datastructures/arr.h" #include "datastructures/hash.h" #include "log.h" #include "platform/assert.h" #include "platform/mem.h" #define k_empty 0x80 // 0b10000000 #define k_deleted 0xfe // 0b11111110 #define k_full(v) !(v & (1 << 7)) // k_full = 0b0xxxxxxx #define ASSERT_VALID_CAP(cap) \ assert(cap >= 8); \ assert((cap & (cap - 1)) == 0); #define LOAD_FACTOR 0.5f struct strkey { const char *str; uint64_t len; }; static uint64_t fnv_1a_64_str(const struct hash *hash, const void *_key) { const struct strkey *key = _key; uint64_t h = 14695981039346656037u; uint16_t i; for (i = 0; i < key->len; i++) { h ^= key->str[i]; h *= 1099511628211u; } return h; } static uint64_t fnv_1a_64(const struct hash *hash, const void *_key) { const uint8_t *key = _key; uint64_t h = 14695981039346656037u; uint16_t i; for (i = 0; i < hash->keys.item_size; i++) { h ^= key[i]; h *= 1099511628211u; } return h; } struct hash_elem { uint64_t val, keyi; }; static void fill_meta_with_empty(struct hash *h) { const uint32_t len = h->cap >> 3; uint64_t *e = (uint64_t *)h->meta.e; uint32_t i; for (i = 0; i < len; ++i) { e[i] = 9259542123273814144u; /* this number is just k_empty (128) 8 times: * ((128 << 56) | (128 << 48) | (128 << 40) | (128 << 32) * | (128 << 24) | (128 << 16) | (128 << 8) | (128)) */ } } static void prepare_table(struct hash *h) { fill_meta_with_empty(h); } static bool hash_keycmp_memcmp(const struct hash *h, const void *a, const void *b) { return memcmp(a, b, h->keys.item_size) == 0; } void hash_init(struct hash *h, uint32_t cap, uint32_t keysize) { ASSERT_VALID_CAP(cap); *h = (struct hash){ .cap = cap, .capm = cap - 1, .max_load = (uint32_t)((float)cap * LOAD_FACTOR) }; arr_init(&h->meta, h->cap, sizeof(uint8_t)); arr_init(&h->e, h->cap, sizeof(struct hash_elem)); arr_init(&h->keys, h->cap, keysize); prepare_table(h); h->keycmp = hash_keycmp_memcmp; h->hash_func = fnv_1a_64; } static bool hash_keycmp_strcmp(const struct hash *_h, const void *_a, const void *_b) { const struct strkey *a = _a, *b = _b; return a->len == b->len ? strncmp(a->str, b->str, a->len) == 0 : false; } void hash_init_str(struct hash *h, uint32_t cap) { hash_init(h, cap, sizeof(struct strkey)); h->keycmp = hash_keycmp_strcmp; h->hash_func = fnv_1a_64_str; } void hash_destroy(struct hash *h) { arr_destroy(&h->meta); arr_destroy(&h->e); arr_destroy(&h->keys); } void hash_for_each(struct hash *h, void *ctx, iterator_func ifnc) { uint32_t i; for (i = 0; i < h->cap; ++i) { if (!k_full(((uint8_t *)h->meta.e)[i])) { continue; } switch (ifnc(ctx, &((struct hash_elem *)h->e.e)[i].val)) { case ir_cont: break; case ir_done: case ir_err: return; } } } void hash_for_each_with_keys(struct hash *h, void *ctx, hash_with_keys_iterator_func ifnc) { uint32_t i; struct hash_elem *he; for (i = 0; i < h->cap; ++i) { if (!k_full(((uint8_t *)h->meta.e)[i])) { continue; } he = &((struct hash_elem *)h->e.e)[i]; switch (ifnc(ctx, h->keys.e + he->keyi * h->keys.item_size, he->val)) { case ir_cont: break; case ir_done: case ir_err: return; } } } void hash_clear(struct hash *h) { h->len = h->load = 0; fill_meta_with_empty(h); } static void probe(const struct hash *h, const void *key, struct hash_elem **ret_he, uint8_t **ret_meta, uint64_t *hv) { #define match ((meta & 0x7f) == h2 && h->keycmp(h, h->keys.e + (h->keys.item_size * he->keyi), key)) struct hash_elem *he; *hv = h->hash_func(h, key); const uint64_t h1 = *hv >> 7, h2 = *hv & 0x7f; uint8_t meta; uint64_t hvi = h1 & h->capm; meta = ((uint8_t *)h->meta.e)[hvi]; he = &((struct hash_elem *)h->e.e)[hvi]; while (meta == k_deleted || (k_full(meta) && !match)) { hvi = (hvi + 1) & h->capm; meta = ((uint8_t *)h->meta.e)[hvi]; he = &((struct hash_elem *)h->e.e)[hvi]; } *ret_meta = &((uint8_t *)h->meta.e)[hvi]; *ret_he = he; #undef match } static void resize(struct hash *h, uint32_t newcap) { ASSERT_VALID_CAP(newcap); assert(h->len <= newcap); uint32_t i; struct hash_elem *ohe, *he; uint64_t hv; uint8_t *meta; void *key; struct hash newh = (struct hash){ .cap = newcap, .capm = newcap - 1, .keys = h->keys, .len = h->len, .load = h->load, .max_load = (uint32_t)((float)newcap * LOAD_FACTOR), .hash_func = h->hash_func, .keycmp = h->keycmp, }; arr_init(&newh.meta, newh.cap, sizeof(uint8_t)); arr_init(&newh.e, newh.cap, sizeof(struct hash_elem)); prepare_table(&newh); for (i = 0; i < h->cap; ++i) { if (!k_full(((uint8_t *)h->meta.e)[i])) { continue; } ohe = &((struct hash_elem *)h->e.e)[i]; key = h->keys.e + (h->keys.item_size * ohe->keyi); probe(&newh, key, &he, &meta, &hv); assert(!k_full(*meta)); *he = *ohe; *meta = hv & 0x7f; } arr_destroy(&h->meta); arr_destroy(&h->e); *h = newh; } uint64_t * hash_get(const struct hash *h, const void *key) { struct hash_elem *he; uint64_t hv; uint8_t *meta; probe(h, key, &he, &meta, &hv); return k_full(*meta) ? &he->val : NULL; } uint64_t * hash_get_strn(const struct hash *h, const char *str, uint64_t len) { struct strkey key = { .str = str, .len = len }; return hash_get(h, &key); } void hash_unset(struct hash *h, const void *key) { struct hash_elem *he; uint64_t hv; uint8_t *meta; probe(h, key, &he, &meta, &hv); if (k_full(*meta)) { *meta = k_deleted; --h->len; } assert(hash_get(h, key) == NULL); } void hash_unset_strn(struct hash *h, const char *s, uint64_t len) { struct strkey key = { .str = s, .len = len }; hash_unset(h, &key); } void hash_set(struct hash *h, const void *key, uint64_t val) { if (h->load > h->max_load) { resize(h, h->cap << 1); } struct hash_elem *he; uint64_t hv; uint8_t *meta; probe(h, key, &he, &meta, &hv); if (k_full(*meta)) { he->val = val; } else { he->keyi = arr_push(&h->keys, key); he->val = val; *meta = hv & 0x7f; ++h->len; ++h->load; } } void hash_set_strn(struct hash *h, const char *s, uint64_t len, uint64_t val) { struct strkey key = { .str = s, .len = len }; hash_set(h, &key, val); } muon-v0.3.0/src/install.c0000644000175000017500000001263114674562002014250 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "coerce.h" #include "error.h" #include "install.h" #include "log.h" #include "options.h" #include "platform/assert.h" #include "platform/os.h" #include "platform/path.h" static bool rwx_to_perm(const char *rwx, uint32_t *perm) { assert(rwx && perm); if (strlen(rwx) != 9) { return false; } uint32_t bit = S_IRUSR; // 400 uint32_t i; for (i = 0; i < 9; ++i) { switch (rwx[i]) { case '-': break; case 't': case 'T': if (i != 8) { return false; } if (!S_ISVTX) { LOG_W("sticky bit requested, but support is not compiled in"); } *perm |= S_ISVTX; break; case 's': case 'S': if (i != 2 && i != 5) { return false; } *perm |= i == 2 ? S_ISUID : S_ISGID; break; case 'r': if (i != 0 && i != 3 && i != 6) { return false; } break; case 'w': if (i != 1 && i != 4 && i != 7) { return false; } break; case 'x': if (i != 2 && i != 5 && i != 8) { return false; } break; default: return false; } if (rwx[i] != '-' && rwx[i] != 'S' && rwx[i] != 'T') { *perm |= bit; } bit >>= 1; // 400 200 100 40 20 10 4 2 1 } // printf("%o\n", *perm); return true; } struct obj_install_target * push_install_target(struct workspace *wk, obj src, obj dest, obj mode) { obj id; make_obj(wk, &id, obj_install_target); struct obj_install_target *tgt = get_obj_install_target(wk, id); tgt->src = src; tgt->has_perm = false; if (mode) { uint32_t len = get_obj_array(wk, mode)->len; if (len > 3) { LOG_E("install_mode must have 3 or less elements"); return NULL; } if (len > 1) { LOG_W("TODO: install user/group mode"); } obj perm; obj_array_index(wk, mode, 0, &perm); switch (get_obj_type(wk, perm)) { case obj_bool: tgt->has_perm = false; break; case obj_string: if (!rwx_to_perm(get_cstr(wk, perm), &tgt->perm)) { LOG_E("install_mode has malformed permission string: %s", get_cstr(wk, perm)); return NULL; } tgt->has_perm = true; break; default: return NULL; } } obj sdest; if (path_is_absolute(get_cstr(wk, dest))) { sdest = dest; } else { obj prefix; get_option_value(wk, current_project(wk), "prefix", &prefix); SBUF(buf); path_join(wk, &buf, get_cstr(wk, prefix), get_cstr(wk, dest)); sdest = sbuf_into_str(wk, &buf); } tgt->dest = sdest; tgt->type = install_target_default; obj_array_push(wk, wk->install, id); return tgt; } bool push_install_target_install_dir(struct workspace *wk, obj src, obj install_dir, obj mode) { SBUF(basename); path_basename(wk, &basename, get_cstr(wk, src)); SBUF(dest); path_join(wk, &dest, get_cstr(wk, install_dir), basename.buf); obj sdest = sbuf_into_str(wk, &dest); return !!push_install_target(wk, src, sdest, mode); } struct push_install_targets_ctx { obj install_dirs; obj install_mode; bool install_dirs_is_arr, preserve_path; uint32_t i, err_node; }; static enum iteration_result push_install_targets_iter(struct workspace *wk, void *_ctx, obj val_id) { struct push_install_targets_ctx *ctx = _ctx; obj install_dir; if (ctx->install_dirs_is_arr) { obj_array_index(wk, ctx->install_dirs, ctx->i, &install_dir); assert(install_dir); } else { install_dir = ctx->install_dirs; } ++ctx->i; enum obj_type dt = get_obj_type(wk, install_dir); if (dt == obj_bool && !get_obj_bool(wk, install_dir)) { // skip if we get passed `false` for an install dir return ir_cont; } else if (dt != obj_string) { vm_error_at(wk, ctx->err_node, "install_dir values must be strings, got %s", obj_type_to_s(dt)); return ir_err; } obj src, dest, f; switch (get_obj_type(wk, val_id)) { case obj_string: { if (!coerce_file(wk, ctx->err_node, val_id, &f)) { return ir_err; } if (!ctx->preserve_path) { goto handle_file; } SBUF(dest_path); path_join(wk, &dest_path, get_cstr(wk, install_dir), get_cstr(wk, val_id)); src = *get_obj_file(wk, f); dest = sbuf_into_str(wk, &dest_path); break; } case obj_file: if (ctx->preserve_path) { vm_error_at(wk, ctx->err_node, "file arguments are ambiguous with preserve_path: true"); return ir_err; } f = val_id; handle_file: { SBUF(basename); path_basename(wk, &basename, get_file_path(wk, f)); SBUF(dest_path); path_join(wk, &dest_path, get_cstr(wk, install_dir), basename.buf); src = *get_obj_file(wk, f); dest = sbuf_into_str(wk, &dest_path); } break; default: UNREACHABLE; } if (!push_install_target(wk, src, dest, ctx->install_mode)) { return ir_err; } return ir_cont; } bool push_install_targets(struct workspace *wk, uint32_t err_node, obj filenames, obj install_dirs, obj install_mode, bool preserve_path) { struct push_install_targets_ctx ctx = { .err_node = err_node, .preserve_path = preserve_path, .install_dirs = install_dirs, .install_mode = install_mode, .install_dirs_is_arr = get_obj_type(wk, install_dirs) == obj_array, }; assert(ctx.install_dirs_is_arr || get_obj_type(wk, install_dirs) == obj_string); if (ctx.install_dirs_is_arr) { struct obj_array *a1 = get_obj_array(wk, filenames); struct obj_array *a2 = get_obj_array(wk, install_dirs); if (a1->len != a2->len) { vm_error_at(wk, err_node, "number of install_dirs does not match number of sources"); return false; } } return obj_array_foreach(wk, filenames, &ctx, push_install_targets_iter); } muon-v0.3.0/src/opts.c0000644000175000017500000000330214674562002013562 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "lang/workspace.h" #include "log.h" #include "opts.h" bool check_operands(uint32_t argc, uint32_t argi, int32_t expected) { assert(argc >= argi); uint32_t rem = argc - argi; if (expected < 0) { return true; } if (rem < (uint32_t)expected) { LOG_E("missing operand"); return false; } else if (rem > (uint32_t)expected) { LOG_E("too many operands (did you try passing options after operands?)"); return false; } return true; } void print_usage(FILE *f, const struct command *commands, const char *pre, const char *opts, const char *post) { uint32_t i; fprintf(f, "usage: %s%s%s%s\n", pre, opts ? " [opts]" : "", commands ? " [command]" : "", post ? post : ""); if (opts) { fprintf(f, "opts:\n" "%s" " -h - show this message\n", opts); } if (commands) { fprintf(f, "commands:\n"); for (i = 0; commands[i].name; ++i) { if (commands[i].desc) { fprintf(f, " %-12s", commands[i].name); fprintf(f, "- %s", commands[i].desc); fputc('\n', f); } } } } bool find_cmd(const struct command *commands, cmd_func *ret, uint32_t argc, uint32_t argi, char *const argv[], bool optional) { uint32_t i; const char *cmd; if (argi >= argc) { if (optional) { *ret = NULL; return true; } else { LOG_E("missing command"); return false; } } else { cmd = argv[argi]; } for (i = 0; commands[i].name; ++i) { if (strcmp(commands[i].name, cmd) == 0) { *ret = commands[i].cmd; return true; } } LOG_E("invalid command '%s'", cmd); return false; } muon-v0.3.0/src/version.c.in0000644000175000017500000000043714674562002014675 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "version.h" const struct muon_version muon_version = { .version = "@version@", .vcs_tag = "@vcs_tag@", .meson_compat = "@meson_compat@.99", }; muon-v0.3.0/src/error.c0000644000175000017500000002541114674562002013733 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include // exit #include #include "buf_size.h" #include "datastructures/arr.h" #include "embedded.h" #include "error.h" #include "lang/workspace.h" #include "log.h" #include "platform/mem.h" struct error_diagnostic_message { struct source_location location; enum log_level lvl; const char *msg; uint32_t src_idx; }; static struct { struct arr messages; bool init; struct { struct source *src; struct source_location location; bool redirect; } redirect; struct workspace *wk; enum error_diagnostic_store_replay_opts opts; } error_diagnostic_store; void error_diagnostic_store_redirect(struct source *src, struct source_location location) { if (error_diagnostic_store.redirect.redirect) { return; } error_diagnostic_store.redirect.redirect = true; error_diagnostic_store.redirect.src = src; error_diagnostic_store.redirect.location = location; } void error_diagnostic_store_redirect_reset(void) { error_diagnostic_store.redirect.redirect = false; } void error_diagnostic_store_init(struct workspace *wk) { arr_init(&error_diagnostic_store.messages, 32, sizeof(struct error_diagnostic_message)); error_diagnostic_store.init = true; error_diagnostic_store.wk = wk; } void error_diagnostic_store_push(uint32_t src_idx, struct source_location location, enum log_level lvl, const char *msg) { uint32_t mlen = strlen(msg); char *m = z_calloc(mlen + 1, 1); memcpy(m, msg, mlen); arr_push(&error_diagnostic_store.messages, &(struct error_diagnostic_message){ .location = location, .lvl = lvl, .msg = m, .src_idx = src_idx, }); } static int32_t error_diagnostic_store_compare_except_lvl(const void *_a, const void *_b, void *ctx) { const struct error_diagnostic_message *a = _a, *b = _b; int32_t v; if (a->src_idx != b->src_idx) { return (int32_t)a->src_idx - (int32_t)b->src_idx; } else if (a->location.off != b->location.off) { return (int32_t)a->location.off - (int32_t)b->location.off; } else if (a->location.len != b->location.len) { return (int32_t)a->location.len - (int32_t)b->location.len; } else if ((v = strcmp(a->msg, b->msg)) != 0) { return v; } else { return 0; } } static int32_t error_diagnostic_store_compare(const void *_a, const void *_b, void *ctx) { const struct error_diagnostic_message *a = _a, *b = _b; int32_t v; if ((v = error_diagnostic_store_compare_except_lvl(a, b, ctx)) != 0) { return v; } else if (a->lvl != b->lvl) { return a->lvl > b->lvl ? 1 : -1; } else { return 0; } } void error_diagnostic_store_replay(enum error_diagnostic_store_replay_opts opts, bool *saw_error) { error_diagnostic_store.init = false; error_diagnostic_store.opts = opts; uint32_t i; struct error_diagnostic_message *msg; struct source *last_src = 0, *cur_src; arr_sort(&error_diagnostic_store.messages, NULL, error_diagnostic_store_compare); size_t tail, initial_len = error_diagnostic_store.messages.len; if (error_diagnostic_store.messages.len > 1) { struct error_diagnostic_message *prev_msg, tmp; tail = error_diagnostic_store.messages.len; uint32_t initial_len = error_diagnostic_store.messages.len; msg = arr_get(&error_diagnostic_store.messages, 0); arr_push(&error_diagnostic_store.messages, msg); for (i = 1; i < initial_len; ++i) { prev_msg = arr_get(&error_diagnostic_store.messages, i - 1); msg = arr_get(&error_diagnostic_store.messages, i); if (error_diagnostic_store_compare_except_lvl(prev_msg, msg, NULL) == 0) { continue; } tmp = *msg; arr_push(&error_diagnostic_store.messages, &tmp); } } else { tail = 0; } *saw_error = false; struct source src = { 0 }, null_src = { .label = "", }; for (i = tail; i < error_diagnostic_store.messages.len; ++i) { msg = arr_get(&error_diagnostic_store.messages, i); if (opts & error_diagnostic_store_replay_werror) { msg->lvl = log_error; } if ((opts & error_diagnostic_store_replay_errors_only) && msg->lvl != log_error) { continue; } if (msg->lvl == log_error) { *saw_error = true; } cur_src = msg->src_idx == UINT32_MAX ? &null_src : arr_get(&error_diagnostic_store.wk->vm.src, msg->src_idx); if (cur_src != last_src) { if (opts & error_diagnostic_store_replay_include_sources) { if (last_src) { log_plain("\n"); } log_plain("%s%s%s\n", log_clr() ? "\033[31;1m" : "", cur_src->label, log_clr() ? "\033[0m" : ""); } last_src = cur_src; src = *cur_src; } error_message(&src, msg->location, msg->lvl, msg->msg); } for (i = 0; i < initial_len; ++i) { msg = arr_get(&error_diagnostic_store.messages, i); z_free((char *)msg->msg); } arr_destroy(&error_diagnostic_store.messages); memset(&error_diagnostic_store, 0, sizeof(error_diagnostic_store)); } void error_unrecoverable(const char *fmt, ...) { va_list ap; if (log_clr()) { log_plain("\033[31m"); } log_plain("fatal error"); if (log_clr()) { log_plain("\033[0m"); } log_plain(": "); va_start(ap, fmt); log_plainv(fmt, ap); log_plain("\n"); va_end(ap); exit(1); } MUON_ATTR_FORMAT(printf, 3, 4) static uint32_t print_source_line(struct source *src, uint32_t tgt_line, const char *prefix_fmt, ...) { uint64_t i, line = 1, start_of_line = 0; for (i = 0; i < src->len; ++i) { if (src->src[i] == '\n') { ++line; start_of_line = i + 1; } if (line == tgt_line) { break; } } if (i >= src->len) { return 0; } char prefix_buf[32] = { 0 }; va_list ap; va_start(ap, prefix_fmt); uint32_t ret = vsnprintf(prefix_buf, sizeof(prefix_buf), prefix_fmt, ap); va_end(ap); log_plain("%s", prefix_buf); for (i = start_of_line; src->src[i] && src->src[i] != '\n'; ++i) { if (src->src[i] == '\t') { log_plain(" "); } else { putc(src->src[i], stderr); } } log_plain("\n"); return ret; } void get_detailed_source_location(struct source *src, struct source_location loc, struct detailed_source_location *dloc, enum get_detailed_source_location_flag flags) { *dloc = (struct detailed_source_location){ .loc = loc, .line = 1, .col = 1, }; if (!src || loc.off > src->len) { return; } uint32_t i, line = 1, end = loc.off + loc.len; for (i = 0; i < src->len; ++i) { if (i == loc.off) { dloc->col = (i - dloc->start_of_line) + 1; } else if (i == end) { dloc->end_col = i - dloc->start_of_line; return; } if (src->src[i] == '\n') { if (i > loc.off && !(flags & get_detailed_source_location_flag_multiline)) { dloc->loc.len = ((i - dloc->start_of_line) - dloc->col); return; } ++line; if (i <= loc.off) { dloc->line = line; } else { dloc->end_line = line; } dloc->start_of_line = i + 1; } } } static void list_line_underline(const struct source *src, struct detailed_source_location *dloc, uint32_t line_pre_len, bool end) { uint32_t i; if (end) { line_pre_len -= 2; } for (i = 0; i < line_pre_len; ++i) { log_plain(" "); } if (end) { log_plain("|_"); } uint32_t col; const char *tab, *space; if (end) { col = dloc->end_col; tab = "________"; space = "_"; } else { col = dloc->col; tab = " "; space = " "; } for (i = 0; i < col; ++i) { if (dloc->start_of_line + i < src->len && src->src[dloc->start_of_line + i] == '\t') { log_plain("%s", tab); } else { log_plain("%s", i == col - 1 ? "^" : space); } } if (!end) { for (i = 1; i < dloc->loc.len; ++i) { log_plain("_"); } } log_plain("\n"); } static void reopen_source(struct source *src, bool *destroy_source) { if (!src->len) { switch (src->reopen_type) { case source_reopen_type_none: return; case source_reopen_type_embedded: src->src = embedded_get(src->label); src->len = strlen(src->src); break; case source_reopen_type_file: if (!fs_read_entire_file(src->label, src)) { return; } *destroy_source = true; break; } } } void list_line_range(struct source *src, struct source_location location, uint32_t context) { log_plain("-> %s%s%s\n", log_clr() ? "\033[32m" : "", src->label, log_clr() ? "\033[0m" : ""); bool destroy_source = false; reopen_source(src, &destroy_source); struct detailed_source_location dloc; get_detailed_source_location(src, location, &dloc, 0); int32_t i; for (i = -(int32_t)context; i <= (int32_t)context; ++i) { uint32_t line_pre_len; line_pre_len = print_source_line(src, dloc.line + i, "%s%3d | ", i == 0 ? ">" : " ", dloc.line + i); if (i == 0) { list_line_underline(src, &dloc, line_pre_len, false); } } if (destroy_source) { fs_source_destroy(src); } } void error_message(struct source *src, struct source_location location, enum log_level lvl, const char *msg) { if (error_diagnostic_store.init) { if (error_diagnostic_store.redirect.redirect) { src = error_diagnostic_store.redirect.src; location = error_diagnostic_store.redirect.location; } else if (src->len == 0 && src->src == 0) { // Skip messages generated for code regions with no // sources return; } uint32_t i; for (i = 0; i < error_diagnostic_store.wk->vm.src.len; ++i) { if (src == (struct source *)(arr_get(&error_diagnostic_store.wk->vm.src, i))) { break; } } assert(i < error_diagnostic_store.wk->vm.src.len); error_diagnostic_store_push(i, location, lvl, msg); return; } bool destroy_source = false; reopen_source(src, &destroy_source); struct detailed_source_location dloc; get_detailed_source_location(src, location, &dloc, get_detailed_source_location_flag_multiline); log_plain("%s:%d:%d: ", src->label, dloc.line, dloc.col); if (lvl != log_info) { if (log_clr()) { log_plain("\033[%sm%s\033[0m ", log_level_clr[lvl], log_level_name[lvl]); } else { log_plain("%s ", log_level_name[lvl]); } } log_plain("%s\n", msg); if (error_diagnostic_store.init && !(error_diagnostic_store.opts & error_diagnostic_store_replay_include_sources)) { goto ret; } uint32_t line_pre_len = 0; if (dloc.end_line) { for (uint32_t i = dloc.line; i <= dloc.end_line; ++i) { line_pre_len = print_source_line(src, i, "%3d | %s ", i, i == dloc.line ? "/" : "|"); } list_line_underline(src, &dloc, line_pre_len, true); } else { if (!(line_pre_len = print_source_line(src, dloc.line, "%3d | ", dloc.line))) { goto ret; } list_line_underline(src, &dloc, line_pre_len, false); } ret: if (destroy_source) { fs_source_destroy(src); } } void error_messagev(struct source *src, struct source_location location, enum log_level lvl, const char *fmt, va_list args) { static char buf[BUF_SIZE_4k]; vsnprintf(buf, BUF_SIZE_4k, fmt, args); error_message(src, location, lvl, buf); } void error_messagef(struct source *src, struct source_location location, enum log_level lvl, const char *fmt, ...) { va_list ap; va_start(ap, fmt); error_messagev(src, location, lvl, fmt, ap); va_end(ap); } muon-v0.3.0/src/functions/0002755000175000017500000000000014674562002014445 5ustar buildbuildmuon-v0.3.0/src/functions/source_set.c0000644000175000017500000002057314674562002016771 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "error.h" #include "lang/func_lookup.h" #include "functions/source_set.h" #include "lang/typecheck.h" #include "log.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; make_obj(wk, &rule, 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; obj_array_index(wk, v, 0, &when); obj_array_index(wk, v, 1, &if_true); obj_array_index(wk, v, 2, &if_false); 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; make_obj(wk, &arr, 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; make_obj(wk, res, 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.3.0/src/functions/template.c0000644000175000017500000000053514674562002016425 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" #include "log.h" static bool func_(struct workspace *wk, obj self, obj *res) { } const struct func_impl impl_tbl_xxx[] = { { "", func_ }, { NULL, NULL }, }; muon-v0.3.0/src/functions/dict.c0000644000175000017500000000376414674562002015544 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" #include "log.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; } make_obj(wk, res, 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } 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; } 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 }, }; 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 }, { NULL, NULL }, }; muon-v0.3.0/src/functions/subproject.c0000644000175000017500000000327414674562002016775 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "lang/func_lookup.h" #include "functions/subproject.h" #include "lang/typecheck.h" #include "log.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) { 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 }, { NULL, NULL }, }; muon-v0.3.0/src/functions/run_result.c0000644000175000017500000000414114674562002017011 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" #include "log.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; } make_obj(wk, res, 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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.3.0/src/functions/array.c0000644000175000017500000000512114674562002015724 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 "log.h" static bool func_array_length(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } make_obj(wk, res, 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 { obj_array_index(wk, self, i, res); } 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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } 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, }, { NULL, NULL }, }; muon-v0.3.0/src/functions/feature_opt.c0000644000175000017500000001254114674562002017127 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" #include "log.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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 { make_obj(wk, res, 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 { make_obj(wk, res, 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 { make_obj(wk, res, 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 { make_obj(wk, res, 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 { make_obj(wk, res, 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.3.0/src/functions/kernel/0002755000175000017500000000000014674562002015725 5ustar buildbuildmuon-v0.3.0/src/functions/kernel/subproject.c0000644000175000017500000001133214674562002020247 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 "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 sbuf *cwd_buf, const char **cwd, struct sbuf *build_dir_buf, const char **build_dir, bool required, bool *found) { if (!fs_dir_exists(*cwd)) { bool wrap_ok = false; SBUF(wrap_path); SBUF(base_path); sbuf_pushf(wk, &wrap_path, "%s.wrap", *cwd); if (!fs_file_exists(wrap_path.buf)) { goto wrap_done; } path_dirname(wk, &base_path, *cwd); struct wrap wrap = { 0 }; enum wrap_mode wrap_mode = get_option_wrap_mode(wk); if (!wrap_handle(wrap_path.buf, base_path.buf, &wrap, wrap_mode != wrap_mode_nodownload)) { goto wrap_cleanup; } if (wrap.fields[wf_directory]) { path_join(wk, cwd_buf, base_path.buf, wrap.fields[wf_directory]); path_dirname(wk, &base_path, *build_dir); path_join(wk, build_dir_buf, base_path.buf, wrap.fields[wf_directory]); *cwd = cwd_buf->buf; *build_dir = build_dir_buf->buf; } wrap_ok = true; wrap_cleanup: wrap_destroy(&wrap); wrap_done: if (!wrap_ok) { if (required) { LOG_E("project %s not found", *cwd); return false; } else { *found = false; return true; } } } SBUF(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; } make_obj(wk, res, obj_subproject); if (req == requirement_skip) { return true; } const char *subproj_name = get_cstr(wk, name); SBUF(cwd); SBUF(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; SBUF(sp_cwd_buf); SBUF(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); bool compare_result; if (!version_compare( wk, versions->node, get_str(wk, subp->cfg.version), versions->val, &compare_result)) { goto not_found; } if (!compare_result) { 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; } } } make_obj(wk, res, 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); 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", wk->complex_types.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; } 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.3.0/src/functions/kernel/install.c0000644000175000017500000002530314674562002017540 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "coerce.h" #include "error.h" #include "functions/kernel/install.h" #include "install.h" #include "lang/typecheck.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) { SBUF(path); SBUF(name); char *sep; const char *name_tail; sbuf_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 = sbuf_into_str(wk, &path); } SBUF(path); path_join(wk, &path, workspace_cwd(wk), get_cstr(wk, an[0].val)); obj src = sbuf_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); SBUF(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(&WKSTR(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); } } SBUF(path); path_join(wk, &path, get_cstr(wk, install_dir), basename); obj dest = sbuf_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) { SBUF(path); path_join(wk, &path, get_cstr(wk, mandir), get_cstr(wk, akw[kw_locale].val)); ctx.install_dir = sbuf_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; } SBUF(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, sbuf_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; obj_array_index(wk, ctx->rename, ctx->i, &rename); SBUF(d); path_join(wk, &d, get_cstr(wk, ctx->dest), get_cstr(wk, rename)); dest = sbuf_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); SBUF(buf); path_join(wk, &buf, get_cstr(wk, install_dir_base), get_cstr(wk, current_project(wk)->cfg.name)); install_dir = sbuf_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) { SBUF(buf); path_join(wk, &buf, get_cstr(wk, install_dir_base), get_cstr(wk, akw[kw_subdir].val)); install_dir = sbuf_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.3.0/src/functions/kernel/options.c0000644000175000017500000001437014674562002017567 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "error.h" #include "lang/func_lookup.h" #include "functions/kernel/options.h" #include "lang/typecheck.h" #include "log.h" #include "options.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", }; enum build_option_type type; for (type = 0; type < build_option_type_count; ++type) { 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, }; // TODO: this winds up creating 4 typeinfo objects every time you call // option, it'd be nice to have some caching type_tag deprecated_type = make_complex_type(wk, complex_type_or, tc_string | tc_bool, make_complex_type(wk, complex_type_or, make_complex_type(wk, complex_type_nested, tc_dict, tc_string), make_complex_type(wk, complex_type_nested, tc_array, tc_string))); 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", deprecated_type }, [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: make_obj(wk, &val, obj_bool); set_obj_bool(wk, val, 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; } obj_array_index(wk, akw[kw_choices].val, 0, &val); break; case op_array: if (akw[kw_choices].set) { val = akw[kw_choices].val; } else { make_obj(wk, &val, obj_array); } break; case op_feature: make_obj(wk, &val, obj_feature_opt); set_obj_feature_opt(wk, val, feature_opt_auto); break; default: UNREACHABLE_RETURN; } } obj opt; make_obj(wk, &opt, 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; if (akw[kw_kind].set) { if (str_eql(&WKSTR("default"), get_str(wk, akw[kw_kind].val))) { o->kind = build_option_kind_default; } else if (str_eql(&WKSTR("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); *res = o->val; return true; } muon-v0.3.0/src/functions/kernel/configure_file.c0000644000175000017500000005067014674562002021057 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/func_lookup.h" #include "lang/object_iterators.h" #include "lang/typecheck.h" #include "log.h" #include "platform/filesystem.h" #include "platform/mem.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 void configure_file_skip_whitespace(const struct source *src, uint32_t *i) { while (src->src[*i] && strchr(" \t", src->src[*i])) { ++(*i); } } 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 = 0 << 1, configure_file_syntax_cmakevar = 1 << 1, }; static bool substitute_config(struct workspace *wk, uint32_t dict, uint32_t in_node, const char *in, obj out, enum configure_file_syntax syntax) { const char *define; if (syntax & configure_file_syntax_cmakedefine) { define = "#cmakedefine "; } else { define = "#mesondefine "; } const char *varstart; char varend; if (syntax & configure_file_syntax_cmakevar) { varstart = "${"; varend = '}'; } else { varstart = "@"; varend = '@'; } const uint32_t define_len = strlen(define), varstart_len = strlen(varstart); bool ret = true; struct source src; if (!fs_read_entire_file(in, &src)) { ret = false; goto cleanup; } SBUF_manual(out_buf); struct source_location location = { 0, 1 }, id_location; uint32_t i, id_start, id_len, col = 0, orig_i; obj elem; char tmp_buf[BUF_SIZE_1k] = { 0 }; for (i = 0; i < src.len; ++i) { if (src.src[i] == '\n') { col = i + 1; } if (i == col && strncmp(&src.src[i], define, define_len) == 0) { i += define_len; configure_file_skip_whitespace(&src, &i); id_start = i; id_location = location; ++id_location.off; id_len = configure_var_len(&src.src[id_start]); i += id_len; const char *sub = NULL, *deftype = "#define"; configure_file_skip_whitespace(&src, &i); if (!(src.src[i] == '\n' || src.src[i] == 0)) { if (syntax & configure_file_syntax_cmakedefine) { if (src.src[i] == '@' && strncmp(&src.src[i + 1], &src.src[id_start], id_len) == 0 && src.src[i + 1 + id_len] == '@') { i += 2 + id_len; configure_file_skip_whitespace(&src, &i); if (!(src.src[i] == '\n' || src.src[i] == 0)) { goto extraneous_cmake_chars; } } else { extraneous_cmake_chars: orig_i = i; while (src.src[i] && src.src[i] != '\n') { ++i; } /* id_location.col = orig_i - location.col + 1; */ error_messagef(&src, id_location, log_warn, "ignoring trailing characters (%.*s) in cmakedefine", i - orig_i, &src.src[orig_i]); } } else { /* id_location.col = i - location.col + 1; */ error_messagef(&src, id_location, log_error, "expected exactly one token on mesondefine line"); return false; } } if (i == id_start) { error_messagef(&src, id_location, log_error, "key of zero length not supported"); return false; } else if (!obj_dict_index_strn(wk, dict, &src.src[id_start], id_len, &elem)) { deftype = "/* undef"; sub = "*/"; goto write_mesondefine; } switch (get_obj_type(wk, elem)) { case obj_bool: { if (!get_obj_bool(wk, elem)) { deftype = "#undef"; } break; } case obj_string: { sub = get_cstr(wk, elem); break; } case obj_number: snprintf(tmp_buf, BUF_SIZE_1k, "%" PRId64, get_obj_number(wk, elem)); sub = tmp_buf; break; default: error_messagef(&src, id_location, log_error, "invalid type for %s: '%s'", define, obj_type_to_s(get_obj_type(wk, elem))); return false; } write_mesondefine: sbuf_pushn(wk, &out_buf, deftype, strlen(deftype)); sbuf_pushn(wk, &out_buf, " ", 1); sbuf_pushn(wk, &out_buf, &src.src[id_start], id_len); if (sub) { sbuf_pushn(wk, &out_buf, " ", 1); sbuf_pushn(wk, &out_buf, sub, strlen(sub)); } i -= 1; // so we catch the newline } else if (src.src[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; src.src[i + j] && src.src[i + j] == '\\'; ++j) { } if (strncmp(&src.src[i + j], varstart, varstart_len) == 0) { output_backslashes = j / 2; if (*varstart == '@') { 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) { sbuf_pushn(wk, &out_buf, "\\", 1); } if (output_format_char) { sbuf_pushn(wk, &out_buf, varstart, varstart_len); i += varstart_len - 1; } } else if (strncmp(&src.src[i], varstart, varstart_len) == 0) { i += varstart_len; id_start = i; id_location = location; /* id_location.col = id_start - location.col + 1; */ i += configure_var_len(&src.src[id_start]); if (src.src[i] != varend) { i = id_start - 1; sbuf_pushn(wk, &out_buf, varstart, varstart_len); continue; } if (i <= id_start) { // This means we got a key of length zero sbuf_pushs(wk, &out_buf, "@@"); continue; } else if (!obj_dict_index_strn(wk, dict, &src.src[id_start], i - id_start, &elem)) { error_messagef(&src, id_location, log_error, "key not found in configuration data"); return false; } obj sub; if (!coerce_string(wk, in_node, elem, &sub)) { error_messagef(&src, id_location, log_error, "unable to substitute value"); return false; } const struct str *ss = get_str(wk, sub); sbuf_pushn(wk, &out_buf, ss->s, ss->len); } else { sbuf_pushn(wk, &out_buf, &src.src[i], 1); } } if (file_exists_with_content(wk, get_cstr(wk, out), out_buf.buf, out_buf.len)) { goto cleanup; } if (!fs_write(get_cstr(wk, out), (uint8_t *)out_buf.buf, out_buf.len)) { ret = false; goto cleanup; } if (!fs_copy_metadata(in, get_cstr(wk, out))) { ret = false; goto cleanup; } cleanup: fs_source_destroy(&src); sbuf_destroy(&out_buf); 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) { SBUF_manual(out_buf); if (macro_name) { sbuf_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) { sbuf_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) { sbuf_push(wk, &out_buf, ','); } first = false; sbuf_push_json_escaped_quoted(wk, &out_buf, get_str(wk, key)); sbuf_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: sbuf_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: { sbuf_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)) { sbuf_pushf(wk, &out_buf, "%cdefine %s\n", define_prefix, get_cstr(wk, key)); } else { sbuf_pushf(wk, &out_buf, "%cundef %s\n", define_prefix, get_cstr(wk, key)); } break; case configure_file_output_format_json: sbuf_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: sbuf_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)); sbuf_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) { sbuf_pushf(wk, &out_buf, "#endif\n"); } else if (format == configure_file_output_format_json) { sbuf_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: sbuf_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; make_obj(wk, &f, obj_file); *get_obj_file(wk, f) = out_path; make_obj(wk, &output_arr, 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; make_obj(wk, &f, 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, }; make_obj(wk, &opts.depends, 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(get_cstr(wk, current_project(wk)->build_dir))) { return false; } obj env; make_obj(wk, &env, 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; } obj_array_index(wk, arr, 0, res); 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); SBUF(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; } SBUF(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, &WKSTR("c"))) { output_format = configure_file_output_format_c; } else if (str_eql(output_format_str, &WKSTR("nasm"))) { output_format = configure_file_output_format_nasm; } else if (str_eql(output_format_str, &WKSTR("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); SBUF(out_path); if (!path_is_basename(out)) { vm_error_at(wk, akw[kw_output].node, "config file output '%s' contains path separator", out); return false; } if (!fs_mkdir_p(get_cstr(wk, current_project(wk)->build_dir))) { return false; } path_join(wk, &out_path, get_cstr(wk, current_project(wk)->build_dir), out); LOG_I("configuring '%s'", out_path.buf); output_str = sbuf_into_str(wk, &out_path); make_obj(wk, res, 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, &WKSTR("meson"))) { syntax = configure_file_syntax_mesondefine | configure_file_syntax_mesonvar; } else if (str_eql(fmt, &WKSTR("cmake"))) { syntax = configure_file_syntax_cmakedefine | configure_file_syntax_cmakevar; } else if (str_eql(fmt, &WKSTR("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, akw[kw_input].node, path, 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.3.0/src/functions/kernel/dependency.c0000644000175000017500000007732114674562002020217 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "coerce.h" #include "error.h" #include "external/libpkgconf.h" #include "functions/kernel/dependency.h" #include "functions/kernel/subproject.h" #include "functions/string.h" #include "functions/subproject.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "log.h" #include "machines.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/run_cmd.h" 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, dependency_lookup_method_cmake, // 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, }; 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; 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; bool disabler; bool fallback_allowed; bool fallback_only; bool from_cache; bool found; }; static enum iteration_result check_dependency_override_iter(struct workspace *wk, void *_ctx, obj n) { struct dep_lookup_ctx *ctx = _ctx; if (ctx->lib_mode != dep_lib_mode_shared) { if (obj_dict_index(wk, wk->dep_overrides_static, n, ctx->res)) { ctx->lib_mode = dep_lib_mode_static; ctx->found = true; return ir_done; } } if (ctx->lib_mode != dep_lib_mode_static) { if (obj_dict_index(wk, wk->dep_overrides_dynamic, n, ctx->res)) { ctx->lib_mode = dep_lib_mode_shared; ctx->found = true; return ir_done; } } return ir_cont; } static bool check_dependency_override(struct workspace *wk, struct dep_lookup_ctx *ctx) { obj_array_foreach(wk, ctx->names, ctx, check_dependency_override_iter); 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->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->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, uint32_t err_node, obj ver, bool *res) { if (!ver) { *res = true; return true; } if (!version_compare(wk, err_node, get_str(wk, dep_ver_str), ver, res)) { return false; } return true; } 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: obj_array_index(wk, ctx->fallback, 1, &subproj_dep); /* FALLTHROUGH */ case 1: obj_array_index(wk, ctx->fallback, 0, &subproj_name); 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 { make_obj(wk, &ctx->default_options->val, 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 (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 { if (!check_dependency_override(wk, ctx)) { vm_warning_at(wk, ctx->fallback_node, "subproject does not override dependency %o", ctx->name); 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; } *found = true; return true; not_found: obj_fprintf(wk, log_file(), "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 pkgconf_info info = { 0 }; *found = false; if (!muon_pkgconf_lookup(wk, ctx->name, ctx->lib_mode == dep_lib_mode_static, &info)) { return true; } obj ver_str = make_str(wk, info.version); bool ver_match; if (!check_dependency_version(wk, ver_str, ctx->err_node, ctx->versions->val, &ver_match)) { return false; } else if (!ver_match) { obj_fprintf(wk, log_file(), "pkgconf found dependency %o, but the version %o does not match the requested version %o\n", ctx->name, ver_str, ctx->versions->val); return true; } make_obj(wk, ctx->res, 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; dep->dep.link_with = info.libs; dep->dep.link_with_not_found = info.not_found_libs; dep->dep.include_directories = info.includes; dep->dep.compile_args = info.compile_args; dep->dep.link_args = info.link_args; *found = true; return true; } static bool get_dependency(struct workspace *wk, struct dep_lookup_ctx *ctx) { { obj cached_dep; if (check_dependency_cache(wk, ctx, &cached_dep)) { bool ver_match; struct obj_dependency *dep = get_obj_dependency(wk, cached_dep); if (!check_dependency_version( wk, dep->version, ctx->versions->node, ctx->versions->val, &ver_match)) { return false; } if (!ver_match) { return true; } *ctx->res = cached_dep; ctx->found = true; ctx->from_cache = true; return true; } } if (check_dependency_override(wk, ctx)) { return true; } bool force_fallback = false; enum wrap_mode wrap_mode = get_option_wrap_mode(wk); if (!ctx->fallback) { obj provided_fallback; if (obj_dict_index(wk, current_project(wk)->wrap_provides_deps, ctx->name, &provided_fallback)) { ctx->fallback = provided_fallback; } } // implicitly fallback on a subproject named the same as this dependency if (!ctx->fallback && ctx->fallback_allowed) { make_obj(wk, &ctx->fallback, obj_array); obj_array_push(wk, ctx->fallback, ctx->name); } if (ctx->fallback) { obj force_fallback_for, subproj_name; get_option_value(wk, current_project(wk), "force_fallback_for", &force_fallback_for); obj_array_index(wk, ctx->fallback, 0, &subproj_name); force_fallback = wrap_mode == wrap_mode_forcefallback || obj_array_in(wk, force_fallback_for, ctx->name) || obj_dict_in(wk, wk->subprojects, subproj_name); } if (!ctx->found) { if (ctx->fallback && (force_fallback || ctx->fallback_only)) { if (!handle_dependency_fallback(wk, ctx, &ctx->found)) { return false; } } else { if (!get_dependency_pkgconfig(wk, ctx, &ctx->found)) { return false; } if (!ctx->found && ctx->fallback) { if (!handle_dependency_fallback(wk, ctx, &ctx->found)) { return false; } } } } return true; } static enum iteration_result handle_appleframeworks_modules_iter(struct workspace *wk, void *_ctx, obj val) { struct dep_lookup_ctx *ctx = _ctx; struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); obj_array_push(wk, dep->dep.link_args, make_str(wk, "-framework")); obj_array_push(wk, dep->dep.link_args, val); return ir_cont; } static bool handle_special_dependency(struct workspace *wk, struct dep_lookup_ctx *ctx, bool *handled) { obj handler; if (obj_dict_index(wk, wk->dependency_handlers, ctx->name, &handler)) { *handled = true; if (!vm_eval_capture(wk, handler, 0, ctx->handler_kwargs, ctx->res)) { return false; } } else if (strcmp(get_cstr(wk, ctx->name), "threads") == 0) { LOG_I("dependency threads found"); *handled = true; make_obj(wk, ctx->res, 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; make_obj(wk, &dep->dep.compile_args, obj_array); obj_array_push(wk, dep->dep.compile_args, make_str(wk, "-pthread")); make_obj(wk, &dep->dep.link_args, obj_array); obj_array_push(wk, dep->dep.link_args, make_str(wk, "-pthread")); } else if (strcmp(get_cstr(wk, ctx->name), "curses") == 0) { *handled = true; ctx->name = make_str(wk, "ncurses"); if (!get_dependency(wk, ctx)) { return false; } if (!ctx->found) { *handled = false; } } else if (strcmp(get_cstr(wk, ctx->name), "appleframeworks") == 0) { *handled = true; if (!ctx->modules) { vm_error_at(wk, ctx->err_node, "'appleframeworks' dependency requires the modules keyword"); return false; } make_obj(wk, ctx->res, obj_dependency); if (host_machine.sys == machine_system_darwin) { struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); dep->name = make_str(wk, "appleframeworks"); dep->flags |= dep_flag_found; dep->type = dependency_type_appleframeworks; make_obj(wk, &dep->dep.link_args, obj_array); obj_array_foreach(wk, ctx->modules, ctx, handle_appleframeworks_modules_iter); } } else if (strcmp(get_cstr(wk, ctx->name), "") == 0) { *handled = true; if (ctx->requirement == requirement_required) { vm_error_at(wk, ctx->err_node, "dependency '' cannot be required"); return false; } make_obj(wk, ctx->res, obj_dependency); } else { *handled = false; } return true; } static enum iteration_result dependency_iter(struct workspace *wk, void *_ctx, obj name) { bool handled; struct dep_lookup_ctx *parent_ctx = _ctx; struct dep_lookup_ctx ctx = *parent_ctx; parent_ctx->name = ctx.name = name; if (!handle_special_dependency(wk, &ctx, &handled)) { return ir_err; } else if (handled) { ctx.found = true; } else { if (!get_dependency(wk, &ctx)) { return ir_err; } } if (ctx.found) { parent_ctx->lib_mode = ctx.lib_mode; parent_ctx->from_cache = ctx.from_cache; parent_ctx->found = true; return ir_done; } else { return ir_cont; } } static enum iteration_result set_dependency_cache_iter(struct workspace *wk, void *_ctx, obj name) { struct dep_lookup_ctx *ctx = _ctx; if (ctx->lib_mode != dep_lib_mode_shared) { obj_dict_set(wk, current_project(wk)->dep_cache.static_deps, name, *ctx->res); } if (ctx->lib_mode != dep_lib_mode_static) { obj_dict_set(wk, current_project(wk)->dep_cache.shared_deps, name, *ctx->res); } return ir_cont; } 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, // ignored kw_version, kw_static, kw_modules, // ignored kw_optional_modules, // ignored kw_components, // 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 }, [kw_optional_modules] = { "optional_modules", TYPE_TAG_LISTIFY | obj_string }, [kw_components] = { "components", TYPE_TAG_LISTIFY | obj_string }, [kw_fallback] = { "fallback", TYPE_TAG_LISTIFY | obj_string }, [kw_allow_fallback] = { "allow_fallback", obj_bool }, [kw_default_options] = { "default_options", wk->complex_types.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; } 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; } } // TODO: lookup_method is unused. This is partially because cmake and // extraframework aren't supported so the default (auto) is the same as // just saying pkgconfig. It also seems that rarely is the lookup // method specified to be builtin or system in practice. enum dependency_lookup_method lookup_method = dependency_lookup_method_auto; if (akw[kw_method].set) { struct { const char *name; enum dependency_lookup_method method; } 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 }, }; uint32_t i; for (i = 0; i < ARRAY_LEN(lookup_method_names); ++i) { if (str_eql(get_str(wk, akw[kw_method].val), &WKSTR(lookup_method_names[i].name))) { lookup_method = lookup_method_names[i].method; break; } } if (i == ARRAY_LEN(lookup_method_names)) { 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_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 requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } if (requirement == requirement_skip) { make_obj(wk, res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *res); obj_array_index(wk, an[0].val, 0, &dep->name); return true; } 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 = /* - allow_fallback: true */ (akw[kw_allow_fallback].set && get_obj_bool(wk, akw[kw_allow_fallback].val)) /* - allow_fallback is not specified and the requirement is required */ || (!akw[kw_allow_fallback].set && 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_allow_fallback].set && 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; } } struct args_kw handler_kwargs[] = { { "required", .val = requirement == requirement_required ? obj_bool_true : obj_bool_false }, { "static", .val = lib_mode == dep_lib_mode_static ? obj_bool_true : obj_bool_false }, 0, }; struct dep_lookup_ctx ctx = { .res = res, .handler_kwargs = handler_kwargs, .names = an[0].val, .requirement = requirement, .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, }; if (!obj_array_foreach(wk, an[0].val, &ctx, dependency_iter)) { return false; } if (!ctx.found && fallback_allowed) { ctx.fallback_allowed = fallback_allowed; ctx.fallback_only = true; if (!obj_array_foreach(wk, an[0].val, &ctx, dependency_iter)) { return false; } } if (!ctx.found) { if (ctx.requirement == requirement_required) { LLOG_E("required "); } else { LLOG_W("%s", ""); } obj_fprintf(wk, log_file(), "dependency %o not found", an[0].val); if (ctx.not_found_message) { obj_fprintf(wk, log_file(), ", %#o", ctx.not_found_message); } log_plain("\n"); if (ctx.requirement == requirement_required) { vm_error_at(wk, ctx.err_node, "required dependency not found"); return false; } else { if (ctx.disabler) { *ctx.res = disabler_id; } else { make_obj(wk, ctx.res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *ctx.res); dep->name = ctx.name; dep->type = dependency_type_not_found; } } } else if (!str_eql(get_str(wk, ctx.name), &WKSTR(""))) { struct obj_dependency *dep = get_obj_dependency(wk, *ctx.res); LLOG_I("found dependency "); if (dep->type == dependency_type_declared) { obj_fprintf(wk, log_file(), "%o (declared dependency)", ctx.name); } else { log_plain("%s", get_cstr(wk, dep->name)); } if (dep->version) { log_plain(" version %s", get_cstr(wk, dep->version)); } if (ctx.lib_mode == dep_lib_mode_static) { log_plain(" static"); } log_plain("\n"); if (dep->type == dependency_type_declared) { L("(%s)", get_cstr(wk, dep->name)); } } if (get_obj_type(wk, *res) == obj_dependency) { struct obj_dependency *dep = get_obj_dependency(wk, *res); if (ctx.from_cache) { obj dup; make_obj(wk, &dup, obj_dependency); struct obj_dependency *newdep = get_obj_dependency(wk, dup); *newdep = *dep; dep = newdep; *res = dup; } // set the include type if the return value is not a disabler dep->include_type = inc_type; if (dep->flags & dep_flag_found && !ctx.from_cache) { obj_array_foreach(wk, ctx.names, &ctx, set_dependency_cache_iter); } } return true; } struct process_dependency_sources_ctx { uint32_t err_node; 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, ctx->err_node, val, &res)) { return ir_err; } obj_array_extend_nodup(wk, ctx->res, res); } } return ir_cont; } 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_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; make_obj(wk, res, obj_dependency); dep = get_obj_dependency(wk, *res); if (akw[kw_objects].set) { dep->dep.objects = akw[kw_objects].val; } build_dep_init(wk, &dep->dep); dep->name = make_strf(wk, "%s:declared_dep", get_cstr(wk, current_project(wk)->cfg.name)), dep->flags |= dep_flag_found; dep->type = dependency_type_declared; 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_link_args].set) { obj_array_extend_nodup(wk, dep->dep.link_args, akw[kw_link_args].val); } if (akw[kw_compile_args].set) { obj_array_extend_nodup(wk, dep->dep.compile_args, akw[kw_compile_args].val); } if (akw[kw_version].set) { dep->version = akw[kw_version].val; } else { dep->version = current_project(wk)->cfg.version; } if (akw[kw_sources].set) { struct process_dependency_sources_ctx ctx = { .err_node = akw[kw_sources].node, .res = dep->dep.sources, }; if (!obj_array_foreach_flat(wk, akw[kw_sources].val, &ctx, coerce_dependency_sources_iter)) { return false; } } if (akw[kw_link_with].set) { if (!dep_process_link_with(wk, akw[kw_link_with].node, akw[kw_link_with].val, &dep->dep)) { return false; } } if (akw[kw_link_whole].set) { if (!dep_process_link_whole(wk, akw[kw_link_whole].node, akw[kw_link_whole].val, &dep->dep)) { return false; } } if (akw[kw_include_directories].set) { dep_process_includes( wk, akw[kw_include_directories].val, include_type_preserve, dep->dep.include_directories); } if (akw[kw_dependencies].set) { dep_process_deps(wk, akw[kw_dependencies].val, &dep->dep); } 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 { obj dest; 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) { make_obj(wk, &inc_id, 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->dest, inc_id); return ir_cont; } void dep_process_includes(struct workspace *wk, obj arr, enum include_type include_type, obj dest) { obj_array_foreach_flat(wk, arr, &(struct dep_process_includes_ctx){ .include_type = include_type, .dest = dest, }, dep_process_includes_iter); } void build_dep_init(struct workspace *wk, struct build_dep *dep) { if (!dep->include_directories) { make_obj(wk, &dep->include_directories, obj_array); } if (!dep->link_with) { make_obj(wk, &dep->link_with, obj_array); } if (!dep->link_whole) { make_obj(wk, &dep->link_whole, obj_array); } if (!dep->link_with_not_found) { make_obj(wk, &dep->link_with_not_found, obj_array); } if (!dep->link_args) { make_obj(wk, &dep->link_args, obj_array); } if (!dep->compile_args) { make_obj(wk, &dep->compile_args, obj_array); } if (!dep->order_deps) { make_obj(wk, &dep->order_deps, obj_array); } if (!dep->rpath) { make_obj(wk, &dep->rpath, obj_array); } if (!dep->sources) { make_obj(wk, &dep->sources, obj_array); } if (!dep->objects) { make_obj(wk, &dep->objects, obj_array); } } static void merge_build_deps(struct workspace *wk, struct build_dep *src, struct build_dep *dest, bool dep) { 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 (dep && 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 (dep && 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 (dep && src->sources) { obj_array_extend(wk, dest->sources, src->sources); } if (dep && src->objects) { obj_array_extend(wk, dest->objects, src->objects); } } static enum iteration_result dedup_link_args_iter(struct workspace *wk, void *_ctx, obj val) { obj new_args = *(obj *)_ctx; static const char *known[] = { "-pthread", }; const char *s = get_cstr(wk, val); uint32_t i; for (i = 0; i < ARRAY_LEN(known); ++i) { if (strcmp(known[i], s) == 0) { if (obj_array_in(wk, new_args, val)) { return ir_cont; } else { break; } } } obj_array_push(wk, new_args, val); return ir_cont; } static void dedup_build_dep(struct workspace *wk, struct build_dep *dep) { 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->raw.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 new_link_args; make_obj(wk, &new_link_args, obj_array); obj_array_foreach(wk, dep->link_args, &new_link_args, dedup_link_args_iter); dep->link_args = new_link_args; obj new_compile_args; make_obj(wk, &new_compile_args, obj_array); obj_array_foreach(wk, dep->compile_args, &new_compile_args, dedup_link_args_iter); dep->compile_args = new_compile_args; } struct dep_process_link_with_ctx { struct build_dep *dest; bool link_whole; uint32_t err_node; }; static enum iteration_result dep_process_link_with_iter(struct workspace *wk, void *_ctx, obj val) { struct dep_process_link_with_ctx *ctx = _ctx; if (skip_if_present(wk, ctx->dest->raw.link_with, val)) { return ir_cont; } enum obj_type t = get_obj_type(wk, val); /* obj_fprintf(wk, log_file(), "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: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, val); const char *path = get_cstr(wk, tgt->build_path); if (ctx->link_whole && tgt->type != tgt_static_library) { vm_error_at(wk, ctx->err_node, "link whole only accepts static libraries"); return ir_err; } 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) { SBUF(abs); SBUF(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); } } merge_build_deps(wk, &tgt->dep, ctx->dest, false); break; } case obj_custom_target: { obj_array_foreach(wk, get_obj_custom_target(wk, val)->output, ctx, dep_process_link_with_iter); break; } case obj_file: { obj_array_push(wk, dest_link_with, *get_obj_file(wk, val)); break; } case obj_string: obj_array_push(wk, dest_link_with, val); break; default: vm_error_at(wk, ctx->err_node, "invalid type for link_with: '%s'", obj_type_to_s(t)); return ir_err; } return ir_cont; } bool dep_process_link_with(struct workspace *wk, uint32_t err_node, obj arr, struct build_dep *dest) { build_dep_init(wk, dest); dest->raw.link_with = arr; hash_clear(&wk->vm.objects.obj_hash); if (!obj_array_foreach_flat(wk, arr, &(struct dep_process_link_with_ctx){ .dest = dest, .err_node = err_node, }, dep_process_link_with_iter)) { return false; } dedup_build_dep(wk, dest); return true; } bool dep_process_link_whole(struct workspace *wk, uint32_t err_node, obj arr, struct build_dep *dest) { build_dep_init(wk, dest); dest->raw.link_whole = arr; hash_clear(&wk->vm.objects.obj_hash); if (!obj_array_foreach_flat(wk, arr, &(struct dep_process_link_with_ctx){ .dest = dest, .link_whole = true, .err_node = err_node, }, dep_process_link_with_iter)) { return false; } dedup_build_dep(wk, dest); return true; } static enum iteration_result dep_process_deps_iter(struct workspace *wk, void *_ctx, obj val) { struct build_dep *dest = _ctx; /* obj_fprintf(wk, log_file(), "dep: %o\n", val); */ if (skip_if_present(wk, dest->raw.deps, val)) { return ir_cont; } struct obj_dependency *dep = get_obj_dependency(wk, val); if (!(dep->flags & dep_flag_found)) { return ir_cont; } merge_build_deps(wk, &dep->dep, dest, true); return ir_cont; } void dep_process_deps(struct workspace *wk, obj deps, struct build_dep *dest) { build_dep_init(wk, dest); dest->raw.deps = deps; hash_clear(&wk->vm.objects.obj_hash); obj_array_foreach(wk, deps, dest, dep_process_deps_iter); dedup_build_dep(wk, dest); } muon-v0.3.0/src/functions/kernel/build_target.c0000644000175000017500000007204514674562002020544 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 "error.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, // TODO 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, // TODO 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, }; #define bt_kwarg_lang_args(cl) ; #define bt_kwarg_lang_static_args(cl) ; #define bt_kwarg_lang_shared_args(cl) ; #define bt_kwarg_lang_pch(cl) ; 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)); SBUF(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)->compilers, 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; 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; } SBUF(dir); SBUF(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, sbuf_into_str(wk, &path)); if (!ctx->implicit_include_directories) { return true; } path_dirname(wk, &dir, src); obj inc; make_obj(wk, &inc, obj_include_directory); struct obj_include_directory *d = get_obj_include_directory(wk, inc); d->path = sbuf_into_str(wk, &dir); obj_array_push(wk, tgt->dep_internal.include_directories, inc); return true; } 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) { // 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; } 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); } 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)); return; } 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); return; } } tgt->soname = make_strf(wk, "%s.dll", plain_name); } 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) { SBUF(soname_symlink); SBUF(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 = sbuf_into_str(wk, &soname_symlink); } if (!str_eql(&WKSTR(plain_name), get_str(wk, tgt->soname)) && !str_eql(&WKSTR(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 = sbuf_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 }; make_obj(wk, res, 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; make_obj(wk, &tgt->args, obj_dict); make_obj(wk, &tgt->src, obj_array); make_obj(wk, &tgt->required_compilers, obj_dict); build_dep_init(wk, &tgt->dep_internal); { // linker args (process before dependencies so link_with libs come first on link line if (akw[bt_kw_link_with].set) { if (!dep_process_link_with( wk, akw[bt_kw_link_with].node, akw[bt_kw_link_with].val, &tgt->dep_internal)) { return false; } } if (akw[bt_kw_link_whole].set) { if (!dep_process_link_whole( wk, akw[bt_kw_link_whole].node, akw[bt_kw_link_whole].val, &tgt->dep_internal)) { return false; } } } if (akw[bt_kw_dependencies].set) { dep_process_deps(wk, akw[bt_kw_dependencies].val, &tgt->dep_internal); } if (akw[bt_kw_override_options].set) { // override options 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].set); 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, &WKSTR("default"))) { tgt->visibility = compiler_visibility_default; } else if (str_eql(str, &WKSTR("hidden"))) { tgt->visibility = compiler_visibility_hidden; } else if (str_eql(str, &WKSTR("internal"))) { tgt->visibility = compiler_visibility_internal; } else if (str_eql(str, &WKSTR("protected"))) { tgt->visibility = compiler_visibility_protected; } else if (str_eql(str, &WKSTR("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 */ SBUF(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); sbuf_pushs(wk, &path, ".p"); tgt->private_path = sbuf_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; { // 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 { make_obj(wk, &tgt->objects, 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, .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; } } { // include directories obj inc_dirs; make_obj(wk, &inc_dirs, obj_array); uint32_t node = an[0].node; // TODO: not a very informative error node if (implicit_include_directories) { obj_array_push(wk, inc_dirs, current_project(wk)->cwd); } if (akw[bt_kw_include_directories].set) { node = akw[bt_kw_include_directories].node; obj_array_extend(wk, inc_dirs, akw[bt_kw_include_directories].val); } obj coerced; if (!coerce_include_dirs(wk, node, inc_dirs, 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); } } 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; } } SBUF(install_src); path_join(wk, &install_src, get_cstr(wk, tgt->build_dir), get_cstr(wk, tgt->build_name)); SBUF(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, sbuf_into_str(wk, &install_src), sbuf_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; } { // rpaths if (akw[bt_kw_build_rpath].set) { obj_array_push(wk, tgt->dep_internal.rpath, akw[bt_kw_build_rpath].val); } if (akw[bt_kw_install_rpath].set) { obj_array_push(wk, tgt->dep_internal.rpath, akw[bt_kw_install_rpath].val); } } 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; 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); } 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 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 }, // ignored [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 }, [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", wk->complex_types.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), 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) { make_obj(wk, &akw[bt_kw_pic].val, obj_bool); set_obj_bool(wk, akw[bt_kw_pic].val, 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; } if (tgt && !multi_target) { multi_target = true; make_obj(wk, res, obj_array); obj_array_push(wk, *res, tgt); // 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; } } if (!create_target(wk, an, akw, t, multi_target, &tgt)) { return false; } if (multi_target) { obj_array_push(wk, *res, tgt); } else { *res = tgt; } } if (multi_target) { obj val; make_obj(wk, &val, obj_both_libs); struct obj_both_libs *both = get_obj_both_libs(wk, val); obj_array_index(wk, *res, 0, &both->static_lib); obj_array_index(wk, *res, 1, &both->dynamic_lib); *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.3.0/src/functions/kernel/custom_target.c0000644000175000017500000005411014674562002020750 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/custom_target.h" #include "functions/string.h" #include "install.h" #include "lang/typecheck.h" #include "log.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, &WKSTR(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) { SBUF(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. SBUF(exe); path_executable(wk, &exe, rel.buf); *res = sbuf_into_str(wk, &exe); } else { *res = sbuf_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, &WKSTR(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; } obj_array_index(wk, arr, 0, &e); 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. */ SBUF(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->build_dir), get_cstr(wk, ctx->opts->name)); sbuf_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, &in0); const struct str *orig_str = get_str(wk, *get_obj_file(wk, in0)); SBUF(plainname); path_basename(wk, &plainname, orig_str->s); if (key == key_basename) { SBUF(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; } obj_array_index(wk, arr, index, &e); 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); switch (t) { case obj_both_libs: case obj_build_target: case obj_external_program: case obj_python_installation: case obj_file: { obj str, args; if (!coerce_executable(wk, ctx->opts->err_node, val, &str, &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); } if (args) { obj_array_push(wk, *ctx->res, ss); obj_array_extend_nodup(wk, *ctx->res, args); return ir_cont; } break; } case obj_string: { if (ctx->opts->input && str_eql(get_str(wk, val), &WKSTR("@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), &WKSTR("@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), &WKSTR("@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; 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; 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); 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) { make_obj(wk, res, 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, &WKSTR(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; obj_array_index(wk, ctx->opts->input, 0, &in0); const struct str *ss = get_str(wk, *get_obj_file(wk, in0)); SBUF(buf); switch (key) { case key_plainname: path_basename(wk, &buf, ss->s); break; case key_basename: { SBUF(basename); path_basename(wk, &basename, ss->s); path_without_ext(wk, &buf, basename.buf); break; } default: assert(false && "unreachable"); return format_cb_error; } *elem = sbuf_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; make_obj(wk, &f, 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; make_obj(wk, res, obj_custom_target); struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); tgt->name = opts->name; // A custom_target won't have a name if it is from a generator if (opts->name) { /* private path */ SBUF(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->build_dir), get_cstr(wk, opts->name)); sbuf_pushs(wk, &path, ".p"); tgt->private_path = sbuf_into_str(wk, &path); } if (opts->input_orig) { make_obj(wk, &input, 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; make_obj(wk, &arr_input, 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; } make_obj(wk, &output, 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, }; make_obj(wk, &cmdline_opts.depends, 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 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; obj_array_index(wk, akw[kw_output].val, 0, &v); 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 ((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 || !get_obj_array(wk, akw[kw_install_dir].val)->len) { vm_error_at(wk, akw[kw_install].node, "custom target installation requires install_dir"); return false; } if (!akw[kw_build_by_default].set) { tgt->flags |= custom_target_build_by_default; } obj install_mode_id = 0; if (akw[kw_install_mode].set) { install_mode_id = akw[kw_install_mode].val; } obj install_dir = akw[kw_install_dir].val; if (get_obj_array(wk, akw[kw_install_dir].val)->len == 1) { obj i0; obj_array_index(wk, akw[kw_install_dir].val, 0, &i0); install_dir = i0; } if (!push_install_targets( wk, akw[kw_install_dir].node, tgt->output, install_dir, install_mode_id, false)) { 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, }; 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 }, 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; make_obj(wk, &command, obj_array); push_args_null_terminated(wk, command, (char *const[]){ (char *)wk->argv0, "internal", "eval", "-e", "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, input); obj_array_push(wk, command, make_str(wk, "@OUTPUT@")); obj_array_push(wk, command, replace_string); obj_array_push(wk, command, fallback); obj_array_push(wk, command, make_str(wk, wk->source_root)); if (akw[kw_command].set) { 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; obj_array_push(wk, current_project(wk)->targets, *res); return true; } muon-v0.3.0/src/functions/disabler.c0000644000175000017500000000105714674562002016377 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" #include "log.h" static bool func_disabler_found(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, false); return true; } const struct func_impl impl_tbl_disabler[] = { { "found", func_disabler_found, tc_bool }, { NULL, NULL }, }; muon-v0.3.0/src/functions/kernel.c0000644000175000017500000015264614674562002016105 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/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "wrap.h" static bool project_add_language(struct workspace *wk, uint32_t err_node, obj str, enum requirement_type req, bool *found) { if (req == requirement_skip) { return true; } obj comp_id; 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; } } obj res; if (obj_dict_geti(wk, current_project(wk)->compilers, l, &res)) { *found = true; return true; } if (!toolchain_detect(wk, &comp_id, 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; } } obj_dict_seti(wk, current_project(wk)->compilers, 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)->compilers, 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; make_obj(wk, &llvm_ir_compiler, 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)->compilers, 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)->compilers, compiler_language_c, &c_compiler) && !obj_dict_geti(wk, current_project(wk)->compilers, compiler_language_cpp, &c_compiler)) { bool c_found; if (!project_add_language(wk, err_node, make_str(wk, "c"), req, &c_found)) { return false; } } } break; case compiler_language_objcpp: { obj cpp_compiler; if (!obj_dict_geti(wk, current_project(wk)->compilers, compiler_language_cpp, &cpp_compiler)) { bool cpp_found; if (!project_add_language(wk, err_node, make_str(wk, "cpp"), req, &cpp_found)) { return false; } } } break; default: break; } *found = true; return true; } struct project_add_language_iter_ctx { uint32_t err_node; enum requirement_type req; bool missing; }; static enum iteration_result project_add_language_iter(struct workspace *wk, void *_ctx, obj val) { struct project_add_language_iter_ctx *ctx = _ctx; bool found = false; if (!project_add_language(wk, ctx->err_node, val, ctx->req, &found)) { return ir_err; } if (!found) { ctx->missing = true; } return ir_cont; } 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_version, }; struct args_kw akw[] = { [kw_default_options] = { "default_options", wk->complex_types.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_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; } current_project(wk)->cfg.name = an[0].val; #ifndef MUON_BOOTSTRAPPED if (wk->cur_project == 0 && !str_eql(get_str(wk, an[0].val), &WKSTR("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 if (!obj_array_foreach_flat(wk, an[1].val, &(struct project_add_language_iter_ctx){ .err_node = an[1].node, .req = requirement_required, }, project_add_language_iter)) { 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; } } if (akw[kw_subproject_dir].set) { current_project(wk)->subprojects_dir = akw[kw_subproject_dir].val; } { // subprojects SBUF(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; } } LOG_I("configuring '%s', version: %s", get_cstr(wk, current_project(wk)->cfg.name), get_cstr(wk, current_project(wk)->cfg.version)); 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)) { make_obj(wk, &arg_arr, 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, obj *res) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_language, kw_native, // ignored }; 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, .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); } struct add_project_dependencies_ctx { uint32_t lang_node; struct build_dep *d; }; static enum iteration_result add_project_dependencies_iter(struct workspace *wk, void *_ctx, obj lang) { struct add_project_dependencies_ctx *ctx = _ctx; enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, lang), &l)) { vm_error_at(wk, ctx->lang_node, "unknown language '%s'", get_cstr(wk, lang)); return ir_err; } obj res; if (!obj_dict_geti(wk, current_project(wk)->compilers, 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, ctx->lang_node, "undeclared language '%s'", get_cstr(wk, lang)); return ir_err; } obj_array_extend(wk, get_project_argument_array(wk, current_project(wk)->args, l), ctx->d->compile_args); obj_array_extend(wk, get_project_argument_array(wk, current_project(wk)->link_args, l), ctx->d->link_args); obj_array_extend( wk, get_project_argument_array(wk, current_project(wk)->include_dirs, l), ctx->d->include_directories); return ir_cont; } 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, // ignored }; 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 build_dep d = { 0 }; dep_process_deps(wk, an[0].val, &d); struct add_project_dependencies_ctx ctx = { .lang_node = akw[kw_language].node, .d = &d, }; obj_array_foreach(wk, akw[kw_language].val, &ctx, add_project_dependencies_iter); 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, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_native] = { "native", obj_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } struct project_add_language_iter_ctx ctx = { .err_node = an[0].node, }; if (!coerce_requirement(wk, &akw[kw_required], &ctx.req)) { return false; } if (!obj_array_foreach(wk, an[0].val, &ctx, project_add_language_iter)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, !ctx.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, an[0].node, an[0].val, res); } struct find_program_iter_ctx { bool found; uint32_t node, version_node; obj version, version_argument; obj dirs; obj *res; enum requirement_type requirement; struct args_kw *default_options; }; struct find_program_custom_dir_ctx { const char *prog; struct sbuf *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; } static bool find_program_check_override(struct workspace *wk, struct find_program_iter_ctx *ctx, obj prog) { obj override; if (!obj_dict_index(wk, wk->find_program_overrides, prog, &override)) { return true; } obj over = 0, op; switch (get_obj_type(wk, override)) { case obj_array: obj_array_index(wk, override, 0, &op); obj_array_index(wk, override, 1, &over); 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, &over); } break; default: UNREACHABLE; } if (ctx->version && over) { bool comparison_result; if (!version_compare(wk, ctx->version_node, get_str(wk, over), ctx->version, &comparison_result)) { return false; } else if (!comparison_result) { return true; } } if (get_obj_type(wk, op) == obj_file) { obj newres; make_obj(wk, &newres, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, newres); ep->found = true; make_obj(wk, &ep->cmd_array, 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_iter_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, prog, &_)) { vm_warning_at(wk, 0, "subproject %o claims to provide %o, but did not override it", subproj_name, prog); } } } return true; } static bool find_program(struct workspace *wk, struct find_program_iter_ctx *ctx, obj prog) { const char *str; obj ver = 0; bool guessed_ver = false; 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; SBUF(buf); struct find_program_custom_dir_ctx dir_ctx = { .buf = &buf, .prog = str, }; enum wrap_mode wrap_mode = 0; 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 */ 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 */ if (t == obj_string) { if (have_samurai && (strcmp(str, "ninja") == 0 || strcmp(str, "samu") == 0)) { make_obj(wk, ctx->res, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *ctx->res); ep->found = true; make_obj(wk, &ep->cmd_array, 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")); ctx->found = true; return true; } else if (strcmp(str, "meson") == 0) { make_obj(wk, ctx->res, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *ctx->res); ep->found = true; make_obj(wk, &ep->cmd_array, obj_array); obj_array_push(wk, ep->cmd_array, make_str(wk, wk->argv0)); obj_array_push(wk, ep->cmd_array, make_str(wk, "meson")); ctx->found = true; return true; } } return true; found: { obj cmd_array; make_obj(wk, &cmd_array, 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 (!ver) { return true; // no version to check against } bool comparison_result; if (!version_compare(wk, ctx->version_node, get_str(wk, ver), ctx->version, &comparison_result)) { return false; } else if (!comparison_result) { return true; } } make_obj(wk, ctx->res, 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", wk->complex_types.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 = disabler_id; } else { make_obj(wk, res, obj_external_program); get_obj_external_program(wk, *res)->found = false; } return true; } struct find_program_iter_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], }; { 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 = disabler_id; } else { make_obj(wk, res, 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; make_obj(wk, &command, obj_array); obj_array_push(wk, command, an[0].val); obj_array_extend(wk, command, akw[kw_arguments].val); make_obj(wk, res, 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)); } return false; } return true; } static bool func_debug(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } log_plain(log_clr() ? "\033[35mdebug\033[0m: " : "debug: "); obj val; obj_array_for(wk, an[0].val, val) { obj_fprintf(wk, log_file(), "%#o ", val); } log_plain("\n"); *res = 0; return true; } static bool func_message(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } log_plain(log_clr() ? "\033[34mmessage\033[0m " : "message: "); obj val; obj_array_for(wk, an[0].val, val) { obj_fprintf(wk, log_file(), "%#o ", val); } log_plain("\n"); *res = 0; return true; } static bool func_error(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } log_plain(log_clr() ? "\033[31merror\033[0m " : "error: "); obj val; obj_array_for(wk, an[0].val, val) { obj_fprintf(wk, log_file(), "%#o ", val); } log_plain("\n"); *res = 0; return false; } 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; } fprintf(log_file(), "%s", get_cstr(wk, an[0].val)); *res = 0; return true; } static bool func_warning(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } log_plain(log_clr() ? "\033[33mwarn\033[0m " : "warn: "); obj val; obj_array_for(wk, an[0].val, val) { obj_fprintf(wk, log_file(), "%#o ", val); } log_plain("\n"); *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_iter_ctx find_program_ctx = { .node = an[0].node, .res = &cmd_file, .requirement = requirement_auto, }; if (!get_obj_array(wk, an[0].val)->len) { vm_error(wk, "missing command"); return false; } obj_array_index(wk, an[0].val, 0, &arg0); if (get_obj_type(wk, arg0) == obj_compiler) { obj cmd_arr = get_obj_compiler(wk, arg0)->cmd_arr; 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 = { 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("stdout:\n%s", cmd_ctx.out.buf); } if (cmd_ctx.err.len) { log_plain("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("stdout:\n%s", cmd_ctx.out.buf); } if (cmd_ctx.err.len) { log_plain("stderr:\n%s", cmd_ctx.err.buf); } goto ret; } make_obj(wk, res, 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 = sbuf_into_str(wk, &cmd_ctx.out); run_result->err = sbuf_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, }; type_tag if_found_type = wk->vm.in_analyzer ? tc_any : TYPE_TAG_LISTIFY | tc_dependency; struct args_kw akw[] = { [kw_if_found] = { "if_found", if_found_type }, 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; } } SBUF(build_dir); obj old_cwd = current_project(wk)->cwd; obj old_build_dir = current_project(wk)->build_dir; SBUF(new_cwd); path_join(wk, &new_cwd, get_cstr(wk, old_cwd), get_cstr(wk, an[0].val)); current_project(wk)->cwd = make_str(wk, new_cwd.buf); path_join(wk, &build_dir, get_cstr(wk, old_build_dir), get_cstr(wk, an[0].val)); current_project(wk)->build_dir = sbuf_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; path_push(wk, &new_cwd, "meson.build"); ret = wk->vm.behavior.eval_project_file(wk, new_cwd.buf, false); 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; } make_obj(wk, res, obj_configuration_data); if (an[0].set) { get_obj_configuration_data(wk, *res)->dict = an[0].val; } else { obj dict; make_obj(wk, &dict, 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; make_obj(wk, &test_setup, 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) { make_obj(wk, ¤t_project(wk)->test_setups, 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) { SBUF(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, sbuf_into_str(wk, &rel)); break; case obj_both_libs: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ 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, sbuf_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), &WKSTR(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, akw[kw_args].val, &args)) { return false; } if (exe_args) { obj_array_extend_nodup(wk, exe_args, args); args = exe_args; } } obj test; make_obj(wk, &test, 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 }; make_obj(wk, &t->depends, 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 sbuf *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; } SBUF(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 = sbuf_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] = WKSTR("set"), [environment_set_mode_append] = WKSTR("append"), [environment_set_mode_prepend] = WKSTR("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; } make_obj(wk, res, obj_environment); struct obj_environment *d = get_obj_environment(wk, *res); make_obj(wk, &d->actions, 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) { make_obj(wk, res, 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 = disabler_id; 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; make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 bool func_summary(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { tc_any }, { tc_any, .optional = true }, ARG_TYPE_NULL }; enum kwargs { kw_section, kw_bool_yn, // ignored kw_list_sep, // ignored }; 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 sec = akw[kw_section].set ? akw[kw_section].val : make_str(wk, ""); obj dict; if (an[1].set) { if (!typecheck(wk, an[0].node, an[0].val, obj_string)) { return false; } make_obj(wk, &dict, obj_dict); obj_dict_set(wk, dict, an[0].val, an[1].val); } else { if (!typecheck(wk, an[0].node, an[0].val, obj_dict)) { return false; } dict = an[0].val; } obj prev; if (obj_dict_index(wk, current_project(wk)->summary, sec, &prev)) { obj ndict; obj_dict_merge(wk, prev, dict, &ndict); dict = ndict; } obj_dict_set(wk, current_project(wk)->summary, sec, dict); 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; make_obj(wk, &id, 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: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ 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; make_obj(wk, &deps_id, 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; } make_obj(wk, res, 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_VOID }, 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, log_file(), an[0].val); } else { const char *fmt = akw[kw_pretty].set && get_obj_bool(wk, akw[kw_pretty].val) ? "%#o\n" : "%o\n"; obj_fprintf(wk, log_file(), 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), &WKSTR("-"))) { 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_void(struct workspace *wk, obj _, obj *res) { struct args_norm an[] = { { TYPE_TAG_ALLOW_VOID | 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_VOID | 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; } // 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 }, { "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 }, { "disabler", func_disabler, tc_disabler }, { "environment", func_environment, tc_environment }, { "error", func_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, }, { "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 }, { "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 }, { "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, .extension = true }, { NULL, NULL }, }; const struct func_impl impl_tbl_kernel_internal[] = { { "assert", func_assert }, { "configuration_data", func_configuration_data, tc_configuration_data }, { "disabler", func_disabler, tc_disabler }, { "environment", func_environment, tc_environment }, { "error", func_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, .fuzz_unsafe = true }, { "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, .fuzz_unsafe = true }, { "is_void", func_is_void, tc_bool, true }, { "typeof", func_typeof, tc_string, true }, { "exit", func_exit }, { 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.3.0/src/functions/boolean.c0000644000175000017500000000211314674562002016223 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" #include "log.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; make_obj(wk, res, 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.3.0/src/functions/source_configuration.c0000644000175000017500000000163014674562002021036 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" #include "log.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.3.0/src/functions/external_program.c0000644000175000017500000000441014674562002020157 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } obj_array_index(wk, get_obj_external_program(wk, self)->cmd_array, 0, res); 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.3.0/src/functions/string.c0000644000175000017500000002607014674562002016122 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 "buf_size.h" #include "coerce.h" #include "functions/string.h" #include "guess.h" #include "lang/func_lookup.h" #include "lang/lexer.h" #include "lang/typecheck.h" #include "log.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; } obj_array_index(wk, ctx->arr, i, elem); 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); } struct version_compare_ctx { bool res; uint32_t err_node; const struct str *ver1; }; static enum iteration_result version_compare_iter(struct workspace *wk, void *_ctx, obj s2) { struct version_compare_ctx *ctx = _ctx; struct str ver2 = *get_str(wk, s2); 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[] = { { WKSTR(">="), op_ge, }, { WKSTR(">"), op_gt, }, { WKSTR("=="), op_eq, }, { WKSTR("!="), op_ne, }, { WKSTR("<="), op_le, }, { WKSTR("<"), op_lt, }, { WKSTR("="), 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(ctx->ver1, &ver2); switch (op) { case op_eq: ctx->res = cmp == 0; break; case op_ne: ctx->res = cmp != 0; break; case op_gt: ctx->res = cmp == 1; break; case op_ge: ctx->res = cmp >= 0; break; case op_lt: ctx->res = cmp == -1; break; case op_le: ctx->res = cmp <= 0; break; } if (!ctx->res) { return ir_done; } return ir_cont; } bool version_compare(struct workspace *wk, uint32_t err_node, const struct str *ver, obj cmp_arr, bool *res) { struct version_compare_ctx ctx = { .err_node = err_node, .ver1 = ver, }; if (!obj_array_foreach(wk, cmp_arr, &ctx, version_compare_iter)) { return false; } *res = ctx.res; 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; } struct version_compare_ctx ctx = { .err_node = an[0].node, .ver1 = get_str(wk, self), }; if (version_compare_iter(wk, &ctx, an[0].val) == ir_err) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ctx.res); 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; } make_obj(wk, res, 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 }, }; muon-v0.3.0/src/functions/dependency.c0000644000175000017500000002125614674562002016733 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 "coerce.h" #include "external/libpkgconf.h" #include "functions/build_target.h" #include "lang/func_lookup.h" #include "functions/dependency.h" #include "functions/kernel/dependency.h" #include "lang/typecheck.h" #include "log.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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, (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 *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_pkgconf_get_variable(wk, get_cstr(wk, d->name), get_cstr(wk, var), 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, 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 dep_pkgconfig_define(struct workspace *wk, obj dep, uint32_t node, obj var) { struct obj_array *array = get_obj_array(wk, var); uint32_t arraylen = array->len; if (arraylen % 2 != 0) { vm_error_at(wk, node, "non-even number of arguments in list"); return false; } for (int64_t idx = 0; idx < arraylen; idx += 2) { obj key, val; obj_array_index(wk, var, idx, &key); obj_array_index(wk, var, idx + 1, &val); const char *ckey = get_cstr(wk, key); const char *cval = get_cstr(wk, val); if (!muon_pkgconf_define(wk, ckey, cval)) { vm_error_at(wk, node, "error setting %s=%s", ckey, cval); return false; } } return true; } 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_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_default_value] = { "default_value", obj_string }, 0, }; if (!pop_args(wk, an, akw)) { return false; } if (an[0].set) { if (!akw[kw_pkgconfig].set) { akw[kw_pkgconfig].set = true; akw[kw_pkgconfig].node = an[0].node; akw[kw_pkgconfig].val = an[0].val; } if (!akw[kw_internal].set) { akw[kw_internal].set = true; akw[kw_internal].node = an[0].node; akw[kw_internal].val = an[0].val; } } struct obj_dependency *dep = get_obj_dependency(wk, self); if (dep->type == dependency_type_pkgconf) { if (akw[kw_pkgconfig_define].set) { if (!dep_pkgconfig_define( wk, self, akw[kw_pkgconfig_define].node, akw[kw_pkgconfig_define].val)) { return false; } } if (akw[kw_pkgconfig].set) { if (dep_get_pkgconfig_variable(wk, self, akw[kw_pkgconfig].node, akw[kw_pkgconfig].val, res)) { return true; } } } else if (dep->variables) { if (akw[kw_internal].set) { if (obj_dict_index(wk, dep->variables, akw[kw_internal].val, res)) { return true; } } } if (akw[kw_default_value].set) { *res = akw[kw_default_value].val; return true; } else { vm_error(wk, "pkgconfig file 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 (dep->type) { case dependency_type_pkgconf: n = "pkgconfig"; break; case dependency_type_declared: n = "internal"; break; case dependency_type_appleframeworks: case dependency_type_threads: n = "system"; break; case dependency_type_external_library: n = "library"; break; case dependency_type_not_found: n = "not-found"; 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->type == dependency_type_declared) { *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; } make_obj(wk, res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, self), *partial = get_obj_dependency(wk, *res); *partial = *dep; partial->dep = (struct build_dep){ 0 }; if (akw[kw_compile_args].set && get_obj_bool(wk, akw[kw_compile_args].val)) { partial->dep.compile_args = dep->dep.compile_args; } if (akw[kw_includes].set && get_obj_bool(wk, akw[kw_includes].val)) { partial->dep.include_directories = dep->dep.include_directories; } if (akw[kw_link_args].set && get_obj_bool(wk, akw[kw_link_args].val)) { partial->dep.link_args = dep->dep.link_args; } if (akw[kw_links].set && get_obj_bool(wk, akw[kw_links].val)) { partial->dep.link_with = dep->dep.link_with; partial->dep.link_whole = dep->dep.link_whole; partial->dep.link_with_not_found = dep->dep.link_with_not_found; partial->dep.raw.link_with = dep->dep.raw.link_with; partial->dep.raw.link_whole = dep->dep.raw.link_whole; } if (akw[kw_sources].set && get_obj_bool(wk, akw[kw_sources].val)) { partial->dep.sources = dep->dep.sources; } build_dep_init(wk, &partial->dep); 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; } } make_obj(wk, res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *res); *dep = *get_obj_dependency(wk, self); obj old_includes = dep->dep.include_directories; make_obj(wk, &dep->dep.include_directories, obj_array); dep_process_includes(wk, old_includes, inc_type, dep->dep.include_directories); dep->include_type = inc_type; return true; } 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_system", func_dependency_as_system, tc_dependency }, { "found", func_dependency_found, tc_bool }, { "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 }, { "partial_dependency", func_dependency_partial_dependency, tc_dependency }, { "type_name", func_dependency_type_name, tc_string }, { "name", func_dependency_name, tc_string }, { "version", func_dependency_version, tc_string }, { NULL, NULL }, }; muon-v0.3.0/src/functions/build_target.c0000644000175000017500000001712214674562002017257 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/typecheck.h" #include "log.h" #include "platform/path.h" bool tgt_src_to_object_path(struct workspace *wk, const struct obj_build_target *tgt, obj src_file, bool relative, struct sbuf *res) { obj src = *get_obj_file(wk, src_file); SBUF(private_path_rel); SBUF(rel); const char *base, *private_path = get_cstr(wk, tgt->private_path); if (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 = ".o"; { enum compiler_language lang; obj comp_id; if (filename_to_compiler_language(res->buf, &lang) && obj_dict_geti(wk, current_project(wk)->compilers, lang, &comp_id)) { ext = toolchain_compiler_object_ext(wk, get_obj_compiler(wk, comp_id))->args[0]; } } sbuf_pushs(wk, res, ext); return true; } 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 { uint32_t err_node; struct obj_build_target *tgt; obj tgt_id; obj *res; }; static enum iteration_result build_target_extract_objects_iter(struct workspace *wk, void *_ctx, obj val) { struct build_target_extract_objects_ctx *ctx = _ctx; obj file; enum obj_type t = get_obj_type(wk, val); if (!typecheck(wk, ctx->err_node, 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 ir_err; } break; } case obj_file: file = val; break; case obj_custom_target: { struct obj_custom_target *tgt = get_obj_custom_target(wk, val); if (!obj_array_flatten_one(wk, tgt->output, &file)) { vm_error_at(wk, ctx->err_node, "cannot coerce custom_target with multiple outputs to file"); return ir_err; } break; } case obj_generated_list: { obj res; if (!generated_list_process_for_target(wk, ctx->err_node, val, ctx->tgt_id, false, &res)) { return ir_err; } if (!obj_array_foreach(wk, res, ctx, build_target_extract_objects_iter)) { return ir_err; } return ir_cont; } default: UNREACHABLE_RETURN; } enum compiler_language l; if (!filename_to_compiler_language(get_file_path(wk, file), &l)) { return ir_cont; } switch (l) { case compiler_language_cpp_hdr: case compiler_language_c_hdr: case compiler_language_c_obj: // skip non-compileable sources return ir_cont; 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 (!obj_array_in(wk, ctx->tgt->src, file)) { vm_error_at(wk, ctx->err_node, "%o is not in target sources (%o)", file, ctx->tgt->src); return ir_err; } SBUF(dest_path); if (!tgt_src_to_object_path(wk, ctx->tgt, file, false, &dest_path)) { return ir_err; } obj new_file; make_obj(wk, &new_file, obj_file); *get_obj_file(wk, new_file) = sbuf_into_str(wk, &dest_path); obj_array_push(wk, *ctx->res, new_file); return ir_cont; } static bool build_target_extract_objects(struct workspace *wk, obj self, uint32_t err_node, obj *res, obj arr) { make_obj(wk, res, obj_array); struct build_target_extract_objects_ctx ctx = { .err_node = err_node, .res = res, .tgt = get_obj_build_target(wk, self), .tgt_id = self, }; return obj_array_foreach_flat(wk, arr, &ctx, build_target_extract_objects_iter); } 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; } return build_target_extract_objects(wk, self, an[0].node, res, an[0].val); } static enum iteration_result build_target_extract_all_objects_iter(struct workspace *wk, void *_ctx, obj val) { struct build_target_extract_objects_ctx *ctx = _ctx; return build_target_extract_objects_iter(wk, ctx, val); } bool build_target_extract_all_objects(struct workspace *wk, uint32_t ip, obj self, obj *res, bool recursive) { make_obj(wk, res, obj_array); struct build_target_extract_objects_ctx ctx = { .res = res, .tgt = get_obj_build_target(wk, self), .tgt_id = self, }; if (!obj_array_foreach_flat(wk, ctx.tgt->src, &ctx, build_target_extract_all_objects_iter)) { return false; } if (recursive) { obj_array_extend(wk, *res, ctx.tgt->objects); } return true; } 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; } make_obj(wk, res, 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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.3.0/src/functions/both_libs.c0000644000175000017500000000254214674562002016557 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "buf_size.h" #include "functions/both_libs.h" #include "functions/build_target.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "log.h" 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 get_obj_both_libs(wk, self)->dynamic_lib; } 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.3.0/src/functions/modules.c0000644000175000017500000001457214674562002016270 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "embedded.h" #include "error.h" #include "functions/modules.h" #include "functions/modules/fs.h" #include "functions/modules/keyval.h" #include "functions/modules/pkgconfig.h" #include "functions/modules/python.h" #include "functions/modules/sourceset.h" #include "functions/modules/toolchain.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "log.h" #include "platform/filesystem.h" #define MODULE_INFO(mod, path_prefix, _implemented) \ { .name = #mod, .path = path_prefix "/" #mod, .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 sbuf *path, struct obj_module *m, const struct module_lookup_script_opts *opts) { struct source src; if (opts->embedded) { src = (struct source){ .label = path->buf }; if (!(src.src = embedded_get(src.label))) { return false; } } else { if (!fs_file_exists(path->buf)) { return false; } if (!fs_read_entire_file(path->buf, &src)) { UNREACHABLE; } } src.label = get_cstr(wk, sbuf_into_str(wk, path)); src.len = strlen(src.src); bool ret = false; enum language_mode old_language_mode = wk->vm.lang_mode; wk->vm.lang_mode = language_extended; if (opts->encapsulate) { stack_push(&wk->stack, wk->vm.scope_stack, wk->vm.behavior.scope_stack_dup(wk, wk->vm.default_scope_stack)); } obj res; if (!eval(wk, &src, eval_mode_default, &res)) { goto ret; } if (opts->encapsulate) { if (!typecheck(wk, 0, res, make_complex_type(wk, complex_type_nested, tc_dict, tc_capture))) { goto ret; } m->found = true; m->has_impl = true; m->exports = res; } ret = true; ret: if (opts->encapsulate) { stack_pop(&wk->stack, wk->vm.scope_stack); } wk->vm.lang_mode = old_language_mode; return ret; } const char *module_paths[] = { [language_external] = "embedded:modules/%.meson;builtin:public/%", [language_internal] = "embedded:lib/%.meson;modules/%.meson;builtin:private/%;builtin:public/%", [language_opts] = "", [language_extended] = "embedded:lib/%.meson;modules/%.meson;builtin:private/%;builtin:public/%", }; bool module_import(struct workspace *wk, const char *name, bool encapsulate, obj *res) { struct obj_module *m = 0; if (encapsulate) { make_obj(wk, res, obj_module); m = get_obj_module(wk, *res); } { enum { schema_type_none, schema_type_embedded, schema_type_builtin, } schema; const char *schema_type_str[] = { [schema_type_embedded] = "embedded", [schema_type_builtin] = "builtin", }; bool loop = true; struct str path; SBUF(path_interpolated); const char *p = module_paths[wk->vm.lang_mode], *sep; 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; schema < ARRAY_LEN(schema_type_str); ++schema) { if (schema_type_str[schema] && str_eql(&WKSTR(schema_type_str[schema]), &schema_str)) { break; } } if (schema == ARRAY_LEN(schema_type_str)) { vm_error(wk, "invalid schema %.*s in module path", schema_str.len, schema_str.s); return false; } path.s = sep + 1; path.len -= (schema_str.len + 1); } else { schema = schema_type_none; } } { // Interpolate path sbuf_clear(&path_interpolated); uint32_t i; for (i = 0; i < path.len; ++i) { if (path.s[i] == '%') { sbuf_pushs(wk, &path_interpolated, name); } else { sbuf_push(wk, &path_interpolated, path.s[i]); } } } switch (schema) { case schema_type_none: case schema_type_embedded: { struct module_lookup_script_opts opts = { .encapsulate = encapsulate, .embedded = schema == schema_type_embedded, }; if (module_lookup_script(wk, &path_interpolated, m, &opts)) { 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; } 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 } }, }; 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.3.0/src/functions/generator.c0000644000175000017500000001353614674562002016605 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "coerce.h" #include "error.h" #include "functions/build_target.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/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) { SBUF(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)); SBUF(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; make_obj(wk, &tmp_arr, 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; } SBUF(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_both_libs: build_target = get_obj_both_libs(wk, build_target)->dynamic_lib; /* fallthrough */ 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; } make_obj(wk, res, 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; } make_obj(wk, res, 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; } make_obj(wk, &gl->input, obj_array); { obj v, coercible_files; make_obj(wk, &coercible_files, 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.3.0/src/functions/file.c0000644000175000017500000000154314674562002015531 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "lang/func_lookup.h" #include "functions/file.h" #include "lang/typecheck.h" #include "log.h" bool file_is_linkable(struct workspace *wk, obj file) { const struct str *s = get_str(wk, *get_obj_file(wk, file)); const char *suffs[] = { ".a", ".dll", ".lib", ".so", ".dylib", NULL }; uint32_t i; for (i = 0; suffs[i]; ++i) { if (str_endswith(s, &WKSTR(suffs[i]))) { return true; } } return false; } 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.3.0/src/functions/environment.c0000644000175000017500000001236414674562002017161 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #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/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; obj_array_index(wk, action, 0, &mode_num); obj_array_index(wk, action, 1, &key); obj_array_index(wk, action, 2, &val); obj_array_index(wk, action, 3, &sep); 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 = getenv(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; } make_obj(wk, res, 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); } 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) { SBUF(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; make_obj(wk, &mode_num, obj_number); set_obj_number(wk, mode_num, mode); make_obj(wk, &elem, 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 }, ARG_TYPE_NULL }; if (!pop_args(wk, an, 0)) { return false; } obj to_delete, action, key, actions = get_obj_environment(wk, self)->actions; make_obj(wk, &to_delete, obj_array); uint32_t i = 0; obj_array_for(wk, actions, action) { /* obj_array_index(wk, action, 0, &mode_num); */ obj_array_index(wk, action, 1, &key); /* obj_array_index(wk, action, 2, &val); */ /* obj_array_index(wk, action, 3, &sep); */ 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.3.0/src/functions/meson.c0000644000175000017500000003526314674562002015741 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 "coerce.h" #include "compilers.h" #include "error.h" #include "functions/build_target.h" #include "functions/meson.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "log.h" #include "options.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)->compilers, 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) { make_obj(wk, res, 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) { make_obj(wk, res, 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 = 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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } *res = make_str(wk, "ninja"); return true; } static bool func_meson_is_cross_build(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, false); return true; } static bool func_meson_is_unity(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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, // ignored }; struct args_kw akw[] = { [kw_static] = { "static", obj_bool }, [kw_native] = { "native", obj_bool }, 0 }; if (!pop_args(wk, an, akw)) { return false; } obj override_dict; if (akw[kw_static].set) { if (get_obj_bool(wk, akw[kw_static].val)) { override_dict = wk->dep_overrides_static; } else { override_dict = wk->dep_overrides_dynamic; } } else { switch (get_option_default_library(wk)) { case tgt_static_library: override_dict = wk->dep_overrides_static; break; default: override_dict = wk->dep_overrides_dynamic; break; } } obj_dict_set(wk, override_dict, an[0].val, an[1].val); 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 }; 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: make_obj(wk, &override, 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, an[0].val, override); return true; } struct process_script_commandline_ctx { uint32_t node; obj arr; uint32_t i; bool allow_not_built; bool make_deps_default; }; static enum iteration_result process_script_commandline_iter(struct workspace *wk, void *_ctx, obj val) { struct process_script_commandline_ctx *ctx = _ctx; obj str; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_string: if (ctx->i) { str = val; } else { const char *p = get_cstr(wk, val); if (path_is_absolute(p)) { str = val; } else { SBUF(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->cwd), p); str = sbuf_into_str(wk, &path); } } 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; } if (!obj_array_foreach(wk, o->output, ctx, process_script_commandline_iter)) { return false; } goto cont; 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 args; if (!coerce_executable(wk, ctx->node, val, &str, &args)) { return ir_err; } if (args) { obj_array_push(wk, ctx->arr, str); obj_array_extend_nodup(wk, ctx->arr, args); return ir_cont; } break; } default: type_error: vm_error_at(wk, ctx->node, "invalid type for script commandline '%s'", obj_type_to_s(t)); return ir_err; } obj_array_push(wk, ctx->arr, str); cont: ++ctx->i; return ir_cont; } 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, // ignored 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, }; make_obj(wk, &ctx.arr, obj_array); if (!obj_array_foreach_flat(wk, an[0].val, &ctx, process_script_commandline_iter)) { return false; } if (!akw[kw_dry_run].set) { make_obj(wk, &akw[kw_dry_run].val, obj_bool); set_obj_bool(wk, akw[kw_dry_run].val, false); } obj install_script; make_obj(wk, &install_script, obj_array); 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, }; make_obj(wk, &ctx.arr, obj_array); if (!obj_array_foreach_flat(wk, an[0].val, &ctx, process_script_commandline_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, }; make_obj(wk, &ctx.arr, obj_array); if (!obj_array_foreach_flat(wk, an[0].val, &ctx, process_script_commandline_iter)) { return false; } // TODO: uncomment when muon dist is implemented /* obj_array_push(wk, wk->dist_scripts, ctx.arr); */ return true; } 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; } if (an[1].set) { *res = an[1].val; } else { vm_error_at(wk, an[0].node, "TODO: get cross property"); return false; } return true; } 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; } if (an[1].set) { *res = an[1].val; } else { vm_error_at(wk, an[0].node, "TODO: get external property"); return false; } return true; } static bool func_meson_can_run_host_binaries(struct workspace *wk, obj _, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, true); // TODO: can return false in cross compile 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 }, { NULL, NULL }, }; static enum iteration_result compiler_dict_to_str_dict_iter(struct workspace *wk, void *_ctx, obj k, obj v) { obj_dict_set(wk, *(obj *)_ctx, make_str(wk, compiler_language_to_s(k)), v); return ir_cont; } static obj compiler_dict_to_str_dict(struct workspace *wk, obj d) { obj r; make_obj(wk, &r, obj_dict); obj_dict_foreach(wk, d, &r, compiler_dict_to_str_dict_iter); return r; } 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); make_obj(wk, res, obj_dict); obj_dict_set(wk, *res, make_str(wk, "opts"), proj->opts); obj_dict_set(wk, *res, make_str(wk, "compilers"), compiler_dict_to_str_dict(wk, proj->compilers)); 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 }, { tc_capture }, ARG_TYPE_NULL, }; if (!pop_args(wk, an, NULL)) { return false; } obj_dict_set(wk, wk->dependency_handlers, an[0].val, an[1].val); 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; } const struct func_impl impl_tbl_meson_internal[] = { { "project", func_meson_project, tc_dict }, { "register_dependency_handler", func_meson_register_dependency_handler }, { "argv0", func_meson_argv0, tc_string }, { NULL, NULL }, }; muon-v0.3.0/src/functions/configuration_data.c0000644000175000017500000001143114674562002020447 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" #include "log.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; make_obj(wk, &n, 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; make_obj(wk, res, 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; make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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.3.0/src/functions/number.c0000644000175000017500000000272614674562002016106 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" #include "log.h" static bool func_number_is_odd(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, (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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, (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.3.0/src/functions/machine.c0000644000175000017500000000476614674562002016230 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "error.h" #include "functions/machine.h" #include "lang/func_lookup.h" #include "lang/typecheck.h" #include "log.h" #include "machines.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; } 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 = make_str(wk, machine_system_to_s(m->sys)); 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); enum endianness e = m->endianness; const char *s = NULL; switch (e) { case little_endian: s = "little"; break; case big_endian: s = "big"; break; } *res = make_str(wk, 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; } static bool func_machine_subsystem(struct workspace *wk, obj self, obj *res) { if (!pop_args(wk, NULL, NULL)) { return false; } LOG_W("machine.subsystem is not supported"); *res = make_str(wk, ""); 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, tc_string }, { "system", func_machine_system, tc_string }, { "kernel", func_machine_kernel, tc_string }, { "subsystem", func_machine_subsystem, tc_string }, { NULL, NULL }, }; muon-v0.3.0/src/functions/compiler.c0000644000175000017500000020231014674562002016417 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti SPDX-FileCopyrightText: Luke Drummond * SPDX-FileCopyrightText: Eli Schwartz * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "args.h" #include "backend/common_args.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/filesystem.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "sha_256.h" enum compile_mode { compile_mode_preprocess, compile_mode_compile, compile_mode_link, compile_mode_run, }; struct compiler_check_opts { struct run_cmd_ctx cmd_ctx; enum compile_mode mode; obj comp_id; struct args_kw *deps, *inc, *required, *werror; obj args; bool skip_run_check; bool src_is_path; const char *output_path; bool from_cache; obj cache_key, cache_val; }; static const char * bool_to_yn(bool v) { return v ? "\033[32mYES\033[0m" : "\033[31mNO\033[0m"; } 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: ", compiler_language_to_s(comp->lang)); log_plainv(fmt, args); log_plain("\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: ", compiler_language_to_s(comp->lang)); log_plainv(fmt, args); if (opts->from_cache) { log_plain(" \033[36mcached\033[0m"); } log_plain("\n"); va_end(args); } static void add_extra_compiler_check_args(struct workspace *wk, struct obj_compiler *comp, obj args) { if (comp->lang == compiler_language_cpp) { // From meson: // -fpermissive allows non-conforming code to compile which is necessary // for many C++ checks. Particularly, the has_header_symbol check is // too strict without this and always fails. obj_array_push(wk, args, make_str(wk, "-fpermissive")); } } 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; make_obj(wk, &include_dirs, 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); } setup_compiler_args_includes(wk, comp_id, include_dirs, compiler_args, false); return true; } static bool compiler_check_cache(struct workspace *wk, struct obj_compiler *comp, const char *argstr, uint32_t argc, const char *src, uint8_t sha_res[32], bool *res, obj *res_val) { uint32_t argstr_len; { uint32_t i = 0; const char *p = argstr; for (;; ++p) { if (!p[0]) { if (++i >= argc) { break; } } } argstr_len = p - argstr; } enum { sha_idx_argstr = 0, sha_idx_ver = sha_idx_argstr + 32, sha_idx_src = sha_idx_ver + 32, sha_len = sha_idx_src + 32 }; uint8_t sha[sha_len] = { 0 }; calc_sha_256(&sha[sha_idx_argstr], argstr, argstr_len); if (comp->ver) { const struct str *ver = get_str(wk, comp->ver); calc_sha_256(&sha[sha_idx_ver], ver->s, ver->len); } calc_sha_256(&sha[sha_idx_src], src, strlen(src)); calc_sha_256(sha_res, sha, sha_len); /* LLOG_I("sha: "); */ /* uint32_t i; */ /* for (i = 0; i < 32; ++i) { */ /* log_plain("%02x", sha_res[i]); */ /* } */ /* log_plain("\n"); */ obj arr; if (obj_dict_index_strn(wk, wk->compiler_check_cache, (const char *)sha_res, 32, &arr)) { obj cache_res; obj_array_index(wk, arr, 0, &cache_res); *res = get_obj_bool(wk, cache_res); obj_array_index(wk, arr, 1, res_val); return true; } else { return false; } } static void set_compiler_cache(struct workspace *wk, obj key, bool res, obj val) { if (!key) { return; } obj arr, cache_res; if (obj_dict_index(wk, wk->compiler_check_cache, key, &arr)) { obj_array_index(wk, arr, 0, &cache_res); set_obj_bool(wk, cache_res, res); obj_array_set(wk, arr, 1, val); } else { make_obj(wk, &arr, obj_array); make_obj(wk, &cache_res, obj_bool); set_obj_bool(wk, cache_res, res); obj_array_push(wk, arr, cache_res); obj_array_push(wk, arr, val); obj_dict_set(wk, wk->compiler_check_cache, key, arr); } } static 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); /* enum compiler_type t = comp->type; */ obj compiler_args; make_obj(wk, &compiler_args, obj_array); obj_array_extend(wk, compiler_args, comp->cmd_arr); push_args(wk, compiler_args, toolchain_compiler_always(wk, comp)); get_std_args(wk, comp, current_project(wk), NULL, compiler_args); add_extra_compiler_check_args(wk, comp, compiler_args); 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 compile_mode_run: case compile_mode_link: get_option_link_args(wk, comp, current_project(wk), NULL, compiler_args); /* fallthrough */ case compile_mode_compile: get_option_compile_args(wk, comp, current_project(wk), NULL, compiler_args); /* fallthrough */ case compile_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 compile_mode_preprocess: push_args(wk, compiler_args, toolchain_compiler_preprocess_only(wk, comp)); break; case compile_mode_compile: push_args(wk, compiler_args, toolchain_compiler_compile_only(wk, comp)); break; case compile_mode_run: break; case compile_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 { SBUF(test_source_path); path_join(wk, &test_source_path, wk->muon_private, "test."); sbuf_pushs(wk, &test_source_path, compiler_language_extension(comp->lang)); source_path = sbuf_into_str(wk, &test_source_path); } obj_array_push(wk, compiler_args, source_path); SBUF(test_output_path); const char *output_path; if (opts->output_path) { output_path = opts->output_path; } else if (opts->mode == compile_mode_run) { path_join(wk, &test_output_path, wk->muon_private, "compiler_check_exe"); output_path = test_output_path.buf; } else { path_join(wk, &test_output_path, wk->muon_private, "test."); sbuf_pushs(wk, &test_output_path, compiler_language_extension(comp->lang)); sbuf_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 setup_linker_args_ctx sctx = { .compiler = comp, .args = &dep, }; setup_linker_args(wk, 0, 0, &sctx); 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); uint8_t sha[32]; if (compiler_check_cache(wk, comp, argstr, argc, src, sha, res, &opts->cache_val)) { opts->from_cache = true; return true; } opts->cache_key = make_strn(wk, (const char *)sha, 32); 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.err.buf); L("compiler stderr: '%s'", cmd_ctx.out.buf); if (opts->mode == compile_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 set_compiler_cache(wk, opts->cache_key, *res, 0); ret = true; ret: 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) { \ make_obj(wk, res, obj_bool); \ set_obj_bool(wk, *res, 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 = compile_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) { make_obj(wk, res, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); } } else { if (!opts.from_cache) { make_obj(wk, res, obj_number); set_obj_number(wk, *res, -1); } } if (opts.from_cache) { *res = opts.cache_val; } else { run_cmd_ctx_destroy(&opts.cmd_ctx); set_compiler_cache(wk, opts.cache_key, true, *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 = compile_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 { make_obj(wk, res, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); run_cmd_ctx_destroy(&opts.cmd_ctx); set_compiler_cache(wk, opts.cache_key, true, *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 = compile_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 { make_obj(wk, res, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); run_cmd_ctx_destroy(&opts.cmd_ctx); set_compiler_cache(wk, opts.cache_key, true, *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, &WKSTR(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 = compile_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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } make_obj(wk, res, 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 = compile_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), &WKSTR("__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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 = compile_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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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) { SBUF(output_path); path_join(wk, &output_path, wk->muon_private, "get_define_output"); opts->output_path = output_path.buf; opts->mode = compile_mode_preprocess; char src[BUF_SIZE_4k]; const struct str delim_start = WKSTR("\"MUON_GET_DEFINE_DELIMITER_START\"\n"), delim_end = WKSTR("\n\"MUON_GET_DEFINE_DELIMITER_END\""), delim_sentinel = WKSTR("\"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; } set_compiler_cache(wk, opts->cache_key, true, *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; make_obj(wk, &b, obj_bool); set_obj_bool(wk, b, !!*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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, str_eql(get_str(wk, pre), &WKSTR("_"))); return true; } static bool func_compiler_check_common(struct workspace *wk, obj self, obj *res, enum compile_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 compile_mode_run: mode_s = "runs"; break; case compile_mode_link: mode_s = "links"; break; case compile_mode_compile: mode_s = "compiles"; break; case compile_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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ok); return true; } static bool func_compiler_compiles(struct workspace *wk, obj self, obj *res) { return func_compiler_check_common(wk, self, res, compile_mode_compile); } static bool func_compiler_links(struct workspace *wk, obj self, obj *res) { return func_compiler_check_common(wk, self, res, compile_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 compile_mode_compile: mode_s = "is usable"; break; case compile_mode_preprocess: mode_s = "found"; break; default: abort(); } compiler_handle_has_required_kw(required, ok); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 compile_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, compile_mode_preprocess); } static bool func_compiler_check_header(struct workspace *wk, obj self, obj *res) { return compiler_check_header_common(wk, self, res, compile_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 = compile_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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 = compile_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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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 = compile_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) { make_obj(wk, res, 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 { make_obj(wk, res, 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; } set_compiler_cache(wk, opts.cache_key, ok, *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 compile_mode mode) { struct obj_compiler *comp = get_obj_compiler(wk, comp_id); obj args; make_obj(wk, &args, obj_array); 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; } push_args(wk, args, toolchain_compiler_werror(wk, comp)); struct compiler_check_opts opts = { .mode = mode, .comp_id = comp_id, .args = args, }; const char *src = "int main(void){}\n"; if (!compiler_check(wk, &opts, src, err_node, has_argument)) { return false; } 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 compile_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 compile_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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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, compile_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, compile_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, compile_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, compile_mode_link); } static bool compiler_get_supported_arguments(struct workspace *wk, obj self, obj *res, enum compile_mode mode) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } make_obj(wk, res, 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, compile_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, compile_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 compile_mode mode) { struct args_norm an[] = { { TYPE_TAG_GLOB | obj_string }, ARG_TYPE_NULL }; if (!pop_args(wk, an, NULL)) { return false; } make_obj(wk, res, 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, compile_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, compile_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_posix: case compiler_gcc: case compiler_clang: case compiler_apple_clang: syntax = "gcc"; break; default: syntax = "other"; break; } *res = make_str(wk, syntax); return true; } struct compiler_find_library_ctx { struct sbuf *path; obj lib_name; bool only_static; bool found; }; static enum iteration_result compiler_find_library_iter(struct workspace *wk, void *_ctx, obj libdir) { struct compiler_find_library_ctx *ctx = _ctx; SBUF(lib); static const char *pref[] = { "", "lib", NULL }; const char *suf[] = { ".so", ".a", NULL }; if (ctx->only_static) { suf[0] = ".a"; suf[1] = NULL; } uint32_t i, j; for (i = 0; suf[i]; ++i) { for (j = 0; pref[j]; ++j) { sbuf_clear(&lib); sbuf_pushf(wk, &lib, "%s%s%s", pref[j], get_cstr(wk, ctx->lib_name), suf[i]); path_join(wk, ctx->path, get_cstr(wk, libdir), lib.buf); if (fs_file_exists(ctx->path->buf)) { ctx->found = true; return ir_done; } } } return ir_cont; } 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; } 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; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } make_obj(wk, res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *res); dep->type = dependency_type_external_library; if (requirement == requirement_skip) { return true; } SBUF(library_path); if (!akw[kw_static].set) { get_option_value(wk, current_project(wk), "prefer_static", &akw[kw_static].val); } struct compiler_find_library_ctx ctx = { .path = &library_path, .lib_name = an[0].val, .only_static = get_obj_bool(wk, akw[kw_static].val), }; struct obj_compiler *comp = get_obj_compiler(wk, self); bool found_from_dirs_kw = false; if (akw[kw_dirs].set) { if (!obj_array_foreach(wk, akw[kw_dirs].val, &ctx, compiler_find_library_iter)) { return false; } if (ctx.found) { found_from_dirs_kw = true; } } if (!ctx.found) { if (!obj_array_foreach(wk, comp->libdirs, &ctx, compiler_find_library_iter)) { return false; } } if (ctx.found && akw[kw_has_headers].set) { 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], }; struct compiler_check_opts header_check_opts = { 0 }; compiler_opts_init(self, header_kwargs, &header_check_opts); struct compiler_find_library_check_headers_ctx check_headers_ctx = { .ok = true, .err_node = akw[kw_has_headers].node, .opts = &header_check_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) { ctx.found = false; } } if (!ctx.found) { if (requirement == requirement_required) { vm_error_at(wk, an[0].node, "library not found"); return false; } LOG_W("library '%s' not found", get_cstr(wk, an[0].val)); if (akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val)) { *res = disabler_id; } return true; } compiler_log(wk, self, "found library '%s' at '%s'", get_cstr(wk, an[0].val), ctx.path->buf); dep->flags |= dep_flag_found; make_obj(wk, &dep->dep.link_with, obj_array); obj path_str = make_str(wk, ctx.path->buf); obj_array_push(wk, dep->dep.link_with, path_str); if (found_from_dirs_kw) { make_obj(wk, &dep->dep.rpath, obj_array); obj_array_push(wk, dep->dep.rpath, path_str); } dep->dep.link_language = comp->lang; 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, .required = true }, [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, &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)); get_std_args(wk, comp, current_project(wk), NULL, base_cmd); 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); } make_obj(wk, res, obj_array); SBUF(output_dir); sbuf_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 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, .output_node = akw[kw_output].node, .command_node = 0, .input_orig = v, .output_orig = akw[kw_output].val, .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; 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 compiler_handle_toolchain_args(struct workspace *wk, obj self, obj *res, enum toolchain_component component) { struct args_norm an[] = { { 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))) }, ARG_TYPE_NULL, }; enum kwargs { kw_overwrite, }; struct args_kw akw[] = { [kw_overwrite] = { "overwrite", tc_bool }, 0, }; if (!pop_args(wk, an, akw)) { return false; } struct obj_compiler *c = get_obj_compiler(wk, self); { // validate function signatures const struct toolchain_arg_handler *handler; obj k, v; obj_dict_for(wk, an[0].val, 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; } } } obj *overrides = &c->overrides[component]; bool overwrite = akw[kw_overwrite].set ? get_obj_bool(wk, akw[kw_overwrite].val) : *overrides == 0; if (overwrite) { *overrides = an[0].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, an[0].val); } return true; } static bool func_compiler_handle_compiler_args(struct workspace *wk, obj self, obj *res) { return compiler_handle_toolchain_args(wk, self, res, toolchain_component_compiler); } static bool func_compiler_handle_linker_args(struct workspace *wk, obj self, obj *res) { return compiler_handle_toolchain_args(wk, self, res, toolchain_component_linker); } static bool func_compiler_handle_static_linker_args(struct workspace *wk, obj self, obj *res) { return compiler_handle_toolchain_args(wk, self, res, toolchain_component_static_linker); } const struct func_impl impl_tbl_compiler_internal[] = { { "handle_compiler_args", func_compiler_handle_compiler_args }, { "handle_linker_args", func_compiler_handle_linker_args }, { "handle_static_linker_args", func_compiler_handle_static_linker_args }, { NULL, NULL }, }; muon-v0.3.0/src/functions/modules/0002755000175000017500000000000014674562002016115 5ustar buildbuildmuon-v0.3.0/src/functions/modules/toolchain.c0000644000175000017500000000445314674562002020245 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "error.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_string | tc_compiler }, [kw_inherit_compiler] = { "inherit_compiler", tc_string | tc_compiler }, [kw_inherit_linker] = { "inherit_linker", tc_string | tc_compiler }, [kw_inherit_static_linker] = { "inherit_static_linker", tc_string | tc_compiler }, 0, }; if (!pop_args(wk, NULL, akw)) { return false; } make_obj(wk, res, obj_compiler); struct obj_compiler *c = get_obj_compiler(wk, *res); { 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; 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]; } c->type[i] = type; c->overrides[i] = override; } } return true; } const struct func_impl impl_tbl_module_toolchain[] = { { "create", func_module_toolchain_create, tc_compiler }, { NULL, NULL }, }; muon-v0.3.0/src/functions/modules/fs.c0000644000175000017500000005225314674562002016676 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 "coerce.h" #include "error.h" #include "formats/editorconfig.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/filesystem.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 = get_obj_both_libs(wk, o)->dynamic_lib; /* 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; } SBUF(joined); path_join(wk, &joined, get_cstr(wk, tgt->build_dir), name); return get_cstr(wk, sbuf_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 sbuf *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; } path_join(wk, buf, home, &s[1]); } 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; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, opts, &path)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_noabs, &path)) { return false; } SBUF(buf); path_dirname(wk, &buf, path.buf); *res = sbuf_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), &WKSTR("utf-8"))) { vm_error_at(wk, akw[kw_encoding].node, "only 'utf-8' supported"); } } SBUF(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; } make_obj(wk, res, obj_bool); // TODO: Handle this set_obj_bool(wk, *res, 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; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path)) { return false; } *res = sbuf_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; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_noexpanduser, &path)) { return false; } SBUF(basename); path_basename(wk, &basename, path.buf); *res = sbuf_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; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_noexpanduser, &path)) { return false; } SBUF(basename); path_basename(wk, &basename, path.buf); char *dot; if ((dot = strrchr(basename.buf, '.'))) { *dot = 0; basename.len = strlen(basename.buf); } *res = sbuf_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; SBUF(buf); for (p = path; *p; ++p) { if (*p == '\\') { sbuf_push(wk, &buf, '/'); if (*(p + 1) == '\\') { ++p; } } else { sbuf_push(wk, &buf, *p); } } *res = sbuf_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; } SBUF(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); } sbuf_pushs(wk, &path, get_cstr(wk, an[1].val)); *res = sbuf_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), &WKSTR("sha256"))) { vm_error_at(wk, an[1].node, "only sha256 is supported"); return false; } SBUF(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 }; uint32_t i, bufi = 0; for (i = 0; i < 32; ++i) { snprintf(&buf[bufi], 3, "%x", hash[i]); bufi += 2; } *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; } SBUF(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); make_obj(wk, res, 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; } SBUF(path1); if (!fix_file_path(wk, an[0].node, an[0].val, 0, &path1)) { return false; } SBUF(path2); if (!fix_file_path(wk, an[1].node, an[1].val, 0, &path2)) { return false; } // TODO: handle symlinks make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } SBUF(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 { SBUF(dest); path_basename(wk, &dest, path.buf); output = sbuf_into_str(wk, &dest); } obj command; make_obj(wk, &command, obj_array); push_args_null_terminated(wk, command, (char *const[]){ (char *)wk->argv0, "internal", "eval", "-e", "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); 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; } SBUF(b1); SBUF(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); } SBUF(path); path_relative_to(wk, &path, b2.buf, b1.buf); *res = sbuf_into_str(wk, &path); return true; } const struct func_impl impl_tbl_module_fs[] = { { "as_posix", func_module_fs_as_posix, tc_string, true }, { "copyfile", func_module_fs_copyfile, tc_custom_target }, { "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 }, { 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; } SBUF(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 }; if (!pop_args(wk, an, NULL)) { return false; } SBUF(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))) { 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; } SBUF(cwd); path_copy_cwd(wk, &cwd); *res = sbuf_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; } SBUF(path); path_make_absolute(wk, &path, get_cstr(wk, an[0].val)); *res = sbuf_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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } SBUF(path); path_without_ext(wk, &path, get_cstr(wk, an[0].val)); *res = sbuf_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; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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; } SBUF(path); path_copy(wk, &path, get_cstr(wk, an[0].val)); sbuf_pushs(wk, &path, get_cstr(wk, an[1].val)); *res = sbuf_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; } SBUF(path); path_executable(wk, &path, get_cstr(wk, an[0].val)); *res = sbuf_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, &WKSTR("**"))) { subctx.mode = fs_glob_mode_recurse; subctx.matchprefix = "/"; goto recurse_all_mode; } if (str_eql_glob(&pat, &WKSTR(name))) { SBUF(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, sbuf_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: { SBUF(path); path_join(wk, &path, subctx.matchprefix, name); SBUF(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, sbuf_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; } SBUF(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); */ path_copy(wk, &pat, _pat->s); } SBUF(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; } } sbuf_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); */ make_obj(wk, res, obj_array); if (!fs_dir_exists(prefix.buf)) { return true; } 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); } const struct func_impl impl_tbl_module_fs_internal[] = { { "as_posix", func_module_fs_as_posix, tc_string, true }, { "copyfile", func_module_fs_copyfile }, { "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 }, { "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 }, { "copy", func_module_fs_copy, .fuzz_unsafe = true }, { "cwd", func_module_fs_cwd, tc_string }, { "executable", func_module_fs_executable, tc_string, true }, { "glob", func_module_fs_glob, tc_array }, { "is_basename", func_module_fs_is_basename, tc_bool, true }, { "is_subpath", func_module_fs_is_subpath, tc_bool, true }, { "make_absolute", func_module_fs_make_absolute, tc_string }, { "mkdir", func_module_fs_mkdir, .fuzz_unsafe = true }, { "relative_to", func_module_fs_relative_to, tc_string, true }, { "rmdir", func_module_fs_rmdir, .fuzz_unsafe = true }, { "without_ext", func_module_fs_without_ext, tc_string, true }, { "write", func_module_fs_write, .fuzz_unsafe = true }, { NULL, NULL }, }; muon-v0.3.0/src/functions/modules/pkgconfig.c0000644000175000017500000006241614674562002020237 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "error.h" #include "functions/custom_target.h" #include "functions/file.h" #include "install.h" #include "lang/typecheck.h" #include "log.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), &WKSTR("."))) { obj_array_push(wk, *cflags, make_str(wk, "-I${includedir}")); } else { SBUF(path); path_join(wk, &path, "-I${includedir}", get_cstr(wk, val)); obj_array_push(wk, *cflags, sbuf_into_str(wk, &path)); } return ir_cont; } static bool module_pkgconf_lib_to_lname(struct workspace *wk, obj lib, obj *res) { SBUF(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 = WKSTR(str); if (str_startswith(&s, &WKSTR("-l"))) { s.len -= 2; s.s += 2; } else if (str_startswith(&s, &WKSTR("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 = get_obj_both_libs(wk, val)->dynamic_lib; /* 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; make_obj(wk, &f, 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_fprintf(wk, log_file(), "%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 = get_obj_both_libs(wk, val)->dynamic_lib; /* 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_priv, 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_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; make_obj(wk, &link_with_files, obj_array); 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; } break; } case dependency_type_appleframeworks: // TODO: actually add correct -framework arguments 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; obj_array_index(wk, tgt->output, 0, &out); if (str_endswith(get_str(wk, *get_obj_file(wk, out)), &WKSTR(".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, &WKSTR(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, &WKSTR(module_pkgconf_diropts[i].optname))) { module_pkgconf_diropts[i].added = true; } if (module_pkgconf_diropts[i].refd) { continue; } if (str_startswith(val, &WKSTR(module_pkgconf_diropts[i].name))) { module_pkgconf_diropts[module_pkgconf_diropt_prefix].refd = true; // prefix module_pkgconf_diropts[i].refd = true; } } } SBUF(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, &WKSTR(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) { SBUF(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; make_obj(wk, &arr, 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; make_obj(wk, &arr, 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) { make_obj(wk, &pc.libs[i], obj_array); make_obj(wk, &pc.reqs[i], obj_array); } make_obj(wk, &pc.cflags, obj_array); make_obj(wk, &pc.variables, obj_array); make_obj(wk, &pc.builtin_dir_variables, obj_array); make_obj(wk, &pc.exclude, obj_array); obj mainlib = 0; if (an[0].set) { switch (get_obj_type(wk, an[0].val)) { case obj_both_libs: { mainlib = get_obj_both_libs(wk, an[0].val)->dynamic_lib; 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; } SBUF(path); path_join(wk, &path, wk->muon_private, get_cstr(wk, filebase)); sbuf_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; } make_obj(wk, res, obj_file); *get_obj_file(wk, *res) = sbuf_into_str(wk, &path); { SBUF(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; } SBUF(dest); path_join(wk, &dest, install_dir, get_cstr(wk, filebase)); sbuf_pushs(wk, &dest, ".pc"); push_install_target(wk, *get_obj_file(wk, *res), sbuf_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.3.0/src/functions/modules/sourceset.c0000644000175000017500000000120514674562002020271 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; } make_obj(wk, res, obj_source_set); struct obj_source_set *ss = get_obj_source_set(wk, *res); make_obj(wk, &ss->rules, 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.3.0/src/functions/modules/python.c0000644000175000017500000003455714674562002017616 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "coerce.h" #include "embedded.h" #include "external/tinyjson.h" #include "functions/external_program.h" #include "functions/modules/python.h" #include "install.h" #include "lang/object.h" #include "lang/typecheck.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) { const char *pyinfo = embedded_get("python_info.py"); if (!pyinfo) { return false; } struct run_cmd_ctx cmd_ctx = { 0 }; char *const var_args[] = { (char *)path, "-c", (char *)pyinfo, 0 }; if (!run_cmd_argv(&cmd_ctx, var_args, NULL, 0) || cmd_ctx.status != 0) { return false; } obj res_introspect; bool success = muon_json_to_dict(wk, cmd_ctx.out.buf, &res_introspect); if (!success) { goto end; } if (!obj_dict_index_str(wk, res_introspect, "version", &python->language_version)) { success = false; goto end; } if (!obj_dict_index_str(wk, res_introspect, "sysconfig_paths", &python->sysconfig_paths)) { success = false; goto end; } if (!obj_dict_index_str(wk, res_introspect, "variables", &python->sysconfig_vars)) { success = false; goto end; } if (!obj_dict_index_str(wk, res_introspect, "install_paths", &python->install_paths)) { success = false; goto end; } end: run_cmd_ctx_destroy(&cmd_ctx); return success; } static bool python_module_present(struct workspace *wk, const char *pythonpath, const char *mod) { struct run_cmd_ctx cmd_ctx = { 0 }; SBUF(importstr); sbuf_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 sbuf cmd_path, bool found, bool pure) { make_obj(wk, res, obj_python_installation); struct obj_python_installation *python = get_obj_python_installation(wk, *res); python->pure = pure; make_obj(wk, &python->prog, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, python->prog); ep->found = found; make_obj(wk, &ep->cmd_array, obj_array); obj_array_push(wk, ep->cmd_array, sbuf_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; } } SBUF(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 = disabler_id; 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 = disabler_id; 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); } SBUF(cmd_path); if (!fs_find_cmd(wk, &cmd_path, cmd)) { vm_error(wk, "python3 not found"); return false; } make_obj(wk, res, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *res); ep->found = true; make_obj(wk, &ep->cmd_array, obj_array); obj_array_push(wk, ep->cmd_array, sbuf_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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, 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); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, found); return true; } static bool get_install_dir(struct workspace *wk, obj self, bool pure, const char *subdir, obj *res) { SBUF(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), &WKSTR(""))) { 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), &WKSTR(""))) { 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 = sbuf_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; obj_array_index(wk, ctx->rename, ctx->i, &rename); SBUF(d); path_join(wk, &d, get_cstr(wk, ctx->dest), get_cstr(wk, rename)); dest = sbuf_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; } obj_array_index(wk, get_obj_external_program(wk, self)->cmd_array, 0, res); return true; } 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 }, { NULL, NULL }, }; muon-v0.3.0/src/functions/modules/keyval.c0000644000175000017500000000301414674562002017550 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "error.h" #include "formats/ini.h" #include "lang/func_lookup.h" #include "functions/modules/keyval.h" #include "lang/typecheck.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; } make_obj(wk, res, 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.3.0/src/functions/custom_target.c0000644000175000017500000000244214674562002017471 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" #include "log.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, &out); 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.3.0/src/options.c0000644000175000017500000007746514674562002014315 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "backend/output.h" #include "embedded.h" #include "error.h" #include "lang/object_iterators.h" #include "lang/serial.h" #include "lang/typecheck.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" bool initializing_builtin_options = false; static const char *build_option_type_to_s[build_option_type_count] = { [op_string] = "string", [op_boolean] = "boolean", [op_combo] = "combo", [op_integer] = "integer", [op_array] = "array", [op_feature] = "feature", }; static bool set_option(struct workspace *wk, uint32_t node, obj opt, obj new_val, enum option_value_source source, bool coerce); static bool parse_config_string(struct workspace *wk, const struct str *ss, struct option_override *oo, bool key_only) { if (str_has_null(ss)) { LOG_E("option cannot contain NUL"); return false; } struct str subproject = { 0 }, key = { 0 }, val = { 0 }, cur = { 0 }; cur.s = ss->s; bool reading_key = true, have_subproject = false; uint32_t i; for (i = 0; i < ss->len; ++i) { if (reading_key) { if (ss->s[i] == ':') { if (have_subproject) { LOG_E("multiple ':' in option '%s'", ss->s); return false; } have_subproject = true; subproject = cur; cur.s = &ss->s[i + 1]; cur.len = 0; continue; } else if (ss->s[i] == '=') { key = cur; cur.s = &ss->s[i + 1]; cur.len = 0; reading_key = false; continue; } } ++cur.len; } if (!reading_key) { val = cur; } else { key = cur; } if (have_subproject && !subproject.len) { LOG_E("missing subproject in option '%s'", ss->s); return false; } if (!key.len) { LOG_E("missing key in option '%s'", ss->s); return false; } else if (key_only && val.len) { LOG_E("unexpected '=' in option '%s'", ss->s); return false; } else if (!key_only && !val.len) { LOG_E("expected '=' in option '%s'", ss->s); return false; } oo->name = make_strn(wk, key.s, key.len); if (!key_only) { oo->val = make_strn(wk, val.s, val.len); } if (have_subproject) { oo->proj = make_strn(wk, subproject.s, subproject.len); } return true; } static bool subproj_name_matches(struct workspace *wk, const char *name, const char *test) { if (test) { return name && strcmp(test, name) == 0; } else { return !name; } } static void log_option_override(struct workspace *wk, struct option_override *oo) { log_plain("'"); if (oo->proj) { log_plain("%s:", get_cstr(wk, oo->proj)); } obj_fprintf(wk, log_file(), "%s=%#o", get_cstr(wk, oo->name), oo->val); log_plain("'"); } bool check_invalid_subproject_option(struct workspace *wk) { uint32_t i, j; struct option_override *oo; struct project *proj; bool found, ret = true; for (i = 0; i < wk->option_overrides.len; ++i) { oo = arr_get(&wk->option_overrides, i); if (!oo->proj || oo->source < option_value_source_commandline) { continue; } found = false; for (j = 1; j < wk->projects.len; ++j) { proj = arr_get(&wk->projects, j); if (proj->not_ok) { continue; } if (strcmp(get_cstr(wk, proj->subproject_name), get_cstr(wk, oo->proj)) == 0) { found = true; break; } } if (!found) { LLOG_E("invalid option: "); log_option_override(wk, oo); log_plain(" (no such subproject)\n"); ret = false; } } return ret; } static bool coerce_feature_opt(struct workspace *wk, const struct str *val, obj *res) { enum feature_opt_state f; if (str_eql(val, &WKSTR("auto"))) { f = feature_opt_auto; } else if (str_eql(val, &WKSTR("enabled"))) { f = feature_opt_enabled; } else if (str_eql(val, &WKSTR("disabled"))) { f = feature_opt_disabled; } else { vm_error(wk, "unable to coerce '%s' into a feature", val->s); return false; } make_obj(wk, res, obj_feature_opt); set_obj_feature_opt(wk, *res, f); return true; } struct check_deprecated_option_ctx { struct obj_option *opt; obj *val; obj sval; }; static enum iteration_result check_deprecated_option_iter(struct workspace *wk, void *_ctx, obj old, obj new) { struct check_deprecated_option_ctx *ctx = _ctx; switch (ctx->opt->type) { case op_array: { uint32_t idx; if (obj_array_index_of(wk, *ctx->val, old, &idx)) { vm_warning(wk, "option value %o is deprecated", old); if (new) { obj_array_set(wk, *ctx->val, idx, new); } } break; } default: if (str_eql(get_str(wk, ctx->sval), get_str(wk, old))) { vm_warning(wk, "option value %o is deprecated", old); if (new) { *ctx->val = new; } } } return ir_cont; } static bool check_deprecated_option(struct workspace *wk, struct obj_option *opt, obj sval, obj *val) { struct check_deprecated_option_ctx ctx = { .val = val, .sval = sval, .opt = opt, }; switch (get_obj_type(wk, opt->deprecated)) { case obj_bool: if (get_obj_bool(wk, opt->deprecated)) { vm_warning(wk, "option %o is deprecated", ctx.opt->name); } break; case obj_string: { struct project *cur_proj = current_project(wk); vm_warning(wk, "option %o is deprecated to %o", opt->name, opt->deprecated); obj newopt; if (get_option(wk, cur_proj, get_str(wk, opt->deprecated), &newopt)) { set_option(wk, 0, newopt, sval, option_value_source_deprecated_rename, true); } else { struct option_override oo = { .proj = current_project(wk)->cfg.name, .name = opt->deprecated, .val = sval, .source = option_value_source_deprecated_rename, }; arr_push(&wk->option_overrides, &oo); } break; } case obj_dict: case obj_array: obj_iterable_foreach(wk, opt->deprecated, &ctx, check_deprecated_option_iter); break; default: UNREACHABLE; } return true; } static bool coerce_option_override(struct workspace *wk, struct obj_option *opt, obj sval, obj *res) { const struct str *val = get_str(wk, sval); *res = 0; if (opt->type == op_array) { // coerce array early so that its elements may be checked for deprecation if (!val->len) { // make -Doption= equivalent to an empty list make_obj(wk, res, obj_array); } else if (val->s[0] == '[') { if (!eval_str(wk, val->s, eval_mode_repl, res)) { LOG_E("malformed array option value '%s'", val->s); return false; } } else { *res = str_split(wk, val, &WKSTR(",")); } } if (opt->deprecated) { if (!check_deprecated_option(wk, opt, sval, res)) { return false; } if (*res) { if (get_obj_type(wk, *res) == obj_string) { sval = *res; val = get_str(wk, *res); } else { return true; } } } switch (opt->type) { case op_combo: case op_string: *res = sval; break; case op_boolean: { bool b; if (str_eql(val, &WKSTR("true"))) { b = true; } else if (str_eql(val, &WKSTR("false"))) { b = false; } else { vm_error(wk, "unable to coerce '%s' into a boolean", val->s); return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, b); break; } case op_integer: { int64_t num; char *endptr; num = strtoll(val->s, &endptr, 10); if (!val->len || *endptr) { vm_error(wk, "unable to coerce '%s' into a number", val->s); return false; } make_obj(wk, res, obj_number); set_obj_number(wk, *res, num); break; } case op_array: { // do nothing, array values were already coerced above break; } case op_feature: return coerce_feature_opt(wk, val, res); default: UNREACHABLE; } return true; } static bool typecheck_opt(struct workspace *wk, uint32_t node, obj val, enum build_option_type type, obj name, obj *res) { enum obj_type expected_type; if (type == op_feature && get_obj_type(wk, val) == obj_string) { if (!coerce_feature_opt(wk, get_str(wk, val), res)) { return false; } val = *res; } switch (type) { case op_feature: expected_type = obj_feature_opt; break; case op_string: expected_type = obj_string; break; case op_boolean: expected_type = obj_bool; break; case op_combo: expected_type = obj_string; break; case op_integer: expected_type = obj_number; break; case op_array: expected_type = obj_array; break; default: UNREACHABLE_RETURN; } char msg[256]; snprintf(msg, sizeof(msg), "expected type %%s for option %s, got %%s", get_cstr(wk, name)); if (!typecheck_custom(wk, node, val, expected_type, msg)) { return false; } *res = val; return true; } struct prefix_dir_opts_ctx { const struct str *prefix; }; static enum iteration_result prefix_dir_opts_iter(struct workspace *wk, void *_ctx, obj _k, obj v) { struct prefix_dir_opts_ctx *ctx = _ctx; struct obj_option *opt = get_obj_option(wk, v); if (opt->kind != build_option_kind_prefixed_dir) { return ir_cont; } const char *path = get_cstr(wk, opt->val); if (!path_is_absolute(path)) { return ir_cont; } SBUF(new_path); if (path_is_subpath(ctx->prefix->s, path)) { path_relative_to(wk, &new_path, ctx->prefix->s, path); } else { path_join(wk, &new_path, ctx->prefix->s, path); } opt->val = sbuf_into_str(wk, &new_path); return ir_cont; } bool prefix_dir_opts(struct workspace *wk) { obj prefix; get_option_value(wk, NULL, "prefix", &prefix); return obj_dict_foreach(wk, wk->global_opts, &(struct prefix_dir_opts_ctx){ .prefix = get_str(wk, prefix), }, prefix_dir_opts_iter); } static void extend_array_option(struct workspace *wk, obj opt, obj new_val, enum option_value_source source) { struct obj_option *o = get_obj_option(wk, opt); if (o->source > source) { return; } o->source = source; obj_array_extend_nodup(wk, o->val, new_val); } static bool set_option(struct workspace *wk, uint32_t node, obj opt, obj new_val, enum option_value_source source, bool coerce) { struct obj_option *o = get_obj_option(wk, opt); // Only set options that haven't set from a source with higher // precedence. This means that option precedence doesn't have to rely // on the order in which options are set. This means that e.g. // command-line set options can be set before the main meson.build file // has even been parsed. // // This mostly follows meson's behavior, except that deprecated options // cannot override command line options. /* { */ /* const char *sourcenames[] = { */ /* [option_value_source_unset] = "unset", */ /* [option_value_source_default] = "default", */ /* [option_value_source_environment] = "environment", */ /* [option_value_source_default_options] = "default_options", */ /* [option_value_source_subproject_default_options] = "subproject_default_options", */ /* [option_value_source_yield] = "yield", */ /* [option_value_source_commandline] = "commandline", */ /* [option_value_source_deprecated_rename] = "deprecated rename", */ /* [option_value_source_override_options] = "override_options", */ /* }; */ /* obj_fprintf(wk, log_file(), */ /* "%s option %o to %o from %s, last set by %s\n", */ /* o->source > source */ /* ? "\033[31mnot setting\033[0m" */ /* : "\033[32msetting\033[0m", */ /* o->name, new_val, sourcenames[source], sourcenames[o->source]); */ /* } */ if (o->source > source) { return true; } o->source = source; if (get_obj_type(wk, o->deprecated) == obj_bool && get_obj_bool(wk, o->deprecated)) { vm_warning_at(wk, node, "option %o is deprecated", o->name); } if (coerce) { obj coerced; if (!coerce_option_override(wk, o, new_val, &coerced)) { return false; } new_val = coerced; } if (!typecheck_opt(wk, node, new_val, o->type, o->name, &new_val)) { return false; } switch (o->type) { case op_combo: { if (!obj_array_in(wk, o->choices, new_val)) { vm_error_at(wk, node, "'%o' is not one of %o", new_val, o->choices); return false; } break; } case op_integer: { int64_t num = get_obj_number(wk, new_val); if ((o->max && num > get_obj_number(wk, o->max)) || (o->min && num < get_obj_number(wk, o->min))) { vm_error_at(wk, node, "value %" PRId64 " is out of range (%" PRId64 "..%" PRId64 ")", get_obj_number(wk, new_val), (o->min ? get_obj_number(wk, o->min) : INT64_MIN), (o->max ? get_obj_number(wk, o->max) : INT64_MAX)); return false; } break; } case op_array: { if (o->choices) { obj val; obj_array_for(wk, new_val, val) { if (!obj_array_in(wk, o->choices, val)) { vm_error_at(wk, node, "array element %o is not one of %o", val, o->choices); return false; } } } break; } case op_string: case op_feature: case op_boolean: break; default: UNREACHABLE_RETURN; } o->val = new_val; return true; } bool create_option(struct workspace *wk, obj opts, obj opt, obj val) { if (!set_option(wk, 0, opt, val, option_value_source_default, false)) { return false; } struct obj_option *o = get_obj_option(wk, opt); if (initializing_builtin_options) { o->builtin = true; } obj _; struct project *proj = NULL; if (wk->projects.len) { proj = current_project(wk); } const struct str *name = get_str(wk, o->name); if (str_has_null(name) || strchr(name->s, ':')) { vm_error(wk, "invalid option name %o", o->name); return false; } else if (get_option(wk, proj, name, &_)) { vm_error(wk, "duplicate option %o", o->name); return false; } obj_dict_set(wk, opts, o->name, opt); return true; } bool get_option_overridable(struct workspace *wk, const struct project *proj, obj overrides, const struct str *name, obj *res) { if (overrides && obj_dict_index_strn(wk, overrides, name->s, name->len, res)) { return true; } else if (proj && obj_dict_index_strn(wk, proj->opts, name->s, name->len, res)) { return true; } else if (obj_dict_index_strn(wk, wk->global_opts, name->s, name->len, res)) { return true; } else { return false; } } bool get_option(struct workspace *wk, const struct project *proj, const struct str *name, obj *res) { return get_option_overridable(wk, proj, 0, name, res); } void get_option_value_overridable(struct workspace *wk, const struct project *proj, obj overrides, const char *name, obj *res) { obj opt; if (!get_option_overridable(wk, proj, overrides, &WKSTR(name), &opt)) { LOG_E("attempted to get unknown option '%s'", name); UNREACHABLE; } struct obj_option *o = get_obj_option(wk, opt); *res = o->val; } void get_option_value(struct workspace *wk, const struct project *proj, const char *name, obj *res) { get_option_value_overridable(wk, proj, 0, name, res); } static void set_binary_from_env(struct workspace *wk, const char *envvar, const char *dest) { obj opt; if (!get_option(wk, NULL, &WKSTR(dest), &opt)) { UNREACHABLE; } const char *v; if (!(v = getenv(envvar)) || !*v) { return; } // TODO: implement something like shlex.split() obj cmd = str_split(wk, &WKSTR(v), NULL); set_option(wk, 0, opt, cmd, option_value_source_environment, false); } static void set_compile_opt_from_env(struct workspace *wk, const char *name, const char *flags, const char *extra) { obj opt; if (!get_option(wk, NULL, &WKSTR(name), &opt)) { UNREACHABLE; } if ((flags = getenv(flags)) && *flags) { extend_array_option(wk, opt, str_split(wk, &WKSTR(flags), NULL), option_value_source_environment); } if ((extra = getenv(extra)) && *extra) { extend_array_option(wk, opt, str_split(wk, &WKSTR(extra), NULL), option_value_source_environment); } } static void set_str_opt_from_env(struct workspace *wk, const char *env_name, const char *opt_name) { obj opt; if (!get_option(wk, NULL, &WKSTR(opt_name), &opt)) { UNREACHABLE; } const char *env_val; if ((env_val = getenv(env_name)) && *env_val) { set_option(wk, 0, opt, make_str(wk, env_val), option_value_source_environment, false); } } static bool init_builtin_options(struct workspace *wk, const char *script, const char *fallback) { const char *opts; if (!(opts = embedded_get(script))) { opts = fallback; } enum language_mode old_mode = wk->vm.lang_mode; wk->vm.lang_mode = language_opts; obj _; initializing_builtin_options = true; bool ret = eval_str_label(wk, script, opts, eval_mode_default, &_); initializing_builtin_options = false; wk->vm.lang_mode = old_mode; return ret; } static bool init_per_project_options(struct workspace *wk) { return init_builtin_options(wk, "per_project_options.meson", "option('default_library', type: 'string', value: 'static')\n" "option('warning_level', type: 'string', value: '3')\n" "option('c_std', type: 'string', value: 'c99')\n"); } static enum iteration_result set_yielding_project_options_iter(struct workspace *wk, void *_ctx, obj _k, obj opt) { struct project *parent_proj = _ctx; struct obj_option *o = get_obj_option(wk, opt), *po; if (!o->yield) { return ir_cont; } obj parent_opt; if (!get_option(wk, parent_proj, get_str(wk, o->name), &parent_opt)) { return ir_cont; } po = get_obj_option(wk, parent_opt); if (po->type != o->type) { vm_warning_at(wk, 0, "option %o cannot yield to parent option due to a type mismatch (%s != %s)", o->name, build_option_type_to_s[po->type], build_option_type_to_s[o->type]); return ir_cont; } if (!set_option(wk, 0, opt, po->val, option_value_source_yield, false)) { return ir_err; } return ir_cont; } bool setup_project_options(struct workspace *wk, const char *cwd) { if (!init_per_project_options(wk)) { return false; } if (!cwd) { return true; } const char *option_file_names[] = { "meson.options", "meson_options.txt", }; SBUF(meson_opts); bool exists = false; uint32_t i; for (i = 0; i < ARRAY_LEN(option_file_names); ++i) { path_join(wk, &meson_opts, cwd, option_file_names[i]); if (fs_file_exists(meson_opts.buf)) { exists = true; break; } } if (exists) { enum language_mode old_mode = wk->vm.lang_mode; wk->vm.lang_mode = language_opts; if (!wk->vm.behavior.eval_project_file(wk, meson_opts.buf, false)) { wk->vm.lang_mode = old_mode; return false; } wk->vm.lang_mode = old_mode; } bool is_master_project = wk->cur_project == 0; if (!is_master_project) { if (!obj_dict_foreach(wk, current_project(wk)->opts, arr_get(&wk->projects, 0), set_yielding_project_options_iter)) { return false; } } bool ret = true; struct option_override *oo; for (i = 0; i < wk->option_overrides.len; ++i) { oo = arr_get(&wk->option_overrides, i); if (!subproj_name_matches( wk, get_cstr(wk, current_project(wk)->subproject_name), get_cstr(wk, oo->proj))) { continue; } const struct str *name = get_str(wk, oo->name); obj opt; if (obj_dict_index_strn(wk, current_project(wk)->opts, name->s, name->len, &opt) || (is_master_project && obj_dict_index_strn(wk, wk->global_opts, name->s, name->len, &opt))) { if (!set_option(wk, 0, opt, oo->val, oo->source, !oo->obj_value)) { ret = false; } } else { LLOG_E("invalid option: "); log_option_override(wk, oo); log_plain("\n"); ret = false; } } return ret; } bool init_global_options(struct workspace *wk) { if (!init_builtin_options(wk, "global_options.meson", "option('buildtype', type: 'string', value: 'debugoptimized')\n" "option('prefix', type: 'string', value: '/usr/local')\n" "option('bindir', type: 'string', value: 'bin')\n" "option('mandir', type: 'string', value: 'share/man')\n" "option('datadir', type: 'string', value: 'share')\n" "option('libdir', type: 'string', value: 'lib')\n" "option('includedir', type: 'string', value: 'include')\n" "option('wrap_mode', type: 'string', value: 'nopromote')\n" "option('force_fallback_for', type: 'array', value: [])\n" "option('pkg_config_path', type: 'string', value: '')\n" "option('c_args', type: 'array', value: [])\n" "option('c_link_args', type: 'array', value: [])\n" "option('werror', type: 'boolean', value: false)\n" "option('prefer_static', type: 'boolean', value: false)\n" "option('env.CC', type: 'array', value: ['cc'])\n" "option('env.NINJA', type: 'array', value: ['ninja'])\n" "option('env.AR', type: 'array', value: ['ar'])\n" "option('env.LD', type: 'array', value: ['ld'])\n")) { return false; } set_binary_from_env(wk, "CC", "env.CC"); set_binary_from_env(wk, "NINJA", "env.NINJA"); set_binary_from_env(wk, "AR", "env.AR"); set_binary_from_env(wk, "LD", "env.LD"); set_compile_opt_from_env(wk, "c_args", "CFLAGS", "CPPFLAGS"); set_compile_opt_from_env(wk, "c_link_args", "CFLAGS", "LDFLAGS"); #ifdef MUON_BOOTSTRAPPED set_binary_from_env(wk, "CXX", "env.CXX"); set_binary_from_env(wk, "OBJC", "env.OBJC"); set_binary_from_env(wk, "OBJCPP", "env.OBJCPP"); set_binary_from_env(wk, "NASM", "env.NASM"); set_compile_opt_from_env(wk, "cpp_args", "CXXFLAGS", "CPPFLAGS"); set_compile_opt_from_env(wk, "cpp_link_args", "CXXFLAGS", "LDFLAGS"); set_str_opt_from_env(wk, "PKG_CONFIG_PATH", "pkg_config_path"); #endif return true; } bool parse_and_set_cmdline_option(struct workspace *wk, char *lhs) { struct option_override oo = { .source = option_value_source_commandline }; if (!parse_config_string(wk, &WKSTR(lhs), &oo, false)) { return false; } arr_push(&wk->option_overrides, &oo); return true; } enum parse_and_set_option_flag { parse_and_set_option_flag_override = 1 << 0, parse_and_set_option_flag_for_subproject = 1 << 1, parse_and_set_option_flag_have_value = 1 << 2, }; struct parse_and_set_option_params { uint32_t node; obj project_name; obj opt; obj value; obj opts; enum parse_and_set_option_flag flags; }; static bool parse_and_set_option(struct workspace *wk, const struct parse_and_set_option_params *params) { struct option_override oo = { 0 }; if (params->flags & parse_and_set_option_flag_override) { oo.source = option_value_source_override_options; } else { oo.source = option_value_source_default_options; } bool key_only = false; if (params->flags & parse_and_set_option_flag_have_value) { key_only = true; oo.val = params->value; oo.obj_value = true; } if (!parse_config_string(wk, get_str(wk, params->opt), &oo, key_only)) { vm_error_at(wk, params->node, "invalid option string"); return false; } if (params->flags & parse_and_set_option_flag_override) { if (oo.proj) { vm_error_at(wk, params->node, "subproject options may not be set in override_options"); return false; } } else { bool oo_for_subproject = true; if (!oo.proj) { oo_for_subproject = false; oo.proj = params->project_name; } if ((params->flags & parse_and_set_option_flag_for_subproject) || oo_for_subproject) { oo.source = option_value_source_subproject_default_options; arr_push(&wk->option_overrides, &oo); return true; } } obj opt; if (!get_option(wk, current_project(wk), get_str(wk, oo.name), &opt)) { vm_error_at(wk, params->node, "invalid option %o", oo.name); return false; } if (params->flags & parse_and_set_option_flag_override) { obj newopt; make_obj(wk, &newopt, obj_option); struct obj_option *o = get_obj_option(wk, newopt); *o = *get_obj_option(wk, opt); opt = newopt; } if (!set_option(wk, params->node, opt, oo.val, oo.source, !oo.obj_value)) { return false; } if (params->flags & parse_and_set_option_flag_override) { if (obj_dict_in(wk, params->opts, oo.name)) { vm_error_at(wk, params->node, "duplicate option %o in override_options", oo.name); return false; } obj_dict_set(wk, params->opts, oo.name, opt); } return true; } static bool parse_and_set_options(struct workspace *wk, struct parse_and_set_option_params *params, obj opts) { enum obj_type t = get_obj_type(wk, opts); switch (t) { case obj_dict: { params->flags |= parse_and_set_option_flag_have_value; obj k, v; obj_dict_for(wk, opts, k, v) { params->opt = k; params->value = v; if (!parse_and_set_option(wk, params)) { return false; } } break; } case obj_string: params->opt = opts; if (!parse_and_set_option(wk, params)) { return false; } break; case obj_array: { obj v; obj_array_for(wk, opts, v) { params->opt = v; if (!parse_and_set_option(wk, params)) { return false; } } break; } default: UNREACHABLE; } return true; } bool parse_and_set_default_options(struct workspace *wk, uint32_t err_node, obj opts, obj project_name, bool for_subproject) { struct parse_and_set_option_params params = { .node = err_node, .project_name = project_name, }; if (for_subproject) { params.flags |= parse_and_set_option_flag_for_subproject; } return parse_and_set_options(wk, ¶ms, opts); } bool parse_and_set_override_options(struct workspace *wk, uint32_t err_node, obj opts, obj *res) { struct parse_and_set_option_params params = { .node = err_node, .flags = parse_and_set_option_flag_override, }; make_obj(wk, ¶ms.opts, obj_dict); *res = params.opts; return parse_and_set_options(wk, ¶ms, opts); } /* helper functions */ enum wrap_mode get_option_wrap_mode(struct workspace *wk) { obj opt; get_option_value(wk, current_project(wk), "wrap_mode", &opt); const char *s = get_cstr(wk, opt); const char *names[] = { [wrap_mode_nopromote] = "nopromote", [wrap_mode_nodownload] = "nodownload", [wrap_mode_nofallback] = "nofallback", [wrap_mode_forcefallback] = "forcefallback", NULL, }; uint32_t i; for (i = 0; names[i]; ++i) { if (strcmp(names[i], s) == 0) { return i; } } UNREACHABLE_RETURN; } enum tgt_type get_option_default_library(struct workspace *wk) { obj opt; get_option_value(wk, current_project(wk), "default_library", &opt); if (str_eql(get_str(wk, opt), &WKSTR("static"))) { return tgt_static_library; } else if (str_eql(get_str(wk, opt), &WKSTR("shared"))) { return tgt_dynamic_library; } else if (str_eql(get_str(wk, opt), &WKSTR("both"))) { return tgt_dynamic_library | tgt_static_library; } else { UNREACHABLE_RETURN; } } bool get_option_bool(struct workspace *wk, obj overrides, const char *name, bool fallback) { obj opt; if (get_option_overridable(wk, current_project(wk), overrides, &WKSTR(name), &opt)) { return get_obj_bool(wk, get_obj_option(wk, opt)->val); } else { return fallback; } } /* options listing subcommand */ struct make_option_choices_ctx { obj selected; const char *val_clr, *sel_clr, *no_clr; uint32_t i, len; obj res; }; static enum iteration_result make_option_choices_iter(struct workspace *wk, void *_ctx, obj val) { struct make_option_choices_ctx *ctx = _ctx; const struct str *ss = get_str(wk, val); const char *clr = ctx->val_clr; if (ctx->selected && obj_array_in(wk, ctx->selected, val)) { clr = ctx->sel_clr; } str_app(wk, &ctx->res, clr); str_appn(wk, &ctx->res, ss->s, ss->len); str_app(wk, &ctx->res, ctx->no_clr); if (ctx->i < ctx->len - 1) { str_app(wk, &ctx->res, "|"); } ++ctx->i; return ir_cont; } struct list_options_ctx { bool show_builtin; const struct list_options_opts *list_opts; }; static enum iteration_result list_options_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct list_options_ctx *ctx = _ctx; struct obj_option *opt = get_obj_option(wk, val); if (opt->builtin != ctx->show_builtin) { return ir_cont; } else if (ctx->list_opts->only_modified && opt->source == option_value_source_default) { return ir_cont; } /* const char *option_type_names[] = { */ /* [op_string] = "string", */ /* [op_boolean] = "boolean", */ /* [op_combo] = "combo", */ /* [op_integer] = "integer", */ /* [op_array] = "array", */ /* [op_feature] = "feature", */ /* }; */ const char *key_clr = "", *val_clr = "", *sel_clr = "", *no_clr = ""; if (fs_is_a_tty(stdout)) { key_clr = "\033[1;34m"; val_clr = "\033[1;37m"; sel_clr = "\033[1;32m"; no_clr = "\033[0m"; } obj_fprintf(wk, log_file(), " %s%#o%s=", key_clr, key, no_clr); obj choices = 0; obj selected = 0; if (opt->type == op_combo) { choices = opt->choices; make_obj(wk, &selected, obj_array); obj_array_push(wk, selected, opt->val); } else if (opt->type == op_array && opt->choices) { choices = opt->choices; selected = opt->val; } else { make_obj(wk, &choices, obj_array); switch (opt->type) { case op_string: obj_array_push(wk, choices, make_str(wk, "string")); break; case op_boolean: obj_array_push(wk, choices, make_str(wk, "true")); obj_array_push(wk, choices, make_str(wk, "false")); make_obj(wk, &selected, obj_array); obj_array_push(wk, selected, make_str(wk, get_obj_bool(wk, opt->val) ? "true" : "false")); break; case op_feature: obj_array_push(wk, choices, make_str(wk, "enabled")); obj_array_push(wk, choices, make_str(wk, "disabled")); obj_array_push(wk, choices, make_str(wk, "auto")); make_obj(wk, &selected, obj_array); obj_array_push(wk, selected, make_str(wk, (char *[]){ [feature_opt_enabled] = "enabled", [feature_opt_disabled] = "disabled", [feature_opt_auto] = "auto", }[get_obj_feature_opt(wk, opt->val)])); break; case op_combo: case op_array: case op_integer: break; default: UNREACHABLE; } } if (choices) { struct make_option_choices_ctx ctx = { .len = get_obj_array(wk, choices)->len, .val_clr = val_clr, .no_clr = no_clr, .sel_clr = sel_clr, .selected = selected, .res = make_str(wk, ""), }; obj_array_foreach(wk, choices, &ctx, make_option_choices_iter); choices = ctx.res; } switch (opt->type) { case op_boolean: case op_combo: case op_feature: obj_fprintf(wk, log_file(), "<%s>", get_cstr(wk, choices)); break; case op_string: { const struct str *def = get_str(wk, opt->val); obj_fprintf(wk, log_file(), "<%s>, default: %s%s%s", get_cstr(wk, choices), sel_clr, def->len ? def->s : "''", no_clr); break; } case op_integer: log_plain("<%sN%s>", val_clr, no_clr); if (opt->min || opt->max) { log_plain(" where "); if (opt->min) { obj_fprintf(wk, log_file(), "%o <= ", opt->min); } log_plain("%sN%s", val_clr, no_clr); if (opt->max) { obj_fprintf(wk, log_file(), " <= %o", opt->max); } } obj_fprintf(wk, log_file(), ", default: %s%o%s", sel_clr, opt->val, no_clr); break; case op_array: log_plain("<%svalue%s[,%svalue%s[...]]>", val_clr, no_clr, val_clr, no_clr); if (opt->choices) { obj_fprintf(wk, log_file(), " where value in %s", get_cstr(wk, choices)); } break; default: UNREACHABLE; } if (opt->source != option_value_source_default) { obj_fprintf(wk, log_file(), "*"); } if (opt->description) { obj_fprintf(wk, log_file(), "\n %#o", opt->description); } log_plain("\n"); return ir_cont; } bool list_options(const struct list_options_opts *list_opts) { bool ret = false; struct workspace wk = { 0 }; workspace_init(&wk); wk.vm.lang_mode = language_opts; arr_push(&wk.projects, &(struct project){ 0 }); struct project *proj = arr_get(&wk.projects, 0); make_obj(&wk, &proj->opts, obj_dict); if (fs_file_exists("meson.build")) { SBUF(meson_opts); path_make_absolute(&wk, &meson_opts, "meson_options.txt"); if (fs_file_exists(meson_opts.buf)) { if (!wk.vm.behavior.eval_project_file(&wk, meson_opts.buf, false)) { goto ret; } } if (list_opts->list_all) { make_obj(&wk, ¤t_project(&wk)->opts, obj_dict); if (!init_per_project_options(&wk)) { goto ret; } } } else { SBUF(option_info); path_join(&wk, &option_info, output_path.private_dir, output_path.option_info); if (!fs_file_exists(option_info.buf)) { LOG_I("run this command must be run from a build directory or the project root"); goto ret; } obj arr; if (!serial_load_from_private_dir(&wk, &arr, output_path.option_info)) { goto ret; } obj_array_index(&wk, arr, 0, &wk.global_opts); obj_array_index(&wk, arr, 1, ¤t_project(&wk)->opts); } struct list_options_ctx ctx = { .list_opts = list_opts }; if (get_obj_dict(&wk, current_project(&wk)->opts)->len) { log_plain("project options:\n"); obj_dict_foreach(&wk, current_project(&wk)->opts, &ctx, list_options_iter); } else if (!list_opts->list_all) { log_plain("no project options defined\n"); } if (list_opts->list_all) { if (get_obj_dict(&wk, current_project(&wk)->opts)->len) { log_plain("\n"); } ctx.show_builtin = true; log_plain("project builtin-options:\n"); obj_dict_foreach(&wk, current_project(&wk)->opts, &ctx, list_options_iter); log_plain("\n"); log_plain("global options:\n"); obj_dict_foreach(&wk, wk.global_opts, &ctx, list_options_iter); } ret = true; ret: workspace_destroy(&wk); return ret; } muon-v0.3.0/src/log.c0000644000175000017500000000620614674562002013364 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Simon Zeni * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "buf_size.h" #include "log.h" #include "platform/filesystem.h" #include "platform/log.h" const char *log_level_clr[log_level_count] = { [log_error] = "31", [log_warn] = "33", [log_info] = "0", [log_debug] = "36", }; const char *log_level_name[log_level_count] = { [log_error] = "error", [log_warn] = "warning", [log_info] = "info", [log_debug] = "debug", }; const char *log_level_shortname[log_level_count] = { [log_error] = "err ", [log_warn] = "warn ", [log_info] = "", [log_debug] = "dbg ", }; static struct { FILE *file; enum log_level level; uint32_t filter; bool initialized, clr; const char *prefix; } log_cfg = { .level = log_info, }; bool log_should_print(enum log_level lvl) { return lvl <= log_cfg.level; } const char * log_get_prefix(void) { return log_cfg.prefix; } void log_set_prefix(const char *prefix) { log_cfg.prefix = prefix; } uint32_t log_print_prefix(enum log_level lvl, char *buf, uint32_t size) { uint32_t len = 0; assert(log_cfg.initialized); if (log_cfg.prefix) { len += snprintf(&buf[len], size - len, "%s ", log_cfg.prefix); } if (*log_level_shortname[lvl]) { if (log_cfg.clr) { len += snprintf(&buf[len], BUF_SIZE_4k - len, "\033[%sm%s\033[0m", log_level_clr[lvl], log_level_shortname[lvl]); } else { len = strlen(log_level_shortname[lvl]); strncpy(buf, log_level_shortname[lvl], BUF_SIZE_4k); } } return len; } void log_print(bool nl, enum log_level lvl, const char *fmt, ...) { static char buf[BUF_SIZE_4k + 3]; if (log_should_print(lvl)) { uint32_t len = log_print_prefix(lvl, buf, BUF_SIZE_4k); assert(log_cfg.initialized); va_list ap; va_start(ap, fmt); len += vsnprintf(&buf[len], BUF_SIZE_4k - len, fmt, ap); va_end(ap); if (nl && len < BUF_SIZE_4k) { buf[len] = '\n'; buf[len + 1] = 0; } if (log_cfg.clr) { print_colorized(log_cfg.file, buf); } else { fputs(buf, log_cfg.file); } } } void log_plainv(const char *fmt, va_list ap) { static char buf[BUF_SIZE_32k]; if (log_cfg.clr) { vsnprintf(buf, ARRAY_LEN(buf) - 1, fmt, ap); print_colorized(log_cfg.file, buf); } else { vfprintf(log_cfg.file, fmt, ap); } } void log_plain(const char *fmt, ...) { va_list ap; va_start(ap, fmt); log_plainv(fmt, ap); va_end(ap); } bool log_clr(void) { return log_cfg.clr; } void log_init(void) { char *sll; uint64_t ll; assert(!log_cfg.initialized); log_cfg.file = stderr; log_cfg.initialized = true; if ((sll = getenv("MUON_LOG_LVL"))) { ll = strtoul(sll, NULL, 10); log_set_lvl(ll); } log_cfg.clr = fs_is_a_tty(log_cfg.file); } void log_set_file(FILE *log_file) { log_cfg.file = log_file; log_cfg.clr = fs_is_a_tty(log_file); } void log_set_lvl(enum log_level lvl) { if (lvl > log_level_count) { L("attempted to set log level to invalid value %d (max: %d)", lvl, log_level_count); return; } log_cfg.level = lvl; } FILE * log_file(void) { return log_cfg.file; } muon-v0.3.0/src/meson.build0000644000175000017500000000526714674562002014607 0ustar buildbuild# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only src = files( 'backend/backend.c', 'backend/common_args.c', 'backend/ninja.c', 'backend/ninja/alias_target.c', 'backend/ninja/build_target.c', 'backend/ninja/custom_target.c', 'backend/ninja/rules.c', 'backend/output.c', 'datastructures/arr.c', 'datastructures/bucket_arr.c', 'datastructures/hash.c', 'datastructures/stack.c', 'formats/editorconfig.c', 'formats/ini.c', 'formats/lines.c', 'formats/tap.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/fs.c', 'functions/modules/keyval.c', 'functions/modules/pkgconfig.c', 'functions/modules/python.c', 'functions/modules/sourceset.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/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_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', '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 muon-v0.3.0/src/coerce.c0000644000175000017500000003625114674562002014046 0ustar buildbuild/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "coerce.h" #include "functions/environment.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" bool coerce_environment_from_kwarg(struct workspace *wk, struct args_kw *kw, bool set_subdir, obj *res) { if (kw->set) { if (get_obj_type(wk, kw->val) == obj_environment) { *res = kw->val; } else if (!coerce_key_value_dict(wk, kw->node, kw->val, res)) { return false; } } else { make_obj(wk, res, obj_dict); } set_default_environment_vars(wk, *res, set_subdir); return true; } struct coerce_environment_ctx { uint32_t err_node; obj res; }; static enum iteration_result coerce_environment_iter(struct workspace *wk, void *_ctx, obj val) { struct coerce_environment_ctx *ctx = _ctx; if (!typecheck(wk, ctx->err_node, val, obj_string)) { return false; } const struct str *ss = get_str(wk, val); if (str_has_null(ss)) { vm_error_at(wk, ctx->err_node, "environment string %o must not contain NUL", val); return ir_err; } const char *eql; if (!(eql = strchr(ss->s, '='))) { vm_error_at( wk, ctx->err_node, "invalid env element %o; env elements must be of the format key=value", val); return ir_err; } uint32_t key_len = eql - ss->s; obj key = make_strn(wk, ss->s, key_len); val = make_strn(wk, ss->s + key_len + 1, ss->len - (key_len + 1)); obj_dict_set(wk, ctx->res, key, val); return ir_cont; } static enum iteration_result typecheck_environment_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { uint32_t err_node = *(uint32_t *)_ctx; const struct str *k = get_str(wk, key), *v = get_str(wk, val); if (!k->len) { vm_error_at(wk, err_node, "environment key may not be an empty string (value is '%s')", v->s); return ir_err; } else if (str_has_null(k)) { vm_error_at(wk, err_node, "environment key may not contain NUL"); return ir_err; } else if (str_has_null(v)) { vm_error_at(wk, err_node, "environment value may not contain NUL"); return ir_err; } else if (strchr(k->s, '=')) { vm_error_at(wk, err_node, "environment key '%s' contains '='", k->s); return ir_err; } return ir_cont; } bool coerce_key_value_dict(struct workspace *wk, uint32_t err_node, obj val, obj *res) { make_obj(wk, res, obj_dict); struct coerce_environment_ctx ctx = { .err_node = err_node, .res = *res, }; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_string: return coerce_environment_iter(wk, &ctx, val) != ir_err; case obj_array: return obj_array_foreach_flat(wk, val, &ctx, coerce_environment_iter); case obj_dict: if (!typecheck(wk, err_node, val, make_complex_type(wk, complex_type_nested, tc_dict, tc_string))) { return false; } *res = val; break; default: vm_error_at(wk, err_node, "unable to coerce type '%s' into key=value dict", obj_type_to_s(t)); return false; } if (!obj_dict_foreach(wk, *res, &err_node, typecheck_environment_dict_iter)) { return false; } return true; } bool coerce_include_type(struct workspace *wk, const struct str *str, uint32_t err_node, enum include_type *res) { static const char *include_type_strs[] = { [include_type_preserve] = "preserve", [include_type_system] = "system", [include_type_non_system] = "non-system", 0, }; uint32_t i; for (i = 0; include_type_strs[i]; ++i) { if (str_eql(str, &WKSTR(include_type_strs[i]))) { *res = i; return true; } } vm_error_at(wk, err_node, "invalid value for include_type: %s", str->s); return false; } bool coerce_num_to_string(struct workspace *wk, uint32_t node, obj val, obj *res) { switch (get_obj_type(wk, val)) { case obj_number: { *res = make_strf(wk, "%" PRId64, get_obj_number(wk, val)); break; } case obj_string: { *res = val; break; } default: vm_error_at(wk, node, "unable to coerce %o to string", val); return false; } return true; } bool coerce_string(struct workspace *wk, uint32_t node, obj val, obj *res) { switch (get_obj_type(wk, val)) { case obj_bool: if (get_obj_bool(wk, val)) { *res = make_str(wk, "true"); } else { *res = make_str(wk, "false"); } break; case obj_file: *res = *get_obj_file(wk, val); break; case obj_number: { *res = make_strf(wk, "%" PRId64, get_obj_number(wk, val)); break; } case obj_string: { *res = val; break; } case obj_feature_opt: { const char *s = 0; switch (get_obj_feature_opt(wk, val)) { case feature_opt_auto: s = "auto"; break; case feature_opt_enabled: s = "enabled"; break; case feature_opt_disabled: s = "disabled"; break; } *res = make_strf(wk, "