protobuf.bzl 12.5 KB
Newer Older
1 2 3 4 5 6
def _GetPath(ctx, path):
  if ctx.label.workspace_root:
    return ctx.label.workspace_root + '/' + path
  else:
    return path

7 8 9 10 11 12 13
def _IsNewExternal(ctx):
  # Bazel 0.4.4 and older have genfiles paths that look like:
  #   bazel-out/local-fastbuild/genfiles/external/repo/foo
  # After the exec root rearrangement, they look like:
  #   ../repo/bazel-out/local-fastbuild/genfiles/foo
  return ctx.label.workspace_root.startswith("../")

Jisi Liu's avatar
Jisi Liu committed
14
def _GenDir(ctx):
15 16 17 18 19 20 21 22 23 24
  if _IsNewExternal(ctx):
    # We are using the fact that Bazel 0.4.4+ provides repository-relative paths
    # for ctx.genfiles_dir.
    return ctx.genfiles_dir.path + (
        "/" + ctx.attr.includes[0] if ctx.attr.includes and ctx.attr.includes[0] else "")
  # This means that we're either in the old version OR the new version in the local repo.
  # Either way, appending the source path to the genfiles dir works.
  return ctx.var["GENDIR"] + "/" + _SourceDir(ctx)

def _SourceDir(ctx):
25
  if not ctx.attr.includes:
26
    return ctx.label.workspace_root
27
  if not ctx.attr.includes[0]:
28
    return _GetPath(ctx, ctx.label.package)
29
  if not ctx.label.package:
30 31
    return _GetPath(ctx, ctx.attr.includes[0])
  return _GetPath(ctx, ctx.label.package + '/' + ctx.attr.includes[0])
32

33 34 35 36 37 38 39 40
def _CcHdrs(srcs, use_grpc_plugin=False):
  ret = [s[:-len(".proto")] + ".pb.h" for s in srcs]
  if use_grpc_plugin:
    ret += [s[:-len(".proto")] + ".grpc.pb.h" for s in srcs]
  return ret

def _CcSrcs(srcs, use_grpc_plugin=False):
  ret = [s[:-len(".proto")] + ".pb.cc" for s in srcs]
41
  if use_grpc_plugin:
42
    ret += [s[:-len(".proto")] + ".grpc.pb.cc" for s in srcs]
43
  return ret
44

45 46 47
def _CcOuts(srcs, use_grpc_plugin=False):
  return _CcHdrs(srcs, use_grpc_plugin) + _CcSrcs(srcs, use_grpc_plugin)

Jisi Liu's avatar
Jisi Liu committed
48
def _PyOuts(srcs):
Jisi Liu's avatar
Jisi Liu committed
49
  return [s[:-len(".proto")] + "_pb2.py" for s in srcs]
50

51
def _RelativeOutputPath(path, include, dest=""):
Jisi Liu's avatar
Jisi Liu committed
52 53 54 55 56 57 58 59
  if include == None:
    return path

  if not path.startswith(include):
    fail("Include path %s isn't part of the path %s." % (include, path))

  if include and include[-1] != '/':
    include = include + '/'
60 61
  if dest and dest[-1] != '/':
    dest = dest + '/'
Jisi Liu's avatar
Jisi Liu committed
62 63

  path = path[len(include):]
64
  return dest + path
Jisi Liu's avatar
Jisi Liu committed
65

Jisi Liu's avatar
Jisi Liu committed
66 67
def _proto_gen_impl(ctx):
  """General implementation for generating protos"""
68 69 70
  srcs = ctx.files.srcs
  deps = []
  deps += ctx.files.srcs
71
  source_dir = _SourceDir(ctx)
Jisi Liu's avatar
Jisi Liu committed
72
  gen_dir = _GenDir(ctx)
73 74
  if source_dir:
    import_flags = ["-I" + source_dir, "-I" + gen_dir]
75 76 77
  else:
    import_flags = ["-I."]

78 79 80 81 82 83
  for dep in ctx.attr.deps:
    import_flags += dep.proto.import_flags
    deps += dep.proto.deps

  args = []
  if ctx.attr.gen_cc:
84
    args += ["--cpp_out=" + gen_dir]
85
  if ctx.attr.gen_py:
86
    args += ["--python_out=" + gen_dir]
87

88
  inputs = srcs + deps
