build automake packages into multiarch paths and make sure the commands we run don...
[auto-debuild.git] / auto_debuild.py
1 #!/usr/bin/python
2 import os, stat, time, subprocess, sys
3 from collections import OrderedDict
4
5 # abstract representation of rules file
6 class RulesFile:
7         def __init__(self):
8                 self.env = []
9                 self.dh = []
10                 self.rules = OrderedDict()
11         
12         def write(self, f):
13                 print >>f, "#!/usr/bin/make -f"
14                 print >>f, ""
15                 print >>f, "%:"
16                 print >>f, '\t'+' '.join(self.env)+' dh $@',' '.join(self.dh)
17                 for rule in self.rules:
18                         print >>f, ""
19                         print >>f, "override_dh_"+rule+":"
20                         for line in self.rules[rule]:
21                                 print >>f, "\t"+line
22
23 # build-system specific part of rules file
24 def cmakeRules(config):
25         r = RulesFile()
26         r.dh += ["--buildsystem=cmake", "--builddirectory=build.dir"] # dh parameters: "build" is not a good idea, as that's also the name of a target...
27         r.rules['auto_configure'] = [
28                 'mkdir -p build.dir',
29                 "cd build.dir && cmake .. -DCMAKE_INSTALL_PREFIX=/usr "+' '.join(config.get('cmakeParameters', []))
30         ]
31         r.rules['auto_clean'] = ['rm -f build.dir/CMakeCache.txt'] # clean old cmake cache
32         return r
33
34 def automakeRules(config):
35         r = RulesFile()
36         r.dh += ["--buildsystem=autoconf"]
37         r.rules['auto_configure'] = [
38                 '@dpkg-architecture -qDEB_BUILD_GNU_TYPE > /dev/null', # make sure this command runs successfully (and hope it does so again)
39                 '@dpkg-architecture -qDEB_BUILD_MULTIARCH > /dev/null', # make sure this command runs successfully (and hope it does so again)
40                 './configure --build=$$(dpkg-architecture -qDEB_BUILD_GNU_TYPE) ' +
41                         '--prefix=/usr --includedir=/usr/include --mandir=/usr/share/man --infodir=/usr/share/info ' +
42                         '--libdir=/usr/lib/$$(dpkg-architecture -qDEB_BUILD_MULTIARCH) '+
43                         '--libexecdir=/usr/lib/$$(dpkg-architecture -qDEB_BUILD_MULTIARCH) '+
44                         '--sysconfdir=/etc --localstatedir=/var ' +
45                         ' '.join(config.get('automakeParameters', []))
46         ]
47         r.rules['auto_clean'] = ['rm -f config.status'] # do not re-use old configuration
48         return r
49
50 # utility functions
51 def commandInBuildEnv(config, command):
52         schroot = config.get('schroot')
53         if schroot is not None: command = ['schroot', '-c', schroot, '--'] + command
54         return command
55
56 def getArchitecture(config):
57         cmd = commandInBuildEnv(config, ['dpkg-architecture', '-qDEB_BUILD_ARCH'])
58         p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
59         res = p.communicate()[0] # get only stdout
60         if p.returncode != 0: raise Exception("Querying dpkg for the architecture failed")
61         return res[0:len(res)-1] # chop of the \n at the end
62
63 def writeDebList(list):
64         return ', '.join(list)
65
66 # actual work functions
67 def createDebianFiles(config):
68         sourceName = config['sourceName']
69         binaryName = config.get('binaryName', sourceName+'-local')
70         name = config.get('name', os.getlogin())
71         email = config.get('email', os.getlogin()+'@'+os.uname()[1]) # user@hostname
72         debDir = os.path.expanduser(config['debDir'])
73         buildSystem = config['buildSystem']
74         version = config['version']
75         dbgPackage = config.get('dbgPackage', False)
76         packageArchitecture = config.get('architecture', 'any')
77         # we return the list of files generated
78         arch = getArchitecture(config)
79         files = []
80         # source format file
81         if not os.path.exists('debian/source'): os.mkdir('debian/source')
82         with open('debian/source/format', 'w') as f:
83                 print >>f, "3.0 (native)"
84         # compat file
85         with open('debian/compat', 'w') as f:
86                 print >>f, "9"
87         # copyright file
88         with open('debian/copyright', 'w') as f:
89                 print >>f, "Auto-generated by auto-debuild, not suited for distribution"
90         # changelog file
91         with open('debian/changelog', 'w') as f:
92                 print >>f, sourceName,"("+version+")","UNRELEASED; urgency=low"
93                 print >>f, ""
94                 print >>f, "  * Auto-generated by auto-debuild"
95                 print >>f, ""
96                 print >>f, " --",name,"<"+email+">  "+time.strftime('%a, %d %b %Y %H:%M:%S %z')
97         # control file
98         with open('debian/control', 'w') as f:
99                 print >>f, "Source:",sourceName
100                 print >>f, "Section:",config.get('section', 'misc')
101                 print >>f, "Priority: extra"
102                 print >>f, "Maintainer: %s <%s>" % (name, email)
103                 print >>f, "Build-Depends:",writeDebList(["debhelper (>= 9)"] + config.get('buildDepends', []))
104                 print >>f, "Standards-Version: 3.9.3"
105                 print >>f, ""
106                 print >>f, "Package:",binaryName
107                 print >>f, "Architecture:",packageArchitecture
108                 print >>f, "Depends:",writeDebList(["${shlibs:Depends}", "${misc:Depends}"] + config.get('binaryDepends', []))
109                 print >>f, "Provides:",writeDebList(config.get('binaryProvides', [sourceName]))
110                 print >>f, "Description:",sourceName,"(auto-debuild)"
111                 print >>f, " Package auto-generated by auto-debuild."
112                 files.append(os.path.join(debDir, "%s_%s_%s.deb" % (binaryName, version, arch)))
113                 if dbgPackage:
114                         print >>f, ""
115                         print >>f, "Package:",binaryName+"-dbg"
116                         print >>f, "Architecture:",packageArchitecture
117                         print >>f, "Depends:",writeDebList(["${misc:Depends}", binaryName+" (= ${binary:Version})"])
118                         print >>f, "Description:",sourceName,"debug smbols (auto-debuild)"
119                         print >>f, " Package containing debug symbols for "+sourceName+", auto-generated by auto-debuild."
120                         files.append(os.path.join(debDir, "%s-dbg_%s_%s.deb" % (binaryName, version, arch)))
121         # rules file: build system specific
122         with open('debian/rules', 'w') as f:
123                 # get rule file for build system: may only touch auto_config and auto_clean rules and the dh options
124                 if buildSystem == 'cmake':
125                         r = cmakeRules(config)
126                 elif buildSystem == 'automake':
127                         r = automakeRules(config)
128                 else:
129                         raise Exception("Invalid build system "+buildSystem)
130                 # global rules
131                 r.env += ["DEB_BUILD_OPTIONS='parallel=2'"]
132                 if not dbgPackage: r.env += ["DEB_CFLAGS_APPEND='-g0'", "DEB_CXXFLAGS_APPEND='-g0'"] # disable debug information
133                 r.dh += ['--parallel']
134                 r.rules['builddeb'] = ['dh_builddeb --destdir='+debDir] # passing this gobally to dh results in weird problems (like stuff being installed there, and not in the package...)
135                 r.rules['auto_test'] = []
136                 r.rules['auto_install'] = ['dh_auto_install --destdir=debian/'+binaryName] # install everything into the binary package
137                 # for debug packages
138                 if dbgPackage:
139                         r.rules['strip'] = ['dh_strip --dbg-package='+binaryName+"-dbg"]
140                         r.rules['installdocs'] = ['dh_installdocs --link-doc='+binaryName]
141                 # dump it to a file
142                 r.write(f)
143         mode = os.stat('debian/rules').st_mode
144         os.chmod('debian/rules', mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
145         # return list of files affected
146         return files
147
148 def buildDebianPackage(config):
149         commands = ['dpkg-checkbuilddeps', 'debian/rules clean', 'debian/rules build', 'fakeroot debian/rules binary', 'debian/rules clean']
150         command = ['bash', '-c', ' && '.join(commands)]
151         subprocess.check_call(commandInBuildEnv(config, command))
152
153 # if we are called directly as script
154 if __name__ == "__main__":
155         # generate debian files
156         import imp
157         config = imp.load_source('config', 'debian/auto-debuild.conf').__dict__
158         os.remove('debian/auto-debuild.confc')
159         files = createDebianFiles(config)
160         # check if a file is overwritten
161         for file in files:
162                 if os.path.exists(file):
163                         if raw_input("Do you want to overwrite %s (y/N)? " % file).lower() != "y":
164                                 sys.exit(1)
165         # run compilation
166         buildDebianPackage(config)
167         # install files
168         subprocess.check_call(['sudo', 'dpkg', '--install'] + files)