JEP draft: Launch Single-File Source-Code Programs

OwnerJonathan Gibbons
Created2017/12/01 19:04
Updated2018/02/11 21:30
TypeFeature
StatusDraft
Componenttools / javac
ScopeJDK
Discussioncompiler dash dev at openjdk dot java dot net
Priority4
Issue8192920

Summary

Enhance the java launcher to support running a program supplied as a single file of Java source code.

Motivation

Single-file programs -- where the whole program fits in a single source file -- are common in the early stages of learning Java, and when writing small utility programs. In this context, it is pure ceremony to have to compile the program before running it. In addition, a single source file may compile to multiple class files, which adds packaging overhead to the simple goal of "run this program". It is desirable to be able to run the program directly from source with the java launcher:

java HelloWorld.java

Description

The Java launcher currently operates in three modes: launching a class, a JAR, or the main program of a module. A new, fourth mode is added: launching a class declared in a source file.

Source-file mode is determined by considering two items on the command line.

  1. The first item on the command line that is neither an option nor part of an option. (In other words, the item that until now has been the class name.)
  2. An optional --source option.

If the "class name" identifies an existing file with the .java extension, source-file mode will be selected, with that file to be compiled and run. If the file does not have the .java extension, the --source option may be used to force source-file mode. This is for cases such as when the source file is a "script" to be executed and the name of the source file does not follow the normal naming conventions for Java source files. (See "Shebang" files below.)

In source-file mode, the effect is as though the source-file is compiled into memory, and the first class found in the source file is executed. For example, if a file called HelloWorld.java contains a class called hello.World, then the command

java HelloWorld.java

will be equivalent to

javac -d <memory> HelloWorld.java   
java -cp <memory> hello.World

Any arguments placed after the name of the source file in the original command line will be passed to the compiled class when it is executed. For example, if a file called Factorial.java contains a class called Factorial to calculate the factorials of its arguments, then the command

java Factorial.java 3 4 5

will be equivalent to

javac -d <memory> Factorial.java   
java -cp <memory> Factorial 3 4 5

Java command-line argument files (@-files) may be used in the standard way in source-file mode. Long lists of arguments for either the VM or the program being invoked may be placed in files which are specified on the command-line by prefixing the filename with an @ character.

Note that there is a potential minor ambiguity when using a simple command-line like java HelloWorld.java. Previously, HelloWorld.java would have been interpreted as a class called java in a package called HelloWorld, but which will now be resolved in favor of a file called HelloWorld.java if such a file exists. Given that such a class name and such a package name both violate the nearly-universally-followed naming conventions, and given the unlikeliness of such a class being on the class path and a like-named file being in the current directory, this seems an acceptable compromise.

Implementation

Source-file mode requires the presence of the jdk.compiler module. When source-file mode for a file Foo.java is requested, the launcher will behave as if the command line were translated to:

java [ VM args ] -m jdk.compiler/jdk.somepackage.SourceLauncher Foo.java [ program args ]

The SourceLauncher class will programmatically invoke the compiler, compile the source to an in-memory representation, create a class loader to load compiled classes from that in-memory representation, and invoke the standard main(String[]) method of the first top-level class found in the source file, in a class loading environment that includes the class path and module path specified on the command line, as well as the newly compiled classes. This environment will be in an unnamed module. For both the compilation and subsequent execution, it will be as though --add-modules=ALL-DEFAULT is in effect. Any arguments appearing after the name of the file on the command line will be passed to the main(String[]) method in the obvious way. For "shebang" files, this includes options specified in the first line of the file, and any arguments provided on the command line when the file is executed.

When the compiler is invoked, it will have access to any relevant VM args, such as those to define the class path, module path, and the module graph, and will use those arguments to configure the compilation environment. No provision is made to tunnel any additional options to the compiler. Implicit compilation of additional source files is not supported.

In source-file mode, the compiler does not enforce the optional restriction defined at the end of JLS 7.6, that a type in a named package should exist in a file whose name is composed from the type name followed by the .java extension.

If the class that is invoked throws an exception, that exception will be passed back to the launcher for handling in the normal way. However, the initial stackframes leading up to the execution of the class will be removed from the stacktrace of the exception. The intent is that the handling of the exception will be similar to the handling if the class had been executed directly by the launcher itself.

"Shebang" files

Single-file programs are also common when the task at hand needs a small utility program. In this context, it is desirable to be able to run a Java program directly from source using the "#!" mechanism on Unix-derived systems, such as MacOS and Linux. This is a mechanism provided by the operating system which allows script or source code to be placed in any conveniently named executable file whose first line begins with #! and which specifies the name of a program to "execute" the contents of the file.

A "shebang" file to invoke the Java launcher using source-file mode will typically begin with something like:

#!/path/to/java --source

To allow for such files in source-file mode, if the file begins with #! the contents of the first line up to but not including the first newline are ignored by the launcher, while the rest of the file is passed to the compiler, javac. The content of the file that appears after the first line must consist of a valid CompilationUnit as defined by the edition of the Java Language Specification that is appropriate to the version of the platform being used to run the program. As such, no changes to the JLS are required in support of this feature. If the source file contains errors, appropriate error messages will be written to the standard error stream, and the launcher will exit with a non-zero exit code. When the file begins with #!, the newline at the end of the first line is preserved so that the line numbers in any error messages remain unchanged.

In a "shebang" file, the first two bytes must be 0x23 0x21, the two-character ASCII encoding of #!. All subsequent bytes are read with the default platform character encoding that is in effect.

A first line beginning #! is only required when it is desired to execute the file with the operating system's "shebang" mechanism. There is no need for any special first line when the Java launcher is used explicitly to run the code in a source file, as in the HelloWorld.java and Factorial.java examples, given above.

Alternatives

The status quo has worked for 20+ years; we could continue to do so.

We could create a source launcher, but call it something else besides java, such as jrun. Given the number of execution modes the launcher already has, this would likely be perceived as a gratuitous difference.

We could delegate the task of "one-off runs" to the jshell tool. While this may at first seem obvious, this was an explicit non-goal in the design of jshell. The jshell tool was designed to be an interactive shell, and many design decisions were made in favor of providing a better interactive experience. Burdening it with the additional constraints of being the batch runner would detract from the interactive experience.