14 """Provides distutils command classes for the GRPC Python setup process."""
29 from setuptools.command
import build_ext
30 from setuptools.command
import build_py
33 PYTHON_STEM = os.path.dirname(os.path.abspath(__file__))
34 GRPC_STEM = os.path.abspath(PYTHON_STEM +
'../../../../')
35 PROTO_STEM = os.path.join(GRPC_STEM,
'src',
'proto')
36 PROTO_GEN_STEM = os.path.join(GRPC_STEM,
'src',
'python',
'gens')
37 CYTHON_STEM = os.path.join(PYTHON_STEM,
'grpc',
'_cython')
41 """Simple exception class for GRPC custom commands."""
47 """Returns a string path to a bdist file for Linux to install.
49 If we can retrieve a pre-compiled bdist from online, uses it. Else, emits a
50 warning and builds from source.
60 from six.moves.urllib
import request
61 decorated_path = decorated_basename + GRPC_CUSTOM_BDIST_EXT
63 url = BINARIES_REPOSITORY +
'/{target}'.
format(target=decorated_path)
64 bdist_data = request.urlopen(url).
read()
65 except IOError
as error:
66 raise CommandError(
'{}\n\nCould not find the bdist {}: {}'.
format(
67 traceback.format_exc(), decorated_path, error.message))
69 bdist_path = target_bdist_basename + GRPC_CUSTOM_BDIST_EXT
71 with open(bdist_path,
'w')
as bdist_file:
72 bdist_file.write(bdist_data)
73 except IOError
as error:
74 raise CommandError(
'{}\n\nCould not write grpcio bdist: {}'.
format(
75 traceback.format_exc(), error.message))
80 """Command to generate documentation via sphinx."""
82 description =
'generate sphinx documentation'
94 import sphinx.cmd.build
95 source_dir = os.path.join(GRPC_STEM,
'doc',
'python',
'sphinx')
96 target_dir = os.path.join(GRPC_STEM,
'doc',
'build')
97 exit_code = sphinx.cmd.build.build_main(
98 [
'-b',
'html',
'-W',
'--keep-going', source_dir, target_dir])
101 "Documentation generation has warnings or errors")
105 """Command to generate project metadata in a module."""
107 description =
'build grpcio project metadata files'
117 with open(os.path.join(PYTHON_STEM,
'grpc/_grpcio_metadata.py'),
119 module_file.write(
'__version__ = """{}"""'.
format(
120 self.distribution.get_version()))
124 """Custom project build command."""
127 self.run_command(
'build_project_metadata')
128 build_py.build_py.run(self)
132 """Includes a file that will always fail to compile in all extensions."""
133 poison_filename = os.path.join(PYTHON_STEM,
'poison.c')
134 with open(poison_filename,
'w')
as poison:
135 poison.write(
'#error {}'.
format(message))
136 for extension
in extensions:
137 extension.sources = [poison_filename]
141 """Replace .pyx files with their generated counterparts and return whether or
142 not cythonization still needs to occur."""
143 for extension
in extensions:
144 generated_pyx_sources = []
146 for source
in extension.sources:
147 base, file_ext = os.path.splitext(source)
148 if file_ext ==
'.pyx':
149 generated_pyx_source =
next((base + gen_ext
for gen_ext
in (
152 )
if os.path.isfile(base + gen_ext)),
None)
153 if generated_pyx_source:
154 generated_pyx_sources.append(generated_pyx_source)
156 sys.stderr.write(
'Cython-generated files are missing...\n')
159 other_sources.append(source)
160 extension.sources = generated_pyx_sources + other_sources
161 sys.stderr.write(
'Found cython-generated files...\n')
166 """Attempt to cythonize the extensions.
169 extensions: A list of `distutils.extension.Extension`.
170 linetracing: A bool indicating whether or not to enable linetracing.
171 mandatory: Whether or not having Cython-generated files is mandatory. If it
172 is, extensions will be poisoned when they can't be fully generated.
180 "This package needs to generate C files with Cython but it cannot. "
181 "Poisoning extension sources to disallow extension commands...")
184 "Extensions have been poisoned due to missing Cython-generated code."
187 cython_compiler_directives = {}
189 additional_define_macros = [(
'CYTHON_TRACE_NOGIL',
'1')]
190 cython_compiler_directives[
'linetrace'] =
True
191 return Cython.Build.cythonize(
194 include_dir
for extension
in extensions
195 for include_dir
in extension.include_dirs
197 compiler_directives=cython_compiler_directives)
201 """Custom build_ext command to enable compiler-specific flags."""
204 'unix': (
'-pthread',),
216 filename = build_ext.build_ext.get_ext_filename(self, ext_name)
217 orig_ext_suffix = sysconfig.get_config_var(
'EXT_SUFFIX')
218 new_ext_suffix = os.getenv(
'GRPC_PYTHON_OVERRIDE_EXT_SUFFIX')
219 if new_ext_suffix
and filename.endswith(orig_ext_suffix):
220 filename = filename[:-
len(orig_ext_suffix)] + new_ext_suffix
225 def compiler_ok_with_extra_std():
226 """Test if default compiler is okay with specifying c++ version
227 when invoked in C mode. GCC is okay with this, while clang is not.
231 cc_test = subprocess.Popen([
'cc',
'-x',
'c',
'-std=c++14',
'-'],
232 stdin=subprocess.PIPE,
233 stdout=subprocess.PIPE,
234 stderr=subprocess.PIPE)
235 _, cc_err = cc_test.communicate(input=b
'int main(){return 0;}')
236 return not 'invalid argument' in str(cc_err)
238 sys.stderr.write(
'Non-fatal exception:' +
239 traceback.format_exc() +
'\n')
251 if not compiler_ok_with_extra_std():
252 old_compile = self.compiler._compile
254 def new_compile(obj, src, ext, cc_args, extra_postargs, pp_opts):
255 if src.endswith(
'.c'):
257 arg
for arg
in extra_postargs
if not '-std=c++' in arg
259 elif src.endswith(
'.cc')
or src.endswith(
'.cpp'):
261 arg
for arg
in extra_postargs
if not '-std=gnu99' in arg
263 return old_compile(obj, src, ext, cc_args, extra_postargs,
266 self.compiler._compile = new_compile
268 compiler = self.compiler.compiler_type
269 if compiler
in BuildExt.C_OPTIONS:
271 extension.extra_compile_args += list(
272 BuildExt.C_OPTIONS[compiler])
273 if compiler
in BuildExt.LINK_OPTIONS:
275 extension.extra_link_args += list(
276 BuildExt.LINK_OPTIONS[compiler])
280 build_ext.build_ext.build_extensions(self)
281 except Exception
as error:
282 formatted_exception = traceback.format_exc()
285 "Failed `build_ext` step:\n{}".
format(formatted_exception))
289 """Command to gather project dependencies."""
291 description =
'gather dependencies for grpcio'
293 (
'test',
't',
'flag indicating to gather test dependencies'),
294 (
'install',
'i',
'flag indicating to gather install dependencies')
306 if self.install
and self.distribution.install_requires:
307 self.distribution.fetch_build_eggs(
308 self.distribution.install_requires)
309 if self.test
and self.distribution.tests_require:
310 self.distribution.fetch_build_eggs(self.distribution.tests_require)
314 """Command to clean build artifacts."""
316 description =
'Clean build artifacts.'
318 (
'all',
'a',
'a phony flag to allow our script to continue'),
323 'src/python/grpcio/__pycache__/',
324 'src/python/grpcio/grpc/_cython/cygrpc.cpp',
325 'src/python/grpcio/grpc/_cython/*.so',
326 'src/python/grpcio/grpcio.egg-info/',
328 _CURRENT_DIRECTORY = os.path.normpath(
329 os.path.join(os.path.dirname(os.path.realpath(__file__)),
"../../.."))
338 for path_spec
in self._FILE_PATTERNS:
339 this_glob = os.path.normpath(
340 os.path.join(Clean._CURRENT_DIRECTORY, path_spec))
341 abs_paths = glob.glob(this_glob)
342 for path
in abs_paths:
343 if not str(path).startswith(Clean._CURRENT_DIRECTORY):
345 "Cowardly refusing to delete {}.".
format(path))
346 print(
"Removing {}".
format(os.path.relpath(path)))
347 if os.path.isfile(path):
350 shutil.rmtree(
str(path))