-
Notifications
You must be signed in to change notification settings - Fork 6
/
athdir.py
264 lines (241 loc) · 9.74 KB
/
athdir.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
"""
A Python re-implementation of Athena's libathdir
"""
import os
import subprocess
import sys
import logging
logger = logging.getLogger('athdir')
def _machtype(arg=None):
"""
Convenience function to run _machtype to get the canonical
values of -C and -S in the event the environment doesn't
have them. Returns None or the output. Per Debian policy,
it will first attempt to run machtype in PATH, then explicitly.
"""
rv = None
for machtype in ['machtype', '/bin/machtype']:
cmd = [machtype]
if arg is not None:
cmd.append(arg)
try:
rv = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0].strip()
return rv
except OSError as e:
logger.info(e)
return rv
class AthdirError(Exception):
pass
class AthdirInternalError(AthdirError):
pass
class Flavors():
"""
An enum-esque thing representing the various "flavors" of
directories we can request.
"""
ARCH = 1
SYS = 2
MACH = 3
PLAIN = 4
printableFlags = { 'A': ARCH,
'M': MACH,
'S': SYS,
'P': PLAIN
}
class AthdirConvention():
"""
The various types of "conventions" we have for paths, such
as arch-dependent, old-style sysnames and machnames, etc.
"""
def __init__(self, template, custom=False):
self.template = template
self.atsys = '%s' in template
self.custom = custom
self.flavors = { Flavors.ARCH: 'arch' in template,
Flavors.MACH: '%m' in template,
Flavors.SYS: self.atsys and not 'arch' in template}
self.flavors[Flavors.PLAIN] = True not in self.flavors.values()
def dependencyMatch(self, dependent=False):
"""
Return True if the convention's dependency matches the
dependency specified. (e.g. .dependencyMatch(True) would
return True if the convention is arch dependent.)
"""
return True if self.custom else (dependent != self.flavors[Flavors.PLAIN])
def acceptableFor(self, listOfAcceptable):
"""
Return True if this convention is accepted for one or more of
the list of flavors provided.
"""
if self.custom:
return True
for a in listOfAcceptable:
if self.flavors[a]:
return True
return False
def __repr__(self):
flags = { 'A': Flavors.ARCH,
'M': Flavors.MACH,
'S': Flavors.SYS,
'P': Flavors.PLAIN
}
return "AthdirConvention: %s (%s%s%s)" % (self.template, ''.join([k for k,v in Flavors.printableFlags.items() if self.flavors[v]]), '@' if self.atsys else '', 'C' if self.custom else '')
class Athdir():
_indepTypes = ("man", "include")
# We are NOT adding any new ones below
_sysFlavors = ("bin", "lib", "etc")
_machFlavors = ("bin", "lib", "etc")
_conventions = (AthdirConvention("%p/arch/%s/%t"),
AthdirConvention("%p/%s/%t"),
AthdirConvention("%p/%m%t"),
AthdirConvention("%p/%t"))
def __init__(self, basePath='%p', dirType='%t', customTemplate=None,
sysName=None, hostType=None):
self.path = basePath
self.dirType = dirType
self.compatlist = [sysName if sysName is not None else self.sysname()] + self.syscompatlist()
self.hostType = hostType if hostType is not None else self.hosttype()
# for unknown types, assume arch dependent
self.archDependent = not dirType in self._indepTypes
# We always try the arch flavors
self.flavorsAcceptable = [ Flavors.ARCH ]
if dirType in self._sysFlavors:
logger.debug("Adding 'SYS' flavor")
self.flavorsAcceptable.append(Flavors.SYS)
if dirType in self._machFlavors:
logger.debug("Adding 'MACH' flavor")
self.flavorsAcceptable.append(Flavors.MACH)
if ((Flavors.SYS not in self.flavorsAcceptable) and
(Flavors.MACH not in self.flavorsAcceptable)):
logger.debug("Adding 'PLAIN' flavor")
self.flavorsAcceptable.append(Flavors.PLAIN)
self.conventions = list(self._conventions)
if customTemplate is not None:
logger.debug("Adding custom template: %s", customTemplate)
self.conventions.insert(0, AthdirConvention(customTemplate,
custom=True))
def __repr__(self):
return "Athdir: %s (%s, %s)" % (self.path, self.dirType,
''.join([k for k,v in Flavors.printableFlags.items() if v in self.flavorsAcceptable]))
def __str__(self):
return "Athdir: path=%s, type=%s, flags=%s, host=%s\n compat=%s" % (self.path, self.dirType, ''.join([k for k,v in Flavors.printableFlags.items() if v in self.flavorsAcceptable]), self.hostType, self.compatlist)
def _expand(self, template, sys):
"""
Expand a template by filling in substitutions.
"""
rv = template
replacements = { '%s': sys,
'%m': self.hostType,
'%p': self.path,
'%t': self.dirType }
for k,v in replacements.items():
rv = rv.replace(k, v)
return rv
def get_paths(self, suppressEditorials=False, suppressSearch=False,
forceDependent=False, forceIndependent=False,
listAll=False):
"""
Get a list of acceptable paths for an athdir specification.
Parameters:
- suppressEditorals: If True, consider all possible conventions,
not just the "correct" ones.
- suppressSearch: If True, return the first appropriate path,
even if it doesn't exist.
- forceDependent: Used with suppressSearch. If True, force an
arch-dependent path, even for things that are usually
arch-independent.
- forceIndependent: Used with suppressSearch. If True, force an
arch-independent path, even for things that are usually
arch-dependent.
- listAll: If True, list all possible paths, expanding as
appropriate.
"""
if forceDependent and forceIndependent:
raise AthdirError("forceDependent and forceIndependent are mutually exclusive.")
if (forceDependent or forceIndependent) and not suppressSearch:
raise AthdirError("forceDependent and forceIndependent are meaningless without suppressSearch.")
rv = []
if forceDependent:
logger.debug("Forcing architecture-dependent")
self.archDependent = True
if forceIndependent:
logger.debug("Forcing architecture-independent")
self.archDependent = False
for c in self.conventions:
logger.debug("** Considering %s", c)
if (c.acceptableFor(self.flavorsAcceptable) or
suppressEditorials or
((forceDependent or forceIndependent) and suppressSearch)):
if suppressSearch and not c.dependencyMatch(self.archDependent):
logger.debug("discarding %s", c)
continue
for compat in self.compatlist:
logger.debug("Considering sysname %s", compat)
path = self._expand(c.template, compat)
logger.debug("Expanding to %s", path)
if listAll or suppressSearch:
rv.append(path)
logger.debug("Storing %s", path)
if suppressSearch:
logger.debug("Returning...")
return rv
elif os.path.exists(path):
logger.debug("Path %s exists, returning...", path)
rv.append(path)
return rv
if not c.atsys:
logger.debug("Skipping sysname iteration")
break
return rv
@staticmethod
def sysname():
"""
Attempt to determine the sysname
"""
sysname = os.getenv("ATHENA_SYS")
if sysname is not None:
return sysname
logger.info("ATHENA_SYS is unset")
sysname = _machtype('-S')
if sysname is not None:
return sysname
else:
raise AthdirInternalError("Unable to determine sysname.")
@staticmethod
def syscompatlist():
"""
Attempt to determine the sysname compat list
"""
compat = os.getenv("ATHENA_SYS_COMPAT")
if compat is not None:
return compat.split(':')
logger.info("ATHENA_SYS_COMPAT is unset")
compat = _machtype('-C')
if compat is not None:
return compat.split(':')
else:
raise AthdirInternalError("Unable to determine sysname compatibility list.")
@staticmethod
def hosttype():
"""
Attempt to determine the machname (host type)
"""
hosttype = os.getenv("HOSTTYPE")
if hosttype is not None:
return hosttype
logger.info("HOSTTYPE is unset")
hosttype = _machtype()
if hosttype is not None:
return hosttype
else:
raise AthdirInternalError("Unable to determine host type.")
@classmethod
def is_native(cls, somePath, sysn=None):
"""
Determine if somePath is the native (i.e. not using
compatibility) sysname for the specified sysname (or current
platform)
"""
if sysn is None:
sysn = cls.sysname()
return sysn in somePath