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
, 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'
20 if id.startswith('/'):
23 raise SafeException("Directory '%s' no longer exists. Try '0compile setup'" % id)
25 return iface_cache
.stores
.lookup(id)
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
)
35 path
= basedir
.load_first_cache(namespaces
.config_site
, 'interfaces', escape(uri
))
36 if path
and os
.path
.isfile(path
):
38 raise SafeException("Interface '%s' not found in cache. Hint: try '0compile setup'" % uri
)
41 if os
.path
.isdir(d
): return
43 raise SafeException("'%s' exists, but is not a directory!" % 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
):
53 def spawn_and_check(prog
, args
):
54 status
= os
.spawnv(os
.P_WAIT
, prog
, [prog
] + args
)
56 raise SafeException("Program '%s' failed with exit code %d" % (prog
, status
))
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)
64 if os
.WIFEXITED(status
):
65 exit_code
= os
.WEXITSTATUS(status
)
69 raise SafeException('Command failed with exit status %d' % exit_code
)
71 raise SafeException('Command failed with signal %d' % WTERMSIG(status
))
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
:
84 def spawn_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
):
89 exec_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
)
93 print >>sys
.stderr
, "Exec failed"
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)"
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
)
115 elif use_plash
== 'true':
118 raise Exception('$%s must be "true" or "false", not "%s"' % (USE_PLASH
, 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']
128 pola_args
+= ['-a', a
]
130 pola_args
+= ['-f', r
]
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']
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')
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')
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)
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
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 if hasattr(model
, 'InterfaceDependency'):
203 dep
= model
.InterfaceDependency(dep_uri
)
204 impl
.requires
.append(dep
)
206 dep
= model
.Dependency(dep_uri
)
207 impl
.dependencies
[dep_uri
] = dep
208 for x
in dep_elem
.attributes
.values():
209 dep
.metadata
[x
.name
] = x
.value
211 for e
in children(dep_elem
, XMLNS_0COMPILE
, 'environment'):
212 env
= EnvironmentBinding(e
.getAttributeNS(None, 'name'),
213 e
.getAttributeNS(None, 'insert'),
214 e
.getAttributeNS(None, 'default') or None)
215 dep
.bindings
.append(env
)
219 local_download_iface
= property(lambda self
: '%s-%s.xml' % (self
.iface_name
, self
.root_impl
.get_version()))
222 root
= node
.ownerDocument
.documentElement
224 while node
and node
is not root
:
225 node
= node
.parentNode
229 if hasattr(model
, 'format_version'):
230 format_version
= model
.format_version
231 parse_version
= model
.parse_version
233 def format_version(v
):
235 parse_version
= reader
.parse_version
238 if s
== 'true': return True
239 if s
== 'false': return False
240 raise SafeException('Expected "true" or "false" but got "%s"' % s
)