1#     Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com
2#
3#     Part of "Nuitka", an optimizing Python compiler that is compatible and
4#     integrates with CPython, but also works on its own.
5#
6#     Licensed under the Apache License, Version 2.0 (the "License");
7#     you may not use this file except in compliance with the License.
8#     You may obtain a copy of the License at
9#
10#        http://www.apache.org/licenses/LICENSE-2.0
11#
12#     Unless required by applicable law or agreed to in writing, software
13#     distributed under the License is distributed on an "AS IS" BASIS,
14#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#     See the License for the specific language governing permissions and
16#     limitations under the License.
17#
18""" Utility module.
19
20Here the small things that fit nowhere else and don't deserve their own module.
21
22"""
23
24import os
25import platform
26import sys
27
28from nuitka.PythonVersions import python_version
29
30
31def getOS():
32    if os.name == "nt":
33        return "Windows"
34    elif os.name == "posix":
35        result = os.uname()[0]
36
37        # Handle msys2 posix nature still meaning it's Windows.
38        if result.startswith(("MSYS_NT-", "MINGW64_NT-")):
39            result = "Windows"
40
41        return result
42    else:
43        assert False, os.name
44
45
46_linux_distribution = None
47
48
49def getLinuxDistribution():
50    """Name of the Linux distribution.
51
52    We should usually avoid this, and rather test for the feature,
53    but in some cases it's hard to manage that.
54    """
55    # singleton, pylint: disable=global-statement
56    global _linux_distribution
57
58    if getOS() != "Linux":
59        return None
60
61    if _linux_distribution is None:
62        # pylint: disable=I0021,deprecated-method,no-member
63        try:
64            result = platform.dist()[0].title()
65        except AttributeError:
66            from .Execution import check_output
67
68            try:
69                result = check_output(["lsb_release", "-i", "-s"], shell=False).title()
70
71                if str is not bytes:
72                    result = result.decode("utf8")
73            except FileNotFoundError:
74                from .FileOperations import getFileContentByLine
75
76                for line in getFileContentByLine("/etc/os-release"):
77                    if line.startswith("ID="):
78                        result = line[3:]
79                        break
80                else:
81                    from nuitka.Tracing import general
82
83                    general.sysexit("Error, cannot detect Linux distribution.")
84
85        _linux_distribution = result.title()
86
87    return _linux_distribution
88
89
90def isDebianBasedLinux():
91    # TODO: What is with Mint, maybe others, this list should be expanded potentially.
92    return getLinuxDistribution() in ("Debian", "Ubuntu")
93
94
95def isWin32Windows():
96    """The Win32 variants of Python does have win32 only, not posix."""
97    return os.name == "nt"
98
99
100def isPosixWindows():
101    """The MSYS2 variant of Python does have posix only, not Win32."""
102    return os.name == "posix" and getOS() == "Windows"
103
104
105_is_alpine = None
106
107
108def isAlpineLinux():
109    if os.name == "posix":
110
111        # Avoid repeated file system lookup, pylint: disable=global-statement
112        global _is_alpine
113        if _is_alpine is None:
114            _is_alpine = os.path.isfile("/etc/alpine-release")
115
116        return _is_alpine
117    else:
118        return False
119
120
121def getArchitecture():
122    if getOS() == "Windows":
123        if "AMD64" in sys.version:
124            return "x86_64"
125        else:
126            return "x86"
127    else:
128        return os.uname()[4]
129
130
131def getCoreCount():
132    cpu_count = 0
133
134    # Try to sum up the CPU cores, if the kernel shows them.
135    try:
136        # Try to get the number of logical processors
137        with open("/proc/cpuinfo") as cpuinfo_file:
138            cpu_count = cpuinfo_file.read().count("processor\t:")
139    except IOError:
140        pass
141
142    if not cpu_count:
143        import multiprocessing
144
145        cpu_count = multiprocessing.cpu_count()
146
147    return cpu_count
148
149
150def encodeNonAscii(var_name):
151    """Encode variable name that is potentially not ASCII to ASCII only.
152
153    For Python3, unicode identifiers can be used, but these are not
154    possible in C, so we need to replace them.
155    """
156    if python_version < 0x300:
157        return var_name
158    else:
159        # Using a escaping here, because that makes it safe in terms of not
160        # to occur in the encoding escape sequence for unicode use.
161        var_name = var_name.replace("$$", "$_$")
162
163        var_name = var_name.encode("ascii", "xmlcharrefreplace")
164        var_name = var_name.decode("ascii")
165
166        return var_name.replace("&#", "$$").replace(";", "")
167
168
169def hasOnefileSupportedOS():
170    return getOS() in ("Linux", "Windows", "Darwin", "FreeBSD")
171
172
173def getUserName():
174    """Return the user name.
175
176    Notes: Currently doesn't work on Windows.
177    """
178    import pwd  # pylint: disable=I0021,import-error
179
180    return pwd.getpwuid(os.getuid())[0]
181