2 Executes a set of implementations as a program.
5 # Copyright (C) 2006, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
10 from logging
import debug
, info
12 from zeroinstall
.injector
import selections
13 from zeroinstall
.injector
.model
import SafeException
, EnvironmentBinding
, ZeroInstallImplementation
14 from zeroinstall
.injector
.iface_cache
import iface_cache
15 from zeroinstall
.support
import basedir
17 def do_env_binding(environ
, binding
, path
):
18 """Update this process's environment by applying the binding.
19 @param binding: the binding to apply
20 @type binding: L{model.EnvironmentBinding}
21 @param path: the selected implementation
23 environ
[binding
.name
] = binding
.get_value(path
,
24 environ
.get(binding
.name
, None))
25 info("%s=%s", binding
.name
, environ
[binding
.name
])
27 def add_env_bindings(environ
, sels
):
28 for selection
in sels
.values():
29 _do_bindings(sels
, environ
, selection
, selection
.bindings
)
30 for dep
in selection
.dependencies
:
31 dep_impl
= sels
[dep
.interface
]
32 if not dep_impl
.id.startswith('package:'):
33 _do_bindings(sels
, environ
, dep_impl
, dep
.bindings
)
35 debug("Implementation %s is native; no bindings needed", dep_impl
)
37 def execute(policy
, prog_args
, dry_run
= False, main
= None, wrapper
= None):
38 """Execute program. On success, doesn't return. On failure, raises an Exception.
39 Returns normally only for a successful dry run.
40 @param policy: a policy with the selected versions
41 @type policy: L{policy.Policy}
42 @param prog_args: arguments to pass to the program
43 @type prog_args: [str]
44 @param dry_run: if True, just print a message about what would have happened
46 @param main: the name of the binary to run, or None to use the default
48 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
50 @precondition: C{policy.ready and policy.get_uncached_implementations() == []}
52 selns
= selections
.Selections(policy
)
53 execute_selections(selns
, prog_args
, dry_run
=dry_run
, main
=main
,
56 def _do_bindings(selns
, environ
, impl
, bindings
):
58 if isinstance(b
, EnvironmentBinding
):
59 do_env_binding(environ
, b
, _get_implementation_path(selns
, impl
))
61 def read_file(filename
):
62 fh
= open(filename
, "r")
68 def write_file(filename
, data
):
69 fh
= open(filename
, "w")
75 def copy_tree(src_path
, dest_path
, copy_file
):
76 if os
.path
.islink(src_path
):
77 if os
.path
.islink(dest_path
):
79 os
.symlink(os
.readlink(src_path
), dest_path
)
80 elif os
.path
.isdir(src_path
):
81 if not os
.path
.exists(dest_path
):
83 for leafname
in os
.listdir(src_path
):
84 copy_tree(os
.path
.join(src_path
, leafname
),
85 os
.path
.join(dest_path
, leafname
),
88 copy_file(src_path
, dest_path
)
90 def rewrite_dir(impl_dir
, dep_rewrites
, self_rewrites
):
91 cache_dir
= os
.path
.join(basedir
.xdg_cache_home
, "0install.net", "rewrites")
92 if not os
.path
.exists(cache_dir
):
93 os
.makedirs(cache_dir
)
94 digest
= hashlib
.sha1(str(dep_rewrites
+ self_rewrites
)).hexdigest()
95 # We don't need the full path. Rewrites are local.
97 dest_dir
= os
.path
.join(cache_dir
, leafname
)
98 # TODO: fix race condition
99 if os
.path
.exists(dest_dir
):
101 rewrites
= dep_rewrites
[:]
102 for rewrite
in self_rewrites
:
103 rewrites
.append((rewrite
, dest_dir
))
105 def copy_file(src_path
, dest_path
):
106 data
= read_file(src_path
)
107 # This is not an optimal way to do several
108 # substitutions, but when I tried to use the "array"
109 # module to do it in-place, it was slower.
110 for from_str
, to_str
in rewrites
:
111 data
= data
.replace(from_str
, to_str
)
112 write_file(dest_path
, data
)
113 os
.chmod(dest_path
, os
.stat(src_path
).st_mode
)
115 copy_tree(impl_dir
, dest_dir
, copy_file
)
118 def _get_implementation_path(selns
, impl
):
119 if impl
.id.startswith('/'):
122 for dependency
in impl
.dependencies
:
123 for rewrite_string
in dependency
.rewrites
:
124 dep_impl
= selns
.selections
[dependency
.interface
]
125 to_path
= _get_implementation_path(selns
, dep_impl
)
126 rewrites
.append((rewrite_string
, to_path
))
127 impl_dir
= iface_cache
.stores
.lookup(impl
.id)
128 if len(rewrites
) == 0 and len(impl
.self_rewrites
) == 0:
130 return rewrite_dir(impl_dir
, rewrites
, impl
.self_rewrites
)
132 def execute_selections(selections
, prog_args
, dry_run
= False, main
= None, wrapper
= None):
133 """Execute program. On success, doesn't return. On failure, raises an Exception.
134 Returns normally only for a successful dry run.
135 @param selections: the selected versions
136 @type selections: L{selections.Selections}
137 @param prog_args: arguments to pass to the program
138 @type prog_args: [str]
139 @param dry_run: if True, just print a message about what would have happened
141 @param main: the name of the binary to run, or None to use the default
143 @param wrapper: a command to use to actually run the binary, or None to run the binary directly
146 @precondition: All implementations are in the cache.
148 add_env_bindings(os
.environ
, selections
.selections
)
149 _execute(selections
, prog_args
, dry_run
, main
, wrapper
)
151 def test_selections(selections
, prog_args
, dry_run
, main
, wrapper
= None):
152 """Run the program in a child process, collecting stdout and stderr.
153 @return: the output produced by the process
158 output
= tempfile
.TemporaryFile(prefix
= '0launch-test')
165 os
.dup2(output
.fileno(), 1)
166 os
.dup2(output
.fileno(), 2)
167 execute_selections(selections
, prog_args
, dry_run
, main
)
170 traceback
.print_exc()
176 info("Waiting for test process to finish...")
178 pid
, status
= os
.waitpid(child
, 0)
182 results
= output
.read()
184 results
+= "Error from child process: exit code = %d" % status
190 def get_executable_for_selections(selns
, main
=None):
191 root_impl
= selns
.selections
[selns
.interface
]
192 assert root_impl
is not None
194 if root_impl
.id.startswith('package:'):
195 main
= main
or root_impl
.main
199 main
= root_impl
.main
200 elif main
.startswith('/'):
203 main
= os
.path
.join(os
.path
.dirname(root_impl
.main
), main
)
205 prog_path
= os
.path
.join(_get_implementation_path(selns
, root_impl
), main
)
208 raise SafeException("Implementation '%s' cannot be executed directly; it is just a library "
209 "to be used by other programs (or missing 'main' attribute)" %
212 if not os
.path
.exists(prog_path
):
213 raise SafeException("File '%s' does not exist.\n"
214 "(implementation '%s' + program '%s')" %
215 (prog_path
, root_impl
.id, main
))
218 def _execute(selns
, prog_args
, dry_run
, main
, wrapper
):
219 prog_path
= get_executable_for_selections(selns
, main
)
221 prog_args
= ['-c', wrapper
+ ' "$@"', '-', prog_path
] + list(prog_args
)
222 prog_path
= '/bin/sh'
225 print "Would execute:", prog_path
, ' '.join(prog_args
)
227 info("Executing: %s", prog_path
)
231 os
.execl(prog_path
, prog_path
, *prog_args
)
233 raise SafeException("Failed to run '%s': %s" % (prog_path
, str(ex
)))