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