From 3f0b9591d84b50fe4a55d11add999cdfce231e14 Mon Sep 17 00:00:00 2001 From: jotrorox Date: Wed, 29 Apr 2026 21:53:45 +0200 Subject: [PATCH 1/7] Implementing a preset option for targets in libraries --- resources/project_schema.json | 4 + src/build/libraries.c | 1 + src/build/project.c | 159 ++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) diff --git a/resources/project_schema.json b/resources/project_schema.json index f2a26e68c4..9fb7adf49c 100644 --- a/resources/project_schema.json +++ b/resources/project_schema.json @@ -77,6 +77,10 @@ "description": "Executable or library type for the target.", "enum": ["executable", "static-lib", "dynamic-lib"] }, + "preset": { + "type": "string", + "description": "Reference a preset configuration from a library dependency, format: 'library:preset'." + }, "dependencies": { "type": "array", "description": "Additional libraries for this target.", diff --git a/src/build/libraries.c b/src/build/libraries.c index 537d03e55d..c5dbafe4f2 100644 --- a/src/build/libraries.c +++ b/src/build/libraries.c @@ -12,6 +12,7 @@ const char *manifest_default_keys[][2] = { {"dependencies", "List of C3 libraries to also include."}, {"exec", "Scripts run for all platforms."}, {"linklib-dir", "Set the directory where to find linked libraries."}, + {"presets", "Predefined target configurations for users of this library."}, {"provides", "The library name"}, {"targets", "The map of supported platforms"}, {"vendor", "Vendor specific extensions, ignored by c3c."}, diff --git a/src/build/project.c b/src/build/project.c index 73d4062c08..a2f5b4fc51 100644 --- a/src/build/project.c +++ b/src/build/project.c @@ -138,6 +138,7 @@ const char* project_target_keys[][2] = { {"output", "Output location, relative to project file."}, {"panic-msg", "Turn panic message output on or off."}, {"panicfn", "Override the panic function."}, + {"preset", "Use a preset configuration from a library, format 'library:preset'."}, {"quiet", "Silence unnecessary output."}, {"reloc", "Relocation model: none, pic, PIC, pie, PIE."}, {"riscv-abi", "RiscV ABI: int-only, float, double."}, @@ -588,6 +589,155 @@ static void duplicate_prop(const char ***prop_ref) } *prop_ref = copy; } + +static void parse_preset_ref(const char *ref, const char **lib_name, const char **preset_name) +{ + const char *colon = strchr(ref, ':'); + if (!colon || colon == ref || colon[1] == '\0') + { + error_exit("Error reading project: invalid preset reference '%s', expected 'library:preset'.", ref); + } + *lib_name = str_copy(ref, colon - ref); + *preset_name = str_copy(colon + 1, strlen(colon + 1)); +} + +static JSONObject* read_manifest_from_path(const char *lib_path, const char **manifest_path_ref) +{ + const char *manifest_path = file_append_path(lib_path, "manifest.json"); + if (file_exists(manifest_path)) + { + size_t size; + char *data = file_read_all(manifest_path, &size); + if (data) + { + JsonParser parser; + json_init_string(&parser, data); + JSONObject *json = json_parse(&parser); + if (json && !parser.error_message) + { + *manifest_path_ref = manifest_path; + return json; + } + } + } + return NULL; +} + +static JSONObject* read_manifest_from_zip(const char *lib_path, const char **manifest_path_ref) +{ + FILE *f = fopen(lib_path, "rb"); + if (!f) return NULL; + + ZipDirIterator iterator; + if (zip_dir_iterator(f, &iterator)) + { + fclose(f); + return NULL; + } + + ZipFile file; + bool found = false; + while (iterator.current_file < iterator.files) + { + if (zip_dir_iterator_next(&iterator, &file)) break; + if (strcmp(file.name, "manifest.json") == 0) + { + found = true; + break; + } + } + + if (!found) + { + fclose(f); + return NULL; + } + + char *manifest_data; + if (zip_file_read(f, &file, (void**)&manifest_data)) + { + fclose(f); + return NULL; + } + + JsonParser parser; + json_init_string(&parser, manifest_data); + JSONObject *json = json_parse(&parser); + fclose(f); + + if (json && !parser.error_message) + { + *manifest_path_ref = lib_path; + return json; + } + return NULL; +} + +static JSONObject* resolve_preset(BuildTarget *target, const char *preset_ref, const char **manifest_path_ref) +{ + const char *lib_name; + const char *preset_name; + parse_preset_ref(preset_ref, &lib_name, &preset_name); + + bool found_dep = false; + FOREACH(const char *, dep, target->libs) + { + if (str_eq(dep, lib_name)) + { + found_dep = true; + break; + } + } + if (!found_dep) + { + error_exit("Error reading project: preset '%s' references library '%s' which is not listed in 'dependencies'.", preset_ref, lib_name); + } + + JSONObject *manifest = NULL; + FOREACH(const char *, dir, target->libdirs) + { + scratch_buffer_clear(); + scratch_buffer_printf("%s/%s.c3l", dir, lib_name); + const char *lib_path = scratch_buffer_copy(); + + manifest = read_manifest_from_path(lib_path, manifest_path_ref); + if (manifest) break; + + manifest = read_manifest_from_zip(lib_path, manifest_path_ref); + if (manifest) break; + } + + if (!manifest) + { + error_exit("Error reading project: could not find manifest for library '%s' needed by preset '%s'.", lib_name, preset_ref); + } + + BuildParseContext manifest_context = { *manifest_path_ref, NULL }; + const char *provides = get_optional_string(manifest_context, manifest, "provides"); + if (!provides || !str_eq(provides, lib_name)) + { + error_exit("Error reading %s: library manifest 'provides' ('%s') does not match preset library name '%s'.", *manifest_path_ref, provides ? provides : "(missing)", lib_name); + } + + JSONObject *presets = json_map_get(manifest, "presets"); + if (!presets || presets->type != J_OBJECT) + { + error_exit("Error reading %s: library '%s' does not define any presets.", *manifest_path_ref, lib_name); + } + + JSONObject *preset = json_map_get(presets, preset_name); + if (!preset) + { + error_exit("Error reading %s: preset '%s' not found in library '%s'.", *manifest_path_ref, preset_name, lib_name); + } + if (preset->type != J_OBJECT) + { + error_exit("Error reading %s: preset '%s' in library '%s' is not a JSON object.", *manifest_path_ref, preset_name, lib_name); + } + + return preset; +} + static void project_add_target(BuildParseContext context, Project *project, BuildTarget *default_target, JSONObject *json, const char *type, TargetType target_type) { @@ -609,6 +759,15 @@ static void project_add_target(BuildParseContext context, Project *project, Buil duplicate_prop(&target->linker_libs); duplicate_prop(&target->link_args); + const char *preset_ref = get_optional_string(context, json, "preset"); + if (preset_ref) + { + const char *manifest_path = NULL; + JSONObject *preset_json = resolve_preset(target, preset_ref, &manifest_path); + BuildParseContext preset_context = { manifest_path, str_printf("preset '%s'", preset_ref) }; + load_into_build_target(preset_context, preset_json, target); + } + vec_add(project->targets, target); target->name = context.target; target->type = target_type; From d3768c3873fc1a11aec29c70c2d2ee3e5baf8947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20=28jotrorox=29=20M=C3=BCller?= Date: Sun, 10 May 2026 19:12:00 +0200 Subject: [PATCH 2/7] Improving the target preset support and fix the overriding --- src/build/build_internal.h | 1 + src/build/libraries.c | 43 ++++++++++++- src/build/project.c | 127 +++++++++++-------------------------- 3 files changed, 79 insertions(+), 92 deletions(-) diff --git a/src/build/build_internal.h b/src/build/build_internal.h index 0f1ca4c261..709a55023b 100644 --- a/src/build/build_internal.h +++ b/src/build/build_internal.h @@ -165,6 +165,7 @@ static const char *sanitize_modes[4] = { JSONObject *project_json_load(const char **filename_ref); Project *project_load(const char **filename_ref); BuildTarget *project_select_target(const char *filename, Project *project, const char *optional_target); +JSONObject *read_library_manifest_for_path(const char *lib_path, const char **manifest_path_ref); const char *get_string(BuildParseContext context, JSONObject *table, const char *key, const char *default_value); int get_valid_bool(BuildParseContext context, JSONObject *json, const char *key, int default_val); diff --git a/src/build/libraries.c b/src/build/libraries.c index c5dbafe4f2..5bccbd12df 100644 --- a/src/build/libraries.c +++ b/src/build/libraries.c @@ -180,7 +180,7 @@ INLINE JSONObject* read_manifest(const char *lib, const char *manifest_data) JSONObject *json = json_parse(&parser); if (parser.error_message) { - error_exit("Error on line %d reading '%s':'%s'", parser.line, lib, parser.error_message); + error_exit("Error on line %d reading '%s':%s", parser.line, lib, parser.error_message); } if (!json) { @@ -189,6 +189,47 @@ INLINE JSONObject* read_manifest(const char *lib, const char *manifest_data) return json; } +JSONObject *read_library_manifest_for_path(const char *lib_path, const char **manifest_path_ref) +{ + if (file_is_dir(lib_path)) + { + const char *manifest_path = file_append_path(lib_path, MANIFEST_FILE); + if (!file_exists(manifest_path)) return NULL; + size_t size; + char *manifest_data = file_read_all(manifest_path, &size); + *manifest_path_ref = manifest_path; + return read_manifest(manifest_path, manifest_data); + } + if (!file_exists(lib_path)) return NULL; + + FILE *f = fopen(lib_path, "rb"); + if (!f) return NULL; + + ZipDirIterator iterator; + const char *zip_error = zip_dir_iterator(f, &iterator); + if (zip_error) + { + fclose(f); + zip_check_err(lib_path, zip_error); + } + + ZipFile file; + while (iterator.current_file < iterator.files) + { + zip_check_err(lib_path, zip_dir_iterator_next(&iterator, &file)); + if (strcmp(file.name, MANIFEST_FILE) == 0) + { + char *manifest_data; + zip_check_err(lib_path, zip_file_read(f, &file, (void**)&manifest_data)); + fclose(f); + *manifest_path_ref = lib_path; + return read_manifest(lib_path, manifest_data); + } + } + fclose(f); + return NULL; +} + static inline JSONObject *resolve_zip_library(BuildTarget *build_target, const char *lib, const char **resulting_library) { FILE *f = fopen(lib, "rb"); diff --git a/src/build/project.c b/src/build/project.c index a2f5b4fc51..e8b25ae26a 100644 --- a/src/build/project.c +++ b/src/build/project.c @@ -601,78 +601,6 @@ static void parse_preset_ref(const char *ref, const char **lib_name, const char *preset_name = str_copy(colon + 1, strlen(colon + 1)); } -static JSONObject* read_manifest_from_path(const char *lib_path, const char **manifest_path_ref) -{ - const char *manifest_path = file_append_path(lib_path, "manifest.json"); - if (file_exists(manifest_path)) - { - size_t size; - char *data = file_read_all(manifest_path, &size); - if (data) - { - JsonParser parser; - json_init_string(&parser, data); - JSONObject *json = json_parse(&parser); - if (json && !parser.error_message) - { - *manifest_path_ref = manifest_path; - return json; - } - } - } - return NULL; -} - -static JSONObject* read_manifest_from_zip(const char *lib_path, const char **manifest_path_ref) -{ - FILE *f = fopen(lib_path, "rb"); - if (!f) return NULL; - - ZipDirIterator iterator; - if (zip_dir_iterator(f, &iterator)) - { - fclose(f); - return NULL; - } - - ZipFile file; - bool found = false; - while (iterator.current_file < iterator.files) - { - if (zip_dir_iterator_next(&iterator, &file)) break; - if (strcmp(file.name, "manifest.json") == 0) - { - found = true; - break; - } - } - - if (!found) - { - fclose(f); - return NULL; - } - - char *manifest_data; - if (zip_file_read(f, &file, (void**)&manifest_data)) - { - fclose(f); - return NULL; - } - - JsonParser parser; - json_init_string(&parser, manifest_data); - JSONObject *json = json_parse(&parser); - fclose(f); - - if (json && !parser.error_message) - { - *manifest_path_ref = lib_path; - return json; - } - return NULL; -} - static JSONObject* resolve_preset(BuildTarget *target, const char *preset_ref, const char **manifest_path_ref) { const char *lib_name; @@ -694,29 +622,38 @@ static JSONObject* resolve_preset(BuildTarget *target, const char *preset_ref, c } JSONObject *manifest = NULL; - FOREACH(const char *, dir, target->libdirs) + static const char *c3lib_suffix = ".c3l"; + const char **c3_libs = NULL; + if (vec_size(target->libdirs)) { - scratch_buffer_clear(); - scratch_buffer_printf("%s/%s.c3l", dir, lib_name); - const char *lib_path = scratch_buffer_copy(); - - manifest = read_manifest_from_path(lib_path, manifest_path_ref); - if (manifest) break; - - manifest = read_manifest_from_zip(lib_path, manifest_path_ref); - if (manifest) break; + FOREACH(const char *, dir, target->libdirs) + { + file_add_wildcard_files(&c3_libs, dir, false, &c3lib_suffix, 1); + } + } + else + { + file_add_wildcard_files(&c3_libs, ".", false, &c3lib_suffix, 1); } - if (!manifest) + FOREACH(const char *, lib_path, c3_libs) { - error_exit("Error reading project: could not find manifest for library '%s' needed by preset '%s'.", lib_name, preset_ref); + const char *manifest_path = NULL; + JSONObject *candidate = read_library_manifest_for_path(lib_path, &manifest_path); + if (!candidate) continue; + BuildParseContext manifest_context = { manifest_path, NULL }; + const char *provides = get_optional_string(manifest_context, candidate, "provides"); + if (provides && str_eq(provides, lib_name)) + { + manifest = candidate; + *manifest_path_ref = manifest_path; + break; + } } - BuildParseContext manifest_context = { *manifest_path_ref, NULL }; - const char *provides = get_optional_string(manifest_context, manifest, "provides"); - if (!provides || !str_eq(provides, lib_name)) + if (!manifest) { - error_exit("Error reading %s: library manifest 'provides' ('%s') does not match preset library name '%s'.", *manifest_path_ref, provides ? provides : "(missing)", lib_name); + error_exit("Error reading project: could not find manifest for library '%s' needed by preset '%s'.", lib_name, preset_ref); } JSONObject *presets = json_map_get(manifest, "presets"); @@ -759,11 +696,20 @@ static void project_add_target(BuildParseContext context, Project *project, Buil duplicate_prop(&target->linker_libs); duplicate_prop(&target->link_args); - const char *preset_ref = get_optional_string(context, json, "preset"); + BuildParseContext target_context = { context.file, str_printf("%s %s", type, context.target) }; + const char *preset_ref = get_optional_string(target_context, json, "preset"); if (preset_ref) { + // Target-local dependencies and dependency-search-paths are needed to find the preset. + // Other target-local settings are intentionally loaded after the preset so they override it. + const char **default_libdirs = target->libdirs; + const char **default_libs = target->libs; + APPEND_STRING_LIST(&target->libdirs, "dependency-search-paths"); + APPEND_STRING_LIST(&target->libs, "dependencies"); const char *manifest_path = NULL; JSONObject *preset_json = resolve_preset(target, preset_ref, &manifest_path); + target->libdirs = default_libdirs; + target->libs = default_libs; BuildParseContext preset_context = { manifest_path, str_printf("preset '%s'", preset_ref) }; load_into_build_target(preset_context, preset_json, target); } @@ -779,8 +725,7 @@ static void project_add_target(BuildParseContext context, Project *project, Buil error_exit("More %s contained more than one target with the name %s. Please make all target names unique.", context.file, target->name); } } - context.target = str_printf("%s %s", type, context.target); - load_into_build_target(context, json, target); + load_into_build_target(target_context, json, target); } static void project_add_targets(const char *filename, Project *project, JSONObject *project_data) From ca902f9ac72dcb84df5aad002b26fff80a76b6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20=28jotrorox=29=20M=C3=BCller?= Date: Mon, 11 May 2026 10:03:20 +0200 Subject: [PATCH 3/7] Enhance project schema and manipulation to support additional target configurations and CPU options --- resources/project_schema.json | 83 +++++++++++++++++++++++++++++++- src/build/project_manipulation.c | 7 +++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/resources/project_schema.json b/resources/project_schema.json index 9fb7adf49c..88799a0df3 100644 --- a/resources/project_schema.json +++ b/resources/project_schema.json @@ -75,7 +75,16 @@ "type": { "type": "string", "description": "Executable or library type for the target.", - "enum": ["executable", "static-lib", "dynamic-lib"] + "enum": ["executable", "static-lib", "dynamic-lib", "benchmark", "test", "object-files", "prepare"] + }, + "name": { + "type": "string", + "description": "Override the output name for this target." + }, + "extension": { + "type": "string", + "description": "Override the default file extension for the build output. Must start with '.'.", + "pattern": "^\\..*" }, "preset": { "type": "string", @@ -101,6 +110,59 @@ "items": { "type": "string" } + }, + "opt": { + "type": "string", + "description": "Optimization setting.", + "enum": ["O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz"] + }, + "cpu": { + "type": "string", + "description": "CPU name, used for optimizations in the LLVM backend." + }, + "cpu-flags": { + "type": "string", + "description": "Additional CPU flags to add or remove with the format '+avx,-sse'." + }, + "target": { + "type": "string", + "description": "Compile for a particular architecture + OS target." + }, + "linker": { + "type": "string", + "description": "Use 'builtin' for the builtin linker, 'cc' for the system linker or a path to a custom compiler." + }, + "riscv-cpu": { + "type": "string", + "description": "Set general level of RISC-V CPU.", + "enum": ["rvi", "rvimac", "rvimafc", "rvgc", "rvgcv"] + }, + "riscv-abi": { + "type": "string", + "description": "RISC-V ABI.", + "enum": ["int-only", "float", "double"] + }, + "memory-env": { + "type": "string", + "description": "Memory environment.", + "enum": ["normal", "small", "tiny", "none"] + }, + "reloc": { + "type": "string", + "description": "Relocation model.", + "enum": ["none", "pic", "PIC", "pie", "PIE"] + }, + "use-stdlib": { + "type": "boolean", + "description": "Include the standard library." + }, + "link-libc": { + "type": "boolean", + "description": "Link libc other default libraries." + }, + "no-entry": { + "type": "boolean", + "description": "Do not generate or require a main function." } }, "required": ["type"] @@ -116,6 +178,20 @@ "description": "CPU name, used for optimizations in the LLVM backend.", "default": "generic" }, + "cpu-flags": { + "type": "string", + "description": "Additional CPU flags to add or remove with the format '+avx,-sse'." + }, + "riscv-cpu": { + "type": "string", + "description": "Set general level of RISC-V CPU.", + "enum": ["rvi", "rvimac", "rvimafc", "rvgc", "rvgcv"] + }, + "riscv-abi": { + "type": "string", + "description": "RISC-V ABI.", + "enum": ["int-only", "float", "double"] + }, "debug-info": { "type": "string", "description": "Debug information level.", @@ -198,6 +274,11 @@ "description": "Use the system linker.", "default": "cc" }, + "no-entry": { + "type": "boolean", + "description": "Do not generate or require a main function.", + "default": false + }, "use-stdlib": { "type": "boolean", "description": "Include the standard library.", diff --git a/src/build/project_manipulation.c b/src/build/project_manipulation.c index 4f46723764..3b335fd528 100644 --- a/src/build/project_manipulation.c +++ b/src/build/project_manipulation.c @@ -223,6 +223,8 @@ static void view_target(BuildParseContext context, JSONObject *target, bool verb TARGET_VIEW_STRING("Additional C compiler flags", "cflags"); TARGET_VIEW_STRING("C compiler flags (override)", "cflags-override"); TARGET_VIEW_STRING("CPU name", "cpu"); + TARGET_VIEW_STRING("CPU flags", "cpu-flags"); + TARGET_VIEW_STRING("CPU flags (override)", "cpu-flags-override"); TARGET_VIEW_SETTING("Debug level", "debug-info", debug_levels); TARGET_VIEW_STRING_ARRAY("Additional scripts to run", "exec", ", "); TARGET_VIEW_STRING_ARRAY("Scripts to run (override)", "exec", ", "); @@ -255,6 +257,8 @@ static void view_target(BuildParseContext context, JSONObject *target, bool verb TARGET_VIEW_BOOL("Strip unused code/globals", "strip-unused"); TARGET_VIEW_INTEGER("Preferred symtab size", "symtab"); TARGET_VIEW_STRING("Target", "target"); + TARGET_VIEW_SETTING("RISC-V CPU level", "riscv-cpu", riscv_cpu_set); + TARGET_VIEW_SETTING("RISC-V ABI", "riscv-abi", riscv_abi); TARGET_VIEW_STRING("Test function override", "testfn"); TARGET_VIEW_BOOL("Integers panic on wrapping", "trap-on-wrap"); TARGET_VIEW_BOOL("Include standard library", "use-stdlib"); @@ -551,6 +555,7 @@ void view_project(BuildOptions *build_options) VIEW_STRING("Android API version", "android-api"); VIEW_STRING("Android NDK directory", "android-ndk"); VIEW_STRING("CPU name", "cpu"); + VIEW_STRING("CPU flags", "cpu-flags"); VIEW_SETTING("Debug level", "debug-info", debug_levels); VIEW_STRING_ARRAY("Scripts to run", "exec", ", "); VIEW_STRING_ARRAY("Enabled features", "features", ", "); @@ -579,6 +584,8 @@ void view_project(BuildOptions *build_options) VIEW_BOOL("Strip unused code/globals", "strip-unused"); VIEW_INTEGER("Preferred symtab size", "symtab"); VIEW_STRING("Target", "target"); + VIEW_SETTING("RISC-V CPU level", "riscv-cpu", riscv_cpu_set); + VIEW_SETTING("RISC-V ABI", "riscv-abi", riscv_abi); VIEW_STRING("Test function override", "testfn"); VIEW_BOOL("Integers panic on wrapping", "trap-on-wrap"); VIEW_BOOL("Include standard library", "use-stdlib"); From 017eac45c82b2b4f1f35fdeccf03c37c82a59886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20=28jotrorox=29=20M=C3=BCller?= Date: Mon, 11 May 2026 12:09:39 +0200 Subject: [PATCH 4/7] Adding support for features in project presets --- resources/project_schema.json | 7 +++++++ src/build/build.h | 2 ++ src/build/libraries.c | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/resources/project_schema.json b/resources/project_schema.json index 88799a0df3..e851a99421 100644 --- a/resources/project_schema.json +++ b/resources/project_schema.json @@ -111,6 +111,13 @@ "type": "string" } }, + "features": { + "type": "array", + "description": "Feature flags enabled for this target.", + "items": { + "type": "string" + } + }, "opt": { "type": "string", "description": "Optimization setting.", diff --git a/src/build/build.h b/src/build/build.h index 9368d8eef2..bd3b387661 100644 --- a/src/build/build.h +++ b/src/build/build.h @@ -18,6 +18,7 @@ #define DEFAULT_SWITCH_JUMP_MAX_SIZE (0x3FFF) #define DEFAULT_PATH "." +typedef struct JSONObject_ JSONObject; typedef enum { @@ -358,6 +359,7 @@ typedef struct Library__ const char **csource_dirs; const char **cinclude_dirs; WinCrtLinking win_crt; + JSONObject *features; LibraryTarget *target_used; LibraryTarget **targets; } Library; diff --git a/src/build/libraries.c b/src/build/libraries.c index 5bccbd12df..67e7ae6751 100644 --- a/src/build/libraries.c +++ b/src/build/libraries.c @@ -11,6 +11,7 @@ const char *manifest_default_keys[][2] = { {"cflags", "C compiler flags."}, {"dependencies", "List of C3 libraries to also include."}, {"exec", "Scripts run for all platforms."}, + {"features", "Optional feature-specific library configuration, keyed by feature name."}, {"linklib-dir", "Set the directory where to find linked libraries."}, {"presets", "Predefined target configurations for users of this library."}, {"provides", "The library name"}, @@ -33,6 +34,7 @@ const char *manifest_target_keys[][2] = { {"cflags-override", "C compiler flags for the target, overriding global settings."}, {"dependencies", "List of C3 libraries to also include for this target."}, {"exec", "Scripts to also run for the target."}, + {"features", "Optional feature-specific library configuration, keyed by feature name."}, {"linked-libraries", "Libraries linked by the linker for this target, overriding global settings."}, {"link-args", "Linker arguments for this target."}, {"vendor", "Vendor specific extensions, ignored by c3c."}, @@ -121,6 +123,11 @@ static Library *add_library(JSONObject *json, const char *dir, const char **libs library->cc = get_optional_string(context, json, "cc"); library->cflags = get_cflags(context, json, NULL); library->win_crt = (WinCrtLinking)get_valid_string_setting(context, json, "wincrt", wincrt_linking, 0, 3, "'none', 'static' or 'dynamic'."); + library->features = json_map_get(json, "features"); + if (library->features && library->features->type != J_OBJECT) + { + error_exit("Invalid 'features' in %s, expected a map of feature names to library settings.", library->dir); + } APPEND_STRING_LIST(&library->source_dirs, "sources"); APPEND_STRING_LIST(&library->csource_dirs, "c-sources"); APPEND_STRING_LIST(&library->cinclude_dirs, "c-include-dirs"); @@ -138,6 +145,35 @@ static Library *find_library(Library **libs, size_t lib_count, const char *name) UNREACHABLE } +static bool build_target_has_feature(BuildTarget *build_target, const char *feature) +{ + FOREACH(const char *, enabled_feature, build_target->feature_list) + { + if (str_eq(enabled_feature, feature)) return true; + } + return false; +} + +static void apply_library_features(BuildTarget *build_target, Library *library, LibraryTarget *target) +{ + if (!library->features) return; + FOREACH_IDX(i, JSONObject *, feature_json, library->features->members) + { + const char *feature = library->features->keys[i]; + if (!str_is_valid_constant(feature)) + { + error_exit("Invalid feature name '%s' in %s, expected an all-uppercase constant name.", feature, library->dir); + } + if (!build_target_has_feature(build_target, feature)) continue; + if (feature_json->type != J_OBJECT) + { + error_exit("Invalid definition for feature '%s' in %s, expected a map of library settings.", feature, library->dir); + } + check_json_keys(manifest_target_keys, manifest_target_keys_count, manifest_deprecated_target_keys, manifest_deprecated_target_key_count, feature_json, feature, "--list-manifest-properties"); + parse_library_target(library, target, feature, feature_json); + } +} + static void add_library_dependency(BuildTarget *build_target, Library *library, Library **library_list, size_t lib_count) { if (library->target_used) return; @@ -155,6 +191,7 @@ static void add_library_dependency(BuildTarget *build_target, Library *library, error_exit("Library '%s' cannot be used with arch/os '%s'.", library->provides, arch_os_target[build_target->arch_os_target]); } library->target_used = target_found; + apply_library_features(build_target, library, target_found); FOREACH(const char *, dependency, library->dependencies) { add_library_dependency(build_target, find_library(library_list, lib_count, dependency), library_list, lib_count); From d34a86a0dedc702ba95fc312f478346fb2d3ac75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20=28jotrorox=29=20M=C3=BCller?= Date: Thu, 21 May 2026 23:32:33 +0200 Subject: [PATCH 5/7] Fixing two missin gcases and enforcing proper naming --- resources/project_schema.json | 3 ++- src/build/build_internal.h | 1 + src/build/common_build.c | 4 ++++ src/build/project.c | 20 +++++++++++++++----- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/resources/project_schema.json b/resources/project_schema.json index e851a99421..e8f07c697c 100644 --- a/resources/project_schema.json +++ b/resources/project_schema.json @@ -88,7 +88,8 @@ }, "preset": { "type": "string", - "description": "Reference a preset configuration from a library dependency, format: 'library:preset'." + "description": "Reference a preset configuration from a library dependency, format: 'library:preset'.", + "pattern": "^[a-z][a-z0-9_]{0,126}:[a-z][a-z0-9_]{0,126}$" }, "dependencies": { "type": "array", diff --git a/src/build/build_internal.h b/src/build/build_internal.h index 709a55023b..ac987c526e 100644 --- a/src/build/build_internal.h +++ b/src/build/build_internal.h @@ -11,6 +11,7 @@ typedef struct { const char *file; const char *target; + bool is_preset; } BuildParseContext; typedef struct diff --git a/src/build/common_build.c b/src/build/common_build.c index b049db34f1..b568c51313 100644 --- a/src/build/common_build.c +++ b/src/build/common_build.c @@ -22,6 +22,10 @@ void check_json_keys(const char* valid_keys[][2], size_t key_count, const char* goto OK; } } + if (target_name && strncmp(target_name, "preset '", strlen("preset '")) == 0) + { + error_exit("Unknown parameter '%s' in '%s'. You can use '%s' to list all valid properties.", key, target_name, option); + } WARNING("Unknown parameter '%s' in '%s'", key, target_name); failed = true; OK:; diff --git a/src/build/project.c b/src/build/project.c index e8b25ae26a..e47adb13b4 100644 --- a/src/build/project.c +++ b/src/build/project.c @@ -184,9 +184,10 @@ const int project_deprecated_target_keys_count = ELEMENTLEN(project_deprecated_t // Json -> target / default target static void load_into_build_target(BuildParseContext context, JSONObject *json, BuildTarget *target) { - if (context.target) + if (context.target || context.is_preset) { - check_json_keys(project_target_keys, project_target_keys_count, project_deprecated_target_keys, project_deprecated_target_keys_count, json, context.target, "--list-project-properties"); + check_json_keys(project_target_keys, project_target_keys_count, project_deprecated_target_keys, project_deprecated_target_keys_count, + json, context.target ? context.target : "preset", "--list-project-properties"); } else { @@ -358,7 +359,8 @@ static void load_into_build_target(BuildParseContext context, JSONObject *json, target->feature.panic_level); // Overridden name - target->output_name = get_optional_string(context, json, "name"); + const char* name = get_optional_string(context, json, "name"); + if (name) target->output_name = name; // Single module target->single_module = (SingleModule) get_valid_bool(context, json, "single-module", target->single_module); @@ -593,12 +595,20 @@ static void duplicate_prop(const char ***prop_ref) static void parse_preset_ref(const char *ref, const char **lib_name, const char **preset_name) { const char *colon = strchr(ref, ':'); - if (!colon || colon == ref || colon[1] == '\0') + if (!colon || colon == ref || colon[1] == '\0' || strchr(colon + 1, ':')) { error_exit("Error reading project: invalid preset reference '%s', expected 'library:preset'.", ref); } *lib_name = str_copy(ref, colon - ref); *preset_name = str_copy(colon + 1, strlen(colon + 1)); + if (!str_is_valid_lowercase_name(*lib_name)) + { + error_exit("Error reading project: invalid library name '%s' in preset reference '%s' – it should only contain alphanumerical letters and '_'.", *lib_name, ref); + } + if (!str_is_valid_lowercase_name(*preset_name)) + { + error_exit("Error reading project: invalid preset name '%s' in preset reference '%s' – it should only contain alphanumerical letters and '_'.", *preset_name, ref); + } } static JSONObject* resolve_preset(BuildTarget *target, const char *preset_ref, const char **manifest_path_ref) @@ -710,7 +720,7 @@ static void project_add_target(BuildParseContext context, Project *project, Buil JSONObject *preset_json = resolve_preset(target, preset_ref, &manifest_path); target->libdirs = default_libdirs; target->libs = default_libs; - BuildParseContext preset_context = { manifest_path, str_printf("preset '%s'", preset_ref) }; + BuildParseContext preset_context = { manifest_path, str_printf("preset '%s'", preset_ref), true }; load_into_build_target(preset_context, preset_json, target); } From 50c615d673b2f0ecd1ff7c285f551a6c11d730fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20=28jotrorox=29=20M=C3=BCller?= Date: Sat, 30 May 2026 16:20:32 +0200 Subject: [PATCH 6/7] Adding testing inside the testproject and adding that to the CI --- resources/testproject/lib/clib3.c3l/clib3.c3i | 4 +++ resources/testproject/lib/clib3.c3l/feature.c | 6 ++++ .../testproject/lib/clib3.c3l/local_feature.c | 6 ++++ .../testproject/lib/clib3.c3l/manifest.json | 30 +++++++++++++++++++ resources/testproject/project.json | 29 +++++++++++++++++- .../testproject/src/hello/hello_world.c3 | 4 ++- scripts/tools/ci_tests.sh | 12 ++++++++ 7 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 resources/testproject/lib/clib3.c3l/clib3.c3i create mode 100644 resources/testproject/lib/clib3.c3l/feature.c create mode 100644 resources/testproject/lib/clib3.c3l/local_feature.c create mode 100644 resources/testproject/lib/clib3.c3l/manifest.json diff --git a/resources/testproject/lib/clib3.c3l/clib3.c3i b/resources/testproject/lib/clib3.c3l/clib3.c3i new file mode 100644 index 0000000000..752214c12c --- /dev/null +++ b/resources/testproject/lib/clib3.c3l/clib3.c3i @@ -0,0 +1,4 @@ +module clib3; + +extern fn void hello_from_preset_feature(); +extern fn void hello_from_local_feature(); diff --git a/resources/testproject/lib/clib3.c3l/feature.c b/resources/testproject/lib/clib3.c3l/feature.c new file mode 100644 index 0000000000..471902d747 --- /dev/null +++ b/resources/testproject/lib/clib3.c3l/feature.c @@ -0,0 +1,6 @@ +#include + +void hello_from_preset_feature(void) +{ + printf("Hello from clib3 preset feature!\n"); +} diff --git a/resources/testproject/lib/clib3.c3l/local_feature.c b/resources/testproject/lib/clib3.c3l/local_feature.c new file mode 100644 index 0000000000..070629fc4b --- /dev/null +++ b/resources/testproject/lib/clib3.c3l/local_feature.c @@ -0,0 +1,6 @@ +#include + +void hello_from_local_feature(void) +{ + printf("Hello from clib3 local feature!\n"); +} diff --git a/resources/testproject/lib/clib3.c3l/manifest.json b/resources/testproject/lib/clib3.c3l/manifest.json new file mode 100644 index 0000000000..bf04739106 --- /dev/null +++ b/resources/testproject/lib/clib3.c3l/manifest.json @@ -0,0 +1,30 @@ +{ + "provides": "clib3", + "presets": { + "native_feature": { + "features": ["CLIB3_PRESET_TEST"] + }, + "native_local_feature": { + "features": ["CLIB3_LOCAL_TEST"] + } + }, + "features": { + "CLIB3_PRESET_TEST": { + "c-sources": ["feature.c"] + }, + "CLIB3_LOCAL_TEST": { + "c-sources": ["local_feature.c"] + } + }, + "targets": { + "macos-x64": {}, + "macos-aarch64": {}, + "netbsd-x64": {}, + "netbsd-aarch64": {}, + "openbsd-x64": {}, + "linux-x64": {}, + "windows-x64": {}, + "android-aarch64": {}, + "android-x86_64": {} + } +} diff --git a/resources/testproject/project.json b/resources/testproject/project.json index 147322786d..f756755e26 100644 --- a/resources/testproject/project.json +++ b/resources/testproject/project.json @@ -12,7 +12,7 @@ "src" ], "dependency-search-paths": [ "./lib" ], - "dependencies": ["clib", "clib2"], + "dependencies": ["clib", "clib2", "clib3"], "cpu-flags": ",,", "features": ["ABCD"], "exec": ["scriptme.c3 myarg"], @@ -26,6 +26,33 @@ ], "reloc": "PIE", }, + "hello_world_preset_feature": { + "type": "executable", + "preset": "clib3:native_feature", + "cc" : "cc", + "c-sources": [ + "./csource/**" + ], + "reloc": "PIE", + }, + "hello_world_library_feature": { + "type": "executable", + "features": ["CLIB3_LOCAL_TEST"], + "cc" : "cc", + "c-sources": [ + "./csource/**" + ], + "reloc": "PIE", + }, + "hello_world_preset_local_feature": { + "type": "executable", + "preset": "clib3:native_local_feature", + "cc" : "cc", + "c-sources": [ + "./csource/**" + ], + "reloc": "PIE", + }, "hello_world_win32": { "type": "executable", "c-include-dirs": [ "C:\\"], diff --git a/resources/testproject/src/hello/hello_world.c3 b/resources/testproject/src/hello/hello_world.c3 index f4bb75f378..8b28234cdf 100644 --- a/resources/testproject/src/hello/hello_world.c3 +++ b/resources/testproject/src/hello/hello_world.c3 @@ -1,5 +1,5 @@ module hello_world; -import std, bar, clib, clib2; +import std, bar, clib, clib2, clib3; fn int test_doubler(int x) @if(env::WIN32) => x * x; extern fn int test_doubler(int) @if(!env::WIN32); @@ -23,5 +23,7 @@ fn int main() if ($feature(ABCD)) io::printn("ABCD"); clib::hello_from_c(); clib2::hello_from_c_zip(); + if ($feature(CLIB3_PRESET_TEST)) clib3::hello_from_preset_feature(); + if ($feature(CLIB3_LOCAL_TEST)) clib3::hello_from_local_feature(); return 0; } \ No newline at end of file diff --git a/scripts/tools/ci_tests.sh b/scripts/tools/ci_tests.sh index e912fec040..3abc963aed 100755 --- a/scripts/tools/ci_tests.sh +++ b/scripts/tools/ci_tests.sh @@ -211,6 +211,18 @@ run_testproject() { run_c3c run -vv $ARGS run_c3c clean + echo "Running Test Project (library preset feature)..." + run_c3c -vv run hello_world_preset_feature $ARGS + run_c3c clean + + echo "Running Test Project (library feature)..." + run_c3c -vv run hello_world_library_feature $ARGS + run_c3c clean + + echo "Running Test Project (library preset with alternate feature)..." + run_c3c -vv run hello_world_preset_local_feature $ARGS + run_c3c clean + if [[ "$OS_MODE" == "windows" ]]; then echo "Running Test Project (hello_world_win32)..." run_c3c -vv --emit-llvm run hello_world_win32 $ARGS From 5737ebe48adc7184b3fb05a2c281afd1b7e33387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20=28jotrorox=29=20M=C3=BCller?= Date: Mon, 1 Jun 2026 00:48:00 +0200 Subject: [PATCH 7/7] Replacing added en-Dashes with standard ASCII-Dashes --- src/build/project.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build/project.c b/src/build/project.c index e47adb13b4..9032daf0fc 100644 --- a/src/build/project.c +++ b/src/build/project.c @@ -603,11 +603,11 @@ static void parse_preset_ref(const char *ref, const char **lib_name, const char *preset_name = str_copy(colon + 1, strlen(colon + 1)); if (!str_is_valid_lowercase_name(*lib_name)) { - error_exit("Error reading project: invalid library name '%s' in preset reference '%s' – it should only contain alphanumerical letters and '_'.", *lib_name, ref); + error_exit("Error reading project: invalid library name '%s' in preset reference '%s' - it should only contain alphanumerical letters and '_'.", *lib_name, ref); } if (!str_is_valid_lowercase_name(*preset_name)) { - error_exit("Error reading project: invalid preset name '%s' in preset reference '%s' – it should only contain alphanumerical letters and '_'.", *preset_name, ref); + error_exit("Error reading project: invalid preset name '%s' in preset reference '%s' - it should only contain alphanumerical letters and '_'.", *preset_name, ref); } }