fcca3560497391048aa373f4613bc19faa9f7e2e
[archive/20170607/tools/tic-core.git] / tic / dependency.py
1 #!/usr/bin/python
2 # Copyright (c) 2000 - 2016 Samsung Electronics Co., Ltd. All rights reserved.
3 #
4 # Contact: 
5 # @author Chulwoo Shin <cw1.shin@samsung.com>
6
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
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
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.
18 #
19 # Contributors:
20 # - S-Core Co., Ltd
21
22 import rpm
23 import logging
24 from lxml import etree
25 from tic.utils.error import TICError
26
27 def analyze_dependency(pkg_group):
28     
29     def dep_dfs(pkg_id):
30         logger = logging.getLogger(__name__)
31         if pkg_list[pkg_id].get('dependency') is not None:
32             return pkg_list[pkg_id].get('dependency')
33         
34         number[0] += 1
35         visited[pkg_id] = number[0]
36         min_num[pkg_id] = number[0]
37         stack.append(pkg_id)
38
39         dep_set = set([pkg_list[pkg_id]['name']])
40         
41         if pkg_list[pkg_id].get('requires'):
42             for req in pkg_list[pkg_id].get('requires'):
43                 req_id = req.get('id')
44                 if req_id is not None:
45                     if scc_list[req_id] > 0:
46                         dep_set.update(pkg_list[req_id].get('dependency'))
47                         continue
48                     
49                     if visited[req_id] == 0:
50                         dep_set.update(dep_dfs(req_id))
51                     
52                     min_num[pkg_id] = min(min_num[pkg_id], min_num[req_id])
53                 else:
54                     #TODO: package does not exist
55                     #logger.warning('%s does not exist in repo', req['name'])
56                     pass
57         
58         if min_num[pkg_id] == visited[pkg_id]:
59             # scc (string connected components)
60             make_scc(pkg_id, list(dep_set))
61         
62         return dep_set
63     
64     def make_scc(pkg_id, dep_list):
65         p_id = 0 
66         scc_num[0] += 1
67         # stack is not empty
68         while stack:
69             p_id = stack.pop()
70             scc_list[p_id] = scc_num[0]
71             pkg_list[p_id]['dependency'] = dep_list
72             if pkg_id == p_id:
73                 break
74     
75     def analyze():
76         for pkg_id in range(len(pkg_list)):
77             if visited[pkg_id] == 0:
78                 dep_dfs(pkg_id)
79     
80     #TODO: Exception handling
81     if not pkg_group:
82         return None
83     
84     # package install-dependency analysis
85     pkg_list = pkg_group.get('pkg_list')
86     number = [0]
87     scc_num = [0]
88     visited = [0]*len(pkg_list)
89     min_num = [0]*len(pkg_list)
90     scc_list = [0]*len(pkg_list)
91     stack = []
92     
93     return analyze()
94     
95 def get_installed_packages(recipe, repoinfo, pkg_group):
96     logger = logging.getLogger(__name__)
97     
98     def _compare_ver(ver1, ver2):
99         return rpm.labelCompare((ver1.get('epoch'), ver1.get('ver'), ver1.get('rel')), 
100                                 (ver2.get('epoch'), ver2.get('ver'), ver2.get('rel')))
101         
102     def _compare_req_cap_ver(req, cap):
103         epoch = cap.get('epoch')
104         ver = cap.get('ver')
105         rel = cap.get('rel')
106         if not req.get('epoch'): epoch = None
107         if not req.get('rel'): rel = None
108         return rpm.labelCompare((req.get('epoch'), req.get('ver'), req.get('rel')), (epoch, ver, rel))
109     
110     def _select_rpm(capability, require):
111         
112         # TODO: temporary code (to support efl-data capability)
113         if len(capability) == 1:
114             return pkg_dict.get(capability[0].get('name'))
115         
116         provide_list = []
117         # 1. Choose the rpm included in version from provides
118         if require.get('ver') is not None:
119             for provide in capability:
120                 cap_info = provide.get('data')
121                 cmp_ret = _compare_req_cap_ver(require, cap_info)
122                 if cmp_ret == 0 and (require['flags'] == 'EQ' or require['flags'] == 'GE' or require['flags'] == 'LE'):
123                     provide_list.append(provide)
124                 elif cmp_ret == 1 and (require['flags'] == 'LT' or require['flags'] == 'LE'):
125                     provide_list.append(provide)
126                 elif cmp_ret == -1 and (require['flags'] == 'GT' or require['flags'] == 'GE'):
127                     provide_list.append(provide)
128         else:
129             provide_list = capability
130             
131         # error case (the rpm does not exist)
132         if not provide_list:
133             return None
134         
135         if len(provide_list) == 1:
136             return pkg_dict.get(provide_list[0].get('name'))
137         
138         # 2 Select one of the rpms by priority
139         # 2-1. Choose the default rpm or the selected rpm 
140         # TODO: default profile rpm should be selected
141         for i in range(0, len(provide_list)):
142             tmp_info = pkg_dict.get(provide_list[i].get('name'))
143             if tmp_info['name'] in pkg_set or selected[tmp_info['id']] == 1:
144                 return tmp_info
145         # 2-2. Select the latest version of rpm
146         max_ver = provide_list[0]
147         for i in range(1, len(provide_list)):
148             cap_info = provide_list[i].get('data')
149             ret = _compare_ver(max_ver.get('data'), cap_info)
150             # cap_info is greater than max_ver
151             if ret == -1:
152                 max_ver = provide_list[i]
153         
154         pkg_name = max_ver.get('name')
155         return pkg_dict.get(pkg_name)
156     
157     def _create_reference(pkg1, pkg2):
158         # duplicate check
159         if pkg1.get('forward') and pkg2['name'] in pkg1.get('forward'):
160             return
161         
162         if pkg1.get('forward'):
163             pkg1['forward'].append(pkg2['name'])
164         else:
165             pkg1['forward'] = [pkg2['name']]
166         
167         if pkg2.get('backward'):
168             pkg2['backward'].append(pkg1['name'])
169         else:
170             pkg2['backward'] = [pkg1['name']]
171             
172     def _make_scc(pkg_id):
173         scc_num[0] += 1
174         scc_list = []
175         # stack is not empty
176         while stack:
177             pkg = stack.pop()
178             scc_id[pkg['id']] = scc_num[0]
179             scc_list.append(pkg)
180             if pkg_id == pkg['id']:
181                 break;
182             
183         # circular dependency
184         if len(scc_list) > 1:
185             group_num[0] += 1
186             group_names = []
187             # add group id
188             for pkg in scc_list:
189                 pkg['group'] = group_num[0]
190                 group_names.append(pkg['name'])
191             
192             # { group_id = [ pkg_name_1, pkg_name_2 ], ... }
193             groups[group_num[0]] = group_names
194             print(group_names)
195         
196     def _analyze_dep(pkg_info):
197         if not pkg_info:
198             return 
199         
200         pkg_id = pkg_info['id'];
201         number[0] += 1
202         selected[pkg_id] = number[0]
203         min_num[pkg_id] = number[0]
204         stack.append(pkg_info)
205         
206         dep_rpms = set([pkg_info['name']])
207         
208         # Installation dependency analysis of rpm
209         for dep_tag in ['requires']: # 'recommends'
210             if pkg_info.get(dep_tag):
211                 for req in pkg_info['requires']:
212                     choose = None
213                     #  Find dependency rpm based on capability/files
214                     if req['name'] in provides:
215                         # capability : [provide_rpm_1, provide_rpm_2, ... ] 
216                         cap_list = provides.get(req['name'])
217                         # Select the rpm that meets the condition (version)
218                         choose = _select_rpm(cap_list, req)
219                     elif req['name'] in files:
220                         choose = pkg_dict.get(files.get(req['name'])[0])
221                         
222                     if choose:
223                         # add forward/backward reference
224                         _create_reference(pkg_info, choose)
225                         
226                         if selected[choose['id']] == 0:
227                             dep_rpms.update(_analyze_dep(choose))
228                             min_num[pkg_id] = min(min_num[pkg_id], min_num[choose['id']])
229                         elif scc_id[choose['id']] == 0: 
230                             # cross edge that can not be ignored
231                             min_num[pkg_id] = min(min_num[pkg_id], min_num[choose['id']])
232                     else:
233                         # the rpm does not exists
234                         # TODO: Error handle
235                         logger.info('the capability(%s) does not exist. should be checked for error' % req['name'])
236                         
237         if min_num[pkg_id] == selected[pkg_id]:
238             # scc(strong connected components)
239             _make_scc(pkg_id)
240         
241         return dep_rpms
242             
243     # recipe/repo
244     if not recipe or not repoinfo:
245         return []
246     
247     default = recipe.get('Default')
248     config = recipe.get('Configurations')[0]
249     platform_name = config.get('Platform')
250     platform = recipe.get(platform_name)
251     
252     # check groups/extraPackages
253     group_set = set([])
254     pkg_set = set([])
255     for g in [default, platform, config]:
256         if g.has_key('Groups'):
257             group_set.update(g.get('Groups'))
258         if g.has_key('ExtraPackages'):
259             pkg_set.update(g.get('ExtraPackages'))
260     group_dict = dict.fromkeys(group_set)
261     
262     # parsing group.xml
263     try:
264         tree = etree.parse(repoinfo[0].get('comps'))
265         root = tree.getroot()
266     except etree.XMLSyntaxError as e:
267         raise TICError('primary.xml syntax error. %s', e)
268     
269     # Convert groups to packages
270     for elm in root.findall('group'):
271         group_name = elm.find('name').text
272         if group_dict.has_key(group_name):
273             pkglist = elm.find('packagelist')
274             plist = []
275             for pkgreq in pkglist.findall('packagereq'):                
276                 plist.append(pkgreq.text)
277             pkg_set.update(set(plist))
278     
279     pkg_dict = pkg_group.get('pkg_dict')
280     provides = pkg_group.get('provides')
281     files = pkg_group.get('files')
282     groups = pkg_group.get('groups')
283     
284     stack = []
285     number = [0]    # for pkg count
286     scc_num = [0]   # for scc count
287     group_num = [0]     # for group count
288     scc_id = [0] * len(pkg_dict)
289     min_num = [0] * len(pkg_dict)
290     selected = [0] * len(pkg_dict)
291     install_rpm = set([])
292     
293     for pkg_name in pkg_set:
294         pkg_info = pkg_dict.get(pkg_name)
295         
296         # TODO: temporary code (Define capability in group)
297         if not pkg_info:
298             pro = provides.get(pkg_name)[0]
299             pkg_info = pkg_dict.get(pro['name'])
300             
301         pkg_info['selfChecked'] = True;
302         if selected[pkg_info['id']] == 0:
303             install_rpm.update(_analyze_dep(pkg_info))
304             
305     return list(install_rpm)