Skip to content

Commit fb2a8a4

Browse files
committed
distutils.ccompiler: Make has_function work with more C99 compilers
C99 removed support for implicit function declarations. This means that just calling a function, without declaring the function first, can result in a compilation error. Today, has_function works with most compilers because they issue just a warning, create an object file, and attempt a link, which then detects available of the symbol at link time, as intended. With future compilers, compilation will already fail, and no link test is performed. The has_function interface provides the caller with a way to supply a list of header files to include. However, even with today's compilers, this only works if the function does not expect any parameters. Otherwise, the function call in the C fragment created by has_function will not supply the correct argument list and fail compilation. Therefore, this change supplies and incorrect prototype without arguments. This is what autoconf does today in a very similar situation, so it is quite likely that compilers will support this construct in this context in the future. The includes and include_dirs arguments are deprecated because of the parameter list mismatch issue. Fixes #3648.
1 parent 6f7dd7c commit fb2a8a4

File tree

3 files changed

+53
-4
lines changed

3 files changed

+53
-4
lines changed

changelog.d/3723.change.rst

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Updated ``CCompiler.has_function`` to work with more C99 compilers.
2+
Deprecated the ```includes`` and ``include_dirs`` keyword arguments.

setuptools/_distutils/ccompiler.py

+35-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import os
88
import re
9+
import warnings
910

1011
from .errors import (
1112
CompileError,
@@ -824,9 +825,19 @@ def has_function( # noqa: C901
824825
libraries=None,
825826
library_dirs=None,
826827
):
827-
"""Return a boolean indicating whether funcname is supported on
828-
the current platform. The optional arguments can be used to
829-
augment the compilation environment.
828+
"""Return a boolean indicating whether funcname is provided as
829+
a symbol on the current platform. The optional arguments can
830+
be used to augment the compilation environment.
831+
832+
The libraries argument is a list of flags to be passed to the
833+
linker to make additional symbol definitions available for
834+
linking.
835+
836+
The includes and include_dirs arguments are deprecated.
837+
Usually, supplying include files with function declarations
838+
will cause function detection to fail even in cases where the
839+
symbol is available for linking.
840+
830841
"""
831842
# this can't be included at module scope because it tries to
832843
# import math which might not be available at that point - maybe
@@ -835,8 +846,12 @@ def has_function( # noqa: C901
835846

836847
if includes is None:
837848
includes = []
849+
else:
850+
warnings.warn("The include argument is deprecated")
838851
if include_dirs is None:
839852
include_dirs = []
853+
else:
854+
warnings.warn("The include_dirs argument is deprecated")
840855
if libraries is None:
841856
libraries = []
842857
if library_dirs is None:
@@ -845,7 +860,23 @@ def has_function( # noqa: C901
845860
f = os.fdopen(fd, "w")
846861
try:
847862
for incl in includes:
848-
f.write("""#include "%s"\n""" % incl)
863+
f.write("""#include %s\n""" % incl)
864+
if not includes:
865+
# Use "char func(void);" as the prototype to follow
866+
# what autoconf does. This prototype does not match
867+
# any well-known function the compiler might recognize
868+
# as a builtin, so this ends up as a true link test.
869+
# Without a fake prototype, the test would need to
870+
# know the exact argument types, and the has_function
871+
# interface does not provide that level of information.
872+
f.write("""\
873+
#ifdef __cplusplus
874+
extern "C"
875+
#endif
876+
char %s(void);
877+
"""
878+
% funcname
879+
)
849880
f.write(
850881
"""\
851882
int main (int argc, char **argv) {

setuptools/_distutils/tests/test_unixccompiler.py

+16
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,19 @@ def test_has_function(self):
304304
self.cc.output_dir = 'scratch'
305305
os.chdir(self.mkdtemp())
306306
self.cc.has_function('abort', includes=['stdlib.h'])
307+
308+
def test_has_function_prototype(self):
309+
# Issue https://github.com/pypa/setuptools/issues/3648
310+
# Test prototype-generating behavior.
311+
312+
# Every C implementation should have these.
313+
assert self.cc.has_function('abort')
314+
assert self.cc.has_function('exit')
315+
# abort() is a valid expression with the <stdlib.h> prototype.
316+
assert self.cc.has_function('abort', includes=['<stdlib.h>'])
317+
# But exit() is not valid with the actual prototype in scope.
318+
assert not self.cc.has_function('exit', includes=['<stdlib.h>'])
319+
# And setuptools_does_not_exist is not declared or defined at all.
320+
assert not self.cc.has_function('setuptools_does_not_exist')
321+
assert not self.cc.has_function('setuptools_does_not_exist',
322+
includes=['<stdio.h>'])

0 commit comments

Comments
 (0)