| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* Source fragment for Lucy's charmonizer.c. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include "Charmonizer/Probe.h" |
| #include "Charmonizer/Probe/AtomicOps.h" |
| #include "Charmonizer/Probe/DirManip.h" |
| #include "Charmonizer/Probe/Floats.h" |
| #include "Charmonizer/Probe/FuncMacro.h" |
| #include "Charmonizer/Probe/Headers.h" |
| #include "Charmonizer/Probe/Integers.h" |
| #include "Charmonizer/Probe/LargeFiles.h" |
| #include "Charmonizer/Probe/Memory.h" |
| #include "Charmonizer/Probe/SymbolVisibility.h" |
| #include "Charmonizer/Probe/VariadicMacros.h" |
| #include "Charmonizer/Core/HeaderChecker.h" |
| #include "Charmonizer/Core/ConfWriter.h" |
| #include "Charmonizer/Core/ConfWriterC.h" |
| #include "Charmonizer/Core/ConfWriterPerl.h" |
| #include "Charmonizer/Core/ConfWriterRuby.h" |
| |
| typedef struct lucy_MakeFile { |
| chaz_CLI *cli; |
| chaz_MakeFile *makefile; |
| chaz_MakeBinary *lib; |
| |
| /* Directories. */ |
| const char *base_dir; |
| char *core_dir; |
| char *test_dir; |
| const char *host_src_dir; |
| char *autogen_src_dir; |
| char *autogen_inc_dir; |
| char *lemon_dir; |
| char *modules_dir; |
| char *snowstem_dir; |
| char *snowstem_inc_dir; |
| char *snowstop_dir; |
| char *ucd_dir; |
| char *utf8proc_dir; |
| char *json_dir; |
| |
| /* Files. */ |
| char *autogen_target; |
| const char **autogen_src_files; |
| |
| /* Clownfish library. */ |
| char *cfish_lib_dir; |
| const char *cfish_lib_name; |
| } lucy_MakeFile; |
| |
| typedef struct SourceFileContext { |
| chaz_MakeVar *var; |
| } SourceFileContext; |
| |
| static const char lucy_version[] = "0.5.0"; |
| static const char lucy_major_version[] = "0.5"; |
| |
| static void |
| S_add_compiler_flags(struct chaz_CLI *cli); |
| |
| static lucy_MakeFile* |
| lucy_MakeFile_new(chaz_CLI *cli); |
| |
| static void |
| lucy_MakeFile_destroy(lucy_MakeFile *self); |
| |
| static void |
| lucy_MakeFile_write(lucy_MakeFile *self); |
| |
| static void |
| lucy_MakeFile_write_c_cfc_rules(lucy_MakeFile *self); |
| |
| static void |
| lucy_MakeFile_write_c_test_rules(lucy_MakeFile *self); |
| |
| static void |
| S_cfh_file_callback(const char *dir, char *file, void *context); |
| |
| static int |
| S_ends_with(const char *string, const char *postfix); |
| |
| int main(int argc, const char **argv) { |
| /* Initialize. */ |
| chaz_CLI *cli |
| = chaz_CLI_new(argv[0], "charmonizer: Probe C build environment"); |
| chaz_CLI_register(cli, "host", "specify host binding language", |
| CHAZ_CLI_ARG_REQUIRED); |
| chaz_CLI_register(cli, "disable-threads", "whether to disable threads", |
| CHAZ_CLI_NO_ARG); |
| chaz_CLI_register(cli, "clownfish-prefix", |
| "prefix of Clownfish installation", |
| CHAZ_CLI_ARG_OPTIONAL); |
| chaz_CLI_register(cli, "enable-go", "enable Go bindings", |
| CHAZ_CLI_NO_ARG); |
| chaz_CLI_set_usage(cli, "Usage: charmonizer [OPTIONS] [-- [CFLAGS]]"); |
| if (!chaz_Probe_parse_cli_args(argc, argv, cli)) { |
| chaz_Probe_die_usage(); |
| } |
| chaz_Probe_init(cli); |
| S_add_compiler_flags(cli); |
| |
| /* Employ integer features but don't define stdint types in charmony.h. */ |
| chaz_ConfWriter_append_conf( |
| "#define CHY_EMPLOY_INTEGERLIMITS\n" |
| "#define CHY_EMPLOY_INTEGERLITERALS\n" |
| "#define CHY_EMPLOY_INTEGERFORMATSTRINGS\n\n" |
| ); |
| |
| /* Run probe modules. Booleans is only needed for the Charmonizer tests. */ |
| chaz_BuildEnv_run(); |
| chaz_DirManip_run(); |
| chaz_Headers_run(); |
| chaz_Booleans_run(); |
| chaz_Integers_run(); |
| chaz_Floats_run(); |
| chaz_LargeFiles_run(); |
| chaz_Memory_run(); |
| chaz_RegularExpressions_run(); |
| chaz_VariadicMacros_run(); |
| |
| /* Write custom postamble. */ |
| chaz_ConfWriter_append_conf( |
| "#ifdef CHY_HAS_SYS_TYPES_H\n" |
| " #include <sys/types.h>\n" |
| "#endif\n\n" |
| ); |
| chaz_ConfWriter_append_conf( |
| "#ifdef CHY_HAS_ALLOCA_H\n" |
| " #include <alloca.h>\n" |
| "#elif defined(CHY_HAS_MALLOC_H)\n" |
| " #include <malloc.h>\n" |
| "#elif defined(CHY_ALLOCA_IN_STDLIB_H)\n" |
| " #include <stdlib.h>\n" |
| "#endif\n\n" |
| ); |
| chaz_ConfWriter_append_conf( |
| "#ifdef CHY_HAS_WINDOWS_H\n" |
| " /* Target Windows XP. */\n" |
| " #ifndef WINVER\n" |
| " #define WINVER 0x0500\n" |
| " #endif\n" |
| " #ifndef _WIN32_WINNT\n" |
| " #define _WIN32_WINNT 0x0500\n" |
| " #endif\n" |
| "#endif\n\n" |
| ); |
| |
| if (chaz_CLI_defined(cli, "enable-makefile")) { |
| lucy_MakeFile *mf = lucy_MakeFile_new(cli); |
| lucy_MakeFile_write(mf); |
| lucy_MakeFile_destroy(mf); |
| } |
| |
| /* Clean up. */ |
| chaz_CLI_destroy(cli); |
| chaz_Probe_clean_up(); |
| |
| return 0; |
| } |
| |
| static void |
| S_add_compiler_flags(struct chaz_CLI *cli) { |
| chaz_CFlags *extra_cflags = chaz_CC_get_extra_cflags(); |
| |
| if (chaz_Probe_gcc_version_num()) { |
| if (getenv("LUCY_VALGRIND")) { |
| chaz_CFlags_append(extra_cflags, |
| "-DLUCY_VALGRIND -fno-inline-functions"); |
| } |
| else if (getenv("LUCY_DEBUG")) { |
| chaz_CFlags_append(extra_cflags, "-DLUCY_DEBUG"); |
| } |
| |
| chaz_CFlags_append(extra_cflags, |
| "-pedantic -Wall -Wextra -Wno-variadic-macros"); |
| if (chaz_CLI_defined(cli, "enable-perl")) { |
| chaz_CFlags_append(extra_cflags, "-DPERL_GCC_PEDANTIC"); |
| } |
| |
| /* Only core source files require this -- not our headers and |
| * autogenerated files. */ |
| chaz_CFlags_append(extra_cflags, "-std=gnu99 -D_GNU_SOURCE"); |
| } |
| else if (chaz_Probe_msvc_version_num()) { |
| if (chaz_Probe_msvc_version_num() < 1800) { |
| /* Compile as C++ under MSVC11 and below. */ |
| chaz_CFlags_append(extra_cflags, "/TP"); |
| } |
| |
| chaz_CFlags_append(extra_cflags, "/W3"); |
| /* Thwart stupid warnings. */ |
| chaz_CFlags_append(extra_cflags, |
| "/D_CRT_SECURE_NO_WARNINGS /D_SCL_SECURE_NO_WARNINGS /wd4996"); |
| |
| if (chaz_Probe_msvc_version_num() < 1300) { |
| /* Redefine 'for' to fix broken 'for' scoping under MSVC6. */ |
| chaz_CFlags_append(extra_cflags, "/Dfor=\"if(0);else for\""); |
| } |
| } |
| |
| chaz_CFlags_hide_symbols(extra_cflags); |
| } |
| |
| static lucy_MakeFile* |
| lucy_MakeFile_new(chaz_CLI *cli) { |
| const char *dir_sep = chaz_OS_dir_sep(); |
| const char *cfish_prefix = chaz_CLI_strval(cli, "clownfish-prefix"); |
| |
| lucy_MakeFile *self = malloc(sizeof(lucy_MakeFile)); |
| |
| self->cli = cli; |
| self->makefile = chaz_MakeFile_new(); |
| self->lib = NULL; |
| |
| /* Initialize directories. */ |
| self->base_dir = ".."; |
| self->core_dir = chaz_Util_join(dir_sep, self->base_dir, "core", NULL); |
| self->test_dir = chaz_Util_join(dir_sep, self->base_dir, "test", NULL); |
| if (chaz_CLI_defined(cli, "enable-perl")) { |
| self->host_src_dir = "xs"; |
| } |
| else if (chaz_CLI_defined(cli, "enable-go")) { |
| self->host_src_dir = "cfext"; |
| } |
| else { |
| self->host_src_dir = "src"; |
| } |
| self->autogen_src_dir = chaz_Util_join(dir_sep, "autogen", "source", NULL); |
| self->autogen_inc_dir |
| = chaz_Util_join(dir_sep, "autogen", "include", NULL); |
| self->lemon_dir = chaz_Util_join(dir_sep, self->base_dir, "lemon", NULL); |
| self->modules_dir |
| = chaz_Util_join(dir_sep, self->base_dir, "modules", NULL); |
| self->snowstem_dir |
| = chaz_Util_join(dir_sep, self->modules_dir, "analysis", "snowstem", |
| "source", NULL); |
| self->snowstem_inc_dir |
| = chaz_Util_join(dir_sep, self->snowstem_dir, "include", NULL); |
| self->snowstop_dir |
| = chaz_Util_join(dir_sep, self->modules_dir, "analysis", "snowstop", |
| "source", NULL); |
| self->ucd_dir |
| = chaz_Util_join(dir_sep, self->modules_dir, "unicode", "ucd", NULL); |
| self->utf8proc_dir |
| = chaz_Util_join(dir_sep, self->modules_dir, "unicode", "utf8proc", |
| NULL); |
| self->json_dir |
| = chaz_Util_join(dir_sep, self->core_dir, "Lucy", "Util", "Json", |
| NULL); |
| |
| /* Initialize file names. */ |
| if (chaz_CLI_defined(cli, "enable-perl")) { |
| static const char *perl_autogen_src_files[] = { |
| "boot.c", |
| "callbacks.c", |
| "lucy_parcel.c", |
| "testlucy_parcel.c", |
| NULL |
| }; |
| self->autogen_src_files = perl_autogen_src_files; |
| } |
| else { |
| static const char *c_autogen_src_files[] = { |
| "lucy_parcel.c", |
| "testlucy_parcel.c", |
| NULL |
| }; |
| self->autogen_src_files = c_autogen_src_files; |
| } |
| self->autogen_target |
| = chaz_Util_join(dir_sep, "autogen", "hierarchy.json", NULL); |
| |
| /* Clownfish library. */ |
| if (cfish_prefix) { |
| self->cfish_lib_dir |
| = chaz_Util_join(dir_sep, cfish_prefix, "lib", NULL); |
| } |
| else { |
| self->cfish_lib_dir = NULL; |
| } |
| if (chaz_CC_binary_format() == CHAZ_CC_BINFMT_PE) { |
| self->cfish_lib_name = "cfish-0.5"; |
| } |
| else { |
| self->cfish_lib_name = "cfish"; |
| } |
| |
| return self; |
| } |
| |
| static void |
| lucy_MakeFile_destroy(lucy_MakeFile *self) { |
| chaz_MakeFile_destroy(self->makefile); |
| |
| free(self->core_dir); |
| free(self->test_dir); |
| free(self->autogen_inc_dir); |
| free(self->autogen_src_dir); |
| free(self->lemon_dir); |
| free(self->modules_dir); |
| free(self->snowstem_dir); |
| free(self->snowstem_inc_dir); |
| free(self->snowstop_dir); |
| free(self->ucd_dir); |
| free(self->utf8proc_dir); |
| free(self->json_dir); |
| |
| free(self->autogen_target); |
| |
| free(self->cfish_lib_dir); |
| |
| free(self); |
| } |
| |
| static void |
| lucy_MakeFile_write(lucy_MakeFile *self) { |
| const char *dir_sep = chaz_OS_dir_sep(); |
| const char *host = chaz_CLI_strval(self->cli, "host"); |
| const char *math_lib = chaz_Floats_math_library(); |
| |
| chaz_MakeVar *var; |
| chaz_MakeRule *rule; |
| |
| chaz_CFlags *extra_cflags = chaz_CC_get_extra_cflags(); |
| chaz_CFlags *makefile_cflags; |
| chaz_CFlags *compile_flags; |
| chaz_CFlags *link_flags; |
| |
| char *scratch; |
| int i; |
| |
| printf("Creating Makefile...\n"); |
| |
| /* Directories */ |
| |
| chaz_MakeFile_add_var(self->makefile, "BASE_DIR", self->base_dir); |
| |
| /* C compiler */ |
| |
| chaz_MakeFile_add_var(self->makefile, "CC", chaz_CC_get_cc()); |
| |
| makefile_cflags = chaz_CC_new_cflags(); |
| |
| chaz_CFlags_enable_optimization(makefile_cflags); |
| chaz_CFlags_enable_debugging(makefile_cflags); |
| chaz_CFlags_disable_strict_aliasing(makefile_cflags); |
| if (chaz_CLI_defined(self->cli, "enable-coverage")) { |
| chaz_CFlags_enable_code_coverage(makefile_cflags); |
| } |
| |
| chaz_CFlags_add_include_dir(makefile_cflags, "."); |
| chaz_CFlags_add_include_dir(makefile_cflags, self->core_dir); |
| chaz_CFlags_add_include_dir(makefile_cflags, self->autogen_inc_dir); |
| chaz_CFlags_add_include_dir(makefile_cflags, self->snowstem_inc_dir); |
| chaz_CFlags_add_include_dir(makefile_cflags, self->ucd_dir); |
| chaz_CFlags_add_include_dir(makefile_cflags, self->utf8proc_dir); |
| |
| var = chaz_MakeFile_add_var(self->makefile, "CFLAGS", NULL); |
| chaz_MakeVar_append(var, chaz_CFlags_get_string(extra_cflags)); |
| chaz_MakeVar_append(var, chaz_CFlags_get_string(makefile_cflags)); |
| chaz_MakeVar_append(var, chaz_CC_get_cflags()); |
| |
| chaz_CFlags_destroy(makefile_cflags); |
| |
| /* Binary. */ |
| |
| if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) { |
| chaz_MakeFile_add_rule(self->makefile, "all", "$(LUCY_SHARED_LIB)"); |
| |
| self->lib |
| = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "lucy", |
| lucy_version, lucy_major_version); |
| rule = chaz_MakeFile_add_rule(self->makefile, |
| "$(LUCY_SHARED_LIB_OBJS)", |
| self->autogen_target); |
| /* |
| * The dependency is actually on JsonParser.h, but make doesn't cope |
| * well with multiple output files. |
| */ |
| scratch = chaz_Util_join(dir_sep, self->json_dir, "JsonParser.c", |
| NULL); |
| chaz_MakeRule_add_prereq(rule, scratch); |
| free(scratch); |
| |
| link_flags = chaz_MakeBinary_get_link_flags(self->lib); |
| chaz_CFlags_enable_debugging(link_flags); |
| if (self->cfish_lib_dir) { |
| chaz_CFlags_add_library_path(link_flags, self->cfish_lib_dir); |
| } |
| if (math_lib) { |
| chaz_CFlags_add_external_lib(link_flags, math_lib); |
| } |
| chaz_CFlags_add_external_lib(link_flags, self->cfish_lib_name); |
| if (chaz_HeadCheck_check_header("pcre.h")) { |
| chaz_CFlags_add_external_lib(link_flags, "pcre"); |
| } |
| if (chaz_CLI_defined(self->cli, "enable-coverage")) { |
| chaz_CFlags_enable_code_coverage(link_flags); |
| } |
| } |
| else { |
| chaz_MakeFile_add_rule(self->makefile, "static", "$(LUCY_STATIC_LIB)"); |
| |
| self->lib |
| = chaz_MakeFile_add_static_lib(self->makefile, NULL, "lucy"); |
| rule = chaz_MakeFile_add_rule(self->makefile, |
| "$(LUCY_STATIC_LIB_OBJS)", |
| self->autogen_target); |
| scratch = chaz_Util_join(dir_sep, self->json_dir, "JsonParser.c", |
| NULL); |
| chaz_MakeRule_add_prereq(rule, scratch); |
| free(scratch); |
| } |
| |
| chaz_MakeBinary_add_src_dir(self->lib, self->host_src_dir); |
| chaz_MakeBinary_add_src_dir(self->lib, self->core_dir); |
| chaz_MakeBinary_add_src_dir(self->lib, self->test_dir); |
| chaz_MakeBinary_add_src_dir(self->lib, self->snowstem_dir); |
| chaz_MakeBinary_add_src_dir(self->lib, self->snowstop_dir); |
| chaz_MakeBinary_add_src_dir(self->lib, self->utf8proc_dir); |
| |
| chaz_MakeBinary_add_src_file(self->lib, self->json_dir, "JsonParser.c"); |
| |
| for (i = 0; self->autogen_src_files[i] != NULL; ++i) { |
| chaz_MakeBinary_add_src_file(self->lib, self->autogen_src_dir, |
| self->autogen_src_files[i]); |
| } |
| |
| compile_flags = chaz_MakeBinary_get_compile_flags(self->lib); |
| chaz_CFlags_add_define(compile_flags, "CFP_LUCY", NULL); |
| chaz_CFlags_add_define(compile_flags, "CFP_TESTLUCY", NULL); |
| |
| /* Additional rules. */ |
| |
| chaz_MakeFile_add_lemon_exe(self->makefile, self->lemon_dir); |
| scratch = chaz_Util_join(dir_sep, self->json_dir, "JsonParser", NULL); |
| chaz_MakeFile_add_lemon_grammar(self->makefile, scratch); |
| free(scratch); |
| |
| if (strcmp(host, "c") == 0) { |
| lucy_MakeFile_write_c_cfc_rules(self); |
| lucy_MakeFile_write_c_test_rules(self); |
| } |
| |
| /* Needed for parallel builds. */ |
| for (i = 0; self->autogen_src_files[i] != NULL; ++i) { |
| char *path = chaz_Util_join("", self->autogen_src_dir, dir_sep, |
| self->autogen_src_files[i], NULL); |
| rule = chaz_MakeFile_add_rule(self->makefile, path, |
| self->autogen_target); |
| free(path); |
| } |
| |
| rule = chaz_MakeFile_clean_rule(self->makefile); |
| chaz_MakeRule_add_recursive_rm_command(rule, "autogen"); |
| |
| chaz_MakeFile_write(self->makefile); |
| } |
| |
| static void |
| lucy_MakeFile_write_c_cfc_rules(lucy_MakeFile *self) { |
| SourceFileContext sfc; |
| chaz_MakeRule *rule; |
| |
| const char *dir_sep = chaz_OS_dir_sep(); |
| const char *cfish_prefix = chaz_CLI_strval(self->cli, "clownfish-prefix"); |
| |
| char *cfc_command; |
| |
| sfc.var = chaz_MakeFile_add_var(self->makefile, "CLOWNFISH_HEADERS", NULL); |
| chaz_Make_list_files(self->core_dir, "cfh", S_cfh_file_callback, &sfc); |
| chaz_Make_list_files(self->test_dir, "cfh", S_cfh_file_callback, &sfc); |
| |
| rule = chaz_MakeFile_add_rule(self->makefile, self->autogen_target, NULL); |
| chaz_MakeRule_add_prereq(rule, "$(CLOWNFISH_HEADERS)"); |
| if (cfish_prefix == NULL) { |
| cfc_command |
| = chaz_Util_join("", "cfc --source=", self->core_dir, |
| " --source=", self->test_dir, |
| " --dest=autogen --header=cfc_header", NULL); |
| } |
| else { |
| cfc_command |
| = chaz_Util_join("", cfish_prefix, dir_sep, "bin", dir_sep, |
| "cfc --source=", self->core_dir, " --source=", |
| self->test_dir, " --include=", cfish_prefix, |
| dir_sep, "share", dir_sep, "clownfish", dir_sep, |
| "include --dest=autogen --header=cfc_header", |
| NULL); |
| } |
| chaz_MakeRule_add_command(rule, cfc_command); |
| |
| free(cfc_command); |
| } |
| |
| static void |
| lucy_MakeFile_write_c_test_rules(lucy_MakeFile *self) { |
| chaz_MakeBinary *test_exe; |
| chaz_CFlags *link_flags; |
| chaz_MakeRule *rule; |
| |
| test_exe = chaz_MakeFile_add_exe(self->makefile, "t", "test_lucy"); |
| chaz_MakeBinary_add_src_file(test_exe, "t", "test_lucy.c"); |
| chaz_MakeFile_add_rule(self->makefile, "$(TEST_LUCY_EXE_OBJS)", |
| self->autogen_target); |
| link_flags = chaz_MakeBinary_get_link_flags(test_exe); |
| chaz_CFlags_add_shared_lib(link_flags, NULL, "lucy", lucy_major_version); |
| chaz_MakeBinary_add_prereq(test_exe, "$(LUCY_SHARED_LIB)"); |
| if (self->cfish_lib_dir) { |
| chaz_CFlags_add_library_path(link_flags, self->cfish_lib_dir); |
| } |
| chaz_CFlags_add_external_lib(link_flags, self->cfish_lib_name); |
| chaz_CFlags_add_rpath(link_flags, "\"$$PWD\""); |
| if (self->cfish_lib_dir) { |
| chaz_CFlags_add_rpath(link_flags, self->cfish_lib_dir); |
| } |
| |
| rule = chaz_MakeFile_add_rule(self->makefile, "test", "$(TEST_LUCY_EXE)"); |
| chaz_MakeRule_add_command(rule, "$(TEST_LUCY_EXE)"); |
| |
| if (chaz_CLI_defined(self->cli, "enable-coverage")) { |
| rule = chaz_MakeFile_add_rule(self->makefile, "coverage", |
| "$(TEST_LUCY_EXE)"); |
| chaz_MakeRule_add_command(rule, |
| "lcov" |
| " --zerocounters" |
| " --directory $(BASE_DIR)"); |
| chaz_MakeRule_add_command(rule, "$(TEST_LUCY_EXE)"); |
| chaz_MakeRule_add_command(rule, |
| "lcov" |
| " --capture" |
| " --directory $(BASE_DIR)" |
| " --base-directory ." |
| " --rc lcov_branch_coverage=1" |
| " --output-file lucy.info"); |
| chaz_MakeRule_add_command(rule, |
| "genhtml" |
| " --branch-coverage" |
| " --output-directory coverage" |
| " lucy.info"); |
| |
| rule = chaz_MakeFile_clean_rule(self->makefile); |
| chaz_MakeRule_add_rm_command(rule, "lucy.info"); |
| chaz_MakeRule_add_recursive_rm_command(rule, "coverage"); |
| } |
| } |
| |
| static void |
| S_cfh_file_callback(const char *dir, char *file, void *context) { |
| SourceFileContext *sfc = (SourceFileContext*)context; |
| const char *dir_sep = chaz_OS_dir_sep(); |
| char *cfh_file; |
| |
| if (!S_ends_with(file, ".cfh")) { |
| chaz_Util_warn("Unexpected Clownfish header filename: %s", file); |
| return; |
| } |
| |
| cfh_file = chaz_Util_join(dir_sep, dir, file, NULL); |
| chaz_MakeVar_append(sfc->var, cfh_file); |
| free(cfh_file); |
| } |
| |
| static int |
| S_ends_with(const char *string, const char *postfix) { |
| size_t len = strlen(string); |
| size_t postfix_len = strlen(postfix); |
| return len >= postfix_len |
| && memcmp(string + len - postfix_len, postfix, postfix_len) == 0; |
| } |
| |
| |