Native shebang: Difference between revisions
(initial draft task) |
m (minor tidy) |
||
Line 12: | Line 12: | ||
'''Difficulties:''' |
'''Difficulties:''' |
||
* Naturally, some languages are not compiled. These languages are forced to use shebang executables from another language, eg "<tt>#!/usr/bin/env python<tt>" uses the C binaries /usr/bin/env and /usr/bin/python. If this is the case, then |
* Naturally, some languages are not compiled. These languages are forced to use shebang executables from another language, eg "<tt>#!/usr/bin/env python</tt>" 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 |
* 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 [wp:exec (computing)|exec]ed and hence retain the original [[wp:Process identifier|Process identifier]]. |
* The "test binary" should be [[wp:exec (computing)|exec]]-ed and hence retain the original [[wp:Process identifier|Process identifier]]. |
||
'''Test case:''' |
'''Test case:''' |
||
Line 159: | Line 159: | ||
$ ./echo.c Hello, world! |
$ ./echo.c Hello, world! |
||
</pre> |
</pre> |
||
'''Test Output''' |
'''Test Output:''' |
||
<pre> |
<pre> |
||
Hello, world! |
Hello, world! |
Revision as of 00:20, 3 September 2013
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>
- include <libgen.h>
- include <stdarg.h>
- include <stdio.h>
- include <stdlib.h>
- include <string.h>
- include <sys/stat.h>
- 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 */
- 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
- include <stdio.h>
- 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!