diff options
Diffstat (limited to '')
| -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; +} | 
