Skip to content

Commit

Permalink
inf_parser: add support for scoped source files
Browse files Browse the repository at this point in the history
Adds additional parsing to the inf_parser to support scoped source files
which can be retrieved via the function get_sources(arch_list), which
will return all sources used for a given arch(s). Re-writes the logic
that does the same for scoped libraries to make it more extensible, but
no user facing changes.

Updates instanced_inf_table to use the new function to get the sources.
  • Loading branch information
Javagedes committed Oct 4, 2023
1 parent 5f391d3 commit 85ce665
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 30 deletions.
17 changes: 14 additions & 3 deletions edk2toollib/database/tables/instanced_inf_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ def _parse_inf_recursively(
visited.append(inf)
library_instance_list = []
library_class_list = []
arch = scope.split(".")[-1].upper()

#
# 0. Use the existing parser to parse the INF file. This parser parses an INF as an independent file
Expand All @@ -217,7 +218,7 @@ def _parse_inf_recursively(
# 1. Convert all libraries to their actual instances for this component. This takes into account
# any overrides for this component
#
for lib in infp.get_libraries(self.arch):
for lib in infp.get_libraries([arch]):
lib = lib.split(" ")[0]
library_instance_list.append(self._lib_to_instance(lib.lower(), scope, library_dict, override_dict))
library_class_list.append(lib)
Expand Down Expand Up @@ -246,6 +247,16 @@ def to_posix(path):
return Path(path).as_posix()
library_instance_list = list(map(to_posix, library_instance_list))

source_list = []
full_inf = self.pathobj.GetAbsolutePathOnThisSystemFromEdk2RelativePath(inf)
pkg = self.pathobj.GetContainingPackage(full_inf)
for source in infp.get_sources([arch]):
source = (Path(inf).parent / source).resolve().as_posix()
if pkg is not None:
source_list.append(source[source.find(pkg):])
else:
source_list.append(source)

Check warning on line 258 in edk2toollib/database/tables/instanced_inf_table.py

View check run for this annotation

Codecov / codecov/patch

edk2toollib/database/tables/instanced_inf_table.py#L258

Added line #L258 was not covered by tests

# Return Paths as posix paths, which is Edk2 standard.
to_return.append({
"DSC": Path(self.dsc).name,
Expand All @@ -255,8 +266,8 @@ def to_posix(path):
"LIBRARY_CLASS": library_class,
"COMPONENT": Path(component).as_posix(),
"MODULE_TYPE": infp.Dict["MODULE_TYPE"],
"ARCH": scope.split(".")[0].upper(),
"SOURCES_USED": list(map(lambda p: Path(p).as_posix(), infp.Sources)),
"ARCH": arch,
"SOURCES_USED": source_list,
"LIBRARIES_USED": list(library_instance_list),
"PROTOCOLS_USED": [], # TODO
"GUIDS_USED": [], # TODO
Expand Down
75 changes: 48 additions & 27 deletions edk2toollib/uefi/edk2/parsers/inf_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class InfParser(HashFileParser):
NOTE: Key / Value pairs determined by lines that contain a single =
"""
SECTION_REGEX = re.compile(r"\[(.*)\]")
SECTION_LIBRARY = "libraryclasses"
SECTION_LIBRARY = re.compile(r'libraryclasses(?:\.([^,.\]]+))?[,.\]]', re.IGNORECASE)
SECTION_SOURCE = re.compile(r'sources(?:\.([^,.\]]+))?[,.\]]', re.IGNORECASE)

def __init__(self):
"""Inits an empty parser."""
Expand All @@ -51,6 +52,7 @@ def __init__(self):
self.PackagesUsed = []
self.LibrariesUsed = []
self.ScopedLibraryDict = {}
self.ScopedSourceDict = {}
self.ProtocolsUsed = []
self.GuidsUsed = []
self.PpisUsed = []
Expand All @@ -59,14 +61,22 @@ def __init__(self):
self.Binaries = []
self.Path = ""

def get_libraries(self, archs: list[str]):
def get_libraries(self, arch_list: list[str]):
"""Returns a list of libraries depending on the requested archs."""
libraries = self.ScopedLibraryDict.get("common", []).copy()

for arch in archs:
libraries + self.ScopedLibraryDict.get(arch, []).copy()
for arch in arch_list:
libraries = libraries + self.ScopedLibraryDict.get(arch.lower(), []).copy()
return list(set(libraries))

def get_sources(self, arch_list: list[str]):
"""Returns a list of sources depending on the requested archs."""
sources = self.ScopedSourceDict.get("common", []).copy()

for arch in arch_list:
sources = sources + self.ScopedSourceDict.get(arch.lower(), []).copy()
return list(set(sources))

def ParseFile(self, filepath):
"""Parses the INF file provided."""
self.Logger.debug("Parsing file: %s" % filepath)
Expand Down Expand Up @@ -205,40 +215,51 @@ def ParseFile(self, filepath):
elif sline.strip().lower().startswith('[binaries'):
InBinariesSection = True

# Create scoped_library_dict
# Re-parse for scoped information
# Hopefully eventually replace all of the above with this
# logic
current_section = ""
arch = ""
arch_list = []

for line in self.Lines:
match = self.SECTION_REGEX.match(line)

if line.strip() == "":
continue

if line.strip().startswith("#"):
continue

# Match the current section we are in
match = self.SECTION_LIBRARY.findall(line)
if match:
section = match.group(1)
section = section.lower()

# A Library section
if section.startswith(self.SECTION_LIBRARY):
if section.count(".") == 1:
current_section, arch = tuple(section.split("."))
else:
current_section, arch = (section, "common")
# Some other section
else:
current_section = ""
arch = ""
current_section = "LibraryClasses"
arch_list = match
continue
match = self.SECTION_SOURCE.findall(line)
if match:
current_section = "Sources"
arch_list = match
continue

# Catch all others
match = self.SECTION_REGEX.match(line)
if match:
current_section = match.group(1)
arch_list = []

# Handle lines when we are in a library section
if current_section == self.SECTION_LIBRARY:
if arch in self.ScopedLibraryDict:
self.ScopedLibraryDict[arch].append(line.split()[0].strip())
else:
self.ScopedLibraryDict[arch] = [line.split()[0].strip()]
if current_section == "LibraryClasses":
for arch in arch_list:
arch = 'common' if arch == '' else arch.lower()
if arch in self.ScopedLibraryDict:
self.ScopedLibraryDict[arch].append(line.split()[0].strip())
else:
self.ScopedLibraryDict[arch] = [line.split()[0].strip()]

# Handle lines when we are in a source section
if current_section == "Sources":
for arch in arch_list:
arch = 'common' if arch == '' else arch.lower()
if arch in self.ScopedSourceDict:
self.ScopedSourceDict[arch].append(line.split()[0].strip())
else:
self.ScopedSourceDict[arch] = [line.split()[0].strip()]
self.Parsed = True
70 changes: 70 additions & 0 deletions tests.unit/parsers/test_inf_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from pathlib import Path

from edk2toollib.uefi.edk2.parsers.inf_parser import InfParser

INF_EXAMPLE1 = """
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = TestLib
FILE_GUID = ffffffff-ffff-ffff-ffff-ffffffffffff
MODULE_TYPE = DXE_DRIVER
VERSION_STRING = 1.0
LIBRARY_CLASS = BaseTestLib
[Sources]
# [Binaries]
File1.c
[Sources.common]
File2.c
[Sources.IA32]
# Random Comment
File3.c
# File999.c
[sources.IA32, sources.X64]
File4.c
[LibraryClasses]
Library1
[Binaries]
Binary1.efi
[LibraryClasses.common]
Library2
[LibraryClasses.IA32]
Library3
[LibraryClasses.IA32, LibraryClasses.X64]
Library4
"""
def test_inf_parser_scoped_libraryclasses(tmp_path: Path):
"""Test that we accurately detect scoped library classes."""
inf_path = tmp_path / "test.inf"
inf_path.touch()
inf_path.write_text(INF_EXAMPLE1)

infp = InfParser()
infp.ParseFile(inf_path)

assert sorted(infp.get_libraries([])) == sorted(["Library1", "Library2"])
assert sorted(infp.get_libraries(["Common"])) == sorted(["Library1", "Library2"])
assert sorted(infp.get_libraries(["IA32"])) == sorted(["Library1", "Library2", "Library3", "Library4"])
assert sorted(infp.get_libraries(["X64"])) == sorted(["Library1", "Library2", "Library4"])

def test_inf_parser_scoped_sources(tmp_path: Path):
"""Test that we accurately detect scoped sources."""
inf_path = tmp_path / "test.inf"
inf_path.touch()
inf_path.write_text(INF_EXAMPLE1)

infp = InfParser()
infp.ParseFile(inf_path)

assert sorted(infp.get_sources([])) == sorted(["File1.c", "File2.c"])
assert sorted(infp.get_sources(["Common"])) == sorted(["File1.c", "File2.c"])
assert sorted(infp.get_sources(["IA32"])) == sorted(["File1.c", "File2.c", "File3.c", "File4.c"])
assert sorted(infp.get_sources(["X64"])) == sorted(["File1.c", "File2.c", "File4.c"])

0 comments on commit 85ce665

Please sign in to comment.