waf/waflib/Tools/javaw.py

594 lines
16 KiB
Python
Raw Normal View History

2011-09-10 11:13:51 +02:00
#!/usr/bin/env python
# encoding: utf-8
2018-01-01 20:53:49 +01:00
# Thomas Nagy, 2006-2018 (ita)
2011-09-10 11:13:51 +02:00
"""
Java support
Javac is one of the few compilers that behaves very badly:
#. it outputs files where it wants to (-d is only for the package root)
#. it recompiles files silently behind your back
#. it outputs an undefined amount of files (inner classes)
Remember that the compilation can be performed using Jython[1] rather than regular Python. Instead of
running one of the following commands::
2012-06-02 04:04:59 +02:00
./waf configure
python waf configure
2011-09-10 11:13:51 +02:00
You would have to run::
2012-06-02 04:04:59 +02:00
java -jar /path/to/jython.jar waf configure
2011-09-10 11:13:51 +02:00
[1] http://www.jython.org/
Usage
=====
Load the "java" tool.
def configure(conf):
conf.load('java')
Java tools will be autodetected and eventually, if present, the quite
standard JAVA_HOME environment variable will be used. The also standard
CLASSPATH variable is used for library searching.
In configuration phase checks can be done on the system environment, for
2018-12-15 19:45:47 +01:00
example to check if a class is known in the classpath::
conf.check_java_class('java.io.FileOutputStream')
2018-12-15 19:45:47 +01:00
or if the system supports JNI applications building::
conf.check_jni_headers()
The java tool supports compiling java code, creating jar files and
creating javadoc documentation. This can be either done separately or
2018-12-15 19:45:47 +01:00
together in a single definition. For example to manage them separately::
bld(features = 'javac',
srcdir = 'src',
compat = '1.7',
use = 'animals',
name = 'cats-src',
)
bld(features = 'jar',
basedir = '.',
destfile = '../cats.jar',
name = 'cats',
use = 'cats-src'
)
2018-12-15 19:45:47 +01:00
Or together by defining all the needed attributes::
bld(features = 'javac jar javadoc',
srcdir = 'src/', # folder containing the sources to compile
outdir = 'src', # folder where to output the classes (in the build directory)
compat = '1.6', # java compatibility version number
classpath = ['.', '..'],
# jar
basedir = 'src', # folder containing the classes and other files to package (must match outdir)
destfile = 'foo.jar', # do not put the destfile in the folder of the java classes!
use = 'NNN',
jaropts = ['-C', 'default/src/', '.'], # can be used to give files
manifest = 'src/Manifest.mf', # Manifest file to include
# javadoc
javadoc_package = ['com.meow' , 'com.meow.truc.bar', 'com.meow.truc.foo'],
javadoc_output = 'javadoc',
)
External jar dependencies can be mapped to a standard waf "use" dependency by
2018-12-15 19:45:47 +01:00
setting an environment variable with a CLASSPATH prefix in the configuration,
for example::
conf.env.CLASSPATH_NNN = ['aaaa.jar', 'bbbb.jar']
2018-12-15 19:45:47 +01:00
and then NNN can be freely used in rules as::
use = 'NNN',
In the java tool the dependencies via use are not transitive by default, as
this necessity depends on the code. To enable recursive dependency scanning
use on a specific rule:
recurse_use = True
2018-12-15 19:45:47 +01:00
Or build-wise by setting RECURSE_JAVA:
bld.env.RECURSE_JAVA = True
2018-12-15 19:45:47 +01:00
Unit tests can be integrated in the waf unit test environment using the javatest extra.
2011-09-10 11:13:51 +02:00
"""
2016-06-15 20:24:34 +02:00
import os, shutil
from waflib import Task, Utils, Errors, Node
2012-06-02 04:04:59 +02:00
from waflib.Configure import conf
from waflib.TaskGen import feature, before_method, after_method, taskgen_method
2011-09-10 11:13:51 +02:00
from waflib.Tools import ccroot
ccroot.USELIB_VARS['javac'] = set(['CLASSPATH', 'JAVACFLAGS'])
SOURCE_RE = '**/*.java'
JAR_RE = '**/*'
class_check_source = '''
public class Test {
public static void main(String[] argv) {
Class lib;
if (argv.length < 1) {
System.err.println("Missing argument");
System.exit(77);
}
try {
lib = Class.forName(argv[0]);
} catch (ClassNotFoundException e) {
System.err.println("ClassNotFoundException");
System.exit(1);
}
lib = null;
System.exit(0);
}
}
'''
@feature('javac')
@before_method('process_source')
def apply_java(self):
"""
Create a javac task for compiling *.java files*. There can be
only one javac task by task generator.
"""
Utils.def_attrs(self, jarname='', classpath='',
sourcepath='.', srcdir='.',
jar_mf_attributes={}, jar_mf_classpath=[])
outdir = getattr(self, 'outdir', None)
if outdir:
if not isinstance(outdir, Node.Node):
outdir = self.path.get_bld().make_node(self.outdir)
else:
outdir = self.path.get_bld()
outdir.mkdir()
self.outdir = outdir
2016-06-25 23:54:12 +02:00
self.env.OUTDIR = outdir.abspath()
2011-09-10 11:13:51 +02:00
self.javac_task = tsk = self.create_task('javac')
tmp = []
srcdir = getattr(self, 'srcdir', '')
if isinstance(srcdir, Node.Node):
srcdir = [srcdir]
for x in Utils.to_list(srcdir):
if isinstance(x, Node.Node):
y = x
else:
y = self.path.find_dir(x)
if not y:
self.bld.fatal('Could not find the folder %s from %s' % (x, self.path))
tmp.append(y)
2011-09-10 11:13:51 +02:00
tsk.srcdir = tmp
if getattr(self, 'compat', None):
tsk.env.append_value('JAVACFLAGS', ['-source', str(self.compat)])
2011-09-10 11:13:51 +02:00
if hasattr(self, 'sourcepath'):
fold = [isinstance(x, Node.Node) and x or self.path.find_dir(x) for x in self.to_list(self.sourcepath)]
names = os.pathsep.join([x.srcpath() for x in fold])
else:
names = [x.srcpath() for x in tsk.srcdir]
if names:
tsk.env.append_value('JAVACFLAGS', ['-sourcepath', names])
@taskgen_method
def java_use_rec(self, name, **kw):
"""
Processes recursively the *use* attribute for each referred java compilation
"""
if name in self.tmp_use_seen:
return
self.tmp_use_seen.append(name)
try:
y = self.bld.get_tgen_by_name(name)
except Errors.WafError:
self.uselib.append(name)
return
else:
y.post()
# Add generated JAR name for CLASSPATH. Task ordering (set_run_after)
2019-03-08 04:13:40 +01:00
# is already guaranteed by ordering done between the single tasks
if hasattr(y, 'jar_task'):
self.use_lst.append(y.jar_task.outputs[0].abspath())
else:
if hasattr(y,'outdir'):
self.use_lst.append(y.outdir.abspath())
else:
self.use_lst.append(y.path.get_bld().abspath())
for x in self.to_list(getattr(y, 'use', [])):
self.java_use_rec(x)
2011-09-10 11:13:51 +02:00
@feature('javac')
@before_method('propagate_uselib_vars')
2011-09-10 11:13:51 +02:00
@after_method('apply_java')
def use_javac_files(self):
"""
2016-06-26 11:59:27 +02:00
Processes the *use* attribute referring to other java compilations
2011-09-10 11:13:51 +02:00
"""
self.use_lst = []
self.tmp_use_seen = []
2011-09-10 11:13:51 +02:00
self.uselib = self.to_list(getattr(self, 'uselib', []))
names = self.to_list(getattr(self, 'use', []))
get = self.bld.get_tgen_by_name
for x in names:
try:
tg = get(x)
2016-06-26 11:59:27 +02:00
except Errors.WafError:
2011-09-10 11:13:51 +02:00
self.uselib.append(x)
else:
tg.post()
if hasattr(tg, 'jar_task'):
self.use_lst.append(tg.jar_task.outputs[0].abspath())
self.javac_task.set_run_after(tg.jar_task)
self.javac_task.dep_nodes.extend(tg.jar_task.outputs)
else:
if hasattr(tg, 'outdir'):
base_node = tg.outdir
else:
base_node = tg.path.get_bld()
self.use_lst.append(base_node.abspath())
self.javac_task.dep_nodes.extend([x for x in base_node.ant_glob(JAR_RE, remove=False, quiet=True)])
for tsk in tg.tasks:
self.javac_task.set_run_after(tsk)
# If recurse use scan is enabled recursively add use attribute for each used one
if getattr(self, 'recurse_use', False) or self.bld.env.RECURSE_JAVA:
self.java_use_rec(x)
self.env.append_value('CLASSPATH', self.use_lst)
2011-09-10 11:13:51 +02:00
@feature('javac')
@after_method('apply_java', 'propagate_uselib_vars', 'use_javac_files')
def set_classpath(self):
"""
2016-06-26 11:59:27 +02:00
Sets the CLASSPATH value on the *javac* task previously created.
2011-09-10 11:13:51 +02:00
"""
if getattr(self, 'classpath', None):
self.env.append_unique('CLASSPATH', getattr(self, 'classpath', []))
2011-09-10 11:13:51 +02:00
for x in self.tasks:
x.env.CLASSPATH = os.pathsep.join(self.env.CLASSPATH) + os.pathsep
@feature('jar')
@after_method('apply_java', 'use_javac_files')
@before_method('process_source')
def jar_files(self):
"""
2016-06-26 11:59:27 +02:00
Creates a jar task (one maximum per task generator)
2011-09-10 11:13:51 +02:00
"""
destfile = getattr(self, 'destfile', 'test.jar')
jaropts = getattr(self, 'jaropts', [])
2012-06-02 04:04:59 +02:00
manifest = getattr(self, 'manifest', None)
2011-09-10 11:13:51 +02:00
basedir = getattr(self, 'basedir', None)
if basedir:
if not isinstance(self.basedir, Node.Node):
basedir = self.path.get_bld().make_node(basedir)
else:
basedir = self.path.get_bld()
if not basedir:
self.bld.fatal('Could not find the basedir %r for %r' % (self.basedir, self))
self.jar_task = tsk = self.create_task('jar_create')
2012-06-02 04:04:59 +02:00
if manifest:
jarcreate = getattr(self, 'jarcreate', 'cfm')
if not isinstance(manifest,Node.Node):
node = self.path.find_resource(manifest)
else:
node = manifest
if not node:
self.bld.fatal('invalid manifest file %r for %r' % (manifest, self))
2012-06-02 04:04:59 +02:00
tsk.dep_nodes.append(node)
jaropts.insert(0, node.abspath())
else:
jarcreate = getattr(self, 'jarcreate', 'cf')
2011-09-10 11:13:51 +02:00
if not isinstance(destfile, Node.Node):
destfile = self.path.find_or_declare(destfile)
if not destfile:
self.bld.fatal('invalid destfile %r for %r' % (destfile, self))
tsk.set_outputs(destfile)
tsk.basedir = basedir
jaropts.append('-C')
jaropts.append(basedir.bldpath())
jaropts.append('.')
2016-06-25 23:54:12 +02:00
tsk.env.JAROPTS = jaropts
tsk.env.JARCREATE = jarcreate
2011-09-10 11:13:51 +02:00
if getattr(self, 'javac_task', None):
tsk.set_run_after(self.javac_task)
@feature('jar')
@after_method('jar_files')
def use_jar_files(self):
"""
2016-06-26 11:59:27 +02:00
Processes the *use* attribute to set the build order on the
2011-09-10 11:13:51 +02:00
tasks created by another task generator.
"""
self.uselib = self.to_list(getattr(self, 'uselib', []))
names = self.to_list(getattr(self, 'use', []))
get = self.bld.get_tgen_by_name
for x in names:
try:
y = get(x)
2016-06-26 11:59:27 +02:00
except Errors.WafError:
2011-09-10 11:13:51 +02:00
self.uselib.append(x)
else:
y.post()
self.jar_task.run_after.update(y.tasks)
2016-06-15 20:24:34 +02:00
class JTask(Task.Task):
2016-06-26 11:59:27 +02:00
"""
Base class for java and jar tasks; provides functionality to run long commands
"""
2016-06-15 20:24:34 +02:00
def split_argfile(self, cmd):
inline = [cmd[0]]
infile = []
for x in cmd[1:]:
# jar and javac do not want -J flags in @file
if x.startswith('-J'):
inline.append(x)
else:
infile.append(self.quote_flag(x))
return (inline, infile)
class jar_create(JTask):
2011-09-10 11:13:51 +02:00
"""
2016-06-26 11:59:27 +02:00
Creates a jar file
2011-09-10 11:13:51 +02:00
"""
color = 'GREEN'
run_str = '${JAR} ${JARCREATE} ${TGT} ${JAROPTS}'
def runnable_status(self):
"""
Wait for dependent tasks to be executed, then read the
files to update the list of inputs.
"""
for t in self.run_after:
if not t.hasrun:
return Task.ASK_LATER
if not self.inputs:
try:
self.inputs = [x for x in self.basedir.ant_glob(JAR_RE, remove=False, quiet=True) if id(x) != id(self.outputs[0])]
2012-02-11 14:49:27 +01:00
except Exception:
2011-09-10 11:13:51 +02:00
raise Errors.WafError('Could not find the basedir %r for %r' % (self.basedir, self))
return super(jar_create, self).runnable_status()
2016-06-15 20:24:34 +02:00
class javac(JTask):
2011-09-10 11:13:51 +02:00
"""
2016-06-26 11:59:27 +02:00
Compiles java files
2011-09-10 11:13:51 +02:00
"""
color = 'BLUE'
2016-06-15 20:24:34 +02:00
run_str = '${JAVAC} -classpath ${CLASSPATH} -d ${OUTDIR} ${JAVACFLAGS} ${SRC}'
2012-06-02 04:04:59 +02:00
vars = ['CLASSPATH', 'JAVACFLAGS', 'JAVAC', 'OUTDIR']
2011-09-10 11:13:51 +02:00
"""
The javac task will be executed again if the variables CLASSPATH, JAVACFLAGS, JAVAC or OUTDIR change.
"""
def uid(self):
"""Identify java tasks by input&output folder"""
lst = [self.__class__.__name__, self.generator.outdir.abspath()]
for x in self.srcdir:
lst.append(x.abspath())
return Utils.h_list(lst)
2011-09-10 11:13:51 +02:00
def runnable_status(self):
"""
2016-06-26 11:59:27 +02:00
Waits for dependent tasks to be complete, then read the file system to find the input nodes.
2011-09-10 11:13:51 +02:00
"""
for t in self.run_after:
if not t.hasrun:
return Task.ASK_LATER
if not self.inputs:
self.inputs = []
for x in self.srcdir:
if x.exists():
self.inputs.extend(x.ant_glob(SOURCE_RE, remove=False, quiet=True))
2011-09-10 11:13:51 +02:00
return super(javac, self).runnable_status()
def post_run(self):
"""
2016-06-26 11:59:27 +02:00
List class files created
2011-09-10 11:13:51 +02:00
"""
for node in self.generator.outdir.ant_glob('**/*.class', quiet=True):
2016-01-02 00:58:38 +01:00
self.generator.bld.node_sigs[node] = self.uid()
2011-09-10 11:13:51 +02:00
self.generator.bld.task_sigs[self.uid()] = self.cache_sig
2012-03-23 03:31:56 +01:00
@feature('javadoc')
@after_method('process_rule')
2012-03-23 03:42:05 +01:00
def create_javadoc(self):
"""
Creates a javadoc task (feature 'javadoc')
"""
2012-03-23 03:31:56 +01:00
tsk = self.create_task('javadoc')
tsk.classpath = getattr(self, 'classpath', [])
2012-03-23 03:42:05 +01:00
self.javadoc_package = Utils.to_list(self.javadoc_package)
if not isinstance(self.javadoc_output, Node.Node):
self.javadoc_output = self.bld.path.find_or_declare(self.javadoc_output)
2012-03-23 03:31:56 +01:00
class javadoc(Task.Task):
2016-06-26 11:59:27 +02:00
"""
Builds java documentation
"""
2012-03-23 03:31:56 +01:00
color = 'BLUE'
def __str__(self):
2012-03-23 03:42:05 +01:00
return '%s: %s -> %s\n' % (self.__class__.__name__, self.generator.srcdir, self.generator.javadoc_output)
2012-03-23 03:31:56 +01:00
def run(self):
env = self.env
bld = self.generator.bld
2016-01-02 01:54:52 +01:00
wd = bld.bldnode
2012-03-23 03:31:56 +01:00
#add src node + bld node (for generated java code)
2012-03-23 03:50:15 +01:00
srcpath = self.generator.path.abspath() + os.sep + self.generator.srcdir
2012-03-23 03:31:56 +01:00
srcpath += os.pathsep
2012-03-23 03:50:15 +01:00
srcpath += self.generator.path.get_bld().abspath() + os.sep + self.generator.srcdir
2012-03-23 03:31:56 +01:00
classpath = env.CLASSPATH
classpath += os.pathsep
classpath += os.pathsep.join(self.classpath)
classpath = "".join(classpath)
self.last_cmd = lst = []
2016-06-25 23:54:12 +02:00
lst.extend(Utils.to_list(env.JAVADOC))
2012-03-23 03:42:05 +01:00
lst.extend(['-d', self.generator.javadoc_output.abspath()])
2012-03-23 03:31:56 +01:00
lst.extend(['-sourcepath', srcpath])
lst.extend(['-classpath', classpath])
2012-03-23 03:42:05 +01:00
lst.extend(['-subpackages'])
lst.extend(self.generator.javadoc_package)
2012-03-23 03:31:56 +01:00
lst = [x for x in lst if x]
self.generator.bld.cmd_and_log(lst, cwd=wd, env=env.env or None, quiet=0)
def post_run(self):
nodes = self.generator.javadoc_output.ant_glob('**', quiet=True)
2016-01-02 00:58:38 +01:00
for node in nodes:
self.generator.bld.node_sigs[node] = self.uid()
2012-03-23 03:31:56 +01:00
self.generator.bld.task_sigs[self.uid()] = self.cache_sig
2011-09-10 11:13:51 +02:00
def configure(self):
"""
2016-06-26 11:59:27 +02:00
Detects the javac, java and jar programs
2011-09-10 11:13:51 +02:00
"""
# If JAVA_PATH is set, we prepend it to the path list
java_path = self.environ['PATH'].split(os.pathsep)
v = self.env
if 'JAVA_HOME' in self.environ:
java_path = [os.path.join(self.environ['JAVA_HOME'], 'bin')] + java_path
2016-06-25 23:54:12 +02:00
self.env.JAVA_HOME = [self.environ['JAVA_HOME']]
2011-09-10 11:13:51 +02:00
2012-03-23 03:31:56 +01:00
for x in 'javac java jar javadoc'.split():
self.find_program(x, var=x.upper(), path_list=java_path, mandatory=(x not in ('javadoc')))
2011-09-10 11:13:51 +02:00
if 'CLASSPATH' in self.environ:
2016-06-25 23:54:12 +02:00
v.CLASSPATH = self.environ['CLASSPATH']
2011-09-10 11:13:51 +02:00
2016-06-25 23:54:12 +02:00
if not v.JAR:
self.fatal('jar is required for making java packages')
if not v.JAVAC:
self.fatal('javac is required for compiling java classes')
2012-03-23 03:31:56 +01:00
2016-06-25 23:54:12 +02:00
v.JARCREATE = 'cf' # can use cvf
v.JAVACFLAGS = []
2011-09-10 11:13:51 +02:00
@conf
def check_java_class(self, classname, with_classpath=None):
"""
2016-06-26 11:59:27 +02:00
Checks if the specified java class exists
2011-09-10 11:13:51 +02:00
:param classname: class to check, like java.util.HashMap
:type classname: string
:param with_classpath: additional classpath to give
:type with_classpath: string
"""
javatestdir = '.waf-javatest'
classpath = javatestdir
2016-06-25 23:54:12 +02:00
if self.env.CLASSPATH:
classpath += os.pathsep + self.env.CLASSPATH
2011-09-10 11:13:51 +02:00
if isinstance(with_classpath, str):
classpath += os.pathsep + with_classpath
shutil.rmtree(javatestdir, True)
os.mkdir(javatestdir)
Utils.writef(os.path.join(javatestdir, 'Test.java'), class_check_source)
2011-09-10 11:13:51 +02:00
# Compile the source
2016-06-25 23:54:12 +02:00
self.exec_command(self.env.JAVAC + [os.path.join(javatestdir, 'Test.java')], shell=False)
2011-09-10 11:13:51 +02:00
# Try to run the app
2016-06-25 23:54:12 +02:00
cmd = self.env.JAVA + ['-cp', classpath, 'Test', classname]
2011-09-10 11:13:51 +02:00
self.to_log("%s\n" % str(cmd))
found = self.exec_command(cmd, shell=False)
self.msg('Checking for java class %s' % classname, not found)
shutil.rmtree(javatestdir, True)
return found
@conf
def check_jni_headers(conf):
"""
2016-06-26 11:59:27 +02:00
Checks for jni headers and libraries. On success the conf.env variables xxx_JAVA are added for use in C/C++ targets::
2011-09-10 11:13:51 +02:00
def options(opt):
opt.load('compiler_c')
def configure(conf):
conf.load('compiler_c java')
conf.check_jni_headers()
def build(bld):
bld.shlib(source='a.c', target='app', use='JAVA')
"""
if not conf.env.CC_NAME and not conf.env.CXX_NAME:
conf.fatal('load a compiler first (gcc, g++, ..)')
if not conf.env.JAVA_HOME:
conf.fatal('set JAVA_HOME in the system environment')
# jni requires the jvm
2016-06-25 23:54:12 +02:00
javaHome = conf.env.JAVA_HOME[0]
2011-09-10 11:13:51 +02:00
dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/include')
2012-04-21 00:22:44 +02:00
if dir is None:
2012-04-21 10:22:22 +02:00
dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/../Headers') # think different?!
2011-09-10 11:13:51 +02:00
if dir is None:
conf.fatal('JAVA_HOME does not seem to be set properly')
f = dir.ant_glob('**/(jni|jni_md).h')
incDirs = [x.parent.abspath() for x in f]
dir = conf.root.find_dir(conf.env.JAVA_HOME[0])
f = dir.ant_glob('**/*jvm.(so|dll|dylib)')
libDirs = [x.parent.abspath() for x in f] or [javaHome]
# On windows, we need both the .dll and .lib to link. On my JDK, they are
# in different directories...
f = dir.ant_glob('**/*jvm.(lib)')
if f:
libDirs = [[x, y.parent.abspath()] for x in libDirs for y in f]
if conf.env.DEST_OS == 'freebsd':
conf.env.append_unique('LINKFLAGS_JAVA', '-pthread')
2011-09-10 11:13:51 +02:00
for d in libDirs:
try:
conf.check(header_name='jni.h', define_name='HAVE_JNI_H', lib='jvm',
libpath=d, includes=incDirs, uselib_store='JAVA', uselib='JAVA')
2012-02-11 14:49:27 +01:00
except Exception:
2011-09-10 11:13:51 +02:00
pass
else:
break
else:
conf.fatal('could not find lib jvm in %r (see config.log)' % libDirs)