nitj: compile generated Java files using make and Javac
authorAlexandre Terrasa <alexandre@moz-code.org>
Fri, 17 Jul 2015 17:16:49 +0000 (13:16 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Tue, 21 Jul 2015 21:23:19 +0000 (17:23 -0400)
This will be used to compile generated files into an executable jar.
Also introduce a shell script to directly run the generated jar to come like it was a binary.

Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

src/compiler/java_compiler.nit

index 19adee5..3d1384d 100644 (file)
@@ -48,9 +48,169 @@ redef class ModelBuilder
                var time0 = get_time
                toolcontext.info("*** GENERATING JAVA ***", 1)
 
-               toolcontext.info("NOT YET IMPLEMENTED", 0)
+               var compiler = new JavaCompiler(mainmodule, self, runtime_type_analysis)
+               compiler.do_compilation
 
                var time1 = get_time
                toolcontext.info("*** END GENERATING JAVA: {time1-time0} ***", 2)
+               write_and_make(compiler)
        end
+
+       # Write Java code and compile it into an executable jar
+       fun write_and_make(compiler: JavaCompiler) do
+               var time0 = get_time
+               toolcontext.info("*** WRITING JAVA ***", 1)
+
+               compiler.compile_dir.mkdir
+
+               var jfiles = write_java_files(compiler)
+
+               var time1 = get_time
+               toolcontext.info("*** END WRITING JAVA: {time1-time0} ***", 2)
+
+               time0 = time1
+               toolcontext.info("*** COMPILING JAVA ***", 1)
+
+               build_with_make(compiler, jfiles)
+               write_shell_script(compiler)
+
+               time1 = get_time
+               toolcontext.info("*** END COMPILING JAVA: {time1-time0} ***", 2)
+       end
+
+       # Write files managed by `compiler` into concrete files
+       fun write_java_files(compiler: JavaCompiler): Array[String] do
+               var jfiles = new Array[String]
+               for f in compiler.files do
+                       var file = new FileWriter.open("{compiler.compile_dir}/{f.filename}")
+                       for line in f.lines do file.write(line)
+                       file.close
+                       jfiles.add(f.filename)
+               end
+               return jfiles
+       end
+
+       # Compile Java generated files using `make`
+       fun build_with_make(compiler: JavaCompiler, jfiles: Array[String]) do
+               write_manifest(compiler)
+               write_makefile(compiler, jfiles)
+               var compile_dir = compiler.compile_dir
+               var outname = compiler.outname.to_path.filename
+               toolcontext.info("make -N -C {compile_dir} -f {outname}.mk", 2)
+               var res
+               if toolcontext.verbose_level >= 3 then
+                       res = sys.system("make -B -C {compile_dir} -f {outname}.mk 2>&1")
+               else
+                       res = sys.system("make -B -C {compile_dir} -f {outname}.mk 2>&1 > /dev/null")
+               end
+               if res != 0 then toolcontext.error(null, "make failed! Error code: {res}.")
+       end
+
+       # Write the Makefile used to compile Java generated files into an executable jar
+       fun write_makefile(compiler: JavaCompiler, jfiles: Array[String]) do
+               # list class files from jfiles
+               var ofiles = new List[String]
+               for f in jfiles do ofiles.add(f.strip_extension(".java") + ".class")
+
+               var compile_dir = compiler.compile_dir
+               var outname = compiler.outname.to_path.filename
+               var outpath = (sys.getcwd / compiler.outname).simplify_path
+               var makename = "{compile_dir}/{outname}.mk"
+               var makefile = new FileWriter.open(makename)
+
+               makefile.write("JC = javac\n")
+               makefile.write("JAR = jar\n\n")
+
+               makefile.write("all: {outpath}.jar\n\n")
+
+               makefile.write("{outpath}.jar: {compiler.mainmodule.jname}_Main.class\n")
+               makefile.write("\t$(JAR) cfm {outpath}.jar {outname}.mf {ofiles.join(" ")}\n\n")
+
+               makefile.write("{compiler.mainmodule.jname}_Main.class:\n")
+               makefile.write("\t$(JC) {jfiles.join(" ")}\n\n")
+
+               makefile.write("clean:\n")
+               makefile.write("\trm {ofiles.join(" ")} 2>/dev/null\n\n")
+
+               makefile.close
+               toolcontext.info("Generated makefile: {makename}", 2)
+       end
+
+       # Write the Java manifest file
+       private fun write_manifest(compiler: JavaCompiler) do
+               var compile_dir = compiler.compile_dir
+               var outname = compiler.outname.to_path.filename
+               var maniffile = new FileWriter.open("{compile_dir}/{outname}.mf")
+               maniffile.write("Manifest-Version: 1.0\n")
+               maniffile.write("Main-Class: {compiler.mainmodule.jname}_Main\n")
+               maniffile.close
+       end
+
+       # Write a simple bash script that runs the jar like it was a binary generated by nitc
+       private fun write_shell_script(compiler: JavaCompiler) do
+               var outname = compiler.outname
+               var shfile = new FileWriter.open(outname)
+               shfile.write("#!/bin/bash\n")
+               shfile.write("java -jar {outname}.jar \"$@\"\n")
+               shfile.close
+               sys.system("chmod +x {outname}")
+       end
+end
+
+# Compiler that translates Nit code to Java code
+class JavaCompiler
+       # The main module of the program currently compiled
+       var mainmodule: MModule
+
+       # Modelbuilder used to know the model and the AST
+       var modelbuilder: ModelBuilder
+
+       # The result of the RTA (used to know live types and methods)
+       var runtime_type_analysis: RapidTypeAnalysis
+
+       # Where to generate tmp files
+       var compile_dir: String is lazy do
+               var dir = modelbuilder.toolcontext.opt_compile_dir.value
+               if dir == null then dir = "nitj_compile"
+               return dir
+       end
+
+       # Name of the generated executable
+       var outname: String is lazy do
+               var name = modelbuilder.toolcontext.opt_output.value
+               if name == null then name = mainmodule.jname
+               return name
+       end
+
+       # The list of all associated files
+       # Used to generate .java files
+       var files: Array[JavaCodeFile] = new Array[JavaCodeFile]
+
+       # Force the creation of a new file
+       # The point is to avoid contamination between must-be-compiled-separately files
+       fun new_file(name: String): JavaCodeFile do
+               var file = new JavaCodeFile(name)
+               files.add(file)
+               return file
+       end
+
+       # Compile Nit code to Java
+       fun do_compilation do
+               modelbuilder.toolcontext.info("NOT YET IMPLEMENTED", 0)
+       end
+end
+
+# A file containing Java code.
+class JavaCodeFile
+
+       # File name
+       var filename: String
+
+       # Lines to write
+       var lines: List[String] = new List[String]
+end
+
+redef class MEntity
+       # A Java compatible name for `self`
+       private fun jname: String do return name.to_cmangle
 end