cleanup resgen code

A test for how Gemini can clean-up code. Changes:

- Configuration Struct: Global variables have been grouped into an 
AppConfig struct. This makes the program's configuration explicit and 
avoids polluting the global namespace.

- Clearer Argument Parsing: The handleArguments function now returns an 
AppConfig object, encapsulating all parsing logic and making the main 
function cleaner.

- Helper Functions: Repetitive tasks like string replacement, file I/O, 
and writing file entries have been extracted into small, well-defined 
helper functions (replaceAll, openOutputFile, readFile, etc.).

- Simplified main Function: The main function is now a high-level 
coordinator, delegating work to helper functions. This makes the overall
 program flow much easier to follow.

- Modern C++ Idioms: The code now consistently uses C++ streams 
(std::cout, std::ofstream) and std::string objects, which is more 
idiomatic than a mix of C and C++ styles.

- added short comments
This commit is contained in:
Mathias Agopian
2025-07-21 21:59:46 -07:00
committed by Mathias Agopian
parent 3a5f558874
commit ddca795cc9

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,28 +19,37 @@
#include <getopt/getopt.h>
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
#include <string.h>
using namespace std;
using namespace utils;
static const char* g_jsonMagicString = "__RESGEN__";
static const char* g_packageName = "resources";
static const char* g_deployDir = ".";
static bool g_keepExtension = false;
static bool g_appendNull = false;
static bool g_generateC = false;
static bool g_quietMode = false;
static bool g_embedJson = false;
// Holds all command-line configuration for the resource generator.
struct AppConfig {
const char* packageName = "resources";
const char* deployDir = ".";
bool keepExtension = false;
bool appendNull = false;
bool generateC = false;
bool quietMode = false;
bool embedJson = false;
std::vector<Path> inputPaths;
};
// A special marker used to identify where to insert the JSON summary blob.
static const char* g_jsonMagicString = "__RESGEN__";
// Defines the help message text displayed to the user.
static const char* USAGE = R"TXT(
RESGEN aggregates a sequence of binary blobs, each of which becomes a "resource" whose id
is the basename of the input file. It produces the following set of files:
@@ -82,6 +91,7 @@ Examples:
TEXTURES_BEACH_DATA, TEXTURES_BEACH_SIZE
)TXT";
// Template for the Apple-specific assembly file.
static const char* APPLE_ASM_TEMPLATE = R"ASM(
.global _{RESOURCES}PACKAGE
.section __TEXT,__const
@@ -89,6 +99,7 @@ _{RESOURCES}PACKAGE:
.incbin "{resources}.bin"
)ASM";
// Template for the standard assembly file.
static const char* ASM_TEMPLATE = R"ASM(
.global {RESOURCES}PACKAGE
.section .rodata
@@ -96,287 +107,304 @@ static const char* ASM_TEMPLATE = R"ASM(
.incbin "{resources}.bin"
)ASM";
// Prints the usage string, replacing the placeholder with the actual executable name.
static void printUsage(const char* name) {
std::string execName(Path(name).getName());
std::string const execName(Path(name).getName());
const std::string from("RESGEN");
std::string usage(USAGE);
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
std::size_t pos = 0;
while ((pos = usage.find(from, pos)) != std::string::npos) {
usage.replace(pos, from.length(), execName);
pos += execName.length();
}
puts(usage.c_str());
std::cout << usage;
}
// Prints the license text to the console.
static void license() {
static const char *license[] = {
#include "licenses/licenses.inc"
nullptr
};
const char **p = &license[0];
while (*p)
std::cout << *p++ << std::endl;
for (const char **p = &license[0]; *p; ++p) {
std::cout << *p << std::endl;
}
}
static int handleArguments(int argc, char* argv[]) {
// A helper function to replace all occurrences of a substring within a string.
static std::string& replaceAll(std::string& context, const std::string& from, const std::string& to) {
std::size_t pos = 0;
while ((pos = context.find(from, pos)) != std::string::npos) {
context.replace(pos, from.length(), to);
pos += to.length();
}
return context;
}
// Parses command-line arguments using getopt and populates the AppConfig struct.
static AppConfig handleArguments(int const argc, char* argv[]) {
static constexpr const char* OPTSTR = "hLp:x:ktcqj";
static const struct option OPTIONS[] = {
{ "help", no_argument, 0, 'h' },
{ "license", no_argument, 0, 'L' },
{ "package", required_argument, 0, 'p' },
{ "deploy", required_argument, 0, 'x' },
{ "keep", no_argument, 0, 'k' },
{ "text", no_argument, 0, 't' },
{ "cfile", no_argument, 0, 'c' },
{ "quiet", no_argument, 0, 'q' },
{ "json", no_argument, 0, 'j' },
{ 0, 0, 0, 0 } // termination of the option list
static const option OPTIONS[] = {
{ "help", no_argument, nullptr, 'h' },
{ "license", no_argument, nullptr, 'L' },
{ "package", required_argument, nullptr, 'p' },
{ "deploy", required_argument, nullptr, 'x' },
{ "keep", no_argument, nullptr, 'k' },
{ "text", no_argument, nullptr, 't' },
{ "cfile", no_argument, nullptr, 'c' },
{ "quiet", no_argument, nullptr, 'q' },
{ "json", no_argument, nullptr, 'j' },
{ nullptr, 0, nullptr, 0 }
};
AppConfig config;
int opt;
int optionIndex = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(optarg ? optarg : "");
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, nullptr)) >= 0) {
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
std::exit(0);
case 'L':
license();
exit(0);
std::exit(0);
case 'p':
g_packageName = optarg;
config.packageName = optarg;
break;
case 'x':
g_deployDir = optarg;
config.deployDir = optarg;
break;
case 'k':
g_keepExtension = true;
config.keepExtension = true;
break;
case 't':
g_appendNull = true;
config.appendNull = true;
break;
case 'c':
g_generateC = true;
config.generateC = true;
break;
case 'q':
g_quietMode = true;
config.quietMode = true;
break;
case 'j':
g_embedJson = true;
config.embedJson = true;
break;
default:
printUsage(argv[0]);
std::exit(1);
}
}
return optind;
// Treat remaining arguments as input file paths.
for (int i = optind; i < argc; ++i) {
config.inputPaths.emplace_back(argv[i]);
}
return config;
}
// Opens a file for writing, printing an error and exiting on failure.
static std::ofstream openOutputFile(const Path& path, std::ios_base::openmode const mode = std::ios_base::out) {
std::ofstream stream(path.getPath(), mode);
if (!stream) {
std::cerr << "Unable to open " << path << std::endl;
std::exit(1);
}
return stream;
}
// Reads an entire binary file into a vector of bytes.
static std::vector<std::uint8_t> readFile(const Path& path) {
std::ifstream inStream(path.getPath(), std::ios::binary);
if (!inStream) {
std::cerr << "Unable to open " << path << std::endl;
std::exit(1);
}
return std::vector<std::uint8_t>((std::istreambuf_iterator<char>(inStream)),
(std::istreambuf_iterator<char>()));
}
// Writes the generated header file, but only if its content has changed.
// This helps avoid unnecessary recompilation in build systems.
static void writeHeaderIfChanged(const Path& path, const std::string& newContents) {
std::ifstream headerInStream(path.getPath(), std::ifstream::ate);
if (headerInStream) {
long const fileSize = headerInStream.tellg();
if (fileSize == newContents.size()) {
std::vector<char> previous(fileSize);
headerInStream.seekg(0);
headerInStream.read(previous.data(), fileSize);
if (0 == memcmp(previous.data(), newContents.c_str(), fileSize)) {
return;
}
}
}
std::ofstream headerOutStream = openOutputFile(path);
headerOutStream << newContents;
}
// Writes a resource's data to a C-style char array (similar to 'xxd -i').
static void writeXxdEntry(std::ofstream& xxdStream, const std::string& resourceName,
const std::vector<std::uint8_t>& content) {
xxdStream << "// " << resourceName << "\n";
xxdStream << std::setfill('0') << std::hex;
for (std::size_t i = 0; i < content.size(); i++) {
if (i > 0 && i % 20 == 0) {
xxdStream << "\n";
}
xxdStream << "0x" << std::setw(2) << int(content[i]) << ", ";
}
if (!content.empty() && content.size() % 20 != 0) {
xxdStream << "\n";
}
xxdStream << "\n";
}
int main(int argc, char* argv[]) {
const int optionIndex = handleArguments(argc, argv);
const int numArgs = argc - optionIndex;
if (numArgs < 1) {
// Parse arguments and check for input files.
const AppConfig config = handleArguments(argc, argv);
if (config.inputPaths.empty()) {
printUsage(argv[0]);
return 1;
}
vector<Path> inputPaths;
for (int argIndex = optionIndex; argIndex < argc; ++argIndex) {
inputPaths.emplace_back(argv[argIndex]);
}
if (g_embedJson) {
inputPaths.push_back(g_jsonMagicString);
// If JSON embedding is enabled, add a placeholder to the input list.
std::vector<Path> inputPaths = config.inputPaths;
if (config.embedJson) {
inputPaths.emplace_back(g_jsonMagicString);
}
std::string packageFile = g_packageName;
std::string packagePrefix = std::string(g_packageName) + "_";
transform(packagePrefix.begin(), packagePrefix.end(), packagePrefix.begin(), ::toupper);
std::string package = packagePrefix + "PACKAGE";
// Generate names and symbols based on the package name.
const std::string packageFile = config.packageName;
std::string packagePrefix = std::string(config.packageName) + "_";
std::transform(packagePrefix.begin(), packagePrefix.end(), packagePrefix.begin(), [](unsigned char c) { return std::toupper(c); });
const std::string packageSymbol = packagePrefix + "PACKAGE";
const Path deployDir(g_deployDir);
// Create the deployment directory if it doesn't exist.
const Path deployDir(config.deployDir);
if (!deployDir.exists()) {
deployDir.mkdirRecursive();
}
// Define all output file paths.
const Path appleAsmPath(deployDir + (packageFile + ".apple.S"));
const Path asmPath(deployDir + (packageFile + ".S"));
const Path binPath(deployDir + (packageFile + ".bin"));
const Path headerPath(deployDir + (packageFile + ".h"));
const Path xxdPath(deployDir + (packageFile + ".c"));
// In the assembly language templates, replace {RESOURCES} with packagePrefix and replace
// {resources} with packageFile.
const std::string k1("{RESOURCES}");
const std::string k2("{resources}");
// Prepare assembly file content by replacing placeholders.
std::string aasmstr(APPLE_ASM_TEMPLATE);
replaceAll(aasmstr, "{RESOURCES}", packagePrefix);
replaceAll(aasmstr, "{resources}", packageFile);
std::string asmstr(ASM_TEMPLATE);
for (size_t pos = aasmstr.find(k1); pos != std::string::npos; pos = aasmstr.find(k1, pos))
aasmstr.replace(pos, k1.length(), packagePrefix);
for (size_t pos = aasmstr.find(k2); pos != std::string::npos; pos = aasmstr.find(k2, pos))
aasmstr.replace(pos, k2.length(), packageFile);
for (size_t pos = asmstr.find(k1); pos != std::string::npos; pos = asmstr.find(k1, pos))
asmstr.replace(pos, k1.length(), packagePrefix);
for (size_t pos = asmstr.find(k2); pos != std::string::npos; pos = asmstr.find(k2, pos))
asmstr.replace(pos, k2.length(), packageFile);
replaceAll(asmstr, "{RESOURCES}", packagePrefix);
replaceAll(asmstr, "{resources}", packageFile);
// Open the Apple-friendly assembly language file.
ofstream appleAsmStream(appleAsmPath.getPath());
if (!appleAsmStream) {
cerr << "Unable to open " << appleAsmPath << endl;
exit(1);
}
// Open all output file streams.
auto appleAsmStream = openOutputFile(appleAsmPath);
auto asmStream = openOutputFile(asmPath);
auto binStream = openOutputFile(binPath, std::ios::binary);
// Open the non-Apple assembly language file.
ofstream asmStream(asmPath.getPath());
if (!asmStream) {
cerr << "Unable to open " << asmPath << endl;
exit(1);
}
// Begin constructing the C header file content.
std::ostringstream headerStream;
headerStream << "#ifndef " << packagePrefix << "H_\n"
<< "#define " << packagePrefix << "H_\n\n"
<< "#include <stdint.h>\n\n"
<< "extern \"C\" {\n"
<< " extern const uint8_t " << packageSymbol << "[];\n";
// Open the bin file for writing.
ofstream binStream(binPath.getPath(), ios::binary);
if (!binStream) {
cerr << "Unable to open " << binPath << endl;
exit(1);
}
// String streams to accumulate header macros and the JSON summary.
std::ostringstream headerMacros;
std::ostringstream jsonStream;
// Open the header file stream for writing.
ostringstream headerStream;
headerStream << "#ifndef " << packagePrefix << "H_" << endl
<< "#define " << packagePrefix << "H_" << endl << endl
<< "#include <stdint.h>\n" << endl
<< "extern \"C\" {" << endl
<< " extern const uint8_t " << package << "[];" << endl;
ostringstream headerMacros;
ostringstream appleDataAsmStream;
ostringstream dataAsmStream;
ostringstream jsonStream;
// Open the generated C file for writing.
ofstream xxdStream;
if (g_generateC) {
xxdStream = ofstream(xxdPath.getPath());
if (!xxdStream) {
cerr << "Unable to open " << xxdPath << endl;
exit(1);
}
// If generating a C file, open the stream and write the initial boilerplate.
std::ofstream xxdStream;
if (config.generateC) {
xxdStream = openOutputFile(xxdPath);
xxdStream << "#include <stdint.h>\n"
<< "const uint8_t " << package << "[] = {\n";
<< "const uint8_t " << packageSymbol << "[] = {\n";
}
// Consume each input file and write it back out into the various output streams.
// Process each input file to build the resource collection.
jsonStream << "{";
size_t offset = 0;
std::size_t offset = 0;
for (const auto& inPath : inputPaths) {
vector<uint8_t> content;
std::vector<std::uint8_t> content;
if (inPath != g_jsonMagicString) {
ifstream inStream(inPath.getPath(), ios::binary);
if (!inStream) {
cerr << "Unable to open " << inPath << endl;
exit(1);
}
content = vector<uint8_t>((istreambuf_iterator<char>(inStream)), {});
// For a regular file, read its binary content.
content = readFile(inPath);
} else {
// To finalize the JSON string, we replace the trailing comma with an end bracket and
// prefix it with the magic identifier and string size.
// For the JSON placeholder, finalize and embed the JSON summary blob.
// The blob is formatted as: __RESGEN__\0<size>\0{...json...}
std::string jsonString = jsonStream.str();
jsonString[jsonString.size()-1] = '}';
ostringstream jsonBlob;
jsonBlob << g_jsonMagicString << "\0";
jsonBlob << jsonString.size() << "\0";
jsonString[jsonString.size() - 1] = '}';
std::ostringstream jsonBlob;
jsonBlob << g_jsonMagicString << '\0';
jsonBlob << jsonString.size() << '\0';
jsonBlob << jsonString;
jsonString = jsonBlob.str();
uint8_t const* jsonPtr = (uint8_t const*) jsonString.c_str();
content = vector<uint8_t>(jsonPtr, jsonPtr + jsonBlob.str().size());
const auto* jsonPtr = reinterpret_cast<const std::uint8_t*>(jsonString.c_str());
content.assign(jsonPtr, jsonPtr + jsonBlob.str().size());
}
if (g_appendNull) {
// Optionally append a null terminator, useful for text resources.
if (config.appendNull) {
content.push_back(0);
}
// Formulate the resource name and the prefixed resource name.
std::string rname = g_keepExtension ? inPath.getName() : inPath.getNameWithoutExtension();
replace(rname.begin(), rname.end(), '.', '_');
transform(rname.begin(), rname.end(), rname.begin(), ::toupper);
// Generate the resource's symbol name from its file name.
std::string rname = config.keepExtension ? inPath.getName() : inPath.getNameWithoutExtension();
replaceAll(rname, ".", "_");
std::transform(rname.begin(), rname.end(), rname.begin(), [](unsigned char c) { return std::toupper(c); });
const std::string prname = packagePrefix + rname;
// Write the binary blob into the bin file.
binStream.write((const char*) content.data(), content.size());
// Write the resource content to the aggregate binary file.
binStream.write(reinterpret_cast<const char*>(content.data()), content.size());
// Write the offsets and sizes.
headerMacros
<< "#define " << prname << "_OFFSET " << offset << "\n"
<< "#define " << prname << "_SIZE " << content.size() << "\n"
<< "#define " << prname << "_DATA (" << package << " + " << prname << "_OFFSET)\n\n";
// Generate C preprocessor macros for the resource's offset, size, and data pointer.
headerMacros << "#define " << prname << "_OFFSET " << offset << "\n"
<< "#define " << prname << "_SIZE " << content.size() << "\n"
<< "#define " << prname << "_DATA (" << packageSymbol << " + " << prname << "_OFFSET)\n\n";
// Write the xxd-style ASCII array, followed by a blank line.
if (g_generateC) {
xxdStream << "// " << rname << "\n";
xxdStream << setfill('0') << hex;
size_t i = 0;
for (; i < content.size(); i++) {
if (i > 0 && i % 20 == 0) {
xxdStream << "\n";
}
xxdStream << "0x" << setw(2) << (int) content[i] << ", ";
}
if (i % 20 != 0) xxdStream << "\n";
xxdStream << "\n";
// If enabled, write the content to the C source file.
if (config.generateC) {
writeXxdEntry(xxdStream, rname, content);
}
// Add an entry to the JSON summary and update the running offset.
jsonStream << "\"" << rname << "\":" << content.size() << ",";
offset += content.size();
}
// Finalize the header file content.
headerStream << "}\n\n";
headerStream << headerMacros.str();
headerStream << "#endif\n";
// To optimize builds, avoid overwriting the header file if nothing has changed.
bool headerIsDirty = true;
ifstream headerInStream(headerPath.getPath(), std::ifstream::ate);
string headerContents = headerStream.str();
if (headerInStream) {
long fileSize = static_cast<long>(headerInStream.tellg());
if (fileSize == headerContents.size()) {
vector<char> previous(fileSize);
headerInStream.seekg(0);
headerInStream.read(previous.data(), fileSize);
headerIsDirty = 0 != memcmp(previous.data(), headerContents.c_str(), fileSize);
}
// Write the header and assembly files.
writeHeaderIfChanged(headerPath, headerStream.str());
asmStream << asmstr << std::endl;
appleAsmStream << aasmstr << std::endl;
// Report generated files to the console unless in quiet mode.
if (!config.quietMode) {
std::cout << "Generated files: " << headerPath << " " << asmPath << " " << appleAsmPath << " "
<< binPath;
}
if (headerIsDirty) {
ofstream headerOutStream(headerPath.getPath());
if (!headerOutStream) {
cerr << "Unable to open " << headerPath << endl;
exit(1);
}
headerOutStream << headerContents;
}
asmStream << asmstr << dataAsmStream.str() << endl;
asmStream.close();
appleAsmStream << aasmstr << appleDataAsmStream.str() << endl;
appleAsmStream.close();
if (!g_quietMode) {
cout << "Generated files: "
<< headerPath << " "
<< asmPath << " "
<< appleAsmPath << " "
<< binPath;
}
if (g_generateC) {
// Finalize the C file and report it.
if (config.generateC) {
xxdStream << "};\n\n";
if (!g_quietMode) {
cout << " " << xxdPath;
if (!config.quietMode) {
std::cout << " " << xxdPath;
}
}
if (!g_quietMode) {
cout << endl;
if (!config.quietMode) {
std::cout << std::endl;
}
}
return 0;
}