summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeni Burovski <evgeny.burovskiy@gmail.com>2022-01-28 14:50:32 +0300
committerGitHub <noreply@github.com>2022-01-28 12:50:32 +0100
commitcdad82d01479408701d6f29c6a8e271cb5169b36 (patch)
treec5b361431c994c10525d8788eba246508f3167c9
parente30913d738d5d0d04379396ce98587f75b2bab1b (diff)
downloadcython-cdad82d01479408701d6f29c6a8e271cb5169b36.tar.gz
Backport GH-4563 : generate dependency files (GH-4576)
Add a new command line option so that $ cythonize -M foo.pyx produces a file `foo.c.dep` with the dependencies of foo.pyx, in addition to `foo.c`. Try to write relative paths as much as possible. Backport of https://github.com/cython/cython/pull/4563
-rw-r--r--Cython/Build/Cythonize.py2
-rw-r--r--Cython/Build/Dependencies.py24
-rw-r--r--tests/build/depfile.srctree33
-rw-r--r--tests/build/depfile_numpy.srctree49
-rw-r--r--tests/build/depfile_package.srctree58
5 files changed, 166 insertions, 0 deletions
diff --git a/Cython/Build/Cythonize.py b/Cython/Build/Cythonize.py
index 9de84d5c8..c85b6eaba 100644
--- a/Cython/Build/Cythonize.py
+++ b/Cython/Build/Cythonize.py
@@ -103,6 +103,7 @@ def cython_compile(path_pattern, options):
compile_time_env=options.compile_time_env,
force=options.force,
quiet=options.quiet,
+ depfile=options.depfile,
**options.options)
if ext_modules and options.build:
@@ -194,6 +195,7 @@ def parse_args(args):
help='increase Python compatibility by ignoring some compile time errors')
parser.add_option('-k', '--keep-going', dest='keep_going', action='store_true',
help='compile as much as possible, ignore compilation failures')
+ parser.add_option('-M', '--depfile', action='store_true', help='produce depfiles for the sources')
options, args = parser.parse_args(args)
if not args:
diff --git a/Cython/Build/Dependencies.py b/Cython/Build/Dependencies.py
index e1c0ae833..7eb55e260 100644
--- a/Cython/Build/Dependencies.py
+++ b/Cython/Build/Dependencies.py
@@ -944,6 +944,8 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False,
:param compiler_directives: Allow to set compiler directives in the ``setup.py`` like this:
``compiler_directives={'embedsignature': True}``.
See :ref:`compiler-directives`.
+
+ :param depfile: produce depfiles for the sources if True.
"""
if exclude is None:
exclude = []
@@ -952,6 +954,8 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False,
if 'common_utility_include_dir' in options:
safe_makedirs(options['common_utility_include_dir'])
+ depfile = options.pop('depfile', None)
+
if pythran is None:
pythran_options = None
else:
@@ -1023,6 +1027,26 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False,
dir = os.path.dirname(c_file)
safe_makedirs_once(dir)
+ # write out the depfile, if requested
+ if depfile:
+ dependencies = deps.all_dependencies(source)
+ src_base_dir, _ = os.path.split(source)
+ if not src_base_dir.endswith(os.sep):
+ src_base_dir += os.sep
+ # paths below the base_dir are relative, otherwise absolute
+ paths = []
+ for fname in dependencies:
+ if (fname.startswith(src_base_dir) or
+ fname.startswith('.' + os.path.sep)):
+ paths.append(os.path.relpath(fname, src_base_dir))
+ else:
+ paths.append(os.path.abspath(fname))
+
+ depline = os.path.split(c_file)[1] + ": \\\n "
+ depline += " \\\n ".join(paths) + "\n"
+ with open(c_file+'.dep', 'w') as outfile:
+ outfile.write(depline)
+
if os.path.exists(c_file):
c_timestamp = os.path.getmtime(c_file)
else:
diff --git a/tests/build/depfile.srctree b/tests/build/depfile.srctree
new file mode 100644
index 000000000..0c2194f75
--- /dev/null
+++ b/tests/build/depfile.srctree
@@ -0,0 +1,33 @@
+PYTHON -m Cython.Build.Cythonize -M foo.pyx
+PYTHON check.py
+
+######## foo.pyx ########
+
+from bar cimport empty
+
+include "baz.pxi"
+
+empty()
+print(foo())
+
+
+######## baz.pxi ########
+
+def foo():
+ return "foo"
+
+
+######## bar.pxd ########
+
+cdef inline void empty():
+ print("empty")
+
+
+######## check.py ########
+
+import os
+
+with open("foo.c.dep", "r") as f:
+ contents = f.read().replace("\n", " ").replace("\\", "")
+
+assert sorted(contents.split()) == ['bar.pxd', 'baz.pxi', 'foo.c:', 'foo.pyx'], contents
diff --git a/tests/build/depfile_numpy.srctree b/tests/build/depfile_numpy.srctree
new file mode 100644
index 000000000..4a2461c64
--- /dev/null
+++ b/tests/build/depfile_numpy.srctree
@@ -0,0 +1,49 @@
+# tag: numpy
+
+PYTHON -m Cython.Build.Cythonize -M dep_np.pyx
+PYTHON check_np.py
+
+######## dep_np.pyx ########
+
+cimport numpy as np
+np.import_array()
+
+
+
+######## check_np.py ########
+
+import os
+import re
+
+import numpy as np
+import Cython
+
+with open("dep_np.c.dep", "r") as f:
+ contents = f.read().replace("\n", " ").replace("\\", "")
+
+contents = contents.split()
+
+cy_prefix, _ = os.path.split(Cython.__file__)
+contents = [fname.replace(cy_prefix, "cy_prefix") for fname in contents]
+
+np_prefix, _ = os.path.split(np.__file__)
+contents = [fname.replace(np_prefix, "np_prefix") for fname in contents]
+
+expected = ['cy_prefix/Includes/cpython/buffer.pxd',
+ 'cy_prefix/Includes/cpython/mem.pxd',
+ 'cy_prefix/Includes/cpython/object.pxd',
+ 'cy_prefix/Includes/cpython/ref.pxd',
+ 'cy_prefix/Includes/cpython/type.pxd',
+ 'cy_prefix/Includes/libc/stdio.pxd',
+ 'cy_prefix/Includes/libc/string.pxd',
+ 'dep_np.c:',
+ 'dep_np.pyx',]
+
+# Also account for legacy numpy versions, which do not ship
+# `__init__.pxd` hence the fallback is used:
+if 'cy_prefix/Includes/numpy/__init__.pxd' in contents:
+ expected.append('cy_prefix/Includes/numpy/__init__.pxd')
+else:
+ expected.append('np_prefix/__init__.pxd')
+
+assert sorted(contents) == sorted(expected), sorted(contents)
diff --git a/tests/build/depfile_package.srctree b/tests/build/depfile_package.srctree
new file mode 100644
index 000000000..54dcad9f1
--- /dev/null
+++ b/tests/build/depfile_package.srctree
@@ -0,0 +1,58 @@
+'''
+PYTHON -m Cython.Build.Cythonize -i pkg --depfile
+PYTHON package_test.py
+'''
+
+######## package_test.py ########
+
+import os
+
+with open("pkg/test.c.dep", "r") as f:
+ contents = f.read().replace("\n", " ").replace("\\", "")
+
+assert sorted(contents.split()) == sorted(['test.c:', 'sub/incl.pxi', 'test.pxd', 'test.pyx']), contents
+
+
+with open("pkg/sub/test.c.dep", "r") as f:
+ contents = f.read().replace("\n", " ").replace("\\", "")
+
+contents = [os.path.relpath(entry, '.')
+ if os.path.isabs(entry) else entry for entry in contents.split()]
+assert sorted(contents) == sorted(['test.c:', 'incl.pxi', 'test.pyx', '../test.pxd']), contents
+
+
+######## pkg/__init__.py ########
+
+
+######## pkg/test.pyx ########
+
+TEST = "pkg.test"
+
+include "sub/incl.pxi"
+
+cdef object get_str():
+ return TEST
+
+
+######## pkg/test.pxd ########
+
+cdef object get_str()
+
+
+######## pkg/sub/__init__.py ########
+
+
+######## pkg/sub/test.pyx ########
+# cython: language_level=3
+
+from ..test cimport get_str
+
+include 'incl.pxi'
+
+TEST = 'pkg.sub.test'
+
+
+######## pkg/sub/incl.pxi ########
+
+pass
+