aboutsummaryrefslogblamecommitdiff
path: root/protobuf.bzl
blob: ae391aa643146cf0898e1795240dbcbe7583b930 (plain) (tree)
1
2
3
4
5
6
7
8

                                           





                                                






                                                             
                 









                                                                                         
                           
                                   
                              
                                           
                           

                                                                      
 







                                                            
                     
                                                             
            
 


                                                                        




                                                              
 
                                                







                                                                        

                              

                            
                    
 

                                                    


                        
                              
                                    

                                                      


                          



                                          








                                                                               
                  

             
                                         





                                                              
             

                                                                              
                       
                                                   
                                                                           
                       
                                                      
                                                                           
 
                                                                       








                                                                             
                                             


                                                                 
                                                                            


                                              










                                                     







                                                                    
          


                                                              






                                                    


                   




                                    
 
                 
             

                                                       
                                       
                             
                         
                              
                                     

                             
                             
                         
                               
                              
          

                                             




                                   
                                     
 















                                                                                
                                                                        


                                                                            

                     

                
                
                   
                     
                                               
                                      
                              
                                                          
                 

                                                                        



                                                                            











                                                                              

                                                                           

                                                                                


                                                                   
 



                        


                                                                            
              

                                
                                             
                          
                      
                                           


                                                             
                  
                

          



                                                  


                                           
 
            

                              
                                           
                        
                    

                             

                
                                         

   
                                                        
                                         
                     
                                               
 
                    
                

                    
                          
                        
              
 





                                                                  

                                                                               
                
                               
       
                                          











                                                                




























                                                                               






                         

                                                                 
                              
                 

                                                                           



                                                                            








                                                                         

                                                                                
                                                                       

                                                                             


                                                                   
                                       




                        






                                                                              
            


                                           
                        


                    
                                         

                                

   
                                                               
                                         
 

                    

                              
                       





                               








                                                                             
                   
                                                   

                         

                 
                







                                                                            
                                                 
load("@bazel_skylib//:lib.bzl", "versions")

def _GetPath(ctx, path):
  if ctx.label.workspace_root:
    return ctx.label.workspace_root + '/' + path
  else:
    return path

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("../")

def _GenDir(ctx):
  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):
  if not ctx.attr.includes:
    return ctx.label.workspace_root
  if not ctx.attr.includes[0]:
    return _GetPath(ctx, ctx.label.package)
  if not ctx.label.package:
    return _GetPath(ctx, ctx.attr.includes[0])
  return _GetPath(ctx, ctx.label.package + '/' + ctx.attr.includes[0])

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]
  if use_grpc_plugin:
    ret += [s[:-len(".proto")] + ".grpc.pb.cc" for s in srcs]
  return ret

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

def _PyOuts(srcs, use_grpc_plugin=False):
  ret = [s[:-len(".proto")] + "_pb2.py" for s in srcs]
  if use_grpc_plugin:
    ret += [s[:-len(".proto")] + "_pb2_grpc.py" for s in srcs]
  return ret

def _RelativeOutputPath(path, include, dest=""):
  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 + '/'
  if dest and dest[-1] != '/':
    dest = dest + '/'

  path = path[len(include):]
  return dest + path

def _proto_gen_impl(ctx):
  """General implementation for generating protos"""
  srcs = ctx.files.srcs
  deps = []
  deps += ctx.files.srcs
  source_dir = _SourceDir(ctx)
  gen_dir = _GenDir(ctx).rstrip('/')
  if source_dir:
    import_flags = ["-I" + source_dir, "-I" + gen_dir]
  else:
    import_flags = ["-I."]

  for dep in ctx.attr.deps:
    import_flags += dep.proto.import_flags
    deps += dep.proto.deps

  if not ctx.attr.gen_cc and not ctx.attr.gen_py and not ctx.executable.plugin:
    return struct(
        proto=struct(
            srcs=srcs,
            import_flags=import_flags,
            deps=deps,
        ),  
    )   

  for src in srcs:
    args = []

    in_gen_dir = src.root.path == gen_dir
    if in_gen_dir:
      import_flags_real = []
      for f in depset(import_flags):
        path = f.replace('-I', '') 
        import_flags_real.append('-I$(realpath -s %s)' % path)

    outs = []
    use_grpc_plugin = (ctx.attr.plugin_language == "grpc" and ctx.attr.plugin)
    path_tpl = "$(realpath %s)" if in_gen_dir else "%s"
    if ctx.attr.gen_cc:
      args += [("--cpp_out=" + path_tpl) % gen_dir]
      outs.extend(_CcOuts([src.basename], use_grpc_plugin=use_grpc_plugin))
    if ctx.attr.gen_py:
      args += [("--python_out=" + path_tpl) % gen_dir]
      outs.extend(_PyOuts([src.basename], use_grpc_plugin=use_grpc_plugin))

    outs = [ctx.actions.declare_file(out, sibling=src) for out in outs]
    inputs = [src] + deps
    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")

      outdir = "." if in_gen_dir else gen_dir

      if ctx.attr.plugin_options:
        outdir = ",".join(ctx.attr.plugin_options) + ":" + outdir
      args += [("--plugin=protoc-gen-%s=" + path_tpl) % (lang, plugin.path)]
      args += ["--%s_out=%s" % (lang, outdir)]
      inputs += [plugin]

    if not in_gen_dir:
      ctx.action(
          inputs=inputs,
          outputs=outs,
          arguments=args + import_flags + [src.path],
          executable=ctx.executable.protoc,
          mnemonic="ProtoCompile",
          use_default_shell_env=True,
      )
    else:
      for out in outs:
        orig_command = " ".join(
            ["$(realpath %s)" % ctx.executable.protoc.path] + args +
            import_flags_real + ["-I.", src.basename])
        command = ";".join([
            'CMD="%s"' % orig_command,
            "cd %s" % src.dirname,
            "${CMD}",
            "cd -",
        ])
        generated_out = '/'.join([gen_dir,  out.basename])
        if generated_out != out.path:
            command += ";mv %s %s" % (generated_out, out.path)
        ctx.action(
            inputs=inputs + [ctx.executable.protoc],
            outputs=[out],
            command=command,
            mnemonic="ProtoCompile",
            use_default_shell_env=True,
        )

  return struct(
      proto=struct(
          srcs=srcs,
          import_flags=import_flags,
          deps=deps,
      ),
  )

