|
1 # |
|
2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 # All rights reserved. |
|
4 # This component and the accompanying materials are made available |
|
5 # under the terms of "Eclipse Public License v1.0" |
|
6 # which accompanies this distribution, and is available |
|
7 # at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 # |
|
9 # Initial Contributors: |
|
10 # Nokia Corporation - initial contribution. |
|
11 # |
|
12 # Contributors: |
|
13 # |
|
14 # Description: |
|
15 # |
|
16 ''' |
|
17 Generator classes |
|
18 ''' |
|
19 |
|
20 |
|
21 import re |
|
22 import os |
|
23 import sys |
|
24 import logging |
|
25 import subprocess |
|
26 import shutil |
|
27 |
|
28 ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) |
|
29 from cone.public import utils, exceptions |
|
30 |
|
31 class InvalidInputFileException(RuntimeError): |
|
32 """ |
|
33 Exception thrown in case of an invalid input file list. |
|
34 """ |
|
35 pass |
|
36 |
|
37 class OutputGenerator(object): |
|
38 def __init__(self,outputpath,**kwargs): |
|
39 self._configuration = None |
|
40 self._subpath = '' |
|
41 self._contentpath = '' |
|
42 self._command = '' |
|
43 self._inputs = [] |
|
44 for arg in kwargs.keys(): |
|
45 setattr(self, arg, kwargs[arg]) |
|
46 self._outputpath = outputpath |
|
47 |
|
48 def __str__(self): |
|
49 return "Generator for output %s: %s" % (self.path,self.get_command()) |
|
50 |
|
51 def generate(self, context=None): |
|
52 command = self.get_command() |
|
53 if command: |
|
54 return command.execute() |
|
55 else: |
|
56 return 0 |
|
57 |
|
58 def get_outputpath(self): |
|
59 """ |
|
60 Get the confml ref value from configuration if the outputpath is actually a ref |
|
61 """ |
|
62 if self._outputpath and ConfmlRefs.is_confml_ref(self._outputpath): |
|
63 oref = ConfmlRefs.get_confml_ref(self._outputpath) |
|
64 opath = self.configuration.get_default_view().get_feature(oref).get_value() |
|
65 if opath == None: |
|
66 logging.getLogger('cone.imageml').warning('Output path not set.') |
|
67 return self._outputpath |
|
68 #raise exceptions.NotBound("Output path reference has no value %s" % oref) |
|
69 (drive,opath) = os.path.splitdrive(opath) |
|
70 opath = utils.resourceref.norm(opath) |
|
71 opath = utils.resourceref.remove_begin_slash(opath) |
|
72 return opath |
|
73 else: |
|
74 return self._outputpath |
|
75 |
|
76 def set_outputpath(self, value): |
|
77 self._outputpath = value |
|
78 |
|
79 def del_outputpath(self): |
|
80 self._outputpath = None |
|
81 |
|
82 def get_subpath(self): |
|
83 return self._subpath |
|
84 |
|
85 def set_subpath(self, value): |
|
86 self._subpath = value |
|
87 |
|
88 def del_subpath(self): |
|
89 self._subpath = None |
|
90 |
|
91 def get_inputs(self): |
|
92 return self._inputs |
|
93 |
|
94 def set_inputs(self, value): |
|
95 self._inputs = value |
|
96 |
|
97 def del_inputs(self): |
|
98 self._inputs = [] |
|
99 |
|
100 def get_configuration(self): |
|
101 return self._configuration |
|
102 |
|
103 def set_configuration(self, value): |
|
104 self._configuration= value |
|
105 for input in self.inputs: |
|
106 input.configuration = self.configuration |
|
107 |
|
108 def del_configuration(self): |
|
109 self._configuration= None |
|
110 |
|
111 @property |
|
112 def path(self): |
|
113 return utils.resourceref.join_refs([self.subpath, self.outputpath]) |
|
114 |
|
115 def get_command(self): |
|
116 (_,ext) = os.path.splitext(self.path) |
|
117 if ext == '.mbm': |
|
118 return BmconvCommand(self) |
|
119 elif ext == '.mif': |
|
120 return MifconvCommand(self) |
|
121 elif ext == '.gif': |
|
122 return CopyCommand(self) |
|
123 else: |
|
124 return None |
|
125 |
|
126 def get_refs(self): |
|
127 refs = [] |
|
128 for input in self.inputs: |
|
129 refs.extend(input.get_refs()) |
|
130 return refs |
|
131 |
|
132 configuration = property(get_configuration, set_configuration, del_configuration) |
|
133 inputs = property(get_inputs, set_inputs, del_inputs) |
|
134 outputpath = property(get_outputpath, set_outputpath, del_outputpath) |
|
135 subpath = property(get_subpath, set_subpath, del_subpath) |
|
136 |
|
137 class Command(object): |
|
138 def __init__(self,generator): |
|
139 self._generator = generator |
|
140 self._workdir = 'conversion_workdir' |
|
141 |
|
142 def execute(self): |
|
143 """ Execute this command """ |
|
144 pass |
|
145 |
|
146 def get_command(self, input_files): |
|
147 """ return the command as an array """ |
|
148 return [] |
|
149 |
|
150 def create_workdir(self, input_files): |
|
151 """ |
|
152 Extract the necessary input files from storage to a working directory |
|
153 @param input_files: The input files (a list of InputFile objects) |
|
154 """ |
|
155 if not os.path.exists(self._workdir): |
|
156 os.makedirs(self._workdir) |
|
157 |
|
158 for file in input_files: |
|
159 self.import_to_work(file.filename) |
|
160 |
|
161 def clean_workdir(self): |
|
162 """ |
|
163 Clean up working directory |
|
164 """ |
|
165 if os.path.exists(self._workdir): |
|
166 shutil.rmtree(self._workdir) |
|
167 |
|
168 def import_to_work(self,storage_filename): |
|
169 """ |
|
170 Convert a storage filename to a work filename |
|
171 """ |
|
172 workfile = self.workfilename(storage_filename) |
|
173 res = self._generator.configuration.get_resource(storage_filename,"rb") |
|
174 workfile = open(workfile,"wb") |
|
175 workfile.write(res.read()) |
|
176 res.close() |
|
177 workfile.close() |
|
178 |
|
179 def workfilename(self,filename): |
|
180 """ |
|
181 Convert a storage filename to a work filename |
|
182 """ |
|
183 (_,workname) = os.path.split(filename) |
|
184 return os.path.join(self.workdir,workname) |
|
185 |
|
186 def quote_needed(self,str): |
|
187 """ |
|
188 Add quotes around str if it has spaces |
|
189 """ |
|
190 if str.split(' ',1) > 1: |
|
191 return '"%s"' % str |
|
192 else: |
|
193 return str |
|
194 |
|
195 @property |
|
196 def tool(self): |
|
197 return '' |
|
198 |
|
199 @property |
|
200 def generator(self): |
|
201 return self._generator |
|
202 |
|
203 @property |
|
204 def workdir(self): |
|
205 return self._workdir |
|
206 |
|
207 def _get_filtered_input_files(self): |
|
208 """ |
|
209 Get the list of InputFile objects and with ignored |
|
210 (optional empty or invalid files) entries filtered out. |
|
211 |
|
212 Raise InvalidInputFileException if the input file list is invalid. |
|
213 """ |
|
214 # Get all input files |
|
215 input_files = [] |
|
216 for input in self.generator.inputs: |
|
217 input_files.extend(input.files) |
|
218 |
|
219 # Check if all are empty |
|
220 all_empty = True |
|
221 for file in input_files: |
|
222 if not file.is_empty(): |
|
223 all_empty = False |
|
224 break |
|
225 if all_empty: |
|
226 return [] |
|
227 |
|
228 # Create the filtered list |
|
229 result = [] |
|
230 for file in input_files: |
|
231 if file.is_empty(): |
|
232 if file.is_optional(): |
|
233 # Optional file is empty: no error |
|
234 pass |
|
235 else: |
|
236 raise InvalidInputFileException("Input file empty but not optional") |
|
237 else: |
|
238 if not file.is_valid(): |
|
239 raise InvalidInputFileException("Invalid input file: '%s'" % file.path) |
|
240 else: |
|
241 result.append(file) |
|
242 return result |
|
243 |
|
244 class BmconvCommand(Command): |
|
245 def __init__(self,generator): |
|
246 super(BmconvCommand, self).__init__(generator) |
|
247 |
|
248 def execute(self): |
|
249 """ |
|
250 Execute the command in the current working directory |
|
251 """ |
|
252 input_files = self._get_filtered_input_files() |
|
253 if len(input_files) == 0: return 0 |
|
254 self.create_workdir(input_files) |
|
255 |
|
256 if not os.path.exists(os.path.dirname(self.generator.path)): |
|
257 os.makedirs(os.path.dirname(self.generator.path)) |
|
258 |
|
259 command = self.get_command(input_files) |
|
260 p = subprocess.Popen(command, |
|
261 stdout=subprocess.PIPE, |
|
262 stderr=subprocess.PIPE) |
|
263 |
|
264 # Wait for the process to return |
|
265 out, err = [ e.splitlines() for e in p.communicate() ] |
|
266 for outl in out: |
|
267 if outl not in err: |
|
268 logging.getLogger('cone.bmconv').info(outl) |
|
269 for outl in err: |
|
270 logging.getLogger('cone.bmconv').error(outl) |
|
271 if p.returncode != 0: |
|
272 logging.getLogger('cone.bmconv').error("Command returned with returncode %s: %s" % (p.returncode, ' '.join(command))) |
|
273 else: |
|
274 logging.getLogger('cone.bmconv').info("Command returned with returncode %s: %s" % (p.returncode, ' '.join(command))) |
|
275 if p.returncode == 0: |
|
276 self.clean_workdir() |
|
277 return p.returncode |
|
278 |
|
279 def get_command(self, input_files): |
|
280 command = [self.tool] |
|
281 """ Add palette file """ |
|
282 if hasattr(self._generator,'palette'): |
|
283 command.append('/p%s' % os.path.abspath(self.generator.palette)) |
|
284 |
|
285 """ Add output file """ |
|
286 """ Add output file as compressed if needed """ |
|
287 if self.rom: |
|
288 if self.compress: |
|
289 command.append('/s') |
|
290 else: |
|
291 command.append('/r') |
|
292 else: |
|
293 pass |
|
294 command.append(os.path.normpath(self.generator.path)) |
|
295 |
|
296 |
|
297 for inputfile in input_files: |
|
298 depth = '' |
|
299 if inputfile.depth: |
|
300 depth = '/%s' % inputfile.depth |
|
301 command.append('%s%s' % (depth,self.workfilename(inputfile.filename))) |
|
302 return command |
|
303 |
|
304 @property |
|
305 def tool(self): |
|
306 if hasattr(self._generator,'tool'): |
|
307 return os.path.abspath(self._generator.tool) |
|
308 elif hasattr(self._generator, 'tooldir'): |
|
309 return os.path.abspath(os.path.join(self._generator.tooldir, 'bmconv')) |
|
310 else: |
|
311 return 'bmconv' |
|
312 |
|
313 @property |
|
314 def rom(self): |
|
315 if hasattr(self._generator,'rom') and self._generator.rom.lower() == 'true': |
|
316 return True |
|
317 else: |
|
318 return False |
|
319 |
|
320 @property |
|
321 def compress(self): |
|
322 if hasattr(self._generator,'compress') and self._generator.compress.lower() == 'true': |
|
323 return True |
|
324 else: |
|
325 return False |
|
326 |
|
327 class MifconvCommand(Command): |
|
328 def __init__(self,generator): |
|
329 super(MifconvCommand, self).__init__(generator) |
|
330 |
|
331 def execute(self): |
|
332 """ |
|
333 Execute the command in the current working directory |
|
334 """ |
|
335 input_files = self._get_filtered_input_files() |
|
336 if len(input_files) == 0: return 0 |
|
337 self.create_workdir(input_files) |
|
338 |
|
339 runenv = None |
|
340 runshell = True |
|
341 if os.path.dirname(self.tool): |
|
342 runenv = {} |
|
343 runenv['path'] = os.path.dirname(self.tool) |
|
344 runshell = True |
|
345 if not os.path.exists(os.path.dirname(self.generator.path)): |
|
346 os.makedirs(os.path.dirname(self.generator.path)) |
|
347 |
|
348 command = self.get_command(input_files) |
|
349 p = subprocess.Popen(command, |
|
350 stdout=subprocess.PIPE, |
|
351 stderr=subprocess.PIPE, |
|
352 env=runenv, |
|
353 shell=runshell) |
|
354 |
|
355 # Wait for the process to return |
|
356 out, err = [ e.splitlines() for e in p.communicate() ] |
|
357 for outl in out: |
|
358 if outl not in err: |
|
359 logging.getLogger('cone.mifconv').info(outl) |
|
360 for outl in err: |
|
361 logging.getLogger('cone.mifconv').error(outl) |
|
362 if p.returncode != 0: |
|
363 logging.getLogger('cone.mifconv').error("Command returned with returncode %s: %s" % (p.returncode, ' '.join(command))) |
|
364 else: |
|
365 logging.getLogger('cone.mifconv').info("Command returned with returncode %s: %s" % (p.returncode, ' '.join(command))) |
|
366 if p.returncode == 0: |
|
367 self.clean_workdir() |
|
368 return p.returncode |
|
369 |
|
370 def get_command(self, input_files): |
|
371 command = [self.tool] |
|
372 |
|
373 """ Add output file """ |
|
374 command.append(os.path.normpath(self.generator.path)) |
|
375 |
|
376 """ Add temp_path """ |
|
377 command.append("/t%s" % self.temppath) |
|
378 |
|
379 # Add tool directory if given |
|
380 if hasattr(self._generator,'tooldir'): |
|
381 command.append('/S%s' % os.path.abspath(self.generator.tooldir)) |
|
382 |
|
383 """ Get input files """ |
|
384 for inputfile in input_files: |
|
385 depth = 'c8' |
|
386 if inputfile.depth: |
|
387 depth = inputfile.depth |
|
388 command.append('/%s' % depth) |
|
389 command.append( '%s' % self.workfilename(inputfile.filename)) |
|
390 return command |
|
391 |
|
392 @property |
|
393 def tool(self): |
|
394 if hasattr(self._generator,'tool'): |
|
395 return os.path.abspath(self._generator.tool) |
|
396 elif hasattr(self._generator, 'tooldir'): |
|
397 return os.path.abspath(os.path.join(self._generator.tooldir, 'mifconv')) |
|
398 else: |
|
399 return 'mifconv' |
|
400 |
|
401 @property |
|
402 def temppath(self): |
|
403 if hasattr(self._generator,'temp'): |
|
404 return os.path.abspath(self._generator.temp) |
|
405 else: |
|
406 return self.workdir |
|
407 |
|
408 class CopyCommand(object): |
|
409 def __init__(self,generator): |
|
410 self._generator = generator |
|
411 |
|
412 def execute(self): |
|
413 pass |
|
414 |
|
415 @property |
|
416 def tool(self): |
|
417 return 'copy' |
|
418 |
|
419 class InputFile(object): |
|
420 def __init__(self,path,**kwargs): |
|
421 self.configuration = None |
|
422 self._depth = None |
|
423 for arg in kwargs.keys(): |
|
424 if arg == 'depth': |
|
425 # Special handling for depth ('depth' is a property that |
|
426 # expands refs using '_depth' as the base) |
|
427 self._depth = kwargs[arg] |
|
428 else: |
|
429 setattr(self, arg, kwargs[arg]) |
|
430 self._path= path |
|
431 |
|
432 def get_input(self): |
|
433 """ |
|
434 Get the confml ref value from configuration if the outputpath is actually a ref |
|
435 """ |
|
436 if self._path and self.configuration is not None: |
|
437 dview = self.configuration.get_default_view() |
|
438 def expand(ref, index): |
|
439 value = dview.get_feature(ref).get_original_value() |
|
440 if value is None: return '' |
|
441 else: return value |
|
442 return utils.expand_delimited_tokens(self._path, expand) |
|
443 else: |
|
444 return self._path |
|
445 |
|
446 def set_input(self, value): self._path = value |
|
447 |
|
448 def del_input(self): self._path = None |
|
449 |
|
450 @property |
|
451 def type(self): |
|
452 return 'file' |
|
453 |
|
454 @property |
|
455 def depth(self): |
|
456 if self._depth and self.configuration: |
|
457 dview = self.configuration.get_default_view() |
|
458 return utils.expand_refs_by_default_view(self._depth, dview) |
|
459 else: |
|
460 return self._depth or '' |
|
461 |
|
462 @property |
|
463 def files(self): |
|
464 """ |
|
465 Return a list of file names |
|
466 """ |
|
467 return [self] |
|
468 |
|
469 @property |
|
470 def filename(self): |
|
471 """ |
|
472 Return a the path to the layer specific filename |
|
473 """ |
|
474 if self.configuration and self.path: |
|
475 content = self.configuration.layered_content().flatten() |
|
476 inputpath = self.path |
|
477 return content.get(inputpath) |
|
478 else: |
|
479 return self.path |
|
480 |
|
481 path = property(get_input, set_input, del_input, "The input 'path'.") |
|
482 |
|
483 def is_valid(self): |
|
484 """ |
|
485 Return whether the input file is valid (not empty |
|
486 and exists in project content). |
|
487 """ |
|
488 return not self.is_empty() and self.filename |
|
489 |
|
490 def is_empty(self): |
|
491 """ |
|
492 Return whether the input file is empty. |
|
493 """ |
|
494 return self.path in ('', None) |
|
495 |
|
496 def is_optional(self): |
|
497 """ |
|
498 Return whether the input file is optional. |
|
499 """ |
|
500 return hasattr(self, 'optional') \ |
|
501 and self.optional.lower() in ('1', 't', 'true', 'yes', 'y') |
|
502 |
|
503 def get_refs(self): |
|
504 return utils.extract_delimited_tokens(self._path) |
|
505 |
|
506 def __repr__(self): |
|
507 return "InputFile(path=%r, optional=%r)" % (self._path, self.is_optional()) |
|
508 |
|
509 class InputDir(InputFile): |
|
510 def __init__(self,path,**kwargs): |
|
511 super(InputDir,self).__init__(path,**kwargs) |
|
512 self._files = [] |
|
513 self._include = None |
|
514 self._exclude = None |
|
515 |
|
516 def get_include(self): |
|
517 return self._include.get('pattern',[]) |
|
518 |
|
519 def set_include(self, value): |
|
520 self._include = value |
|
521 |
|
522 def del_include(self): |
|
523 self._include = None |
|
524 |
|
525 def get_exclude(self): |
|
526 return self._exclude.get('pattern',[]) |
|
527 |
|
528 def set_exclude(self, value): |
|
529 self._exclude = value |
|
530 |
|
531 def del_exclude(self): |
|
532 self._exclude = None |
|
533 |
|
534 @property |
|
535 def type(self): |
|
536 return 'dir' |
|
537 |
|
538 @property |
|
539 def files(self): |
|
540 """ |
|
541 Return a list of file names under this directory definition |
|
542 """ |
|
543 if self.configuration: |
|
544 inputlist = [] |
|
545 content = self.configuration.layered_content().flatten() |
|
546 contentfiles = content.keys() |
|
547 |
|
548 folderfiles = utils.resourceref.filter_resources(contentfiles, "^%s" % self.path) |
|
549 for inputfilter in self.include: |
|
550 folderfiles = utils.resourceref.filter_resources(folderfiles, inputfilter) |
|
551 for excludefilter in self.exclude: |
|
552 folderfiles = utils.resourceref.neg_filter_resources(folderfiles, excludefilter) |
|
553 folderfiles.sort() |
|
554 for filename in folderfiles: |
|
555 inputlist.append(InputFile(filename, **self.__dict__)) |
|
556 return inputlist |
|
557 else: |
|
558 return [] |
|
559 |
|
560 include = property(get_include, set_include, del_include) |
|
561 exclude = property(get_exclude, set_exclude, del_exclude) |
|
562 |
|
563 |
|
564 |
|
565 class ConfmlRefs(object): |
|
566 |
|
567 ref_pattern = re.compile('^\$\{(.*)\}$') |
|
568 |
|
569 @classmethod |
|
570 def is_confml_ref(cls, variableref): |
|
571 """ |
|
572 |
|
573 Returns true if the given variable ref is a confml reference |
|
574 """ |
|
575 return cls.ref_pattern.match(variableref) != None |
|
576 |
|
577 @classmethod |
|
578 def get_confml_ref(cls, variableref): |
|
579 """ |
|
580 |
|
581 Returns true if the given variable ref is a confml reference |
|
582 """ |
|
583 matchref = cls.ref_pattern.match(variableref) |
|
584 if matchref: |
|
585 return matchref.group(1) |
|
586 else: |
|
587 return None |