Repository: conan-io/conan

Issue:
[feature] Conan should recognize elementaryOS as a distro that uses apt
elementaryOS is a Linux distro based on Ubuntu. So, it uses `apt`. However, Conan, via the `conans` module, disagrees and says that it does not use `apt` when calling `tools.os_info.with_apt`.

```
Python 3.7.4 (default, Oct  9 2019, 00:16:19) 
[GCC 7.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from conans import tools
>>> tools.os_info.with_apt
False
```

Letting Conan know that elementaryOS uses `apt` is important since recipes that have to know whether or not the distro they are running in uses `apt` may have to resort to doing something less than ideal when ran under elementaryOS. For example, using `opengl/system` in a project under elementaryOS will fail (just like [this](https://github.com/bincrafters/community/issues/1216#issuecomment-653540067) in bincrafters/community#1216) because the recipe, as [it is currently is](https://github.com/conan-io/conan-center-index/blob/97d32ad939ce0acebe681201ef95896736ce1946/recipes/opengl/all/conanfile.py#L60) (and supported by conan-io/conan-center-index#2114), fails hard when being used in an unrecognized distro.

As such, Conan should be updated to recognize elementaryOS as a distro that uses `apt`. Based on my quick skim of the `conans` module code, it seems that a change in [`client/tools/oss.py:181`](https://github.com/conan-io/conan/blob/d40f2a9f644c390bfaf202a573d7fb999a30949f/conans/client/tools/oss.py#L181) to:

```
# ...
        apt_distros = ("debian", "ubuntu", "knoppix", "linuxmint", "raspbian", "neon", "pop", "elementary")
# ...
````

should do the trick.

Note: `import distro; distro.id()` returns `'elementary'`.

- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).



Discussion:
Hi @seanballais 

Thanks for submitting this issue. Yes, we recently added ``pop`` there, so I see no problem to add there ``elementary`` too. Would you like to send a PR yourself, as you have already investigated where to add it?
Hey, @memsharded. Sure! I'll send a PR as soon as I can.
okay, maybe we can nail everything down at once, as there are many debian and ubuntu based distributions: https://en.wikipedia.org/wiki/List_of_Linux_distributions#Debian-based
https://distrowatch.com/search.php?basedon=Debian#simple
https://distrowatch.com/search.php?basedon=Ubuntu%20(LTS)#simple

Resolving patch:
diff --git a/conans/client/tools/oss.py b/conans/client/tools/oss.py
--- a/conans/client/tools/oss.py
+++ b/conans/client/tools/oss.py
@@ -9,7 +9,7 @@
 
 from conans.client.tools.env import environment_append
 from conans.client.tools.files import load, which
-from conans.errors import ConanException
+from conans.errors import CalledProcessErrorWithStderr, ConanException
 from conans.model.version import Version
 from conans.util.runners import check_output_runner
 
@@ -178,8 +178,21 @@ def _get_linux_distro_info(self):
 
     @property
     def with_apt(self):
-        apt_distros = ("debian", "ubuntu", "knoppix", "linuxmint", "raspbian", "neon", "pop")
-        return self.is_linux and self.linux_distro in apt_distros
+        if not self.is_linux:
+            return False
+
+        apt_location = which('apt-get')
+        if apt_location:
+            # Check if we actually have the official apt package.
+            try:
+                output = check_output_runner([apt_location, 'moo'])
+            except CalledProcessErrorWithStderr:
+                return False
+            else:
+                # Yes, we have mooed today. :-) MOOOOOOOO.
+                return True
+        else:
+            return False
 
     @property
     def with_yum(self):


Test patch:
diff --git a/conans/test/unittests/client/tools/system_pm_test.py b/conans/test/unittests/client/tools/system_pm_test.py
--- a/conans/test/unittests/client/tools/system_pm_test.py
+++ b/conans/test/unittests/client/tools/system_pm_test.py
@@ -56,20 +56,25 @@ def verify_update_test(self):
             self.assertIn('Not updating system_requirements. CONAN_SYSREQUIRES_MODE=verify',
                           self.out)
 
-    def add_repositories_exception_cases_test(self):
+    # We gotta mock the with_apt property, since it checks for the existence of apt.
+    @mock.patch('conans.client.tools.oss.OSInfo.with_apt', new_callable=mock.PropertyMock)
+    def add_repositories_exception_cases_test(self, patched_with_apt):
         os_info = OSInfo()
         os_info.is_macos = False
         os_info.is_linux = True
         os_info.is_windows = False
         os_info.linux_distro = "fedora"  # Will instantiate DnfTool
 
+        patched_with_apt.return_value = False
+
         with six.assertRaisesRegex(self, ConanException, "add_repository not implemented"):
             new_out = StringIO()
             spt = SystemPackageTool(os_info=os_info, output=ConanOutput(new_out))
             spt.add_repository(repository="deb http://repo/url/ saucy universe multiverse",
                                repo_key=None)
 
-    def add_repository_test(self):
+    @mock.patch('conans.client.tools.oss.OSInfo.with_apt', new_callable=mock.PropertyMock)
+    def add_repository_test(self, patched_with_apt):
         class RunnerOrderedMock:
             commands = []  # Command + return value
 
@@ -99,6 +104,8 @@ def _run_add_repository_test(repository, gpg_key, sudo, isatty, update):
                 os_info.is_linux = True
                 os_info.is_windows = False
                 os_info.linux_distro = "debian"
+                patched_with_apt.return_value = True
+
                 new_out = StringIO()
                 spt = SystemPackageTool(runner=runner, os_info=os_info, output=ConanOutput(new_out))
 
@@ -118,7 +125,9 @@ def _run_add_repository_test(repository, gpg_key, sudo, isatty, update):
         with mock.patch("sys.stdout.isatty", return_value=True):
             _run_add_repository_test(repository, gpg_key, sudo=True, isatty=True, update=True)
 
-    def system_package_tool_test(self):
+    # We gotta mock the with_apt property, since it checks for the existence of apt.
+    @mock.patch('conans.client.tools.oss.OSInfo.with_apt', new_callable=mock.PropertyMock)
+    def system_package_tool_test(self, patched_with_apt):
 
         with tools.environment_append({"CONAN_SYSREQUIRES_SUDO": "True"}):
             runner = RunnerMock()
@@ -127,6 +136,8 @@ def system_package_tool_test(self):
             os_info.is_macos = False
             os_info.is_linux = True
             os_info.is_windows = False
+            patched_with_apt.return_value = True
+
             os_info.linux_distro = "debian"
 
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
@@ -148,6 +159,9 @@ def system_package_tool_test(self):
             spt.update()
             self.assertEqual(runner.command_called, "sudo -A apt-get update")
 
+            # We'll be testing non-Ubuntu and non-Debian-based distros.
+            patched_with_apt.return_value = False
+
             with mock.patch("conans.client.tools.oss.which", return_value=True):
                 os_info.linux_distro = "fedora"
                 spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
@@ -183,6 +197,7 @@ def system_package_tool_test(self):
             self.assertEqual(runner.command_called, "sudo -A yum install -y a_package.i?86")
 
             os_info.linux_distro = "debian"
+            patched_with_apt.return_value = True
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
             with self.assertRaises(ConanException):
                 runner.return_ok = False
@@ -198,6 +213,7 @@ def system_package_tool_test(self):
             os_info.is_macos = True
             os_info.is_linux = False
             os_info.is_windows = False
+            patched_with_apt.return_value = False
 
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
             spt.update()
@@ -207,6 +223,7 @@ def system_package_tool_test(self):
 
             os_info.is_freebsd = True
             os_info.is_macos = False
+            patched_with_apt.return_value = False
 
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
             spt.update()
@@ -220,6 +237,7 @@ def system_package_tool_test(self):
             if platform.system() == "Windows" and which("choco.exe"):
                 os_info.is_freebsd = False
                 os_info.is_windows = True
+                patched_with_apt.return_value = False
                 spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out,
                                         tool=ChocolateyTool(output=self.out))
                 spt.update()
@@ -236,6 +254,8 @@ def system_package_tool_test(self):
             os_info = OSInfo()
             os_info.is_linux = True
             os_info.linux_distro = "redhat"
+            patched_with_apt.return_value = False
+
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
             spt.install("a_package", force=True)
             self.assertEqual(runner.command_called, "yum install -y a_package")
@@ -243,6 +263,7 @@ def system_package_tool_test(self):
             self.assertEqual(runner.command_called, "yum check-update -y")
 
             os_info.linux_distro = "ubuntu"
+            patched_with_apt.return_value = True
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
             spt.install("a_package", force=True)
             self.assertEqual(runner.command_called,
@@ -280,6 +301,7 @@ def system_package_tool_test(self):
             os_info.is_macos = True
             os_info.is_linux = False
             os_info.is_windows = False
+            patched_with_apt.return_value = False
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
 
             spt.update()
@@ -290,6 +312,7 @@ def system_package_tool_test(self):
             os_info.is_freebsd = True
             os_info.is_macos = False
             os_info.is_windows = False
+            patched_with_apt.return_value = False
 
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
             spt.update()
@@ -302,6 +325,7 @@ def system_package_tool_test(self):
             os_info.is_solaris = True
             os_info.is_freebsd = False
             os_info.is_windows = False
+            patched_with_apt.return_value = False
 
             spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out)
             spt.update()
@@ -315,6 +339,7 @@ def system_package_tool_test(self):
             if platform.system() == "Windows" and which("choco.exe"):
                 os_info.is_solaris = False
                 os_info.is_windows = True
+                patched_with_apt.return_value = False
 
                 spt = SystemPackageTool(runner=runner, os_info=os_info, output=self.out,
                                         tool=ChocolateyTool(output=self.out))