aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore15
-rw-r--r--LICENSE15
-rw-r--r--Makefile40
-rw-r--r--TODO4
-rw-r--r--config.mk8
-rw-r--r--makeenv.c430
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fccd785
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..a205668
--- /dev/null
+++ b/TODO
@@ -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;
+}