Skip to content

Commit d18ed41

Browse files
authored
feat: Make GAPIC Bazel rules production ready (#402)
Shoud fix #400 and #390, plus a bunch of other not-yet-opened issues. This includes: 1) Fix long initial load time (5+ min). This was caused by python_rules buildling `grpcio` dependency from sources in one core (which was super slow). Switched to using bazel-native `"@com_github_grpc_grpc//src/python/grpcio/grpc:grpcio"` target instead, which is not only much faster, but is also already used in googleapis, so there is no additional cost for reusing it in microgenerator rules. 2) Properly handle `pandoc` dependency (platform-sepcific version of pandoc is properly pulled by bazel itself using toolchains). 3) Add simplistic version of the `py_gapic_assembly_pkg` rule, to make output of microgenerator compatible with `GAPICBazel` class in synthtool. 4) Add `plugin_args` argument for python_gapic_library rule to pass custom argumetns to the plugin (similar to PHP rules). 5) Add compatibility with `python3.6` runtime (otherwise `python3.7` is minimum because of dependency on `dataclasses` module). Python 3.6 compatibility can be enabled by adding `--define=gapic_gen_python=3.6` command line argument to `bazel build` command. 6) Add support for Python runtimes installed with `pyenv`. To tell bazel using Python3 installed via pyenv add `--extra_toolchains=@gapic_generator_python//:pyenv3_toolchain` argument to `bazel build` command.
1 parent e716cb3 commit d18ed41

File tree

9 files changed

+276
-12
lines changed

9 files changed

+276
-12
lines changed

BUILD.bazel

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,73 @@
1+
load("//:gapic_generator_python.bzl", "pandoc_binary", "pandoc_toolchain")
12
load("@gapic_generator_python_pip_deps//:requirements.bzl", "requirement")
3+
load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair")
4+
5+
toolchain_type(
6+
name = "pandoc_toolchain_type",
7+
visibility = ["//visibility:public"],
8+
)
9+
10+
pandoc_toolchain(
11+
exec_compatible_with = [
12+
"@bazel_tools//platforms:linux",
13+
"@bazel_tools//platforms:x86_64",
14+
],
15+
platform = "linux",
16+
)
17+
18+
pandoc_toolchain(
19+
exec_compatible_with = [
20+
"@bazel_tools//platforms:osx",
21+
"@bazel_tools//platforms:x86_64",
22+
],
23+
platform = "macOS",
24+
)
25+
26+
pandoc_binary(
27+
name = "pandoc_binary",
28+
)
29+
30+
config_setting(
31+
name = "gapic_gen_python_3_6",
32+
values = {"define": "gapic_gen_python=3.6"},
33+
)
34+
35+
py_runtime(
36+
name = "pyenv3_runtime",
37+
interpreter = ":pyenv3wrapper.sh",
38+
python_version="PY3",
39+
)
40+
41+
py_runtime_pair(
42+
name = "pyenv3_runtime_pair",
43+
py3_runtime = ":pyenv3_runtime",
44+
)
45+
46+
toolchain(
47+
name = "pyenv3_toolchain",
48+
toolchain = ":pyenv3_runtime_pair",
49+
toolchain_type = "@bazel_tools//tools/python:toolchain_type",
50+
)
251

352
py_binary(
453
name = "gapic_plugin",
554
srcs = glob(["gapic/**/*.py"]),
6-
data = glob(["gapic/**/*.j2"]),
7-
main = "gapic/cli/generate.py",
55+
data = [":pandoc_binary"] + glob(["gapic/**/*.j2"]),
56+
main = "gapic/cli/generate_with_pandoc.py",
57+
python_version = "PY3",
858
visibility = ["//visibility:public"],
959
deps = [
1060
"@com_google_protobuf//:protobuf_python",
61+
"@com_github_grpc_grpc//src/python/grpcio/grpc:grpcio",
1162
requirement("click"),
1263
requirement("google-api-core"),
1364
requirement("googleapis-common-protos"),
14-
requirement("grpcio"),
1565
requirement("jinja2"),
1666
requirement("MarkupSafe"),
1767
requirement("pypandoc"),
1868
requirement("PyYAML"),
19-
],
20-
python_version = "PY3",
69+
] + select({
70+
":gapic_gen_python_3_6": [requirement("dataclasses")],
71+
"//conditions:default": [],
72+
}),
2173
)

WORKSPACE

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ workspace(name = "gapic_generator_python")
22

33
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
44

