renamed SCons.Tool.ninja -> SCons.Tool.ninja_tool and added alias in tool loading...
[scons.git] / SCons / Tool / swig.py
blob3072960cc4422364f5ed54deff8119a9c91547d8
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 swig.
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
33 import sys
34 from subprocess import PIPE
36 import SCons.Action
37 import SCons.Defaults
38 import SCons.Node
39 import SCons.Tool
40 import SCons.Util
41 import SCons.Warnings
43 verbose = False
45 swigs = [ 'swig', 'swig3.0', 'swig2.0' ]
47 SwigAction = SCons.Action.Action('$SWIGCOM', '$SWIGCOMSTR')
49 def swigSuffixEmitter(env, source) -> str:
50 if '-c++' in SCons.Util.CLVar(env.subst("$SWIGFLAGS", source=source)):
51 return '$SWIGCXXFILESUFFIX'
52 else:
53 return '$SWIGCFILESUFFIX'
55 # Match '%module test', as well as '%module(directors="1") test'
56 # Also allow for test to be quoted (SWIG permits double quotes, but not single)
57 # Also allow for the line to have spaces after test if not quoted
58 _reModule = re.compile(r'%module(\s*\(.*\))?\s+("?)(\S+)\2')
60 def _find_modules(src):
61 """Find all modules referenced by %module lines in `src`, a SWIG .i file.
62 Returns a list of all modules, and a flag set if SWIG directors have
63 been requested (SWIG will generate an additional header file in this
64 case.)"""
65 directors = 0
66 mnames = []
67 try:
68 with open(src) as f:
69 data = f.read()
70 matches = _reModule.findall(data)
71 except OSError:
72 # If the file's not yet generated, guess the module name from the file stem
73 matches = []
74 mnames.append(os.path.splitext(os.path.basename(src))[0])
76 for m in matches:
77 mnames.append(m[2])
78 directors = directors or 'directors' in m[0]
79 return mnames, directors
81 def _add_director_header_targets(target, env) -> None:
82 # Directors only work with C++ code, not C
83 suffix = env.subst(env['SWIGCXXFILESUFFIX'])
84 # For each file ending in SWIGCXXFILESUFFIX, add a new target director
85 # header by replacing the ending with SWIGDIRECTORSUFFIX.
86 for x in target[:]:
87 n = x.name
88 d = x.dir
89 if n[-len(suffix):] == suffix:
90 target.append(d.File(n[:-len(suffix)] + env['SWIGDIRECTORSUFFIX']))
92 def _swigEmitter(target, source, env):
93 swigflags = env.subst("$SWIGFLAGS", target=target, source=source)
94 flags = SCons.Util.CLVar(swigflags)
95 for src in source:
96 src = str(src.rfile())
97 mnames = None
98 if "-python" in flags and "-noproxy" not in flags:
99 if mnames is None:
100 mnames, directors = _find_modules(src)
101 if directors:
102 _add_director_header_targets(target, env)
103 python_files = [m + ".py" for m in mnames]
104 outdir = env.subst('$SWIGOUTDIR', target=target, source=source)
105 # .py files should be generated in SWIGOUTDIR if specified,
106 # otherwise in the same directory as the target
107 if outdir:
108 python_files = [env.fs.File(os.path.join(outdir, j)) for j in python_files]
109 else:
110 python_files = [target[0].dir.File(m) for m in python_files]
111 target.extend(python_files)
112 if "-java" in flags:
113 if mnames is None:
114 mnames, directors = _find_modules(src)
115 if directors:
116 _add_director_header_targets(target, env)
117 java_files = [[m + ".java", m + "JNI.java"] for m in mnames]
118 java_files = SCons.Util.flatten(java_files)
119 outdir = env.subst('$SWIGOUTDIR', target=target, source=source)
120 if outdir:
121 java_files = [os.path.join(outdir, j) for j in java_files]
122 java_files = list(map(env.fs.File, java_files))
123 def t_from_s(t, p, s, x):
124 return t.dir
125 tsm = SCons.Node._target_from_source_map
126 tkey = len(tsm)
127 tsm[tkey] = t_from_s
128 for jf in java_files:
129 jf._func_target_from_source = tkey
130 target.extend(java_files)
131 return (target, source)
133 def _get_swig_version(env, swig):
134 """Run the SWIG command line tool to get and return the version number"""
135 version = None
136 swig = env.subst(swig)
137 if not swig:
138 return version
140 cp = SCons.Action.scons_subproc_run(
141 env, SCons.Util.CLVar(swig) + ['-version'], stdout=PIPE
143 if cp.returncode:
144 return version
145 out = SCons.Util.to_str(cp.stdout)
146 match = re.search(r'SWIG Version\s+(\S+).*', out, re.MULTILINE)
147 if match:
148 version = match.group(1)
149 if verbose:
150 print("Version is: %s" % version)
151 else:
152 if verbose:
153 print("Unable to detect version: [%s]" % out)
155 return version
157 def generate(env) -> None:
158 """Add Builders and construction variables for swig to an Environment."""
159 c_file, cxx_file = SCons.Tool.createCFileBuilders(env)
161 c_file.suffix['.i'] = swigSuffixEmitter
162 cxx_file.suffix['.i'] = swigSuffixEmitter
164 c_file.add_action('.i', SwigAction)
165 c_file.add_emitter('.i', _swigEmitter)
166 cxx_file.add_action('.i', SwigAction)
167 cxx_file.add_emitter('.i', _swigEmitter)
169 java_file = SCons.Tool.CreateJavaFileBuilder(env)
171 java_file.suffix['.i'] = swigSuffixEmitter
173 java_file.add_action('.i', SwigAction)
174 java_file.add_emitter('.i', _swigEmitter)
176 from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
177 from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
178 from SCons.Platform.win32 import CHOCO_DEFAULT_PATH
180 if sys.platform == 'win32':
181 swig = SCons.Tool.find_program_path(env, 'swig', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS + CHOCO_DEFAULT_PATH)
182 if swig:
183 swig_bin_dir = os.path.dirname(swig)
184 env.AppendENVPath('PATH', swig_bin_dir)
185 else:
186 SCons.Warnings.warn(
187 SCons.Warnings.SConsWarning,
188 'swig tool requested, but binary not found in ENV PATH'
191 if 'SWIG' not in env:
192 env['SWIG'] = env.Detect(swigs) or swigs[0]
194 env['SWIGVERSION'] = _get_swig_version(env, env['SWIG'])
195 env['SWIGFLAGS'] = SCons.Util.CLVar('')
196 env['SWIGDIRECTORSUFFIX'] = '_wrap.h'
197 env['SWIGCFILESUFFIX'] = '_wrap$CFILESUFFIX'
198 env['SWIGCXXFILESUFFIX'] = '_wrap$CXXFILESUFFIX'
199 env['_SWIGOUTDIR'] = r'${"-outdir \"%s\"" % SWIGOUTDIR}'
200 env['SWIGPATH'] = []
201 env['SWIGINCPREFIX'] = '-I'
202 env['SWIGINCSUFFIX'] = ''
203 env['_SWIGINCFLAGS'] = '${_concat(SWIGINCPREFIX, SWIGPATH, SWIGINCSUFFIX,' \
204 '__env__, RDirs, TARGET, SOURCE, affect_signature=False)}'
205 env['SWIGCOM'] = '$SWIG -o $TARGET ${_SWIGOUTDIR} ${_SWIGINCFLAGS} $SWIGFLAGS $SOURCES'
207 def exists(env):
208 swig = env.get('SWIG') or env.Detect(['swig'])
209 return swig
211 # Local Variables:
212 # tab-width:4
213 # indent-tabs-mode:nil
214 # End:
215 # vim: set expandtab tabstop=4 shiftwidth=4: