don't hard-code the path to the python interpreter
[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', 'autogen'):
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