89 90 91 92 93 94 95 96
  if ctx.executable.plugin:
    plugin = ctx.executable.plugin
    lang = ctx.attr.plugin_language
    if not lang and plugin.basename.startswith('protoc-gen-'):
      lang = plugin.basename[len('protoc-gen-'):]
    if not lang:
      fail("cannot infer the target language of plugin", "plugin_language")

97
    outdir = gen_dir
98 99 100 101
    if ctx.attr.plugin_options:
      outdir = ",".join(ctx.attr.plugin_options) + ":" + outdir
    args += ["--plugin=protoc-gen-%s=%s" % (lang, plugin.path)]
    args += ["--%s_out=%s" % (lang, outdir)]
102
    inputs += [plugin]
103

104 105
  if args:
    ctx.action(
106
        inputs=inputs,
107
        outputs=ctx.outputs.outs,
Jisi Liu's avatar
Jisi Liu committed
108
        arguments=args + import_flags + [s.path for s in srcs],
Jisi Liu's avatar
Jisi Liu committed
109
        executable=ctx.executable.protoc,
110
        mnemonic="ProtoCompile",
111 112 113 114
    )

  return struct(
      proto=struct(
Jisi Liu's avatar
Jisi Liu committed
115 116 117 118 119
          srcs=srcs,
          import_flags=import_flags,
          deps=deps,
      ),
  )
120

121
proto_gen = rule(
122
    attrs = {
Jisi Liu's avatar
Jisi Liu committed
123 124
        "srcs": attr.label_list(allow_files = True),
        "deps": attr.label_list(providers = ["proto"]),
125
        "includes": attr.string_list(),
Jisi Liu's avatar
Jisi Liu committed
126
        "protoc": attr.label(
127
            cfg = "host",
Jisi Liu's avatar
Jisi Liu committed
128 129 130 131
            executable = True,
            single_file = True,
            mandatory = True,
        ),
132
        "plugin": attr.label(
133
            cfg = "host",
134
            allow_files = True,
135 136
            executable = True,
        ),
137 138
        "plugin_language": attr.string(),
        "plugin_options": attr.string_list(),
Jisi Liu's avatar
Jisi Liu committed
139 140 141 142 143
        "gen_cc": attr.bool(),
        "gen_py": attr.bool(),
        "outs": attr.output_list(),
    },
    output_to_genfiles = True,
Jisi Liu's avatar
Jisi Liu committed
144
    implementation = _proto_gen_impl,
145
)
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
"""Generates codes from Protocol Buffers definitions.

This rule helps you to implement Skylark macros specific to the target
language. You should prefer more specific `cc_proto_library `,
`py_proto_library` and others unless you are adding such wrapper macros.

Args:
  srcs: Protocol Buffers definition files (.proto) to run the protocol compiler
    against.
  deps: a list of dependency labels; must be other proto libraries.
  includes: a list of include paths to .proto files.
  protoc: the label of the protocol compiler to generate the sources.
  plugin: the label of the protocol compiler plugin to be passed to the protocol
    compiler.
  plugin_language: the language of the generated sources
  plugin_options: a list of options to be passed to the plugin
162
  gen_cc: generates C++ sources in addition to the ones from the plugin.
163 164 165
  gen_py: generates Python sources in addition to the ones from the plugin.
  outs: a list of labels of the expected outputs from the protocol compiler.
"""
166 167

def cc_proto_library(
Jisi Liu's avatar
Jisi Liu committed
168 169 170
        name,
        srcs=[],
        deps=[],
171
        cc_libs=[],
Jisi Liu's avatar
Jisi Liu committed
172
        include=None,
173
        protoc="//:protoc",
174
        internal_bootstrap_hack=False,
175
        use_grpc_plugin=False,
176
        default_runtime="//:protobuf",
Jisi Liu's avatar
Jisi Liu committed
177
        **kargs):
178 179
  """Bazel rule to create a C++ protobuf library from proto source files

180 181 182 183
  NOTE: the rule is only an internal workaround to generate protos. The
  interface may change and the rule may be removed when bazel has introduced
  the native rule.

184 185 186 187 188 189 190 191 192 193 194 195
  Args:
    name: the name of the cc_proto_library.
    srcs: the .proto files of the cc_proto_library.
    deps: a list of dependency labels; must be cc_proto_library.
    cc_libs: a list of other cc_library targets depended by the generated
        cc_library.
    include: a string indicating the include path of the .proto files.
    protoc: the label of the protocol compiler to generate the sources.
    internal_bootstrap_hack: a flag indicate the cc_proto_library is used only
        for bootstraping. When it is set to True, no files will be generated.
        The rule will simply be a provider for .proto files, so that other
        cc_proto_library can depend on it.
196 197
    use_grpc_plugin: a flag to indicate whether to call the grpc C++ plugin
        when processing the proto files.
198 199
    default_runtime: the implicitly default runtime which will be depended on by
        the generated cc_library target.
200 201 202
    **kargs: other keyword arguments that are passed to cc_library.

  """