proto_gen = rule(
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        "deps": attr.label_list(providers = ["proto"]),
        "includes": attr.string_list(),
        "protoc": attr.label(
            cfg = "host",
            executable = True,
            allow_single_file = True,
            mandatory = True,
        ),
        "plugin": attr.label(
            cfg = "host",
            allow_files = True,
            executable = True,
        ),
        "plugin_language": attr.string(),
        "plugin_options": attr.string_list(),
        "gen_cc": attr.bool(),
        "gen_py": attr.bool(),
        "outs": attr.output_list(),
    },
    output_to_genfiles = True,
    implementation = _proto_gen_impl,
)
"""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
  gen_cc: generates C++ sources in addition to the ones from the plugin.
  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.
"""

def cc_proto_library(
        name,
        srcs=[],
        deps=[],
        cc_libs=[],
        include=None,
        protoc="@com_google_protobuf//:protoc",
        internal_bootstrap_hack=False,
        use_grpc_plugin=False,
        default_runtime="@com_google_protobuf//:protobuf",
        **kargs):
  """Bazel rule to create a C++ protobuf library from proto source files

  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.

  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.
    use_grpc_plugin: a flag to indicate whether to call the grpc C++ plugin
        when processing the proto files.
    default_runtime: the implicitly default runtime which will be depended on by
        the generated cc_library target.
    **kargs: other keyword arguments that are passed to cc_library.

  """

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

  if internal_bootstrap_hack:
    # For pre-checked-in generated files, we add the internal_bootstrap_hack
    # which will skip the codegen action.
    proto_gen(
        name=name + "_genproto",
        srcs=srcs,
        deps=[s + "_genproto" for s in deps],
        includes=includes,
        protoc=protoc,
        visibility=["//visibility:public"],
    )
    # An empty cc_library to make rule dependency consistent.
    native.cc_library(
        name=name,
        **kargs)
    return

  grpc_cpp_plugin = None
  if use_grpc_plugin:
    grpc_cpp_plugin = "//external:grpc_cpp_plugin"

  gen_srcs = _CcSrcs(srcs, use_grpc_plugin)
  gen_hdrs = _CcHdrs(srcs, use_grpc_plugin)
  outs = gen_srcs + gen_hdrs

  proto_gen(
      name=name + "_genproto",
      srcs=srcs,
      deps=[s + "_genproto" for s in deps],
      includes=includes,
      protoc=protoc,
      plugin=grpc_cpp_plugin,
      plugin_language="grpc",
      gen_cc=1,
      outs=outs,
      visibility=["//visibility:public"],
  )

  if default_runtime and not default_runtime in cc_libs:
    cc_libs = cc_libs + [default_runtime]
  if use_grpc_plugin:
    cc_libs = cc_libs + ["//external:grpc_lib"]

  native.cc_library(
      name=name,
      srcs=gen_srcs,
      hdrs=gen_hdrs,
      deps=cc_libs + deps,
      includes=includes,
      **kargs)

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" % (native.repository_name())).workspace_root
  pkg = native.package_name() + "/" if native.package_name() else ""
  if root == "":
    include = " -I%ssrc " % pkg
  else:
    include = " -I%s/%ssrc " % (root, pkg)
  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"],
  )

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)

def py_proto_library(
        name,
        srcs=[],
        deps=[],
        py_libs=[],
        py_extra_srcs=[],
        include=None,
        default_runtime="@com_google_protobuf//:protobuf_python",
        protoc="@com_google_protobuf//:protoc",
        use_grpc_plugin=False,
        **kargs):
  """Bazel rule to create a Python protobuf library from proto source files

  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.

  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.
    default_runtime: the implicitly default runtime which will be depended on by
        the generated py_library target.
    protoc: the label of the protocol compiler to generate the sources.
    use_grpc_plugin: a flag to indicate whether to call the Python C++ plugin
        when processing the proto files.
    **kargs: other keyword arguments that are passed to cc_library.

  """
  outs = _PyOuts(srcs, use_grpc_plugin)

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

  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.

  proto_gen(
      name=name + "_genproto",
      srcs=srcs,
      deps=[s + "_genproto" for s in deps],
      includes=includes,
      protoc=protoc,
      gen_py=1,
      outs=outs,
      visibility=["//visibility:public"],
      plugin=grpc_python_plugin,
      plugin_language="grpc"
  )

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

  native.py_library(
      name=name,
      srcs=outs+py_extra_srcs,
      deps=py_libs+deps,
      imports=includes,
      **kargs)

def internal_protobuf_py_tests(
    name,
    modules=[],
    **kargs):
  """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.

  """
  for m in modules:
    s = "python/google/protobuf/internal/%s.py" % m
    native.py_test(
        name="py_%s" % m,
        srcs=[s],
        main=s,
        **kargs)


def check_protobuf_required_bazel_version():
  """For WORKSPACE files, to check the installed version of bazel.

  This ensures bazel supports our approach to proto_library() depending on a
  copied filegroup. (Fixed in bazel 0.5.4)
  """
  versions.check(minimum_bazel_version = "0.5.4")