개요
nasm.exe
기반으로 nasm_libray
rule를 정의하는 작업은 매우 흥미롭다. 기본적으로 빌드 시스템이 외부 tool를 제어해 원하는 결과물은 만드는 tool chain 기술이라고 볼 수 있고, 단순한 nasm.exe 파일은 툴을 넘어서, 동시에 앞으로 만날 가장 단순한 형태의 컴파일러
라고도 볼 수 있다.
풀어야 과제
풀어야 할 과제는 명확하다.nasm_library
가 작동하도록 nasm_library.bzl
파일을 생성해야 한다. nasm_library
의 src
항목은 filegroup
정보를 입력으로 사용할 수도 있지만, 일반 asm
파일을 받아낼 수 있어야 한다. 이렇게 만들어진 룰을 곧바로 cc_binary
나 cc_library
과 연계될 수 있어야 한다.
load("//project:nasm_library.bzl", "nasm_library")
filegroup(
name = "asm_src",
srcs = ["asm/hello.asm",],
)
filegroup(
name = "main_src",
srcs = ["main.cc"],
)
nasm_library(
name = "asm_lib",
srcs = [":asm_src", "asm/hello2.asm"],
flags = ["-fwin64","-DWIN64","-D__x86_64__"],
data = ["//assets:testdata"]
)
cc_binary(
name = "main",
srcs = [":main_src",":asm_lib"],
deps = [],
linkopts = ["/DEFAULTLIB:legacy_stdio_definitions.lib"],
)
nasm_library.bzl 전체 구현 소스는 아래와 같다.
def _nasm_library_impl(ctx):
tools_list = ctx.attr._exe.files.to_list()
outputs = []
for src in ctx.files.srcs:
out = ctx.actions.declare_file(src.path[:len(src.path) - len(src.extension) - 1] + ".obj")
outputs.append(out)
ctx.actions.run_shell(
command = "{} {} -o {} {}".format(
ctx.executable._exe.path,
" ".join(ctx.attr.flags),
out.path,
src.path
),
arguments = [],
inputs = [src],
outputs = [out],
tools = tools_list
)
cc_info = ctx.attr._exe[CcInfo]
runfiles = ctx.runfiles(files = [])
for deps in (ctx.attr.data, ctx.attr.deps):
for dep in deps:
runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
app =[]
app.append(cc_info)
app.append(
DefaultInfo(
files = depset(outputs),
runfiles = runfiles
)
)
return app
nasm_library = rule(
implementation = _nasm_library_impl,
attrs = {
"_exe": attr.label(
default = Label("@nasm//:nasm"),
executable = True,
cfg = "target",
),
"srcs": attr.label_list(allow_files = [".asm"]),
"deps": attr.label_list(allow_files = True),
"data": attr.label_list(allow_files = True),
"flags": attr.string_list(mandatory = False, allow_empty = True, default = [], doc = None),
},
)
nasm.exe
파일을 먼저 컴파일하고 다음으로 컴파일된 nasm.exe
파일을 기반으로 asm 파일을 컴파일해야 한다.
어떻게 nasm.exe
을 먼저 컴파일되도록 할 수 있을까?
ctx.actions.run_shell
의 tools
항목에 추가에 ctx.attr._exe.files.to_list()
를 사용하면 된다.ctx.attr._exe.files
는 nasm 타겟의 빌드 결과 파일 목록이다. ctx.actions.run_shell
는 tools
항목에 의존해서 수행할 action
위치를 결정한다. 결국 마지막 단계에서 .asm
파일에 대한 action
이 등장해야 한다.
ctx.executable._exe.path
는 nasm.exe 실행 파일
경로다.
tools_list = ctx.attr._exe.files.to_list()
...
ctx.actions.run_shell(
command = "{} {} -o {} {}".format(
ctx.executable._exe.path,
" ".join(ctx.attr.flags),
out.path,
src.path
),
arguments = [],
inputs = [src],
outputs = [out],
tools = tools_list
)
아래 코딩은 파일 이름의 확장자를 ".obj"
로 변경한 후 outputs
리스트에 선언된 파일을 추가한다. outputs
는 최종적으로 DefaultInfo
의 files
항목으로 전달해 해당 rule
에서 만든 결과 파일임을 알려주어야 한다. 프로그램 실행 환경에서 필요한 데이터를 dep[DefaultInfo].default_runfiles
로부터 모아, DefaultInfo
의 runfiles
항목으로 전달해야 한다. 이렇게 모아진 파일 정보는 맵핑 파일을 정보를 구성하거나, 일부 옵션을 조절하면, 실행 환경에서 사용할 수 있는 assets를 만들어낼 수 있다.
def _nasm_library_impl(ctx):
tools_list = ctx.attr._exe.files.to_list()
outputs = []
for src in ctx.files.srcs:
out = ctx.actions.declare_file(src.path[:len(src.path) - len(src.extension) - 1] + ".obj")
outputs.append(out)
ctx.actions.run_shell(
command = "{} {} -o {} {}".format(
ctx.executable._exe.path,
" ".join(ctx.attr.flags),
out.path,
src.path
),
arguments = [],
inputs = [src],
outputs = [out],
tools = tools_list
)
cc_info = ctx.attr._exe[CcInfo]
runfiles = ctx.runfiles(files = [])
for deps in (ctx.attr.data, ctx.attr.deps):
for dep in deps:
runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
app =[]
app.append(cc_info)
app.append(
DefaultInfo(
files = depset(outputs),
runfiles = runfiles
)
)
return app
다음으로 해야 할 중요한 내용은 cc_로 시작하는 룰과 연계작업을 가능하도록 해야 한다. _exe
과 함께 전달되 CcInfo
정보를 추출해 반환값의 일부 정보로 해야 한다.
cc_info = ctx.attr._exe[CcInfo]
app =[]
app.append(cc_info)
...
return app
implicit dependency
로 nasm 빌드 결과를 사용할 예정이다. attr
이름은 외부에서 접근할 수 없도록 _로 시작하고 default
항목으로 외부 의존성 이름을 설정한다. "_exe"
가 executable = True
임으로 ctx.executable._exe
로 실행 File
객체를 얻어낼 수 있다.
nasm_library = rule(
implementation = _nasm_library_impl,
attrs = {
"_exe": attr.label(
default = Label("@nasm//:nasm"),
executable = True,
cfg = "target",
),
"srcs": attr.label_list(allow_files = [".asm"]),
"deps": attr.label_list(allow_files = True),
"data": attr.label_list(allow_files = True),
"flags": attr.string_list(mandatory = False, allow_empty = True, default = [], doc = None),
},
)
전체 실행 결과 보기
정상적으로 작동하는지 여부는 마지막 단계에서 5초 타임아웃이 작동하는 것으로 확인할 수 있다.
D:\buildsystem\bazel\test>bazelisk clean --expunge
Starting local Bazel server and connecting to it...
INFO: Starting clean.
D:\buildsystem\bazel\test>bazelisk run //project:main --compilation_mode=opt
INFO: Analyzed target //project:main (42 packages loaded, 317 targets configured).
INFO: From Compiling output/codeview.c:
...: warning C4819: The file contains a character that cannot be represented in the current code page (949). Save the file in Unicode format to prevent data loss
INFO: From Compiling output/outcoff.c:
...: warning C4819: The file contains a character that cannot be represented in the current code page (949). Save the file in Unicode format to prevent data loss
INFO: From Compiling nasmlib/mmap.c:
...: warning C4005: 'HAVE_STRUCT__STATI64': macro redefinition
external/nasm~2.14.02\config/msvc.h(117): note: see previous definition of 'HAVE_STRUCT__STATI64'
INFO: From Compiling nasmlib/file.c:
...: warning C4005: 'HAVE_STRUCT__STATI64': macro redefinition
external/nasm~2.14.02\config/msvc.h(117): note: see previous definition of 'HAVE_STRUCT__STATI64'
INFO: Found 1 target...
Target //project:main up-to-date:
bazel-bin/project/main.exe
INFO: Elapsed time: 12.092s, Critical Path: 6.31s
INFO: 96 processes: 10 internal, 86 local.
INFO: Build completed successfully, 96 total actions
INFO: Running command line: bazel-bin/project/main.exe
world c++
Hello NASM1
Hello NASM2
'building system > bazel' 카테고리의 다른 글
Depsets 이해하기 (0) | 2024.02.27 |
---|---|
DefaultInfo 프로바이더 이해하기 (0) | 2024.02.23 |
msvc 환경에서 bazel /MD 모드로 컴파일하기 (1) | 2024.02.01 |
generated file 활용하기 (0) | 2024.01.30 |
platforms module 사용하기 (0) | 2024.01.29 |