1# Copyright (c) 2009, Willow Garage, Inc.
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7#     * Redistributions of source code must retain the above copyright
8#       notice, this list of conditions and the following disclaimer.
9#     * Redistributions in binary form must reproduce the above copyright
10#       notice, this list of conditions and the following disclaimer in the
11#       documentation and/or other materials provided with the distribution.
12#     * Neither the name of the Willow Garage, Inc. nor the names of its
13#       contributors may be used to endorse or promote products derived from
14#       this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26# POSSIBILITY OF SUCH DAMAGE.
27
28# Author Tully Foote/tfoote@willowgarage.com, Ken Conley/kwc@willowgarage.com
29
30"""
31Library for detecting the current OS, including detecting specific
32Linux distributions.
33"""
34
35from __future__ import print_function
36
37import codecs
38import locale
39import os
40import platform
41import subprocess
42
43
44def _read_stdout(cmd):
45    try:
46        pop = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
47        (std_out, std_err) = pop.communicate()
48        # Python 2.6 compatibility
49        if isinstance(std_out, str):
50            return std_out.strip()
51        return std_out.decode(encoding='UTF-8').strip()
52    except:
53        return None
54
55
56def uname_get_machine():
57    """
58    Linux: wrapper around uname to determine if OS is 64-bit
59    """
60    return _read_stdout(['uname', '-m'])
61
62
63def read_issue(filename="/etc/issue"):
64    """
65    :returns: list of strings in issue file, or None if issue file cannot be read/split
66    """
67    if os.path.exists(filename):
68        with codecs.open(filename, 'r', encoding=locale.getpreferredencoding()) as f:
69            return f.read().split()
70    return None
71
72
73def read_os_release(filename=None):
74    """
75    :returns: Dictionary of key value pairs from /etc/os-release or fallback to
76      /usr/lib/os-release, with quotes stripped from values
77    """
78    if filename is None:
79        filename = '/etc/os-release'
80        if not os.path.exists(filename):
81            filename = '/usr/lib/os-release'
82
83    if not os.path.exists(filename):
84        return None
85
86    release_info = {}
87    with codecs.open(filename, 'r', encoding=locale.getpreferredencoding()) as f:
88        for line in f:
89            key, val = line.rstrip('\n').partition('=')[::2]
90            release_info[key] = val.strip('"')
91    return release_info
92
93
94class OsNotDetected(Exception):
95    """
96    Exception to indicate failure to detect operating system.
97    """
98    pass
99
100
101class OsDetector(object):
102    """
103    Generic API for detecting a specific OS.
104    """
105    def is_os(self):
106        """
107        :returns: if the specific OS which this class is designed to
108          detect is present.  Only one version of this class should
109          return for any version.
110        """
111        raise NotImplementedError("is_os unimplemented")
112
113    def get_version(self):
114        """
115        :returns: standardized version for this OS. (aka Ubuntu Hardy Heron = "8.04")
116        :raises: :exc:`OsNotDetected` if called on incorrect OS.
117        """
118        raise NotImplementedError("get_version unimplemented")
119
120    def get_codename(self):
121        """
122        :returns: codename for this OS. (aka Ubuntu Hardy Heron = "hardy").  If codenames are not available for this OS, return empty string.
123        :raises: :exc:`OsNotDetected` if called on incorrect OS.
124        """
125        raise NotImplementedError("get_codename unimplemented")
126
127
128class LsbDetect(OsDetector):
129    """
130    Generic detector for Debian, Ubuntu, and Mint
131    """
132    def __init__(self, lsb_name, get_version_fn=None):
133        self.lsb_name = lsb_name
134        if hasattr(platform, "linux_distribution"):
135            self.lsb_info = platform.linux_distribution(full_distribution_name=0)
136        elif hasattr(platform, "dist"):
137            self.lsb_info = platform.dist()
138        else:
139            self.lsb_info = None
140
141    def is_os(self):
142        return self.lsb_info is not None and self.lsb_info[0] == self.lsb_name
143
144    def get_version(self):
145        if self.is_os():
146            return self.lsb_info[1]
147        raise OsNotDetected('called in incorrect OS')
148
149    def get_codename(self):
150        if self.is_os():
151            return self.lsb_info[2]
152        raise OsNotDetected('called in incorrect OS')
153
154
155class Debian(LsbDetect):
156
157    def __init__(self, get_version_fn=None):
158        super(Debian, self).__init__('debian', get_version_fn)
159
160    def get_codename(self):
161        if self.is_os():
162            v = self.get_version()
163            if v.startswith('7.'):
164                return 'wheezy'
165            if v.startswith('8.'):
166                return 'jessie'
167            if v.startswith('9.'):
168                return 'stretch'
169            if v.startswith('10.'):
170                return 'buster'
171            return ''
172
173
174class FdoDetect(OsDetector):
175    """
176    Generic detector for operating systems implementing /etc/os-release, as defined by the os-release spec hosted at Freedesktop.org (Fdo):
177    http://www.freedesktop.org/software/systemd/man/os-release.html
178    Requires that the "ID", and "VERSION_ID" keys are set in the os-release file.
179
180    Codename is parsed from the VERSION key if available: either using the format "foo, CODENAME" or "foo (CODENAME)."
181    If the VERSION key is not present, the VERSION_ID is value is used as the codename.
182    """
183    def __init__(self, fdo_id):
184        release_info = read_os_release()
185        if release_info is not None and "ID" in release_info and release_info["ID"] == fdo_id:
186            self.release_info = release_info
187        else:
188            self.release_info = None
189
190    def is_os(self):
191        return self.release_info is not None and "VERSION_ID" in self.release_info
192
193    def get_version(self):
194        if self.is_os():
195            return self.release_info["VERSION_ID"]
196        raise OsNotDetected("called in incorrect OS")
197
198    def get_codename(self):
199        if self.is_os():
200            if "VERSION" in self.release_info:
201                version = self.release_info["VERSION"]
202                # FDO style: works with Fedora, Debian, Suse.
203                if version.find("(") is not -1:
204                    codename = version[version.find("(") + 1:version.find(")")]
205                # Ubuntu style
206                elif version.find(",") is not -1:
207                    codename = version[version.find(",") + 1:].lstrip(' ').split()[0]
208                # Indeterminate style
209                else:
210                    codename = version
211                return codename.lower()
212            else:
213                return self.get_version()
214        raise OsNotDetected("called in incorrect OS")
215
216
217class OpenEmbedded(OsDetector):
218    """
219    Detect OpenEmbedded.
220    """
221    def is_os(self):
222        return "ROS_OS_OVERRIDE" in os.environ and os.environ["ROS_OS_OVERRIDE"] == "openembedded"
223
224    def get_version(self):
225        if self.is_os():
226            return ""
227        raise OsNotDetected('called in incorrect OS')
228
229    def get_codename(self):
230        if self.is_os():
231            return ""
232        raise OsNotDetected('called in incorrect OS')
233
234
235class OpenSuse(OsDetector):
236    """
237    Detect OpenSuse OS.
238    """
239    def __init__(self, brand_file="/etc/SuSE-brand", release_file="/etc/SuSE-release"):
240        self._brand_file = brand_file
241        self._release_file = release_file
242
243    def is_os(self):
244        os_list = read_issue(self._brand_file)
245        return os_list and os_list[0] == "openSUSE"
246
247    def get_version(self):
248        if self.is_os() and os.path.exists(self._brand_file):
249            with open(self._brand_file, 'r') as fh:
250                os_list = fh.read().strip().split('\n')
251                if len(os_list) == 2:
252                    os_list = os_list[1].split(' = ')
253                    if os_list[0] == "VERSION":
254                        return os_list[1]
255        raise OsNotDetected('cannot get version on this OS')
256
257    def get_codename(self):
258        # /etc/SuSE-release is deprecated since 13.1
259        if self._release_file is None:
260            return ""
261        if self.is_os() and os.path.exists(self._release_file):
262            with open(self._release_file, 'r') as fh:
263                os_list = fh.read().strip().split('\n')
264                for line in os_list:
265                    kv = line.split(' = ')
266                    if kv[0] == "CODENAME":
267                        return kv[1]
268        raise OsNotDetected('called in incorrect OS')
269
270
271class Fedora(OsDetector):
272    """
273    Detect Fedora OS.
274    """
275    def __init__(self, release_file="/etc/redhat-release", issue_file="/etc/issue"):
276        self._release_file = release_file
277        self._issue_file = issue_file
278
279    def is_os(self):
280        os_list = read_issue(self._release_file)
281        return os_list and os_list[0] == "Fedora"
282
283    def get_version(self):
284        if self.is_os():
285            os_list = read_issue(self._issue_file)
286            idx = os_list.index('release')
287            if idx > 0:
288                return os_list[idx + 1]
289        raise OsNotDetected('cannot get version on this OS')
290
291    def get_codename(self):
292        if self.is_os():
293            os_list = read_issue(self._release_file)
294            idx = os_list.index('release')
295            matches = [x for x in os_list if x[0] == '(']
296            codename = matches[0][1:]
297            if codename[-1] == ')':
298                codename = codename[:-1]
299            return codename.lower()
300        raise OsNotDetected('called in incorrect OS')
301
302
303class Rhel(Fedora):
304    """
305    Detect Redhat OS.
306    """
307    def __init__(self, release_file="/etc/redhat-release"):
308        self._release_file = release_file
309
310    def is_os(self):
311        os_list = read_issue(self._release_file)
312        return os_list and os_list[:3] == ['Red', 'Hat', 'Enterprise']
313
314    def get_version(self):
315        if self.is_os():
316            os_list = read_issue(self._release_file)
317            idx = os_list.index('release')
318            return os_list[idx + 1]
319        raise OsNotDetected('called in incorrect OS')
320
321    def get_codename(self):
322        # taroon, nahant, tikanga, santiago, pensacola
323        if self.is_os():
324            os_list = read_issue(self._release_file)
325            idx = os_list.index('release')
326            matches = [x for x in os_list if x[0] == '(']
327            codename = matches[0][1:]
328            if codename[-1] == ')':
329                codename = codename[:-1]
330            return codename.lower()
331        raise OsNotDetected('called in incorrect OS')
332
333
334# Source: https://en.wikipedia.org/wiki/MacOS#Versions
335_osx_codename_map = {
336    4: 'tiger',
337    5: 'leopard',
338    6: 'snow',
339    7: 'lion',
340    8: 'mountain lion',
341    9: 'mavericks',
342    10: 'yosemite',
343    11: 'el capitan',
344    12: 'sierra',
345    13: 'high sierra',
346    14: 'mojave',
347    15: 'catalina',
348}
349
350
351def _osx_codename(major, minor):
352    if major != 10 or minor not in _osx_codename_map:
353        raise OsNotDetected("unrecognized version: %s.%s" % (major, minor))
354    return _osx_codename_map[minor]
355
356
357class OSX(OsDetector):
358    """
359    Detect OS X
360    """
361    def __init__(self, sw_vers_file="/usr/bin/sw_vers"):
362        self._sw_vers_file = sw_vers_file
363
364    def is_os(self):
365        return os.path.exists(self._sw_vers_file)
366
367    def get_codename(self):
368        if self.is_os():
369            version = self.get_version()
370            import distutils.version  # To parse version numbers
371            try:
372                ver = distutils.version.StrictVersion(version).version
373            except ValueError:
374                raise OsNotDetected("invalid version string: %s" % (version))
375            return _osx_codename(*ver[0:2])
376        raise OsNotDetected('called in incorrect OS')
377
378    def get_version(self):
379        if self.is_os():
380            return _read_stdout([self._sw_vers_file, '-productVersion'])
381        raise OsNotDetected('called in incorrect OS')
382
383
384class QNX(OsDetector):
385    '''
386    Detect QNX realtime OS.
387    @author: Isaac Saito
388    '''
389    def __init__(self, uname_file='/bin/uname'):
390        '''
391        @param uname_file: An executable that can be used for detecting
392                           OS name and version.
393        '''
394        self._os_name_qnx = 'QNX'
395        self._uname_file = uname_file
396
397    def is_os(self):
398        if os.path.exists(self._uname_file):
399            std_out = _read_stdout([self._uname_file])
400            return std_out.strip() == self._os_name_qnx
401        else:
402            return False
403
404    def get_codename(self):
405        if self.is_os():
406            return ''
407        raise OsNotDetected('called in incorrect OS')
408
409    def get_version(self):
410        if self.is_os() and os.path.exists(self._uname_file):
411            return _read_stdout([self._uname_file, "-r"])
412        raise OsNotDetected('called in incorrect OS')
413
414
415class Arch(OsDetector):
416    """
417    Detect Arch Linux.
418    """
419    def __init__(self, release_file='/etc/arch-release'):
420        self._release_file = release_file
421
422    def is_os(self):
423        return os.path.exists(self._release_file)
424
425    def get_version(self):
426        if self.is_os():
427            return ""
428        raise OsNotDetected('called in incorrect OS')
429
430    def get_codename(self):
431        if self.is_os():
432            return ""
433        raise OsNotDetected('called in incorrect OS')
434
435
436class Manjaro(Arch):
437    """
438    Detect Manjaro.
439    """
440    def __init__(self, release_file='/etc/manjaro-release'):
441        super(Manjaro, self).__init__(release_file)
442
443
444class Centos(OsDetector):
445    """
446    Detect CentOS.
447    """
448    def __init__(self, release_file='/etc/redhat-release'):
449        self._release_file = release_file
450
451    def is_os(self):
452        os_list = read_issue(self._release_file)
453        return os_list and os_list[0] == 'CentOS'
454
455    def get_version(self):
456        if self.is_os():
457            os_list = read_issue(self._release_file)
458            idx = os_list.index('release')
459            return os_list[idx + 1]
460        raise OsNotDetected('called in incorrect OS')
461
462    def get_codename(self):
463        if self.is_os():
464            os_list = read_issue(self._release_file)
465            idx = os_list.index('release')
466            matches = [x for x in os_list if x[0] == '(']
467            codename = matches[0][1:]
468            if codename[-1] == ')':
469                codename = codename[:-1]
470            return codename.lower()
471        raise OsNotDetected('called in incorrect OS')
472
473
474class Cygwin(OsDetector):
475    """
476    Detect Cygwin presence on Windows OS.
477    """
478    def is_os(self):
479        return os.path.exists("/usr/bin/cygwin1.dll")
480
481    def get_version(self):
482        if self.is_os():
483            return _read_stdout(['uname', '-r'])
484        raise OsNotDetected('called in incorrect OS')
485
486    def get_codename(self):
487        if self.is_os():
488            return ''
489        raise OsNotDetected('called in incorrect OS')
490
491
492class Gentoo(OsDetector):
493    """
494    Detect Gentoo OS.
495    """
496    def __init__(self, release_file="/etc/gentoo-release"):
497        self._release_file = release_file
498
499    def is_os(self):
500        os_list = read_issue(self._release_file)
501        return os_list and os_list[0] == "Gentoo" and os_list[1] == "Base"
502
503    def get_version(self):
504        if self.is_os():
505            os_list = read_issue(self._release_file)
506            return os_list[4]
507        raise OsNotDetected('called in incorrect OS')
508
509    def get_codename(self):
510        if self.is_os():
511            return ''
512        raise OsNotDetected('called in incorrect OS')
513
514
515class Funtoo(Gentoo):
516    """
517    Detect Funtoo OS, a Gentoo Variant.
518    """
519    def __init__(self, release_file="/etc/gentoo-release"):
520        Gentoo.__init__(self, release_file)
521
522    def is_os(self):
523        os_list = read_issue(self._release_file)
524        return os_list and os_list[0] == "Funtoo" and os_list[1] == "Linux"
525
526
527class FreeBSD(OsDetector):
528    """
529    Detect FreeBSD OS.
530    """
531    def __init__(self, uname_file="/usr/bin/uname"):
532        self._uname_file = uname_file
533
534    def is_os(self):
535        if os.path.exists(self._uname_file):
536            std_out = _read_stdout([self._uname_file])
537            return std_out.strip() == "FreeBSD"
538        else:
539            return False
540
541    def get_version(self):
542        if self.is_os() and os.path.exists(self._uname_file):
543            return _read_stdout([self._uname_file, "-r"])
544        raise OsNotDetected('called in incorrect OS')
545
546    def get_codename(self):
547        if self.is_os():
548            return ''
549        raise OsNotDetected('called in incorrect OS')
550
551
552class Slackware(OsDetector):
553    """
554    Detect SlackWare Linux.
555    """
556    def __init__(self, release_file='/etc/slackware-version'):
557        self._release_file = release_file
558
559    def is_os(self):
560        return os.path.exists(self._release_file)
561
562    def get_version(self):
563        if self.is_os():
564            os_list = read_issue(self._release_file)
565            return os_list[1]
566        raise OsNotDetected('called in incorrect OS')
567
568    def get_codename(self):
569        if self.is_os():
570            return ''
571        raise OsNotDetected('called in incorrect OS')
572
573
574class Windows(OsDetector):
575    """
576    Detect Windows OS.
577    """
578    def is_os(self):
579        return platform.system() == "Windows"
580
581    def get_version(self):
582        if self.is_os():
583            return platform.version()
584        raise OsNotDetected('called in incorrect OS')
585
586    def get_codename(self):
587        if self.is_os():
588            return platform.release()
589        raise OsNotDetected('called in incorrect OS')
590
591
592class OsDetect:
593    """
594    This class will iterate over registered classes to lookup the
595    active OS and version
596    """
597
598    default_os_list = []
599
600    def __init__(self, os_list=None):
601        if os_list is None:
602            os_list = OsDetect.default_os_list
603        self._os_list = os_list
604        self._os_name = None
605        self._os_version = None
606        self._os_codename = None
607        self._os_detector = None
608        self._override = False
609
610    @staticmethod
611    def register_default(os_name, os_detector):
612        """
613        Register detector to be used with all future instances of
614        :class:`OsDetect`.  The new detector will have precedence over
615        any previously registered detectors associated with *os_name*.
616
617        :param os_name: OS key associated with OS detector
618        :param os_detector: :class:`OsDetector` instance
619        """
620        OsDetect.default_os_list.insert(0, (os_name, os_detector))
621
622    def detect_os(self, env=None):
623        """
624        Detect operating system.  Return value can be overridden by
625        the :env:`ROS_OS_OVERRIDE` environment variable.
626
627        :param env: override ``os.environ``
628        :returns: (os_name, os_version, os_codename), ``(str, str, str)``
629        :raises: :exc:`OsNotDetected` if OS could not be detected
630        """
631        if env is None:
632            env = os.environ
633        if 'ROS_OS_OVERRIDE' in env:
634            splits = env["ROS_OS_OVERRIDE"].split(':')
635            self._os_name = splits[0]
636            if len(splits) > 1:
637                self._os_version = splits[1]
638                if len(splits) > 2:
639                    self._os_codename = splits[2]
640                else:
641                    self._os_codename = ''
642            else:
643                self._os_version = self._os_codename = ''
644            self._override = True
645        else:
646            for os_name, os_detector in self._os_list:
647                if os_detector.is_os():
648                    self._os_name = os_name
649                    self._os_version = os_detector.get_version()
650                    self._os_codename = os_detector.get_codename()
651                    self._os_detector = os_detector
652                    break
653
654        if self._os_name:
655            return self._os_name, self._os_version, self._os_codename
656        else:  # No solution found
657            attempted = [x[0] for x in self._os_list]
658            raise OsNotDetected("Could not detect OS, tried %s" % attempted)
659
660    def get_detector(self, name=None):
661        """
662        Get detector used for specified OS name, or the detector for this OS if name is ``None``.
663
664        :raises: :exc:`KeyError`
665        """
666        if name is None:
667            if not self._os_detector:
668                self.detect_os()
669            return self._os_detector
670        else:
671            try:
672                return [d for d_name, d in self._os_list if d_name == name][0]
673            except IndexError:
674                raise KeyError(name)
675
676    def add_detector(self, name, detector):
677        """
678        Add detector to list of detectors used by this instance.  *detector* will override any previous
679        detectors associated with *name*.
680
681        :param name: OS name that detector matches
682        :param detector: :class:`OsDetector` instance
683        """
684        self._os_list.insert(0, (name, detector))
685
686    def get_name(self):
687        if not self._os_name:
688            self.detect_os()
689        return self._os_name
690
691    def get_version(self):
692        if not self._os_version:
693            self.detect_os()
694        return self._os_version
695
696    def get_codename(self):
697        if not self._os_codename:
698            self.detect_os()
699        return self._os_codename
700
701
702OS_ALPINE = 'alpine'
703OS_ARCH = 'arch'
704OS_MANJARO = 'manjaro'
705OS_CENTOS = 'centos'
706OS_CYGWIN = 'cygwin'
707OS_DEBIAN = 'debian'
708OS_ELEMENTARY = 'elementary'
709OS_ELEMENTARY_OLD = 'elementary'
710OS_FEDORA = 'fedora'
711OS_FREEBSD = 'freebsd'
712OS_FUNTOO = 'funtoo'
713OS_GENTOO = 'gentoo'
714OS_LINARO = 'linaro'
715OS_MINT = 'mint'
716OS_MX = 'mx'
717OS_NEON = 'neon'
718OS_OPENEMBEDDED = 'openembedded'
719OS_OPENSUSE = 'opensuse'
720OS_OPENSUSE13 = 'opensuse'
721OS_TIZEN = 'tizen'
722OS_OSX = 'osx'
723OS_QNX = 'qnx'
724OS_RHEL = 'rhel'
725OS_SLACKWARE = 'slackware'
726OS_UBUNTU = 'ubuntu'
727OS_CLEARLINUX = 'clearlinux'
728OS_NIXOS = 'nixos'
729OS_WINDOWS = 'windows'
730OS_ZORIN =  'zorin'
731
732OsDetect.register_default(OS_ALPINE, FdoDetect("alpine"))
733OsDetect.register_default(OS_ARCH, Arch())
734OsDetect.register_default(OS_MANJARO, Manjaro())
735OsDetect.register_default(OS_CENTOS, Centos())
736OsDetect.register_default(OS_CYGWIN, Cygwin())
737OsDetect.register_default(OS_DEBIAN, Debian())
738OsDetect.register_default(OS_ELEMENTARY, LsbDetect("elementary"))
739OsDetect.register_default(OS_ELEMENTARY_OLD, LsbDetect("elementary OS"))
740OsDetect.register_default(OS_FEDORA, FdoDetect("fedora"))
741OsDetect.register_default(OS_FREEBSD, FreeBSD())
742OsDetect.register_default(OS_FUNTOO, Funtoo())
743OsDetect.register_default(OS_GENTOO, Gentoo())
744OsDetect.register_default(OS_LINARO, LsbDetect("Linaro"))
745OsDetect.register_default(OS_MINT, LsbDetect("LinuxMint"))
746OsDetect.register_default(OS_MX, LsbDetect("MX"))
747OsDetect.register_default(OS_NEON, LsbDetect("neon"))
748OsDetect.register_default(OS_OPENEMBEDDED, OpenEmbedded())
749OsDetect.register_default(OS_OPENSUSE, OpenSuse())
750OsDetect.register_default(OS_OPENSUSE13, OpenSuse(brand_file='/etc/SUSE-brand', release_file=None))
751OsDetect.register_default(OS_OPENSUSE, FdoDetect("opensuse-tumbleweed"))
752OsDetect.register_default(OS_OPENSUSE, FdoDetect("opensuse"))
753OsDetect.register_default(OS_TIZEN, FdoDetect("tizen"))
754OsDetect.register_default(OS_OSX, OSX())
755OsDetect.register_default(OS_QNX, QNX())
756OsDetect.register_default(OS_RHEL, Rhel())
757OsDetect.register_default(OS_SLACKWARE, Slackware())
758OsDetect.register_default(OS_UBUNTU, LsbDetect("Ubuntu"))
759OsDetect.register_default(OS_CLEARLINUX, FdoDetect("clear-linux-os"))
760OsDetect.register_default(OS_NIXOS, FdoDetect("nixos"))
761OsDetect.register_default(OS_WINDOWS, Windows())
762OsDetect.register_default(OS_ZORIN, LsbDetect("Zorin"))
763
764
765if __name__ == '__main__':
766    detect = OsDetect()
767    print("OS Name:     %s" % detect.get_name())
768    print("OS Version:  %s" % detect.get_version())
769    print("OS Codename: %s" % detect.get_codename())
770