Describe function arguments for run module.
[zeroinstall/zeroinstall-mseaborn.git] / zeroinstall / injector / run.py
blob10946e0d46a7bbee2177f031c77ef615b0127a27
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 Interface, SafeException, EnvironmentBinding, DistributionImplementation, 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 execute(policy, prog_args, dry_run = False, main = None, wrapper = None):
25 """Execute program. On success, doesn't return. On failure, raises an Exception.
26 Returns normally only for a successful dry run.
27 @param policy: a policy with the selected versions
28 @type policy: L{policy.Policy}
29 @param prog_args: arguments to pass to the program
30 @type prog_args: [str]
31 @param dry_run: if True, just print a message about what would have happened
32 @type dry_run: bool
33 @param main: the name of the binary to run, or None to use the default
34 @type main: str
35 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
36 @type wrapper: str
37 @precondition: C{policy.ready and policy.get_uncached_implementations() == []}
38 """
39 iface = iface_cache.get_interface(policy.root)
41 for needed_iface in policy.implementation:
42 impl = policy.implementation[needed_iface]
43 assert impl
44 _do_bindings(impl, impl.bindings)
45 for dep in impl.requires:
46 dep_iface = iface_cache.get_interface(dep.interface)
47 dep_impl = policy.get_implementation(dep_iface)
48 if isinstance(dep_impl, ZeroInstallImplementation):
49 _do_bindings(dep_impl, dep.bindings)
50 else:
51 debug("Implementation %s is native; no bindings needed", dep_impl)
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)))