Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ cmake-build-debug/

**/.DS_Store

bazel-*
58 changes: 48 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cc_resources(
name = "my_resources",
srcs = ["path/to/resource1.bin", "path/to/resource2.bin"],
out_prefix = "my_res", # Optional prefix for generated file names
_tool = "//tools:bin_to_cc" # Path to the conversion tool
data_type = "uchar", # Optional data type for the array (default: "uchar")
)
```

Expand All @@ -32,7 +32,10 @@ cc_resources(

- `out_prefix`: An optional string that specifies a prefix for the output file names and the corresponding C variable names. For example, if `out_prefix` is set to `ui` and the input is `icon.png`, the outputs will be named `ui_icon.h` and `ui_icon.cpp`, and the resource name will be `ui_icon`.

- `_tool`: A label for the conversion tool that should be executed to process the binary files. By default, this is set to `//tools:bin_to_cc`, but it can be overridden to use a custom tool.
- `data_type`: An optional string that specifies the data type for the generated array. Available options are:
- `"char"`: Signed char array, suitable for text data
- `"uchar"`: Unsigned char array (default), suitable for binary data
- `"uint"`: Unsigned int array, reduces generated source file size by combining 4 bytes into one integer

### Output

Expand All @@ -41,10 +44,9 @@ When you invoke the `cc_resources` rule, it generates:
- `.h` files containing the C-compatible struct definitions, including resource metadata.
- `.cpp` files implementing the logic to handle these resources.

### Example

Given the following `BUILD.bazel` setup:
### Examples

Basic usage with default settings:
```python
load("@rules_cc_resources//rules:defs.bzl", "cc_resources")

Expand All @@ -55,13 +57,49 @@ cc_resources(
)
```

Using unsigned int type to reduce generated file size:
```python
cc_resources(
name = "large_resources",
srcs = ["data/large_file.bin"],
out_prefix = "data",
data_type = "uint" # Combines 4 bytes into one unsigned int
)
```

Text data handling:
```python
cc_resources(
name = "text_resources",
srcs = ["text/strings.txt"],
out_prefix = "text",
data_type = "char" # Uses signed char for text data
)
```

This will produce the following files:
- `assets_logo.h` and `assets_logo.cpp`
- `assets_background.h` and `assets_background.cpp`
- `data_large_file.h` and `data_large_file.cpp`
- `text_strings.h` and `text_strings.cpp`

### Data Type Selection Guide

- Use `"uchar"` (default) when:
- Working with general binary data
- Maximum compatibility is needed
- Individual byte access is important

- Use `"uint"` when:
- You have large binary files
- You want to reduce the generated source file size
- Memory alignment is not a concern

- `assets_logo.h`
- `assets_logo.cpp`
- `assets_background.h`
- `assets_background.cpp`
- Use `"char"` when:
- Working primarily with text data
- Sign information is important
- You need to handle ASCII/text data

### Conclusion

The `cc_resources` rule facilitates the integration of binary resources into C/C++ projects by automating the generation of corresponding source files, thereby streamlining resource management in Bazel builds.
The `cc_resources` rule facilitates the integration of binary resources into C/C++ projects by automating the generation of corresponding source files, thereby streamlining resource management in Bazel builds. With the flexible data type options, you can optimize the generated code size and choose the most appropriate representation for your data.
1 change: 1 addition & 0 deletions example/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ cc_resources(
"assets/my_resource_next.txt",
"my_resource.txt",
],
data_type = "uint",
out_prefix = "example",
)

Expand Down
36 changes: 22 additions & 14 deletions rules/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@ def _cc_resources_impl(ctx):
base_name = src_stem
file_path_prefix = src_stem

# Declare output files for this specific resource
# Ensure declared paths are unique if out_prefix is not used and multiple inputs might have same stem from different dirs
# However, ctx.actions.declare_file handles uniqueness within the rule's output directory.
# If src_file.short_path is "path/to/data.bin", we might want "path_to_data"
# For simplicity, let's assume basenames are unique enough or out_prefix is used.
# A more robust way for uniqueness without out_prefix would be to incorporate parts of the path.
# For now, file_path_prefix from above is used.

h_file = ctx.actions.declare_file(file_path_prefix + ".h")
cpp_file = ctx.actions.declare_file(file_path_prefix + ".cpp")

Expand All @@ -48,10 +40,10 @@ def _cc_resources_impl(ctx):
args.add("--input", src_file.path)
args.add("--output_h", h_file.path)
args.add("--output_cpp", cpp_file.path)

# The resource_name passed to the tool should be what the C variable is named
# This will be 'base_name' which incorporates the out_prefix if present.
args.add("--resource_name", base_name)

# 添加数据类型选择
args.add("--data_type", ctx.attr.data_type)

