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.
ci-tools/cpp/update-maintainer-lib.cpp

215 lines
7.7 KiB

// Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "update-maintainer-lib.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <regex>
#include <stdexcept>
#include <string>
#include <optional>
namespace fs = std::filesystem;
// Definitions from update-maintainer.cpp moved here
static const char* PREVIOUS_UBUNTU_MAINTAINERS[] = {
"ubuntu core developers <ubuntu-devel@lists.ubuntu.com>",
"ubuntu core developers <ubuntu-devel-discuss@lists.ubuntu.com>",
"ubuntu motu developers <ubuntu-motu@lists.ubuntu.com>"
};
static const char* UBUNTU_MAINTAINER = "Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>";
class MaintainerUpdateException : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
static std::optional<fs::path> find_control_file(const fs::path &debian_dir) {
fs::path control_in = debian_dir / "control.in";
fs::path control = debian_dir / "control";
if (fs::exists(control_in)) return control_in;
if (fs::exists(control)) return control;
return std::nullopt;
}
static fs::path find_changelog_file(const fs::path &debian_dir) {
fs::path changelog = debian_dir / "changelog";
if (!fs::exists(changelog)) {
throw MaintainerUpdateException("No changelog file found");
}
return changelog;
}
static bool xsbc_managed_by_rules(const fs::path &debian_dir) {
fs::path rules = debian_dir / "rules";
if (!fs::exists(rules)) return false;
std::ifstream rf(rules);
std::string line;
while (std::getline(rf, line)) {
if (line.find("XSBC-Original-") != std::string::npos) {
return true;
}
}
return false;
}
static std::string get_distribution(const fs::path &changelog_file) {
// parse first line of changelog: "package (version) dist; urgency=..."
// dist is the token after ')'
std::ifstream f(changelog_file);
if(!f) throw MaintainerUpdateException("Unable to open changelog.");
std::string first_line;
std::getline(f, first_line);
size_t pos = first_line.find(')');
if(pos == std::string::npos) throw MaintainerUpdateException("Invalid changelog format");
pos++;
while(pos < first_line.size() && std::isspace((unsigned char)first_line[pos])) pos++;
size_t start = pos;
while(pos < first_line.size() && !std::isspace((unsigned char)first_line[pos]) && first_line[pos] != ';') pos++;
std::string dist = first_line.substr(start, pos - start);
size_t dashpos = dist.find('-');
if (dashpos != std::string::npos) {
dist = dist.substr(0, dashpos);
}
return dist;
}
static std::string read_file(const fs::path &p) {
std::ifstream f(p);
if(!f) throw MaintainerUpdateException("Cannot read file: " + p.string());
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
}
static void write_file(const fs::path &p, const std::string &content) {
std::ofstream f(p);
if(!f) throw MaintainerUpdateException("Cannot write file: " + p.string());
f << content;
}
static std::optional<std::string> get_field(const std::string &content, const std::string &field_regex) {
std::regex r(field_regex, std::regex_constants::multiline);
std::smatch m;
if(std::regex_search(content, m, r)) {
return m[1].str();
}
return std::nullopt;
}
static std::string set_field(const std::string &content, const std::string &field_regex, const std::string &new_line) {
std::regex r(field_regex, std::regex_constants::multiline);
return std::regex_replace(content, r, new_line);
}
static void update_maintainer_file(const fs::path &control_file, const std::string &distribution, bool verbose) {
std::string c = read_file(control_file);
auto original_maintainer = get_field(c, "^Maintainer:\\s?(.*)$");
if(!original_maintainer) {
throw MaintainerUpdateException("No Maintainer field found");
}
std::string om = *original_maintainer;
std::string om_lower = om;
for (auto &ch : om_lower) ch = (char)std::tolower((unsigned char)ch);
// Check previous ubuntu maintainers
for (auto &pm : PREVIOUS_UBUNTU_MAINTAINERS) {
std::string pm_lower = pm;
for (auto &ch: pm_lower) ch=(char)std::tolower((unsigned char)ch);
if(pm_lower == om_lower) {
if(verbose) {
std::cout<<"The old maintainer was: "<<om<<"\n";
std::cout<<"Resetting as: "<<UBUNTU_MAINTAINER<<"\n";
}
// just set maintainer
std::regex maint_re("^Maintainer:\\s?.*$", std::regex_constants::multiline);
c = std::regex_replace(c, maint_re, "Maintainer: " + std::string(UBUNTU_MAINTAINER));
write_file(control_file, c);
return;
}
}
// If ends with ubuntu.com, do nothing
{
std::string lower_om = om_lower;
if (lower_om.rfind("ubuntu.com>", lower_om.size()-11) != std::string::npos) {
if(verbose) {
std::cout<<"The Maintainer email is ubuntu.com address. Doing nothing.\n";
}
return;
}
}
// Debian distributions: stable, testing, unstable, experimental
if(distribution=="stable"||distribution=="testing"||distribution=="unstable"||distribution=="experimental") {
if(verbose) {
std::cout<<"The package targets Debian. Doing nothing.\n";
}
return;
}
// set XSBC-Original-Maintainer if needed
auto orig_field = get_field(c, "^(?:[XSBC]*-)?Original-Maintainer:\\s?(.*)$");
if(orig_field && verbose) {
std::cout<<"Overwriting original maintainer: "<< *orig_field <<"\n";
}
if(verbose) {
std::cout<<"The original maintainer is: "<< om <<"\n";
std::cout<<"Resetting as: "<<UBUNTU_MAINTAINER<<"\n";
}
// set original maint
if(orig_field) {
// pattern to replace original maint
std::regex orig_re("^(?:[XSBC]*-)?Original-Maintainer:.*$", std::regex_constants::multiline);
c = std::regex_replace(c, orig_re, "XSBC-Original-Maintainer: " + om);
} else {
// insert after Maintainer line
std::regex maint_re("^(Maintainer:.*)$", std::regex_constants::multiline);
c = std::regex_replace(c, maint_re, "$1\nXSBC-Original-Maintainer: " + om);
}
// now set maint
{
std::regex maint_re("^Maintainer:\\s?.*$", std::regex_constants::multiline);
c = std::regex_replace(c, maint_re, "Maintainer: " + std::string(UBUNTU_MAINTAINER));
}
write_file(control_file, c);
}
void update_maintainer(const std::string &debian_directory, bool verbose) {
fs::path debian_dir(debian_directory);
auto control_file = find_control_file(debian_dir);
if(!control_file) {
throw MaintainerUpdateException("No control file found");
}
fs::path changelog = find_changelog_file(debian_dir);
if(xsbc_managed_by_rules(debian_dir)) {
if(verbose) {
std::cout<<"XSBC-Original is managed by rules. Doing nothing.\n";
}
return;
}
std::string distribution = get_distribution(changelog);
update_maintainer_file(*control_file, distribution, verbose);
}