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')))
21 if id.startswith('/'):
24 raise SafeException("Directory '%s' no longer exists. Try '0compile setup'" % id)
26 return iface_cache
.stores
.lookup(id)
28 raise NotStored(str(ex
) + "\nHint: try '0compile setup'")
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
)
37 path
= basedir
.load_first_cache(namespaces
.config_site
, 'interfaces', escape(uri
))
38 if path
and os
.path
.isfile(path
):
40 raise SafeException("Interface '%s' not found in cache. Hint: try '0compile setup'" % uri
)
43 if os
.path
.isdir(d
): return
45 raise SafeException("'%s' exists, but is not a directory!" % 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
):
55 def spawn_and_check(prog
, args
):
56 status
= os
.spawnv(os
.P_WAIT
, prog
, [prog
] + args
)
58 raise SafeException("Program '%s' failed with exit code %d" % (prog
, status
))
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)
66 if os
.WIFEXITED(status
):
67 exit_code
= os
.WEXITSTATUS(status
)
71 raise SafeException('Command failed with exit status %d' % exit_code
)
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
:
81 def spawn_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
):
86 exec_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
)
90 print >>sys
.stderr
, "Exec failed"
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)"
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
)
112 elif use_plash
== 'true':
115 raise Exception('$%s must be "true" or "false", not "%s"' % (USE_PLASH
, 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']
125 pola_args
+= ['-a', a
]
127 pola_args
+= ['-f', r
]
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',
139 interface
= property(lambda self
: self
.selections
.interface
)
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'
158 self
.srcdir
= lookup(self
.root_impl
.id)
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]
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')
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 ""))
192 root
= node
.ownerDocument
.documentElement
194 while node
and node
is not root
:
195 node
= node
.parentNode
199 if hasattr(model
, 'format_version'):
200 format_version
= model
.format_version
201 parse_version
= model
.parse_version
203 def format_version(v
):
205 parse_version
= reader
.parse_version
208 if s
== 'true': return True
209 if s
== 'false': return False
210 raise SafeException('Expected "true" or "false" but got "%s"' % s
)