mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-01 00:33:05 +08:00
Remove elvish-stub.
This commit is contained in:
parent
05069da92f
commit
36271874d8
19
Makefile
19
Makefile
|
@ -3,9 +3,8 @@ PKG_COVERS := $(shell go list ./... | grep -v /vendor/ | grep "^github.com/elves
|
|||
COVER_MODE := count
|
||||
|
||||
FIRST_GOPATH=$(shell go env GOPATH | cut -d: -f1)
|
||||
STUB := $(FIRST_GOPATH)/bin/elvish-stub
|
||||
|
||||
default: get stub test
|
||||
default: get test
|
||||
|
||||
get:
|
||||
go get .
|
||||
|
@ -13,16 +12,8 @@ get:
|
|||
generate:
|
||||
go generate ./...
|
||||
|
||||
stub: $(STUB)
|
||||
|
||||
$(STUB): ./stubimpl/main.c
|
||||
test -n $(FIRST_GOPATH)
|
||||
mkdir -p $(FIRST_GOPATH)/bin
|
||||
$(CC) ./stubimpl/main.c -o $@
|
||||
|
||||
test: stub
|
||||
test:
|
||||
go test $(PKGS)
|
||||
: ./stubimpl/test.sh
|
||||
|
||||
cover/%: %
|
||||
mkdir -p cover
|
||||
|
@ -36,8 +27,8 @@ goveralls: cover/all
|
|||
go get -u github.com/mattn/goveralls
|
||||
$(FIRST_GOPATH)/bin/goveralls -coverprofile=cover/all -service=travis-ci \
|
||||
|
||||
upload: get stub
|
||||
tar cfz elvish.tar.gz -C $(FIRST_GOPATH)/bin elvish elvish-stub
|
||||
upload: get
|
||||
tar cfz elvish.tar.gz -C $(FIRST_GOPATH)/bin elvish
|
||||
test "$(TRAVIS_GO_VERSION)" = 1.7 -a "$(TRAVIS_PULL_REQUEST)" = false \
|
||||
&& test -n "$(TRAVIS_TAG)" -o "$(TRAVIS_BRANCH)" = master \
|
||||
&& curl http://ul.elvish.io:6060/ -F name=elvish-$(TRAVIS_OS_NAME).tar.gz \
|
||||
|
@ -46,4 +37,4 @@ upload: get stub
|
|||
|
||||
travis: goveralls upload
|
||||
|
||||
.PHONY: default get generate stub test goveralls upload travis
|
||||
.PHONY: default get generate test goveralls upload travis
|
||||
|
|
|
@ -150,7 +150,7 @@ Elvish mimics bash and zsh in a lot of places. The following shows some key diff
|
|||
|
||||
Go >= 1.6 is required. Linux is fully supported. It is likely to work on BSDs and Mac OS X. Windows is **not** supported yet.
|
||||
|
||||
The main binary can be installed using `go get github.com/elves/elvish`. There is also an auxiliary program called elvish-stub; install it with `make stub`. Elvish is functional without the stub, but job control features depend on it.
|
||||
Elvish is a go-gettable package, and can be installed using `go get github.com/elves/elvish`.
|
||||
|
||||
If you are lazy and use `bash` or `zsh` now, here is something you can copy-paste into your terminal:
|
||||
|
||||
|
@ -160,7 +160,6 @@ export PATH=$PATH:$GOPATH/bin
|
|||
mkdir -p $GOPATH
|
||||
|
||||
go get github.com/elves/elvish
|
||||
make -C $GOPATH/src/github.com/elves/elvish stub
|
||||
|
||||
for f in ~/.bashrc ~/.zshrc; do
|
||||
printf 'export %s=%s\n' GOPATH '$HOME/go' PATH '$PATH:$GOPATH/bin' >> $f
|
||||
|
|
|
@ -1169,7 +1169,4 @@ func preExit(ec *EvalCtx) {
|
|||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
if ec.Stub != nil {
|
||||
ec.Stub.Terminate()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/stub"
|
||||
)
|
||||
|
||||
// Op is an operation on an EvalCtx.
|
||||
|
@ -41,14 +40,7 @@ func (cp *compiler) pipeline(n *parse.Pipeline) OpFunc {
|
|||
bg := n.Background
|
||||
if bg {
|
||||
ec = ec.fork("background job " + n.SourceText())
|
||||
|
||||
// Set up a new stub.
|
||||
st, err := stub.NewStub(os.Stderr)
|
||||
if err != nil {
|
||||
throwf("failed to spawn stub: %v", err)
|
||||
}
|
||||
st.SetTitle(n.SourceText())
|
||||
ec.Stub = st
|
||||
ec.intCh = nil
|
||||
|
||||
if ec.Editor != nil {
|
||||
// TODO: Redirect output in interactive mode so that the line
|
||||
|
@ -104,7 +96,6 @@ func (cp *compiler) pipeline(n *parse.Pipeline) OpFunc {
|
|||
// Background job, wait for form termination asynchronously.
|
||||
go func() {
|
||||
wg.Wait()
|
||||
ec.Stub.Terminate()
|
||||
msg := "job " + n.SourceText() + " finished"
|
||||
if !allok(errors) {
|
||||
msg += ", errors = " + makeCompositeError(errors).Error()
|
||||
|
|
64
eval/eval.go
64
eval/eval.go
|
@ -19,7 +19,6 @@ import (
|
|||
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/store"
|
||||
"github.com/elves/elvish/stub"
|
||||
"github.com/elves/elvish/sys"
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
@ -40,7 +39,6 @@ type Evaler struct {
|
|||
Modules map[string]Namespace
|
||||
Store *store.Store
|
||||
Editor Editor
|
||||
Stub *stub.Stub
|
||||
intCh chan struct{}
|
||||
}
|
||||
|
||||
|
@ -65,7 +63,7 @@ func (ec *EvalCtx) falsify() {
|
|||
|
||||
// NewEvaler creates a new Evaler.
|
||||
func NewEvaler(st *store.Store) *Evaler {
|
||||
return &Evaler{Namespace{}, map[string]Namespace{}, st, nil, nil, nil}
|
||||
return &Evaler{Namespace{}, map[string]Namespace{}, st, nil, nil}
|
||||
}
|
||||
|
||||
func (e *Evaler) searchPaths() []string {
|
||||
|
@ -166,47 +164,32 @@ func (ev *Evaler) Eval(op Op, name, text string) error {
|
|||
{File: os.Stderr, Chan: BlackholeChan},
|
||||
}
|
||||
|
||||
signal.Ignore(syscall.SIGTTIN)
|
||||
signal.Ignore(syscall.SIGTTOU)
|
||||
// signal.Ignore(syscall.SIGTTIN)
|
||||
// signal.Ignore(syscall.SIGTTOU)
|
||||
stopSigGoroutine := make(chan struct{})
|
||||
sigGoRoutineDone := make(chan struct{})
|
||||
// XXX Should use fd of /dev/terminal instead of 0.
|
||||
if ev.Stub != nil && ev.Stub.Alive() && sys.IsATTY(0) {
|
||||
ev.Stub.SetTitle(summarize(text))
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
dir = "/"
|
||||
}
|
||||
ev.Stub.Chdir(dir)
|
||||
err = sys.Tcsetpgrp(0, ev.Stub.Process().Pid)
|
||||
if err != nil {
|
||||
fmt.Println("failed to put stub in foreground:", err)
|
||||
}
|
||||
|
||||
ev.intCh = make(chan struct{})
|
||||
go func() {
|
||||
closedIntCh := false
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case sig := <-ev.Stub.Signals():
|
||||
switch sig {
|
||||
case syscall.SIGINT, syscall.SIGQUIT:
|
||||
if !closedIntCh {
|
||||
close(ev.intCh)
|
||||
closedIntCh = true
|
||||
}
|
||||
}
|
||||
case <-stopSigGoroutine:
|
||||
break loop
|
||||
// Set up intCh.
|
||||
ev.intCh = make(chan struct{})
|
||||
sigCh := make(chan os.Signal)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGQUIT)
|
||||
go func() {
|
||||
closedIntCh := false
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-sigCh:
|
||||
if !closedIntCh {
|
||||
close(ev.intCh)
|
||||
closedIntCh = true
|
||||
}
|
||||
case <-stopSigGoroutine:
|
||||
break loop
|
||||
}
|
||||
ev.intCh = nil
|
||||
close(sigGoRoutineDone)
|
||||
}()
|
||||
} else {
|
||||
}
|
||||
ev.intCh = nil
|
||||
signal.Stop(sigCh)
|
||||
close(sigGoRoutineDone)
|
||||
}
|
||||
}()
|
||||
|
||||
ret, err := ev.eval(op, ports, name, text)
|
||||
close(outCh)
|
||||
|
@ -218,7 +201,8 @@ func (ev *Evaler) Eval(op Op, name, text string) error {
|
|||
fmt.Println(falseIndicator)
|
||||
}
|
||||
|
||||
// XXX Should use fd of /dev/tty instead of 0.
|
||||
// Put myself in foreground, in case some command has put me in background.
|
||||
// XXX Should probably use fd of /dev/tty instead of 0.
|
||||
if sys.IsATTY(0) {
|
||||
err := sys.Tcsetpgrp(0, syscall.Getpgrp())
|
||||
if err != nil {
|
||||
|
|
|
@ -66,10 +66,6 @@ func (e ExternalCmd) Call(ec *EvalCtx, argVals []Value, opts map[string]Value) {
|
|||
}
|
||||
|
||||
sys := syscall.SysProcAttr{}
|
||||
if ec.Stub != nil && ec.Stub.Alive() {
|
||||
sys.Setpgid = true
|
||||
sys.Pgid = ec.Stub.Process().Pid
|
||||
}
|
||||
attr := syscall.ProcAttr{Env: os.Environ(), Files: files[:], Sys: &sys}
|
||||
|
||||
path, err := ec.Search(e.Name)
|
||||
|
|
12
run/run.go
12
run/run.go
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/store"
|
||||
"github.com/elves/elvish/stub"
|
||||
"github.com/elves/elvish/sys"
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
@ -83,17 +82,6 @@ func Main() {
|
|||
}
|
||||
}()
|
||||
|
||||
stub, err := stub.NewStub(os.Stderr)
|
||||
if err != nil {
|
||||
fmt.Println("failed to spawn stub:", err)
|
||||
} else {
|
||||
ev.Stub = stub
|
||||
go func() {
|
||||
<-stub.State()
|
||||
fmt.Println("stub has died")
|
||||
}()
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
if *cmd {
|
||||
evalText(ev, "code from -c", args[0])
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
package stub
|
||||
|
||||
type BadSignal struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
func (bs BadSignal) String() string {
|
||||
return "bad signal: " + bs.Error.Error()
|
||||
}
|
||||
|
||||
func (BadSignal) Signal() {}
|
156
stub/stub.go
156
stub/stub.go
|
@ -1,156 +0,0 @@
|
|||
// Package stub is used to start and manage an elvish-stub process.
|
||||
package stub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
var Logger = util.GetLogger("[stub] ")
|
||||
var stubname = "elvish-stub"
|
||||
|
||||
type Stub struct {
|
||||
process *os.Process
|
||||
// write is the other end of stdin of the stub.
|
||||
write *os.File
|
||||
// read is the other end of stdout of the stub.
|
||||
read *os.File
|
||||
sigch chan os.Signal
|
||||
statech chan struct{}
|
||||
}
|
||||
|
||||
var stubEnv = []string{"A=BCDEFGHIJKLMNOPQRSTUVWXYZ"}
|
||||
|
||||
// NewStub spawns a new stub. The specified stderr is used for the subprocess.
|
||||
func NewStub(stderr *os.File) (*Stub, error) {
|
||||
// Find stub.
|
||||
stubpath, err := searchStub()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search: %v", err)
|
||||
}
|
||||
|
||||
// Make pipes.
|
||||
stdin, write, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pipe: %v", err)
|
||||
}
|
||||
read, stdout, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pipe: %v", err)
|
||||
}
|
||||
|
||||
// Spawn stub.
|
||||
attr := os.ProcAttr{
|
||||
Env: stubEnv,
|
||||
Files: []*os.File{stdin, stdout, stderr},
|
||||
Sys: &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
},
|
||||
}
|
||||
process, err := os.StartProcess(stubpath, []string{stubpath}, &attr)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("spawn: %v", err)
|
||||
}
|
||||
|
||||
// Wait for startup message.
|
||||
_, err = fmt.Fscanf(read, "ok\n")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read startup msg: %v", err)
|
||||
}
|
||||
|
||||
// Spawn signal relayer and waiter.
|
||||
sigch := make(chan os.Signal)
|
||||
statech := make(chan struct{})
|
||||
go relaySignals(read, sigch)
|
||||
go wait(process, statech)
|
||||
|
||||
return &Stub{process, write, read, sigch, statech}, nil
|
||||
}
|
||||
|
||||
func searchStub() (string, error) {
|
||||
// os.Args[0] contains an absolute path. Find elvish-stub in the same
|
||||
// directory where elvish was started.
|
||||
if len(os.Args) > 0 && path.IsAbs(os.Args[0]) {
|
||||
stubpath := path.Join(path.Dir(os.Args[0]), stubname)
|
||||
if util.IsExecutable(stubpath) {
|
||||
return stubpath, nil
|
||||
}
|
||||
}
|
||||
return util.Search(strings.Split(os.Getenv("PATH"), ":"), stubname)
|
||||
}
|
||||
|
||||
func (stub *Stub) Process() *os.Process {
|
||||
return stub.process
|
||||
}
|
||||
|
||||
// Terminate terminates the stub.
|
||||
func (stub *Stub) Terminate() {
|
||||
stub.write.Close()
|
||||
}
|
||||
|
||||
// SetTitle sets the title of the stub.
|
||||
func (stub *Stub) SetTitle(s string) {
|
||||
s = strings.TrimSpace(s)
|
||||
fmt.Fprintf(stub.write, "t%04d%s", len(s), s)
|
||||
}
|
||||
|
||||
func (stub *Stub) Chdir(dir string) {
|
||||
fmt.Fprintf(stub.write, "d%04d%s", len(dir), dir)
|
||||
}
|
||||
|
||||
// Signals returns a channel into which signals sent to the stub are relayed.
|
||||
func (stub *Stub) Signals() <-chan os.Signal {
|
||||
return stub.sigch
|
||||
}
|
||||
|
||||
// State returns a channel that is closed when the stub exits.
|
||||
func (stub *Stub) State() <-chan struct{} {
|
||||
return stub.statech
|
||||
}
|
||||
|
||||
// Alive reports whether the stub is alive.
|
||||
func (stub *Stub) Alive() bool {
|
||||
select {
|
||||
case <-stub.statech:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// relaySignals relays output of the stub to sigch, assuming that outputs
|
||||
// represent signal numbers.
|
||||
func relaySignals(reader io.Reader, sigch chan<- os.Signal) {
|
||||
for {
|
||||
var signum int
|
||||
_, err := fmt.Fscanf(reader, "%d", &signum)
|
||||
Logger.Println("signal:", signum, err)
|
||||
if err != nil {
|
||||
sigch <- BadSignal{err}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
sig := syscall.Signal(signum)
|
||||
sigch <- sig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func wait(proc *os.Process, ch chan<- struct{}) {
|
||||
for {
|
||||
state, err := proc.Wait()
|
||||
Logger.Println("wait:", state, err)
|
||||
if err != nil || state.Exited() {
|
||||
break
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package stub
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testSignal(t *testing.T, stub *Stub, sig syscall.Signal) {
|
||||
stub.Process().Signal(sig)
|
||||
select {
|
||||
case gotsig := <-stub.Signals():
|
||||
if gotsig != sig {
|
||||
t.Errorf("got %v, want %v", gotsig, sig)
|
||||
}
|
||||
case <-time.After(time.Millisecond * 10):
|
||||
t.Errorf("signal not relayed after 10ms")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStub(t *testing.T) {
|
||||
stub, err := NewStub(os.Stderr)
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
// Non-INT signals should be relayed onto Signals, but not IntSignals.
|
||||
testSignal(t, stub, syscall.SIGUSR1)
|
||||
testSignal(t, stub, syscall.SIGINT)
|
||||
|
||||
// Setting title and dir of the stub shouldn't cause the stub to terminate,
|
||||
// even if the payload is invalid or contains newlines.
|
||||
stub.SetTitle("x\ny")
|
||||
stub.Chdir("/xyz/haha")
|
||||
select {
|
||||
case <-stub.State():
|
||||
t.Errorf("stub exited prematurely")
|
||||
default:
|
||||
}
|
||||
|
||||
// Calling Terminate should really terminate the process.
|
||||
stub.Terminate()
|
||||
select {
|
||||
case <-stub.State():
|
||||
case <-time.After(time.Millisecond * 10):
|
||||
t.Errorf("stub didn't exit within 10ms")
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
void must(int ok, char *s) {
|
||||
if (!ok) {
|
||||
perror(s);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void handler(int signum) {
|
||||
char s[5] = " \n";
|
||||
char *p = &s[3];
|
||||
while (signum > 0) {
|
||||
p--;
|
||||
*p = (signum % 10) + '0';
|
||||
signum /= 10;
|
||||
}
|
||||
must(write(1, p, s+4-p) != -1, "write signum");
|
||||
}
|
||||
|
||||
enum { ARGV0MAX = 32 };
|
||||
|
||||
void badmsg() {
|
||||
must(write(1, "bad msg\n", 8), "write bad msg");
|
||||
}
|
||||
|
||||
// read as much as possible, but no more than n.
|
||||
int readn(int fd, char *buf, int n) {
|
||||
int nr, nrall;
|
||||
nrall = 0;
|
||||
while (n > 0 && (nr = read(fd, buf, n)) > 0) {
|
||||
buf += nr;
|
||||
nrall += nr;
|
||||
n -= nr;
|
||||
}
|
||||
return nrall;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int i;
|
||||
for (i = 1; i <= 64; i++) {
|
||||
signal(i, handler);
|
||||
}
|
||||
signal(SIGTTIN, SIG_IGN);
|
||||
signal(SIGTTOU, SIG_IGN);
|
||||
|
||||
must(write(1, "ok\n", 3) != -1, "write ok");
|
||||
|
||||
int nr;
|
||||
char opbuf[6] = "12345";
|
||||
while ((nr = readn(0, opbuf, 5)) == 5) {
|
||||
char opcode = opbuf[0];
|
||||
int len = atoi(opbuf+1);
|
||||
char *buf = malloc(len+1);
|
||||
buf[len] = '\0';
|
||||
//fprintf(stderr, "code = %c, len = %d\n", opcode, len);
|
||||
if (readn(0, buf, len) < len) {
|
||||
free(buf);
|
||||
break;
|
||||
}
|
||||
//fprintf(stderr, "data = %s\n", buf);
|
||||
switch (opcode) {
|
||||
case 'd':
|
||||
// Change directory.
|
||||
chdir(buf);
|
||||
break;
|
||||
case 't':
|
||||
if (len > ARGV0MAX) {
|
||||
buf[ARGV0MAX] = '\0';
|
||||
}
|
||||
strcpy(argv[0], buf);
|
||||
break;
|
||||
default:
|
||||
badmsg();
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
if (nr != 0) {
|
||||
badmsg();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
#!/bin/sh -x
|
||||
|
||||
# This test is broken. I haven't yet found a way to start a background process
|
||||
# with a stdin that I can later write to. Presumably this can be doen with a
|
||||
# pipe, but there is no way to create pipes explicitly in sh(1).
|
||||
|
||||
fail() {
|
||||
echo "$*; log left in $dir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
waitlog() {
|
||||
for i in `seq 51`; do
|
||||
test $i = 51 && {
|
||||
return 1
|
||||
}
|
||||
test "$(tail -n1 log)" = "$*" && break
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
|
||||
dir=`mktemp -d elvishXXXX`
|
||||
cd "$dir"
|
||||
mkfifo fifo
|
||||
|
||||
# Start elvish-stub.
|
||||
elvish-stub > log < fifo &
|
||||
stub=$!
|
||||
|
||||
# Wait for startup message.
|
||||
waitlog ok || fail "didn't write startup message"
|
||||
|
||||
# Send SIG{INT,TERM,TSTP}
|
||||
for sig in 2 15 20; do
|
||||
kill -$sig $stub
|
||||
ps $stub >/dev/null || {
|
||||
fail "stub killed by signal #$sig"
|
||||
}
|
||||
waitlog $sig || fail "didn't record signal #$sig"
|
||||
done
|
||||
|
||||
# Really kill stub.
|
||||
kill -9 $stub
|
||||
|
||||
rmdir "$dir"
|
Loading…
Reference in New Issue
Block a user