Repository: conan-io/conan

Issue:
uname, detect_windows_subsystem, is_windows are broken for MinGW, MSYS2 and Cygwin shells
for the start, uname (command) returns the following output:
- `CYGWIN_NT-10.0` in Cygwin shell
- `MINGW64_NT-10.0` in Git for Windows shell
- `MINGW32_NT-10.0` in MinGW shell
- `MSYS_NT-10.0` in MSYS2 shell
it's easy to check that `platform.system()` returns the same value as uname in the shells mentioned above.

conan's `uname` function has the following in its implementation:

```
        if platform.system() != "Windows":
            raise ConanException("Command only for Windows operating system")
```
obviously, it raises under shells mentioned above.
`detect_windows_subsystem` method uses `uname`:
```
        try:
            output = OSInfo.uname()
        except ConanException:
            return None
```
and `is_windows` is declared as the following:
```
self.is_windows = platform.system() == "Windows"
```
as result, mentioned methods don't work as expected in the mentioned shells.
for instance, I am getting the following error trying to invoke `uname`:
```
ERROR: libiconv/1.15@bincrafters/testing: Error in build() method, line 105
        print(tools.OSInfo.uname())
        ConanException: Command only for Windows operating system
```
or if I just use `detect_windows_subsystem`:
```
ERROR: libiconv/1.15@bincrafters/testing: Error in build() method, line 106
        raise Exception("This recipe needs a Windows Subsystem to be compiled. "
        Exception: This recipe needs a Windows Subsystem to be compiled. You can specify a build_require to: 'msys2_installer/latest@bincrafters/stable' or 'cygwin_installer/2.9.0@bincrafters/stable' or put in the PATH your own installation
```
which is not something I would expect in the MSYS2 shell.

To help us debug your issue please explain:

- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.




Discussion:
one more broken thing is `AutoToolsBuildEnvironment` helper (e.g. it sets `--build=x86_64-msys_nt-10.0`), but this goes to the separate bug

Resolving patch:
diff --git a/conans/client/conf/detect.py b/conans/client/conf/detect.py
--- a/conans/client/conf/detect.py
+++ b/conans/client/conf/detect.py
@@ -5,6 +5,7 @@
 
 from conans.client.output import Color
 from conans.client.tools.win import latest_visual_studio_version_installed
+from conans.client.tools import OSInfo
 from conans.model.version import Version
 
 
@@ -168,16 +169,11 @@ def _detect_compiler_version(result, output):
 
 
 def detected_os():
-    result = platform.system()
-    if result == "Darwin":
+    if OSInfo().is_macos:
         return "Macos"
-    if result.startswith("CYGWIN"):
+    if OSInfo().is_windows:
         return "Windows"
