75b55376aff6b85ec68d6ead64da0352282349ae
[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
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                         raw_input('Configuration done. Hit "Enter" to build the project. ')
71                 # run compilation
72                 jobs = int(self.config['jobs'])
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                                 'parallelJobs': self.config['jobs'],
102                                 'version': versionName + self.config.get('versionSuffix', ''),
103                                 'waitAfterConfig': waitAfterConfig,
104                         }
105                         # copy some more optional configuration
106                         for option in ('epoch', 'dbgPackage', 'section', 'withPython2', 'withSIP', 'binarySkipFiles', 'binaryInstallFiles',
107                                         'buildDepends', 'binaryDepends', 'binaryShims', 'binaryRecommends', 'binaryProvides', 'binaryConflicts', 'binaryBreaks',
108                                         'binaryReplaces', 'binaryBreaksReplaces',
109                                         'alternatives', 'cmakeParameters', 'automakeParameters'):
110                                 if option in self.config:
111                                         autoDebuildConfig[option] = self.config[option]
112                         # create Debian files
113                         os.chdir(self.sourceFolder)
114                         if os.path.isdir('debian'): # clean previous build attempts
115                                 shutil.rmtree('debian')
116                         files = auto_debuild.createDebianFiles(autoDebuildConfig)
117                         # build package(s)
118                         auto_debuild.buildDebianPackage(autoDebuildConfig)
119                         # install package(s)
120                         if self.config.get('debInstall', True):
121                                 subprocess.check_call(['sudo', 'dpkg', '--install'] + files)
122
123 except ImportError:
124         #print "auto_debuild not found, disabling auto-debuild system"
125         pass