5-
#
6-
# Import rules_python
7-
#
5+
http_archive(
6+
name = "bazel_skylib",
7+
urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/0.9.0/bazel_skylib-0.9.0.tar.gz"],
8+
)
9+
810
http_archive(
911
name = "rules_python",
1012
strip_prefix = "rules_python-748aa53d7701e71101dfd15d800e100f6ff8e5d1",
@@ -22,14 +24,43 @@ pip_repositories()
2224
#
2325
# Import gapic-generator-python specific dependencies
2426
#
25-
load("//:repositories.bzl", "gapic_generator_python")
27+
load("//:repositories.bzl",
28+
"gapic_generator_python",
29+
"gapic_generator_register_toolchains"
30+
)
2631

2732
gapic_generator_python()
2833

34+
gapic_generator_register_toolchains()
35+
2936
load("@gapic_generator_python_pip_deps//:requirements.bzl", "pip_install")
3037

3138
pip_install()
3239

3340
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
3441

3542
protobuf_deps()
43+
44+
#
45+
# Import grpc as a native bazel dependency. This avoids duplication and also
46+
# speeds up loading phase a lot (otherwise python_rules will be building grpcio
47+
# from sources in a single-core speed, which takes around 5 minutes on a regular
48+
# workstation)
49+
#
50+
load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
51+
52+
grpc_deps()
53+
54+
load("@upb//bazel:repository_defs.bzl", "bazel_version_repository")
55+
56+
bazel_version_repository(
57+
name = "bazel_version",
58+
)
59+
60+
load("@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies")
61+
62+
apple_rules_dependencies()
63+
64+
load("@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependencies")
65+
66+
apple_support_dependencies()

gapic/cli/generate_with_pandoc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import os
2+
3+
from gapic.cli import generate
4+
5+
if __name__ == '__main__':
6+
os.environ['PYPANDOC_PANDOC'] = os.path.join(
7+
os.path.abspath(__file__).rsplit("gapic", 1)[0], "pandoc")
8+
os.environ['LC_ALL'] = 'C.UTF-8'
9+
generate.generate()

gapic_generator_python.bzl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
def _pandoc_binary_impl(ctx):
2+
toolchain = ctx.toolchains["@gapic_generator_python//:pandoc_toolchain_type"]
3+
output = ctx.actions.declare_file(ctx.attr.binary_name)
4+
5+
script = """
6+
cp {input} {output}
7+
chmod +x {output}
8+
""".format(
9+
input = toolchain.pandoc.files.to_list()[0].path,
10+
output = output.path,
11+
)
12+
ctx.actions.run_shell(
13+
command = script,
14+
inputs = toolchain.pandoc.files,
15+
outputs = [output],
16+
)
17+
return [DefaultInfo(files = depset(direct = [output]), executable = output)]
18+
19+
pandoc_binary = rule(
20+
attrs = {
21+
"binary_name": attr.string(default = "pandoc")
22+
},
23+
executable = True,
24+
toolchains = ["@gapic_generator_python//:pandoc_toolchain_type"],
25+
implementation = _pandoc_binary_impl,
26+
)
27+
28+
#
29+
# Toolchains
30+
#
31+
def _pandoc_toolchain_info_impl(ctx):
32+
return [
33+
platform_common.ToolchainInfo(
34+
pandoc = ctx.attr.pandoc,
35+
),
36+
]
37+
38+
_pandoc_toolchain_info = rule(
39+
attrs = {
40+
"pandoc": attr.label(
41+
allow_single_file = True,
42+
cfg = "host",
43+
executable = True,
44+
),
45+
},
46+
implementation = _pandoc_toolchain_info_impl,
47+
)
48+
49+
def pandoc_toolchain(platform, exec_compatible_with):
50+
toolchain_info_name = "pandoc_toolchain_info_%s" % platform
51+
_pandoc_toolchain_info(
52+
name = toolchain_info_name,
53+
pandoc = "@pandoc_%s//:pandoc" % platform,
54+
visibility = ["//visibility:public"],
55+
)
56+
57+
native.toolchain(
58+
name = "pandoc_toolchain_%s" % platform,
59+
exec_compatible_with = exec_compatible_with,
60+
toolchain = toolchain_info_name,
61+
toolchain_type = ":pandoc_toolchain_type",
62+
)

pyenv3wrapper.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
3+
HOME_DIR=$(getent passwd "$(whoami)" | cut -d: -f6)
4+
exec "$HOME_DIR/.pyenv/shims/python3" "$@"

repositories.bzl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
22
load("@rules_python//python:pip.bzl", "pip_import")
33

4+
_PANDOC_BUILD_FILE = """
5+
filegroup(
6+
name = "pandoc",
7+
srcs = ["bin/pandoc"],
8+
visibility = ["//visibility:public"],
9+
)"""
10+
411
def gapic_generator_python():
512
_maybe(
613
pip_import,
@@ -25,13 +32,42 @@ def gapic_generator_python():
2532
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/2169ae1c374aab4a09aa90e65efe1a3aad4e279b.tar.gz"],
2633
)
2734

35+
_maybe(
36+
http_archive,
37+
name = "com_github_grpc_grpc",
38+
strip_prefix = "grpc-8347f4753568b5b66e49111c60ae2841278d3f33", # this is 1.25.0 with fixes
39+
urls = ["https://github.com/grpc/grpc/archive/8347f4753568b5b66e49111c60ae2841278d3f33.zip"],
40+
)
41+
42+
_maybe(
43+
http_archive,
44+
name = "pandoc_linux",
45+
build_file_content = _PANDOC_BUILD_FILE,
46+
strip_prefix = "pandoc-2.2.1",
47+
url = "https://github.com/jgm/pandoc/releases/download/2.2.1/pandoc-2.2.1-linux.tar.gz",
48+
)
49+
50+
_maybe(
51+
http_archive,
52+
name = "pandoc_macOS",
53+
build_file_content = _PANDOC_BUILD_FILE,
54+
strip_prefix = "pandoc-2.2.1",
55+
url = "https://github.com/jgm/pandoc/releases/download/2.2.1/pandoc-2.2.1-macOS.zip",
56+
)
57+
2858
_maybe(
2959
http_archive,
3060
name = "com_google_api_codegen",
3161
strip_prefix = "gapic-generator-b32c73219d617f90de70bfa6ff0ea0b0dd638dfe",
3262
urls = ["https://github.com/googleapis/gapic-generator/archive/b32c73219d617f90de70bfa6ff0ea0b0dd638dfe.zip"],
3363
)
3464

65+
def gapic_generator_register_toolchains():
66+
native.register_toolchains(
67+
"@gapic_generator_python//:pandoc_toolchain_linux",
68+
"@gapic_generator_python//:pandoc_toolchain_macOS",
69+
)
70+
3571
def _maybe(repo_rule, name, strip_repo_prefix = "", **kwargs):
3672
if not name.startswith(strip_repo_prefix):
3773
return

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
click==7.1.2
22
google-api-core==1.17.0
33
googleapis-common-protos==1.51.0
4-
grpcio==1.28.1
54
jinja2==2.11.2
65
MarkupSafe==1.1.1
76
protobuf==3.11.3
87
pypandoc==1.5
98
PyYAML==5.3.1
9+
dataclasses==0.6

rules_python_gapic/py_gapic.bzl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
load("@com_google_api_codegen//rules_gapic:gapic.bzl", "proto_custom_library")
1616

17-
def py_gapic_library(name, srcs, **kwargs):
17+
def py_gapic_library(name, srcs, plugin_args = [], **kwargs):
1818
# srcjar_target_name = "%s_srcjar" % name
1919
srcjar_target_name = name
2020
srcjar_output_suffix = ".srcjar"
@@ -23,7 +23,7 @@ def py_gapic_library(name, srcs, **kwargs):
2323
name = srcjar_target_name,
2424
deps = srcs,
2525
plugin = Label("@gapic_generator_python//:gapic_plugin"),
26-
plugin_args = [],
26+
plugin_args = plugin_args,
2727
plugin_file_args = {},
2828
output_type = "python_gapic",
2929
output_suffix = srcjar_output_suffix,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright 2020 Google LLC
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+
# https://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+
load("@com_google_api_codegen//rules_gapic:gapic_pkg.bzl", "construct_package_dir_paths")
16+
17+
def _py_gapic_src_pkg_impl(ctx):
18+
srcjar_srcs = []
19+
for dep in ctx.attr.deps:
20+
for f in dep.files.to_list():
21+
if f.extension in ("srcjar", "jar", "zip"):
22+
srcjar_srcs.append(f)
23+
24+
paths = construct_package_dir_paths(ctx.attr.package_dir, ctx.outputs.pkg, ctx.label.name)
25+
26+
script = """
27+
mkdir -p {package_dir_path}
28+
for srcjar_src in {srcjar_srcs}; do
29+
unzip -q -o $srcjar_src -d {package_dir_path}
30+
done
31+
cd {package_dir_path}/..
32+
tar -zchpf {package_dir}/{package_dir}.tar.gz {package_dir}
33+
cd -
34+
mv {package_dir_path}/{package_dir}.tar.gz {pkg}
35+
rm -rf {package_dir_path}
36+
""".format(
37+
srcjar_srcs = " ".join(["'%s'" % f.path for f in srcjar_srcs]),
38+
package_dir_path = paths.package_dir_path,
39+
package_dir = paths.package_dir,
40+
pkg = ctx.outputs.pkg.path,
41+
package_dir_expr = paths.package_dir_expr,
42+
)
43+
44+
ctx.actions.run_shell(
45+
inputs = srcjar_srcs,
46+
command = script,
47+
outputs = [ctx.outputs.pkg],
48+
)
49+
50+
_py_gapic_src_pkg = rule(
51+
attrs = {
52+
"deps": attr.label_list(allow_files = True, mandatory = True),
53+
"package_dir": attr.string(mandatory = True),
54+
},
55+
outputs = {"pkg": "%{name}.tar.gz"},
56+
implementation = _py_gapic_src_pkg_impl,
57+
)
58+
59+
def py_gapic_assembly_pkg(name, deps, assembly_name = None, **kwargs):
60+
package_dir = name
61+
if assembly_name:
62+
package_dir = "%s-%s" % (assembly_name, name)
63+
_py_gapic_src_pkg(
64+
name = name,
65+
deps = deps,
66+
package_dir = package_dir,
67+
**kwargs
68+
)
69+
70+

0 commit comments

Comments
 (0)