blob: 1af1443f476b3bb15d95af6320aa4927f550d522 [file] [log] [blame]
Brad Bishop316dfdd2018-06-25 12:45:53 -04001# This script is used as a bitbake task to create a new python manifest
2# $ bitbake python -c create_manifest
3#
4# Our goal is to keep python-core as small as posible and add other python
5# packages only when the user needs them, hence why we split upstream python
6# into several packages.
7#
8# In a very simplistic way what this does is:
9# Launch python and see specifically what is required for it to run at a minimum
10#
11# Go through the python-manifest file and launch a separate task for every single
12# one of the files on each package, this task will check what was required for that
13# specific module to run, these modules will be called dependencies.
14# The output of such task will be a list of the modules or dependencies that were
15# found for that file.
16#
17# Such output will be parsed by this script, we will look for each dependency on the
18# manifest and if we find that another package already includes it, then we will add
19# that package as an RDEPENDS to the package we are currently checking; in case we dont
20# find the current dependency on any other package we will add it to the current package
21# as part of FILES.
22#
23#
24# This way we will create a new manifest from the data structure that was built during
25# this process, ont this new manifest each package will contain specifically only
26# what it needs to run.
27#
28# There are some caveats which we try to deal with, such as repeated files on different
29# packages, packages that include folders, wildcards, and special packages.
30# Its also important to note that this method only works for python files, and shared
31# libraries. Static libraries, header files and binaries need to be dealt with manually.
32#
33# Author: Alejandro Enedino Hernandez Samaniego "aehs29" <aehs29@gmail.com>
34
35
36import sys
37import subprocess
38import json
39import os
Andrew Geissler730fed82018-09-19 09:22:27 -070040import collections
Brad Bishop316dfdd2018-06-25 12:45:53 -040041
42# Hack to get native python search path (for folders), not fond of it but it works for now
43pivot='recipe-sysroot-native'
44for p in sys.path:
45 if pivot in p:
46 nativelibfolder=p[:p.find(pivot)+len(pivot)]
47
48# Empty dict to hold the whole manifest
Andrew Geissler730fed82018-09-19 09:22:27 -070049new_manifest = collections.OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -040050
51# Check for repeated files, folders and wildcards
52allfiles=[]
53repeated=[]
54wildcards=[]
55
56hasfolders=[]
57allfolders=[]
58
59def isFolder(value):
60 if os.path.isdir(value.replace('${libdir}',nativelibfolder+'/usr/lib')) or os.path.isdir(value.replace('${libdir}',nativelibfolder+'/usr/lib64')) or os.path.isdir(value.replace('${libdir}',nativelibfolder+'/usr/lib32')):
61 return True
62 else:
63 return False
64
65# Read existing JSON manifest
66with open('python2-manifest.json') as manifest:
Andrew Geissler730fed82018-09-19 09:22:27 -070067 old_manifest = json.load(manifest, object_pairs_hook=collections.OrderedDict)
Brad Bishop316dfdd2018-06-25 12:45:53 -040068
69
70# First pass to get core-package functionality, because we base everything on the fact that core is actually working
71# Not exactly the same so it should not be a function
72print ("Getting dependencies for core package:")
73
74# Special call to check for core package
75output = subprocess.check_output([sys.executable, 'get_module_deps2.py', 'python-core-package'])
76for item in output.split():
77 # We append it so it doesnt hurt what we currently have:
78 if item not in old_manifest['core']['files']:
79 # We use the same data structure since its the one which will be used to check
80 # dependencies for other packages
81 old_manifest['core']['files'].append(item)
82
83for value in old_manifest['core']['files']:
84 # Ignore folders, since we don't import those, difficult to handle multilib
85 if isFolder(value):
86 # Pass it directly
87 if value not in old_manifest['core']['files']:
88 old_manifest['core']['files'].append(value)
89 # Ignore binaries, since we don't import those, assume it was added correctly (manually)
90 if '${bindir}' in value:
91 # Pass it directly
92 if value not in old_manifest['core']['files']:
93 old_manifest['core']['files'].append(value)
94 continue
95 # Ignore empty values
96 if value == '':
97 continue
98 if '${includedir}' in value:
99 if value not in old_manifest['core']['files']:
100 old_manifest['core']['files'].append(value)
101 continue
102 # Get module name , shouldnt be affected by libdir/bindir
103 value = os.path.splitext(os.path.basename(os.path.normpath(value)))[0]
104
105
106 # Launch separate task for each module for deterministic behavior
107 # Each module will only import what is necessary for it to work in specific
108 print ('Getting dependencies for module: %s' % value)
109 output = subprocess.check_output([sys.executable, 'get_module_deps2.py', '%s' % value])
110 for item in output.split():
111 # We append it so it doesnt hurt what we currently have:
112 if item not in old_manifest['core']['files']:
113 old_manifest['core']['files'].append(item)
114
115# We check which packages include folders
116for key in old_manifest:
117 for value in old_manifest[key]['files']:
118 # Ignore folders, since we don't import those, difficult to handle multilib
119 if isFolder(value):
120 print ('%s is a folder' % value)
121 if key not in hasfolders:
122 hasfolders.append(key)
123 if value not in allfolders:
124 allfolders.append(value)
125
126for key in old_manifest:
127 # Use an empty dict as data structure to hold data for each package and fill it up
Andrew Geissler730fed82018-09-19 09:22:27 -0700128 new_manifest[key] = collections.OrderedDict()
129 new_manifest[key]['summary'] = old_manifest[key]['summary']
Brad Bishop316dfdd2018-06-25 12:45:53 -0400130 new_manifest[key]['rdepends']=[]
Andrew Geissler730fed82018-09-19 09:22:27 -0700131 new_manifest[key]['files'] = []
132
Brad Bishop316dfdd2018-06-25 12:45:53 -0400133 # All packages should depend on core
134 if key != 'core':
Andrew Geissler730fed82018-09-19 09:22:27 -0700135 new_manifest[key]['rdepends'].append('core')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400136
137 # Handle special cases, we assume that when they were manually added
138 # to the manifest we knew what we were doing.
139 print ('Handling package %s' % key)
140 special_packages=['misc', 'modules', 'dev']
141 if key in special_packages or 'staticdev' in key:
142 print('Passing %s package directly' % key)
143 new_manifest[key]=old_manifest[key]
144 continue
145
146 for value in old_manifest[key]['files']:
147 # We already handled core on the first pass
148 if key == 'core':
149 new_manifest[key]['files'].append(value)
150 continue
151 # Ignore folders, since we don't import those, difficult to handle multilib
152 if isFolder(value):
153 # Pass folders directly
154 new_manifest[key]['files'].append(value)
155 # Ignore binaries, since we don't import those
156 if '${bindir}' in value:
157 # Pass it directly to the new manifest data structure
158 if value not in new_manifest[key]['files']:
159 new_manifest[key]['files'].append(value)
160 continue
161 # Ignore empty values
162 if value == '':
163 continue
164 if '${includedir}' in value:
165 if value not in new_manifest[key]['files']:
166 new_manifest[key]['files'].append(value)
167 continue
168 # Get module name , shouldnt be affected by libdir/bindir
169 value = os.path.splitext(os.path.basename(os.path.normpath(value)))[0]
170
171 # Launch separate task for each module for deterministic behavior
172 # Each module will only import what is necessary for it to work in specific
173 print ('Getting dependencies for module: %s' % value)
174 output = subprocess.check_output([sys.executable, 'get_module_deps2.py', '%s' % value])
175
176 # We can print dependencies for debugging purposes
177 #print (output)
178 # Output will have all dependencies
179 for item in output.split():
180
181 # Warning: This first part is ugly
182 # One of the dependencies that was found, could be inside of one of the folders included by another package
183 # We need to check if this happens so we can add the package containing the folder as an RDEPENDS
184 # e.g. Folder encodings contained in codecs
185 # This would be solved if no packages included any folders
186
187 # This can be done in two ways:
188 # 1 - We assume that if we take out the filename from the path we would get
189 # the folder string, then we would check if folder string is in the list of folders
190 # This would not work if a package contains a folder which contains another folder
191 # e.g. path/folder1/folder2/filename folder_string= path/folder1/folder2
192 # folder_string would not match any value contained in the list of folders
193 #
194 # 2 - We do it the other way around, checking if the folder is contained in the path
195 # e.g. path/folder1/folder2/filename folder_string= path/folder1/folder2
196 # is folder_string inside path/folder1/folder2/filename?,
197 # Yes, it works, but we waste a couple of milliseconds.
198
199 inFolders=False
200 for folder in allfolders:
201 if folder in item:
202 inFolders = True # Did we find a folder?
203 folderFound = False # Second flag to break inner for
204 # Loop only through packages which contain folders
205 for keyfolder in hasfolders:
206 if (folderFound == False):
207 #print("Checking folder %s on package %s" % (item,keyfolder))
208 for file_folder in old_manifest[keyfolder]['files']:
209 if file_folder==folder:
210 print ('%s found in %s' % (folder, keyfolder))
211 folderFound = True
212 if keyfolder not in new_manifest[key]['rdepends'] and keyfolder != key:
213 new_manifest[key]['rdepends'].append(keyfolder)
214 else:
215 break
216
217 # A folder was found so we're done with this item, we can go on
218 if inFolders:
219 continue
220
221 # We might already have it on the dictionary since it could depend on a (previously checked) module
222 if item not in new_manifest[key]['files']:
223 # Handle core as a special package, we already did it so we pass it to NEW data structure directly
224 if key=='core':
225 print('Adding %s to %s FILES' % (item, key))
226 if item.endswith('*'):
227 wildcards.append(item)
228 new_manifest[key]['files'].append(item)
229
230 # Check for repeated files
231 if item not in allfiles:
232 allfiles.append(item)
233 else:
234 repeated.append(item)
235
236 else:
237
238 # Check if this dependency is already contained on another package, so we add it
239 # as an RDEPENDS, or if its not, it means it should be contained on the current
240 # package, so we should add it to FILES
241 for newkey in old_manifest:
242 # Debug
243 #print("Checking %s " % item + " in %s" % newkey)
244 if item in old_manifest[newkey]['files']:
245 # Since were nesting, we need to check its not the same key
246 if(newkey!=key):
247 if newkey not in new_manifest[key]['rdepends']:
248 # Add it to the new manifest data struct
249 # Debug
250 print('Adding %s to %s RDEPENDS, because it contains %s' % (newkey, key, item))
251 new_manifest[key]['rdepends'].append(newkey)
252 break
253 else:
254 # Debug
255 print('Adding %s to %s FILES' % (item, key))
256 # Since it wasnt found on another package, its not an RDEP, so add it to FILES for this package
257 new_manifest[key]['files'].append(item)
258 if item.endswith('*'):
259 wildcards.append(item)
260 if item not in allfiles:
261 allfiles.append(item)
262 else:
263 repeated.append(item)
264
265print ('The following files are repeated (contained in more than one package), please check which package should get it:')
266print (repeated)
267print('The following files contain wildcards, please check they are necessary')
268print(wildcards)
269print('The following files contain folders, please check they are necessary')
270print(hasfolders)
271
272# Sort it just so it looks nice
273for key in new_manifest:
274 new_manifest[key]['files'].sort()
275 new_manifest[key]['rdepends'].sort()
276
277# Create the manifest from the data structure that was built
278with open('python2-manifest.json.new','w') as outfile:
Andrew Geissler730fed82018-09-19 09:22:27 -0700279 json.dump(new_manifest,outfile, indent=4)