Skip to content

Commit 5cb3279

Browse files
authored
Merge pull request github#12319 from github/redsun82/swift-codegen
Codegen: make Swift codegen language agnostic
2 parents 7c85448 + 1218145 commit 5cb3279

File tree

193 files changed

+959
-816
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

193 files changed

+959
-816
lines changed

.github/workflows/swift.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
paths:
66
- "swift/**"
77
- "misc/bazel/**"
8+
- "misc/codegen/**"
89
- "*.bazel*"
910
- .github/workflows/swift.yml
1011
- .github/actions/**
@@ -19,6 +20,7 @@ on:
1920
paths:
2021
- "swift/**"
2122
- "misc/bazel/**"
23+
- "misc/codegen/**"
2224
- "*.bazel*"
2325
- .github/workflows/swift.yml
2426
- .github/actions/**

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ repos:
5353
name: Run Swift code generation unit tests
5454
files: ^swift/codegen/.*\.py$
5555
language: system
56-
entry: bazel test //swift/codegen/test
56+
entry: bazel test //misc/codegen/test
5757
pass_filenames: false

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
/python/ @github/codeql-dynamic
77
/ruby/ @github/codeql-dynamic
88
/swift/ @github/codeql-swift
9+
/misc/codegen/ @github/codeql-swift
910
/java/kotlin-extractor/ @github/codeql-kotlin
1011
/java/kotlin-explorer/ @github/codeql-kotlin
1112

misc/bazel/workspace.bzl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,13 @@ def codeql_workspace(repository_name = "codeql"):
3333
"https://github.com/bazelbuild/rules_python/archive/refs/tags/0.8.1.tar.gz",
3434
],
3535
)
36+
37+
maybe(
38+
repo_rule = http_archive,
39+
name = "bazel_skylib",
40+
sha256 = "b8a1527901774180afc798aeb28c4634bdccf19c4d98e7bdd1ce79d1fe9aaad7",
41+
urls = [
42+
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.4.1/bazel-skylib-1.4.1.tar.gz",
43+
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.1/bazel-skylib-1.4.1.tar.gz",
44+
],
45+
)

misc/bazel/workspace_deps.bzl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
22
load("@rules_python//python:pip.bzl", "pip_install")
3+
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
34

45
def codeql_workspace_deps(repository_name = "codeql"):
56
pip_install(
6-
name = "swift_codegen_deps",
7-
requirements = "@%s//swift/codegen:requirements.txt" % repository_name,
7+
name = "codegen_deps",
8+
requirements = "@%s//misc/codegen:requirements.txt" % repository_name,
89
)
10+
bazel_skylib_workspace()
911
rules_pkg_dependencies()
File renamed without changes.

misc/codegen/BUILD.bazel

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
load("@codegen_deps//:requirements.bzl", "requirement")
2+
3+
py_binary(
4+
name = "codegen",
5+
srcs = ["codegen.py"],
6+
data = [
7+
"//misc/codegen/templates:cpp",
8+
"//misc/codegen/templates:trap",
9+
],
10+
visibility = ["//visibility:public"],
11+
deps = [
12+
"//misc/codegen/generators",
13+
],
14+
)

misc/codegen/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Code generation suite
2+
3+
This directory contains the code generation suite used by the Swift extractor and the QL library. This suite will use
4+
the abstract class specification of `schema.py` to generate:
5+
6+
* the `dbscheme` file (see [`dbschemegen.py`](generators/dbschemegen.py))
7+
* the QL generated code and when appropriate the corresponding stubs (see [`qlgen.py`](generators/qlgen.py))
8+
* C++ tags and trap entries (see [`trapgen.py`](generators/trapgen.py))
9+
* C++ structured classes (see [`cppgen.py`](generators/cppgen.py))
10+
11+
An example `schema.py` [can be found in the Swift package](../../swift/schema.py).
12+
13+
## Usage
14+
15+
By default `bazel run //misc/codegen -- -c your-codegen.conf` will load options from `your-codegen.conf`. See
16+
the [Swift configuration](../../swift/codegen.conf) for an example. Calling `misc/codegen/codegen.py` directly (provided
17+
you installed dependencies via `pip3 install -r misc/codegen/requirements.txt`) will use a file named `codegen.conf`
18+
contained in an ancestor directory if any exists.
19+
20+
See `bazel run //misc/codegen -- --help` for a list of all options. In particular `--generate` can be used with a comma
21+
separated list to select what to generate (choosing among `dbscheme`, `ql`, `trap` and `cpp`).
22+
23+
## Implementation notes
24+
25+
The suite uses [mustache templating](https://mustache.github.io/) for generation. Templates are
26+
in [the `templates` directory](templates), prefixed with the generation target they are used for.
27+
28+
Rather than passing dictionaries to the templating engine, python dataclasses are used as defined
29+
in [the `lib` directory](lib). For each of the four generation targets the entry point for the implementation is
30+
specified as the `generate` function in the modules within [the `generators` directory](generators).
31+
32+
Finally, [`codegen.py`](codegen.py) is the driver script gluing everything together and specifying the command line
33+
options.
34+
35+
Unit tests are in [the `test` directory](test) and can be run via `bazel test //misc/codegen/test`.
36+
37+
For more details about each specific generation target, please refer to the module docstrings
38+
in [the `generators` directory](generators).

misc/codegen/codegen.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
""" Driver script to run all code generation """
3+
4+
import argparse
5+
import logging
6+
import os
7+
import sys
8+
import pathlib
9+
import typing
10+
import shlex
11+
12+
if 'BUILD_WORKSPACE_DIRECTORY' not in os.environ:
13+
# we are not running with `bazel run`, set up module search path
14+
_repo_root = pathlib.Path(__file__).resolve().parents[2]
15+
sys.path.append(str(_repo_root))
16+
17+
from misc.codegen.lib import render, paths
18+
from misc.codegen.generators import generate
19+
20+
21+
def _parse_args() -> argparse.Namespace:
22+
dirs = [pathlib.Path().resolve()]
23+
dirs.extend(dirs[0].parents)
24+
for dir in dirs:
25+
conf = dir / "codegen.conf"
26+
if conf.exists():
27+
break
28+
else:
29+
conf = None
30+
31+
p = argparse.ArgumentParser(description="Code generation suite")
32+
p.add_argument("--generate", type=lambda x: x.split(","),
33+
help="specify what targets to generate as a comma separated list, choosing among dbscheme, ql, trap "
34+
"and cpp")
35+
p.add_argument("--verbose", "-v", action="store_true", help="print more information")
36+
p.add_argument("--quiet", "-q", action="store_true", help="only print errors")
37+
p.add_argument("--configuration-file", "-c", type=_abspath, default=conf,
38+
help="A configuration file to load options from. By default, the first codegen.conf file found by "
39+
"going up directories from the current location. If present all paths provided in options are "
40+
"considered relative to its directory")
41+
p.add_argument("--root-dir", type=_abspath,
42+
help="the directory that should be regarded as the root of the language pack codebase. Used to "
43+
"compute QL imports and in some comments and as root for relative paths provided as options. "
44+
"If not provided it defaults to the directory of the configuration file, if any")
45+
path_arguments = [
46+
p.add_argument("--schema", default="schema.py",
47+
help="input schema file (default %(default)s)"),
48+
p.add_argument("--dbscheme",
49+
help="output file for dbscheme generation, input file for trap generation"),
50+
p.add_argument("--ql-output",
51+
help="output directory for generated QL files"),
52+
p.add_argument("--ql-stub-output",
53+
help="output directory for QL stub/customization files. Defines also the "
54+
"generated qll file importing every class file"),
55+
p.add_argument("--ql-test-output",
56+
help="output directory for QL generated extractor test files"),
57+
p.add_argument("--cpp-output",
58+
help="output directory for generated C++ files, required if trap or cpp is provided to "
59+
"--generate"),
60+
p.add_argument("--generated-registry",
61+
help="registry file containing information about checked-in generated code"),
62+
]
63+
p.add_argument("--script-name",
64+
help="script name to put in header comments of generated files. By default, the path of this "
65+
"script relative to the root directory")
66+
p.add_argument("--trap-library",
67+
help="path to the trap library from an include directory, required if generating C++ trap bindings"),
68+
p.add_argument("--ql-format", action="store_true", default=True,
69+
help="use codeql to autoformat QL files (which is the default)")
70+
p.add_argument("--no-ql-format", action="store_false", dest="ql_format", help="do not format QL files")
71+
p.add_argument("--codeql-binary", default="codeql", help="command to use for QL formatting (default %(default)s)")
72+
p.add_argument("--force", "-f", action="store_true",
73+
help="generate all files without skipping unchanged files and overwriting modified ones")
74+
p.add_argument("--use-current-directory", action="store_true",
75+
help="do not consider paths as relative to --root-dir or the configuration directory")
76+
opts = p.parse_args()
77+
if opts.configuration_file is not None:
78+
with open(opts.configuration_file) as config:
79+
defaults = p.parse_args(shlex.split(config.read(), comments=True))
80+
for flag, value in opts._get_kwargs():
81+
if value is None:
82+
setattr(opts, flag, getattr(defaults, flag))
83+
if opts.root_dir is None:
84+
opts.root_dir = opts.configuration_file.parent
85+
if not opts.generate:
86+
p.error("Nothing to do, specify --generate")
87+
# absolutize all paths
88+
for arg in path_arguments:
89+
path = getattr(opts, arg.dest)
90+
if path is not None:
91+
setattr(opts, arg.dest, _abspath(path) if opts.use_current_directory else (opts.root_dir / path))
92+
if not opts.script_name:
93+
opts.script_name = paths.exe_file.relative_to(opts.root_dir)
94+
return opts
95+
96+
97+
def _abspath(x: str) -> typing.Optional[pathlib.Path]:
98+
return pathlib.Path(x).resolve() if x else None
99+
100+
101+
def run():
102+
opts = _parse_args()
103+
if opts.verbose:
104+
log_level = logging.DEBUG
105+
elif opts.quiet:
106+
log_level = logging.ERROR
107+
else:
108+
log_level = logging.INFO
109+
logging.basicConfig(format="{levelname} {message}", style='{', level=log_level)
110+
for target in opts.generate:
111+
generate(target, opts, render.Renderer(opts.script_name, opts.root_dir))
112+
113+
114+
if __name__ == "__main__":
115+
run()

misc/codegen/generators/BUILD.bazel

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
load("@codegen_deps//:requirements.bzl", "requirement")
2+
3+
py_library(
4+
name = "generators",
5+
srcs = glob(["*.py"]),
6+
visibility = ["//misc/codegen:__subpackages__"],
7+
deps = [
8+
"//misc/codegen/lib",
9+
"//misc/codegen/loaders",
10+
],
11+
)

0 commit comments

Comments
 (0)