diff options
-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 |