2 # Copyright (c) 2000 - 2016 Samsung Electronics Co., Ltd. All rights reserved.
5 # @author Chulwoo Shin <cw1.shin@samsung.com>
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
23 from lxml import etree
24 from tic.utils.error import TICError
25 from tic.utils.rpmmisc import meetRequireVersion, compare_ver
26 from tic.utils.rpmmisc import Dependency
27 from tic.config import configmgr
29 DEFAULT_PROFILE = 'EMPTY'
31 def analyze_dependency(pkg_group):
34 #logger = logging.getLogger(__name__)
35 if pkg_list[pkg_id].get('dependency') is not None:
36 return pkg_list[pkg_id].get('dependency')
39 visited[pkg_id] = number[0]
40 min_num[pkg_id] = number[0]
43 dep_set = set([pkg_list[pkg_id]['name']])
45 if pkg_list[pkg_id].get('requires'):
46 for req in pkg_list[pkg_id].get('requires'):
47 req_id = req.get('id')
48 if req_id is not None:
49 if scc_list[req_id] > 0:
50 dep_set.update(pkg_list[req_id].get('dependency'))
53 if visited[req_id] == 0:
54 dep_set.update(dep_dfs(req_id))
56 min_num[pkg_id] = min(min_num[pkg_id], min_num[req_id])
58 #TODO: package does not exist
59 #logger.warning('%s does not exist in repo', req['name'])
62 if min_num[pkg_id] == visited[pkg_id]:
63 # scc (string connected components)
64 make_scc(pkg_id, list(dep_set))
68 def make_scc(pkg_id, dep_list):
74 scc_list[p_id] = scc_num[0]
75 pkg_list[p_id]['dependency'] = dep_list
80 for pkg_id in range(len(pkg_list)):
81 if visited[pkg_id] == 0:
84 #TODO: Exception handling
88 # package install-dependency analysis
89 pkg_list = pkg_group.get('pkg_list')
92 visited = [0]*len(pkg_list)
93 min_num = [0]*len(pkg_list)
94 scc_list = [0]*len(pkg_list)
99 def get_installed_packages(recipe, repoinfo, pkg_group):
100 logger = logging.getLogger(__name__)
102 def _select_rpm_from_files(fileList, require):
103 if not fileList or not require:
107 # 2-1. Choose the default rpm or the selected rpm
108 for fname in fileList:
109 file_info = pkg_dict.get(fname)
110 if fname in pkg_set or selected[file_info['id']] >= 1:
112 # 2-2. Choose the rpm (it does not conflitcs)
113 for fname in fileList:
114 file_info = pkg_dict.get(fname)
115 if not _check_conflicts(file_info):
117 return pkg_dict.get(fileList[0])
119 def _select_rpm(capability, require, recommends=None):
121 # 1. Choose the rpm included in version from provides
122 if require.get('ver'):
123 for provide in capability:
124 ver_data = provide['data']
125 # If there is no capability version, use version of package
126 if not ver_data.get('ver'):
127 ver_data = pkg_dict.get(provide['name']).get('version')
128 if meetRequireVersion(require, ver_data):
129 provide_list.append(provide)
131 provide_list = capability
133 # error case (the rpm does not exist)
137 if len(provide_list) == 1:
138 return pkg_dict.get(provide_list[0].get('name'))
140 # 2 Select one of the rpms by priority
141 # 2-1. Choose the default rpm or the selected rpm
142 for pro in provide_list:
143 provide_info = pkg_dict.get(pro.get('name'))
144 if provide_info['name'] in pkg_set or selected[provide_info['id']] >= 1:
147 # 2-2. Choose the defualt profile
148 # TODO: should be supported
149 # if provide_info['profile'] == DEFAULT_PROFILE:
150 # return provide_info
152 # 2.3. Choose recommends pkg (pkg-name or capability)
154 for pro in provide_list:
155 provide_info = pkg_dict.get(pro.get('name'))
156 for reco in recommends:
157 # 2-3 Case. select a pkg that is named
158 if reco['name'] == provide_info['name']:
160 # 2-3 Case. select a pkg that provides
161 for cap in provide_info.get('provides'):
162 if reco['name'] == cap['name']:
164 # 2-4. Select the latest version of rpm
166 for pro in provide_list:
167 if not _check_conflicts(pkg_dict.get(pro.get('name'))):
169 cap_info = pro.get('data')
170 ret = compare_ver(max_ver.get('data'), cap_info)
171 if ret == 0: # equals
172 # string compare (compare string lexicographically using ASCII value)
173 if max_ver.get('name') > pro.get('name'):
175 elif ret == -1: # greater than max_ver
180 # all of capability pkg are in conflict
182 return pkg_dict.get(provide_list[0]['name'])
184 return pkg_dict.get(max_ver.get('name'))
186 def _create_reference(pkg1, pkg2):
188 if pkg1.get('forward') and pkg2['name'] in pkg1.get('forward'):
191 if pkg1.get('forward'):
192 pkg1['forward'].append(pkg2['name'])
194 pkg1['forward'] = [pkg2['name']]
196 if pkg2.get('backward'):
197 pkg2['backward'].append(pkg1['name'])
199 pkg2['backward'] = [pkg1['name']]
201 def _make_scc(pkg_id):
207 scc_id[pkg['id']] = scc_num[0]
209 if pkg_id == pkg['id']:
212 # circular dependency
213 if len(scc_list) > 1:
218 pkg['group'] = group_num[0]
219 group_names.append(pkg['name'])
221 # { group_id = [ pkg_name_1, pkg_name_2 ], ... }
222 groups[group_num[0]] = group_names
224 def _check_conflicts(pkg_info):
225 # 1. Check whether node can be installed
226 if pkg_info.get('provides') is not None:
227 for pro in pkg_info['provides']:
228 if pro['name'] in conflicts:
229 for con in conflicts[pro['name']]:
230 if not con['data'].get('ver') or meetRequireVersion(con['data'], pro):
232 logger.info('Conflict %s and %s' % (pkg_info['name'], con['name']))
235 #2. If the conflict package defined by node is installed
236 if pkg_info.get('conflicts') is not None:
237 for con in pkg_info['conflicts']:
238 if con['name'] in provides:
239 for pro in provides[con['name']]:
240 pkg = pkg_dict[pro['name']]
241 if selected[pkg['id']] != 0:
242 if not con.get('ver') or meetRequireVersion(con, pkg['version']):
243 logger.info('Conflict %s and %s' % (pkg_info['name'], pkg['name']))
245 elif con['name'] in pkg_dict:
246 pkg = pkg_dict[con['name']]
247 if selected[pkg['id']] != 0:
248 if not con.get('ver') or meetRequireVersion(con, pkg['version']):
249 logger.info('Conflict %s and %s' % (pkg_info['name'], pkg['name']))
253 def _add_conflicts(pkg_info):
254 if pkg_info.get('conflicts'):
255 for con in pkg_info['conflicts']:
256 if not con['name'] in conflicts:
257 conflicts[con['name']] = []
258 conflicts[con['name']].append(dict(name=pkg_info['name'], data=con))
259 logger.info('%s add conflict package : %s' % (pkg_info['name'], con['name']))
261 def _analyze_dep(pkg_info):
265 pkg_id = pkg_info['id']
267 selected[pkg_id] = number[0]
268 min_num[pkg_id] = number[0]
269 stack.append(pkg_info)
271 dep_rpms = set([pkg_info['name']])
273 # check for conflicts
274 if _check_conflicts(pkg_info):
275 progress['status'] = False
279 # add rpms into conflicts table.
280 _add_conflicts(pkg_info)
282 # Installation dependency analysis of rpm
283 for dep_tag in [Dependency.REQUIRES, Dependency.RECOMMENDS]:
284 if pkg_info.get(dep_tag):
285 for req in pkg_info.get(dep_tag):
287 # Find dependency rpm based on capability/files
288 if req['name'] in provides:
289 # capability : [provide_rpm_1, provide_rpm_2, ... ]
290 # Select the rpm that meets the condition (version)
291 if dep_tag == Dependency.REQUIRES:
292 choose = _select_rpm(provides[req['name']], req, pkg_info.get('recommends'))
294 choose = _select_rpm(provides[req['name']], req)
295 elif req['name'] in files:
296 choose = _select_rpm_from_files(files[req['name']], req)
297 #choose = pkg_dict.get(files[req['name']][0])
298 elif req['name'] in pkg_dict:
299 choose = pkg_dict.get(req['name'])
301 if dep_tag == Dependency.RECOMMENDS:
302 # A Recommends B: B is installed when A is installed and B has no conflicts.
303 if not choose or _check_conflicts(choose):
304 logger.info('%s recommended by %s is ignored for selection (Conflict)' % (req['name'], pkg_info['name']))
308 if selected[choose['id']] == 0:
309 dep_set = _analyze_dep(choose)
310 if not progress['status']:
313 dep_rpms.update(dep_set)
314 min_num[pkg_id] = min(min_num[pkg_id], min_num[choose['id']])
315 elif scc_id[choose['id']] == 0:
316 # cross edge that can not be ignored
317 min_num[pkg_id] = min(min_num[pkg_id], min_num[choose['id']])
319 # add forward/backward reference
320 _create_reference(pkg_info, choose)
322 # the rpm does not exists
323 logger.info(configmgr.message['dependency_not_exist'] % (req['name'], pkg_info['name']))
324 progress['status'] = False
327 if not progress['status']:
329 if min_num[pkg_id] == selected[pkg_id]:
330 # scc(strong connected components)
335 def _check_circular_dep(node):
336 g_id = node.get('group')
337 g_pkg_list = groups[g_id]
341 for pkgname in g_pkg_list:
342 g_dict[pkgname] = None
344 for pkgname in g_pkg_list:
345 pkg = pkg_dict[pkgname]
346 # the node is selfchecked (the root node ignores selfchecked)
347 if stack[0]['id'] != pkg['id'] and pkg['selfChecked']:
349 # check backward ref.
350 for bname in pkg.get('backward'):
351 # If node is Referenced by another node (Not a node in the group),
352 # unable to uncheck group nodes
353 if not bname in g_dict:
357 group_visited[g_id] = {}
358 # delete backward reference of group node
359 for pkgname in g_pkg_list:
360 pkg = pkg_dict[pkgname]
361 pkg['backward'] = None;
362 group_visited[g_id][pkg['name']] = -1
365 def _delete_conflictdata(node):
366 if node.get('conflicts'):
367 for con in node.get('conflicts'):
368 if con['name'] in conflicts:
369 con_list = conflicts[con['name']]
370 for i in range(len(con_list)):
371 if con_list[i]['name'] == node['name']:
375 def _remove_reference(parent, node):
376 if parent is not None:
377 # remove backward reference (parent)
378 if node.get('backward'):
379 for i in range(len(node['backward'])):
380 if node['backward'][i] == parent['name']:
381 del node['backward'][i]
383 # selfCheck node do not remove
384 if pkg_info.get('selfChecked'):
387 if node.get('backward'):
388 if node.get('group') is None or _check_circular_dep(node):
391 # the selected node is uncheckable
392 if node.get('group') and group_visited[node['group']]:
393 group_visited[node['group']][node['name']] = 1
395 # if selected node has forward references
396 if node.get('forward'):
397 for fname in node.get('forward'):
398 fnode = pkg_dict[fname]
400 # If pkg has a circular dependency and is unchekcable,
401 # circular dep. pkgs can only be visited once
402 gvisite = group_visited.get(fnode.get('group'))
403 if gvisite and gvisite[fnode['name']] == 1:
405 _remove_reference(node, fnode)
406 node['forward'] = None
408 # delete conflict data from conflicts dict
409 _delete_conflictdata(node)
412 if not recipe or not repoinfo:
415 default = recipe.get('Default')
416 config = recipe.get('Configurations')[0]
417 platform_name = config.get('Platform')
418 platform = recipe.get(platform_name)
420 # check groups/extraPackages
423 for g in [default, platform, config]:
424 if g.has_key('Groups'):
425 group_set.update(g.get('Groups'))
426 if g.has_key('ExtraPackages'):
427 pkg_set.update(g.get('ExtraPackages'))
428 #group_dict = dict.fromkeys(group_set)
432 for repo in repoinfo:
433 if repo.get('comps'):
435 tree = etree.parse(repo.get('comps'))
436 root = tree.getroot()
437 except etree.XMLSyntaxError as e:
439 raise TICError(configmgr.message['xml_parse_error'] % ('group.xml', repo['baseurl']))
441 # Convert groups to packages
442 for elm in root.findall('group'):
443 group_name = elm.find('name').text
444 if group_name in group_set:
445 pkglist = elm.find('packagelist')
447 for pkgreq in pkglist.findall('packagereq'):
448 plist.append(pkgreq.text)
449 pkg_set.update(set(plist))
450 group_set.discard(group_name);
452 pkg_dict = pkg_group.get('pkg_dict')
453 provides = pkg_group.get('provides')
454 files = pkg_group.get('files')
455 groups = pkg_group.get('groups')
456 conflicts = pkg_group.get('conflicts')
459 number = [0] # for pkg count
460 scc_num = [0] # for scc count
461 group_num = [0] # for group count
462 scc_id = [0] * len(pkg_dict)
463 min_num = [0] * len(pkg_dict)
464 selected = [0] * len(pkg_dict)
466 install_rpm = set([])
467 progress = dict(status=True, message=None)
469 for pkg_name in pkg_set:
470 progress['status'] = True
471 pkg_info = pkg_dict.get(pkg_name)
474 if provides.get(pkg_name):
475 pro = provides.get(pkg_name)[0]
476 pkg_info = pkg_dict.get(pro['name'])
478 logger.info(configmgr.message['package_not_exist'] % pkg_name)
481 pkg_info['selfChecked'] = True
482 if selected[pkg_info['id']] == 0:
483 dep_set = _analyze_dep(pkg_info)
484 if progress['status']:
485 install_rpm.update(dep_set)
487 # delete forward/backward reference
489 _remove_reference(None, pkg_info)
490 return list(install_rpm)