203

204 205 206 207
  includes = []
  if include != None:
    includes = [include]

208 209 210
  if internal_bootstrap_hack:
    # For pre-checked-in generated files, we add the internal_bootstrap_hack
    # which will skip the codegen action.
211
    proto_gen(
Jisi Liu's avatar
Jisi Liu committed
212 213
        name=name + "_genproto",
        srcs=srcs,
214
        deps=[s + "_genproto" for s in deps],
215
        includes=includes,
Jisi Liu's avatar
Jisi Liu committed
216
        protoc=protoc,
217
        visibility=["//visibility:public"],
218 219 220
    )
    # An empty cc_library to make rule dependency consistent.
    native.cc_library(
Jisi Liu's avatar
Jisi Liu committed
221
        name=name,
222
        **kargs)
223 224
    return

225 226 227 228
  grpc_cpp_plugin = None
  if use_grpc_plugin:
    grpc_cpp_plugin = "//external:grpc_cpp_plugin"

229 230 231
  gen_srcs = _CcSrcs(srcs, use_grpc_plugin)
  gen_hdrs = _CcHdrs(srcs, use_grpc_plugin)
  outs = gen_srcs + gen_hdrs
232

233
  proto_gen(
Jisi Liu's avatar
Jisi Liu committed
234 235
      name=name + "_genproto",
      srcs=srcs,
236
      deps=[s + "_genproto" for s in deps],
237
      includes=includes,
Jisi Liu's avatar
Jisi Liu committed
238
      protoc=protoc,
239 240
      plugin=grpc_cpp_plugin,
      plugin_language="grpc",
Jisi Liu's avatar
Jisi Liu committed
241 242
      gen_cc=1,
      outs=outs,
243
      visibility=["//visibility:public"],
244 245
  )

246 247
  if default_runtime and not default_runtime in cc_libs:
    cc_libs += [default_runtime]
248 249
  if use_grpc_plugin:
    cc_libs += ["//external:grpc_lib"]
Jisi Liu's avatar
Jisi Liu committed
250

251
  native.cc_library(
Jisi Liu's avatar
Jisi Liu committed
252
      name=name,
253 254
      srcs=gen_srcs,
      hdrs=gen_hdrs,
255
      deps=cc_libs + deps,
Jisi Liu's avatar
Jisi Liu committed
256
      includes=includes,
257
      **kargs)
Jisi Liu's avatar
Jisi Liu committed
258

259 260 261 262 263 264 265
def internal_gen_well_known_protos_java(srcs):
  """Bazel rule to generate the gen_well_known_protos_java genrule

  Args:
    srcs: the well known protos
  """
  root = Label("%s//protobuf_java" % (REPOSITORY_NAME)).workspace_root
266
  pkg = PACKAGE_NAME + "/" if PACKAGE_NAME else ""
267
  if root == "":
268
    include = " -I%ssrc " % pkg
269
  else:
270
    include = " -I%s/%ssrc " % (root, pkg)
271 272 273 274 275 276 277 278 279 280 281 282
  native.genrule(
    name = "gen_well_known_protos_java",
    srcs = srcs,
    outs = [
        "wellknown.srcjar",
    ],
    cmd = "$(location :protoc) --java_out=$(@D)/wellknown.jar" +
          " %s $(SRCS) " % include +
          " && mv $(@D)/wellknown.jar $(@D)/wellknown.srcjar",
    tools = [":protoc"],
  )

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
def internal_copied_filegroup(name, srcs, strip_prefix, dest, **kwargs):
  """Macro to copy files to a different directory and then create a filegroup.

  This is used by the //:protobuf_python py_proto_library target to work around
  an issue caused by Python source files that are part of the same Python
  package being in separate directories.

  Args:
    srcs: The source files to copy and add to the filegroup.
    strip_prefix: Path to the root of the files to copy.
    dest: The directory to copy the source files into.
    **kwargs: extra arguments that will be passesd to the filegroup.
  """
  outs = [_RelativeOutputPath(s, strip_prefix, dest) for s in srcs]

  native.genrule(
      name = name + "_genrule",
      srcs = srcs,
      outs = outs,
      cmd = " && ".join(
          ["cp $(location %s) $(location %s)" %
           (s, _RelativeOutputPath(s, strip_prefix, dest)) for s in srcs]),
  )

  native.filegroup(
      name = name,
      srcs = outs,
      **kwargs)

