diff options
-rw-r--r-- | .gitignore | 15 | ||||
-rw-r--r-- | LICENSE | 15 | ||||
-rw-r--r-- | Makefile | 40 | ||||
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | config.mk | 8 | ||||
-rw-r--r-- | makeenv.c | 430 |
6 files changed, 512 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9143c5d --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*\#* +*~ +*.o +*.a +*.lo +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda +/makeenv @@ -0,0 +1,15 @@ +ISC License + +© 2024 Mattias Andrée <maandree@kth.se> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..12205d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ + makeenv.o + +HDR = + +all: makeenv +$(OBJ): $(HDR) + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +makeenv: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) + +install: makeenv + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man5/" + cp -- makeenv "$(DESTDIR)$(PREFIX)/bin/" + cp -- makeenv.1 "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- makeenv.5 "$(DESTDIR)$(MANPREFIX)/man5/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/makeenv" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/makeenv.1" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man5/makeenv.5" + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda + -rm -f -- makeenv + +.SUFFIXES: +.SUFFIXES: .o .c + +.PHONY: all install uninstall clean @@ -0,0 +1,4 @@ +Write makeenv.1 +Write makeenv.5 +Write README +Test diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..f4adf12 --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = c99 + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = +LDFLAGS = diff --git a/makeenv.c b/makeenv.c new file mode 100644 index 0000000..a091b49 --- /dev/null +++ b/makeenv.c @@ -0,0 +1,430 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +static const char *argv0 = "makeenv"; +static const char **options = NULL; +static const char **macros = NULL; +static const char **targets = NULL; +static size_t noptions = 0; +static size_t nmacros = 0; +static size_t ntargets = 0; +static size_t options_size = 0; +static size_t macros_size = 0; +static size_t targets_size = 0; + + +static void +put_option(const char *s) +{ + if (noptions == options_size) { + options_size += 16; + options = realloc(options, options_size * sizeof(*options)); + if (!options) { + fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); + exit(125); + } + } + options[noptions] = s; +} + + +static void +put_option_short(int c) +{ + char buf[3]; + buf[0] = '-'; + buf[1] = (char)c; + buf[2] = '\0'; + put_option(buf); +} + + +static void +put_option_prefix_dash(const char *s) +{ + size_t len = strlen(s); + char *arg = malloc(len + 2); + if (!arg) { + fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); + exit(125); + } + stpcpy(stpcpy(arg, "-"), s); + put_option(arg); +} + + +static void +put_options(char *s) +{ + const char *word = s; + for (; *s; s++) { + if (isspace(*s)) { + *s = '\0'; + if (*word) + put_option(word); + word = &s[1]; + } + } + if (*word) + put_option(word); +} + + +static void +put_macro(const char *s) +{ + if (putenv(*(char **)(void *)&s)) { + fprintf(stderr, "%s: failed to update environment: %s\n", argv0, strerror(errno)); + exit(125); + } + if (nmacros == macros_size) { + macros_size += 16; + macros = realloc(macros, macros_size * sizeof(*macros)); + if (!macros) { + fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); + exit(125); + } + } + macros[nmacros] = s; +} + + +static void +put_target(const char *s) +{ + if (ntargets == targets_size) { + targets_size += 16; + targets = realloc(targets, targets_size * sizeof(*targets)); + if (!targets) { + fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); + exit(125); + } + } + targets[ntargets] = s; +} + + +static void +put_targets(char *s) +{ + const char *word = s; + for (; *s; s++) { + if (isspace(*s)) { + *s = '\0'; + if (*word) + put_target(word); + word = &s[1]; + } + } + if (*word) + put_target(word); +} + +static void +put_operand(char *s) +{ + /* + * Behaviour is unspecified if any target is specified before + * a macro, however make(1) shall allow options to be mixed in + * with both macros and targets + */ + static int warned_mixed = 0; + static int found_targets = 0; + + if (strchr(s, '=')) { + /* + * Adding macro as a target as we don't want it to be + * but into the environment and we don't want to reorder + * them if the user mixes macros with targets (targets + * ared pulled into the command line after macros so + * this will not cause any problems) + */ + put_target(s); + if (found_targets && !warned_mixed) { + warned_mixed = 1; + fprintf(stderr, "%s: warning: mixing targets and macros in the " + "command line results in unspecified behaviour\n", argv0); + } + } else { + put_target(s); + found_targets = 1; + } +} + + + +#if defined(__GNUC__) +__attribute__((__pure__)) +#endif +static int +contains(const char *set, const char *sought) +{ + size_t m, n = strlen(sought); + const char *p, *q; + for (p = set; (q = strchr(p, ' ')); p = &q[1]) { + m = (size_t)(q - p); + if (m == n && !strncmp(p, sought, n)) + return 1; + } + return !strcmp(p, sought); +} + + +static const char ** +create_cmdline(void) +{ + size_t i, j = 1, n = noptions + nmacros + ntargets + 2; + const char **args = malloc(n * sizeof(char *)); + if (!args) { + fprintf(stderr, "%s: failed to allocate enough memory to execute make\n", argv0); + exit(125); + } + for (i = 0; i < noptions; i++) + args[j++] = options[i]; + for (i = 0; i < nmacros; i++) + args[j++] = macros[i]; + for (i = 0; i < ntargets; i++) + args[j++] = targets[i]; + args[j] = NULL; + return args; +} + + +static void +trim(char *s) +{ + size_t key_start, key_end; + size_t val_start, val_end; + size_t i, n; + + key_start = 0; + while (isspace(s[key_start])) + key_start += 1; + + key_end = key_start; + while (s[key_end] != '=') + key_end += 1; + + val_start = key_end + 1; + + while (key_end > key_start && isspace(s[key_end - 1])) + key_end -= 1; + + while (isspace(s[val_start])) + key_start += 1; + + val_end = val_start; + for (i = val_end; s[i]; i++) + if (!isspace(s[i])) + val_end = i; + + memmove(s, &s[key_start], key_end - key_start); + n = key_end - key_start; + s[n++] = '='; + + memmove(&s[n], &s[val_start], val_end - val_start); + n += val_end - val_start; + s[n] = '\0'; +} + + +int +main(int argc, char *argv[]) +{ + int exitstatus, fd; + char *env = NULL; + size_t envsize = 0; + size_t envlen = 0; + ssize_t r; + size_t i; + char *line; + int has_equals; + const char **args; + const char *arg; + const char *make; + const char *unarged_opts; + const char *arged_opts; + const char *optatarged_opts; + const char *optarged_opts; + const char *unarged_longopts; + const char *arged_longopts; + const char *optarged_longopts; + int operand_found = 0; + int warned_reordered = 0; + + args = (void *)argv; + argv0 = *argv++; + (void) argc; + + fd = open(".makeenv", O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + goto exec; + fprintf(stderr, "%s: failed to open .makeenv for reading: %s\n", argv0, strerror(errno)); + return 125; + } + + for (;;) { + if (envlen == envsize) { + envsize += 8096; + env = realloc(env, envsize); + if (!env) { + fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); + return 125; + } + } + r = read(fd, &env[envlen], envsize - envlen); + if (r <= 0) { + if (!r) + break; + fprintf(stderr, "%s: failed to read .makeenv\n", argv0); + return 125; + } + envlen += (size_t)r; + } + + if (!envlen) { + close(fd); + goto exec; + } + + env = realloc(env, envlen + 1); + if (!env) { + fprintf(stderr, "%s: failed to allocate enough memory to load .makeenv\n", argv0); + return 125; + } + env[envlen++] = '\n'; + + line = env; + has_equals = 0; + for (i = 0; i < envlen; i++) { + if (env[i] == '\n') { + env[i] = '\0'; + trim(line); + if (line[0] == '-' && line[1]) { + put_options(line); + } else if (has_equals) { + if (*line && *line != '=' && *line != '#') + put_macro(line); + } else { + if (*line && *line != '#') + put_targets(line); + } + line = &env[i + 1]; + has_equals = 0; + } else if (env[i] == '=') { + has_equals = 1; + } + } + + close(fd); + + env = getenv("MAKEENV_OPTS_NO_ARG"); + unarged_opts = env ? env : "eiknpqrSst"; + env = getenv("MAKEENV_OPTS_ARG"); + arged_opts = env ? env : "f"; + env = getenv("MAKEENV_OPTS_OPT_ATTACHED_ARG"); + optatarged_opts = env ? env : ""; + env = getenv("MAKEENV_OPTS_OPT_ARG"); + optarged_opts = env ? env : ""; + env = getenv("MAKEENV_LONG_OPTS_NO_ARG"); + unarged_longopts = env ? env : ""; + env = getenv("MAKEENV_LONG_OPTS_ARG"); + arged_longopts = env ? env : ""; + env = getenv("MAKEENV_LONG_OPTS_OPT_ARG"); + optarged_longopts = env ? env : ""; + + for (; *argv; argv++) { + if (!strcmp(*argv, "--")) { + argv++; + break; + } else if ((*argv)[0] != '-' || !(*argv)[1]) { + /* do not break, as make(1) allows mixing options and operands */ + put_operand(*argv); + operand_found = 1; + continue; + } + + if (operand_found && !warned_reordered) { + warned_reordered = 1; + fprintf(stderr, "%s: warning: reordering operands to after options\n", argv0); + } + + if ((*argv)[1] == '-') { + arg = *argv; + if (strchr(arg, '=') || contains(unarged_longopts, arg)) { + put_option(arg); + } else if (contains(arged_longopts, arg)) { + put_option(arg); + if (!argv[1]) { + fprintf(stderr, "%s: argument for option %s missing\n", argv0, arg); + return 125; + } + put_option(*++argv); + } else if (contains(optarged_longopts, arg)) { + put_option(arg); + if (argv[1] && argv[1][0] != '-') + put_option(*++argv); + } else { + fprintf(stderr, "%s: option %s not recognised\n", argv0, arg); + return 125; + } + } else { + arg = &(*argv)[1]; + while (*arg) { + if (strchr(unarged_opts, *arg)) { + put_option_short(*arg++); + } else if (strchr(arged_opts, *arg)) { + if (arg[1]) { + put_option_short(*arg++); + put_option(arg); + break; + } else if (argv[1]) { + put_option_short(*arg++); + put_option(*++argv); + break; + } else { + fprintf(stderr, "%s: argument for option -%c missing\n", argv0, *arg); + return 125; + } + } else if (strchr(optatarged_opts, *arg)) { + put_option_prefix_dash(arg); + break; + } else if (strchr(optarged_opts, *arg)) { + if (arg[1]) { + put_option_prefix_dash(arg); + } else { + put_option_short(*arg++); + if (argv[1] && argv[1][0] != '-') + put_option(*++argv); + } + break; + } else { + fprintf(stderr, "%s: option -%c not recognised\n", argv0, *arg); + return 125; + } + } + } + } + + while (*argv) + put_operand(*argv); + + args = create_cmdline(); + +exec: + make = getenv("MAKEENV_MAKE"); + make = make ? make : getenv("MAKE"); + args[0] = make ? make : "make"; + execvp(args[0], (void *)args); + exitstatus = errno == ENOENT ? 127 : 126; + fprintf(stderr, "%s: failed to execute %s: %s\n", argv0, args[0], strerror(errno)); + return exitstatus; +} |