diff --git a/changelog.d/3723.change.rst b/changelog.d/3723.change.rst new file mode 100644 index 0000000000..2ddd02f602 --- /dev/null +++ b/changelog.d/3723.change.rst @@ -0,0 +1,2 @@ +Updated ``CCompiler.has_function`` to work with more C99 compilers. +Deprecated the ```includes`` and ``include_dirs`` keyword arguments. diff --git a/setuptools/_distutils/ccompiler.py b/setuptools/_distutils/ccompiler.py index 646353111f..989feded0a 100644 --- a/setuptools/_distutils/ccompiler.py +++ b/setuptools/_distutils/ccompiler.py @@ -6,6 +6,7 @@ import sys import os import re +import warnings from .errors import ( CompileError, @@ -824,9 +825,19 @@ def has_function( # noqa: C901 libraries=None, library_dirs=None, ): - """Return a boolean indicating whether funcname is supported on - the current platform. The optional arguments can be used to - augment the compilation environment. + """Return a boolean indicating whether funcname is provided as + a symbol on the current platform. The optional arguments can + be used to augment the compilation environment. + + The libraries argument is a list of flags to be passed to the + linker to make additional symbol definitions available for + linking. + + The includes and include_dirs arguments are deprecated. + Usually, supplying include files with function declarations + will cause function detection to fail even in cases where the + symbol is available for linking. + """ # this can't be included at module scope because it tries to # import math which might not be available at that point - maybe @@ -835,8 +846,12 @@ def has_function( # noqa: C901 if includes is None: includes = [] + else: + warnings.warn("The include argument is deprecated") if include_dirs is None: include_dirs = [] + else: + warnings.warn("The include_dirs argument is deprecated") if libraries is None: libraries = [] if library_dirs is None: @@ -845,7 +860,23 @@ def has_function( # noqa: C901 f = os.fdopen(fd, "w") try: for incl in includes: - f.write("""#include "%s"\n""" % incl) + f.write("""#include %s\n""" % incl) + if not includes: + # Use "char func(void);" as the prototype to follow + # what autoconf does. This prototype does not match + # any well-known function the compiler might recognize + # as a builtin, so this ends up as a true link test. + # Without a fake prototype, the test would need to + # know the exact argument types, and the has_function + # interface does not provide that level of information. + f.write("""\ +#ifdef __cplusplus +extern "C" +#endif +char %s(void); +""" + % funcname + ) f.write( """\ int main (int argc, char **argv) { diff --git a/setuptools/_distutils/tests/test_unixccompiler.py b/setuptools/_distutils/tests/test_unixccompiler.py index 3978c23952..afcac0178d 100644 --- a/setuptools/_distutils/tests/test_unixccompiler.py +++ b/setuptools/_distutils/tests/test_unixccompiler.py @@ -304,3 +304,19 @@ def test_has_function(self): self.cc.output_dir = 'scratch' os.chdir(self.mkdtemp()) self.cc.has_function('abort', includes=['stdlib.h']) + + def test_has_function_prototype(self): + # Issue https://github.com/pypa/setuptools/issues/3648 + # Test prototype-generating behavior. + + # Every C implementation should have these. + assert self.cc.has_function('abort') + assert self.cc.has_function('exit') + # abort() is a valid expression with the prototype. + assert self.cc.has_function('abort', includes=['']) + # But exit() is not valid with the actual prototype in scope. + assert not self.cc.has_function('exit', includes=['']) + # And setuptools_does_not_exist is not declared or defined at all. + assert not self.cc.has_function('setuptools_does_not_exist') + assert not self.cc.has_function('setuptools_does_not_exist', + includes=[''])