From 3d61cc3a1e22a836aac7e121eb8fa047823313c4 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sat, 15 Jan 2011 20:03:16 +0000 Subject: [PATCH] WIP: Requirements object --- tests/basetest.py | 49 ++++++++++++-- tests/testdownload.py | 126 +++++++++++++++-------------------- tests/testlaunch.py | 14 ++-- zeroinstall/cmd/select.py | 62 ++++++----------- zeroinstall/helpers.py | 7 +- zeroinstall/injector/autopolicy.py | 21 +----- zeroinstall/injector/background.py | 12 ++-- zeroinstall/injector/cli.py | 25 ++++--- zeroinstall/injector/iface_cache.py | 2 +- zeroinstall/injector/policy.py | 70 ++++++++++--------- zeroinstall/injector/requirements.py | 54 +++++++++++++++ zeroinstall/zerostore/__init__.py | 2 +- 12 files changed, 252 insertions(+), 192 deletions(-) create mode 100644 zeroinstall/injector/requirements.py diff --git a/tests/basetest.py b/tests/basetest.py index 5de15ad..ce4e2d8 100755 --- a/tests/basetest.py +++ b/tests/basetest.py @@ -11,8 +11,8 @@ os.environ['LANGUAGE'] = 'C' sys.path.insert(0, '..') from zeroinstall.injector import qdom -from zeroinstall.injector import iface_cache, download, distro, model -from zeroinstall.zerostore import Store; Store._add_with_helper = lambda *unused: False +from zeroinstall.injector import iface_cache, download, distro, model, handler, policy +from zeroinstall.zerostore import Store, Stores; Store._add_with_helper = lambda *unused: False from zeroinstall import support, helpers from zeroinstall.support import basedir @@ -56,9 +56,46 @@ class DummyPackageKit: def get_candidates(self, package, factory, prefix): pass +class DummyHandler(handler.Handler): + __slots__ = ['ex', 'tb'] + + def __init__(self): + handler.Handler.__init__(self) + self.ex = None + + def wait_for_blocker(self, blocker): + self.ex = None + handler.Handler.wait_for_blocker(self, blocker) + if self.ex: + raise self.ex, None, self.tb + + def report_error(self, ex, tb = None): + assert self.ex is None, self.ex + self.ex = ex + self.tb = tb + + #import traceback + #traceback.print_exc() + +class TestFetcher: + def download_impls(self, impls, stores): + assert impls == [], impls + +class TestConfig: + freshness = 0 + help_test_new_versions = False + network_use = model.network_full + + def __init__(self): + self.iface_cache = iface_cache.IfaceCache() + self.handler = DummyHandler() + self.stores = Stores() + self.fetcher = TestFetcher() + class BaseTest(unittest.TestCase): def setUp(self): warnings.resetwarnings() + self.config_home = tempfile.mktemp() self.cache_home = tempfile.mktemp() self.cache_system = tempfile.mktemp() @@ -70,7 +107,6 @@ class BaseTest(unittest.TestCase): os.environ['XDG_CACHE_DIRS'] = self.cache_system reload(basedir) assert basedir.xdg_config_home == self.config_home - #iface_cache.iface_cache.__init__() os.mkdir(self.config_home, 0700) os.mkdir(self.cache_home, 0700) @@ -80,6 +116,9 @@ class BaseTest(unittest.TestCase): if os.environ.has_key('DISPLAY'): del os.environ['DISPLAY'] + self.config = TestConfig() + policy._config = self.config # XXX + logging.getLogger().setLevel(logging.WARN) download._downloads = {} @@ -92,8 +131,10 @@ class BaseTest(unittest.TestCase): distro._host_distribution._packagekit = DummyPackageKit() my_dbus.system_services = {} - + def tearDown(self): + assert self.config.handler.ex is None, self.config.handler.ex + shutil.rmtree(self.config_home) support.ro_rmtree(self.cache_home) shutil.rmtree(self.cache_system) diff --git a/tests/testdownload.py b/tests/testdownload.py index aef3b20..22e3ab5 100755 --- a/tests/testdownload.py +++ b/tests/testdownload.py @@ -11,7 +11,8 @@ sys.path.insert(0, '..') os.environ["http_proxy"] = "localhost:8000" -from zeroinstall.injector import model, autopolicy, gpg, iface_cache, download, reader, trust, handler, background, arch, selections, qdom +from zeroinstall.injector import model, gpg, iface_cache, download, reader, trust, handler, background, arch, selections, qdom, run +from zeroinstall.injector.policy import Policy from zeroinstall.zerostore import Store, NotStored; Store._add_with_helper = lambda *unused: False from zeroinstall.support import basedir, tasks from zeroinstall.injector import fetch @@ -54,26 +55,11 @@ class Reply: def readline(self): return self.reply -class DummyHandler(handler.Handler): - __slots__ = ['ex', 'tb'] - - def __init__(self): - handler.Handler.__init__(self) - self.ex = None - - def wait_for_blocker(self, blocker): - self.ex = None - handler.Handler.wait_for_blocker(self, blocker) - if self.ex: - raise self.ex, None, self.tb - - def report_error(self, ex, tb = None): - assert self.ex is None, self.ex - self.ex = ex - self.tb = tb - - #import traceback - #traceback.print_exc() +def download_and_execute(policy, prog_args, main = None): + downloaded = policy.solve_and_download_impls() + if downloaded: + policy.config.handler.wait_for_blocker(downloaded) + run.execute_selections(policy.solver.selections, prog_args, stores = policy.config.stores, main = main) class NetworkManager: def state(self): @@ -83,6 +69,8 @@ class TestDownload(BaseTest): def setUp(self): BaseTest.setUp(self) + self.config.fetcher = fetch.Fetcher(self.config.handler) + stream = tempfile.TemporaryFile() stream.write(data.thomas_key) stream.seek(0) @@ -101,34 +89,34 @@ class TestDownload(BaseTest): def testRejectKey(self): with output_suppressed(): self.child = server.handle_requests('Hello', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B') - policy = autopolicy.AutoPolicy('http://localhost:8000/Hello', download_only = False, - handler = DummyHandler()) + policy = Policy('http://localhost:8000/Hello', config = self.config) assert policy.need_download() sys.stdin = Reply("N\n") try: - policy.download_and_execute(['Hello']) + download_and_execute(policy, ['Hello']) assert 0 except model.SafeException, ex: if "has no usable implementations" not in str(ex): raise ex if "Not signed with a trusted key" not in str(policy.handler.ex): raise ex + self.config.handler.ex = None def testRejectKeyXML(self): with output_suppressed(): self.child = server.handle_requests('Hello.xml', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B') - policy = autopolicy.AutoPolicy('http://example.com:8000/Hello.xml', download_only = False, - handler = DummyHandler()) + policy = Policy('http://example.com:8000/Hello.xml', config = self.config) assert policy.need_download() sys.stdin = Reply("N\n") try: - policy.download_and_execute(['Hello']) + download_and_execute(policy, ['Hello']) assert 0 except model.SafeException, ex: if "has no usable implementations" not in str(ex): raise ex if "Not signed with a trusted key" not in str(policy.handler.ex): raise + self.config.handler.ex = None def testImport(self): from zeroinstall.injector import cli @@ -145,7 +133,7 @@ class TestDownload(BaseTest): rootLogger.disabled = False rootLogger.setLevel(WARN) - hello = iface_cache.iface_cache.get_feed('http://localhost:8000/Hello') + hello = self.config.iface_cache.get_feed('http://localhost:8000/Hello') self.assertEquals(None, hello) with output_suppressed(): @@ -157,7 +145,7 @@ class TestDownload(BaseTest): assert trust.trust_db.is_trusted('DE937DD411906ACF7C263B396FCF121BE2390E0B') # Check we imported the interface after trusting the key - hello = iface_cache.iface_cache.get_feed('http://localhost:8000/Hello', force = True) + hello = self.config.iface_cache.get_feed('http://localhost:8000/Hello', force = True) self.assertEquals(1, len(hello.implementations)) # Shouldn't need to prompt the second time @@ -174,15 +162,15 @@ class TestDownload(BaseTest): self.child = server.handle_requests('Hello.xml', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B', 'HelloWorld.tgz') sys.stdin = Reply("Y\n") try: - iface_cache.iface_cache.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests) + self.config.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests) assert False except NotStored: pass cli.main(['--download-only', 'selections.xml']) - path = iface_cache.iface_cache.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests) + path = self.config.iface_cache.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests) assert os.path.exists(os.path.join(path, 'HelloWorld', 'main')) - assert sels.download_missing(iface_cache.iface_cache, None) is None + assert sels.download_missing(self.config.iface_cache, None) is None def testHelpers(self): from zeroinstall import helpers @@ -191,9 +179,9 @@ class TestDownload(BaseTest): self.child = server.handle_requests('Hello.xml', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B', 'HelloWorld.tgz') sys.stdin = Reply("Y\n") sels = helpers.ensure_cached('http://example.com:8000/Hello.xml') - path = iface_cache.iface_cache.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests) + path = self.config.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests) assert os.path.exists(os.path.join(path, 'HelloWorld', 'main')) - assert sels.download_missing(iface_cache.iface_cache, None) is None + assert sels.download_missing(self.config.iface_cache, None) is None def testSelectionsWithFeed(self): from zeroinstall.injector import cli @@ -204,56 +192,51 @@ class TestDownload(BaseTest): self.child = server.handle_requests('Hello.xml', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B', 'HelloWorld.tgz') sys.stdin = Reply("Y\n") - from zeroinstall.injector.handler import Handler - handler = Handler() - fetcher = fetch.Fetcher(handler) - handler.wait_for_blocker(fetcher.download_and_import_feed('http://example.com:8000/Hello.xml', iface_cache.iface_cache)) + self.config.handler.wait_for_blocker(self.config.fetcher.download_and_import_feed('http://example.com:8000/Hello.xml', self.config.iface_cache)) - cli.main(['--download-only', 'selections.xml']) - path = iface_cache.iface_cache.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests) + cli.main(['--download-only', 'selections.xml'], config = self.config) + path = self.config.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests) assert os.path.exists(os.path.join(path, 'HelloWorld', 'main')) - assert sels.download_missing(iface_cache.iface_cache, None) is None + assert sels.download_missing(self.config.iface_cache, None) is None def testAcceptKey(self): with output_suppressed(): self.child = server.handle_requests('Hello', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B', 'HelloWorld.tgz') - policy = autopolicy.AutoPolicy('http://localhost:8000/Hello', download_only = False, - handler = DummyHandler()) + policy = Policy('http://localhost:8000/Hello', config = self.config) assert policy.need_download() sys.stdin = Reply("Y\n") try: - policy.download_and_execute(['Hello'], main = 'Missing') + download_and_execute(policy, ['Hello'], main = 'Missing') assert 0 except model.SafeException, ex: if "HelloWorld/Missing" not in str(ex): - raise ex + raise def testDistro(self): with output_suppressed(): native_url = 'http://example.com:8000/Native.xml' # Initially, we don't have the feed at all... - master_feed = iface_cache.iface_cache.get_feed(native_url) + master_feed = self.config.iface_cache.get_feed(native_url) assert master_feed is None, master_feed trust.trust_db.trust_key('DE937DD411906ACF7C263B396FCF121BE2390E0B', 'example.com:8000') self.child = server.handle_requests('Native.xml', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B') - h = DummyHandler() - policy = autopolicy.AutoPolicy(native_url, download_only = False, handler = h) + policy = Policy(native_url, config = self.config) assert policy.need_download() solve = policy.solve_with_downloads() - h.wait_for_blocker(solve) + self.config.handler.wait_for_blocker(solve) tasks.check(solve) - master_feed = iface_cache.iface_cache.get_feed(native_url) + master_feed = self.config.iface_cache.get_feed(native_url) assert master_feed is not None assert master_feed.implementations == {} distro_feed_url = master_feed.get_distro_feed() assert distro_feed_url is not None - distro_feed = iface_cache.iface_cache.get_feed(distro_feed_url) + distro_feed = self.config.iface_cache.get_feed(distro_feed_url) assert distro_feed is not None assert len(distro_feed.implementations) == 2, distro_feed.implementations @@ -261,12 +244,11 @@ class TestDownload(BaseTest): with output_suppressed(): self.child = server.handle_requests('Hello-wrong-size', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B', 'HelloWorld.tgz') - policy = autopolicy.AutoPolicy('http://localhost:8000/Hello-wrong-size', download_only = False, - handler = DummyHandler()) + policy = Policy('http://localhost:8000/Hello-wrong-size', config = self.config) assert policy.need_download() sys.stdin = Reply("Y\n") try: - policy.download_and_execute(['Hello'], main = 'Missing') + download_and_execute(policy, ['Hello'], main = 'Missing') assert 0 except model.SafeException, ex: if "Downloaded archive has incorrect size" not in str(ex): @@ -277,9 +259,9 @@ class TestDownload(BaseTest): try: sys.stdout = StringIO() self.child = server.handle_requests(('HelloWorld.tar.bz2', 'dummy_1-1_all.deb')) - policy = autopolicy.AutoPolicy(os.path.abspath('Recipe.xml'), download_only = False) + policy = Policy(os.path.abspath('Recipe.xml'), config = self.config) try: - policy.download_and_execute([]) + download_and_execute(policy, []) assert False except model.SafeException, ex: if "HelloWorld/Missing" not in str(ex): @@ -292,10 +274,9 @@ class TestDownload(BaseTest): try: sys.stdout = StringIO() self.child = server.handle_requests(('HelloWorld.tar.bz2', 'HelloSym.tgz')) - policy = autopolicy.AutoPolicy(os.path.abspath('RecipeSymlink.xml'), download_only = False, - handler = DummyHandler()) + policy = Policy(os.path.abspath('RecipeSymlink.xml'), config = self.config) try: - policy.download_and_execute([]) + download_and_execute(policy, []) assert False except model.SafeException, ex: if 'Attempt to unpack dir over symlink "HelloWorld"' not in str(ex): @@ -309,9 +290,9 @@ class TestDownload(BaseTest): try: sys.stdout = StringIO() self.child = server.handle_requests('HelloWorld.autopackage') - policy = autopolicy.AutoPolicy(os.path.abspath('Autopackage.xml'), download_only = False) + policy = Policy(os.path.abspath('Autopackage.xml'), config = self.config) try: - policy.download_and_execute([]) + download_and_execute(policy, []) assert False except model.SafeException, ex: if "HelloWorld/Missing" not in str(ex): @@ -324,10 +305,9 @@ class TestDownload(BaseTest): try: sys.stdout = StringIO() self.child = server.handle_requests('*') - policy = autopolicy.AutoPolicy(os.path.abspath('Recipe.xml'), download_only = False, - handler = DummyHandler()) + policy = Policy(os.path.abspath('Recipe.xml'), config = self.config) try: - policy.download_and_execute([]) + download_and_execute(policy, []) assert False except download.DownloadError, ex: if "Connection" not in str(ex): @@ -342,7 +322,7 @@ class TestDownload(BaseTest): getLogger().setLevel(ERROR) trust.trust_db.trust_key('DE937DD411906ACF7C263B396FCF121BE2390E0B', 'example.com:8000') self.child = server.handle_requests(server.Give404('/Hello.xml'), 'latest.xml', '/0mirror/keys/6FCF121BE2390E0B.gpg') - policy = autopolicy.AutoPolicy('http://example.com:8000/Hello.xml', download_only = False) + policy = Policy('http://example.com:8000/Hello.xml', config = self.config) policy.fetcher.feed_mirror = 'http://example.com:8000/0mirror' refreshed = policy.solve_with_downloads() @@ -356,21 +336,21 @@ class TestDownload(BaseTest): try: sys.stdout = StringIO() getLogger().setLevel(ERROR) - iface = iface_cache.iface_cache.get_interface('http://example.com:8000/Hello.xml') + iface = self.config.iface_cache.get_interface('http://example.com:8000/Hello.xml') mtime = int(os.stat('Hello-new.xml').st_mtime) - iface_cache.iface_cache.update_feed_from_network(iface.uri, file('Hello-new.xml').read(), mtime + 10000) + self.config.iface_cache.update_feed_from_network(iface.uri, file('Hello-new.xml').read(), mtime + 10000) trust.trust_db.trust_key('DE937DD411906ACF7C263B396FCF121BE2390E0B', 'example.com:8000') self.child = server.handle_requests(server.Give404('/Hello.xml'), 'latest.xml', '/0mirror/keys/6FCF121BE2390E0B.gpg', 'Hello.xml') - policy = autopolicy.AutoPolicy('http://example.com:8000/Hello.xml', download_only = False) + policy = Policy('http://example.com:8000/Hello.xml', config = self.config) policy.fetcher.feed_mirror = 'http://example.com:8000/0mirror' # Update from mirror (should ignore out-of-date timestamp) - refreshed = policy.fetcher.download_and_import_feed(iface.uri, iface_cache.iface_cache) + refreshed = policy.fetcher.download_and_import_feed(iface.uri, self.config.iface_cache) policy.handler.wait_for_blocker(refreshed) # Update from upstream (should report an error) - refreshed = policy.fetcher.download_and_import_feed(iface.uri, iface_cache.iface_cache) + refreshed = policy.fetcher.download_and_import_feed(iface.uri, self.config.iface_cache) try: policy.handler.wait_for_blocker(refreshed) raise Exception("Should have been rejected!") @@ -378,13 +358,13 @@ class TestDownload(BaseTest): assert "New feed's modification time is before old version" in str(ex) # Must finish with the newest version - self.assertEquals(1235911552, iface_cache.iface_cache._get_signature_date(iface.uri)) + self.assertEquals(1235911552, self.config.iface_cache._get_signature_date(iface.uri)) finally: sys.stdout = old_out def testBackground(self, verbose = False): - p = autopolicy.AutoPolicy('http://example.com:8000/Hello.xml') - reader.update(iface_cache.iface_cache.get_interface(p.root), 'Hello.xml') + p = Policy('http://example.com:8000/Hello.xml', config = self.config) + reader.update(p.config.iface_cache.get_interface(p.root), 'Hello.xml') p.freshness = 0 p.network_use = model.network_minimal p.solver.solve(p.root, arch.get_host_architecture()) diff --git a/tests/testlaunch.py b/tests/testlaunch.py index f59b132..362138e 100755 --- a/tests/testlaunch.py +++ b/tests/testlaunch.py @@ -9,7 +9,8 @@ foo_iface_uri = 'http://foo' sys.path.insert(0, '..') from zeroinstall import SafeException -from zeroinstall.injector import autopolicy, model, cli, namespaces, qdom, selections +from zeroinstall.injector.policy import Policy +from zeroinstall.injector import handler, run, model, cli, namespaces, qdom, selections from zeroinstall.zerostore import Store; Store._add_with_helper = lambda *unused: False from zeroinstall.support import basedir @@ -118,9 +119,12 @@ class TestLaunch(BaseTest): """ % foo_iface_uri) tmp.flush() - policy = autopolicy.AutoPolicy(tmp.name) + policy = Policy(tmp.name, config = self.config) try: - policy.download_and_execute([]) + downloaded = policy.solve_and_download_impls() + if downloaded: + policy.handler.wait_for_blocker(downloaded) + run.execute_selections(policy.solver.selections, [], stores = policy.config.stores) assert False except SafeException, ex: assert 'Command path must be relative' in str(ex), ex @@ -151,16 +155,12 @@ class TestLaunch(BaseTest): self.assertEquals("", err) def testNeedDownload(self): - policy = autopolicy.AutoPolicy(foo_iface_uri) - policy.save_config() os.environ['DISPLAY'] = ':foo' out, err = self.run_0launch(['--download-only', '--dry-run', 'Foo.xml']) self.assertEquals("", err) self.assertEquals("Finished\n", out) def testSelectOnly(self): - policy = autopolicy.AutoPolicy(foo_iface_uri) - policy.save_config() os.environ['DISPLAY'] = ':foo' out, err = self.run_0launch(['--get-selections', '--select-only', 'Hello.xml']) self.assertEquals("", err) diff --git a/zeroinstall/cmd/select.py b/zeroinstall/cmd/select.py index 3b51f0a..8d88df0 100644 --- a/zeroinstall/cmd/select.py +++ b/zeroinstall/cmd/select.py @@ -11,7 +11,7 @@ import logging from zeroinstall import cmd, SafeException, _ from zeroinstall.cmd import UsageError -from zeroinstall.injector import model, selections, handler +from zeroinstall.injector import model, selections, handler, requirements from zeroinstall.injector.policy import Policy syntax = "URI" @@ -48,39 +48,41 @@ def get_selections(config, options, iface_uri, select_only, download_only, test_ maybe_selections = config.iface_cache.get_feed(iface_uri, selections_ok = True) if isinstance(maybe_selections, selections.Selections): if not select_only: - from zeroinstall.injector import fetch - fetcher = fetch.Fetcher(h) - blocker = maybe_selections.download_missing(config.iface_cache, fetcher) + blocker = maybe_selections.download_missing(config.iface_cache, config.fetcher) if blocker: logging.info(_("Waiting for selected implementations to be downloaded...")) - h.wait_for_blocker(blocker) - _download_missing_selections(h, maybe_selections) + config.handler.wait_for_blocker(blocker) return maybe_selections root_iface = config.iface_cache.get_interface(iface_uri) - command_name = options.command - if command_name is None: - command_name = 'run' - elif command_name == '': - command_name = None - policy = Policy(iface_uri, - handler = None, # (use config instead) - src = options.source, - command = command_name, - config = config) + r = requirements.Requirements(iface_uri) + # (None becomes 'run', while '' becomes None) + if options.command is None: + r.command = 'run' + else: + r.command = options.command or None + r.source = bool(options.source) + + r.before = options.before + r.not_before = options.not_before + + policy = Policy(config = config, requirements = r) + + # XXX if options.before or options.not_before: policy.solver.extra_restrictions[root_iface] = [ model.VersionRangeRestriction(model.parse_version(options.before), model.parse_version(options.not_before))] - - if options.os or options.cpu: from zeroinstall.injector import arch policy.target_arch = arch.get_architecture(options.os, options.cpu) + r.os = options.os + r.cpu = options.cpu + if options.offline: - policy.network_use = model.network_offline + config.network_use = model.network_offline # Note that need_download() triggers a solve if options.refresh or options.gui: @@ -121,41 +123,21 @@ def get_selections(config, options, iface_uri, select_only, download_only, test_ logging.info(_("Switching to GUI mode... (use --console to disable)")) if options.gui: - gui_args = [] + gui_args = policy.requirements.get_as_options() if download_only: # Just changes the button's label gui_args.append('--download-only') if options.refresh: gui_args.append('--refresh') - if options.not_before: - gui_args.insert(0, options.not_before) - gui_args.insert(0, '--not-before') - if options.before: - gui_args.insert(0, options.before) - gui_args.insert(0, '--before') - if options.source: - gui_args.insert(0, '--source') - if options.message: - gui_args.insert(0, options.message) - gui_args.insert(0, '--message') if options.verbose: gui_args.insert(0, '--verbose') if options.verbose > 1: gui_args.insert(0, '--verbose') - if options.cpu: - gui_args.insert(0, options.cpu) - gui_args.insert(0, '--cpu') - if options.os: - gui_args.insert(0, options.os) - gui_args.insert(0, '--os') if options.with_store: for x in options.with_store: gui_args += ['--with-store', x] if select_only: gui_args.append('--select-only') - if command_name is not None: - gui_args.append('--command') - gui_args.append(command_name) from zeroinstall import helpers sels = helpers.get_selections_gui(iface_uri, gui_args, test_callback) diff --git a/zeroinstall/helpers.py b/zeroinstall/helpers.py index c3814ba..888c47b 100644 --- a/zeroinstall/helpers.py +++ b/zeroinstall/helpers.py @@ -108,10 +108,11 @@ def ensure_cached(uri, command = 'run'): @return: the selected implementations, or None if the user cancelled @rtype: L{zeroinstall.injector.selections.Selections} """ - from zeroinstall.injector import autopolicy, selections + from zeroinstall.injector import policy, selections, handler - p = autopolicy.AutoPolicy(uri, download_only = True, command = command) - p.freshness = 0 # Don't check for updates + config = policy.load_config(handler.Handler()) + p = policy.Policy(uri, command = command, config = config) + p.freshness = 0 # XXX: Don't check for updates if p.need_download() or not p.ready: if os.environ.get('DISPLAY', None): diff --git a/zeroinstall/injector/autopolicy.py b/zeroinstall/injector/autopolicy.py index 5529f7f..47f6975 100644 --- a/zeroinstall/injector/autopolicy.py +++ b/zeroinstall/injector/autopolicy.py @@ -18,30 +18,11 @@ from zeroinstall.injector import policy, run from zeroinstall.injector.handler import Handler class AutoPolicy(policy.Policy): - __slots__ = ['download_only'] - def __init__(self, interface_uri, download_only = False, dry_run = False, src = False, handler = None, command = 'run'): """@param handler: (new in 0.30) handler to use, or None to create a L{Handler}""" + assert download_only is False handler = handler or Handler() if dry_run: info(_("Note: dry_run is deprecated. Pass it to the handler instead!")) handler.dry_run = True policy.Policy.__init__(self, interface_uri, handler, src = src, command = command) - self.download_only = download_only - - def execute(self, prog_args, main = None, wrapper = None): - """@deprecated: use L{solve_and_download_impls} and L{run.execute}""" - downloaded = self.download_uncached_implementations() - if downloaded: - self.handler.wait_for_blocker(downloaded) - if not self.download_only: - run.execute(self, prog_args, dry_run = self.handler.dry_run, main = main, wrapper = wrapper) - else: - info(_("Downloads done (download-only mode)")) - - def download_and_execute(self, prog_args, refresh = False, main = None): - """@deprecated: use L{solve_and_download_impls} instead""" - downloaded = self.solve_and_download_impls(refresh) - if downloaded: - self.handler.wait_for_blocker(downloaded) - self.execute(prog_args, main = main) diff --git a/zeroinstall/injector/background.py b/zeroinstall/injector/background.py index 248a184..4ee62a8 100644 --- a/zeroinstall/injector/background.py +++ b/zeroinstall/injector/background.py @@ -13,7 +13,6 @@ from zeroinstall import _ import sys, os from logging import info, warn from zeroinstall.support import tasks -from zeroinstall.injector.iface_cache import iface_cache from zeroinstall.injector import handler def _escape_xml(s): @@ -160,10 +159,14 @@ def _detach(): return False -def _check_for_updates(policy, verbose): - root_iface = iface_cache.get_interface(policy.root).get_name() +def _check_for_updates(old_policy, verbose): + from zeroinstall.injector.policy import load_config, Policy - policy.handler = BackgroundHandler(root_iface, policy.root) + iface_cache = old_policy.config.iface_cache + root_iface = iface_cache.get_interface(old_policy.root).get_name() + + background_config = load_config(BackgroundHandler(root_iface, old_policy.root)) + policy = Policy(config = background_config, requirements = old_policy.requirements) info(_("Checking for updates to '%s' in a background process"), root_iface) if verbose: @@ -227,6 +230,7 @@ def spawn_background_update(policy, verbose): @type policy: L{policy.Policy} @param verbose: whether to notify the user about minor events @type verbose: bool""" + iface_cache = policy.config.iface_cache # Mark all feeds as being updated. Do this before forking, so that if someone is # running lots of 0launch commands in series on the same program we don't start # huge numbers of processes. diff --git a/zeroinstall/injector/cli.py b/zeroinstall/injector/cli.py index ba573b0..420ab43 100644 --- a/zeroinstall/injector/cli.py +++ b/zeroinstall/injector/cli.py @@ -11,7 +11,7 @@ from optparse import OptionParser import logging from zeroinstall import SafeException, NeedDownload -from zeroinstall.injector import model, policy, autopolicy, selections +from zeroinstall.injector import handler, policy from zeroinstall.cmd import UsageError #def program_log(msg): os.access('MARK: 0launch: ' + msg, os.F_OK) @@ -19,7 +19,7 @@ from zeroinstall.cmd import UsageError #__main__.__builtins__.program_log = program_log #program_log('0launch ' + ' '.join((sys.argv[1:]))) -def main(command_args): +def main(command_args, config = None): """Act as if 0launch was run with the given arguments. @arg command_args: array of arguments (e.g. C{sys.argv[1:]}) @type command_args: [str] @@ -80,7 +80,14 @@ def main(command_args): if options.select_only or options.show: options.download_only = True - config = policy.load_config() + if os.isatty(1): + h = handler.ConsoleHandler() + else: + h = handler.Handler() + h.dry_run = bool(options.dry_run) + + if config is None: + config = policy.load_config(h) if options.with_store: from zeroinstall import zerostore @@ -94,7 +101,7 @@ def main(command_args): try: if options.list: from zeroinstall.cmd import list - list.handle(options, args) + list.handle(config, options, args) elif options.version: import zeroinstall print "0launch (zero-install) " + zeroinstall.version @@ -107,18 +114,18 @@ def main(command_args): elif getattr(options, 'import'): # (import is a keyword) cmd = __import__('zeroinstall.cmd.import', globals(), locals(), ["import"], 0) - cmd.handle(options, args) + cmd.handle(config, options, args) elif options.feed: from zeroinstall.cmd import add_feed - add_feed.handle(options, args, add_ok = True, remove_ok = True) + add_feed.handle(config, options, args, add_ok = True, remove_ok = True) elif options.select_only: from zeroinstall.cmd import select if not options.show: options.quiet = True - select.handle(options, args) + select.handle(config, options, args) elif options.download_only or options.xml or options.show: from zeroinstall.cmd import download - download.handle(options, args) + download.handle(config, options, args) else: if len(args) < 1: if options.gui: @@ -128,7 +135,7 @@ def main(command_args): raise UsageError() else: from zeroinstall.cmd import run - run.handle(options, args) + run.handle(config, options, args) except NeedDownload, ex: # This only happens for dry runs print ex diff --git a/zeroinstall/injector/iface_cache.py b/zeroinstall/injector/iface_cache.py index b519bc8..19186b5 100644 --- a/zeroinstall/injector/iface_cache.py +++ b/zeroinstall/injector/iface_cache.py @@ -195,7 +195,7 @@ class IfaceCache(object): def stores(self): """deprecated""" from zeroinstall.injector import policy - raise Exception("iface_cache.stores") + #raise Exception("iface_cache.stores") return policy.get_deprecated_singleton_config().stores @property diff --git a/zeroinstall/injector/policy.py b/zeroinstall/injector/policy.py index fcf5252..b46b398 100644 --- a/zeroinstall/injector/policy.py +++ b/zeroinstall/injector/policy.py @@ -16,6 +16,7 @@ import ConfigParser from zeroinstall import zerostore, SafeException from zeroinstall.injector import arch from zeroinstall.injector.model import Interface, Implementation, network_levels, network_offline, DistributionImplementation, network_full +from zeroinstall.injector.handler import Handler from zeroinstall.injector.namespaces import config_site, config_prog from zeroinstall.support import tasks, basedir @@ -30,6 +31,7 @@ class Config(object): __slots__ = ['help_with_testing', 'freshness', 'network_use', '_fetcher', '_stores', '_iface_cache', 'handler'] def __init__(self, handler): + assert handler is not None self.help_with_testing = False self.freshness = 60 * 60 * 24 * 30 self.network_use = network_full @@ -90,6 +92,8 @@ def load_config(handler): config.network_use = parser.get('global', 'network_use') config.freshness = int(parser.get('global', 'freshness')) + assert config.network_use in network_levels, self.network_use + return config class Policy(object): @@ -117,9 +121,9 @@ class Policy(object): @ivar stale_feeds: set of feeds which are present but haven't been checked for a long time @type stale_feeds: set """ - __slots__ = ['root', 'watchers', 'command', 'config', - '_warned_offline', - 'target_arch', 'src', 'stale_feeds', 'solver', '_fetcher'] + __slots__ = ['root', 'watchers', 'requirements', 'config', '_warned_offline', + 'command', 'target_arch', 'src', + 'stale_feeds', 'solver'] help_with_testing = property(lambda self: self.config.help_with_testing, lambda self, value: setattr(self.config, 'help_with_testing', bool(value))) @@ -134,7 +138,7 @@ class Policy(object): ready = property(lambda self: self.solver.ready) - def __init__(self, root, handler = None, src = False, command = -1, config = None): + def __init__(self, root = None, handler = None, src = None, command = -1, config = None, requirements = None): """ @param root: The URI of the root interface (the program we want to run). @param handler: A handler for main-loop integration. @@ -147,17 +151,28 @@ class Policy(object): @type config: L{ConfigParser.ConfigParser} """ self.watchers = [] - self.src = src # Root impl must be a "src" machine type + if requirements is None: + from zeroinstall.injector.requirements import Requirements + requirements = Requirements(root) + requirements.source = bool(src) # Root impl must be a "src" machine type + if command == -1: + if src: + command = 'compile' + else: + command = 'run' + requirements.command = command + self.target_arch = arch.get_host_architecture() + else: + assert root == src == None + assert command == -1 + self.target_arch = arch.get_architecture(requirements.os, requirements.cpu) + self.requirements = requirements + self.stale_feeds = set() - if command == -1: - if src: - command = 'compile' - else: - command = 'run' - self.command = command if config is None: - self.config = load_config(handler) + assert False #XXX + self.config = load_config(handler or Handler()) else: assert handler is None, "can't pass a handler and a config" self.config = config @@ -168,16 +183,10 @@ class Policy(object): # If we need to download something but can't because we are offline, # warn the user. But only the first time. self._warned_offline = False - self._fetcher = None debug(_("Supported systems: '%s'"), arch.os_ranks) debug(_("Supported processors: '%s'"), arch.machine_ranks) - assert self.network_use in network_levels, self.network_use - self.set_root(root) - - self.target_arch = arch.get_host_architecture() - @property def fetcher(self): return self.config.fetcher @@ -186,12 +195,6 @@ class Policy(object): def handler(self): return self.config.handler - def set_root(self, root): - """Change the root interface URI.""" - assert isinstance(root, (str, unicode)) - self.root = root - for w in self.watchers: w() - def save_config(self): self.config.save_globals() @@ -203,7 +206,7 @@ class Policy(object): self.stale_feeds = set() host_arch = self.target_arch - if self.src: + if self.requirements.source: host_arch = arch.SourceArchitecture(host_arch) self.solver.solve(self.root, host_arch, command_name = self.command) @@ -230,7 +233,7 @@ class Policy(object): """Generator for C{iface.feeds} that are valid for our architecture. @rtype: generator @see: L{arch}""" - if self.src and iface.uri == self.root: + if self.requirements.source and iface.uri == self.root: # Note: when feeds are recursive, we'll need a better test for root here machine_ranks = {'src': 1} else: @@ -364,7 +367,7 @@ class Policy(object): downloads_in_progress = {} # URL -> Download host_arch = self.target_arch - if self.src: + if self.requirements.source: host_arch = arch.SourceArchitecture(host_arch) # There are three cases: @@ -453,7 +456,7 @@ class Policy(object): @return: true if we MUST download something (feeds or implementations) @rtype: bool""" host_arch = self.target_arch - if self.src: + if self.requirements.source: host_arch = arch.SourceArchitecture(host_arch) self.solver.solve(self.root, host_arch, command_name = self.command) for w in self.watchers: w() @@ -497,10 +500,17 @@ class Policy(object): warnings.warn("Policy.get_interface is deprecated!", DeprecationWarning, stacklevel = 2) return self.config.iface_cache.get_interface(uri) + @property + def command(self): + return self.requirements.command + + @property + def root(self): + return self.requirements.interface_uri + _config = None def get_deprecated_singleton_config(): global _config if _config is None: - from zeroinstall.injector import handler - _config = load_config(handler.Handler()) + _config = load_config(Handler()) return _config diff --git a/zeroinstall/injector/requirements.py b/zeroinstall/injector/requirements.py new file mode 100644 index 0000000..ff2f9e6 --- /dev/null +++ b/zeroinstall/injector/requirements.py @@ -0,0 +1,54 @@ +""" +Holds information about what the user asked for (which program, version constraints, etc). +""" + +# Copyright (C) 2011, Thomas Leonard +# See the README file for details, or visit http://0install.net. + +from zeroinstall import _ + +class Requirements(object): + """ + Holds information about what the user asked for (which program, version constraints, etc). + """ + __slots__ = [ + 'interface_uri', + 'command', + 'source', + 'before', 'not_before', + 'os', 'cpu', + 'message', + ] + + def __init__(self, interface_uri): + self.interface_uri = interface_uri + self.command = 'run' + self.source = False + self.before = self.not_before = None + self.os = self.cpu = None + self.message = None + + def get_as_options(self): + gui_args = [] + if self.not_before: + gui_args.insert(0, self.not_before) + gui_args.insert(0, '--not-before') + if self.before: + gui_args.insert(0, self.before) + gui_args.insert(0, '--before') + if self.source: + gui_args.insert(0, '--source') + if self.message: + gui_args.insert(0, self.message) + gui_args.insert(0, '--message') + if self.cpu: + gui_args.insert(0, self.cpu) + gui_args.insert(0, '--cpu') + if self.os: + gui_args.insert(0, self.os) + gui_args.insert(0, '--os') + if self.command is not None: + gui_args.append('--command') + gui_args.append(self.command) + + return gui_args diff --git a/zeroinstall/zerostore/__init__.py b/zeroinstall/zerostore/__init__.py index d02d789..531e9bf 100644 --- a/zeroinstall/zerostore/__init__.py +++ b/zeroinstall/zerostore/__init__.py @@ -87,7 +87,7 @@ class Store: def add_archive_to_cache(self, required_digest, data, url, extract = None, type = None, start_offset = 0, try_helper = False): import unpack - info(_("Caching new implementation (digest %s)"), required_digest) + info(_("Caching new implementation (digest %s) in %s"), required_digest, self.dir) if self.lookup(required_digest): info(_("Not adding %s as it already exists!"), required_digest) -- 2.11.4.GIT