-    if result.startswith("MINGW32_NT") or result.startswith("MINGW64_NT"):
-        return "Windows"
-    if result.startswith("MSYS_NT"):
-        return "Windows"
-    return result
+    return platform.system()
 
 
 def _detect_os_arch(result, output):
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
@@ -85,24 +85,21 @@ class OSInfo(object):
     """
 
     def __init__(self):
+        system = platform.system()
         self.os_version = None
         self.os_version_name = None
-        self.is_linux = platform.system() == "Linux"
+        self.is_linux = system == "Linux"
         self.linux_distro = None
-        self.is_windows = platform.system() == "Windows"
-        self.is_macos = platform.system() == "Darwin"
-        self.is_freebsd = platform.system() == "FreeBSD"
-        self.is_solaris = platform.system() == "SunOS"
+        self.is_msys = system.startswith("MING") or system.startswith("MSYS_NT")
+        self.is_cygwin = system.startswith("CYGWIN_NT")
+        self.is_windows = system == "Windows" or self.is_msys or self.is_cygwin
+        self.is_macos = system == "Darwin"
+        self.is_freebsd = system == "FreeBSD"
+        self.is_solaris = system == "SunOS"
         self.is_posix = os.pathsep == ':'
 
         if self.is_linux:
-            import distro
-            self.linux_distro = distro.id()
-            self.os_version = Version(distro.version())
-            version_name = distro.codename()
-            self.os_version_name = version_name if version_name != "n/a" else ""
-            if not self.os_version_name and self.linux_distro == "debian":
-                self.os_version_name = self.get_debian_version_name(self.os_version)
+            self._get_linux_distro_info()
         elif self.is_windows:
             self.os_version = self.get_win_os_version()
             self.os_version_name = self.get_win_version_name(self.os_version)
@@ -116,6 +113,15 @@ def __init__(self):
             self.os_version = Version(platform.release())
             self.os_version_name = self.get_solaris_version_name(self.os_version)
 
+    def _get_linux_distro_info(self):
+        import distro
+        self.linux_distro = distro.id()
+        self.os_version = Version(distro.version())
+        version_name = distro.codename()
+        self.os_version_name = version_name if version_name != "n/a" else ""
+        if not self.os_version_name and self.linux_distro == "debian":
+            self.os_version_name = self.get_debian_version_name(self.os_version)
+
     @property
     def with_apt(self):
         return self.is_linux and self.linux_distro in \
@@ -143,7 +149,7 @@ def with_zypper(self):
         if "opensuse" in self.linux_distro or "sles" in self.linux_distro:
             return True
         return False
-    
+
     @staticmethod
     def get_win_os_version():
         """
@@ -264,7 +270,7 @@ def bash_path():
     @staticmethod
     def uname(options=None):
         options = " %s" % options if options else ""
-        if platform.system() != "Windows":
+        if not OSInfo().is_windows:
             raise ConanException("Command only for Windows operating system")
         custom_bash_path = OSInfo.bash_path()
         if not custom_bash_path:
@@ -305,7 +311,6 @@ def detect_windows_subsystem():
 
 
 def cross_building(settings, self_os=None, self_arch=None):
-
     ret = get_cross_building_settings(settings, self_os, self_arch)
     build_os, build_arch, host_os, host_arch = ret
 


