Passed
Push — master ( b25270...aeb165 )
by Dean
03:03
created

SystemHelper   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Test Coverage

Coverage 42.5%
Metric Value
dl 0
loc 210
ccs 51
cts 120
cp 0.425
rs 8.295
wmc 42

11 Methods

Rating   Name   Duplication   Size   Complexity  
A name() 0 11 2
A arm() 0 17 3
C arm_float_type() 0 23 7
B arm_version() 0 23 6
A cpu_name() 0 14 4
A cpu_type() 0 15 2
B architecture() 0 23 5
B elf_attributes() 0 36 6
A vcr_version() 0 17 2
A _find_elf_section() 0 7 3
A page_size() 0 13 2

How to fix   Complexity   

Complex Class

Complex classes like SystemHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1 1
from plugin.core.libraries.helpers.arm import ArmHelper
2
3 1
from elftools.elf.attributes import AttributesSection
4 1
from elftools.elf.elffile import ELFFile
5 1
import logging
6 1
import os
7 1
import platform
8 1
import sys
9
10 1
log = logging.getLogger(__name__)
11
12 1
BITS_MAP = {
13
    '32bit': 'i386',
14
    '64bit': 'x86_64'
15
}
16
17 1
MACHINE_MAP = {
18
    ('32bit', 'i686'): 'i686'
19
}
20
21 1
MSVCR_MAP = {
22
    'msvcr120.dll': 'vc12',
23
    'msvcr130.dll': 'vc14'
24
}
25
26 1
NAME_MAP = {
27
    'Darwin': 'MacOSX'
28
}
29
30 1
FALLBACK_EXECUTABLE = '/bin/ls'
31
32
33 1
class SystemHelper(object):
34 1
    @classmethod
35
    def architecture(cls):
36
        """Retrieve system architecture (i386, i686, x86_64)"""
37
38 1
        bits, _ = platform.architecture()
39 1
        machine = platform.machine()
40
41
        # Check for ARM machine
42 1
        if bits == '32bit' and machine.startswith('armv'):
43
            return cls.arm(machine)
44
45
        # Check (bits, machine) map
46 1
        machine_key = (bits, machine)
47
48 1
        if machine_key in MACHINE_MAP:
49
            return MACHINE_MAP[machine_key]
50
51
        # Check (bits) map
52 1
        if bits in BITS_MAP:
53 1
            return BITS_MAP[bits]
54
55
        log.error('Unable to determine system architecture - bits: %r, machine: %r', bits, machine)
56
        return None
57
58 1
    @classmethod
59
    def name(cls):
60
        """Retrieve system name (Windows, Linux, FreeBSD, MacOSX)"""
61
62 1
        system = platform.system()
63
64
        # Apply system map
65 1
        if system in NAME_MAP:
66
            system = NAME_MAP[system]
67
68 1
        return system
69
70 1
    @classmethod
71
    def arm(cls, machine):
72
        # Determine floating-point type
73
        float_type = cls.arm_float_type()
74
75
        if float_type is None:
76
            log.warn('Unable to use ARM libraries, unsupported floating-point type?')
77
            return None
78
79
        # Determine ARM version
80
        version = cls.arm_version()
81
82
        if version is None:
83
            log.warn('Unable to use ARM libraries, unsupported ARM version (%r)?' % machine)
84
            return None
85
86
        return '%s_%s' % (version, float_type)
87
88 1
    @classmethod
89 1
    def arm_version(cls, machine=None):
90
        # Read `machine` name if not provided
91
        if machine is None:
92
            machine = platform.machine()
93
94
        # Ensure `machine` is valid
95
        if not machine:
96
            return None
97
98
        # ARMv5
99
        if machine.startswith('armv5'):
100
            return 'armv5'
101
102
        # ARMv6
103
        if machine.startswith('armv6'):
104
            return 'armv6'
105
106
        # ARMv7
107
        if machine.startswith('armv7'):
108
            return 'armv7'
109
110
        return None
111
112 1
    @classmethod
113 1
    def arm_float_type(cls, executable_path=sys.executable):
114
        if os.path.exists('/lib/arm-linux-gnueabihf'):
115
            return 'hf'
