1"""Copyright (C) 2022 The Android Open Source Project
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7     http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14"""
15
16load("@bazel_skylib//lib:paths.bzl", "paths")
17
18"""Build rule for converting `.l` or `.ll` to C or C++ sources with Flex.
19
20Uses flex (and m4 under the hood) to convert .l and .ll source files into
21.c and .cc files. Does not support .lex or .lpp extensions
22
23Examples
24--------
25
26This is a simple example.
27```
28genlex(
29    name = "html_lex",
30    src = "html.l",
31)
32```
33
34This example uses some options for flex.
35```
36genlex(
37    name = "rules_l",
38    src = "rules.l",
39    lexopts = ["-d", "-v"],
40)
41```
42"""
43
44def _genlex_impl(ctx):
45    """Implementation for genlex rule."""
46
47    # TODO(b/190006308): When fixed, l and ll sources can coexist. Remove this.
48    exts = [f.extension for f in ctx.files.srcs]
49    contains_l = False
50    contains_ll = False
51    for ext in exts:
52        if ext == "l":
53            contains_l = True
54        if ext == "ll":
55            contains_ll = True
56    if contains_l and contains_ll:
57        fail(
58            "srcs contains both .l and .ll files. Please use separate targets.",
59        )
60
61    outputs = []
62    for src_file in ctx.files.srcs:
63        args = ctx.actions.args()
64        output_filename = ""
65
66        src_ext = src_file.extension
67        split_filename = src_file.basename.partition(".")
68        filename_without_ext = split_filename[0]
69
70        if src_ext == "l":
71            output_filename = paths.replace_extension(filename_without_ext, ".c")
72        elif src_ext == "ll":
73            output_filename = paths.replace_extension(filename_without_ext, ".cc")
74        output_file = ctx.actions.declare_file(output_filename)
75        outputs.append(output_file)
76        args.add("-o", output_file.path)
77
78        args.add_all(ctx.attr.lexopts)
79        args.add(src_file)
80
81        ctx.actions.run(
82            executable = ctx.executable._flex,
83            env = {
84                "M4": ctx.executable._m4.path,
85            },
86            arguments = [args],
87            inputs = [src_file],
88            tools = [ctx.executable._m4],
89            outputs = [output_file],
90            mnemonic = "Flex",
91            progress_message = "Generating %s from %s" % (
92                output_filename,
93                src_file.short_path,
94            ),
95        )
96    return [DefaultInfo(files = depset(outputs))]
97
98genlex = rule(
99    implementation = _genlex_impl,
100    doc = "Generate C/C++-language sources from a lex file using Flex.",
101    attrs = {
102        "srcs": attr.label_list(
103            mandatory = True,
104            allow_files = [".l", ".ll"],
105            doc = "The lex source file for this rule",
106        ),
107        "lexopts": attr.string_list(
108            doc = "A list of options to be added to the flex command line.",
109        ),
110        "_flex": attr.label(
111            default = "//prebuilts/build-tools:flex",
112            executable = True,
113            cfg = "exec",
114        ),
115        "_m4": attr.label(
116            default = "//prebuilts/build-tools:m4",
117            executable = True,
118            cfg = "exec",
119        ),
120    },
121)
122