Test patch:
diff --git a/conans/test/unittests/client/conf/detect/__init__.py b/conans/test/unittests/client/conf/detect/__init__.py
new file mode 100644
--- /dev/null
+++ b/conans/test/unittests/client/conf/detect/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/conans/test/unittests/client/conf/detect/detected_os_test.py b/conans/test/unittests/client/conf/detect/detected_os_test.py
new file mode 100644
--- /dev/null
+++ b/conans/test/unittests/client/conf/detect/detected_os_test.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+
+import unittest
+from mock import mock
+from conans.client.conf.detect import detected_os
+from conans.client.tools import OSInfo
+
+
+class DetectedOSTest(unittest.TestCase):
+    def test_windows(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Windows')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_cygwin(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='CYGWIN_NT-10.0')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_msys(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MSYS_NT-10.0')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_mingw32(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MINGW32_NT-10.0')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_mingw64(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MINGW64_NT-10.0')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertEqual(detected_os(), "Windows")
+
+    def test_darwin(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Darwin')):
+            self.assertEqual(detected_os(), "Macos")
+
+    def test_linux(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Linux')):
+            with mock.patch.object(OSInfo, '_get_linux_distro_info'):
+                self.assertEqual(detected_os(), "Linux")
+
+    def test_freebsd(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='FreeBSD')):
+            self.assertEqual(detected_os(), "FreeBSD")
+
+    def test_solaris(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='SunOS')):
+            self.assertEqual(detected_os(), "SunOS")
diff --git a/conans/test/unittests/client/tools/os_info/__init__.py b/conans/test/unittests/client/tools/os_info/__init__.py
new file mode 100644
--- /dev/null
+++ b/conans/test/unittests/client/tools/os_info/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/conans/test/unittests/client/tools/os_info/osinfo_test.py b/conans/test/unittests/client/tools/os_info/osinfo_test.py
new file mode 100644
--- /dev/null
+++ b/conans/test/unittests/client/tools/os_info/osinfo_test.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+
+import unittest
+import platform
+import os
+from conans.client.tools import OSInfo, environment_append, CYGWIN, MSYS2, MSYS, remove_from_path
+from conans.errors import ConanException
+from mock import mock
+from nose.plugins.attrib import attr
+
+
+class OSInfoTest(unittest.TestCase):
+
+    def setUp(self):
+        self._uname = None
+        self._version = None
+
+    def subprocess_check_output_mock(self, cmd, shell):
+        if cmd.endswith('"uname"'):
+            return self._uname.encode()
+        elif cmd.endswith('"uname -or"'):
+            return self._version.encode()
+        raise ValueError("don't know how to respond to %s" % cmd)
+
+    def test_windows(self):
+        self._uname = None
+        self._version = None
+        with mock.patch("platform.system", mock.MagicMock(return_value='Windows')):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    with remove_from_path("uname"):
+                        with remove_from_path("bash"):
+                            # FIXME: bug in environment_append removing variables which aren't exist (another PR)
+                            new_env = dict()
+                            if 'CONAN_BASH_PATH' in os.environ:
+                                new_env['CONAN_BASH_PATH'] = None
+                            with environment_append(new_env):
+                                self.assertTrue(OSInfo().is_windows)
+                                self.assertFalse(OSInfo().is_cygwin)
+                                self.assertFalse(OSInfo().is_msys)
+                                self.assertFalse(OSInfo().is_linux)
+                                self.assertFalse(OSInfo().is_freebsd)
+                                self.assertFalse(OSInfo().is_macos)
+                                self.assertFalse(OSInfo().is_solaris)
+
+                                with self.assertRaises(ConanException):
+                                    OSInfo.uname()
+                                self.assertIsNone(OSInfo.detect_windows_subsystem())
+
+    def test_cygwin(self):
+        self._uname = 'CYGWIN_NT-10.0'
+        self._version = '2.11.2(0.329/5/3)'
+        with mock.patch("platform.system", mock.MagicMock(return_value=self._uname)):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertTrue(OSInfo().is_windows)
+                    self.assertTrue(OSInfo().is_cygwin)
+                    self.assertFalse(OSInfo().is_msys)
+                    self.assertFalse(OSInfo().is_linux)
+                    self.assertFalse(OSInfo().is_freebsd)
+                    self.assertFalse(OSInfo().is_macos)
+                    self.assertFalse(OSInfo().is_solaris)
+
+                    with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
+                        with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                            self.assertEqual(OSInfo.uname(), self._uname.lower())
+                            self.assertEqual(OSInfo.detect_windows_subsystem(), CYGWIN)
+
+    def test_msys2(self):
+        self._uname = 'MSYS_NT-10.0'
+        self._version = '1.0.18(0.48/3/2)'
+        with mock.patch("platform.system", mock.MagicMock(return_value=self._uname)):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertTrue(OSInfo().is_windows)
+                    self.assertFalse(OSInfo().is_cygwin)
+                    self.assertTrue(OSInfo().is_msys)
+                    self.assertFalse(OSInfo().is_linux)
+                    self.assertFalse(OSInfo().is_freebsd)
+                    self.assertFalse(OSInfo().is_macos)
+                    self.assertFalse(OSInfo().is_solaris)
+
+                    with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
+                        with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                            self.assertEqual(OSInfo.uname(), self._uname.lower())
+                            self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS)
+
+    def test_mingw32(self):
+        self._uname = 'MINGW32_NT-10.0'
+        self._version = '2.10.0(0.325/5/3)'
+        with mock.patch("platform.system", mock.MagicMock(return_value=self._uname)):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertTrue(OSInfo().is_windows)
+                    self.assertFalse(OSInfo().is_cygwin)
+                    self.assertTrue(OSInfo().is_msys)
+                    self.assertFalse(OSInfo().is_linux)
+                    self.assertFalse(OSInfo().is_freebsd)
+                    self.assertFalse(OSInfo().is_macos)
+                    self.assertFalse(OSInfo().is_solaris)
+
+                    with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
+                        with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                            self.assertEqual(OSInfo.uname(), self._uname.lower())
+                            self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS2)
+
+    def test_mingw64(self):
+        self._uname = 'MINGW64_NT-10.0'
+        self._version = '2.4.0(0.292/5/3)'
+        with mock.patch("platform.system", mock.MagicMock(return_value=self._uname)):
+            with mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"):
+                with mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+                    self.assertTrue(OSInfo().is_windows)
+                    self.assertFalse(OSInfo().is_cygwin)
+                    self.assertTrue(OSInfo().is_msys)
+                    self.assertFalse(OSInfo().is_linux)
+                    self.assertFalse(OSInfo().is_freebsd)
+                    self.assertFalse(OSInfo().is_macos)
+                    self.assertFalse(OSInfo().is_solaris)
+
+                    with environment_append({"CONAN_BASH_PATH": "/fake/bash.exe"}):
+                        with mock.patch('subprocess.check_output', new=self.subprocess_check_output_mock):
+                            self.assertEqual(OSInfo.uname(), self._uname.lower())
+                            self.assertEqual(OSInfo.detect_windows_subsystem(), MSYS2)
+
+    def test_linux(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Linux')):
+            with mock.patch.object(OSInfo, '_get_linux_distro_info'):
+                self.assertFalse(OSInfo().is_windows)
+                self.assertFalse(OSInfo().is_cygwin)
+                self.assertFalse(OSInfo().is_msys)
+                self.assertTrue(OSInfo().is_linux)
+                self.assertFalse(OSInfo().is_freebsd)
+                self.assertFalse(OSInfo().is_macos)
+                self.assertFalse(OSInfo().is_solaris)
+
+                with self.assertRaises(ConanException):
+                    OSInfo.uname()
+                self.assertIsNone(OSInfo.detect_windows_subsystem())
+
+    def test_macos(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Darwin')):
+            self.assertFalse(OSInfo().is_windows)
+            self.assertFalse(OSInfo().is_cygwin)
+            self.assertFalse(OSInfo().is_msys)
+            self.assertFalse(OSInfo().is_linux)
+            self.assertFalse(OSInfo().is_freebsd)
+            self.assertTrue(OSInfo().is_macos)
+            self.assertFalse(OSInfo().is_solaris)
+
+            with self.assertRaises(ConanException):
+                OSInfo.uname()
+            self.assertIsNone(OSInfo.detect_windows_subsystem())
+
+    def test_freebsd(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='FreeBSD')):
+            self.assertFalse(OSInfo().is_windows)
+            self.assertFalse(OSInfo().is_cygwin)
+            self.assertFalse(OSInfo().is_msys)
+            self.assertFalse(OSInfo().is_linux)
+            self.assertTrue(OSInfo().is_freebsd)
+            self.assertFalse(OSInfo().is_macos)
+            self.assertFalse(OSInfo().is_solaris)
+
+            with self.assertRaises(ConanException):
+                OSInfo.uname()
+            self.assertIsNone(OSInfo.detect_windows_subsystem())
+
+    def test_solaris(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='SunOS')):
+            self.assertFalse(OSInfo().is_windows)
+            self.assertFalse(OSInfo().is_cygwin)
+            self.assertFalse(OSInfo().is_msys)
+            self.assertFalse(OSInfo().is_linux)
+            self.assertFalse(OSInfo().is_freebsd)
+            self.assertFalse(OSInfo().is_macos)
+            self.assertTrue(OSInfo().is_solaris)
+
+            with self.assertRaises(ConanException):
+                OSInfo.uname()
+            self.assertIsNone(OSInfo.detect_windows_subsystem())