blob: b6917c3bc204b86b38e612278b8db822a8650bc9 [file] [log] [blame] [edit]
import collections.abc
import pathlib
import shlex
import textwrap
import ModuleVerifierLanguage
from ModuleVerifierLanguage import Language, LanguageStandard
from ModuleVerifierTypes import (
ModuleVerifierInputs,
ModuleVerifierTarget,
ModuleVerifierTargetSet,
)
def _module_verifier_target_set_combinations(
targets: list[ModuleVerifierTarget],
languages: list[Language],
standards: list[LanguageStandard],
) -> list[ModuleVerifierTargetSet]:
result: list[ModuleVerifierTargetSet] = []
languages_and_standards = [
(language, language.supported_standards(standards)) for language in languages
]
for target in targets:
# FIXME: Support Catalyst targets.
if target.suffix is not None and "macabi" in target.suffix:
continue
for language, supported_standards in languages_and_standards:
for standard in supported_standards:
result.append(ModuleVerifierTargetSet(target, language, standard))
return result
class GenerateModuleVerifierInputsTask:
"""
A type used to generate inputs from an environment, for a module verifier to later consume.
Each task describes a set of inputs which, when combined, form the following structure:
```
{target_temp_dir}/
└── VerifyModule/
└── {product_name}/
├── {language}/
│ ├── Test.{extension}
│ └── Test.framework/
│ ├── Headers/
│ │ └── Test.h
│ └── Modules/
│ └── module.modulemap
└── {language_1}/
├── ...
└── ...
```
"""
target_set: ModuleVerifierTargetSet
inputs: ModuleVerifierInputs
@classmethod
def create_tasks(
cls, environment: collections.abc.Mapping[str, str]
) -> list["GenerateModuleVerifierInputsTask"]:
"""
Creates a collection of `ModuleVerifierInputGeneratorTask`s using the specified environment.
The environment variables this routine depends on are:
```
MODULE_VERIFIER_SUPPORTED_LANGUAGES
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS
ARCHS
LLVM_TARGET_TRIPLE_VENDOR
LLVM_TARGET_TRIPLE_OS_VERSION
LLVM_TARGET_TRIPLE_SUFFIX
PRODUCT_NAME
TARGET_TEMP_DIR
```
Args:
environment: Specifies the types and combinations of tasks that should be created.
Returns:
The collection of input generator tasks; one for each language in `MODULE_VERIFIER_SUPPORTED_LANGUAGES`.
Raises:
AssertionError: If an invalid language or standard is specified, or an invalid combination thereof.
"""
language_names = shlex.split(
environment.get("MODULE_VERIFIER_SUPPORTED_LANGUAGES", "")
)
languages: list[Language] = []
for name in language_names:
try:
languages.append(Language(name))
except ValueError:
raise AssertionError(f"Unsupported module verifier language {name}")
standard_names = shlex.split(
environment.get("MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS", "")
)
standards: list[LanguageStandard] = []
for name in standard_names:
try:
standards.append(LanguageStandard(name))
except ValueError:
raise AssertionError(
f"Unsupported module verifier language standard {name}"
)
ModuleVerifierLanguage.verify_languages(languages, standards)
arch_names = shlex.split(environment.get("ARCHS", ""))
vendor = environment["LLVM_TARGET_TRIPLE_VENDOR"]
os_version = environment["LLVM_TARGET_TRIPLE_OS_VERSION"]
suffix = environment.get("LLVM_TARGET_TRIPLE_SUFFIX")
targets = [
ModuleVerifierTarget(arch, vendor, os_version, suffix)
for arch in arch_names
]
product_name = environment["PRODUCT_NAME"]
target_temp_dir = environment["TARGET_TEMP_DIR"]
languages_to_inputs: dict[Language, ModuleVerifierInputs] = {}
for language in languages:
output_path = pathlib.Path(
f"{target_temp_dir}/VerifyModule/{product_name}/{language.value}"
)
inputs = ModuleVerifierInputs(
output_path / f"Test.{language.source_file_extension()}",
output_path / "Test.framework" / "Headers" / "Test.h",
output_path / "Test.framework" / "Modules" / "module.modulemap",
output_path,
)
languages_to_inputs[language] = inputs
target_sets = _module_verifier_target_set_combinations(
targets, languages, standards
)
result: list[GenerateModuleVerifierInputsTask] = []
for target_set in target_sets:
inputs = languages_to_inputs[target_set.language]
result.append(GenerateModuleVerifierInputsTask(target_set, inputs))
return result
def __init__(
self, target_set: ModuleVerifierTargetSet, inputs: ModuleVerifierInputs
):
self.target_set = target_set
self.inputs = inputs
def perform_action(self, headers: list[str]) -> None:
"""
Generates the module verifier inputs described by this task, using the specified list of header files.
The input test framework contains a module map that uses `Test.h` as an umbrella header, which itself includes all files in `headers`. This framework is then included by the translation unit file created for each valid language.
This routine creates parent directories for each newly created file, if needed.
Args:
headers: A list of header files to include when generating the test header input. Each element in this list should be a string of the form `product_name/file.h`.
"""
assert headers, "The list of headers must not be empty"
for input_directory in (
self.inputs.main,
self.inputs.header,
self.inputs.module_map,
):
input_directory.parent.mkdir(parents=True, exist_ok=True)
include = self.target_set.language.include_statement()
self.inputs.main.write_text(f"{include} <Test/Test.h>")
output = "\n".join(f"{include} <{header}>" for header in headers)
self.inputs.header.write_text(output)
module_map_contents = textwrap.dedent("""\
framework module Test {
umbrella header "Test.h"
export *
module * { export * }
}
""")
self.inputs.module_map.write_text(module_map_contents)