github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/rules/proto_gazelle.bzl (about)

     1  # Copyright 2017 The Bazel Authors. All rights reserved.
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #    http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  
    15  """proto_gazelle.bzl provides the proto_gazelle rule.
    16  """
    17  
    18  load(
    19      "@bazel_skylib//lib:shell.bzl",
    20      "shell",
    21  )
    22  
    23  DEFAULT_LANGUAGES = [
    24      "@bazel_gazelle//language/proto:go_default_library",
    25      "@bazel_gazelle//language/go:go_default_library",
    26      "@build_stack_rules_proto//language/protobuf",
    27  ]
    28  
    29  def _valid_env_variable_name(name):
    30      """ Returns if a string is in the regex [a-zA-Z_][a-zA-Z0-9_]*
    31  
    32      Given that bazel lacks support of regex, we need to implement
    33      a poor man validation
    34      """
    35      if not name:
    36          return False
    37      for i, c in enumerate(name.elems()):
    38          if c.isalpha() or c == "_" or (i > 0 and c.isdigit()):
    39              continue
    40          return False
    41      return True
    42  
    43  def _rlocation_path(ctx, file):
    44      if file.short_path.startswith("../"):
    45          return file.short_path[3:]
    46      else:
    47          return ctx.workspace_name + "/" + file.short_path
    48  
    49  def _gazelle_runner_impl(ctx):
    50      args = [ctx.attr.command]
    51      if ctx.attr.mode:
    52          args.extend(["-mode", ctx.attr.mode])
    53      if ctx.attr.external:
    54          args.extend(["-external", ctx.attr.external])
    55      if ctx.attr.prefix:
    56          args.extend(["-go_prefix", ctx.attr.prefix])
    57      if ctx.attr.build_tags:
    58          args.extend(["-build_tags", ",".join(ctx.attr.build_tags)])
    59      if ctx.attr.cfgs:
    60          cfgs = ",".join([f.short_path for f in ctx.files.cfgs])
    61          args.extend(["-proto_configs", cfgs])
    62      if ctx.attr.imports:
    63          imports = ",".join([f.short_path for f in ctx.files.imports])
    64          args.extend(["-proto_imports_in", imports])
    65  
    66      args.extend([ctx.expand_location(arg, ctx.attr.data) for arg in ctx.attr.extra_args])
    67  
    68      for key in ctx.attr.env:
    69          if not _valid_env_variable_name(key):
    70              fail("Invalid environmental variable name: '%s'" % key)
    71  
    72      env = "\n".join(["export %s=%s" % (x, shell.quote(y)) for (x, y) in ctx.attr.env.items()])
    73  
    74      out_file = ctx.actions.declare_file(ctx.label.name + ".bash")
    75      go_tool = ctx.toolchains["@io_bazel_rules_go//go:toolchain"].sdk.go
    76      repo_config = ctx.file._repo_config
    77      substitutions = {
    78          "@@ARGS@@": shell.array_literal(args),
    79          "@@GAZELLE_PATH@@": shell.quote(_rlocation_path(ctx, ctx.executable.gazelle)),
    80          "@@GENERATED_MESSAGE@@": """
    81  # Generated by {label}
    82  # DO NOT EDIT
    83  """.format(label = str(ctx.label)),
    84          "@@GOTOOL@@": shell.quote(_rlocation_path(ctx, go_tool)),
    85          "@@ENV@@": env,
    86          "@@REPO_CONFIG_PATH@@": shell.quote(_rlocation_path(ctx, repo_config)) if repo_config else "",
    87      }
    88      ctx.actions.expand_template(
    89          template = ctx.file._template,
    90          output = out_file,
    91          substitutions = substitutions,
    92          is_executable = True,
    93      )
    94      runfiles = ctx.runfiles(files = ctx.files.cfgs + ctx.files.imports + [
    95          ctx.executable.gazelle,
    96          go_tool,
    97      ] + ([repo_config] if repo_config else [])).merge(
    98          ctx.attr.gazelle[DefaultInfo].default_runfiles,
    99      )
   100      for d in ctx.attr.data:
   101          runfiles = runfiles.merge(d[DefaultInfo].default_runfiles)
   102      return [DefaultInfo(
   103          files = depset([out_file]),
   104          runfiles = runfiles,
   105          executable = out_file,
   106      )]
   107  
   108  _gazelle_runner = rule(
   109      implementation = _gazelle_runner_impl,
   110      attrs = {
   111          "gazelle": attr.label(
   112              default = "@build_stack_rules_proto//cmd/gazelle",
   113              executable = True,
   114              cfg = "exec",
   115          ),
   116          "command": attr.string(
   117              values = [
   118                  "fix",
   119                  "update",
   120                  "update-repos",
   121              ],
   122              default = "update",
   123          ),
   124          "mode": attr.string(
   125              values = ["", "print", "fix", "diff"],
   126              default = "",
   127          ),
   128          "external": attr.string(
   129              values = ["", "external", "static", "vendored"],
   130              default = "",
   131          ),
   132          "build_tags": attr.string_list(),
   133          "prefix": attr.string(),
   134          "extra_args": attr.string_list(),
   135          "data": attr.label_list(allow_files = True),
   136          "imports": attr.label_list(allow_files = True),
   137          "cfgs": attr.label_list(allow_files = True),
   138          "env": attr.string_dict(),
   139          "_repo_config": attr.label(
   140              default = None,
   141              allow_single_file = True,
   142          ),
   143          "_template": attr.label(
   144              default = "@bazel_gazelle//internal:gazelle.bash.in",
   145              allow_single_file = True,
   146          ),
   147      },
   148      executable = True,
   149      toolchains = ["@io_bazel_rules_go//go:toolchain"],
   150  )
   151  
   152  def proto_gazelle(name, **kwargs):
   153      """proto_gazelle is the macro that calls the gazelle runner
   154  
   155      Args:
   156          name: the name of the rule
   157          **kwargs: arguments to gazelle_runner
   158      """
   159      if "args" in kwargs:
   160          # The args attribute has special meaning for executable rules, but we
   161          # always want extra_args here instead.
   162          if "extra_args" in kwargs:
   163              fail("{}: both args and extra_args were provided".format(name))
   164          kwargs["extra_args"] = kwargs["args"]
   165          kwargs.pop("args")
   166  
   167      visibility = kwargs.pop("visibility", default = None)
   168  
   169      tags_set = {t: "" for t in kwargs.pop("tags", [])}
   170      tags_set["manual"] = ""
   171      tags = [k for k in tags_set.keys()]
   172      runner_name = name + "-runner"
   173      _gazelle_runner(
   174          name = runner_name,
   175          tags = tags,
   176          **kwargs
   177      )
   178      native.sh_binary(
   179          name = name,
   180          srcs = [runner_name],
   181          tags = tags,
   182          visibility = visibility,
   183          deps = ["@bazel_tools//tools/bash/runfiles"],
   184          data = kwargs["data"] if "data" in kwargs else [],
   185      )