본문 바로가기

building system/bazel

Depsets 이해하기

반응형

Depsets 개요

Depsets는 target의 transitive dependency를 효율적으로 수집할 수 있도록 설계한 데이터 구조를 갖고 있고, rule 구현 과정에서 필수적으로 사용된다.

Depsets 생성자

depset( direct = [], transitive = [], order = default)

depset 생성자의 direct 항목과 transitive 항목은 둘다 list 정보를 입력받아, direct 집합과 모든 transitive 집합의 합집합으로 dependency 요소를 갖고 있는 depset 객체를 반환한다. depset 생성자의 order 항목으로 "postorder", "preorder", "topological" 중 하나를 선택할 수 있고, dependency 요소의 정렬 방식을 정의하고, 한번 방문한 depset 노드를 다시 방문하지 않는다.

depset는 빌드 시스템 구축 과정의 전반에 걸쳐 사용된다. 예를 들어, library 결과인 object file 목록을 depset 객체로 저장하는 provider를 정의하고, provider로부터 depset 정보를 받아, linker action에서 활용할 수 있다. 또 다른 예로, interpreted 언어에서는, 실행 가능 target의 runfilesdepset 객체를 전달해, 실행 시점에 depset 정보를 활용해 transitive 소스 파일을 구성할 수 있다.

  s1 = depset(["a", "b", "c"])
  s2 = depset(["d", "e"])
  t1 = depset(["f", "g"], transitive = [s2, s1], order = "postorder")
  t2 = depset(["f", "g"], transitive = [s2, s1], order = "preorder")
  t3 = depset(["f", "g"], transitive = [s2, s1], order = "topological")

  print(t1)                       # depset(["d", "e", "a", "b", "c", "f", "g"])
  print(t2)                       # depset(["f", "g", "d", "e", "a", "b", "c"], order = "preorder")
  print(t3)                       # depset(["f", "g", "e", "d", "c", "b", "a"], order = "topological")

  print("c" in t1.to_list())      # True
  print(t1.to_list())             # ["d", "e", "a", "b", "c", "f", "g"]

사용 예

py_binary rule로 설정한 foocc.py 파일에 *.foo 파일 아규먼트로 전달하려고 한다.
foocc.py 파일은 첫 아규먼트 파일 이름으로 파일을 생성한 후, 다음에 오는 모든 파일을 순차적으로 첫 아규먼트 파일에 기록한다.

여기에서 foocc.py.foo 파일에 대한 컴파일러 역할이다. 따라서 target 이름으로 foocc가 적절하고, 수행된 프로젝트 이름, 즉 target 이름에 .out 확장자를 갖는 컴파일 결과를 생성한다고 가정한다.
이로써 온전하지만 작은 컴파일러 세계를 구축하고자 한다. 이름하여 foo 컴파일러.foo 파일!!

컴파일러 역할을 하는 python 파일은 아래와 같다.

# foocc.py
# "Foo compiler" that just concatenates its inputs to form its output.
import sys

if __name__ == "__main__":
  assert len(sys.argv) >= 1
  output = open(sys.argv[1], "wt")
  for path in sys.argv[2:]:
    input = open(path, "rt")
    output.write(input.read())

아래 구문을 실행하면 bazel-bin/depset/d.out 결과 파일이 생성되어야 한다.

cmd> bazelisk build //depset:d
INFO: Analyzed target //depset:d (72 packages loaded, 3465 targets configured).
INFO: Found 1 target...
Target //depset:d up-to-date:
  bazel-bin/depset/d.out
INFO: Elapsed time: 23.636s, Critical Path: 6.81s
INFO: 9 processes: 6 internal, 3 local.
INFO: Build completed successfully, 9 total actions

BUILD.bazel는 아래와 같다. 프로그래머가 원하는 형태로 프로젝트 설정을 진행할 수 있지만, 맨 상단위 py_binary rule는 컴파일러를 의미함으로 그대로 유지해야 한다.

# BUILD.bazel
load("@rules_python//python:defs.bzl", "py_binary")
load(":foo.bzl", "foo_binary", "foo_library")

# Our hypothetical Foo compiler.
py_binary(
    name = "foocc",
    srcs = ["foocc.py"],
)

foo_library(
    name = "a",
    srcs = [
        "a.foo",
        "a_impl.foo",
    ],
)

foo_library(
    name = "b",
    srcs = [
        "b.foo",
        "b_impl.foo",
    ],
    deps = [":a"],
)

foo_library(
    name = "c",
    srcs = [
        "c.foo",
        "c_impl.foo",
    ],
    deps = [":a"],
)

foo_binary(
    name = "d",
    srcs = ["d.foo"],
    deps = [
        ":b",
        ":c",
    ],
)

아래 코딩으로 주목해야 할 내용은 FooFilesInfo providertransitive_sources 필드가 바로 depset 객체를 저장한다.
모든 depset 객체는 get_transitive_srcs 함수를 통해서 생성된다. 이미 익숙한 DefaultInfo providerfiles 필드 역시 depset 객체를 저장한다.

# foo.bzl
# buildifier: disable=module-docstring
FooFilesInfo = provider(doc = "", fields = ["transitive_sources"])

def get_transitive_srcs(srcs, deps):
    """Obtain the source files for a target and its transitive dependencies.

    Args:
      srcs: a list of source files
      deps: a list of targets that are direct dependencies
    Returns:
      a collection of the transitive sources
    """
    return depset(
        srcs,
        transitive = [dep[FooFilesInfo].transitive_sources for dep in deps],
    )

def _foo_library_impl(ctx):
    trans_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps)
    return [
        FooFilesInfo(transitive_sources = trans_srcs),
        DefaultInfo(files = trans_srcs),
    ]

foo_library = rule(
    implementation = _foo_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        "deps": attr.label_list(),
    },
)

def _foo_binary_impl(ctx):
    foocc = ctx.executable._foocc
    out = ctx.outputs.out
    trans_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps)
    srcs_list = trans_srcs.to_list()
    ctx.actions.run(
        executable = foocc,
        arguments = [out.path] + [src.path for src in srcs_list],
        inputs = srcs_list,
        tools = [foocc],
        outputs = [out],
    )

_foo_binary = rule(
    implementation = _foo_binary_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        "deps": attr.label_list(),
        "_foocc": attr.label(
            default = ":foocc",
            allow_files = True,
            executable = True,
            cfg = "exec",
        ),
        "out": attr.output(),
    },
)

def foo_binary(**kwargs):
    _foo_binary(out = "{name}.out".format(**kwargs), **kwargs)

depset.zip
3.2 kB

728x90
반응형