Config file and package tracking utility
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

246 lines
6.0 KiB

/*
* Copyright (C) 2021-2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <algorithm> // replace
#include <array>
#include <cstdio> // fprintf, printf, stderr
#include <cstdlib> // system
#include <filesystem> // exists
#include <optional>
#include <sstream> // istringstream
#include <string> // getline
#include <vector>
#include "machine.h"
#include "package.h"
#include "util/file.h"
#include "util/shell.h"
#include "util/system.h"
Package::Package(s)
{
}
Package::~Package()
{
}
// -----------------------------------------
void Package::aurInstall()
{
installOrAurInstall(InstallType::AurInstall);
}
void Package::install()
{
installOrAurInstall(InstallType::Install);
}
void Package::list(const std::vector<std::string>& targets)
{
auto packagesOrEmpty = getPackageList();
if (!packagesOrEmpty.has_value()) {
return;
}
std::string packages = packagesOrEmpty.value();
if (targets.empty()) {
printf("%s", packages.c_str());
return;
}
auto stream = std::istringstream(packages);
packages.clear();
// FIXME: Decide on the type of match, currently 'or, any part of the string'.
std::string line;
while (std::getline(stream, line)) {
for (const auto& target : targets) {
if (line.find(target) != std::string::npos) {
packages.append(line + '\n');
break;
}
}
}
printf("%s", packages.c_str());
}
void Package::store()
{
auto packagesOrEmpty = getPackageList();
if (!packagesOrEmpty.has_value()) {
return;
}
auto packageFile = Util::File::create("./packages");
packageFile.clear();
packageFile.append(packagesOrEmpty.value());
packageFile.flush();
}
// -----------------------------------------
std::optional<std::string> Package::fetchAurHelper()
{
const std::string helpers[] = {
"yay",
"paru",
"trizen",
};
for (const auto& helper : helpers) {
if (findDependency(helper)) {
return { helper };
}
}
return {};
}
void Package::installOrAurInstall(InstallType type)
{
distroDetect();
distroDependencies();
std::optional<std::string> aurHelper;
if (type == InstallType::AurInstall) {
if (m_distro != Distro::Arch) {
fprintf(stderr, "\033[31;1mPackage:\033[0m AUR is not supported on this distribution\n");
return;
}
aurHelper = fetchAurHelper();
if (!aurHelper.has_value()) {
fprintf(stderr, "\033[31;1mPackage:\033[0m no supported AUR helper found\n");
return;
}
}
std::string command = "";
Util::System $;
if (m_distro == Distro::Arch) {
// Grab everything off enabled official repositories that is in the list
auto repoList = $("pacman -Ssq") | $("grep -xf ./packages");
if (type == InstallType::AurInstall) {
// Determine which packages in the list are from the AUR
// NOTE: Util::System does not support commands with newlines
auto aurList = Util::Shell()("grep -vx '" + repoList.output() + "' ./packages");
command = aurHelper.value() + " -Sy --devel --needed --noconfirm " + aurList.output();
}
else {
command = "pacman -Sy --needed " + repoList.output();
}
}
else if (m_distro == Distro::Debian) {
// Grab everything off enabled official repositories that is in the list
auto repoList = $("apt-cache search .").cut(1, ' ') | $("grep -xf ./packages");
command = "apt install " + repoList.output();
}
std::replace(command.begin(), command.end(), '\n', ' ');
#ifndef NDEBUG
printf("running: $ %s\n", command.c_str());
#endif
system(command.c_str());
}
bool Package::findDependency(const std::string& search)
{
return std::filesystem::exists("/bin/" + search)
|| std::filesystem::exists("/usr/bin/" + search)
|| std::filesystem::exists("/usr/local/bin/" + search);
}
bool Package::distroDetect()
{
std::string id = Machine::the().distroId();
std::string idLike = Machine::the().distroIdLike();
if (id == "arch") {
m_distro = Distro::Arch;
}
else if (id == "debian") {
m_distro = Distro::Debian;
}
else if (id == "ubuntu") {
m_distro = Distro::Debian;
}
else if (idLike.find("arch") != std::string::npos) {
m_distro = Distro::Arch;
}
else if (idLike.find("debian") != std::string::npos) {
m_distro = Distro::Debian;
}
else if (idLike.find("ubuntu") != std::string::npos) {
m_distro = Distro::Debian;
}
else {
fprintf(stderr, "\033[31;1mPackage:\033[0m unsupported distribution\n");
return false;
}
return true;
}
bool Package::distroDependencies()
{
std::vector<std::array<std::string, 2>> dependencies;
if (m_distro == Distro::Arch) {
dependencies.push_back({ "pacman", "pacman" });
dependencies.push_back({ "pactree", "pacman-contrib" });
}
else if (m_distro == Distro::Debian) {
dependencies.push_back({ "apt-cache", "apt" });
dependencies.push_back({ "apt-mark", "apt" });
dependencies.push_back({ "dpkg-query", "dpkg" });
}
for (const auto& dependency : dependencies) {
if (!findDependency(dependency.at(0))) {
fprintf(stderr, "\033[31;1mPackage:\033[0m required dependency '%s' is missing\n", dependency.at(1).c_str());
return false;
}
}
return true;
}
std::optional<std::string> Package::getPackageList()
{
if (!distroDetect() || !distroDependencies()) {
return {};
}
std::string packages;
Util::System $;
if (m_distro == Distro::Arch) {
auto basePackages = $("pactree -u base").tail(2, true);
auto develPackages = $("pacman -Qqg base-devel");
auto filterList = (basePackages + develPackages).sort(true);
auto packageList = ($("pacman -Qqe") | $("grep -xv " + filterList.output())).sort();
packages = packageList.output();
}
else if (m_distro == Distro::Debian) {
auto installedList = $("dpkg-query --show --showformat=${Package}\\t${Priority}\\n");
auto filterList = (installedList | $("grep -E required|important|standard")).cut(1);
installedList = installedList.cut(1);
auto installedManuallyList = $("awk '/Commandline:.* install / && !/APT::/ { print $NF }' /var/log/apt/history.log");
installedManuallyList = (installedManuallyList + $("apt-mark showmanual")).sort(true);
auto packageList = installedManuallyList | $("grep -x " + installedList.output()) | $("grep -xv " + filterList.output());
packages = packageList.output();
}
return packages;
}