Remove elvish-stub.

This commit is contained in:
Qi Xiao 2016-10-26 17:36:26 +08:00
parent 05069da92f
commit 36271874d8
12 changed files with 31 additions and 434 deletions

View File

@ -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

View File

@ -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

View File

@ -1169,7 +1169,4 @@ func preExit(ec *EvalCtx) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
if ec.Stub != nil {
ec.Stub.Terminate()
}
}

View File

@ -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()

View File

@ -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 {

View File

@ -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)

View File

@ -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])

View File

@ -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() {}

View File

@ -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)
}

View File

@ -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")
}
}

View File

@ -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;
}

View File

@ -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"