Native shebang: Difference between revisions
m Clarify: this task must be done in the specimen language, '''neither''' using a shell script '''nor''' any other 3rd language. |
|||
Line 1: | Line 1: | ||
{{draft task|Basic language learning}} |
|||
'''In short:''' Use the specimen language for "scripting". |
|||
Example: If your language is "foo", then the test case of "echo.foo" run in a terminal as "./echo.foo Hello, world!". |
|||
'''In long''': Create a program (in the specimen language) that will automatically compile a ''test case'' (of the same specimen language) to a native binary executable and then transparently load and run this ''test case'' executable. |
|||
Make it so that all that is required is a custom [[wp:Shebang (Unix)|shebangs]] at the start of the ''test case''. |
|||
'''Importantly:''' This task must be coded '''strictly''' in the specimen language, '''neither''' using a shell script '''nor''' any other 3rd language. |
|||
Optimise this progress so that the ''test program'' '''binary executable''' is only created if the original ''test program'' '''source code''' as been touched/edited. |
|||
Note: If the lauguage (or a specific implementation) handles this automatically, then simple provide an example of "echo.foo" |
|||
'''Background:''' |
|||
Simple [[wp:Shebang (Unix)|shebangs]] can help with scripting, e.g. "<tt>#!/usr/bin/env python</tt>" at the top of a [[Python]] script will allow it to be run in a terminal as "./script.py". |
Simple [[wp:Shebang (Unix)|shebangs]] can help with scripting, e.g. "<tt>#!/usr/bin/env python</tt>" 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. |
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. |
||
'''This task:''' |
|||
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. when using [[C]] with gcc "<tt>#!/usr/local/bin/script_gcc</tt>" to extract, compile and run the native "script" source code. |
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. when using [[C]] with gcc "<tt>#!/usr/local/bin/script_gcc</tt>" to extract, compile and run the native "script" source code. |
||
Line 280: | Line 298: | ||
</pre> |
</pre> |
||
'''Test Output:''' |
'''Test Output:''' |
||
<pre> |
|||
Hello, world! |
|||
</pre> |
|||
=={{header|python}}== |
|||
Extract: "If you need to create a .pyc file for a module that is not imported, you can use the py_compile and compileall modules. The py_compile module can manually compile any module. One way is to use the py_compile.compile function in that module interactively:[http://effbot.org/pyfaq/how-do-i-create-a-pyc-file.htm]:" |
|||
<pre> |
|||
>>> import py_compile |
|||
>>> py_compile.compile('echo.py') |
|||
</pre> |
|||
'''File: echo.py''' |
|||
<lang python>#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- |
|||
import sys |
|||
print " ".join(sys.argv[1:])</lang> |
|||
'''Usage:''' |
|||
<pre> |
|||
./echo.py Hello, world! |
|||
</pre> |
|||
'''Output:''' |
|||
<pre> |
<pre> |
||
Hello, world! |
Hello, world! |
Revision as of 23:09, 6 August 2014
In short: Use the specimen language for "scripting".
Example: If your language is "foo", then the test case of "echo.foo" run in a terminal as "./echo.foo Hello, world!".
In long: Create a program (in the specimen language) that will automatically compile a test case (of the same specimen language) to a native binary executable and then transparently load and run this test case executable.
Make it so that all that is required is a custom shebangs at the start of the test case.
Importantly: This task must be coded strictly in the specimen language, neither using a shell script nor any other 3rd language.
Optimise this progress so that the test program binary executable is only created if the original test program source code as been touched/edited.
Note: If the lauguage (or a specific implementation) handles this automatically, then simple provide an example of "echo.foo"
Background:
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.
This task:
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. when using C with gcc "#!/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 a new 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!"
ALGOL 68
Note: With Algol68G the option "-O3" will compile the script file to a ".so" file, this ".so" file is a binary executable and dynamically loaded library. Also note that this ".so" will only be generated if the ".a68" source file has been touched. File: echo.a68<lang algol68>#!/usr/bin/a68g --script #
- -*- coding: utf-8 -*- #
STRING ofs := ""; FOR i FROM 4 TO argc DO print((ofs, argv(i))); ofs:=" " OD</lang>Test Execution:
$ ./echo.a68 Hello, world!
Output:
Hello, world!
C
File: script_gcc.c <lang c>#!/usr/local/bin/script_gcc.sh /* Optional: this C code initially is-being/can-be boot strapped (compiled) using bash script_gcc.sh */
- 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 actual shebang for C target scripts is:
- !/usr/local/bin/script_gcc.c
- /
/* general readability constants */ typedef char /* const */ *STRING; typedef enum{FALSE=0, TRUE=1} BOOL; const STRING ENDCAT = NULL;
/* script_gcc.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 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) != EXIT_SUCCESS)perror(filename); return buf.st_mtime;
}
/* script_gcc 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++; /* shift */
/* Warning: current dir "." is in path, AND * /tmp directories are common/shared */
STRING paths[] = { dirname(strdup(srcpath)), /* not sure why strdup is required? */ STRCAT(getenv("HOME"), "/bin", ENDCAT), "/usr/local/bin", ".", STRCAT(getenv("HOME"), "/tmp", ENDCAT), getenv("HOME"), STRCAT(getenv("HOME"), "/Desktop", ENDCAT),
/* "/tmp" ... a bit of a security hole */
ENDCAT };
for(dirnamew = paths; *dirnamew; dirnamew++){ if(access(*dirnamew, W_OK) == EXIT_SUCCESS) 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) == EXIT_SUCCESS){ 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) == EXIT_SUCCESS) && (modtime(binpath) >= modtime_srcpath)) execvp(binpath, argv); } }
binpath=STRCAT(*dirnamew, "/", argv0_basename, OEXT, ENDCAT); if(compile(srcpath, binpath) == EXIT_SUCCESS) 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.c
- include <stdio.h>
- include <string.h>
- include <stdlib.h>
int 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'); exit(EXIT_SUCCESS);
}</lang>
Test Execution:
$ ./echo.c Hello, world!
Test Output:
Hello, world!
UNIX Shell
Note: this Native shebang task does not exactly apply to bash because bash is interpretive, but as a skeleton template the following script is an example of how compiled languages can implement the shebang. Also: this bash code can be used to automatically compile the C code in /usr/local/bin/script_gcc.c above.
File: script_gcc.sh <lang bash>#!/bin/bash
- Actual shebang when using bash:
- !/usr/local/bin/script_gcc.sh
- Alternative shebang when using bash:
- !/bin/bash /usr/local/bin/script_gcc.sh
- CACHE=No # to turn off caching...
- Note: this shell should be re-written in actual C! :-)
DIALECT=c # or cpp CC="gcc" COPTS="-lm -x $DIALECT" IEXT=.$DIALECT OEXT=.out
ENOENT=2
srcpath="$1"; shift # => "$@"
- basename="$(basename "$srcpath" ."$DIALECT")"
basename="$(basename "$srcpath")"
- Warning: current dir "." is in path, AND */tmp directories are common/shared
paths="$(dirname "$srcpath") $HOME/bin /usr/local/bin . $HOME/tmp $HOME $HOME/Desktop"
- /tmp
while read dirnamew; do
[ -w "$dirnamew" ] && break
done << end_here_is $paths end_here_is
compile(){
sed -n '2,$p' "$srcpath" | "$CC" $COPTS -o "$binpath" -
}
if [ "'$CACHE'" = "'No'" ]; then
binpath="$dirnamew/$basename-v$$$OEXT" if compile; then ( sleep 0.1; exec rm "$binpath" ) & exec "$binpath" "$@" fi
else
while read dirnamex; do binpath="$dirnamex/$basename$OEXT" if [ -x "$binpath" -a "$binpath" -nt "$srcpath" ]; then exec "$binpath" "$@"; fi done << end_here_is
$paths end_here_is
binpath="$dirnamew/$basename$OEXT" if compile; then exec "$binpath" "$@"; fi
echo "$binpath: executable not available" 1>&2 exit $ENOENT
fi</lang> Test Source File: echo.c <lang c>#!/usr/local/bin/script_gcc.sh
- include <stdio.h>
- include <string.h>
- include <stdlib.h>
int 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'); exit(EXIT_SUCCESS);
}</lang>
Test Execution:
$ ./echo.c Hello, world!
Test Output:
Hello, world!
python
Extract: "If you need to create a .pyc file for a module that is not imported, you can use the py_compile and compileall modules. The py_compile module can manually compile any module. One way is to use the py_compile.compile function in that module interactively:[1]:"
>>> import py_compile >>> py_compile.compile('echo.py')
File: echo.py <lang python>#!/usr/bin/env python
- -*- coding: utf-8 -*-
import sys print " ".join(sys.argv[1:])</lang>
Usage:
./echo.py Hello, world!
Output:
Hello, world!