Nicer error if windows prefix is not configured when it is required.
[cdist.git] / cdist / target.py
1 #
2 #    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
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., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 import tempfile
19 import os
20
21 import globals
22 from util import *
23
24 class Target(object):
25     """
26     platform -- platform string (e.g. 'windows', 'linux', 'osx')
27     directory -- directory to work in; if None we will use a temporary directory
28     Temporary directories will be removed after use; specified directories will not.
29     """
30     def __init__(self, platform, directory=None):
31         self.platform = platform
32         self.parallel = int(globals.config.get('parallel'))
33
34         # self.directory is the working directory
35         if directory is None:
36             self.directory = tempfile.mkdtemp('', 'tmp', globals.config.get('temporary_directory'))
37             self.rmdir = True
38         else:
39             self.directory = directory
40             self.rmdir = False
41
42         # Environment variables that we will use when we call cscripts
43         self.variables = {}
44         self.debug = False
45
46     def package(self, project, checkout):
47         tree = globals.trees.get(project, checkout, self)
48         tree.build()
49         return tree.call('package', tree.version), tree.git_commit
50
51     def test(self, tree):
52         tree.build()
53         return tree.call('test')
54
55     def set(self, a, b):
56         self.variables[a] = b
57
58     def unset(self, a):
59         del(self.variables[a])
60
61     def get(self, a):
62         return self.variables[a]
63
64     def append_with_space(self, k, v):
65         if (not k in self.variables) or len(self.variables[k]) == 0:
66             self.variables[k] = '"%s"' % v
67         else:
68             e = self.variables[k]
69             if e[0] == '"' and e[-1] == '"':
70                 self.variables[k] = '"%s %s"' % (e[1:-1], v)
71             else:
72                 self.variables[k] = '"%s %s"' % (e, v)
73
74     def variables_string(self, escaped_quotes=False):
75         e = ''
76         for k, v in self.variables.iteritems():
77             if escaped_quotes:
78                 v = v.replace('"', '\\"')
79             e += '%s=%s ' % (k, v)
80         return e
81
82     def cleanup(self):
83         if self.rmdir:
84             rmtree(self.directory)
85
86 #
87 # Windows
88 #
89
90 class WindowsTarget(Target):
91     def __init__(self, bits, directory=None):
92         super(WindowsTarget, self).__init__('windows', directory)
93         self.bits = bits
94
95         if globals.config.get('windows_environment_prefix') is None:
96             raise Error('windows prefix not configured')
97         self.windows_prefix = '%s/%d' % (globals.config.get('windows_environment_prefix'), self.bits)
98         if not os.path.exists(self.windows_prefix):
99             raise Error('windows prefix %s does not exist' % self.windows_prefix)
100
101         if self.bits == 32:
102             self.mingw_name = 'i686'
103         else:
104             self.mingw_name = 'x86_64'
105
106         self.mingw_path = '%s/%d/bin' % (globals.config.get('mingw_prefix'), self.bits)
107         self.mingw_prefixes = ['/%s/%d' % (globals.config.get('mingw_prefix'), self.bits),
108                                '%s/%d/%s-w64-mingw32' % (globals.config.get('mingw_prefix'), bits, self.mingw_name)]
109
110         self.set('PKG_CONFIG_LIBDIR', '%s/lib/pkgconfig' % self.windows_prefix)
111         self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/bin/pkgconfig' % (self.directory, self.directory))
112         self.set('PATH', '%s/bin:%s:%s' % (self.windows_prefix, self.mingw_path, os.environ['PATH']))
113         self.set('CC', '%s-w64-mingw32-gcc' % self.mingw_name)
114         self.set('CXX', '%s-w64-mingw32-g++' % self.mingw_name)
115         self.set('LD', '%s-w64-mingw32-ld' % self.mingw_name)
116         self.set('RANLIB', '%s-w64-mingw32-ranlib' % self.mingw_name)
117         self.set('WINRC', '%s-w64-mingw32-windres' % self.mingw_name)
118         cxx = '-I%s/include -I%s/include' % (self.windows_prefix, self.directory)
119         link = '-L%s/lib -L%s/lib' % (self.windows_prefix, self.directory)
120         for p in self.mingw_prefixes:
121             cxx += ' -I%s/include' % p
122             link += ' -L%s/lib' % p
123         self.set('CXXFLAGS', '"%s"' % cxx)
124         self.set('CPPFLAGS', '')
125         self.set('LINKFLAGS', '"%s"' % link)
126         self.set('LDFLAGS', '"%s"' % link)
127
128     def command(self, c):
129         log('host -> %s' % c)
130         command('%s %s' % (self.variables_string(), c))
131
132 class LinuxTarget(Target):
133     """Parent for Linux targets"""
134     def __init__(self, distro, version, bits, directory=None):
135         super(LinuxTarget, self).__init__('linux', directory)
136         self.distro = distro
137         self.version = version
138         self.bits = bits
139
140         self.set('CXXFLAGS', '-I%s/include' % self.directory)
141         self.set('CPPFLAGS', '')
142         self.set('LINKFLAGS', '-L%s/lib' % self.directory)
143         self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:/usr/local/lib/pkgconfig' % self.directory)
144         self.set('PATH', '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin')
145
146 class ChrootTarget(LinuxTarget):
147     """Build in a chroot"""
148     def __init__(self, distro, version, bits, directory=None):
149         super(ChrootTarget, self).__init__(distro, version, bits, directory)
150         # e.g. ubuntu-14.04-64
151         if self.version is not None and self.bits is not None:
152             self.chroot = '%s-%s-%d' % (self.distro, self.version, self.bits)
153         else:
154             self.chroot = self.distro
155         # e.g. /home/carl/Environments/ubuntu-14.04-64
156         self.chroot_prefix = '%s/%s' % (globals.config.get('linux_chroot_prefix'), self.chroot)
157
158     def command(self, c):
159         command('%s schroot -c %s -p -- %s' % (self.variables_string(), self.chroot, c))
160
161
162 class HostTarget(LinuxTarget):
163     """Build directly on the host"""
164     def __init__(self, distro, version, bits, directory=None):
165         super(HostTarget, self).__init__(distro, version, bits, directory)
166
167     def command(self, c):
168         command('%s %s' % (self.variables_string(), c))
169
170 #
171 # OS X
172 #
173
174 class OSXTarget(Target):
175     def __init__(self, directory=None):
176         super(OSXTarget, self).__init__('osx', directory)
177         self.sdk = globals.config.get('osx_sdk')
178         self.sdk_prefix = globals.config.get('osx_sdk_prefix')
179
180     def command(self, c):
181         command('%s %s' % (self.variables_string(False), c))
182
183
184 class OSXSingleTarget(OSXTarget):
185     def __init__(self, bits, directory=None):
186         super(OSXSingleTarget, self).__init__(directory)
187         self.bits = bits
188
189         if bits == 32:
190             arch = 'i386'
191         else:
192             arch = 'x86_64'
193
194         flags = '-isysroot %s/MacOSX%s.sdk -arch %s' % (self.sdk_prefix, self.sdk, arch)
195         enviro = '%s/%d' % (globals.config.get('osx_environment_prefix'), bits)
196
197         # Environment variables
198         self.set('CFLAGS', '"-I%s/include -I%s/include %s"' % (self.directory, enviro, flags))
199         self.set('CPPFLAGS', '')
200         self.set('CXXFLAGS', '"-I%s/include -I%s/include %s"' % (self.directory, enviro, flags))
201         self.set('LDFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.directory, enviro, flags))
202         self.set('LINKFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.directory, enviro, flags))
203         self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/lib/pkgconfig:/usr/lib/pkgconfig' % (self.directory, enviro))
204         self.set('PATH', '$PATH:/usr/bin:/sbin:/usr/local/bin:%s/bin' % enviro)
205         self.set('MACOSX_DEPLOYMENT_TARGET', globals.config.get('osx_sdk'))
206
207     def package(self, project, checkout):
208         raise Error('cannot package non-universal OS X versions')
209
210
211 class OSXUniversalTarget(OSXTarget):
212     def __init__(self, directory=None):
213         super(OSXUniversalTarget, self).__init__(directory)
214
215     def package(self, project, checkout):
216
217         for b in [32, 64]:
218             target = OSXSingleTarget(b, os.path.join(self.directory, '%d' % b))
219             tree = globals.trees.get(project, checkout, target)
220             tree.build()
221
222         tree = globals.trees.get(project, checkout, self)
223         with TreeDirectory(tree):
224             return tree.call('package', tree.version), tree.git_commit
225
226 class SourceTarget(Target):
227     """Build a source .tar.bz2"""
228     def __init__(self):
229         super(SourceTarget, self).__init__('source')
230
231     def command(self, c):
232         log('host -> %s' % c)
233         command('%s %s' % (self.variables_string(), c))
234
235     def cleanup(self):
236         rmtree(self.directory)
237
238     def package(self, project, checkout):
239         tree = globals.trees.get(project, checkout, self)
240         with TreeDirectory(tree):
241             name = read_wscript_variable(os.getcwd(), 'APPNAME')
242             command('./waf dist')
243             return os.path.abspath('%s-%s.tar.bz2' % (name, tree.version)), tree.git_commit
244
245 class TestTarget(Target):
246     def __init__(self):
247         super(TestTarget, self).__init__('test', os.path.realpath('test'))
248
249 # @param s Target string:
250 #       windows-{32,64}
251 #    or ubuntu-version-{32,64}
252 #    or debian-version-{32,64}
253 #    or centos-version-{32,64}
254 #    or osx-{32,64}
255 #    or source
256 # @param debug True to build with debugging symbols (where possible)
257 def factory(s, debug, work):
258     target = None
259     if s.startswith('windows-'):
260         target = WindowsTarget(int(s.split('-')[1]), work)
261     elif s.startswith('ubuntu-') or s.startswith('debian-') or s.startswith('centos-'):
262         p = s.split('-')
263         if len(p) != 3:
264             raise Error("Bad Linux target name `%s'; must be something like ubuntu-12.04-32 (i.e. distro-version-bits)" % s)
265         target = ChrootTarget(p[0], p[1], int(p[2]), work)
266     elif s == 'raspbian':
267         target = ChrootTarget(s, None, None, work)
268     elif s == 'host':
269         try:
270             f = open('/etc/fedora-release', 'r')
271             l = f.readline().strip().split()
272             if command_and_read('uname -m').read().strip() == 'x86_64':
273                 bits = 64
274             else:
275                 bits = 32
276             target = HostTarget("fedora", l[2], bits, work)
277         except Exception as e:
278             raise Error("could not identify distribution for `host' target (%s)" % e)
279     elif s.startswith('osx-'):
280         target = OSXSingleTarget(int(s.split('-')[1]), work)
281     elif s == 'osx':
282         if globals.command == 'build':
283             target = OSXSingleTarget(64, work)
284         else:
285             target = OSXUniversalTarget(work)
286     elif s == 'source':
287         target = SourceTarget()
288     elif s == 'test':
289         target = TestTarget()
290
291     if target is None:
292         raise Error("Bad target `%s'" % s)
293
294     target.debug = debug
295     return target