Add test with an example of a feed with a dependency
[zeroinstall/zeroinstall-mseaborn.git] / zeroinstall / injector / run.py
blobb81b5156de6878888a9dee5a615d9348b8501fce
1 """
2 Executes a set of implementations as a program.
3 """
5 # Copyright (C) 2006, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 import os, sys
9 from logging import debug, info
11 from zeroinstall.injector.model import SafeException, EnvironmentBinding, ZeroInstallImplementation
12 from zeroinstall.injector.iface_cache import iface_cache
14 def do_env_binding(binding, path):
15 """Update this process's environment by applying the binding.
16 @param binding: the binding to apply
17 @type binding: L{model.EnvironmentBinding}
18 @param path: the selected implementation
19 @type path: str"""
20 os.environ[binding.name] = binding.get_value(path,
21 os.environ.get(binding.name, None))
22 info("%s=%s", binding.name, os.environ[binding.name])
24 def add_env_bindings(policy):
25 for impl in policy.implementation.values():
26 assert impl
27 _do_bindings(impl, impl.bindings)
28 for dep in impl.requires:
29 dep_iface = iface_cache.get_interface(dep.interface)
30 dep_impl = policy.get_implementation(dep_iface)
31 if isinstance(dep_impl, ZeroInstallImplementation):
32 _do_bindings(dep_impl, dep.bindings)
33 else:
34 debug("Implementation %s is native; no bindings needed", dep_impl)
36 def execute(policy, prog_args, dry_run = False, main = None, wrapper = None):
37 """Execute program. On success, doesn't return. On failure, raises an Exception.
38 Returns normally only for a successful dry run.
39 @param policy: a policy with the selected versions
40 @type policy: L{policy.Policy}
41 @param prog_args: arguments to pass to the program
42 @type prog_args: [str]
43 @param dry_run: if True, just print a message about what would have happened
44 @type dry_run: bool
45 @param main: the name of the binary to run, or None to use the default
46 @type main: str
47 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
48 @type wrapper: str
49 @precondition: C{policy.ready and policy.get_uncached_implementations() == []}
50 """
51 iface = iface_cache.get_interface(policy.root)
52 add_env_bindings(policy)
53 root_impl = policy.get_implementation(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.id))
61 def _get_implementation_path(id):
62 if id.startswith('/'): return id
63 return iface_cache.stores.lookup(id)
65 def execute_selections(selections, prog_args, dry_run = False, main = None, wrapper = None):
66 """Execute program. On success, doesn't return. On failure, raises an Exception.
67 Returns normally only for a successful dry run.
68 @param selections: the selected versions
69 @type selections: L{selections.Selections}
70 @param prog_args: arguments to pass to the program
71 @type prog_args: [str]
72 @param dry_run: if True, just print a message about what would have happened
73 @type dry_run: bool
74 @param main: the name of the binary to run, or None to use the default
75 @type main: str
76 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
77 @type wrapper: str
78 @since: 0.27
79 @precondition: All implementations are in the cache.
80 """
81 sels = selections.selections
82 for selection in sels.values():
83 _do_bindings(selection, selection.bindings)
84 for dep in selection.dependencies:
85 dep_impl = sels[dep.interface]
86 if not dep_impl.id.startswith('package:'):
87 _do_bindings(dep_impl, dep.bindings)
89 root_impl = sels[selections.interface]
90 _execute(root_impl, prog_args, dry_run, main, wrapper)
92 def test_selections(selections, prog_args, dry_run, main, wrapper = None):
93 """Run the program in a child process, collecting stdout and stderr.
94 @return: the output produced by the process
95 @since: 0.27
96 """
97 args = []
98 import tempfile
99 output = tempfile.TemporaryFile(prefix = '0launch-test')
100 try:
101 child = os.fork()
102 if child == 0:
103 # We are the child
104 try:
105 try:
106 os.dup2(output.fileno(), 1)
107 os.dup2(output.fileno(), 2)
108 execute_selections(selections, prog_args, dry_run, main)
109 except:
110 import traceback
111 traceback.print_exc()
112 finally:
113 sys.stdout.flush()
114 sys.stderr.flush()
115 os._exit(1)
117 info("Waiting for test process to finish...")
119 pid, status = os.waitpid(child, 0)
120 assert pid == child
122 output.seek(0)
123 results = output.read()
124 if status != 0:
125 results += "Error from child process: exit code = %d" % status
126 finally:
127 output.close()
129 return results
131 def _execute(root_impl, prog_args, dry_run, main, wrapper):
132 assert root_impl is not None
134 if root_impl.id.startswith('package:'):
135 main = main or root_impl.main
136 prog_path = main
137 else:
138 if main is None:
139 main = root_impl.main
140 elif main.startswith('/'):
141 main = main[1:]
142 elif root_impl.main:
143 main = os.path.join(os.path.dirname(root_impl.main), main)
144 if main:
145 prog_path = os.path.join(_get_implementation_path(root_impl.id), main)
147 if main is None:
148 raise SafeException("Implementation '%s' cannot be executed directly; it is just a library "
149 "to be used by other programs (or missing 'main' attribute)" %
150 root_impl)
152 if not os.path.exists(prog_path):
153 raise SafeException("File '%s' does not exist.\n"
154 "(implementation '%s' + program '%s')" %
155 (prog_path, root_impl.id, 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:", prog_path, ' '.join(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 '%s': %s" % (prog_path, str(ex)))