mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-07 16:29:07 -04:00
Compare commits
8 commits
0f417ec6f4
...
dc6fe66d32
Author | SHA1 | Date | |
---|---|---|---|
|
dc6fe66d32 | ||
|
778a979714 | ||
|
6acf0fe5a0 | ||
|
077610b877 | ||
|
9429ac5f41 | ||
|
ca4ef1db88 | ||
|
c5949de543 | ||
|
f2d4643bf5 |
7 changed files with 185 additions and 36 deletions
|
@ -25,7 +25,8 @@ from functools import cached_property
|
||||||
import gi # type: ignore
|
import gi # type: ignore
|
||||||
|
|
||||||
gi.require_version("GIRepository", "2.0")
|
gi.require_version("GIRepository", "2.0")
|
||||||
from gi.repository import GIRepository # type: ignore
|
gi.require_version("GLib", "2.0")
|
||||||
|
from gi.repository import GIRepository, GLib # type: ignore
|
||||||
|
|
||||||
from . import typelib, xml_reader
|
from . import typelib, xml_reader
|
||||||
from .errors import CompileError, CompilerBugError
|
from .errors import CompileError, CompilerBugError
|
||||||
|
@ -35,19 +36,60 @@ _namespace_cache: T.Dict[str, "Namespace"] = {}
|
||||||
_xml_cache = {}
|
_xml_cache = {}
|
||||||
|
|
||||||
_user_search_paths = []
|
_user_search_paths = []
|
||||||
|
_typelib_search_paths_initalized: bool = False
|
||||||
|
_typelib_search_paths: list[str] = []
|
||||||
|
_gir_search_paths_initalized: bool = False
|
||||||
|
_gir_search_paths: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
def add_typelib_search_path(path: str):
|
def add_user_typelib_paths(paths: list):
|
||||||
_user_search_paths.append(path)
|
global _user_search_paths
|
||||||
|
_user_search_paths += paths
|
||||||
|
|
||||||
|
|
||||||
|
def collect_typelib_search_paths():
|
||||||
|
global _typelib_search_paths_initalized
|
||||||
|
if _typelib_search_paths_initalized:
|
||||||
|
return
|
||||||
|
|
||||||
|
global _typelib_search_paths
|
||||||
|
_typelib_search_paths += [
|
||||||
|
*_user_search_paths,
|
||||||
|
*GIRepository.Repository.get_search_path(),
|
||||||
|
# fallback paths
|
||||||
|
"/usr/local/lib/girepository-1.0",
|
||||||
|
"/usr/local/lib64/girepository-1.0",
|
||||||
|
"/usr/lib/girepository-1.0",
|
||||||
|
"/usr/lib64/girepository-1.0",
|
||||||
|
]
|
||||||
|
_typelib_search_paths_initalized = True
|
||||||
|
|
||||||
|
|
||||||
|
def collect_gir_search_paths():
|
||||||
|
global _gir_search_paths_initalized
|
||||||
|
if _gir_search_paths_initalized:
|
||||||
|
return
|
||||||
|
|
||||||
|
base_paths = [
|
||||||
|
GLib.get_user_data_dir(),
|
||||||
|
*GLib.get_system_data_dirs(),
|
||||||
|
# fallback paths
|
||||||
|
"/usr/local/share/",
|
||||||
|
"/usr/share/",
|
||||||
|
]
|
||||||
|
|
||||||
|
global _gir_search_paths
|
||||||
|
_gir_search_paths = [os.path.join(path, "gir-1.0") for path in base_paths]
|
||||||
|
_gir_search_paths_initalized = True
|
||||||
|
|
||||||
|
|
||||||
def get_namespace(namespace: str, version: str) -> "Namespace":
|
def get_namespace(namespace: str, version: str) -> "Namespace":
|
||||||
search_paths = [*GIRepository.Repository.get_search_path(), *_user_search_paths]
|
collect_typelib_search_paths()
|
||||||
|
|
||||||
filename = f"{namespace}-{version}.typelib"
|
filename = f"{namespace}-{version}.typelib"
|
||||||
|
|
||||||
if filename not in _namespace_cache:
|
if filename not in _namespace_cache:
|
||||||
for search_path in search_paths:
|
for search_path in _typelib_search_paths:
|
||||||
path = os.path.join(search_path, filename)
|
path = os.path.join(search_path, filename)
|
||||||
|
|
||||||
if os.path.exists(path) and os.path.isfile(path):
|
if os.path.exists(path) and os.path.isfile(path):
|
||||||
|
@ -60,7 +102,7 @@ def get_namespace(namespace: str, version: str) -> "Namespace":
|
||||||
if filename not in _namespace_cache:
|
if filename not in _namespace_cache:
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"Namespace {namespace}-{version} could not be found",
|
f"Namespace {namespace}-{version} could not be found",
|
||||||
hints=["search path: " + os.pathsep.join(search_paths)],
|
hints=["search path: " + os.pathsep.join(_typelib_search_paths)],
|
||||||
)
|
)
|
||||||
|
|
||||||
return _namespace_cache[filename]
|
return _namespace_cache[filename]
|
||||||
|
@ -73,12 +115,8 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
|
||||||
if len(_available_namespaces):
|
if len(_available_namespaces):
|
||||||
return _available_namespaces
|
return _available_namespaces
|
||||||
|
|
||||||
search_paths: list[str] = [
|
collect_typelib_search_paths()
|
||||||
*GIRepository.Repository.get_search_path(),
|
for search_path in _typelib_search_paths:
|
||||||
*_user_search_paths,
|
|
||||||
]
|
|
||||||
|
|
||||||
for search_path in search_paths:
|
|
||||||
try:
|
try:
|
||||||
filenames = os.listdir(search_path)
|
filenames = os.listdir(search_path)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -93,17 +131,12 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
|
||||||
|
|
||||||
|
|
||||||
def get_xml(namespace: str, version: str):
|
def get_xml(namespace: str, version: str):
|
||||||
search_paths = []
|
collect_gir_search_paths()
|
||||||
|
|
||||||
if data_paths := os.environ.get("XDG_DATA_DIRS"):
|
|
||||||
search_paths += [
|
|
||||||
os.path.join(path, "gir-1.0") for path in data_paths.split(os.pathsep)
|
|
||||||
]
|
|
||||||
|
|
||||||
filename = f"{namespace}-{version}.gir"
|
filename = f"{namespace}-{version}.gir"
|
||||||
|
|
||||||
if filename not in _xml_cache:
|
if filename not in _xml_cache:
|
||||||
for search_path in search_paths:
|
for search_path in _gir_search_paths:
|
||||||
path = os.path.join(search_path, filename)
|
path = os.path.join(search_path, filename)
|
||||||
|
|
||||||
if os.path.exists(path) and os.path.isfile(path):
|
if os.path.exists(path) and os.path.isfile(path):
|
||||||
|
@ -113,7 +146,7 @@ def get_xml(namespace: str, version: str):
|
||||||
if filename not in _xml_cache:
|
if filename not in _xml_cache:
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"GObject introspection file '{namespace}-{version}.gir' could not be found",
|
f"GObject introspection file '{namespace}-{version}.gir' could not be found",
|
||||||
hints=["search path: " + os.pathsep.join(search_paths)],
|
hints=["search path: " + os.pathsep.join(_gir_search_paths)],
|
||||||
)
|
)
|
||||||
|
|
||||||
return _xml_cache[filename]
|
return _xml_cache[filename]
|
||||||
|
|
|
@ -171,6 +171,24 @@ class LookupOp(InfixExpr):
|
||||||
did_you_mean=(self.property_name, self.lhs.type.properties.keys()),
|
did_you_mean=(self.property_name, self.lhs.type.properties.keys()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@validate("property")
|
||||||
|
def property_deprecated(self):
|
||||||
|
if self.lhs.type is None or not (
|
||||||
|
isinstance(self.lhs.type, gir.Class)
|
||||||
|
or isinstance(self.lhs.type, gir.Interface)
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
if property := self.lhs.type.properties.get(self.property_name):
|
||||||
|
if property.deprecated:
|
||||||
|
hints = []
|
||||||
|
if property.deprecated_doc:
|
||||||
|
hints.append(property.deprecated_doc)
|
||||||
|
raise DeprecatedWarning(
|
||||||
|
f"{property.signature} is deprecated",
|
||||||
|
hints=hints,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CastExpr(InfixExpr):
|
class CastExpr(InfixExpr):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
|
|
@ -149,7 +149,7 @@ class LanguageServer:
|
||||||
|
|
||||||
def _send(self, data):
|
def _send(self, data):
|
||||||
data["jsonrpc"] = "2.0"
|
data["jsonrpc"] = "2.0"
|
||||||
line = json.dumps(data, separators=(",", ":")) + "\r\n"
|
line = json.dumps(data, separators=(",", ":"))
|
||||||
printerr("output: " + line)
|
printerr("output: " + line)
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
f"Content-Length: {len(line.encode())}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{line}"
|
f"Content-Length: {len(line.encode())}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{line}"
|
||||||
|
|
|
@ -27,7 +27,7 @@ import typing as T
|
||||||
from . import formatter, interactive_port, parser, tokenizer
|
from . import formatter, interactive_port, parser, tokenizer
|
||||||
from .decompiler import decompile_string
|
from .decompiler import decompile_string
|
||||||
from .errors import CompileError, CompilerBugError, PrintableError, report_bug
|
from .errors import CompileError, CompilerBugError, PrintableError, report_bug
|
||||||
from .gir import add_typelib_search_path
|
from .gir import add_user_typelib_paths
|
||||||
from .lsp import LanguageServer
|
from .lsp import LanguageServer
|
||||||
from .outputs import XmlOutput
|
from .outputs import XmlOutput
|
||||||
from .utils import Colors
|
from .utils import Colors
|
||||||
|
@ -145,8 +145,7 @@ class BlueprintApp:
|
||||||
|
|
||||||
def cmd_compile(self, opts):
|
def cmd_compile(self, opts):
|
||||||
if opts.typelib_path != None:
|
if opts.typelib_path != None:
|
||||||
for typelib_path in opts.typelib_path:
|
add_user_typelib_paths(opts.typelib_path)
|
||||||
add_typelib_search_path(typelib_path)
|
|
||||||
|
|
||||||
data = opts.input.read()
|
data = opts.input.read()
|
||||||
try:
|
try:
|
||||||
|
@ -166,8 +165,7 @@ class BlueprintApp:
|
||||||
|
|
||||||
def cmd_batch_compile(self, opts):
|
def cmd_batch_compile(self, opts):
|
||||||
if opts.typelib_path != None:
|
if opts.typelib_path != None:
|
||||||
for typelib_path in opts.typelib_path:
|
add_user_typelib_paths(opts.typelib_path)
|
||||||
add_typelib_search_path(typelib_path)
|
|
||||||
|
|
||||||
for file in opts.inputs:
|
for file in opts.inputs:
|
||||||
data = file.read()
|
data = file.read()
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Dialog {
|
|
||||||
use-header-bar: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Window {
|
|
||||||
keys-changed => $on_window_keys_changed();
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
3,1,6,Gtk.Dialog is deprecated
|
|
110
tests/test_deprecations.py
Normal file
110
tests/test_deprecations.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# test_samples.py
|
||||||
|
#
|
||||||
|
# Copyright 2024 James Westman <james@jwestman.net>
|
||||||
|
#
|
||||||
|
# This file is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as
|
||||||
|
# published by the Free Software Foundation; either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This file 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "4.0")
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
from blueprintcompiler import parser, tokenizer
|
||||||
|
from blueprintcompiler.errors import DeprecatedWarning, PrintableError
|
||||||
|
|
||||||
|
# Testing deprecation warnings requires special handling because libraries can add deprecations with new versions,
|
||||||
|
# causing tests to break if we're not careful.
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeprecations(unittest.TestCase):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.gtkVersion = f"{Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}"
|
||||||
|
|
||||||
|
def assertDeprecation(self, blueprint: str, message: str):
|
||||||
|
try:
|
||||||
|
tokens = tokenizer.tokenize(blueprint)
|
||||||
|
_ast, errors, warnings = parser.parse(tokens)
|
||||||
|
|
||||||
|
self.assertIsNone(errors)
|
||||||
|
self.assertEqual(len(warnings), 1)
|
||||||
|
self.assertIsInstance(warnings[0], DeprecatedWarning)
|
||||||
|
self.assertEqual(warnings[0].message, message)
|
||||||
|
except PrintableError as e: # pragma: no cover
|
||||||
|
e.pretty_print("<deprecations test>", blueprint)
|
||||||
|
raise AssertionError()
|
||||||
|
|
||||||
|
def test_class_deprecation(self):
|
||||||
|
if Gtk.check_version(4, 10, 0) is not None:
|
||||||
|
self.skipTest(f"Gtk.Dialog is not deprecated in GTK {self.gtkVersion}")
|
||||||
|
|
||||||
|
blueprint = """
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
use-header-bar: 1;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
message = "Gtk.Dialog is deprecated"
|
||||||
|
|
||||||
|
self.assertDeprecation(blueprint, message)
|
||||||
|
|
||||||
|
def test_property_deprecation(self):
|
||||||
|
self.skipTest(
|
||||||
|
"gobject-introspection does not currently write property deprecations to the typelib. See <https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/410>."
|
||||||
|
)
|
||||||
|
|
||||||
|
if Gtk.check_version(4, 4, 0) is not None:
|
||||||
|
self.skipTest(
|
||||||
|
f"Gtk.DropTarget:drop is not deprecated in GTK {self.gtkVersion}"
|
||||||
|
)
|
||||||
|
|
||||||
|
blueprint = """
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
$MyObject {
|
||||||
|
a: bind drop_target.drop;
|
||||||
|
}
|
||||||
|
|
||||||
|
DropTarget drop_target {
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
message = "Gtk.DropTarget:drop is deprecated"
|
||||||
|
|
||||||
|
self.assertDeprecation(blueprint, message)
|
||||||
|
|
||||||
|
def test_signal_deprecation(self):
|
||||||
|
if Gtk.check_version(4, 10, 0) is not None:
|
||||||
|
self.skipTest(
|
||||||
|
f"Gtk.Window::keys-changed is not deprecated in GTK {self.gtkVersion}"
|
||||||
|
)
|
||||||
|
|
||||||
|
blueprint = """
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Window {
|
||||||
|
keys-changed => $handler();
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
message = "signal Gtk.Window::keys-changed () is deprecated"
|
||||||
|
|
||||||
|
self.assertDeprecation(blueprint, message)
|
Loading…
Add table
Add a link
Reference in a new issue