1"""Internal rules for building upb."""
2
3load(":upb_proto_library.bzl", "GeneratedSrcsInfo")
4
5def _librule(name):
6    return name + "_lib"
7
8runfiles_init = """\
9# --- begin runfiles.bash initialization v2 ---
10# Copy-pasted from the Bazel Bash runfiles library v2.
11set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
12source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
13  source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
14  source "$0.runfiles/$f" 2>/dev/null || \
15  source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
16  source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
17  { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
18# --- end runfiles.bash initialization v2 ---
19"""
20
21def _get_real_short_path(file):
22    # For some reason, files from other archives have short paths that look like:
23    #   ../com_google_protobuf/google/protobuf/descriptor.proto
24    short_path = file.short_path
25    if short_path.startswith("../"):
26        second_slash = short_path.index("/", 3)
27        short_path = short_path[second_slash + 1:]
28    return short_path
29
30def _get_real_root(file):
31    real_short_path = _get_real_short_path(file)
32    return file.path[:-len(real_short_path) - 1]
33
34def _get_real_roots(files):
35    roots = {}
36    for file in files:
37        real_root = _get_real_root(file)
38        if real_root:
39            roots[real_root] = True
40    return roots.keys()
41
42def _remove_prefix(str, prefix):
43    if not str.startswith(prefix):
44        fail("%s doesn't start with %s" % (str, prefix))
45    return str[len(prefix):]
46
47def _remove_suffix(str, suffix):
48    if not str.endswith(suffix):
49        fail("%s doesn't end with %s" % (str, suffix))
50    return str[:-len(suffix)]
51
52def make_shell_script(name, contents, out):
53    contents = runfiles_init + contents  # copybara:strip_for_google3
54    contents = contents.replace("$", "$$")
55    native.genrule(
56        name = "gen_" + name,
57        outs = [out],
58        cmd = "(cat <<'HEREDOC'\n%s\nHEREDOC\n) > $@" % contents,
59    )
60
61def generated_file_staleness_test(name, outs, generated_pattern):
62    """Tests that checked-in file(s) match the contents of generated file(s).
63
64    The resulting test will verify that all output files exist and have the
65    correct contents.  If the test fails, it can be invoked with --fix to
66    bring the checked-in files up to date.
67
68    Args:
69      name: Name of the rule.
70      outs: the checked-in files that are copied from generated files.
71      generated_pattern: the pattern for transforming each "out" file into a
72        generated file.  For example, if generated_pattern="generated/%s" then
73        a file foo.txt will look for generated file generated/foo.txt.
74    """
75
76    script_name = name + ".py"
77    script_src = "//:tools/staleness_test.py"
78
79    # Filter out non-existing rules so Blaze doesn't error out before we even
80    # run the test.
81    existing_outs = native.glob(include = outs)
82
83    # The file list contains a few extra bits of information at the end.
84    # These get unpacked by the Config class in staleness_test_lib.py.
85    file_list = outs + [generated_pattern, native.package_name() or ".", name]
86
87    native.genrule(
88        name = name + "_makescript",
89        outs = [script_name],
90        srcs = [script_src],
91        testonly = 1,
92        cmd = "cat $(location " + script_src + ") > $@; " +
93              "sed -i.bak -e 's|INSERT_FILE_LIST_HERE|" + "\\\n  ".join(file_list) + "|' $@",
94    )
95
96    native.py_test(
97        name = name,
98        srcs = [script_name],
99        data = existing_outs + [generated_pattern % file for file in outs],
100        deps = [
101            "//:staleness_test_lib",
102        ],
103    )
104
105# upb_amalgamation() rule, with file_list aspect.
106
107SrcList = provider(
108    fields = {
109        "srcs": "list of srcs",
110    },
111)
112
113def _file_list_aspect_impl(target, ctx):
114    if GeneratedSrcsInfo in target:
115        srcs = target[GeneratedSrcsInfo]
116        return [SrcList(srcs = srcs.srcs + srcs.hdrs)]
117
118    srcs = []
119    for src in ctx.rule.attr.srcs:
120        srcs += src.files.to_list()
121    for hdr in ctx.rule.attr.hdrs:
122        srcs += hdr.files.to_list()
123    for hdr in ctx.rule.attr.textual_hdrs:
124        srcs += hdr.files.to_list()
125    return [SrcList(srcs = srcs)]
126
127_file_list_aspect = aspect(
128    implementation = _file_list_aspect_impl,
129)
130
131def _upb_amalgamation(ctx):
132    inputs = []
133    for lib in ctx.attr.libs:
134        inputs += lib[SrcList].srcs
135    srcs = [src for src in inputs if src.path.endswith("c")]
136    ctx.actions.run(
137        inputs = inputs,
138        outputs = ctx.outputs.outs,
139        arguments = [ctx.bin_dir.path + "/", ctx.attr.prefix] + [f.path for f in srcs] + ["-I" + root for root in _get_real_roots(inputs)],
140        progress_message = "Making amalgamation",
141        executable = ctx.executable.amalgamator,
142    )
143    return []
144
145upb_amalgamation = rule(
146    attrs = {
147        "amalgamator": attr.label(
148            executable = True,
149            cfg = "host",
150        ),
151        "prefix": attr.string(
152            default = "",
153        ),
154        "libs": attr.label_list(aspects = [_file_list_aspect]),
155        "outs": attr.output_list(),
156    },
157    implementation = _upb_amalgamation,
158)
159
160def licenses(*args):
161    # No-op (for Google-internal usage).
162    pass
163