The GUI's Publish button now works.
[0compile.git] / support.py
blob6ff5a03724b703e918e65b360b91ea8ecb35548e
1 # Copyright (C) 2006, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import os, sys, tempfile, shutil, traceback
5 from xml.dom import minidom, XMLNS_NAMESPACE, Node
6 from os.path import join
8 from zeroinstall.injector import model
9 from zeroinstall.injector.model import Interface, Implementation, Dependency, EnvironmentBinding, escape
10 from zeroinstall.injector import namespaces, basedir, reader
11 from zeroinstall.injector.iface_cache import iface_cache
12 from zeroinstall import SafeException
13 from zeroinstall.injector import run
14 from zeroinstall.zerostore import Stores, NotStored
16 ENV_FILE = '0compile-env.xml'
17 XMLNS_0COMPILE = 'http://zero-install.sourceforge.net/2006/namespaces/0compile'
19 def lookup(id):
20 if id.startswith('/'):
21 if os.path.isdir(id):
22 return id
23 raise SafeException("Directory '%s' no longer exists. Try '0compile setup'" % id)
24 try:
25 return iface_cache.stores.lookup(id)
26 except NotStored, ex:
27 raise NotStored(str(ex) + "\nHint: try '0compile setup'")
29 def get_cached_iface_path(uri):
30 if uri.startswith('/'):
31 if not os.path.isfile(uri):
32 raise SafeException("Local source interface '%s' does not exist!" % uri)
33 return uri
34 else:
35 path = basedir.load_first_cache(namespaces.config_site, 'interfaces', escape(uri))
36 if path and os.path.isfile(path):
37 return path
38 raise SafeException("Interface '%s' not found in cache. Hint: try '0compile setup'" % uri)
40 def ensure_dir(d):
41 if os.path.isdir(d): return
42 if os.path.exists(d):
43 raise SafeException("'%s' exists, but is not a directory!" % d)
44 os.mkdir(d)
46 def find_in_path(prog):
47 for d in os.environ['PATH'].split(':'):
48 path = os.path.join(d, prog)
49 if os.path.isfile(path):
50 return path
51 return None
53 def spawn_and_check(prog, args):
54 status = os.spawnv(os.P_WAIT, prog, [prog] + args)
55 if status > 0:
56 raise SafeException("Program '%s' failed with exit code %d" % (prog, status))
57 elif status < 0:
58 raise SafeException("Program '%s' failed with signal %d" % (prog, -status))
60 def wait_for_child(child):
61 """Wait for child to exit and reap it. Throw an exception if it doesn't return success."""
62 pid, status = os.waitpid(child, 0)
63 assert pid == child
64 if os.WIFEXITED(status):
65 exit_code = os.WEXITSTATUS(status)
66 if exit_code == 0:
67 return
68 else:
69 raise SafeException('Command failed with exit status %d' % exit_code)
70 else:
71 raise SafeException('Command failed with signal %d' % WTERMSIG(status))
73 def get_env_doc():
74 if not os.path.isfile(ENV_FILE):
75 raise SafeException("Run 0compile from a directory containing a '%s' file" % ENV_FILE)
76 return minidom.parse(ENV_FILE)
78 def children(parent, uri, name):
79 """Yield all direct children with the given name."""
80 for x in parent.childNodes:
81 if x.nodeType == Node.ELEMENT_NODE and x.namespaceURI == uri and x.localName == name:
82 yield x
84 def spawn_maybe_sandboxed(readable, writable, tmpdir, prog, args):
85 child = os.fork()
86 if child == 0:
87 try:
88 try:
89 exec_maybe_sandboxed(readable, writable, tmpdir, prog, args)
90 except:
91 traceback.print_exc()
92 finally:
93 print >>sys.stderr, "Exec failed"
94 os._exit(1)
95 wait_for_child(child)
97 def exec_maybe_sandboxed(readable, writable, tmpdir, prog, args):
98 """execl prog, with (only) the 'writable' directories writable if sandboxing is available.
99 The readable directories will be readable, as well as various standard locations.
100 If no sandbox is available, run without a sandbox."""
102 USE_PLASH = 'USE_PLASH_0COMPILE'
104 assert prog.startswith('/')
105 _pola_run = find_in_path('pola-run')
107 if _pola_run is None:
108 print "Not using sandbox (plash not installed)"
109 use_plash = False
110 else:
111 use_plash = os.environ.get(USE_PLASH, '').lower() or 'not set'
112 if use_plash in ('not set', 'false'):
113 print "Not using plash: $%s is %s" % (USE_PLASH, use_plash)
114 use_plash = False
115 elif use_plash == 'true':
116 use_plash = True
117 else:
118 raise Exception('$%s must be "true" or "false", not "%s"' % (USE_PLASH, use_plash))
120 if not use_plash:
121 os.execlp(prog, prog, *args)
123 print "Using plash to sandbox the build..."
125 # We have pola-shell :-)
126 pola_args = ['--prog', prog, '-B']
127 for a in args:
128 pola_args += ['-a', a]
129 for r in readable:
130 pola_args += ['-f', r]
131 for w in writable:
132 pola_args += ['-fw', w]
133 pola_args += ['-tw', '/tmp', tmpdir]
134 os.environ['TMPDIR'] = '/tmp'
135 os.execl(_pola_run, _pola_run, *pola_args)
137 class BuildEnv(object):
138 __slots__ = ['doc', 'interface', 'interfaces', 'root_impl', 'srcdir',
139 'download_base_url', 'distdir', 'metadir', 'local_iface_file', 'iface_name']
141 def __init__(self):
142 self.doc = get_env_doc()
143 root = self.doc.documentElement
144 self.interface = root.getAttributeNS(None, 'interface')
145 assert self.interface
147 self.download_base_url = root.getAttributeNS(None, 'download-base-url')
149 self.interfaces = {}
150 for child in children(root, XMLNS_0COMPILE, 'interface'):
151 iface = self.interface_from_elem(child)
152 assert iface.uri not in self.interfaces
153 self.interfaces[iface.uri] = iface
155 assert self.interface in self.interfaces
156 self.root_impl = self.chosen_impl(self.interface)
158 if os.path.isdir('src'):
159 self.srcdir = os.path.realpath('src')
160 else:
161 self.srcdir = lookup(self.root_impl.id)
163 self.iface_name = os.path.basename(self.interface)
164 if self.iface_name.endswith('.xml'):
165 self.iface_name = self.iface_name[:-4]
166 self.iface_name = self.iface_name.replace(' ', '-')
167 distdir_name = '%s-%s' % (self.iface_name.lower(), self.root_impl.get_version())
168 assert '/' not in distdir_name
169 self.distdir = os.path.realpath(distdir_name)
171 metadir = self.root_impl.metadata.get('metadir', None)
172 if metadir is None:
173 metadir = '0install'
174 assert not metadir.startswith('/')
175 self.metadir = join(self.distdir, metadir)
176 self.local_iface_file = join(self.metadir, '%s.xml' % self.iface_name)
178 def chosen_impl(self, uri):
179 assert uri in self.interfaces
180 impls = self.interfaces[uri].implementations.values()
181 assert len(impls) == 1
182 return impls[0]
184 def interface_from_elem(self, elem):
185 uri = elem.getAttributeNS(None, 'uri')
187 iface = Interface(uri)
189 impl_elems = list(children(elem, XMLNS_0COMPILE, 'implementation'))
190 assert len(impl_elems) == 1
191 impl_elem = impl_elems[0]
193 impl = iface.get_impl(impl_elem.getAttributeNS(None, 'id'))
194 impl.main = impl_elem.getAttributeNS(None, 'main') or None
195 impl.version = reader.parse_version(impl_elem.getAttributeNS(None, 'version'))
197 for x in impl_elem.attributes.values():
198 impl.metadata[x.name] = x.value
200 for dep_elem in children(impl_elem, XMLNS_0COMPILE, 'requires'):
201 dep_uri = dep_elem.getAttributeNS(None, 'interface')
202 dep = Dependency(dep_uri)
203 impl.dependencies[dep_uri] = dep
204 for x in dep_elem.attributes.values():
205 dep.metadata[x.name] = x.value
207 for e in children(dep_elem, XMLNS_0COMPILE, 'environment'):
208 env = EnvironmentBinding(e.getAttributeNS(None, 'name'),
209 e.getAttributeNS(None, 'insert'),
210 e.getAttributeNS(None, 'default') or None)
211 dep.bindings.append(env)
213 return iface
215 local_download_iface = property(lambda self: '%s-%s.xml' % (self.iface_name, self.root_impl.get_version()))
217 def depth(node):
218 root = node.ownerDocument.documentElement
219 depth = 0
220 while node and node is not root:
221 node = node.parentNode
222 depth += 1
223 return depth
225 if hasattr(model, 'format_version'):
226 format_version = model.format_version
227 parse_version = model.parse_version
228 else:
229 def format_version(v):
230 return '.'.join(v)
231 parse_version = reader.parse_version
233 def parse_bool(s):
234 if s == 'true': return True
235 if s == 'false': return False
236 raise SafeException('Expected "true" or "false" but got "%s"' % s)