more general module loading; do not use __dict__ directly
[mass-build.git] / build_system.py
1 # mass-build - Easily Build Software Involving a Large Amount of Source Repositories
2 # Copyright (C) 2012-2013 Ralf Jung <post@ralfj.de>
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 as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 import os, shutil, subprocess, multiprocessing
19
20 '''A build system must have a "build" method with parameters "reconfigure" and "waitAfterConfig".'''
21
22 # Compile, build, and install cmake projects:
23 class CMake:
24         def __init__(self, sourceFolder, buildFolder, config):
25                 self.sourceFolder = os.path.abspath(sourceFolder)
26                 self.buildFolder = os.path.abspath(buildFolder)
27                 self.config = config
28         
29         def setEnv(self, name, val):
30                 '''Set the given environment variable, return old value'''
31                 oldVal = os.getenv(name)
32                 os.putenv(name, val)
33                 return oldVal
34         
35         def prependDirToEnv(self, name, dir, default):
36                 '''Prepends the given directory to the environment variable. If the variable is empty, dir isprepended to the default.
37                    Returns the old value.'''
38                 oldVal = os.getenv(name)
39                 oldPaths = default if oldVal is None else oldVal
40                 os.putenv(name, dir+':'+oldPaths)
41                 return oldVal
42         
43         def restoreEnv(self, name, oldVal):
44                 '''Restore environment variable to previous value'''
45                 if oldVal is None:
46                         os.unsetenv(name)
47                 else:
48                         os.putenv(name, oldVal)
49         
50         def build(self, reconfigure, waitAfterConfig):
51                 # Make sure we have a build directory
52                 if not os.path.exists(self.buildFolder): os.makedirs(self.buildFolder)
53                 os.chdir(self.buildFolder)
54                 # In case of reconfiguration, delete cache file if it exists
55                 cacheFile = 'CMakeCache.txt'
56                 if os.path.exists(cacheFile) and reconfigure: os.remove(cacheFile)
57                 # Run cmake, in the proper environment, then restore old environment
58                 oldPKGConfigPath = self.setEnv('PKG_CONFIG_PATH', os.path.join(self.config['installDir'], 'lib', 'pkgconfig'))
59                 oldCMakePrefixPath = self.setEnv('CMAKE_PREFIX_PATH', self.config['installDir'])
60                 oldXDGDataDirs = self.prependDirToEnv('XDG_DATA_DIRS', os.path.join(self.config['installDir'], 'share'), '/usr/share')
61                 oldXDGConfigDirs = self.prependDirToEnv('XDG_CONFIG_DIRS', os.path.join(self.config['installDir'], 'etc', 'xdg'), '/etc/xdg')
62                 subprocess.check_call(['cmake', self.sourceFolder, '-DCMAKE_BUILD_TYPE='+self.config['buildType'],
63                         '-DCMAKE_INSTALL_PREFIX='+self.config['installDir']]+self.config.get('cmakeParameters', []))
64                 self.restoreEnv('PKG_CONFIG_PATH', oldPKGConfigPath)
65                 self.restoreEnv('CMAKE_PREFIX_PATH', oldCMakePrefixPath)
66                 self.restoreEnv('XDG_DATA_DIRS', oldXDGDataDirs)
67                 self.restoreEnv('XDG_CONFIG_DIRS', oldXDGConfigDirs)
68                 # if asked to do so, wait
69                 if waitAfterConfig:
70                         input('Configuration done. Hit "Enter" to build the project. ')
71                 # run compilation
72                 jobs = multiprocessing.cpu_count()+1
73                 subprocess.check_call(self.config.get('buildCmdPrefix', []) + ['make', '-j'+str(jobs)])
74                 # run installation
75                 subprocess.check_call(self.config.get('installCmdPrefix', []) + ['make', 'install', '-j'+str((jobs+1)//2)]) # jobs/2, rounded up
76
77 # if auto-debuild is available, provide a wrapper for it
78 try:
79         import auto_debuild
80         class AutoDebuild:
81                 def __init__(self, sourceFolder, buildFolder, config, vcs):
82                         self.sourceFolder = os.path.abspath(sourceFolder)
83                         self.buildFolder = os.path.abspath(buildFolder)
84                         self.debFolder = os.path.abspath(config['debDir'])
85                         self.config = config
86                         self.vcs = vcs
87
88                 def build(self, reconfigure, waitAfterConfig): # reconfigure is ignored (we always do a reconfiguration)
89                         # get version name
90                         versionName = self.config['versionName'] if 'versionName' in self.config else self.vcs.version()
91                         if versionName is None:
92                                 raise Exception("VCS did not provide us with a proper version number, please provide one manually")
93                         # create auto-debuild configuration
94                         autoDebuildConfig = {
95                                 'sourceName': self.config['name'],
96                                 'buildSystem': self.config['buildSystem'],
97                                 'debDir': self.debFolder,
98                                 'buildDir': self.buildFolder,
99                                 'name': self.config['debName'],
100                                 'email': self.config['debEMail'],
101                                 'version': versionName + self.config.get('versionSuffix', ''),
102                                 'waitAfterConfig': waitAfterConfig,
103                         }
104                         # copy some more optional configuration
105                         for option in ('epoch', 'dbgPackage', 'section', 'withPython2', 'withSIP', 'binarySkipFiles', 'binaryInstallFiles',
106                                         'buildDepends', 'binaryDepends', 'binaryShims', 'binaryRecommends', 'binaryProvides', 'binaryConflicts', 'binaryBreaks',
107                                         'binaryReplaces', 'binaryBreaksReplaces',
108                                         'alternatives', 'cmakeParameters', 'automakeParameters'):
109                                 if option in self.config:
110                                         autoDebuildConfig[option] = self.config[option]
111                         # create Debian files
112                         os.chdir(self.sourceFolder)
113                         auto_debuild.deleteDebianFolder()
114                         files = auto_debuild.createDebianFiles(autoDebuildConfig)
115                         # build package(s)
116                         auto_debuild.buildDebianPackage(autoDebuildConfig)
117                         # install package(s)
118                         if self.config.get('debInstall', True):
119                                 subprocess.check_call(['sudo', 'dpkg', '--install'] + files)
120
121 except ImportError:
122         #print "auto_debuild not found, disabling auto-debuild system"
123         pass