Skip to content

Commit

Permalink
TgBot++: Initial C++/Python based Android ROM builder
Browse files Browse the repository at this point in the history
  • Loading branch information
Royna2544 committed Jul 4, 2024
1 parent 5e1e04e commit ccff606
Show file tree
Hide file tree
Showing 27 changed files with 1,331 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ libTgBot*.so
src/command_modules/cmds.gen.cpp
modules.load
resources/about.html
__pycache__

# Visual studio code
build/
Expand Down
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,12 @@ endif()
include(cmake/tgbotutils.cmake)
#####################################################################

################# Android ROM builder with C++/Python #################
if (UNIX)
include(src/android_builder/builder.cmake)
endif()
#####################################################################

################### The Bot's main functionaility ###################
add_library_san(${PROJECT_NAME} SHARED ${SRC_LIST})
target_include_directories(${PROJECT_NAME} PRIVATE src/third-party/rapidjson/include)
Expand Down
58 changes: 58 additions & 0 deletions src/android_builder/ArgumentBuilder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include "ArgumentBuilder.hpp"

#include <absl/log/check.h>
#include <absl/log/log.h>

ArgumentBuilder::ArgumentBuilder(Py_ssize_t argument_count)
: argument_count(argument_count) {
arguments.reserve(argument_count);
}

ArgumentBuilder& ArgumentBuilder::add_argument(Variants value) {
arguments.emplace_back(value);
CHECK(argument_count >= arguments.size())
<< "Too many arguments, expected " << argument_count;
return *this;
}

PyObject* ArgumentBuilder::build() {
if (arguments.size() != argument_count) {
LOG(ERROR) << "Too few arguments, expected " << argument_count;
return nullptr;
}
PyObject* result = PyTuple_New(argument_count);
std::vector<PyObject*> arguments_ref;

if (result == nullptr) {
LOG(ERROR) << "Failed to create tuple";
PyErr_Print();
return nullptr;
}
arguments_ref.reserve(argument_count);
for (Py_ssize_t i = 0; i < argument_count; ++i) {
PyObject* value = nullptr;
std::visit(
[&value, &arguments_ref](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, long>) {
value = PyLong_FromLong(arg);
arguments_ref.emplace_back(value);
} else if constexpr (std::is_same_v<T, std::string>) {
value = PyUnicode_FromString(arg.c_str());
arguments_ref.emplace_back(value);
}
},
arguments[i]);
if (value == nullptr) {
LOG(ERROR) << "Failed to convert argument to Python object";
Py_DECREF(result);
PyErr_Print();
for (const auto& obj : arguments_ref) {
Py_XDECREF(obj);
}
return nullptr;
}
PyTuple_SetItem(result, i, value);
}
return result;
}
38 changes: 38 additions & 0 deletions src/android_builder/ArgumentBuilder.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <Python.h>
#include <string>
#include <variant>
#include <vector>

class ArgumentBuilder {
public:
using Variants = std::variant<long, std::string>;

/**
* @brief Constructs an ArgumentBuilder
* object with the specified argument count.
*
* @param argument_count The number of
* arguments to be added to the builder.
*/
explicit ArgumentBuilder(Py_ssize_t argument_count);

/**
* @brief Adds a variant argument to the builder.
*
* @param value The variant argument to be added.
*
* @return Reference to the builder for method chaining.
*/
ArgumentBuilder& add_argument(Variants value);

/**
* @brief Builds and returns the constructed arguments as a Python object.
*
* @return A Python object containing the constructed arguments.
*/
PyObject* build();

private:
std::vector<Variants> arguments;
Py_ssize_t argument_count = 0;
};
93 changes: 93 additions & 0 deletions src/android_builder/ConfigParsers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "ConfigParsers.hpp"

#include <absl/log/log.h>

constexpr bool parserDebug = true;

// Name RomName LocalManifestUrl LocalManifestBranch device variant
template <>
std::vector<BuildConfig> parse(std::ifstream data) {
std::vector<BuildConfig> configs;
std::string line;
while (std::getline(data, line)) {
if (line.empty() || line[0] == '#') {
continue;
}
BuildConfig config;
std::vector<std::string> items;
splitWithSpaces(line, items);
if (items.size() != BuildConfig::elem_size) {
LOG(WARNING) << "Skipping invalid line: " << line;
continue;
}
config.name = items[BuildConfig::indexOf_name];
config.romName = items[BuildConfig::indexOf_romName];
config.local_manifest.url = items[BuildConfig::indexOf_localManifestUrl];
config.local_manifest.branch = items[BuildConfig::indexOf_localManifestBranch];
config.device = items[BuildConfig::indexOf_device];
if (items[BuildConfig::indexOf_variant] == "user") {
config.variant = BuildConfig::Variant::kUser;
} else if (items[BuildConfig::indexOf_variant] == "userdebug") {
config.variant = BuildConfig::Variant::kUserDebug;
} else if (items[BuildConfig::indexOf_variant] == "eng") {
config.variant = BuildConfig::Variant::kEng;
} else {
LOG(WARNING) << "Skipping invalid variant: " << items[BuildConfig::indexOf_variant];
continue;
}
if (parserDebug) {
LOG(INFO) << "---------------------------";
LOG(INFO) << "Parsed config";
LOG(INFO) << "Name: " << config.name;
LOG(INFO) << "RomName: " << config.romName;
LOG(INFO) << "LocalManifestUrl: " << config.local_manifest.url;
LOG(INFO) << "LocalManifestBranch: "
<< config.local_manifest.branch;
LOG(INFO) << "Device: " << config.device;
LOG(INFO) << "Variant: "
<< (config.variant == BuildConfig::Variant::kUser
? "User"
: (config.variant ==
BuildConfig::Variant::kUserDebug
? "UserDebug"
: "Eng"));
LOG(INFO) << "---------------------------";
LOG(INFO) << "---------------------------";
}
configs.emplace_back(config);
}
return configs;
}