Jisi Liu's avatar
Jisi Liu committed
312 313 314 315 316 317 318
def py_proto_library(
        name,
        srcs=[],
        deps=[],
        py_libs=[],
        py_extra_srcs=[],
        include=None,
319 320
        default_runtime="//:protobuf_python",
        protoc="//:protoc",
321
        use_grpc_plugin=False,
Jisi Liu's avatar
Jisi Liu committed
322
        **kargs):
Jisi Liu's avatar
Jisi Liu committed
323 324
  """Bazel rule to create a Python protobuf library from proto source files

325 326 327 328
  NOTE: the rule is only an internal workaround to generate protos. The
  interface may change and the rule may be removed when bazel has introduced
  the native rule.

Jisi Liu's avatar
Jisi Liu committed
329 330 331 332 333 334 335 336 337
  Args:
    name: the name of the py_proto_library.
    srcs: the .proto files of the py_proto_library.
    deps: a list of dependency labels; must be py_proto_library.
    py_libs: a list of other py_library targets depended by the generated
        py_library.
    py_extra_srcs: extra source files that will be added to the output
        py_library. This attribute is used for internal bootstrapping.
    include: a string indicating the include path of the .proto files.
338 339
    default_runtime: the implicitly default runtime which will be depended on by
        the generated py_library target.
Jisi Liu's avatar
Jisi Liu committed
340
    protoc: the label of the protocol compiler to generate the sources.
341 342
    use_grpc_plugin: a flag to indicate whether to call the Python C++ plugin
        when processing the proto files.
Jisi Liu's avatar
Jisi Liu committed
343 344 345
    **kargs: other keyword arguments that are passed to cc_library.

  """
Jisi Liu's avatar
Jisi Liu committed
346
  outs = _PyOuts(srcs)
347 348 349 350 351

  includes = []
  if include != None:
    includes = [include]

352 353 354 355 356 357 358
  grpc_python_plugin = None
  if use_grpc_plugin:
    grpc_python_plugin = "//external:grpc_python_plugin"
    # Note: Generated grpc code depends on Python grpc module. This dependency
    # is not explicitly listed in py_libs. Instead, host system is assumed to
    # have grpc installed.

359
  proto_gen(
Jisi Liu's avatar
Jisi Liu committed
360 361 362
      name=name + "_genproto",
      srcs=srcs,
      deps=[s + "_genproto" for s in deps],
363
      includes=includes,
Jisi Liu's avatar
Jisi Liu committed
364 365 366
      protoc=protoc,
      gen_py=1,
      outs=outs,
367
      visibility=["//visibility:public"],
368 369
      plugin=grpc_python_plugin,
      plugin_language="grpc"
Jisi Liu's avatar
Jisi Liu committed
370 371
  )

372 373 374
  if default_runtime and not default_runtime in py_libs + deps:
    py_libs += [default_runtime]

Jisi Liu's avatar
Jisi Liu committed
375 376
  native.py_library(
      name=name,
377 378
      srcs=outs+py_extra_srcs,
      deps=py_libs+deps,
379
      imports=includes,
Jisi Liu's avatar
Jisi Liu committed
380 381 382 383 384 385
      **kargs)

def internal_protobuf_py_tests(
    name,
    modules=[],
    **kargs):
Jisi Liu's avatar
Jisi Liu committed
386 387 388 389 390 391 392 393 394
  """Bazel rules to create batch tests for protobuf internal.

  Args:
    name: the name of the rule.
    modules: a list of modules for tests. The macro will create a py_test for
        each of the parameter with the source "google/protobuf/%s.py"
    kargs: extra parameters that will be passed into the py_test.

  """
Jisi Liu's avatar
Jisi Liu committed
395
  for m in modules:
396
    s = "python/google/protobuf/internal/%s.py" % m
Jisi Liu's avatar
Jisi Liu committed
397 398
    native.py_test(
        name="py_%s" % m,
Jisi Liu's avatar
Jisi Liu committed
399 400
        srcs=[s],
        main=s,
Jisi Liu's avatar
Jisi Liu committed
401
        **kargs)