Commit f7f445cb authored by Stefan Behnel's avatar Stefan Behnel

Include .h and _api.h files in target overwrite check.

See https://github.com/cython/cython/issues/4177
See https://github.com/cython/cython/pull/4178
parent 83020d99
......@@ -179,6 +179,14 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
return 1
return 0
def assure_safe_target(self, path, allow_failed=False):
# Check for a common gotcha for new users: naming your .pyx file after the .c file you want to wrap
if not is_cython_generated_file(path, allow_failed=allow_failed, if_not_found=True):
# Raising a fatal CompileError instead of calling error() to prevent castrating an existing file.
raise CompileError(
self.pos, 'The output file already exists and does not look like it was generated by Cython: "%s"' %
os.path.basename(path))
def generate_h_code(self, env, options, result):
def h_entries(entries, api=0, pxd=0):
return [entry for entry in entries
......@@ -189,8 +197,11 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
h_vars = h_entries(env.var_entries)
h_funcs = h_entries(env.cfunc_entries)
h_extension_types = h_entries(env.c_class_entries)
if h_types or h_vars or h_funcs or h_extension_types:
result.h_file = replace_suffix_encoded(result.c_file, ".h")
self.assure_safe_target(result.h_file)
h_code_writer = Code.CCodeWriter()
c_code_config = generate_c_code_config(env, options)
globalstate = Code.GlobalState(h_code_writer, self, c_code_config)
......@@ -299,8 +310,11 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
api_vars = api_entries(env.var_entries)
api_funcs = api_entries(env.cfunc_entries)
api_extension_types = api_entries(env.c_class_entries)
if api_vars or api_funcs or api_extension_types:
result.api_file = replace_suffix_encoded(result.c_file, "_api.h")
self.assure_safe_target(result.api_file)
h_code = Code.CCodeWriter()
c_code_config = generate_c_code_config(env, options)
Code.GlobalState(h_code, self, c_code_config)
......@@ -401,13 +415,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
i_code.dedent()
def generate_c_code(self, env, options, result):
# Check for a common gotcha for new users: naming your .pyx file after the .c file you want to wrap
if not is_cython_generated_file(result.c_file, allow_failed=True, if_not_found=True):
# Raising a fatal CompileError instead of calling error() to prevent castrating an existing file.
raise CompileError(
self.pos, 'The output file already exists and does not look like it was generated by Cython: "%s"' %
os.path.basename(result.c_file))
self.assure_safe_target(result.c_file, allow_failed=True)
modules = self.referenced_modules
if Options.annotate or options.annotate:
......
......@@ -12,7 +12,7 @@ from Cython.Build.Dependencies import cythonize
# Make sure the source files are newer than the .c files, so that cythonize() regenerates them.
files = {}
for source_file in sorted(os.listdir(os.getcwd())):
if 'module' in source_file and not source_file.endswith(".c"):
if 'module' in source_file and (source_file.endswith(".pyx") or source_file.endswith(".py")):
c_file = files[source_file] = os.path.splitext(source_file)[0] + ".c"
os.utime(source_file, None)
assert not os.path.exists(c_file) or os.path.getmtime(source_file) >= os.path.getmtime(c_file)
......@@ -20,35 +20,45 @@ for source_file in sorted(os.listdir(os.getcwd())):
for source_file, c_file in files.items():
print("Testing:", source_file, c_file)
assert is_cython_generated_file(c_file, allow_failed=True, if_not_found=True)
# cythonizing should (re)generate the file
cythonize(source_file, language_level=3)
assert is_cython_generated_file(c_file, if_not_found=False)
assert os.path.getmtime(source_file) <= os.path.getmtime(c_file)
# calling cythonize again should not rewrite the file
# (not asserting this here, but at least it shouldn't fail)
cythonize(source_file, language_level=3)
assert is_cython_generated_file(c_file, if_not_found=False)
assert os.path.getmtime(source_file) <= os.path.getmtime(c_file)
# But overwriting an unknown file should fail, even when requested multiple times.
for source_file in [
"refuse_to_overwrite.pyx",
"refuse_to_overwrite.pyx",
"refuse_to_overwrite.pyx",
#
"refuse_to_overwrite.py",
"refuse_to_overwrite.py",
"refuse_to_overwrite.py",
#
"compile_failure.pyx",
"compile_failure.pyx",
"compile_failure.pyx",
"refuse_to_overwrite_header.pyx",
"refuse_to_overwrite_api_header.pyx",
]:
os.utime(source_file, None)
c_file = os.path.splitext(source_file)[0] + ".c"
assert not is_cython_generated_file(c_file)
try:
print("Testing:", source_file)
cythonize(source_file, language_level=3)
except CompileError:
print("REFUSED to overwrite %s, OK" % c_file)
assert not is_cython_generated_file(c_file)
if 'api_header' in source_file:
target_file = os.path.splitext(source_file)[0] + "_api.h"
elif 'header' in source_file:
target_file = os.path.splitext(source_file)[0] + ".h"
else:
assert False, "FAILURE: Existing output file was overwritten for source file %s" % source_file
target_file = os.path.splitext(source_file)[0] + ".c"
for _ in range(3):
os.utime(source_file, None)
assert not is_cython_generated_file(target_file)
try:
print("Testing:", source_file)
cythonize(source_file, language_level=3)
except CompileError:
print("REFUSED to overwrite %s, OK" % target_file)
assert not is_cython_generated_file(target_file)
else:
assert False, "FAILURE: Existing output file was overwritten for source file %s" % source_file
######## pymodule.c ########
......@@ -114,3 +124,56 @@ Do not overwrite an unknown output file even on compile failures.
"""
Not Python syntax!
######## write_module_header.pyx ########
cdef public int func():
return 1
######## overwrite_module_header.c ########
/* Generated by Cython 0.8.15 */
######## overwrite_module_header.pyx ########
cdef public int func():
return 1
######## refuse_to_overwrite_header.h ########
static int external_function(int x) {
return x + 1;
}
######## refuse_to_overwrite_header.pyx ########
cdef public int func():
return 1
######## write_module_api_header.pyx ########
cdef api int func():
return 1
######## overwrite_module_api_header.c ########
/* Generated by Cython 0.8.15 */
######## overwrite_module_api_header.pyx ########
cdef public int func():
return 1
######## refuse_to_overwrite_api_header_api.h ########
static int external_function(int x) {
return x + 1;
}
######## refuse_to_overwrite_api_header.pyx ########
cdef api int func():
return 1
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment