You've already forked libadalang
mirror of
https://github.com/AdaCore/libadalang.git
synced 2026-02-12 12:28:54 -08:00
The recent refactoring for GPR projects handling (#932) broke this function. Do what is necessary to fix it. TN: VB02-014
316 lines
11 KiB
Plaintext
316 lines
11 KiB
Plaintext
## vim: filetype=makopython
|
|
|
|
def token_match(self, other):
|
|
"""
|
|
Helper for the finditer/find/findall methods, so that a token matches
|
|
another token even if they are not strictly equivalent.
|
|
"""
|
|
return self == other or self.text == other
|
|
|
|
|
|
def _coerce_bytes(label, value, what='a bytes string', or_none=False):
|
|
"""
|
|
Take bytes (forwarded as-is to C) but also accept text (encoded using
|
|
the system encoding).
|
|
"""
|
|
if value is None and or_none:
|
|
return None
|
|
elif isinstance(value, bytes):
|
|
return value
|
|
elif isinstance(value, str):
|
|
return value.encode()
|
|
else:
|
|
raise TypeError('`{}` argument must be {} (got {})'
|
|
.format(label, what, _type_fullname(type(value))))
|
|
|
|
|
|
## Handling of string arrays
|
|
|
|
class _c_string_array(ctypes.Structure):
|
|
_fields_ = [
|
|
("length", ctypes.c_int),
|
|
("c_ptr", ctypes.POINTER(ctypes.c_char_p)),
|
|
# Omit the "items" field: it has variable size and is not necessary
|
|
# to just read the items.
|
|
]
|
|
|
|
@property
|
|
def wrap(self) -> List[str]:
|
|
return [self.c_ptr[i] for i in range(self.length)]
|
|
|
|
_c_string_array_ptr = ctypes.POINTER(_c_string_array)
|
|
|
|
_c_free_string_array = _import_func(
|
|
"ada_free_string_array", [_c_string_array_ptr], None,
|
|
)
|
|
|
|
|
|
@property
|
|
def doc_name(n):
|
|
"""
|
|
Format this name to be a readable qualified name for the entity designated
|
|
by it. Meant to be used in documentation context.
|
|
|
|
If the entity is local, it will return the relative name. If it is
|
|
non-local, return the shortest qualified name not taking use clauses into
|
|
account.
|
|
|
|
.. WARNING:: This is an EXPERIMENTAL feature. This is a python specific
|
|
method, because for the moment this is not conveniently implementable
|
|
directly as a libadalang property. Consider it an experimental API
|
|
endpoint, and use it at your own risk.
|
|
"""
|
|
if n.p_is_defining and not n.is_a(DefiningName):
|
|
n = n.p_enclosing_defining_name
|
|
|
|
ref_decl = n.p_basic_decl if n.p_is_defining else n.p_referenced_decl()
|
|
ref_decl_fqn = ref_decl.p_fully_qualified_name
|
|
|
|
enclosing_package = next(
|
|
(p for p in n.parents() if p.is_a(BasePackageDecl)),
|
|
None
|
|
)
|
|
|
|
if enclosing_package is None or enclosing_package == ref_decl:
|
|
return ref_decl_fqn
|
|
|
|
enclosing_decl_fqn = enclosing_package.p_fully_qualified_name
|
|
|
|
if ref_decl_fqn.lower().startswith(enclosing_decl_fqn.lower()):
|
|
return ref_decl_fqn[len(enclosing_decl_fqn):].strip(".")
|
|
else:
|
|
return ref_decl_fqn
|
|
|
|
Token.match = token_match
|
|
Name.doc_name = doc_name
|
|
|
|
|
|
import enum
|
|
class SourceFilesMode(enum.Enum):
|
|
"""
|
|
Mode to get a list of source files from a project file.
|
|
|
|
See ``SourceFiles.for_project``.
|
|
"""
|
|
default = 0
|
|
root_project = 1
|
|
whole_project = 2
|
|
whole_project_with_runtime = 3
|
|
|
|
|
|
class GPRProject:
|
|
"""
|
|
Load a GPR project file.
|
|
"""
|
|
|
|
class _UnitProvider(UnitProvider):
|
|
def __init__(self, project: GPRProject, c_value: Any):
|
|
super().__init__(c_value)
|
|
|
|
# Keep a reference on the GPRProject instance that was used to
|
|
# create this unit provider so that the project lives at least as
|
|
# long as the unit provider.
|
|
self._project = project
|
|
|
|
def __init__(self,
|
|
project_file: str,
|
|
scenario_vars: Dict[str, str] = {},
|
|
target: Opt[str] = None,
|
|
runtime: Opt[str] = None,
|
|
print_errors: bool = True):
|
|
"""
|
|
Load a GPR project file.
|
|
|
|
This may raise an ``InvalidProjectError`` exception if an error occurs
|
|
when loading the project.
|
|
|
|
:param project_file: Filename for the project to load.
|
|
:param screnario_vars: External variables for the project to load.
|
|
:param target: Name of the target for the project to load. Assume the
|
|
native platform if left to None.
|
|
:param runtime: Name of the runtime for the project to load. Use the
|
|
default runtime for the selected target if left to None.
|
|
:param print_errors: Whether to print non-critical error messages
|
|
emitted during project loading on the standard error stream. See
|
|
the ``errors`` method to have programmatic access to the list of
|
|
errors.
|
|
"""
|
|
# First, define this attribute so that __del__ work even if the
|
|
# constructor aborts later on because of an exception.
|
|
self._c_value = None
|
|
|
|
# Turn arguments into C API values
|
|
c_project_file = _coerce_bytes('project_file', project_file)
|
|
c_target = _coerce_bytes('target', target, or_none=True)
|
|
c_runtime = _coerce_bytes('runtime', runtime, or_none=True)
|
|
|
|
if scenario_vars:
|
|
items = scenario_vars.items()
|
|
scn_vars_array_type = (
|
|
self._c_scenario_variable * (len(items) + 1)
|
|
)
|
|
c_scenario_vars = scn_vars_array_type()
|
|
for i, (name, value) in enumerate(items):
|
|
what = 'a dict mapping bytes strings to bytes strings'
|
|
name = _coerce_bytes('scenario_vars', name, what)
|
|
value = _coerce_bytes('scenario_vars', value, what)
|
|
c_scenario_vars[i] = self._c_scenario_variable(
|
|
name, value
|
|
)
|
|
c_scenario_vars[-1] = self._c_scenario_variable(None, None)
|
|
else:
|
|
c_scenario_vars = None
|
|
|
|
# Load the project
|
|
c_errors = _c_string_array_ptr()
|
|
c_project = self._c_type()
|
|
self._c_load(
|
|
c_project_file,
|
|
c_scenario_vars,
|
|
c_target,
|
|
c_runtime,
|
|
ctypes.byref(c_project),
|
|
ctypes.byref(c_errors),
|
|
)
|
|
self._c_value = c_project
|
|
|
|
# Extract the possibly empty list of error messages and print it if
|
|
# requested. For user convenience, convert error messages to unicode
|
|
# strings and discard decoding errors.
|
|
self._errors = [
|
|
msg.decode("utf-8", "replace") for msg in c_errors.contents.wrap
|
|
]
|
|
_c_free_string_array(c_errors)
|
|
if print_errors and self.errors:
|
|
print(f"Errors while loading {project_file}:", file=sys.stderr)
|
|
for e in self.errors:
|
|
print(e, file=sys.stderr)
|
|
|
|
def __del__(self):
|
|
if self._c_value is not None:
|
|
self._c_free(self._c_value)
|
|
|
|
@property
|
|
def errors(self) -> List[str]:
|
|
"""
|
|
Possibly empty list of non-critical error messages emitted during
|
|
project loading.
|
|
"""
|
|
return list(self._errors)
|
|
|
|
def create_unit_provider(self, project: Opt[str] = None) -> UnitProvider:
|
|
"""
|
|
Return a unit provider that uses this GPR project.
|
|
|
|
:param project: If None, let the unit provider use the whole project
|
|
tree. Otherwise, restrict the unit provider to the project with the
|
|
given name in the project tree.
|
|
|
|
As unit providers must guarantee that there exists at most one
|
|
source file for each couple (unit name, unit kind), aggregate
|
|
projects that contains several conflicting units are not supported:
|
|
trying to use one will yield an ``InvalidProjectError`` exception.
|
|
"""
|
|
c_project = _coerce_bytes('project', project, or_none=True)
|
|
c_value = self._c_create_unit_provider(self._c_value, c_project)
|
|
return self._UnitProvider(self, c_value)
|
|
|
|
def source_files(self, mode: SourceFilesMode = SourceFilesMode.default):
|
|
"""
|
|
Return the list of source files in this project according to ``mode``:
|
|
|
|
* ``default``: sources in the root project and its non-externally built
|
|
dependencies;
|
|
|
|
* ``root_project``: sources in the root project only;
|
|
|
|
* ``whole_project``: sources in the whole project tree (i.e. including
|
|
externally built dependencies);
|
|
|
|
* ``whole_project_with_runtime``: sources in the whole project tree
|
|
plus runtime sources.
|
|
"""
|
|
|
|
assert isinstance(mode, SourceFilesMode)
|
|
c_mode = mode.value
|
|
|
|
# Compute the list of source files, extract it (no error expected there
|
|
# unless we have a bug) and free the resources.
|
|
c_value = self._c_source_files(self._c_value, c_mode)
|
|
assert c_value
|
|
result = c_value.contents.wrap
|
|
_c_free_string_array(c_value)
|
|
|
|
# Now convert filenames to Unicode strings using the system default
|
|
# encoding, to be more consistent with other Python APIs.
|
|
return [f.decode() for f in result]
|
|
|
|
def create_preprocessor(
|
|
self,
|
|
project: Opt[str] = None,
|
|
line_mode: Optional[FileReader.LineMode] = None,
|
|
) -> FileReader:
|
|
"""
|
|
Create preprocessor data from compiler arguments found in the given GPR
|
|
project (``-gnateP`` and ``-gnateD`` arguments), or from the
|
|
``project`` sub-project (if the argument is passed).
|
|
|
|
Note that this function collects all arguments and returns an
|
|
approximation from them: it does not replicates exactly gprbuild's
|
|
behavior. This may raise a ``File_Read_Error`` exception if this fails
|
|
to read a preprocessor data file and a ``Syntax_Error`` exception if
|
|
one such file has invalid syntax.
|
|
"""
|
|
c_project = _coerce_bytes('project', project, or_none=True)
|
|
|
|
if line_mode is None:
|
|
c_line_mode_ref = None
|
|
else:
|
|
c_line_mode = ctypes.c_int(FileReader.LineMode._unwrap(line_mode))
|
|
c_line_mode_ref = ctypes.byref(c_line_mode)
|
|
|
|
return FileReader(
|
|
self._c_create_preprocessor(
|
|
self._c_value, c_project, c_line_mode_ref
|
|
),
|
|
)
|
|
|
|
_c_type = _hashable_c_pointer()
|
|
|
|
class _c_scenario_variable(ctypes.Structure):
|
|
_fields_ = [('name', ctypes.c_char_p),
|
|
('value', ctypes.c_char_p)]
|
|
|
|
_c_load = staticmethod(_import_func(
|
|
"ada_gpr_project_load",
|
|
[ctypes.c_char_p,
|
|
ctypes.POINTER(_c_scenario_variable),
|
|
ctypes.c_char_p,
|
|
ctypes.c_char_p,
|
|
ctypes.POINTER(_c_type),
|
|
ctypes.POINTER(_c_string_array_ptr)],
|
|
None,
|
|
))
|
|
|
|
_c_free = staticmethod(
|
|
_import_func("ada_gpr_project_free", [_c_type], None)
|
|
)
|
|
|
|
_c_create_unit_provider = staticmethod(_import_func(
|
|
"ada_gpr_project_create_unit_provider",
|
|
[_c_type, ctypes.c_char_p],
|
|
_unit_provider,
|
|
))
|
|
|
|
_c_source_files = staticmethod(_import_func(
|
|
"ada_gpr_project_source_files",
|
|
[_c_type, ctypes.c_int],
|
|
_c_string_array_ptr,
|
|
))
|
|
|
|
_c_create_preprocessor = staticmethod(_import_func(
|
|
"ada_gpr_project_create_preprocessor",
|
|
[_c_type, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int)],
|
|
_file_reader,
|
|
))
|