//
// Copyright (c) 2010, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 1 Feb 10 Brian Frank Creation
//
**
** JarDist compiles a set of Fantom pods into a single Java JAR file.
**
class JarDist : JdkTask
{
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
**
** Construct uninitialized task
**
new make(BuildScript script)
: super(script)
{
}
//////////////////////////////////////////////////////////////////////////
// Run
//////////////////////////////////////////////////////////////////////////
**
** Run the javac task
**
override Void run()
{
log.info("JarDist")
verifyConfig
log.indent
initTempDir
sysClasses
podNames.each |name| { podClasses(name) }
etcFiles
main
manifest
jar
cleanupTempDir
log.info("Success [$outFile]")
log.unindent
}
private Void verifyConfig()
{
if (podNames.isEmpty) throw fatal("Not configured: JarDist.podNames")
if (podNames.contains("sys")) throw fatal("sys is implied in JarDist.podNames")
if (outFile == null) throw fatal("Not configured: JarDist.outFile")
if (mainMethod == null) throw fatal("Not configured: JarDist.mainMethod")
m := Slot.findMethod(mainMethod, false)
if (m == null) throw fatal("mainMethod not found: $mainMethod")
if (!m.isStatic) throw fatal("mainMethod not static: $mainMethod")
if (!isMainParamsOk(m)) throw fatal("mainMethod params must be () or (Str[]): $mainMethod")
mainMethodArg = !m.params.isEmpty
}
private static Bool isMainParamsOk(Method m)
{
if (m.params.isEmpty) return true
if (m.params.size == 1 && m.params[0].type == Str[]#) return true
return false
}
private Void initTempDir()
{
tempDir = (Env.cur.tempDir + `jardist-$Int.random.toHex/`).create
log.debug("TempDir [$tempDir]")
}
private Void cleanupTempDir()
{
tempDir.delete
manifestFile.delete
}
private Void sysClasses()
{
log.info("Pod [sys]")
extractClassfilesToTemp(script.devHomeDir + `lib/java/sys.jar`)
reflect("sys")
}
private Void podClasses(Str podName)
{
log.info("Pod [$podName]")
// open as zip and
podFile := Env.cur.findPodFile(podName)
if (!podFile.exists) throw Err("Pod not found: $podFile")
podZip := Zip.open(podFile)
meta := podZip.contents[`/meta.props`].readProps
podZip.close
// double check dependencies; for basic sanity check
// we just check names not version numbers
meta.get("pod.depends", "").split(';').each |dependStr|
{
if (dependStr.isEmpty) return
depend := Depend(dependStr)
if (!podNames.contains(depend.name) && depend.name != "sys")
throw fatal("Missing dependency for '$podName': $depend")
}
// if pod already has its java native code, then the pod
// zip should already contain all the classfiles, otherwise
// we need to kick off a JStub
if (meta["pod.native.java"] == "true")
{
log.info(" Extract pre-stubbed classfiles")
extractClassfilesToTemp(podFile)
}
else
{
log.info(" JStub to classfiles")
// stub into Java classfiles using JStub
Exec(script,
[javaExe,
"-cp", (script.devHomeDir + `lib/java/sys.jar`).osPath,
"-Dfan.home=$script.devHomeDir.osPath",
"fanx.tools.Jstub",
"-d", tempDir.osPath,
podName]).run
// stub is "tempDir/{pod}.jar" - extract to tempDir and then delete it
jar := tempDir + `${podName}.jar`
extractClassfilesToTemp(jar)
jar.delete
}
reflect(podName)
}
private Void extractClassfilesToTemp(File zipFile)
{
zip := Zip.open(zipFile)
copyOpts := ["overwrite":true]
zip.contents.each |f|
{
if (f.isDir) return
if (f.ext != "class") return
path := f.uri.toStr[1..-1]
dest := tempDir + path.toUri
f.copyTo(dest, copyOpts)
}
zip.close
}
private Void reflect(Str podName)
{
copyOpts := ["overwrite":true]
resources := Str[,]
zip := Zip.open(Env.cur.findPodFile(podName))
zip.contents.each |f|
{
if (f.isDir) return
if (f.name == "meta.props" || f.ext == "def" || f.ext == "fcode")
{
dest := tempDir + "reflect/${podName}${f.pathStr}".toUri
f.copyTo(dest, copyOpts)
}
else
{
// decide if this is a resource file we should bundle
if (f.ext == "class") return
if (f.ext == "apidoc") return
resources.add(f.pathStr)
dest := tempDir + "res/${podName}${f.pathStr}".toUri
f.copyTo(dest, copyOpts)
}
}
(tempDir + `res/${podName}/res-manifest.txt`).out.print(resources.join("\n")).close
}
private Void etcFiles()
{
copyEtcFile(`etc/sys/timezones.ftz`)
copyEtcFile(`etc/sys/timezone-aliases.props`, `res/sys/timezone-aliases.props`)
copyEtcFile(`etc/sys/ext2mime.props`, `res/sys/ext2mime.props`)
copyEtcFile(`etc/sys/units.txt`)
}
private Void copyEtcFile(Uri uri, Uri destUri := uri)
{
src := script.devHomeDir + uri
dest := tempDir + destUri
src.copyTo(dest)
}
private Void manifest()
{
log.info("Manifest")
this.manifestFile = Env.cur.workDir + `Manifest.mf`
out := this.manifestFile.out
out.printLine("Manifest-Version: 1.0")
out.printLine("Main-Class: fanjardist.Main")
out.printLine("Created-By: Fantom JarDist $typeof.pod.version")
out.close
}
private Void main()
{
log.info("Main")
// explicitly initialize all the pod constants
podInits := StrBuf();
podNames.each |podName|
{
podInits.add(""" Env.cur().loadPodClass(Pod.find("$podName"));\n""")
}
mainArgs := mainMethodArg ? "Env.cur().args()" : ""
// write out Main Java class
file := tempDir + `fanjardist/Main.java`
file.out.print(
"""package fanjardist;
import fan.sys.*;
public class Main
{
public static void boot()
{
boot(new String[0]);
}
public static void boot(String[] args)
{
System.getProperties().put("fan.jardist", "true");
System.getProperties().put("fan.home", ".");
Sys.boot();
Sys.bootEnv.setArgs(args);
$podInits
}
public static void main(String[] args)
{
try
{
boot(args);
Method m = Slot.findMethod("$mainMethod");
m.call($mainArgs);
}
catch (Err e) { e.trace(); }
catch (Throwable e) { e.printStackTrace(); }
}
}
""").close
// compile main
Exec(script,
[javacExe,
"-cp", tempDir.osPath,
"-d", tempDir.osPath,
file.osPath], tempDir).run
// delete source file once we have compiled into .class file
Delete(script, file).run
}
private Void jar()
{
// jar everything back up outFile
log.info("Jar [$outFile]")
Exec(script,
[jarExe,
"cfm", outFile.osPath, manifestFile.osPath,
"-C", tempDir.osPath,
"."], tempDir).run
}
//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////
** Required output jar file to create
File? outFile
** Qualified name of main method to run for JAR.
** This must be a static void method with no arguments.
Str? mainMethod
** Does the main method accept a Str[] arg
Bool mainMethodArg
** List of pods to compile into JAR; sys is always implied
Str[] podNames := Str[,]
private File? tempDir // initTempDir
private File? manifestFile // manifest
}