codeql/misc/codegen/codegen.py

118 строки
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
""" Driver script to run all code generation """
import argparse
import logging
import os
import sys
import pathlib
import typing
import shlex
if 'BUILD_WORKSPACE_DIRECTORY' not in os.environ:
# we are not running with `bazel run`, set up module search path
_repo_root = pathlib.Path(__file__).resolve().parents[2]
sys.path.append(str(_repo_root))
from misc.codegen.lib import render, paths
from misc.codegen.generators import generate
def _parse_args() -> argparse.Namespace:
dirs = [pathlib.Path().resolve()]
dirs.extend(dirs[0].parents)
for dir in dirs:
conf = dir / "codegen.conf"
if conf.exists():
break
else:
conf = None
p = argparse.ArgumentParser(description="Code generation suite")
p.add_argument("--generate", type=lambda x: x.split(","),
help="specify what targets to generate as a comma separated list, choosing among dbscheme, ql, trap "
"and cpp")
p.add_argument("--verbose", "-v", action="store_true", help="print more information")
p.add_argument("--quiet", "-q", action="store_true", help="only print errors")
p.add_argument("--configuration-file", "-c", type=_abspath, default=conf,
help="A configuration file to load options from. By default, the first codegen.conf file found by "
"going up directories from the current location. If present all paths provided in options are "
"considered relative to its directory")
p.add_argument("--root-dir", type=_abspath,
help="the directory that should be regarded as the root of the language pack codebase. Used to "
"compute QL imports and in some comments and as root for relative paths provided as options. "
"If not provided it defaults to the directory of the configuration file, if any")
path_arguments = [
p.add_argument("--schema", default="schema.py",
help="input schema file (default %(default)s)"),
p.add_argument("--dbscheme",
help="output file for dbscheme generation, input file for trap generation"),
p.add_argument("--ql-output",
help="output directory for generated QL files"),
p.add_argument("--ql-stub-output",
help="output directory for QL stub/customization files. Defines also the "
"generated qll file importing every class file"),
p.add_argument("--ql-test-output",
help="output directory for QL generated extractor test files"),
p.add_argument("--cpp-output",
help="output directory for generated C++ files, required if trap or cpp is provided to "
"--generate"),
p.add_argument("--generated-registry",
help="registry file containing information about checked-in generated code. A .gitattributes"
"file is generated besides it to mark those files with linguist-generated=true. Must"
"be in a directory containing all generated code."),
]
p.add_argument("--script-name",
help="script name to put in header comments of generated files. By default, the path of this "
"script relative to the root directory")
p.add_argument("--trap-library",
help="path to the trap library from an include directory, required if generating C++ trap bindings"),
p.add_argument("--ql-format", action="store_true", default=True,
help="use codeql to autoformat QL files (which is the default)")
p.add_argument("--no-ql-format", action="store_false", dest="ql_format", help="do not format QL files")
p.add_argument("--codeql-binary", default="codeql", help="command to use for QL formatting (default %(default)s)")
p.add_argument("--force", "-f", action="store_true",
help="generate all files without skipping unchanged files and overwriting modified ones")
p.add_argument("--use-current-directory", action="store_true",
help="do not consider paths as relative to --root-dir or the configuration directory")
opts = p.parse_args()
if opts.configuration_file is not None:
with open(opts.configuration_file) as config:
defaults = p.parse_args(shlex.split(config.read(), comments=True))
for flag, value in opts._get_kwargs():
if value is None:
setattr(opts, flag, getattr(defaults, flag))
if opts.root_dir is None:
opts.root_dir = opts.configuration_file.parent
if not opts.generate:
p.error("Nothing to do, specify --generate")
# absolutize all paths
for arg in path_arguments:
path = getattr(opts, arg.dest)
if path is not None:
setattr(opts, arg.dest, _abspath(path) if opts.use_current_directory else (opts.root_dir / path))
if not opts.script_name:
opts.script_name = paths.exe_file.relative_to(opts.root_dir)
return opts
def _abspath(x: str) -> typing.Optional[pathlib.Path]:
return pathlib.Path(x).resolve() if x else None
def run():
opts = _parse_args()
if opts.verbose:
log_level = logging.DEBUG
elif opts.quiet:
log_level = logging.ERROR
else:
log_level = logging.INFO
logging.basicConfig(format="{levelname} {message}", style='{', level=log_level)
for target in opts.generate:
generate(target, opts, render.Renderer(opts.script_name))
if __name__ == "__main__":
run()