1 # Copyright (C) 2006, Thomas Leonard
2 # See http://0install.net/0compile.html
6 from os
.path
import join
7 from logging
import info
10 from zeroinstall
.injector
import model
, selections
, qdom
, arch
11 from zeroinstall
.injector
.arch
import canonicalize_os
, canonicalize_machine
13 from zeroinstall
.injector
.iface_cache
import iface_cache
14 from zeroinstall
import SafeException
15 from zeroinstall
.zerostore
import Store
, NotStored
16 from zeroinstall
.support
import find_in_path
18 Prefixes
= qdom
.Prefixes
23 # This is An os.uname() substitute that uses as much of ZI's
24 # arch._uname as is available and yet has all four elements one
25 # normally expects from os.uname() on Posix (on Windows, arch._uname
26 # has only two elements).
28 uname
= arch
._uname
+ platform
.uname()[len(arch
._uname
):]
30 ENV_FILE
= '0compile.properties'
31 XMLNS_0COMPILE
= 'http://zero-install.sourceforge.net/2006/namespaces/0compile'
33 zeroinstall_dir
= os
.environ
.get('0COMPILE_ZEROINSTALL', None)
35 # XXX: we're assuming that, if installed through 0install, 0launch requires
36 # the same version of Python as 0compile. This is currently needed for Arch
37 # Linux, but long-term we need to use the <runner>.
38 install_prog
= [sys
.executable
, os
.path
.join(zeroinstall_dir
, '0install')]
39 if not os
.path
.exists(install_prog
[1]):
40 # For the Windows version...
41 install_prog
[1] = os
.path
.join(zeroinstall_dir
, 'zeroinstall', 'scripts', 'install.py')
43 install_prog
= ['0install']
45 if os
.path
.isdir('dependencies'):
46 dep_dir
= os
.path
.realpath('dependencies')
47 iface_cache
.stores
.stores
.append(Store(dep_dir
))
48 install_prog
.append('--with-store='+ dep_dir
)
55 def is_package_impl(impl
):
56 return impl
.id.startswith("package:")
58 def lookup(impl_or_sel
):
60 if id.startswith('package:'):
62 local_path
= impl_or_sel
.local_path
63 if local_path
is not None:
64 if os
.path
.isdir(local_path
):
66 raise SafeException("Directory '%s' no longer exists. Try '0compile setup'" % local_path
)
68 return iface_cache
.stores
.lookup_any(impl_or_sel
.digests
)
70 raise NotStored(str(ex
) + "\nHint: try '0compile setup'")
72 def ensure_dir(d
, clean
= False):
80 raise SafeException("'%s' exists, but is not a directory!" % d
)
83 def spawn_and_check(prog
, args
):
84 status
= os
.spawnv(os
.P_WAIT
, prog
, [prog
] + args
)
86 raise SafeException("Program '%s' failed with exit code %d" % (prog
, status
))
88 raise SafeException("Program '%s' failed with signal %d" % (prog
, -status
))
90 def spawn_and_check_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
):
91 child
= spawn_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
)
94 raise SafeException('Command failed with exit status %d' % status
)
96 raise SafeException('Command failed with signal %d' % -status
)
98 def spawn_maybe_sandboxed(readable
, writable
, tmpdir
, prog
, args
):
99 """spawn prog, with (only) the 'writable' directories writable if sandboxing is available.
100 The readable directories will be readable, as well as various standard locations.
101 If no sandbox is available, run without a sandbox."""
103 USE_PLASH
= 'USE_PLASH_0COMPILE'
105 assert os
.path
.isabs(prog
)
106 _pola_run
= find_in_path('pola-run')
108 if _pola_run
is None:
109 #print "Not using sandbox (plash not installed)"
112 use_plash
= os
.environ
.get(USE_PLASH
, '').lower() or 'not set'
113 if use_plash
in ('not set', 'false'):
114 print "Not using plash: $%s is %s" % (USE_PLASH
, use_plash
)
116 elif use_plash
== 'true':
119 raise Exception('$%s must be "true" or "false", not "%s"' % (USE_PLASH
, use_plash
))
122 return subprocess
.Popen([prog
] + args
)
124 print "Using plash to sandbox the build..."
126 # We have pola-shell :-)
127 pola_args
= ['--prog', prog
, '-B']
129 pola_args
+= ['-a', a
]
131 pola_args
+= ['-f', r
]
133 pola_args
+= ['-fw', w
]
134 pola_args
+= ['-tw', '/tmp', tmpdir
]
135 os
.environ
['TMPDIR'] = '/tmp'
136 return subprocess
.Popen([_pola_run
] + pola_args
)
139 target_os
= canonicalize_os(uname
[0])
140 target_machine
= canonicalize_machine(uname
[4])
141 if target_os
== 'Darwin' and target_machine
== 'i386':
142 # this system detection shell script comes from config.guess (20090918):
143 CC
= os
.getenv("CC_FOR_BUILD") or os
.getenv("CC") or os
.getenv("HOST_CC") or "cc"
144 process
= subprocess
.Popen("(echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | " +
145 "(CCOPTS= %s -E - 2>/dev/null) | " % CC
+
146 "grep IS_64BIT_ARCH >/dev/null", stdout
=subprocess
.PIPE
, shell
=True)
147 output
, error
= process
.communicate()
148 retcode
= process
.poll()
150 target_machine
='x86_64'
151 if target_machine
in ('i585', 'i686'):
152 target_machine
= 'i486' # (sensible default)
153 return target_os
+ '-' + target_machine
156 def __init__(self
, need_config
= True):
157 if need_config
and not os
.path
.isfile(ENV_FILE
):
158 raise SafeException("Run 0compile from a directory containing a '%s' file" % ENV_FILE
)
160 self
.config
= ConfigParser
.RawConfigParser()
161 self
.config
.add_section('compile')
162 self
.config
.set('compile', 'download-base-url', '')
163 self
.config
.set('compile', 'version-modifier', '')
164 self
.config
.set('compile', 'interface', '')
165 self
.config
.set('compile', 'selections', '')
166 self
.config
.set('compile', 'metadir', '0install')
167 self
.config
.set('compile', 'distdir', '')
169 self
.config
.read(ENV_FILE
)
171 self
._selections
= None
176 def iface_name(self
):
177 iface_name
= os
.path
.basename(self
.interface
)
178 if iface_name
.endswith('.xml'):
179 iface_name
= iface_name
[:-4]
180 iface_name
= iface_name
.replace(' ', '-')
181 if iface_name
.endswith('-src'):
182 iface_name
= iface_name
[:-4]
185 interface
= property(lambda self
: model
.canonical_iface_uri(self
.config
.get('compile', 'interface')))
189 distdir_name
= self
.config
.get('compile', 'distdir')
191 arch
= self
.target_arch
.replace('*', 'any')
192 distdir_name
= self
.iface_name
.lower()
193 distdir_name
+= '-' + arch
.lower()
194 assert os
.path
.dirname(distdir_name
) == ''
195 return os
.path
.realpath(distdir_name
)
197 def get_binary_template(self
):
198 """Find the <compile:implementation> element for the selected compile command, if any"""
199 sels
= self
.get_selections()
201 for elem
in sels
.commands
[0].qdom
.childNodes
:
202 if elem
.uri
== XMLNS_0COMPILE
and elem
.name
== 'implementation':
208 metadir
= self
.config
.get('compile', 'metadir')
209 assert not os
.path
.isabs(metadir
)
210 return join(self
.distdir
, metadir
)
213 def local_iface_file(self
):
214 return join(self
.metadir
, 'feed.xml')
217 def target_arch(self
):
218 temp
= self
.get_binary_template()
219 arch
= temp
and temp
.getAttribute('arch')
220 return arch
or get_arch_name()
223 def version_modifier(self
):
224 vm
= self
.config
.get('compile', 'version-modifier')
231 def archive_stem(self
):
232 # Use the version that we actually built, not the version we would build now
233 feed
= self
.load_built_feed()
234 assert len(feed
.implementations
) == 1
235 version
= feed
.implementations
.values()[0].get_version()
237 # Don't use the feed's name, as it may contain the version number
238 name
= feed
.get_name().lower().replace(' ', '-')
239 arch
= self
.target_arch
.lower().replace('*-*', 'bin').replace('*', 'any')
241 return '%s-%s-%s' % (name
, arch
, version
)
243 def load_built_feed(self
):
244 path
= self
.local_iface_file
247 feed
= model
.ZeroInstallFeed(qdom
.parse(stream
), local_path
= path
)
252 def load_built_selections(self
):
253 path
= join(self
.metadir
, 'build-environment.xml')
254 if os
.path
.exists(path
):
257 return selections
.Selections(qdom
.parse(stream
))
263 def download_base_url(self
):
264 return self
.config
.get('compile', 'download-base-url')
266 def chosen_impl(self
, uri
):
267 sels
= self
.get_selections()
268 assert uri
in sels
.selections
269 return sels
.selections
[uri
]
272 def local_download_iface(self
):
273 impl
, = self
.load_built_feed().implementations
.values()
274 return '%s-%s.xml' % (self
.iface_name
, impl
.get_version())
277 stream
= file(ENV_FILE
, 'w')
279 self
.config
.write(stream
)
283 def get_selections(self
, prompt
= False):
286 return self
._selections
288 selections_file
= self
.config
.get('compile', 'selections')
291 raise SafeException("Selections are fixed by %s" % selections_file
)
292 stream
= file(selections_file
)
294 self
._selections
= selections
.Selections(qdom
.parse(stream
))
297 from zeroinstall
.injector
import handler
298 from zeroinstall
.injector
.config
import load_config
300 h
= handler
.ConsoleHandler()
302 h
= handler
.Handler()
303 config
= load_config(h
)
304 blocker
= self
._selections
.download_missing(config
)
306 print "Waiting for selected implementations to be downloaded..."
307 h
.wait_for_blocker(blocker
)
309 command
= install_prog
+ ['download', '--source', '--xml']
310 if prompt
and '--console' not in install_prog
:
313 command
.append('--gui')
314 command
.append(self
.interface
)
315 child
= subprocess
.Popen(command
, stdout
= subprocess
.PIPE
)
317 self
._selections
= selections
.Selections(qdom
.parse(child
.stdout
))
320 raise SafeException(' '.join(repr(x
) for x
in command
) + " failed (exit code %d)" % child
.returncode
)
322 self
.root_impl
= self
._selections
.selections
[self
.interface
]
324 self
.orig_srcdir
= os
.path
.realpath(lookup(self
.root_impl
))
325 self
.user_srcdir
= None
327 if os
.path
.isdir('src'):
328 self
.user_srcdir
= os
.path
.realpath('src')
329 if self
.user_srcdir
== self
.orig_srcdir
or \
330 self
.user_srcdir
.startswith(os
.path
.join(self
.orig_srcdir
, '')) or \
331 self
.orig_srcdir
.startswith(os
.path
.join(self
.user_srcdir
, '')):
332 info("Ignoring 'src' directory because it coincides with %s",
334 self
.user_srcdir
= None
336 return self
._selections
338 def get_build_changes(self
):
339 sels
= self
.get_selections()
340 old_sels
= self
.load_built_selections()
343 # See if things have changed since the last build
344 all_ifaces
= set(sels
.selections
) |
set(old_sels
.selections
)
346 old_impl
= old_sels
.selections
.get(x
, no_impl
)
347 new_impl
= sels
.selections
.get(x
, no_impl
)
348 if old_impl
.version
!= new_impl
.version
:
349 changes
.append("Version change for %s: %s -> %s" % (x
, old_impl
.version
, new_impl
.version
))
350 elif old_impl
.id != new_impl
.id:
351 changes
.append("Version change for %s: %s -> %s" % (x
, old_impl
.id, new_impl
.id))
355 root
= node
.ownerDocument
.documentElement
357 while node
and node
is not root
:
358 node
= node
.parentNode
363 if s
== 'true': return True
364 if s
== 'false': return False
365 raise SafeException('Expected "true" or "false" but got "%s"' % s
)