1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-14 02:08:27 +08:00

StdIo: Provide metadata about stdin, stdout, stderr streams

Detect the kind of terminal to which they are attached, if any.

Issue: #26924
This commit is contained in:
Brad King
2025-05-06 11:55:42 -04:00
parent 151a635325
commit d6a1ff59f1
6 changed files with 323 additions and 1 deletions

View File

@@ -470,6 +470,8 @@ add_library(
cmStateTypes.h
cmStdIoInit.h
cmStdIoInit.cxx
cmStdIoStream.h
cmStdIoStream.cxx
cmStringAlgorithms.cxx
cmStringAlgorithms.h
cmSyntheticTargetCache.h

View File

@@ -5,6 +5,7 @@
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <fcntl.h>
@@ -18,6 +19,8 @@
# include <unistd.h>
#endif
#include "cmStdIoStream.h"
namespace cm {
namespace StdIo {
@@ -82,7 +85,11 @@ struct InitStdPipes
class Globals
{
public:
std::ios::Init InitIos;
InitStdPipes InitPipes;
IStream StdIn{ std::cin, stdin };
OStream StdOut{ std::cout, stdout };
OStream StdErr{ std::cerr, stderr };
static Globals& Get();
};
@@ -98,5 +105,20 @@ Init::Init()
Globals::Get();
}
IStream& In()
{
return Globals::Get().StdIn;
}
OStream& Out()
{
return Globals::Get().StdOut;
}
OStream& Err()
{
return Globals::Get().StdErr;
}
}
}

161
Source/cmStdIoStream.cxx Normal file
View File

@@ -0,0 +1,161 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmStdIoStream.h"
#include <algorithm>
#include <array>
#include <cstdio>
#include <istream> // IWYU pragma: keep
#include <ostream> // IWYU pragma: keep
#ifdef _WIN32
# include <windows.h>
# include <io.h> // for _get_osfhandle
#else
# include <string>
# include <cm/optional>
# include <cm/string_view>
# include <cmext/string_view>
# include <unistd.h>
#endif
#include "cm_fileno.hxx"
#ifndef _WIN32
# include "cmSystemTools.h"
#endif
namespace cm {
namespace StdIo {
namespace {
#ifndef _WIN32
// List of known `TERM` names that support VT100 escape sequences.
// Order by `LC_COLLATE=C sort` to search using `std::lower_bound`.
std::array<cm::string_view, 56> const kVT100Names{ {
"Eterm"_s,
"alacritty"_s,
"alacritty-direct"_s,
"ansi"_s,
"color-xterm"_s,
"con132x25"_s,
"con132x30"_s,
"con132x43"_s,
"con132x60"_s,
"con80x25"_s,
"con80x28"_s,
"con80x30"_s,
"con80x43"_s,
"con80x50"_s,
"con80x60"_s,
"cons25"_s,
"console"_s,
"cygwin"_s,
"dtterm"_s,
"eterm-color"_s,
"gnome"_s,
"gnome-256color"_s,
"konsole"_s,
"konsole-256color"_s,
"kterm"_s,
"linux"_s,
"linux-c"_s,
"mach-color"_s,
"mlterm"_s,
"msys"_s,
"putty"_s,
"putty-256color"_s,
"rxvt"_s,
"rxvt-256color"_s,
"rxvt-cygwin"_s,
"rxvt-cygwin-native"_s,
"rxvt-unicode"_s,
"rxvt-unicode-256color"_s,
"screen"_s,
"screen-256color"_s,
"screen-256color-bce"_s,
"screen-bce"_s,
"screen-w"_s,
"screen.linux"_s,
"st-256color"_s,
"tmux"_s,
"tmux-256color"_s,
"vt100"_s,
"xterm"_s,
"xterm-16color"_s,
"xterm-256color"_s,
"xterm-88color"_s,
"xterm-color"_s,
"xterm-debian"_s,
"xterm-kitty"_s,
"xterm-termite"_s,
} };
bool TermIsVT100()
{
if (cm::optional<std::string> term = cmSystemTools::GetEnvVar("TERM")) {
// NOLINTNEXTLINE(readability-qualified-auto)
auto i = std::lower_bound(kVT100Names.begin(), kVT100Names.end(), *term);
if (i != kVT100Names.end() && *i == *term) {
return true;
}
}
return false;
}
#endif
} // anonymous namespace
Stream::Stream(std::ios& s, FILE* file, Direction direction)
: IOS_(s)
, FD_(cm_fileno(file))
{
#ifdef _WIN32
DWORD mode;
auto h = reinterpret_cast<HANDLE>(_get_osfhandle(this->FD_));
if (GetConsoleMode(h, &mode)) {
this->Console_ = h;
DWORD vtMode = mode |
(direction == Direction::In ? ENABLE_VIRTUAL_TERMINAL_INPUT
: ENABLE_VIRTUAL_TERMINAL_PROCESSING);
if (SetConsoleMode(this->Console_, vtMode)) {
this->Kind_ = TermKind::VT100;
} else {
SetConsoleMode(this->Console_, mode);
this->Kind_ = TermKind::Console;
}
}
#else
static_cast<void>(direction);
if (isatty(this->FD_) && TermIsVT100()) {
this->Kind_ = TermKind::VT100;
}
#endif
}
IStream::IStream(std::istream& is, FILE* file)
: Stream(is, file, Direction::In)
{
}
std::istream& IStream::IOS() const
{
return dynamic_cast<std::istream&>(this->Stream::IOS());
}
OStream::OStream(std::ostream& os, FILE* file)
: Stream(os, file, Direction::Out)
{
}
std::ostream& OStream::IOS() const
{
return dynamic_cast<std::ostream&>(this->Stream::IOS());
}
}
}

