Starlark는 원래 Bazel에서 사용하도록 개발되었으며 이후 다른 도구에서 채택된 Python과 유사한 구성 언어입니다. Bazel의 BUILD
및 .bzl
파일은 '빌드 언어'라고 적절하게 알려진 Starlark의 방언으로 작성되지만, 특히 기능이 Bazel의 내장 또는 '네이티브' 부분이 아니라 빌드 언어로 표현된다는 점을 강조할 때는 'Starlark'라고만 간단히 지칭하는 경우가 많습니다. Bazel은 glob
, genrule
, java_binary
등과 같은 수많은 빌드 관련 함수로 핵심 언어를 보강합니다.
자세한 내용은 Bazel 및 Starlark 문서를 참고하고 새 규칙 집합의 시작점으로 Rules SIG 템플릿을 참고하세요.
빈 규칙
첫 번째 규칙을 만들려면 foo.bzl
파일을 만듭니다.
def _foo_binary_impl(ctx):
pass
foo_binary = rule(
implementation = _foo_binary_impl,
)
rule
함수를 호출할 때는 콜백 함수를 정의해야 합니다. 로직이 여기에 추가되지만 지금은 함수를 비워 두어도 됩니다. ctx
인수는 타겟에 관한 정보를 제공합니다.
BUILD
파일에서 규칙을 로드하고 사용할 수 있습니다.
같은 디렉터리에 BUILD
파일을 만듭니다.
load(":foo.bzl", "foo_binary")
foo_binary(name = "bin")
이제 타겟을 빌드할 수 있습니다.
$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)
이 규칙은 아무것도 하지 않지만 이미 다른 규칙처럼 작동합니다. 필수 이름이 있고 visibility
, testonly
, tags
와 같은 일반적인 속성을 지원합니다.
평가 모델
계속하기 전에 코드가 평가되는 방식을 이해하는 것이 중요합니다.
몇 가지 print 문을 사용하여 foo.bzl
를 업데이트합니다.
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
및 BUILD:
load(":foo.bzl", "foo_binary")
print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")
ctx.label
는 분석 중인 타겟의 라벨에 해당합니다. ctx
객체에는 유용한 필드와 메서드가 많이 있습니다. 전체 목록은 API 참조에서 확인할 수 있습니다.
코드를 쿼리합니다.
$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1
다음 사항을 확인합니다.
- 'bzl 파일 평가'가 먼저 출력됩니다. Bazel은
BUILD
파일을 평가하기 전에 로드하는 모든 파일을 평가합니다. 여러BUILD
파일이 foo.bzl을 로드하는 경우 Bazel은 평가 결과를 캐시하므로 'bzl 파일 평가'가 한 번만 표시됩니다. - 콜백 함수
_foo_binary_impl
가 호출되지 않습니다. Bazel 쿼리는BUILD
파일을 로드하지만 타겟을 분석하지는 않습니다.
타겟을 분석하려면 cquery
('구성된 쿼리') 또는 build
명령어를 사용합니다.
$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
보시다시피 이제 _foo_binary_impl
가 타겟마다 한 번씩 두 번 호출됩니다.
일부 독자는 foo.bzl의 평가가 bazel query
호출 후 캐시되지만 'bzl 파일 평가'가 다시 출력되는 것을 볼 수 있습니다. Bazel은 코드를 재평가하지 않고 출력 이벤트만 재생합니다. 캐시 상태와 관계없이 동일한 출력이 표시됩니다.
파일 만들기
규칙을 더 유용하게 만들려면 파일을 생성하도록 업데이트하세요. 먼저 파일을 선언하고 이름을 지정합니다. 이 예에서는 타겟과 동일한 이름의 파일을 만듭니다.
ctx.actions.declare_file(ctx.label.name)
이제 bazel build :all
를 실행하면 다음과 같은 오류가 발생합니다.
The following files have no generating action:
bin2
파일을 선언할 때마다 작업을 만들어 파일을 생성하는 방법을 Bazel에 알려야 합니다. ctx.actions.write
를 사용하여 지정된 콘텐츠가 포함된 파일을 만듭니다.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
코드가 유효하지만 아무것도 실행되지 않습니다.
$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)
ctx.actions.write
함수는 Bazel에 파일을 생성하는 방법을 알려주는 작업을 등록했습니다. 그러나 Bazel은 실제로 요청될 때까지 파일을 만들지 않습니다. 마지막으로 해야 할 일은 Bazel에 이 파일이 규칙 구현 내에 사용되는 임시 파일이 아니라 규칙의 출력이라고 알리는 것입니다.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello!\n",
)
return [DefaultInfo(files = depset([out]))]
나중에 DefaultInfo
및 depset
함수를 살펴보세요. 지금은 마지막 줄이 규칙의 출력을 선택하는 방법이라고 가정해 보겠습니다.
이제 Bazel을 실행합니다.
$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
bazel-bin/bin1
$ cat bazel-bin/bin1
Hello!
파일이 생성되었습니다.
속성
규칙을 더 유용하게 만들려면 attr
모듈을 사용하여 새 속성을 추가하고 규칙 정의를 업데이트합니다.
username
라는 문자열 속성을 추가합니다.
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
그런 다음 BUILD
파일에서 설정합니다.
foo_binary(
name = "bin",
username = "Alice",
)
콜백 함수에서 값에 액세스하려면 ctx.attr.username
를 사용하세요. 예를 들면 다음과 같습니다.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello {}!\n".format(ctx.attr.username),
)
return [DefaultInfo(files = depset([out]))]
속성을 필수 속성으로 지정하거나 기본값을 설정할 수 있습니다. attr.string
문서를 참고하세요.
불리언 또는 정수 목록과 같은 다른 유형의 속성을 사용할 수도 있습니다.
종속 항목
attr.label
및 attr.label_list
와 같은 종속 항목 속성은 속성을 소유한 대상에서 속성 값에 라벨이 표시되는 대상에 대한 종속 항목을 선언합니다. 이러한 종류의 속성은 타겟 그래프의 기반을 형성합니다.
BUILD
파일에서 타겟 라벨은 //pkg:name
와 같은 문자열 객체로 표시됩니다. 구현 함수에서 타겟은 Target
객체로 액세스할 수 있습니다. 예를 들어 Target.files
를 사용하여 타겟에서 반환된 파일을 확인합니다.
여러 파일
기본적으로 규칙으로 생성된 타겟만 종속 항목으로 표시될 수 있습니다 (예: foo_library()
타겟). 속성이 입력 파일(예: 저장소의 소스 파일)인 타겟을 허용하도록 하려면 allow_files
를 사용하여 허용되는 파일 확장자 목록을 지정하거나 True
를 사용하여 모든 파일 확장자를 허용할 수 있습니다.
"srcs": attr.label_list(allow_files = [".java"]),
ctx.files.<attribute name>
를 사용하여 파일 목록에 액세스할 수 있습니다. 예를 들어 srcs
속성의 파일 목록은 다음을 통해 액세스할 수 있습니다.
ctx.files.srcs
단일 파일
파일이 하나만 필요한 경우 allow_single_file
를 사용합니다.
"src": attr.label(allow_single_file = [".java"])
그러면 이 파일은 ctx.file.<attribute name>
에서 액세스할 수 있습니다.
ctx.file.src
템플릿으로 파일 만들기
템플릿을 기반으로 .cc 파일을 생성하는 규칙을 만들 수 있습니다. 또한 ctx.actions.write
를 사용하여 규칙 구현 함수에서 생성된 문자열을 출력할 수 있지만, 이 경우 두 가지 문제가 있습니다. 첫째, 템플릿이 커질수록 별도의 파일에 배치하고 분석 단계에서 큰 문자열을 생성하지 않는 것이 메모리 효율이 높습니다. 둘째, 별도의 파일을 사용하면 사용자에게 더 편리합니다. 대신 템플릿 파일에서 대체를 실행하는 ctx.actions.expand_template
를 사용하세요.
template
속성을 만들어 템플릿 파일의 종속 항목을 선언합니다.
def _hello_world_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".cc")
ctx.actions.expand_template(
output = out,
template = ctx.file.template,
substitutions = {"{NAME}": ctx.attr.username},
)
return [DefaultInfo(files = depset([out]))]
hello_world = rule(
implementation = _hello_world_impl,
attrs = {
"username": attr.string(default = "unknown person"),
"template": attr.label(
allow_single_file = [".cc.tpl"],
mandatory = True,
),
},
)
사용자는 다음과 같이 규칙을 사용할 수 있습니다.
hello_world(
name = "hello",
username = "Alice",
template = "file.cc.tpl",
)
cc_binary(
name = "hello_bin",
srcs = [":hello"],
)
템플릿을 최종 사용자에게 노출하지 않고 항상 동일한 템플릿을 사용하려면 기본값을 설정하고 속성을 비공개로 설정하면 됩니다.
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
밑줄로 시작하는 속성은 비공개이며 BUILD
파일에서 설정할 수 없습니다. 이제 템플릿은 암시적 종속 항목입니다. 모든 hello_world
타겟에는 이 파일에 대한 종속 항목이 있습니다. BUILD
파일을 업데이트하고 exports_files
를 사용하여 이 파일을 다른 패키지에 표시해야 합니다.
exports_files(["file.cc.tpl"])