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