3 Tool-specific initialization for javac.
5 There normally shouldn't be any need to import this module directly.
6 It will usually be imported through the generic SCons.Tool.Tool()
14 # Permission is hereby granted, free of charge, to any person obtaining
15 # a copy of this software and associated documentation files (the
16 # "Software"), to deal in the Software without restriction, including
17 # without limitation the rights to use, copy, modify, merge, publish,
18 # distribute, sublicense, and/or sell copies of the Software, and to
19 # permit persons to whom the Software is furnished to do so, subject to
20 # the following conditions:
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
26 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
27 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
45 # Parse Java files for class names.
47 # This is a really cool parser from Charles Crain
48 # that finds appropriate class names in Java source.
50 # A regular expression that will find, in a java file,
51 # any alphanumeric token (keyword, class name, specifier); open or
52 # close brackets; a single-line comment "//"; the multi-line comment
53 # begin and end tokens /* and */; single or double quotes; and
54 # single or double quotes preceeded by a backslash.
55 _reToken = re.compile(r'(//[^\r\n]*|\\[\'"]|[\'"\{\}]|[A-Za-z_][\w\.]*|' +
59 """The initial state for parsing a Java file for classes,
60 interfaces, and anonymous inner classes."""
64 self.stackBrackets = []
69 def __getClassState(self):
71 return self.classState
72 except AttributeError:
73 ret = ClassState(self)
77 def __getPackageState(self):
79 return self.packageState
80 except AttributeError:
81 ret = PackageState(self)
82 self.packageState = ret
85 def __getAnonClassState(self):
88 except AttributeError:
89 ret = SkipState(1, AnonClassState(self))
93 def __getSkipState(self):
96 except AttributeError:
97 ret = SkipState(1, self)
101 def parseToken(self, token):
102 if token[:2] == '//':
103 pass # ignore comment
105 return IgnoreState('*/', self)
107 self.brackets = self.brackets + 1
109 self.brackets = self.brackets - 1
110 if len(self.stackBrackets) and \
111 self.brackets == self.stackBrackets[-1]:
112 self.listOutputs.append(string.join(self.listClasses, '$'))
113 self.listClasses.pop()
114 self.stackBrackets.pop()
115 elif token == '"' or token == "'":
116 return IgnoreState(token, self)
118 # anonymous inner class
119 if len(self.listClasses) > 0:
120 return self.__getAnonClassState()
121 return self.__getSkipState() # Skip the class name
122 elif token == 'class' or token == 'interface':
123 if len(self.listClasses) == 0:
125 self.stackBrackets.append(self.brackets)
126 return self.__getClassState()
127 elif token == 'package':
128 return self.__getPackageState()
131 def addAnonClass(self):
132 """Add an anonymous inner class"""
133 clazz = self.listClasses[0]
134 self.listOutputs.append('%s$%d' % (clazz, self.nextAnon))
135 self.brackets = self.brackets + 1
136 self.nextAnon = self.nextAnon + 1
138 def setPackage(self, package):
139 self.package = package
141 class AnonClassState:
142 """A state that looks for anonymous inner classes."""
143 def __init__(self, outer_state):
144 # outer_state is always an instance of OuterState
145 self.outer_state = outer_state
146 self.tokens_to_find = 2
147 def parseToken(self, token):
148 # This is an anonymous class if and only if the next token is a bracket
150 self.outer_state.addAnonClass()
151 return self.outer_state
154 """A state that will skip a specified number of tokens before
155 reverting to the previous state."""
156 def __init__(self, tokens_to_skip, old_state):
157 self.tokens_to_skip = tokens_to_skip
158 self.old_state = old_state
159 def parseToken(self, token):
160 self.tokens_to_skip = self.tokens_to_skip - 1
161 if self.tokens_to_skip < 1:
162 return self.old_state
166 """A state we go into when we hit a class or interface keyword."""
167 def __init__(self, outer_state):
168 # outer_state is always an instance of OuterState
169 self.outer_state = outer_state
170 def parseToken(self, token):
171 # the only token we get should be the name of the class.
172 self.outer_state.listClasses.append(token)
173 return self.outer_state
176 """A state that will ignore all tokens until it gets to a
178 def __init__(self, ignore_until, old_state):
179 self.ignore_until = ignore_until
180 self.old_state = old_state
181 def parseToken(self, token):
182 if self.ignore_until == token:
183 return self.old_state
187 """The state we enter when we encounter the package keyword.
188 We assume the next token will be the package name."""
189 def __init__(self, outer_state):
190 # outer_state is always an instance of OuterState
191 self.outer_state = outer_state
192 def parseToken(self, token):
193 self.outer_state.setPackage(token)
194 return self.outer_state
197 """Parse a .java file and return a double of package directory,
198 plus a list of .class files that compiling that .java file will
201 initial = OuterState()
203 for token in _reToken.findall(open(fn, 'r').read()):
204 # The regex produces a bunch of groups, but only one will
205 # have anything in it.
206 currstate = currstate.parseToken(token)
208 package = string.replace(initial.package, '.', os.sep)
209 return (package, initial.listOutputs)
212 # Don't actually parse Java files for class names.
214 # We might make this a configurable option in the future if
215 # Java-file parsing takes too long (although it shouldn't relative
216 # to how long the Java compiler itself seems to take...).
218 def parse_java(file):
219 """ "Parse" a .java file.
221 This actually just splits the file name, so the assumption here
222 is that the file name matches the public class name, and that
223 the path to the file is the same as the package name.
225 return os.path.split(file)
228 """Add Builders and construction variables for javac to an Environment."""
230 def emit_java_files(target, source, env):
231 """Create and return lists of source java files
232 and their corresponding target class files.
234 env['_JAVACLASSDIR'] = target[0]
235 env['_JAVASRCDIR'] = source[0]
236 java_suffix = env.get('JAVASUFFIX', '.java')
237 class_suffix = env.get('JAVACLASSSUFFIX', '.class')
240 def visit(arg, dirname, names, js=java_suffix):
241 java_files = filter(lambda n, js=js: n[-len(js):] == js, names)
242 java_paths = map(lambda f, d=dirname:
245 arg.extend(java_paths)
246 os.path.walk(source[0], visit, slist)
250 pkg_dir, classes = parse_java(file)
253 tlist.append(os.path.join(target[0],
258 tlist.append(os.path.join(target[0], c + class_suffix))
260 # This is an odd end case: no package and no classes.
261 # Just do our best based on the source file name.
262 tlist.append(os.path.join(target[0],
263 file[:-len(java_suffix)] + class_suffix))
267 JavaBuilder = SCons.Builder.Builder(action = '$JAVACCOM',
268 emitter = emit_java_files,
269 target_factory = SCons.Node.FS.default_fs.File,
270 source_factory = SCons.Node.FS.default_fs.File)
272 env['BUILDERS']['Java'] = JavaBuilder
274 env['JAVAC'] = 'javac'
275 env['JAVACFLAGS'] = ''
276 env['JAVACCOM'] = '$JAVAC $JAVACFLAGS -d $_JAVACLASSDIR -sourcepath $_JAVASRCDIR $SOURCES'
277 env['JAVACLASSSUFFIX'] = '.class'
278 env['JAVASUFFIX'] = '.java'
281 return env.Detect('javac')