//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 3 Nov 06 Brian Frank Creation
//
using compiler
**
** BuildPod is the base class for build scripts used to manage
** building a Fantom source code and resources into a Fantom pod.
**
** See `docTools::Build` for details.
**
abstract class BuildPod : BuildScript
{
//////////////////////////////////////////////////////////////////////////
// Env
//////////////////////////////////////////////////////////////////////////
**
** Required name of the pod.
**
Str? podName := null
**
** Required summary description of pod.
**
Str? summary := null
**
** Version of the pod - default is set to `BuildScript.config`
** prop 'buildVersion'.
**
Version version := Version(config("buildVersion", "0"))
**
** List of dependencies for pod formatted as `sys::Depend`.
** Strings are automatically run through `BuildScript.applyMacros`.
**
Str[] depends := Str[,]
**
** Pod meta-data name/value pairs to compile into pod. See `sys::Pod.meta`.
**
Str:Str meta := Str:Str[:] { ordered = true }
**
** Pod index name/value pairs to compile into pod. See `sys::Env.index`.
** The index values can be a single Str or a Str[] if there are
** multiple values mapped to one key.
**
Str:Obj index := Str:Obj[:]
**
** Indicates if if fandoc API should be included in the documentation.
** By default API *is* included.
**
Bool docApi := true
**
** Indicates if if source code should be included in the pod/documentation.
** By default source code it *not* included.
**
Bool docSrc := false
**
** List of Uris relative to build script of directories containing
** the Fan source files to compile.
**
Uri[]? srcDirs
**
** List of optional Uris relative to build script of directories of
** resources files to package into pod zip file. If a file has a "jar"
** extension then its contents are unzipped into the target pod.
**
Uri[]? resDirs
**
** List of Uris relative to build script of directories containing
** the Java source files to compile for Java native methods.
**
Uri[]? javaDirs
** List of Uris relative to build script of directories containing
** the JNI C source files to compile.
Uri[]? jniDirs
** If non-null, whitelist of platforms JNI should be enabled for.
** Platform string may be full platform name ("macosx-x86_64") or OS
** only ("macosx").
Str[]? jniPlatforms
**
** List of Uris relative to build script of directories containing
** the C# source files to compile for .NET native methods.
**
Uri[]? dotnetDirs
**
** List of Uris relative to build script of directories containing
** the JavaScript source files to compile for JavaScript native methods.
**
Uri[]? jsDirs
**
** List of Uris relative to build script that should be searched for '.props'
** files to compile to JavaScript. You may also give relative paths to files
** with a '.props' ext. If this field is null, it defaults to `resDirs`.
**
Uri[]? jsProps
**
** The directory to look in for the dependency pod file (and
** potentially their recursive dependencies). If null then we
** use the compiler's own pod definitions via reflection (which
** is more efficient). As a general rule you shouldn't mess
** with this field - it is used by the 'build' and 'compiler'
** build scripts for bootstrap build.
**
Uri? dependsDir := null
**
** Directory to output pod file. By default it goes into
** "{Env.cur.workDir}/lib/fan/"
**
Uri outPodDir := Env.cur.workDir.plus(`lib/fan/`).uri
**
** Directory to output documentation (docs always get placed in sub-directory
** named by pod). By default it goes into
** "{Env.cur.workDir}/doc/"
**
Uri outDocDir := Env.cur.workDir.plus(`doc/`).uri
//////////////////////////////////////////////////////////////////////////
// Validate
//////////////////////////////////////////////////////////////////////////
private Void validate()
{
if (podName == null) throw fatal("Must set BuildPod.podName")
if (summary == null) throw fatal("Must set BuildPod.summary")
// boot strap checking
if (["sys", "build", "compiler", "compilerJava"].contains(podName))
{
if (Env.cur.homeDir == devHomeDir)
throw fatal("Must update 'devHome' for bootstrap build")
}
}
//////////////////////////////////////////////////////////////////////////
// Compile
//////////////////////////////////////////////////////////////////////////
**
** Compile the source into a pod file and all associated
** natives. See `compileFan`, `compileJava`, and `compileDotnet`.
**
@Target { help = "Compile to pod file and associated natives" }
virtual Void compile()
{
validate
log.info("compile [$podName]")
log.indent
compileFan
compileJava
compileJni
// TODO-FACET
// compileDotnet
log.unindent
}
**
** Compile to all classes to run in Node.js
**
@Target { help = "Compile all types to run in Node.js" }
virtual Void nodeJs()
{
switch (podName)
{
case "compilerJs":
case "compilerEs":
case "testCompiler":
return
}
validate
log.info("nodeJs [$podName]")
log.indent
compileNodeJs
log.unindent
}
//////////////////////////////////////////////////////////////////////////
// Compile Fan
//////////////////////////////////////////////////////////////////////////
**
** Compile Fan code into pod file
**
virtual Void compileFan()
{
// generate standard compiler input
ci := stdFanCompilerInput
// subclass hook
onCompileFan(ci)
try
{
Compiler(ci).compile
}
catch (CompilerErr err)
{
// all errors should already be logged by Compiler
throw FatalBuildErr()
}
catch (Err err)
{
log.err("Internal compiler error")
err.trace
throw FatalBuildErr.make
}
}
@NoDoc protected virtual CompilerInput stdFanCompilerInput()
{
// add my own meta
meta := this.meta.dup
meta["pod.docApi"] = docApi.toStr
meta["pod.docSrc"] = docSrc.toStr
meta["pod.native.java"] = (javaDirs != null && !javaDirs.isEmpty).toStr
meta["pod.native.jni"] = (jniDirs != null && !jniDirs.isEmpty).toStr
meta["pod.native.dotnet"] = (dotnetDirs != null && !dotnetDirs.isEmpty).toStr
meta["pod.native.js"] = (jsDirs != null && !jsDirs.isEmpty).toStr
// TODO: add additinal meta props defined by config file/env var
// this behavior is not guaranteed in future versions, rather we
// need to potentially overhaul how build data is defined
// See topic https://fantom.org/forum/topic/1584
config("meta", "").split(',').each |pair|
{
if (pair.isEmpty) return
tuples := pair.split('=')
if (tuples.size != 2) throw Err("Invalid config meta: $pair")
meta[tuples[0]] = tuples[1]
}
// if stripTest config property is set to true then don't
// compile any Fantom code under test/ or include any res files
srcDirs := this.srcDirs
resDirs := this.resDirs
if (config("stripTest", "false") == "true")
{
if (srcDirs != null) srcDirs = srcDirs.dup.findAll |uri| { uri.path.first != "test" }
if (resDirs != null) resDirs = resDirs.dup.findAll |uri| { uri.path.first != "test" }
}
// stripDocs overrides the configured docApi
if (config("stripDocs", "false") == "true")
docApi = false
// stripSrc overrides the configured docSrc
if (config("stripSrc", "false") == "true")
docSrc = false
// map my config to CompilerInput structure
ci := CompilerInput()
ci.inputLoc = Loc.makeFile(scriptFile)
ci.podName = podName
ci.summary = summary
ci.version = version
ci.depends = depends.map |s->Depend| { Depend(applyMacros(s)) }
ci.meta = meta
ci.index = index
ci.baseDir = scriptDir
ci.srcFiles = srcDirs
ci.resFiles = resDirs
ci.jsFiles = jsDirs
ci.jsPropsFiles = jsProps ?: resDirs
ci.log = log
ci.includeDoc = docApi
ci.includeSrc = docSrc
ci.mode = CompilerInputMode.file
ci.outDir = outPodDir.toFile
ci.output = CompilerOutputMode.podFile
if (dependsDir != null)
{
f := dependsDir.toFile
if (!f.exists) throw fatal("Invalid dependsDir: $f")
ci.ns = FPodNamespace(f)
}
return ci
}
**
** Callback to tune the Fantom compiler input
**
virtual Void onCompileFan(CompilerInput ci) {}
//////////////////////////////////////////////////////////////////////////
// Compile Java
//////////////////////////////////////////////////////////////////////////
**
** Compile Java class files if javaDirs is configured
**
virtual Void compileJava()
{
if (this.javaDirs == null) return
javaDirs := resolveDirs(this.javaDirs)
log.info("javaNative [$podName]")
log.indent
// env
jtemp := scriptDir + `temp-java/`
jstub := jtemp + "${podName}.jar".toUri
jdk := JdkTask(this)
javaExe := jdk.javaExe
jarExe := jdk.jarExe
sysJar := devHomeDir + `lib/java/sys.jar`
libFan := devHomeDir + `lib/fan/`
curPod := outPodDir.toFile + `${podName}.pod`
depends := depends.map |s->Depend| { Depend(applyMacros(s)) }
// stub the pods fan classes into Java classfiles
// by calling the JStub tool in the jsys runtime
jtemp.create
Exec(this, [javaExe,
"-cp", sysJar.osPath,
"-Dfan.home=$devHomeDir.osPath",
"fanx.tools.Jstub",
"-d", jtemp.osPath,
podName]).run
// compile
if (!javaDirs.isEmpty)
{
javac := CompileJava(this)
javac.outDir = jtemp
javac.cp.add(jstub)
javac.cpAddExtJars
javac.cp.add(sysJar)
depends.each |Depend d| { javac.cp.add(Env.cur.findPodFile(d.name)) }
javac.src = javaDirs
javac.run
}
// extract stub jar into the temp directory
Exec(this, [jarExe, "-xf", jstub.osPath], jtemp).run
// now we can nuke the stub jar (and manifest)
Delete(this, jstub).run
Delete(this, jtemp + `meta-inf/`).run
// append files to the pod zip (we use java's jar tool)
Exec(this, [jarExe, "-fu", curPod.osPath, "-C", jtemp.osPath, "."], jtemp).run
// cleanup temp
Delete(this, jtemp).run
log.unindent
}
//////////////////////////////////////////////////////////////////////////
// JNI
//////////////////////////////////////////////////////////////////////////
**
** Compile JNI bindings if jniDirs configured.
**
virtual Void compileJni()
{
if (jniDirs == null) return
log.info("JNI [$podName]")
log.indent
// check whitelist
if (jniPlatforms != null)
{
if (!jniPlatforms.contains(Env.cur.os) &&
!jniPlatforms.contains(Env.cur.platform))
{
log.info(" Skipping platform $Env.cur.platform")
return
}
}
// env
jtemp := scriptDir + `temp-jni/`
jdirs := this->resolveDirs(jniDirs)
// start with a clean directory
Delete(this, jtemp).run
CreateDir(this, jtemp).run
// compile
cc := CompileJni(this)
cc.src = jdirs
cc.out = jtemp
cc.lib = podName
cc.run
// override target platform
plat := config("jniPlatform") ?: Env.cur.platform
// move files to /lib/java/ext/<plat>/
libSrc := jtemp + cc.platLib
libDst := (outPodDir.parent + `java/ext/${plat}/${cc.platLib}`).toFile
log.info("Move [$libDst.osPath]")
libSrc.copyTo(libDst, ["overwrite":true])
// cleanup temp
Delete(this, jtemp).run
log.unindent
}
//////////////////////////////////////////////////////////////////////////
// Javascript
//////////////////////////////////////////////////////////////////////////
**
** Compile to javascript node module
**
virtual Void compileNodeJs()
{
ci := stdFanCompilerInput
ci.forceJs = true
try
{
c := Compiler(ci)
c.frontend
esmDir := Env.cur.homeDir.plus(`lib/es/esm/`)
if (Env.cur.vars.containsKey("FAN_ESM_DIR"))
esmDir = Env.cur.vars["FAN_ESM_DIR"].toUri.toFile
Env.cur.homeDir.plus(`lib/js/node_modules/${podName}.js`).out.writeChars(c.js).flush.close
if (c.esm != null)
{
esmDir.plus(`${podName}.js`).out.writeChars(c.esm).flush.close
// write ts declarations
t := Type.find("nodeJs::GenTsDecl", false)
if (t == null) return
decl := esmDir.plus(`${podName}.d.ts`).out
t.make([decl, c.pod, ["allTypes":true]])->run
decl.flush.close
}
}
catch (CompilerErr err)
{
throw FatalBuildErr()
}
catch (Err err)
{
log.err("Internal compiler error")
err.trace
throw FatalBuildErr()
}
}
//////////////////////////////////////////////////////////////////////////
// DotnetNative
//////////////////////////////////////////////////////////////////////////
**
** Compile native .NET assembly dotnetDirs configured
**
virtual Void compileDotnet()
{
if (dotnetDirs == null) return
if (Env.cur.os != "win32")
{
log.info("dotnetNative skipping [$podName]")
return
}
log.info("dotnetNative [$podName]")
log.indent
// env
ntemp := scriptDir + `temp-dotnet/`
nstub := ntemp + `${podName}.dll`
nout := ntemp + `${podName}Native_.dll`
ndirs := resolveDirs(dotnetDirs)
nlibs := [devHomeDir+`lib/dotnet/sys.dll`, nstub]
nstubExe := devHomeDir + `bin/nstub`
// start with a clean directory
Delete(this, ntemp).run
CreateDir(this, ntemp).run
// stub the pods fan classes into Java classfiles
// by calling the JStub tool in the jsys runtime
Exec(this, [nstubExe.osPath, "-d", ntemp.osPath, podName]).run
// compile
csc := CompileCs(this)
csc.output = nout
csc.targetType = "library"
csc.src = ndirs
csc.libs = nlibs
csc.run
// append files to the pod zip (we use java's jar tool)
jdk := JdkTask(this)
jarExe := jdk.jarExe
curPod := devHomeDir + `lib/fan/${podName}.pod`
Exec(this, [jarExe, "-fu", curPod.osPath, "-C", ntemp.osPath,
"${podName}Native_.dll", "${podName}Native_.pdb"], ntemp).run
// cleanup temp
Delete(this, ntemp).run
log.unindent
}
//////////////////////////////////////////////////////////////////////////
// Clean
//////////////////////////////////////////////////////////////////////////
**
** Delete all intermediate and target files
**
@Target { help = "Delete all intermediate and target files" }
virtual Void clean()
{
log.info("clean [$podName]")
log.indent
dir := isFantomCore ? devHomeDir : Env.cur.workDir
Delete(this, dir+`lib/fan/${podName}.pod`).run
Delete(this, dir+`lib/java/${podName}.jar`).run
Delete(this, dir+`lib/js/node_modules/${podName}.js`).run
Delete(this, dir+`lib/es/esm/${podName}.js`).run
Delete(this, dir+`lib/es/esm/${podName}.d.ts`).run
Delete(this, dir+`lib/dotnet/${podName}.dll`).run
Delete(this, dir+`lib/dotnet/${podName}.pdb`).run
Delete(this, dir+`lib/tmp/${podName}.dll`).run
Delete(this, dir+`lib/tmp/${podName}.pdb`).run
Delete(this, scriptDir+`temp-java/`).run
Delete(this, scriptDir+`temp-dotnet/`).run
log.unindent
}
private Bool isFantomCore() { meta["proj.name"] == "Fantom Core" }
//////////////////////////////////////////////////////////////////////////
// Test
//////////////////////////////////////////////////////////////////////////
**
** Run the unit tests using 'fant' for this pod
**
@Target { help = "Run the pod unit tests via fant" }
virtual Void test()
{
log.info("test [$podName]")
log.indent
fant := devHomeDir + `bin/fant`
cmd := [fant.osPath, podName]
if (Env.cur.os == "win32") cmd = ["cmd", "/C"].addAll(cmd)
Exec(this, cmd).run
log.unindent
}
//////////////////////////////////////////////////////////////////////////
// Full
//////////////////////////////////////////////////////////////////////////
**
** Run clean, compile, and test
**
@Target { help = "Run clean, compile, and test" }
virtual Void full()
{
clean
compile
test
}
}