Repository: conan-io/conan-center-index

Issue:
[package] nodejs/18.15.0: glibc version check might fail on some linux distributions
### Description

In case of linux ``nodejs`` conan recipe checks the version of glibc on the build machine. The version of the installed glibc is figured out via the first line of the output of ``ldd --version`` command. 

This line may vary among the various linux distibutions. To name some of them:

| distro | version| output |
| ---- | ---- | ---- | 
| gentoo | head | ldd (Gentoo 2.34-r13 p17) 2.34 |
| ubuntu | 18.04.6 | ldd (Ubuntu GLIBC 2.27-3ubuntu1.6) 2.27 |
| debian | 11 | ldd (Debian GLIBC 2.31-13+deb11u6) 2.31|
| fedora | 276 | ldd (GNU libc) 2.26 |

The current regular expression checks literal ``GLIBC`` then the major and minor part of the version number **inside the parentheses**, however as we can see from the table above ``GLIBC`` is not always present.

It might be more roboust to check the version number **at the end of the line.**


### Package and Environment Details

* Package Name/Version: **nodejs/18.15.0**
* Operating System+version: **Linux gentoo 28 (head)**
* Compiler+version: **GCC 7.5.0**
* Docker image: **N/A**
* Conan version: **conan 1.60.0**
* Python version: **Python 3.9.13**


### Conan profile

[settings]
os=Linux
os_build=Linux
arch=x86_64
arch_build=x86_64
compiler=gcc
compiler.version=7
compiler.libcxx=libstdc++11
build_type=Release
[options]
[conf]
[build_requires]
[env]


### Steps to reproduce

conan create recipes/nodejs/all nodejs/18.15.0 -pr default

### Logs

<details><summary>Click to expand log</summary>

```
# ...
[HOOK - conan-center.py] post_export(): [CONANDATA.YML REDUCE (KB-H031)] OK
[HOOK - conan-center.py] post_export(): [DEFAULT SHARED OPTION VALUE (KB-H050)] OK
[HOOK - conan-center.py] post_export(): [LICENSE PUBLIC DOMAIN (KB-H056)] OK
nodejs/18.15.0: A new conanfile.py version was exported
nodejs/18.15.0: Folder: /home/nbukuli/.conan/data/nodejs/18.15.0/_/_/export
nodejs/18.15.0: Using the exported files summary hash as the recipe revision: 461f35fe0fe2215b362cf52f4684c775 
nodejs/18.15.0: Package recipe modified in export, forcing source folder removal
nodejs/18.15.0: Use the --keep-source, -k option to skip it
nodejs/18.15.0: Removing the local binary packages from different recipe revisions
nodejs/18.15.0: Exported revision: 461f35fe0fe2215b362cf52f4684c775
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=7
os=Linux
os_build=Linux
[options]
[build_requires]
[env]

ERROR: nodejs/18.15.0: Error in validate() method, line 51
        if Version(self._glibc_version) < '2.27':
while calling '_glibc_version', line 41
        return str(re.search(r'GLIBC (\d{1,3}.\d{1,3})', buff.getvalue()).group(1))
        AttributeError: 'NoneType' object has no attribute 'group'
```

</details>



Discussion:
In addition to the distros mentioned in the description, the current nodejs package also fails on OpenSuse.
ldd --version output on OpenSuse 15.5 is:
```
ldd (GNU libc) 2.31
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
```

Resolving patch:
diff --git a/recipes/nodejs/all/conanfile.py b/recipes/nodejs/all/conanfile.py
--- a/recipes/nodejs/all/conanfile.py
+++ b/recipes/nodejs/all/conanfile.py
@@ -1,33 +1,35 @@
 import os
-import re
-from six import StringIO
-from conan import ConanFile, conan_version
-from conan.tools.scm import Version
-from conan.tools.files import copy, get
+
+from conan import ConanFile
 from conan.errors import ConanInvalidConfiguration
+from conan.tools.files import copy, get
 
 required_conan_version = ">=1.59.0"
 
 
 class NodejsConan(ConanFile):
     name = "nodejs"
-    description = "nodejs binaries for use in recipes"
-    topics = ("node", "javascript", "runtime")
+    description = "Node.js is an open-source, cross-platform JavaScript runtime environment."
+    license = "MIT"
     url = "https://github.com/conan-io/conan-center-index"
     homepage = "https://nodejs.org"
-    license = "MIT"
+    topics = ("node", "javascript", "runtime", "pre-built")
+
     package_type = "application"
-    settings = "os", "arch", "compiler"
+    settings = "os", "arch", "compiler", "build_type"
     no_copy_source = True
     short_paths = True
 
-    @property
-    def _source_subfolder(self):
-        return os.path.join(self.source_folder, "source_subfolder")
+    def layout(self):
+        pass
+
+    def package_id(self):
+        del self.info.settings.compiler
+        del self.info.settings.build_type
 
     @property
     def _nodejs_arch(self):
-        if str(self.settings.os) == "Linux":
+        if str(self.settings.os) in ["Linux", "FreeBSD"]:
             if str(self.settings.arch).startswith("armv7"):
                 return "armv7"
             if str(self.settings.arch).startswith("armv8") and "32" not in str(self.settings.arch):
@@ -35,40 +37,30 @@ def _nodejs_arch(self):
         return str(self.settings.arch)
 
     @property
