aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml8
-rw-r--r--LICENSE21
-rw-r--r--README.md10
-rw-r--r--Redofile25
-rwxr-xr-xautogen.sh4
-rwxr-xr-xbuild.sh11
-rw-r--r--configure.ac30
-rw-r--r--out/CC.do12
-rw-r--r--out/config.sh8
-rw-r--r--out/default.o.do2
-rw-r--r--out/redo-ifchange.do5
-rw-r--r--out/redo.do5
-rw-r--r--src/build.c262
-rw-r--r--src/build.h16
-rw-r--r--src/dbg.h28
-rw-r--r--src/redo-ifchange.c10
-rw-r--r--src/redo.c15
-rw-r--r--src/util.c60
-rw-r--r--src/util.h13
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cf89105
--- /dev/null
+++ b/LICENSE
@@ -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