add --library output to revdep-rebuild.
[gentoolkit.git] / pym / gentoolkit / revdep_rebuild / analyse.py
1 #!/usr/bin/python
2
3 """Analysis module"""
4
5 from __future__ import print_function
6
7 import os
8 import re
9 import platform
10 import glob
11
12 from portage.output import bold, blue, yellow, green
13
14 from .stuff import scan
15 from .collect import (prepare_search_dirs, parse_revdep_config,
16         collect_libraries_from_dir, collect_binaries_from_dir)
17 from .assign import assign_packages
18 from .cache import save_cache
19
20
21 def prepare_checks(files_to_check, libraries, bits, cmd_max_args):
22         ''' Calls scanelf for all files_to_check, 
23         then returns found libraries and dependencies
24         '''
25
26         # libs found by scanelf
27         libs = []
28         # list of lists of files (from file_to_check) that uses
29         # library (for dependencies[id] and libs[id] => id==id)
30         dependencies = []
31         for line in scan(
32                 ['-M', str(bits), '-nBF', '%F %n'],
33                 files_to_check, cmd_max_args
34                 ):
35
36                 parts = line.strip().split(' ')
37                 if len(parts) < 2: # no dependencies?
38                         continue
39
40                 deps = parts[1].split(',')
41                 for dep in deps:
42                         if dep in libs:
43                                 index = libs.index(dep)
44                                 dependencies[index].append(parts[0])
45                         else:
46                                 libs.append(dep)
47                                 dependencies.append([parts[0],])
48         
49         return (libs, dependencies)
50
51
52 def extract_dependencies_from_la(la, libraries, to_check, logger):
53         broken = []
54
55         libnames = []
56         for lib in libraries:
57                 match = re.match('.+\/(.+)\.(so|la|a)(\..+)?', lib)
58                 if match is not None:
59                         libname = match.group(1)
60                         if libname not in libnames:
61                                 libnames += [libname, ]
62
63         for _file in la:
64                 if not os.path.exists(_file):
65                         continue
66
67                 for line in open(_file, 'r').readlines():
68                         line = line.strip()
69                         if line.startswith('dependency_libs='):
70                                 match = re.match("dependency_libs='([^']+)'", line)
71                                 if match is not None:
72                                         for el in match.group(1).split(' '):
73                                                 el = el.strip()
74                                                 if (len(el) < 1 or el.startswith('-L')
75                                                         or el.startswith('-R')
76                                                         ):
77                                                         continue
78
79                                                 if el.startswith('-l') and 'lib'+el[2:] in libnames:
80                                                         pass
81                                                 elif el in la or el in libraries:
82                                                         pass
83                                                 else:
84                                                         if to_check:
85                                                                 _break = False
86                                                                 for tc in to_check:
87                                                                         if tc in el:
88                                                                                 _break = True
89                                                                                 break
90                                                                 if not _break:
91                                                                         continue
92
93                                                         logger.info(yellow(' * ') + _file + 
94                                                                 ' is broken (requires: ' + bold(el)+')')
95                                                         broken.append(_file)
96         return broken
97
98
99 def find_broken(found_libs, system_libraries, to_check):
100         ''' Search for broken libraries.
101                 Check if system_libraries contains found_libs, where
102                 system_libraries is list of obsolute pathes and found_libs
103                 is list of library names.
104         '''
105
106         # join libraries and looking at it as string
107         # is way faster than for-jumping
108
109         broken = []
110         syslibs = '|'.join(system_libraries)
111
112         if not to_check:
113                 for found in found_libs:
114                         if found + '|' not in syslibs:
115                                 broken.append(found_libs.index(found))
116         else:
117                 for tc in to_check:
118                         for found in found_libs:
119                                 if tc in found:# and found+'|' not in syslibs:
120                                         broken.append(found_libs.index(found))
121
122         return broken
123
124
125 def main_checks(found_libs, broken_list, dependencies, logger):
126         ''' Checks for broken dependencies.
127                 found_libs have to be the same as returned by prepare_checks
128                 broken_list is list of libraries found by scanelf
129                 dependencies is the value returned by prepare_checks
130         '''
131
132         broken_pathes = []
133
134         for broken in broken_list:
135                 found = found_libs[broken]
136                 logger.info('Broken files that requires: ' + bold(found))
137                 for dep_path in dependencies[broken]:
138                         logger.info(yellow(' * ') + dep_path)
139                         broken_pathes.append(dep_path)
140         return broken_pathes
141
142
143 def analyse(settings, logger, libraries=None, la_libraries=None,
144                 libraries_links=None, binaries=None, _libs_to_check=set()):
145         """Main program body.  It will collect all info and determine the
146         pkgs needing rebuilding.
147
148         @param logger: logger used for logging messages, instance of logging.Logger
149                                    class. Can be logging (RootLogger).
150         @param _libs_to_check Libraries that need to be checked only
151         @rtype list: list of pkgs that need rebuilding
152         """
153
154         if libraries and la_libraries and libraries_links and binaries:
155                 logger.info(blue(' * ') + 
156                         bold('Found a valid cache, skipping collecting phase'))
157         else:
158                 #TODO: add partial cache (for ex. only libraries)
159                 # when found for some reason
160
161                 logger.warn(green(' * ') + 
162                         bold('Collecting system binaries and libraries'))
163                 bin_dirs, lib_dirs = prepare_search_dirs(logger, settings)
164
165                 masked_dirs, masked_files, ld = \
166                         parse_revdep_config(settings['REVDEP_CONFDIR'])
167                 lib_dirs.update(ld)
168                 bin_dirs.update(ld)
169                 masked_dirs.update([
170                         '/lib/modules',
171                         '/lib32/modules',
172                         '/lib64/modules'
173                         ]
174                         )
175
176                 logger.info(green(' * ') +
177                         bold('Collecting dynamic linking informations'))
178                 libraries, la_libraries, libraries_links, symlink_pairs = \
179                         collect_libraries_from_dir(lib_dirs, masked_dirs, logger)
180                 binaries = collect_binaries_from_dir(bin_dirs, masked_dirs, logger)
181
182                 if settings['USE_TMP_FILES']:
183                         save_cache(logger=logger, 
184                                 to_save={'libraries':libraries, 'la_libraries':la_libraries,
185                                         'libraries_links':libraries_links, 'binaries':binaries
186                                 },
187                         temp_path=settings['DEFAULT_TMP_DIR']
188                         )
189
190
191         logger.debug('Found '+ str(len(libraries)) + 
192                 ' libraries (+' + str(len(libraries_links)) +
193                 ' symlinks) and ' + str(len(binaries)) +
194                 ' binaries')
195
196         logger.warn(green(' * ') + bold('Checking dynamic linking consistency'))
197         logger.debug('Search for ' + str(len(binaries)+len(libraries)) +
198                 ' within ' + str(len(libraries)+len(libraries_links)))
199         libs_and_bins = libraries+binaries
200
201         found_libs = []
202         dependencies = []
203
204         if _libs_to_check:
205                 nltc = []
206                 for ltc in _libs_to_check:
207                         if os.path.isfile(ltc):
208                                 ltc = scan(['-nBSF', '%S'], [ltc,], settings['CMD_MAX_ARGS'])[0].split()[0]
209                         else:
210                                 logger.warn(yellow(' * ') + bold('Library "%s" was not found' % ltc))
211                         nltc += [ltc,]
212                 _libs_to_check = nltc
213
214         _bits, linkg = platform.architecture()
215         if _bits.startswith('32'):
216                 bits = 32
217         elif _bits.startswith('64'):
218                 bits = 64
219
220         broken = []
221         for av_bits in glob.glob('/lib[0-9]*') or ('/lib32',):
222                 bits = int(av_bits[4:])
223
224                 _libraries = libraries+libraries_links
225
226                 found_libs, dependencies = prepare_checks(libs_and_bins,
227                         _libraries, bits, settings['CMD_MAX_ARGS'])
228                 broken = find_broken(found_libs, _libraries, _libs_to_check)
229
230                 bits /= 2
231                 bits = int(bits)
232
233         broken_la = extract_dependencies_from_la(la_libraries,
234                 libraries+libraries_links, _libs_to_check, logger)
235
236
237         broken_pathes = main_checks(found_libs, broken, dependencies, logger)
238         broken_pathes += broken_la
239
240         logger.warn(green(' * ') + bold('Assign files to packages'))
241
242         return assign_packages(broken_pathes, logger, settings)
243
244
245
246 if __name__ == '__main__':
247         print("This script shouldn't be called directly")