3 # Thomas Nagy, 2006-2018 (ita)
8 Javac is one of the few compilers that behaves very badly:
10 #. it outputs files where it wants to (-d is only for the package root)
12 #. it recompiles files silently behind your back
14 #. it outputs an undefined amount of files (inner classes)
16 Remember that the compilation can be performed using Jython[1] rather than regular Python. Instead of
17 running one of the following commands::
22 You would have to run::
24 java -jar /path/to/jython.jar waf configure
26 [1] http://www.jython.org/
36 Java tools will be autodetected and eventually, if present, the quite
37 standard JAVA_HOME environment variable will be used. The also standard
38 CLASSPATH variable is used for library searching.
40 In configuration phase checks can be done on the system environment, for
41 example to check if a class is known in the classpath::
43 conf.check_java_class('java.io.FileOutputStream')
45 or if the system supports JNI applications building::
47 conf.check_jni_headers()
50 The java tool supports compiling java code, creating jar files and
51 creating javadoc documentation. This can be either done separately or
52 together in a single definition. For example to manage them separately::
54 bld(features = 'javac',
63 destfile = '../cats.jar',
69 Or together by defining all the needed attributes::
71 bld(features = 'javac jar javadoc',
72 srcdir = 'src/', # folder containing the sources to compile
73 outdir = 'src', # folder where to output the classes (in the build directory)
74 compat = '1.6', # java compatibility version number
75 classpath = ['.', '..'],
78 basedir = 'src', # folder containing the classes and other files to package (must match outdir)
79 destfile = 'foo.jar', # do not put the destfile in the folder of the java classes!
81 jaropts = ['-C', 'default/src/', '.'], # can be used to give files
82 manifest = 'src/Manifest.mf', # Manifest file to include
85 javadoc_package = ['com.meow' , 'com.meow.truc.bar', 'com.meow.truc.foo'],
86 javadoc_output = 'javadoc',
89 External jar dependencies can be mapped to a standard waf "use" dependency by
90 setting an environment variable with a CLASSPATH prefix in the configuration,
93 conf.env.CLASSPATH_NNN = ['aaaa.jar', 'bbbb.jar']
95 and then NNN can be freely used in rules as::
99 In the java tool the dependencies via use are not transitive by default, as
100 this necessity depends on the code. To enable recursive dependency scanning
101 use on a specific rule:
105 Or build-wise by setting RECURSE_JAVA:
107 bld.env.RECURSE_JAVA = True
109 Unit tests can be integrated in the waf unit test environment using the javatest extra.
113 from waflib import Task, Utils, Errors, Node
114 from waflib.Configure import conf
115 from waflib.TaskGen import feature, before_method, after_method, taskgen_method
117 from waflib.Tools import ccroot
118 ccroot.USELIB_VARS['javac'] = set(['CLASSPATH', 'JAVACFLAGS'])
120 SOURCE_RE = '**/*.java'
123 class_check_source = '''
125 public static void main(String[] argv) {
127 if (argv.length < 1) {
128 System.err.println("Missing argument");
132 lib = Class.forName(argv[0]);
133 } catch (ClassNotFoundException e) {
134 System.err.println("ClassNotFoundException");
144 @before_method('process_source')
145 def apply_java(self):
147 Create a javac task for compiling *.java files*. There can be
148 only one javac task by task generator.
150 Utils.def_attrs(self, jarname='', classpath='',
151 sourcepath='.', srcdir='.',
152 jar_mf_attributes={}, jar_mf_classpath=[])
154 outdir = getattr(self, 'outdir', None)
156 if not isinstance(outdir, Node.Node):
157 outdir = self.path.get_bld().make_node(self.outdir)
159 outdir = self.path.get_bld()
162 self.env.OUTDIR = outdir.abspath()
164 self.javac_task = tsk = self.create_task('javac')
167 srcdir = getattr(self, 'srcdir', '')
168 if isinstance(srcdir, Node.Node):
170 for x in Utils.to_list(srcdir):
171 if isinstance(x, Node.Node):
174 y = self.path.find_dir(x)
176 self.bld.fatal('Could not find the folder %s from %s' % (x, self.path))
181 if getattr(self, 'compat', None):
182 tsk.env.append_value('JAVACFLAGS', ['-source', str(self.compat)])
184 if hasattr(self, 'sourcepath'):
185 fold = [isinstance(x, Node.Node) and x or self.path.find_dir(x) for x in self.to_list(self.sourcepath)]
186 names = os.pathsep.join([x.srcpath() for x in fold])
188 names = [x.srcpath() for x in tsk.srcdir]
191 tsk.env.append_value('JAVACFLAGS', ['-sourcepath', names])
195 def java_use_rec(self, name, **kw):
197 Processes recursively the *use* attribute for each referred java compilation
199 if name in self.tmp_use_seen:
202 self.tmp_use_seen.append(name)
205 y = self.bld.get_tgen_by_name(name)
206 except Errors.WafError:
207 self.uselib.append(name)
211 # Add generated JAR name for CLASSPATH. Task ordering (set_run_after)
212 # is already guaranteed by ordering done between the single tasks
213 if hasattr(y, 'jar_task'):
214 self.use_lst.append(y.jar_task.outputs[0].abspath())
216 if hasattr(y,'outdir'):
217 self.use_lst.append(y.outdir.abspath())
219 self.use_lst.append(y.path.get_bld().abspath())
221 for x in self.to_list(getattr(y, 'use', [])):
225 @before_method('propagate_uselib_vars')
226 @after_method('apply_java')
227 def use_javac_files(self):
229 Processes the *use* attribute referring to other java compilations
232 self.tmp_use_seen = []
233 self.uselib = self.to_list(getattr(self, 'uselib', []))
234 names = self.to_list(getattr(self, 'use', []))
235 get = self.bld.get_tgen_by_name
239 except Errors.WafError:
240 self.uselib.append(x)
243 if hasattr(tg, 'jar_task'):
244 self.use_lst.append(tg.jar_task.outputs[0].abspath())
245 self.javac_task.set_run_after(tg.jar_task)
246 self.javac_task.dep_nodes.extend(tg.jar_task.outputs)
248 if hasattr(tg, 'outdir'):
249 base_node = tg.outdir
251 base_node = tg.path.get_bld()
253 self.use_lst.append(base_node.abspath())
254 self.javac_task.dep_nodes.extend([x for x in base_node.ant_glob(JAR_RE, remove=False, quiet=True)])
257 self.javac_task.set_run_after(tsk)
259 # If recurse use scan is enabled recursively add use attribute for each used one
260 if getattr(self, 'recurse_use', False) or self.bld.env.RECURSE_JAVA:
263 self.env.append_value('CLASSPATH', self.use_lst)
266 @after_method('apply_java', 'propagate_uselib_vars', 'use_javac_files')
267 def set_classpath(self):
269 Sets the CLASSPATH value on the *javac* task previously created.
271 if getattr(self, 'classpath', None):
272 self.env.append_unique('CLASSPATH', getattr(self, 'classpath', []))
274 x.env.CLASSPATH = os.pathsep.join(self.env.CLASSPATH) + os.pathsep
277 @after_method('apply_java', 'use_javac_files')
278 @before_method('process_source')
281 Creates a jar task (one maximum per task generator)
283 destfile = getattr(self, 'destfile', 'test.jar')
284 jaropts = getattr(self, 'jaropts', [])
285 manifest = getattr(self, 'manifest', None)
287 basedir = getattr(self, 'basedir', None)
289 if not isinstance(self.basedir, Node.Node):
290 basedir = self.path.get_bld().make_node(basedir)
292 basedir = self.path.get_bld()
294 self.bld.fatal('Could not find the basedir %r for %r' % (self.basedir, self))
296 self.jar_task = tsk = self.create_task('jar_create')
298 jarcreate = getattr(self, 'jarcreate', 'cfm')
299 if not isinstance(manifest,Node.Node):
300 node = self.path.find_resource(manifest)
304 self.bld.fatal('invalid manifest file %r for %r' % (manifest, self))
305 tsk.dep_nodes.append(node)
306 jaropts.insert(0, node.abspath())
308 jarcreate = getattr(self, 'jarcreate', 'cf')
309 if not isinstance(destfile, Node.Node):
310 destfile = self.path.find_or_declare(destfile)
312 self.bld.fatal('invalid destfile %r for %r' % (destfile, self))
313 tsk.set_outputs(destfile)
314 tsk.basedir = basedir
317 jaropts.append(basedir.bldpath())
320 tsk.env.JAROPTS = jaropts
321 tsk.env.JARCREATE = jarcreate
323 if getattr(self, 'javac_task', None):
324 tsk.set_run_after(self.javac_task)
327 @after_method('jar_files')
328 def use_jar_files(self):
330 Processes the *use* attribute to set the build order on the
331 tasks created by another task generator.
333 self.uselib = self.to_list(getattr(self, 'uselib', []))
334 names = self.to_list(getattr(self, 'use', []))
335 get = self.bld.get_tgen_by_name
339 except Errors.WafError:
340 self.uselib.append(x)
343 self.jar_task.run_after.update(y.tasks)
345 class JTask(Task.Task):
347 Base class for java and jar tasks; provides functionality to run long commands
349 def split_argfile(self, cmd):
353 # jar and javac do not want -J flags in @file
354 if x.startswith('-J'):
357 infile.append(self.quote_flag(x))
358 return (inline, infile)
360 class jar_create(JTask):
365 run_str = '${JAR} ${JARCREATE} ${TGT} ${JAROPTS}'
367 def runnable_status(self):
369 Wait for dependent tasks to be executed, then read the
370 files to update the list of inputs.
372 for t in self.run_after:
374 return Task.ASK_LATER
377 self.inputs = [x for x in self.basedir.ant_glob(JAR_RE, remove=False, quiet=True) if id(x) != id(self.outputs[0])]
379 raise Errors.WafError('Could not find the basedir %r for %r' % (self.basedir, self))
380 return super(jar_create, self).runnable_status()
387 run_str = '${JAVAC} -classpath ${CLASSPATH} -d ${OUTDIR} ${JAVACFLAGS} ${SRC}'
388 vars = ['CLASSPATH', 'JAVACFLAGS', 'JAVAC', 'OUTDIR']
390 The javac task will be executed again if the variables CLASSPATH, JAVACFLAGS, JAVAC or OUTDIR change.
393 """Identify java tasks by input&output folder"""
394 lst = [self.__class__.__name__, self.generator.outdir.abspath()]
395 for x in self.srcdir:
396 lst.append(x.abspath())
397 return Utils.h_list(lst)
399 def runnable_status(self):
401 Waits for dependent tasks to be complete, then read the file system to find the input nodes.
403 for t in self.run_after:
405 return Task.ASK_LATER
409 for x in self.srcdir:
411 self.inputs.extend(x.ant_glob(SOURCE_RE, remove=False, quiet=True))
412 return super(javac, self).runnable_status()
416 List class files created
418 for node in self.generator.outdir.ant_glob('**/*.class', quiet=True):
419 self.generator.bld.node_sigs[node] = self.uid()
420 self.generator.bld.task_sigs[self.uid()] = self.cache_sig
423 @after_method('process_rule')
424 def create_javadoc(self):
426 Creates a javadoc task (feature 'javadoc')
428 tsk = self.create_task('javadoc')
429 tsk.classpath = getattr(self, 'classpath', [])
430 self.javadoc_package = Utils.to_list(self.javadoc_package)
431 if not isinstance(self.javadoc_output, Node.Node):
432 self.javadoc_output = self.bld.path.find_or_declare(self.javadoc_output)
434 class javadoc(Task.Task):
436 Builds java documentation
441 return '%s: %s -> %s\n' % (self.__class__.__name__, self.generator.srcdir, self.generator.javadoc_output)
445 bld = self.generator.bld
448 #add src node + bld node (for generated java code)
449 srcpath = self.generator.path.abspath() + os.sep + self.generator.srcdir
450 srcpath += os.pathsep
451 srcpath += self.generator.path.get_bld().abspath() + os.sep + self.generator.srcdir
453 classpath = env.CLASSPATH
454 classpath += os.pathsep
455 classpath += os.pathsep.join(self.classpath)
456 classpath = "".join(classpath)
458 self.last_cmd = lst = []
459 lst.extend(Utils.to_list(env.JAVADOC))
460 lst.extend(['-d', self.generator.javadoc_output.abspath()])
461 lst.extend(['-sourcepath', srcpath])
462 lst.extend(['-classpath', classpath])
463 lst.extend(['-subpackages'])
464 lst.extend(self.generator.javadoc_package)
465 lst = [x for x in lst if x]
467 self.generator.bld.cmd_and_log(lst, cwd=wd, env=env.env or None, quiet=0)
470 nodes = self.generator.javadoc_output.ant_glob('**', quiet=True)
472 self.generator.bld.node_sigs[node] = self.uid()
473 self.generator.bld.task_sigs[self.uid()] = self.cache_sig
477 Detects the javac, java and jar programs
479 # If JAVA_PATH is set, we prepend it to the path list
480 java_path = self.environ['PATH'].split(os.pathsep)
483 if 'JAVA_HOME' in self.environ:
484 java_path = [os.path.join(self.environ['JAVA_HOME'], 'bin')] + java_path
485 self.env.JAVA_HOME = [self.environ['JAVA_HOME']]
487 for x in 'javac java jar javadoc'.split():
488 self.find_program(x, var=x.upper(), path_list=java_path, mandatory=(x not in ('javadoc')))
490 if 'CLASSPATH' in self.environ:
491 v.CLASSPATH = self.environ['CLASSPATH']
494 self.fatal('jar is required for making java packages')
496 self.fatal('javac is required for compiling java classes')
498 v.JARCREATE = 'cf' # can use cvf
502 def check_java_class(self, classname, with_classpath=None):
504 Checks if the specified java class exists
506 :param classname: class to check, like java.util.HashMap
507 :type classname: string
508 :param with_classpath: additional classpath to give
509 :type with_classpath: string
511 javatestdir = '.waf-javatest'
513 classpath = javatestdir
514 if self.env.CLASSPATH:
515 classpath += os.pathsep + self.env.CLASSPATH
516 if isinstance(with_classpath, str):
517 classpath += os.pathsep + with_classpath
519 shutil.rmtree(javatestdir, True)
520 os.mkdir(javatestdir)
522 Utils.writef(os.path.join(javatestdir, 'Test.java'), class_check_source)
525 self.exec_command(self.env.JAVAC + [os.path.join(javatestdir, 'Test.java')], shell=False)
528 cmd = self.env.JAVA + ['-cp', classpath, 'Test', classname]
529 self.to_log("%s\n" % str(cmd))
530 found = self.exec_command(cmd, shell=False)
532 self.msg('Checking for java class %s' % classname, not found)
534 shutil.rmtree(javatestdir, True)
539 def check_jni_headers(conf):
541 Checks for jni headers and libraries. On success the conf.env variables xxx_JAVA are added for use in C/C++ targets::
544 opt.load('compiler_c')
547 conf.load('compiler_c java')
548 conf.check_jni_headers()
551 bld.shlib(source='a.c', target='app', use='JAVA')
553 if not conf.env.CC_NAME and not conf.env.CXX_NAME:
554 conf.fatal('load a compiler first (gcc, g++, ..)')
556 if not conf.env.JAVA_HOME:
557 conf.fatal('set JAVA_HOME in the system environment')
559 # jni requires the jvm
560 javaHome = conf.env.JAVA_HOME[0]
562 dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/include')
564 dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/../Headers') # think different?!
566 conf.fatal('JAVA_HOME does not seem to be set properly')
568 f = dir.ant_glob('**/(jni|jni_md).h')
569 incDirs = [x.parent.abspath() for x in f]
571 dir = conf.root.find_dir(conf.env.JAVA_HOME[0])
572 f = dir.ant_glob('**/*jvm.(so|dll|dylib)')
573 libDirs = [x.parent.abspath() for x in f] or [javaHome]
575 # On windows, we need both the .dll and .lib to link. On my JDK, they are
576 # in different directories...
577 f = dir.ant_glob('**/*jvm.(lib)')
579 libDirs = [[x, y.parent.abspath()] for x in libDirs for y in f]
581 if conf.env.DEST_OS == 'freebsd':
582 conf.env.append_unique('LINKFLAGS_JAVA', '-pthread')
585 conf.check(header_name='jni.h', define_name='HAVE_JNI_H', lib='jvm',
586 libpath=d, includes=incDirs, uselib_store='JAVA', uselib='JAVA')
592 conf.fatal('could not find lib jvm in %r (see config.log)' % libDirs)