waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

javaw.py (16882B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2006-2018 (ita)
      4 
      5 """
      6 Java support
      7 
      8 Javac is one of the few compilers that behaves very badly:
      9 
     10 #. it outputs files where it wants to (-d is only for the package root)
     11 
     12 #. it recompiles files silently behind your back
     13 
     14 #. it outputs an undefined amount of files (inner classes)
     15 
     16 Remember that the compilation can be performed using Jython[1] rather than regular Python. Instead of
     17 running one of the following commands::
     18 
     19    ./waf configure
     20    python waf configure
     21 
     22 You would have to run::
     23 
     24    java -jar /path/to/jython.jar waf configure
     25 
     26 [1] http://www.jython.org/
     27 
     28 Usage
     29 =====
     30 
     31 Load the "java" tool.
     32 
     33 def configure(conf):
     34 	conf.load('java')
     35 
     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.
     39 
     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::
     42 
     43 	conf.check_java_class('java.io.FileOutputStream')
     44 
     45 or if the system supports JNI applications building::
     46 
     47 	conf.check_jni_headers()
     48 
     49 
     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::
     53 
     54 	bld(features  = 'javac',
     55 		srcdir    = 'src',
     56 		compat    = '1.7',
     57 		use       = 'animals',
     58 		name      = 'cats-src',
     59 	)
     60 
     61 	bld(features  = 'jar',
     62 		basedir   = '.',
     63 		destfile  = '../cats.jar',
     64 		name      = 'cats',
     65 		use       = 'cats-src'
     66 	)
     67 
     68 
     69 Or together by defining all the needed attributes::
     70 
     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  = ['.', '..'],
     76 
     77 		# jar
     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!
     80 		use        = 'NNN',
     81 		jaropts    = ['-C', 'default/src/', '.'], # can be used to give files
     82 		manifest   = 'src/Manifest.mf', # Manifest file to include
     83 
     84 		# javadoc
     85 		javadoc_package = ['com.meow' , 'com.meow.truc.bar', 'com.meow.truc.foo'],
     86 		javadoc_output  = 'javadoc',
     87 	)
     88 
     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,
     91 for example::
     92 
     93 	conf.env.CLASSPATH_NNN = ['aaaa.jar', 'bbbb.jar']
     94 
     95 and then NNN can be freely used in rules as::
     96 
     97 	use        = 'NNN',
     98 
     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:
    102 
    103 		recurse_use = True
    104 
    105 Or build-wise by setting RECURSE_JAVA:
    106 
    107 		bld.env.RECURSE_JAVA = True
    108 
    109 Unit tests can be integrated in the waf unit test environment using the javatest extra.
    110 """
    111 
    112 import os, shutil
    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
    116 
    117 from waflib.Tools import ccroot
    118 ccroot.USELIB_VARS['javac'] = set(['CLASSPATH', 'JAVACFLAGS'])
    119 
    120 SOURCE_RE = '**/*.java'
    121 JAR_RE = '**/*'
    122 
    123 class_check_source = '''
    124 public class Test {
    125 	public static void main(String[] argv) {
    126 		Class lib;
    127 		if (argv.length < 1) {
    128 			System.err.println("Missing argument");
    129 			System.exit(77);
    130 		}
    131 		try {
    132 			lib = Class.forName(argv[0]);
    133 		} catch (ClassNotFoundException e) {
    134 			System.err.println("ClassNotFoundException");
    135 			System.exit(1);
    136 		}
    137 		lib = null;
    138 		System.exit(0);
    139 	}
    140 }
    141 '''
    142 
    143 @feature('javac')
    144 @before_method('process_source')
    145 def apply_java(self):
    146 	"""
    147 	Create a javac task for compiling *.java files*. There can be
    148 	only one javac task by task generator.
    149 	"""
    150 	Utils.def_attrs(self, jarname='', classpath='',
    151 		sourcepath='.', srcdir='.',
    152 		jar_mf_attributes={}, jar_mf_classpath=[])
    153 
    154 	outdir = getattr(self, 'outdir', None)
    155 	if outdir:
    156 		if not isinstance(outdir, Node.Node):
    157 			outdir = self.path.get_bld().make_node(self.outdir)
    158 	else:
    159 		outdir = self.path.get_bld()
    160 	outdir.mkdir()
    161 	self.outdir = outdir
    162 	self.env.OUTDIR = outdir.abspath()
    163 
    164 	self.javac_task = tsk = self.create_task('javac')
    165 	tmp = []
    166 
    167 	srcdir = getattr(self, 'srcdir', '')
    168 	if isinstance(srcdir, Node.Node):
    169 		srcdir = [srcdir]
    170 	for x in Utils.to_list(srcdir):
    171 		if isinstance(x, Node.Node):
    172 			y = x
    173 		else:
    174 			y = self.path.find_dir(x)
    175 			if not y:
    176 				self.bld.fatal('Could not find the folder %s from %s' % (x, self.path))
    177 		tmp.append(y)
    178 
    179 	tsk.srcdir = tmp
    180 
    181 	if getattr(self, 'compat', None):
    182 		tsk.env.append_value('JAVACFLAGS', ['-source', str(self.compat)])
    183 
    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])
    187 	else:
    188 		names = [x.srcpath() for x in tsk.srcdir]
    189 
    190 	if names:
    191 		tsk.env.append_value('JAVACFLAGS', ['-sourcepath', names])
    192 
    193 
    194 @taskgen_method
    195 def java_use_rec(self, name, **kw):
    196 	"""
    197 	Processes recursively the *use* attribute for each referred java compilation
    198 	"""
    199 	if name in self.tmp_use_seen:
    200 		return
    201 
    202 	self.tmp_use_seen.append(name)
    203 
    204 	try:
    205 		y = self.bld.get_tgen_by_name(name)
    206 	except Errors.WafError:
    207 		self.uselib.append(name)
    208 		return
    209 	else:
    210 		y.post()
    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())
    215 		else:
    216 			if hasattr(y,'outdir'):
    217 				self.use_lst.append(y.outdir.abspath())
    218 			else:
    219 				self.use_lst.append(y.path.get_bld().abspath())
    220 
    221 	for x in self.to_list(getattr(y, 'use', [])):
    222 		self.java_use_rec(x)
    223 
    224 @feature('javac')
    225 @before_method('propagate_uselib_vars')
    226 @after_method('apply_java')
    227 def use_javac_files(self):
    228 	"""
    229 	Processes the *use* attribute referring to other java compilations
    230 	"""
    231 	self.use_lst = []
    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
    236 	for x in names:
    237 		try:
    238 			tg = get(x)
    239 		except Errors.WafError:
    240 			self.uselib.append(x)
    241 		else:
    242 			tg.post()
    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)
    247 			else:
    248 				if hasattr(tg, 'outdir'):
    249 					base_node = tg.outdir
    250 				else:
    251 					base_node = tg.path.get_bld()
    252 
    253 				self.use_lst.append(base_node.abspath())
    254 				self.javac_task.dep_nodes.extend([dx for dx in base_node.ant_glob(JAR_RE, remove=False, quiet=True)])
    255 
    256 				for tsk in tg.tasks:
    257 					self.javac_task.set_run_after(tsk)
    258 
    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:
    261 			self.java_use_rec(x)
    262 
    263 	self.env.append_value('CLASSPATH', self.use_lst)
    264 
    265 @feature('javac')
    266 @after_method('apply_java', 'propagate_uselib_vars', 'use_javac_files')
    267 def set_classpath(self):
    268 	"""
    269 	Sets the CLASSPATH value on the *javac* task previously created.
    270 	"""
    271 	if getattr(self, 'classpath', None):
    272 		self.env.append_unique('CLASSPATH', getattr(self, 'classpath', []))
    273 	for x in self.tasks:
    274 		x.env.CLASSPATH = os.pathsep.join(self.env.CLASSPATH) + os.pathsep
    275 
    276 @feature('jar')
    277 @after_method('apply_java', 'use_javac_files')
    278 @before_method('process_source')
    279 def jar_files(self):
    280 	"""
    281 	Creates a jar task (one maximum per task generator)
    282 	"""
    283 	destfile = getattr(self, 'destfile', 'test.jar')
    284 	jaropts = getattr(self, 'jaropts', [])
    285 	manifest = getattr(self, 'manifest', None)
    286 
    287 	basedir = getattr(self, 'basedir', None)
    288 	if basedir:
    289 		if not isinstance(self.basedir, Node.Node):
    290 			basedir = self.path.get_bld().make_node(basedir)
    291 	else:
    292 		basedir = self.path.get_bld()
    293 	if not basedir:
    294 		self.bld.fatal('Could not find the basedir %r for %r' % (self.basedir, self))
    295 
    296 	self.jar_task = tsk = self.create_task('jar_create')
    297 	if manifest:
    298 		jarcreate = getattr(self, 'jarcreate', 'cfm')
    299 		if not isinstance(manifest,Node.Node):
    300 			node = self.path.find_resource(manifest)
    301 		else:
    302 			node = manifest
    303 		if not node:
    304 			self.bld.fatal('invalid manifest file %r for %r' % (manifest, self))
    305 		tsk.dep_nodes.append(node)
    306 		jaropts.insert(0, node.abspath())
    307 	else:
    308 		jarcreate = getattr(self, 'jarcreate', 'cf')
    309 	if not isinstance(destfile, Node.Node):
    310 		destfile = self.path.find_or_declare(destfile)
    311 	if not destfile:
    312 		self.bld.fatal('invalid destfile %r for %r' % (destfile, self))
    313 	tsk.set_outputs(destfile)
    314 	tsk.basedir = basedir
    315 
    316 	jaropts.append('-C')
    317 	jaropts.append(basedir.bldpath())
    318 	jaropts.append('.')
    319 
    320 	tsk.env.JAROPTS = jaropts
    321 	tsk.env.JARCREATE = jarcreate
    322 
    323 	if getattr(self, 'javac_task', None):
    324 		tsk.set_run_after(self.javac_task)
    325 
    326 @feature('jar')
    327 @after_method('jar_files')
    328 def use_jar_files(self):
    329 	"""
    330 	Processes the *use* attribute to set the build order on the
    331 	tasks created by another task generator.
    332 	"""
    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
    336 	for x in names:
    337 		try:
    338 			y = get(x)
    339 		except Errors.WafError:
    340 			self.uselib.append(x)
    341 		else:
    342 			y.post()
    343 			self.jar_task.run_after.update(y.tasks)
    344 
    345 class JTask(Task.Task):
    346 	"""
    347 	Base class for java and jar tasks; provides functionality to run long commands
    348 	"""
    349 	def split_argfile(self, cmd):
    350 		inline = [cmd[0]]
    351 		infile = []
    352 		for x in cmd[1:]:
    353 			# jar and javac do not want -J flags in @file
    354 			if x.startswith('-J'):
    355 				inline.append(x)
    356 			else:
    357 				infile.append(self.quote_flag(x))
    358 		return (inline, infile)
    359 
    360 class jar_create(JTask):
    361 	"""
    362 	Creates a jar file
    363 	"""
    364 	color   = 'GREEN'
    365 	run_str = '${JAR} ${JARCREATE} ${TGT} ${JAROPTS}'
    366 
    367 	def runnable_status(self):
    368 		"""
    369 		Wait for dependent tasks to be executed, then read the
    370 		files to update the list of inputs.
    371 		"""
    372 		for t in self.run_after:
    373 			if not t.hasrun:
    374 				return Task.ASK_LATER
    375 		if not self.inputs:
    376 			try:
    377 				self.inputs = [x for x in self.basedir.ant_glob(JAR_RE, remove=False, quiet=True) if id(x) != id(self.outputs[0])]
    378 			except Exception:
    379 				raise Errors.WafError('Could not find the basedir %r for %r' % (self.basedir, self))
    380 		return super(jar_create, self).runnable_status()
    381 
    382 class javac(JTask):
    383 	"""
    384 	Compiles java files
    385 	"""
    386 	color   = 'BLUE'
    387 	run_str = '${JAVAC} -classpath ${CLASSPATH} -d ${OUTDIR} ${JAVACFLAGS} ${SRC}'
    388 	vars = ['CLASSPATH', 'JAVACFLAGS', 'JAVAC', 'OUTDIR']
    389 	"""
    390 	The javac task will be executed again if the variables CLASSPATH, JAVACFLAGS, JAVAC or OUTDIR change.
    391 	"""
    392 	def uid(self):
    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)
    398 
    399 	def runnable_status(self):
    400 		"""
    401 		Waits for dependent tasks to be complete, then read the file system to find the input nodes.
    402 		"""
    403 		for t in self.run_after:
    404 			if not t.hasrun:
    405 				return Task.ASK_LATER
    406 
    407 		if not self.inputs:
    408 			self.inputs  = []
    409 			for x in self.srcdir:
    410 				if x.exists():
    411 					self.inputs.extend(x.ant_glob(SOURCE_RE, remove=False, quiet=True))
    412 		return super(javac, self).runnable_status()
    413 
    414 	def post_run(self):
    415 		"""
    416 		List class files created
    417 		"""
    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
    421 
    422 @feature('javadoc')
    423 @after_method('process_rule')
    424 def create_javadoc(self):
    425 	"""
    426 	Creates a javadoc task (feature 'javadoc')
    427 	"""
    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)
    433 
    434 class javadoc(Task.Task):
    435 	"""
    436 	Builds java documentation
    437 	"""
    438 	color = 'BLUE'
    439 
    440 	def __str__(self):
    441 		return '%s: %s -> %s\n' % (self.__class__.__name__, self.generator.srcdir, self.generator.javadoc_output)
    442 
    443 	def run(self):
    444 		env = self.env
    445 		bld = self.generator.bld
    446 		wd = bld.bldnode
    447 
    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
    452 
    453 		classpath = env.CLASSPATH
    454 		classpath += os.pathsep
    455 		classpath += os.pathsep.join(self.classpath)
    456 		classpath = "".join(classpath)
    457 
    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]
    466 
    467 		self.generator.bld.cmd_and_log(lst, cwd=wd, env=env.env or None, quiet=0)
    468 
    469 	def post_run(self):
    470 		nodes = self.generator.javadoc_output.ant_glob('**', quiet=True)
    471 		for node in nodes:
    472 			self.generator.bld.node_sigs[node] = self.uid()
    473 		self.generator.bld.task_sigs[self.uid()] = self.cache_sig
    474 
    475 def configure(self):
    476 	"""
    477 	Detects the javac, java and jar programs
    478 	"""
    479 	# If JAVA_PATH is set, we prepend it to the path list
    480 	java_path = self.environ['PATH'].split(os.pathsep)
    481 	v = self.env
    482 
    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']]
    486 
    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')))
    489 
    490 	if 'CLASSPATH' in self.environ:
    491 		v.CLASSPATH = self.environ['CLASSPATH']
    492 
    493 	if not v.JAR:
    494 		self.fatal('jar is required for making java packages')
    495 	if not v.JAVAC:
    496 		self.fatal('javac is required for compiling java classes')
    497 
    498 	v.JARCREATE = 'cf' # can use cvf
    499 	v.JAVACFLAGS = []
    500 
    501 @conf
    502 def check_java_class(self, classname, with_classpath=None):
    503 	"""
    504 	Checks if the specified java class exists
    505 
    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
    510 	"""
    511 	javatestdir = '.waf-javatest'
    512 
    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
    518 
    519 	shutil.rmtree(javatestdir, True)
    520 	os.mkdir(javatestdir)
    521 
    522 	Utils.writef(os.path.join(javatestdir, 'Test.java'), class_check_source)
    523 
    524 	# Compile the source
    525 	self.exec_command(self.env.JAVAC + [os.path.join(javatestdir, 'Test.java')], shell=False)
    526 
    527 	# Try to run the app
    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)
    531 
    532 	self.msg('Checking for java class %s' % classname, not found)
    533 
    534 	shutil.rmtree(javatestdir, True)
    535 
    536 	return found
    537 
    538 @conf
    539 def check_jni_headers(conf):
    540 	"""
    541 	Checks for jni headers and libraries. On success the conf.env variables xxx_JAVA are added for use in C/C++ targets::
    542 
    543 		def options(opt):
    544 			opt.load('compiler_c')
    545 
    546 		def configure(conf):
    547 			conf.load('compiler_c java')
    548 			conf.check_jni_headers()
    549 
    550 		def build(bld):
    551 			bld.shlib(source='a.c', target='app', use='JAVA')
    552 	"""
    553 	if not conf.env.CC_NAME and not conf.env.CXX_NAME:
    554 		conf.fatal('load a compiler first (gcc, g++, ..)')
    555 
    556 	if not conf.env.JAVA_HOME:
    557 		conf.fatal('set JAVA_HOME in the system environment')
    558 
    559 	# jni requires the jvm
    560 	javaHome = conf.env.JAVA_HOME[0]
    561 
    562 	dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/include')
    563 	if dir is None:
    564 		dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/../Headers') # think different?!
    565 	if dir is None:
    566 		conf.fatal('JAVA_HOME does not seem to be set properly')
    567 
    568 	f = dir.ant_glob('**/(jni|jni_md).h')
    569 	incDirs = [x.parent.abspath() for x in f]
    570 
    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]
    574 
    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)')
    578 	if f:
    579 		libDirs = [[x, y.parent.abspath()] for x in libDirs for y in f]
    580 
    581 	if conf.env.DEST_OS == 'freebsd':
    582 		conf.env.append_unique('LINKFLAGS_JAVA', '-pthread')
    583 	for d in libDirs:
    584 		try:
    585 			conf.check(header_name='jni.h', define_name='HAVE_JNI_H', lib='jvm',
    586 				libpath=d, includes=incDirs, uselib_store='JAVA', uselib='JAVA')
    587 		except Exception:
    588 			pass
    589 		else:
    590 			break
    591 	else:
    592 		conf.fatal('could not find lib jvm in %r (see config.log)' % libDirs)
    593