-    def _glibc_version(self):
-        cmd = ['ldd', '--version'] if conan_version.major == "1" else ['ldd --version']
-        buff = StringIO()
-        self.run(cmd, buff)
-        return str(re.search(r'GLIBC (\d{1,3}.\d{1,3})', buff.getvalue()).group(1))
-
-    def package_id(self):
-        del self.info.settings.compiler
+    def _dl_info(self):
+        return self.conan_data["sources"].get(self.version, {}).get(str(self.settings.os), {}).get(self._nodejs_arch)
 
     def validate(self):
-        if not self.version in self.conan_data["sources"] or \
-           not str(self.settings.os) in self.conan_data["sources"][self.version] or \
-           not self._nodejs_arch in self.conan_data["sources"][self.version][str(self.settings.os)]:
+        if not self._dl_info:
             raise ConanInvalidConfiguration("Binaries for this combination of architecture/version/os not available")
 
-        if Version(self.version) >= "18.0.0":
-            if str(self.settings.os) == "Linux":
-                if Version(self._glibc_version) < '2.27':
-                    raise ConanInvalidConfiguration("Binaries for this combination of architecture/version/os not available")
-
     def build(self):
-        get(self, **self.conan_data["sources"][self.version][str(self.settings.os)][self._nodejs_arch],
-            destination=self._source_subfolder, strip_root=True)
+        get(self, **self._dl_info, strip_root=True)
 
     def package(self):
-        copy(self, pattern="LICENSE", dst=os.path.join(self.package_folder, "licenses"), src=self._source_subfolder)
-        copy(self, pattern="*", dst=os.path.join(self.package_folder, "bin"), src=os.path.join(self._source_subfolder, "bin"))
-        copy(self, pattern="*", dst=os.path.join(self.package_folder, "lib"), src=os.path.join(self._source_subfolder, "lib"))
-        copy(self, pattern="node.exe", dst=os.path.join(self.package_folder, "bin"), src=self._source_subfolder)
-        copy(self, pattern="npm", dst=os.path.join(self.package_folder, "bin"), src=self._source_subfolder)
-        copy(self, pattern="npx", dst=os.path.join(self.package_folder, "bin"), src=self._source_subfolder)
+        copy(self, "LICENSE", dst=os.path.join(self.package_folder, "licenses"), src=self.build_folder)
+        copy(self, "*", dst=os.path.join(self.package_folder, "bin"), src=os.path.join(self.build_folder, "bin"))
+        copy(self, "*", dst=os.path.join(self.package_folder, "lib"), src=os.path.join(self.build_folder, "lib"))
+        copy(self, "node.exe", dst=os.path.join(self.package_folder, "bin"), src=self.build_folder)
+        copy(self, "npm", dst=os.path.join(self.package_folder, "bin"), src=self.build_folder)
+        copy(self, "npx", dst=os.path.join(self.package_folder, "bin"), src=self.build_folder)
 
     def package_info(self):
         self.cpp_info.includedirs = []
+        self.cpp_info.frameworkdirs = []
+        self.cpp_info.libdirs = []
+        self.cpp_info.resdirs = []
+
+        # TODO: Legacy, to be removed on Conan 2.0
         bin_dir = os.path.join(self.package_folder, "bin")
-        self.output.info('Appending PATH environment variable: {}'.format(bin_dir))
         self.env_info.PATH.append(bin_dir)


Test patch:
diff --git a/recipes/nodejs/all/test_package/conanfile.py b/recipes/nodejs/all/test_package/conanfile.py
--- a/recipes/nodejs/all/test_package/conanfile.py
+++ b/recipes/nodejs/all/test_package/conanfile.py
@@ -1,16 +1,27 @@
+import platform
+
 from conan import ConanFile
 from conan.tools.build import can_run
+from conan.tools.cmake import cmake_layout
+from conan.tools.scm import Version
 
 
 class TestPackageConan(ConanFile):
-
-    settings = "os", "arch"
+    settings = "os", "arch", "compiler", "build_type"
+    generators = "VirtualRunEnv"
     test_type = "explicit"
 
+    def layout(self):
+        cmake_layout(self)
+
     def build_requirements(self):
         self.tool_requires(self.tested_reference_str)
 
     def test(self):
         if can_run(self):
-            self.output.info("Node version:")
+            if self.settings.os in ["Linux", "FreeBSD"]:
+                libc_version = Version(platform.libc_ver()[1])
+                if libc_version < "2.29":
+                    self.output.warning(f"System libc version {libc_version} < 2.29, skipping test_package")
+                    return
             self.run("node --version")
diff --git a/recipes/nodejs/all/test_v1_package/conanfile.py b/recipes/nodejs/all/test_v1_package/conanfile.py
--- a/recipes/nodejs/all/test_v1_package/conanfile.py
+++ b/recipes/nodejs/all/test_v1_package/conanfile.py
@@ -1,5 +1,8 @@
+import platform
+
 from conan import ConanFile
 from conan.tools.build import cross_building
+from conans.model.version import Version
 
 
 class TestPackageConan(ConanFile):
@@ -7,10 +10,15 @@ class TestPackageConan(ConanFile):
     settings = "os", "arch"
     test_type = "explicit"
 
-    def requirements(self):
-        self.requires(self.tested_reference_str)
+    def build_requirements(self):
+        self.build_requires(self.tested_reference_str)
 
     def test(self):
         if not cross_building(self):
+            if self.settings.os in ["Linux", "FreeBSD"]:
+                libc_version = Version(platform.libc_ver()[1])
+                if libc_version < "2.29":
+                    self.output.warning(f"System libc version {libc_version} < 2.29, skipping test_package")
+                    return
             self.output.info("Node version:")
             self.run("node --version")