1 |
commit: efc87322e3016a5178feb579b468ff44d8cc0b04 |
2 |
Author: Andre Erdmann <dywi <AT> mailerd <DOT> de> |
3 |
AuthorDate: Wed May 30 14:54:28 2012 +0000 |
4 |
Commit: André Erdmann <dywi <AT> mailerd <DOT> de> |
5 |
CommitDate: Wed May 30 14:54:28 2012 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=efc87322 |
7 |
|
8 |
roverlay, ebuildjob: status code and dep resolve logic |
9 |
modified: ebuildjob.py |
10 |
|
11 |
--- |
12 |
roverlay/ebuildjob.py | 260 ++++++++++++++++++++++++++++++++++--------------- |
13 |
1 files changed, 181 insertions(+), 79 deletions(-) |
14 |
|
15 |
diff --git a/roverlay/ebuildjob.py b/roverlay/ebuildjob.py |
16 |
index 171150c..d45dfdf 100644 |
17 |
--- a/roverlay/ebuildjob.py |
18 |
+++ b/roverlay/ebuildjob.py |
19 |
@@ -6,11 +6,42 @@ from roverlay.fileio import DescriptionReader |
20 |
from roverlay.ebuild import Ebuild |
21 |
|
22 |
class EbuildJob: |
23 |
- STATUS_LIST = [ 'INIT', 'BUSY', 'WAIT', 'SUCCESS', 'FAIL' ] |
24 |
- STATUS_MAP = dict ( ( name, code ) for code, name in enumerate ( STATUS_LIST ) ) |
25 |
+ # move this to const / config |
26 |
+ DEPENDENCY_FIELDS = { |
27 |
+ 'R_SUGGESTS' : [ 'Suggests' ], |
28 |
+ 'DEPENDS' : ['Depends', 'Imports' ], |
29 |
+ 'RDEPENDS' : [ 'LinkingTo', 'SystemRequirements' ] |
30 |
+ } |
31 |
+ |
32 |
+ ## |
33 |
+ |
34 |
+ |
35 |
+ STATUS_LIST = [ 'INIT', 'BUSY', 'WAIT_RESOLVE', 'SUCCESS', 'FAIL' ] |
36 |
+ |
37 |
+ # status 'jump' control |
38 |
+ # FAIL is always allowed, S -> S has to be explicitly allowed |
39 |
+ STATUS_BRANCHMAP = dict ( |
40 |
+ INIT = [ 'BUSY' ], |
41 |
+ BUSY = [ 'BUSY', 'WAIT_RESOLVE', 'SUCCESS' ], |
42 |
+ WAIT_RESOLVE = [ 'BUSY' ], |
43 |
+ SUCCESS = [], |
44 |
+ FAIL = [], |
45 |
+ ) |
46 |
|
47 |
@classmethod |
48 |
def __init__ ( self, package_file, dep_resolver=None ): |
49 |
+ """Initializes an EbuildJob, which creates an ebuild for an R package. |
50 |
+ |
51 |
+ arguments: |
52 |
+ * package_file -- path to the R package file |
53 |
+ * dep_resolver -- dependency resolver |
54 |
+ """ |
55 |
+ |
56 |
+ """Note: |
57 |
+ it is intended to run this job as thread, that's why it has its own |
58 |
+ dep resolver 'communication channel', status codes etc. |
59 |
+ """ |
60 |
+ |
61 |
self.package_file = package_file |
62 |
self.dep_resolver = dep_resolver |
63 |
# get description reader from args? |
64 |
@@ -18,44 +49,10 @@ class EbuildJob: |
65 |
|
66 |
self.ebuild = None |
67 |
|
68 |
- self._status = 0 # todo |
69 |
+ self.status = 'INIT' |
70 |
|
71 |
# --- end of __init__ (...) --- |
72 |
|
73 |
- @staticmethod |
74 |
- def get_statuscode ( status_id ): |
75 |
- if status_id == 'ALL': |
76 |
- return EbuildJob.STATUS_LIST |
77 |
- elif isinstance ( status_id, int ): |
78 |
- if status_id > 0 and status_id < len ( STATUS_LIST ): |
79 |
- return EbuildJob.STATUS_LIST [status_id] |
80 |
- elif status_id in EbuildJob.STATUS_MAP: |
81 |
- return EbuildJob.STATUS_MAP [status_id] |
82 |
- |
83 |
- return None |
84 |
- |
85 |
- # --- end of get_statuscode (...) --- |
86 |
- |
87 |
- @classmethod |
88 |
- def status ( self, expected_status=None ): |
89 |
- """Returns the current status of this job or a bool that indicates |
90 |
- whether to current status matches the expected one. |
91 |
- |
92 |
- arguments: |
93 |
- * expected_status -- if not None: check if this job's state is expected_status |
94 |
- """ |
95 |
- if expected_status: |
96 |
- if isinstance ( expected_status, int ): |
97 |
- return bool ( self._status == expected_status ) |
98 |
- elif expected_status in EbuildJob.STATUS_MAP: |
99 |
- return bool ( self._status == EbuildJob.STATUS_MAP [expected_status] ) |
100 |
- else: |
101 |
- return False |
102 |
- |
103 |
- return self._status |
104 |
- |
105 |
- # --- end of status (...) --- |
106 |
- |
107 |
@classmethod |
108 |
def get_ebuild ( self ): |
109 |
"""Returns the Ebuild that is created by this object. Note that you should |
110 |
@@ -68,73 +65,178 @@ class EbuildJob: |
111 |
# --- end of get_ebuild (...) --- |
112 |
|
113 |
@classmethod |
114 |
- def _set_status ( self, new_status ): |
115 |
- self._status = EbuildJob.get_statuscode ( new_status ) |
116 |
- return True |
117 |
+ def get_status ( self, expected_status=None ): |
118 |
+ """Returns the current status of this job or a bool that indicates |
119 |
+ whether to current status matches the expected one. |
120 |
|
121 |
- # --- end of _set_status (...) --- |
122 |
+ arguments: |
123 |
+ * expected_status -- if not None: check if this job's state is expected_status |
124 |
+ """ |
125 |
+ if not expected_status is None: |
126 |
+ return bool ( self.status == expected_status ) |
127 |
+ else: |
128 |
+ return self.status |
129 |
+ |
130 |
+ # --- end of get_status (...) --- |
131 |
+ |
132 |
+ @classmethod |
133 |
+ def done_success ( self ): |
134 |
+ """Returns True if this has been successfully finished.""" |
135 |
+ return get_status ( 'SUCCESS' ) |
136 |
+ |
137 |
+ # --- end of done_success (...) --- |
138 |
|
139 |
|
140 |
@classmethod |
141 |
def run ( self ): |
142 |
"""Tells this EbuildJob to run. This means that it reads the package file, |
143 |
- resolves dependencies (TODO) and creates an Ebuild object that is ready |
144 |
- to be written into a file. |
145 |
+ resolves dependencies using its resolver (TODO) and creates |
146 |
+ an Ebuild object that is ready to be written into a file. |
147 |
""" |
148 |
|
149 |
- # check status |
150 |
- if not self.status ( 'INIT' ): |
151 |
- return |
152 |
+ # TODO move hardcoded entries to config/const |
153 |
|
154 |
- if not self._set_status ( 'BUSY' ): |
155 |
- return False |
156 |
+ try: |
157 |
|
158 |
- read_data = self.description_reader.readfile ( self.package_file ) |
159 |
+ # set status or return |
160 |
+ if not self._set_status ( 'BUSY', True ): return |
161 |
|
162 |
- if read_data is None: |
163 |
- # set status accordingly |
164 |
- self._set_status ( 'FAIL' ) |
165 |
- return False |
166 |
+ read_data = self.description_reader.readfile ( self.package_file ) |
167 |
|
168 |
- fileinfo = read_data ['fileinfo'] |
169 |
- desc = read_data ['description_data'] |
170 |
+ if read_data is None: |
171 |
+ # set status accordingly |
172 |
+ self._set_status ( 'FAIL' ) |
173 |
+ return |
174 |
|
175 |
- ebuild = Ebuild() |
176 |
+ fileinfo = read_data ['fileinfo'] |
177 |
+ desc = read_data ['description_data'] |
178 |
|
179 |
- have_description = False |
180 |
+ ebuild = Ebuild() |
181 |
|
182 |
- print ( str ( desc ) ) |
183 |
+ have_description = False |
184 |
|
185 |
- if 'Title' in desc: |
186 |
- have_description = True |
187 |
- ebuild.add ( 'DESCRIPTION', desc ['Title'] ) |
188 |
+ if 'Title' in desc: |
189 |
+ ebuild.add ( 'DESCRIPTION', desc ['Title'] ) |
190 |
+ have_description = True |
191 |
|
192 |
- if 'Description' in desc: |
193 |
- have_description = True |
194 |
- ebuild.add ( 'DESCRIPTION', ( '// ' if have_description else '' ) + desc ['Description'] ) |
195 |
+ if 'Description' in desc: |
196 |
+ ebuild.add ( 'DESCRIPTION', ( '// ' if have_description else '' ) + desc ['Description'] ) |
197 |
+ #have_description=True |
198 |
|
199 |
- if not have_description: |
200 |
- ebuild.add ( 'DESCRIPTION', '<none>' ) |
201 |
- del have_description |
202 |
|
203 |
- # origin is todo (sync module knows the package origin) |
204 |
- ebuild.add ( 'PKG_ORIGIN', 'CRAN' ) |
205 |
+ # origin is todo (sync module knows the package origin) |
206 |
+ ebuild.add ( 'PKG_ORIGIN', 'CRAN' ) |
207 |
|
208 |
- ebuild.add ( 'PKG_FILE', fileinfo ['package_file'] ) |
209 |
+ ebuild.add ( 'PKG_FILE', fileinfo ['package_file'] ) |
210 |
|
211 |
- ebuild.add ( 'ebuild_header', [ '# test header' ], False ) |
212 |
+ ebuild.add ( 'ebuild_header', |
213 |
+ [ '# test header, first line\n', |
214 |
+ '# test header, second line\n\n\n\n', |
215 |
+ '#third\n\n#fifth' ], |
216 |
+ False |
217 |
+ ) |
218 |
|
219 |
- ## have to resolve deps here |
220 |
+ if self.dep_resolver and self.dep_resolver.enabled(): |
221 |
|
222 |
- # enter status that allows transferring ebuild -> self.ebuild |
223 |
- if self._set_status ( 'WAIT' ): |
224 |
- # finalize self.ebuild: forced text creation + make it readonly |
225 |
+ # collect depdencies from desc and add them to the resolver |
226 |
+ raw_depends = dict () |
227 |
+ |
228 |
+ dep_type = field = None |
229 |
+ |
230 |
+ for dep_type in EbuildJob.DEPENDENCY_FIELDS.keys(): |
231 |
+ |
232 |
+ raw_depends [dep_type] = [] |
233 |
+ |
234 |
+ for field in EbuildJob.DEPENDENCY_FIELDS [dep_type]: |
235 |
+ |
236 |
+ if field in desc: |
237 |
+ if isinstance ( desc [field], list ): |
238 |
+ raw_depends.extend ( desc [field] ) |
239 |
+ self.dep_resolver.add_dependencies ( desc [field] ) |
240 |
+ |
241 |
+ else: |
242 |
+ raw_depends.append ( desc [field] ) |
243 |
+ self.dep_resolver.add_depency ( desc [field] ) |
244 |
+ |
245 |
+ del field, dep_type |
246 |
+ |
247 |
+ |
248 |
+ while not self.dep_resolver.done(): |
249 |
+ |
250 |
+ if not self._set_status ( 'WAIT_RESOLVE' ): return |
251 |
+ |
252 |
+ # tell the resolver to run (again) |
253 |
+ self.dep_resolver.run() |
254 |
+ |
255 |
+ if not self._set_status ( 'BUSY' ): return |
256 |
+ |
257 |
+ if self.dep_resolver.satisfy_request(): |
258 |
+ |
259 |
+ dep_type = dep_str = dep = None |
260 |
+ |
261 |
+ # dependencies resolved, add them to the ebuild |
262 |
+ for dep_type in raw_depends.keys(): |
263 |
+ |
264 |
+ for dep_str in raw_depends [dep_type]: |
265 |
+ # lookup (str) should return a str here |
266 |
+ dep = self.dep_resolver.lookup ( dep_str ) |
267 |
+ if dep is None: |
268 |
+ raise Exception ( |
269 |
+ "dep_resolver is broken: lookup() returns None but satisfy_request() says ok." |
270 |
+ ) |
271 |
+ else: |
272 |
+ # add depencies in append mode |
273 |
+ dep = self.dep_resolver.lookup ( dep_str ) |
274 |
+ ebuild.add ( dep_type, |
275 |
+ self.dep_resolver.lookup ( dep_str ), |
276 |
+ True |
277 |
+ ) |
278 |
+ |
279 |
+ del dep, dep_str, dep_type |
280 |
+ |
281 |
+ # tell the dep resolver that we're done here |
282 |
+ self.dep_resolver.close() |
283 |
+ |
284 |
+ else: |
285 |
+ # ebuild is not creatable, set status to FAIL and close dep resolver |
286 |
+ self._set_status ( 'FAIL' ) |
287 |
+ self.dep_resolver.close() |
288 |
+ return |
289 |
+ |
290 |
+ ## finalize self.ebuild: forced text creation + make it readonly |
291 |
if ebuild.prepare ( True, True ): |
292 |
self.ebuild = ebuild |
293 |
- return self._set_status ( 'SUCCESS' ) |
294 |
- |
295 |
- self._set_status ( 'FAIL' ) |
296 |
- return False |
297 |
+ return None |
298 |
+ else: |
299 |
+ return None |
300 |
|
301 |
+ except Exception as any_exception: |
302 |
+ # any exception means failure |
303 |
+ self.status = 'FAIL' |
304 |
+ raise |
305 |
|
306 |
# --- end of run (...) --- |
307 |
+ |
308 |
+ @classmethod |
309 |
+ def _set_status ( self, new_status, ignore_invalid=False ): |
310 |
+ """Changes the status of this job. May refuse to do that if invalid change |
311 |
+ requested (e.g. 'FAIL' -> 'SUCCESS'). |
312 |
+ |
313 |
+ arguments: |
314 |
+ new_status -- |
315 |
+ """ |
316 |
+ |
317 |
+ if new_status == 'FAIL': |
318 |
+ # always allowed |
319 |
+ self.status = new_status |
320 |
+ |
321 |
+ if new_status and new_status in EbuildJob.STATUS_LIST: |
322 |
+ # check if jumping from self.status to new_status is allowed |
323 |
+ if new_status in EbuildJob.STATUS_BRANCHMAP [self.status]: |
324 |
+ self.status = new_status |
325 |
+ return True |
326 |
+ |
327 |
+ # default return |
328 |
+ return False |
329 |
+ |
330 |
+ # --- end of _set_status (...) --- |