2 This class brings together a L{solve.Solver} to choose a set of implmentations, a
3 L{fetch.Fetcher} to download additional components, and the user's configuration
8 # Copyright (C) 2011, Thomas Leonard
9 # See the README file for details, or visit http://0install.net.
11 from zeroinstall
import _
13 from logging
import info
, debug
15 from zeroinstall
import SafeException
16 from zeroinstall
.injector
import arch
, model
17 from zeroinstall
.injector
.model
import Interface
, Implementation
, network_levels
, network_offline
, network_full
18 from zeroinstall
.injector
.namespaces
import config_site
, config_prog
19 from zeroinstall
.injector
.config
import load_config
20 from zeroinstall
.support
import tasks
23 """Chooses a set of implementations based on a policy.
25 1. Create a Driver object, giving it the requirements about the program to be run.
26 2. Call L{solve_with_downloads}. If more information is needed, a L{fetch.Fetcher} will be used to download it.
27 3. When all downloads are complete, the L{solver} contains the chosen versions.
28 4. Use L{get_uncached_implementations} to find where to get these versions and download them
29 using L{download_uncached_implementations}.
31 @ivar target_arch: target architecture for binaries
32 @type target_arch: L{arch.Architecture}
33 @ivar solver: solver used to choose a set of implementations
34 @type solver: L{solve.Solver}
35 @ivar watchers: callbacks to invoke after solving
37 __slots__
= ['watchers', 'requirements', 'config', '_warned_offline', 'target_arch', 'solver']
39 def __init__(self
, config
, requirements
):
41 @param config: The configuration settings to use
42 @type config: L{config.Config}
43 @param requirements: Details about the program we want to run
44 @type requirements: L{requirements.Requirements}
53 self
.requirements
= requirements
55 self
.target_arch
= arch
.get_architecture(requirements
.os
, requirements
.cpu
)
57 from zeroinstall
.injector
.solver
import DefaultSolver
58 self
.solver
= DefaultSolver(self
.config
)
60 # If we need to download something but can't because we are offline,
61 # warn the user. But only the first time.
62 self
._warned
_offline
= False
64 debug(_("Supported systems: '%s'"), arch
.os_ranks
)
65 debug(_("Supported processors: '%s'"), arch
.machine_ranks
)
67 if requirements
.before
or requirements
.not_before
:
68 self
.solver
.extra_restrictions
[config
.iface_cache
.get_interface(requirements
.interface_uri
)] = [
69 model
.VersionRangeRestriction(model
.parse_version(requirements
.before
),
70 model
.parse_version(requirements
.not_before
))]
72 def get_uncached_implementations(self
):
73 """List all chosen implementations which aren't yet available locally.
74 @rtype: [(L{model.Interface}, L{model.Implementation})]"""
75 iface_cache
= self
.config
.iface_cache
76 stores
= self
.config
.stores
78 for uri
, selection
in self
.solver
.selections
.selections
.iteritems():
80 assert impl
, self
.solver
.selections
81 if not impl
.is_available(stores
):
82 uncached
.append((iface_cache
.get_interface(uri
), impl
))
86 def solve_with_downloads(self
, force
= False, update_local
= False):
87 """Run the solver, then download any feeds that are missing or
88 that need to be updated. Each time a new feed is imported into
89 the cache, the solver is run again, possibly adding new downloads.
90 @param force: whether to download even if we're already ready to run.
91 @param update_local: fetch PackageKit feeds even if we're ready to run."""
93 downloads_finished
= set() # Successful or otherwise
94 downloads_in_progress
= {} # URL -> Download
96 host_arch
= self
.target_arch
97 if self
.requirements
.source
:
98 host_arch
= arch
.SourceArchitecture(host_arch
)
100 # There are three cases:
101 # 1. We want to run immediately if possible. If not, download all the information we can.
102 # (force = False, update_local = False)
103 # 2. We're in no hurry, but don't want to use the network unnecessarily.
104 # We should still update local information (from PackageKit).
105 # (force = False, update_local = True)
106 # 3. The user explicitly asked us to refresh everything.
109 try_quick_exit
= not (force
or update_local
)
112 self
.solver
.solve(self
.requirements
.interface_uri
, host_arch
, command_name
= self
.requirements
.command
)
113 for w
in self
.watchers
: w()
115 if try_quick_exit
and self
.solver
.ready
:
117 try_quick_exit
= False
119 if not self
.solver
.ready
:
122 for f
in self
.solver
.feeds_used
:
123 if f
in downloads_finished
or f
in downloads_in_progress
:
127 self
.config
.iface_cache
.get_feed(f
, force
= True)
128 downloads_in_progress
[f
] = tasks
.IdleBlocker('Refresh local feed')
130 elif f
.startswith('distribution:'):
131 if force
or update_local
:
132 downloads_in_progress
[f
] = self
.config
.fetcher
.download_and_import_feed(f
, self
.config
.iface_cache
)
133 elif force
and self
.config
.network_use
!= network_offline
:
134 downloads_in_progress
[f
] = self
.config
.fetcher
.download_and_import_feed(f
, self
.config
.iface_cache
)
135 # Once we've starting downloading some things,
136 # we might as well get them all.
139 if not downloads_in_progress
:
140 if self
.config
.network_use
== network_offline
:
141 info(_("Can't choose versions and in off-line mode, so aborting"))
144 # Wait for at least one download to finish
145 blockers
= downloads_in_progress
.values()
147 tasks
.check(blockers
, self
.config
.handler
.report_error
)
149 for f
in downloads_in_progress
.keys():
150 if f
in downloads_in_progress
and downloads_in_progress
[f
].happened
:
151 del downloads_in_progress
[f
]
152 downloads_finished
.add(f
)
154 # Need to refetch any "distribution" feed that
155 # depends on this one
156 distro_feed_url
= 'distribution:' + f
157 if distro_feed_url
in downloads_finished
:
158 downloads_finished
.remove(distro_feed_url
)
159 if distro_feed_url
in downloads_in_progress
:
160 del downloads_in_progress
[distro_feed_url
]
163 def solve_and_download_impls(self
, refresh
= False, select_only
= False):
164 """Run L{solve_with_downloads} and then get the selected implementations too.
165 @raise SafeException: if we couldn't select a set of implementations
167 refreshed
= self
.solve_with_downloads(refresh
)
170 tasks
.check(refreshed
)
172 if not self
.solver
.ready
:
173 raise self
.solver
.get_failure_reason()
176 downloaded
= self
.download_uncached_implementations()
179 tasks
.check(downloaded
)
181 def need_download(self
):
182 """Decide whether we need to download anything (but don't do it!)
183 @return: true if we MUST download something (feeds or implementations)
185 host_arch
= self
.target_arch
186 if self
.requirements
.source
:
187 host_arch
= arch
.SourceArchitecture(host_arch
)
188 self
.solver
.solve(self
.requirements
.interface_uri
, host_arch
, command_name
= self
.requirements
.command
)
189 for w
in self
.watchers
: w()
191 if not self
.solver
.ready
:
192 return True # Maybe a newer version will work?
194 if self
.get_uncached_implementations():
199 def download_uncached_implementations(self
):
200 """Download all implementations chosen by the solver that are missing from the cache."""
201 assert self
.solver
.ready
, "Solver is not ready!\n%s" % self
.solver
.selections
202 stores
= self
.config
.stores
203 return self
.config
.fetcher
.download_impls([impl
for impl
in self
.solver
.selections
.values() if not impl
.is_available(stores
)],