template <>
std::vector<RomConfig> parse(std::ifstream data) {
std::vector<RomConfig> roms;
std::string line;
while (std::getline(data, line)) {
if (line.empty() || line[0] == '#') {
continue;
}
RomConfig rom;
std::vector<std::string> items;
splitWithSpaces(line, items);
if (items.size() != RomConfig::elem_size) {
LOG(WARNING) << "Skipping invalid line: " << line;
continue;
}
rom.name = items[RomConfig::indexOf_name];
rom.url = items[RomConfig::indexOf_url];
rom.branch = items[RomConfig::indexOf_branch];
rom.target = items[RomConfig::indexOf_target];
if (parserDebug) {
LOG(INFO) << "---------------------------";
LOG(INFO) << "Parsed rom";
LOG(INFO) << "Name: " << rom.name;
LOG(INFO) << "Url: " << rom.url;
LOG(INFO) << "Branch: " << rom.branch;
LOG(INFO) << "Target: " << rom.target;
LOG(INFO) << "---------------------------";
}
roms.emplace_back(rom);
}
return roms;
}
52 changes: 52 additions & 0 deletions src/android_builder/ConfigParsers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include <fstream>
#include <StringToolsExt.hpp>
#include <string>
#include <vector>
#include <absl/log/log.h>
#include "RepoUtils.hpp"

struct BuildConfig {
static constexpr int elem_size = 6;
static constexpr int indexOf_name = 0;
static constexpr int indexOf_romName = 1;
static constexpr int indexOf_localManifestUrl = 2;
static constexpr int indexOf_localManifestBranch = 3;
static constexpr int indexOf_device = 4;
static constexpr int indexOf_variant = 5;

std::string name; // name of the build
std::string romName; // name of the ROM
RepoUtils::CloneOptions local_manifest; // local manifest information
std::string device; // codename of the device
enum class Variant {
kUser,
kUserDebug,
kEng
} variant; // Target build variant
};

struct RomConfig {
static constexpr int elem_size = 4;
static constexpr int indexOf_name = 0;
static constexpr int indexOf_url = 1;
static constexpr int indexOf_branch = 2;
static constexpr int indexOf_target = 3;

std::string name; // name of the ROM
std::string url; // URL of the ROM repo
std::string branch; // branch of the repo
std::string target; // build target to build a ROM
};

template <typename Data>
std::vector<Data> parse(std::ifstream data) = delete;

// Name RomName LocalManifestUrl LocalManifestBranch device variant
template <>
std::vector<BuildConfig> parse(std::ifstream data);

// Name RepoUrl RepoBranch TargetName
template <>
std::vector<RomConfig> parse(std::ifstream data);
100 changes: 100 additions & 0 deletions src/android_builder/ForkAndRun.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include "ForkAndRun.hpp"

#include <absl/log/log.h>
#include <internal/_FileDescriptor_posix.h>
#include <sys/wait.h>

#include <cstdlib>
#include <thread>
#include "libos/libsighandler.h"

bool ForkAndRun::execute() {
Pipe stdout_pipe{};
Pipe stderr_pipe{};

if (!stderr_pipe.pipe() || !stdout_pipe.pipe()) {
PLOG(ERROR) << "Failed to create pipes";
return false;
}
pid_t pid = fork();
if (pid == 0) {
dup2(stdout_pipe.writeEnd(), STDOUT_FILENO);
dup2(stderr_pipe.writeEnd(), STDERR_FILENO);
close(stdout_pipe.readEnd());
close(stderr_pipe.readEnd());
if (runFunction()) {
_exit(EXIT_SUCCESS);
} else {
_exit(EXIT_FAILURE);
}
} else if (pid > 0) {
Pipe program_termination_pipe{};
bool breakIt = false;
int status = 0;

close(stdout_pipe.writeEnd());
close(stderr_pipe.writeEnd());
program_termination_pipe.pipe();

selector.add(
stdout_pipe.readEnd(),
[stdout_pipe, this]() {
BufferType buf{};
ssize_t bytes_read =
read(stdout_pipe.readEnd(), buf.data(), buf.size() - 1);
if (bytes_read >= 0) {
onNewStdoutBuffer(buf);
buf.fill(0);
}
},
Selector::Mode::READ);
selector.add(
stderr_pipe.readEnd(),
[stderr_pipe, this]() {
BufferType buf{};
ssize_t bytes_read =
read(stderr_pipe.readEnd(), buf.data(), buf.size() - 1);
if (bytes_read >= 0) {
onNewStderrBuffer(buf);
buf.fill(0);
}
},
Selector::Mode::READ);
selector.add(
program_termination_pipe.readEnd(),
[program_termination_pipe, &breakIt]() { breakIt = true; },
Selector::Mode::READ);
std::thread pollThread([&breakIt, this]() {
while (!breakIt) {
switch (selector.poll()) {
case Selector::PollResult::OK:
break;
case Selector::PollResult::FAILED:
case Selector::PollResult::TIMEOUT:
breakIt = true;
break;
}
}
});
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
onExit(WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
onSignal(WTERMSIG(status));
} else {
LOG(WARNING) << "Unknown program termination: " << status;
onExit(0);
}
// Notify the polling thread that program has ended.
write(program_termination_pipe.writeEnd(), &status, sizeof(status));
pollThread.join();

// Cleanup
program_termination_pipe.close();
stderr_pipe.close();
stdout_pipe.close();
} else {
PLOG(ERROR) << "Unable to fork";
}
return true;
}
Loading

0 comments on commit ccff606

Please sign in to comment.