1 # This script is part of 0export
2 # Copyright (C) 2010, Thomas Leonard
3 # See http://0install.net for details.
5 # This file goes inside the generated setup.sh archive
6 # It runs or installs the program
8 import os
, sys
, subprocess
, tempfile
, tarfile
, gobject
, signal
11 mydir
= os
.path
.dirname(os
.path
.abspath(sys
.argv
[0]))
12 zidir
= os
.path
.join(mydir
, 'zeroinstall')
13 sys
.path
.insert(0, zidir
)
14 feeds_dir
= os
.path
.join(mydir
, 'feeds')
15 pypath
= os
.environ
.get('PYTHONPATH')
20 os
.environ
['PYTHONPATH'] = zidir
+ pypath
22 from zeroinstall
.injector
import gpg
, trust
, qdom
, iface_cache
, driver
, handler
, model
, namespaces
, config
, requirements
23 from zeroinstall
.support
import basedir
, find_in_path
24 from zeroinstall
import SafeException
, zerostore
25 from zeroinstall
.gtkui
import xdgutils
28 config
= config
.load_config(handler
= h
)
30 # During the install we copy this to the local cache
31 copied_0launch_in_cache
= None
33 if not os
.path
.isdir(feeds_dir
):
34 print >>sys
.stderr
, "Directory %s not found." % feeds_dir
35 print >>sys
.stderr
, "This script should be run from an unpacked setup.sh archive."
36 print >>sys
.stderr
, "(are you trying to install 0export? you're in the wrong place!)"
39 def check_call(*args
, **kwargs
):
40 exitstatus
= subprocess
.call(*args
, **kwargs
)
42 raise SafeException("Command failed with exit code %d:\n%s" % (exitstatus
, ' '.join(args
[0])))
50 def lookup(self
, digest
):
51 if digest
in self
.impls
:
52 return "/fake/" + digest
57 return find_in_path('gpg') or find_in_path('gpg2')
64 if self
.child
is not None:
65 os
.kill(self
.child
.pid
, signal
.SIGTERM
)
69 def do_install(self
, archive_stream
, progress_bar
, archive_offset
):
70 # Step 1. Import GPG keys
72 # Maybe GPG has never been run before. Let it initialse, or we'll get an error code
73 # from the first import... (ignore return value here)
74 subprocess
.call([get_gpg(), '--check-trustdb', '-q'])
76 key_dir
= os
.path
.join(mydir
, 'keys')
77 for key
in os
.listdir(key_dir
):
78 check_call([get_gpg(), '--import', '-q', os
.path
.join(key_dir
, key
)])
80 # Step 2. Import feeds and trust their signing keys
81 for root
, dirs
, files
in os
.walk(os
.path
.join(mydir
, 'feeds')):
82 if 'latest.xml' in files
:
83 feed_path
= os
.path
.join(root
, 'latest.xml')
84 icon_path
= os
.path
.join(root
, 'icon.png')
87 feed_stream
= file(feed_path
)
88 doc
= qdom
.parse(feed_stream
)
89 uri
= doc
.getAttribute('uri')
90 assert uri
, "Missing 'uri' attribute on root element in '%s'" % feed_path
91 domain
= trust
.domain_from_url(uri
)
94 stream
, sigs
= gpg
.check_stream(feed_stream
)
96 if not trust
.trust_db
.is_trusted(s
.fingerprint
, domain
):
97 print "Adding key %s to trusted list for %s" % (s
.fingerprint
, domain
)
98 trust
.trust_db
.trust_key(s
.fingerprint
, domain
)
99 oldest_sig
= min([s
.get_timestamp() for s
in sigs
])
101 config
.iface_cache
.update_feed_from_network(uri
, stream
.read(), oldest_sig
)
102 except iface_cache
.ReplayAttack
:
103 # OK, the user has a newer copy already
105 if feed_stream
!= stream
:
109 if os
.path
.exists(icon_path
):
110 icons_cache
= basedir
.save_cache_path(namespaces
.config_site
, 'interface_icons')
111 icon_file
= os
.path
.join(icons_cache
, model
.escape(uri
))
112 if not os
.path
.exists(icon_file
):
113 shutil
.copyfile(icon_path
, icon_file
)
115 # Step 3. Solve to find out which implementations we actually need
116 archive_stream
.seek(archive_offset
)
118 extract_impls
= {} # Impls we need but which are compressed (ID -> Impl)
119 tmp
= tempfile
.mkdtemp(prefix
= '0export-')
121 # Create a "fake store" with the implementation in the archive
122 archive
= tarfile
.open(name
=archive_stream
.name
, mode
='r|', fileobj
=archive_stream
)
123 fake_store
= FakeStore()
124 for tarmember
in archive
:
125 if tarmember
.name
.startswith('implementations'):
126 impl
= os
.path
.basename(tarmember
.name
).split('.')[0]
127 fake_store
.impls
.add(impl
)
129 bootstrap_store
= zerostore
.Store(os
.path
.join(mydir
, 'implementations'))
130 stores
= config
.stores
132 toplevel_uris
= [uri
.strip() for uri
in file(os
.path
.join(mydir
, 'toplevel_uris'))]
133 ZEROINSTALL_URI
= "@ZEROINSTALL_URI@"
134 for uri
in [ZEROINSTALL_URI
] + toplevel_uris
:
135 # This is so the solver treats versions in the setup archive as 'cached',
136 # meaning that it will prefer using them to doing a download
137 stores
.stores
.append(bootstrap_store
)
138 stores
.stores
.append(fake_store
)
140 # Shouldn't need to download anything, but we might not have all feeds
141 r
= requirements
.Requirements(uri
)
142 d
= driver
.Driver(config
= config
, requirements
= r
)
143 config
.network_use
= model
.network_minimal
144 download_feeds
= d
.solve_with_downloads()
145 h
.wait_for_blocker(download_feeds
)
146 assert d
.solver
.ready
, d
.solver
.get_failure_reason()
148 # Add anything chosen from the setup store to the main store
149 stores
.stores
.remove(fake_store
)
150 stores
.stores
.remove(bootstrap_store
)
151 for iface
, impl
in d
.get_uncached_implementations():
152 print >>sys
.stderr
, "Need to import", impl
153 if impl
.id in fake_store
.impls
:
155 extract_impls
[impl
.id] = impl
157 impl_src
= os
.path
.join(mydir
, 'implementations', impl
.id)
159 if os
.path
.isdir(impl_src
):
160 stores
.add_dir_to_cache(impl
.id, impl_src
)
162 print >>sys
.stderr
, "Required impl %s (for %s) not present" % (impl
, iface
)
164 # Remember where we copied 0launch to, because we'll need it after
165 # the temporary directory is deleted.
166 if uri
== ZEROINSTALL_URI
:
167 global copied_0launch_in_cache
168 impl
= d
.solver
.selections
.selections
[uri
]
169 if not impl
.id.startswith('package:'):
170 copied_0launch_in_cache
= impl
.get_path(stores
= config
.stores
)
171 # (else we selected the distribution version of Zero Install)
175 # Count total number of bytes to extract
177 for impl
in extract_impls
.values():
178 impl_info
= archive
.getmember('implementations/' + impl
.id + '.tar.bz2')
179 extract_total
+= impl_info
.size
183 # Actually extract+import implementations in archive
184 archive_stream
.seek(archive_offset
)
185 archive
= tarfile
.open(name
=archive_stream
.name
, mode
='r|',
186 fileobj
=archive_stream
)
188 for tarmember
in archive
:
189 if not tarmember
.name
.startswith('implementations'):
191 impl_id
= tarmember
.name
.split('/')[1].split('.')[0]
192 if impl_id
not in extract_impls
:
193 print "Skip", impl_id
195 print "Extracting", impl_id
196 tmp
= tempfile
.mkdtemp(prefix
= '0export-')
198 impl_stream
= archive
.extractfile(tarmember
)
199 self
.child
= subprocess
.Popen('bunzip2|tar xf -', shell
= True, stdin
= subprocess
.PIPE
, cwd
= tmp
)
200 mainloop
= gobject
.MainLoop(gobject
.main_context_default())
202 def pipe_ready(src
, cond
):
203 data
= impl_stream
.read(4096)
206 self
.child
.stdin
.close()
208 self
.sent
+= len(data
)
210 progress_bar
.set_fraction(float(self
.sent
) / extract_total
)
211 self
.child
.stdin
.write(data
)
213 gobject
.io_add_watch(self
.child
.stdin
, gobject
.IO_OUT | gobject
.IO_HUP
, pipe_ready
, priority
= gobject
.PRIORITY_LOW
)
218 if self
.child
.returncode
:
219 raise Exception("Failed to unpack archive (code %d)" % self
.child
.returncode
)
221 stores
.add_dir_to_cache(impl_id
, tmp
)
228 def add_to_menu(uris
):
230 iface
= config
.iface_cache
.get_interface(uri
)
231 icon_path
= config
.iface_cache
.get_icon_path(iface
)
234 for meta
in iface
.get_metadata(namespaces
.XMLNS_IFACE
, 'category'):
237 raise Exception("Invalid category '%s'" % c
)
241 xdgutils
.add_to_menu(iface
, icon_path
, feed_category
)
243 if find_in_path('0launch'):
246 if find_in_path('sudo') and find_in_path('gnome-terminal') and find_in_path('apt-get'):
247 check_call(['gnome-terminal', '--disable-factory', '-x', 'sh', '-c',
248 'echo "We need to install the zeroinstall-injector package to make the menu items work."; '
249 'sudo apt-get install zeroinstall-injector || sleep 4'])
251 if find_in_path('0launch'):
255 box
= gtk
.MessageDialog(None, 0, buttons
= gtk
.BUTTONS_OK
)
256 box
.set_markup("The new menu item won't work until the '<b>zeroinstall-injector</b>' package is installed.\n"
257 "Please install it using your distribution's package manager.")
262 def run(uri
, args
, prog_args
):
263 print "Running program..."
264 if copied_0launch_in_cache
:
265 launch
= os
.path
.join(copied_0launch_in_cache
, '0launch')
267 launch
= find_in_path('0launch')
268 os
.execv(launch
, [launch
] + args
+ [uri
] + prog_args
)