Implementation.id doesn't have to be path or digest
[zeroinstall.git] / zeroinstall / injector / run.py
blob459e9fcd07be0c0bd21f48ae8b85c29192dbfcf7
1 """
2 Executes a set of implementations as a program.
3 """
5 # Copyright (C) 2009, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 from zeroinstall import _
9 import os, sys
10 from logging import debug, info
12 from zeroinstall.injector.model import SafeException, EnvironmentBinding, ZeroInstallImplementation
13 from zeroinstall.injector.iface_cache import iface_cache
15 def do_env_binding(binding, path):
16 """Update this process's environment by applying the binding.
17 @param binding: the binding to apply
18 @type binding: L{model.EnvironmentBinding}
19 @param path: the selected implementation
20 @type path: str"""
21 os.environ[binding.name] = binding.get_value(path,
22 os.environ.get(binding.name, None))
23 info("%s=%s", binding.name, os.environ[binding.name])
25 def execute(policy, prog_args, dry_run = False, main = None, wrapper = None):
26 """Execute program. On success, doesn't return. On failure, raises an Exception.
27 Returns normally only for a successful dry run.
28 @param policy: a policy with the selected versions
29 @type policy: L{policy.Policy}
30 @param prog_args: arguments to pass to the program
31 @type prog_args: [str]
32 @param dry_run: if True, just print a message about what would have happened
33 @type dry_run: bool
34 @param main: the name of the binary to run, or None to use the default
35 @type main: str
36 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
37 @type wrapper: str
38 @precondition: C{policy.ready and policy.get_uncached_implementations() == []}
39 """
41 for iface, impl in policy.implementation.iteritems():
42 assert impl
43 _do_bindings(impl, impl.bindings)
44 for dep in policy.solver.requires[iface]:
45 dep_iface = iface_cache.get_interface(dep.interface)
46 dep_impl = policy.get_implementation(dep_iface)
47 if isinstance(dep_impl, ZeroInstallImplementation):
48 _do_bindings(dep_impl, dep.bindings)
49 else:
50 debug(_("Implementation %s is native; no bindings needed"), dep_impl)
52 root_iface = iface_cache.get_interface(policy.root)
53 root_impl = policy.get_implementation(root_iface)
54 _execute(root_impl, prog_args, dry_run, main, wrapper)
56 def _do_bindings(impl, bindings):
57 for b in bindings:
58 if isinstance(b, EnvironmentBinding):
59 do_env_binding(b, _get_implementation_path(impl))
61 def _get_implementation_path(impl):
62 return impl.local_path or iface_cache.stores.lookup_any(impl.digests)
64 def execute_selections(selections, prog_args, dry_run = False, main = None, wrapper = None):
65 """Execute program. On success, doesn't return. On failure, raises an Exception.
66 Returns normally only for a successful dry run.
67 @param selections: the selected versions
68 @type selections: L{selections.Selections}
69 @param prog_args: arguments to pass to the program
70 @type prog_args: [str]
71 @param dry_run: if True, just print a message about what would have happened
72 @type dry_run: bool
73 @param main: the name of the binary to run, or None to use the default
74 @type main: str
75 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
76 @type wrapper: str
77 @since: 0.27
78 @precondition: All implementations are in the cache.
79 """
80 sels = selections.selections
81 for selection in sels.values():
82 _do_bindings(selection, selection.bindings)
83 for dep in selection.dependencies:
84 dep_impl = sels[dep.interface]
85 if not dep_impl.id.startswith('package:'):
86 _do_bindings(dep_impl, dep.bindings)
88 root_impl = sels[selections.interface]
89 _execute(root_impl, prog_args, dry_run, main, wrapper)
91 def test_selections(selections, prog_args, dry_run, main, wrapper = None):
92 """Run the program in a child process, collecting stdout and stderr.
93 @return: the output produced by the process
94 @since: 0.27
95 """
96 args = []
97 import tempfile
98 output = tempfile.TemporaryFile(prefix = '0launch-test')
99 try:
100 child = os.fork()
101 if child == 0:
102 # We are the child
103 try:
104 try:
105 os.dup2(output.fileno(), 1)
106 os.dup2(output.fileno(), 2)
107 execute_selections(selections, prog_args, dry_run, main)
108 except:
109 import traceback
110 traceback.print_exc()
111 finally:
112 sys.stdout.flush()
113 sys.stderr.flush()
114 os._exit(1)
116 info(_("Waiting for test process to finish..."))
118 pid, status = os.waitpid(child, 0)
119 assert pid == child
121 output.seek(0)
122 results = output.read()
123 if status != 0:
124 results += _("Error from child process: exit code = %d") % status
125 finally:
126 output.close()
128 return results
130 def _execute(root_impl, prog_args, dry_run, main, wrapper):
131 assert root_impl is not None
133 if root_impl.id.startswith('package:'):
134 main = main or root_impl.main
135 prog_path = main
136 else:
137 if main is None:
138 main = root_impl.main
139 elif main.startswith('/'):
140 main = main[1:]
141 elif root_impl.main:
142 main = os.path.join(os.path.dirname(root_impl.main), main)
143 if main is not None:
144 prog_path = os.path.join(_get_implementation_path(root_impl), main)
146 if main is None:
147 raise SafeException(_("Implementation '%s' cannot be executed directly; it is just a library "
148 "to be used by other programs (or missing 'main' attribute)") %
149 root_impl)
151 if not os.path.exists(prog_path):
152 raise SafeException(_("File '%(program_path)s' does not exist.\n"
153 "(implementation '%(implementation_id)s' + program '%(main)s')") %
154 {'program_path': prog_path, 'implementation_id': root_impl.id,
155 'main': main})
156 if wrapper:
157 prog_args = ['-c', wrapper + ' "$@"', '-', prog_path] + list(prog_args)
158 prog_path = '/bin/sh'
160 if dry_run:
161 print _("Would execute: %s") % ' '.join([prog_path] + prog_args)
162 else:
163 info(_("Executing: %s"), prog_path)
164 sys.stdout.flush()
165 sys.stderr.flush()
166 try:
167 os.execl(prog_path, prog_path, *prog_args)
168 except OSError, ex:
169 raise SafeException(_("Failed to run '%(program_path)s': %(exception)s") % {'program_path': prog_path, 'exception': str(ex)})