logging working in NewParallel, but changed to be default. Need to figure out how...
[scons.git] / SCons / Tool / qt.py
blobff995657db5501efa20b86471dc7b5a9821b679d
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """Tool-specific initialization for Qt.
26 There normally shouldn't be any need to import this module directly.
27 It will usually be imported through the generic SCons.Tool.Tool()
28 selection method.
29 """
31 import os.path
32 import re
34 import SCons.Action
35 import SCons.Builder
36 import SCons.Defaults
37 import SCons.Scanner
38 import SCons.Tool
39 import SCons.Util
40 import SCons.Tool.cxx
41 import SCons.Warnings
42 cplusplus = SCons.Tool.cxx
44 class ToolQtWarning(SCons.Warnings.SConsWarning):
45 pass
47 class GeneratedMocFileNotIncluded(ToolQtWarning):
48 pass
50 class QtdirNotFound(ToolQtWarning):
51 pass
53 SCons.Warnings.enableWarningClass(ToolQtWarning)
55 header_extensions = [".h", ".hxx", ".hpp", ".hh"]
56 if SCons.Util.case_sensitive_suffixes('.h', '.H'):
57 header_extensions.append('.H')
59 cxx_suffixes = cplusplus.CXXSuffixes
62 def find_platform_specific_qt_paths():
63 """
64 find non-standard QT paths
66 If the platform does not put QT tools in standard search paths,
67 the path is expected to be set using QTDIR. SCons violates
68 the normal rule of not pulling from the user's environment
69 in this case. However, some test cases try to validate what
70 happens when QTDIR is unset, so we need to try to make a guess.
72 :return: a guess at a path
73 """
75 # qt_bin_dirs = []
76 qt_bin_dir = None
77 if os.path.isfile('/etc/redhat-release'):
78 with open('/etc/redhat-release','r') as rr:
79 lines = rr.readlines()
80 distro = lines[0].split()[0]
81 if distro == 'CentOS':
82 # Centos installs QT under /usr/{lib,lib64}/qt{4,5,-3.3}/bin
83 # so we need to handle this differently
84 # qt_bin_dirs = glob.glob('/usr/lib64/qt*/bin')
85 # TODO: all current Fedoras do the same, need to look deeper here.
86 qt_bin_dir = '/usr/lib64/qt-3.3/bin'
88 return qt_bin_dir
91 QT_BIN_DIR = find_platform_specific_qt_paths()
93 def checkMocIncluded(target, source, env):
94 moc = target[0]
95 cpp = source[0]
96 # looks like cpp.includes is cleared before the build stage :-(
97 # not really sure about the path transformations (moc.cwd? cpp.cwd?) :-/
98 path = SCons.Defaults.CScan.path(env, moc.cwd)
99 includes = SCons.Defaults.CScan(cpp, env, path)
100 if moc not in includes:
101 SCons.Warnings.warn(
102 GeneratedMocFileNotIncluded,
103 "Generated moc file '%s' is not included by '%s'" %
104 (str(moc), str(cpp)))
106 def find_file(filename, paths, node_factory):
107 for dir in paths:
108 node = node_factory(filename, dir)
109 if node.rexists():
110 return node
111 return None
113 class _Automoc:
115 Callable class, which works as an emitter for Programs, SharedLibraries and
116 StaticLibraries.
119 def __init__(self, objBuilderName):
120 self.objBuilderName = objBuilderName
122 def __call__(self, target, source, env):
124 Smart autoscan function. Gets the list of objects for the Program
125 or Lib. Adds objects and builders for the special qt files.
127 try:
128 if int(env.subst('$QT_AUTOSCAN')) == 0:
129 return target, source
130 except ValueError:
131 pass
132 try:
133 debug = int(env.subst('$QT_DEBUG'))
134 except ValueError:
135 debug = 0
137 # some shortcuts used in the scanner
138 splitext = SCons.Util.splitext
139 objBuilder = getattr(env, self.objBuilderName)
141 # some regular expressions:
142 # Q_OBJECT detection
143 q_object_search = re.compile(r'[^A-Za-z0-9]Q_OBJECT[^A-Za-z0-9]')
144 # cxx and c comment 'eater'
145 #comment = re.compile(r'(//.*)|(/\*(([^*])|(\*[^/]))*\*/)')
146 # CW: something must be wrong with the regexp. See also bug #998222
147 # CURRENTLY THERE IS NO TEST CASE FOR THAT
149 # The following is kind of hacky to get builders working properly (FIXME)
150 objBuilderEnv = objBuilder.env
151 objBuilder.env = env
152 mocBuilderEnv = env.Moc.env
153 env.Moc.env = env
155 # make a deep copy for the result; MocH objects will be appended
156 out_sources = source[:]
158 for obj in source:
159 if not obj.has_builder():
160 # binary obj file provided
161 if debug:
162 print("scons: qt: '%s' seems to be a binary. Discarded." % str(obj))
163 continue
164 cpp = obj.sources[0]
165 if not splitext(str(cpp))[1] in cxx_suffixes:
166 if debug:
167 print("scons: qt: '%s' is no cxx file. Discarded." % str(cpp))
168 # c or fortran source
169 continue
170 #cpp_contents = comment.sub('', cpp.get_text_contents())
171 if debug:
172 print("scons: qt: Getting contents of %s" % cpp)
173 cpp_contents = cpp.get_text_contents()
174 h=None
175 for h_ext in header_extensions:
176 # try to find the header file in the corresponding source
177 # directory
178 hname = splitext(cpp.name)[0] + h_ext
179 h = find_file(hname, (cpp.get_dir(),), env.File)
180 if h:
181 if debug:
182 print("scons: qt: Scanning '%s' (header of '%s')" % (str(h), str(cpp)))
183 #h_contents = comment.sub('', h.get_text_contents())
184 h_contents = h.get_text_contents()
185 break
186 if not h and debug:
187 print("scons: qt: no header for '%s'." % (str(cpp)))
188 if h and q_object_search.search(h_contents):
189 # h file with the Q_OBJECT macro found -> add moc_cpp
190 moc_cpp = env.Moc(h)
191 moc_o = objBuilder(moc_cpp)
192 out_sources.append(moc_o)
193 #moc_cpp.target_scanner = SCons.Defaults.CScan
194 if debug:
195 print("scons: qt: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(h), str(moc_cpp)))
196 if cpp and q_object_search.search(cpp_contents):
197 # cpp file with Q_OBJECT macro found -> add moc
198 # (to be included in cpp)
199 moc = env.Moc(cpp)
200 env.Ignore(moc, moc)
201 if debug:
202 print("scons: qt: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(cpp), str(moc)))
203 #moc.source_scanner = SCons.Defaults.CScan
204 # restore the original env attributes (FIXME)
205 objBuilder.env = objBuilderEnv
206 env.Moc.env = mocBuilderEnv
208 return (target, out_sources)
210 AutomocShared = _Automoc('SharedObject')
211 AutomocStatic = _Automoc('StaticObject')
213 def _detect(env):
214 """Not really safe, but fast method to detect the QT library"""
216 QTDIR = env.get('QTDIR',None)
217 if not QTDIR:
218 QTDIR = os.environ.get('QTDIR',None)
219 if not QTDIR:
220 moc = env.WhereIs('moc') or env.WhereIs('moc',QT_BIN_DIR)
221 if moc:
222 QTDIR = os.path.dirname(os.path.dirname(moc))
223 SCons.Warnings.warn(
224 QtdirNotFound,
225 "Could not detect qt, using moc executable as a hint (QTDIR=%s)" % QTDIR)
226 else:
227 QTDIR = None
228 SCons.Warnings.warn(
229 QtdirNotFound,
230 "Could not detect qt, using empty QTDIR")
231 return QTDIR
233 def uicEmitter(target, source, env):
234 adjustixes = SCons.Util.adjustixes
235 bs = SCons.Util.splitext(str(source[0].name))[0]
236 bs = os.path.join(str(target[0].get_dir()),bs)
237 # first target (header) is automatically added by builder
238 if len(target) < 2:
239 # second target is implementation
240 target.append(adjustixes(bs,
241 env.subst('$QT_UICIMPLPREFIX'),
242 env.subst('$QT_UICIMPLSUFFIX')))
243 if len(target) < 3:
244 # third target is moc file
245 target.append(adjustixes(bs,
246 env.subst('$QT_MOCHPREFIX'),
247 env.subst('$QT_MOCHSUFFIX')))
248 return target, source
250 def uicScannerFunc(node, env, path):
251 lookout = []
252 lookout.extend(env['CPPPATH'])
253 lookout.append(str(node.rfile().dir))
254 includes = re.findall("<include.*?>(.*?)</include>", node.get_text_contents())
255 result = []
256 for incFile in includes:
257 dep = env.FindFile(incFile,lookout)
258 if dep:
259 result.append(dep)
260 return result
262 uicScanner = SCons.Scanner.ScannerBase(uicScannerFunc,
263 name = "UicScanner",
264 node_class = SCons.Node.FS.File,
265 node_factory = SCons.Node.FS.File,
266 recursive = 0)
268 def generate(env):
269 """Add Builders and construction variables for qt to an Environment."""
270 CLVar = SCons.Util.CLVar
271 Action = SCons.Action.Action
272 Builder = SCons.Builder.Builder
274 SCons.Warnings.warn(
275 SCons.Warnings.ToolQtDeprecatedWarning, "Tool module for Qt version 3 is deprecated"
278 env.SetDefault(QTDIR = _detect(env),
279 QT_BINPATH = os.path.join('$QTDIR', 'bin'),
280 QT_CPPPATH = os.path.join('$QTDIR', 'include'),
281 QT_LIBPATH = os.path.join('$QTDIR', 'lib'),
282 QT_MOC = os.path.join('$QT_BINPATH','moc'),
283 QT_UIC = os.path.join('$QT_BINPATH','uic'),
284 QT_LIB = 'qt', # may be set to qt-mt
286 QT_AUTOSCAN = 1, # scan for moc'able sources
288 # Some QT specific flags. I don't expect someone wants to
289 # manipulate those ...
290 QT_UICIMPLFLAGS = CLVar(''),
291 QT_UICDECLFLAGS = CLVar(''),
292 QT_MOCFROMHFLAGS = CLVar(''),
293 QT_MOCFROMCXXFLAGS = CLVar('-i'),
295 # suffixes/prefixes for the headers / sources to generate
296 QT_UICDECLPREFIX = '',
297 QT_UICDECLSUFFIX = '.h',
298 QT_UICIMPLPREFIX = 'uic_',
299 QT_UICIMPLSUFFIX = '$CXXFILESUFFIX',
300 QT_MOCHPREFIX = 'moc_',
301 QT_MOCHSUFFIX = '$CXXFILESUFFIX',
302 QT_MOCCXXPREFIX = '',
303 QT_MOCCXXSUFFIX = '.moc',
304 QT_UISUFFIX = '.ui',
306 # Commands for the qt support ...
307 # command to generate header, implementation and moc-file
308 # from a .ui file
309 QT_UICCOM = [
310 CLVar('$QT_UIC $QT_UICDECLFLAGS -o ${TARGETS[0]} $SOURCE'),
311 CLVar('$QT_UIC $QT_UICIMPLFLAGS -impl ${TARGETS[0].file} '
312 '-o ${TARGETS[1]} $SOURCE'),
313 CLVar('$QT_MOC $QT_MOCFROMHFLAGS -o ${TARGETS[2]} ${TARGETS[0]}')],
314 # command to generate meta object information for a class
315 # declarated in a header
316 QT_MOCFROMHCOM = (
317 '$QT_MOC $QT_MOCFROMHFLAGS -o ${TARGETS[0]} $SOURCE'),
318 # command to generate meta object information for a class
319 # declarated in a cpp file
320 QT_MOCFROMCXXCOM = [
321 CLVar('$QT_MOC $QT_MOCFROMCXXFLAGS -o ${TARGETS[0]} $SOURCE'),
322 Action(checkMocIncluded,None)])
324 # ... and the corresponding builders
325 uicBld = Builder(action=SCons.Action.Action('$QT_UICCOM', '$QT_UICCOMSTR'),
326 emitter=uicEmitter,
327 src_suffix='$QT_UISUFFIX',
328 suffix='$QT_UICDECLSUFFIX',
329 prefix='$QT_UICDECLPREFIX',
330 source_scanner=uicScanner)
331 mocBld = Builder(action={}, prefix={}, suffix={})
332 for h in header_extensions:
333 act = SCons.Action.Action('$QT_MOCFROMHCOM', '$QT_MOCFROMHCOMSTR')
334 mocBld.add_action(h, act)
335 mocBld.prefix[h] = '$QT_MOCHPREFIX'
336 mocBld.suffix[h] = '$QT_MOCHSUFFIX'
337 for cxx in cxx_suffixes:
338 act = SCons.Action.Action('$QT_MOCFROMCXXCOM', '$QT_MOCFROMCXXCOMSTR')
339 mocBld.add_action(cxx, act)
340 mocBld.prefix[cxx] = '$QT_MOCCXXPREFIX'
341 mocBld.suffix[cxx] = '$QT_MOCCXXSUFFIX'
343 # register the builders
344 env['BUILDERS']['Uic'] = uicBld
345 env['BUILDERS']['Moc'] = mocBld
346 static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
347 static_obj.add_src_builder('Uic')
348 shared_obj.add_src_builder('Uic')
350 # We use the emitters of Program / StaticLibrary / SharedLibrary
351 # to scan for moc'able files
352 # We can't refer to the builders directly, we have to fetch them
353 # as Environment attributes because that sets them up to be called
354 # correctly later by our emitter.
355 env.AppendUnique(PROGEMITTER =[AutomocStatic],
356 SHLIBEMITTER=[AutomocShared],
357 LDMODULEEMITTER=[AutomocShared],
358 LIBEMITTER =[AutomocStatic],
359 # Of course, we need to link against the qt libraries
360 CPPPATH=["$QT_CPPPATH"],
361 LIBPATH=["$QT_LIBPATH"],
362 LIBS=['$QT_LIB'])
364 def exists(env):
365 return _detect(env)
367 # Local Variables:
368 # tab-width:4
369 # indent-tabs-mode:nil
370 # End:
371 # vim: set expandtab tabstop=4 shiftwidth=4: