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
11 from zeroinstall
.injector
import basedir
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')))
27 if id.startswith('/'):
30 raise SafeException("Directory '%s' no longer exists. Try '0compile setup'" % id)
32 return iface_cache
.stores
.lookup(id)
34 raise NotStored(str(ex
) + "\nHint: try '0compile setup'")
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
)
43 path
= basedir
.load_first_cache(namespaces
.config_site
, 'interfaces', escape(uri
))
44 if path
and os
.path
.isfile(path
):
46 raise SafeException("Interface '%s' not found in cache. Hint: try '0compile setup'" % uri
)
49 if os
.path
.isdir(d
): return
51 raise SafeException("'%s' exists, but is not a directory!" % 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
):
61 def spawn_and_check(prog
, args
):
62 status
= os
.spawnv(os
.P_WAIT
, prog
, [prog
] + args
)
64 raise SafeException("Program '%s' failed with exit code %d" % (prog
, status
))
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)
72 if os
.WIFEXITED(status
):
73 exit_code
= os
.WEXITSTATUS(status
)
77 raise SafeException('Command failed with exit status %d' % exit_code
)
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
:
87 def spawn_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
):
92 exec_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
)
96 print >>sys
.stderr
, "Exec failed"
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)"
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
)
118 elif use_plash
== 'true':
121 raise Exception('$%s must be "true" or "false", not "%s"' % (USE_PLASH
, 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']
131 pola_args
+= ['-a', a
]
133 pola_args
+= ['-f', r
]
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
)
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',
152 interface
= property(lambda self
: self
.selections
.interface
)
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'
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]
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')
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 ""))
203 root
= node
.ownerDocument
.documentElement
205 while node
and node
is not root
:
206 node
= node
.parentNode
210 if hasattr(model
, 'format_version'):
211 format_version
= model
.format_version
212 parse_version
= model
.parse_version
214 def format_version(v
):
216 parse_version
= reader
.parse_version
219 if s
== 'true': return True
220 if s
== 'false': return False
221 raise SafeException('Expected "true" or "false" but got "%s"' % s
)