The main benefit is it is faster than loading language runtimes for simple scripts like nodejs or python. It will run faster for longer executing tasks. It has immediate access to libraries without wrappers. Avoids the friction of managing multiple files and exporting an executable when you just want a simple script. And all in all just gives another scripting option.

I'm sure other projects like this exist but this one is mine.

For example we can make a simple random number generator for the command line with this script.

#!/usr/bin/env runc
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

int checkDecimal(char *str) {
 return (strstr(str,".")!=NULL)?1:0;
}

void seedrandoms() {
 struct timeval time;
 gettimeofday(&time,NULL);
 srand(time.tv_usec);
 srand48(time.tv_usec);
}

int main(int argc,char **argv) {
 if(argc!=2) {
  fprintf(stderr,"Expected to see a top number\n");
  return 1;
 }
 char *input = argv[1];
 int isDecimal = checkDecimal(input);
 seedrandoms();
 if(isDecimal) {
  double input_d = atof(input);
  printf("%f\n",drand48()); //Fix
 }
 else {
  int input_i = atoi(input);
  printf("%d\n",rand()%input_i+1);
 }
 return 0;
}

The comparable nodeJS would be

#!/usr/bin/env node

var input = process.argv[2];
var isDecimal = input.includes('.');

if(isDecimal) {
 console.log(Math.random()*parseFloat(input));
}
else {
 console.log(Math.floor(Math.random()*parseInt(input))+1);
}

But even running that simple script in node is laggy just because of the initial load. Not a good feel. Don't make humans wait.

$ time rand 15
9

real	0m0.014s
user	0m0.003s
sys	0m0.012s

$ time rand.js 15
14

real	0m0.224s
user	0m0.226s
sys	0m0.044s

That quarter of a second seems trivial but not only is this not a good fit for some applications you can feel it. You shouldn't have to feel it for such a simple operation.

An immediate obvious downside that one might expect is that we pass options into gcc for a reason. Surely we are now isolated to just simple C programs with no linking...?! Nope. I solved that problem. It wasn't too hard. We can pass extra options to the end of the shebang and interpret them as packages to pass to pkg-config. We just have two different ways to use it if we are using env or a direct path in the shebang.

#!/usr/local/bin/runc openssl curl glib-2.0

OR

#!/usr/bin/env -S runc openssl curl glib-2.0

Also worth noting is that we cache the executable to .runc.${name}. We compare the modification dates. So we don't have an executable file to manage ourselves and we aren't adding compilation time to every execution. Maybe a downside is that if you put one of these script in a root owned folder you would need write access on your first run. There are different ways that could be handled. Maybe runc needs its own folder in $HOME/.config/runc or $HOME/runc/bin or something like that. But then you get to compile again when you run as a new user. Maybe a hybrid approach could exist. Those bridges can be crossed quite easily when there is a demand. For now its great for writing an odd script in ~/bin.

Now for the actual program.

#!/bin/sh
pkgs=""
while [ $# -gt 0 ]; do
 if [ -f "$1" ]; then
  SRC="$1"
  shift
  break
 else
  pkgs="$pkgs $1"
  shift
 fi
done
[ -z "$SRC" ] && echo "no source file" >&2 && exit 1

DIR=$(dirname "$SRC")
NAME=$(basename "$SRC")
BASE="${NAME%.*}"
BIN="$DIR/.runc.$BASE"

if [ ! -f "$BIN" ] || [ "$SRC" -nt "$BIN" ]; then
 if [ -n "$pkgs" ]; then
  PKGFLAGS=$(pkg-config --cflags --libs $pkgs) || exit 1
 fi
 tail -n +2 "$SRC" | gcc -x c $PKGFLAGS -o "$BIN" - || exit 1
fi

exec "$BIN" "$@"

Oddly enough it's written in bash. At least it loads faster than nodejs. Self compiling when?

Here is another program to show the library support.

#!/usr/bin/env -S runc glib-2.0
#include <glib.h>

int main(void) {
    /* GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION are macros
       provided by <glib.h>, so this will confirm that the compile flags
       actually pulled in GLib headers and libs. */
    g_print("Hello, GLib!\n");
    g_print("GLib version: %d.%d.%d\n",
            GLIB_MAJOR_VERSION,
            GLIB_MINOR_VERSION,
            GLIB_MICRO_VERSION);
    return 0;
}

Now the use of pkg-config does mean that libraries that haven't been put into the pack config system aren't accessible. This would push out the use of obscure libraries unless you do extra work or oddly enough pretty much just one very common library -lm for math. Two possible solutions for that when the demand is high enough is just add math to your pkg-config which honestly it should be a system default IMO. Or modify runc to pass any options starting with dash directly to gcc. Simple things to do once there is real demand.

What should I do to it next?


Comments:

Comment preview