Repository: conda/conda-build

Issue:
conda skeleton cpan should add a C compiler dep if a module contains xs files
### What happened?

When running `conda skeleton cpan` to create a recipe for a Perl module, a heuristics is used to decide whether specific compilers or build tools like `make` are required for building the package. Currently, this approach fails to add the C compiler requirement for many Perl modules, e.g. for  [`Sub::Identify`](https://metacpan.org/pod/Sub::Identify). 

### Steps to reproduce: 

Run `conda skeleton cpan Sub::Identify`. 

### What happens

 The generated `meta.yaml` only lists `perl` as a requirement. Compilers and `make` are not added. 

### What should happen

The deps `{{ compiler('c') }}` and `make` should be added, since the distribution contains compiled code. This can be verified by looking at the files of the distribution (`cpanm --look Sub::Identify`): it contains `Identify.xs`, and `.xs´ files contain C code interfaced by Perl (cf. the [docs](https://perldoc.perl.org/perlxs)).

### Background

The heuristics checks for files with certain extensions to determine the requirements. More precisely, function `get_build_dependencies_from_src_archive()` from file `conda_build/skeletons/cpan.py` checks the files in the distribution for  specific extensions. The C compiler dependency is only added when extension `.c` is found. However, it should also be added whenever an `.xs` file is found. 

### Proposed fix

Add `.xs` to the list of extensions that trigger the addition of the C compiler dep. I will open a PR containing this suggested fix soon.

### Related issues

#3662 has some interesting background information, but considers other aspects of this problem.

### Conda Details

</details>

<details>
<summary><code>conda info</code></summary>

```shell
active environment : None
            shell level : 0
       user config file : /home/felix/.condarc
 populated config files : /home/felix/.condarc
          conda version : 22.9.0
    conda-build version : not installed
         python version : 3.7.12.final.0
       virtual packages : __linux=5.15.0=0
                          __glibc=2.31=0
                          __unix=0=0
                          __archspec=1=x86_64
       base environment : /home/felix/miniconda3  (writable)
      conda av data dir : /home/felix/miniconda3/etc/conda
  conda av metadata url : None
           channel URLs : https://conda.anaconda.org/conda-forge/linux-64
                          https://conda.anaconda.org/conda-forge/noarch
                          https://conda.anaconda.org/bioconda/linux-64
                          https://conda.anaconda.org/bioconda/noarch
                          https://repo.anaconda.com/pkgs/main/linux-64
                          https://repo.anaconda.com/pkgs/main/noarch
                          https://repo.anaconda.com/pkgs/r/linux-64
                          https://repo.anaconda.com/pkgs/r/noarch
          package cache : /home/felix/miniconda3/pkgs
                          /home/felix/.conda/pkgs
       envs directories : /home/felix/miniconda3/envs
                          /home/felix/.conda/envs
               platform : linux-64
             user-agent : conda/22.9.0 requests/2.28.1 CPython/3.7.12 Linux/5.15.0-48-generic neon/20.04 glibc/2.31
                UID:GID : 1000:1000
             netrc file : None
           offline mode : False
```

</details>

<details>
<summary><code>conda config</code></summary>

```shell
==> /home/felix/.condarc <==
auto_activate_base: False
channels:
  - conda-forge
  - bioconda
  - defaults
```

</details>

<details>
<summary><code>conda list</code></summary>

```shell
# packages in environment at /home/felix/miniconda3:
#
# Name                    Version                   Build  Channel
_libgcc_mutex             0.1                 conda_forge    conda-forge
_openmp_mutex             4.5                       2_gnu    conda-forge
brotlipy                  0.7.0           py37h540881e_1004    conda-forge
bzip2                     1.0.8                h7f98852_4    conda-forge
c-ares                    1.18.1               h7f98852_0    conda-forge
ca-certificates           2022.9.24            ha878542_0    conda-forge
certifi                   2022.9.24          pyhd8ed1ab_0    conda-forge
cffi                      1.15.1           py37h43b0acd_0    conda-forge
charset-normalizer        2.1.1              pyhd8ed1ab_0    conda-forge
colorama                  0.4.5              pyhd8ed1ab_0    conda-forge
conda                     22.9.0           py37h89c1867_1    conda-forge
conda-package-handling    1.9.0            py37h540881e_0    conda-forge
cryptography              37.0.4           py37h38fbfac_0    conda-forge
icu                       70.1                 h27087fc_0    conda-forge
idna                      3.4                pyhd8ed1ab_0    conda-forge
keyutils                  1.6.1                h166bdaf_0    conda-forge
krb5                      1.19.3               h3790be6_0    conda-forge
ld_impl_linux-64          2.36.1               hea4e1c9_2    conda-forge
libarchive                3.5.2                hb890918_3    conda-forge
libcurl                   7.85.0               h7bff187_0    conda-forge
libedit                   3.1.20191231         he28a2e2_2    conda-forge
libev                     4.33                 h516909a_1    conda-forge
libffi                    3.4.2                h7f98852_5    conda-forge
libgcc-ng                 12.1.0              h8d9b700_16    conda-forge
libgomp                   12.1.0              h8d9b700_16    conda-forge
libiconv                  1.17                 h166bdaf_0    conda-forge
libmamba                  0.26.0               h0dd8ff0_0    conda-forge
libmambapy                0.26.0           py37h1ee4b26_0    conda-forge
libnghttp2                1.47.0               hdcd2b5c_1    conda-forge
libnsl                    2.0.0                h7f98852_0    conda-forge
libsolv                   0.7.22               h6239696_0    conda-forge
libsqlite                 3.39.4               h753d276_0    conda-forge
libssh2                   1.10.0               haa6b8db_3    conda-forge
libstdcxx-ng              12.1.0              ha89aaad_16    conda-forge
libxml2                   2.10.2               h4c7fe37_1    conda-forge
libzlib                   1.2.12               h166bdaf_4    conda-forge
lz4-c                     1.9.3                h9c3ff4c_1    conda-forge
lzo                       2.10              h516909a_1000    conda-forge
mamba                     0.26.0           py37h6dacc13_0    conda-forge
ncurses                   6.3                  h27087fc_1    conda-forge
openssl                   1.1.1q               h166bdaf_0    conda-forge
pip                       22.2.2             pyhd8ed1ab_0    conda-forge
pybind11-abi              4                    hd8ed1ab_3    conda-forge
pycosat                   0.6.3           py37h540881e_1010    conda-forge
pycparser                 2.21               pyhd8ed1ab_0    conda-forge
pyopenssl                 22.0.0             pyhd8ed1ab_1    conda-forge
pysocks                   1.7.1            py37h89c1867_5    conda-forge
python                    3.7.12          hb7a2778_100_cpython    conda-forge
python_abi                3.7                     2_cp37m    conda-forge
readline                  8.1.2                h0f457ee_0    conda-forge
reproc                    14.2.3               h7f98852_0    conda-forge
reproc-cpp                14.2.3               h9c3ff4c_0    conda-forge
requests                  2.28.1             pyhd8ed1ab_1    conda-forge
ruamel_yaml               0.15.80         py37h540881e_1007    conda-forge
setuptools                65.4.1             pyhd8ed1ab_0    conda-forge
sqlite                    3.39.4               h4ff8645_0    conda-forge
tk                        8.6.12               h27826a3_0    conda-forge
toolz                     0.12.0             pyhd8ed1ab_0    conda-forge
tqdm                      4.64.1             pyhd8ed1ab_0    conda-forge
urllib3                   1.26.11            pyhd8ed1ab_0    conda-forge
wheel                     0.37.1             pyhd8ed1ab_0    conda-forge
xz                        5.2.6                h166bdaf_0    conda-forge
yaml                      0.2.5                h7f98852_2    conda-forge
yaml-cpp                  0.7.0                h27087fc_2    conda-forge
zlib                      1.2.12               h166bdaf_4    conda-forge
zstd                      1.5.2                h6239696_4    conda-forge
```

</details>


Discussion:

Resolving patch:
diff --git a/conda_build/skeletons/cpan.py b/conda_build/skeletons/cpan.py
--- a/conda_build/skeletons/cpan.py
+++ b/conda_build/skeletons/cpan.py
@@ -205,7 +205,7 @@ def get_build_dependencies_from_src_archive(package_url, sha256, src_cache):
         need_f = any([f.name.lower().endswith(('.f', '.f90', '.f77', '.f95', '.f03')) for f in tf])
         # Fortran builds use CC to perform the link (they do not call the linker directly).
         need_c = True if need_f else \
-            any([f.name.lower().endswith('.c') for f in tf])
+            any([f.name.lower().endswith(('.c', '.xs')) for f in tf])
         need_cxx = any([f.name.lower().endswith(('.cxx', '.cpp', '.cc', '.c++'))
                         for f in tf])
         need_autotools = any([f.name.lower().endswith('/configure') for f in tf])


Test patch:
diff --git a/tests/test_api_skeleton_cpan.py b/tests/test_api_skeleton_cpan.py
new file mode 100644
--- /dev/null
+++ b/tests/test_api_skeleton_cpan.py
@@ -0,0 +1,23 @@
+# Copyright (C) 2014 Anaconda, Inc
+# SPDX-License-Identifier: BSD-3-Clause
+'''
+Integrative tests of the CPAN skeleton that start from
+conda_build.api.skeletonize and check the output files
+'''
+
+
+import pytest
+
+from conda_build import api
+from conda_build.jinja_context import compiler
+
+
+@pytest.mark.slow
+@pytest.mark.flaky(rerun=5, reruns_delay=2)
+def test_xs_needs_c_compiler(testing_config):
+    """Perl packages with XS files need a C compiler"""
+    # This uses Sub::Identify=0.14 since it includes no .c files but a .xs file.
+    api.skeletonize("Sub::Identify", version="0.14", repo="cpan", config=testing_config)
+    m = api.render("perl-sub-identify/0.14", finalize=False, bypass_env_check=True)[0][0]
+    build_requirements = m.get_value("requirements/build")
+    assert compiler("c", testing_config) in build_requirements