]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | # Status: ported. |
2 | # Base revision 64444. | |
3 | # | |
4 | # Copyright 2003 Dave Abrahams | |
5 | # Copyright 2005, 2006 Rene Rivera | |
6 | # Copyright 2002, 2003, 2004, 2005, 2006, 2010 Vladimir Prus | |
7 | # Distributed under the Boost Software License, Version 1.0. | |
1e59de90 | 8 | # (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt) |
7c673cae FG |
9 | |
10 | # This module defines the 'install' rule, used to copy a set of targets to a | |
11 | # single location. | |
12 | ||
13 | import b2.build.feature as feature | |
14 | import b2.build.targets as targets | |
15 | import b2.build.property as property | |
16 | import b2.build.property_set as property_set | |
17 | import b2.build.generators as generators | |
18 | import b2.build.virtual_target as virtual_target | |
19 | ||
20 | from b2.manager import get_manager | |
21 | from b2.util.sequence import unique | |
22 | from b2.util import bjam_signature | |
23 | ||
24 | import b2.build.type | |
25 | ||
26 | import os.path | |
27 | import re | |
28 | import types | |
29 | ||
30 | feature.feature('install-dependencies', ['off', 'on'], ['incidental']) | |
31 | feature.feature('install-type', [], ['free', 'incidental']) | |
32 | feature.feature('install-source-root', [], ['free', 'path']) | |
33 | feature.feature('so-version', [], ['free', 'incidental']) | |
34 | ||
35 | # If 'on', version symlinks for shared libraries will not be created. Affects | |
36 | # Unix builds only. | |
37 | feature.feature('install-no-version-symlinks', ['on'], ['optional', 'incidental']) | |
38 | ||
39 | class InstallTargetClass(targets.BasicTarget): | |
40 | ||
41 | def update_location(self, ps): | |
42 | """If <location> is not set, sets it based on the project data.""" | |
43 | ||
44 | loc = ps.get('location') | |
45 | if not loc: | |
46 | loc = os.path.join(self.project().get('location'), self.name()) | |
47 | ps = ps.add_raw(["<location>" + loc]) | |
48 | ||
49 | return ps | |
50 | ||
51 | def adjust_properties(self, target, build_ps): | |
52 | a = target.action() | |
53 | properties = [] | |
54 | if a: | |
55 | ps = a.properties() | |
56 | properties = ps.all() | |
57 | ||
58 | # Unless <hardcode-dll-paths>true is in properties, which can happen | |
59 | # only if the user has explicitly requested it, nuke all <dll-path> | |
60 | # properties. | |
61 | ||
62 | if build_ps.get('hardcode-dll-paths') != ['true']: | |
63 | properties = [p for p in properties if p.feature.name != 'dll-path'] | |
64 | ||
65 | # If any <dll-path> properties were specified for installing, add | |
66 | # them. | |
67 | properties.extend(build_ps.get_properties('dll-path')) | |
68 | ||
69 | # Also copy <linkflags> feature from current build set, to be used | |
70 | # for relinking. | |
71 | properties.extend(build_ps.get_properties('linkflags')) | |
72 | ||
73 | # Remove the <tag> feature on original targets. | |
74 | # And <location>. If stage target has another stage target in | |
75 | # sources, then we shall get virtual targets with the <location> | |
76 | # property set. | |
77 | properties = [p for p in properties | |
78 | if not p.feature.name in ['tag', 'location']] | |
79 | ||
80 | properties.extend(build_ps.get_properties('dependency')) | |
81 | ||
82 | properties.extend(build_ps.get_properties('location')) | |
83 | ||
84 | ||
85 | properties.extend(build_ps.get_properties('install-no-version-symlinks')) | |
86 | ||
87 | d = build_ps.get_properties('install-source-root') | |
88 | ||
89 | # Make the path absolute: we shall use it to compute relative paths and | |
90 | # making the path absolute will help. | |
91 | if d: | |
92 | p = d[0] | |
93 | properties.append(property.Property(p.feature, os.path.abspath(p.value))) | |
94 | ||
95 | return property_set.create(properties) | |
96 | ||
97 | ||
98 | def construct(self, name, source_targets, ps): | |
99 | ||
100 | source_targets = self.targets_to_stage(source_targets, ps) | |
101 | ps = self.update_location(ps) | |
102 | ||
103 | ename = ps.get('name') | |
104 | if ename: | |
105 | ename = ename[0] | |
106 | if ename and len(source_targets) > 1: | |
107 | get_manager().errors()("When <name> property is used in 'install', only one source is allowed") | |
108 | ||
109 | result = [] | |
110 | ||
111 | for i in source_targets: | |
112 | ||
113 | staged_targets = [] | |
114 | new_ps = self.adjust_properties(i, ps) | |
115 | ||
116 | # See if something special should be done when staging this type. It | |
117 | # is indicated by the presence of a special "INSTALLED_" type. | |
118 | t = i.type() | |
119 | if t and b2.build.type.registered("INSTALLED_" + t): | |
120 | ||
121 | if ename: | |
122 | get_manager().errors()("In 'install': <name> property specified with target that requires relinking.") | |
123 | else: | |
124 | (r, targets) = generators.construct(self.project(), name, "INSTALLED_" + t, | |
125 | new_ps, [i]) | |
126 | assert isinstance(r, property_set.PropertySet) | |
127 | staged_targets.extend(targets) | |
128 | ||
129 | else: | |
130 | staged_targets.append(copy_file(self.project(), ename, i, new_ps)) | |
131 | ||
132 | if not staged_targets: | |
133 | get_manager().errors()("Unable to generate staged version of " + i) | |
134 | ||
135 | result.extend(get_manager().virtual_targets().register(t) for t in staged_targets) | |
136 | ||
137 | return (property_set.empty(), result) | |
138 | ||
139 | def targets_to_stage(self, source_targets, ps): | |
140 | """Given the list of source targets explicitly passed to 'stage', returns the | |
141 | list of targets which must be staged.""" | |
142 | ||
143 | result = [] | |
144 | ||
145 | # Traverse the dependencies, if needed. | |
146 | if ps.get('install-dependencies') == ['on']: | |
147 | source_targets = self.collect_targets(source_targets) | |
148 | ||
149 | # Filter the target types, if needed. | |
150 | included_types = ps.get('install-type') | |
151 | for r in source_targets: | |
152 | ty = r.type() | |
153 | if ty: | |
154 | # Do not stage searched libs. | |
155 | if ty != "SEARCHED_LIB": | |
156 | if included_types: | |
157 | if self.include_type(ty, included_types): | |
158 | result.append(r) | |
159 | else: | |
160 | result.append(r) | |
161 | elif not included_types: | |
162 | # Don't install typeless target if there is an explicit list of | |
163 | # allowed types. | |
164 | result.append(r) | |
165 | ||
166 | return result | |
167 | ||
168 | # CONSIDER: figure out why we can not use virtual-target.traverse here. | |
169 | # | |
170 | def collect_targets(self, targets): | |
171 | ||
172 | s = [t.creating_subvariant() for t in targets] | |
173 | s = unique(filter(lambda l: l != None,s)) | |
174 | ||
175 | result = set(targets) | |
176 | for i in s: | |
177 | i.all_referenced_targets(result) | |
178 | ||
179 | result2 = [] | |
180 | for r in result: | |
181 | if isinstance(r, property.Property): | |
182 | ||
183 | if r.feature.name != 'use': | |
184 | result2.append(r.value) | |
185 | else: | |
186 | result2.append(r) | |
187 | result2 = unique(result2) | |
188 | return result2 | |
189 | ||
190 | # Returns true iff 'type' is subtype of some element of 'types-to-include'. | |
191 | # | |
192 | def include_type(self, type, types_to_include): | |
193 | return any(b2.build.type.is_subtype(type, ti) for ti in types_to_include) | |
194 | ||
195 | # Creates a copy of target 'source'. The 'properties' object should have a | |
196 | # <location> property which specifies where the target must be placed. | |
197 | # | |
198 | def copy_file(project, name, source, ps): | |
199 | ||
200 | if not name: | |
201 | name = source.name() | |
202 | ||
203 | relative = "" | |
204 | ||
205 | new_a = virtual_target.NonScanningAction([source], "common.copy", ps) | |
206 | source_root = ps.get('install-source-root') | |
207 | if source_root: | |
208 | source_root = source_root[0] | |
209 | # Get the real path of the target. We probably need to strip relative | |
210 | # path from the target name at construction. | |
211 | path = os.path.join(source.path(), os.path.dirname(name)) | |
212 | # Make the path absolute. Otherwise, it would be hard to compute the | |
213 | # relative path. The 'source-root' is already absolute, see the | |
214 | # 'adjust-properties' method above. | |
215 | path = os.path.abspath(path) | |
216 | ||
217 | relative = os.path.relpath(path, source_root) | |
218 | ||
219 | name = os.path.join(relative, os.path.basename(name)) | |
220 | return virtual_target.FileTarget(name, source.type(), project, new_a, exact=True) | |
221 | ||
222 | def symlink(name, project, source, ps): | |
223 | a = virtual_target.Action([source], "symlink.ln", ps) | |
224 | return virtual_target.FileTarget(name, source.type(), project, a, exact=True) | |
225 | ||
226 | def relink_file(project, source, ps): | |
227 | action = source[0].action() | |
228 | cloned_action = virtual_target.clone_action(action, project, "", ps) | |
229 | targets = cloned_action.targets() | |
230 | # We relink only on Unix, where exe or shared lib is always a single file. | |
231 | assert len(targets) == 1 | |
232 | return targets[0] | |
233 | ||
234 | ||
235 | # Declare installed version of the EXE type. Generator for this type will cause | |
236 | # relinking to the new location. | |
237 | b2.build.type.register('INSTALLED_EXE', [], 'EXE') | |
238 | ||
239 | class InstalledExeGenerator(generators.Generator): | |
240 | ||
241 | def __init__(self): | |
242 | generators.Generator.__init__(self, "install-exe", False, ['EXE'], ['INSTALLED_EXE']) | |
243 | ||
244 | def run(self, project, name, ps, source): | |
245 | ||
246 | need_relink = False; | |
247 | ||
248 | if ps.get('os') in ['NT', 'CYGWIN'] or ps.get('target-os') in ['windows', 'cygwin']: | |
249 | # Never relink | |
250 | pass | |
251 | else: | |
252 | # See if the dll-path properties are not changed during | |
253 | # install. If so, copy, don't relink. | |
254 | need_relink = source[0].action() and ps.get('dll-path') != source[0].action().properties().get('dll-path') | |
255 | ||
256 | if need_relink: | |
257 | return [relink_file(project, source, ps)] | |
258 | else: | |
259 | return [copy_file(project, None, source[0], ps)] | |
260 | ||
261 | generators.register(InstalledExeGenerator()) | |
262 | ||
263 | ||
264 | # Installing a shared link on Unix might cause a creation of versioned symbolic | |
265 | # links. | |
266 | b2.build.type.register('INSTALLED_SHARED_LIB', [], 'SHARED_LIB') | |
267 | ||
268 | class InstalledSharedLibGenerator(generators.Generator): | |
269 | ||
270 | def __init__(self): | |
271 | generators.Generator.__init__(self, 'install-shared-lib', False, ['SHARED_LIB'], ['INSTALLED_SHARED_LIB']) | |
272 | ||
273 | def run(self, project, name, ps, source): | |
274 | ||
275 | source = source[0] | |
276 | if ps.get('os') in ['NT', 'CYGWIN'] or ps.get('target-os') in ['windows', 'cygwin']: | |
277 | copied = copy_file(project, None, source, ps) | |
278 | return [get_manager().virtual_targets().register(copied)] | |
279 | else: | |
280 | a = source.action() | |
281 | if not a: | |
282 | # Non-derived file, just copy. | |
283 | copied = copy_file(project, None, source, ps) | |
284 | else: | |
285 | ||
286 | need_relink = ps.get('dll-path') != source.action().properties().get('dll-path') | |
287 | ||
288 | if need_relink: | |
289 | # Rpath changed, need to relink. | |
290 | copied = relink_file(project, source, ps) | |
291 | else: | |
292 | copied = copy_file(project, None, source, ps) | |
293 | ||
294 | result = [get_manager().virtual_targets().register(copied)] | |
295 | # If the name is in the form NNN.XXX.YYY.ZZZ, where all 'X', 'Y' and | |
296 | # 'Z' are numbers, we need to create NNN.XXX and NNN.XXX.YYY | |
297 | # symbolic links. | |
298 | m = re.match("(.*)\\.([0123456789]+)\\.([0123456789]+)\\.([0123456789]+)$", | |
299 | copied.name()); | |
300 | if m: | |
301 | # Symlink without version at all is used to make | |
302 | # -lsome_library work. | |
303 | result.append(symlink(m.group(1), project, copied, ps)) | |
304 | ||
305 | # Symlinks of some libfoo.N and libfoo.N.M are used so that | |
306 | # library can found at runtime, if libfoo.N.M.X has soname of | |
307 | # libfoo.N. That happens when the library makes some binary | |
308 | # compatibility guarantees. If not, it is possible to skip those | |
309 | # symlinks. | |
310 | if ps.get('install-no-version-symlinks') != ['on']: | |
311 | ||
312 | result.append(symlink(m.group(1) + '.' + m.group(2), project, copied, ps)) | |
313 | result.append(symlink(m.group(1) + '.' + m.group(2) + '.' + m.group(3), | |
314 | project, copied, ps)) | |
315 | ||
316 | return result | |
317 | ||
318 | generators.register(InstalledSharedLibGenerator()) | |
319 | ||
320 | ||
321 | # Main target rule for 'install'. | |
322 | # | |
323 | @bjam_signature((["name"], ["sources", "*"], ["requirements", "*"], | |
324 | ["default_build", "*"], ["usage_requirements", "*"])) | |
325 | def install(name, sources, requirements=[], default_build=[], usage_requirements=[]): | |
326 | ||
327 | requirements = requirements[:] | |
328 | # Unless the user has explicitly asked us to hardcode dll paths, add | |
329 | # <hardcode-dll-paths>false in requirements, to override default value. | |
330 | if not '<hardcode-dll-paths>true' in requirements: | |
331 | requirements.append('<hardcode-dll-paths>false') | |
332 | ||
333 | if any(r.startswith('<tag>') for r in requirements): | |
334 | get_manager().errors()("The <tag> property is not allowed for the 'install' rule") | |
335 | ||
336 | from b2.manager import get_manager | |
337 | t = get_manager().targets() | |
338 | ||
339 | project = get_manager().projects().current() | |
340 | ||
341 | return t.main_target_alternative( | |
342 | InstallTargetClass(name, project, | |
343 | t.main_target_sources(sources, name), | |
344 | t.main_target_requirements(requirements, project), | |
345 | t.main_target_default_build(default_build, project), | |
346 | t.main_target_usage_requirements(usage_requirements, project))) | |
347 | ||
348 | get_manager().projects().add_rule("install", install) | |
349 | get_manager().projects().add_rule("stage", install) | |
350 |