2 Executes a set of implementations as a program.
5 # Copyright (C) 2009, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 from zeroinstall
import _
10 from logging
import debug
, info
12 from zeroinstall
.injector
.model
import SafeException
, EnvironmentBinding
13 from zeroinstall
.injector
import namespaces
14 from zeroinstall
.injector
.iface_cache
import iface_cache
16 def do_env_binding(binding
, path
):
17 """Update this process's environment by applying the binding.
18 @param binding: the binding to apply
19 @type binding: L{model.EnvironmentBinding}
20 @param path: the selected implementation
22 os
.environ
[binding
.name
] = binding
.get_value(path
,
23 os
.environ
.get(binding
.name
, None))
24 info("%s=%s", binding
.name
, os
.environ
[binding
.name
])
26 def execute(policy
, prog_args
, dry_run
= False, main
= None, wrapper
= None):
27 """Execute program. On success, doesn't return. On failure, raises an Exception.
28 Returns normally only for a successful dry run.
29 @param policy: a policy with the selected versions
30 @type policy: L{policy.Policy}
31 @param prog_args: arguments to pass to the program
32 @type prog_args: [str]
33 @param dry_run: if True, just print a message about what would have happened
35 @param main: the name of the binary to run, or None to use the default
37 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
39 @precondition: C{policy.ready and policy.get_uncached_implementations() == []}
41 execute_selections(policy
.solver
.selections
, prog_args
, dry_run
, main
, wrapper
)
43 def _do_bindings(impl
, bindings
):
45 if isinstance(b
, EnvironmentBinding
):
46 do_env_binding(b
, _get_implementation_path(impl
))
48 def _get_implementation_path(impl
):
49 return impl
.local_path
or iface_cache
.stores
.lookup_any(impl
.digests
)
51 def test_selections(selections
, prog_args
, dry_run
, main
, wrapper
= None):
52 """Run the program in a child process, collecting stdout and stderr.
53 @return: the output produced by the process
58 output
= tempfile
.TemporaryFile(prefix
= '0launch-test')
65 os
.dup2(output
.fileno(), 1)
66 os
.dup2(output
.fileno(), 2)
67 execute_selections(selections
, prog_args
, dry_run
, main
)
76 info(_("Waiting for test process to finish..."))
78 pid
, status
= os
.waitpid(child
, 0)
82 results
= output
.read()
84 results
+= _("Error from child process: exit code = %d") % status
90 def execute_selections(selections
, prog_args
, dry_run
= False, main
= None, wrapper
= None):
91 """Execute program. On success, doesn't return. On failure, raises an Exception.
92 Returns normally only for a successful dry run.
93 @param selections: the selected versions
94 @type selections: L{selections.Selections}
95 @param prog_args: arguments to pass to the program
96 @type prog_args: [str]
97 @param dry_run: if True, just print a message about what would have happened
99 @param main: the name of the binary to run, or None to use the default
101 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
104 @precondition: All implementations are in the cache.
106 sels
= selections
.selections
107 for selection
in sels
.values():
108 _do_bindings(selection
, selection
.bindings
)
109 for dep
in selection
.dependencies
:
110 dep_impl
= sels
[dep
.interface
]
111 if not dep_impl
.id.startswith('package:'):
112 _do_bindings(dep_impl
, dep
.bindings
)
114 root_sel
= sels
[selections
.interface
]
116 assert root_sel
is not None
118 command
= selections
.command
120 # Process command's dependencies' bindings
121 for dep
in command
.requires
:
122 dep_impl
= sels
[dep
.interface
]
123 if not dep_impl
.id.startswith('package:'):
124 _do_bindings(dep_impl
, dep
.bindings
)
126 command_path
= command
.path
130 for child
in command
.qdom
.childNodes
:
131 if child
.uri
== namespaces
.XMLNS_IFACE
and child
.name
== 'arg':
132 command_args
.append(child
.content
)
133 prog_args
= command_args
+ prog_args
135 if root_sel
.id.startswith('package:'):
136 main
= main
or command_path
139 if command_path
and command_path
.startswith('/'):
140 raise SafeException(_("Command path must be relative, but '%s' starts with '/'!") %
143 main
= command_path
# User didn't specify a main path
144 elif main
.startswith('/'):
145 main
= main
[1:] # User specified a path relative to the package root
147 main
= os
.path
.join(os
.path
.dirname(command_path
), main
) # User main is relative to command's name
150 prog_path
= os
.path
.join(_get_implementation_path(root_sel
), main
)
153 # This shouldn't happen, because the solve would fail first.
154 raise Exception(_("Implementation '%s' cannot be executed directly; it is just a library "
155 "to be used by other programs (or missing 'main' attribute)") %
158 if not os
.path
.exists(prog_path
):
159 raise SafeException(_("File '%(program_path)s' does not exist.\n"
160 "(implementation '%(implementation_id)s' + program '%(main)s')") %
161 {'program_path': prog_path
, 'implementation_id': root_sel
.id,
164 prog_args
= ['-c', wrapper
+ ' "$@"', '-', prog_path
] + list(prog_args
)
165 prog_path
= '/bin/sh'
168 print _("Would execute: %s") % ' '.join([prog_path
] + prog_args
)
170 info(_("Executing: %s"), prog_path
)
174 os
.execl(prog_path
, prog_path
, *prog_args
)
176 raise SafeException(_("Failed to run '%(program_path)s': %(exception)s") % {'program_path': prog_path
, 'exception': str(ex
)})