compileAndGo
Abstract
compileAndGo
is a shebang header command that allows compiled-language source code such as Java and C++ to be used as an executable script.compileAndGo
caches the compiled, executable file(s) out of sight and does make-like automatic recompilation. Wrapping source code in this way combines the convenience of a script-like, cross-platform executable source file with the benefits of a compiled language.
Here are some Hello World examples.
Writing a
|
|
Java: |
|
|
|
Mozilla Rhino JavaScript (compiled as Java .class files): |
|
|
|
C: |
|
|
Running a scriptRun the above scripts just like any other program: |
|
|
|
Run an ordinary source-code file like this: |
|
|
|
Feed some code into the standard input of |
|
|
|
The Java compilers also require the main class name after the compiler argument to |
|
|
|
Or, with the execute bit set, and with a little help from your shell, you will someday be able to do this: |
|
|
There are also more-elaborate examples.
See examples for Java javago
andmodtime
, which uses JNIDirect to wrap thestat
system callJavaScript jshello
C cgo
andrealpath
, a simple wrapper for the system call of the same name. Also in the download are commands that wrap system calls to manipulate extended attributes on files:listxattr
,getxattr
,setxattr
,removexattr
, adapted from Apple sample code.
You can copy and paste the compileAndGo
code from this page, or you can download a compressed file that contains these web pages, the compileAndGo
and cg
scripts, many demo files, and a beta of JNIDirect.jar
.
cacheDir |
Full path of the directory where the source and executables are cached |
commandDir |
Path to the directory containing the compileAndGo script. |
Either language
or compiler
is required.
All other parameters default to values appropriate for the compiler
.
Compilers known to compileAndGo
and cg
are
javac
,
gcj
,
jsc
,
cc,
g++
,
gcc
,
c89
,
c99
,
clang
,
clang++
.
(See note below on JavaScript.)
language=java |
Recommended: The language, so an IDE can help you edit the file. If specified, then implies an appropriate default compiler . Known langauges are java , c , c++ , and javascript . |
compiler=/usr/bin/javac |
Required (if language not supplied): The compiler. Full path not required. The compiler runs in the current directory, not in $cacheDir . |
commandName=javago |
The name of your script. Exported to the environment. Defaults to the name by which the script is invoked (which can be expressed as ${1##*/} . |
mainClass=$commandBasename |
For Java, the name of the main class. Defaults as shown. |
sourceFilename=$mainClass.java |
The source code will be extracted from the compileAndGo script and put into the file $cacheDir/$sourceFilename . Defaults as appropriate for the compiler . |
classpath=$commandDir/JNIDirect.jar |
For Java, include this in the classpath given to the compiler and the java program runner. Optional. |
executableFilename=$mainClass.class |
Compiling the source file will produce at least the file $cacheDir/$sourceFilename . Defaults as shown. |
compilerArgs=d $cacheDir -sourcepath . $cacheDir/$sourceFilename |
Arguments to the compiler. Required for jikes if CLASSPATH environment variable is not set; otherwise optional. |
linkerArgs= |
Arguments to the compiler after $compilerArgs .
Applies only to C/C++ compilers. |
execute=/usr/bin/java -cp $cacheDir:$commandDir/JNIDirect.jar $mainClass |
How to execute the program once it’s built. Defaults as appropriate for the compiler . When the compiler is javac , the path to the java program defaults to the same as the path given for javac (if any). |
verbose=1 |
1 echoes "Compiling", 2 instead echoes the commands to compile and execute, 3 also shows variable values. Higher settings show internal debugging details. Defaults to 0. |
!# |
A line containing only !# marks the end of the parameter settings.
From the next line on, it’s source code. |
compileAndGo |
A line starting with compileAndGo works to mark the end of the parameter settings,
but this usage is deprecated and replaced by '!#'. |
Tabs and spaces at beginning of line and around = signs are optional. Blank lines and comments beginning with #
are ignored.
You can set and use whatever intermediate variables you may need, and you can export them.
Even though the compileAndGo
program is implemented as a /bin/bash
script, additional shell code in the parameter header beyond the setting of variables and echo
will not work.
Elements of the cacheDir
path include the modify time of the script, the compiler
, and the OS and CPU architecture (as output by uname
). Thus we isolate multiple versions of the source on multiple architectures and allow testing with multiple compilers without conflicts and spurious recompiles. When the script is run, if a cached executable exists in the appropriate cacheDir
, we just run it; otherwise, we first make the cacheDir
and compile the source there.If compiler
is set to jsc
If compiler
is set to javac
, compileAndGo
tells javac
to parse for the highest language version it accepts (for example, javac -source 1.5
). To determine this version, compileAndGo
runs java -version
.
If compiler
is set to jsc
, you have to put jsc.jar
in your Java’s extensions folder (jre/ext/
or on Mac /Library/Java/Extensions
), and compileAndGo
doesn’t run a jsc
command; rather, it invokes
java -cp $cacheDir${classPath:+:$classPath} org.mozilla.javascript.tools.jsc.Main $compilerArgs
#!/bin/bash # See notice of copyright and license at end. # If you use this program for a #! script, please include the following # as the second line of the script: # See http://Yost.com/computers/compileAndGo # Bug: doesn't recompile if invoked file is a symlink. Fixed? # Needs a call to realpath. ## Bug: The following fails for the cg command after fixing it to work for compileAndGo # Something about evaluation order of the variables in the source header. # compiler = gcc # compilerArgs = -O2 -o $cacheDir/$executableFilename $cacheDir/$sourceFilename # The #! compileAndGo script that invoked us. # If $0 is a symlink, this is the invoked name, not the symlink referent name typeset -r commandFile="$1" pathOfDirPartOfExecPath() { if [[ -h "$1" ]] ; then local lsout="$(ls -l "$1")" local referent="${lsout#* -> }" pathOfDirPartOfExecPath "$referent" elif [[ -d "${1%/*}" ]] ; then echo "${1%/*}" else echo . fi } typeset -r commandDir="$(pathOfDirPartOfExecPath "$0")/" # If $0 is a symlink, this is the invoked name, not the symlink referent name ourName=${0##*/} #------------------------------------------------------------------------------- extensionToLanguage() { case "$1" in js) echo javascript ;; java) echo java ;; c) echo c ;; cp | cpp | C | cxx | cc) echo c++ ;; *) ;; esac } languageToCompiler() { case "$1" in javascript) echo jsc ;; java) echo javac ;; c) if [[ -x /usr/bin/gcc ]] ; then echo /usr/bin/gcc else echo /usr/bin/cc fi ;; c++) echo /usr/bin/g++ ;; *) ;; esac } echo2() { # This works for sh, ksh, and bash, but not zsh. echo "$@" } echoVariables() { echo 1>&2 $1 if [[ ! $ourName == cg ]] ; then # Echo all but the builtins. echo 1>&2 "$( eval "$( echo "$cmdSetters" \ | grep -v 'commandBasename commandDir sourceFilename language mainClass executableFilename compilerDir classPath compilerArgs linkerArgs execute firstArgIsCommandName verbose' \ | sed " s,^eval,echo, s,=, = , s,',,g " )" \ | sed " s,= ,= ', s,$,', " )" else echo 1>&2 commandName = "$commandName" echo 1>&2 compiler = "$compiler" fi # Echo the builtins. echo 1>&2 commandBasename = "$commandBasename" echo 1>&2 commandDir = "$commandDir" echo 1>&2 sourceFilename = "$sourceFilename" echo 1>&2 language = "$language" echo 1>&2 mainClass = "$mainClass" echo 1>&2 executableFilename = "$executableFilename" echo 1>&2 compilerDir = "$compilerDir" echo 1>&2 classPath = "$classPath" echo 1>&2 compilerArgs = "$compilerArgs" echo 1>&2 linkerArgs = "$linkerArgs" echo 1>&2 execute = "$execute" echo 1>&2 firstArgIsCommandName = "$firstArgIsCommandName" echo 1>&2 verbose = "$verbose" } #------------------------------------------------------------------------------- # If we use zsh, we could do this: # zmodload zsh/stat # stat -F "%Y-%m-%d_%H-%M-%S" +mtime . ls-linux() { # 11742 2005-07-28 11:54:01.000000000 ( ls -dl --full-time --time-style=full-iso "$1" \ | sed 's,.*[0-9] \([12][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\) \([0-9][0-9]\):\([0-9][0-9]\):\([0-9][0-9]\).[0-9]* .*,\1.\2-\3-\4,' ) 2> /dev/null } ls-oldlinux() { # 11742 Tue Mar 01 17:22:50 2005 ( ls -dl --full-time "$1" \ | sed 's,.*[0-9] ... \(...\) \([0-9][0-9]\) \([0-9][0-9]\):\([0-9][0-9]\):\([0-9][0-9]\) \([12][0-9][0-9][0-9]\) .*,\4-\1-\2.\3-\5-\6,' ) 2> /dev/null } ls-bsd() { # 11742 Jul 28 12:38:31 2005 ( ls -dlT "$1" \ | awk ' BEGIN { months["Jan"] = "01" ; months["Feb"] = "02" ; months["Mar"] = "03" months["Apr"] = "04" ; months["May"] = "05" ; months["Jun"] = "06" months["Jul"] = "07" ; months["Aug"] = "08" ; months["Sep"] = "09" months["Oct"] = "10" ; months["Nov"] = "11" ; months["Dec"] = "12" } { month = sprintf(months[$6]) ; day = $7 ; time = $8 ; year = $9 # What about Europe? gsub(":", "-", time) date = year "-" month "-" day "_" time print date } ' ) 2> /dev/null } #------------------------------------------------------------------------------- set -e sourceInput= case $ourName in cg) # Invoked as cg if [[ $# == 0 ]] ; then echo 1>&2 "Usage: cg sourcefile.<extension> [ args ... ]" exit 2 fi sourceInput="$1" export commandName=${commandFile##*/} commandBasename="${commandName%.*}" commandExtension="${commandFile##*.}" sourceFilename=$commandName language=$(extensionToLanguage $commandExtension) compiler=$(languageToCompiler $language) ;; cgs) sourceInput=/dev/stdin export commandName=$ourName commandBasename=$commandName compilerOpt="$1" case "$compilerOpt" in -jsc) sourceFileName=$commandName.js ;; -javac | -gcj | -jikes) shift export commandName=$1 commandBasename=$commandName sourceFileName=$commandName.java ;; -gcc | -c99 | -c89 | -cc) sourceFileName=$commandName.c ;; -g++) sourceFileName=$commandName.cp ;; "") echo 1>&2 cgs: missing compiler option exit 2 ;; *) echo 1>&2 cgs: unknown compiler option: "$compilerOpt" exit 2 ;; esac compiler=${compilerOpt/-/} ;; *) sourceInput= # Invoked as compileAndGo # Collect the variable declarations at the top of $commmandFile. declarations="$( sed -n ' s,^[ ]*,, /^!#$/q /^compileAndGo/q /^[ ]*#/d s,^commandName,export commandName, /^echo[ ]/{ s/$/ ;/p d } s,\$\({*commandBasename\),\\$\1,g s,\$\({*commandDir\),\\$\1,g s,\$\({*sourceFilename\),\\$\1,g s,\$\({*language\),\\$\1,g s,\$\({*mainClass\),\\$\1,g s,\$\({*executableFilename\),\\$\1,g s,\$\({*compilerDir\),\\$\1,g s,\$\({*classPath\),\\$\1,g s,\$\({*compilerArgs\),\\$\1,g s,\$\({*linkerArgs\),\\$\1,g s,\$\({*execute\),\\$\1,g s,\$\({*cacheDir\),\\$\1,g s,\$\({*firstArgIsCommandName\),\\$\1,g s,[ ]*=[ ]*,=", s,$," ;, p ' "$commandFile" )" eval $declarations [[ ! -z ${commandName:="${1##*/}"} ]] commandBasename="${commandName%.*}" if (( 0$verbose >= 5 )) ; then echo 1>&2 \=== Declarations echo 1>&2 "$declarations" fi if [[ -z "$compiler" ]] ; then if [[ -z "$language" ]] ; then echo 1>&2 compileAndGo: compiler or language must be set trouble=true fi compiler=$(languageToCompiler $language) fi if [[ ! -z "$trouble" ]] ; then exit 2 fi ;; esac #------------------------------------------------------------------------------- # Collect the source code newsed() { local arg if sed --regex-extended < /dev/null >& /dev/null ; then arg=--regex-extended else arg=-E fi sed $arg "$@" } case "$sourceInput" in /dev/stdin) # from stdin sourceCode=$(cat) ;; '') # from the end of $commandFile sourceCode=$( newsed ' 1,/^(!#|[ ]*compileAndGo)/s,.*,, ' "$commandFile" ) if [[ -z "$sourceCode" ]] ; then echo 1>&2 "$commandName: Missing '#!compileAndGo' line before source code starts." exit 2 fi ;; *) # from the filename as first argument sourceCode=$(cat $1) ;; esac #------------------------------------------------------------------------------- # Construct the cacheDir variable. abi=$(uname -sm) abi=${abi// /-} # Why must I use `` instead of $() here? id=` case "$sourceInput" in /dev/stdin) local tmp=($(echo "$sourceCode" | cksum)) echo ${tmp[0]}${tmp[1]} ;; *) case "$abi" in Linux* | CYGWIN*) ls-linux "$1" \ || ls-oldlinux "$1" ;; *) ls-bsd "$1" \ || ls-linux "$1" \ || ls-oldlinux "$1" ;; esac \ || ( local tmp=($(echo "$sourceCode" | cksum)) echo ${tmp[0]}${tmp[1]} ) ;; esac ` compilerPath=$(type -p "$compiler" 2> /dev/null) || compilerPath=$compiler realHOME=$(eval 'echo ~'$(whoami)) if [[ -x $realHOME/Library/Caches ]] ; then # Mac OS X cacheDirRoot=$realHOME/Library/Caches/CompileAndGo else cacheDirRoot=$realHOME/.compileAndGo fi cacheDirParent=$cacheDirRoot/${commandName} cacheDir=$cacheDirParent/$abi/${id}_${compilerPath//\//-} #------------------------------------------------------------------------------- # Apply defaults and then set the variables again. compilerName=${compiler##*/} # Some settings common among different compiler groups: case $compilerName in javac* | jikes*) [[ ! -z ${mainClass:="$commandBasename"} ]] [[ ! -z ${sourceFilename:="${mainClass}.java"} ]] ;; gcj*) [[ ! -z ${mainClass:="$commandBasename"} ]] [[ ! -z ${sourceFilename:="${mainClass}.java"} ]] [[ ! -z ${executableFilename:="$commandBasename"} ]] [[ ! -z ${execute:="PATH=$cacheDir:$PATH $executableFilename"} ]] ;; gcc* | g++* | c89* | c99*) [[ ! -z ${executableFilename:="$commandBasename"} ]] [[ ! -z ${execute:="PATH=$cacheDir:$PATH $executableFilename"} ]] ;; esac case $compilerName in jsc*) [[ ! -z ${mainClass:="$commandBasename"} ]] [[ ! -z ${sourceFilename:="${mainClass}.js"} ]] [[ ! -z ${executableFilename:="${mainClass}.class"} ]] [[ ! -z "${execute:="${javaBinDir}java -cp $cacheDir${classPath:+:$classPath} $mainClass"}" ]] [[ ! -z "${compilerArgs:="-o $executableFilename $cacheDir/$sourceFilename"}" ]] compileCmd="java -cp $cacheDir${classPath:+:$classPath} org.mozilla.javascript.tools.jsc.Main $compilerArgs" ;; javac* | jikes*) [[ ! -z ${executableFilename:="${mainClass}.class"} ]] sourceVersion= case $compilerName in javac*) if [[ $compilerName == $compiler ]] ; then compilerDir= else compilerDir=${compiler%/*}/ fi [[ ! -z "${execute:="${compilerDir}java -cp $cacheDir${classPath:+:$classPath} $mainClass"}" ]] # Prepare to tell javac to compile for the latest language version it supports sourceVersion="-source $(${compilerDir}java -version 2>&1 | sed -n '1s,[^"]*"\([1-9][1-9]*\.[1-9][1-9]*\).*,\1,p')" ;; jikes*) if [[ -z "$classPath" && -z "$compilerArgs" && -z "$CLASSPATH" ]] ; then # Mac: export CLASSPATH=/System/Library/Frameworks/JavaVM.framework/Classes/classes.jar echo 1>&2 compileAndGo: for jikes, if neither classPath nor CLASSPATH are set, compilerArgs must be set. exit 2 fi [[ ! -z "${execute:="java -cp $cacheDir${classPath:+:$classPath} $mainClass"}" ]] ;; esac [[ ! -z "${compilerArgs:="$sourceVersion -d $cacheDir -sourcepath . ${classPath:+-classpath $classPath} $cacheDir/$sourceFilename"}" ]] compileCmd="$compiler $compilerArgs" ;; gcj*) [[ ! -z ${compilerArgs:="--main=$mainClass -o $cacheDir/$commandBasename $cacheDir/$sourceFilename"} ]] compileCmd="$compiler $compilerArgs $linkerArgs" ;; gcc* | g++* | c89* | c99* | clang | clang++) case $compilerName in cc* | gcc* | c89* | c99* | clang) [[ ! -z ${sourceFilename:="${commandName}.c"} ]] ;; g++* | clang++) [[ ! -z ${sourceFilename:="${commandName}.cp"} ]] ;; esac [[ ! -z ${compilerArgs:="-O2 -o $cacheDir/$executableFilename $cacheDir/$sourceFilename"} ]] compileCmd="$compiler $compilerArgs $linkerArgs" ;; esac #------------------------------------------------------------------------------- # Set the variables if [[ ! $ourName == cg ]] ; then vars=$( echo "$declarations" \ | sed -n 's,\([^=]*\)=.*,\1,p' ) cmdSetters=$( for x in $vars do echo eval "'"${x}='"'$(eval echo \$$x)'"'"'" done ) eval "$cmdSetters" fi if (( 0$verbose >= 3 )) ; then echoVariables "=== the variables before defaults" fi if [[ $ourName == cg ]] ; then if (( 0$verbose >= 4 )) ; then echo 1>&2 \=== eval command to set variables echo2 1>&2 eval "$cmdSetters" fi fi #------------------------------------------------------------------------------- # Check that all the required variables are set. for x in sourceFilename executableFilename compilerArgs execute do eval 'if [ -z "'\$$x'" ] ; then trouble=true ; fi' done if [[ ! -z "$trouble" ]] ; then echo 1>&2 compileAndGo: unknown compiler setting "$compiler" ; exit 2 ; fi for x in sourceFilename executableFilename compilerArgs execute do eval 'if [ -z "'\$$x'" ] ; then echo 1>&2 compileAndGo: $x must be set ; fi' done if [[ ! -z "$trouble" ]] ; then exit 2 ; fi [[ ! -z ${firstArgIsCommandName:=false} ]] [[ ! -z ${verbose:=0} ]] eval "$cmdSetters" if (( 0$verbose >= 3 )) ; then echoVariables "=== the variables after defaults" fi #set -x #------------------------------------------------------------------------------- # Compile if necessary # shorthand cachedExecutable=$cacheDir/$executableFilename # The security precautions go like this: # The executable and the folder in which it resides are # * owned by user # * 700 permissions (rwx------) # The important facts are: # * Only the user or root can chmod a file or folder owned by him. # * Only the user or root can write into a file or folder that is 700. # * Only root can chown a file or folder to the user. # so only the user or root can construct a suitable file in the suitable # folder. No one else can. That's about as good as it can get on unix. # The attack would be limited to finding some existing folder containing # an executable of the correct name, both owned by the user and 700, # then moving the folder into the appropriate path. # The implementation should be expanded to require that all folders from # $cacheDir through $cacheDirParent must be owned by user and be 700. if [[ ! -O $cachedExecutable ]] ; then if [[ -e $cachedExecutable ]] ; then echo 1>&2 "$commandName: Aborting because $cachedExecutable exists," echo 1>&2 "$commandName: and you don't own it." echo 1>&2 "$commandName: This is a possible security violation." exit 2 fi # Try to make it harder for others to tamper with our cache. umask 077 # Insist that $cacheDirParent is a directory and is owned by the user. if [[ -d $cacheDirParent ]] ; then if [[ ! -O $cacheDirParent ]] ; then echo 1>&2 "$commandName: Aborting because $cacheDirParent/ exists, and you don't own it." echo 1>&2 "$commandName: This is a security risk." exit 2 fi chmod 700 $cacheDirParent else mkdir -p $cacheDirParent echo > $cacheDirParent/../README "See http://Yost.com/computers/compileAndGo" fi mkdir -p $cacheDir # Compile the source. if (( 0$verbose == 1 )) ; then echo 1>&2 "[ $commandName: compiling. ]" elif (( 0$verbose >= 2 )) ; then echo -n 1>&2 "[ " echo2 -n 1>&2 "$compileCmd" echo 1>&2 " ]" fi echo "$sourceCode" > $cacheDir/$sourceFilename eval $compileCmd # Make a canonical name we can look at to determine access time. ln -f $cachedExecutable $cacheDir/.executable fi #------------------------------------------------------------------------------- # Execute the built program. if [[ "$firstArgIsCommandName" != true ]] ; then shift fi if (( 0$verbose >= 2 )) ; then echo -n 1>&2 "[ " echo2 -n 1>&2 $execute "$@" echo 1>&2 " ]" fi eval "$execute"' "$@"' status=$? if [[ true ]] ; then # Run a background task to clean the cache occasionally. ( # Check every this-many days. checkInterval="-mtime -7" # Max number of days a version cam be unused and not be removed. maxAge="-atime +14" # Every $checkInterval days, remove stuff not used in $maxAge days. stamp=$(nice -n 20 find $cacheDirRoot -maxdepth 1 -name .timestamp $checkInterval) if [[ ! -z "$stamp" ]] ; then # Too soon exit fi nice -n 20 touch $cacheDirRoot/.timestamp # Remove dirs of executable versions not accessed in the last $maxAge days. candidates=$(nice -n 20 find $cacheDirRoot -mindepth 3 -name .executable $maxAge) if [[ ! -z "$candidates" ]] ; then #echo "$candidates" echo "$candidates" \ | nice -n 20 sed 's,/.executable,,' \ | nice -n 20 xargs rm -rf fi ) \ > /dev/null 2>&1 \ & fi exit $status # Copyright 2005-2010 Dave Yost <Dave@Yost.com> # All rights reserved. # This version is # compileAndGo 5.0 2010-11-06 # which at time of this publication can be found at: # http://Yost.com/computers/compileAndGo # Redistribution and use in the form of source code or derivative data built # from the source code, with or without modification, are permitted provided # that the following conditions are met: # 1. THE USER AGREES THAT THERE IS NO WARRANTY. # 2. If and only if appropriate, the above phrase "This version is" must be # followed by the phrase "a modified form of" or "extracted from" or # "extracted and modified from". # 3. Redistributions of source code must retain this notice intact. # 4. Redistributions in the form of derivative data built from the source # code must reproduce this notice intact in the documentation and/or other # materials provided with the distribution, and each file in the derivative # data must reproduce any Yost.com URI included in the original distribution. # 5. Neither the name of Dave Yost nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # 6. Written permission by the author is required for redistribution as part # of a commercial product. # This notice comprises all text from "Copyright" above through the end of # this sentence. |
compileAndGo
computes a hash (using cksum
) of the source code
from which it constructs a directory name (key),
which it looks for in a directory (hashtable) of cached executables.
compileAndGo
could have been and should have been written decades ago. I first thought of it in March, 2004, as a special-purpose technique for Java programs.
A shell enhancement could allow you to invoke a source file as a command, similar to the way cg
does.
A shell enhancement could allow a shell script to include functions written in compiled languages,
using the same hash-of-source-code trick used by compileAndGo
.
A special-case Java implementation could use a single JVM to do compiling and to run the compiled program. Might make a nice addition to the JDK.
If you would like to implement compileAndGo
in a compiled language, please coordinate with me.
To keep the cache clean, there is code at the end of the script you can turn on. After the invoked command has completed, it runs a background process that every once in a while looks around for old versions and unused versions and deletes them from the cache.
So far, compileAndGo
has been tested only on Mac OS X 10.4.2 and two versions of linux.
You will note that the compileAndGo
scripts don’t just invoke
#!/usr/local/bin/compileAndGo
but instead they invoke compileAndGo
via env
, like this:
#!/usr/bin/env /usr/local/bin/compileAndGo
This is because compileAndGo
is itself implemented as a #!
script. For more information, see the Wikipedia article on Shebang
ls
classpath
and commandDir
and exports commandName
. Also, download link added
CLASSPATH
setting in the parameters, echo, and bug fixes
commandName
is now optional.
cgs
variant of the cg
command
~/.TemporaryItems/
on Mac; optional automatic cache cleaning
javac
to parse for the highest source version it can accept
language
parameter added