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 언어
에서는, 실행 가능 targe
t의 runfiles
에 depset
객체를 전달해, 실행 시점에 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
provider
의 transitive_sources
필드가 바로 depset
객체를 저장한다.
모든 depset 객체는 get_transitive_srcs
함수를 통해서 생성된다. 이미 익숙한 DefaultInfo
provider
의 files
필드 역시 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)
'building system > bazel' 카테고리의 다른 글
DefaultInfo 프로바이더 이해하기 (0) | 2024.02.23 |
---|---|
nasm_library 구현해보기 (0) | 2024.02.20 |
msvc 환경에서 bazel /MD 모드로 컴파일하기 (1) | 2024.02.01 |
generated file 활용하기 (0) | 2024.01.30 |
platforms module 사용하기 (0) | 2024.01.29 |