blob: 8c8cc4887cd29a65c23bc32bc6cb3892d0a172fb [file] [log] [blame] [edit]
#!/usr/bin/env python3
# Python bindings for libgdiagnostics, using ctypes
# Contributed by David Malcolm <dmalcolm@redhat.com>
#
# Copyright (C) 2025 Free Software Foundation, Inc.
#
# This file is part of GCC.
#
# GCC is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GCC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING. If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
from contextlib import contextmanager
import ctypes
# Lower-level API: use ctypes and FFI to wrap the C API directly
cdll = ctypes.cdll.LoadLibrary('libgdiagnostics.so')
libc = ctypes.CDLL(None)
c_stderr = ctypes.c_void_p.in_dll(libc, 'stderr')
# Opaque structs
class c_diagnostic_manager(ctypes.Structure):
pass
c_diagnostic_manager_ptr = ctypes.POINTER(c_diagnostic_manager)
class c_diagnostic(ctypes.Structure):
pass
c_diagnostic_ptr = ctypes.POINTER(c_diagnostic)
class c_diagnostic_file(ctypes.Structure):
pass
c_diagnostic_file_ptr = ctypes.POINTER(c_diagnostic_file)
class c_diagnostic_physical_location(ctypes.Structure):
pass
c_diagnostic_physical_location_ptr = ctypes.POINTER(c_diagnostic_physical_location)
# Enums
DIAGNOSTIC_COLORIZE_IF_TTY = 0
DIAGNOSTIC_LEVEL_ERROR = 0
DIAGNOSTIC_LEVEL_WARNING = 1
DIAGNOSTIC_LEVEL_NOTE = 2
DIAGNOSTIC_LEVEL_SORRY = 3
# Entrypoints
cdll.diagnostic_manager_new.argtypes = []
cdll.diagnostic_manager_new.restype = c_diagnostic_manager_ptr
cdll.diagnostic_manager_release.argtypes = [c_diagnostic_manager_ptr]
cdll.diagnostic_manager_release.restype = None
cdll.diagnostic_manager_set_tool_name.argtypes = [c_diagnostic_manager_ptr,
ctypes.c_char_p]
cdll.diagnostic_manager_set_tool_name.restype = None
cdll.diagnostic_manager_begin_group.argtypes = [c_diagnostic_manager_ptr]
cdll.diagnostic_manager_begin_group.restype = None
cdll.diagnostic_manager_end_group.argtypes = [c_diagnostic_manager_ptr]
cdll.diagnostic_manager_end_group.restype = None
cdll.diagnostic_begin.argtypes = [c_diagnostic_manager_ptr,
ctypes.c_int]
cdll.diagnostic_begin.restype = c_diagnostic_ptr
cdll.diagnostic_finish.argtypes = [c_diagnostic_ptr,
ctypes.c_char_p]#, ctypes.c_char_p] # FIXME: should be variadic
cdll.diagnostic_finish.restype = None
cdll.diagnostic_manager_new_file.argtypes = [c_diagnostic_manager_ptr,
ctypes.c_char_p,
ctypes.c_char_p]
cdll.diagnostic_manager_new_file.restype = c_diagnostic_file_ptr
cdll.diagnostic_manager_new_location_from_file_and_line.argtypes \
= [c_diagnostic_manager_ptr,
c_diagnostic_file_ptr,
ctypes.c_int]
cdll.diagnostic_manager_new_location_from_file_and_line.restype \
= c_diagnostic_physical_location_ptr
cdll.diagnostic_manager_new_location_from_file_line_column.argtypes \
= [c_diagnostic_manager_ptr,
c_diagnostic_file_ptr,
ctypes.c_int,
ctypes.c_int]
cdll.diagnostic_manager_new_location_from_file_line_column.restype \
= c_diagnostic_physical_location_ptr
cdll.diagnostic_manager_new_location_from_range.argtypes\
= [c_diagnostic_manager_ptr,
c_diagnostic_physical_location_ptr,
c_diagnostic_physical_location_ptr,
c_diagnostic_physical_location_ptr]
cdll.diagnostic_manager_new_location_from_range.restype \
= c_diagnostic_physical_location_ptr
cdll.diagnostic_set_location.argtypes = [c_diagnostic_ptr,
c_diagnostic_physical_location_ptr]
cdll.diagnostic_set_location.restype = None
cdll.diagnostic_add_fix_it_hint_replace.argtypes \
= [c_diagnostic_ptr,
c_diagnostic_physical_location_ptr,
ctypes.c_char_p]
cdll.diagnostic_add_fix_it_hint_replace.restype = None
cdll.diagnostic_manager_add_sink_from_spec.argtypes \
= [c_diagnostic_manager_ptr,
ctypes.c_char_p,
ctypes.c_char_p,
c_diagnostic_manager_ptr]
cdll.diagnostic_manager_add_sink_from_spec.restype = ctypes.c_int
# Helper functions
def _to_utf8(s: str):
if s is None:
return None
return s.encode('utf-8')
# Higher-level API, a more pythonic approach, with classes
class Manager:
def __init__(self):
self.c_mgr = cdll.diagnostic_manager_new()
if 0:
print('__init__: %r' % self.c_mgr)
def __del__(self):
if 0:
print('__del__: %r' % self.c_mgr)
self.c_mgr = cdll.diagnostic_manager_release(self.c_mgr)
def set_tool_name(self, name: str):
assert self.c_mgr
assert str
cdll.diagnostic_manager_set_tool_name (self.c_mgr, _to_utf8(name))
def add_text_sink(self):
assert self.c_mgr
# FIXME: hardcode the args for now
cdll.diagnostic_manager_add_text_sink (self.c_mgr,
c_stderr,
DIAGNOSTIC_COLORIZE_IF_TTY)
def add_sink_from_spec(self, option_name: str, scheme: str, control_mgr):
assert self.c_mgr
assert control_mgr.c_mgr
res = cdll.diagnostic_manager_add_sink_from_spec (self.c_mgr,
_to_utf8(option_name),
_to_utf8(scheme),
control_mgr.c_mgr)
if res:
raise RuntimeError()
def get_file(self, path: str, sarif_lang: str = None):
assert self.c_mgr
assert path
c_file = cdll.diagnostic_manager_new_file (self.c_mgr,
_to_utf8(path),
_to_utf8(sarif_lang))
return c_file
def get_file_and_line(self,
c_file: c_diagnostic_file_ptr,
line_num: int):
assert self.c_mgr
assert c_file
c_phys_loc = cdll.diagnostic_manager_new_location_from_file_and_line (self.c_mgr,
c_file,
line_num)
return c_phys_loc
def get_file_line_and_column(self,
c_file: c_diagnostic_file_ptr,
line_num: int,
column_num: int):
assert self.c_mgr
assert c_file
c_phys_loc = cdll.diagnostic_manager_new_location_from_file_line_column (self.c_mgr,
c_file,
line_num,
column_num)
return c_phys_loc
def get_range(self,
caret: c_diagnostic_physical_location_ptr,
start: c_diagnostic_physical_location_ptr,
finish: c_diagnostic_physical_location_ptr):
assert self.c_mgr
c_phys_loc = cdll.diagnostic_manager_new_location_from_range (self.c_mgr,
caret,
start,
finish)
return c_phys_loc
@contextmanager
def make_group(self):
assert self.c_mgr
cdll.diagnostic_manager_begin_group (self.c_mgr)
try:
yield
finally:
assert self.c_mgr
cdll.diagnostic_manager_end_group (self.c_mgr)
class Diagnostic:
def __init__(self, mgr: Manager, level: int):
self.c_diag = cdll.diagnostic_begin (mgr.c_mgr, level)
if 0:
print('__init__: %r' % self.c_diag)
def finish(self, fmt: str, *args):
assert self.c_diag
c_args = []
for arg in args:
if type(arg) is str:
arg = _to_utf8(arg)
c_args.append(arg)
cdll.diagnostic_finish (self.c_diag, _to_utf8(fmt), *c_args)
self.c_diagnostic = None
def set_location(self,
phys_loc: c_diagnostic_physical_location_ptr):
assert self.c_diag
cdll.diagnostic_set_location(self.c_diag, phys_loc)
# Chaining-style API
def at(self,
phys_loc: c_diagnostic_physical_location_ptr):
self.set_location(phys_loc)
return self
def with_fix_it_replace(self,
phys_loc: c_diagnostic_physical_location_ptr,
replacement: str):
assert self.c_diag
assert replacement
cdll.diagnostic_add_fix_it_hint_replace(self.c_diag,
phys_loc,
_to_utf8(replacement))
return self
def emit(self, fmt: str, *args):
self.finish(fmt, *args)