103
Source/cmStdIoStream.h Normal file
View File

@@ -0,0 +1,103 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <cstdio>
#include <iosfwd>
namespace cm {
namespace StdIo {
/**
* Identify the kind of terminal to which a stream is attached, if any.
*/
enum class TermKind
{
/** Not an interactive terminal. */
None,
/** A VT100 terminal. */
VT100,
#ifdef _WIN32
/** A Windows Console that does not support VT100 sequences. */
Console,
#endif
};
/**
* Represent stdin, stdout, or stderr stream metadata.
*/
class Stream
{
public:
/** The kind of terminal to which the stream is attached, if any. */
TermKind Kind() const { return this->Kind_; }
/** The underlying C++ stream. */
std::ios& IOS() const { return this->IOS_; }
/** The underlying file descriptor. */
int FD() const { return this->FD_; }
#ifdef _WIN32
/** The underlying HANDLE of an attached Windows Console, if any. */
void* Console() const { return this->Console_; }
#endif
protected:
enum class Direction
{
In,
Out,
};
Stream(std::ios& s, FILE* file, Direction direction);
private:
std::ios& IOS_;
int FD_ = -1;
TermKind Kind_ = TermKind::None;
#ifdef _WIN32
void* Console_ = nullptr;
#endif
};
/**
* Represent stdin metadata.
*/
class IStream : public Stream
{
friend class Globals;
IStream(std::istream& is, FILE* file);
public:
/** The underlying C++ stream. */
std::istream& IOS() const;
};
/**
* Represent stdout or stderr metadata.
*/
class OStream : public Stream
{
friend class Globals;
OStream(std::ostream& os, FILE* file);
public:
/** The underlying C++ stream. */
std::ostream& IOS() const;
};
/** Metadata for stdin. */
IStream& In();
/** Metadata for stdout. */
OStream& Out();
/** Metadata for stderr. */
OStream& Err();
}
}

View File

@@ -1,15 +1,48 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include <cm/string_view>
#include <cmext/string_view>
#include "cmStdIoInit.h"
#include "cmStdIoStream.h"
#include "testCommon.h"
namespace {
void printTermKind(cm::string_view t, cm::StdIo::Stream& s)
{
switch (s.Kind()) {
case cm::StdIo::TermKind::None:
std::cout << " " << t << " is not a terminal.\n";
break;
case cm::StdIo::TermKind::VT100:
std::cout << " " << t << " is a VT100 terminal.\n";
break;
#ifdef _WIN32
case cm::StdIo::TermKind::Console:
std::cout << " " << t << " is a Windows Console.\n";
break;
#endif
};
}
bool testStream()
{
std::cout << "testStream()\n";
printTermKind("stdin"_s, cm::StdIo::In());
printTermKind("stdout"_s, cm::StdIo::Out());
printTermKind("stderr"_s, cm::StdIo::Err());
return true;
}
}
int testStdIo(int /*unused*/, char* /*unused*/[])
{
cm::StdIo::Init();
return runTests({});
return runTests({
testStream,
});
}

View File

@@ -490,6 +490,7 @@ CMAKE_CXX_SOURCES="\
cmStateDirectory \
cmStateSnapshot \
cmStdIoInit \
cmStdIoStream \
cmString \
cmStringAlgorithms \
cmStringReplaceHelper \