From 67a630e5af1ace1dd528ea9652ee69102b3136c3 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Mon, 1 Aug 2022 12:45:10 +0100 Subject: make: new applet This is an experimental implementation of make for busybox-w32, based on my public domain POSIX make: https://frippery.org/make/ (GitHub issue #44) --- archival/ar.c | 4 +- archival/libarchive/Kbuild.src | 1 + archival/libarchive/unpack_ar_archive.c | 8 +- configs/mingw32_defconfig | 4 +- configs/mingw64_defconfig | 4 +- miscutils/make.c | 2621 +++++++++++++++++++++++++++++++ testsuite/make.tests | 413 +++++ win32/Kbuild | 1 + win32/glob.c | 343 ++++ win32/glob.h | 89 ++ 10 files changed, 3482 insertions(+), 6 deletions(-) create mode 100644 miscutils/make.c create mode 100755 testsuite/make.tests create mode 100644 win32/glob.c create mode 100644 win32/glob.h diff --git a/archival/ar.c b/archival/ar.c index beccab217..b5565c936 100644 --- a/archival/ar.c +++ b/archival/ar.c @@ -30,12 +30,12 @@ //config:config FEATURE_AR_LONG_FILENAMES //config: bool "Support long filenames (not needed for debs)" //config: default y -//config: depends on AR +//config: depends on AR || MAKE //config: help //config: By default the ar format can only store the first 15 characters //config: of the filename, this option removes that limitation. //config: It supports the GNU ar long filename method which moves multiple long -//config: filenames into a the data section of a new ar entry. +//config: filenames into the data section of a new ar entry. //config: //config:config FEATURE_AR_CREATE //config: bool "Support archive creation" diff --git a/archival/libarchive/Kbuild.src b/archival/libarchive/Kbuild.src index d2f284b08..1c74250a2 100644 --- a/archival/libarchive/Kbuild.src +++ b/archival/libarchive/Kbuild.src @@ -47,6 +47,7 @@ lib-$(CONFIG_DPKG_DEB) += $(DPKG_FILES) lib-$(CONFIG_AR) += get_header_ar.o unpack_ar_archive.o lib-$(CONFIG_CPIO) += get_header_cpio.o +lib-$(CONFIG_MAKE) += get_header_ar.o unpack_ar_archive.o lib-$(CONFIG_TAR) += get_header_tar.o unsafe_prefix.o lib-$(CONFIG_FEATURE_TAR_TO_COMMAND) += data_extract_to_command.o lib-$(CONFIG_LZOP) += lzo1x_1.o lzo1x_1o.o lzo1x_d.o diff --git a/archival/libarchive/unpack_ar_archive.c b/archival/libarchive/unpack_ar_archive.c index 125d424c9..923a0b2ab 100644 --- a/archival/libarchive/unpack_ar_archive.c +++ b/archival/libarchive/unpack_ar_archive.c @@ -16,6 +16,10 @@ void FAST_FUNC unpack_ar_archive(archive_handle_t *ar_archive) } ar_archive->offset += AR_MAGIC_LEN; - while (get_header_ar(ar_archive) == EXIT_SUCCESS) - continue; + while (get_header_ar(ar_archive) == EXIT_SUCCESS) { +#if ENABLE_MAKE + free(ar_archive->file_header->name); + ar_archive->file_header->name = NULL; +#endif + } } diff --git a/configs/mingw32_defconfig b/configs/mingw32_defconfig index 78e359500..81bbda4dd 100644 --- a/configs/mingw32_defconfig +++ b/configs/mingw32_defconfig @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Busybox version: 1.36.0.git -# Thu May 12 08:13:00 2022 +# Thu Jul 7 08:08:14 2022 # CONFIG_HAVE_DOT_CONFIG=y # CONFIG_PLATFORM_POSIX is not set @@ -831,6 +831,8 @@ CONFIG_FEATURE_LESS_LINENUMS=y CONFIG_FEATURE_LESS_RAW=y CONFIG_FEATURE_LESS_ENV=y # CONFIG_LSSCSI is not set +CONFIG_MAKE=y +CONFIG_FEATURE_MAKE_POSIX=y # CONFIG_MAKEDEVS is not set # CONFIG_FEATURE_MAKEDEVS_LEAF is not set # CONFIG_FEATURE_MAKEDEVS_TABLE is not set diff --git a/configs/mingw64_defconfig b/configs/mingw64_defconfig index c88da8007..0e54eb78d 100644 --- a/configs/mingw64_defconfig +++ b/configs/mingw64_defconfig @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Busybox version: 1.36.0.git -# Thu May 12 08:13:00 2022 +# Thu Jul 7 08:08:14 2022 # CONFIG_HAVE_DOT_CONFIG=y # CONFIG_PLATFORM_POSIX is not set @@ -831,6 +831,8 @@ CONFIG_FEATURE_LESS_LINENUMS=y CONFIG_FEATURE_LESS_RAW=y CONFIG_FEATURE_LESS_ENV=y # CONFIG_LSSCSI is not set +CONFIG_MAKE=y +CONFIG_FEATURE_MAKE_POSIX=y # CONFIG_MAKEDEVS is not set # CONFIG_FEATURE_MAKEDEVS_LEAF is not set # CONFIG_FEATURE_MAKEDEVS_TABLE is not set diff --git a/miscutils/make.c b/miscutils/make.c new file mode 100644 index 000000000..b92819266 --- /dev/null +++ b/miscutils/make.c @@ -0,0 +1,2621 @@ +/* vi: set sw=4 ts=4: */ +/* + * make implementation for BusyBox + * + * Based on public domain POSIX make: https://frippery.org/make + */ +//config:config MAKE +//config: bool "make (18 kb)" +//config: default n +//config: help +//config: The make command can be used to maintain files that depend on +//config: other files. Normally it's used to build programs from source +//config: code but it can be used in other situations too. +//config: +//config:config FEATURE_MAKE_POSIX +//config: bool "Runtime enforcement of POSIX" +//config: default n +//config: depends on MAKE +//config: help +//config: Allow strict enforcement of POSIX mode at runtime by: +//config: - .POSIX special target in makefile +//config: - '--posix' command line option +//config: - PDPMAKE_POSIXLY_CORRECT environment variable +//config: Enable this if you want to check whether your makefiles are +//config: POSIX compliant. This adds about 500 bytes. + +//applet:IF_MAKE(APPLET(make, BB_DIR_USR_BIN, BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_MAKE) += make.o + +//usage:#define make_trivial_usage +//usage: IF_FEATURE_MAKE_POSIX( +//usage: "[--posix] [-C DIR] [-f FILE] [j NUM] [-eiknpqrsSt] [MACRO[::]=VAL]... [TARGET]..." +//usage: ) +//usage: IF_NOT_FEATURE_MAKE_POSIX( +//usage: "[-C DIR] [-f FILE] [j NUM] [-eiknpqrsSt] [MACRO[::]=VAL]... [TARGET]..." +//usage: ) +//usage:#define make_full_usage "\n\n" +//usage: "Maintain files based on their dependencies\n" +//usage: IF_FEATURE_MAKE_POSIX( +//usage: "\n --posix Enforce POSIX mode" +//usage: ) +//usage: "\n -C DIR Change to DIR" +//usage: "\n -f FILE Makefile" +//usage: "\n -j NUM Jobs to run in parallel (not implemented)" +//usage: "\n -e Environment variables override macros in makefiles" +//usage: "\n -i Ignore exit status" +//usage: "\n -k Continue on error" +//usage: "\n -n Dry run" +//usage: "\n -p Print macros and targets" +//usage: "\n -q Query target; exit status 1 if not up to date" +//usage: "\n -r Don't use built-in rules" +//usage: "\n -s Make silently" +//usage: "\n -S Stop on error" +//usage: "\n -t Touch files instead of making them" + +#include "libbb.h" +#include "bb_archive.h" +#include "common_bufsiz.h" +#include + +#define OPTSTR1 "eij:+knqrsSt" +#define OPTSTR2 "pf:*C:*" + +enum { + OPT_e = (1 << 0), + OPT_i = (1 << 1), + OPT_j = (1 << 2), + OPT_k = (1 << 3), + OPT_n = (1 << 4), + OPT_q = (1 << 5), + OPT_r = (1 << 6), + OPT_s = (1 << 7), + OPT_S = (1 << 8), + OPT_t = (1 << 9), + // These options aren't allowed in MAKEFLAGS + OPT_p = (1 << 10), + OPT_f = (1 << 11), + OPT_C = (1 << 12), + // The following aren't command line options and must be last + OPT_precious = (1 << 13), + OPT_phony = (1 << 14), + OPT_include = (1 << 15), + OPT_make = (1 << 16), +}; + +// Options in OPTSTR1 that aren't included in MAKEFLAGS +#define OPT_MASK (~OPT_S) + +#define useenv (opts & OPT_e) +#define ignore (opts & OPT_i) +#define errcont (opts & OPT_k) +#define dryrun (opts & OPT_n) +#define print (opts & OPT_p) +#define quest (opts & OPT_q) +#define norules (opts & OPT_r) +#define silent (opts & OPT_s) +#define dotouch (opts & OPT_t) +#define precious (opts & OPT_precious) +#define doinclude (opts & OPT_include) +#define domake (opts & OPT_make) + +// A name. This represents a file, either to be made, or pre-existing. +struct name { + struct name *n_next; // Next in the list of names + char *n_name; // Called + struct rule *n_rule; // Rules to build this (prerequisites/commands) + struct timespec n_tim; // Modification time of this name + uint16_t n_flag; // Info about the name +}; + +#define N_DOING 0x01 // Name in process of being built +#define N_DONE 0x02 // Name looked at +#define N_TARGET 0x04 // Name is a target +#define N_PRECIOUS 0x08 // Target is precious +#define N_DOUBLE 0x10 // Double-colon target +#define N_SILENT 0x20 // Build target silently +#define N_IGNORE 0x40 // Ignore build errors +#define N_SPECIAL 0x80 // Special target +#define N_MARK 0x100 // Mark for deduplication +#define N_PHONY 0x200 // Name is a phony target + +// List of rules to build a target +struct rule { + struct rule *r_next; // Next rule + struct depend *r_dep; // Prerequisites for this rule + struct cmd *r_cmd; // Commands for this rule +}; + +// NOTE: the layout of the following two structures must be compatible. +// Also, their first two members must be compatible with llist_t. + +// List of prerequisites for a rule +struct depend { + struct depend *d_next; // Next prerequisite + struct name *d_name; // Name of prerequisite + int d_refcnt; // Reference count +}; + +// List of commands for a rule +struct cmd { + struct cmd *c_next; // Next command line + char *c_cmd; // Text of command line + int c_refcnt; // Reference count +}; + +// Macro storage +struct macro { + struct macro *m_next; // Next variable + char *m_name; // Its name + char *m_val; // Its value + bool m_immediate; // Immediate-expansion macro set using ::= + bool m_flag; // Infinite loop check + uint8_t m_level; // Level at which macro was created +}; + +// Flags passed to setmacro() +#define M_IMMEDIATE 8 // immediate-expansion macro is being defined +#define M_VALID 16 // assert macro name is valid + +#define HTABSIZE 39 + +struct globals { + uint32_t opts; + const char *makefile; + llist_t *makefiles; + llist_t *dirs; + struct name *namehead[HTABSIZE]; + struct macro *macrohead[HTABSIZE]; + struct name *firstname; + struct name *target; + time_t ar_mtime; + int lineno; // Physical line number in file + int dispno; // Line number for display purposes + const char *rulepos; +#define IF_MAX 10 + uint8_t clevel; + uint8_t cstate[IF_MAX + 1]; +#if ENABLE_FEATURE_MAKE_POSIX + bool posix; + bool seen_first; +#endif + int numjobs; +} FIX_ALIASING; + +#define G (*(struct globals*)bb_common_bufsiz1) +#define INIT_G() do { \ + setup_common_bufsiz(); \ +} while (0) + +#define opts (G.opts) +#define makefile (G.makefile) +#define makefiles (G.makefiles) +#define dirs (G.dirs) +#define namehead (G.namehead) +#define macrohead (G.macrohead) +#define firstname (G.firstname) +#define target (G.target) +#define ar_mtime (G.ar_mtime) +#define lineno (G.lineno) +#define dispno (G.dispno) +#define rulepos (G.rulepos) +#define clevel (G.clevel) +#define cstate (G.cstate) +#if ENABLE_FEATURE_MAKE_POSIX +#define posix (G.posix) +#define seen_first (G.seen_first) +#else +#define posix 0 +#endif +#define numjobs (G.numjobs) + +static int make(struct name *np, int level); + +// Return TRUE if c is allowed in a POSIX 2017 macro or target name +#define ispname(c) (isalpha(c) || isdigit(c) || c == '.' || c == '_') +// Return TRUE if c is in the POSIX 'portable filename character set' +#define isfname(c) (ispname(c) || c == '-') + +/* + * Utility functions. + */ + +/* + * Error handler. Print message, with line number, and exit. + */ +static void error(const char *msg, ...) NORETURN; +static void +error(const char *msg, ...) +{ + va_list list; + + if (makefile) { + const char *num = itoa(dispno); + char *s = malloc(strlen(makefile) + strlen(num) + 2); + if (s) { + sprintf(s, "%s:%s", makefile, num); + applet_name = s; + } + } + va_start(list, msg); + bb_verror_msg(msg, list, NULL); + va_end(list); + exit(2); +} + +static void error_unexpected(const char *s) NORETURN; +static void +error_unexpected(const char *s) +{ + error("unexpected %s", s); +} + +static void error_in_inference_rule(const char *s) NORETURN; +static void +error_in_inference_rule(const char *s) +{ + error("%s in inference rule", s); +} + +static char * +auto_concat(const char *s1, const char *s2) +{ + return auto_string(xasprintf("%s%s", s1, s2)); +} + +/* + * Append a word to a space-separated string of words. The first + * call should use a NULL pointer for str, subsequent calls should + * pass an allocated string which will be freed. + */ +static char * +xappendword(const char *str, const char *word) +{ + char *newstr = str ? xasprintf("%s %s", str, word) : xstrdup(word); + free((void *)str); + return newstr; +} + +static unsigned int +getbucket(const char *name) +{ + unsigned int hashval = 0; + const unsigned char *p = (unsigned char *)name; + + while (*p) + hashval ^= (hashval << 5) + (hashval >> 2) + *p++; + return hashval % HTABSIZE; +} + +/* + * Add a prerequisite to the end of the supplied list. + */ +static void +newdep(struct depend **dphead, struct name *np) +{ + while (*dphead) + dphead = &(*dphead)->d_next; + *dphead = xzalloc(sizeof(struct depend)); + /*(*dphead)->d_next = NULL; - xzalloc did it */ + (*dphead)->d_name = np; + /*(*dphead)->d_refcnt = 0; */ +} + +static void +freedeps(struct depend *dp) +{ + if (dp && --dp->d_refcnt <= 0) + llist_free((llist_t *)dp, NULL); +} + +/* + * Add a command to the end of the supplied list of commands. + */ +static void +newcmd(struct cmd **cphead, char *str) +{ + while (isspace(*str)) + str++; + + if (*str == '\0') // No command, leave current head unchanged + return; + + while (*cphead) + cphead = &(*cphead)->c_next; + *cphead = xzalloc(sizeof(struct cmd)); + /*(*cphead)->c_next = NULL; - xzalloc did it */ + (*cphead)->c_cmd = xstrdup(str); + /*(*cphead)->c_refcnt = 0; */ +} + +static void +freecmds(struct cmd *cp) +{ + if (cp && --cp->c_refcnt <= 0) + llist_free((llist_t *)cp, free); +} + +static struct name * +findname(const char *name) +{ + struct name *np = namehead[getbucket(name)]; + return (struct name *)llist_find_str((llist_t *)np, name); +} + +static int +is_valid_target(const char *name) +{ + const char *s; + for (s = name; *s; ++s) { + if (posix && !ispname(*s)) + return FALSE; + } + return TRUE; +} + +/* + * Intern a name. Return a pointer to the name struct + */ +static struct name * +newname(const char *name) +{ + struct name *np = findname(name); + + if (np == NULL) { + unsigned int bucket; + + if (!is_valid_target(name)) + error("invalid target name '%s'", name); + + bucket = getbucket(name); + np = xzalloc(sizeof(struct name)); + np->n_next = namehead[bucket]; + namehead[bucket] = np; + np->n_name = xstrdup(name); + /*np->n_rule = NULL; - xzalloc did it */ + /*np->n_tim = (struct timespec){0, 0}; */ + /*np->n_flag = 0; */ + } + return np; +} + +/* + * Return the commands on the first rule that has them or NULL. + */ +static struct cmd * +getcmd(struct name *np) +{ + struct rule *rp; + + if (np == NULL) + return NULL; + + for (rp = np->n_rule; rp; rp = rp->r_next) + if (rp->r_cmd) + return rp->r_cmd; + return NULL; +} + +#if ENABLE_FEATURE_CLEAN_UP +static void +freenames(void) +{ + int i; + struct name *np, *nextnp; + + for (i = 0; i < HTABSIZE; i++) { + for (np = namehead[i]; np; np = nextnp) { + nextnp = np->n_next; + free(np->n_name); + freerules(np->n_rule); + free(np); + } + } +} +#endif + +static void +freerules(struct rule *rp) +{ + struct rule *nextrp; + + for (; rp; rp = nextrp) { + nextrp = rp->r_next; + freedeps(rp->r_dep); + freecmds(rp->r_cmd); + free(rp); + } +} + +static void * +inc_ref(void *vp) +{ + if (vp) { + struct depend *dp = vp; + if (dp->d_refcnt == INT_MAX) + bb_die_memory_exhausted(); + dp->d_refcnt++; + } + return vp; +} + +/* + * Add a new rule to a target. This checks to see if commands already + * exist for the target. If flag is TRUE the target can have multiple + * rules with commands (double-colon rules). + * + * i) If the name is a special target and there are no prerequisites + * or commands to be added remove all prerequisites and commands. + * This is necessary when clearing a built-in inference rule. + * ii) If name is a special target and has commands, replace them. + * This is for redefining commands for an inference rule. + */ +static void +addrule(struct name *np, struct depend *dp, struct cmd *cp, int flag) +{ + struct rule *rp; + struct rule **rpp; + + // Can't mix single-colon and double-colon rules + if (!posix && (np->n_flag & N_TARGET)) { + if (!(np->n_flag & N_DOUBLE) != !flag) // like xor + error("inconsistent rules for target %s", np->n_name); + } + + // Clear out prerequisites and commands + if ((np->n_flag & N_SPECIAL) && !dp && !cp) { + if (strcmp(np->n_name, ".PHONY") == 0) + return; + freerules(np->n_rule); + np->n_rule = NULL; + return; + } + + if (cp && !(np->n_flag & N_DOUBLE) && getcmd(np)) { + // Handle the inference rule redefinition case + if ((np->n_flag & N_SPECIAL) && !dp) { + freerules(np->n_rule); + np->n_rule = NULL; + } else { + error("commands defined twice for target %s", np->n_name); + } + } + + rpp = &np->n_rule; + while (*rpp) + rpp = &(*rpp)->r_next; + + *rpp = rp = xzalloc(sizeof(struct rule)); + /*rp->r_next = NULL; - xzalloc did it */ + rp->r_dep = inc_ref(dp); + rp->r_cmd = inc_ref(cp); + + np->n_flag |= N_TARGET; + if (flag) + np->n_flag |= N_DOUBLE; +} + +/* + * Macro control for make + */ +static struct macro * +getmp(const char *name) +{ + struct macro *mp = macrohead[getbucket(name)]; + return (struct macro *)llist_find_str((llist_t *)mp, name); +} + +static int +is_valid_macro(const char *name) +{ + const char *s; + for (s = name; *s; ++s) { + // In POSIX mode only a limited set of characters are guaranteed + // to be allowed in macro names. + if (posix && !isfname(*s)) + return FALSE; + // As an extension allow anything that can get through the + // input parser, apart from the following. + if (*s == '=' || isblank(*s) || iscntrl(*s)) + return FALSE; + } + return TRUE; +} + +static void +setmacro(const char *name, const char *val, int level) +{ + struct macro *mp; + bool valid = level & M_VALID; + bool immediate = level & M_IMMEDIATE; + + level &= ~(M_IMMEDIATE | M_VALID); + mp = getmp(name); + if (mp) { + // Don't replace existing macro from a lower level + if (level > mp->m_level) + return; + + // Replace existing macro + free(mp->m_val); + } else { + // If not defined, allocate space for new + unsigned int bucket; + + if (!valid && !is_valid_macro(name)) + error("invalid macro name '%s'", name); + + bucket = getbucket(name); + mp = xzalloc(sizeof(struct macro)); + mp->m_next = macrohead[bucket]; + macrohead[bucket] = mp; + /* mp->m_flag = FALSE; - xzalloc did it */ + mp->m_name = xstrdup(name); + } + mp->m_immediate = immediate; + mp->m_level = level; + mp->m_val = xstrdup(val ? val : ""); +} + +#if ENABLE_FEATURE_CLEAN_UP +static void +freemacros(void) +{ + int i; + struct macro *mp, *nextmp; + + for (i = 0; i < HTABSIZE; i++) { + for (mp = macrohead[i]; mp; mp = nextmp) { + nextmp = mp->m_next; + free(mp->m_name); + free(mp->m_val); + free(mp); + } + } +} +#endif + +/* + * Get modification time of file or archive member + */ +static void FAST_FUNC +record_mtime(const file_header_t *file_header) +{ + ar_mtime = file_header->mtime; +} + +static time_t +artime(const char *archive, const char *member) +{ + archive_handle_t *archive_handle; + + ar_mtime = 0; + archive_handle = init_handle(); + archive_handle->src_fd = open(archive, O_RDONLY); + if (archive_handle->src_fd != -1) { + archive_handle->action_header = record_mtime; + archive_handle->filter = filter_accept_list; + llist_add_to_end(&archive_handle->accept, (void *)member); + unpack_ar_archive(archive_handle); + close(archive_handle->src_fd); + } + +#if ENABLE_FEATURE_AR_LONG_FILENAMES + free(archive_handle->ar__long_names); +#endif + llist_free(archive_handle->accept, NULL); + free(archive_handle->file_header); + free(archive_handle); + + return ar_mtime; +} + +/* + * If the name is of the form 'libname(member.o)' split it into its + * name and member parts and set the member pointer to point to the + * latter. Otherwise just take a copy of the name and don't alter + * the member pointer. + * + * In either case the return value is an allocated string which must + * be freed by the caller. + */ +static char * +splitlib(const char *name, char **member) +{ + char *s, *t; + size_t len; + + t = xstrdup(name); + s = strchr(t, '('); + if (s) { + // We have 'libname(member.o)' + *s++ = '\0'; + len = strlen(s); + if (len <= 1 || s[len - 1] != ')' || *t == '\0') + error("invalid name '%s'", name); + s[len - 1] = '\0'; + *member = s; + } + return t; +} + +/* + * Get the modification time of a file. Set it to 0 if the file + * doesn't exist. + */ +static void +modtime(struct name *np) +{ + char *name, *member = NULL; + struct stat info; + + name = splitlib(np->n_name, &member); + if (member) { + // Looks like library(member) + np->n_tim.tv_sec = artime(name, member); + np->n_tim.tv_nsec = 0; + } else if (stat(name, &info) < 0) { + if (errno != ENOENT) + bb_perror_msg("can't open %s", name); + np->n_tim.tv_sec = 0; + np->n_tim.tv_nsec = 0; + } else { + np->n_tim.tv_sec = info.st_mtim.tv_sec; + np->n_tim.tv_nsec = info.st_mtim.tv_nsec; + } + free(name); +} + +/* + * Control of the implicit suffix rules + */ + +/* + * Return a pointer to the suffix of a name (which may be the + * terminating NUL if there's no suffix). + */ +static char * +suffix(const char *name) +{ + char *p = strrchr(name, '.'); + return p ? p : (char *)name + strlen(name); +} + +/* + * Dynamic dependency. This routine applies the suffix rules + * to try and find a source and a set of rules for a missing + * target. NULL is returned on failure. On success the name of + * the implicit prerequisite is returned and the details are + * placed in the imprule structure provided by the caller. + */ +static struct name * +dyndep(struct name *np, struct rule *imprule) +{ + char *suff, *newsuff; + char *base, *name, *member; + struct name *xp; // Suffixes + struct name *sp; // Suffix rule + struct name *pp = NULL; // Implicit prerequisite + struct rule *rp; + struct depend *dp; + bool chain = FALSE; + + member = NULL; + name = splitlib(np->n_name, &member); + + suff = xstrdup(suffix(name)); + base = member ? member : name; + *suffix(base) = '\0'; + + xp = newname(".SUFFIXES"); + retry: + for (rp = xp->n_rule; rp; rp = rp->r_next) { + for (dp = rp->r_dep; dp; dp = dp->d_next) { + // Generate new suffix rule to try + newsuff = dp->d_name->n_name; + sp = findname(auto_concat(newsuff, suff)); + if (sp && sp->n_rule) { + // Generate a name for an implicit prerequisite + pp = newname(auto_concat(base, newsuff)); + if (!pp->n_tim.tv_sec) + modtime(pp); + if ((!chain && (pp->n_tim.tv_sec || getcmd(pp))) || + (chain && dyndep(pp, NULL))) { + // Prerequisite exists or we know how to make it + if (imprule) { + dp = NULL; + newdep(&dp, pp); + imprule->r_dep = dp; + imprule->r_cmd = sp->n_rule->r_cmd; + } + goto finish; + } + pp = NULL; + } + } + } + // If we didn't find an existing file or an explicit rule try + // again, this time looking for a chained inference rule. + if (!posix && !chain) { + chain = TRUE; + goto retry; + } + finish: + free(suff); + free(name); + return pp; +} + +#define RULES \ + ".SUFFIXES:.o .c .y .l .a .sh .f\n" \ + ".c.o:\n" \ + " $(CC) $(CFLAGS) -c $<\n" \ + ".f.o:\n" \ + " $(FC) $(FFLAGS) -c $<\n" \ + ".y.o:\n" \ + " $(YACC) $(YFLAGS) $<\n" \ + " $(CC) $(CFLAGS) -c y.tab.c\n" \ + " rm -f y.tab.c\n" \ + " mv y.tab.o $@\n" \ + ".y.c:\n" \ + " $(YACC) $(YFLAGS) $<\n" \ + " mv y.tab.c $@\n" \ + ".l.o:\n" \ + " $(LEX) $(LFLAGS) $<\n" \ + " $(CC) $(CFLAGS) -c lex.yy.c\n" \ + " rm -f lex.yy.c\n" \ + " mv lex.yy.o $@\n" \ + ".l.c:\n" \ + " $(LEX) $(LFLAGS) $<\n" \ + " mv lex.yy.c $@\n" \ + ".c.a:\n" \ + " $(CC) -c $(CFLAGS) $<\n" \ + " $(AR) $(ARFLAGS) $@ $*.o\n" \ + " rm -f $*.o\n" \ + ".f.a:\n" \ + " $(FC) -c $(FFLAGS) $<\n" \ + " $(AR) $(ARFLAGS) $@ $*.o\n" \ + " rm -f $*.o\n" \ + ".c:\n" \ + " $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<\n" \ + ".f:\n" \ + " $(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \ + ".sh:\n" \ + " cp $< $@\n" \ + " chmod a+x $@\n" + +#define MACROS \ + "CC=c99\n" \ + "CFLAGS=-O1\n" \ + "FC=fort77\n" \ + "FFLAGS=-O1\n" \ + "YACC=yacc\n" \ + "YFLAGS=\n" \ + "LEX=lex\n" \ + "LFLAGS=\n" \ + "AR=ar\n" \ + "ARFLAGS=-rv\n" \ + "LDFLAGS=\n" + +/* + * Read the built-in rules using a fake fgets-like interface. + */ +static char * +getrules(char *s, int size) +{ + char *r = s; + + if (rulepos == NULL) + rulepos = (RULES MACROS) + (norules ? sizeof(RULES) - 1 : 0); + + if (*rulepos == '\0') + return NULL; + + while (--size) { + if ((*r++ = *rulepos++) == '\n') + break; + } + *r = '\0'; + return s; +} + +/* + * Parse a makefile + */ + +/* + * Return a pointer to the next blank-delimited word or NULL if + * there are none left. + */ +static char * +gettok(char **ptr) +{ + char *p; + + while (isblank(**ptr)) // Skip blanks + (*ptr)++; + + if (**ptr == '\0') // Nothing after blanks + return NULL; + + p = *ptr; // Word starts here + + while (**ptr != '\0' && !isblank(**ptr)) + (*ptr)++; // Find end of word + + // Terminate token and move on unless already at end of string + if (**ptr != '\0') + *(*ptr)++ = '\0'; + + return(p); +} + +/* + * Skip over (possibly adjacent or nested) macro expansions. + */ +static char * +skip_macro(const char *s) +{ + while (*s && s[0] == '$') { + if (s[1] == '(' || s[1] == '{') { + char end = *++s == '(' ? ')' : '}'; + while (*s && *s != end) + s = skip_macro(s + 1); + if (*s == end) + ++s; + } else if (s[1] != '\0') { + s += 2; + } else { + break; + } + } + return (char *)s; +} + +/* + * Process each whitespace-separated word in the input string: + * + * - replace paths with their directory or filename part + * - replace prefixes and suffixes + * + * Returns an allocated string or NULL if the input is unmodified. + */ +static char * +modify_words(const char *val, int modifier, size_t lenf, + const char *find_pref, const char *repl_pref, + const char *find_suff, const char *repl_suff) +{ + char *s, *copy, *word, *sep, *buf = NULL; + size_t find_pref_len = 0, find_suff_len = 0; + + if (!modifier && !lenf) + return buf; + + if (find_pref) { + // get length of find prefix, e.g: src/ + find_pref_len = strlen(find_pref); + // get length of find suffix, e.g: .c + find_suff_len = lenf - find_pref_len - 1; + } + + s = copy = xstrdup(val); + while ((word = gettok(&s)) != NULL) { + if (modifier) { + sep = strrchr(word, '/'); + if (modifier == 'D') { + if (!sep) { + word[0] = '.'; // no '/', return "." + sep = word + 1; + } else if (sep == word) { + // '/' at start of word, return "/" + sep = word + 1; + } + // else terminate at separator + *sep = '\0'; + } else if (/* modifier == 'F' && */ sep) { + word = sep + 1; + } + } + if (lenf) { + size_t lenw = strlen(word); + // This code implements pattern macro expansions: + // https://austingroupbugs.net/view.php?id=519 + // + // find: % + // example: src/%.c + if (lenw >= lenf - 1 && find_pref) { + // If prefix and suffix of word match find_pref and + // find_suff, then do substitution. + if (strncmp(word, find_pref, find_pref_len) == 0 && + strcmp(word + lenw - find_suff_len, find_suff) == 0) { + // replace: [%] + // example: build/%.o or build/all.o (notice no %) + // If repl_suff is NULL, replace whole word with repl_pref. + if (!repl_suff) { + word = xstrdup(repl_pref); + } else { + word[lenw - find_suff_len] = '\0'; + word = xasprintf("%s%s%s", repl_pref, + word + find_pref_len, repl_suff); + } + word = auto_string(word); + } + } else if (lenw >= lenf && + strcmp(word + lenw - lenf, find_suff) == 0) { + word[lenw - lenf] = '\0'; + word = auto_concat(word, repl_suff); + } + } + buf = xappendword(buf, word); + } + free(copy); + return buf; +} + +/* + * Return a pointer to the next instance of a given character. Macro + * expansions are skipped so the ':' and '=' in $(VAR:.s1=.s2) aren't + * detected as separators for rules or macro definitions. + */ +static char * +find_char(const char *str, int c) +{ + const char *s; + + for (s = skip_macro(str); *s; s = skip_macro(s + 1)) { + if (*s == c) + return (char *)s; + } + return NULL; +} + +/* + * Recursively expand any macros in str to an allocated string. + */ +static char * +expand_macros(const char *str, int except_dollar) +{ + char *exp, *newexp, *s, *t, *p, *q, *name; + char *find, *replace, *modified; + char *expval, *expfind, *find_suff, *repl_suff; + char *find_pref = NULL, *repl_pref = NULL; + size_t lenf; + char modifier; + struct macro *mp; + + exp = xstrdup(str); + for (t = exp; *t; t++) { + if (*t == '$') { + if (t[1] == '\0') { + break; + } + if (t[1] == '$' && except_dollar) { + t++; + continue; + } + // Need to expand a macro. Find its extent (s to t inclusive) + // and take a copy of its content. + s = t; + t++; + if (*t == '{' || *t == '(') { + t = find_char(t, *t == '{' ? '}' : ')'); + if (t == NULL) + error("unterminated variable '%s'", s); + name = xstrndup(s + 2, t - s - 2); + } else { + name = xzalloc(2); + name[0] = *t; + /*name[1] = '\0'; - xzalloc did it */ + } + + // Only do suffix replacement or pattern macro expansion + // if both ':' and '=' are found. This is indicated by + // lenf != 0. + expfind = NULL; + find_suff = repl_suff = NULL; + lenf = 0; + if ((find = find_char(name, ':'))) { + *find++ = '\0'; + expfind = expand_macros(find, FALSE); + if ((replace = find_char(expfind, '='))) { + *replace++ = '\0'; + lenf = strlen(expfind); + if (!posix && (find_suff = strchr(expfind, '%'))) { + find_pref = expfind; + repl_pref = replace; + *find_suff++ = '\0'; + if ((repl_suff = strchr(replace, '%'))) + *repl_suff++ = '\0'; + } else { + find_suff = expfind; + repl_suff = replace; + } + } + } + + p = q = name; + // If not in POSIX mode expand macros in the name. + if (!posix) { + char *expname = expand_macros(name, FALSE); + free(name); + name = expname; + } else { + // Skip over nested expansions in name + do { + *q++ = *p; + } while ((p = skip_macro(p + 1)) && *p); + } + + // The internal macros support 'D' and 'F' modifiers + modifier = '\0'; + switch (name[0]) { + case '^': + if (posix) + break; + // fall through + case '@': case '%': case '?': case '<': case '*': + if ((name[1] == 'D' || name[1] == 'F') && name[2] == '\0') { + modifier = name[1]; + name[1] = '\0'; + } + break; + } + + modified = NULL; + if ((mp = getmp(name))) { + // Recursive expansion + if (mp->m_flag) + error("recursive macro %s", name); + // Note if we've expanded $(MAKE) + if (strcmp(name, "MAKE") == 0) + opts |= OPT_make; + mp->m_flag = TRUE; + expval = expand_macros(mp->m_val, FALSE); + mp->m_flag = FALSE; + modified = modify_words(expval, modifier, lenf, + find_pref, repl_pref, find_suff, repl_suff); + if (modified) + free(expval); + else + modified = expval; + } + free(name); + free(expfind); + + if (modified && *modified) { + // The text to be replaced by the macro expansion is + // from s to t inclusive. + *s = '\0'; + newexp = xasprintf("%s%s%s", exp, modified, t + 1); + t = newexp + (s - exp) + strlen(modified) - 1; + free(exp); + exp = newexp; + } else { + // Macro wasn't expanded or expanded to nothing. + // Close the space occupied by the macro reference. + q = t + 1; + t = s - 1; + while ((*s++ = *q++)) + continue; + } + free(modified); + } + } + return exp; +} + +/* + * Process a non-command line + */ +static char * +process_line(char *s) +{ + char *r, *t; + + // Skip leading blanks + while (isblank(*s)) + s++; + r = s; + + // Strip comment + t = strchr(s, '#'); + if (t) + *t = '\0'; + + // Replace escaped newline and any leading white space on the + // following line with a single space. Stop processing at a + // non-escaped newline. + for (t = s; *s && *s != '\n'; ) { + if (s[0] == '\\' && s[1] == '\n') { + s += 2; + while (isspace(*s)) + ++s; + *t++ = ' '; + } else { + *t++ = *s++; + } + } + *t = '\0'; + + return r; +} + +enum { + INITIAL = 0, + SKIP_LINE = 1 << 0, + EXPECT_ELSE = 1 << 1, + GOT_MATCH = 1 << 2 +}; + +#define IFDEF 0 +#define IFNDEF 1 +#define ELSE 0 +#define ENDIF 1 + +/* + * Process conditional directives and return TRUE if the current line + * should be skipped. + */ +static int +skip_line(const char *str1) +{ + char *copy, *q, *token, *next_token; + bool new_level = TRUE; + // Default is to return skip flag for current level + int ret = cstate[clevel] & SKIP_LINE; + int key; + + if (*str1 == '\t') + return ret; + + copy = xstrdup(str1); + q = process_line(copy); + if ((token = gettok(&q)) != NULL) { + next_token = gettok(&q); + + switch (index_in_strings("else\0endif\0", token)) { + case ENDIF: + if (next_token != NULL) + error_unexpected("text"); + if (clevel == 0) + error_unexpected(token); + --clevel; + ret = TRUE; + goto end; + case ELSE: + if (!(cstate[clevel] & EXPECT_ELSE)) + error_unexpected(token); + + // If an earlier condition matched we'll now skip lines. + // If not we don't, though an 'else if' may override this. + if ((cstate[clevel] & GOT_MATCH)) + cstate[clevel] |= SKIP_LINE; + else + cstate[clevel] &= ~SKIP_LINE; + + if (next_token == NULL) { + // Simple else with no conditional directive + cstate[clevel] &= ~EXPECT_ELSE; + ret = TRUE; + goto end; + } else { + // A conditional directive is now required ('else if'). + token = next_token; + next_token = gettok(&q); + new_level = FALSE; + } + break; + } + + key = index_in_strings("ifdef\0ifndef\0", token); + if (key != -1) { + if (next_token != NULL && gettok(&q) == NULL) { + if (new_level) { + // Start a new level. + if (clevel == IF_MAX) + error("nesting too deep"); + ++clevel; + cstate[clevel] = EXPECT_ELSE | SKIP_LINE; + // If we were skipping lines at the previous level + // we need to continue doing that unconditionally + // at the new level. + if ((cstate[clevel - 1] & SKIP_LINE)) + cstate[clevel] |= GOT_MATCH; + } + + if (!(cstate[clevel] & GOT_MATCH)) { + char *t = expand_macros(next_token, FALSE); + struct macro *mp = getmp(t); + int match = mp != NULL && mp->m_val[0] != '\0'; + + if (key == IFNDEF) + match = !match; + if (match) { + cstate[clevel] &= ~SKIP_LINE; + cstate[clevel] |= GOT_MATCH; + } + free(t); + } + } else { + error("invalid condition"); + } + ret = TRUE; + } else if (!new_level) { + error("missing conditional"); + } + } + end: + free(copy); + return ret; +} + +/* + * If fd is NULL read the built-in rules. Otherwise read from the + * specified file descriptor. + */ +static char * +make_fgets(char *s, int size, FILE *fd) +{ + return fd ? fgets(s, size, fd) : getrules(s, size); +} + +/* + * Read a newline-terminated line into an allocated string. + * Backslash-escaped newlines don't terminate the line. + * Ignore comment lines. Return NULL on EOF. + */ +static char * +readline(FILE *fd) +{ + char *p, *str; + int pos = 0; + int len = 256; + + str = xmalloc(len); + + for (;;) { + if (make_fgets(str + pos, len - pos, fd) == NULL) { + if (pos) + return str; + free(str); + return NULL; // EOF + } + + if ((p = strchr(str + pos, '\n')) == NULL) { + // Need more room + pos = len - 1; + len += 256; + str = xrealloc(str, len); + continue; + } + lineno++; + +#if ENABLE_PLATFORM_MINGW32 + // Remove CR before LF + if (p != str && p[-1] == '\r') { + p[-1] = '\n'; + *p-- = '\0'; + } +#endif + // Keep going if newline has been escaped + if (p != str && p[-1] == '\\') { + pos = p - str + 1; + continue; + } + dispno = lineno; + + // Check for comment lines and lines that are conditionally skipped. + p = str; + while (isblank(*p)) + p++; + + if (*p != '\n' && *str != '#' && (posix || !skip_line(str))) + return str; + + pos = 0; + } +} + +/* + * Return TRUE if the argument is a known suffix. + */ +static int +is_suffix(const char *s) +{ + struct name *np; + struct rule *rp; + struct depend *dp; + + np = newname(".SUFFIXES"); + for (rp = np->n_rule; rp; rp = rp->r_next) { + for (dp = rp->r_dep; dp; dp = dp->d_next) { + if (strcmp(s, dp->d_name->n_name) == 0) { + return TRUE; + } + } + } + return FALSE; +} + +#define T_NORMAL 0 +#define T_SPECIAL 1 +#define T_INFERENCE 2 +/* + * Determine if the argument is a special target and return a set + * of flags indicating its properties. + */ +static int +target_type(char *s) +{ + char *sfx; + int ret; + static const char *s_name[] = { + ".DEFAULT", + ".POSIX", + ".IGNORE", + ".PRECIOUS", + ".SILENT", + ".SUFFIXES", + ".PHONY", + }; + + if (*s != '.') + return T_NORMAL; + + // Check for one of the known special targets + for (ret = 0; ret < ARRAY_SIZE(s_name); ret++) + if (strcmp(s_name[ret], s) == 0) + return T_SPECIAL; + + // Check for an inference rule + ret = T_NORMAL; + sfx = suffix(s); + if (is_suffix(sfx)) { + if (s == sfx) { // Single suffix rule + ret = T_INFERENCE; + } else { + // Suffix is valid, check that prefix is too + *sfx = '\0'; + if (is_suffix(s)) + ret = T_INFERENCE; + *sfx = '.'; + } + } + return ret; +} + +static int +ends_with_bracket(const char *s) +{ + return last_char_is(s, ')') != NULL; +} + +/* + * Process a command line + */ +static char * +process_command(char *s) +{ + char *t, *u; + + // Remove tab following escaped newline. Stop processing at a + // non-escaped newline. + for (t = u = s; *u && *u != '\n'; u++) { + if (u[0] == '\\' && u[1] == '\n' && u[2] == '\t') { + *t++ = *u++; + *t++ = *u++; + } else { + *t++ = *u; + } + } + *t = '\0'; + return s; +} + +static char * +run_command(const char *cmd) +{ + FILE *fd; + char *s, *val = NULL; + char buf[256]; + size_t len = 0, nread; + + if ((fd = popen(cmd, "r")) == NULL) + return val; + + for (;;) { + nread = fread(buf, 1, sizeof(buf), fd); + if (nread == 0) + break; + + val = xrealloc(val, len + nread + 1); + memcpy(val + len, buf, nread); + len += nread; + val[len] = '\0'; + } + pclose(fd); + + if (val) { +#if ENABLE_PLATFORM_MINGW32 + len = remove_cr(val, len + 1) - 1; + if (len == 0) { + free(val); + return NULL; + } +#endif + // Remove one newline from the end (BSD compatibility) + if (val[len - 1] == '\n') + val[len - 1] = '\0'; + // Other newlines are changed to spaces + for (s = val; *s; ++s) { + if (*s == '\n') + *s = ' '; + } + } + return val; +} + +/* + * Check for an unescaped wildcard character + */ +static int wildchar(const char *p) +{ + while (*p) { + switch (*p) { + case '?': + case '*': + case '[': + return 1; + case '\\': + if (p[1] != '\0') + ++p; + break; + } + ++p; + } + return 0; +} + +/* + * Expand any wildcards in a pattern. Return TRUE if a match is + * found, in which case the caller should call globfree() on the + * glob_t structure. + */ +static int +wildcard(char *p, glob_t *gd) +{ + int ret; + char *s; + + // Don't call glob() if there are no wildcards. + if (!wildchar(p)) { + nomatch: + // Remove backslashes from the name. + for (s = p; *p; ++p) { + if (*p == '\\' && p[1] != '\0') + continue; + *s++ = *p; + } + *s = '\0'; + return 0; + } + + memset(gd, 0, sizeof(*gd)); + ret = glob(p, GLOB_NOSORT, NULL, gd); + if (ret == GLOB_NOMATCH) { + globfree(gd); + goto nomatch; + } else if (ret != 0) { + error("glob error for '%s'", p); + } + return 1; +} + +/* + * Parse input from the makefile and construct a tree structure of it. + */ +static void +input(FILE *fd, int ilevel) +{ + char *p, *q, *s, *a, *str, *expanded, *copy; + char *str1, *str2; + struct name *np; + struct depend *dp; + struct cmd *cp; + int startno, count; + bool semicolon_cmd, seen_inference; + uint8_t old_clevel = clevel; + bool dbl; + char *lib = NULL; + glob_t gd; + int nfile, i; + char **files; + bool minus; + + lineno = 0; + str1 = readline(fd); + while (str1) { + str2 = NULL; + if (*str1 == '\t') // Command without target + error("command not allowed here"); + + // Newlines and comments are handled differently in command lines + // and other types of line. Take a copy of the current line before + // processing it as a non-command line in case it contains a + // rule with a command line. That is, a line of the form: + // + // target: prereq; command + // + copy = xstrdup(str1); + str = process_line(str1); + + // Check for an include line + minus = !posix && *str == '-'; + p = str + minus; + if (strncmp(p, "include", 7) == 0 && isblank(p[7])) { + const char *old_makefile = makefile; + int old_lineno = lineno; + + if (ilevel > 16) + error("too many includes"); + + q = expanded = expand_macros(p + 7, FALSE); + while ((p = gettok(&q)) != NULL) { + FILE *ifd; + + if (!posix) { + // Try to create include file or bring it up-to-date + opts |= OPT_include; + make(newname(p), 1); + opts &= ~OPT_include; + } + if ((ifd = fopen(p, "r")) == NULL) { + if (!minus) + error("can't open include file '%s'", p); + } else { + makefile = p; + input(ifd, ilevel + 1); + fclose(ifd); + } + if (posix) + break; + } + if (posix && (p == NULL || gettok(&q))) + error("one include file per line"); + + makefile = old_makefile; + lineno = old_lineno; + goto end_loop; + } + + // Check for a macro definition + q = find_char(str, '='); + if (q != NULL) { + int level = (useenv || fd == NULL) ? 4 : 3; + char *newq = NULL; + char eq = '\0'; + + if (q - 1 > str) { + switch (q[-1]) { + case ':': + // '::=' and ':::=' are from POSIX 202X. + if (!posix && q - 2 > str && q[-2] == ':') { + if (q - 3 > str && q[-3] == ':') { + eq = 'B'; // BSD-style ':=' + q[-3] = '\0'; + } else { + eq = ':'; // GNU-style ':=' + q[-2] = '\0'; + } + break; + } + case '!': + // ':=' and '!=' are non-POSIX extensions. + case '+': + case '?': + // '+=' and '?=' are from POSIX 202X. + if (posix) + break; + eq = q[-1]; + q[-1] = '\0'; + break; + } + } + *q++ = '\0'; // Separate name and value + while (isblank(*q)) + q++; + if ((p = strrchr(q, '\n')) != NULL) + *p = '\0'; + + // Expand left-hand side of assignment + p = expanded = expand_macros(str, FALSE); + if ((a = gettok(&p)) == NULL || gettok(&p)) + error("invalid macro assignment"); + + if (eq == ':') { + // GNU-style ':='. Expand right-hand side of assignment. + // Macro is of type immediate-expansion. + q = newq = expand_macros(q, FALSE); + level |= M_IMMEDIATE; + } + else if (eq == 'B') { + // BSD-style ':='. Expand right-hand side of assignment, + // though not '$$'. Macro is of type delayed-expansion. + q = newq = expand_macros(q, TRUE); + } else if (eq == '?' && getmp(a) != NULL) { + // Skip assignment if macro is already set + goto end_loop; + } else if (eq == '+') { + // Append to current value + struct macro *mp = getmp(a); + char *rhs; + newq = mp && mp->m_val[0] ? xstrdup(mp->m_val) : NULL; + if (mp && mp->m_immediate) { + // Expand right-hand side of assignment (GNU make + // compatibility) + rhs = expand_macros(q, FALSE); + level |= M_IMMEDIATE; + } else { + rhs = q; + } + newq = xappendword(newq, rhs); + if (rhs != q) + free(rhs); + q = newq; + } + else if (eq == '!') { + char *cmd = expand_macros(q, FALSE); + q = newq = run_command(cmd); + free(cmd); + } + setmacro(a, q, level); + free(newq); + goto end_loop; + } + + // If we get here it must be a target rule + p = expanded = expand_macros(str, FALSE); + + // Look for colon separator + q = find_char(p, ':'); + if (q == NULL) + error("expected separator"); + + *q++ = '\0'; // Separate targets and prerequisites + + // Double colon + dbl = !posix && *q == ':'; + if (dbl) + q++; + + // Look for semicolon separator + cp = NULL; + s = strchr(q, ';'); + if (s) { + *s = '\0'; + // Retrieve command from copy of line + if ((p = find_char(copy, ':')) && (p = strchr(p, ';'))) + newcmd(&cp, process_command(p + 1)); + } + semicolon_cmd = cp != NULL; + + // Create list of prerequisites + dp = NULL; + while (((p = gettok(&q)) != NULL)) { + char *newp = NULL; + + if (!posix) { + // Allow prerequisites of form library(member1 member2). + // Leading and trailing spaces in the brackets are skipped. + if (!lib) { + s = strchr(p, '('); + if (s && !ends_with_bracket(s) && strchr(q, ')')) { + // Looks like an unterminated archive member + // with a terminator later on the line. + lib = p; + if (s[1] != '\0') { + p = newp = auto_concat(lib, ")"); + s[1] = '\0'; + } else { + continue; + } + } + } else if (ends_with_bracket(p)) { + if (*p != ')') + p = newp = auto_concat(lib, p); + lib = NULL; + if (newp == NULL) + continue; + } else { + p = newp = auto_string(xasprintf("%s%s)", lib, p)); + } + } + + // If not in POSIX mode expand wildcards in the name. + nfile = 1; + files = &p; + if (!posix && wildcard(p, &gd)) { + nfile = gd.gl_pathc; + files = gd.gl_pathv; + } + for (i = 0; i < nfile; ++i) { + np = newname(files[i]); + newdep(&dp, np); + } + if (files != &p) + globfree(&gd); + free(newp); + } + lib = NULL; + + // Create list of commands + startno = dispno; + while ((str2 = readline(fd)) && *str2 == '\t') { + newcmd(&cp, process_command(str2)); + free(str2); + } + dispno = startno; + + // Create target names and attach rule to them + q = expanded; + count = 0; + seen_inference = FALSE; + while ((p = gettok(&q)) != NULL) { + // If not in POSIX mode expand wildcards in the name. + nfile = 1; + files = &p; + if (!posix && wildcard(p, &gd)) { + nfile = gd.gl_pathc; + files = gd.gl_pathv; + } + for (i = 0; i < nfile; ++i) { + int ttype = target_type(files[i]); + + np = newname(files[i]); + if (ttype != T_NORMAL) { + if (ttype == T_INFERENCE && posix) { + if (semicolon_cmd) + error_in_inference_rule("'; command'"); + seen_inference = TRUE; + } + np->n_flag |= N_SPECIAL; + } else if (!firstname) { + firstname = np; + } + addrule(np, dp, cp, dbl); + count++; + } + if (files != &p) + globfree(&gd); + } + if (seen_inference && count != 1) + error_in_inference_rule("multiple targets"); + + // Prerequisites and commands will be unused if there were + // no targets. Avoid leaking memory. + if (count == 0) { + freedeps(dp); + freecmds(cp); + } + end_loop: + free(str1); + dispno = lineno; + str1 = str2 ? str2 : readline(fd); + free(copy); + free(expanded); +#if ENABLE_FEATURE_MAKE_POSIX + if (!seen_first && fd) { + if (findname(".POSIX")) { + // The first non-comment line from a real makefile + // defined the .POSIX special target. + setenv("PDPMAKE_POSIXLY_CORRECT", "", 1); + posix = TRUE; + } + seen_first = TRUE; + } +#endif + } + // Conditionals aren't allowed to span files + if (clevel != old_clevel) + error("invalid conditional"); +} + +static void +remove_target(void) +{ + if (!dryrun && !print && !precious && + target && !(target->n_flag & (N_PRECIOUS | N_PHONY)) && + unlink(target->n_name) == 0) { + bb_error_msg("'%s' removed", target->n_name); + } +} + +/* + * Do commands to make a target + */ +static int +docmds(struct name *np, struct cmd *cp) +{ + int estat = 0; // 0 exit status is success + char *q, *command; + + for (; cp; cp = cp->c_next) { + uint8_t ssilent, signore, sdomake; + + opts &= ~OPT_make; // We want to know if $(MAKE) is expanded + q = command = expand_macros(cp->c_cmd, FALSE); + ssilent = silent || (np->n_flag & N_SILENT) || dotouch; + signore = ignore || (np->n_flag & N_IGNORE); + sdomake = (!dryrun || doinclude || domake) && !dotouch; + for (;;) { + if (*q == '@') // Specific silent + ssilent = TRUE + 1; + else if (*q == '-') // Specific ignore + signore = TRUE; + else if (*q == '+') // Specific domake + sdomake = TRUE + 1; + else + break; + q++; + } + + if (sdomake > TRUE) { + // '+' must not override '@' or .SILENT + if (ssilent != TRUE + 1 && !(np->n_flag & N_SILENT)) + ssilent = FALSE; + } else if (!sdomake) + ssilent = dotouch; + + if (!ssilent) + puts(q); + + if (sdomake) { + // Get the shell to execute it + int status; + char *cmd = !signore ? auto_concat("set -e;", q) : q; + + target = np; + status = system(cmd); + target = NULL; + // If this command was being run to create an include file + // or bring it up-to-date errors should be ignored and a + // failure status returned. + if (status == -1 && !doinclude) { + error("couldn't execute '%s'", q); + } else if (status != 0 && !signore) { + if (!doinclude) + bb_error_msg("failed to build '%s'", np->n_name); +#if !ENABLE_PLATFORM_MINGW32 + if (status == SIGINT || status == SIGQUIT) +#endif + remove_target(); + if (errcont || doinclude) + estat = 1; // 1 exit status is failure + else + exit(status); + } + } + free(command); + } + return estat; +} + +/* + * Update the modification time of a file to now. + */ +static void +touch(struct name *np) +{ + if (dryrun || !silent) + printf("touch %s\n", np->n_name); + + if (!dryrun) { + const struct timespec timebuf[2] = {{0, UTIME_NOW}, {0, UTIME_NOW}}; + + if (utimensat(AT_FDCWD, np->n_name, timebuf, 0) < 0) { + if (errno == ENOENT) { + int fd = open(np->n_name, O_RDWR | O_CREAT, 0666); + if (fd >= 0) { + close(fd); + return; + } + } + bb_perror_msg("touch %s failed", np->n_name); + } + } +} + +static int +make1(struct name *np, struct cmd *cp, char *oodate, char *allsrc, + struct name *implicit) +{ + int estat = 0; // 0 exit status is success + char *name, *member = NULL, *base; + + name = splitlib(np->n_name, &member); + setmacro("?", oodate, 0 | M_VALID); + if (!posix) + setmacro("^", allsrc, 0 | M_VALID); + setmacro("%", member, 0 | M_VALID); + setmacro("@", name, 0 | M_VALID); + if (implicit) { + setmacro("<", implicit->n_name, 0 | M_VALID); + base = member ? member : name; + *suffix(base) = '\0'; + setmacro("*", base, 0 | M_VALID); + } + free(name); + + estat = docmds(np, cp); + if (dotouch && !(np->n_flag & N_PHONY)) + touch(np); + + return estat; +} + +/* + * Determine if the modification time of a target, t, is less than + * that of a prerequisite, p. If the tv_nsec member of either is + * exactly 0 we assume (possibly incorrectly) that the time resolution + * is 1 second and only compare tv_sec values. + */ +static int +timespec_le(const struct timespec *t, const struct timespec *p) +{ + if (t->tv_nsec == 0 || p->tv_nsec == 0) + return t->tv_sec <= p->tv_sec; + else if (t->tv_sec < p->tv_sec) + return TRUE; + else if (t->tv_sec == p->tv_sec) + return t->tv_nsec <= p->tv_nsec; + return FALSE; +} + +/* + * Return the greater of two struct timespecs + */ +static const struct timespec * +timespec_max(const struct timespec *t, const struct timespec *p) +{ + return timespec_le(t, p) ? p : t; +} + +/* + * Recursive routine to make a target. + */ +static int +make(struct name *np, int level) +{ + struct depend *dp; + struct rule *rp; + struct name *impdep = NULL; // implicit prerequisite + struct rule imprule; + struct cmd *sc_cmd = NULL; // commands for single-colon rule + char *oodate = NULL; + char *allsrc = NULL; + struct timespec dtim = {1, 0}; + bool didsomething = 0; + bool estat = 0; // 0 exit status is success + + if (np->n_flag & N_DONE) + return 0; + if (np->n_flag & N_DOING) + error("circular dependency for %s", np->n_name); + np->n_flag |= N_DOING; + + if (!np->n_tim.tv_sec) + modtime(np); // Get modtime of this file + + if (!(np->n_flag & N_DOUBLE)) { + // Find the commands needed for a single-colon rule, using + // an inference rule or .DEFAULT rule if necessary + sc_cmd = getcmd(np); + if (!sc_cmd) { + impdep = dyndep(np, &imprule); + if (impdep) { + sc_cmd = imprule.r_cmd; + addrule(np, imprule.r_dep, NULL, FALSE); + } + } + + // As a last resort check for a default rule + if (!(np->n_flag & N_TARGET) && np->n_tim.tv_sec == 0) { + sc_cmd = getcmd(findname(".DEFAULT")); + if (!sc_cmd) { + if (doinclude) + return 1; + error("don't know how to make %s", np->n_name); + } + impdep = np; + } + } + else { + // If any double-colon rule has no commands we need + // an inference rule + for (rp = np->n_rule; rp; rp = rp->r_next) { + if (!rp->r_cmd) { + impdep = dyndep(np, &imprule); + if (!impdep) { + if (doinclude) + return 1; + error("don't know how to make %s", np->n_name); + } + break; + } + } + } + + // Reset flag to detect duplicate prerequisites + if (!quest && !(np->n_flag & N_DOUBLE)) { + for (rp = np->n_rule; rp; rp = rp->r_next) { + for (dp = rp->r_dep; dp; dp = dp->d_next) { + dp->d_name->n_flag &= ~N_MARK; + } + } + } + + for (rp = np->n_rule; rp; rp = rp->r_next) { + struct name *locdep = NULL; + + // Each double-colon rule is handled separately. + if ((np->n_flag & N_DOUBLE)) { + // If the rule has no commands use the inference rule. + if (!rp->r_cmd) { + locdep = impdep; + imprule.r_dep->d_next = rp->r_dep; + rp->r_dep = imprule.r_dep; + rp->r_cmd = imprule.r_cmd; + } + // A rule with no prerequisities is executed unconditionally. + if (!rp->r_dep) + dtim = np->n_tim; + // Reset flag to detect duplicate prerequisites + if (!quest) { + for (dp = rp->r_dep; dp; dp = dp->d_next) { + dp->d_name->n_flag &= ~N_MARK; + } + } + } + for (dp = rp->r_dep; dp; dp = dp->d_next) { + // Make prerequisite + estat |= make(dp->d_name, level + 1); + + // Make strings of out-of-date prerequisites (for $?) + // and all prerequisites (for $^). But not if we were + // invoked with -q. + if (!quest + // Skip duplicate entries. + && (posix || !(dp->d_name->n_flag & N_MARK)) + ) { + if (timespec_le(&np->n_tim, &dp->d_name->n_tim)) { + oodate = xappendword(oodate, dp->d_name->n_name); + } + allsrc = xappendword(allsrc, dp->d_name->n_name); + dp->d_name->n_flag |= N_MARK; + } + dtim = *timespec_max(&dtim, &dp->d_name->n_tim); + } + if ((np->n_flag & N_DOUBLE)) { + if (!quest && ((np->n_flag & N_PHONY) || + timespec_le(&np->n_tim, &dtim))) { + if (estat == 0) { + estat = make1(np, rp->r_cmd, oodate, allsrc, locdep); + dtim = (struct timespec){1, 0}; + didsomething = 1; + } + free(oodate); + oodate = NULL; + } + free(allsrc); + allsrc = NULL; + if (locdep) { + rp->r_dep = rp->r_dep->d_next; + rp->r_cmd = NULL; + } + } + } + if ((np->n_flag & N_DOUBLE) && impdep) + free(imprule.r_dep); + + np->n_flag |= N_DONE; + np->n_flag &= ~N_DOING; + + if (quest) { + if (timespec_le(&np->n_tim, &dtim)) { + clock_gettime(CLOCK_REALTIME, &np->n_tim); + return 1; // 1 means rebuild is needed + } + } else if (!(np->n_flag & N_DOUBLE) && + ((np->n_flag & N_PHONY) || (timespec_le(&np->n_tim, &dtim)))) { + if (estat == 0) { + if (!sc_cmd) { + if (!doinclude) + bb_error_msg("nothing to be done for %s", np->n_name); + } else { + estat = make1(np, sc_cmd, oodate, allsrc, impdep); + clock_gettime(CLOCK_REALTIME, &np->n_tim); + } + } else if (!doinclude) { + bb_error_msg("'%s' not built due to errors", np->n_name); + } + free(oodate); + } else if (didsomething) { + clock_gettime(CLOCK_REALTIME, &np->n_tim); + } else if (level == 0) { + printf("%s: '%s' is up to date\n", applet_name, np->n_name); + } + free(allsrc); + return estat; +} + +/* + * Check structures for make. + */ + +static void +print_name(struct name *np) +{ + if (np == firstname) + printf("# default target\n"); + printf("%s:", np->n_name); + if ((np->n_flag & N_DOUBLE)) + putchar(':'); +} + +static void +print_prerequisites(struct rule *rp) +{ + struct depend *dp; + + for (dp = rp->r_dep; dp; dp = dp->d_next) + printf(" %s", dp->d_name->n_name); +} + +static void +print_commands(struct rule *rp) +{ + struct cmd *cp; + + for (cp = rp->r_cmd; cp; cp = cp->c_next) + printf("\t%s\n", cp->c_cmd); +} + +static void +print_details(void) +{ + int i; + struct macro *mp; + struct name *np; + struct rule *rp; + + for (i = 0; i < HTABSIZE; i++) + for (mp = macrohead[i]; mp; mp = mp->m_next) + printf("%s = %s\n", mp->m_name, mp->m_val); + putchar('\n'); + + for (i = 0; i < HTABSIZE; i++) { + for (np = namehead[i]; np; np = np->n_next) { + if (!(np->n_flag & N_DOUBLE)) { + print_name(np); + for (rp = np->n_rule; rp; rp = rp->r_next) { + print_prerequisites(rp); + } + putchar('\n'); + + for (rp = np->n_rule; rp; rp = rp->r_next) { + print_commands(rp); + } + putchar('\n'); + } else { + for (rp = np->n_rule; rp; rp = rp->r_next) { + print_name(np); + print_prerequisites(rp); + putchar('\n'); + + print_commands(rp); + putchar('\n'); + } + } + } + } +} + +/* + * Process options from an argv array. If from_env is non-zero we're + * handling options from MAKEFLAGS so skip '-C', '-f' and '-p'. + */ +static uint32_t +process_options(char **argv, int from_env) +{ + uint32_t flags; + + flags = getopt32(argv, "^" OPTSTR1 OPTSTR2 "\0k-S:S-k", + &numjobs, &makefiles, &dirs); + if (from_env && (flags & (OPT_C | OPT_f | OPT_p))) + error("invalid MAKEFLAGS"); + if (posix && (flags & OPT_C)) + error("-C not allowed"); + + return flags; +} + +/* + * Split the contents of MAKEFLAGS into an argv array. If the return + * value (call it fargv) isn't NULL the caller should free fargv[1] and + * fargv. + */ +static char ** +expand_makeflags(void) +{ + const char *m, *makeflags = getenv("MAKEFLAGS"); + char *p, *argstr; + int argc; + char **argv; + + if (makeflags == NULL) + return NULL; + + while (isblank(*makeflags)) + makeflags++; + + if (*makeflags == '\0') + return NULL; + + p = argstr = xzalloc(strlen(makeflags) + 2); + + // If MAKEFLAGS doesn't start with a hyphen, doesn't look like + // a macro definition and only contains valid option characters, + // add a hyphen. + argc = 3; + if (makeflags[0] != '-' && strchr(makeflags, '=') == NULL) { + if (strspn(makeflags, OPTSTR1) != strlen(makeflags)) + error("invalid MAKEFLAGS"); + *p++ = '-'; + } else { + // MAKEFLAGS may need to be split, estimate size of argv array. + for (m = makeflags; *m; ++m) { + if (isblank(*m)) + argc++; + } + } + + argv = xzalloc(argc * sizeof(char *)); + argc = 0; + argv[argc++] = (char *)applet_name; + argv[argc++] = argstr; + + // Copy MAKEFLAGS into argstr, splitting at non-escaped blanks. + m = makeflags; + do { + if (*m == '\\' && m[1] != '\0') + m++; // Skip backslash, copy next character unconditionally. + else if (isblank(*m)) { + // Terminate current argument and start a new one. + /* *p = '\0'; - xzalloc did it */ + argv[argc++] = ++p; + do { + m++; + } while (isblank(*m)); + continue; + } + *p++ = *m++; + } while (*m != '\0'); + /* *p = '\0'; - xzalloc did it */ + /* argv[argc] = NULL; - and this */ + + return argv; +} + +// These macros require special treatment +#define MAKEFLAGS_SHELL "MAKEFLAGS\0SHELL\0" +#define MAKEFLAGS 0 +#define SHELL 1 + +/* + * Instantiate all macros in an argv-style array of pointers. Stop + * processing at the first string that doesn't contain an equal sign. + */ +static char ** +process_macros(char **argv, int level) +{ + char *p; + + while (*argv && (p = strchr(*argv, '=')) != NULL) { + int immediate = 0; + + if (p - 2 > *argv && p[-1] == ':' && p[-2] == ':') { + if (posix) + error("invalid macro assignment"); + immediate = M_IMMEDIATE; + p[-2] = '\0'; + } else + *p = '\0'; + if (level != 3 || index_in_strings(MAKEFLAGS_SHELL, *argv) < 0) { + if (immediate) { + char *exp = expand_macros(p + 1, FALSE); + setmacro(*argv, exp, level | immediate); + free(exp); + } else { + setmacro(*argv, p + 1, level); + } + } + *p = '='; + if (immediate) + p[-2] = ':'; + + argv++; + } + return argv; +} + +/* + * Update the MAKEFLAGS macro and environment variable to include any + * command line options that don't have their default value (apart from + * -f, -p and -S). Also add any macros defined on the command line or + * by the MAKEFLAGS environment variable (apart from MAKEFLAGS itself). + * Add macros that were defined on the command line to the environment. + */ +static void +update_makeflags(void) +{ + int i; + char optbuf[] = "-?"; + char *makeflags = NULL; + char *macro, *s; + const char *t; + struct macro *mp; + + t = OPTSTR1; + for (i = 0; *t; t++) { + if (*t == ':' || *t == '+') + continue; + if ((opts & OPT_MASK & (1 << i))) { + optbuf[1] = *t; + makeflags = xappendword(makeflags, optbuf); + if (*t == 'j') { + s = auto_string(xasprintf("%d", numjobs)); + makeflags = xappendword(makeflags, s); + } + } + i++; + } + + for (i = 0; i < HTABSIZE; ++i) { + for (mp = macrohead[i]; mp; mp = mp->m_next) { + if (mp->m_level == 1 || mp->m_level == 2) { + int idx = index_in_strings(MAKEFLAGS_SHELL, mp->m_name); + if (idx == MAKEFLAGS) + continue; + macro = xzalloc(strlen(mp->m_name) + 2 * strlen(mp->m_val) + 1); + s = stpcpy(macro, mp->m_name); + *s++ = '='; + for (t = mp->m_val; *t; t++) { + if (*t == '\\' || isblank(*t)) + *s++ = '\\'; + *s++ = *t; + } + /* *s = '\0'; - xzalloc did it */ + + makeflags = xappendword(makeflags, macro); + free(macro); + + // Add command line macro definitions to the environment + if (mp->m_level == 1 && idx != SHELL) + setenv(mp->m_name, mp->m_val, 1); + } + } + } + + if (makeflags) { + setmacro("MAKEFLAGS", makeflags, 0); + setenv("MAKEFLAGS", makeflags, 1); + free(makeflags); + } +} + +#if !ENABLE_PLATFORM_MINGW32 +static void +make_handler(int sig) +{ + signal(sig, SIG_DFL); + remove_target(); + kill(getpid(), sig); +} + +static void +init_signal(int sig) +{ + struct sigaction sa, new_action; + + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = 0; + new_action.sa_handler = make_handler; + + sigaction(sig, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + sigaction_set(sig, &new_action); +} +#endif + +/* + * If the global option flag associated with a special target hasn't + * been set mark all prerequisites of the target with a flag. If the + * target had no prerequisites set the global option flag. + */ +static void +mark_special(const char *special, uint32_t oflag, uint16_t nflag) +{ + struct name *np; + struct rule *rp; + struct depend *dp; + int marked = FALSE; + + if (!(opts & oflag) && (np = findname(special))) { + for (rp = np->n_rule; rp; rp = rp->r_next) { + for (dp = rp->r_dep; dp; dp = dp->d_next) { + dp->d_name->n_flag |= nflag; + marked = TRUE; + } + } + + if (!marked) + opts |= oflag; + } +} + +int make_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int make_main(int argc UNUSED_PARAM, char **argv) +{ + const char *path, *newpath = NULL; + char **fargv, **fargv0; + const char *dir, *file; + char def_make[] = "makefile"; + int estat; + FILE *ifd; + + INIT_G(); + +#if ENABLE_FEATURE_MAKE_POSIX + if (argv[1] && strcmp(argv[1], "--posix") == 0) { + argv[1] = argv[0]; + ++argv; + --argc; + setenv("PDPMAKE_POSIXLY_CORRECT", "", 1); + posix = TRUE; + } else { + posix = getenv("PDPMAKE_POSIXLY_CORRECT") != NULL; + } +#endif + + if (!posix) { + path = argv[0]; +#if ENABLE_PLATFORM_MINGW32 + if (has_path(argv[0])) { + // Add extension if necessary, else realpath() will fail + char *p = xmalloc(strlen(argv[0]) + 5); + add_win32_extension(strcpy(p, argv[0])); + path = newpath = xmalloc_realpath(p); + free(p); + if (!path) { + bb_perror_msg("can't resolve path for %s", argv[0]); + } + } +#else + if (argv[0][0] != '/' && strchr(argv[0], '/')) { + // Make relative path absolute + path = newpath = realpath(argv[0], NULL); + if (!path) { + bb_perror_msg("can't resolve path for %s", argv[0]); + } + } +#endif + } else { + path = "make"; + } + + // Process options from MAKEFLAGS + fargv = fargv0 = expand_makeflags(); + if (fargv0) { + opts = process_options(fargv, TRUE); + fargv = fargv0 + optind; + } + // Reset getopt(3) so we can call it again + GETOPT_RESET(); + + // Process options from the command line + opts |= process_options(argv, FALSE); + argv += optind; + + while ((dir = llist_pop(&dirs))) { + if (chdir(dir) == -1) { + bb_perror_msg("can't chdir to %s", dir); + } + } + +#if !ENABLE_PLATFORM_MINGW32 + init_signal(SIGHUP); + init_signal(SIGTERM); +#endif + + setmacro("$", "$", 0 | M_VALID); + + // Process macro definitions from the command line + argv = process_macros(argv, 1); + + // Process macro definitions from MAKEFLAGS + if (fargv) { + process_macros(fargv, 2); + free(fargv0[1]); + free(fargv0); + } + + // Process macro definitions from the environment + process_macros(environ, 3 | M_VALID); + + // Update MAKEFLAGS and environment + update_makeflags(); + + // Read built-in rules + input(NULL, 0); + + setmacro("SHELL", "/bin/sh", 4); + setmacro("MAKE", path, 4); + free((void *)newpath); + + if (!makefiles) { // Look for a default Makefile +#if !ENABLE_PLATFORM_MINGW32 + for (; def_make[0] >= 'M'; def_make[0] -= 0x20) { +#else + { +#endif + if ((ifd = fopen(def_make, "r")) != NULL) { + makefile = def_make; + goto read_makefile; + } + } + error("no makefile found"); + } + + while ((file = llist_pop(&makefiles))) { + if (strcmp(file, "-") == 0) { // Can use stdin as makefile + ifd = stdin; + makefile = "stdin"; + } else { + ifd = xfopen_for_read(file); + makefile = file; + } + read_makefile: + input(ifd, 0); + fclose(ifd); + makefile = NULL; + } + + if (print) + print_details(); + + mark_special(".SILENT", OPT_s, N_SILENT); + mark_special(".IGNORE", OPT_i, N_IGNORE); + mark_special(".PRECIOUS", OPT_precious, N_PRECIOUS); + if (!posix) + mark_special(".PHONY", OPT_phony, N_PHONY); + + estat = 0; + if (*argv == NULL) { + if (!firstname) + error("no targets defined"); + estat = make(firstname, 0); + } else { + while (*argv != NULL) + estat |= make(newname(*argv++), 0); + } + +#if ENABLE_FEATURE_CLEAN_UP + freenames(); + freemacros(); + llist_free(makefiles, NULL); + llist_free(dirs, NULL); +#endif + + return estat; +} diff --git a/testsuite/make.tests b/testsuite/make.tests new file mode 100755 index 000000000..75091b0f7 --- /dev/null +++ b/testsuite/make.tests @@ -0,0 +1,413 @@ +#!/bin/sh + +. ./testing.sh + +# testing "test name" "command" "expected result" "file input" "stdin" + +testing "Basic makefile" \ + "make -f -" "target\n" "" ' +target: + @echo target +' + +# .DEFAULT rules with no commands or some prerequisites are ignored. +# .DEFAULT rules with commands can be redefined. +testing ".DEFAULT rule" \ + "make -f - default" "default2\n" "" ' +.DEFAULT: ignored +.DEFAULT: + @echo default1 +.DEFAULT: + @echo default2 +target: +' + +# Macros should be expanded before suffix substitution. The suffixes +# can be obtained by macro expansion. +testing "Macro expansion and suffix substitution" \ + "make -f -" "src1.o src2.o\n" "" ' +DOTC = .c +DOTO = .o +SRC1 = src1.c +SRCS = $(SRC1) src2.c +target: + @echo $(SRCS:$(DOTC)=$(DOTO)) +' + +# Indeed, everything after the can be obtained by macro +# macro expansion. +testing "Macro expansion and suffix substitution 2" \ + "make -f -" "src1.o src2.o\n" "" ' +DOTS = .c=.o +SRC1 = src1.c +SRCS = $(SRC1) src2.c +target: + @echo $(SRCS:$(DOTS)) +' + +# It should be possible for an inference rule to determine that a +# prerequisite can be created using an explicit rule. +mkdir make.tempdir && cd make.tempdir || exit 1 +testing "Inference rule with explicit rule for prerequisite" \ + "make -f -" "touch x.p\ncat x.p >x.q\n" "" ' +.SUFFIXES: .p .q +x.q: +x.p: + touch $@ +.p.q: + cat $< >$@ +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# A macro created using ::= remembers it's of type immediate-expansion. +# Immediate expansion also occurs when += is used to append to such a macro. +testing "Appending to immediate-expansion macro" \ + "make -f -" \ + "hello 1 2 3\nhello 4 4\n" "" ' +world = 1 +hello ::= hello $(world) +world = 2 +hello += $(world) +world = 3 +hello += $(world) +world = 4 + +world = 1 +reset ::= hello $(world) +world = 2 +# No longer immediate-expansion +reset = hello $(world) +world = 3 +reset += $(world) +world = 4 + +target: + @echo $(hello) + @echo $(reset) +' + +# basic pattern macro expansion +testing "Basic pattern macro expansion" \ + "make -f -" \ + "obj/util.o obj/main.o\n" "" ' +SRC = src/util.c src/main.c +OBJ = $(SRC:src/%.c=obj/%.o) + +target: + @echo $(OBJ) +' + +# pattern macro expansion; match any value +testing "Pattern macro expansion; match any value" \ + "make -f -" \ + "any_value.o\n" "" ' +SRC = any_value +OBJ = $(SRC:%=%.o) + +target: + @echo $(OBJ) +' + +# pattern macro expansion with empty rvalue +testing "Pattern macro expansion with empty rvalue" \ + "make -f -" \ + "\n" "" ' +SRC = util.c main.c +OBJ = $(SRC:%.c=) + +target: + @echo $(OBJ) +' + +# pattern macro expansion with multiple in rvalue +# POSIX requires the first to be expanded, others +# may or may not be expanded. Permit either case. +testing "Pattern macro expansion with multiple in rvalue" \ + "make -f - | sed 's/mainmainmain/main%%/'" \ + "main%%\n" "" ' +SRC = main.c +OBJ = $(SRC:%.c=%%%) + +target: + @echo $(OBJ) +' + +# pattern macro expansion; zero match +testing "Pattern macro expansion; zero match" \ + "make -f -" \ + "nsnp\n" "" ' +WORD = osop +REPL = $(WORD:os%op=ns%np) + +target: + @echo $(REPL) +' + +# Check that MAKE will contain argv[0], e.g make in this case +testing "Basic MAKE macro expansion" \ + "make -f -" \ + "make\n" "" ' +target: + @echo $(MAKE) +' + +# Check that MAKE defined as environment variable will overwrite default MAKE +testing "MAKE macro expansion; overwrite with env macro" \ + "MAKE=hello make -f -" \ + "hello\n" "" ' +target: + @echo $(MAKE) +' + +# Check that MAKE defined on the command-line will overwrite MAKE defined in +# Makefile +testing "MAKE macro expansion; overwrite with command-line macro" \ + "make -f - MAKE=hello" \ + "hello\n" "" ' +MAKE = test + +target: + @echo $(MAKE) +' + +# POSIX draft states that if make was invoked using relative path, MAKE must +# contain absolute path, not just argv[0] +testing "MAKE macro expansion; turn relative path into absolute" \ + "../runtest-tempdir-links/make -f -" \ + "ok\n" "" ' +target: + @test -e "$(MAKE)" && test "$(MAKE)" = "$$(env which make)" && echo ok +' + +# $? contains prerequisites newer than target, file2 in this case +# $^ has all prerequisites, file1 and file2 +touch -t 202206171200 file1 +touch -t 202206171201 target +touch -t 202206171202 file2 +testing "Compare \$? and \$^ internal macros" \ + "make -f -" \ + "file2\nfile1 file2\n" "" ' +target: file1 file2 + @echo $? + @echo $^ +' +rm -f target file1 file2 + +# Phony targets are executed (once) even if a matching file exists. +# A .PHONY target with no prerequisites is ignored. +touch -t 202206171201 target +testing "Phony target" \ + "make -f -" \ + "phony\n" "" ' +.PHONY: target +.PHONY: +target: + @echo phony +' +rm -f target + +# Phony targets aren't touched with -t +testing "Phony target not touched" \ + "make -t -f - >/dev/null && test -f target && echo target" \ + "" "" ' +.PHONY: target +target: + @: +' +rm -f target + +# Include files are created or brought up-to-date +mkdir make.tempdir && cd make.tempdir || exit 1 +testing "Create include file" \ + "make -f -" \ + "made\n" "" ' +target: + @echo $(VAR) +mk: + @echo "VAR = made" >mk +include mk +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# Include files are created or brought up-to-date even when the -n +# option is given. +mkdir make.tempdir && cd make.tempdir || exit 1 +testing "Create include file even with -n" \ + "make -n -f -" \ + "echo made\n" "" ' +target: + @echo $(VAR) +mk: + @echo "VAR = made" >mk +include mk +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# Failure to create an include file isn't an error. (Provided the +# include line is ignoring non-existent files.) +testing "Failure to create include file is OK" \ + "make -f -" \ + "OK\n" "" ' +target: + @echo OK +mk: + @: +-include mk +' + +# Nested macro expansion is allowed. This should be compatible +# with other implementations. +testing "Nested macro expansion" \ + "make -f -" "0 bc\n1 d\n2\n3\n4 bcd\n5 bcd\n" "" ' +a = b +b = c +c = d +$(a:.q=.v)$(b:.z=.v) = bc +bcd = bcd +target: + @echo 0 $(bc) + @echo 1 $($($(a))) + @echo 2 $($(a) $(b) $(c)) + @echo 3 $($a $b $c) + @echo 4 $($(a)$(b)$(c)) + @echo 5 $($a$b$c) +' + +testing "Double-colon rule" \ + "make -f -" "target1\ntarget2\n" "" ' +target:: + @echo target1 +target:: + @echo target2 +' + +# There was a bug whereby the modification time of a file created by +# double-colon rules wasn't correctly updated. This test checks that +# the bug is now fixed. +mkdir make.tempdir && cd make.tempdir || exit 1 +touch -t 202206171200 file1 +touch -t 202206171201 intermediate +touch -t 202206171202 target +touch -t 202206171203 file2 +testing "Target depends on prerequisite updated by double-colon rule" \ + "make -f -" \ + "file2\n" "" ' +target: intermediate + @cat intermediate +intermediate:: file1 + @echo file1 >>intermediate +intermediate:: file2 + @echo file2 >>intermediate +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# Use chained inference rules to determine prerequisites. +mkdir make.tempdir && cd make.tempdir || exit 1 +touch target.p +testing "Chained inference rules" \ + "make -s -f - target.s" \ + "target.q\ntarget.r\ntarget.s\n" "" ' +.SUFFIXES: .p .q .r .s +.p.q: + @cp $< $*.q + @echo $*.q +.q.r: + @cp $< $*.r + @echo $*.r +.r.s: + @cp $< $*.s + @echo $*.s +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# Assign the output of a shell command to a macro. +testing "Shell assignment" \ + "make -f -" \ + "1 2 3 4\n" "" ' +hello != echo 1; echo 2; echo 3; echo; echo + +target: + @echo "$(hello) 4" +' + +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null +# make supports *, ? and [] wildcards in targets and prerequisites +mkdir make.tempdir && cd make.tempdir || exit 1 +touch -t 202206171201 t1a t2aa t3b +touch s1a s2aa s3b +testing "Expand wildcards in filenames" \ + "make -f - t1a t2aa t3b" \ + "t1a s1a s2aa s3b\nt2aa s1a s2aa s3b\nt3b s1a s2aa s3b\n" "" ' +t1? t2* t3[abc]: s1? s2* s3[abc] + @echo $@ $? +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# Skip duplicate entries in $? and $^ +mkdir make.tempdir && cd make.tempdir || exit 1 +touch -t 202206171200 file1 file3 +touch -t 202206171201 target +touch -t 202206171202 file2 +testing "Skip duplicate entries in \$? and \$^" \ + "make -f -" \ + "file2\nfile1 file2 file3\n" "" ' +target: file1 file2 file2 file3 file3 + @echo $? + @echo $^ +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# Skip duplicate entries in $? and $^, with each double-colon rule +# handled separately +mkdir make.tempdir && cd make.tempdir || exit 1 +touch -t 202206171200 file1 file3 +touch -t 202206171201 target +touch -t 202206171202 file2 +testing "Skip duplicate entries: double-colon rules" \ + "make -f -" \ + "file2\nfile1 file3 file2\nfile2\nfile2 file3\n" "" ' +target:: file1 file3 file1 file2 file3 + @echo $? + @echo $^ +target:: file2 file3 file3 + @echo $? + @echo $^ +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# Skip duplicate entries in $? and $^, with each double-colon rule +# handled separately. No prerequisites out-of-date in the first. +mkdir make.tempdir && cd make.tempdir || exit 1 +touch -t 202206171200 file1 file3 +touch -t 202206171201 target +touch -t 202206171202 file2 +testing "Skip duplicate entries: double-colon rules, only second invoked" \ + "make -f -" \ + "file2\nfile2 file3\n" "" ' +target:: file1 file3 file1 file3 + @echo $? + @echo $^ +target:: file2 file3 file3 + @echo $? + @echo $^ +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null + +# Double-colon rules didn't work properly if their target was phony: +# - they didn't ignore the presence of a file matching the target name; +# - they were also invoked as if they were a single-colon rule. +mkdir make.tempdir && cd make.tempdir || exit 1 +touch -t 202206171200 file1 +touch -t 202206171201 target +testing "Phony target of double-colon rule" \ + "make -f - 2>&1" \ + "unconditional\nconditional\n" "" ' +.PHONY: target +target:: + @echo unconditional +target:: file1 + @echo conditional +file1: + @touch file1 +' +cd .. || exit 1; rm -rf make.tempdir 2>/dev/null diff --git a/win32/Kbuild b/win32/Kbuild index 9f486d5e9..10614461a 100644 --- a/win32/Kbuild +++ b/win32/Kbuild @@ -8,6 +8,7 @@ lib-$(CONFIG_PLATFORM_MINGW32) += dirent.o lib-$(CONFIG_PLATFORM_MINGW32) += env.o lib-$(CONFIG_PLATFORM_MINGW32) += fnmatch.o lib-$(CONFIG_PLATFORM_MINGW32) += fsync.o +lib-$(CONFIG_MAKE) += glob.o lib-$(CONFIG_PLATFORM_MINGW32) += inet_pton.o lib-$(CONFIG_PLATFORM_MINGW32) += ioctl.o lib-$(CONFIG_FEATURE_PRNG_ISAAC) += isaac.o diff --git a/win32/glob.c b/win32/glob.c new file mode 100644 index 000000000..1cc6483e7 --- /dev/null +++ b/win32/glob.c @@ -0,0 +1,343 @@ +/* + glob from musl (https://www.musl-libc.org/). + + MIT licensed: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +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 "libbb.h" +#include +#include + +struct match +{ + struct match *next; + char name[]; +}; + +static int append(struct match **tail, const char *name, size_t len, int mark) +{ + struct match *new = malloc(sizeof(struct match) + len + 2); + if (!new) return -1; + (*tail)->next = new; + new->next = NULL; + memcpy(new->name, name, len+1); + if (mark && len && name[len-1]!='/') { + new->name[len] = '/'; + new->name[len+1] = 0; + } + *tail = new; + return 0; +} + +static int do_glob(char *buf, size_t pos, int type, char *pat, int flags, int (*errfunc)(const char *path, int err), struct match **tail) +{ + ptrdiff_t i, j; + int in_bracket, overflow; + char *p2, saved_sep; + int readerr, old_errno; + DIR *dir; + struct dirent *de; + + /* If GLOB_MARK is unused, we don't care about type. */ + if (!type && !(flags & GLOB_MARK)) type = DT_REG; + + /* Special-case the remaining pattern being all slashes, in + * which case we can use caller-passed type if it's a dir. */ + if (*pat && type!=DT_DIR) type = 0; + while (pos+1 < PATH_MAX && *pat=='/') buf[pos++] = *pat++; + + /* Consume maximal [escaped-]literal prefix of pattern, copying + * and un-escaping it to the running buffer as we go. */ + i=0; j=0; + in_bracket = 0; overflow = 0; + for (; pat[i]!='*' && pat[i]!='?' && (!in_bracket || pat[i]!=']'); i++) { + if (!pat[i]) { + if (overflow) return 0; + pat += i; + pos += j; + i = j = 0; + break; + } else if (pat[i] == '[') { + in_bracket = 1; + } else if (pat[i] == '\\' && !(flags & GLOB_NOESCAPE)) { + /* Backslashes inside a bracket are (at least by + * our interpretation) non-special, so if next + * char is ']' we have a complete expression. */ + if (in_bracket && pat[i+1]==']') break; + /* Unpaired final backslash never matches. */ + if (!pat[i+1]) return 0; + i++; + } + if (pat[i] == '/') { + if (overflow) return 0; + in_bracket = 0; + pat += i+1; + i = -1; + pos += j+1; + j = -1; + } + /* Only store a character if it fits in the buffer, but if + * a potential bracket expression is open, the overflow + * must be remembered and handled later only if the bracket + * is unterminated (and thereby a literal), so as not to + * disallow long bracket expressions with short matches. */ + if (pos+(j+1) < PATH_MAX) { + buf[pos+j++] = pat[i]; + } else if (in_bracket) { + overflow = 1; + } else { + return 0; + } + /* If we consume any new components, the caller-passed type + * or dummy type from above is no longer valid. */ + type = 0; + } + buf[pos] = 0; + if (!*pat) { + /* If we consumed any components above, or if GLOB_MARK is + * requested and we don't yet know if the match is a dir, + * we must confirm the file exists and/or determine its type. + * + * If marking dirs, symlink type is inconclusive; we need the + * type for the symlink target, and therefore must try stat + * first unless type is known not to be a symlink. Otherwise, + * or if that fails, use lstat for determining existence to + * avoid false negatives in the case of broken symlinks. */ + struct stat st; + if ((flags & GLOB_MARK) && (!type||type==DT_LNK) && !stat(buf, &st)) { + if (S_ISDIR(st.st_mode)) type = DT_DIR; + else type = DT_REG; + } + if (!type && lstat(buf, &st)) { + if (errno!=ENOENT && (errfunc(buf, errno) || (flags & GLOB_ERR))) + return GLOB_ABORTED; + return 0; + } + if (append(tail, buf, pos, (flags & GLOB_MARK) && type==DT_DIR)) + return GLOB_NOSPACE; + return 0; + } + p2 = strchr(pat, '/'); + saved_sep = '/'; + /* Check if the '/' was escaped and, if so, remove the escape char + * so that it will not be unpaired when passed to fnmatch. */ + if (p2 && !(flags & GLOB_NOESCAPE)) { + char *p; + for (p=p2; p>pat && p[-1]=='\\'; p--); + if ((p2-p)%2) { + p2--; + saved_sep = '\\'; + } + } + dir = opendir(pos ? buf : "."); + if (!dir) { + if (errfunc(buf, errno) || (flags & GLOB_ERR)) + return GLOB_ABORTED; + return 0; + } + old_errno = errno; + while (errno=0, de=readdir(dir)) { + size_t l; + int fnm_flags, r; + + /* Quickly skip non-directories when there's pattern left. */ + if (p2 && de->d_type && de->d_type!=DT_DIR && de->d_type!=DT_LNK) + continue; + + l = strlen(de->d_name); + if (l >= PATH_MAX-pos) continue; + + if (p2) *p2 = 0; + + fnm_flags= ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0) + | ((!(flags & GLOB_PERIOD)) ? FNM_PERIOD : 0); + + if (fnmatch(pat, de->d_name, fnm_flags)) + continue; + + /* With GLOB_PERIOD, don't allow matching . or .. unless + * fnmatch would match them with FNM_PERIOD rules in effect. */ + if (p2 && (flags & GLOB_PERIOD) && de->d_name[0]=='.' + && (!de->d_name[1] || (de->d_name[1]=='.' && !de->d_name[2])) + && fnmatch(pat, de->d_name, fnm_flags | FNM_PERIOD)) + continue; + + memcpy(buf+pos, de->d_name, l+1); + if (p2) *p2 = saved_sep; + r = do_glob(buf, pos+l, de->d_type, p2 ? p2 : (char *)"", flags, errfunc, tail); + if (r) { + closedir(dir); + return r; + } + } + readerr = errno; + if (p2) *p2 = saved_sep; + closedir(dir); + if (readerr && (errfunc(buf, errno) || (flags & GLOB_ERR))) + return GLOB_ABORTED; + errno = old_errno; + return 0; +} + +static int ignore_err(const char *path UNUSED_PARAM, int err UNUSED_PARAM) +{ + return 0; +} + +static void freelist(struct match *head) +{ + struct match *match, *next; + for (match=head->next; match; match=next) { + next = match->next; + free(match); + } +} + +#if !ENABLE_PLATFORM_MINGW32 +static int sort(const void *a, const void *b) +{ + return strcmp(*(const char **)a, *(const char **)b); +} + +static int expand_tilde(char **pat, char *buf, size_t *pos) +{ + char *p = *pat + 1; + size_t i = 0; + + char delim, *name_end = __strchrnul(p, '/'); + if ((delim = *name_end)) *name_end++ = 0; + *pat = name_end; + + char *home = *p ? NULL : getenv("HOME"); + if (!home) { + struct passwd pw, *res; + switch (*p ? getpwnam_r(p, &pw, buf, PATH_MAX, &res) + : getpwuid_r(getuid(), &pw, buf, PATH_MAX, &res)) { + case ENOMEM: + return GLOB_NOSPACE; + case 0: + if (!res) + default: + return GLOB_NOMATCH; + } + home = pw.pw_dir; + } + while (i < PATH_MAX - 2 && *home) + buf[i++] = *home++; + if (*home) + return GLOB_NOMATCH; + if ((buf[i] = delim)) + buf[++i] = 0; + *pos = i; + return 0; +} +#endif + +int glob(const char *restrict pat, int flags, int (*errfunc)(const char *path, int err), glob_t *restrict g) +{ + struct match head = { .next = NULL }, *tail = &head; + size_t cnt, i; + size_t offs = (flags & GLOB_DOOFFS) ? g->gl_offs : 0; + int error = 0; + char buf[PATH_MAX]; + + if (!errfunc) errfunc = ignore_err; + + if (!(flags & GLOB_APPEND)) { + g->gl_offs = offs; + g->gl_pathc = 0; + g->gl_pathv = NULL; + } + + if (*pat) { + char *p = strdup(pat); + size_t pos = 0; + char *s = p; + if (!p) return GLOB_NOSPACE; + buf[0] = 0; +#if !ENABLE_PLATFORM_MINGW32 + if ((flags & (GLOB_TILDE | GLOB_TILDE_CHECK)) && *p == '~') + error = expand_tilde(&s, buf, &pos); + if (!error) +#endif + error = do_glob(buf, pos, 0, s, flags, errfunc, &tail); + free(p); + } + + if (error == GLOB_NOSPACE) { + freelist(&head); + return error; + } + + for (cnt=0, tail=head.next; tail; tail=tail->next, cnt++); + if (!cnt) { + if (flags & GLOB_NOCHECK) { + tail = &head; + if (append(&tail, pat, strlen(pat), 0)) + return GLOB_NOSPACE; + cnt++; + } else + return GLOB_NOMATCH; + } + + if (flags & GLOB_APPEND) { + char **pathv = realloc(g->gl_pathv, (offs + g->gl_pathc + cnt + 1) * sizeof(char *)); + if (!pathv) { + freelist(&head); + return GLOB_NOSPACE; + } + g->gl_pathv = pathv; + offs += g->gl_pathc; + } else { + g->gl_pathv = malloc((offs + cnt + 1) * sizeof(char *)); + if (!g->gl_pathv) { + freelist(&head); + return GLOB_NOSPACE; + } + for (i=0; igl_pathv[i] = NULL; + } + for (i=0, tail=head.next; inext, i++) + g->gl_pathv[offs + i] = tail->name; + g->gl_pathv[offs + i] = NULL; + g->gl_pathc += cnt; + +#if !ENABLE_PLATFORM_MINGW32 + if (!(flags & GLOB_NOSORT)) + qsort(g->gl_pathv+offs, cnt, sizeof(char *), sort); +#endif + + return error; +} + +void globfree(glob_t *g) +{ + size_t i; + for (i=0; igl_pathc; i++) + free(g->gl_pathv[g->gl_offs + i] - offsetof(struct match, name)); + free(g->gl_pathv); + g->gl_pathc = 0; + g->gl_pathv = NULL; +} diff --git a/win32/glob.h b/win32/glob.h new file mode 100644 index 000000000..a8141b8bf --- /dev/null +++ b/win32/glob.h @@ -0,0 +1,89 @@ +/* + glob from musl (https://www.musl-libc.org/). + + MIT licensed: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +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 _GLOB_H +#define _GLOB_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + size_t gl_pathc; + char **gl_pathv; + size_t gl_offs; + int __dummy1; + void *__dummy2[5]; +} glob_t; + +int glob(const char *__restrict, int, int (*)(const char *, int), glob_t *__restrict); +void globfree(glob_t *); + +#if ENABLE_PLATFORM_MINGW32 +// Set some flags to zero so the compiler can exclude unused code. +#define GLOB_ERR 0 +#define GLOB_MARK 0 +#define GLOB_NOSORT 0x04 +#define GLOB_DOOFFS 0 +#define GLOB_NOCHECK 0x10 +#define GLOB_APPEND 0 +#define GLOB_NOESCAPE 0x40 +#define GLOB_PERIOD 0 + +#define GLOB_TILDE 0 +#define GLOB_TILDE_CHECK 0 +#else +#define GLOB_ERR 0x01 +#define GLOB_MARK 0x02 +#define GLOB_NOSORT 0x04 +#define GLOB_DOOFFS 0x08 +#define GLOB_NOCHECK 0x10 +#define GLOB_APPEND 0x20 +#define GLOB_NOESCAPE 0x40 +#define GLOB_PERIOD 0x80 + +#define GLOB_TILDE 0x1000 +#define GLOB_TILDE_CHECK 0x4000 +#endif + +#define GLOB_NOSPACE 1 +#define GLOB_ABORTED 2 +#define GLOB_NOMATCH 3 +#define GLOB_NOSYS 4 + +#if defined(_LARGEFILE64_SOURCE) || defined(_GNU_SOURCE) +#define glob64 glob +#define globfree64 globfree +#define glob64_t glob_t +#endif + +#ifdef __cplusplus +} +#endif + +#endif -- cgit v1.2.3-55-g6feb