
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의 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 actionsBUILD.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 모드로 컴파일하기 (0) | 2024.02.01 |
| generated file 활용하기 (0) | 2024.01.30 |
| platforms module 사용하기 (0) | 2024.01.29 |