Native shebang

From Rosetta Code
Revision as of 00:20, 3 September 2013 by rosettacode>NevilleDNZ (minor tidy)
Native shebang is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Simple shebangs can help with scripting, e.g. "#!/usr/bin/env python" at the top of a Python script will allow it to be run in a terminal as "./script.py".

The task Multiline shebang largely demonstrates how to use "shell" code in the shebang to compile and/or run source-code from a 3rd language.

However in this task Native shebang task we are go native. In the shebang, instead of running a shell, we call a binary-executable generated from the original native language, e.g. "#!/usr/local/bin/script_gcc" to extract, compile and run the native "script" source code.

Other small innovations required of this Native shebang task:

  • Cache the executable in some appropriate place in a path, dependant on available write permissions.
  • Generate the cached executable only when the source has been touched.
  • If a cached is available, then run this instead of regenerating a new executable.

Difficulties:

  • Naturally, some languages are not compiled. These languages are forced to use shebang executables from another language, eg "#!/usr/bin/env python" uses the C binaries /usr/bin/env and /usr/bin/python. If this is the case, then simply document the details of the case.
  • In a perfect world, the test file (e.g. echo.c) would still be a valid program, and would compile without error using the native compiler (e.g. gcc for text.c). The problem is that "#!" is syntactically incorrect on many languages, but in others it can be parsed as a comment.
  • The "test binary" should be exec-ed and hence retain the original Process identifier.

Test case:

  • Create a simple "script file" (in the same native language) called "echo" then use the "script" to output "Hello, world!"

C

File: script_gcc.c <lang c>#include <errno.h>

  1. include <libgen.h>
  2. include <stdarg.h>
  3. include <stdio.h>
  4. include <stdlib.h>
  5. include <string.h>
  6. include <sys/stat.h>
  7. include <unistd.h>

/* the shebang is:

*  * #!/usr/local/bin/script_gcc 
*   * */

/* general readability constants */ typedef char /* const */ *STRING; typedef enum{FALSE=0, TRUE=1}BOOL; enum {RETURNED_OK=0, RETURNED_FAIL=-1} RETURNED; const STRING EMPTY = NULL,

            ENDCAT = NULL;

/* gcc_script.c specific constants */

  1. define DIALECT "c" /* or cpp */

const STRING

 CC="gcc", 
 COPTS="-lm -x "DIALECT, 
 IEXT="."DIALECT, 
 OEXT=".out";

const BOOL OPT_CACHE = TRUE;

/* general utility procedured */ char strcat_out[BUFSIZ];

STRING srcpath;

STRING STRCAT(STRING argv, ... ){

 va_list ap;
 va_start(ap, argv);
 STRING arg;
 strcat_out[0]='\0';
 for(arg=argv; arg!=ENDCAT; arg=va_arg(ap, STRING)){
    strncat(strcat_out, arg, sizeof strcat_out);
 }
 va_end(ap);
 return strndup(strcat_out, sizeof strcat_out);

}

char itoa_out[BUFSIZ];

STRING itoa(int i){

 sprintf(itoa_out, "%d", i);
 return itoa_out;

}

time_t modtime(STRING filename){

 struct stat buf;
 if(stat(filename, &buf)==RETURNED_FAIL)perror(filename);
 return buf.st_mtime;

}

/* gcc_script specific procedure */ BOOL compile(STRING srcpath, STRING binpath){

 int out;
 STRING compiler_command=STRCAT(CC, " ", COPTS, " -o ", binpath, " -", ENDCAT);
 FILE *src=fopen(srcpath, "r"), 
      *compiler=popen(compiler_command, "w");
 char buf[BUFSIZ];
 BOOL shebang;
 for(shebang=TRUE; fgets(buf, sizeof buf, src); shebang=FALSE)
   if(!shebang)fwrite(buf, strlen(buf), 1, compiler);
 out=pclose(compiler);
 return out;

}

void main(int argc, STRING *argv, STRING *envp){

 STRING binpath, 
        srcpath=argv[1], 
        argv0_basename=STRCAT(basename((char*)srcpath /*, .DIALECT */), ENDCAT), 
        *dirnamew, *dirnamex;
 argv++;


/* Warning: current dir "." is in path, AND all/tmp directories are common/shared */

 STRING paths[] ={dirname(strdup(srcpath)), STRCAT(getenv("HOME"), "/bin", ENDCAT), "/usr/local/bin", 
                  ".", STRCAT(getenv("HOME"), "/tmp", ENDCAT), "/tmp", ENDCAT};
 for(dirnamew = paths; *dirnamew; dirnamew++){
   if(access(*dirnamew, W_OK)==RETURNED_OK) break;
 }
 

/* if a CACHEd copy is not to be kept, then fork a sub-process to unlink the .out file */

 if(OPT_CACHE == FALSE){ 
   binpath=STRCAT(*dirnamew, "/", argv0_basename, itoa(getpid()), OEXT, ENDCAT);
   if(compile(srcpath, binpath) == RETURNED_OK){ 
     if(fork()){
       sleep(0.1); unlink(binpath);
     } else {
       execvp(binpath, argv);
     }
   }
 } else {

/* else a CACHEd copy is kept, so find it */

   time_t modtime_srcpath = modtime(srcpath);
   for(dirnamex = paths; *dirnamex; dirnamex++){
     binpath=STRCAT(*dirnamex, "/", argv0_basename, OEXT, ENDCAT);
     if((access(binpath, X_OK) == RETURNED_OK) && (modtime(binpath) >= modtime_srcpath))
       execvp(binpath, argv);
   }
 }
 binpath=STRCAT(*dirnamew, "/", argv0_basename, OEXT, ENDCAT);
 if(compile(srcpath, binpath) == RETURNED_OK)
   execvp(binpath, argv);
 perror(STRCAT(binpath, ": executable not available", ENDCAT));
 exit(errno);

}</lang>

Test Source File: echo.c <lang c>#!/usr/local/bin/script_gcc

  1. include <stdio.h>
  2. include <string.h>

main(int argc, char **argv, char **envp){

 char ofs = '\0';
 for(argv++; *argv; argv++){
   if(ofs)putchar(ofs); else ofs=' ';
   fwrite(*argv, strlen(*argv), 1, stdout);
 }
 putchar('\n');

}</lang>

Test Execution:

$ ./echo.c Hello, world! 

Test Output:

Hello, world!