Published on

Python / GCC Clash

Authors
  • avatar
    Name
    Timothy Joseph Baney
    Twitter

I received the following exception during installation of dependencies for a Django project that relies on the mysqlclient Python package for database access. For context, these dependencies were being installed into a python virtual environment I created on a shared remote Linux machine. This development server is provisioned, and mantained by a dedicated team, and has been around for a while.

Below, pip attemps to install mysqlclient, but it can't find a pre-existing build artifact, known as a wheel, for the python version, architecture, and os of the machine. When this happens, the dependency has to be built from source, which sometimes involves the use of a C compiler (gcc) to compile C-extensions the dependency may have. C-extensions are modules written in C that can be imported and used by Python code that make heavy IO faster, such as interfacing with a MySQL database.

[me@shared-server]$ pip install mysqlclient==1.4.5
Collecting mysqlclient==1.4.5
  Using cached mysqlclient-1.4.5.tar.gz (85 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  ...
Building wheels for collected packages: mysqlclient
  Building wheel for mysqlclient (pyproject.toml) ... error
  error: subprocess-exited-with-error
  ...
  gcc -pthread -Who-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -03 -Wall -fno-semantic-interposition ... \
   -c src/MYSQLdb/_mysql.c -o build/temp.linux-x86_64-cpython-38/src/MySQLdb/_mysql.o ...
  gcc: error: unrecognized command line option '-fno-semantic-interposition'
  error: command '/usr/bin/gcc' failed with exit status 1
  [end of output]

The best laid plans of package management systems often go awry. An exception occurs because gcc is given a build flag that it does not recognize. Resolving the issue was contingent on first understanding the context of the error. Who/what is supplying build flags to gcc, why doesn't gcc recognize them, and can flags be manually provided instead.

What supplies the build flags to gcc?

As mentioned earlier, Python packages sometimes use C-extensions in it's code, and because of this, a C compiler needs to compile C-extension C code. Distutils, from Python's standard library, is responsible for supplying the build flags to gcc when compiling/building those extensions. It ontains utility functions that aid in packaging, distributing, and installing Python modules, which involves compiling and linking C code. Distutils uses the configurations provided during Python's build, and installation phase to generate the build flags, in addition to it's own defaults.

Why doesn't gcc recognize the flags?

Simple, -fno-semantic-interposition was introduced in gcc version 5.0, and the gcc version on the machine is 4.8.5, mystery solved ... but the question remains, why would gcc be given a flag it didn't recognize?

[me@shared-server]$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)

Recall that Python has it's own defaults, including compiler flags. If CFLAGS is available in the environment, flags set on that variable will be used instead. Recall that in addition to the defaults of CFLAGS, custom flags can be provided in a Python project's setup.py file. CFLAGS was not set however, and mysqlclient/setup.py didn't declare any custom compiler flags, meaning only Python's defaults were used. So why would a Python interpreter on a machine with an older gcc version try to suupply a new gcc flag? Getting into the Python's REPL clears this up.

In [1]: import sys
In [2]: import distutils.sysconfig as sysconfig

In [3]: sys.version
Out[3]: '3.8.8 (default, May 19 2021, 20:45:24) \n[GCC 9.3.1 20200408 (Red Hat 9.3.1-2)]'

In [4]: sysconfig.get_config_var('CFLAGS')
Out[4]: '-Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fno-semantic-interposition'

Distutils sets it's defaults, including CFLAGS, during build of Python itself. That being said, when installing python using yum, apt, etc. you'll get Python built alongside a gcc version that you probably won't always have on your machine. This is typically not a problem, and only showed up here because the gcc on the machine is so far behind the latest version. When python is built, it goes off of the gcc version on the machine, and sets the CFLAGS accordingly. Sys.version shows that when my Python installation was built, it was done so on a machine with gcc 9.3.1.

That settles that, but how can the issue be resolved?

Setting CFLAG manually?

CFLAGS can be manually provided to override the default flags supplied by distutils by settings the FLAGS environment variables. It should be noted however that this doesn't mean compilation of the C-extension will succeed. The flags provided must be compatible with the source. Below CLAGS is set to "02", and optimization flag, but you set the flags to whatever you'll need.

[me@shared-server]$ export CFLAGS="-02"
[me@shared-server]$ pip install mysqlclient==1.4.5