Files
CMake/Source/CTest/cmCTestBZR.cxx
T
Brad King bcbb212df7 Revert use of libuv for process execution for 3.28
Wide use of CMake 3.28.{1,0[-rcN]} has uncovered some hangs and crashes
in libuv SIGCHLD handling on some platforms, particularly in virtualization
environments on macOS hosts.  Although the bug does not seem to be in CMake,
we can restore stability in the CMake 3.28 release series for users of such
platforms by reverting our new uses of libuv for process execution.

Revert implementation changes merged by commit 4771544386 (Merge topic
'replace-cmsysprocess-with-cmuvprocesschain', 2023-09-06, v3.28.0-rc1~138),
but keep test suite updates.

Issue: #25414, #25500, #25562, #25589
2024-01-24 17:10:00 -05:00

477 lines
15 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCTestBZR.h"
#include <cstdlib>
#include <list>
#include <map>
#include <ostream>
#include <vector>
#include <cmext/algorithm>
#include <cm3p/expat.h>
#include "cmsys/RegularExpression.hxx"
#include "cmCTest.h"
#include "cmCTestVC.h"
#include "cmSystemTools.h"
#include "cmXMLParser.h"
static int cmBZRXMLParserUnknownEncodingHandler(void* /*unused*/,
const XML_Char* name,
XML_Encoding* info)
{
static const int latin1[] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008,
0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011,
0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A,
0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023,
0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C,
0x002D, 0x002E, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035,
0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E,
0x003F, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062,
0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B,
0x006C, 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074,
0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D,
0x007E, 0x007F, 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020,
0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,
0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC,
0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, 0x00A0, 0x00A1,
0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA,
0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0, 0x00B1, 0x00B2, 0x00B3,
0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC,
0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5,
0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE,
0x00CF, 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0,
0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9,
0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x00F0, 0x00F1, 0x00F2,
0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB,
0x00FC, 0x00FD, 0x00FE, 0x00FF
};
// The BZR xml output plugin can use some encodings that are not
// recognized by expat. This will lead to an error, e.g. "Error
// parsing bzr log xml: unknown encoding", the following is a
// workaround for these unknown encodings.
if (name == std::string("ascii") || name == std::string("cp1252") ||
name == std::string("ANSI_X3.4-1968")) {
for (unsigned int i = 0; i < 256; ++i) {
info->map[i] = latin1[i];
}
return 1;
}
return 0;
}
cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log)
: cmCTestGlobalVC(ct, log)
{
this->PriorRev = this->Unknown;
// Even though it is specified in the documentation, with bzr 1.13
// BZR_PROGRESS_BAR has no effect. In the future this bug might be fixed.
// Since it doesn't hurt, we specify this environment variable.
cmSystemTools::PutEnv("BZR_PROGRESS_BAR=none");
}
cmCTestBZR::~cmCTestBZR() = default;
class cmCTestBZR::InfoParser : public cmCTestVC::LineParser
{
public:
InfoParser(cmCTestBZR* bzr, const char* prefix)
: BZR(bzr)
{
this->SetLog(&bzr->Log, prefix);
this->RegexCheckOut.compile("checkout of branch: *([^\t\r\n]+)$");
this->RegexParent.compile("parent branch: *([^\t\r\n]+)$");
}
private:
cmCTestBZR* BZR;
bool CheckOutFound = false;
cmsys::RegularExpression RegexCheckOut;
cmsys::RegularExpression RegexParent;
bool ProcessLine() override
{
if (this->RegexCheckOut.find(this->Line)) {
this->BZR->URL = this->RegexCheckOut.match(1);
this->CheckOutFound = true;
} else if (!this->CheckOutFound && this->RegexParent.find(this->Line)) {
this->BZR->URL = this->RegexParent.match(1);
}
return true;
}
};
class cmCTestBZR::RevnoParser : public cmCTestVC::LineParser
{
public:
RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev)
: Rev(rev)
{
this->SetLog(&bzr->Log, prefix);
this->RegexRevno.compile("^([0-9]+)$");
}
private:
std::string& Rev;
cmsys::RegularExpression RegexRevno;
bool ProcessLine() override
{
if (this->RegexRevno.find(this->Line)) {
this->Rev = this->RegexRevno.match(1);
}
return true;
}
};
std::string cmCTestBZR::LoadInfo()
{
// Run "bzr info" to get the repository info from the work tree.
const char* bzr = this->CommandLineTool.c_str();
const char* bzr_info[] = { bzr, "info", nullptr };
InfoParser iout(this, "info-out> ");
OutputLogger ierr(this->Log, "info-err> ");
this->RunChild(bzr_info, &iout, &ierr);
// Run "bzr revno" to get the repository revision number from the work tree.
const char* bzr_revno[] = { bzr, "revno", nullptr };
std::string rev;
RevnoParser rout(this, "revno-out> ", rev);
OutputLogger rerr(this->Log, "revno-err> ");
this->RunChild(bzr_revno, &rout, &rerr);
return rev;
}
bool cmCTestBZR::NoteOldRevision()
{
this->OldRevision = this->LoadInfo();
this->Log << "Revision before update: " << this->OldRevision << "\n";
cmCTestLog(this->CTest, HANDLER_OUTPUT,
" Old revision of repository is: " << this->OldRevision
<< "\n");
this->PriorRev.Rev = this->OldRevision;
return true;
}
bool cmCTestBZR::NoteNewRevision()
{
this->NewRevision = this->LoadInfo();
this->Log << "Revision after update: " << this->NewRevision << "\n";
cmCTestLog(this->CTest, HANDLER_OUTPUT,
" New revision of repository is: " << this->NewRevision
<< "\n");
this->Log << "URL = " << this->URL << "\n";
return true;
}
class cmCTestBZR::LogParser
: public cmCTestVC::OutputLogger
, private cmXMLParser
{
public:
LogParser(cmCTestBZR* bzr, const char* prefix)
: OutputLogger(bzr->Log, prefix)
, BZR(bzr)
, EmailRegex("(.*) <([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>")
{
this->InitializeParser();
}
~LogParser() override { this->CleanupParser(); }
int InitializeParser() override
{
int res = this->cmXMLParser::InitializeParser();
if (res) {
XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser),
cmBZRXMLParserUnknownEncodingHandler,
nullptr);
}
return res;
}
private:
cmCTestBZR* BZR;
using Revision = cmCTestBZR::Revision;
using Change = cmCTestBZR::Change;
Revision Rev;
std::vector<Change> Changes;
Change CurChange;
std::vector<char> CData;
cmsys::RegularExpression EmailRegex;
bool ProcessChunk(const char* data, int length) override
{
this->OutputLogger::ProcessChunk(data, length);
this->ParseChunk(data, length);
return true;
}
void StartElement(const std::string& name, const char** /*atts*/) override
{
this->CData.clear();
if (name == "log") {
this->Rev = Revision();
this->Changes.clear();
}
// affected-files can contain blocks of
// modified, unknown, renamed, kind-changed, removed, conflicts, added
else if (name == "modified" || name == "renamed" ||
name == "kind-changed") {
this->CurChange = Change();
this->CurChange.Action = 'M';
} else if (name == "added") {
this->CurChange = Change();
this->CurChange = 'A';
} else if (name == "removed") {
this->CurChange = Change();
this->CurChange = 'D';
} else if (name == "unknown" || name == "conflicts") {
// Should not happen here
this->CurChange = Change();
}
}
void CharacterDataHandler(const char* data, int length) override
{
cm::append(this->CData, data, data + length);
}
void EndElement(const std::string& name) override
{
if (name == "log") {
this->BZR->DoRevision(this->Rev, this->Changes);
} else if (!this->CData.empty() &&
(name == "file" || name == "directory")) {
this->CurChange.Path.assign(this->CData.data(), this->CData.size());
cmSystemTools::ConvertToUnixSlashes(this->CurChange.Path);
this->Changes.push_back(this->CurChange);
} else if (!this->CData.empty() && name == "symlink") {
// symlinks have an arobase at the end in the log
this->CurChange.Path.assign(this->CData.data(), this->CData.size() - 1);
cmSystemTools::ConvertToUnixSlashes(this->CurChange.Path);
this->Changes.push_back(this->CurChange);
} else if (!this->CData.empty() && name == "committer") {
this->Rev.Author.assign(this->CData.data(), this->CData.size());
if (this->EmailRegex.find(this->Rev.Author)) {
this->Rev.Author = this->EmailRegex.match(1);
this->Rev.EMail = this->EmailRegex.match(2);
}
} else if (!this->CData.empty() && name == "timestamp") {
this->Rev.Date.assign(this->CData.data(), this->CData.size());
} else if (!this->CData.empty() && name == "message") {
this->Rev.Log.assign(this->CData.data(), this->CData.size());
} else if (!this->CData.empty() && name == "revno") {
this->Rev.Rev.assign(this->CData.data(), this->CData.size());
}
this->CData.clear();
}
void ReportError(int /*line*/, int /*column*/, const char* msg) override
{
this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n";
}
};
class cmCTestBZR::UpdateParser : public cmCTestVC::LineParser
{
public:
UpdateParser(cmCTestBZR* bzr, const char* prefix)
: BZR(bzr)
{
this->SetLog(&bzr->Log, prefix);
this->RegexUpdate.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$");
}
private:
cmCTestBZR* BZR;
cmsys::RegularExpression RegexUpdate;
bool ProcessChunk(const char* first, int length) override
{
bool last_is_new_line = (*first == '\r' || *first == '\n');
const char* const last = first + length;
for (const char* c = first; c != last; ++c) {
if (*c == '\r' || *c == '\n') {
if (!last_is_new_line) {
// Log this line.
if (this->Log && this->Prefix) {
*this->Log << this->Prefix << this->Line << "\n";
}
// Hand this line to the subclass implementation.
if (!this->ProcessLine()) {
this->Line.clear();
return false;
}
this->Line.clear();
last_is_new_line = true;
}
} else {
// Append this character to the line under construction.
this->Line.append(1, *c);
last_is_new_line = false;
}
}
return true;
}
bool ProcessLine() override
{
if (this->RegexUpdate.find(this->Line)) {
this->DoPath(this->RegexUpdate.match(1)[0],
this->RegexUpdate.match(2)[0],
this->RegexUpdate.match(3)[0], this->RegexUpdate.match(4));
}
return true;
}
void DoPath(char c0, char c1, char c2, std::string path)
{
if (path.empty()) {
return;
}
cmSystemTools::ConvertToUnixSlashes(path);
const std::string dir = cmSystemTools::GetFilenamePath(path);
const std::string name = cmSystemTools::GetFilenameName(path);
if (c0 == 'C') {
this->BZR->Dirs[dir][name].Status = PathConflicting;
return;
}
if (c1 == 'M' || c1 == 'K' || c1 == 'N' || c1 == 'D' || c2 == '*') {
this->BZR->Dirs[dir][name].Status = PathUpdated;
return;
}
}
};
bool cmCTestBZR::UpdateImpl()
{
// Get user-specified update options.
std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
if (opts.empty()) {
opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions");
}
std::vector<std::string> args = cmSystemTools::ParseArguments(opts);
// TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
// Use "bzr pull" to update the working tree.
std::vector<char const*> bzr_update;
bzr_update.push_back(this->CommandLineTool.c_str());
bzr_update.push_back("pull");
for (std::string const& arg : args) {
bzr_update.push_back(arg.c_str());
}
bzr_update.push_back(this->URL.c_str());
bzr_update.push_back(nullptr);
// For some reason bzr uses stderr to display the update status.
OutputLogger out(this->Log, "pull-out> ");
UpdateParser err(this, "pull-err> ");
return this->RunUpdateCommand(bzr_update.data(), &out, &err);
}
bool cmCTestBZR::LoadRevisions()
{
cmCTestLog(this->CTest, HANDLER_OUTPUT,
" Gathering version information (one . per revision):\n"
" "
<< std::flush);
// We are interested in every revision included in the update.
this->Revisions.clear();
std::string revs;
if (atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) {
// DoRevision takes care of discarding the information about OldRevision
revs = this->OldRevision + ".." + this->NewRevision;
} else {
return true;
}
// Run "bzr log" to get all global revisions of interest.
const char* bzr = this->CommandLineTool.c_str();
const char* bzr_log[] = {
bzr, "log", "-v", "-r", revs.c_str(), "--xml", this->URL.c_str(), nullptr
};
{
LogParser out(this, "log-out> ");
OutputLogger err(this->Log, "log-err> ");
this->RunChild(bzr_log, &out, &err);
}
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl);
return true;
}
class cmCTestBZR::StatusParser : public cmCTestVC::LineParser
{
public:
StatusParser(cmCTestBZR* bzr, const char* prefix)
: BZR(bzr)
{
this->SetLog(&bzr->Log, prefix);
this->RegexStatus.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$");
}
private:
cmCTestBZR* BZR;
cmsys::RegularExpression RegexStatus;
bool ProcessLine() override
{
if (this->RegexStatus.find(this->Line)) {
this->DoPath(this->RegexStatus.match(1)[0],
this->RegexStatus.match(2)[0],
this->RegexStatus.match(3)[0], this->RegexStatus.match(4));
}
return true;
}
void DoPath(char c0, char c1, char c2, std::string path)
{
if (path.empty()) {
return;
}
cmSystemTools::ConvertToUnixSlashes(path);
if (c0 == 'C') {
this->BZR->DoModification(PathConflicting, path);
return;
}
if (c0 == '+' || c0 == 'R' || c0 == 'P' || c1 == 'M' || c1 == 'K' ||
c1 == 'N' || c1 == 'D' || c2 == '*') {
this->BZR->DoModification(PathModified, path);
return;
}
}
};
bool cmCTestBZR::LoadModifications()
{
// Run "bzr status" which reports local modifications.
const char* bzr = this->CommandLineTool.c_str();
const char* bzr_status[] = { bzr, "status", "-SV", nullptr };
StatusParser out(this, "status-out> ");
OutputLogger err(this->Log, "status-err> ");
this->RunChild(bzr_status, &out, &err);
return true;
}