116
117
        if os.path.exists('/lib/arm-linux-gnueabi'):
118
            return 'sf'
119
120
        # Determine system float-type from python executable
121
        section, attributes = cls.elf_attributes(executable_path)
122
123
        if not section or not attributes:
124
            return None
125
126
        if section.name != 'aeabi':
127
            log.warn('Unknown attributes section name: %r', section.name)
128
            return None
129
130
        # Assume hard-float if "tag_abi_vfp_args" is present
131
        if attributes.get('abi_vfp_args'):
132
            return 'hf'
133
134
        return 'sf'
135
136 1
    @classmethod
137 1
    def cpu_name(cls, executable_path=sys.executable):
138
        # Retrieve CPU name from ELF
139 1
        section, attributes = cls.elf_attributes(executable_path)
140
141 1
        if not section or not attributes:
142 1
            return None
143
144
        name = attributes.get('cpu_name')
145
146
        if not name:
147
            return None
148
149
        return name.lower()
150
151 1
    @classmethod
152 1
    def cpu_type(cls, executable_path=sys.executable):
153 1
        try:
154
            # Retrieve cpu type via "/proc/cpuinfo"
155 1
            _, _, cpu_type = ArmHelper.identifier()
156
157 1
            if cpu_type:
158
                # Valid cpu type found
159
                return cpu_type
160
161
        except Exception, ex:
162
            log.warn('Unable to retrieve cpu type from "/proc/cpuinfo": %s', ex, exc_info=True)
163
164
        # Fallback to using the ELF cpu name
165 1
        return cls.cpu_name(executable_path)
166
167 1
    @classmethod
168 1
    def elf_attributes(cls, executable_path=sys.executable):
169 1
        if cls.name() == 'MacOSX':
170
            log.info('Unable to retrieve ELF attributes on Mac OSX (not supported)')
171
            return None, None
172
173
        # Read attributes from "/bin/ls" if `executable_path` doesn't exist
174 1
        if not executable_path or not os.path.exists(executable_path):
175
            log.info('Executable at %r doesn\'t exist, using %r instead', executable_path, FALLBACK_EXECUTABLE)
176
            executable_path = FALLBACK_EXECUTABLE
177
178 1
        try:
179
            # Open executable stream
180 1
            stream = open(executable_path, 'rb')
181
182
            # Parse ELF
183 1
            elf = ELFFile(stream)
184
185
            # Find attributes section
186 1
            section = cls._find_elf_section(elf, AttributesSection)
187
188 1
            if section is None:
189 1
                log.info('Unable to find attributes section in ELF: %r', executable_path)
190 1
                return None, None
191
192
            # Build dictionary of attributes
193
            attributes = dict([
194
                (attr.tag.lower(), attr.value)
195
                for attr in section.iter_attributes()
196
            ])
197
198
            return section, attributes
199
        except Exception, ex:
200
            log.warn('Unable to retrieve attributes from ELF %r: %s', executable_path, ex, exc_info=True)
201
202
        return None, None
203
204 1
    @classmethod
205
    def page_size(cls):
206 1
        try:
207 1
            import resource
208 1
            page_size = resource.getpagesize()
209
210 1
            if not page_size:
211
                return None
212
213 1
            return '%dk' % (page_size / 1024)
214
        except Exception, ex:
215
            log.warn('Unable to retrieve memory page size: %s', ex, exc_info=True)
216
            return None
217
218 1
    @staticmethod
219
    def _find_elf_section(elf, cls):
220 1
        for section in elf.iter_sections():
221 1
            if isinstance(section, cls):
222
                return section
223
224 1
        return None
225
226 1
    @classmethod
227
    def vcr_version(cls):
228
        try:
229
            import ctypes.util
230
231
            # Retrieve linked msvcr dll
232
            name = ctypes.util.find_msvcrt()
233
234
            # Return VC++ version from map
235
            if name not in MSVCR_MAP:
236
                log.error('Unknown VC++ runtime: %r', name)
237
                return None
238
239
            return MSVCR_MAP[name]
240
        except Exception:
241
            log.error('Unable to retrieve VC++ runtime version', exc_info=True)
242
            return None
243