Bazel结合了宏

时间:2018-05-09 13:52:54

标签: bazel

我的项目结构中有多个子模块,这些子模块本质上也是项目本身。

对于给定的python项目,每个项目都包含子项目。我希望能够编写一个宏,它可以创建一组要在给定子项目上执行的操作。

基本上是WORKSPACE中的这些内容:

load("@io_bazel_rules_python//:python/pip.bzl",
   "pip_import"
)

pip_import(
   name = "foo",
   requirements = "@subprojectname//:requirements.txt",
)

load("@foo//:requirements.bzl", "pip_install")
pip_install()

我想为上述行动做多个子项目。但是,如果我将所有代码放入bzl文件中的函数中。

pyrules.bzl

load("@io_bazel_rules_python//:python/pip.bzl",
   "pip_import"
)


def doit(name):
    pip_import(
       name = "foo",
       requirements = "@{repo}//:requirements.txt".format(repo=name)
    )

    load("@foo//:requirements.bzl", "pip_install")
    pip_install()

由于"加载':期望表达式"的语法错误,我无法使用第二个加载命令。如果没有多个加载到父WORKSPACE的bzl文件,还有其他方法可以创建可重用的逻辑块吗?

更新1。 评论要求有关工​​作流程的更多信息。警告,我有一个非标准的布局,如何使用Bazel其他地方,即不是monorepo。但它应该采用类似的实用布局。

项目布局如下:

projectname/
    WORKSPACE
    BUILD
    src/
       stuff
    submodule/ # < git modules that are checked out into folder
        subproject1/
            BUILD
            src/
                stuff
        subproject2/
            BUILD
            src/ 
                stuff

首先,我有一个在WORKSPACE中加载的repository_rule,它找到子模块文件夹中的所有项目并将它们添加为存储库。在子项目中,其中一些是python。我想加载其requirements.txt文件。因此,总体问题是这些requirements.txt文件在哪里,并在它们上执行pip安装。理想情况下,我希望子项目BUILD文件中的py_library定义知道子项目的需求文件存在依赖性,但这可能并不重要,因为父BUILD文件是唯一创建par_binaries的东西,所以on,所以只要pip_install和设置依赖项发生,项目本身就应该可用。

Bazel似乎不允许子项目定义他们自己的存储库操作,如上面的pip_install。我假设这是因为您无法在BUILD文件中拥有存储库操作,并且子WORKSPACE文件似乎没有任何效果。所以我最终不得不将其添加到父WORKSPACE文件中。

如果它在父WORKSPACE中,我必须复制粘贴我想要使用的每个子项目的pip操作。但是,我更愿意设置一个通用规则来定位需求文件并将其安装到pip中。但是,尝试从此创建宏意味着我无法在其中使用加载调用。所有pip操作似乎都需要与存储库操作进行交互,然后需要仅从父WORKSPACE文件调用这些操作。

1 个答案:

答案 0 :(得分:0)

对于此方案,看起来不存在任何合理的解决方案。但是,我认为如果您愿意在内部支持它们,那么实现您所依赖的规则是很重要的。我最终修改了rules_python存储库中的piptool.py,以便在一次调用中添加对多个requirements.txt文件的支持。然后将以下规则添加到我在内部使用的规则库中。

#
# Python
#

def _pip_import_requirements_impl(repository_ctx):
  """Core implementation of pip_import."""
  # Add an empty top-level BUILD file.
  # This is because Bazel requires BUILD files along all paths accessed
  # via //this/sort/of:path and we wouldn't be able to load our generated
  # requirements.bzl without it.
  repository_ctx.file("BUILD", "")

  # To see the output, pass: quiet=False
  result = repository_ctx.execute([
    "python3", repository_ctx.path(repository_ctx.attr._script),
    "--name", repository_ctx.attr.name,
    "--input"
    ] +
    [repository_ctx.path(r) for r in repository_ctx.attr.requirements] +
    [
      "--output", repository_ctx.path("requirements.bzl"),
      "--directory", repository_ctx.path("")
    ],
  quiet = repository_ctx.attr.quiet)

  if result.return_code:
    fail("pip_import failed: %s (%s)" % (result.stdout, result.stderr))


pip_import_requirements = repository_rule(
  attrs = {
    "quiet" : attr.bool(default = False),
    "requirements": attr.label_list(
       allow_files = True,
       mandatory = True,
     ),
     "_script": attr.label(
       executable = True,
       default = Label("@io_bazel_rules_python//tools:piptool.par"),
       cfg = "host",
     ),
  },
  implementation = _pip_import_requirements_impl,
)

然后我可以在我的WORKSPACE中执行以下操作。

pip_import_requirements(
  name = "py_requirements",
  requirements = [
    "@mycore//:requirements.txt",
    "@myother//:requirements.txt"
  ]
)

load(
    "@py_requirements//:requirements.bzl",
    "pip_install",
)

pip_install()

以下在我碰巧需要的任何BUILD文件中。请注意,py_requirements引用随后将始终可用于项目中的任何BUILD文件。

load(
    "@py_requirements//:requirements.bzl",
    "all_requirements"
)

par_binary(
    name = "funkyserver",
    main = "src/main.py",
    srcs = glob(["src/**/*.py"]),
    deps = [
        "@mycore//:core",
        "@myother//:other"
    ] + all_requirements,
)

对于piptool.py。您需要使用rules_python repo中的update_tools.sh重建piptool.par。

diff --git a/rules_python/piptool.py b/rules_python/piptool.py
index f5d504a..ab520d8 100644
--- a/rules_python/piptool.py
+++ b/rules_python/piptool.py
@@ -87,7 +87,7 @@ def pip_main(argv):
 parser.add_argument('--name', action='store',
                     help=('The namespace of the import.'))

-parser.add_argument('--input', action='store',
+parser.add_argument('--input', action='store', nargs='+',
                     help=('The requirements.txt file to import.'))

 parser.add_argument('--output', action='store',
@@ -154,7 +154,8 @@ def main():
   args = parser.parse_args()

   # https://github.com/pypa/pip/blob/9.0.1/pip/__init__.py#L209
-  if pip_main(["wheel", "-w", args.directory, "-r", args.input]):
+  if pip_main(["wheel", "-w", args.directory] + [p for x in [
+      ('-r',  i) for i in args.input] for p in x]):
     sys.exit(1)

   # Enumerate the .whl files we downloaded.
@@ -219,7 +220,7 @@ def requirement(name):
   if name_key not in _requirements:
     fail("Could not find pip-provided dependency: '%s'" % name)
   return _requirements[name_key]
-""".format(input=args.input,
+""".format(input=" ".join(args.input),
            whl_libraries='\n'.join(map(whl_library, whls)) if whls else "pass",
            mappings=whl_targets))