Don't second-guess version choices
[0compile.git] / support.py
blob617ac7d5af655e4361a7cc8d6664e6d8b90829e3
1 # Copyright (C) 2006, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import os, sys, tempfile, shutil, traceback
5 from os.path import join
7 from zeroinstall.injector import model, selections, qdom
8 from zeroinstall.injector.model import Interface, Implementation, EnvironmentBinding, escape
9 from zeroinstall.injector import namespaces, reader
10 try:
11 from zeroinstall.injector import basedir
12 except ImportError:
13 from zeroinstall.support import basedir
15 from zeroinstall.injector.iface_cache import iface_cache
16 from zeroinstall import SafeException
17 from zeroinstall.injector import run
18 from zeroinstall.zerostore import Stores, Store, NotStored
20 ENV_FILE = '0compile-env.xml'
21 XMLNS_0COMPILE = 'http://zero-install.sourceforge.net/2006/namespaces/0compile'
23 if os.path.isdir('dependencies'):
24 iface_cache.stores.stores.append(Store(os.path.realpath('dependencies')))
26 def lookup(id):
27 if id.startswith('/'):
28 if os.path.isdir(id):
29 return id
30 raise SafeException("Directory '%s' no longer exists. Try '0compile setup'" % id)
31 try:
32 return iface_cache.stores.lookup(id)
33 except NotStored, ex:
34 raise NotStored(str(ex) + "\nHint: try '0compile setup'")
36 # No longer used
37 def get_cached_iface_path(uri):
38 if uri.startswith('/'):
39 if not os.path.isfile(uri):
40 raise SafeException("Local source interface '%s' does not exist!" % uri)
41 return uri
42 else:
43 path = basedir.load_first_cache(namespaces.config_site, 'interfaces', escape(uri))
44 if path and os.path.isfile(path):
45 return path
46 raise SafeException("Interface '%s' not found in cache. Hint: try '0compile setup'" % uri)
48 def ensure_dir(d):
49 if os.path.isdir(d): return
50 if os.path.exists(d):
51 raise SafeException("'%s' exists, but is not a directory!" % d)
52 os.mkdir(d)
54 def find_in_path(prog):
55 for d in os.environ['PATH'].split(':'):
56 path = os.path.join(d, prog)
57 if os.path.isfile(path):
58 return path
59 return None
61 def spawn_and_check(prog, args):
62 status = os.spawnv(os.P_WAIT, prog, [prog] + args)
63 if status > 0:
64 raise SafeException("Program '%s' failed with exit code %d" % (prog, status))
65 elif status < 0:
66 raise SafeException("Program '%s' failed with signal %d" % (prog, -status))
68 def wait_for_child(child):
69 """Wait for child to exit and reap it. Throw an exception if it doesn't return success."""
70 pid, status = os.waitpid(child, 0)
71 assert pid == child
72 if os.WIFEXITED(status):
73 exit_code = os.WEXITSTATUS(status)
74 if exit_code == 0:
75 return
76 else:
77 raise SafeException('Command failed with exit status %d' % exit_code)
78 else:
79 raise SafeException('Command failed with signal %d' % WTERMSIG(status))
81 def children(parent, uri, name):
82 """Yield all direct children with the given name."""
83 for x in parent.childNodes:
84 if x.nodeType == Node.ELEMENT_NODE and x.namespaceURI == uri and x.localName == name:
85 yield x
87 def spawn_maybe_sandboxed(readable, writable, tmpdir, prog, args):
88 child = os.fork()
89 if child == 0:
90 try:
91 try:
92 exec_maybe_sandboxed(readable, writable, tmpdir, prog, args)
93 except:
94 traceback.print_exc()
95 finally:
96 print >>sys.stderr, "Exec failed"
97 os._exit(1)
98 wait_for_child(child)
100 def exec_maybe_sandboxed(readable, writable, tmpdir, prog, args):
101 """execl prog, with (only) the 'writable' directories writable if sandboxing is available.
102 The readable directories will be readable, as well as various standard locations.
103 If no sandbox is available, run without a sandbox."""
105 USE_PLASH = 'USE_PLASH_0COMPILE'
107 assert prog.startswith('/')
108 _pola_run = find_in_path('pola-run')
110 if _pola_run is None:
111 print "Not using sandbox (plash not installed)"
112 use_plash = False
113 else:
114 use_plash = os.environ.get(USE_PLASH, '').lower() or 'not set'
115 if use_plash in ('not set', 'false'):
116 print "Not using plash: $%s is %s" % (USE_PLASH, use_plash)
117 use_plash = False
118 elif use_plash == 'true':
119 use_plash = True
120 else:
121 raise Exception('$%s must be "true" or "false", not "%s"' % (USE_PLASH, use_plash))
123 if not use_plash:
124 os.execlp(prog, prog, *args)
126 print "Using plash to sandbox the build..."
128 # We have pola-shell :-)
129 pola_args = ['--prog', prog, '-B']
130 for a in args:
131 pola_args += ['-a', a]
132 for r in readable:
133 pola_args += ['-f', r]
134 for w in writable:
135 pola_args += ['-fw', w]
136 pola_args += ['-tw', '/tmp', tmpdir]
137 os.environ['TMPDIR'] = '/tmp'
138 os.execl(_pola_run, _pola_run, *pola_args)
140 def get_arch_name():
141 uname = os.uname()
142 target_os, target_machine = uname[0], uname[-1]
143 if target_machine in ('i585', 'i686'):
144 target_machine = 'i486' # (sensible default)
145 return target_os + '-' + target_machine
147 class BuildEnv(object):
148 __slots__ = ['doc', 'selections', 'root_impl', 'srcdir', 'version_modifier',
149 'download_base_url', 'distdir', 'metadir', 'local_iface_file', 'iface_name',
150 'target_arch']
152 interface = property(lambda self: self.selections.interface)
154 def __init__(self):
155 if not os.path.isfile(ENV_FILE):
156 raise SafeException("Run 0compile from a directory containing a '%s' file" % ENV_FILE)
157 self.doc = qdom.parse(file(ENV_FILE))
158 if self.doc.name == 'build-environment':
159 raise SafeException(("Sorry, this %s file is in an old format that is no longer supported. "
160 "Please delete it and try again.") % os.path.abspath(ENV_FILE))
161 self.selections = selections.Selections(self.doc)
163 self.download_base_url = self.doc.getAttribute(XMLNS_0COMPILE + ' download-base-url')
165 self.version_modifier = self.doc.getAttribute(XMLNS_0COMPILE + ' version-modifier')
167 self.root_impl = self.selections.selections[self.interface]
169 if os.path.isdir('src'):
170 self.srcdir = os.path.realpath('src')
171 if not self.version_modifier:
172 self.version_modifier = '-1'
173 else:
174 self.srcdir = lookup(self.root_impl.id)
176 self.target_arch = get_arch_name()
178 self.iface_name = os.path.basename(self.interface)
179 if self.iface_name.endswith('.xml'):
180 self.iface_name = self.iface_name[:-4]
181 self.iface_name = self.iface_name.replace(' ', '-')
182 if self.iface_name.endswith('-src'):
183 self.iface_name = self.iface_name[:-4]
184 uname = os.uname()
185 distdir_name = '%s-%s-%s%s' % (self.iface_name.lower(), self.target_arch.lower(), self.root_impl.version, self.version_modifier or "")
186 assert '/' not in distdir_name
187 self.distdir = os.path.realpath(distdir_name)
189 metadir = self.doc.getAttribute(XMLNS_0COMPILE + ' metadir')
190 if metadir is None:
191 metadir = '0install'
192 assert not metadir.startswith('/')
193 self.metadir = join(self.distdir, metadir)
194 self.local_iface_file = join(self.metadir, '%s.xml' % self.iface_name)
196 def chosen_impl(self, uri):
197 assert uri in self.selections.selections
198 return self.selections.selections[uri]
200 local_download_iface = property(lambda self: '%s-%s%s.xml' % (self.iface_name, self.root_impl.version, self.version_modifier or ""))
202 def depth(node):
203 root = node.ownerDocument.documentElement
204 depth = 0
205 while node and node is not root:
206 node = node.parentNode
207 depth += 1
208 return depth
210 if hasattr(model, 'format_version'):
211 format_version = model.format_version
212 parse_version = model.parse_version
213 else:
214 def format_version(v):
215 return '.'.join(v)
216 parse_version = reader.parse_version
218 def parse_bool(s):
219 if s == 'true': return True
220 if s == 'false': return False
221 raise SafeException('Expected "true" or "false" but got "%s"' % s)