]>
git.ipfire.org Git - thirdparty/openembedded/openembedded-core.git/blob - scripts/pythondeps
3 # Copyright OpenEmbedded Contributors
5 # SPDX-License-Identifier: GPL-2.0-only
7 # Determine dependencies of python scripts or available python modules in a search path.
9 # Given the -d argument and a filename/filenames, returns the modules imported by those files.
10 # Given the -d argument and a directory/directories, recurses to find all
11 # python packages and modules, returns the modules imported by these.
12 # Given the -p argument and a path or paths, scans that path for available python modules/packages.
17 from importlib
import machinery
23 logger
= logging
.getLogger('pythondeps')
25 suffixes
= importlib
.machinery
.all_suffixes()
27 class PythonDepError(Exception):
31 class DependError(PythonDepError
):
32 def __init__(self
, path
, error
):
35 PythonDepError
.__init
__(self
, error
)
38 return "Failure determining dependencies of {}: {}".format(self
.path
, self
.error
)
41 class ImportVisitor(ast
.NodeVisitor
):
46 def visit_Import(self
, node
):
47 for alias
in node
.names
:
48 self
.imports
.add(alias
.name
)
50 def visit_ImportFrom(self
, node
):
51 self
.importsfrom
.append((node
.module
, [a
.name
for a
in node
.names
], node
.level
))
57 path
, _
, _
= path
.rpartition(os
.sep
)
60 def get_provides(path
):
61 path
= os
.path
.realpath(path
)
64 for suffix
in suffixes
:
65 if fn
.endswith(suffix
):
66 return fn
[:-len(suffix
)]
68 isdir
= os
.path
.isdir(path
)
73 pkg_path
= get_fn_name(path
)
76 walk_path
= os
.path
.dirname(path
)
78 for curpath
in walk_up(walk_path
):
79 if not os
.path
.exists(os
.path
.join(curpath
, '__init__.py')):
85 package_relpath
= pkg_path
[len(libdir
)+1:]
86 package
= '.'.join(package_relpath
.split(os
.sep
))
90 if os
.path
.exists(os
.path
.join(path
, '__init__.py')):
93 for dirpath
, dirnames
, filenames
in os
.walk(path
):
94 relpath
= dirpath
[len(path
)+1:]
96 if '__init__.py' not in filenames
:
100 context
= '.'.join(relpath
.split(os
.sep
))
102 context
= package
+ '.' + context
103 yield context
, dirpath
108 adjusted_fn
= get_fn_name(fn
)
109 if not adjusted_fn
or adjusted_fn
== '__init__':
112 fullfn
= os
.path
.join(dirpath
, fn
)
114 yield context
+ '.' + adjusted_fn
, fullfn
116 yield adjusted_fn
, fullfn
119 def get_code_depends(code_string
, path
=None, provide
=None, ispkg
=False):
121 code
= ast
.parse(code_string
, path
)
122 except TypeError as exc
:
123 raise DependError(path
, exc
)
124 except SyntaxError as exc
:
125 raise DependError(path
, exc
)
127 visitor
= ImportVisitor()
129 for builtin_module
in sys
.builtin_module_names
:
130 if builtin_module
in visitor
.imports
:
131 visitor
.imports
.remove(builtin_module
)
134 provide_elements
= provide
.split('.')
136 provide_elements
.append("__self__")
137 context
= '.'.join(provide_elements
[:-1])
138 package_path
= os
.path
.dirname(path
)
143 levelzero_importsfrom
= (module
for module
, names
, level
in visitor
.importsfrom
145 for module
in visitor
.imports |
set(levelzero_importsfrom
):
147 module_basepath
= os
.path
.join(package_path
, module
.replace('.', '/'))
148 if os
.path
.exists(module_basepath
):
149 # Implicit relative import
150 yield context
+ '.' + module
, path
153 for suffix
in suffixes
:
154 if os
.path
.exists(module_basepath
+ suffix
):
155 # Implicit relative import
156 yield context
+ '.' + module
, path
163 for module
, names
, level
in visitor
.importsfrom
:
167 raise DependError("Error: ImportFrom non-zero level outside of a package: {0}".format((module
, names
, level
)), path
)
168 elif level
> len(provide_elements
):
169 raise DependError("Error: ImportFrom level exceeds package depth: {0}".format((module
, names
, level
)), path
)
171 context
= '.'.join(provide_elements
[:-level
])
174 yield context
+ '.' + module
, path
179 def get_file_depends(path
):
181 code_string
= open(path
, 'r').read()
182 except (OSError, IOError) as exc
:
183 raise DependError(path
, exc
)
185 return get_code_depends(code_string
, path
)
188 def get_depends_recursive(directory
):
189 directory
= os
.path
.realpath(directory
)
191 provides
= dict((v
, k
) for k
, v
in get_provides(directory
))
192 for filename
, provide
in provides
.items():
193 if os
.path
.isdir(filename
):
194 filename
= os
.path
.join(filename
, '__init__.py')
196 elif not filename
.endswith('.py'):
201 with
open(filename
, 'r') as f
:
204 depends
= get_code_depends(source
, filename
, provide
, ispkg
)
205 for depend
, by
in depends
:
209 def get_depends(path
):
210 if os
.path
.isdir(path
):
211 return get_depends_recursive(path
)
213 return get_file_depends(path
)
217 logging
.basicConfig()
219 parser
= argparse
.ArgumentParser(description
='Determine dependencies and provided packages for python scripts/modules')
220 parser
.add_argument('path', nargs
='+', help='full path to content to be processed')
221 group
= parser
.add_mutually_exclusive_group()
222 group
.add_argument('-p', '--provides', action
='store_true',
223 help='given a path, display the provided python modules')
224 group
.add_argument('-d', '--depends', action
='store_true',
225 help='given a filename, display the imported python modules')
227 args
= parser
.parse_args()
230 for path
in args
.path
:
231 for provide
, fn
in get_provides(path
):
234 for module
in sorted(modules
):
237 for path
in args
.path
:
239 modules
= get_depends(path
)
240 except PythonDepError
as exc
:
241 logger
.error(str(exc
))
244 for module
, imp_by
in modules
:
245 print("{}\t{}".format(module
, imp_by
))
251 if __name__
== '__main__':