ctx.actions.run(
executable = ctx.executable._tool,
Expand Down Expand Up @@ -80,7 +72,6 @@ cc_resources = rule(
implementation = _cc_resources_impl,
attrs = {
"srcs": attr.label_list(
# Changed from src to srcs
mandatory = True,
allow_files = True, # Allow actual file paths
doc = "List of input binary files.",
Expand All @@ -92,12 +83,29 @@ cc_resources = rule(
default = True,
doc = "Whether to include the extension '.bin' in the output file names.",
),
"data_type": attr.string(
values = ["char", "uchar", "uint"],
default = "uchar",
doc = "Data type for the array (char, uchar=unsigned char, uint=unsigned int).",
),
"_tool": attr.label(
default = Label("//tools:bin_to_cc"), # Make sure this path is correct
default = Label("//tools:bin_to_cc"),
cfg = "exec",
executable = True,
doc = "The binary to C/C++ conversion tool.",
),
},
doc = "Converts binary files into .cpp and .h files. Each input file results in a separate .cpp and .h pair, defining a C-compatible struct {name, size, data}.",
doc = """
Converts binary files into C/C++ source files.

This rule takes binary files and generates corresponding C/C++ header and source files
that contain the binary data as arrays. The generated files can be used to embed
resources directly into C/C++ programs.

Attributes:
srcs: List of input binary files.
out_prefix: Optional prefix for output file names and C variable names.
ext_hide: Whether to exclude the file extension from generated names.
data_type: Data type for the generated array (char, uchar, or uint).
""",
)
55 changes: 46 additions & 9 deletions tools/bin_to_cc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,22 @@ def main():
parser.add_argument("--output_h", required=True, help="Output header file path (.h).")
parser.add_argument("--output_cpp", required=True, help="Output source file path (.cpp).")
parser.add_argument("--resource_name", required=True, help="Base name for C variable and include guard.")
parser.add_argument("--data_type", choices=['char', 'uchar', 'uint'], default='uchar',
help="Data type for the array (char, uchar=unsigned char, uint=unsigned int). Default: uchar")
args = parser.parse_args()

c_var_name = sanitize_for_c_identifier(args.resource_name)
header_basename = os.path.basename(args.output_h) # e.g., my_resource.h
include_guard = f"__{sanitize_for_c_identifier(args.resource_name).upper()}_H__"

# 根据选择的数据类型确定C类型
c_type_map = {
'char': 'char',
'uchar': 'unsigned char',
'uint': 'unsigned int'
}
data_type = c_type_map[args.data_type]

try:
with open(args.input, "rb") as f_in:
data = f_in.read()
Expand All @@ -50,7 +60,7 @@ def main():
typedef struct {{
const char* name;
size_t size;
const unsigned char* data;
const {data_type}* data;
}} __ResourceData_{c_var_name};

extern const __ResourceData_{c_var_name} {c_var_name};
Expand All @@ -63,7 +73,8 @@ def main():
"""
try:
os.makedirs(os.path.dirname(args.output_h), exist_ok=True)
with open(args.output_h, "w") as f_h:
# 使用UTF-8编码写入头文件
with open(args.output_h, "w", encoding='utf-8') as f_h:
f_h.write(h_content)
except IOError as e:
print(f"Error writing header file {args.output_h}: {e}")
Expand All @@ -77,28 +88,54 @@ def main():

cpp_content_parts.append(f"\n#ifdef __cplusplus\nextern \"C\" {{\n#endif\n")

cpp_content_parts.append(f"static const unsigned char {c_var_name}_data[] = {{")
if data:
cpp_content_parts.append(f"static const {data_type} {c_var_name}_data[] = {{")

if args.data_type == 'uint':
# 对于unsigned int,每4个字节组成一个整数
for i in range(0, len(data), 4):
if i % 4 == 0:
cpp_content_parts.append("\n ")
chunk = data[i:i+4]
# 如果最后一个块不足4字节,用0填充
while len(chunk) < 4:
chunk = chunk + b'\x00'
value = int.from_bytes(chunk, byteorder='little')
cpp_content_parts.append(f"0x{value:08X}u, ")
else:
# 对于char和unsigned char,保持原来的字节模式
for i, byte in enumerate(data):
if i % 12 == 0:
cpp_content_parts.append("\n ")
cpp_content_parts.append(f"0x{byte:02X}, ")
# Remove trailing comma and space if data was not empty
if args.data_type == 'char':
cpp_content_parts.append(f"{byte:d}, ")
else: # uchar
cpp_content_parts.append(f"0x{byte:02X}, ")

# Remove trailing comma and space if data was not empty
if data:
cpp_content_parts[-1] = cpp_content_parts[-1].rstrip(', ')
cpp_content_parts.append("\n};\n\n")

cpp_content_parts.append(f"const __ResourceData_{c_var_name} {c_var_name} = {{\n")
cpp_content_parts.append(f" \"{args.resource_name}\",\n")
cpp_content_parts.append(f" sizeof({c_var_name}_data),\n")

# 对于uint类型,size需要调整为字节大小
if args.data_type == 'uint':
array_size = ((len(data) + 3) // 4) * 4 # 向上取整到4的倍数
cpp_content_parts.append(f" {len(data)}, // 原始字节大小\n")
else:
cpp_content_parts.append(f" sizeof({c_var_name}_data),\n")

cpp_content_parts.append(f" {c_var_name}_data\n")
cpp_content_parts.append(f"}};\n")

cpp_content_parts.append(f"\n#ifdef __cplusplus\n}} // extern \"C\"\n#endif\n")

try:
os.makedirs(os.path.dirname(args.output_cpp), exist_ok=True)
with open(args.output_cpp, "w") as f_cpp:
f_cpp.write("".join(cpp_content_parts))
# 使用UTF-8编码写入源文件
with open(args.output_cpp, "w", encoding='utf-8') as f_cpp:
f_cpp.write(''.join(cpp_content_parts))
except IOError as e:
print(f"Error writing source file {args.output_cpp}: {e}")
return 1
Expand Down