wayland/protocol/generate-shm-formats.py
Simon Ser 8854f64bae Add an automated script to update wl_shm.format
This prevents mismatches and missing formats between wl_shm.formats and
drm_fourcc.h.

The script collects DRM_FORMAT_* constants from drm_fourcc.h, compares the list
with the current wayland.xml entries (checking for any mismatch) and then
appends missing entries to wayland.xml.

Enum values are obtained by executing a generated C file which prints the
constants. There is no other reliable way to get these values as they are
defined via various macros.

There is no widespread Python library able to parse an XML file and format it
with all whitespace preserved. For this reason, we don't use an XML library to
create the new XML elements. Instead, we keep track of the line number of the
last wl_shm.format enum entry and add new entries right after. To be able to
read the line number of an element, we use lxml (the standard library doesn't
retain line number information).

Signed-off-by: Simon Ser <contact@emersion.fr>
2019-09-06 12:09:22 +00:00

154 lines
5.2 KiB
Python
Executable File

#!/usr/bin/env python3
# This script synchronizes wayland.xml's wl_shm.format enum with drm_fourcc.h.
# Invoke it to update wayland.xml, then manually check the changes applied.
#
# Requires Python 3, python-lxml, a C compiler and pkg-config.
import os
import subprocess
import sys
import tempfile
# We need lxml instead of the standard library because we want
# Element.sourceline
from lxml import etree as ElementTree
proto_dir = os.path.dirname(os.path.realpath(__file__))
wayland_proto = proto_dir + "/wayland.xml"
cc = os.getenv("CC", "cc")
pkg_config = os.getenv("PKG_CONFIG", "pkg-config")
# Find drm_fourcc.h
version = subprocess.check_output([pkg_config, "libdrm",
"--modversion"]).decode().strip()
cflags = subprocess.check_output([pkg_config, "libdrm",
"--cflags-only-I"]).decode().strip().split()
libdrm_include = None
for include_flag in cflags:
if not include_flag.startswith("-I"):
raise Exception("Expected one include dir for libdrm")
include_dir = include_flag[2:]
if include_dir.endswith("/libdrm"):
libdrm_include = include_dir
fourcc_include = libdrm_include + "/drm_fourcc.h"
if libdrm_include == None:
raise Exception("Failed to find libdrm include dir")
print("Using libdrm " + version, file=sys.stderr)
def drm_format_to_wl(ident):
return ident.replace("DRM_FORMAT_", "").lower()
# Collect DRM format constant names
ident_list = []
descriptions = {}
prev_comment = None
with open(fourcc_include) as input_file:
for l in input_file.readlines():
l = l.strip()
# Collect comments right before format definitions
if l.startswith("/*") and l.endswith("*/"):
prev_comment = l[2:-2]
continue
desc = prev_comment
prev_comment = None
# Recognize format definitions
parts = l.split()
if len(parts) < 3 or parts[0] != "#define":
continue
ident = parts[1]
if not ident.startswith("DRM_FORMAT_") or ident.startswith(
"DRM_FORMAT_MOD_"):
continue
ident_list.append(ident)
# Prefer in-line comments
if l.endswith("*/"):
desc = l[l.rfind("/*") + 2:-2]
if desc != None:
descriptions[drm_format_to_wl(ident)] = desc.strip()
# Collect DRM format values
idents = {}
with tempfile.TemporaryDirectory() as work_dir:
c_file_name = work_dir + "/print-formats.c"
exe_file_name = work_dir + "/print-formats"
with open(c_file_name, "w+") as c_file:
c_file.write('#include <inttypes.h>\n')
c_file.write('#include <stdint.h>\n')
c_file.write('#include <stdio.h>\n')
c_file.write('#include <drm_fourcc.h>\n')
c_file.write('\n')
c_file.write('int main(void) {\n')
for ident in ident_list:
c_file.write('printf("0x%" PRIX64 "\\n", (uint64_t)' + ident + ');\n')
c_file.write('}\n')
subprocess.check_call([cc, "-Wall", "-Wextra", "-o", exe_file_name,
c_file_name] + cflags)
output = subprocess.check_output([exe_file_name]).decode().strip()
for i, val in enumerate(output.splitlines()):
idents[ident_list[i]] = val
# We don't need those
del idents["DRM_FORMAT_BIG_ENDIAN"]
del idents["DRM_FORMAT_INVALID"]
del idents["DRM_FORMAT_RESERVED"]
# Convert from DRM constants to Wayland wl_shm.format entries
formats = {}
for ident, val in idents.items():
formats[drm_format_to_wl(ident)] = val.lower()
# Special case for ARGB8888 and XRGB8888
formats["argb8888"] = "0"
formats["xrgb8888"] = "1"
print("Loaded {} formats from drm_fourcc.h".format(len(formats)), file=sys.stderr)
tree = ElementTree.parse("wayland.xml")
root = tree.getroot()
wl_shm_format = root.find("./interface[@name='wl_shm']/enum[@name='format']")
if wl_shm_format == None:
raise Exception("wl_shm.format not found in wayland.xml")
# Remove formats we already know about
last_line = None
for node in wl_shm_format:
if node.tag != "entry":
continue
fmt = node.attrib["name"]
val = node.attrib["value"]
if fmt not in formats:
raise Exception("Format present in wl_shm.formats but not in "
"drm_fourcc.h: " + fmt)
if val != formats[fmt]:
raise Exception("Format value in wl_shm.formats ({}) differs "
"from value in drm_fourcc.h ({}) for format {}"
.format(val, formats[fmt], fmt))
del formats[fmt]
last_line = node.sourceline
if last_line == None:
raise Exception("Expected at least one existing wl_shm.format entry")
print("Adding {} formats to wayland.xml...".format(len(formats)), file=sys.stderr)
# Append new formats
new_wayland_proto = wayland_proto + ".new"
with open(new_wayland_proto, "w+") as output_file, \
open(wayland_proto) as input_file:
for i, l in enumerate(input_file.readlines()):
output_file.write(l)
if i + 1 == last_line:
for fmt, val in formats.items():
output_file.write(' <entry name="{}" value="{}"'
.format(fmt, val))
if fmt in descriptions:
output_file.write(' summary="{}"'.format(descriptions[fmt]))
output_file.write('/>\n')
os.rename(new_wayland_proto, wayland_proto)