javaw.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2006-2018 (ita)
  4. """
  5. Java support
  6. Javac is one of the few compilers that behaves very badly:
  7. #. it outputs files where it wants to (-d is only for the package root)
  8. #. it recompiles files silently behind your back
  9. #. it outputs an undefined amount of files (inner classes)
  10. Remember that the compilation can be performed using Jython[1] rather than regular Python. Instead of
  11. running one of the following commands::
  12. ./waf configure
  13. python waf configure
  14. You would have to run::
  15. java -jar /path/to/jython.jar waf configure
  16. [1] http://www.jython.org/
  17. """
  18. import os, shutil
  19. from waflib import Task, Utils, Errors, Node
  20. from waflib.Configure import conf
  21. from waflib.TaskGen import feature, before_method, after_method
  22. from waflib.Tools import ccroot
  23. ccroot.USELIB_VARS['javac'] = set(['CLASSPATH', 'JAVACFLAGS'])
  24. SOURCE_RE = '**/*.java'
  25. JAR_RE = '**/*'
  26. class_check_source = '''
  27. public class Test {
  28. public static void main(String[] argv) {
  29. Class lib;
  30. if (argv.length < 1) {
  31. System.err.println("Missing argument");
  32. System.exit(77);
  33. }
  34. try {
  35. lib = Class.forName(argv[0]);
  36. } catch (ClassNotFoundException e) {
  37. System.err.println("ClassNotFoundException");
  38. System.exit(1);
  39. }
  40. lib = null;
  41. System.exit(0);
  42. }
  43. }
  44. '''
  45. @feature('javac')
  46. @before_method('process_source')
  47. def apply_java(self):
  48. """
  49. Create a javac task for compiling *.java files*. There can be
  50. only one javac task by task generator.
  51. """
  52. Utils.def_attrs(self, jarname='', classpath='',
  53. sourcepath='.', srcdir='.',
  54. jar_mf_attributes={}, jar_mf_classpath=[])
  55. outdir = getattr(self, 'outdir', None)
  56. if outdir:
  57. if not isinstance(outdir, Node.Node):
  58. outdir = self.path.get_bld().make_node(self.outdir)
  59. else:
  60. outdir = self.path.get_bld()
  61. outdir.mkdir()
  62. self.outdir = outdir
  63. self.env.OUTDIR = outdir.abspath()
  64. self.javac_task = tsk = self.create_task('javac')
  65. tmp = []
  66. srcdir = getattr(self, 'srcdir', '')
  67. if isinstance(srcdir, Node.Node):
  68. srcdir = [srcdir]
  69. for x in Utils.to_list(srcdir):
  70. if isinstance(x, Node.Node):
  71. y = x
  72. else:
  73. y = self.path.find_dir(x)
  74. if not y:
  75. self.bld.fatal('Could not find the folder %s from %s' % (x, self.path))
  76. tmp.append(y)
  77. tsk.srcdir = tmp
  78. if getattr(self, 'compat', None):
  79. tsk.env.append_value('JAVACFLAGS', ['-source', str(self.compat)])
  80. if hasattr(self, 'sourcepath'):
  81. fold = [isinstance(x, Node.Node) and x or self.path.find_dir(x) for x in self.to_list(self.sourcepath)]
  82. names = os.pathsep.join([x.srcpath() for x in fold])
  83. else:
  84. names = [x.srcpath() for x in tsk.srcdir]
  85. if names:
  86. tsk.env.append_value('JAVACFLAGS', ['-sourcepath', names])
  87. @feature('javac')
  88. @before_method('propagate_uselib_vars')
  89. @after_method('apply_java')
  90. def use_javac_files(self):
  91. """
  92. Processes the *use* attribute referring to other java compilations
  93. """
  94. lst = []
  95. self.uselib = self.to_list(getattr(self, 'uselib', []))
  96. names = self.to_list(getattr(self, 'use', []))
  97. get = self.bld.get_tgen_by_name
  98. for x in names:
  99. try:
  100. y = get(x)
  101. except Errors.WafError:
  102. self.uselib.append(x)
  103. else:
  104. y.post()
  105. if hasattr(y, 'jar_task'):
  106. lst.append(y.jar_task.outputs[0].abspath())
  107. self.javac_task.set_run_after(y.jar_task)
  108. else:
  109. for tsk in y.tasks:
  110. self.javac_task.set_run_after(tsk)
  111. self.env.append_value('CLASSPATH', lst)
  112. @feature('javac')
  113. @after_method('apply_java', 'propagate_uselib_vars', 'use_javac_files')
  114. def set_classpath(self):
  115. """
  116. Sets the CLASSPATH value on the *javac* task previously created.
  117. """
  118. if getattr(self, 'classpath', None):
  119. self.env.append_unique('CLASSPATH', getattr(self, 'classpath', []))
  120. for x in self.tasks:
  121. x.env.CLASSPATH = os.pathsep.join(self.env.CLASSPATH) + os.pathsep
  122. @feature('jar')
  123. @after_method('apply_java', 'use_javac_files')
  124. @before_method('process_source')
  125. def jar_files(self):
  126. """
  127. Creates a jar task (one maximum per task generator)
  128. """
  129. destfile = getattr(self, 'destfile', 'test.jar')
  130. jaropts = getattr(self, 'jaropts', [])
  131. manifest = getattr(self, 'manifest', None)
  132. basedir = getattr(self, 'basedir', None)
  133. if basedir:
  134. if not isinstance(self.basedir, Node.Node):
  135. basedir = self.path.get_bld().make_node(basedir)
  136. else:
  137. basedir = self.path.get_bld()
  138. if not basedir:
  139. self.bld.fatal('Could not find the basedir %r for %r' % (self.basedir, self))
  140. self.jar_task = tsk = self.create_task('jar_create')
  141. if manifest:
  142. jarcreate = getattr(self, 'jarcreate', 'cfm')
  143. if not isinstance(manifest,Node.Node):
  144. node = self.path.find_resource(manifest)
  145. else:
  146. node = manifest
  147. if not node:
  148. self.bld.fatal('invalid manifest file %r for %r' % (manifest, self))
  149. tsk.dep_nodes.append(node)
  150. jaropts.insert(0, node.abspath())
  151. else:
  152. jarcreate = getattr(self, 'jarcreate', 'cf')
  153. if not isinstance(destfile, Node.Node):
  154. destfile = self.path.find_or_declare(destfile)
  155. if not destfile:
  156. self.bld.fatal('invalid destfile %r for %r' % (destfile, self))
  157. tsk.set_outputs(destfile)
  158. tsk.basedir = basedir
  159. jaropts.append('-C')
  160. jaropts.append(basedir.bldpath())
  161. jaropts.append('.')
  162. tsk.env.JAROPTS = jaropts
  163. tsk.env.JARCREATE = jarcreate
  164. if getattr(self, 'javac_task', None):
  165. tsk.set_run_after(self.javac_task)
  166. @feature('jar')
  167. @after_method('jar_files')
  168. def use_jar_files(self):
  169. """
  170. Processes the *use* attribute to set the build order on the
  171. tasks created by another task generator.
  172. """
  173. self.uselib = self.to_list(getattr(self, 'uselib', []))
  174. names = self.to_list(getattr(self, 'use', []))
  175. get = self.bld.get_tgen_by_name
  176. for x in names:
  177. try:
  178. y = get(x)
  179. except Errors.WafError:
  180. self.uselib.append(x)
  181. else:
  182. y.post()
  183. self.jar_task.run_after.update(y.tasks)
  184. class JTask(Task.Task):
  185. """
  186. Base class for java and jar tasks; provides functionality to run long commands
  187. """
  188. def split_argfile(self, cmd):
  189. inline = [cmd[0]]
  190. infile = []
  191. for x in cmd[1:]:
  192. # jar and javac do not want -J flags in @file
  193. if x.startswith('-J'):
  194. inline.append(x)
  195. else:
  196. infile.append(self.quote_flag(x))
  197. return (inline, infile)
  198. class jar_create(JTask):
  199. """
  200. Creates a jar file
  201. """
  202. color = 'GREEN'
  203. run_str = '${JAR} ${JARCREATE} ${TGT} ${JAROPTS}'
  204. def runnable_status(self):
  205. """
  206. Wait for dependent tasks to be executed, then read the
  207. files to update the list of inputs.
  208. """
  209. for t in self.run_after:
  210. if not t.hasrun:
  211. return Task.ASK_LATER
  212. if not self.inputs:
  213. try:
  214. self.inputs = [x for x in self.basedir.ant_glob(JAR_RE, remove=False) if id(x) != id(self.outputs[0])]
  215. except Exception:
  216. raise Errors.WafError('Could not find the basedir %r for %r' % (self.basedir, self))
  217. return super(jar_create, self).runnable_status()
  218. class javac(JTask):
  219. """
  220. Compiles java files
  221. """
  222. color = 'BLUE'
  223. run_str = '${JAVAC} -classpath ${CLASSPATH} -d ${OUTDIR} ${JAVACFLAGS} ${SRC}'
  224. vars = ['CLASSPATH', 'JAVACFLAGS', 'JAVAC', 'OUTDIR']
  225. """
  226. The javac task will be executed again if the variables CLASSPATH, JAVACFLAGS, JAVAC or OUTDIR change.
  227. """
  228. def uid(self):
  229. """Identify java tasks by input&output folder"""
  230. lst = [self.__class__.__name__, self.generator.outdir.abspath()]
  231. for x in self.srcdir:
  232. lst.append(x.abspath())
  233. return Utils.h_list(lst)
  234. def runnable_status(self):
  235. """
  236. Waits for dependent tasks to be complete, then read the file system to find the input nodes.
  237. """
  238. for t in self.run_after:
  239. if not t.hasrun:
  240. return Task.ASK_LATER
  241. if not self.inputs:
  242. self.inputs = []
  243. for x in self.srcdir:
  244. if x.exists():
  245. self.inputs.extend(x.ant_glob(SOURCE_RE, remove=False))
  246. return super(javac, self).runnable_status()
  247. def post_run(self):
  248. """
  249. List class files created
  250. """
  251. for node in self.generator.outdir.ant_glob('**/*.class'):
  252. self.generator.bld.node_sigs[node] = self.uid()
  253. self.generator.bld.task_sigs[self.uid()] = self.cache_sig
  254. @feature('javadoc')
  255. @after_method('process_rule')
  256. def create_javadoc(self):
  257. """
  258. Creates a javadoc task (feature 'javadoc')
  259. """
  260. tsk = self.create_task('javadoc')
  261. tsk.classpath = getattr(self, 'classpath', [])
  262. self.javadoc_package = Utils.to_list(self.javadoc_package)
  263. if not isinstance(self.javadoc_output, Node.Node):
  264. self.javadoc_output = self.bld.path.find_or_declare(self.javadoc_output)
  265. class javadoc(Task.Task):
  266. """
  267. Builds java documentation
  268. """
  269. color = 'BLUE'
  270. def __str__(self):
  271. return '%s: %s -> %s\n' % (self.__class__.__name__, self.generator.srcdir, self.generator.javadoc_output)
  272. def run(self):
  273. env = self.env
  274. bld = self.generator.bld
  275. wd = bld.bldnode
  276. #add src node + bld node (for generated java code)
  277. srcpath = self.generator.path.abspath() + os.sep + self.generator.srcdir
  278. srcpath += os.pathsep
  279. srcpath += self.generator.path.get_bld().abspath() + os.sep + self.generator.srcdir
  280. classpath = env.CLASSPATH
  281. classpath += os.pathsep
  282. classpath += os.pathsep.join(self.classpath)
  283. classpath = "".join(classpath)
  284. self.last_cmd = lst = []
  285. lst.extend(Utils.to_list(env.JAVADOC))
  286. lst.extend(['-d', self.generator.javadoc_output.abspath()])
  287. lst.extend(['-sourcepath', srcpath])
  288. lst.extend(['-classpath', classpath])
  289. lst.extend(['-subpackages'])
  290. lst.extend(self.generator.javadoc_package)
  291. lst = [x for x in lst if x]
  292. self.generator.bld.cmd_and_log(lst, cwd=wd, env=env.env or None, quiet=0)
  293. def post_run(self):
  294. nodes = self.generator.javadoc_output.ant_glob('**')
  295. for node in nodes:
  296. self.generator.bld.node_sigs[node] = self.uid()
  297. self.generator.bld.task_sigs[self.uid()] = self.cache_sig
  298. def configure(self):
  299. """
  300. Detects the javac, java and jar programs
  301. """
  302. # If JAVA_PATH is set, we prepend it to the path list
  303. java_path = self.environ['PATH'].split(os.pathsep)
  304. v = self.env
  305. if 'JAVA_HOME' in self.environ:
  306. java_path = [os.path.join(self.environ['JAVA_HOME'], 'bin')] + java_path
  307. self.env.JAVA_HOME = [self.environ['JAVA_HOME']]
  308. for x in 'javac java jar javadoc'.split():
  309. self.find_program(x, var=x.upper(), path_list=java_path)
  310. if 'CLASSPATH' in self.environ:
  311. v.CLASSPATH = self.environ['CLASSPATH']
  312. if not v.JAR:
  313. self.fatal('jar is required for making java packages')
  314. if not v.JAVAC:
  315. self.fatal('javac is required for compiling java classes')
  316. v.JARCREATE = 'cf' # can use cvf
  317. v.JAVACFLAGS = []
  318. @conf
  319. def check_java_class(self, classname, with_classpath=None):
  320. """
  321. Checks if the specified java class exists
  322. :param classname: class to check, like java.util.HashMap
  323. :type classname: string
  324. :param with_classpath: additional classpath to give
  325. :type with_classpath: string
  326. """
  327. javatestdir = '.waf-javatest'
  328. classpath = javatestdir
  329. if self.env.CLASSPATH:
  330. classpath += os.pathsep + self.env.CLASSPATH
  331. if isinstance(with_classpath, str):
  332. classpath += os.pathsep + with_classpath
  333. shutil.rmtree(javatestdir, True)
  334. os.mkdir(javatestdir)
  335. Utils.writef(os.path.join(javatestdir, 'Test.java'), class_check_source)
  336. # Compile the source
  337. self.exec_command(self.env.JAVAC + [os.path.join(javatestdir, 'Test.java')], shell=False)
  338. # Try to run the app
  339. cmd = self.env.JAVA + ['-cp', classpath, 'Test', classname]
  340. self.to_log("%s\n" % str(cmd))
  341. found = self.exec_command(cmd, shell=False)
  342. self.msg('Checking for java class %s' % classname, not found)
  343. shutil.rmtree(javatestdir, True)
  344. return found
  345. @conf
  346. def check_jni_headers(conf):
  347. """
  348. Checks for jni headers and libraries. On success the conf.env variables xxx_JAVA are added for use in C/C++ targets::
  349. def options(opt):
  350. opt.load('compiler_c')
  351. def configure(conf):
  352. conf.load('compiler_c java')
  353. conf.check_jni_headers()
  354. def build(bld):
  355. bld.shlib(source='a.c', target='app', use='JAVA')
  356. """
  357. if not conf.env.CC_NAME and not conf.env.CXX_NAME:
  358. conf.fatal('load a compiler first (gcc, g++, ..)')
  359. if not conf.env.JAVA_HOME:
  360. conf.fatal('set JAVA_HOME in the system environment')
  361. # jni requires the jvm
  362. javaHome = conf.env.JAVA_HOME[0]
  363. dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/include')
  364. if dir is None:
  365. dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/../Headers') # think different?!
  366. if dir is None:
  367. conf.fatal('JAVA_HOME does not seem to be set properly')
  368. f = dir.ant_glob('**/(jni|jni_md).h')
  369. incDirs = [x.parent.abspath() for x in f]
  370. dir = conf.root.find_dir(conf.env.JAVA_HOME[0])
  371. f = dir.ant_glob('**/*jvm.(so|dll|dylib)')
  372. libDirs = [x.parent.abspath() for x in f] or [javaHome]
  373. # On windows, we need both the .dll and .lib to link. On my JDK, they are
  374. # in different directories...
  375. f = dir.ant_glob('**/*jvm.(lib)')
  376. if f:
  377. libDirs = [[x, y.parent.abspath()] for x in libDirs for y in f]
  378. if conf.env.DEST_OS == 'freebsd':
  379. conf.env.append_unique('LINKFLAGS_JAVA', '-pthread')
  380. for d in libDirs:
  381. try:
  382. conf.check(header_name='jni.h', define_name='HAVE_JNI_H', lib='jvm',
  383. libpath=d, includes=incDirs, uselib_store='JAVA', uselib='JAVA')
  384. except Exception:
  385. pass
  386. else:
  387. break
  388. else:
  389. conf.fatal('could not find lib jvm in %r (see config.log)' % libDirs)