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
        use_default_shell_env=True,
112 113 114 115
    )

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

122
proto_gen = rule(
123
    attrs = {
Jisi Liu's avatar
Jisi Liu committed
124 125
        "srcs": attr.label_list(allow_files = True),
        "deps": attr.label_list(providers = ["proto"]),
126
        "includes": attr.string_list(),
Jisi Liu's avatar
Jisi Liu committed
127
        "protoc": attr.label(
128
            cfg = "host",
Jisi Liu's avatar
Jisi Liu committed
129 130 131 132
            executable = True,
            single_file = True,
            mandatory = True,
        ),
133
        "plugin": attr.label(
134
            cfg = "host",
135
            allow_files = True,
136 137
            executable = True,
        ),
138 139
        "plugin_language": attr.string(),
        "plugin_options": attr.string_list(),
Jisi Liu's avatar
Jisi Liu committed
140 141 142 143 144
        "gen_cc": attr.bool(),
        "gen_py": attr.bool(),
        "outs": attr.output_list(),
    },
    output_to_genfiles = True,
Jisi Liu's avatar
Jisi Liu committed
145
    implementation = _proto_gen_impl,
146
)
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
"""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
163
  gen_cc: generates C++ sources in addition to the ones from the plugin.
164 165 166
  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.
"""
167 168

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

181 182 183 184
  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.

185 186 187 188 189 190 191 192 193 194 195 196
  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.
197 198
    use_grpc_plugin: a flag to indicate whether to call the grpc C++ plugin
        when processing the proto files.
199 200
    default_runtime: the implicitly default runtime which will be depended on by
        the generated cc_library target.
201 202 203
    **kargs: other keyword arguments that are passed to cc_library.

  """
204

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

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

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

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

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

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

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

260 261 262 263 264 265 266
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
267
  pkg = PACKAGE_NAME + "/" if PACKAGE_NAME else ""
268
  if root == "":
269
    include = " -I%ssrc " % pkg
270
  else:
271
    include = " -I%s/%ssrc " % (root, pkg)
272 273 274 275 276 277 278 279 280 281 282 283
  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"],
  )

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

326 327 328 329
  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
330 331 332 333 334 335 336 337 338
  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.
339 340
    default_runtime: the implicitly default runtime which will be depended on by
        the generated py_library target.
Jisi Liu's avatar
Jisi Liu committed
341
    protoc: the label of the protocol compiler to generate the sources.
342 343
    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
344 345 346
    **kargs: other keyword arguments that are passed to cc_library.

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

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

353 354 355 356 357 358 359
  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.

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

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

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

def internal_protobuf_py_tests(
    name,
    modules=[],
    **kargs):
Jisi Liu's avatar
Jisi Liu committed
387 388 389 390 391 392 393 394 395
  """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
396
  for m in modules:
397
    s = "python/google/protobuf/internal/%s.py" % m
Jisi Liu's avatar
Jisi Liu committed
398 399
    native.py_test(
        name="py_%s" % m,
Jisi Liu's avatar
Jisi Liu committed
400 401
        srcs=[s],
        main=s,
Jisi Liu's avatar
Jisi Liu committed
402
        **kargs)