mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-21 02:37:43 -05:00
Go 1.17 and earlier used a different syntax for build constraint comments, starting with "+build". Go 1.18 changed this to the modern "go:build" form as part of standardizing the structure of toolchain directive comments, and so for a while it was convention to include comments in both styles to allow building with both old and new Go compilers. However, Go 1.17 is no longer supported, and regardless of that we only expect OpenTofu to be built with the specific version we have selected in "go.mod" and ".go-version" anyway, so we no longer need the legacy form of these comments: the all supported Go toolchains now support the new form, which this commit retains. golangci-lint v2.6.0 includes a check for this legacy form, so removing this will also allow us to upgrade to a newer version of that linter aggregator in a future commit. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
167 lines
5.9 KiB
Go
167 lines
5.9 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
//go:build windows
|
|
|
|
package terminal
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"syscall"
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
// We're continuing to use this third-party library on Windows because it
|
|
// has the additional IsCygwinTerminal function, which includes some useful
|
|
// heuristics for recognizing when a pipe seems to be connected to a
|
|
// legacy terminal emulator on Windows versions that lack true pty support.
|
|
// We now use golang.org/x/term's functionality on other platforms.
|
|
isatty "github.com/mattn/go-isatty"
|
|
)
|
|
|
|
func configureOutputHandle(f *os.File) (*OutputStream, error) {
|
|
ret := &OutputStream{
|
|
File: f,
|
|
}
|
|
|
|
if fd := f.Fd(); isatty.IsTerminal(fd) {
|
|
// We have a few things to deal with here:
|
|
// - Activating UTF-8 output support (mandatory)
|
|
// - Activating virtual terminal support (optional)
|
|
// These will not succeed on Windows 8 or early versions of Windows 10.
|
|
|
|
// UTF-8 support means switching the console "code page" to CP_UTF8.
|
|
// Notice that this doesn't take the specific file descriptor, because
|
|
// the console is just ambiently associated with our process.
|
|
err := SetConsoleOutputCP(CP_UTF8)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to set the console to UTF-8 mode; you may need to use a newer version of Windows: %w", err)
|
|
}
|
|
|
|
// If the console also allows us to turn on
|
|
// ENABLE_VIRTUAL_TERMINAL_PROCESSING then we can potentially use VT
|
|
// output, although the methods of Settings will make the final
|
|
// determination on that because we might have some handles pointing at
|
|
// terminals and other handles pointing at files/pipes.
|
|
ret.getColumns = getColumnsWindowsConsole
|
|
var mode uint32
|
|
err = windows.GetConsoleMode(windows.Handle(fd), &mode)
|
|
if err != nil {
|
|
return ret, nil // We'll treat this as success but without VT support
|
|
}
|
|
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
err = windows.SetConsoleMode(windows.Handle(fd), mode)
|
|
if err != nil {
|
|
return ret, nil // We'll treat this as success but without VT support
|
|
}
|
|
|
|
// If we get here then we've successfully turned on VT processing, so
|
|
// we can return an OutputStream that answers true when asked if it
|
|
// is a Terminal.
|
|
ret.isTerminal = staticTrue
|
|
return ret, nil
|
|
|
|
} else if isatty.IsCygwinTerminal(fd) {
|
|
// Cygwin terminals -- and other VT100 "fakers" for older versions of
|
|
// Windows -- are not really terminals in the usual sense, but rather
|
|
// are pipes between the child process (OpenTofu) and the terminal
|
|
// emulator. isatty.IsCygwinTerminal uses some heuristics to
|
|
// distinguish those pipes from other pipes we might see if the user
|
|
// were, for example, using the | operator on the command line.
|
|
// If we get in here then we'll assume that we can send VT100 sequences
|
|
// to this stream, even though it isn't a terminal in the usual sense.
|
|
|
|
ret.isTerminal = staticTrue
|
|
// TODO: Is it possible to detect the width of these fake terminals?
|
|
return ret, nil
|
|
}
|
|
|
|
// If we fall out here then we have a non-terminal filehandle, so we'll
|
|
// just accept all of the default OutputStream behaviors
|
|
return ret, nil
|
|
}
|
|
|
|
func configureInputHandle(f *os.File) (*InputStream, error) {
|
|
ret := &InputStream{
|
|
File: f,
|
|
}
|
|
|
|
if fd := f.Fd(); isatty.IsTerminal(fd) {
|
|
// We have to activate UTF-8 input, or else we fail. This will not
|
|
// succeed on Windows 8 or early versions of Windows 10.
|
|
// Notice that this doesn't take the specific file descriptor, because
|
|
// the console is just ambiently associated with our process.
|
|
err := SetConsoleCP(CP_UTF8)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to set the console to UTF-8 mode; you may need to use a newer version of Windows: %w", err)
|
|
}
|
|
ret.isTerminal = staticTrue
|
|
return ret, nil
|
|
} else if isatty.IsCygwinTerminal(fd) {
|
|
// As with the output handles above, we'll use isatty's heuristic to
|
|
// pretend that a pipe from mintty or a similar userspace terminal
|
|
// emulator is actually a terminal.
|
|
ret.isTerminal = staticTrue
|
|
return ret, nil
|
|
}
|
|
|
|
// If we fall out here then we have a non-terminal filehandle, so we'll
|
|
// just accept all of the default InputStream behaviors
|
|
return ret, nil
|
|
}
|
|
|
|
func getColumnsWindowsConsole(f *os.File) int {
|
|
// We'll just unconditionally ask the given file for its console buffer
|
|
// info here, and let it fail if the file isn't actually a console.
|
|
// (In practice, the init functions above only hook up this function
|
|
// if the handle looks like a console, so this should succeed.)
|
|
var info windows.ConsoleScreenBufferInfo
|
|
err := windows.GetConsoleScreenBufferInfo(windows.Handle(f.Fd()), &info)
|
|
if err != nil {
|
|
return defaultColumns
|
|
}
|
|
return int(info.Size.X)
|
|
}
|
|
|
|
// Unfortunately not all of the Windows kernel functions we need are in
|
|
// x/sys/windows at the time of writing, so we need to call some of them
|
|
// directly. (If you're maintaining this in future and have the capacity to
|
|
// test it well, consider checking if these functions have been added upstream
|
|
// yet and switch to their wrapper stubs if so.
|
|
var modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
var procSetConsoleCP = modkernel32.NewProc("SetConsoleCP")
|
|
var procSetConsoleOutputCP = modkernel32.NewProc("SetConsoleOutputCP")
|
|
|
|
const CP_UTF8 = 65001
|
|
|
|
// (These are written in the style of the stubs in x/sys/windows, which is
|
|
// a little non-idiomatic just due to the awkwardness of the low-level syscall
|
|
// interface.)
|
|
|
|
func SetConsoleCP(codepageID uint32) (err error) {
|
|
r1, _, e1 := syscall.Syscall(procSetConsoleCP.Addr(), 1, uintptr(codepageID), 0, 0)
|
|
if r1 == 0 {
|
|
err = e1
|
|
}
|
|
return
|
|
}
|
|
|
|
func SetConsoleOutputCP(codepageID uint32) (err error) {
|
|
r1, _, e1 := syscall.Syscall(procSetConsoleOutputCP.Addr(), 1, uintptr(codepageID), 0, 0)
|
|
if r1 == 0 {
|
|
err = e1
|
|
}
|
|
return
|
|
}
|
|
|
|
func staticTrue(f *os.File) bool {
|
|
return true
|
|
}
|
|
|
|
func staticFalse(f *os.File) bool {
|
|
return false
|
|
}
|