Skip to content

Commit

Permalink
distutils.ccompiler: Make has_function work with more C99 compilers
Browse files Browse the repository at this point in the history
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 pypa#3648.
  • Loading branch information
fweimer-rh committed Dec 14, 2022
1 parent 6f7dd7c commit 99700e2
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
39 changes: 35 additions & 4 deletions setuptools/_distutils/ccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import sys
import os
import re
import warnings

from .errors import (
CompileError,
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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) {
Expand Down
16 changes: 16 additions & 0 deletions setuptools/_distutils/tests/test_unixccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdlib.h> prototype.
assert self.cc.has_function('abort', includes=['<stdlib.h>'])
# But exit() is not valid with the actual prototype in scope.
assert not self.cc.has_function('exit', includes=['<stdlib.h>'])
# 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=['<stdio.h>'])

0 comments on commit 99700e2

Please sign in to comment.