diff options
| author | Tharre <tharre3@gmail.com> | 2014-04-08 09:19:33 +0200 | 
|---|---|---|
| committer | xRamses <tharre3@gmail.com> | 2014-04-08 09:19:33 +0200 | 
| commit | c766f073e60c3aa51a3cc1555af87ded5823e5a0 (patch) | |
| tree | 43595070e833b251b3f7ac7e94e5e992918194ef | |
| download | redo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.tar.gz redo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.tar.xz redo-c766f073e60c3aa51a3cc1555af87ded5823e5a0.zip  | |
Initial codebase
| -rw-r--r-- | .travis.yml | 8 | ||||
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | Redofile | 25 | ||||
| -rwxr-xr-x | autogen.sh | 4 | ||||
| -rwxr-xr-x | build.sh | 11 | ||||
| -rw-r--r-- | configure.ac | 30 | ||||
| -rw-r--r-- | out/CC.do | 12 | ||||
| -rw-r--r-- | out/config.sh | 8 | ||||
| -rw-r--r-- | out/default.o.do | 2 | ||||
| -rw-r--r-- | out/redo-ifchange.do | 5 | ||||
| -rw-r--r-- | out/redo.do | 5 | ||||
| -rw-r--r-- | src/build.c | 262 | ||||
| -rw-r--r-- | src/build.h | 16 | ||||
| -rw-r--r-- | src/dbg.h | 28 | ||||
| -rw-r--r-- | src/redo-ifchange.c | 10 | ||||
| -rw-r--r-- | src/redo.c | 15 | ||||
| -rw-r--r-- | src/util.c | 60 | ||||
| -rw-r--r-- | src/util.h | 13 | 
19 files changed, 545 insertions, 0 deletions
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c57c818 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: c +compiler: +  - gcc +  - clang +script: ./autogen.sh && ./configure && ./build.sh +before_install: +  - sudo apt-get update -qq +  - sudo apt-get install -q -y libssl-dev @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Tharre + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b722ebb --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# What is redo? +`redo` provides an elegant and minimalistic way of rebuilding files from source. The design stems from Daniel J. Bernstein, who published a basic concept about how `redo` should work on his [website](http://cr.yp.to/redo.html). + +If you want more detailed information about `redo`, a working python implementation of `redo` already exists [here](https://github.com/apenwarr/redo). + +# About this implementation +The goal of this project is not only to implement `redo` in C, but also to rethink and improve it. This also means that this project will not try be compatible with other implementations of `redo`, although this shouldn't prove problematic. + +# Project status +The is work in progress, many features are still missing and the behaviour is not set in stone yet. Missing features include dependency checking, parallel builds, recursive checking and probably a lot more.
\ No newline at end of file diff --git a/Redofile b/Redofile new file mode 100644 index 0000000..c714912 --- /dev/null +++ b/Redofile @@ -0,0 +1,25 @@ +# define all project wide variables + +export ROOTDIR=$(pwd) +export SRCDIR=$ROOTDIR/src +export OUTDIR=$ROOTDIR/out +export VERSION="pre-0.01" + +# TODO: improve this + +if [ "$1" = "all" ]; then +	redo-ifchange $OUTDIR/redo $OUTDIR/redo-ifchange +elif [ "$1" = "clean" ]; then +	rm -rf $OUTDIR/*.tmp $OUTDIR/*.o $OUTDIR/redo $OUTDIR/redo-ifchange $OUTDIR/CC +	# autoconf stuff +	rm -rf autom4te.cache config.h.in configure config.status config.log config.h +elif [ "$1" = "install" ]; then +	redo-ifchange all +	sudo install $OUTDIR/redo /usr/bin +	sudo install $OUTDIR/redo-ifchange /usr/bin +elif [ "$1" = "test" ]; then +	echo "Sadly there are no tests yet ..." +elif [ "$1" = "zip" ]; then +	redo-ifchange clean # we always want to zip the clean workstate +	zip -r redo.zip * +fi diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..a8a0435 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# Just run autoreconf for the moment + +autoreconf diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..9bfbbd1 --- /dev/null +++ b/build.sh @@ -0,0 +1,11 @@ +#!/bin/sh -e + +. out/config.sh +$CC $CFLAGS -o out/util.o -c src/util.c +$CC $CFLAGS -o out/build.o -c src/build.c +$CC $CFLAGS -o out/redo.o -c src/redo.c +$CC $CFLAGS -o out/redo-ifchange.o -c src/redo-ifchange.c +$CC -o out/redo out/redo.o out/util.o out/build.o $LDFLAGS +$CC -o out/redo-ifchange out/redo.o out/util.o out/build.o $LDFLAGS + +echo "Finished compiling" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..9b73edc --- /dev/null +++ b/configure.ac @@ -0,0 +1,30 @@ +#                                               -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.68]) +AC_INIT([redo], [pre-0.01], [BUG-REPORT-ADDRESS]) +AC_CONFIG_SRCDIR([src/redo-ifchange.c]) +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs. +AC_PROG_CXX +AC_PROG_CC + +# Checks for libraries. + +# Checks for header files. +AC_CHECK_HEADERS([stddef.h stdint.h stdlib.h string.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_CHECK_HEADER_STDBOOL +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_FORK +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_CHECK_FUNCS([strdup strerror strrchr]) + +AC_OUTPUT diff --git a/out/CC.do b/out/CC.do new file mode 100644 index 0000000..f18177a --- /dev/null +++ b/out/CC.do @@ -0,0 +1,12 @@ +redo-ifchange config.sh +. ./config.sh + +exec >$3 +cat <<-EOF +	redo-ifchange \$SRCDIR/\$2.c +	$CC $CFLAGS -MD -MF \$3.deps -o \$3 -c \$SRCDIR/\$2.c +	read DEPS <\$3.deps +	rm -f \$3.deps +	redo-ifchange \${DEPS#*:} +EOF +chmod +x $3 diff --git a/out/config.sh b/out/config.sh new file mode 100644 index 0000000..7a1f34c --- /dev/null +++ b/out/config.sh @@ -0,0 +1,8 @@ +if type "clang" > /dev/null; then +	PREF="clang" +else +	PREF="gcc" +fi +CC=${CC-$PREF} +CFLAGS="-g -Wall -Wextra -std=c99 -pedantic" +LDFLAGS=-lcrypto diff --git a/out/default.o.do b/out/default.o.do new file mode 100644 index 0000000..d48bdd1 --- /dev/null +++ b/out/default.o.do @@ -0,0 +1,2 @@ +redo-ifchange CC +. ./CC "$@" diff --git a/out/redo-ifchange.do b/out/redo-ifchange.do new file mode 100644 index 0000000..84941ce --- /dev/null +++ b/out/redo-ifchange.do @@ -0,0 +1,5 @@ +. ./config.sh + +DEPS="redo-ifchange.o build.o util.o" +redo-ifchange $DEPS config.sh +$CC -o $3 $DEPS $LDFLAGS diff --git a/out/redo.do b/out/redo.do new file mode 100644 index 0000000..dce65d4 --- /dev/null +++ b/out/redo.do @@ -0,0 +1,5 @@ +. ./config.sh + +DEPS="redo.o build.o util.o" +redo-ifchange $DEPS config.sh +$CC -o $3 $DEPS $LDFLAGS diff --git a/src/build.c b/src/build.c new file mode 100644 index 0000000..764ec2b --- /dev/null +++ b/src/build.c @@ -0,0 +1,262 @@ +#include <stdio.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <stdbool.h> +#include <assert.h> + +#include <libgen.h> /* dirname(), basename() */ + +#include "build.h" +#include "util.h" +#define __FILENAME__ "build.c" +#include "dbg.h" + +/* TODO: more useful return codes? */ + +const char do_file_ext[] = ".do"; +const char default_name[] = "default"; +const char temp_ext[] = ".redoing.tmp"; + +int build_target(const char *target) { +	assert(target); + +	int retval = 0; +	printf("redo  %s\n", target); + +	/* get the do-file which we are going to execute */ +	char *do_file = get_do_file(target); +	if (do_file == NULL) { +		if (file_exists(target)) { +			/* if our target file has no do file associated but exists, +			   then we treat it as a source */ +			/* TODO: write dependencies */ +			goto exit; +		} +		fprintf(stderr, "%s couldn't be built because no " +				"suitable do-file exists\n", target); +		retval = 1; +		goto exit; +	} + +	debug("Using do-file %s\n", do_file); + +	char *temp_output = concat(2, target, temp_ext); + +    pid_t pid = fork(); +    if (pid == -1) { +        /* failure */ +		fatal("redo: failed to fork() new process"); +    } else if (pid == 0) { +        /* child */ + +		/* change directory to our target */ +		char *dirc = ec_strdup(target); +		char *dtarget = dirname(dirc); +		if (chdir(dtarget) == -1) +			fatal("redo: failed to change directory to %s", dtarget); + +		free(dirc); + +		/* target is now in the cwd so change path accordingly */ +		char *btarget = xbasename(target); +		char *bdo_file = xbasename(do_file); + +		/* read and parse shebang */ +		FILE *fp = fopen(bdo_file, "rb+"); +		if (!fp) +			fatal("redo: failed to open %s", bdo_file); + +		const size_t bufsize = 1024; +		char buf[bufsize]; + +		buf[ fread(buf, 1, sizeof(buf)-1, fp) ] = '\0'; +		if (ferror(fp)) +			fatal("redo: failed to read from %s", bdo_file); + +		fclose(fp); + +		char **argv; +		size_t i = 0; +		if (buf[0] == '#' && buf[1] == '!') { +			argv = parsecmd(&buf[2], &i, 5); +		} else { +			argv = ec_malloc(7 * sizeof(char*)); +			argv[i++] = "/bin/sh"; +			argv[i++] = "-e"; +		} + +		argv[i++] = bdo_file; +		argv[i++] = (char*) btarget; +		char *basename = remove_ext(btarget); +		argv[i++] = basename; +		argv[i++] = temp_output; +		argv[i] = NULL; + +		execv(argv[0], argv); + +		/* execv should never return */ +		fatal("redo: failed to replace the child process with %s", argv[0]); +	} + +    /* parent */ +    int returncode; +    waitpid(pid, &returncode, 0); +	bool remove_temp = true; + +    if (WIFEXITED(returncode)) { +        if (0 != returncode) { +			fprintf(stderr, "redo: invoked do-file %s failed with %d\n", +					do_file, WEXITSTATUS(returncode)); +        } else { +			/* successfull */ + +            /* if the file is 0 bytes long we delete it */ +			off_t f_size = fsize(temp_output); +			if (f_size == -1) { +				if (errno != ENOENT) +					fatal("redo: failed to determine the size of %s", temp_output); +            } else if (f_size) +                remove_temp = false; +        } +    } else { +        /* something very wrong happened with the child */ +		fprintf(stderr, "redo: invoked do-file did not terminate correctly\n"); +    } + +    if (remove_temp) { +	    if (remove(temp_output)) +            if (errno != ENOENT) +		        fatal("redo: failed to remove %s", temp_output); +    } else { +	    if (rename(temp_output, target)) +		    fatal("redo: failed to rename %s to %s", temp_output, target); +    } + +	free(temp_output); + exit: +	free(do_file); + +	return retval; +} + +/* Returns the right do-file for target */ +char *get_do_file(const char *target) { +	assert(target); +	/* target + ".do" */ +	char *temp = concat(2, target, do_file_ext); +	if (file_exists(temp)) +		return temp; +	free(temp); + +	/* default + get_extension(target) + ".do" */ +	temp = concat(3, default_name, take_extension(target), do_file_ext); +	if (file_exists(temp)) +		return temp; +	free(temp); + +	/* Redofile */ +	temp = strdup("Redofile"); +	if (file_exists(temp)) +		return temp; +	free(temp); + +	return NULL; +} + +/* Returns the extension of the target or the empty string if none was found */ +char *take_extension(const char *target) { +	assert(target); +	char *temp = strrchr(target, '.'); +	if (temp) +		return temp; +	else { +		return ""; +	} +} + +/* Checks if target exists and prints a debug message if access() failed +   except if it failed with ENOENT. */ +bool file_exists(const char *target) { +	assert(target); +	if (!access(target, F_OK)) +		return true; +	if (errno != ENOENT) +		debug("Failed to access %s: %s\n", target, strerror(errno)); +	return false; +} + +/* Returns a new copy of str with the extension removed, where the extension is +   everything behind the last dot, including the dot. */ +char *remove_ext(const char *str) { +	assert(str); +	size_t len; +	char *ret, *dot = NULL; + +	for (len = 0; str[len]; ++len) +		if (str[len] == '.') +			dot = (char*) &str[len]; + +	if (dot) /* recalculate length to only reach just before the last dot */ +		len = dot - str; + +	ret = ec_malloc(len+1); +	memcpy(ret, str, len); +	ret[len] = '\0'; + +	return ret; +} + +/* Breaks cmd at spaces and stores a pointer to each argument in the returned +   array. The index i is incremented to point to the next free pointer. The +   returned array is guaranteed to have at least keep_free entries left */ +char **parsecmd(char *cmd, size_t *i, size_t keep_free) { +	assert(cmd); +	size_t argv_len = 16; +	char **argv = ec_malloc(argv_len * sizeof(char*)); +	size_t j = 0; +	bool prev_space = true; +	for (;; ++j) { +		switch (cmd[j]) { +		case ' ': +			cmd[j] = '\0'; +			prev_space = true; +			break; +		case '\n': +		case '\r': +			cmd[j] = '\0'; +		case '\0': +			return argv; +		default: +			if (!prev_space) +				break; +			/* check if we have enough space */ +			while (*i+keep_free >= argv_len) { +				argv_len *= 2; +				debug("Reallocating memory (now %zu)\n", argv_len); +				/* TODO: replace realloc? */ +				char **new_ptr = realloc(argv, argv_len * sizeof(char*)); +				if (!new_ptr) +					fatal("redo: couldn't realloc %zu bytes", +						  argv_len * sizeof(char*)); +				argv = new_ptr; +			} + +			prev_space = false; +			argv[*i] = &cmd[j]; +			++*i; +		} +	} +} + +/* Returns the size of fn */ +off_t fsize(const char *fn) { +    struct stat st; + +    if (stat(fn, &st)) +		return -1; + +	return st.st_size; +} diff --git a/src/build.h b/src/build.h new file mode 100644 index 0000000..418f4b0 --- /dev/null +++ b/src/build.h @@ -0,0 +1,16 @@ +#ifndef __RBUILD_H__ +#define __RBUILD_H__ + +#include <stdbool.h> +#include <stddef.h> +#include <sys/types.h> + +extern char *get_do_file(const char *target); +extern int build_target(const char *target); +extern bool file_exists(const char *target); +extern char *take_extension(const char *target); +extern char *remove_ext(const char *str); +extern char **parsecmd(char *cmd, size_t *i, size_t keep_free); +extern off_t fsize(const char *fn); + +#endif diff --git a/src/dbg.h b/src/dbg.h new file mode 100644 index 0000000..014c358 --- /dev/null +++ b/src/dbg.h @@ -0,0 +1,28 @@ +#ifndef __DBG_H__ +#define __DBG_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +// #define __BFILE__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#ifndef __FILENAME__ +#define __FILENAME__ __FILE__ +#endif + +/* helper functions which help in replacing the GNU extension ##__VA_ARGS__ */ +#define STRINGIFY(x) #x +#define LOG_HELPER(f,l,...) fprintf(stderr, "("f":"STRINGIFY(l)"): "__VA_ARGS__) +#define FATAL_HELPER(M, ...) log_err(M ": %s\n", __VA_ARGS__) + +#ifdef NDEBUG +#define debug(...) +#else +#define debug(...) log_err(__VA_ARGS__) +#endif + +#define log_err(...) LOG_HELPER(__FILENAME__, __LINE__, __VA_ARGS__) +#define fatal(...) {FATAL_HELPER(__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE);} + +#endif diff --git a/src/redo-ifchange.c b/src/redo-ifchange.c new file mode 100644 index 0000000..9415776 --- /dev/null +++ b/src/redo-ifchange.c @@ -0,0 +1,10 @@ +#include <stdio.h> + +#include "build.h" + +int main(int argc, char *argv[]) { +	int i; +	for (i = 1; i < argc; ++i) { +		build_target(argv[i]); +	} +} diff --git a/src/redo.c b/src/redo.c new file mode 100644 index 0000000..c745c54 --- /dev/null +++ b/src/redo.c @@ -0,0 +1,15 @@ +#include <stdio.h> + +#include "build.h" +#include "util.h" + +int main(int argc, char *argv[]) { +	if (argc < 2) { +		build_target("all"); +	} else { +		int i; +		for (i = 1; i < argc; ++i) { +			build_target(argv[i]); +		} +	} +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..e67ec18 --- /dev/null +++ b/src/util.c @@ -0,0 +1,60 @@ +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdarg.h> +#include <assert.h> + +#include "util.h" +#define __FILENAME__ "util.c" +#include "dbg.h" + +#include "../config.h" + +/* A safe malloc wrapper. */ +void *ec_malloc(size_t size) { +    void *ptr = malloc(size); +    if(ptr == NULL) +		fatal("redo: cannot allocate %zu bytes", size); +	 +    return ptr; +} + +/* For concating multiple strings into a single larger one. */ +char *concat(size_t count, ...) { +	assert(count > 0); +	va_list ap, ap2; +	va_start(ap, count); +	va_copy(ap2, ap); +	size_t i, size = 0, args_len[count]; +	for (i = 0; i < count; ++i) { +		args_len[i] = strlen(va_arg(ap, char*)); +		size += args_len[i]; +	} +	size++; +	char *result = ec_malloc(size); +	/* debug("Allocated %zu bytes at %p\n", size, result); */ +	uintptr_t offset = 0; +	for (i = 0; i < count; ++i) { +		strcpy(&result[offset], va_arg(ap2, char*)); +		offset += args_len[i]; +	} +	va_end(ap); +	va_end(ap2); +	return result; +} + +char *xbasename(const char *path) { +	assert(path); +	char *ptr = strrchr(path, '/'); +	return ptr? ptr+1 : (char*) path; +} + +char *ec_strdup(const char* str) { +	assert(str); +	size_t len = strlen(str) + 1; +	char *ptr = malloc(len); +	if (!ptr) +		fatal("redo: failed to duplicate string"); + +	return memcpy(ptr, str, len);; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..40780d8 --- /dev/null +++ b/src/util.h @@ -0,0 +1,13 @@ +#ifndef __RUTIL_H__ +#define __RUTIL_H__ + +#include <stdlib.h> +#include "../config.h" + +extern char *concat(size_t count, ...); +extern void *ec_malloc(size_t size); +extern char *ec_strdup(const char* str); +extern char *strdup(const char *str); +extern char *xbasename(const char *path); + +#endif  | 
