1###############################################################################
2# Copyright (c) 2013 INRIA
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License version 2 as
6# published by the Free Software Foundation;
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16#
17# Authors: Daniel Camara  <daniel.camara@inria.fr>
18#          Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
19###############################################################################
20'''
21 Module.py
22
23 This file stores the generic implementation of the bake options. e.g. how
24 the download works, independently of the technology/repository used to
25 store the code.
26'''
27
28import copy
29import os
30import re
31import sys
32import shutil
33
34from bake.FilesystemMonitor import FilesystemMonitor
35from bake.Exceptions import TaskError
36from bake.Utils import ColorTool
37from bake.Exceptions import NotImplemented
38from bake.ModuleSource import SystemDependency
39from bake.Utils import ModuleAttributeBase
40
41class ModuleDependency(ModuleAttributeBase):
42    """ Dependency information. """
43    instances = []
44    def __init__(self, name = '' , optional = False):
45
46        ModuleAttributeBase.__init__(self)
47
48        self.add_attribute('name', name, 'Name of the Module', mandatory=True)
49        self.add_attribute('optional', str(optional), 'Whether module is optional', mandatory=True)
50
51        self.__class__.instances.append(self)
52
53    @classmethod
54    def subclasses(self):
55        return ModuleDependency.__subclasses__()
56
57    def name(self):
58        return self._name
59
60    @classmethod
61    def name(cls):
62        return 'default'
63
64    @property
65    def _name(self):
66        return self.attribute('name').value
67
68    @property
69    def _optional(self):
70        return self.is_optional()
71
72    @classmethod
73    def create(cls, build_type):
74        for subclass in ModuleDependency.subclasses():
75            if subclass.name() == build_type:
76                instance = subclass()
77                return instance
78
79        return ModuleDependency()
80
81    def is_optional(self):
82        return bool(self.attribute('optional').value.upper() == "TRUE")
83
84    def configure_arguments(self):
85        raise NotImplemented()
86
87    @classmethod
88    def lookup_obj(cl,name):
89        for instance in cl.instances:
90            if instance._name == name:
91                return instance
92
93        return None
94
95class WafModuleDependency(ModuleDependency):
96
97    def __init__(self):
98
99        ModuleDependency.__init__(self)
100        self.add_attribute('configure_arguments', '', 'Arguments to pass to'
101                           ' "waf configure"')
102
103    @classmethod
104    def name(cls):
105        return 'waf'
106
107    def configure_arguments(self):
108        return self.attribute('configure_arguments').value
109
110class Module:
111    followOptional = None
112    FAIL = 0
113    OK = 1
114
115    def __init__(self, name,
116                 source,
117                 build,
118                 mtype,
119                 min_ver,
120                 max_ver,
121                 dependencies = [],
122                 built_once = False,
123                 installed = []):
124        self._name = name
125        self._type = mtype
126        self._dependencies = copy.copy(dependencies)
127        self._source = source
128        self._build = build
129        self._built_once = built_once
130        self._installed = installed
131        self._minVersion = min_ver
132        self._maxVersion = max_ver
133
134
135    @property
136    def installed(self):
137        """ Returns if the module was already installed or not. """
138        return self._installed
139    @installed.setter
140    def installed(self, value):
141        """ Stores the given value on the module installed option. """
142        self._installed = copy.copy(value)
143
144    def _directory(self):
145        return self._name
146
147    def handleStopOnError(self, e):
148        """Handles the stop on error parameter, prints the standard
149        message and calls exist."""
150        colorTool = ColorTool()
151        colorTool.cPrintln(colorTool.OK, " > Stop on error enabled (for more information call bake with -vv or -vvv)")
152        colorTool.cPrintln(colorTool.FAIL, "   >> " + e._reason)
153        os._exit(1)
154
155    def printResult(self, env, operation, result):
156        """Prints the result of the operation."""
157        colorTool = ColorTool()
158        resultStr = "OK"
159        color=colorTool.OK
160        if result == self.FAIL:
161            resultStr = "Problem"
162            color=colorTool.FAIL
163
164        if env._logger._verbose > 0:
165            print()
166            colorTool.cPrintln(color, " >> " + operation + " " +
167                                    self._name + " - " +resultStr)
168        else:
169            colorTool.cPrintln(color, resultStr)
170
171    def _do_download(self, env, source, name, forceDownload):
172        """ Recursive download function, do the download for each
173        target module. """
174
175        srcDirTmp = name
176        if source.attribute('module_directory').value :
177            srcDirTmp = source.attribute('module_directory').value
178
179        env.start_source(name, srcDirTmp)
180        rt = source.check_version(env)
181
182        if forceDownload:
183            try: # when forced download, removes the repository if it exists
184                if os.path.isdir(env.srcdir):
185                    shutil.rmtree(env.srcdir)
186            except OSError as e:
187                env._logger.commands.write('Could not remove source files'
188                                            ' %s for module: %s \n Error: %s\n' %
189                                            (env.srcdir, env._module_name,
190                                             str(e)))
191        aditionalModule=False
192        if source.attribute('additional-module'):
193            aditionalModule = source.attribute('additional-module').value
194
195        if os.path.isdir(env.srcdir) and not aditionalModule :
196            colorTool = ColorTool()
197            if env._logger._verbose == 0:
198                colorTool.cPrint(colorTool.OK, "(Nothing to do, source directory already exists) - ")
199            else:
200                colorTool.cPrintln(colorTool.OK, "  >>> No actions needed, the source directory for " +
201                               self._name + " already exists.")
202                sys.stdout.write ("      If you want to update the module, use update instead download, or, if you want a fresh copy," + os.linesep
203                      +"      either remove it from the source directory, or use the --force_download option.")
204
205            if self._source.attribute('new_variable').value != '':
206                elements = env.replace_variables(self._source.attribute('new_variable').value).split(";")
207                env.add_variables(elements)
208
209
210            env.end_source()
211        else:
212            try:
213                source.download(env)
214                if self._source.attribute('patch').value != '':
215                    self._build.threat_patch(env, self._source.attribute('patch').value)
216                if self._source.attribute('new_variable').value != '':
217                    elements = env.replace_variables(self._source.attribute('new_variable').value).split(";")
218                    env.add_variables(elements)
219                if self._source.attribute('post_download').value != '':
220                    self._source.perform_post_download(env)
221            finally:
222                env.end_source()
223        for child, child_name in source.children():
224            self._do_download(env, child, os.path.join(name, child_name))
225
226    def download(self, env, forceDownload):
227        """ General download function. """
228
229        if self._build.attribute('supported_os').value :
230            if not self._build.check_os(self._build.attribute('supported_os').value) :
231                import platform
232                import distro
233                osName = platform.system().lower()
234                (distname,version,ids)=distro.linux_distribution()
235                print('    Downloading, but this module works only on \"%s\"'
236                      ' platform(s), %s is not supported for %s %s %s %s' %
237                      (self._build.attribute('supported_os').value,
238                       self._name, platform.system(), distname,version,ids))
239
240        try:
241            self._do_download(env, self._source, self._name, forceDownload)
242
243            if isinstance(self._source, SystemDependency):
244                self.printResult(env, "Dependency ", self.OK)
245            else:
246                self.printResult(env, "Download", self.OK)
247
248
249            return True
250        except TaskError as e:
251            if isinstance(self._source, SystemDependency):
252                self.printResult(env, "Dependency ", self.FAIL)
253            else:
254                self.printResult(env, "Download", self.FAIL)
255            env._logger.commands.write(e.reason+'\n')
256            if env.debug :
257                import bake.Utils
258                bake.Utils.print_backtrace()
259            if env.stopOnErrorEnabled:
260                self.handleStopOnError(e)
261            return False
262        except:
263            if isinstance(self._source, SystemDependency):
264                self.printResult(env, "Install", self.FAIL)
265            else:
266                self.printResult(env, "Download", self.FAIL)
267            if env.debug :
268                import bake.Utils
269                bake.Utils.print_backtrace()
270            if env.stopOnErrorEnabled:
271                er = sys.exc_info()[1]
272                self.handleStopOnError(TaskError('Error: %s' % (er)))
273            return False
274
275
276    def _do_update(self, env, source, name):
277        """ Recursive update function, do the update for each
278        target module. """
279
280        srcDirTmp = name
281        if source.attribute('module_directory').value :
282            srcDirTmp = source.attribute('module_directory').value
283
284        env.start_source(name, srcDirTmp)
285
286        try:
287            source.update(env)
288        finally:
289            env.end_source()
290        for child, child_name in source.children():
291            self._do_update(env, child, os.path.join(name, child_name))
292
293    def update(self, env):
294        """ Main update function. """
295
296        try:
297            self._do_update(env, self._source, self._name)
298            self.printResult(env, " Update ", self.OK)
299            return True
300        except TaskError as e:
301            self.printResult(env, " Update ", self.FAIL)
302            env._logger.commands.write(e.reason+'\n')
303            if env.debug :
304                import bake.Utils
305                bake.Utils.print_backtrace()
306            if env.stopOnErrorEnabled:
307                self.handleStopOnError(e)
308            return False
309        except:
310            self.printResult(env, " Update ", self.FAIL)
311            env._logger.commands.write(e.reason+'\n')
312            if env.debug :
313                import bake.Utils
314                bake.Utils.print_backtrace()
315            if env.stopOnErrorEnabled:
316                er = sys.exc_info()[1]
317                self.handleStopOnError(TaskError('Error: %s' % (er)))
318            return False
319
320    def distclean(self, env):
321        """ Main distclean source function, call the modules distclean. """
322
323        srcDirTmp = self._name
324        if self._source.attribute('module_directory').value :
325            srcDirTmp = self._source.attribute('module_directory').value
326
327        env.start_build(self._name, srcDirTmp,
328                        self._build.supports_objdir)
329        if not os.path.isdir(env.objdir) or not os.path.isdir(env.srcdir):
330            env.end_build()
331            return
332        try:
333            self._build.distclean(env)
334            env.end_build()
335            self._built_once = False
336            self.printResult(env, "Distclean ", self.OK)
337            return True
338        except TaskError as e:
339            self.printResult(env, "Distclean ", self.FAIL)
340            env._logger.commands.write(e.reason+'\n')
341            if env.debug :
342                import bake.Utils
343                bake.Utils.print_backtrace()
344            return False
345        except:
346            env.end_build()
347            if env.debug :
348                import bake.Utils
349                bake.Utils.print_backtrace()
350            return False
351
352    def fullclean(self, env):
353        """ Main full clean function, deletes the source and installed files. """
354
355        srcDirTmp = self._name
356        if self._source.attribute('module_directory').value :
357            srcDirTmp = self._source.attribute('module_directory').value
358
359        env.start_build(self._name, srcDirTmp, True)
360        sys.stdout.write(" >> Removing source: " + self._name + ": " + env.srcdir)
361        try:
362            shutil.rmtree(env.srcdir)
363            self.printResult(env, "Removing source: ", self.OK)
364        except Exception as e:
365            err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip()
366            env._logger.commands.write("    > " + err +'\n')
367#            print (e)
368            pass
369
370        if os.path.isdir(env.objdir):
371            sys.stdout.write(" >> Removing build: " + env.objdir)
372            try:
373                shutil.rmtree(env.objdir)
374                self.printResult(env, "Removing build: ", self.OK)
375            except Exception as e:
376                self.printResult(env, "Removing build: ", self.FAIL)
377                err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip()
378                env._logger.commands.write("    > " + err +'\n')
379
380        if os.path.isdir(env.installdir):
381            sys.stdout.write(" >> Removing installation: " + env.installdir)
382            try:
383                shutil.rmtree(env.installdir)
384                self.printResult(env, "Installation removed", self.OK)
385            except Exception as e:
386                self.printResult(env, "Installation removed", self.FAIL)
387                err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip()
388                env._logger.commands.write("    > " + err +'\n')
389
390        return True
391
392
393    def uninstall(self, env):
394        """ Main uninstall function, deletes the installed files. """
395
396        for installed in self._installed:
397            try:
398                os.remove(installed)
399            except OSError:
400                pass
401
402        # delete directories where files were installed if they are empty
403        dirs = [os.path.dirname(installed) for installed in self._installed]
404        def uniq(seq):
405            keys = {}
406            for e in seq:
407                keys[e] = 1
408            return keys.keys()
409        for d in uniq(dirs):
410            try:
411                os.removedirs(d)
412            except OSError:
413                pass
414        self._installed = []
415
416
417    def build(self, env, jobs, force_clean):
418        """ Main build function. """
419
420        # if there is no build we do not need to proceed
421        if self._build.name() == 'none' or self._source.name() == 'system_dependency':
422            srcDirTmp = self._name
423            if self._source.attribute('module_directory').value :
424                srcDirTmp = self._source.attribute('module_directory').value
425
426            if self._build.attribute('pre_installation').value != '':
427                self._build.perform_pre_installation(env)
428            if self._build.attribute('patch').value != '':
429                self._build.threat_patch(env, self._build.attribute('patch').value)
430            if self._build.attribute('post_installation').value != '':
431                self._build.perform_post_installation(env)
432
433            self._build.threat_variables(env)
434
435            return True
436
437        # delete in case this is a new build configuration
438        # and there are old files around
439        if force_clean:
440            self.uninstall(env)
441            if not self._built_once:
442                self.clean(env)
443
444        srcDirTmp = self._name
445        if self._source.attribute('module_directory').value :
446            srcDirTmp = self._source.attribute('module_directory').value
447
448        env.start_build(self._name, srcDirTmp,
449                        self._build.supports_objdir)
450
451        # setup the monitor
452        monitor = FilesystemMonitor(env.installdir)
453        monitor.start()
454
455        if self._build.attribute('supported_os').value :
456            if not self._build.check_os(self._build.attribute('supported_os').value) :
457                import platform
458                import distro
459                osName = platform.system().lower()
460                (distname,version,ids)=distro.linux_distribution()
461                self.printResult(env, "Building", self.FAIL)
462                print('    This module works only on \"%s\"'
463                      ' platform(s), %s is not supported for %s %s %s %s' %
464                      (self._build.attribute('supported_os').value,
465                       self._name, platform.system(), distname,version,ids))
466                return
467
468        if not os.path.isdir(env.installdir):
469            os.mkdir(env.installdir)
470        if self._build.supports_objdir and not os.path.isdir(env.objdir):
471            os.mkdir(env.objdir)
472
473        try:
474            if not os.path.isdir(env.srcdir):
475                raise TaskError('Source is not available for module %s: '
476                            'directory %s not found.  Try %s download first.' %
477                            (env._module_name,env.srcdir, sys.argv[0]))
478
479            if self._build.attribute('pre_installation').value != '':
480                self._build.perform_pre_installation(env)
481
482            self._build.threat_variables(env)
483
484            if self._build.attribute('patch').value != '':
485                self._build.threat_patch(env, self._build.attribute('patch').value)
486
487            self._build.build(env, jobs)
488            self._installed = monitor.end()
489            if self._build.attribute('post_installation').value != '':
490                self._build.perform_post_installation(env)
491            env.end_build()
492            self._built_once = True
493            self.printResult(env, "Built", self.OK)
494            return True
495        except TaskError as e:
496            self.printResult(env, "Building", self.FAIL)
497            env._logger.commands.write("   > " + e.reason+'\n')
498            if env.debug :
499                import bake.Utils
500                bake.Utils.print_backtrace()
501            env.end_build()
502
503            if env.stopOnErrorEnabled:
504                self.handleStopOnError(e)
505
506            return False
507        except:
508            self._installed = monitor.end()
509            env.end_build()
510            if env.debug :
511                import bake.Utils
512                bake.Utils.print_backtrace()
513            if env.stopOnErrorEnabled:
514                er = sys.exc_info()[1]
515                self.handleStopOnError(TaskError('Error: %s' % (er)))
516            return False
517
518    def check_build_version(self, env):
519        """ Checks the version of the selected build tool in the machine. """
520
521        srcDirTmp = self._name
522        if self._source.attribute('module_directory').value :
523            srcDirTmp = self._source.attribute('module_directory').value
524
525        env.start_build(self._name, srcDirTmp,
526                        self._build.supports_objdir)
527
528        retval = self._build.check_version(env)
529        env.end_build()
530        return retval
531
532    def is_downloaded(self, env):
533        """ Checks if the source code is not already available. """
534
535        srcDirTmp = self._name
536        if self._source.name() == 'system_dependency' :
537            return True
538
539        if self._source.name() == 'none' :
540            return True
541
542        if self._source.attribute('module_directory').value :
543            srcDirTmp = self._source.attribute('module_directory').value
544
545        env.start_source(self._name,srcDirTmp)
546        retval = os.path.isdir(env.srcdir)
547        env.end_source()
548        return retval
549
550
551    def check_source_version(self, env):
552        """ Checks if the version of the available version control tool. """
553
554        srcDirTmp = self._name
555        if self._source.attribute('module_directory').value :
556            srcDirTmp = self._source.attribute('module_directory').value
557        env.start_source(self._name, srcDirTmp)
558        retval = self._source.check_version(env)
559        env.end_source()
560        return retval
561
562
563    def update_libpath(self, env):
564        """ Makes it available for the next modules the present libpath. """
565
566        srcDirTmp = self._name
567        if self._source.attribute('module_directory').value :
568            srcDirTmp = self._source.attribute('module_directory').value
569
570        env.start_build(self._name, srcDirTmp,
571                        self._build.supports_objdir)
572        env.add_libpaths([env._lib_path()])
573        env.end_build()
574
575    def clean(self, env):
576        """ Main cleaning build option handler. """
577
578        srcDirTmp = self._name
579        if self._source.attribute('module_directory').value :
580            srcDirTmp = self._source.attribute('module_directory').value
581
582        env.start_build(self._name, srcDirTmp,
583                        self._build.supports_objdir)
584        if not os.path.isdir(env.objdir) or not os.path.isdir(env.srcdir):
585            env.end_build()
586            return
587        try:
588            self._build.clean(env)
589            env.end_build()
590            self._built_once = False
591            self.printResult(env, "Clean ", self.OK)
592            return True
593        except TaskError as e:
594            self.printResult(env, "Clean ", self.FAIL)
595            err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip()
596            env._logger.commands.write(err+'\n')
597            if env.debug :
598                import bake.Utils
599                bake.Utils.print_backtrace()
600            if env.stopOnErrorEnabled:
601                self.handleStopOnError(e)
602            return False
603        except:
604            env.end_build()
605            if env.debug :
606                import bake.Utils
607                bake.Utils.print_backtrace()
608            if env.stopOnErrorEnabled:
609                er = sys.exc_info()[1]
610                self.handleStopOnError(TaskError('Error: %s' % (er)))
611            return False
612
613    def is_built_once(self):
614        return self._built_once
615    def get_source(self):
616        return self._source
617    def get_build(self):
618        return self._build
619    def name(self):
620        return self._name
621    def dependencies(self):
622        return self._dependencies
623    def mtype(self):
624        return self._type
625    def minver(self):
626        return self._minVersion
627    def maxver(self):
628        return self._maxVersion
629    def addDependencies(self, depend):
630        for d in self._dependencies:
631            if d.name() == depend.name():
632                return
633        self._dependencies.append(depend)
634