1
0
mirror of https://github.com/beego/bee.git synced 2024-11-21 18:40:54 +00:00

Merge pull request #1 from beego/master

update from origin
This commit is contained in:
ansiz 2017-12-21 23:31:51 +08:00 committed by GitHub
commit 1937c6ddb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
298 changed files with 54613 additions and 11406 deletions

View File

@ -1,5 +1,22 @@
language: go
go:
- 1.6.3
- 1.7.3
- 1.8
install:
- export PATH=$PATH:$HOME/gopath/bin
- go get -u github.com/opennota/check/cmd/structcheck
- go get -u honnef.co/go/tools/cmd/gosimple
- go get -u honnef.co/go/tools/cmd/staticcheck
- go get -u honnef.co/go/tools/cmd/unused
- go get -u github.com/mdempsky/unconvert
- go get -u github.com/gordonklaus/ineffassign
script:
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
- go vet $(go list ./... | grep -v /vendor/)
- structcheck $(go list ./... | grep -v /vendor/)
- gosimple -ignore "$(cat gosimple.ignore)" $(go list ./... | grep -v /vendor/)
- staticcheck -ignore "$(cat staticcheck.ignore)" $(go list ./... | grep -v /vendor/)
- unused $(go list ./... | grep -v /vendor/)
- unconvert $(go list ./... | grep -v /vendor/)
- ineffassign .

View File

@ -1,7 +1,4 @@
version: 0
gopm:
enable: false
install: false
go_install: false
watch_ext: []
dir_structure:

View File

@ -1,3 +1,5 @@
VERSION = $(shell grep 'const version' cmd/commands/version/version.go | sed -E 's/.*"(.+)"$$/v\1/')
.PHONY: all test clean build install
GOFLAGS ?= $(GOFLAGS:)
@ -18,3 +20,10 @@ bench: install
clean:
go clean $(GOFLAGS) -i ./...
publish:
mkdir -p bin/$(VERSION)
cd bin/$(VERSION)
xgo -v -x --targets="windows/*,darwin/*,linux/386,linux/amd64,linux/arm-5,linux/arm64" -out bee_$(VERSION) github.com/beego/bee
cd ..
ghr -u beego -r bee $(VERSION) $(VERSION)

View File

@ -1,3 +1,4 @@
bee
===
@ -34,17 +35,20 @@ go get -u github.com/beego/bee
Bee provides a variety of commands which can be helpful at various stages of development. The top level commands include:
```
new Creates a Beego application
run Run the application by starting a local development server
pack Compresses a Beego application into a single file
api Creates a Beego API application
hprose Creates an RPC application based on Hprose and Beego frameworks
bale Transforms non-Go files to Go source files
version Prints the current Bee version
generate Source code generator
migrate Runs database migrations
api Creates a Beego API application
bale Transforms non-Go files to Go source files
fix Fixes your application by making it compatible with newer versions of Beego
dlv Start a debugging session using Delve
dockerize Generates a Dockerfile for your Beego application
generate Source code generator
hprose Creates an RPC application based on Hprose and Beego frameworks
new Creates a Beego application
pack Compresses a Beego application into a single file
rs Run customized scripts
run Run the application by starting a local development server
```
### bee version
@ -165,6 +169,38 @@ ______
For more information on the usage, run `bee help pack`.
### bee rs
Inspired by makefile / npm scripts.
Run script allows you to run arbitrary commands using Bee.
Custom commands are provided from the "scripts" object inside bee.json or Beefile.
To run a custom command, use: $ bee rs mycmd ARGS
```bash
$ bee help rs
USAGE
bee rs
DESCRIPTION
Run script allows you to run arbitrary commands using Bee.
Custom commands are provided from the "scripts" object inside bee.json or Beefile.
To run a custom command, use: $ bee rs mycmd ARGS
AVAILABLE SCRIPTS
gtest
APP_ENV=test APP_CONF_PATH=$(pwd)/conf go test -v -cover
gtestall
APP_ENV=test APP_CONF_PATH=$(pwd)/conf go test -v -cover $(go list ./... | grep -v /vendor/)
```
*Run your scripts with:*
```$ bee rs gtest tests/*.go```
```$ bee rs gtestall```
### bee api
To create a Beego API application:
@ -298,6 +334,37 @@ ______
For more information on the usage, run `bee help dockerize`.
### bee dlv
Bee can also help with debugging your application. To start a debugging session:
```bash
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.8.0
2017/03/22 11:17:05 INFO ▶ 0001 Starting Delve Debugger...
Type 'help' for list of commands.
(dlv) break main.main
Breakpoint 1 set at 0x40100f for main.main() ./main.go:8
(dlv) continue
> main.main() ./main.go:8 (hits goroutine(1):1 total:1) (PC: 0x40100f)
3: import (
4: _ "github.com/user/myapp/routers"
5: "github.com/astaxie/beego"
6: )
7:
=> 8: func main() {
9: beego.Run()
10: }
11:
```
For more information on the usage, run `bee help dlv`.
## Shortcuts
Because you'll likely type these generator commands over and over, it makes sense to create aliases:

View File

@ -1,280 +0,0 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import (
"bytes"
"errors"
"fmt"
"go/ast"
gobuild "go/build"
"go/doc"
"go/parser"
"go/token"
"io"
"io/ioutil"
"os"
"path"
"runtime"
"strings"
"time"
)
var cmdRouter = &Command{
UsageLine: "router",
Short: "auto-generate routers for the app controllers",
Long: `
`,
}
func init() {
cmdRouter.Run = autoRouter
}
func autoRouter(cmd *Command, args []string) int {
fmt.Println("[INFO] Starting auto-generating routers...")
return 0
}
// getControllerInfo returns controllers that embeded "beego.controller"
// and their methods of package in given path.
func getControllerInfo(path string) (map[string][]string, error) {
now := time.Now()
path = strings.TrimSuffix(path, "/")
dir, err := os.Open(path)
if err != nil {
return nil, err
}
fis, err := dir.Readdir(0)
if err != nil {
return nil, err
}
files := make([]*source, 0, len(fis))
for _, fi := range fis {
// Only load Go files
if strings.HasSuffix(fi.Name(), ".go") {
f, err := os.Open(path + "/" + fi.Name())
if err != nil {
return nil, err
}
p := make([]byte, fi.Size())
_, err = f.Read(p)
if err != nil {
return nil, err
}
f.Close()
files = append(files, &source{
name: path + "/" + fi.Name(),
data: p,
})
}
}
rw := &routerWalker{
pdoc: &Package{
ImportPath: path,
},
}
cm := make(map[string][]string)
pdoc, err := rw.build(files)
for _, t := range pdoc.Types {
// Check if embeded "beego.Controller".
if strings.Index(t.Decl, "beego.Controller") > -1 {
for _, f := range t.Methods {
cm[t.Name] = append(cm[t.Name], f.Name)
}
}
}
fmt.Println(time.Since(now))
return cm, nil
}
// source represents a source code file.
type source struct {
name string
data []byte
}
func (s *source) Name() string { return s.name }
func (s *source) Size() int64 { return int64(len(s.data)) }
func (s *source) Mode() os.FileMode { return 0 }
func (s *source) ModTime() time.Time { return time.Time{} }
func (s *source) IsDir() bool { return false }
func (s *source) Sys() interface{} { return nil }
// routerWalker holds the state used when building the documentation.
type routerWalker struct {
pdoc *Package
srcs map[string]*source // Source files
fset *token.FileSet
buf []byte // scratch space for printNode method
}
// Package represents full information and documentation for a package.
type Package struct {
ImportPath string
Types []*Type // Top-level declarations
}
// Type represents structs and interfaces.
type Type struct {
Name string // Type name
Decl string
Methods []*Func // Exported methods
}
// Func represents functions
type Func struct {
Name string
}
// build generates data from source files.
func (w *routerWalker) build(srcs []*source) (*Package, error) {
// Add source files to walker, I skipped references here
w.srcs = make(map[string]*source)
for _, src := range srcs {
w.srcs[src.name] = src
}
w.fset = token.NewFileSet()
// Find the package and associated files
ctxt := gobuild.Context{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
CgoEnabled: true,
JoinPath: path.Join,
IsAbsPath: path.IsAbs,
SplitPathList: func(list string) []string { return strings.Split(list, ":") },
IsDir: func(path string) bool { panic("unexpected") },
HasSubdir: func(root, dir string) (rel string, ok bool) { panic("unexpected") },
ReadDir: func(dir string) (fi []os.FileInfo, err error) { return w.readDir(dir) },
OpenFile: func(path string) (r io.ReadCloser, err error) { return w.openFile(path) },
Compiler: "gc",
}
bpkg, err := ctxt.ImportDir(w.pdoc.ImportPath, 0)
// Continue if there are no Go source files; we still want the directory info
_, nogo := err.(*gobuild.NoGoError)
if err != nil {
if nogo {
err = nil
} else {
return nil, errors.New("routerWalker.build -> " + err.Error())
}
}
// Parse the Go files
files := make(map[string]*ast.File)
for _, name := range append(bpkg.GoFiles, bpkg.CgoFiles...) {
file, err := parser.ParseFile(w.fset, name, w.srcs[name].data, parser.ParseComments)
if err != nil {
return nil, errors.New("routerWalker.build -> parse go files: " + err.Error())
}
files[name] = file
}
apkg, _ := ast.NewPackage(w.fset, files, simpleImporter, nil)
mode := doc.Mode(0)
if w.pdoc.ImportPath == "builtin" {
mode |= doc.AllDecls
}
pdoc := doc.New(apkg, w.pdoc.ImportPath, mode)
w.pdoc.Types = w.types(pdoc.Types)
return w.pdoc, err
}
func (w *routerWalker) funcs(fdocs []*doc.Func) []*Func {
var result []*Func
for _, d := range fdocs {
result = append(result, &Func{
Name: d.Name,
})
}
return result
}
func (w *routerWalker) types(tdocs []*doc.Type) []*Type {
var result []*Type
for _, d := range tdocs {
result = append(result, &Type{
Decl: w.printDecl(d.Decl),
Name: d.Name,
Methods: w.funcs(d.Methods),
})
}
return result
}
func (w *routerWalker) printDecl(decl ast.Node) string {
var d Code
d, w.buf = printDecl(decl, w.fset, w.buf)
return d.Text
}
func (w *routerWalker) readDir(dir string) ([]os.FileInfo, error) {
if dir != w.pdoc.ImportPath {
panic("unexpected")
}
fis := make([]os.FileInfo, 0, len(w.srcs))
for _, src := range w.srcs {
fis = append(fis, src)
}
return fis, nil
}
func (w *routerWalker) openFile(path string) (io.ReadCloser, error) {
if strings.HasPrefix(path, w.pdoc.ImportPath+"/") {
if src, ok := w.srcs[path[len(w.pdoc.ImportPath)+1:]]; ok {
return ioutil.NopCloser(bytes.NewReader(src.data)), nil
}
}
panic("unexpected")
}
func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
pkg := imports[path]
if pkg == nil {
// Guess the package name without importing it. Start with the last
// element of the path.
name := path[strings.LastIndex(path, "/")+1:]
// Trim commonly used prefixes and suffixes containing illegal name
// runes.
name = strings.TrimSuffix(name, ".go")
name = strings.TrimSuffix(name, "-go")
name = strings.TrimPrefix(name, "go.")
name = strings.TrimPrefix(name, "go-")
name = strings.TrimPrefix(name, "biogo.")
// It's also common for the last element of the path to contain an
// extra "go" prefix, but not always.
// TODO: examine unresolved ids to detect when trimming the "go" prefix is appropriate.
pkg = ast.NewObj(ast.Pkg, name)
pkg.Data = ast.NewScope(nil)
imports[path] = pkg
}
return pkg, nil
}

View File

@ -1,9 +0,0 @@
package main
import (
"testing"
)
func TestGetControllerInfo(t *testing.T) {
getControllerInfo("testdata/router/")
}

258
bee.go
View File

@ -1,258 +0,0 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Bee is a tool for developing applications based on beego framework.
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"text/template"
)
const version = "1.8.0"
// Command is the unit of execution
type Command struct {
// Run runs the command.
// The args are the arguments after the command name.
Run func(cmd *Command, args []string) int
// PreRun performs an operation before running the command
PreRun func(cmd *Command, args []string)
// UsageLine is the one-line usage message.
// The first word in the line is taken to be the command name.
UsageLine string
// Short is the short description shown in the 'go help' output.
Short string
// Long is the long message shown in the 'go help <this-command>' output.
Long string
// Flag is a set of flags specific to this command.
Flag flag.FlagSet
// CustomFlags indicates that the command will do its own
// flag parsing.
CustomFlags bool
// output out writer if set in SetOutput(w)
output *io.Writer
}
// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
name := c.UsageLine
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
// SetOutput sets the destination for usage and error messages.
// If output is nil, os.Stderr is used.
func (c *Command) SetOutput(output io.Writer) {
c.output = &output
}
// Out returns the out writer of the current command.
// If cmd.output is nil, os.Stderr is used.
func (c *Command) Out() io.Writer {
if c.output != nil {
return *c.output
}
return NewColorWriter(os.Stderr)
}
// Usage puts out the usage for the command.
func (c *Command) Usage() {
tmpl(cmdUsage, c)
os.Exit(2)
}
// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as import path.
func (c *Command) Runnable() bool {
return c.Run != nil
}
func (c *Command) Options() map[string]string {
options := make(map[string]string)
c.Flag.VisitAll(func(f *flag.Flag) {
defaultVal := f.DefValue
if len(defaultVal) > 0 {
options[f.Name+"="+defaultVal] = f.Usage
} else {
options[f.Name] = f.Usage
}
})
return options
}
var availableCommands = []*Command{
cmdNew,
cmdRun,
cmdPack,
cmdApiapp,
cmdHproseapp,
//cmdRouter,
//cmdTest,
cmdBale,
cmdVersion,
cmdGenerate,
//cmdRundocs,
cmdMigrate,
cmdFix,
cmdDockerize,
}
var logger = GetBeeLogger(os.Stdout)
func main() {
currentpath, _ := os.Getwd()
flag.Usage = usage
flag.Parse()
log.SetFlags(0)
args := flag.Args()
if len(args) < 1 {
usage()
}
if args[0] == "help" {
help(args[1:])
return
}
for _, cmd := range availableCommands {
if cmd.Name() == args[0] && cmd.Run != nil {
cmd.Flag.Usage = func() { cmd.Usage() }
if cmd.CustomFlags {
args = args[1:]
} else {
cmd.Flag.Parse(args[1:])
args = cmd.Flag.Args()
}
if cmd.PreRun != nil {
cmd.PreRun(cmd, args)
}
// Check if current directory is inside the GOPATH,
// if so parse the packages inside it.
if IsInGOPATH(currentpath) && isGenerateDocs(cmd.Name(), args) {
parsePackagesFromDir(currentpath)
}
os.Exit(cmd.Run(cmd, args))
return
}
}
printErrorAndExit("Unknown subcommand")
}
func isGenerateDocs(name string, args []string) bool {
if name != "generate" {
return false
}
for _, a := range args {
if a == "docs" {
return true
}
}
return false
}
var usageTemplate = `Bee is a Fast and Flexible tool for managing your Beego Web Application.
{{"USAGE" | headline}}
{{"bee command [arguments]" | bold}}
{{"AVAILABLE COMMANDS" | headline}}
{{range .}}{{if .Runnable}}
{{.Name | printf "%-11s" | bold}} {{.Short}}{{end}}{{end}}
Use {{"bee help [command]" | bold}} for more information about a command.
{{"ADDITIONAL HELP TOPICS" | headline}}
{{range .}}{{if not .Runnable}}
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
Use {{"bee help [topic]" | bold}} for more information about that topic.
`
var helpTemplate = `{{"USAGE" | headline}}
{{.UsageLine | printf "bee %s" | bold}}
{{if .Options}}{{endline}}{{"OPTIONS" | headline}}{{range $k,$v := .Options}}
{{$k | printf "-%s" | bold}}
{{$v}}
{{end}}{{end}}
{{"DESCRIPTION" | headline}}
{{tmpltostr .Long . | trim}}
`
var errorTemplate = `bee: %s.
Use {{"bee help" | bold}} for more information.
`
var cmdUsage = `Use {{printf "bee help %s" .Name | bold}} for more information.{{endline}}`
func usage() {
tmpl(usageTemplate, availableCommands)
os.Exit(2)
}
func tmpl(text string, data interface{}) {
output := NewColorWriter(os.Stderr)
t := template.New("usage").Funcs(BeeFuncMap())
template.Must(t.Parse(text))
err := t.Execute(output, data)
MustCheck(err)
}
func help(args []string) {
if len(args) == 0 {
usage()
}
if len(args) != 1 {
printErrorAndExit("Too many arguments")
}
arg := args[0]
for _, cmd := range availableCommands {
if cmd.Name() == arg {
tmpl(helpTemplate, cmd)
return
}
}
printErrorAndExit("Unknown help topic")
}
func printErrorAndExit(message string) {
tmpl(fmt.Sprintf(errorTemplate, message), nil)
os.Exit(2)
}

View File

@ -1,9 +1,5 @@
{
"version": 0,
"gopm": {
"enable": false,
"install": false
},
"go_install": false,
"watch_ext": [],
"dir_structure": {

102
cmd/bee.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package cmd ...
package cmd
import (
"github.com/beego/bee/cmd/commands"
_ "github.com/beego/bee/cmd/commands/api"
_ "github.com/beego/bee/cmd/commands/bale"
_ "github.com/beego/bee/cmd/commands/beefix"
_ "github.com/beego/bee/cmd/commands/dlv"
_ "github.com/beego/bee/cmd/commands/dockerize"
_ "github.com/beego/bee/cmd/commands/generate"
_ "github.com/beego/bee/cmd/commands/hprose"
_ "github.com/beego/bee/cmd/commands/migrate"
_ "github.com/beego/bee/cmd/commands/new"
_ "github.com/beego/bee/cmd/commands/pack"
_ "github.com/beego/bee/cmd/commands/rs"
_ "github.com/beego/bee/cmd/commands/run"
_ "github.com/beego/bee/cmd/commands/server"
_ "github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/utils"
)
func IfGenerateDocs(name string, args []string) bool {
if name != "generate" {
return false
}
for _, a := range args {
if a == "docs" {
return true
}
}
return false
}
var usageTemplate = `Bee is a Fast and Flexible tool for managing your Beego Web Application.
{{"USAGE" | headline}}
{{"bee command [arguments]" | bold}}
{{"AVAILABLE COMMANDS" | headline}}
{{range .}}{{if .Runnable}}
{{.Name | printf "%-11s" | bold}} {{.Short}}{{end}}{{end}}
Use {{"bee help [command]" | bold}} for more information about a command.
{{"ADDITIONAL HELP TOPICS" | headline}}
{{range .}}{{if not .Runnable}}
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
Use {{"bee help [topic]" | bold}} for more information about that topic.
`
var helpTemplate = `{{"USAGE" | headline}}
{{.UsageLine | printf "bee %s" | bold}}
{{if .Options}}{{endline}}{{"OPTIONS" | headline}}{{range $k,$v := .Options}}
{{$k | printf "-%s" | bold}}
{{$v}}
{{end}}{{end}}
{{"DESCRIPTION" | headline}}
{{tmpltostr .Long . | trim}}
`
var ErrorTemplate = `bee: %s.
Use {{"bee help" | bold}} for more information.
`
func Usage() {
utils.Tmpl(usageTemplate, commands.AvailableCommands)
}
func Help(args []string) {
if len(args) == 0 {
Usage()
}
if len(args) != 1 {
utils.PrintErrorAndExit("Too many arguments", ErrorTemplate)
}
arg := args[0]
for _, cmd := range commands.AvailableCommands {
if cmd.Name() == arg {
utils.Tmpl(helpTemplate, cmd)
return
}
}
utils.PrintErrorAndExit("Unknown help topic", ErrorTemplate)
}

View File

@ -12,16 +12,22 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package apiapp
import (
"fmt"
"os"
path "path/filepath"
"strings"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/generate"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils"
)
var cmdApiapp = &Command{
var CmdApiapp = &commands.Command{
// CustomFlags: true,
UsageLine: "api [appname]",
Short: "Creates a Beego API application",
@ -50,10 +56,9 @@ var cmdApiapp = &Command{
object.go
user.go
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
Run: createapi,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: createAPI,
}
var apiconf = `appname = {{.Appname}}
httpport = 8080
runmode = dev
@ -134,7 +139,7 @@ func init() {
}
`
var apiModels = `package models
var APIModels = `package models
import (
"errors"
@ -189,7 +194,7 @@ func Delete(ObjectId string) {
`
var apiModels2 = `package models
var APIModels2 = `package models
import (
"errors"
@ -532,133 +537,101 @@ func TestGet(t *testing.T) {
`
func init() {
cmdApiapp.Flag.Var(&tables, "tables", "List of table names separated by a comma.")
cmdApiapp.Flag.Var(&driver, "driver", "Database driver. Either mysql, postgres or sqlite.")
cmdApiapp.Flag.Var(&conn, "conn", "Connection string used by the driver to connect to a database instance.")
CmdApiapp.Flag.Var(&generate.Tables, "tables", "List of table names separated by a comma.")
CmdApiapp.Flag.Var(&generate.SQLDriver, "driver", "Database driver. Either mysql, postgres or sqlite.")
CmdApiapp.Flag.Var(&generate.SQLConn, "conn", "Connection string used by the driver to connect to a database instance.")
commands.AvailableCommands = append(commands.AvailableCommands, CmdApiapp)
}
func createapi(cmd *Command, args []string) int {
func createAPI(cmd *commands.Command, args []string) int {
output := cmd.Out()
if len(args) < 1 {
logger.Fatal("Argument [appname] is missing")
beeLogger.Log.Fatal("Argument [appname] is missing")
}
if len(args) > 1 {
cmd.Flag.Parse(args[1:])
err := cmd.Flag.Parse(args[1:])
if err != nil {
beeLogger.Log.Error(err.Error())
}
}
apppath, packpath, err := checkEnv(args[0])
appPath, packPath, err := utils.CheckEnv(args[0])
if err != nil {
logger.Fatalf("%s", err)
beeLogger.Log.Fatalf("%s", err)
}
if driver == "" {
driver = "mysql"
}
if conn == "" {
if generate.SQLDriver == "" {
generate.SQLDriver = "mysql"
}
logger.Info("Creating API...")
beeLogger.Log.Info("Creating API...")
os.MkdirAll(apppath, 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m")
os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
os.Mkdir(path.Join(apppath, "controllers"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers"), "\x1b[0m")
os.Mkdir(path.Join(apppath, "tests"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests"), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
WriteToFile(path.Join(apppath, "conf", "app.conf"),
os.MkdirAll(appPath, 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", appPath, "\x1b[0m")
os.Mkdir(path.Join(appPath, "conf"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "conf"), "\x1b[0m")
os.Mkdir(path.Join(appPath, "controllers"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "controllers"), "\x1b[0m")
os.Mkdir(path.Join(appPath, "tests"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "tests"), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "conf", "app.conf"), "\x1b[0m")
utils.WriteToFile(path.Join(appPath, "conf", "app.conf"),
strings.Replace(apiconf, "{{.Appname}}", path.Base(args[0]), -1))
if conn != "" {
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
maingoContent := strings.Replace(apiMainconngo, "{{.Appname}}", packpath, -1)
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1)
if driver == "mysql" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/go-sql-driver/mysql"`, -1)
} else if driver == "postgres" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
if generate.SQLConn != "" {
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "main.go"), "\x1b[0m")
mainGoContent := strings.Replace(apiMainconngo, "{{.Appname}}", packPath, -1)
mainGoContent = strings.Replace(mainGoContent, "{{.DriverName}}", string(generate.SQLDriver), -1)
if generate.SQLDriver == "mysql" {
mainGoContent = strings.Replace(mainGoContent, "{{.DriverPkg}}", `_ "github.com/go-sql-driver/mysql"`, -1)
} else if generate.SQLDriver == "postgres" {
mainGoContent = strings.Replace(mainGoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
}
WriteToFile(path.Join(apppath, "main.go"),
utils.WriteToFile(path.Join(appPath, "main.go"),
strings.Replace(
maingoContent,
mainGoContent,
"{{.conn}}",
conn.String(),
generate.SQLConn.String(),
-1,
),
)
logger.Infof("Using '%s' as 'driver'", driver)
logger.Infof("Using '%s' as 'conn'", conn)
logger.Infof("Using '%s' as 'tables'", tables)
generateAppcode(string(driver), string(conn), "3", string(tables), apppath)
beeLogger.Log.Infof("Using '%s' as 'driver'", generate.SQLDriver)
beeLogger.Log.Infof("Using '%s' as 'conn'", generate.SQLConn)
beeLogger.Log.Infof("Using '%s' as 'tables'", generate.Tables)
generate.GenerateAppcode(string(generate.SQLDriver), string(generate.SQLConn), "3", string(generate.Tables), appPath)
} else {
os.Mkdir(path.Join(apppath, "models"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
os.Mkdir(path.Join(apppath, "routers"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(appPath, "models"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "models"), "\x1b[0m")
os.Mkdir(path.Join(appPath, "routers"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "routers")+string(path.Separator), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "object.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "controllers", "object.go"),
strings.Replace(apiControllers, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "controllers", "object.go"), "\x1b[0m")
utils.WriteToFile(path.Join(appPath, "controllers", "object.go"),
strings.Replace(apiControllers, "{{.Appname}}", packPath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "user.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "controllers", "user.go"),
strings.Replace(apiControllers2, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "controllers", "user.go"), "\x1b[0m")
utils.WriteToFile(path.Join(appPath, "controllers", "user.go"),
strings.Replace(apiControllers2, "{{.Appname}}", packPath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "tests", "default_test.go"),
strings.Replace(apiTests, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "tests", "default_test.go"), "\x1b[0m")
utils.WriteToFile(path.Join(appPath, "tests", "default_test.go"),
strings.Replace(apiTests, "{{.Appname}}", packPath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "routers", "router.go"),
strings.Replace(apirouter, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "routers", "router.go"), "\x1b[0m")
utils.WriteToFile(path.Join(appPath, "routers", "router.go"),
strings.Replace(apirouter, "{{.Appname}}", packPath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "models", "object.go"), apiModels)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "models", "object.go"), "\x1b[0m")
utils.WriteToFile(path.Join(appPath, "models", "object.go"), APIModels)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "models", "user.go"), "\x1b[0m")
utils.WriteToFile(path.Join(appPath, "models", "user.go"), APIModels2)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(apiMaingo, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(appPath, "main.go"), "\x1b[0m")
utils.WriteToFile(path.Join(appPath, "main.go"),
strings.Replace(apiMaingo, "{{.Appname}}", packPath, -1))
}
logger.Success("New API successfully created!")
beeLogger.Log.Success("New API successfully created!")
return 0
}
func checkEnv(appname string) (apppath, packpath string, err error) {
gps := GetGOPATHs()
if len(gps) == 0 {
logger.Fatal("GOPATH environment variable is not set or empty")
}
currpath, _ := os.Getwd()
currpath = path.Join(currpath, appname)
for _, gpath := range gps {
gsrcpath := path.Join(gpath, "src")
if strings.HasPrefix(currpath, gsrcpath) {
packpath = strings.Replace(currpath[len(gsrcpath)+1:], string(path.Separator), "/", -1)
return currpath, packpath, nil
}
}
// In case of multiple paths in the GOPATH, by default
// we use the first path
gopath := gps[0]
logger.Warn("You current workdir is not inside $GOPATH/src.")
logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath)
gosrcpath := path.Join(gopath, "src")
apppath = path.Join(gosrcpath, appname)
if _, e := os.Stat(apppath); os.IsNotExist(e) == false {
err = fmt.Errorf("Cannot create application without removing '%s' first.", apppath)
logger.Errorf("Path '%s' already exists", apppath)
return
}
packpath = strings.Join(strings.Split(apppath[len(gosrcpath)+1:], string(path.Separator)), "/")
return
}

View File

@ -11,8 +11,7 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
package bale
import (
"bytes"
@ -24,9 +23,15 @@ import (
"path/filepath"
"runtime"
"strings"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/config"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils"
)
var cmdBale = &Command{
var CmdBale = &commands.Command{
UsageLine: "bale",
Short: "Transforms non-Go files to Go source files",
Long: `Bale command compress all the static files in to a single binary file.
@ -37,47 +42,46 @@ var cmdBale = &Command{
It will auto-generate an unpack function to the main package then run it during the runtime.
This is mainly used for zealots who are requiring 100% Go code.
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: runBale,
}
func runBale(cmd *Command, args []string) int {
err := loadConfig()
if err != nil {
logger.Fatalf("Failed to load configuration: %s", err)
}
func init() {
commands.AvailableCommands = append(commands.AvailableCommands, CmdBale)
}
func runBale(cmd *commands.Command, args []string) int {
os.RemoveAll("bale")
os.Mkdir("bale", os.ModePerm)
// Pack and compress data
for _, p := range conf.Bale.Dirs {
if !isExist(p) {
logger.Warnf("Skipped directory: %s", p)
for _, p := range config.Conf.Bale.Dirs {
if !utils.IsExist(p) {
beeLogger.Log.Warnf("Skipped directory: %s", p)
continue
}
logger.Infof("Packaging directory: %s", p)
beeLogger.Log.Infof("Packaging directory: %s", p)
filepath.Walk(p, walkFn)
}
// Generate auto-uncompress function.
buf := new(bytes.Buffer)
buf.WriteString(fmt.Sprintf(BaleHeader, conf.Bale.Import,
buf.WriteString(fmt.Sprintf(BaleHeader, config.Conf.Bale.Import,
strings.Join(resFiles, "\",\n\t\t\""),
strings.Join(resFiles, ",\n\t\tbale.R")))
fw, err := os.Create("bale.go")
if err != nil {
logger.Fatalf("Failed to create file: %s", err)
beeLogger.Log.Fatalf("Failed to create file: %s", err)
}
defer fw.Close()
_, err = fw.Write(buf.Bytes())
if err != nil {
logger.Fatalf("Failed to write data: %s", err)
beeLogger.Log.Fatalf("Failed to write data: %s", err)
}
logger.Success("Baled resources successfully!")
beeLogger.Log.Success("Baled resources successfully!")
return 0
}
@ -138,7 +142,7 @@ func saveFile(filePath string, b []byte) (int, error) {
var resFiles = make([]string, 0, 10)
func walkFn(resPath string, info os.FileInfo, err error) error {
func walkFn(resPath string, info os.FileInfo, _ error) error {
if info.IsDir() || filterSuffix(resPath) {
return nil
}
@ -146,7 +150,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
// Open resource files
fr, err := os.Open(resPath)
if err != nil {
logger.Fatalf("Failed to read file: %s", err)
beeLogger.Log.Fatalf("Failed to read file: %s", err)
}
// Convert path
@ -164,7 +168,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
os.MkdirAll(path.Dir(resPath), os.ModePerm)
fw, err := os.Create("bale/" + resPath + ".go")
if err != nil {
logger.Fatalf("Failed to create file: %s", err)
beeLogger.Log.Fatalf("Failed to create file: %s", err)
}
defer fw.Close()
@ -184,7 +188,7 @@ func walkFn(resPath string, info os.FileInfo, err error) error {
}
func filterSuffix(name string) bool {
for _, s := range conf.Bale.IngExt {
for _, s := range config.Conf.Bale.IngExt {
if strings.HasSuffix(name, s) {
return true
}

View File

@ -1,4 +1,4 @@
package main
package beefix
import (
"fmt"
@ -9,9 +9,14 @@ import (
"path/filepath"
"regexp"
"strings"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
)
var cmdFix = &Command{
var CmdFix = &commands.Command{
UsageLine: "fix",
Short: "Fixes your application by making it compatible with newer versions of Beego",
Long: `As of {{"Beego 1.6"|bold}}, there are some backward compatibility issues.
@ -22,18 +27,19 @@ var cmdFix = &Command{
}
func init() {
cmdFix.Run = runFix
cmdFix.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
CmdFix.Run = runFix
CmdFix.PreRun = func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() }
commands.AvailableCommands = append(commands.AvailableCommands, CmdFix)
}
func runFix(cmd *Command, args []string) int {
func runFix(cmd *commands.Command, args []string) int {
output := cmd.Out()
logger.Info("Upgrading the application...")
beeLogger.Log.Info("Upgrading the application...")
dir, err := os.Getwd()
if err != nil {
logger.Fatalf("Error while getting the current working directory: %s", err)
beeLogger.Log.Fatalf("Error while getting the current working directory: %s", err)
}
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
@ -50,13 +56,13 @@ func runFix(cmd *Command, args []string) int {
return nil
}
err = fixFile(path)
fmt.Fprintf(output, GreenBold("\tfix\t")+"%s\n", path)
fmt.Fprintf(output, colors.GreenBold("\tfix\t")+"%s\n", path)
if err != nil {
logger.Errorf("Could not fix file: %s", err)
beeLogger.Log.Errorf("Could not fix file: %s", err)
}
return err
})
logger.Success("Upgrade Done!")
beeLogger.Log.Success("Upgrade Done!")
return 0
}

94
cmd/commands/command.go Normal file
View File

@ -0,0 +1,94 @@
package commands
import (
"flag"
"io"
"os"
"strings"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
)
// Command is the unit of execution
type Command struct {
// Run runs the command.
// The args are the arguments after the command name.
Run func(cmd *Command, args []string) int
// PreRun performs an operation before running the command
PreRun func(cmd *Command, args []string)
// UsageLine is the one-line Usage message.
// The first word in the line is taken to be the command name.
UsageLine string
// Short is the short description shown in the 'go help' output.
Short string
// Long is the long message shown in the 'go help <this-command>' output.
Long string
// Flag is a set of flags specific to this command.
Flag flag.FlagSet
// CustomFlags indicates that the command will do its own
// flag parsing.
CustomFlags bool
// output out writer if set in SetOutput(w)
output *io.Writer
}
var AvailableCommands = []*Command{}
var cmdUsage = `Use {{printf "bee help %s" .Name | bold}} for more information.{{endline}}`
// Name returns the command's name: the first word in the Usage line.
func (c *Command) Name() string {
name := c.UsageLine
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
// SetOutput sets the destination for Usage and error messages.
// If output is nil, os.Stderr is used.
func (c *Command) SetOutput(output io.Writer) {
c.output = &output
}
// Out returns the out writer of the current command.
// If cmd.output is nil, os.Stderr is used.
func (c *Command) Out() io.Writer {
if c.output != nil {
return *c.output
}
return colors.NewColorWriter(os.Stderr)
}
// Usage puts out the Usage for the command.
func (c *Command) Usage() {
utils.Tmpl(cmdUsage, c)
os.Exit(2)
}
// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as import path.
func (c *Command) Runnable() bool {
return c.Run != nil
}
func (c *Command) Options() map[string]string {
options := make(map[string]string)
c.Flag.VisitAll(func(f *flag.Flag) {
defaultVal := f.DefValue
if len(defaultVal) > 0 {
options[f.Name+"="+defaultVal] = f.Usage
} else {
options[f.Name] = f.Usage
}
})
return options
}

16
cmd/commands/dlv/dlv.go Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2017 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package dlv ...
package dlv

View File

@ -0,0 +1,245 @@
// Copyright 2017 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package dlv ...
package dlv
import (
"flag"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/rpc2"
"github.com/derekparker/delve/service/rpccommon"
"github.com/derekparker/delve/terminal"
"github.com/fsnotify/fsnotify"
)
var cmdDlv = &commands.Command{
CustomFlags: true,
UsageLine: "dlv [-package=\"\"] [-port=8181] [-verbose=false]",
Short: "Start a debugging session using Delve",
Long: `dlv command start a debugging session using debugging tool Delve.
To debug your application using Delve, use: {{"$ bee dlv" | bold}}
For more information on Delve: https://github.com/derekparker/delve
`,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: runDlv,
}
var (
packageName string
verbose bool
port int
)
func init() {
fs := flag.NewFlagSet("dlv", flag.ContinueOnError)
fs.StringVar(&packageName, "package", "", "The package to debug (Must have a main package)")
fs.BoolVar(&verbose, "verbose", false, "Enable verbose mode")
fs.IntVar(&port, "port", 8181, "Port to listen to for clients")
cmdDlv.Flag = *fs
commands.AvailableCommands = append(commands.AvailableCommands, cmdDlv)
}
func runDlv(cmd *commands.Command, args []string) int {
if err := cmd.Flag.Parse(args); err != nil {
beeLogger.Log.Fatalf("Error while parsing flags: %v", err.Error())
}
var (
addr = fmt.Sprintf("127.0.0.1:%d", port)
paths = make([]string, 0)
notifyChan = make(chan int)
)
if err := loadPathsToWatch(&paths); err != nil {
beeLogger.Log.Fatalf("Error while loading paths to watch: %v", err.Error())
}
go startWatcher(paths, notifyChan)
return startDelveDebugger(addr, notifyChan)
}
// buildDebug builds a debug binary in the current working directory
func buildDebug() (string, error) {
args := []string{"-gcflags", "-N -l", "-o", "debug"}
args = append(args, utils.SplitQuotedFields("-ldflags='-linkmode internal'")...)
args = append(args, packageName)
if err := utils.GoCommand("build", args...); err != nil {
return "", err
}
fp, err := filepath.Abs("./debug")
if err != nil {
return "", err
}
return fp, nil
}
// loadPathsToWatch loads the paths that needs to be watched for changes
func loadPathsToWatch(paths *[]string) error {
directory, err := os.Getwd()
if err != nil {
return err
}
filepath.Walk(directory, func(path string, info os.FileInfo, _ error) error {
if strings.HasSuffix(info.Name(), "docs") {
return filepath.SkipDir
}
if strings.HasSuffix(info.Name(), "swagger") {
return filepath.SkipDir
}
if strings.HasSuffix(info.Name(), "vendor") {
return filepath.SkipDir
}
if filepath.Ext(info.Name()) == ".go" {
*paths = append(*paths, path)
}
return nil
})
return nil
}
// startDelveDebugger starts the Delve debugger server
func startDelveDebugger(addr string, ch chan int) int {
beeLogger.Log.Info("Starting Delve Debugger...")
fp, err := buildDebug()
if err != nil {
beeLogger.Log.Fatalf("Error while building debug binary: %v", err)
}
defer os.Remove(fp)
abs, err := filepath.Abs("./debug")
if err != nil {
beeLogger.Log.Fatalf("%v", err)
}
// Create and start the debugger server
listener, err := net.Listen("tcp", addr)
if err != nil {
beeLogger.Log.Fatalf("Could not start listener: %s", err)
}
defer listener.Close()
server := rpccommon.NewServer(&service.Config{
Listener: listener,
AcceptMulti: true,
AttachPid: 0,
APIVersion: 2,
WorkingDir: ".",
ProcessArgs: []string{abs},
}, false)
if err := server.Run(); err != nil {
beeLogger.Log.Fatalf("Could not start debugger server: %v", err)
}
// Start the Delve client REPL
client := rpc2.NewClient(addr)
// Make sure the client is restarted when new changes are introduced
go func() {
for {
if val := <-ch; val == 0 {
if _, err := client.Restart(); err != nil {
utils.Notify("Error while restarting the client: "+err.Error(), "bee")
} else {
if verbose {
utils.Notify("Delve Debugger Restarted", "bee")
}
}
}
}
}()
// Create the terminal and connect it to the client debugger
term := terminal.New(client, nil)
status, err := term.Run()
if err != nil {
beeLogger.Log.Fatalf("Could not start Delve REPL: %v", err)
}
// Stop and kill the debugger server once user quits the REPL
if err := server.Stop(true); err != nil {
beeLogger.Log.Fatalf("Could not stop Delve server: %v", err)
}
return status
}
var eventsModTime = make(map[string]int64)
// startWatcher starts the fsnotify watcher on the passed paths
func startWatcher(paths []string, ch chan int) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
beeLogger.Log.Fatalf("Could not start the watcher: %v", err)
}
defer watcher.Close()
// Feed the paths to the watcher
for _, path := range paths {
if err := watcher.Add(path); err != nil {
beeLogger.Log.Fatalf("Could not set a watch on path: %v", err)
}
}
for {
select {
case evt := <-watcher.Events:
build := true
if filepath.Ext(evt.Name) != ".go" {
continue
}
mt := utils.GetFileModTime(evt.Name)
if t := eventsModTime[evt.Name]; mt == t {
build = false
}
eventsModTime[evt.Name] = mt
if build {
go func() {
if verbose {
utils.Notify("Rebuilding application with the new changes", "bee")
}
// Wait 1s before re-build until there is no file change
scheduleTime := time.Now().Add(1 * time.Second)
time.Sleep(scheduleTime.Sub(time.Now()))
_, err := buildDebug()
if err != nil {
utils.Notify("Build Failed: "+err.Error(), "bee")
} else {
ch <- 0 // Notify listeners
}
}()
}
case err := <-watcher.Errors:
if err != nil {
ch <- -1
}
}
}
}

View File

@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package dockerize
import (
"flag"
@ -21,22 +21,13 @@ import (
"path/filepath"
"strings"
"text/template"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils"
)
var cmdDockerize = &Command{
CustomFlags: true,
UsageLine: "dockerize",
Short: "Generates a Dockerfile for your Beego application",
Long: `Dockerize generates a Dockerfile for your Beego Web Application.
The Dockerfile will compile, get the dependencies with {{"godep"|bold}}, and set the entrypoint.
{{"Example:"|bold}}
$ bee dockerize -expose="3000,80,25"
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
Run: dockerizeApp,
}
const dockerBuildTemplate = `FROM {{.BaseImage}}
# Godep for vendoring
@ -49,12 +40,11 @@ ENV APP_DIR $GOPATH{{.Appdir}}
RUN mkdir -p $APP_DIR
# Set the entrypoint
ENTRYPOINT $APP_DIR/{{.Entrypoint}}
ENTRYPOINT (cd $APP_DIR && ./{{.Entrypoint}})
ADD . $APP_DIR
# Compile the binary and statically link
RUN cd $APP_DIR
RUN CGO_ENABLED=0 godep go build -ldflags '-d -w -s'
RUN cd $APP_DIR && CGO_ENABLED=0 godep go build -ldflags '-d -w -s'
EXPOSE {{.Expose}}
`
@ -67,6 +57,20 @@ type Dockerfile struct {
Expose string
}
var CmdDockerize = &commands.Command{
CustomFlags: true,
UsageLine: "dockerize",
Short: "Generates a Dockerfile for your Beego application",
Long: `Dockerize generates a Dockerfile for your Beego Web Application.
The Dockerfile will compile, get the dependencies with {{"godep"|bold}}, and set the entrypoint.
{{"Example:"|bold}}
$ bee dockerize -expose="3000,80,25"
`,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: dockerizeApp,
}
var (
expose string
baseImage string
@ -76,19 +80,22 @@ func init() {
fs := flag.NewFlagSet("dockerize", flag.ContinueOnError)
fs.StringVar(&baseImage, "image", "library/golang", "Set the base image of the Docker container.")
fs.StringVar(&expose, "expose", "8080", "Port(s) to expose in the Docker container.")
cmdDockerize.Flag = *fs
CmdDockerize.Flag = *fs
commands.AvailableCommands = append(commands.AvailableCommands, CmdDockerize)
}
func dockerizeApp(cmd *Command, args []string) int {
func dockerizeApp(cmd *commands.Command, args []string) int {
if err := cmd.Flag.Parse(args); err != nil {
logger.Fatalf("Error parsing flags: %v", err.Error())
beeLogger.Log.Fatalf("Error parsing flags: %v", err.Error())
}
logger.Info("Generating Dockerfile...")
beeLogger.Log.Info("Generating Dockerfile...")
gopath := os.Getenv("GOPATH")
dir, err := filepath.Abs(".")
MustCheck(err)
if err != nil {
beeLogger.Log.Error(err.Error())
}
appdir := strings.Replace(dir, gopath, "", 1)
@ -112,15 +119,15 @@ func dockerizeApp(cmd *Command, args []string) int {
}
func generateDockerfile(df Dockerfile) {
t := template.Must(template.New("dockerBuildTemplate").Parse(dockerBuildTemplate)).Funcs(BeeFuncMap())
t := template.Must(template.New("dockerBuildTemplate").Parse(dockerBuildTemplate)).Funcs(utils.BeeFuncMap())
f, err := os.Create("Dockerfile")
if err != nil {
logger.Fatalf("Error writing Dockerfile: %v", err.Error())
beeLogger.Log.Fatalf("Error writing Dockerfile: %v", err.Error())
}
defer CloseFile(f)
defer utils.CloseFile(f)
t.Execute(f, df)
logger.Success("Dockerfile generated.")
beeLogger.Log.Success("Dockerfile generated.")
}

View File

@ -0,0 +1,217 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package generate
import (
"os"
"strings"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/config"
"github.com/beego/bee/generate"
"github.com/beego/bee/generate/swaggergen"
"github.com/beego/bee/logger"
"github.com/beego/bee/utils"
)
var CmdGenerate = &commands.Command{
UsageLine: "generate [command]",
Short: "Source code generator",
Long: ` {{"To scaffold out your entire application:"|bold}}
$ bee generate scaffold [scaffoldname] [-fields="title:string,body:text"] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
{{"To generate a Model based on fields:"|bold}}
$ bee generate model [modelname] [-fields="name:type"]
{{"To generate a controller:"|bold}}
$ bee generate controller [controllerfile]
{{"To generate a CRUD view:"|bold}}
$ bee generate view [viewpath]
{{"To generate a migration file for making database schema updates:"|bold}}
$ bee generate migration [migrationfile] [-fields="name:type"]
{{"To generate swagger doc file:"|bold}}
$ bee generate docs
{{"To generate a test case:"|bold}}
$ bee generate test [routerfile]
{{"To generate appcode based on an existing database:"|bold}}
$ bee generate appcode [-tables=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] [-level=3]
`,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: GenerateCode,
}
func init() {
CmdGenerate.Flag.Var(&generate.Tables, "tables", "List of table names separated by a comma.")
CmdGenerate.Flag.Var(&generate.SQLDriver, "driver", "Database SQLDriver. Either mysql, postgres or sqlite.")
CmdGenerate.Flag.Var(&generate.SQLConn, "conn", "Connection string used by the SQLDriver to connect to a database instance.")
CmdGenerate.Flag.Var(&generate.Level, "level", "Either 1, 2 or 3. i.e. 1=models; 2=models and controllers; 3=models, controllers and routers.")
CmdGenerate.Flag.Var(&generate.Fields, "fields", "List of table Fields.")
CmdGenerate.Flag.Var(&generate.DDL, "ddl", "Generate DDL Migration")
commands.AvailableCommands = append(commands.AvailableCommands, CmdGenerate)
}
func GenerateCode(cmd *commands.Command, args []string) int {
currpath, _ := os.Getwd()
if len(args) < 1 {
beeLogger.Log.Fatal("Command is missing")
}
gps := utils.GetGOPATHs()
if len(gps) == 0 {
beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
}
gopath := gps[0]
beeLogger.Log.Debugf("GOPATH: %s", utils.FILE(), utils.LINE(), gopath)
gcmd := args[0]
switch gcmd {
case "scaffold":
scaffold(cmd, args, currpath)
case "docs":
swaggergen.GenerateDocs(currpath)
case "appcode":
appCode(cmd, args, currpath)
case "migration":
migration(cmd, args, currpath)
case "controller":
controller(args, currpath)
case "model":
model(cmd, args, currpath)
case "view":
view(args, currpath)
default:
beeLogger.Log.Fatal("Command is missing")
}
beeLogger.Log.Successf("%s successfully generated!", strings.Title(gcmd))
return 0
}
func scaffold(cmd *commands.Command, args []string, currpath string) {
if len(args) < 2 {
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
}
cmd.Flag.Parse(args[2:])
if generate.SQLDriver == "" {
generate.SQLDriver = utils.DocValue(config.Conf.Database.Driver)
if generate.SQLDriver == "" {
generate.SQLDriver = "mysql"
}
}
if generate.SQLConn == "" {
generate.SQLConn = utils.DocValue(config.Conf.Database.Conn)
if generate.SQLConn == "" {
generate.SQLConn = "root:@tcp(127.0.0.1:3306)/test"
}
}
if generate.Fields == "" {
beeLogger.Log.Hint("Fields option should not be empty, i.e. -Fields=\"title:string,body:text\"")
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
}
sname := args[1]
generate.GenerateScaffold(sname, generate.Fields.String(), currpath, generate.SQLDriver.String(), generate.SQLConn.String())
}
func appCode(cmd *commands.Command, args []string, currpath string) {
cmd.Flag.Parse(args[1:])
if generate.SQLDriver == "" {
generate.SQLDriver = utils.DocValue(config.Conf.Database.Driver)
if generate.SQLDriver == "" {
generate.SQLDriver = "mysql"
}
}
if generate.SQLConn == "" {
generate.SQLConn = utils.DocValue(config.Conf.Database.Conn)
if generate.SQLConn == "" {
if generate.SQLDriver == "mysql" {
generate.SQLConn = "root:@tcp(127.0.0.1:3306)/test"
} else if generate.SQLDriver == "postgres" {
generate.SQLConn = "postgres://postgres:postgres@127.0.0.1:5432/postgres"
}
}
}
if generate.Level == "" {
generate.Level = "3"
}
beeLogger.Log.Infof("Using '%s' as 'SQLDriver'", generate.SQLDriver)
beeLogger.Log.Infof("Using '%s' as 'SQLConn'", generate.SQLConn)
beeLogger.Log.Infof("Using '%s' as 'Tables'", generate.Tables)
beeLogger.Log.Infof("Using '%s' as 'Level'", generate.Level)
generate.GenerateAppcode(generate.SQLDriver.String(), generate.SQLConn.String(), generate.Level.String(), generate.Tables.String(), currpath)
}
func migration(cmd *commands.Command, args []string, currpath string) {
if len(args) < 2 {
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
}
cmd.Flag.Parse(args[2:])
mname := args[1]
beeLogger.Log.Infof("Using '%s' as migration name", mname)
upsql := ""
downsql := ""
if generate.Fields != "" {
dbMigrator := generate.NewDBDriver()
upsql = dbMigrator.GenerateCreateUp(mname)
downsql = dbMigrator.GenerateCreateDown(mname)
}
generate.GenerateMigration(mname, upsql, downsql, currpath)
}
func controller(args []string, currpath string) {
if len(args) == 2 {
cname := args[1]
generate.GenerateController(cname, currpath)
} else {
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
}
}
func model(cmd *commands.Command, args []string, currpath string) {
if len(args) < 2 {
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
}
cmd.Flag.Parse(args[2:])
if generate.Fields == "" {
beeLogger.Log.Hint("Fields option should not be empty, i.e. -Fields=\"title:string,body:text\"")
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
}
sname := args[1]
generate.GenerateModel(sname, generate.Fields.String(), currpath)
}
func view(args []string, currpath string) {
if len(args) == 2 {
cname := args[1]
generate.GenerateView(cname, currpath)
} else {
beeLogger.Log.Fatal("Wrong number of arguments. Run: bee help generate")
}
}

View File

@ -0,0 +1,119 @@
package hprose
import (
"os"
"fmt"
"path"
"strings"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/api"
"github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/generate"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils"
)
var CmdHproseapp = &commands.Command{
// CustomFlags: true,
UsageLine: "hprose [appname]",
Short: "Creates an RPC application based on Hprose and Beego frameworks",
Long: `
The command 'hprose' creates an RPC application based on both Beego and Hprose (http://hprose.com/).
{{"To scaffold out your application, use:"|bold}}
$ bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]
If 'conn' is empty, the command will generate a sample application. Otherwise the command
will connect to your database and generate models based on the existing tables.
The command 'hprose' creates a folder named [appname] with the following structure:
main.go
{{"conf"|foldername}}
app.conf
{{"models"|foldername}}
object.go
user.go
`,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: createhprose,
}
func init() {
CmdHproseapp.Flag.Var(&generate.Tables, "tables", "List of table names separated by a comma.")
CmdHproseapp.Flag.Var(&generate.SQLDriver, "driver", "Database driver. Either mysql, postgres or sqlite.")
CmdHproseapp.Flag.Var(&generate.SQLConn, "conn", "Connection string used by the driver to connect to a database instance.")
commands.AvailableCommands = append(commands.AvailableCommands, CmdHproseapp)
}
func createhprose(cmd *commands.Command, args []string) int {
output := cmd.Out()
if len(args) != 1 {
beeLogger.Log.Fatal("Argument [appname] is missing")
}
curpath, _ := os.Getwd()
if len(args) > 1 {
cmd.Flag.Parse(args[1:])
}
apppath, packpath, err := utils.CheckEnv(args[0])
if err != nil {
beeLogger.Log.Fatalf("%s", err)
}
if generate.SQLDriver == "" {
generate.SQLDriver = "mysql"
}
beeLogger.Log.Info("Creating Hprose application...")
os.MkdirAll(apppath, 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m")
os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "conf", "app.conf"),
strings.Replace(generate.Hproseconf, "{{.Appname}}", args[0], -1))
if generate.SQLConn != "" {
beeLogger.Log.Infof("Using '%s' as 'driver'", generate.SQLDriver)
beeLogger.Log.Infof("Using '%s' as 'conn'", generate.SQLConn)
beeLogger.Log.Infof("Using '%s' as 'tables'", generate.Tables)
generate.GenerateHproseAppcode(string(generate.SQLDriver), string(generate.SQLConn), "1", string(generate.Tables), path.Join(curpath, args[0]))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
maingoContent := strings.Replace(generate.HproseMainconngo, "{{.Appname}}", packpath, -1)
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(generate.SQLDriver), -1)
maingoContent = strings.Replace(maingoContent, "{{HproseFunctionList}}", strings.Join(generate.HproseAddFunctions, ""), -1)
if generate.SQLDriver == "mysql" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/go-sql-driver/mysql"`, -1)
} else if generate.SQLDriver == "postgres" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
}
utils.WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(
maingoContent,
"{{.conn}}",
generate.SQLConn.String(),
-1,
),
)
} else {
os.Mkdir(path.Join(apppath, "models"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "models", "object.go"), apiapp.APIModels)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "models", "user.go"), apiapp.APIModels2)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(generate.HproseMaingo, "{{.Appname}}", packpath, -1))
}
beeLogger.Log.Success("New Hprose application successfully created!")
return 0
}

View File

@ -11,8 +11,7 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
package migrate
import (
"database/sql"
@ -23,9 +22,16 @@ import (
"strconv"
"strings"
"time"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/config"
"github.com/beego/bee/utils"
beeLogger "github.com/beego/bee/logger"
)
var cmdMigrate = &Command{
var CmdMigrate = &commands.Command{
UsageLine: "migrate [Command]",
Short: "Runs database migrations",
Long: `The command 'migrate' allows you to run database migrations to keep it up-to-date.
@ -46,100 +52,75 @@ var cmdMigrate = &Command{
$ bee migrate refresh [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
Run: runMigration,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: RunMigration,
}
var mDriver docValue
var mConn docValue
var mDriver utils.DocValue
var mConn utils.DocValue
func init() {
cmdMigrate.Flag.Var(&mDriver, "driver", "Database driver. Either mysql, postgres or sqlite.")
cmdMigrate.Flag.Var(&mConn, "conn", "Connection string used by the driver to connect to a database instance.")
CmdMigrate.Flag.Var(&mDriver, "driver", "Database driver. Either mysql, postgres or sqlite.")
CmdMigrate.Flag.Var(&mConn, "conn", "Connection string used by the driver to connect to a database instance.")
commands.AvailableCommands = append(commands.AvailableCommands, CmdMigrate)
}
// runMigration is the entry point for starting a migration
func runMigration(cmd *Command, args []string) int {
func RunMigration(cmd *commands.Command, args []string) int {
currpath, _ := os.Getwd()
gps := GetGOPATHs()
gps := utils.GetGOPATHs()
if len(gps) == 0 {
logger.Fatal("GOPATH environment variable is not set or empty")
beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
}
gopath := gps[0]
logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath)
// Load the configuration
err := loadConfig()
if err != nil {
logger.Errorf("Failed to load configuration: %s", err)
}
beeLogger.Log.Debugf("GOPATH: %s", utils.FILE(), utils.LINE(), gopath)
// Getting command line arguments
if len(args) != 0 {
cmd.Flag.Parse(args[1:])
}
if mDriver == "" {
mDriver = docValue(conf.Database.Driver)
mDriver = utils.DocValue(config.Conf.Database.Driver)
if mDriver == "" {
mDriver = "mysql"
}
}
if mConn == "" {
mConn = docValue(conf.Database.Conn)
mConn = utils.DocValue(config.Conf.Database.Conn)
if mConn == "" {
mConn = "root:@tcp(127.0.0.1:3306)/test"
}
}
logger.Infof("Using '%s' as 'driver'", mDriver)
logger.Infof("Using '%s' as 'conn'", mConn)
beeLogger.Log.Infof("Using '%s' as 'driver'", mDriver)
beeLogger.Log.Infof("Using '%s' as 'conn'", mConn)
driverStr, connStr := string(mDriver), string(mConn)
if len(args) == 0 {
// run all outstanding migrations
logger.Info("Running all outstanding migrations")
migrateUpdate(currpath, driverStr, connStr)
beeLogger.Log.Info("Running all outstanding migrations")
MigrateUpdate(currpath, driverStr, connStr)
} else {
mcmd := args[0]
switch mcmd {
case "rollback":
logger.Info("Rolling back the last migration operation")
migrateRollback(currpath, driverStr, connStr)
beeLogger.Log.Info("Rolling back the last migration operation")
MigrateRollback(currpath, driverStr, connStr)
case "reset":
logger.Info("Reseting all migrations")
migrateReset(currpath, driverStr, connStr)
beeLogger.Log.Info("Reseting all migrations")
MigrateReset(currpath, driverStr, connStr)
case "refresh":
logger.Info("Refreshing all migrations")
migrateRefresh(currpath, driverStr, connStr)
beeLogger.Log.Info("Refreshing all migrations")
MigrateRefresh(currpath, driverStr, connStr)
default:
logger.Fatal("Command is missing")
beeLogger.Log.Fatal("Command is missing")
}
}
logger.Success("Migration successful!")
beeLogger.Log.Success("Migration successful!")
return 0
}
// migrateUpdate does the schema update
func migrateUpdate(currpath, driver, connStr string) {
migrate("upgrade", currpath, driver, connStr)
}
// migrateRollback rolls back the latest migration
func migrateRollback(currpath, driver, connStr string) {
migrate("rollback", currpath, driver, connStr)
}
// migrateReset rolls back all migrations
func migrateReset(currpath, driver, connStr string) {
migrate("reset", currpath, driver, connStr)
}
// migrationRefresh rolls back all migrations and start over again
func migrateRefresh(currpath, driver, connStr string) {
migrate("refresh", currpath, driver, connStr)
}
// migrate generates source code, build it, and invoke the binary who does the actual migration
func migrate(goal, currpath, driver, connStr string) {
dir := path.Join(currpath, "database", "migrations")
@ -153,7 +134,7 @@ func migrate(goal, currpath, driver, connStr string) {
// Connect to database
db, err := sql.Open(driver, connStr)
if err != nil {
logger.Fatalf("Could not connect to database using '%s': %s", connStr, err)
beeLogger.Log.Fatalf("Could not connect to database using '%s': %s", connStr, err)
}
defer db.Close()
@ -171,44 +152,44 @@ func migrate(goal, currpath, driver, connStr string) {
func checkForSchemaUpdateTable(db *sql.DB, driver string) {
showTableSQL := showMigrationsTableSQL(driver)
if rows, err := db.Query(showTableSQL); err != nil {
logger.Fatalf("Could not show migrations table: %s", err)
beeLogger.Log.Fatalf("Could not show migrations table: %s", err)
} else if !rows.Next() {
// No migrations table, create new ones
createTableSQL := createMigrationsTableSQL(driver)
logger.Infof("Creating 'migrations' table...")
beeLogger.Log.Infof("Creating 'migrations' table...")
if _, err := db.Query(createTableSQL); err != nil {
logger.Fatalf("Could not create migrations table: %s", err)
beeLogger.Log.Fatalf("Could not create migrations table: %s", err)
}
}
// Checking that migrations table schema are expected
selectTableSQL := selectMigrationsTableSQL(driver)
if rows, err := db.Query(selectTableSQL); err != nil {
logger.Fatalf("Could not show columns of migrations table: %s", err)
beeLogger.Log.Fatalf("Could not show columns of migrations table: %s", err)
} else {
for rows.Next() {
var fieldBytes, typeBytes, nullBytes, keyBytes, defaultBytes, extraBytes []byte
if err := rows.Scan(&fieldBytes, &typeBytes, &nullBytes, &keyBytes, &defaultBytes, &extraBytes); err != nil {
logger.Fatalf("Could not read column information: %s", err)
beeLogger.Log.Fatalf("Could not read column information: %s", err)
}
fieldStr, typeStr, nullStr, keyStr, defaultStr, extraStr :=
string(fieldBytes), string(typeBytes), string(nullBytes), string(keyBytes), string(defaultBytes), string(extraBytes)
if fieldStr == "id_migration" {
if keyStr != "PRI" || extraStr != "auto_increment" {
logger.Hint("Expecting KEY: PRI, EXTRA: auto_increment")
logger.Fatalf("Column migration.id_migration type mismatch: KEY: %s, EXTRA: %s", keyStr, extraStr)
beeLogger.Log.Hint("Expecting KEY: PRI, EXTRA: auto_increment")
beeLogger.Log.Fatalf("Column migration.id_migration type mismatch: KEY: %s, EXTRA: %s", keyStr, extraStr)
}
} else if fieldStr == "name" {
if !strings.HasPrefix(typeStr, "varchar") || nullStr != "YES" {
logger.Hint("Expecting TYPE: varchar, NULL: YES")
logger.Fatalf("Column migration.name type mismatch: TYPE: %s, NULL: %s", typeStr, nullStr)
beeLogger.Log.Hint("Expecting TYPE: varchar, NULL: YES")
beeLogger.Log.Fatalf("Column migration.name type mismatch: TYPE: %s, NULL: %s", typeStr, nullStr)
}
} else if fieldStr == "created_at" {
if typeStr != "timestamp" || defaultStr != "CURRENT_TIMESTAMP" {
logger.Hint("Expecting TYPE: timestamp, DEFAULT: CURRENT_TIMESTAMP")
logger.Fatalf("Column migration.timestamp type mismatch: TYPE: %s, DEFAULT: %s", typeStr, defaultStr)
beeLogger.Log.Hint("Expecting TYPE: timestamp, DEFAULT: CURRENT_TIMESTAMP")
beeLogger.Log.Fatalf("Column migration.timestamp type mismatch: TYPE: %s, DEFAULT: %s", typeStr, defaultStr)
}
}
}
@ -252,22 +233,22 @@ func selectMigrationsTableSQL(driver string) string {
func getLatestMigration(db *sql.DB, goal string) (file string, createdAt int64) {
sql := "SELECT name FROM migrations where status = 'update' ORDER BY id_migration DESC LIMIT 1"
if rows, err := db.Query(sql); err != nil {
logger.Fatalf("Could not retrieve migrations: %s", err)
beeLogger.Log.Fatalf("Could not retrieve migrations: %s", err)
} else {
if rows.Next() {
if err := rows.Scan(&file); err != nil {
logger.Fatalf("Could not read migrations in database: %s", err)
beeLogger.Log.Fatalf("Could not read migrations in database: %s", err)
}
createdAtStr := file[len(file)-15:]
if t, err := time.Parse("20060102_150405", createdAtStr); err != nil {
logger.Fatalf("Could not parse time: %s", err)
beeLogger.Log.Fatalf("Could not parse time: %s", err)
} else {
createdAt = t.Unix()
}
} else {
// migration table has no 'update' record, no point rolling back
if goal == "rollback" {
logger.Fatal("There is nothing to rollback")
beeLogger.Log.Fatal("There is nothing to rollback")
}
file, createdAt = "", 0
}
@ -279,7 +260,7 @@ func getLatestMigration(db *sql.DB, goal string) (file string, createdAt int64)
func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime int64, latestName string, task string) {
changeDir(dir)
if f, err := os.OpenFile(source, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err != nil {
logger.Fatalf("Could not create file: %s", err)
beeLogger.Log.Fatalf("Could not create file: %s", err)
} else {
content := strings.Replace(MigrationMainTPL, "{{DBDriver}}", driver, -1)
content = strings.Replace(content, "{{ConnStr}}", connStr, -1)
@ -287,9 +268,9 @@ func writeMigrationSourceFile(dir, source, driver, connStr string, latestTime in
content = strings.Replace(content, "{{LatestName}}", latestName, -1)
content = strings.Replace(content, "{{Task}}", task, -1)
if _, err := f.WriteString(content); err != nil {
logger.Fatalf("Could not write to file: %s", err)
beeLogger.Log.Fatalf("Could not write to file: %s", err)
}
CloseFile(f)
utils.CloseFile(f)
}
}
@ -298,7 +279,7 @@ func buildMigrationBinary(dir, binary string) {
changeDir(dir)
cmd := exec.Command("go", "build", "-o", binary)
if out, err := cmd.CombinedOutput(); err != nil {
logger.Errorf("Could not build migration binary: %s", err)
beeLogger.Log.Errorf("Could not build migration binary: %s", err)
formatShellErrOutput(string(out))
removeTempFile(dir, binary)
removeTempFile(dir, binary+".go")
@ -312,7 +293,7 @@ func runMigrationBinary(dir, binary string) {
cmd := exec.Command("./" + binary)
if out, err := cmd.CombinedOutput(); err != nil {
formatShellOutput(string(out))
logger.Errorf("Could not run migration binary: %s", err)
beeLogger.Log.Errorf("Could not run migration binary: %s", err)
removeTempFile(dir, binary)
removeTempFile(dir, binary+".go")
os.Exit(2)
@ -325,7 +306,7 @@ func runMigrationBinary(dir, binary string) {
// It exits the system when encouter an error
func changeDir(dir string) {
if err := os.Chdir(dir); err != nil {
logger.Fatalf("Could not find migration directory: %s", err)
beeLogger.Log.Fatalf("Could not find migration directory: %s", err)
}
}
@ -333,7 +314,7 @@ func changeDir(dir string) {
func removeTempFile(dir, file string) {
changeDir(dir)
if err := os.Remove(file); err != nil {
logger.Warnf("Could not remove temporary file: %s", err)
beeLogger.Log.Warnf("Could not remove temporary file: %s", err)
}
}
@ -341,7 +322,7 @@ func removeTempFile(dir, file string) {
func formatShellErrOutput(o string) {
for _, line := range strings.Split(o, "\n") {
if line != "" {
logger.Errorf("|> %s", line)
beeLogger.Log.Errorf("|> %s", line)
}
}
}
@ -350,7 +331,7 @@ func formatShellErrOutput(o string) {
func formatShellOutput(o string) {
for _, line := range strings.Split(o, "\n") {
if line != "" {
logger.Infof("|> %s", line)
beeLogger.Log.Infof("|> %s", line)
}
}
}
@ -421,3 +402,23 @@ CREATE TABLE migrations (
status migrations_status
)`
)
// MigrateUpdate does the schema update
func MigrateUpdate(currpath, driver, connStr string) {
migrate("upgrade", currpath, driver, connStr)
}
// MigrateRollback rolls back the latest migration
func MigrateRollback(currpath, driver, connStr string) {
migrate("rollback", currpath, driver, connStr)
}
// MigrateReset rolls back all migrations
func MigrateReset(currpath, driver, connStr string) {
migrate("reset", currpath, driver, connStr)
}
// migrationRefresh rolls back all migrations and start over again
func MigrateRefresh(currpath, driver, connStr string) {
migrate("refresh", currpath, driver, connStr)
}

View File

@ -12,16 +12,22 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package new
import (
"fmt"
"os"
path "path/filepath"
"strings"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
)
var cmdNew = &Command{
var CmdNew = &commands.Command{
UsageLine: "new [appname]",
Short: "Creates a Beego application",
Long: `
@ -47,74 +53,8 @@ Creates a Beego application for the given app name in the current directory.
index.tpl
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
Run: createApp,
}
func createApp(cmd *Command, args []string) int {
output := cmd.Out()
if len(args) != 1 {
logger.Fatal("Argument [appname] is missing")
}
apppath, packpath, err := checkEnv(args[0])
if err != nil {
logger.Fatalf("%s", err)
}
if isExist(apppath) {
logger.Errorf(bold("Application '%s' already exists"), apppath)
logger.Warn(bold("Do you want to overwrite it? [Yes|No] "))
if !askForConfirmation() {
os.Exit(2)
}
}
logger.Info("Creating application...")
os.MkdirAll(apppath, 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "controllers"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "models"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "routers"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "tests"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "js"), 0755)
WriteToFile(path.Join(apppath, "static", "js", "reload.min.js"), reloadJsClient)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "js")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "css"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "css")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "img"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "img")+string(path.Separator), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "views"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
WriteToFile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", path.Base(args[0]), -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "default.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "controllers", "default.go"), controllers)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views", "index.tpl"), "\x1b[0m")
WriteToFile(path.Join(apppath, "views", "index.tpl"), indextpl)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "routers", "router.go"), strings.Replace(router, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(test, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", packpath, -1))
logger.Success("New application successfully created!")
return 0
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: CreateApp,
}
var appconf = `appname = {{.Appname}}
@ -303,3 +243,73 @@ var indextpl = `<!DOCTYPE html>
var reloadJsClient = `function b(a){var c=new WebSocket(a);c.onclose=function(){setTimeout(function(){b(a)},2E3)};c.onmessage=function(){location.reload()}}try{if(window.WebSocket)try{b("ws://localhost:12450/reload")}catch(a){console.error(a)}else console.log("Your browser does not support WebSockets.")}catch(a){console.error("Exception during connecting to Reload:",a)};
`
func init() {
commands.AvailableCommands = append(commands.AvailableCommands, CmdNew)
}
func CreateApp(cmd *commands.Command, args []string) int {
output := cmd.Out()
if len(args) != 1 {
beeLogger.Log.Fatal("Argument [appname] is missing")
}
apppath, packpath, err := utils.CheckEnv(args[0])
if err != nil {
beeLogger.Log.Fatalf("%s", err)
}
if utils.IsExist(apppath) {
beeLogger.Log.Errorf(colors.Bold("Application '%s' already exists"), apppath)
beeLogger.Log.Warn(colors.Bold("Do you want to overwrite it? [Yes|No] "))
if !utils.AskForConfirmation() {
os.Exit(2)
}
}
beeLogger.Log.Info("Creating application...")
os.MkdirAll(apppath, 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "controllers"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "models"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "routers"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "tests"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "js"), 0755)
utils.WriteToFile(path.Join(apppath, "static", "js", "reload.min.js"), reloadJsClient)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "js")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "css"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "css")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "static", "img"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "static", "img")+string(path.Separator), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views")+string(path.Separator), "\x1b[0m")
os.Mkdir(path.Join(apppath, "views"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "conf", "app.conf"), strings.Replace(appconf, "{{.Appname}}", path.Base(args[0]), -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "controllers", "default.go"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "controllers", "default.go"), controllers)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "views", "index.tpl"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "views", "index.tpl"), indextpl)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "routers", "router.go"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "routers", "router.go"), strings.Replace(router, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "tests", "default_test.go"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "tests", "default_test.go"), strings.Replace(test, "{{.Appname}}", packpath, -1))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
utils.WriteToFile(path.Join(apppath, "main.go"), strings.Replace(maingo, "{{.Appname}}", packpath, -1))
beeLogger.Log.Success("New application successfully created!")
return 0
}

View File

@ -1,18 +1,4 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
package pack
import (
"archive/tar"
@ -31,9 +17,14 @@ import (
"strings"
"syscall"
"time"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils"
)
var cmdPack = &Command{
var CmdPack = &commands.Command{
CustomFlags: true,
UsageLine: "pack",
Short: "Compresses a Beego application into a single file",
@ -43,7 +34,7 @@ var cmdPack = &Command{
{{"Example:"|bold}}
$ bee pack -v -ba="-ldflags '-s -w'"
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: packApp,
}
@ -52,27 +43,16 @@ var (
excludeP string
excludeS string
outputP string
excludeR ListOpts
excludeR utils.ListOpts
fsym bool
ssym bool
build bool
buildArgs string
buildEnvs ListOpts
buildEnvs utils.ListOpts
verbose bool
format string
)
type ListOpts []string
func (opts *ListOpts) String() string {
return fmt.Sprint(*opts)
}
func (opts *ListOpts) Set(value string) error {
*opts = append(*opts, value)
return nil
}
func init() {
fs := flag.NewFlagSet("pack", flag.ContinueOnError)
fs.StringVar(&appPath, "p", "", "Set the application path. Defaults to the current path.")
@ -87,7 +67,8 @@ func init() {
fs.BoolVar(&fsym, "fs", false, "Tell the command to follow symlinks. Defaults to false.")
fs.BoolVar(&ssym, "ss", false, "Tell the command to skip symlinks. Defaults to false.")
fs.BoolVar(&verbose, "v", false, "Be more verbose during the operation. Defaults to false.")
cmdPack.Flag = *fs
CmdPack.Flag = *fs
commands.AvailableCommands = append(commands.AvailableCommands, CmdPack)
}
type walker interface {
@ -115,10 +96,6 @@ type walkFileTree struct {
output *io.Writer
}
func (wft *walkFileTree) setPrefix(prefix string) {
wft.prefix = prefix
}
func (wft *walkFileTree) isExclude(fPath string) bool {
if fPath == "" {
return true
@ -316,12 +293,12 @@ func (wft *tarWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
return false, err
}
if isSym == false {
if !isSym {
fr, err := os.Open(fpath)
if err != nil {
return false, err
}
defer CloseFile(fr)
defer utils.CloseFile(fr)
_, err = io.Copy(tw, fr)
if err != nil {
return false, err
@ -352,12 +329,12 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
return false, err
}
if isSym == false {
if !isSym {
fr, err := os.Open(fpath)
if err != nil {
return false, err
}
defer CloseFile(fr)
defer utils.CloseFile(fr)
_, err = io.Copy(w, fr)
if err != nil {
return false, err
@ -379,10 +356,10 @@ func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
func packDirectory(output io.Writer, excludePrefix []string, excludeSuffix []string,
excludeRegexp []*regexp.Regexp, includePath ...string) (err error) {
logger.Infof("Excluding relpath prefix: %s", strings.Join(excludePrefix, ":"))
logger.Infof("Excluding relpath suffix: %s", strings.Join(excludeSuffix, ":"))
beeLogger.Log.Infof("Excluding relpath prefix: %s", strings.Join(excludePrefix, ":"))
beeLogger.Log.Infof("Excluding relpath suffix: %s", strings.Join(excludeSuffix, ":"))
if len(excludeRegexp) > 0 {
logger.Infof("Excluding filename regex: `%s`", strings.Join(excludeR, "`, `"))
beeLogger.Log.Infof("Excluding filename regex: `%s`", strings.Join(excludeR, "`, `"))
}
w, err := os.OpenFile(outputP, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
@ -437,10 +414,10 @@ func packDirectory(output io.Writer, excludePrefix []string, excludeSuffix []str
return
}
func packApp(cmd *Command, args []string) int {
func packApp(cmd *commands.Command, args []string) int {
output := cmd.Out()
curPath, _ := os.Getwd()
thePath := ""
var thePath string
nArgs := []string{}
has := false
@ -454,19 +431,19 @@ func packApp(cmd *Command, args []string) int {
}
cmd.Flag.Parse(nArgs)
if path.IsAbs(appPath) == false {
if !path.IsAbs(appPath) {
appPath = path.Join(curPath, appPath)
}
thePath, err := path.Abs(appPath)
if err != nil {
logger.Fatalf("Wrong application path: %s", thePath)
beeLogger.Log.Fatalf("Wrong application path: %s", thePath)
}
if stat, err := os.Stat(thePath); os.IsNotExist(err) || stat.IsDir() == false {
logger.Fatalf("Application path does not exist: %s", thePath)
if stat, err := os.Stat(thePath); os.IsNotExist(err) || !stat.IsDir() {
beeLogger.Log.Fatalf("Application path does not exist: %s", thePath)
}
logger.Infof("Packaging application on '%s'...", thePath)
beeLogger.Log.Infof("Packaging application on '%s'...", thePath)
appName := path.Base(thePath)
@ -488,12 +465,12 @@ func packApp(cmd *Command, args []string) int {
// Remove the tmpdir once bee pack is done
err := os.RemoveAll(tmpdir)
if err != nil {
logger.Error("Failed to remove the generated temp dir")
beeLogger.Log.Error("Failed to remove the generated temp dir")
}
}()
if build {
logger.Info("Building application...")
beeLogger.Log.Info("Building application...")
var envs []string
for _, env := range buildEnvs {
parts := strings.SplitN(env, "=", 2)
@ -515,7 +492,7 @@ func packApp(cmd *Command, args []string) int {
os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch)
logger.Infof("Using: GOOS=%s GOARCH=%s", goos, goarch)
beeLogger.Log.Infof("Using: GOOS=%s GOARCH=%s", goos, goarch)
binPath := path.Join(tmpdir, appName)
if goos == "windows" {
@ -538,10 +515,10 @@ func packApp(cmd *Command, args []string) int {
execmd.Dir = thePath
err = execmd.Run()
if err != nil {
logger.Fatal(err.Error())
beeLogger.Log.Fatal(err.Error())
}
logger.Success("Build Successful!")
beeLogger.Log.Success("Build Successful!")
}
switch format {
@ -552,14 +529,14 @@ func packApp(cmd *Command, args []string) int {
outputN := appName + "." + format
if outputP == "" || path.IsAbs(outputP) == false {
if outputP == "" || !path.IsAbs(outputP) {
outputP = path.Join(curPath, outputP)
}
if _, err := os.Stat(outputP); err != nil {
err = os.MkdirAll(outputP, 0755)
if err != nil {
logger.Fatal(err.Error())
beeLogger.Log.Fatal(err.Error())
}
}
@ -581,20 +558,20 @@ func packApp(cmd *Command, args []string) int {
for _, r := range excludeR {
if len(r) > 0 {
if re, err := regexp.Compile(r); err != nil {
logger.Fatal(err.Error())
beeLogger.Log.Fatal(err.Error())
} else {
exr = append(exr, re)
}
}
}
logger.Infof("Writing to output: %s", outputP)
beeLogger.Log.Infof("Writing to output: %s", outputP)
err = packDirectory(output, exp, exs, exr, tmpdir, thePath)
if err != nil {
logger.Fatal(err.Error())
beeLogger.Log.Fatal(err.Error())
}
logger.Success("Application packed!")
beeLogger.Log.Success("Application packed!")
return 0
}

102
cmd/commands/rs/rs.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2017 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package rs ...
package rs
import (
"fmt"
"os"
"os/exec"
"runtime"
"time"
"strings"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/config"
"github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
)
var cmdRs = &commands.Command{
UsageLine: "rs",
Short: "Run customized scripts",
Long: `Run script allows you to run arbitrary commands using Bee.
Custom commands are provided from the "scripts" object inside bee.json or Beefile.
To run a custom command, use: {{"$ bee rs mycmd ARGS" | bold}}
{{if len .}}
{{"AVAILABLE SCRIPTS"|headline}}{{range $cmdName, $cmd := .}}
{{$cmdName | bold}}
{{$cmd}}{{end}}{{end}}
`,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: runScript,
}
func init() {
config.LoadConfig()
cmdRs.Long = utils.TmplToString(cmdRs.Long, config.Conf.Scripts)
commands.AvailableCommands = append(commands.AvailableCommands, cmdRs)
}
func runScript(cmd *commands.Command, args []string) int {
if len(args) == 0 {
cmd.Usage()
}
start := time.Now()
script, args := args[0], args[1:]
if c, exist := config.Conf.Scripts[script]; exist {
command := customCommand{
Name: script,
Command: c,
Args: args,
}
if err := command.run(); err != nil {
beeLogger.Log.Error(err.Error())
}
} else {
beeLogger.Log.Errorf("Command '%s' not found in Beefile/bee.json", script)
}
elapsed := time.Since(start)
fmt.Println(colors.GreenBold(fmt.Sprintf("Finished in %s.", elapsed)))
return 0
}
type customCommand struct {
Name string
Command string
Args []string
}
func (c *customCommand) run() error {
beeLogger.Log.Info(colors.GreenBold(fmt.Sprintf("Running '%s'...", c.Name)))
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin", "linux":
args := append([]string{c.Command}, c.Args...)
cmd = exec.Command("sh", "-c", strings.Join(args, " "))
case "windows":
args := append([]string{c.Command}, c.Args...)
cmd = exec.Command("cmd", "/C", strings.Join(args, " "))
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

88
cmd/commands/run/docs.go Normal file
View File

@ -0,0 +1,88 @@
package run
import (
"archive/zip"
"io"
"net/http"
"os"
"strings"
beeLogger "github.com/beego/bee/logger"
)
var (
swaggerVersion = "3"
swaggerlink = "https://github.com/beego/swagger/archive/v" + swaggerVersion + ".zip"
)
func downloadFromURL(url, fileName string) {
var down bool
if fd, err := os.Stat(fileName); err != nil && os.IsNotExist(err) {
down = true
} else if fd.Size() == int64(0) {
down = true
} else {
beeLogger.Log.Infof("'%s' already exists", fileName)
return
}
if down {
beeLogger.Log.Infof("Downloading '%s' to '%s'...", url, fileName)
output, err := os.Create(fileName)
if err != nil {
beeLogger.Log.Errorf("Error while creating '%s': %s", fileName, err)
return
}
defer output.Close()
response, err := http.Get(url)
if err != nil {
beeLogger.Log.Errorf("Error while downloading '%s': %s", url, err)
return
}
defer response.Body.Close()
n, err := io.Copy(output, response.Body)
if err != nil {
beeLogger.Log.Errorf("Error while downloading '%s': %s", url, err)
return
}
beeLogger.Log.Successf("%d bytes downloaded!", n)
}
}
func unzipAndDelete(src string) error {
beeLogger.Log.Infof("Unzipping '%s'...", src)
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
rp := strings.NewReplacer("swagger-"+swaggerVersion, "swagger")
for _, f := range r.File {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
fname := rp.Replace(f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(fname, f.Mode())
} else {
f, err := os.OpenFile(
fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
}
beeLogger.Log.Successf("Done! Deleting '%s'...", src)
return os.RemoveAll(src)
}

View File

@ -11,13 +11,14 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
package run
import (
"bytes"
"net/http"
"time"
beeLogger "github.com/beego/bee/logger"
"github.com/gorilla/websocket"
)
@ -70,7 +71,7 @@ func (c *wsClient) readPump() {
_, _, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
logger.Errorf("An error happened when reading from the Websocket client: %v", err)
beeLogger.Log.Errorf("An error happened when reading from the Websocket client: %v", err)
}
break
}
@ -109,7 +110,7 @@ func (c *wsClient) writePump() {
n := len(c.send)
for i := 0; i < n; i++ {
w.Write(newline)
w.Write([]byte("/n"))
w.Write(<-c.send)
}
@ -155,13 +156,13 @@ func startReloadServer() {
})
go startServer()
logger.Infof("Reload server listening at %s", reloadAddress)
beeLogger.Log.Infof("Reload server listening at %s", reloadAddress)
}
func startServer() {
err := http.ListenAndServe(reloadAddress, nil)
if err != nil {
logger.Errorf("Failed to start up the Reload server: %v", err)
beeLogger.Log.Errorf("Failed to start up the Reload server: %v", err)
return
}
}
@ -175,7 +176,7 @@ func sendReload(payload string) {
func handleWsRequest(broker *wsBroker, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
logger.Errorf("error while upgrading server connection: %v", err)
beeLogger.Log.Errorf("error while upgrading server connection: %v", err)
return
}

View File

@ -11,8 +11,7 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
package run
import (
"io/ioutil"
@ -20,25 +19,31 @@ import (
path "path/filepath"
"runtime"
"strings"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/config"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils"
)
var cmdRun = &Command{
UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] [-vendor=true] [-e=folderToExclude] [-tags=goBuildTags] [-runmode=BEEGO_RUNMODE]",
var CmdRun = &commands.Command{
UsageLine: "run [appname] [watchall] [-main=*.go] [-downdoc=true] [-gendoc=true] [-vendor=true] [-e=folderToExclude] [-ex=extraPackageToWatch] [-tags=goBuildTags] [-runmode=BEEGO_RUNMODE]",
Short: "Run the application by starting a local development server",
Long: `
Run command will supervise the filesystem of the application for any changes, and recompile/restart it.
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
Run: runApp,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: RunApp,
}
var (
mainFiles ListOpts
downdoc docValue
gendoc docValue
mainFiles utils.ListOpts
downdoc utils.DocValue
gendoc utils.DocValue
// The flags list of the paths excluded from watching
excludedPaths strFlags
excludedPaths utils.StrFlags
// Pass through to -tags arg of "go build"
buildTags string
// Application path
@ -53,65 +58,64 @@ var (
currentGoPath string
// Current runmode
runmode string
// Extra directories
extraPackages utils.StrFlags
)
var started = make(chan bool)
func init() {
cmdRun.Flag.Var(&mainFiles, "main", "Specify main go files.")
cmdRun.Flag.Var(&gendoc, "gendoc", "Enable auto-generate the docs.")
cmdRun.Flag.Var(&downdoc, "downdoc", "Enable auto-download of the swagger file if it does not exist.")
cmdRun.Flag.Var(&excludedPaths, "e", "List of paths to exclude.")
cmdRun.Flag.BoolVar(&vendorWatch, "vendor", false, "Enable watch vendor folder.")
cmdRun.Flag.StringVar(&buildTags, "tags", "", "Set the build tags. See: https://golang.org/pkg/go/build/")
cmdRun.Flag.StringVar(&runmode, "runmode", "", "Set the Beego run mode.")
CmdRun.Flag.Var(&mainFiles, "main", "Specify main go files.")
CmdRun.Flag.Var(&gendoc, "gendoc", "Enable auto-generate the docs.")
CmdRun.Flag.Var(&downdoc, "downdoc", "Enable auto-download of the swagger file if it does not exist.")
CmdRun.Flag.Var(&excludedPaths, "e", "List of paths to exclude.")
CmdRun.Flag.BoolVar(&vendorWatch, "vendor", false, "Enable watch vendor folder.")
CmdRun.Flag.StringVar(&buildTags, "tags", "", "Set the build tags. See: https://golang.org/pkg/go/build/")
CmdRun.Flag.StringVar(&runmode, "runmode", "", "Set the Beego run mode.")
CmdRun.Flag.Var(&extraPackages, "ex", "List of extra package to watch.")
exit = make(chan bool)
commands.AvailableCommands = append(commands.AvailableCommands, CmdRun)
}
func runApp(cmd *Command, args []string) int {
func RunApp(cmd *commands.Command, args []string) int {
if len(args) == 0 || args[0] == "watchall" {
currpath, _ = os.Getwd()
if found, _gopath, _ := SearchGOPATHs(currpath); found {
if found, _gopath, _ := utils.SearchGOPATHs(currpath); found {
appname = path.Base(currpath)
currentGoPath = _gopath
} else {
logger.Fatalf("No application '%s' found in your GOPATH", currpath)
beeLogger.Log.Fatalf("No application '%s' found in your GOPATH", currpath)
}
} else {
// Check if passed Bee application path/name exists in the GOPATH(s)
if found, _gopath, _path := SearchGOPATHs(args[0]); found {
if found, _gopath, _path := utils.SearchGOPATHs(args[0]); found {
currpath = _path
currentGoPath = _gopath
appname = path.Base(currpath)
} else {
logger.Fatalf("No application '%s' found in your GOPATH", args[0])
beeLogger.Log.Fatalf("No application '%s' found in your GOPATH", args[0])
}
if strings.HasSuffix(appname, ".go") && isExist(currpath) {
logger.Warnf("The appname is in conflict with file's current path. Do you want to build appname as '%s'", appname)
logger.Info("Do you want to overwrite it? [yes|no] ")
if !askForConfirmation() {
if strings.HasSuffix(appname, ".go") && utils.IsExist(currpath) {
beeLogger.Log.Warnf("The appname is in conflict with file's current path. Do you want to build appname as '%s'", appname)
beeLogger.Log.Info("Do you want to overwrite it? [yes|no] ")
if !utils.AskForConfirmation() {
return 0
}
}
}
logger.Infof("Using '%s' as 'appname'", appname)
beeLogger.Log.Infof("Using '%s' as 'appname'", appname)
logger.Debugf("Current path: %s", __FILE__(), __LINE__(), currpath)
beeLogger.Log.Debugf("Current path: %s", utils.FILE(), utils.LINE(), currpath)
if runmode == "prod" || runmode == "dev" {
os.Setenv("BEEGO_RUNMODE", runmode)
logger.Infof("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
beeLogger.Log.Infof("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
} else if runmode != "" {
os.Setenv("BEEGO_RUNMODE", runmode)
logger.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
beeLogger.Log.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
} else if os.Getenv("BEEGO_RUNMODE") != "" {
logger.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
}
err := loadConfig()
if err != nil {
logger.Fatalf("Failed to load configuration: %s", err)
beeLogger.Log.Warnf("Using '%s' as 'runmode'", os.Getenv("BEEGO_RUNMODE"))
}
var paths []string
@ -119,10 +123,32 @@ func runApp(cmd *Command, args []string) int {
// Because monitor files has some issues, we watch current directory
// and ignore non-go files.
for _, p := range conf.DirStruct.Others {
for _, p := range config.Conf.DirStruct.Others {
paths = append(paths, strings.Replace(p, "$GOPATH", currentGoPath, -1))
}
if len(extraPackages) > 0 {
// get the full path
for _, packagePath := range extraPackages {
if found, _, _fullPath := utils.SearchGOPATHs(packagePath); found {
readAppDirectories(_fullPath, &paths)
} else {
beeLogger.Log.Warnf("No extra package '%s' found in your GOPATH", packagePath)
}
}
// let paths unique
strSet := make(map[string]struct{})
for _, p := range paths {
strSet[p] = struct{}{}
}
paths = make([]string, len(strSet))
index := 0
for i := range strSet {
paths[index] = i
index++
}
}
files := []string{}
for _, arg := range mainFiles {
if len(arg) > 0 {
@ -139,7 +165,7 @@ func runApp(cmd *Command, args []string) int {
}
// Start the Reload server (if enabled)
if conf.EnableReload {
if config.Conf.EnableReload {
startReloadServer()
}
if gendoc == "true" {
@ -151,10 +177,8 @@ func runApp(cmd *Command, args []string) int {
}
for {
select {
case <-exit:
runtime.Goexit()
}
<-exit
runtime.Goexit()
}
}
@ -181,21 +205,20 @@ func readAppDirectories(directory string, paths *[]string) {
continue
}
if fileInfo.IsDir() == true && fileInfo.Name()[0] != '.' {
if fileInfo.IsDir() && fileInfo.Name()[0] != '.' {
readAppDirectories(directory+"/"+fileInfo.Name(), paths)
continue
}
if useDirectory == true {
if useDirectory {
continue
}
if path.Ext(fileInfo.Name()) == ".go" || (ifStaticFile(fileInfo.Name()) && conf.EnableReload) {
if path.Ext(fileInfo.Name()) == ".go" || (ifStaticFile(fileInfo.Name()) && config.Conf.EnableReload) {
*paths = append(*paths, directory)
useDirectory = true
}
}
return
}
// If a file is excluded
@ -203,16 +226,16 @@ func isExcluded(filePath string) bool {
for _, p := range excludedPaths {
absP, err := path.Abs(p)
if err != nil {
logger.Errorf("Cannot get absolute path of '%s'", p)
beeLogger.Log.Errorf("Cannot get absolute path of '%s'", p)
continue
}
absFilePath, err := path.Abs(filePath)
if err != nil {
logger.Errorf("Cannot get absolute path of '%s'", filePath)
beeLogger.Log.Errorf("Cannot get absolute path of '%s'", filePath)
break
}
if strings.HasPrefix(absFilePath, absP) {
logger.Infof("'%s' is not being watched", filePath)
beeLogger.Log.Infof("'%s' is not being watched", filePath)
return true
}
}

View File

@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package run
import (
"bytes"
@ -24,6 +24,10 @@ import (
"sync"
"time"
"github.com/beego/bee/config"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
"github.com/fsnotify/fsnotify"
)
@ -46,7 +50,7 @@ var (
func NewWatcher(paths []string, files []string, isgenerate bool) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
logger.Fatalf("Failed to create watcher: %s", err)
beeLogger.Log.Fatalf("Failed to create watcher: %s", err)
}
go func() {
@ -55,7 +59,7 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
case e := <-watcher.Events:
isBuild := true
if ifStaticFile(e.Name) && conf.EnableReload {
if ifStaticFile(e.Name) && config.Conf.EnableReload {
sendReload(e.String())
continue
}
@ -67,16 +71,16 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
continue
}
mt := getFileModTime(e.Name)
mt := utils.GetFileModTime(e.Name)
if t := eventTime[e.Name]; mt == t {
logger.Infof(bold("Skipping: ")+"%s", e.String())
beeLogger.Log.Hintf(colors.Bold("Skipping: ")+"%s", e.String())
isBuild = false
}
eventTime[e.Name] = mt
if isBuild {
logger.Infof("Event fired: %s", e)
beeLogger.Log.Hintf("Event fired: %s", e)
go func() {
// Wait 1s before autobuild until there is no file change.
scheduleTime = time.Now().Add(1 * time.Second)
@ -85,40 +89,21 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
}()
}
case err := <-watcher.Errors:
logger.Warnf("Watcher error: %s", err.Error()) // No need to exit here
beeLogger.Log.Warnf("Watcher error: %s", err.Error()) // No need to exit here
}
}
}()
logger.Info("Initializing watcher...")
beeLogger.Log.Info("Initializing watcher...")
for _, path := range paths {
logger.Infof(bold("Watching: ")+"%s", path)
beeLogger.Log.Hintf(colors.Bold("Watching: ")+"%s", path)
err = watcher.Add(path)
if err != nil {
logger.Fatalf("Failed to watch directory: %s", err)
beeLogger.Log.Fatalf("Failed to watch directory: %s", err)
}
}
}
// getFileModTime returns unix timestamp of `os.File.ModTime` for the given path.
func getFileModTime(path string) int64 {
path = strings.Replace(path, "\\", "/", -1)
f, err := os.Open(path)
if err != nil {
logger.Errorf("Failed to open file on '%s': %s", path, err)
return time.Now().Unix()
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
logger.Errorf("Failed to get file stats: %s", err)
return time.Now().Unix()
}
return fi.ModTime().Unix()
}
// AutoBuild builds the specified set of files
func AutoBuild(files []string, isgenerate bool) {
state.Lock()
@ -127,57 +112,32 @@ func AutoBuild(files []string, isgenerate bool) {
os.Chdir(currpath)
cmdName := "go"
if conf.Gopm.Enable {
cmdName = "gopm"
}
var (
err error
stderr bytes.Buffer
stdout bytes.Buffer
)
// For applications use full import path like "github.com/.../.."
// are able to use "go install" to reduce build time.
if conf.GoInstall {
if config.Conf.GoInstall {
icmd := exec.Command(cmdName, "install", "-v")
icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr
icmd.Env = append(os.Environ(), "GOGC=off")
icmd.Run()
}
if conf.Gopm.Install {
icmd := exec.Command("go", "list", "./...")
icmd.Stdout = &stdout
icmd.Env = append(os.Environ(), "GOGC=off")
err = icmd.Run()
if err == nil {
list := strings.Split(stdout.String(), "\n")[1:]
for _, pkg := range list {
if len(pkg) == 0 {
continue
}
icmd = exec.Command(cmdName, "install", pkg)
icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr
icmd.Env = append(os.Environ(), "GOGC=off")
err = icmd.Run()
if err != nil {
break
}
}
}
}
if isgenerate {
logger.Info("Generating the docs...")
beeLogger.Log.Info("Generating the docs...")
icmd := exec.Command("bee", "generate", "docs")
icmd.Env = append(os.Environ(), "GOGC=off")
err = icmd.Run()
if err != nil {
logger.Errorf("Failed to generate the docs.")
utils.Notify("", "Failed to generate the docs.")
beeLogger.Log.Errorf("Failed to generate the docs.")
return
}
logger.Success("Docs generated!")
beeLogger.Log.Success("Docs generated!")
}
if err == nil {
@ -198,12 +158,13 @@ func AutoBuild(files []string, isgenerate bool) {
bcmd.Stderr = &stderr
err = bcmd.Run()
if err != nil {
logger.Errorf("Failed to build the application: %s", stderr.String())
utils.Notify(stderr.String(), "Build Failed")
beeLogger.Log.Errorf("Failed to build the application: %s", stderr.String())
return
}
}
logger.Success("Built Successfully!")
beeLogger.Log.Success("Built Successfully!")
Restart(appname)
}
@ -211,39 +172,39 @@ func AutoBuild(files []string, isgenerate bool) {
func Kill() {
defer func() {
if e := recover(); e != nil {
logger.Infof("Kill recover: %s", e)
beeLogger.Log.Infof("Kill recover: %s", e)
}
}()
if cmd != nil && cmd.Process != nil {
err := cmd.Process.Kill()
if err != nil {
logger.Errorf("Error while killing cmd process: %s", err)
beeLogger.Log.Errorf("Error while killing cmd process: %s", err)
}
}
}
// Restart kills the running command process and starts it again
func Restart(appname string) {
logger.Debugf("Kill running process", __FILE__(), __LINE__())
beeLogger.Log.Debugf("Kill running process", utils.FILE(), utils.LINE())
Kill()
go Start(appname)
}
// Start starts the command process
func Start(appname string) {
logger.Infof("Restarting '%s'...", appname)
if strings.Index(appname, "./") == -1 {
beeLogger.Log.Infof("Restarting '%s'...", appname)
if !strings.Contains(appname, "./") {
appname = "./" + appname
}
cmd = exec.Command(appname)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Args = append([]string{appname}, conf.CmdArgs...)
cmd.Env = append(os.Environ(), conf.Envs...)
cmd.Args = append([]string{appname}, config.Conf.CmdArgs...)
cmd.Env = append(os.Environ(), config.Conf.Envs...)
go cmd.Run()
logger.Successf("'%s' is running...", appname)
beeLogger.Log.Successf("'%s' is running...", appname)
started <- true
}
@ -262,7 +223,7 @@ func shouldIgnoreFile(filename string) bool {
for _, regex := range ignoredFilesRegExps {
r, err := regexp.Compile(regex)
if err != nil {
logger.Fatalf("Could not compile regular expression: %s", err)
beeLogger.Log.Fatalf("Could not compile regular expression: %s", err)
}
if r.MatchString(filename) {
return true

View File

@ -0,0 +1,76 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package apiapp
import (
"net/http"
beeLogger "github.com/beego/bee/logger"
"os"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/cmd/commands/version"
"github.com/beego/bee/utils"
)
var CmdServer = &commands.Command{
// CustomFlags: true,
UsageLine: "server [port]",
Short: "serving static content over HTTP on port",
Long: `
The command 'server' creates a Beego API application.
`,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: createAPI,
}
var (
a utils.DocValue
p utils.DocValue
f utils.DocValue
)
func init() {
CmdServer.Flag.Var(&a, "a", "Listen address")
CmdServer.Flag.Var(&p, "p", "Listen port")
CmdServer.Flag.Var(&f, "f", "Static files fold")
commands.AvailableCommands = append(commands.AvailableCommands, CmdServer)
}
func createAPI(cmd *commands.Command, args []string) int {
if len(args) > 0 {
err := cmd.Flag.Parse(args[1:])
if err != nil {
beeLogger.Log.Error(err.Error())
}
}
if a == "" {
a = "127.0.0.1"
}
if p == "" {
p = "8080"
}
if f == "" {
cwd, _ := os.Getwd()
f = utils.DocValue(cwd)
}
beeLogger.Log.Infof("Start server on http://%s:%s, static file %s", a, p, f)
err := http.ListenAndServe(string(a)+":"+string(p), http.FileServer(http.Dir(f)))
if err != nil {
beeLogger.Log.Error(err.Error())
}
return 0
}

View File

@ -1,4 +1,4 @@
package main
package version
import (
"io"
@ -6,6 +6,10 @@ import (
"os"
"runtime"
"text/template"
"time"
beeLogger "github.com/beego/bee/logger"
)
// RuntimeInfo holds information about the current runtime.
@ -26,12 +30,12 @@ type RuntimeInfo struct {
// print the banner in case of error.
func InitBanner(out io.Writer, in io.Reader) {
if in == nil {
logger.Fatal("The input is nil")
beeLogger.Log.Fatal("The input is nil")
}
banner, err := ioutil.ReadAll(in)
if err != nil {
logger.Fatalf("Error while trying to read the banner: %s", err)
beeLogger.Log.Fatalf("Error while trying to read the banner: %s", err)
}
show(out, string(banner))
@ -43,11 +47,11 @@ func show(out io.Writer, content string) {
Parse(content)
if err != nil {
logger.Fatalf("Cannot parse the banner template: %s", err)
beeLogger.Log.Fatalf("Cannot parse the banner template: %s", err)
}
err = t.Execute(out, RuntimeInfo{
getGoVersion(),
GetGoVersion(),
runtime.GOOS,
runtime.GOARCH,
runtime.NumCPU(),
@ -55,7 +59,14 @@ func show(out io.Writer, content string) {
runtime.GOROOT(),
runtime.Compiler,
version,
getBeegoVersion(),
GetBeegoVersion(),
})
MustCheck(err)
if err != nil {
beeLogger.Log.Error(err.Error())
}
}
// Now returns the current local time in the specified layout
func Now(layout string) string {
return time.Now().Format(layout)
}

View File

@ -1,4 +1,4 @@
package main
package version
import (
"bufio"
@ -14,18 +14,13 @@ import (
"runtime"
"strings"
"github.com/beego/bee/cmd/commands"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
"gopkg.in/yaml.v2"
)
var cmdVersion = &Command{
UsageLine: "version",
Short: "Prints the current Bee version",
Long: `
Prints the current Bee, Beego and Go version alongside the platform information.
`,
Run: versionCmd,
}
const verboseVersionBanner string = `%s%s______
| ___ \
| |_/ / ___ ___
@ -52,21 +47,33 @@ const shortVersionBanner = `______
\____/ \___| \___| v{{ .BeeVersion }}
`
var CmdVersion = &commands.Command{
UsageLine: "version",
Short: "Prints the current Bee version",
Long: `
Prints the current Bee, Beego and Go version alongside the platform information.
`,
Run: versionCmd,
}
var outputFormat string
const version = "1.9.1"
func init() {
fs := flag.NewFlagSet("version", flag.ContinueOnError)
fs.StringVar(&outputFormat, "o", "", "Set the output format. Either json or yaml.")
cmdVersion.Flag = *fs
CmdVersion.Flag = *fs
commands.AvailableCommands = append(commands.AvailableCommands, CmdVersion)
}
func versionCmd(cmd *Command, args []string) int {
func versionCmd(cmd *commands.Command, args []string) int {
cmd.Flag.Parse(args)
stdout := cmd.Out()
if outputFormat != "" {
runtimeInfo := RuntimeInfo{
getGoVersion(),
GetGoVersion(),
runtime.GOOS,
runtime.GOARCH,
runtime.NumCPU(),
@ -74,20 +81,24 @@ func versionCmd(cmd *Command, args []string) int {
runtime.GOROOT(),
runtime.Compiler,
version,
getBeegoVersion(),
GetBeegoVersion(),
}
switch outputFormat {
case "json":
{
b, err := json.MarshalIndent(runtimeInfo, "", " ")
MustCheck(err)
if err != nil {
beeLogger.Log.Error(err.Error())
}
fmt.Println(string(b))
return 0
}
case "yaml":
{
b, err := yaml.Marshal(&runtimeInfo)
MustCheck(err)
if err != nil {
beeLogger.Log.Error(err.Error())
}
fmt.Println(string(b))
return 0
}
@ -102,21 +113,20 @@ func versionCmd(cmd *Command, args []string) int {
// ShowShortVersionBanner prints the short version banner.
func ShowShortVersionBanner() {
output := NewColorWriter(os.Stdout)
InitBanner(output, bytes.NewBufferString(MagentaBold(shortVersionBanner)))
output := colors.NewColorWriter(os.Stdout)
InitBanner(output, bytes.NewBufferString(colors.MagentaBold(shortVersionBanner)))
}
func getBeegoVersion() string {
gopath := os.Getenv("GOPATH")
func GetBeegoVersion() string {
re, err := regexp.Compile(`VERSION = "([0-9.]+)"`)
if err != nil {
return ""
}
if gopath == "" {
err = fmt.Errorf("You need to set GOPATH environment variable")
wgopath := utils.GetGOPATHs()
if len(wgopath) == 0 {
beeLogger.Log.Error("You need to set GOPATH environment variable")
return ""
}
wgopath := path.SplitList(gopath)
for _, wg := range wgopath {
wg, _ = path.EvalSymlinks(path.Join(wg, "src", "github.com", "astaxie", "beego"))
filename := path.Join(wg, "beego.go")
@ -125,11 +135,11 @@ func getBeegoVersion() string {
if os.IsNotExist(err) {
continue
}
logger.Error("Error while getting stats of 'beego.go'")
beeLogger.Log.Error("Error while getting stats of 'beego.go'")
}
fd, err := os.Open(filename)
if err != nil {
logger.Error("Error while reading 'beego.go'")
beeLogger.Log.Error("Error while reading 'beego.go'")
continue
}
reader := bufio.NewReader(fd)
@ -152,14 +162,14 @@ func getBeegoVersion() string {
return "Beego is not installed. Please do consider installing it first: https://github.com/astaxie/beego"
}
func getGoVersion() string {
func GetGoVersion() string {
var (
cmdOut []byte
err error
)
if cmdOut, err = exec.Command("go", "version").Output(); err != nil {
logger.Fatalf("There was an error running 'go version' command: %s", err)
beeLogger.Log.Fatalf("There was an error running 'go version' command: %s", err)
}
return strings.Split(string(cmdOut), " ")[2]
}

261
code.go
View File

@ -1,261 +0,0 @@
// Copyright 2011 Gary Burd
// Copyright 2013 Unknown
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import (
"go/ast"
"go/printer"
"go/scanner"
"go/token"
"math"
"strconv"
)
const (
notPredeclared = iota
predeclaredType
predeclaredConstant
predeclaredFunction
)
// predeclared represents the set of all predeclared identifiers.
var predeclared = map[string]int{
"bool": predeclaredType,
"byte": predeclaredType,
"complex128": predeclaredType,
"complex64": predeclaredType,
"error": predeclaredType,
"float32": predeclaredType,
"float64": predeclaredType,
"int16": predeclaredType,
"int32": predeclaredType,
"int64": predeclaredType,
"int8": predeclaredType,
"int": predeclaredType,
"rune": predeclaredType,
"string": predeclaredType,
"uint16": predeclaredType,
"uint32": predeclaredType,
"uint64": predeclaredType,
"uint8": predeclaredType,
"uint": predeclaredType,
"uintptr": predeclaredType,
"true": predeclaredConstant,
"false": predeclaredConstant,
"iota": predeclaredConstant,
"nil": predeclaredConstant,
"append": predeclaredFunction,
"cap": predeclaredFunction,
"close": predeclaredFunction,
"complex": predeclaredFunction,
"copy": predeclaredFunction,
"delete": predeclaredFunction,
"imag": predeclaredFunction,
"len": predeclaredFunction,
"make": predeclaredFunction,
"new": predeclaredFunction,
"panic": predeclaredFunction,
"print": predeclaredFunction,
"println": predeclaredFunction,
"real": predeclaredFunction,
"recover": predeclaredFunction,
}
const (
ExportLinkAnnotation AnnotationKind = iota
AnchorAnnotation
CommentAnnotation
PackageLinkAnnotation
BuiltinAnnotation
)
// annotationVisitor collects annotations.
type annotationVisitor struct {
annotations []Annotation
}
func (v *annotationVisitor) add(kind AnnotationKind, importPath string) {
v.annotations = append(v.annotations, Annotation{Kind: kind, ImportPath: importPath})
}
func (v *annotationVisitor) ignoreName() {
v.add(-1, "")
}
func (v *annotationVisitor) Visit(n ast.Node) ast.Visitor {
switch n := n.(type) {
case *ast.TypeSpec:
v.ignoreName()
ast.Walk(v, n.Type)
case *ast.FuncDecl:
if n.Recv != nil {
ast.Walk(v, n.Recv)
}
v.ignoreName()
ast.Walk(v, n.Type)
case *ast.Field:
for range n.Names {
v.ignoreName()
}
ast.Walk(v, n.Type)
case *ast.ValueSpec:
for range n.Names {
v.add(AnchorAnnotation, "")
}
if n.Type != nil {
ast.Walk(v, n.Type)
}
for _, x := range n.Values {
ast.Walk(v, x)
}
case *ast.Ident:
switch {
case n.Obj == nil && predeclared[n.Name] != notPredeclared:
v.add(BuiltinAnnotation, "")
case n.Obj != nil && ast.IsExported(n.Name):
v.add(ExportLinkAnnotation, "")
default:
v.ignoreName()
}
case *ast.SelectorExpr:
if x, _ := n.X.(*ast.Ident); x != nil {
if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
if path, err := strconv.Unquote(spec.Path.Value); err == nil {
v.add(PackageLinkAnnotation, path)
if path == "C" {
v.ignoreName()
} else {
v.add(ExportLinkAnnotation, path)
}
return nil
}
}
}
}
ast.Walk(v, n.X)
v.ignoreName()
default:
return v
}
return nil
}
func printDecl(decl ast.Node, fset *token.FileSet, buf []byte) (Code, []byte) {
v := &annotationVisitor{}
ast.Walk(v, decl)
buf = buf[:0]
err := (&printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}).Fprint(sliceWriter{&buf}, fset, decl)
if err != nil {
return Code{Text: err.Error()}, buf
}
var annotations []Annotation
var s scanner.Scanner
fset = token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(buf))
s.Init(file, buf, nil, scanner.ScanComments)
loop:
for {
pos, tok, lit := s.Scan()
switch tok {
case token.EOF:
break loop
case token.COMMENT:
p := file.Offset(pos)
e := p + len(lit)
if p > math.MaxInt16 || e > math.MaxInt16 {
break loop
}
annotations = append(annotations, Annotation{Kind: CommentAnnotation, Pos: int16(p), End: int16(e)})
case token.IDENT:
if len(v.annotations) == 0 {
// Oops!
break loop
}
annotation := v.annotations[0]
v.annotations = v.annotations[1:]
if annotation.Kind == -1 {
continue
}
p := file.Offset(pos)
e := p + len(lit)
if p > math.MaxInt16 || e > math.MaxInt16 {
break loop
}
annotation.Pos = int16(p)
annotation.End = int16(e)
if len(annotations) > 0 && annotation.Kind == ExportLinkAnnotation {
prev := annotations[len(annotations)-1]
if prev.Kind == PackageLinkAnnotation &&
prev.ImportPath == annotation.ImportPath &&
prev.End+1 == annotation.Pos {
// merge with previous
annotation.Pos = prev.Pos
annotations[len(annotations)-1] = annotation
continue loop
}
}
annotations = append(annotations, annotation)
}
}
return Code{Text: string(buf), Annotations: annotations}, buf
}
type AnnotationKind int16
type Annotation struct {
Pos, End int16
Kind AnnotationKind
ImportPath string
}
type Code struct {
Text string
Annotations []Annotation
}
func commentAnnotations(src string) []Annotation {
var annotations []Annotation
var s scanner.Scanner
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
s.Init(file, []byte(src), nil, scanner.ScanComments)
for {
pos, tok, lit := s.Scan()
switch tok {
case token.EOF:
return annotations
case token.COMMENT:
p := file.Offset(pos)
e := p + len(lit)
if p > math.MaxInt16 || e > math.MaxInt16 {
return annotations
}
annotations = append(annotations, Annotation{Kind: CommentAnnotation, Pos: int16(p), End: int16(e)})
}
}
}
type sliceWriter struct{ p *[]byte }
func (w sliceWriter) Write(p []byte) (int, error) {
*w.p = append(*w.p, p...)
return len(p), nil
}

177
conf.go
View File

@ -1,177 +0,0 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import (
"encoding/json"
"io/ioutil"
"os"
"io"
"path/filepath"
"gopkg.in/yaml.v2"
)
const confVer = 0
var defaultConf = `{
"version": 0,
"gopm": {
"enable": false,
"install": false
},
"go_install": true,
"watch_ext": [],
"dir_structure": {
"watch_all": false,
"controllers": "",
"models": "",
"others": []
},
"cmd_args": [],
"envs": [],
"database": {
"driver": "mysql"
},
"enable_reload": false
}
`
var conf struct {
Version int
// gopm support
Gopm struct {
Enable bool
Install bool
}
// Indicates whether execute "go install" before "go build".
GoInstall bool `json:"go_install" yaml:"go_install"`
WatchExt []string `json:"watch_ext" yaml:"watch_ext"`
DirStruct struct {
WatchAll bool `json:"watch_all" yaml:"watch_all"`
Controllers string
Models string
Others []string // Other directories.
} `json:"dir_structure" yaml:"dir_structure"`
CmdArgs []string `json:"cmd_args" yaml:"cmd_args"`
Envs []string
Bale struct {
Import string
Dirs []string
IngExt []string `json:"ignore_ext" yaml:"ignore_ext"`
}
Database struct {
Driver string
Conn string
}
EnableReload bool `json:"enable_reload" yaml:"enable_reload"`
}
// loadConfig loads customized configuration.
func loadConfig() (err error) {
err = filepath.Walk(".", func(path string, fileInfo os.FileInfo, err error) error {
if err != nil {
return nil
}
if fileInfo.IsDir() {
return nil
}
if fileInfo.Name() == "bee.json" {
logger.Info("Loading configuration from 'bee.json'...")
err = parseJSON(path, &conf)
if err != nil {
logger.Errorf("Failed to parse JSON file: %s", err)
return err
}
return io.EOF
}
if fileInfo.Name() == "Beefile" {
logger.Info("Loading configuration from 'Beefile'...")
err = parseYAML(path, &conf)
if err != nil {
logger.Errorf("Failed to parse YAML file: %s", err)
return err
}
return io.EOF
}
return nil
})
// In case no configuration file found or an error different than io.EOF,
// fallback to default configuration
if err != io.EOF {
logger.Info("Loading default configuration...")
err = json.Unmarshal([]byte(defaultConf), &conf)
if err != nil {
return
}
}
// No need to return io.EOF error
err = nil
// Check format version
if conf.Version != confVer {
logger.Warn("Your configuration file is outdated. Please do consider updating it.")
logger.Hint("Check the latest version of bee's configuration file.")
}
// Set variables
if len(conf.DirStruct.Controllers) == 0 {
conf.DirStruct.Controllers = "controllers"
}
if len(conf.DirStruct.Models) == 0 {
conf.DirStruct.Models = "models"
}
// Append watch exts
watchExts = append(watchExts, conf.WatchExt...)
return
}
func parseJSON(path string, v interface{}) error {
var (
data []byte
err error
)
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}
err = json.Unmarshal(data, v)
if err != nil {
return err
}
return nil
}
func parseYAML(path string, v interface{}) error {
var (
data []byte
err error
)
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}
err = yaml.Unmarshal(data, v)
if err != nil {
return err
}
return nil
}

159
config/conf.go Normal file
View File

@ -0,0 +1,159 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package config
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
beeLogger "github.com/beego/bee/logger"
"gopkg.in/yaml.v2"
)
const confVer = 0
var Conf = struct {
Version int
GoInstall bool `json:"go_install" yaml:"go_install"` // Indicates whether execute "go install" before "go build".
DirStruct dirStruct `json:"dir_structure" yaml:"dir_structure"`
CmdArgs []string `json:"cmd_args" yaml:"cmd_args"`
Envs []string
Bale bale
Database database
EnableReload bool `json:"enable_reload" yaml:"enable_reload"`
EnableNotification bool `json:"enable_notification" yaml:"enable_notification"`
Scripts map[string]string `json:"scripts" yaml:"scripts"`
}{
GoInstall: true,
DirStruct: dirStruct{
Others: []string{},
},
CmdArgs: []string{},
Envs: []string{},
Bale: bale{
Dirs: []string{},
IngExt: []string{},
},
Database: database{
Driver: "mysql",
},
EnableNotification: true,
Scripts: map[string]string{},
}
// dirStruct describes the application's directory structure
type dirStruct struct {
WatchAll bool `json:"watch_all" yaml:"watch_all"`
Controllers string
Models string
Others []string // Other directories
}
// bale
type bale struct {
Import string
Dirs []string
IngExt []string `json:"ignore_ext" yaml:"ignore_ext"`
}
// database holds the database connection information
type database struct {
Driver string
Conn string
}
// LoadConfig loads the bee tool configuration.
// It looks for Beefile or bee.json in the current path,
// and falls back to default configuration in case not found.
func LoadConfig() {
currentPath, err := os.Getwd()
if err != nil {
beeLogger.Log.Error(err.Error())
}
dir, err := os.Open(currentPath)
if err != nil {
beeLogger.Log.Error(err.Error())
}
defer dir.Close()
files, err := dir.Readdir(-1)
if err != nil {
beeLogger.Log.Error(err.Error())
}
for _, file := range files {
switch file.Name() {
case "bee.json":
{
err = parseJSON(filepath.Join(currentPath, file.Name()), &Conf)
if err != nil {
beeLogger.Log.Errorf("Failed to parse JSON file: %s", err)
}
break
}
case "Beefile":
{
err = parseYAML(filepath.Join(currentPath, file.Name()), &Conf)
if err != nil {
beeLogger.Log.Errorf("Failed to parse YAML file: %s", err)
}
break
}
}
}
// Check format version
if Conf.Version != confVer {
beeLogger.Log.Warn("Your configuration file is outdated. Please do consider updating it.")
beeLogger.Log.Hint("Check the latest version of bee's configuration file.")
}
// Set variables
if len(Conf.DirStruct.Controllers) == 0 {
Conf.DirStruct.Controllers = "controllers"
}
if len(Conf.DirStruct.Models) == 0 {
Conf.DirStruct.Models = "models"
}
}
func parseJSON(path string, v interface{}) error {
var (
data []byte
err error
)
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}
err = json.Unmarshal(data, v)
return err
}
func parseYAML(path string, v interface{}) error {
var (
data []byte
err error
)
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}
err = yaml.Unmarshal(data, v)
return err
}

200
g.go
View File

@ -1,200 +0,0 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import (
"os"
"strings"
)
var cmdGenerate = &Command{
UsageLine: "generate [command]",
Short: "Source code generator",
Long: ` {{"To scaffold out your entire application:"|bold}}
$ bee generate scaffold [scaffoldname] [-fields="title:string,body:text"] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
{{"To generate a Model based on fields:"|bold}}
$ bee generate model [modelname] [-fields="name:type"]
{{"To generate a controller:"|bold}}
$ bee generate controller [controllerfile]
{{"To generate a CRUD view:"|bold}}
$ bee generate view [viewpath]
{{"To generate a migration file for making database schema updates:"|bold}}
$ bee generate migration [migrationfile] [-fields="name:type"]
{{"To generate swagger doc file:"|bold}}
$ bee generate docs
{{"To generate a test case:"|bold}}
$ bee generate test [routerfile]
{{"To generate appcode based on an existing database:"|bold}}
$ bee generate appcode [-tables=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] [-level=3]
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
Run: generateCode,
}
var driver docValue
var conn docValue
var level docValue
var tables docValue
var fields docValue
func init() {
cmdGenerate.Flag.Var(&tables, "tables", "List of table names separated by a comma.")
cmdGenerate.Flag.Var(&driver, "driver", "Database driver. Either mysql, postgres or sqlite.")
cmdGenerate.Flag.Var(&conn, "conn", "Connection string used by the driver to connect to a database instance.")
cmdGenerate.Flag.Var(&level, "level", "Either 1, 2 or 3. i.e. 1=models; 2=models and controllers; 3=models, controllers and routers.")
cmdGenerate.Flag.Var(&fields, "fields", "List of table fields.")
}
func generateCode(cmd *Command, args []string) int {
currpath, _ := os.Getwd()
if len(args) < 1 {
logger.Fatal("Command is missing")
}
gps := GetGOPATHs()
if len(gps) == 0 {
logger.Fatal("GOPATH environment variable is not set or empty")
}
gopath := gps[0]
logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath)
gcmd := args[0]
switch gcmd {
case "scaffold":
if len(args) < 2 {
logger.Fatal("Wrong number of arguments. Run: bee help generate")
}
// Load the configuration
err := loadConfig()
if err != nil {
logger.Fatalf("Failed to load configuration: %s", err)
}
cmd.Flag.Parse(args[2:])
if driver == "" {
driver = docValue(conf.Database.Driver)
if driver == "" {
driver = "mysql"
}
}
if conn == "" {
conn = docValue(conf.Database.Conn)
if conn == "" {
conn = "root:@tcp(127.0.0.1:3306)/test"
}
}
if fields == "" {
logger.Hint("fields option should not be empty, i.e. -fields=\"title:string,body:text\"")
logger.Fatal("Wrong number of arguments. Run: bee help generate")
}
sname := args[1]
generateScaffold(sname, fields.String(), currpath, driver.String(), conn.String())
case "docs":
generateDocs(currpath)
case "appcode":
// Load the configuration
err := loadConfig()
if err != nil {
logger.Fatalf("Failed to load configuration: %s", err)
}
cmd.Flag.Parse(args[1:])
if driver == "" {
driver = docValue(conf.Database.Driver)
if driver == "" {
driver = "mysql"
}
}
if conn == "" {
conn = docValue(conf.Database.Conn)
if conn == "" {
if driver == "mysql" {
conn = "root:@tcp(127.0.0.1:3306)/test"
} else if driver == "postgres" {
conn = "postgres://postgres:postgres@127.0.0.1:5432/postgres"
}
}
}
if level == "" {
level = "3"
}
logger.Infof("Using '%s' as 'driver'", driver)
logger.Infof("Using '%s' as 'conn'", conn)
logger.Infof("Using '%s' as 'tables'", tables)
logger.Infof("Using '%s' as 'level'", level)
generateAppcode(driver.String(), conn.String(), level.String(), tables.String(), currpath)
case "migration":
if len(args) < 2 {
logger.Fatal("Wrong number of arguments. Run: bee help generate")
}
cmd.Flag.Parse(args[2:])
mname := args[1]
logger.Infof("Using '%s' as migration name", mname)
upsql := ""
downsql := ""
if fields != "" {
dbMigrator := newDBDriver()
upsql = dbMigrator.generateCreateUp(mname)
downsql = dbMigrator.generateCreateDown(mname)
}
generateMigration(mname, upsql, downsql, currpath)
case "controller":
if len(args) == 2 {
cname := args[1]
generateController(cname, currpath)
} else {
logger.Fatal("Wrong number of arguments. Run: bee help generate")
}
case "model":
if len(args) < 2 {
logger.Fatal("Wrong number of arguments. Run: bee help generate")
}
cmd.Flag.Parse(args[2:])
if fields == "" {
logger.Hint("fields option should not be empty, i.e. -fields=\"title:string,body:text\"")
logger.Fatal("Wrong number of arguments. Run: bee help generate")
}
sname := args[1]
generateModel(sname, fields.String(), currpath)
case "view":
if len(args) == 2 {
cname := args[1]
generateView(cname, currpath)
} else {
logger.Fatal("Wrong number of arguments. Run: bee help generate")
}
default:
logger.Fatal("Command is missing")
}
logger.Successf("%s successfully generated!", strings.Title(gcmd))
return 0
}

View File

@ -1,48 +0,0 @@
package main
import "strings"
func generateScaffold(sname, fields, currpath, driver, conn string) {
logger.Infof("Do you want to create a '%s' model? [Yes|No] ", sname)
// Generate the model
if askForConfirmation() {
generateModel(sname, fields, currpath)
}
// Generate the controller
logger.Infof("Do you want to create a '%s' controller? [Yes|No] ", sname)
if askForConfirmation() {
generateController(sname, currpath)
}
// Generate the views
logger.Infof("Do you want to create views for this '%s' resource? [Yes|No] ", sname)
if askForConfirmation() {
generateView(sname, currpath)
}
// Generate a migration
logger.Infof("Do you want to create a '%s' migration and schema for this resource? [Yes|No] ", sname)
if askForConfirmation() {
upsql := ""
downsql := ""
if fields != "" {
dbMigrator := newDBDriver()
upsql = dbMigrator.generateCreateUp(sname)
downsql = dbMigrator.generateCreateDown(sname)
//todo remove
//if driver == "" {
// downsql = strings.Replace(downsql, "`", "", -1)
//}
}
generateMigration(sname, upsql, downsql, currpath)
}
// Run the migration
logger.Infof("Do you want to migrate the database? [Yes|No] ")
if askForConfirmation() {
migrateUpdate(currpath, driver, conn)
}
logger.Successf("All done! Don't forget to add beego.Router(\"/%s\" ,&controllers.%sController{}) to routers/route.go\n", sname, strings.Title(sname))
}

24
generate/g.go Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package generate
import "github.com/beego/bee/utils"
var SQLDriver utils.DocValue
var SQLConn utils.DocValue
var Level utils.DocValue
var Tables utils.DocValue
var Fields utils.DocValue
var DDL utils.DocValue

View File

@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package generate
import (
"database/sql"
@ -23,6 +23,9 @@ import (
"regexp"
"strings"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
)
@ -98,6 +101,7 @@ var typeMappingMysql = map[string]string{
"decimal": "float64",
"binary": "string", // binary
"varbinary": "string",
"year": "int16",
}
// typeMappingPostgres maps SQL data type to corresponding Go data type
@ -179,11 +183,12 @@ type OrmTag struct {
RelFk bool
ReverseMany bool
RelM2M bool
Comment string //column comment
}
// String returns the source code string for the Table struct
func (tb *Table) String() string {
rv := fmt.Sprintf("type %s struct {\n", camelCase(tb.Name))
rv := fmt.Sprintf("type %s struct {\n", utils.CamelCase(tb.Name))
for _, v := range tb.Columns {
rv += v.String() + "\n"
}
@ -252,10 +257,13 @@ func (tag *OrmTag) String() string {
if len(ormOptions) == 0 {
return ""
}
if tag.Comment != "" {
return fmt.Sprintf("`orm:\"%s\" description:\"%s\"`", strings.Join(ormOptions, ";"), tag.Comment)
}
return fmt.Sprintf("`orm:\"%s\"`", strings.Join(ormOptions, ";"))
}
func generateAppcode(driver, connStr, level, tables, currpath string) {
func GenerateAppcode(driver, connStr, level, tables, currpath string) {
var mode byte
switch level {
case "1":
@ -265,7 +273,7 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
case "3":
mode = OModel | OController | ORouter
default:
logger.Fatal("Invalid level value. Must be either \"1\", \"2\", or \"3\"")
beeLogger.Log.Fatal("Invalid level value. Must be either \"1\", \"2\", or \"3\"")
}
var selectedTables map[string]bool
if tables != "" {
@ -278,9 +286,9 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
case "mysql":
case "postgres":
case "sqlite":
logger.Fatal("Generating app code from SQLite database is not supported yet.")
beeLogger.Log.Fatal("Generating app code from SQLite database is not supported yet.")
default:
logger.Fatal("Unknown database driver. Must be either \"mysql\", \"postgres\" or \"sqlite\"")
beeLogger.Log.Fatal("Unknown database driver. Must be either \"mysql\", \"postgres\" or \"sqlite\"")
}
gen(driver, connStr, mode, selectedTables, currpath)
}
@ -290,12 +298,19 @@ func generateAppcode(driver, connStr, level, tables, currpath string) {
func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, apppath string) {
db, err := sql.Open(dbms, connStr)
if err != nil {
logger.Fatalf("Could not connect to '%s' database using '%s': %s", dbms, connStr, err)
beeLogger.Log.Fatalf("Could not connect to '%s' database using '%s': %s", dbms, connStr, err)
}
defer db.Close()
if trans, ok := dbDriver[dbms]; ok {
logger.Info("Analyzing database tables...")
tableNames := trans.GetTableNames(db)
beeLogger.Log.Info("Analyzing database tables...")
var tableNames []string
if len(selectedTableNames) != 0 {
for tableName := range selectedTableNames {
tableNames = append(tableNames, tableName)
}
} else {
tableNames = trans.GetTableNames(db)
}
tables := getTableObjects(tableNames, db, trans)
mvcPath := new(MvcPath)
mvcPath.ModelPath = path.Join(apppath, "models")
@ -305,7 +320,7 @@ func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, ap
pkgPath := getPackagePath(apppath)
writeSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames)
} else {
logger.Fatalf("Generating app code from '%s' database is not supported yet.", dbms)
beeLogger.Log.Fatalf("Generating app code from '%s' database is not supported yet.", dbms)
}
}
@ -313,13 +328,13 @@ func gen(dbms, connStr string, mode byte, selectedTableNames map[string]bool, ap
func (*MysqlDB) GetTableNames(db *sql.DB) (tables []string) {
rows, err := db.Query("SHOW TABLES")
if err != nil {
logger.Fatalf("Could not show tables: %s", err)
beeLogger.Log.Fatalf("Could not show tables: %s", err)
}
defer rows.Close()
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
logger.Fatalf("Could not show tables: %s", err)
beeLogger.Log.Fatalf("Could not show tables: %s", err)
}
tables = append(tables, name)
}
@ -362,12 +377,12 @@ func (*MysqlDB) GetConstraints(db *sql.DB, table *Table, blackList map[string]bo
c.table_schema = database() AND c.table_name = ? AND u.table_schema = database() AND u.table_name = ?`,
table.Name, table.Name) // u.position_in_unique_constraint,
if err != nil {
logger.Fatal("Could not query INFORMATION_SCHEMA for PK/UK/FK information")
beeLogger.Log.Fatal("Could not query INFORMATION_SCHEMA for PK/UK/FK information")
}
for rows.Next() {
var constraintTypeBytes, columnNameBytes, refTableSchemaBytes, refTableNameBytes, refColumnNameBytes, refOrdinalPosBytes []byte
if err := rows.Scan(&constraintTypeBytes, &columnNameBytes, &refTableSchemaBytes, &refTableNameBytes, &refColumnNameBytes, &refOrdinalPosBytes); err != nil {
logger.Fatal("Could not read INFORMATION_SCHEMA for PK/UK/FK information")
beeLogger.Log.Fatal("Could not read INFORMATION_SCHEMA for PK/UK/FK information")
}
constraintType, columnName, refTableSchema, refTableName, refColumnName, refOrdinalPos :=
string(constraintTypeBytes), string(columnNameBytes), string(refTableSchemaBytes),
@ -400,37 +415,38 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin
// retrieve columns
colDefRows, err := db.Query(
`SELECT
column_name, data_type, column_type, is_nullable, column_default, extra
column_name, data_type, column_type, is_nullable, column_default, extra, column_comment
FROM
information_schema.columns
WHERE
table_schema = database() AND table_name = ?`,
table.Name)
if err != nil {
logger.Fatalf("Could not query the database: %s", err)
beeLogger.Log.Fatalf("Could not query the database: %s", err)
}
defer colDefRows.Close()
for colDefRows.Next() {
// datatype as bytes so that SQL <null> values can be retrieved
var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes []byte
if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes); err != nil {
logger.Fatal("Could not query INFORMATION_SCHEMA for column information")
var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes, columnCommentBytes []byte
if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes, &columnCommentBytes); err != nil {
beeLogger.Log.Fatal("Could not query INFORMATION_SCHEMA for column information")
}
colName, dataType, columnType, isNullable, columnDefault, extra :=
string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes)
colName, dataType, columnType, isNullable, columnDefault, extra, columnComment :=
string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes), string(columnCommentBytes)
// create a column
col := new(Column)
col.Name = camelCase(colName)
col.Name = utils.CamelCase(colName)
col.Type, err = mysqlDB.GetGoDataType(dataType)
if err != nil {
logger.Fatalf("%s", err)
beeLogger.Log.Fatalf("%s", err)
}
// Tag info
tag := new(OrmTag)
tag.Column = colName
tag.Comment = columnComment
if table.Pk == colName {
col.Name = "Id"
col.Type = "int"
@ -449,8 +465,8 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin
if isFk && !isBl {
tag.RelFk = true
refStructName := fkCol.RefTable
col.Name = camelCase(colName)
col.Type = "*" + camelCase(refStructName)
col.Name = utils.CamelCase(colName)
col.Type = "*" + utils.CamelCase(refStructName)
} else {
// if the name of column is Id, and it's not primary key
if colName == "id" {
@ -464,7 +480,7 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin
if sign == "unsigned" && extra != "auto_increment" {
col.Type, err = mysqlDB.GetGoDataType(dataType + " " + sign)
if err != nil {
logger.Fatalf("%s", err)
beeLogger.Log.Fatalf("%s", err)
}
}
}
@ -500,9 +516,7 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin
// GetGoDataType maps an SQL data type to Golang data type
func (*MysqlDB) GetGoDataType(sqlType string) (string, error) {
var typeMapping = map[string]string{}
typeMapping = typeMappingMysql
if v, ok := typeMapping[sqlType]; ok {
if v, ok := typeMappingMysql[sqlType]; ok {
return v, nil
}
return "", fmt.Errorf("data type '%s' not found", sqlType)
@ -516,14 +530,14 @@ func (*PostgresDB) GetTableNames(db *sql.DB) (tables []string) {
table_type = 'BASE TABLE' AND
table_schema NOT IN ('pg_catalog', 'information_schema')`)
if err != nil {
logger.Fatalf("Could not show tables: %s", err)
beeLogger.Log.Fatalf("Could not show tables: %s", err)
}
defer rows.Close()
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
logger.Fatalf("Could not show tables: %s", err)
beeLogger.Log.Fatalf("Could not show tables: %s", err)
}
tables = append(tables, name)
}
@ -553,13 +567,13 @@ func (*PostgresDB) GetConstraints(db *sql.DB, table *Table, blackList map[string
AND u.table_name = $2`,
table.Name, table.Name) // u.position_in_unique_constraint,
if err != nil {
logger.Fatalf("Could not query INFORMATION_SCHEMA for PK/UK/FK information: %s", err)
beeLogger.Log.Fatalf("Could not query INFORMATION_SCHEMA for PK/UK/FK information: %s", err)
}
for rows.Next() {
var constraintTypeBytes, columnNameBytes, refTableSchemaBytes, refTableNameBytes, refColumnNameBytes, refOrdinalPosBytes []byte
if err := rows.Scan(&constraintTypeBytes, &columnNameBytes, &refTableSchemaBytes, &refTableNameBytes, &refColumnNameBytes, &refOrdinalPosBytes); err != nil {
logger.Fatalf("Could not read INFORMATION_SCHEMA for PK/UK/FK information: %s", err)
beeLogger.Log.Fatalf("Could not read INFORMATION_SCHEMA for PK/UK/FK information: %s", err)
}
constraintType, columnName, refTableSchema, refTableName, refColumnName, refOrdinalPos :=
string(constraintTypeBytes), string(columnNameBytes), string(refTableSchemaBytes),
@ -609,7 +623,7 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
AND table_name = $1`,
table.Name)
if err != nil {
logger.Fatalf("Could not query INFORMATION_SCHEMA for column information: %s", err)
beeLogger.Log.Fatalf("Could not query INFORMATION_SCHEMA for column information: %s", err)
}
defer colDefRows.Close()
@ -617,16 +631,16 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
// datatype as bytes so that SQL <null> values can be retrieved
var colNameBytes, dataTypeBytes, columnTypeBytes, isNullableBytes, columnDefaultBytes, extraBytes []byte
if err := colDefRows.Scan(&colNameBytes, &dataTypeBytes, &columnTypeBytes, &isNullableBytes, &columnDefaultBytes, &extraBytes); err != nil {
logger.Fatalf("Could not query INFORMATION_SCHEMA for column information: %s", err)
beeLogger.Log.Fatalf("Could not query INFORMATION_SCHEMA for column information: %s", err)
}
colName, dataType, columnType, isNullable, columnDefault, extra :=
string(colNameBytes), string(dataTypeBytes), string(columnTypeBytes), string(isNullableBytes), string(columnDefaultBytes), string(extraBytes)
// Create a column
col := new(Column)
col.Name = camelCase(colName)
col.Name = utils.CamelCase(colName)
col.Type, err = postgresDB.GetGoDataType(dataType)
if err != nil {
logger.Fatalf("%s", err)
beeLogger.Log.Fatalf("%s", err)
}
// Tag info
@ -650,8 +664,8 @@ func (postgresDB *PostgresDB) GetColumns(db *sql.DB, table *Table, blackList map
if isFk && !isBl {
tag.RelFk = true
refStructName := fkCol.RefTable
col.Name = camelCase(colName)
col.Type = "*" + camelCase(refStructName)
col.Name = utils.CamelCase(colName)
col.Type = "*" + utils.CamelCase(refStructName)
} else {
// if the name of column is Id, and it's not primary key
if colName == "id" {
@ -716,22 +730,22 @@ func createPaths(mode byte, paths *MvcPath) {
// Newly geneated files will be inside these folders.
func writeSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) {
if (OModel & mode) == OModel {
logger.Info("Creating model files...")
beeLogger.Log.Info("Creating model files...")
writeModelFiles(tables, paths.ModelPath, selectedTables)
}
if (OController & mode) == OController {
logger.Info("Creating controller files...")
beeLogger.Log.Info("Creating controller files...")
writeControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath)
}
if (ORouter & mode) == ORouter {
logger.Info("Creating router files...")
beeLogger.Log.Info("Creating router files...")
writeRouterFile(tables, paths.RouterPath, selectedTables, pkgPath)
}
}
// writeModelFiles generates model files
func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) {
w := NewColorWriter(os.Stdout)
w := colors.NewColorWriter(os.Stdout)
for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it
@ -744,33 +758,33 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
fpath := path.Join(mPath, filename+".go")
var f *os.File
var err error
if isExist(fpath) {
logger.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() {
if utils.IsExist(fpath) {
beeLogger.Log.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if utils.AskForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
continue
}
} else {
logger.Warnf("Skipped create file '%s'", fpath)
beeLogger.Log.Warnf("Skipped create file '%s'", fpath)
continue
}
} else {
f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
continue
}
}
template := ""
var template string
if tb.Pk == "" {
template = StructModelTPL
} else {
template = ModelTPL
}
fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1)
fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1)
fileStr = strings.Replace(fileStr, "{{modelName}}", utils.CamelCase(tb.Name), -1)
fileStr = strings.Replace(fileStr, "{{tableName}}", tb.Name, -1)
// If table contains time field, import time.Time package
@ -783,17 +797,17 @@ func writeModelFiles(tables []*Table, mPath string, selectedTables map[string]bo
fileStr = strings.Replace(fileStr, "{{timePkg}}", timePkg, -1)
fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1)
if _, err := f.WriteString(fileStr); err != nil {
logger.Fatalf("Could not write model file to '%s': %s", fpath, err)
beeLogger.Log.Fatalf("Could not write model file to '%s': %s", fpath, err)
}
CloseFile(f)
utils.CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath)
utils.FormatSourceCode(fpath)
}
}
// writeControllerFiles generates controller files
func writeControllerFiles(tables []*Table, cPath string, selectedTables map[string]bool, pkgPath string) {
w := NewColorWriter(os.Stdout)
w := colors.NewColorWriter(os.Stdout)
for _, tb := range tables {
// If selectedTables map is not nil and this table is not selected, ignore it
@ -809,39 +823,39 @@ func writeControllerFiles(tables []*Table, cPath string, selectedTables map[stri
fpath := path.Join(cPath, filename+".go")
var f *os.File
var err error
if isExist(fpath) {
logger.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() {
if utils.IsExist(fpath) {
beeLogger.Log.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if utils.AskForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
continue
}
} else {
logger.Warnf("Skipped create file '%s'", fpath)
beeLogger.Log.Warnf("Skipped create file '%s'", fpath)
continue
}
} else {
f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
continue
}
}
fileStr := strings.Replace(CtrlTPL, "{{ctrlName}}", camelCase(tb.Name), -1)
fileStr := strings.Replace(CtrlTPL, "{{ctrlName}}", utils.CamelCase(tb.Name), -1)
fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1)
if _, err := f.WriteString(fileStr); err != nil {
logger.Fatalf("Could not write controller file to '%s': %s", fpath, err)
beeLogger.Log.Fatalf("Could not write controller file to '%s': %s", fpath, err)
}
CloseFile(f)
utils.CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath)
utils.FormatSourceCode(fpath)
}
}
// writeRouterFile generates router file
func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bool, pkgPath string) {
w := NewColorWriter(os.Stdout)
w := colors.NewColorWriter(os.Stdout)
var nameSpaces []string
for _, tb := range tables {
@ -856,40 +870,40 @@ func writeRouterFile(tables []*Table, rPath string, selectedTables map[string]bo
}
// Add namespaces
nameSpace := strings.Replace(NamespaceTPL, "{{nameSpace}}", tb.Name, -1)
nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1)
nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", utils.CamelCase(tb.Name), -1)
nameSpaces = append(nameSpaces, nameSpace)
}
// Add export controller
fpath := path.Join(rPath, "router.go")
fpath := filepath.Join(rPath, "router.go")
routerStr := strings.Replace(RouterTPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1)
routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1)
var f *os.File
var err error
if isExist(fpath) {
logger.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() {
if utils.IsExist(fpath) {
beeLogger.Log.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if utils.AskForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
return
}
} else {
logger.Warnf("Skipped create file '%s'", fpath)
beeLogger.Log.Warnf("Skipped create file '%s'", fpath)
return
}
} else {
f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
return
}
}
if _, err := f.WriteString(routerStr); err != nil {
logger.Fatalf("Could not write router file to '%s': %s", fpath, err)
beeLogger.Log.Fatalf("Could not write router file to '%s': %s", fpath, err)
}
CloseFile(f)
utils.CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath)
utils.FormatSourceCode(fpath)
}
func isSQLTemporalType(t string) bool {
@ -952,19 +966,18 @@ func getFileName(tbName string) (filename string) {
func getPackagePath(curpath string) (packpath string) {
gopath := os.Getenv("GOPATH")
if gopath == "" {
logger.Fatal("GOPATH environment variable is not set or empty")
beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
}
logger.Debugf("GOPATH: %s", __FILE__(), __LINE__(), gopath)
beeLogger.Log.Debugf("GOPATH: %s", utils.FILE(), utils.LINE(), gopath)
appsrcpath := ""
haspath := false
wgopath := filepath.SplitList(gopath)
for _, wg := range wgopath {
wg, _ = filepath.EvalSymlinks(path.Join(wg, "src"))
if filepath.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src"))
if strings.HasPrefix(strings.ToLower(curpath), strings.ToLower(wg)) {
haspath = true
appsrcpath = wg
break
@ -972,11 +985,11 @@ func getPackagePath(curpath string) (packpath string) {
}
if !haspath {
logger.Fatalf("Cannot generate application code outside of GOPATH '%s'", gopath)
beeLogger.Log.Fatalf("Cannot generate application code outside of GOPATH '%s' compare with CWD '%s'", gopath, curpath)
}
if curpath == appsrcpath {
logger.Fatal("Cannot generate application code outside of application path")
beeLogger.Log.Fatal("Cannot generate application code outside of application path")
}
packpath = strings.Join(strings.Split(curpath[len(appsrcpath)+1:], string(filepath.Separator)), "/")

View File

@ -12,17 +12,21 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package generate
import (
"fmt"
"os"
"path"
"strings"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
)
func generateController(cname, currpath string) {
w := NewColorWriter(os.Stdout)
func GenerateController(cname, currpath string) {
w := colors.NewColorWriter(os.Stdout)
p, f := path.Split(cname)
controllerName := strings.Title(f)
@ -33,26 +37,26 @@ func generateController(cname, currpath string) {
packageName = p[i+1 : len(p)-1]
}
logger.Infof("Using '%s' as controller name", controllerName)
logger.Infof("Using '%s' as package name", packageName)
beeLogger.Log.Infof("Using '%s' as controller name", controllerName)
beeLogger.Log.Infof("Using '%s' as package name", packageName)
fp := path.Join(currpath, "controllers", p)
if _, err := os.Stat(fp); os.IsNotExist(err) {
// Create the controller's directory
if err := os.MkdirAll(fp, 0777); err != nil {
logger.Fatalf("Could not create controllers directory: %s", err)
beeLogger.Log.Fatalf("Could not create controllers directory: %s", err)
}
}
fpath := path.Join(fp, strings.ToLower(controllerName)+".go")
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer CloseFile(f)
defer utils.CloseFile(f)
modelPath := path.Join(currpath, "models", strings.ToLower(controllerName)+".go")
var content string
if _, err := os.Stat(modelPath); err == nil {
logger.Infof("Using matching model '%s'", controllerName)
beeLogger.Log.Infof("Using matching model '%s'", controllerName)
content = strings.Replace(controllerModelTpl, "{{packageName}}", packageName, -1)
pkgPath := getPackagePath(currpath)
content = strings.Replace(content, "{{pkgPath}}", pkgPath, -1)
@ -64,10 +68,10 @@ func generateController(cname, currpath string) {
f.WriteString(content)
// Run 'gofmt' on the generated source code
formatSourceCode(fpath)
utils.FormatSourceCode(fpath)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else {
logger.Fatalf("Could not create controller file: %s", err)
beeLogger.Log.Fatalf("Could not create controller file: %s", err)
}
}

View File

@ -15,7 +15,7 @@
* *
\**********************************************************/
package main
package generate
import (
"database/sql"
@ -24,11 +24,256 @@ import (
"path"
"strings"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
)
func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
var Hproseconf = `appname = {{.Appname}}
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
EnableDocs = true
`
var HproseMaingo = `package main
import (
"fmt"
"reflect"
"{{.Appname}}/models"
"github.com/hprose/hprose-golang/rpc"
"github.com/astaxie/beego"
)
func logInvokeHandler(
name string,
args []reflect.Value,
context rpc.Context,
next rpc.NextInvokeHandler) (results []reflect.Value, err error) {
fmt.Printf("%s(%v) = ", name, args)
results, err = next(name, args, context)
fmt.Printf("%v %v\r\n", results, err)
return
}
func main() {
// Create WebSocketServer
// service := rpc.NewWebSocketService()
// Create Http Server
service := rpc.NewHTTPService()
// Use Logger Middleware
service.AddInvokeHandler(logInvokeHandler)
// Publish Functions
service.AddFunction("AddOne", models.AddOne)
service.AddFunction("GetOne", models.GetOne)
// Start Service
beego.Handler("/", service)
beego.Run()
}
`
var HproseMainconngo = `package main
import (
"fmt"
"reflect"
"{{.Appname}}/models"
"github.com/hprose/hprose-golang/rpc"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
{{.DriverPkg}}
)
func init() {
orm.RegisterDataBase("default", "{{.DriverName}}", "{{.conn}}")
}
func logInvokeHandler(
name string,
args []reflect.Value,
context rpc.Context,
next rpc.NextInvokeHandler) (results []reflect.Value, err error) {
fmt.Printf("%s(%v) = ", name, args)
results, err = next(name, args, context)
fmt.Printf("%v %v\r\n", results, err)
return
}
func main() {
// Create WebSocketServer
// service := rpc.NewWebSocketService()
// Create Http Server
service := rpc.NewHTTPService()
// Use Logger Middleware
service.AddInvokeHandler(logInvokeHandler)
{{HproseFunctionList}}
// Start Service
beego.Handler("/", service)
beego.Run()
}
`
var HproseModels = `package models
import (
"errors"
"strconv"
"time"
)
var (
Objects map[string]*Object
)
type Object struct {
ObjectId string
Score int64
PlayerName string
}
func init() {
Objects = make(map[string]*Object)
Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
}
func AddOne(object Object) (ObjectId string) {
object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
Objects[object.ObjectId] = &object
return object.ObjectId
}
func GetOne(ObjectId string) (object *Object, err error) {
if v, ok := Objects[ObjectId]; ok {
return v, nil
}
return nil, errors.New("ObjectId Not Exist")
}
func GetAll() map[string]*Object {
return Objects
}
func Update(ObjectId string, Score int64) (err error) {
if v, ok := Objects[ObjectId]; ok {
v.Score = Score
return nil
}
return errors.New("ObjectId Not Exist")
}
func Delete(ObjectId string) {
delete(Objects, ObjectId)
}
`
var HproseModels2 = `package models
import (
"errors"
"strconv"
"time"
)
var (
UserList map[string]*User
)
func init() {
UserList = make(map[string]*User)
u := User{"user_11111", "astaxie", "11111", Profile{"male", 20, "Singapore", "astaxie@gmail.com"}}
UserList["user_11111"] = &u
}
type User struct {
Id string
Username string
Password string
Profile Profile
}
type Profile struct {
Gender string
Age int
Address string
Email string
}
func AddUser(u User) string {
u.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)
UserList[u.Id] = &u
return u.Id
}
func GetUser(uid string) (u *User, err error) {
if u, ok := UserList[uid]; ok {
return u, nil
}
return nil, errors.New("User not exists")
}
func GetAllUsers() map[string]*User {
return UserList
}
func UpdateUser(uid string, uu *User) (a *User, err error) {
if u, ok := UserList[uid]; ok {
if uu.Username != "" {
u.Username = uu.Username
}
if uu.Password != "" {
u.Password = uu.Password
}
if uu.Profile.Age != 0 {
u.Profile.Age = uu.Profile.Age
}
if uu.Profile.Address != "" {
u.Profile.Address = uu.Profile.Address
}
if uu.Profile.Gender != "" {
u.Profile.Gender = uu.Profile.Gender
}
if uu.Profile.Email != "" {
u.Profile.Email = uu.Profile.Email
}
return u, nil
}
return nil, errors.New("User Not Exist")
}
func Login(username, password string) bool {
for _, u := range UserList {
if u.Username == username && u.Password == password {
return true
}
}
return false
}
func DeleteUser(uid string) {
delete(UserList, uid)
}
`
var HproseAddFunctions = []string{}
func GenerateHproseAppcode(driver, connStr, level, tables, currpath string) {
var mode byte
switch level {
case "1":
@ -38,7 +283,7 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
case "3":
mode = OModel | OController | ORouter
default:
logger.Fatal("Invalid 'level' option. Level must be either \"1\", \"2\" or \"3\"")
beeLogger.Log.Fatal("Invalid 'level' option. Level must be either \"1\", \"2\" or \"3\"")
}
var selectedTables map[string]bool
if tables != "" {
@ -51,9 +296,9 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
case "mysql":
case "postgres":
case "sqlite":
logger.Fatal("Generating app code from SQLite database is not supported yet")
beeLogger.Log.Fatal("Generating app code from SQLite database is not supported yet")
default:
logger.Fatalf("Unknown database driver '%s'. Driver must be one of mysql, postgres or sqlite", driver)
beeLogger.Log.Fatalf("Unknown database driver '%s'. Driver must be one of mysql, postgres or sqlite", driver)
}
genHprose(driver, connStr, mode, selectedTables, currpath)
}
@ -63,11 +308,11 @@ func generateHproseAppcode(driver, connStr, level, tables, currpath string) {
func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bool, currpath string) {
db, err := sql.Open(dbms, connStr)
if err != nil {
logger.Fatalf("Could not connect to '%s' database using '%s': %s", dbms, connStr, err)
beeLogger.Log.Fatalf("Could not connect to '%s' database using '%s': %s", dbms, connStr, err)
}
defer db.Close()
if trans, ok := dbDriver[dbms]; ok {
logger.Info("Analyzing database tables...")
beeLogger.Log.Info("Analyzing database tables...")
tableNames := trans.GetTableNames(db)
tables := getTableObjects(tableNames, db, trans)
mvcPath := new(MvcPath)
@ -76,7 +321,7 @@ func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bo
pkgPath := getPackagePath(currpath)
writeHproseSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames)
} else {
logger.Fatalf("Generating app code from '%s' database is not supported yet", dbms)
beeLogger.Log.Fatalf("Generating app code from '%s' database is not supported yet", dbms)
}
}
@ -85,14 +330,14 @@ func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bo
// Newly geneated files will be inside these folders.
func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) {
if (OModel & mode) == OModel {
logger.Info("Creating model files...")
beeLogger.Log.Info("Creating model files...")
writeHproseModelFiles(tables, paths.ModelPath, selectedTables)
}
}
// writeHproseModelFiles generates model files
func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) {
w := NewColorWriter(os.Stdout)
w := colors.NewColorWriter(os.Stdout)
for _, tb := range tables {
// if selectedTables map is not nil and this table is not selected, ignore it
@ -105,34 +350,34 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
fpath := path.Join(mPath, filename+".go")
var f *os.File
var err error
if isExist(fpath) {
logger.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if askForConfirmation() {
if utils.IsExist(fpath) {
beeLogger.Log.Warnf("'%s' already exists. Do you want to overwrite it? [Yes|No] ", fpath)
if utils.AskForConfirmation() {
f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
continue
}
} else {
logger.Warnf("Skipped create file '%s'", fpath)
beeLogger.Log.Warnf("Skipped create file '%s'", fpath)
continue
}
} else {
f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
continue
}
}
template := ""
var template string
if tb.Pk == "" {
template = HproseStructModelTPL
} else {
template = HproseModelTPL
hproseAddFunctions = append(hproseAddFunctions, strings.Replace(HproseAddFunction, "{{modelName}}", camelCase(tb.Name), -1))
HproseAddFunctions = append(HproseAddFunctions, strings.Replace(HproseAddFunction, "{{modelName}}", utils.CamelCase(tb.Name), -1))
}
fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1)
fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1)
fileStr = strings.Replace(fileStr, "{{modelName}}", utils.CamelCase(tb.Name), -1)
// if table contains time field, import time.Time package
timePkg := ""
importTimePkg := ""
@ -143,11 +388,11 @@ func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[str
fileStr = strings.Replace(fileStr, "{{timePkg}}", timePkg, -1)
fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1)
if _, err := f.WriteString(fileStr); err != nil {
logger.Fatalf("Could not write model file to '%s'", fpath)
beeLogger.Log.Fatalf("Could not write model file to '%s'", fpath)
}
CloseFile(f)
utils.CloseFile(f)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
formatSourceCode(fpath)
utils.FormatSourceCode(fpath)
}
}

View File

@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package generate
import (
"fmt"
@ -20,6 +20,10 @@ import (
"path"
"strings"
"time"
"github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
)
const (
@ -29,18 +33,18 @@ const (
)
type DBDriver interface {
generateCreateUp(tableName string) string
generateCreateDown(tableName string) string
GenerateCreateUp(tableName string) string
GenerateCreateDown(tableName string) string
}
type mysqlDriver struct{}
func (m mysqlDriver) generateCreateUp(tableName string) string {
upsql := `m.SQL("CREATE TABLE ` + tableName + "(" + m.generateSQLFromFields(fields.String()) + `)");`
func (m mysqlDriver) GenerateCreateUp(tableName string) string {
upsql := `m.SQL("CREATE TABLE ` + tableName + "(" + m.generateSQLFromFields(Fields.String()) + `)");`
return upsql
}
func (m mysqlDriver) generateCreateDown(tableName string) string {
func (m mysqlDriver) GenerateCreateDown(tableName string) string {
downsql := `m.SQL("DROP TABLE ` + "`" + tableName + "`" + `")`
return downsql
}
@ -51,21 +55,21 @@ func (m mysqlDriver) generateSQLFromFields(fields string) string {
for i, v := range fds {
kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 {
logger.Error("Fields format is wrong. Should be: key:type,key:type " + v)
beeLogger.Log.Error("Fields format is wrong. Should be: key:type,key:type " + v)
return ""
}
typ, tag := m.getSQLType(kv[1])
if typ == "" {
logger.Error("Fields format is wrong. Should be: key:type,key:type " + v)
beeLogger.Log.Error("Fields format is wrong. Should be: key:type,key:type " + v)
return ""
}
if i == 0 && strings.ToLower(kv[0]) != "id" {
sql += "`id` int(11) NOT NULL AUTO_INCREMENT,"
tags = tags + "PRIMARY KEY (`id`),"
}
sql += "`" + snakeString(kv[0]) + "` " + typ + ","
sql += "`" + utils.SnakeString(kv[0]) + "` " + typ + ","
if tag != "" {
tags = tags + fmt.Sprintf(tag, "`"+snakeString(kv[0])+"`") + ","
tags = tags + fmt.Sprintf(tag, "`"+utils.SnakeString(kv[0])+"`") + ","
}
}
sql = strings.TrimRight(sql+tags, ",")
@ -104,12 +108,12 @@ func (m mysqlDriver) getSQLType(ktype string) (tp, tag string) {
type postgresqlDriver struct{}
func (m postgresqlDriver) generateCreateUp(tableName string) string {
upsql := `m.SQL("CREATE TABLE ` + tableName + "(" + m.generateSQLFromFields(fields.String()) + `)");`
func (m postgresqlDriver) GenerateCreateUp(tableName string) string {
upsql := `m.SQL("CREATE TABLE ` + tableName + "(" + m.generateSQLFromFields(Fields.String()) + `)");`
return upsql
}
func (m postgresqlDriver) generateCreateDown(tableName string) string {
func (m postgresqlDriver) GenerateCreateDown(tableName string) string {
downsql := `m.SQL("DROP TABLE ` + tableName + `")`
return downsql
}
@ -120,20 +124,20 @@ func (m postgresqlDriver) generateSQLFromFields(fields string) string {
for i, v := range fds {
kv := strings.SplitN(v, ":", 2)
if len(kv) != 2 {
logger.Error("Fields format is wrong. Should be: key:type,key:type " + v)
beeLogger.Log.Error("Fields format is wrong. Should be: key:type,key:type " + v)
return ""
}
typ, tag := m.getSQLType(kv[1])
if typ == "" {
logger.Error("Fields format is wrong. Should be: key:type,key:type " + v)
beeLogger.Log.Error("Fields format is wrong. Should be: key:type,key:type " + v)
return ""
}
if i == 0 && strings.ToLower(kv[0]) != "id" {
sql += "id serial primary key,"
}
sql += snakeString(kv[0]) + " " + typ + ","
sql += utils.SnakeString(kv[0]) + " " + typ + ","
if tag != "" {
tags = tags + fmt.Sprintf(tag, snakeString(kv[0])) + ","
tags = tags + fmt.Sprintf(tag, utils.SnakeString(kv[0])) + ","
}
}
if tags != "" {
@ -170,14 +174,14 @@ func (m postgresqlDriver) getSQLType(ktype string) (tp, tag string) {
return "", ""
}
func newDBDriver() DBDriver {
switch driver {
func NewDBDriver() DBDriver {
switch SQLDriver {
case "mysql":
return mysqlDriver{}
case "postgres":
return postgresqlDriver{}
default:
logger.Fatal("Driver not supported")
beeLogger.Log.Fatal("Driver not supported")
return nil
}
}
@ -185,60 +189,102 @@ func newDBDriver() DBDriver {
// generateMigration generates migration file template for database schema update.
// The generated file template consists of an up() method for updating schema and
// a down() method for reverting the update.
func generateMigration(mname, upsql, downsql, curpath string) {
w := NewColorWriter(os.Stdout)
func GenerateMigration(mname, upsql, downsql, curpath string) {
w := colors.NewColorWriter(os.Stdout)
migrationFilePath := path.Join(curpath, DBPath, MPath)
if _, err := os.Stat(migrationFilePath); os.IsNotExist(err) {
// create migrations directory
if err := os.MkdirAll(migrationFilePath, 0777); err != nil {
logger.Fatalf("Could not create migration directory: %s", err)
beeLogger.Log.Fatalf("Could not create migration directory: %s", err)
}
}
// create file
today := time.Now().Format(MDateFormat)
fpath := path.Join(migrationFilePath, fmt.Sprintf("%s_%s.go", today, mname))
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer CloseFile(f)
content := strings.Replace(MigrationTPL, "{{StructName}}", camelCase(mname)+"_"+today, -1)
content = strings.Replace(content, "{{CurrTime}}", today, -1)
content = strings.Replace(content, "{{UpSQL}}", upsql, -1)
content = strings.Replace(content, "{{DownSQL}}", downsql, -1)
f.WriteString(content)
defer utils.CloseFile(f)
ddlSpec := ""
spec := ""
up := ""
down := ""
if DDL != "" {
ddlSpec = "m.ddlSpec()"
switch strings.Title(DDL.String()) {
case "Create":
spec = strings.Replace(DDLSpecCreate, "{{StructName}}", utils.CamelCase(mname)+"_"+today, -1)
case "Alter":
spec = strings.Replace(DDLSpecAlter, "{{StructName}}", utils.CamelCase(mname)+"_"+today, -1)
}
spec = strings.Replace(spec, "{{tableName}}", mname, -1)
} else {
up = strings.Replace(MigrationUp, "{{UpSQL}}", upsql, -1)
up = strings.Replace(up, "{{StructName}}", utils.CamelCase(mname)+"_"+today, -1)
down = strings.Replace(MigrationDown, "{{DownSQL}}", downsql, -1)
down = strings.Replace(down, "{{StructName}}", utils.CamelCase(mname)+"_"+today, -1)
}
header := strings.Replace(MigrationHeader, "{{StructName}}", utils.CamelCase(mname)+"_"+today, -1)
header = strings.Replace(header, "{{ddlSpec}}", ddlSpec, -1)
header = strings.Replace(header, "{{CurrTime}}", today, -1)
f.WriteString(header + spec + up + down)
// Run 'gofmt' on the generated source code
formatSourceCode(fpath)
utils.FormatSourceCode(fpath)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else {
logger.Fatalf("Could not create migration file: %s", err)
beeLogger.Log.Fatalf("Could not create migration file: %s", err)
}
}
const MigrationTPL = `package main
const (
MigrationHeader = `package main
import (
"github.com/astaxie/beego/migration"
)
import (
"github.com/astaxie/beego/migration"
// DO NOT MODIFY
type {{StructName}} struct {
migration.Migration
}
// DO NOT MODIFY
func init() {
m := &{{StructName}}{}
m.Created = "{{CurrTime}}"
{{ddlSpec}}
migration.Register("{{StructName}}", m)
}
`
DDLSpecCreate = `
/*
refer beego/migration/doc.go
*/
func(m *{{StructName}}) ddlSpec(){
m.CreateTable("{{tableName}}", "InnoDB", "utf8")
m.PriCol("id").SetAuto(true).SetNullable(false).SetDataType("INT(10)").SetUnsigned(true)
}
`
DDLSpecAlter = `
/*
refer beego/migration/doc.go
*/
func(m *{{StructName}}) ddlSpec(){
m.AlterTable("{{tableName}}")
}
`
MigrationUp = `
// Run the migrations
func (m *{{StructName}}) Up() {
// use m.SQL("CREATE TABLE ...") to make schema update
{{UpSQL}}
}`
MigrationDown = `
// Reverse the migrations
func (m *{{StructName}}) Down() {
// use m.SQL("DROP TABLE ...") to reverse schema update
{{DownSQL}}
}
`
)
// DO NOT MODIFY
type {{StructName}} struct {
migration.Migration
}
// DO NOT MODIFY
func init() {
m := &{{StructName}}{}
m.Created = "{{CurrTime}}"
migration.Register("{{StructName}}", m)
}
// Run the migrations
func (m *{{StructName}}) Up() {
// use m.SQL("CREATE TABLE ...") to make schema update
{{UpSQL}}
}
// Reverse the migrations
func (m *{{StructName}}) Down() {
// use m.SQL("DROP TABLE ...") to reverse schema update
{{DownSQL}}
}
`

View File

@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package generate
import (
"errors"
@ -20,10 +20,14 @@ import (
"os"
"path"
"strings"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
)
func generateModel(mname, fields, currpath string) {
w := NewColorWriter(os.Stdout)
func GenerateModel(mname, fields, currpath string) {
w := colors.NewColorWriter(os.Stdout)
p, f := path.Split(mname)
modelName := strings.Title(f)
@ -35,23 +39,23 @@ func generateModel(mname, fields, currpath string) {
modelStruct, hastime, err := getStruct(modelName, fields)
if err != nil {
logger.Fatalf("Could not generate the model struct: %s", err)
beeLogger.Log.Fatalf("Could not generate the model struct: %s", err)
}
logger.Infof("Using '%s' as model name", modelName)
logger.Infof("Using '%s' as package name", packageName)
beeLogger.Log.Infof("Using '%s' as model name", modelName)
beeLogger.Log.Infof("Using '%s' as package name", packageName)
fp := path.Join(currpath, "models", p)
if _, err := os.Stat(fp); os.IsNotExist(err) {
// Create the model's directory
if err := os.MkdirAll(fp, 0777); err != nil {
logger.Fatalf("Could not create the model directory: %s", err)
beeLogger.Log.Fatalf("Could not create the model directory: %s", err)
}
}
fpath := path.Join(fp, strings.ToLower(modelName)+".go")
if f, err := os.OpenFile(fpath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer CloseFile(f)
defer utils.CloseFile(f)
content := strings.Replace(modelTpl, "{{packageName}}", packageName, -1)
content = strings.Replace(content, "{{modelName}}", modelName, -1)
content = strings.Replace(content, "{{modelStruct}}", modelStruct, -1)
@ -62,10 +66,10 @@ func generateModel(mname, fields, currpath string) {
}
f.WriteString(content)
// Run 'gofmt' on the generated source code
formatSourceCode(fpath)
utils.FormatSourceCode(fpath)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", fpath, "\x1b[0m")
} else {
logger.Fatalf("Could not create model file: %s", err)
beeLogger.Log.Fatalf("Could not create model file: %s", err)
}
}
@ -95,7 +99,7 @@ func getStruct(structname, fields string) (string, bool, error) {
if hastimeinner {
hastime = true
}
structStr = structStr + camelString(kv[0]) + " " + typ + " " + tag + "\n"
structStr = structStr + utils.CamelString(kv[0]) + " " + typ + " " + tag + "\n"
}
structStr += "}\n"
return structStr, hastime, nil
@ -163,7 +167,7 @@ func Add{{modelName}}(m *{{modelName}}) (id int64, err error) {
func Get{{modelName}}ById(id int64) (v *{{modelName}}, err error) {
o := orm.NewOrm()
v = &{{modelName}}{Id: id}
if err = o.Read(v); err == nil {
if err = o.QueryTable(new({{modelName}})).Filter("Id", id).RelatedSel().One(v); err == nil {
return v, nil
}
return nil, err
@ -221,7 +225,7 @@ func GetAll{{modelName}}(query map[string]string, fields []string, sortby []stri
}
var l []{{modelName}}
qs = qs.OrderBy(sortFields...)
qs = qs.OrderBy(sortFields...).RelatedSel()
if _, err = qs.Limit(limit, offset).All(&l, fields...); err == nil {
if len(fields) == 0 {
for _, v := range l {

50
generate/g_scaffold.go Normal file
View File

@ -0,0 +1,50 @@
package generate
import (
"strings"
"github.com/beego/bee/cmd/commands/migrate"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils"
)
func GenerateScaffold(sname, fields, currpath, driver, conn string) {
beeLogger.Log.Infof("Do you want to create a '%s' model? [Yes|No] ", sname)
// Generate the model
if utils.AskForConfirmation() {
GenerateModel(sname, fields, currpath)
}
// Generate the controller
beeLogger.Log.Infof("Do you want to create a '%s' controller? [Yes|No] ", sname)
if utils.AskForConfirmation() {
GenerateController(sname, currpath)
}
// Generate the views
beeLogger.Log.Infof("Do you want to create views for this '%s' resource? [Yes|No] ", sname)
if utils.AskForConfirmation() {
GenerateView(sname, currpath)
}
// Generate a migration
beeLogger.Log.Infof("Do you want to create a '%s' migration and schema for this resource? [Yes|No] ", sname)
if utils.AskForConfirmation() {
upsql := ""
downsql := ""
if fields != "" {
dbMigrator := NewDBDriver()
upsql = dbMigrator.GenerateCreateUp(sname)
downsql = dbMigrator.GenerateCreateDown(sname)
}
GenerateMigration(sname, upsql, downsql, currpath)
}
// Run the migration
beeLogger.Log.Infof("Do you want to migrate the database? [Yes|No] ")
if utils.AskForConfirmation() {
migrate.MigrateUpdate(currpath, driver, conn)
}
beeLogger.Log.Successf("All done! Don't forget to add beego.Router(\"/%s\" ,&controllers.%sController{}) to routers/route.go\n", sname, strings.Title(sname))
}

View File

@ -12,60 +12,64 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package generate
import (
"fmt"
"os"
"path"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
"github.com/beego/bee/utils"
)
// recipe
// admin/recipe
func generateView(viewpath, currpath string) {
w := NewColorWriter(os.Stdout)
func GenerateView(viewpath, currpath string) {
w := colors.NewColorWriter(os.Stdout)
logger.Info("Generating view...")
beeLogger.Log.Info("Generating view...")
absViewPath := path.Join(currpath, "views", viewpath)
err := os.MkdirAll(absViewPath, os.ModePerm)
if err != nil {
logger.Fatalf("Could not create '%s' view: %s", viewpath, err)
beeLogger.Log.Fatalf("Could not create '%s' view: %s", viewpath, err)
}
cfile := path.Join(absViewPath, "index.tpl")
if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer CloseFile(f)
defer utils.CloseFile(f)
f.WriteString(cfile)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else {
logger.Fatalf("Could not create view file: %s", err)
beeLogger.Log.Fatalf("Could not create view file: %s", err)
}
cfile = path.Join(absViewPath, "show.tpl")
if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer CloseFile(f)
defer utils.CloseFile(f)
f.WriteString(cfile)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else {
logger.Fatalf("Could not create view file: %s", err)
beeLogger.Log.Fatalf("Could not create view file: %s", err)
}
cfile = path.Join(absViewPath, "create.tpl")
if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer CloseFile(f)
defer utils.CloseFile(f)
f.WriteString(cfile)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else {
logger.Fatalf("Could not create view file: %s", err)
beeLogger.Log.Fatalf("Could not create view file: %s", err)
}
cfile = path.Join(absViewPath, "edit.tpl")
if f, err := os.OpenFile(cfile, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err == nil {
defer CloseFile(f)
defer utils.CloseFile(f)
f.WriteString(cfile)
fmt.Fprintf(w, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", cfile, "\x1b[0m")
} else {
logger.Fatalf("Could not create view file: %s", err)
beeLogger.Log.Fatalf("Could not create view file: %s", err)
}
}

View File

@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package swaggergen
import (
"encoding/json"
@ -35,6 +35,8 @@ import (
"github.com/astaxie/beego/swagger"
"github.com/astaxie/beego/utils"
beeLogger "github.com/beego/bee/logger"
bu "github.com/beego/bee/utils"
)
const (
@ -42,6 +44,7 @@ const (
axml = "application/xml"
aplain = "text/plain"
ahtml = "text/html"
aform = "multipart/form-data"
)
var pkgCache map[string]struct{} //pkg:controller:function:comments comments: key:value
@ -50,7 +53,7 @@ var importlist map[string]string
var controllerList map[string]map[string]*swagger.Item //controllername Paths items
var modelsList map[string]map[string]swagger.Schema
var rootapi swagger.Swagger
var astPkgs map[string]*ast.Package
var astPkgs []*ast.Package
// refer to builtin.go
var basicTypes = map[string]string{
@ -74,7 +77,7 @@ var basicTypes = map[string]string{
"byte": "string:byte",
"rune": "string:byte",
// builtin golang objects
"time.Time": "string:string",
"time.Time": "string:string",
}
var stdlibObject = map[string]string{
@ -87,10 +90,10 @@ func init() {
importlist = make(map[string]string)
controllerList = make(map[string]map[string]*swagger.Item)
modelsList = make(map[string]map[string]swagger.Schema)
astPkgs = map[string]*ast.Package{}
astPkgs = make([]*ast.Package, 0)
}
func parsePackagesFromDir(dirpath string) {
func ParsePackagesFromDir(dirpath string) {
c := make(chan error)
go func() {
@ -102,7 +105,11 @@ func parsePackagesFromDir(dirpath string) {
return nil
}
if !strings.Contains(fpath, "vendor") && !strings.Contains(fpath, "tests") {
// 7 is length of 'vendor' (6) + length of file path separator (1)
// so we skip dir 'vendor' which is directly under dirpath
if !(len(fpath) == len(dirpath)+7 && strings.HasSuffix(fpath, "vendor")) &&
!strings.Contains(fpath, "tests") &&
!(len(fpath) > len(dirpath) && fpath[len(dirpath)+1] == '.') {
err = parsePackageFromDir(fpath)
if err != nil {
// Send the error to through the channel and continue walking
@ -116,7 +123,7 @@ func parsePackagesFromDir(dirpath string) {
}()
for err := range c {
logger.Warnf("%s", err)
beeLogger.Log.Warnf("%s", err)
}
}
@ -130,19 +137,19 @@ func parsePackageFromDir(path string) error {
return err
}
for k, v := range folderPkgs {
astPkgs[k] = v
for _, v := range folderPkgs {
astPkgs = append(astPkgs, v)
}
return nil
}
func generateDocs(curpath string) {
func GenerateDocs(curpath string) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path.Join(curpath, "routers", "router.go"), nil, parser.ParseComments)
f, err := parser.ParseFile(fset, filepath.Join(curpath, "routers", "router.go"), nil, parser.ParseComments)
if err != nil {
logger.Fatalf("Error while parsing router.go: %s", err)
beeLogger.Log.Fatalf("Error while parsing router.go: %s", err)
}
rootapi.Infos = swagger.Information{}
@ -182,18 +189,69 @@ func generateDocs(curpath string) {
rootapi.Schemes = strings.Split(strings.TrimSpace(s[len("@Schemes"):]), ",")
} else if strings.HasPrefix(s, "@Host") {
rootapi.Host = strings.TrimSpace(s[len("@Host"):])
} else if strings.HasPrefix(s, "@SecurityDefinition") {
if len(rootapi.SecurityDefinitions) == 0 {
rootapi.SecurityDefinitions = make(map[string]swagger.Security)
}
var out swagger.Security
p := getparams(strings.TrimSpace(s[len("@SecurityDefinition"):]))
if len(p) < 2 {
beeLogger.Log.Fatalf("Not enough params for security: %d\n", len(p))
}
out.Type = p[1]
switch out.Type {
case "oauth2":
if len(p) < 6 {
beeLogger.Log.Fatalf("Not enough params for oauth2: %d\n", len(p))
}
if !(p[3] == "implicit" || p[3] == "password" || p[3] == "application" || p[3] == "accessCode") {
beeLogger.Log.Fatalf("Unknown flow type: %s. Possible values are `implicit`, `password`, `application` or `accessCode`.\n", p[1])
}
out.AuthorizationURL = p[2]
out.Flow = p[3]
if len(p)%2 != 0 {
out.Description = strings.Trim(p[len(p)-1], `" `)
}
out.Scopes = make(map[string]string)
for i := 4; i < len(p)-1; i += 2 {
out.Scopes[p[i]] = strings.Trim(p[i+1], `" `)
}
case "apiKey":
if len(p) < 4 {
beeLogger.Log.Fatalf("Not enough params for apiKey: %d\n", len(p))
}
if !(p[3] == "header" || p[3] == "query") {
beeLogger.Log.Fatalf("Unknown in type: %s. Possible values are `query` or `header`.\n", p[4])
}
out.Name = p[2]
out.In = p[3]
if len(p) > 4 {
out.Description = strings.Trim(p[4], `" `)
}
case "basic":
if len(p) > 2 {
out.Description = strings.Trim(p[2], `" `)
}
default:
beeLogger.Log.Fatalf("Unknown security type: %s. Possible values are `oauth2`, `apiKey` or `basic`.\n", p[1])
}
rootapi.SecurityDefinitions[p[0]] = out
} else if strings.HasPrefix(s, "@Security") {
if len(rootapi.Security) == 0 {
rootapi.Security = make([]map[string][]string, 0)
}
rootapi.Security = append(rootapi.Security, getSecurity(s))
}
}
}
}
// Analyse controller package
for _, im := range f.Imports {
localName := ""
if im.Name != nil {
localName = im.Name.Name
}
analyseControllerPkg(localName, im.Path.Value)
analyseControllerPkg(path.Join(curpath, "vendor"), localName, im.Path.Value)
}
for _, d := range f.Decls {
switch specDecl := d.(type) {
@ -214,7 +272,7 @@ func generateDocs(curpath string) {
for _, p := range params {
switch pp := p.(type) {
case *ast.CallExpr:
controllerName := ""
var controllerName string
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
s, params := analyseNewNamespace(pp)
for _, sp := range params {
@ -251,6 +309,9 @@ func generateDocs(curpath string) {
}
os.Mkdir(path.Join(curpath, "swagger"), 0755)
fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json"))
if err != nil {
panic(err)
}
fdyml, err := os.Create(path.Join(curpath, "swagger", "swagger.yml"))
if err != nil {
panic(err)
@ -293,12 +354,10 @@ func analyseNSInclude(baseurl string, ce *ast.CallExpr) string {
}
if apis, ok := controllerList[cname]; ok {
for rt, item := range apis {
tag := ""
tag := cname
if baseurl != "" {
rt = baseurl + rt
tag = strings.Trim(baseurl, "/")
} else {
tag = cname
}
if item.Get != nil {
item.Get.Tags = []string{tag}
@ -332,7 +391,7 @@ func analyseNSInclude(baseurl string, ce *ast.CallExpr) string {
return cname
}
func analyseControllerPkg(localName, pkgpath string) {
func analyseControllerPkg(vendorPath, localName, pkgpath string) {
pkgpath = strings.Trim(pkgpath, "\"")
if isSystemPackage(pkgpath) {
return
@ -346,18 +405,23 @@ func analyseControllerPkg(localName, pkgpath string) {
pps := strings.Split(pkgpath, "/")
importlist[pps[len(pps)-1]] = pkgpath
}
gopath := os.Getenv("GOPATH")
if gopath == "" {
logger.Fatal("GOPATH environment variable is not set or empty")
gopaths := bu.GetGOPATHs()
if len(gopaths) == 0 {
beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
}
pkgRealpath := ""
wgopath := filepath.SplitList(gopath)
for _, wg := range wgopath {
wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", pkgpath))
if utils.FileExists(wg) {
pkgRealpath = wg
break
wg, _ := filepath.EvalSymlinks(filepath.Join(vendorPath, pkgpath))
if utils.FileExists(wg) {
pkgRealpath = wg
} else {
wgopath := gopaths
for _, wg := range wgopath {
wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", pkgpath))
if utils.FileExists(wg) {
pkgRealpath = wg
break
}
}
}
if pkgRealpath != "" {
@ -366,7 +430,7 @@ func analyseControllerPkg(localName, pkgpath string) {
}
pkgCache[pkgpath] = struct{}{}
} else {
logger.Fatalf("Package '%s' does not exist in the GOPATH", pkgpath)
beeLogger.Log.Fatalf("Package '%s' does not exist in the GOPATH or vendor path", pkgpath)
}
fileSet := token.NewFileSet()
@ -375,7 +439,7 @@ func analyseControllerPkg(localName, pkgpath string) {
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
}, parser.ParseComments)
if err != nil {
logger.Fatalf("Error while parsing dir at '%s': %s", pkgpath, err)
beeLogger.Log.Fatalf("Error while parsing dir at '%s': %s", pkgpath, err)
}
for _, pkg := range astPkgs {
for _, fl := range pkg.Files {
@ -385,7 +449,7 @@ func analyseControllerPkg(localName, pkgpath string) {
if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 {
if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok {
// Parse controller method
parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath)
parserComments(specDecl, fmt.Sprint(t.X), pkgpath)
}
}
case *ast.GenDecl:
@ -413,7 +477,7 @@ func isSystemPackage(pkgpath string) bool {
goroot = runtime.GOROOT()
}
if goroot == "" {
logger.Fatalf("GOROOT environment variable is not set or empty")
beeLogger.Log.Fatalf("GOROOT environment variable is not set or empty")
}
wg, _ := filepath.EvalSymlinks(filepath.Join(goroot, "src", "pkg", pkgpath))
@ -423,11 +487,7 @@ func isSystemPackage(pkgpath string) bool {
//TODO(zh):support go1.4
wg, _ = filepath.EvalSymlinks(filepath.Join(goroot, "src", pkgpath))
if utils.FileExists(wg) {
return true
}
return false
return utils.FileExists(wg)
}
func peekNextSplitString(ss string) (s string, spacePos int) {
@ -442,12 +502,16 @@ func peekNextSplitString(ss string) (s string, spacePos int) {
}
// parse the func comments
func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error {
func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
var routerPath string
var HTTPMethod string
opts := swagger.Operation{
Responses: make(map[string]swagger.Response),
}
funcName := f.Name.String()
comments := f.Doc
funcParamMap := buildParamMap(f.Type.Params)
//TODO: resultMap := buildParamMap(f.Type.Results)
if comments != nil && comments.List != nil {
for _, c := range comments.List {
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
@ -481,7 +545,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
ss = strings.TrimSpace(ss[pos:])
schemaName, pos := peekNextSplitString(ss)
if schemaName == "" {
logger.Fatalf("[%s.%s] Schema must follow {object} or {array}", controllerName, funcName)
beeLogger.Log.Fatalf("[%s.%s] Schema must follow {object} or {array}", controllerName, funcName)
}
if strings.HasPrefix(schemaName, "[]") {
schemaName = schemaName[2:]
@ -496,7 +560,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
m, mod, realTypes := getModel(schemaName)
schema.Ref = "#/definitions/" + m
if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema)
}
modelsList[pkgpath+controllerName][schemaName] = mod
appendModels(pkgpath, controllerName, realTypes)
@ -518,9 +582,19 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
para := swagger.Parameter{}
p := getparams(strings.TrimSpace(t[len("@Param "):]))
if len(p) < 4 {
logger.Fatal(controllerName + "_" + funcName + "'s comments @Param should have at least 4 params")
beeLogger.Log.Fatal(controllerName + "_" + funcName + "'s comments @Param should have at least 4 params")
}
para.Name = p[0]
paramNames := strings.SplitN(p[0], "=>", 2)
para.Name = paramNames[0]
funcParamName := para.Name
if len(paramNames) > 1 {
funcParamName = paramNames[1]
}
paramType, ok := funcParamMap[funcParamName]
if ok {
delete(funcParamMap, funcParamName)
}
switch p[1] {
case "query":
fallthrough
@ -533,7 +607,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
case "body":
break
default:
logger.Warnf("[%s.%s] Unknown param location: %s. Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1])
beeLogger.Log.Warnf("[%s.%s] Unknown param location: %s. Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1])
}
para.In = p[1]
pp := strings.Split(p[2], ".")
@ -544,38 +618,15 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
Ref: "#/definitions/" + m,
}
if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema)
}
modelsList[pkgpath+controllerName][typ] = mod
appendModels(pkgpath, controllerName, realTypes)
} else {
isArray := false
paraType := ""
paraFormat := ""
if strings.HasPrefix(typ, "[]") {
typ = typ[2:]
isArray = true
}
if typ == "string" || typ == "number" || typ == "integer" || typ == "boolean" ||
typ == "array" || typ == "file" {
paraType = typ
} else if sType, ok := basicTypes[typ]; ok {
typeFormat := strings.Split(sType, ":")
paraType = typeFormat[0]
paraFormat = typeFormat[1]
} else {
logger.Warnf("[%s.%s] Unknown param type: %s\n", controllerName, funcName, typ)
}
if isArray {
para.Type = "array"
para.Items = &swagger.ParameterItems{
Type: paraType,
Format: paraFormat,
}
} else {
para.Type = paraType
para.Format = paraFormat
if typ == "auto" {
typ = paramType
}
setParamType(&para, typ, pkgpath, controllerName)
}
switch len(p) {
case 5:
@ -625,12 +676,33 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
case "html":
opts.Consumes = append(opts.Consumes, ahtml)
opts.Produces = append(opts.Produces, ahtml)
case "form":
opts.Consumes = append(opts.Consumes, aform)
}
}
} else if strings.HasPrefix(t, "@Security") {
if len(opts.Security) == 0 {
opts.Security = make([]map[string][]string, 0)
}
opts.Security = append(opts.Security, getSecurity(t))
}
}
}
if routerPath != "" {
//Go over function parameters which were not mapped and create swagger params for them
for name, typ := range funcParamMap {
para := swagger.Parameter{}
para.Name = name
setParamType(&para, typ, pkgpath, controllerName)
if paramInPath(name, routerPath) {
para.In = "path"
} else {
para.In = "query"
}
opts.Parameters = append(opts.Parameters, para)
}
var item *swagger.Item
if itemList, ok := controllerList[pkgpath+controllerName]; ok {
if it, ok := itemList[routerPath]; !ok {
@ -642,27 +714,114 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
controllerList[pkgpath+controllerName] = make(map[string]*swagger.Item)
item = &swagger.Item{}
}
switch HTTPMethod {
case "GET":
item.Get = &opts
case "POST":
item.Post = &opts
case "PUT":
item.Put = &opts
case "PATCH":
item.Patch = &opts
case "DELETE":
item.Delete = &opts
case "HEAD":
item.Head = &opts
case "OPTIONS":
item.Options = &opts
for _, hm := range strings.Split(HTTPMethod, ",") {
switch hm {
case "GET":
item.Get = &opts
case "POST":
item.Post = &opts
case "PUT":
item.Put = &opts
case "PATCH":
item.Patch = &opts
case "DELETE":
item.Delete = &opts
case "HEAD":
item.Head = &opts
case "OPTIONS":
item.Options = &opts
}
}
controllerList[pkgpath+controllerName][routerPath] = item
}
return nil
}
func setParamType(para *swagger.Parameter, typ string, pkgpath, controllerName string) {
isArray := false
paraType := ""
paraFormat := ""
if strings.HasPrefix(typ, "[]") {
typ = typ[2:]
isArray = true
}
if typ == "string" || typ == "number" || typ == "integer" || typ == "boolean" ||
typ == "array" || typ == "file" {
paraType = typ
} else if sType, ok := basicTypes[typ]; ok {
typeFormat := strings.Split(sType, ":")
paraType = typeFormat[0]
paraFormat = typeFormat[1]
} else {
m, mod, realTypes := getModel(typ)
para.Schema = &swagger.Schema{
Ref: "#/definitions/" + m,
}
if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema)
}
modelsList[pkgpath+controllerName][typ] = mod
appendModels(pkgpath, controllerName, realTypes)
}
if isArray {
para.Type = "array"
para.Items = &swagger.ParameterItems{
Type: paraType,
Format: paraFormat,
}
} else {
para.Type = paraType
para.Format = paraFormat
}
}
func paramInPath(name, route string) bool {
return strings.HasSuffix(route, ":"+name) ||
strings.Contains(route, ":"+name+"/")
}
func getFunctionParamType(t ast.Expr) string {
switch paramType := t.(type) {
case *ast.Ident:
return paramType.Name
// case *ast.Ellipsis:
// result := getFunctionParamType(paramType.Elt)
// result.array = true
// return result
case *ast.ArrayType:
return "[]" + getFunctionParamType(paramType.Elt)
case *ast.StarExpr:
return getFunctionParamType(paramType.X)
case *ast.SelectorExpr:
return getFunctionParamType(paramType.X) + "." + paramType.Sel.Name
default:
return ""
}
}
func buildParamMap(list *ast.FieldList) map[string]string {
i := 0
result := map[string]string{}
if list != nil {
funcParams := list.List
for _, fparam := range funcParams {
param := getFunctionParamType(fparam.Type)
var paramName string
if len(fparam.Names) > 0 {
paramName = fparam.Names[0].Name
} else {
paramName = fmt.Sprint(i)
i++
}
result[paramName] = param
}
}
return result
}
// analisys params return []string
// @Param query form string true "The email for login"
// [query form string true "The email for login"]
@ -672,7 +831,7 @@ func getparams(str string) []string {
var start bool
var r []string
var quoted int8
for _, c := range []rune(str) {
for _, c := range str {
if unicode.IsSpace(c) && quoted == 0 {
if !start {
continue
@ -704,20 +863,22 @@ func getModel(str string) (objectname string, m swagger.Schema, realTypes []stri
packageName := ""
m.Type = "object"
for _, pkg := range astPkgs {
for _, fl := range pkg.Files {
for k, d := range fl.Scope.Objects {
if d.Kind == ast.Typ {
if k != objectname {
continue
if strs[0] == pkg.Name {
for _, fl := range pkg.Files {
for k, d := range fl.Scope.Objects {
if d.Kind == ast.Typ {
if k != objectname {
continue
}
packageName = pkg.Name
parseObject(d, k, &m, &realTypes, astPkgs, pkg.Name)
}
packageName = pkg.Name
parseObject(d, k, &m, &realTypes, astPkgs, pkg.Name)
}
}
}
}
if m.Title == "" {
logger.Warnf("Cannot find the object: %s", str)
beeLogger.Log.Warnf("Cannot find the object: %s", str)
// TODO remove when all type have been supported
//os.Exit(1)
}
@ -729,10 +890,10 @@ func getModel(str string) (objectname string, m swagger.Schema, realTypes []stri
return
}
func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package, packageName string) {
func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs []*ast.Package, packageName string) {
ts, ok := d.Decl.(*ast.TypeSpec)
if !ok {
logger.Fatalf("Unknown type without TypeSec: %v\n", d)
beeLogger.Log.Fatalf("Unknown type without TypeSec: %v\n", d)
}
// TODO support other types, such as `ArrayType`, `MapType`, `InterfaceType` etc...
st, ok := ts.Type.(*ast.StructType)
@ -743,7 +904,6 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
if st.Fields.List != nil {
m.Properties = make(map[string]swagger.Propertie)
for _, field := range st.Fields.List {
realType := ""
isSlice, realType, sType := typeAnalyser(field)
if (isSlice && isBasicType(realType)) || sType == "object" {
if len(strings.Split(realType, " ")) > 1 {
@ -759,7 +919,7 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
mp := swagger.Propertie{}
if isSlice {
mp.Type = "array"
if isBasicType(realType) {
if isBasicType(strings.Replace(realType, "[]", "", -1)) {
typeFormat := strings.Split(sType, ":")
mp.Items = &swagger.Propertie{
Type: typeFormat[0],
@ -808,7 +968,7 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
mp.Default = str2RealType(res[1], realType)
} else {
logger.Warnf("Invalid default value: %s", defaultValue)
beeLogger.Log.Warnf("Invalid default value: %s", defaultValue)
}
}
@ -862,7 +1022,7 @@ func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string
func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
if arr, ok := f.Type.(*ast.ArrayType); ok {
if isBasicType(fmt.Sprint(arr.Elt)) {
return false, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)]
return true, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)]
}
if mp, ok := arr.Elt.(*ast.MapType); ok {
return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value), "object"
@ -874,7 +1034,11 @@ func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
}
switch t := f.Type.(type) {
case *ast.StarExpr:
return false, fmt.Sprint(t.X), "object"
basicType := fmt.Sprint(t.X)
if k, ok := basicTypes[basicType]; ok {
return false, basicType, k
}
return false, basicType, "object"
case *ast.MapType:
val := fmt.Sprintf("%v", t.Value)
if isBasicType(val) {
@ -899,16 +1063,6 @@ func isBasicType(Type string) bool {
return false
}
// regexp get json tag
func grepJSONTag(tag string) string {
r, _ := regexp.Compile(`json:"([^"]*)"`)
matches := r.FindAllStringSubmatch(tag, -1)
if len(matches) > 0 {
return matches[0][1]
}
return ""
}
// append models
func appendModels(pkgpath, controllerName string, realTypes []string) {
for _, realType := range realTypes {
@ -924,6 +1078,19 @@ func appendModels(pkgpath, controllerName string, realTypes []string) {
}
}
func getSecurity(t string) (security map[string][]string) {
security = make(map[string][]string)
p := getparams(strings.TrimSpace(t[len("@Security"):]))
if len(p) == 0 {
beeLogger.Log.Fatalf("No params for security specified\n")
}
security[p[0]] = make([]string, 0)
for i := 1; i < len(p); i++ {
security[p[0]] = append(security[p[0]], p[i])
}
return
}
func urlReplace(src string) string {
pt := strings.Split(src, "/")
for i, p := range pt {
@ -956,7 +1123,7 @@ func str2RealType(s string, typ string) interface{} {
}
if err != nil {
logger.Warnf("Invalid default value type '%s': %s", typ, s)
beeLogger.Log.Warnf("Invalid default value type '%s': %s", typ, s)
return s
}

3
gosimple.ignore Normal file
View File

@ -0,0 +1,3 @@
github.com/beego/bee/cmd/commands/run/*.go:S1024
github.com/beego/bee/cmd/commands/dlv/*.go:S1024
github.com/beego/bee/utils/*.go:S1026

View File

@ -1,369 +0,0 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import (
"fmt"
"os"
path "path/filepath"
"strings"
)
var cmdHproseapp = &Command{
// CustomFlags: true,
UsageLine: "hprose [appname]",
Short: "Creates an RPC application based on Hprose and Beego frameworks",
Long: `
The command 'hprose' creates an RPC application based on both Beego and Hprose (http://hprose.com/).
{{"To scaffold out your application, use:"|bold}}
$ bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]
If 'conn' is empty, the command will generate a sample application. Otherwise the command
will connect to your database and generate models based on the existing tables.
The command 'hprose' creates a folder named [appname] with the following structure:
main.go
{{"conf"|foldername}}
app.conf
{{"models"|foldername}}
object.go
user.go
`,
PreRun: func(cmd *Command, args []string) { ShowShortVersionBanner() },
Run: createhprose,
}
var hproseconf = `appname = {{.Appname}}
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
EnableDocs = true
`
var hproseMaingo = `package main
import (
"fmt"
"reflect"
"{{.Appname}}/models"
"github.com/hprose/hprose-golang/rpc"
"github.com/astaxie/beego"
)
func logInvokeHandler(
name string,
args []reflect.Value,
context rpc.Context,
next rpc.NextInvokeHandler) (results []reflect.Value, err error) {
fmt.Printf("%s(%v) = ", name, args)
results, err = next(name, args, context)
fmt.Printf("%v %v\r\n", results, err)
return
}
func main() {
// Create WebSocketServer
// service := rpc.NewWebSocketService()
// Create Http Server
service := rpc.NewHTTPService()
// Use Logger Middleware
service.AddInvokeHandler(logInvokeHandler)
// Publish Functions
service.AddFunction("AddOne", models.AddOne)
service.AddFunction("GetOne", models.GetOne)
// Start Service
beego.Handler("/", service)
beego.Run()
}
`
var hproseMainconngo = `package main
import (
"fmt"
"reflect"
"{{.Appname}}/models"
"github.com/hprose/hprose-golang/rpc"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
{{.DriverPkg}}
)
func init() {
orm.RegisterDataBase("default", "{{.DriverName}}", "{{.conn}}")
}
func logInvokeHandler(
name string,
args []reflect.Value,
context rpc.Context,
next rpc.NextInvokeHandler) (results []reflect.Value, err error) {
fmt.Printf("%s(%v) = ", name, args)
results, err = next(name, args, context)
fmt.Printf("%v %v\r\n", results, err)
return
}
func main() {
// Create WebSocketServer
// service := rpc.NewWebSocketService()
// Create Http Server
service := rpc.NewHTTPService()
// Use Logger Middleware
service.AddInvokeHandler(logInvokeHandler)
{{HproseFunctionList}}
// Start Service
beego.Handler("/", service)
beego.Run()
}
`
var hproseModels = `package models
import (
"errors"
"strconv"
"time"
)
var (
Objects map[string]*Object
)
type Object struct {
ObjectId string
Score int64
PlayerName string
}
func init() {
Objects = make(map[string]*Object)
Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
}
func AddOne(object Object) (ObjectId string) {
object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
Objects[object.ObjectId] = &object
return object.ObjectId
}
func GetOne(ObjectId string) (object *Object, err error) {
if v, ok := Objects[ObjectId]; ok {
return v, nil
}
return nil, errors.New("ObjectId Not Exist")
}
func GetAll() map[string]*Object {
return Objects
}
func Update(ObjectId string, Score int64) (err error) {
if v, ok := Objects[ObjectId]; ok {
v.Score = Score
return nil
}
return errors.New("ObjectId Not Exist")
}
func Delete(ObjectId string) {
delete(Objects, ObjectId)
}
`
var hproseModels2 = `package models
import (
"errors"
"strconv"
"time"
)
var (
UserList map[string]*User
)
func init() {
UserList = make(map[string]*User)
u := User{"user_11111", "astaxie", "11111", Profile{"male", 20, "Singapore", "astaxie@gmail.com"}}
UserList["user_11111"] = &u
}
type User struct {
Id string
Username string
Password string
Profile Profile
}
type Profile struct {
Gender string
Age int
Address string
Email string
}
func AddUser(u User) string {
u.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)
UserList[u.Id] = &u
return u.Id
}
func GetUser(uid string) (u *User, err error) {
if u, ok := UserList[uid]; ok {
return u, nil
}
return nil, errors.New("User not exists")
}
func GetAllUsers() map[string]*User {
return UserList
}
func UpdateUser(uid string, uu *User) (a *User, err error) {
if u, ok := UserList[uid]; ok {
if uu.Username != "" {
u.Username = uu.Username
}
if uu.Password != "" {
u.Password = uu.Password
}
if uu.Profile.Age != 0 {
u.Profile.Age = uu.Profile.Age
}
if uu.Profile.Address != "" {
u.Profile.Address = uu.Profile.Address
}
if uu.Profile.Gender != "" {
u.Profile.Gender = uu.Profile.Gender
}
if uu.Profile.Email != "" {
u.Profile.Email = uu.Profile.Email
}
return u, nil
}
return nil, errors.New("User Not Exist")
}
func Login(username, password string) bool {
for _, u := range UserList {
if u.Username == username && u.Password == password {
return true
}
}
return false
}
func DeleteUser(uid string) {
delete(UserList, uid)
}
`
var hproseAddFunctions = []string{}
func init() {
cmdHproseapp.Flag.Var(&tables, "tables", "List of table names separated by a comma.")
cmdHproseapp.Flag.Var(&driver, "driver", "Database driver. Either mysql, postgres or sqlite.")
cmdHproseapp.Flag.Var(&conn, "conn", "Connection string used by the driver to connect to a database instance.")
}
func createhprose(cmd *Command, args []string) int {
output := cmd.Out()
if len(args) != 1 {
logger.Fatal("Argument [appname] is missing")
}
curpath, _ := os.Getwd()
if len(args) > 1 {
cmd.Flag.Parse(args[1:])
}
apppath, packpath, err := checkEnv(args[0])
if err != nil {
logger.Fatalf("%s", err)
}
if driver == "" {
driver = "mysql"
}
if conn == "" {
}
logger.Info("Creating Hprose application...")
os.MkdirAll(apppath, 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", apppath, "\x1b[0m")
os.Mkdir(path.Join(apppath, "conf"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf"), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "conf", "app.conf"), "\x1b[0m")
WriteToFile(path.Join(apppath, "conf", "app.conf"),
strings.Replace(hproseconf, "{{.Appname}}", args[0], -1))
if conn != "" {
logger.Infof("Using '%s' as 'driver'", driver)
logger.Infof("Using '%s' as 'conn'", conn)
logger.Infof("Using '%s' as 'tables'", tables)
generateHproseAppcode(string(driver), string(conn), "1", string(tables), path.Join(curpath, args[0]))
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
maingoContent := strings.Replace(hproseMainconngo, "{{.Appname}}", packpath, -1)
maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1)
maingoContent = strings.Replace(maingoContent, "{{HproseFunctionList}}", strings.Join(hproseAddFunctions, ""), -1)
if driver == "mysql" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/go-sql-driver/mysql"`, -1)
} else if driver == "postgres" {
maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1)
}
WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(
maingoContent,
"{{.conn}}",
conn.String(),
-1,
),
)
} else {
os.Mkdir(path.Join(apppath, "models"), 0755)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models"), "\x1b[0m")
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "object.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "models", "object.go"), apiModels)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "models", "user.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "models", "user.go"), apiModels2)
fmt.Fprintf(output, "\t%s%screate%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", path.Join(apppath, "main.go"), "\x1b[0m")
WriteToFile(path.Join(apppath, "main.go"),
strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1))
}
logger.Success("New Hprose application successfully created!")
return 0
}

View File

@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package colors
import (
"fmt"
@ -53,7 +53,7 @@ func NewModeColorWriter(w io.Writer, mode outputMode) io.Writer {
return w
}
func bold(message string) string {
func Bold(message string) string {
return fmt.Sprintf("\x1b[1m%s\x1b[21m", message)
}
@ -102,47 +102,47 @@ func Magenta(message string) string {
return fmt.Sprintf("\x1b[35m%s\x1b[0m", message)
}
// BlackBold returns a black bold string
// BlackBold returns a black Bold string
func BlackBold(message string) string {
return fmt.Sprintf("\x1b[30m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[30m%s\x1b[0m", Bold(message))
}
// WhiteBold returns a white bold string
// WhiteBold returns a white Bold string
func WhiteBold(message string) string {
return fmt.Sprintf("\x1b[37m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[37m%s\x1b[0m", Bold(message))
}
// CyanBold returns a cyan bold string
// CyanBold returns a cyan Bold string
func CyanBold(message string) string {
return fmt.Sprintf("\x1b[36m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[36m%s\x1b[0m", Bold(message))
}
// BlueBold returns a blue bold string
// BlueBold returns a blue Bold string
func BlueBold(message string) string {
return fmt.Sprintf("\x1b[34m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[34m%s\x1b[0m", Bold(message))
}
// RedBold returns a red bold string
// RedBold returns a red Bold string
func RedBold(message string) string {
return fmt.Sprintf("\x1b[31m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[31m%s\x1b[0m", Bold(message))
}
// GreenBold returns a green bold string
// GreenBold returns a green Bold string
func GreenBold(message string) string {
return fmt.Sprintf("\x1b[32m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[32m%s\x1b[0m", Bold(message))
}
// YellowBold returns a yellow bold string
// YellowBold returns a yellow Bold string
func YellowBold(message string) string {
return fmt.Sprintf("\x1b[33m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[33m%s\x1b[0m", Bold(message))
}
// GrayBold returns a gray bold string
// GrayBold returns a gray Bold string
func GrayBold(message string) string {
return fmt.Sprintf("\x1b[37m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[37m%s\x1b[0m", Bold(message))
}
// MagentaBold returns a magenta bold string
// MagentaBold returns a magenta Bold string
func MagentaBold(message string) string {
return fmt.Sprintf("\x1b[35m%s\x1b[0m", bold(message))
return fmt.Sprintf("\x1b[35m%s\x1b[0m", Bold(message))
}

View File

@ -14,7 +14,7 @@
// +build !windows
package main
package colors
import "io"

View File

@ -14,7 +14,7 @@
// +build windows
package main
package colors
import (
"bytes"
@ -360,7 +360,7 @@ func isParameterChar(b byte) bool {
}
func (cw *colorWriter) Write(p []byte) (int, error) {
r, nw, first, last := 0, 0, 0, 0
var r, nw, first, last int
if cw.mode != DiscardNonColorEscSeq {
cw.state = outsideCsiCode
cw.resetBuffer()

View File

@ -11,7 +11,7 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
package beeLogger
import (
"errors"
@ -22,19 +22,22 @@ import (
"sync"
"sync/atomic"
"text/template"
"time"
"github.com/beego/bee/logger/colors"
)
var errInvalidLogLevel = errors.New("logger: invalid log level")
const (
levelCritical = iota
levelFatal
levelSuccess
levelHint
levelDebug
levelInfo
levelWarn
levelDebug = iota
levelError
levelFatal
levelCritical
levelSuccess
levelWarn
levelInfo
levelHint
)
var (
@ -42,6 +45,9 @@ var (
instance *BeeLogger
once sync.Once
)
var debugMode = os.Getenv("DEBUG_ENABLED") == "1"
var logLevel = levelInfo
// BeeLogger logs logging records to the specified io.Writer
type BeeLogger struct {
@ -59,6 +65,8 @@ type LogRecord struct {
LineNo int
}
var Log = GetBeeLogger(os.Stdout)
var (
logRecordTemplate *template.Template
debugLogRecordTemplate *template.Template
@ -80,11 +88,15 @@ func GetBeeLogger(w io.Writer) *BeeLogger {
"EndLine": EndLine,
}
logRecordTemplate, err = template.New("simpleLogFormat").Funcs(funcs).Parse(simpleLogFormat)
MustCheck(err)
if err != nil {
panic(err)
}
debugLogRecordTemplate, err = template.New("debugLogFormat").Funcs(funcs).Parse(debugLogFormat)
MustCheck(err)
if err != nil {
panic(err)
}
instance = &BeeLogger{output: NewColorWriter(w)}
instance = &BeeLogger{output: colors.NewColorWriter(w)}
})
return instance
}
@ -93,7 +105,17 @@ func GetBeeLogger(w io.Writer) *BeeLogger {
func (l *BeeLogger) SetOutput(w io.Writer) {
l.mu.Lock()
defer l.mu.Unlock()
l.output = NewColorWriter(w)
l.output = colors.NewColorWriter(w)
}
// Now returns the current local time in the specified layout
func Now(layout string) string {
return time.Now().Format(layout)
}
// EndLine returns the a newline escape character
func EndLine() string {
return "\n"
}
func (l *BeeLogger) getLevelTag(level int) string {
@ -122,21 +144,21 @@ func (l *BeeLogger) getLevelTag(level int) string {
func (l *BeeLogger) getColorLevel(level int) string {
switch level {
case levelCritical:
return RedBold(l.getLevelTag(level))
return colors.RedBold(l.getLevelTag(level))
case levelFatal:
return RedBold(l.getLevelTag(level))
return colors.RedBold(l.getLevelTag(level))
case levelInfo:
return BlueBold(l.getLevelTag(level))
return colors.BlueBold(l.getLevelTag(level))
case levelHint:
return CyanBold(l.getLevelTag(level))
return colors.CyanBold(l.getLevelTag(level))
case levelDebug:
return YellowBold(l.getLevelTag(level))
return colors.YellowBold(l.getLevelTag(level))
case levelError:
return RedBold(l.getLevelTag(level))
return colors.RedBold(l.getLevelTag(level))
case levelWarn:
return YellowBold(l.getLevelTag(level))
return colors.YellowBold(l.getLevelTag(level))
case levelSuccess:
return GreenBold(l.getLevelTag(level))
return colors.GreenBold(l.getLevelTag(level))
default:
panic(errInvalidLogLevel)
}
@ -145,6 +167,9 @@ func (l *BeeLogger) getColorLevel(level int) string {
// mustLog logs the message according to the specified level and arguments.
// It panics in case of an error.
func (l *BeeLogger) mustLog(level int, message string, args ...interface{}) {
if level > logLevel {
return
}
// Acquire the lock
l.mu.Lock()
defer l.mu.Unlock()
@ -157,13 +182,15 @@ func (l *BeeLogger) mustLog(level int, message string, args ...interface{}) {
}
err := logRecordTemplate.Execute(l.output, record)
MustCheck(err)
if err != nil {
panic(err)
}
}
// mustLogDebug logs a debug message only if debug mode
// is enabled. i.e. DEBUG_ENABLED="1"
func (l *BeeLogger) mustLogDebug(message string, file string, line int, args ...interface{}) {
if !IsDebugEnabled() {
if !debugMode {
return
}
@ -179,7 +206,9 @@ func (l *BeeLogger) mustLogDebug(message string, file string, line int, args ...
Filename: filepath.Base(file),
}
err := debugLogRecordTemplate.Execute(l.output, record)
MustCheck(err)
if err != nil {
panic(err)
}
}
// Debug outputs a debug log message

76
main.go Normal file
View File

@ -0,0 +1,76 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import (
"flag"
"log"
"os"
"github.com/beego/bee/cmd"
"github.com/beego/bee/cmd/commands"
"github.com/beego/bee/config"
"github.com/beego/bee/generate/swaggergen"
"github.com/beego/bee/utils"
)
func main() {
currentpath, _ := os.Getwd()
flag.Usage = cmd.Usage
flag.Parse()
log.SetFlags(0)
args := flag.Args()
if len(args) < 1 {
cmd.Usage()
os.Exit(2)
return
}
if args[0] == "help" {
cmd.Help(args[1:])
return
}
for _, c := range commands.AvailableCommands {
if c.Name() == args[0] && c.Run != nil {
c.Flag.Usage = func() { c.Usage() }
if c.CustomFlags {
args = args[1:]
} else {
c.Flag.Parse(args[1:])
args = c.Flag.Args()
}
if c.PreRun != nil {
c.PreRun(c, args)
}
config.LoadConfig()
// Check if current directory is inside the GOPATH,
// if so parse the packages inside it.
if utils.IsInGOPATH(currentpath) && cmd.IfGenerateDocs(c.Name(), args) {
swaggergen.ParsePackagesFromDir(currentpath)
}
os.Exit(c.Run(c, args))
return
}
}
utils.PrintErrorAndExit("Unknown subcommand", cmd.ErrorTemplate)
}

View File

@ -1,155 +0,0 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"strings"
)
var cmdRundocs = &Command{
UsageLine: "rundocs [-isDownload=true] [-docport=8888]",
Short: "rundocs will run the docs server,default is 8089",
Long: `
-d meaning will download the docs file from github
-p meaning server the Server on which port, default is 8089
`,
}
var (
swaggerVersion = "2"
swaggerlink = "https://github.com/beego/swagger/archive/v" + swaggerVersion + ".zip"
)
type docValue string
func (d *docValue) String() string {
return fmt.Sprint(*d)
}
func (d *docValue) Set(value string) error {
*d = docValue(value)
return nil
}
var isDownload docValue
var docport docValue
func init() {
cmdRundocs.Run = runDocs
cmdRundocs.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
cmdRundocs.Flag.Var(&isDownload, "isDownload", "weather download the Swagger Docs")
cmdRundocs.Flag.Var(&docport, "docport", "doc server port")
}
func runDocs(cmd *Command, args []string) int {
if isDownload == "true" {
downloadFromURL(swaggerlink, "swagger.zip")
err := unzipAndDelete("swagger.zip")
if err != nil {
logger.Errorf("Error while unzipping 'swagger.zip' file: %s", err)
}
}
if docport == "" {
docport = "8089"
}
if _, err := os.Stat("swagger"); err != nil && os.IsNotExist(err) {
logger.Fatal("No Swagger dist found. Run: bee rundocs -isDownload=true")
}
logger.Infof("Starting the docs server on: http://127.0.0.1:%s", docport)
err := http.ListenAndServe(":"+string(docport), http.FileServer(http.Dir("swagger")))
if err != nil {
logger.Fatalf("%s", err)
}
return 0
}
func downloadFromURL(url, fileName string) {
var down bool
if fd, err := os.Stat(fileName); err != nil && os.IsNotExist(err) {
down = true
} else if fd.Size() == int64(0) {
down = true
} else {
logger.Infof("'%s' already exists", fileName)
return
}
if down {
logger.Infof("Downloading '%s' to '%s'...", url, fileName)
output, err := os.Create(fileName)
if err != nil {
logger.Errorf("Error while creating '%s': %s", fileName, err)
return
}
defer output.Close()
response, err := http.Get(url)
if err != nil {
logger.Errorf("Error while downloading '%s': %s", url, err)
return
}
defer response.Body.Close()
n, err := io.Copy(output, response.Body)
if err != nil {
logger.Errorf("Error while downloading '%s': %s", url, err)
return
}
logger.Successf("%d bytes downloaded!", n)
}
}
func unzipAndDelete(src string) error {
logger.Infof("Unzipping '%s'...", src)
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
rp := strings.NewReplacer("swagger-"+swaggerVersion, "swagger")
for _, f := range r.File {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
fname := rp.Replace(f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(fname, f.Mode())
} else {
f, err := os.OpenFile(
fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
}
logger.Successf("Done! Deleting '%s'...", src)
return os.RemoveAll(src)
}

1
staticcheck.ignore Normal file
View File

@ -0,0 +1 @@
github.com/beego/bee/generate/swaggergen/*.go:SA1024

102
test.go
View File

@ -1,102 +0,0 @@
// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package main
import (
"os"
"os/exec"
path "path/filepath"
"time"
_ "github.com/smartystreets/goconvey/convey"
)
var cmdTest = &Command{
UsageLine: "test [appname]",
Short: "test the app",
Long: ``,
}
func init() {
cmdTest.Run = testApp
cmdTest.PreRun = func(cmd *Command, args []string) { ShowShortVersionBanner() }
}
func safePathAppend(arr []string, paths ...string) []string {
for _, path := range paths {
if pathExists(path) {
arr = append(arr, path)
}
}
return arr
}
func pathExists(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
var started = make(chan bool)
func testApp(cmd *Command, args []string) int {
if len(args) != 1 {
logger.Fatalf("Failed to start: %s", "argument 'appname' is missing")
}
currpath, _ := os.Getwd()
logger.Debugf("Current path: %s", __FILE__(), __LINE__(), currpath)
err := loadConfig()
if err != nil {
logger.Fatalf("Failed to load configuration: %s", err)
}
var paths []string
readAppDirectories(currpath, &paths)
NewWatcher(paths, nil, false)
appname = args[0]
for {
select {
case <-started:
runTest()
}
}
}
func runTest() {
logger.Info("Start testing...")
time.Sleep(time.Second * 1)
crupwd, _ := os.Getwd()
testDir := path.Join(crupwd, "tests")
if pathExists(testDir) {
os.Chdir(testDir)
}
var err error
icmd := exec.Command("go", "test")
icmd.Stdout = os.Stdout
icmd.Stderr = os.Stderr
logger.Info("============== Test Begin ===================")
err = icmd.Run()
logger.Info("============== Test End =====================")
if err != nil {
logger.Error("============== Test failed ===================")
logger.Errorf("Cause: %s", err)
return
}
logger.Success("Test Completed")
}

View File

@ -1,28 +0,0 @@
package router
import (
"github.com/astaxie/beego"
)
type Router struct {
beego.Controller
}
func (r *Router) Get() {
}
func (r *Router) Post() {
}
type Controller struct {
}
func (c *Controller) Put() {
}
func (c *Controller) Delete() {
}

14
utils/doc_value.go Normal file
View File

@ -0,0 +1,14 @@
package utils
import "fmt"
type DocValue string
func (d *DocValue) String() string {
return fmt.Sprint(*d)
}
func (d *DocValue) Set(value string) error {
*d = DocValue(value)
return nil
}

14
utils/list_opts.go Normal file
View File

@ -0,0 +1,14 @@
package utils
import "fmt"
type ListOpts []string
func (opts *ListOpts) String() string {
return fmt.Sprint(*opts)
}
func (opts *ListOpts) Set(value string) error {
*opts = append(*opts, value)
return nil
}

84
utils/notification.go Normal file
View File

@ -0,0 +1,84 @@
// Copyright 2017 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package utils
import (
"fmt"
"os/exec"
"strconv"
"strings"
"runtime"
"github.com/beego/bee/config"
)
const appName = "Beego"
func Notify(text, title string) {
if !config.Conf.EnableNotification {
return
}
switch runtime.GOOS {
case "darwin":
osxNotify(text, title)
case "linux":
linuxNotify(text, title)
case "windows":
windowsNotify(text, title)
}
}
func osxNotify(text, title string) {
var cmd *exec.Cmd
if existTerminalNotifier() {
cmd = exec.Command("terminal-notifier", "-title", appName, "-message", text, "-subtitle", title)
} else if MacOSVersionSupport() {
notification := fmt.Sprintf("display notification \"%s\" with title \"%s\" subtitle \"%s\"", text, appName, title)
cmd = exec.Command("osascript", "-e", notification)
} else {
cmd = exec.Command("growlnotify", "-n", appName, "-m", title)
}
cmd.Run()
}
func windowsNotify(text, title string) {
exec.Command("growlnotify", "/i:", "", "/t:", title, text).Run()
}
func linuxNotify(text, title string) {
exec.Command("notify-send", "-i", "", title, text).Run()
}
func existTerminalNotifier() bool {
cmd := exec.Command("which", "terminal-notifier")
err := cmd.Start()
if err != nil {
return false
}
err = cmd.Wait()
return err != nil
}
func MacOSVersionSupport() bool {
cmd := exec.Command("sw_vers", "-productVersion")
check, _ := cmd.Output()
version := strings.Split(string(check), ".")
major, _ := strconv.Atoi(version[0])
minor, _ := strconv.Atoi(version[1])
if major < 10 || (major == 10 && minor < 9) {
return false
}
return true
}

15
utils/str_flag.go Normal file
View File

@ -0,0 +1,15 @@
package utils
import "fmt"
// The string flag list, implemented flag.Value interface
type StrFlags []string
func (s *StrFlags) String() string {
return fmt.Sprintf("%s", *s)
}
func (s *StrFlags) Set(value string) error {
*s = append(*s, value)
return nil
}

View File

@ -12,7 +12,7 @@
// License for the specific language governing permissions and limitations
// under the License.
package main
package utils
import (
"bytes"
@ -27,6 +27,10 @@ import (
"strings"
"text/template"
"time"
"unicode"
beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/logger/colors"
)
// Go is a basic promise implementation: it wraps calls a function in a goroutine
@ -39,18 +43,8 @@ func Go(f func() error) chan error {
return ch
}
// Now returns the current local time in the specified layout
func Now(layout string) string {
return time.Now().Format(layout)
}
// EndLine returns the a newline escape character
func EndLine() string {
return "\n"
}
// IsExist returns whether a file or directory exists.
func isExist(path string) bool {
func IsExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
@ -58,22 +52,20 @@ func isExist(path string) bool {
// GetGOPATHs returns all paths in GOPATH variable.
func GetGOPATHs() []string {
gopath := os.Getenv("GOPATH")
var paths []string
if runtime.GOOS == "windows" {
gopath = strings.Replace(gopath, "\\", "/", -1)
paths = strings.Split(gopath, ";")
} else {
paths = strings.Split(gopath, ":")
if gopath == "" && strings.Compare(runtime.Version(), "go1.8") >= 0 {
gopath = defaultGOPATH()
}
return paths
return filepath.SplitList(gopath)
}
// IsInGOPATH checks the path is in the fisrt GOPATH(/src) or not
// IsInGOPATH checks whether the path is inside of any GOPATH or not
func IsInGOPATH(thePath string) bool {
if runtime.GOOS == "windows" {
thePath = filepath.ToSlash(thePath)
for _, gopath := range GetGOPATHs() {
if strings.Contains(thePath, filepath.Join(gopath, "src")) {
return true
}
}
return strings.Contains(thePath, GetGOPATHs()[0]+"/src")
return false
}
// IsBeegoProject checks whether the current path is a Beego application or not
@ -107,7 +99,7 @@ func IsBeegoProject(thePath string) bool {
}()
if err := <-c; err != nil {
logger.Fatalf("Unable to walk '%s' tree: %s", thePath, err)
beeLogger.Log.Fatalf("Unable to walk '%s' tree: %s", thePath, err)
}
if len(mainFiles) > 0 {
@ -121,7 +113,7 @@ func IsBeegoProject(thePath string) bool {
func SearchGOPATHs(app string) (bool, string, string) {
gps := GetGOPATHs()
if len(gps) == 0 {
logger.Fatal("GOPATH environment variable is not set or empty")
beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
}
// Lookup the application inside the user workspace(s)
@ -135,7 +127,7 @@ func SearchGOPATHs(app string) (bool, string, string) {
currentPath = app
}
if isExist(currentPath) {
if IsExist(currentPath) {
return true, gopath, currentPath
}
}
@ -147,11 +139,11 @@ func SearchGOPATHs(app string) (bool, string, string) {
// confirmations. If the input is not recognized, it will ask again. The function does not return
// until it gets a valid response from the user. Typically, you should use fmt to print out a question
// before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)")
func askForConfirmation() bool {
func AskForConfirmation() bool {
var response string
_, err := fmt.Scanln(&response)
if err != nil {
logger.Fatalf("%s", err)
beeLogger.Log.Fatalf("%s", err)
}
okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
nokayResponses := []string{"n", "N", "no", "No", "NO"}
@ -161,7 +153,7 @@ func askForConfirmation() bool {
return false
} else {
fmt.Println("Please type yes or no and then press enter:")
return askForConfirmation()
return AskForConfirmation()
}
}
@ -175,7 +167,7 @@ func containsString(slice []string, element string) bool {
}
// snake string, XxYy to xx_yy
func snakeString(s string) string {
func SnakeString(s string) string {
data := make([]byte, 0, len(s)*2)
j := false
num := len(s)
@ -192,17 +184,17 @@ func snakeString(s string) string {
return strings.ToLower(string(data[:]))
}
func camelString(s string) string {
func CamelString(s string) string {
data := make([]byte, 0, len(s))
j := false
k := false
num := len(s) - 1
for i := 0; i <= num; i++ {
d := s[i]
if k == false && d >= 'A' && d <= 'Z' {
if !k && d >= 'A' && d <= 'Z' {
k = true
}
if d >= 'a' && d <= 'z' && (j || k == false) {
if d >= 'a' && d <= 'z' && (j || !k) {
d = d - 32
j = false
k = true
@ -218,7 +210,7 @@ func camelString(s string) string {
// camelCase converts a _ delimited string to camel case
// e.g. very_important_person => VeryImportantPerson
func camelCase(in string) string {
func CamelCase(in string) string {
tokens := strings.Split(in, "_")
for i := range tokens {
tokens[i] = strings.Title(strings.Trim(tokens[i], " "))
@ -227,25 +219,13 @@ func camelCase(in string) string {
}
// formatSourceCode formats source files
func formatSourceCode(filename string) {
func FormatSourceCode(filename string) {
cmd := exec.Command("gofmt", "-w", filename)
if err := cmd.Run(); err != nil {
logger.Warnf("Error while running gofmt: %s", err)
beeLogger.Log.Warnf("Error while running gofmt: %s", err)
}
}
// The string flag list, implemented flag.Value interface
type strFlags []string
func (s *strFlags) String() string {
return fmt.Sprintf("%s", *s)
}
func (s *strFlags) Set(value string) error {
*s = append(*s, value)
return nil
}
// CloseFile attempts to close the passed file
// or panics with the actual error
func CloseFile(f *os.File) {
@ -260,11 +240,6 @@ func MustCheck(err error) {
}
}
func exitPrint(con string) {
fmt.Fprintln(os.Stderr, con)
os.Exit(2)
}
// WriteToFile creates a file and writes content to it
func WriteToFile(filename, content string) {
f, err := os.Create(filename)
@ -274,20 +249,14 @@ func WriteToFile(filename, content string) {
MustCheck(err)
}
// IsDebugEnabled checks if DEBUG_ENABLED is set or not
func IsDebugEnabled() bool {
debugMode := os.Getenv("DEBUG_ENABLED")
return map[string]bool{"1": true, "0": false}[debugMode]
}
// __FILE__ returns the file name in which the function was invoked
func __FILE__() string {
func FILE() string {
_, file, _, _ := runtime.Caller(1)
return file
}
// __LINE__ returns the line number at which the function was invoked
func __LINE__() int {
func LINE() int {
_, _, line, _ := runtime.Caller(1)
return line
}
@ -296,9 +265,9 @@ func __LINE__() int {
func BeeFuncMap() template.FuncMap {
return template.FuncMap{
"trim": strings.TrimSpace,
"bold": bold,
"headline": MagentaBold,
"foldername": RedBold,
"bold": colors.Bold,
"headline": colors.MagentaBold,
"foldername": colors.RedBold,
"endline": EndLine,
"tmpltostr": TmplToString,
}
@ -315,3 +284,157 @@ func TmplToString(tmpl string, data interface{}) string {
return doc.String()
}
// EndLine returns the a newline escape character
func EndLine() string {
return "\n"
}
func Tmpl(text string, data interface{}) {
output := colors.NewColorWriter(os.Stderr)
t := template.New("Usage").Funcs(BeeFuncMap())
template.Must(t.Parse(text))
err := t.Execute(output, data)
if err != nil {
beeLogger.Log.Error(err.Error())
}
}
func CheckEnv(appname string) (apppath, packpath string, err error) {
gps := GetGOPATHs()
if len(gps) == 0 {
beeLogger.Log.Fatal("GOPATH environment variable is not set or empty")
}
currpath, _ := os.Getwd()
currpath = filepath.Join(currpath, appname)
for _, gpath := range gps {
gsrcpath := filepath.Join(gpath, "src")
if strings.HasPrefix(strings.ToLower(currpath), strings.ToLower(gsrcpath)) {
packpath = strings.Replace(currpath[len(gsrcpath)+1:], string(filepath.Separator), "/", -1)
return currpath, packpath, nil
}
}
// In case of multiple paths in the GOPATH, by default
// we use the first path
gopath := gps[0]
beeLogger.Log.Warn("You current workdir is not inside $GOPATH/src.")
beeLogger.Log.Debugf("GOPATH: %s", FILE(), LINE(), gopath)
gosrcpath := filepath.Join(gopath, "src")
apppath = filepath.Join(gosrcpath, appname)
if _, e := os.Stat(apppath); !os.IsNotExist(e) {
err = fmt.Errorf("Cannot create application without removing '%s' first", apppath)
beeLogger.Log.Errorf("Path '%s' already exists", apppath)
return
}
packpath = strings.Join(strings.Split(apppath[len(gosrcpath)+1:], string(filepath.Separator)), "/")
return
}
func PrintErrorAndExit(message, errorTemplate string) {
Tmpl(fmt.Sprintf(errorTemplate, message), nil)
os.Exit(2)
}
// GoCommand executes the passed command using Go tool
func GoCommand(command string, args ...string) error {
allargs := []string{command}
allargs = append(allargs, args...)
goBuild := exec.Command("go", allargs...)
goBuild.Stderr = os.Stderr
return goBuild.Run()
}
// SplitQuotedFields is like strings.Fields but ignores spaces
// inside areas surrounded by single quotes.
// To specify a single quote use backslash to escape it: '\''
func SplitQuotedFields(in string) []string {
type stateEnum int
const (
inSpace stateEnum = iota
inField
inQuote
inQuoteEscaped
)
state := inSpace
r := []string{}
var buf bytes.Buffer
for _, ch := range in {
switch state {
case inSpace:
if ch == '\'' {
state = inQuote
} else if !unicode.IsSpace(ch) {
buf.WriteRune(ch)
state = inField
}
case inField:
if ch == '\'' {
state = inQuote
} else if unicode.IsSpace(ch) {
r = append(r, buf.String())
buf.Reset()
} else {
buf.WriteRune(ch)
}
case inQuote:
if ch == '\'' {
state = inField
} else if ch == '\\' {
state = inQuoteEscaped
} else {
buf.WriteRune(ch)
}
case inQuoteEscaped:
buf.WriteRune(ch)
state = inQuote
}
}
if buf.Len() != 0 {
r = append(r, buf.String())
}
return r
}
// GetFileModTime returns unix timestamp of `os.File.ModTime` for the given path.
func GetFileModTime(path string) int64 {
path = strings.Replace(path, "\\", "/", -1)
f, err := os.Open(path)
if err != nil {
beeLogger.Log.Errorf("Failed to open file on '%s': %s", path, err)
return time.Now().Unix()
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
beeLogger.Log.Errorf("Failed to get file stats: %s", err)
return time.Now().Unix()
}
return fi.ModTime().Unix()
}
func defaultGOPATH() string {
env := "HOME"
if runtime.GOOS == "windows" {
env = "USERPROFILE"
} else if runtime.GOOS == "plan9" {
env = "home"
}
if home := os.Getenv(env); home != "" {
return filepath.Join(home, "go")
}
return ""
}

View File

@ -22,19 +22,19 @@ package swagger
// Swagger list the resource
type Swagger struct {
SwaggerVersion string `json:"swagger,omitempty" yaml:"swagger,omitempty"`
Infos Information `json:"info" yaml:"info"`
Host string `json:"host,omitempty" yaml:"host,omitempty"`
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Paths map[string]*Item `json:"paths" yaml:"paths"`
Definitions map[string]Schema `json:"definitions,omitempty" yaml:"definitions,omitempty"`
SecurityDefinitions map[string]Security `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
Security map[string][]string `json:"security,omitempty" yaml:"security,omitempty"`
Tags []Tag `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
SwaggerVersion string `json:"swagger,omitempty" yaml:"swagger,omitempty"`
Infos Information `json:"info" yaml:"info"`
Host string `json:"host,omitempty" yaml:"host,omitempty"`
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Paths map[string]*Item `json:"paths" yaml:"paths"`
Definitions map[string]Schema `json:"definitions,omitempty" yaml:"definitions,omitempty"`
SecurityDefinitions map[string]Security `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
Security []map[string][]string `json:"security,omitempty" yaml:"security,omitempty"`
Tags []Tag `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
// Information Provides metadata about the API. The metadata can be used by the clients if needed.
@ -75,16 +75,17 @@ type Item struct {
// Operation Describes a single API operation on a path.
type Operation struct {
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Parameters []Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]Response `json:"responses,omitempty" yaml:"responses,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Parameters []Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]Response `json:"responses,omitempty" yaml:"responses,omitempty"`
Security []map[string][]string `json:"security,omitempty" yaml:"security,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
}
// Parameter Describes a single operation parameter.
@ -100,7 +101,7 @@ type Parameter struct {
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
}
// A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body".
// ParameterItems A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body".
// http://swagger.io/specification/#itemsObject
type ParameterItems struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"`
@ -140,7 +141,7 @@ type Propertie struct {
// Response as they are returned from executing this operation.
type Response struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Description string `json:"description" yaml:"description"`
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
}

View File

@ -61,10 +61,8 @@ func (m *BeeMap) Set(k interface{}, v interface{}) bool {
func (m *BeeMap) Check(k interface{}) bool {
m.lock.RLock()
defer m.lock.RUnlock()
if _, ok := m.bm[k]; !ok {
return false
}
return true
_, ok := m.bm[k]
return ok
}
// Delete the given key and value.
@ -84,3 +82,10 @@ func (m *BeeMap) Items() map[interface{}]interface{} {
}
return r
}
// Count returns the number of items within the map.
func (m *BeeMap) Count() int {
m.lock.RLock()
defer m.lock.RUnlock()
return len(m.bm)
}

View File

@ -1,4 +1,6 @@
Copyright (c) 2013, Space Monkey, Inc.
The MIT License (MIT)
Copyright (c) 2014 Derek Parker
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

134
vendor/github.com/derekparker/delve/config/config.go generated vendored Normal file
View File

@ -0,0 +1,134 @@
package config
import (
"fmt"
"io/ioutil"
"os"
"os/user"
"path"
yaml "gopkg.in/yaml.v2"
)
const (
configDir string = ".dlv"
configFile string = "config.yml"
)
// Describes a rule for substitution of path to source code file.
type SubstitutePathRule struct {
// Directory path will be substituted if it matches `From`.
From string
// Path to which substitution is performed.
To string
}
// Slice of source code path substitution rules.
type SubstitutePathRules []SubstitutePathRule
// Config defines all configuration options available to be set through the config file.
type Config struct {
// Commands aliases.
Aliases map[string][]string
// Source code path substitution rules.
SubstitutePath SubstitutePathRules `yaml:"substitute-path"`
}
// LoadConfig attempts to populate a Config object from the config.yml file.
func LoadConfig() *Config {
err := createConfigPath()
if err != nil {
fmt.Printf("Could not create config directory: %v.", err)
return nil
}
fullConfigFile, err := GetConfigFilePath(configFile)
if err != nil {
fmt.Printf("Unable to get config file path: %v.", err)
return nil
}
f, err := os.Open(fullConfigFile)
if err != nil {
createDefaultConfig(fullConfigFile)
return nil
}
defer func() {
err := f.Close()
if err != nil {
fmt.Printf("Closing config file failed: %v.", err)
}
}()
data, err := ioutil.ReadAll(f)
if err != nil {
fmt.Printf("Unable to read config data: %v.", err)
return nil
}
var c Config
err = yaml.Unmarshal(data, &c)
if err != nil {
fmt.Printf("Unable to decode config file: %v.", err)
return nil
}
return &c
}
func createDefaultConfig(path string) {
f, err := os.Create(path)
if err != nil {
fmt.Printf("Unable to create config file: %v.", err)
return
}
defer func() {
err := f.Close()
if err != nil {
fmt.Printf("Closing config file failed: %v.", err)
}
}()
err = writeDefaultConfig(f)
if err != nil {
fmt.Printf("Unable to write default configuration: %v.", err)
}
}
func writeDefaultConfig(f *os.File) error {
_, err := f.WriteString(
`# Configuration file for the delve debugger.
# This is the default configuration file. Available options are provided, but disabled.
# Delete the leading hash mark to enable an item.
# Provided aliases will be added to the default aliases for a given command.
aliases:
# command: ["alias1", "alias2"]
# Define sources path substitution rules. Can be used to rewrite a source path stored
# in program's debug information, if the sources were moved to a different place
# between compilation and debugging.
# Note that substitution rules will not be used for paths passed to "break" and "trace"
# commands.
substitute-path:
# - {from: path, to: path}
`)
return err
}
// createConfigPath creates the directory structure at which all config files are saved.
func createConfigPath() error {
path, err := GetConfigFilePath("")
if err != nil {
return err
}
return os.MkdirAll(path, 0700)
}
// GetConfigFilePath gets the full path to the given config file name.
func GetConfigFilePath(file string) (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
return path.Join(usr.HomeDir, configDir, file), nil
}

View File

@ -0,0 +1,95 @@
package frame
import (
"encoding/binary"
"fmt"
"sort"
)
// Represents a Common Information Entry in
// the Dwarf .debug_frame section.
type CommonInformationEntry struct {
Length uint32
CIE_id uint32
Version uint8
Augmentation string
CodeAlignmentFactor uint64
DataAlignmentFactor int64
ReturnAddressRegister uint64
InitialInstructions []byte
}
// Represents a Frame Descriptor Entry in the
// Dwarf .debug_frame section.
type FrameDescriptionEntry struct {
Length uint32
CIE *CommonInformationEntry
Instructions []byte
begin, end uint64
order binary.ByteOrder
}
// Returns whether or not the given address is within the
// bounds of this frame.
func (fde *FrameDescriptionEntry) Cover(addr uint64) bool {
if (addr - fde.begin) < fde.end {
return true
}
return false
}
// Address of first location for this frame.
func (fde *FrameDescriptionEntry) Begin() uint64 {
return fde.begin
}
// Address of last location for this frame.
func (fde *FrameDescriptionEntry) End() uint64 {
return fde.begin + fde.end
}
// Set up frame for the given PC.
func (fde *FrameDescriptionEntry) EstablishFrame(pc uint64) *FrameContext {
return executeDwarfProgramUntilPC(fde, pc)
}
// Return the offset from the current SP that the return address is stored at.
func (fde *FrameDescriptionEntry) ReturnAddressOffset(pc uint64) (frameOffset, returnAddressOffset int64) {
frame := fde.EstablishFrame(pc)
return frame.cfa.offset, frame.regs[fde.CIE.ReturnAddressRegister].offset
}
type FrameDescriptionEntries []*FrameDescriptionEntry
func NewFrameIndex() FrameDescriptionEntries {
return make(FrameDescriptionEntries, 0, 1000)
}
type NoFDEForPCError struct {
PC uint64
}
func (err *NoFDEForPCError) Error() string {
return fmt.Sprintf("could not find FDE for PC %#v", err.PC)
}
// Returns the Frame Description Entry for the given PC.
func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, error) {
idx := sort.Search(len(fdes), func(i int) bool {
if fdes[i].Cover(pc) {
return true
}
if fdes[i].LessThan(pc) {
return false
}
return true
})
if idx == len(fdes) {
return nil, &NoFDEForPCError{pc}
}
return fdes[idx], nil
}
func (frame *FrameDescriptionEntry) LessThan(pc uint64) bool {
return frame.End() <= pc
}

View File

@ -0,0 +1,164 @@
package frame
// Operation opcodes
const (
DW_OP_addr = 0x03
DW_OP_const1s = 0x09
)
const (
DW_OP_const2u = 0x0a
DW_OP_const2s = 0x0b
DW_OP_const4u = iota
DW_OP_const4s
DW_OP_const8u
DW_OP_const8s
DW_OP_constu
DW_OP_consts
DW_OP_dup
DW_OP_drop
DW_OP_over
DW_OP_pick
DW_OP_swap
DW_OP_rot
DW_OP_xderef
DW_OP_abs
DW_OP_and
DW_OP_div
DW_OP_minus
DW_OP_mod
DW_OP_mul
DW_OP_neg
DW_OP_not
DW_OP_or
DW_OP_plus
DW_OP_plus_uconst
DW_OP_shl
DW_OP_shr
DW_OP_shra
DW_OP_xor
DW_OP_skip
DW_OP_bra
DW_OP_eq
DW_OP_ge
DW_OP_gt
DW_OP_le
DW_OP_lt
DW_OP_ne
)
const (
DW_OP_lit0 = 0x30
DW_OP_lit1 = 0x31
DW_OP_lit2 = iota
DW_OP_lit3
DW_OP_lit4
DW_OP_lit5
DW_OP_lit6
DW_OP_lit7
DW_OP_lit8
DW_OP_lit9
DW_OP_lit10
DW_OP_lit11
DW_OP_lit12
DW_OP_lit13
DW_OP_lit14
DW_OP_lit15
DW_OP_lit16
DW_OP_lit17
DW_OP_lit18
DW_OP_lit19
DW_OP_lit20
DW_OP_lit21
DW_OP_lit22
DW_OP_lit23
DW_OP_lit24
DW_OP_lit25
DW_OP_lit26
DW_OP_lit27
DW_OP_lit28
DW_OP_lit29
DW_OP_lit30
DW_OP_lit31
DW_OP_reg0
DW_OP_reg1
DW_OP_reg2
DW_OP_reg3
DW_OP_reg4
DW_OP_reg5
DW_OP_reg6
DW_OP_reg7
DW_OP_reg8
DW_OP_reg9
DW_OP_reg10
DW_OP_reg11
DW_OP_reg12
DW_OP_reg13
DW_OP_reg14
DW_OP_reg15
DW_OP_reg16
DW_OP_reg17
DW_OP_reg18
DW_OP_reg19
DW_OP_reg20
DW_OP_reg21
DW_OP_reg22
DW_OP_reg23
DW_OP_reg24
DW_OP_reg25
DW_OP_reg26
DW_OP_reg27
DW_OP_reg28
DW_OP_reg29
DW_OP_reg30
DW_OP_reg31
DW_OP_breg0
DW_OP_breg1
DW_OP_breg2
DW_OP_breg3
DW_OP_breg4
DW_OP_breg5
DW_OP_breg6
DW_OP_breg7
DW_OP_breg8
DW_OP_breg9
DW_OP_breg10
DW_OP_breg11
DW_OP_breg12
DW_OP_breg13
DW_OP_breg14
DW_OP_breg15
DW_OP_breg16
DW_OP_breg17
DW_OP_breg18
DW_OP_breg19
DW_OP_breg20
DW_OP_breg21
DW_OP_breg22
DW_OP_breg23
DW_OP_breg24
DW_OP_breg25
DW_OP_breg26
DW_OP_breg27
DW_OP_breg28
DW_OP_breg29
DW_OP_breg30
DW_OP_breg31
DW_OP_regx
DW_OP_fbreg
DW_OP_bregx
DW_OP_piece
DW_OP_deref_size
DW_OP_xderef_size
DW_OP_nop
DW_OP_push_object_address
DW_OP_call2
DW_OP_call4
DW_OP_call_ref
DW_OP_form_tls_address
DW_OP_call_frame_cfa
DW_OP_bit_piece
DW_OP_lo_user = 0xe0
DW_OP_hi_user = 0xff
)

View File

@ -0,0 +1,125 @@
// Package frame contains data structures and
// related functions for parsing and searching
// through Dwarf .debug_frame data.
package frame
import (
"bytes"
"encoding/binary"
"github.com/derekparker/delve/dwarf/util"
)
type parsefunc func(*parseContext) parsefunc
type parseContext struct {
buf *bytes.Buffer
entries FrameDescriptionEntries
common *CommonInformationEntry
frame *FrameDescriptionEntry
length uint32
}
// Parse takes in data (a byte slice) and returns a slice of
// commonInformationEntry structures. Each commonInformationEntry
// has a slice of frameDescriptionEntry structures.
func Parse(data []byte, order binary.ByteOrder) FrameDescriptionEntries {
var (
buf = bytes.NewBuffer(data)
pctx = &parseContext{buf: buf, entries: NewFrameIndex()}
)
for fn := parselength; buf.Len() != 0; {
fn = fn(pctx)
}
for i := range pctx.entries {
pctx.entries[i].order = order
}
return pctx.entries
}
func cieEntry(data []byte) bool {
return bytes.Equal(data, []byte{0xff, 0xff, 0xff, 0xff})
}
func parselength(ctx *parseContext) parsefunc {
var data = ctx.buf.Next(8)
ctx.length = binary.LittleEndian.Uint32(data[:4]) - 4 // take off the length of the CIE id / CIE pointer.
if cieEntry(data[4:]) {
ctx.common = &CommonInformationEntry{Length: ctx.length}
return parseCIE
}
ctx.frame = &FrameDescriptionEntry{Length: ctx.length, CIE: ctx.common}
return parseFDE
}
func parseFDE(ctx *parseContext) parsefunc {
r := ctx.buf.Next(int(ctx.length))
ctx.frame.begin = binary.LittleEndian.Uint64(r[:8])
ctx.frame.end = binary.LittleEndian.Uint64(r[8:16])
// Insert into the tree after setting address range begin
// otherwise compares won't work.
ctx.entries = append(ctx.entries, ctx.frame)
// The rest of this entry consists of the instructions
// so we can just grab all of the data from the buffer
// cursor to length.
ctx.frame.Instructions = r[16:]
ctx.length = 0
return parselength
}
func parseCIE(ctx *parseContext) parsefunc {
data := ctx.buf.Next(int(ctx.length))
buf := bytes.NewBuffer(data)
// parse version
ctx.common.Version = data[0]
// parse augmentation
ctx.common.Augmentation, _ = util.ParseString(buf)
// parse code alignment factor
ctx.common.CodeAlignmentFactor, _ = util.DecodeULEB128(buf)
// parse data alignment factor
ctx.common.DataAlignmentFactor, _ = util.DecodeSLEB128(buf)
// parse return address register
ctx.common.ReturnAddressRegister, _ = util.DecodeULEB128(buf)
// parse initial instructions
// The rest of this entry consists of the instructions
// so we can just grab all of the data from the buffer
// cursor to length.
ctx.common.InitialInstructions = buf.Bytes() //ctx.buf.Next(int(ctx.length))
ctx.length = 0
return parselength
}
// DwarfEndian determines the endianness of the DWARF by using the version number field in the debug_info section
// Trick borrowed from "debug/dwarf".New()
func DwarfEndian(infoSec []byte) binary.ByteOrder {
if len(infoSec) < 6 {
return binary.BigEndian
}
x, y := infoSec[4], infoSec[5]
switch {
case x == 0 && y == 0:
return binary.BigEndian
case x == 0:
return binary.BigEndian
case y == 0:
return binary.LittleEndian
default:
return binary.BigEndian
}
}

View File

@ -0,0 +1,429 @@
package frame
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/derekparker/delve/dwarf/util"
)
type CurrentFrameAddress struct {
register uint64
offset int64
expression []byte
rule byte
}
type DWRule struct {
rule byte
offset int64
newreg uint64
expression []byte
}
type FrameContext struct {
loc uint64
order binary.ByteOrder
address uint64
cfa CurrentFrameAddress
regs map[uint64]DWRule
initialRegs map[uint64]DWRule
prevRegs map[uint64]DWRule
buf *bytes.Buffer
cie *CommonInformationEntry
codeAlignment uint64
dataAlignment int64
}
func (fctx *FrameContext) CFAOffset() int64 {
return fctx.cfa.offset
}
// Instructions used to recreate the table from the .debug_frame data.
const (
DW_CFA_nop = 0x0 // No ops
DW_CFA_set_loc = 0x01 // op1: address
DW_CFA_advance_loc1 = iota // op1: 1-bytes delta
DW_CFA_advance_loc2 // op1: 2-byte delta
DW_CFA_advance_loc4 // op1: 4-byte delta
DW_CFA_offset_extended // op1: ULEB128 register, op2: ULEB128 offset
DW_CFA_restore_extended // op1: ULEB128 register
DW_CFA_undefined // op1: ULEB128 register
DW_CFA_same_value // op1: ULEB128 register
DW_CFA_register // op1: ULEB128 register, op2: ULEB128 register
DW_CFA_remember_state // No ops
DW_CFA_restore_state // No ops
DW_CFA_def_cfa // op1: ULEB128 register, op2: ULEB128 offset
DW_CFA_def_cfa_register // op1: ULEB128 register
DW_CFA_def_cfa_offset // op1: ULEB128 offset
DW_CFA_def_cfa_expression // op1: BLOCK
DW_CFA_expression // op1: ULEB128 register, op2: BLOCK
DW_CFA_offset_extended_sf // op1: ULEB128 register, op2: SLEB128 BLOCK
DW_CFA_def_cfa_sf // op1: ULEB128 register, op2: SLEB128 offset
DW_CFA_def_cfa_offset_sf // op1: SLEB128 offset
DW_CFA_val_offset // op1: ULEB128, op2: ULEB128
DW_CFA_val_offset_sf // op1: ULEB128, op2: SLEB128
DW_CFA_val_expression // op1: ULEB128, op2: BLOCK
DW_CFA_lo_user = 0x1c // op1: BLOCK
DW_CFA_hi_user = 0x3f // op1: ULEB128 register, op2: BLOCK
DW_CFA_advance_loc = (0x1 << 6) // High 2 bits: 0x1, low 6: delta
DW_CFA_offset = (0x2 << 6) // High 2 bits: 0x2, low 6: register
DW_CFA_restore = (0x3 << 6) // High 2 bits: 0x3, low 6: register
)
// Rules defined for register values.
const (
rule_undefined = iota
rule_sameval
rule_offset
rule_valoffset
rule_register
rule_expression
rule_valexpression
rule_architectural
)
const low_6_offset = 0x3f
type instruction func(frame *FrameContext)
// // Mapping from DWARF opcode to function.
var fnlookup = map[byte]instruction{
DW_CFA_advance_loc: advanceloc,
DW_CFA_offset: offset,
DW_CFA_restore: restore,
DW_CFA_set_loc: setloc,
DW_CFA_advance_loc1: advanceloc1,
DW_CFA_advance_loc2: advanceloc2,
DW_CFA_advance_loc4: advanceloc4,
DW_CFA_offset_extended: offsetextended,
DW_CFA_restore_extended: restoreextended,
DW_CFA_undefined: undefined,
DW_CFA_same_value: samevalue,
DW_CFA_register: register,
DW_CFA_remember_state: rememberstate,
DW_CFA_restore_state: restorestate,
DW_CFA_def_cfa: defcfa,
DW_CFA_def_cfa_register: defcfaregister,
DW_CFA_def_cfa_offset: defcfaoffset,
DW_CFA_def_cfa_expression: defcfaexpression,
DW_CFA_expression: expression,
DW_CFA_offset_extended_sf: offsetextendedsf,
DW_CFA_def_cfa_sf: defcfasf,
DW_CFA_def_cfa_offset_sf: defcfaoffsetsf,
DW_CFA_val_offset: valoffset,
DW_CFA_val_offset_sf: valoffsetsf,
DW_CFA_val_expression: valexpression,
DW_CFA_lo_user: louser,
DW_CFA_hi_user: hiuser,
}
func executeCIEInstructions(cie *CommonInformationEntry) *FrameContext {
initialInstructions := make([]byte, len(cie.InitialInstructions))
copy(initialInstructions, cie.InitialInstructions)
frame := &FrameContext{
cie: cie,
regs: make(map[uint64]DWRule),
initialRegs: make(map[uint64]DWRule),
prevRegs: make(map[uint64]DWRule),
codeAlignment: cie.CodeAlignmentFactor,
dataAlignment: cie.DataAlignmentFactor,
buf: bytes.NewBuffer(initialInstructions),
}
frame.ExecuteDwarfProgram()
return frame
}
// Unwind the stack to find the return address register.
func executeDwarfProgramUntilPC(fde *FrameDescriptionEntry, pc uint64) *FrameContext {
frame := executeCIEInstructions(fde.CIE)
frame.order = fde.order
frame.loc = fde.Begin()
frame.address = pc
fdeInstructions := make([]byte, len(fde.Instructions))
copy(fdeInstructions, fde.Instructions)
frame.ExecuteUntilPC(fdeInstructions)
return frame
}
func (frame *FrameContext) ExecuteDwarfProgram() {
for frame.buf.Len() > 0 {
executeDwarfInstruction(frame)
}
}
// Execute dwarf instructions.
func (frame *FrameContext) ExecuteUntilPC(instructions []byte) {
frame.buf.Truncate(0)
frame.buf.Write(instructions)
// We only need to execute the instructions until
// ctx.loc > ctx.addess (which is the address we
// are currently at in the traced process).
for frame.address >= frame.loc && frame.buf.Len() > 0 {
executeDwarfInstruction(frame)
}
}
func executeDwarfInstruction(frame *FrameContext) {
instruction, err := frame.buf.ReadByte()
if err != nil {
panic("Could not read from instruction buffer")
}
if instruction == DW_CFA_nop {
return
}
fn := lookupFunc(instruction, frame.buf)
fn(frame)
}
func lookupFunc(instruction byte, buf *bytes.Buffer) instruction {
const high_2_bits = 0xc0
var restore bool
// Special case the 3 opcodes that have their argument encoded in the opcode itself.
switch instruction & high_2_bits {
case DW_CFA_advance_loc:
instruction = DW_CFA_advance_loc
restore = true
case DW_CFA_offset:
instruction = DW_CFA_offset
restore = true
case DW_CFA_restore:
instruction = DW_CFA_restore
restore = true
}
if restore {
// Restore the last byte as it actually contains the argument for the opcode.
err := buf.UnreadByte()
if err != nil {
panic("Could not unread byte")
}
}
fn, ok := fnlookup[instruction]
if !ok {
panic(fmt.Sprintf("Encountered an unexpected DWARF CFA opcode: %#v", instruction))
}
return fn
}
func advanceloc(frame *FrameContext) {
b, err := frame.buf.ReadByte()
if err != nil {
panic("Could not read byte")
}
delta := b & low_6_offset
frame.loc += uint64(delta) * frame.codeAlignment
}
func advanceloc1(frame *FrameContext) {
delta, err := frame.buf.ReadByte()
if err != nil {
panic("Could not read byte")
}
frame.loc += uint64(delta) * frame.codeAlignment
}
func advanceloc2(frame *FrameContext) {
var delta uint16
binary.Read(frame.buf, frame.order, &delta)
frame.loc += uint64(delta) * frame.codeAlignment
}
func advanceloc4(frame *FrameContext) {
var delta uint32
binary.Read(frame.buf, frame.order, &delta)
frame.loc += uint64(delta) * frame.codeAlignment
}
func offset(frame *FrameContext) {
b, err := frame.buf.ReadByte()
if err != nil {
panic(err)
}
var (
reg = b & low_6_offset
offset, _ = util.DecodeULEB128(frame.buf)
)
frame.regs[uint64(reg)] = DWRule{offset: int64(offset) * frame.dataAlignment, rule: rule_offset}
}
func restore(frame *FrameContext) {
b, err := frame.buf.ReadByte()
if err != nil {
panic(err)
}
reg := uint64(b & low_6_offset)
oldrule, ok := frame.initialRegs[reg]
if ok {
frame.regs[reg] = DWRule{offset: oldrule.offset, rule: rule_offset}
} else {
frame.regs[reg] = DWRule{rule: rule_undefined}
}
}
func setloc(frame *FrameContext) {
var loc uint64
binary.Read(frame.buf, frame.order, &loc)
frame.loc = loc
}
func offsetextended(frame *FrameContext) {
var (
reg, _ = util.DecodeULEB128(frame.buf)
offset, _ = util.DecodeULEB128(frame.buf)
)
frame.regs[reg] = DWRule{offset: int64(offset) * frame.dataAlignment, rule: rule_offset}
}
func undefined(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf)
frame.regs[reg] = DWRule{rule: rule_undefined}
}
func samevalue(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf)
frame.regs[reg] = DWRule{rule: rule_sameval}
}
func register(frame *FrameContext) {
reg1, _ := util.DecodeULEB128(frame.buf)
reg2, _ := util.DecodeULEB128(frame.buf)
frame.regs[reg1] = DWRule{newreg: reg2, rule: rule_register}
}
func rememberstate(frame *FrameContext) {
frame.prevRegs = frame.regs
}
func restorestate(frame *FrameContext) {
frame.regs = frame.prevRegs
}
func restoreextended(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf)
oldrule, ok := frame.initialRegs[reg]
if ok {
frame.regs[reg] = DWRule{offset: oldrule.offset, rule: rule_offset}
} else {
frame.regs[reg] = DWRule{rule: rule_undefined}
}
}
func defcfa(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf)
offset, _ := util.DecodeULEB128(frame.buf)
frame.cfa.register = reg
frame.cfa.offset = int64(offset)
}
func defcfaregister(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf)
frame.cfa.register = reg
}
func defcfaoffset(frame *FrameContext) {
offset, _ := util.DecodeULEB128(frame.buf)
frame.cfa.offset = int64(offset)
}
func defcfasf(frame *FrameContext) {
reg, _ := util.DecodeULEB128(frame.buf)
offset, _ := util.DecodeSLEB128(frame.buf)
frame.cfa.register = reg
frame.cfa.offset = offset * frame.dataAlignment
}
func defcfaoffsetsf(frame *FrameContext) {
offset, _ := util.DecodeSLEB128(frame.buf)
offset *= frame.dataAlignment
frame.cfa.offset = offset
}
func defcfaexpression(frame *FrameContext) {
var (
l, _ = util.DecodeULEB128(frame.buf)
expr = frame.buf.Next(int(l))
)
frame.cfa.expression = expr
frame.cfa.rule = rule_expression
}
func expression(frame *FrameContext) {
var (
reg, _ = util.DecodeULEB128(frame.buf)
l, _ = util.DecodeULEB128(frame.buf)
expr = frame.buf.Next(int(l))
)
frame.regs[reg] = DWRule{rule: rule_expression, expression: expr}
}
func offsetextendedsf(frame *FrameContext) {
var (
reg, _ = util.DecodeULEB128(frame.buf)
offset, _ = util.DecodeSLEB128(frame.buf)
)
frame.regs[reg] = DWRule{offset: offset * frame.dataAlignment, rule: rule_offset}
}
func valoffset(frame *FrameContext) {
var (
reg, _ = util.DecodeULEB128(frame.buf)
offset, _ = util.DecodeULEB128(frame.buf)
)
frame.regs[reg] = DWRule{offset: int64(offset), rule: rule_valoffset}
}
func valoffsetsf(frame *FrameContext) {
var (
reg, _ = util.DecodeULEB128(frame.buf)
offset, _ = util.DecodeSLEB128(frame.buf)
)
frame.regs[reg] = DWRule{offset: offset * frame.dataAlignment, rule: rule_valoffset}
}
func valexpression(frame *FrameContext) {
var (
reg, _ = util.DecodeULEB128(frame.buf)
l, _ = util.DecodeULEB128(frame.buf)
expr = frame.buf.Next(int(l))
)
frame.regs[reg] = DWRule{rule: rule_valexpression, expression: expr}
}
func louser(frame *FrameContext) {
frame.buf.Next(1)
}
func hiuser(frame *FrameContext) {
frame.buf.Next(1)
}

View File

@ -0,0 +1,122 @@
package line
import (
"bytes"
"encoding/binary"
"github.com/derekparker/delve/dwarf/util"
)
type DebugLinePrologue struct {
UnitLength uint32
Version uint16
Length uint32
MinInstrLength uint8
InitialIsStmt uint8
LineBase int8
LineRange uint8
OpcodeBase uint8
StdOpLengths []uint8
}
type DebugLineInfo struct {
Prologue *DebugLinePrologue
IncludeDirs []string
FileNames []*FileEntry
Instructions []byte
Lookup map[string]*FileEntry
}
type FileEntry struct {
Name string
DirIdx uint64
LastModTime uint64
Length uint64
}
type DebugLines []*DebugLineInfo
func (d *DebugLines) GetLineInfo(name string) *DebugLineInfo {
// Find in which table file exists and return it.
for _, l := range *d {
if _, ok := l.Lookup[name]; ok {
return l
}
}
return nil
}
func Parse(data []byte) DebugLines {
var (
lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data)
)
// We have to parse multiple file name tables here.
for buf.Len() > 0 {
dbl := new(DebugLineInfo)
dbl.Lookup = make(map[string]*FileEntry)
parseDebugLinePrologue(dbl, buf)
parseIncludeDirs(dbl, buf)
parseFileEntries(dbl, buf)
// Instructions size calculation breakdown:
// - dbl.Prologue.UnitLength is the length of the entire unit, not including the 4 bytes to represent that length.
// - dbl.Prologue.Length is the length of the prologue not including unit length, version or prologue length itself.
// - So you have UnitLength - PrologueLength - (version_length_bytes(2) + prologue_length_bytes(4)).
dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - 6))
lines = append(lines, dbl)
}
return lines
}
func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
p := new(DebugLinePrologue)
p.UnitLength = binary.LittleEndian.Uint32(buf.Next(4))
p.Version = binary.LittleEndian.Uint16(buf.Next(2))
p.Length = binary.LittleEndian.Uint32(buf.Next(4))
p.MinInstrLength = uint8(buf.Next(1)[0])
p.InitialIsStmt = uint8(buf.Next(1)[0])
p.LineBase = int8(buf.Next(1)[0])
p.LineRange = uint8(buf.Next(1)[0])
p.OpcodeBase = uint8(buf.Next(1)[0])
p.StdOpLengths = make([]uint8, p.OpcodeBase-1)
binary.Read(buf, binary.LittleEndian, &p.StdOpLengths)
dbl.Prologue = p
}
func parseIncludeDirs(info *DebugLineInfo, buf *bytes.Buffer) {
for {
str, _ := util.ParseString(buf)
if str == "" {
break
}
info.IncludeDirs = append(info.IncludeDirs, str)
}
}
func parseFileEntries(info *DebugLineInfo, buf *bytes.Buffer) {
for {
entry := new(FileEntry)
name, _ := util.ParseString(buf)
if name == "" {
break
}
entry.Name = name
entry.DirIdx, _ = util.DecodeULEB128(buf)
entry.LastModTime, _ = util.DecodeULEB128(buf)
entry.Length, _ = util.DecodeULEB128(buf)
info.FileNames = append(info.FileNames, entry)
info.Lookup[name] = entry
}
}

View File

@ -0,0 +1,252 @@
package line
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/derekparker/delve/dwarf/util"
)
type Location struct {
File string
Line int
Address uint64
Delta int
}
type StateMachine struct {
dbl *DebugLineInfo
file string
line int
address uint64
column uint
isStmt bool
basicBlock bool
endSeq bool
lastWasStandard bool
lastDelta int
}
type opcodefn func(*StateMachine, *bytes.Buffer)
// Special opcodes
const (
DW_LNS_copy = 1
DW_LNS_advance_pc = 2
DW_LNS_advance_line = 3
DW_LNS_set_file = 4
DW_LNS_set_column = 5
DW_LNS_negate_stmt = 6
DW_LNS_set_basic_block = 7
DW_LNS_const_add_pc = 8
DW_LNS_fixed_advance_pc = 9
)
// Extended opcodes
const (
DW_LINE_end_sequence = 1
DW_LINE_set_address = 2
DW_LINE_define_file = 3
)
var standardopcodes = map[byte]opcodefn{
DW_LNS_copy: copyfn,
DW_LNS_advance_pc: advancepc,
DW_LNS_advance_line: advanceline,
DW_LNS_set_file: setfile,
DW_LNS_set_column: setcolumn,
DW_LNS_negate_stmt: negatestmt,
DW_LNS_set_basic_block: setbasicblock,
DW_LNS_const_add_pc: constaddpc,
DW_LNS_fixed_advance_pc: fixedadvancepc,
}
var extendedopcodes = map[byte]opcodefn{
DW_LINE_end_sequence: endsequence,
DW_LINE_set_address: setaddress,
DW_LINE_define_file: definefile,
}
func newStateMachine(dbl *DebugLineInfo) *StateMachine {
return &StateMachine{dbl: dbl, file: dbl.FileNames[0].Name, line: 1}
}
// Returns all PCs for a given file/line. Useful for loops where the 'for' line
// could be split amongst 2 PCs.
func (dbl *DebugLines) AllPCsForFileLine(f string, l int) (pcs []uint64) {
var (
foundFile bool
lastAddr uint64
lineInfo = dbl.GetLineInfo(f)
sm = newStateMachine(lineInfo)
buf = bytes.NewBuffer(lineInfo.Instructions)
)
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
findAndExecOpcode(sm, buf, b)
if foundFile && sm.file != f {
return
}
if sm.line == l && sm.file == f && sm.address != lastAddr {
foundFile = true
pcs = append(pcs, sm.address)
line := sm.line
// Keep going until we're on a different line. We only care about
// when a line comes back around (i.e. for loop) so get to next line,
// and try to find the line we care about again.
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
findAndExecOpcode(sm, buf, b)
if line < sm.line {
break
}
}
}
}
return
}
var NoSourceError = errors.New("no source available")
func (dbl *DebugLines) AllPCsBetween(begin, end uint64, filename string) ([]uint64, error) {
lineInfo := dbl.GetLineInfo(filename)
if lineInfo == nil {
return nil, NoSourceError
}
var (
pcs []uint64
lastaddr uint64
sm = newStateMachine(lineInfo)
buf = bytes.NewBuffer(lineInfo.Instructions)
)
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
findAndExecOpcode(sm, buf, b)
if sm.address > end {
break
}
if sm.address >= begin && sm.address > lastaddr {
lastaddr = sm.address
pcs = append(pcs, sm.address)
}
}
return pcs, nil
}
func findAndExecOpcode(sm *StateMachine, buf *bytes.Buffer, b byte) {
switch {
case b == 0:
execExtendedOpcode(sm, b, buf)
case b < sm.dbl.Prologue.OpcodeBase:
execStandardOpcode(sm, b, buf)
default:
execSpecialOpcode(sm, b)
}
}
func execSpecialOpcode(sm *StateMachine, instr byte) {
var (
opcode = uint8(instr)
decoded = opcode - sm.dbl.Prologue.OpcodeBase
)
if sm.dbl.Prologue.InitialIsStmt == uint8(1) {
sm.isStmt = true
}
sm.lastDelta = int(sm.dbl.Prologue.LineBase + int8(decoded%sm.dbl.Prologue.LineRange))
sm.line += sm.lastDelta
sm.address += uint64(decoded / sm.dbl.Prologue.LineRange)
sm.basicBlock = false
sm.lastWasStandard = false
}
func execExtendedOpcode(sm *StateMachine, instr byte, buf *bytes.Buffer) {
_, _ = util.DecodeULEB128(buf)
b, _ := buf.ReadByte()
fn, ok := extendedopcodes[b]
if !ok {
panic(fmt.Sprintf("Encountered unknown extended opcode %#v\n", b))
}
sm.lastWasStandard = false
fn(sm, buf)
}
func execStandardOpcode(sm *StateMachine, instr byte, buf *bytes.Buffer) {
fn, ok := standardopcodes[instr]
if !ok {
panic(fmt.Sprintf("Encountered unknown standard opcode %#v\n", instr))
}
sm.lastWasStandard = true
fn(sm, buf)
}
func copyfn(sm *StateMachine, buf *bytes.Buffer) {
sm.basicBlock = false
}
func advancepc(sm *StateMachine, buf *bytes.Buffer) {
addr, _ := util.DecodeULEB128(buf)
sm.address += addr * uint64(sm.dbl.Prologue.MinInstrLength)
}
func advanceline(sm *StateMachine, buf *bytes.Buffer) {
line, _ := util.DecodeSLEB128(buf)
sm.line += int(line)
sm.lastDelta = int(line)
}
func setfile(sm *StateMachine, buf *bytes.Buffer) {
i, _ := util.DecodeULEB128(buf)
sm.file = sm.dbl.FileNames[i-1].Name
}
func setcolumn(sm *StateMachine, buf *bytes.Buffer) {
c, _ := util.DecodeULEB128(buf)
sm.column = uint(c)
}
func negatestmt(sm *StateMachine, buf *bytes.Buffer) {
sm.isStmt = !sm.isStmt
}
func setbasicblock(sm *StateMachine, buf *bytes.Buffer) {
sm.basicBlock = true
}
func constaddpc(sm *StateMachine, buf *bytes.Buffer) {
sm.address += (255 / uint64(sm.dbl.Prologue.LineRange))
}
func fixedadvancepc(sm *StateMachine, buf *bytes.Buffer) {
var operand uint16
binary.Read(buf, binary.LittleEndian, &operand)
sm.address += uint64(operand)
}
func endsequence(sm *StateMachine, buf *bytes.Buffer) {
sm.endSeq = true
}
func setaddress(sm *StateMachine, buf *bytes.Buffer) {
var addr uint64
binary.Read(buf, binary.LittleEndian, &addr)
sm.address = addr
}
func definefile(sm *StateMachine, buf *bytes.Buffer) {
var (
_, _ = util.ParseString(buf)
_, _ = util.DecodeULEB128(buf)
_, _ = util.DecodeULEB128(buf)
_, _ = util.DecodeULEB128(buf)
)
// Don't do anything here yet.
}

84
vendor/github.com/derekparker/delve/dwarf/op/op.go generated vendored Normal file
View File

@ -0,0 +1,84 @@
package op
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/derekparker/delve/dwarf/util"
)
const (
DW_OP_addr = 0x3
DW_OP_call_frame_cfa = 0x9c
DW_OP_plus = 0x22
DW_OP_consts = 0x11
DW_OP_plus_uconsts = 0x23
)
type stackfn func(*bytes.Buffer, []int64, int64) ([]int64, error)
var oplut = map[byte]stackfn{
DW_OP_call_frame_cfa: callframecfa,
DW_OP_plus: plus,
DW_OP_consts: consts,
DW_OP_addr: addr,
DW_OP_plus_uconsts: plusuconsts,
}
func ExecuteStackProgram(cfa int64, instructions []byte) (int64, error) {
stack := make([]int64, 0, 3)
buf := bytes.NewBuffer(instructions)
for opcode, err := buf.ReadByte(); err == nil; opcode, err = buf.ReadByte() {
fn, ok := oplut[opcode]
if !ok {
return 0, fmt.Errorf("invalid instruction %#v", opcode)
}
stack, err = fn(buf, stack, cfa)
if err != nil {
return 0, err
}
}
if len(stack) == 0 {
return 0, errors.New("empty OP stack")
}
return stack[len(stack)-1], nil
}
func callframecfa(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
if cfa == 0 {
return stack, fmt.Errorf("Could not retrieve CFA for current PC")
}
return append(stack, int64(cfa)), nil
}
func addr(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
return append(stack, int64(binary.LittleEndian.Uint64(buf.Next(8)))), nil
}
func plus(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
var (
slen = len(stack)
digits = stack[slen-2 : slen]
st = stack[:slen-2]
)
return append(st, digits[0]+digits[1]), nil
}
func plusuconsts(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
slen := len(stack)
num, _ := util.DecodeULEB128(buf)
stack[slen-1] = stack[slen-1] + int64(num)
return stack, nil
}
func consts(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
num, _ := util.DecodeSLEB128(buf)
return append(stack, num), nil
}

345
vendor/github.com/derekparker/delve/dwarf/reader/reader.go generated vendored Executable file
View File

@ -0,0 +1,345 @@
package reader
import (
"errors"
"fmt"
"golang.org/x/debug/dwarf"
"github.com/derekparker/delve/dwarf/op"
)
type Reader struct {
*dwarf.Reader
depth int
}
// New returns a reader for the specified dwarf data.
func New(data *dwarf.Data) *Reader {
return &Reader{data.Reader(), 0}
}
// Seek moves the reader to an arbitrary offset.
func (reader *Reader) Seek(off dwarf.Offset) {
reader.depth = 0
reader.Reader.Seek(off)
}
// SeekToEntry moves the reader to an arbitrary entry.
func (reader *Reader) SeekToEntry(entry *dwarf.Entry) error {
reader.Seek(entry.Offset)
// Consume the current entry so .Next works as intended
_, err := reader.Next()
return err
}
// SeekToFunctionEntry moves the reader to the function that includes the
// specified program counter.
func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) {
reader.Seek(0)
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
if entry.Tag != dwarf.TagSubprogram {
continue
}
lowpc, ok := entry.Val(dwarf.AttrLowpc).(uint64)
if !ok {
continue
}
highpc, ok := entry.Val(dwarf.AttrHighpc).(uint64)
if !ok {
continue
}
if lowpc <= pc && highpc > pc {
return entry, nil
}
}
return nil, fmt.Errorf("unable to find function context")
}
// Returns the address for the named entry.
func (reader *Reader) AddrFor(name string) (uint64, error) {
entry, err := reader.FindEntryNamed(name, false)
if err != nil {
return 0, err
}
instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
if !ok {
return 0, fmt.Errorf("type assertion failed")
}
addr, err := op.ExecuteStackProgram(0, instructions)
if err != nil {
return 0, err
}
return uint64(addr), nil
}
// Returns the address for the named struct member. Expects the reader to be at the parent entry
// or one of the parents children, thus does not seek to parent by itself.
func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (uint64, error) {
for {
entry, err := reader.NextMemberVariable()
if err != nil {
return 0, err
}
if entry == nil {
return 0, fmt.Errorf("nil entry for member named %s", member)
}
name, ok := entry.Val(dwarf.AttrName).(string)
if !ok || name != member {
continue
}
instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte)
if !ok {
continue
}
addr, err := op.ExecuteStackProgram(0, append(initialInstructions, instructions...))
return uint64(addr), err
}
}
var TypeNotFoundErr = errors.New("no type entry found, use 'types' for a list of valid types")
// SeekToType moves the reader to the type specified by the entry,
// optionally resolving typedefs and pointer types. If the reader is set
// to a struct type the NextMemberVariable call can be used to walk all member data.
func (reader *Reader) SeekToType(entry *dwarf.Entry, resolveTypedefs bool, resolvePointerTypes bool) (*dwarf.Entry, error) {
offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
if !ok {
return nil, fmt.Errorf("entry does not have a type attribute")
}
// Seek to the first type offset
reader.Seek(offset)
// Walk the types to the base
for typeEntry, err := reader.Next(); typeEntry != nil; typeEntry, err = reader.Next() {
if err != nil {
return nil, err
}
if typeEntry.Tag == dwarf.TagTypedef && !resolveTypedefs {
return typeEntry, nil
}
if typeEntry.Tag == dwarf.TagPointerType && !resolvePointerTypes {
return typeEntry, nil
}
offset, ok = typeEntry.Val(dwarf.AttrType).(dwarf.Offset)
if !ok {
return typeEntry, nil
}
reader.Seek(offset)
}
return nil, TypeNotFoundErr
}
func (reader *Reader) NextType() (*dwarf.Entry, error) {
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
switch entry.Tag {
case dwarf.TagArrayType, dwarf.TagBaseType, dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType, dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType, dwarf.TagEnumerationType, dwarf.TagPointerType, dwarf.TagSubroutineType, dwarf.TagTypedef, dwarf.TagUnspecifiedType:
return entry, nil
}
}
return nil, nil
}
// SeekToTypeNamed moves the reader to the type specified by the name.
// If the reader is set to a struct type the NextMemberVariable call
// can be used to walk all member data.
func (reader *Reader) SeekToTypeNamed(name string) (*dwarf.Entry, error) {
// Walk the types to the base
for entry, err := reader.NextType(); entry != nil; entry, err = reader.NextType() {
if err != nil {
return nil, err
}
n, ok := entry.Val(dwarf.AttrName).(string)
if !ok {
continue
}
if n == name {
return entry, nil
}
}
return nil, TypeNotFoundErr
}
// Finds the entry for 'name'.
func (reader *Reader) FindEntryNamed(name string, member bool) (*dwarf.Entry, error) {
depth := 1
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
if entry.Children {
depth++
}
if entry.Tag == 0 {
depth--
if depth <= 0 {
return nil, fmt.Errorf("could not find symbol value for %s", name)
}
}
if member {
if entry.Tag != dwarf.TagMember {
continue
}
} else {
if entry.Tag != dwarf.TagVariable && entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagStructType {
continue
}
}
n, ok := entry.Val(dwarf.AttrName).(string)
if !ok || n != name {
continue
}
return entry, nil
}
return nil, fmt.Errorf("could not find symbol value for %s", name)
}
func (reader *Reader) InstructionsForEntryNamed(name string, member bool) ([]byte, error) {
entry, err := reader.FindEntryNamed(name, member)
if err != nil {
return nil, err
}
var attr dwarf.Attr
if member {
attr = dwarf.AttrDataMemberLoc
} else {
attr = dwarf.AttrLocation
}
instr, ok := entry.Val(attr).([]byte)
if !ok {
return nil, errors.New("invalid typecast for Dwarf instructions")
}
return instr, nil
}
func (reader *Reader) InstructionsForEntry(entry *dwarf.Entry) ([]byte, error) {
if entry.Tag == dwarf.TagMember {
instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte)
if !ok {
return nil, fmt.Errorf("member data has no data member location attribute")
}
// clone slice to prevent stomping on the dwarf data
return append([]byte{}, instructions...), nil
}
// non-member
instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
if !ok {
return nil, fmt.Errorf("entry has no location attribute")
}
// clone slice to prevent stomping on the dwarf data
return append([]byte{}, instructions...), nil
}
// NextScopeVariable moves the reader to the next debug entry that describes a local variable and returns the entry.
func (reader *Reader) NextScopeVariable() (*dwarf.Entry, error) {
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
// All scope variables will be at the same depth
reader.SkipChildren()
// End of the current depth
if entry.Tag == 0 {
break
}
if entry.Tag == dwarf.TagVariable || entry.Tag == dwarf.TagFormalParameter {
return entry, nil
}
}
// No more items
return nil, nil
}
// NextMememberVariable moves the reader to the next debug entry that describes a member variable and returns the entry.
func (reader *Reader) NextMemberVariable() (*dwarf.Entry, error) {
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
// All member variables will be at the same depth
reader.SkipChildren()
// End of the current depth
if entry.Tag == 0 {
break
}
if entry.Tag == dwarf.TagMember {
return entry, nil
}
}
// No more items
return nil, nil
}
// NextPackageVariable moves the reader to the next debug entry that describes a package variable.
// Any TagVariable entry that is not inside a sub prgram entry and is marked external is considered a package variable.
func (reader *Reader) NextPackageVariable() (*dwarf.Entry, error) {
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
if entry.Tag == dwarf.TagVariable {
ext, ok := entry.Val(dwarf.AttrExternal).(bool)
if ok && ext {
return entry, nil
}
}
// Ignore everything inside sub programs
if entry.Tag == dwarf.TagSubprogram {
reader.SkipChildren()
}
}
// No more items
return nil, nil
}
func (reader *Reader) NextCompileUnit() (*dwarf.Entry, error) {
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
if entry.Tag == dwarf.TagCompileUnit {
return entry, nil
}
}
return nil, nil
}

81
vendor/github.com/derekparker/delve/dwarf/util/util.go generated vendored Normal file
View File

@ -0,0 +1,81 @@
package util
import "bytes"
// DecodeULEB128 decodes an unsigned Little Endian Base 128
// represented number.
func DecodeULEB128(buf *bytes.Buffer) (uint64, uint32) {
var (
result uint64
shift uint64
length uint32
)
if buf.Len() == 0 {
return 0, 0
}
for {
b, err := buf.ReadByte()
if err != nil {
panic("Could not parse ULEB128 value")
}
length++
result |= uint64((uint(b) & 0x7f) << shift)
// If high order bit is 1.
if b&0x80 == 0 {
break
}
shift += 7
}
return result, length
}
// DecodeSLEB128 decodes a signed Little Endian Base 128
// represented number.
func DecodeSLEB128(buf *bytes.Buffer) (int64, uint32) {
var (
b byte
err error
result int64
shift uint64
length uint32
)
if buf.Len() == 0 {
return 0, 0
}
for {
b, err = buf.ReadByte()
if err != nil {
panic("Could not parse SLEB128 value")
}
length++
result |= int64((int64(b) & 0x7f) << shift)
shift += 7
if b&0x80 == 0 {
break
}
}
if (shift < 8*uint64(length)) && (b&0x40 > 0) {
result |= -(1 << shift)
}
return result, length
}
func ParseString(data *bytes.Buffer) (string, uint32) {
str, err := data.ReadString(0x0)
if err != nil {
panic("Could not parse string")
}
return str[:len(str)-1], uint32(len(str))
}

79
vendor/github.com/derekparker/delve/proc/arch.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
package proc
import "runtime"
// Arch defines an interface for representing a
// CPU architecture.
type Arch interface {
SetGStructOffset(ver GoVersion, iscgo bool)
PtrSize() int
BreakpointInstruction() []byte
BreakpointSize() int
GStructOffset() uint64
}
// AMD64 represents the AMD64 CPU architecture.
type AMD64 struct {
ptrSize int
breakInstruction []byte
breakInstructionLen int
gStructOffset uint64
hardwareBreakpointUsage []bool
}
// AMD64Arch returns an initialized AMD64
// struct.
func AMD64Arch() *AMD64 {
var breakInstr = []byte{0xCC}
return &AMD64{
ptrSize: 8,
breakInstruction: breakInstr,
breakInstructionLen: len(breakInstr),
hardwareBreakpointUsage: make([]bool, 4),
}
}
// SetGStructOffset sets the offset of the G struct on the AMD64
// arch struct. The offset is dependent on the Go compiler Version
// and whether or not the target program was externally linked.
func (a *AMD64) SetGStructOffset(ver GoVersion, isextld bool) {
switch runtime.GOOS {
case "darwin":
a.gStructOffset = 0x8a0
case "linux":
a.gStructOffset = 0xfffffffffffffff0
if isextld || ver.AfterOrEqual(GoVersion{1, 5, -1, 2, 0}) || ver.IsDevel() {
a.gStructOffset += 8
}
case "windows":
// Use ArbitraryUserPointer (0x28) as pointer to pointer
// to G struct per:
// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c
a.gStructOffset = 0x28
}
}
// PtrSize returns the size of a pointer
// on this architecture.
func (a *AMD64) PtrSize() int {
return a.ptrSize
}
// BreakpointInstruction returns the Breakpoint
// instruction for this architecture.
func (a *AMD64) BreakpointInstruction() []byte {
return a.breakInstruction
}
// BreakpointSize returns the size of the
// breakpoint instruction on this architecture.
func (a *AMD64) BreakpointSize() int {
return a.breakInstructionLen
}
// GStructOffset returns the offset of the G
// struct in thread local storage.
func (a *AMD64) GStructOffset() uint64 {
return a.gStructOffset
}

163
vendor/github.com/derekparker/delve/proc/breakpoints.go generated vendored Normal file
View File

@ -0,0 +1,163 @@
package proc
import (
"errors"
"fmt"
"go/ast"
"go/constant"
"reflect"
)
// Breakpoint represents a breakpoint. Stores information on the break
// point including the byte of data that originally was stored at that
// address.
type Breakpoint struct {
// File & line information for printing.
FunctionName string
File string
Line int
Addr uint64 // Address breakpoint is set for.
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
Name string // User defined name of the breakpoint
ID int // Monotonically increasing ID.
Kind BreakpointKind // Whether this is an internal breakpoint (for next'ing or stepping).
// Breakpoint information
Tracepoint bool // Tracepoint flag
Goroutine bool // Retrieve goroutine information
Stacktrace int // Number of stack frames to retrieve
Variables []string // Variables to evaluate
LoadArgs *LoadConfig
LoadLocals *LoadConfig
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
TotalHitCount uint64 // Number of times a breakpoint has been reached
// DeferReturns: when kind == NextDeferBreakpoint this breakpoint
// will also check if the caller is runtime.gopanic or if the return
// address is in the DeferReturns array.
// Next uses NextDeferBreakpoints for the breakpoint it sets on the
// deferred function, DeferReturns is populated with the
// addresses of calls to runtime.deferreturn in the current
// function. This insures that the breakpoint on the deferred
// function only triggers on panic or on the defer call to
// the function, not when the function is called directly
DeferReturns []uint64
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr
}
// Breakpoint Kind determines the behavior of delve when the
// breakpoint is reached.
type BreakpointKind int
const (
// UserBreakpoint is a user set breakpoint
UserBreakpoint BreakpointKind = iota
// NextBreakpoint is a breakpoint set by Next, Continue
// will stop on it and delete it
NextBreakpoint
// NextDeferBreakpoint is a breakpoint set by Next on the
// first deferred function. In addition to checking their condition
// breakpoints of this kind will also check that the function has been
// called by runtime.gopanic or through runtime.deferreturn.
NextDeferBreakpoint
// StepBreakpoint is a breakpoint set by Step on a CALL instruction,
// Continue will set a new breakpoint (of NextBreakpoint kind) on the
// destination of CALL, delete this breakpoint and then continue again
StepBreakpoint
)
func (bp *Breakpoint) String() string {
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.ID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
}
// Clear this breakpoint appropriately depending on whether it is a
// hardware or software breakpoint.
func (bp *Breakpoint) Clear(thread *Thread) (*Breakpoint, error) {
if _, err := thread.writeMemory(uintptr(bp.Addr), bp.OriginalData); err != nil {
return nil, fmt.Errorf("could not clear breakpoint %s", err)
}
return bp, nil
}
// BreakpointExistsError is returned when trying to set a breakpoint at
// an address that already has a breakpoint set for it.
type BreakpointExistsError struct {
file string
line int
addr uint64
}
func (bpe BreakpointExistsError) Error() string {
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
}
// InvalidAddressError represents the result of
// attempting to set a breakpoint at an invalid address.
type InvalidAddressError struct {
address uint64
}
func (iae InvalidAddressError) Error() string {
return fmt.Sprintf("Invalid address %#v\n", iae.address)
}
func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error {
_, err := thread.writeMemory(uintptr(addr), dbp.arch.BreakpointInstruction())
return err
}
func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) {
if bp.Cond == nil {
return true, nil
}
if bp.Kind == NextDeferBreakpoint {
frames, err := thread.Stacktrace(2)
if err == nil {
ispanic := len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic"
isdeferreturn := false
if len(frames) >= 1 {
for _, pc := range bp.DeferReturns {
if frames[0].Ret == pc {
isdeferreturn = true
break
}
}
}
if !ispanic && !isdeferreturn {
return false, nil
}
}
}
scope, err := thread.Scope()
if err != nil {
return true, err
}
v, err := scope.evalAST(bp.Cond)
if err != nil {
return true, fmt.Errorf("error evaluating expression: %v", err)
}
if v.Unreadable != nil {
return true, fmt.Errorf("condition expression unreadable: %v", v.Unreadable)
}
if v.Kind != reflect.Bool {
return true, errors.New("condition expression not boolean")
}
return constant.BoolVal(v.Value), nil
}
// Internal returns true for breakpoints not set directly by the user.
func (bp *Breakpoint) Internal() bool {
return bp.Kind != UserBreakpoint
}
// NoBreakpointError is returned when trying to
// clear a breakpoint that does not exist.
type NoBreakpointError struct {
addr uint64
}
func (nbp NoBreakpointError) Error() string {
return fmt.Sprintf("no breakpoint at %#v", nbp.addr)
}

67
vendor/github.com/derekparker/delve/proc/disasm.go generated vendored Normal file
View File

@ -0,0 +1,67 @@
package proc
type AsmInstruction struct {
Loc Location
DestLoc *Location
Bytes []byte
Breakpoint bool
AtPC bool
Inst *ArchInst
}
type AssemblyFlavour int
const (
GNUFlavour = AssemblyFlavour(iota)
IntelFlavour
)
// Disassemble disassembles target memory between startPC and endPC
// If currentGoroutine is set and thread is stopped at a CALL instruction Disassemble will evaluate the argument of the CALL instruction using the thread's registers
// Be aware that the Bytes field of each returned instruction is a slice of a larger array of size endPC - startPC
func (thread *Thread) Disassemble(startPC, endPC uint64, currentGoroutine bool) ([]AsmInstruction, error) {
if thread.dbp.exited {
return nil, &ProcessExitedError{}
}
mem, err := thread.readMemory(uintptr(startPC), int(endPC-startPC))
if err != nil {
return nil, err
}
r := make([]AsmInstruction, 0, len(mem)/15)
pc := startPC
var curpc uint64
var regs Registers
if currentGoroutine {
regs, _ = thread.Registers(false)
if regs != nil {
curpc = regs.PC()
}
}
for len(mem) > 0 {
bp, atbp := thread.dbp.Breakpoints[pc]
if atbp {
for i := range bp.OriginalData {
mem[i] = bp.OriginalData[i]
}
}
file, line, fn := thread.dbp.PCToLine(pc)
loc := Location{PC: pc, File: file, Line: line, Fn: fn}
inst, err := asmDecode(mem, pc)
if err == nil {
atpc := currentGoroutine && (curpc == pc)
destloc := thread.resolveCallArg(inst, atpc, regs)
r = append(r, AsmInstruction{Loc: loc, DestLoc: destloc, Bytes: mem[:inst.Len], Breakpoint: atbp, AtPC: atpc, Inst: inst})
pc += uint64(inst.Size())
mem = mem[inst.Size():]
} else {
r = append(r, AsmInstruction{Loc: loc, Bytes: mem[:1], Breakpoint: atbp, Inst: nil})
pc++
mem = mem[1:]
}
}
return r, nil
}

View File

@ -0,0 +1,183 @@
package proc
import (
"debug/gosym"
"encoding/binary"
"rsc.io/x86/x86asm"
)
var maxInstructionLength uint64 = 15
type ArchInst x86asm.Inst
func asmDecode(mem []byte, pc uint64) (*ArchInst, error) {
inst, err := x86asm.Decode(mem, 64)
if err != nil {
return nil, err
}
patchPCRel(pc, &inst)
r := ArchInst(inst)
return &r, nil
}
func (inst *ArchInst) Size() int {
return inst.Len
}
// converts PC relative arguments to absolute addresses
func patchPCRel(pc uint64, inst *x86asm.Inst) {
for i := range inst.Args {
rel, isrel := inst.Args[i].(x86asm.Rel)
if isrel {
inst.Args[i] = x86asm.Imm(int64(pc) + int64(rel) + int64(inst.Len))
}
}
return
}
func (inst *AsmInstruction) Text(flavour AssemblyFlavour) string {
if inst.Inst == nil {
return "?"
}
var text string
switch flavour {
case GNUFlavour:
text = x86asm.GNUSyntax(x86asm.Inst(*inst.Inst))
case IntelFlavour:
fallthrough
default:
text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst))
}
if inst.IsCall() && inst.DestLoc != nil && inst.DestLoc.Fn != nil {
text += " " + inst.DestLoc.Fn.Name
}
return text
}
func (inst *AsmInstruction) IsCall() bool {
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
}
func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs Registers) *Location {
if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
return nil
}
var pc uint64
var err error
switch arg := inst.Args[0].(type) {
case x86asm.Imm:
pc = uint64(arg)
case x86asm.Reg:
if !currentGoroutine || regs == nil {
return nil
}
pc, err = regs.Get(int(arg))
if err != nil {
return nil
}
case x86asm.Mem:
if !currentGoroutine || regs == nil {
return nil
}
if arg.Segment != 0 {
return nil
}
regs, err := thread.Registers(false)
if err != nil {
return nil
}
base, err1 := regs.Get(int(arg.Base))
index, err2 := regs.Get(int(arg.Index))
if err1 != nil || err2 != nil {
return nil
}
addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp)
//TODO: should this always be 64 bits instead of inst.MemBytes?
pcbytes, err := thread.readMemory(addr, inst.MemBytes)
if err != nil {
return nil
}
pc = binary.LittleEndian.Uint64(pcbytes)
default:
return nil
}
file, line, fn := thread.dbp.PCToLine(pc)
if fn == nil {
return nil
}
return &Location{PC: pc, File: file, Line: line, Fn: fn}
}
type instrseq []x86asm.Op
// Possible stacksplit prologues are inserted by stacksplit in
// $GOROOT/src/cmd/internal/obj/x86/obj6.go.
// The stacksplit prologue will always begin with loading curg in CX, this
// instruction is added by load_g_cx in the same file and is either 1 or 2
// MOVs.
var prologues []instrseq
func init() {
var tinyStacksplit = instrseq{x86asm.CMP, x86asm.JBE}
var smallStacksplit = instrseq{x86asm.LEA, x86asm.CMP, x86asm.JBE}
var bigStacksplit = instrseq{x86asm.MOV, x86asm.CMP, x86asm.JE, x86asm.LEA, x86asm.SUB, x86asm.CMP, x86asm.JBE}
var unixGetG = instrseq{x86asm.MOV}
var windowsGetG = instrseq{x86asm.MOV, x86asm.MOV}
prologues = make([]instrseq, 0, 2*3)
for _, getG := range []instrseq{unixGetG, windowsGetG} {
for _, stacksplit := range []instrseq{tinyStacksplit, smallStacksplit, bigStacksplit} {
prologue := make(instrseq, 0, len(getG)+len(stacksplit))
prologue = append(prologue, getG...)
prologue = append(prologue, stacksplit...)
prologues = append(prologues, prologue)
}
}
}
// FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn
// If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry
func (dbp *Process) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) {
text, err := dbp.CurrentThread.Disassemble(fn.Entry, fn.End, false)
if err != nil {
return fn.Entry, err
}
if len(text) <= 0 {
return fn.Entry, nil
}
for _, prologue := range prologues {
if len(prologue) >= len(text) {
continue
}
if checkPrologue(text, prologue) {
r := &text[len(prologue)]
if sameline {
if r.Loc.Line != text[0].Loc.Line {
return fn.Entry, nil
}
}
return r.Loc.PC, nil
}
}
return fn.Entry, nil
}
func checkPrologue(s []AsmInstruction, prologuePattern instrseq) bool {
line := s[0].Loc.Line
for i, op := range prologuePattern {
if s[i].Inst.Op != op || s[i].Loc.Line != line {
return false
}
}
return true
}

9
vendor/github.com/derekparker/delve/proc/doc.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
// Package proc is a low-level package that provides methods to manipulate
// the process we are debugging.
//
// proc implements all core functionality including:
// * creating / attaching to a process
// * process manipulation (step, next, continue, halt)
// * methods to explore the memory of the process
//
package proc

1121
vendor/github.com/derekparker/delve/proc/eval.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

283
vendor/github.com/derekparker/delve/proc/exc.h generated vendored Normal file
View File

@ -0,0 +1,283 @@
#ifndef _exc_user_
#define _exc_user_
/* Module exc */
#include <string.h>
#include <mach/ndr.h>
#include <mach/boolean.h>
#include <mach/kern_return.h>
#include <mach/notify.h>
#include <mach/mach_types.h>
#include <mach/message.h>
#include <mach/mig_errors.h>
#include <mach/port.h>
/* BEGIN VOUCHER CODE */
#ifndef KERNEL
#if defined(__has_include)
#if __has_include(<mach/mig_voucher_support.h>)
#ifndef USING_VOUCHERS
#define USING_VOUCHERS
#endif
#ifndef __VOUCHER_FORWARD_TYPE_DECLS__
#define __VOUCHER_FORWARD_TYPE_DECLS__
#ifdef __cplusplus
extern "C" {
#endif
extern boolean_t voucher_mach_msg_set(mach_msg_header_t *msg) __attribute__((weak_import));
#ifdef __cplusplus
}
#endif
#endif // __VOUCHER_FORWARD_TYPE_DECLS__
#endif // __has_include(<mach/mach_voucher_types.h>)
#endif // __has_include
#endif // !KERNEL
/* END VOUCHER CODE */
#ifdef AUTOTEST
#ifndef FUNCTION_PTR_T
#define FUNCTION_PTR_T
typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t);
typedef struct {
char *name;
function_ptr_t function;
} function_table_entry;
typedef function_table_entry *function_table_t;
#endif /* FUNCTION_PTR_T */
#endif /* AUTOTEST */
#ifndef exc_MSG_COUNT
#define exc_MSG_COUNT 3
#endif /* exc_MSG_COUNT */
#include <mach/std_types.h>
#include <mach/mig.h>
#include <mach/mig.h>
#include <mach/mach_types.h>
#ifdef __BeforeMigUserHeader
__BeforeMigUserHeader
#endif /* __BeforeMigUserHeader */
#include <sys/cdefs.h>
__BEGIN_DECLS
/* Routine exception_raise */
#ifdef mig_external
mig_external
#else
extern
#endif /* mig_external */
kern_return_t exception_raise
(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t codeCnt
);
/* Routine exception_raise_state */
#ifdef mig_external
mig_external
#else
extern
#endif /* mig_external */
kern_return_t exception_raise_state
(
mach_port_t exception_port,
exception_type_t exception,
const exception_data_t code,
mach_msg_type_number_t codeCnt,
int *flavor,
const thread_state_t old_state,
mach_msg_type_number_t old_stateCnt,
thread_state_t new_state,
mach_msg_type_number_t *new_stateCnt
);
/* Routine exception_raise_state_identity */
#ifdef mig_external
mig_external
#else
extern
#endif /* mig_external */
kern_return_t exception_raise_state_identity
(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t codeCnt,
int *flavor,
thread_state_t old_state,
mach_msg_type_number_t old_stateCnt,
thread_state_t new_state,
mach_msg_type_number_t *new_stateCnt
);
__END_DECLS
/********************** Caution **************************/
/* The following data types should be used to calculate */
/* maximum message sizes only. The actual message may be */
/* smaller, and the position of the arguments within the */
/* message layout may vary from what is presented here. */
/* For example, if any of the arguments are variable- */
/* sized, and less than the maximum is sent, the data */
/* will be packed tight in the actual message to reduce */
/* the presence of holes. */
/********************** Caution **************************/
/* typedefs for all requests */
#ifndef __Request__exc_subsystem__defined
#define __Request__exc_subsystem__defined
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
} __Request__exception_raise_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} __Request__exception_raise_state_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} __Request__exception_raise_state_identity_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#endif /* !__Request__exc_subsystem__defined */
/* union of all requests */
#ifndef __RequestUnion__exc_subsystem__defined
#define __RequestUnion__exc_subsystem__defined
union __RequestUnion__exc_subsystem {
__Request__exception_raise_t Request_exception_raise;
__Request__exception_raise_state_t Request_exception_raise_state;
__Request__exception_raise_state_identity_t Request_exception_raise_state_identity;
};
#endif /* !__RequestUnion__exc_subsystem__defined */
/* typedefs for all replies */
#ifndef __Reply__exc_subsystem__defined
#define __Reply__exc_subsystem__defined
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} __Reply__exception_raise_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} __Reply__exception_raise_state_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} __Reply__exception_raise_state_identity_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#endif /* !__Reply__exc_subsystem__defined */
/* union of all replies */
#ifndef __ReplyUnion__exc_subsystem__defined
#define __ReplyUnion__exc_subsystem__defined
union __ReplyUnion__exc_subsystem {
__Reply__exception_raise_t Reply_exception_raise;
__Reply__exception_raise_state_t Reply_exception_raise_state;
__Reply__exception_raise_state_identity_t Reply_exception_raise_state_identity;
};
#endif /* !__RequestUnion__exc_subsystem__defined */
#ifndef subsystem_to_name_map_exc
#define subsystem_to_name_map_exc \
{ "exception_raise", 2401 },\
{ "exception_raise_state", 2402 },\
{ "exception_raise_state_identity", 2403 }
#endif
#ifdef __AfterMigUserHeader
__AfterMigUserHeader
#endif /* __AfterMigUserHeader */
#endif /* _exc_user_ */

View File

@ -0,0 +1,768 @@
/*
* IDENTIFICATION:
* stub generated Sun Feb 22 20:54:31 2015
* with a MiG generated by bootstrap_cmds-91
* OPTIONS:
*/
#define __MIG_check__Reply__exc_subsystem__ 1
#include "exc.h"
#ifndef mig_internal
#define mig_internal static __inline__
#endif /* mig_internal */
#ifndef mig_external
#define mig_external
#endif /* mig_external */
#if !defined(__MigTypeCheck) && defined(TypeCheck)
#define __MigTypeCheck TypeCheck /* Legacy setting */
#endif /* !defined(__MigTypeCheck) */
#if !defined(__MigKernelSpecificCode) && defined(_MIG_KERNEL_SPECIFIC_CODE_)
#define __MigKernelSpecificCode _MIG_KERNEL_SPECIFIC_CODE_ /* Legacy setting */
#endif /* !defined(__MigKernelSpecificCode) */
#ifndef LimitCheck
#define LimitCheck 0
#endif /* LimitCheck */
#ifndef min
#define min(a,b) ( ((a) < (b))? (a): (b) )
#endif /* min */
#if !defined(_WALIGN_)
#define _WALIGN_(x) (((x) + 3) & ~3)
#endif /* !defined(_WALIGN_) */
#if !defined(_WALIGNSZ_)
#define _WALIGNSZ_(x) _WALIGN_(sizeof(x))
#endif /* !defined(_WALIGNSZ_) */
#ifndef UseStaticTemplates
#define UseStaticTemplates 0
#endif /* UseStaticTemplates */
#ifndef __MachMsgErrorWithTimeout
#define __MachMsgErrorWithTimeout(_R_) { \
switch (_R_) { \
case MACH_SEND_INVALID_DATA: \
case MACH_SEND_INVALID_DEST: \
case MACH_SEND_INVALID_HEADER: \
mig_put_reply_port(InP->Head.msgh_reply_port); \
break; \
case MACH_SEND_TIMED_OUT: \
case MACH_RCV_TIMED_OUT: \
default: \
mig_dealloc_reply_port(InP->Head.msgh_reply_port); \
} \
}
#endif /* __MachMsgErrorWithTimeout */
#ifndef __MachMsgErrorWithoutTimeout
#define __MachMsgErrorWithoutTimeout(_R_) { \
switch (_R_) { \
case MACH_SEND_INVALID_DATA: \
case MACH_SEND_INVALID_DEST: \
case MACH_SEND_INVALID_HEADER: \
mig_put_reply_port(InP->Head.msgh_reply_port); \
break; \
default: \
mig_dealloc_reply_port(InP->Head.msgh_reply_port); \
} \
}
#endif /* __MachMsgErrorWithoutTimeout */
#ifndef __DeclareSendRpc
#define __DeclareSendRpc(_NUM_, _NAME_)
#endif /* __DeclareSendRpc */
#ifndef __BeforeSendRpc
#define __BeforeSendRpc(_NUM_, _NAME_)
#endif /* __BeforeSendRpc */
#ifndef __AfterSendRpc
#define __AfterSendRpc(_NUM_, _NAME_)
#endif /* __AfterSendRpc */
#ifndef __DeclareSendSimple
#define __DeclareSendSimple(_NUM_, _NAME_)
#endif /* __DeclareSendSimple */
#ifndef __BeforeSendSimple
#define __BeforeSendSimple(_NUM_, _NAME_)
#endif /* __BeforeSendSimple */
#ifndef __AfterSendSimple
#define __AfterSendSimple(_NUM_, _NAME_)
#endif /* __AfterSendSimple */
#define msgh_request_port msgh_remote_port
#define msgh_reply_port msgh_local_port
#if ( __MigTypeCheck )
#if __MIG_check__Reply__exc_subsystem__
#if !defined(__MIG_check__Reply__exception_raise_t__defined)
#define __MIG_check__Reply__exception_raise_t__defined
mig_internal kern_return_t __MIG_check__Reply__exception_raise_t(__Reply__exception_raise_t *Out0P)
{
typedef __Reply__exception_raise_t __Reply;
if (Out0P->Head.msgh_id != 2501) {
if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE)
{ return MIG_SERVER_DIED; }
else
{ return MIG_REPLY_MISMATCH; }
}
#if __MigTypeCheck
if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) ||
(Out0P->Head.msgh_size != (mach_msg_size_t)sizeof(__Reply)))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
{
return Out0P->RetCode;
}
}
#endif /* !defined(__MIG_check__Reply__exception_raise_t__defined) */
#endif /* __MIG_check__Reply__exc_subsystem__ */
#endif /* ( __MigTypeCheck ) */
/* Routine exception_raise */
mig_external kern_return_t exception_raise
(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t codeCnt
)
{
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
} Request;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
mach_msg_trailer_t trailer;
} Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} __Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
union {
Request In;
Reply Out;
} Mess;
Request *InP = &Mess.In;
Reply *Out0P = &Mess.Out;
mach_msg_return_t msg_result;
unsigned int msgh_size;
#ifdef __MIG_check__Reply__exception_raise_t__defined
kern_return_t check_result;
#endif /* __MIG_check__Reply__exception_raise_t__defined */
__DeclareSendRpc(2401, "exception_raise")
#if UseStaticTemplates
const static mach_msg_port_descriptor_t threadTemplate = {
/* name = */ MACH_PORT_NULL,
/* pad1 = */ 0,
/* pad2 = */ 0,
/* disp = */ 19,
/* type = */ MACH_MSG_PORT_DESCRIPTOR,
};
#endif /* UseStaticTemplates */
#if UseStaticTemplates
const static mach_msg_port_descriptor_t taskTemplate = {
/* name = */ MACH_PORT_NULL,
/* pad1 = */ 0,
/* pad2 = */ 0,
/* disp = */ 19,
/* type = */ MACH_MSG_PORT_DESCRIPTOR,
};
#endif /* UseStaticTemplates */
InP->msgh_body.msgh_descriptor_count = 2;
#if UseStaticTemplates
InP->thread = threadTemplate;
InP->thread.name = thread;
#else /* UseStaticTemplates */
InP->thread.name = thread;
InP->thread.disposition = 19;
InP->thread.type = MACH_MSG_PORT_DESCRIPTOR;
#endif /* UseStaticTemplates */
#if UseStaticTemplates
InP->task = taskTemplate;
InP->task.name = task;
#else /* UseStaticTemplates */
InP->task.name = task;
InP->task.disposition = 19;
InP->task.type = MACH_MSG_PORT_DESCRIPTOR;
#endif /* UseStaticTemplates */
InP->NDR = NDR_record;
InP->exception = exception;
if (codeCnt > 2) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->code, (const char *) code, 4 * codeCnt);
InP->codeCnt = codeCnt;
msgh_size = (mach_msg_size_t)(sizeof(Request) - 8) + ((4 * codeCnt));
InP->Head.msgh_bits = MACH_MSGH_BITS_COMPLEX|
MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE);
/* msgh_size passed as argument */
InP->Head.msgh_request_port = exception_port;
InP->Head.msgh_reply_port = mig_get_reply_port();
InP->Head.msgh_id = 2401;
/* BEGIN VOUCHER CODE */
#ifdef USING_VOUCHERS
if (voucher_mach_msg_set != NULL) {
voucher_mach_msg_set(&InP->Head);
}
#endif // USING_VOUCHERS
/* END VOUCHER CODE */
__BeforeSendRpc(2401, "exception_raise")
msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, msgh_size, (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
__AfterSendRpc(2401, "exception_raise")
if (msg_result != MACH_MSG_SUCCESS) {
__MachMsgErrorWithoutTimeout(msg_result);
{ return msg_result; }
}
#if defined(__MIG_check__Reply__exception_raise_t__defined)
check_result = __MIG_check__Reply__exception_raise_t((__Reply__exception_raise_t *)Out0P);
if (check_result != MACH_MSG_SUCCESS)
{ return check_result; }
#endif /* defined(__MIG_check__Reply__exception_raise_t__defined) */
return KERN_SUCCESS;
}
#if ( __MigTypeCheck )
#if __MIG_check__Reply__exc_subsystem__
#if !defined(__MIG_check__Reply__exception_raise_state_t__defined)
#define __MIG_check__Reply__exception_raise_state_t__defined
mig_internal kern_return_t __MIG_check__Reply__exception_raise_state_t(__Reply__exception_raise_state_t *Out0P)
{
typedef __Reply__exception_raise_state_t __Reply;
#if __MigTypeCheck
unsigned int msgh_size;
#endif /* __MigTypeCheck */
if (Out0P->Head.msgh_id != 2502) {
if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE)
{ return MIG_SERVER_DIED; }
else
{ return MIG_REPLY_MISMATCH; }
}
#if __MigTypeCheck
msgh_size = Out0P->Head.msgh_size;
if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) ||
((msgh_size > (mach_msg_size_t)sizeof(__Reply) || msgh_size < (mach_msg_size_t)(sizeof(__Reply) - 896)) &&
(msgh_size != (mach_msg_size_t)sizeof(mig_reply_error_t) ||
Out0P->RetCode == KERN_SUCCESS)))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
if (Out0P->RetCode != KERN_SUCCESS) {
return ((mig_reply_error_t *)Out0P)->RetCode;
}
#if __MigTypeCheck
if ( Out0P->new_stateCnt > 224 )
return MIG_TYPE_ERROR;
if (((msgh_size - (mach_msg_size_t)(sizeof(__Reply) - 896)) / 4< Out0P->new_stateCnt) ||
(msgh_size != (mach_msg_size_t)(sizeof(__Reply) - 896) + Out0P->new_stateCnt * 4))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
return MACH_MSG_SUCCESS;
}
#endif /* !defined(__MIG_check__Reply__exception_raise_state_t__defined) */
#endif /* __MIG_check__Reply__exc_subsystem__ */
#endif /* ( __MigTypeCheck ) */
/* Routine exception_raise_state */
mig_external kern_return_t exception_raise_state
(
mach_port_t exception_port,
exception_type_t exception,
const exception_data_t code,
mach_msg_type_number_t codeCnt,
int *flavor,
const thread_state_t old_state,
mach_msg_type_number_t old_stateCnt,
thread_state_t new_state,
mach_msg_type_number_t *new_stateCnt
)
{
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} Request;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
mach_msg_trailer_t trailer;
} Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} __Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
union {
Request In;
Reply Out;
} Mess;
Request *InP = &Mess.In;
Reply *Out0P = &Mess.Out;
mach_msg_return_t msg_result;
unsigned int msgh_size;
unsigned int msgh_size_delta;
#ifdef __MIG_check__Reply__exception_raise_state_t__defined
kern_return_t check_result;
#endif /* __MIG_check__Reply__exception_raise_state_t__defined */
__DeclareSendRpc(2402, "exception_raise_state")
InP->NDR = NDR_record;
InP->exception = exception;
if (codeCnt > 2) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->code, (const char *) code, 4 * codeCnt);
InP->codeCnt = codeCnt;
msgh_size_delta = (4 * codeCnt);
msgh_size = (mach_msg_size_t)(sizeof(Request) - 904) + msgh_size_delta;
InP = (Request *) ((pointer_t) InP + msgh_size_delta - 8);
InP->flavor = *flavor;
if (old_stateCnt > 224) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->old_state, (const char *) old_state, 4 * old_stateCnt);
InP->old_stateCnt = old_stateCnt;
msgh_size += (4 * old_stateCnt);
InP = &Mess.In;
InP->Head.msgh_bits =
MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE);
/* msgh_size passed as argument */
InP->Head.msgh_request_port = exception_port;
InP->Head.msgh_reply_port = mig_get_reply_port();
InP->Head.msgh_id = 2402;
/* BEGIN VOUCHER CODE */
#ifdef USING_VOUCHERS
if (voucher_mach_msg_set != NULL) {
voucher_mach_msg_set(&InP->Head);
}
#endif // USING_VOUCHERS
/* END VOUCHER CODE */
__BeforeSendRpc(2402, "exception_raise_state")
msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, msgh_size, (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
__AfterSendRpc(2402, "exception_raise_state")
if (msg_result != MACH_MSG_SUCCESS) {
__MachMsgErrorWithoutTimeout(msg_result);
{ return msg_result; }
}
#if defined(__MIG_check__Reply__exception_raise_state_t__defined)
check_result = __MIG_check__Reply__exception_raise_state_t((__Reply__exception_raise_state_t *)Out0P);
if (check_result != MACH_MSG_SUCCESS)
{ return check_result; }
#endif /* defined(__MIG_check__Reply__exception_raise_state_t__defined) */
*flavor = Out0P->flavor;
if (Out0P->new_stateCnt > 224) {
(void)memcpy((char *) new_state, (const char *) Out0P->new_state, 4 * 224);
*new_stateCnt = Out0P->new_stateCnt;
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) new_state, (const char *) Out0P->new_state, 4 * Out0P->new_stateCnt);
*new_stateCnt = Out0P->new_stateCnt;
return KERN_SUCCESS;
}
#if ( __MigTypeCheck )
#if __MIG_check__Reply__exc_subsystem__
#if !defined(__MIG_check__Reply__exception_raise_state_identity_t__defined)
#define __MIG_check__Reply__exception_raise_state_identity_t__defined
mig_internal kern_return_t __MIG_check__Reply__exception_raise_state_identity_t(__Reply__exception_raise_state_identity_t *Out0P)
{
typedef __Reply__exception_raise_state_identity_t __Reply;
#if __MigTypeCheck
unsigned int msgh_size;
#endif /* __MigTypeCheck */
if (Out0P->Head.msgh_id != 2503) {
if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE)
{ return MIG_SERVER_DIED; }
else
{ return MIG_REPLY_MISMATCH; }
}
#if __MigTypeCheck
msgh_size = Out0P->Head.msgh_size;
if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) ||
((msgh_size > (mach_msg_size_t)sizeof(__Reply) || msgh_size < (mach_msg_size_t)(sizeof(__Reply) - 896)) &&
(msgh_size != (mach_msg_size_t)sizeof(mig_reply_error_t) ||
Out0P->RetCode == KERN_SUCCESS)))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
if (Out0P->RetCode != KERN_SUCCESS) {
return ((mig_reply_error_t *)Out0P)->RetCode;
}
#if __MigTypeCheck
if ( Out0P->new_stateCnt > 224 )
return MIG_TYPE_ERROR;
if (((msgh_size - (mach_msg_size_t)(sizeof(__Reply) - 896)) / 4< Out0P->new_stateCnt) ||
(msgh_size != (mach_msg_size_t)(sizeof(__Reply) - 896) + Out0P->new_stateCnt * 4))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
return MACH_MSG_SUCCESS;
}
#endif /* !defined(__MIG_check__Reply__exception_raise_state_identity_t__defined) */
#endif /* __MIG_check__Reply__exc_subsystem__ */
#endif /* ( __MigTypeCheck ) */
/* Routine exception_raise_state_identity */
mig_external kern_return_t exception_raise_state_identity
(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
exception_data_t code,
mach_msg_type_number_t codeCnt,
int *flavor,
thread_state_t old_state,
mach_msg_type_number_t old_stateCnt,
thread_state_t new_state,
mach_msg_type_number_t *new_stateCnt
)
{
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} Request;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
mach_msg_trailer_t trailer;
} Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} __Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
union {
Request In;
Reply Out;
} Mess;
Request *InP = &Mess.In;
Reply *Out0P = &Mess.Out;
mach_msg_return_t msg_result;
unsigned int msgh_size;
unsigned int msgh_size_delta;
#ifdef __MIG_check__Reply__exception_raise_state_identity_t__defined
kern_return_t check_result;
#endif /* __MIG_check__Reply__exception_raise_state_identity_t__defined */
__DeclareSendRpc(2403, "exception_raise_state_identity")
#if UseStaticTemplates
const static mach_msg_port_descriptor_t threadTemplate = {
/* name = */ MACH_PORT_NULL,
/* pad1 = */ 0,
/* pad2 = */ 0,
/* disp = */ 19,
/* type = */ MACH_MSG_PORT_DESCRIPTOR,
};
#endif /* UseStaticTemplates */
#if UseStaticTemplates
const static mach_msg_port_descriptor_t taskTemplate = {
/* name = */ MACH_PORT_NULL,
/* pad1 = */ 0,
/* pad2 = */ 0,
/* disp = */ 19,
/* type = */ MACH_MSG_PORT_DESCRIPTOR,
};
#endif /* UseStaticTemplates */
InP->msgh_body.msgh_descriptor_count = 2;
#if UseStaticTemplates
InP->thread = threadTemplate;
InP->thread.name = thread;
#else /* UseStaticTemplates */
InP->thread.name = thread;
InP->thread.disposition = 19;
InP->thread.type = MACH_MSG_PORT_DESCRIPTOR;
#endif /* UseStaticTemplates */
#if UseStaticTemplates
InP->task = taskTemplate;
InP->task.name = task;
#else /* UseStaticTemplates */
InP->task.name = task;
InP->task.disposition = 19;
InP->task.type = MACH_MSG_PORT_DESCRIPTOR;
#endif /* UseStaticTemplates */
InP->NDR = NDR_record;
InP->exception = exception;
if (codeCnt > 2) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->code, (const char *) code, 4 * codeCnt);
InP->codeCnt = codeCnt;
msgh_size_delta = (4 * codeCnt);
msgh_size = (mach_msg_size_t)(sizeof(Request) - 904) + msgh_size_delta;
InP = (Request *) ((pointer_t) InP + msgh_size_delta - 8);
InP->flavor = *flavor;
if (old_stateCnt > 224) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->old_state, (const char *) old_state, 4 * old_stateCnt);
InP->old_stateCnt = old_stateCnt;
msgh_size += (4 * old_stateCnt);
InP = &Mess.In;
InP->Head.msgh_bits = MACH_MSGH_BITS_COMPLEX|
MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE);
/* msgh_size passed as argument */
InP->Head.msgh_request_port = exception_port;
InP->Head.msgh_reply_port = mig_get_reply_port();
InP->Head.msgh_id = 2403;
/* BEGIN VOUCHER CODE */
#ifdef USING_VOUCHERS
if (voucher_mach_msg_set != NULL) {
voucher_mach_msg_set(&InP->Head);
}
#endif // USING_VOUCHERS
/* END VOUCHER CODE */
__BeforeSendRpc(2403, "exception_raise_state_identity")
msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, msgh_size, (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
__AfterSendRpc(2403, "exception_raise_state_identity")
if (msg_result != MACH_MSG_SUCCESS) {
__MachMsgErrorWithoutTimeout(msg_result);
{ return msg_result; }
}
#if defined(__MIG_check__Reply__exception_raise_state_identity_t__defined)
check_result = __MIG_check__Reply__exception_raise_state_identity_t((__Reply__exception_raise_state_identity_t *)Out0P);
if (check_result != MACH_MSG_SUCCESS)
{ return check_result; }
#endif /* defined(__MIG_check__Reply__exception_raise_state_identity_t__defined) */
*flavor = Out0P->flavor;
if (Out0P->new_stateCnt > 224) {
(void)memcpy((char *) new_state, (const char *) Out0P->new_state, 4 * 224);
*new_stateCnt = Out0P->new_stateCnt;
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) new_state, (const char *) Out0P->new_state, 4 * Out0P->new_stateCnt);
*new_stateCnt = Out0P->new_stateCnt;
return KERN_SUCCESS;
}

112
vendor/github.com/derekparker/delve/proc/exec_darwin.c generated vendored Normal file
View File

@ -0,0 +1,112 @@
#include "exec_darwin.h"
#include "stdio.h"
extern char** environ;
int
close_exec_pipe(int fd[2]) {
if (pipe(fd) < 0) return -1;
if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0) return -1;
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) < 0) return -1;
return 0;
}
int
fork_exec(char *argv0, char **argv, int size,
char *wd,
task_t *task,
mach_port_t *port_set,
mach_port_t *exception_port,
mach_port_t *notification_port)
{
// Since we're using mach exceptions instead of signals,
// we need to coordinate between parent and child via pipes
// to ensure that the parent has set the exception ports on
// the child task before it execs.
int fd[2];
if (close_exec_pipe(fd) < 0) return -1;
// Create another pipe to signal the parent on exec.
int efd[2];
if (close_exec_pipe(efd) < 0) return -1;
kern_return_t kret;
pid_t pid = fork();
if (pid > 0) {
// In parent.
close(fd[0]);
close(efd[1]);
kret = acquire_mach_task(pid, task, port_set, exception_port, notification_port);
if (kret != KERN_SUCCESS) return -1;
char msg = 'c';
write(fd[1], &msg, 1);
close(fd[1]);
char w;
size_t n = read(efd[0], &w, 1);
close(efd[0]);
if (n != 0) {
// Child died, reap it.
waitpid(pid, NULL, 0);
return -1;
}
return pid;
}
// Fork succeeded, we are in the child.
int pret, cret;
char sig;
close(fd[1]);
read(fd[0], &sig, 1);
close(fd[0]);
// Create a new process group.
if (setpgid(0, 0) < 0) {
perror("setpgid");
exit(1);
}
// Set errno to zero before a call to ptrace.
// It is documented that ptrace can return -1 even
// for successful calls.
errno = 0;
pret = ptrace(PT_TRACE_ME, 0, 0, 0);
if (pret != 0 && errno != 0) {
perror("ptrace");
exit(1);
}
// Change working directory if wd is not empty.
if (wd && wd[0]) {
errno = 0;
cret = chdir(wd);
if (cret != 0 && errno != 0) {
char *error_msg;
asprintf(&error_msg, "%s '%s'", "chdir", wd);
perror(error_msg);
exit(1);
}
}
errno = 0;
pret = ptrace(PT_SIGEXC, 0, 0, 0);
if (pret != 0 && errno != 0) {
perror("ptrace");
exit(1);
}
sleep(1);
// Create the child process.
execve(argv0, argv, environ);
// We should never reach here, but if we did something went wrong.
// Write a message to parent to alert that exec failed.
char msg = 'd';
write(efd[1], &msg, 1);
close(efd[1]);
exit(1);
}

10
vendor/github.com/derekparker/delve/proc/exec_darwin.h generated vendored Normal file
View File

@ -0,0 +1,10 @@
#include "proc_darwin.h"
#include <unistd.h>
#include <sys/ptrace.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
int
fork_exec(char *, char **, int, char *, task_t*, mach_port_t*, mach_port_t*, mach_port_t*);

113
vendor/github.com/derekparker/delve/proc/go_version.go generated vendored Normal file
View File

@ -0,0 +1,113 @@
package proc
import (
"strconv"
"strings"
)
// GoVersion represents the Go version of
// the Go compiler version used to compile
// the target binary.
type GoVersion struct {
Major int
Minor int
Rev int
Beta int
RC int
}
func ParseVersionString(ver string) (GoVersion, bool) {
var r GoVersion
var err1, err2, err3 error
if strings.HasPrefix(ver, "devel") {
return GoVersion{-1, 0, 0, 0, 0}, true
}
if strings.HasPrefix(ver, "go") {
ver := strings.Split(ver, " ")[0]
v := strings.SplitN(ver[2:], ".", 3)
switch len(v) {
case 2:
r.Major, err1 = strconv.Atoi(v[0])
vr := strings.SplitN(v[1], "beta", 2)
if len(vr) == 2 {
r.Beta, err3 = strconv.Atoi(vr[1])
} else {
vr = strings.SplitN(v[1], "rc", 2)
if len(vr) == 2 {
r.RC, err3 = strconv.Atoi(vr[1])
} else {
r.Minor, err2 = strconv.Atoi(v[1])
if err2 != nil {
return GoVersion{}, false
}
return r, true
}
}
r.Minor, err2 = strconv.Atoi(vr[0])
r.Rev = -1
if err1 != nil || err2 != nil || err3 != nil {
return GoVersion{}, false
}
return r, true
case 3:
r.Major, err1 = strconv.Atoi(v[0])
r.Minor, err2 = strconv.Atoi(v[1])
r.Rev, err3 = strconv.Atoi(v[2])
if err1 != nil || err2 != nil || err3 != nil {
return GoVersion{}, false
}
return r, true
default:
return GoVersion{}, false
}
}
return GoVersion{}, false
}
// AfterOrEqual returns whether one GoVersion is after or
// equal to the other.
func (v *GoVersion) AfterOrEqual(b GoVersion) bool {
if v.Major < b.Major {
return false
} else if v.Major > b.Major {
return true
}
if v.Minor < b.Minor {
return false
} else if v.Minor > b.Minor {
return true
}
if v.Rev < b.Rev {
return false
} else if v.Rev > b.Rev {
return true
}
if v.Beta < b.Beta {
return false
}
if v.RC < b.RC {
return false
}
return true
}
// IsDevel returns whether the GoVersion
// is a development version.
func (v *GoVersion) IsDevel() bool {
return v.Major < 0
}

119
vendor/github.com/derekparker/delve/proc/mach_exc.defs generated vendored Normal file
View File

@ -0,0 +1,119 @@
/*
* Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
/*
* @OSF_COPYRIGHT@
*/
/*
* Mach Operating System
* Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University
* All Rights Reserved.
*
* Permission to use, copy, modify and distribute this software and its
* documentation is hereby granted, provided that both the copyright
* notice and this permission notice appear in all copies of the
* software, derivative works or modified versions, and any portions
* thereof, and that both notices appear in supporting documentation.
*
* CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
* CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
* ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
*
* Carnegie Mellon requests users of this software to return to
*
* Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
* School of Computer Science
* Carnegie Mellon University
* Pittsburgh PA 15213-3890
*
* any improvements or extensions that they make and grant Carnegie Mellon
* the rights to redistribute these changes.
*/
/*
*/
/*
* Abstract:
* MiG definitions file for Mach exception interface.
*/
subsystem
#if KERNEL_USER
KernelUser
#endif
mach_exc 2405;
#include <mach/std_types.defs>
#include <mach/mach_types.defs>
ServerPrefix catch_;
type mach_exception_data_t = array[*:2] of int64_t;
type exception_type_t = int;
routine mach_exception_raise(
#if KERNEL_USER
exception_port : mach_port_move_send_t;
thread : mach_port_move_send_t;
task : mach_port_move_send_t;
#else /* KERNEL_USER */
exception_port : mach_port_t;
thread : mach_port_t;
task : mach_port_t;
#endif /* KERNEL_USER */
exception : exception_type_t;
code : mach_exception_data_t
);
routine mach_exception_raise_state(
#if KERNEL_USER
exception_port : mach_port_move_send_t;
#else /* KERNEL_USER */
exception_port : mach_port_t;
#endif /* KERNEL_USER */
exception : exception_type_t;
code : mach_exception_data_t, const;
inout flavor : int;
old_state : thread_state_t, const;
out new_state : thread_state_t);
routine mach_exception_raise_state_identity(
#if KERNEL_USER
exception_port : mach_port_move_send_t;
thread : mach_port_move_send_t;
task : mach_port_move_send_t;
#else /* KERNEL_USER */
exception_port : mach_port_t;
thread : mach_port_t;
task : mach_port_t;
#endif /* KERNEL_USER */
exception : exception_type_t;
code : mach_exception_data_t;
inout flavor : int;
old_state : thread_state_t;
out new_state : thread_state_t);
/* vim: set ft=c : */

283
vendor/github.com/derekparker/delve/proc/mach_exc.h generated vendored Normal file
View File

@ -0,0 +1,283 @@
#ifndef _mach_exc_user_
#define _mach_exc_user_
/* Module mach_exc */
#include <string.h>
#include <mach/ndr.h>
#include <mach/boolean.h>
#include <mach/kern_return.h>
#include <mach/notify.h>
#include <mach/mach_types.h>
#include <mach/message.h>
#include <mach/mig_errors.h>
#include <mach/port.h>
/* BEGIN VOUCHER CODE */
#ifndef KERNEL
#if defined(__has_include)
#if __has_include(<mach/mig_voucher_support.h>)
#ifndef USING_VOUCHERS
#define USING_VOUCHERS
#endif
#ifndef __VOUCHER_FORWARD_TYPE_DECLS__
#define __VOUCHER_FORWARD_TYPE_DECLS__
#ifdef __cplusplus
extern "C" {
#endif
extern boolean_t voucher_mach_msg_set(mach_msg_header_t *msg) __attribute__((weak_import));
#ifdef __cplusplus
}
#endif
#endif // __VOUCHER_FORWARD_TYPE_DECLS__
#endif // __has_include(<mach/mach_voucher_types.h>)
#endif // __has_include
#endif // !KERNEL
/* END VOUCHER CODE */
#ifdef AUTOTEST
#ifndef FUNCTION_PTR_T
#define FUNCTION_PTR_T
typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t);
typedef struct {
char *name;
function_ptr_t function;
} function_table_entry;
typedef function_table_entry *function_table_t;
#endif /* FUNCTION_PTR_T */
#endif /* AUTOTEST */
#ifndef mach_exc_MSG_COUNT
#define mach_exc_MSG_COUNT 3
#endif /* mach_exc_MSG_COUNT */
#include <mach/std_types.h>
#include <mach/mig.h>
#include <mach/mig.h>
#include <mach/mach_types.h>
#ifdef __BeforeMigUserHeader
__BeforeMigUserHeader
#endif /* __BeforeMigUserHeader */
#include <sys/cdefs.h>
__BEGIN_DECLS
/* Routine mach_exception_raise */
#ifdef mig_external
mig_external
#else
extern
#endif /* mig_external */
kern_return_t mach_exception_raise
(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt
);
/* Routine mach_exception_raise_state */
#ifdef mig_external
mig_external
#else
extern
#endif /* mig_external */
kern_return_t mach_exception_raise_state
(
mach_port_t exception_port,
exception_type_t exception,
const mach_exception_data_t code,
mach_msg_type_number_t codeCnt,
int *flavor,
const thread_state_t old_state,
mach_msg_type_number_t old_stateCnt,
thread_state_t new_state,
mach_msg_type_number_t *new_stateCnt
);
/* Routine mach_exception_raise_state_identity */
#ifdef mig_external
mig_external
#else
extern
#endif /* mig_external */
kern_return_t mach_exception_raise_state_identity
(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt,
int *flavor,
thread_state_t old_state,
mach_msg_type_number_t old_stateCnt,
thread_state_t new_state,
mach_msg_type_number_t *new_stateCnt
);
__END_DECLS
/********************** Caution **************************/
/* The following data types should be used to calculate */
/* maximum message sizes only. The actual message may be */
/* smaller, and the position of the arguments within the */
/* message layout may vary from what is presented here. */
/* For example, if any of the arguments are variable- */
/* sized, and less than the maximum is sent, the data */
/* will be packed tight in the actual message to reduce */
/* the presence of holes. */
/********************** Caution **************************/
/* typedefs for all requests */
#ifndef __Request__mach_exc_subsystem__defined
#define __Request__mach_exc_subsystem__defined
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
} __Request__mach_exception_raise_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} __Request__mach_exception_raise_state_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} __Request__mach_exception_raise_state_identity_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#endif /* !__Request__mach_exc_subsystem__defined */
/* union of all requests */
#ifndef __RequestUnion__mach_exc_subsystem__defined
#define __RequestUnion__mach_exc_subsystem__defined
union __RequestUnion__mach_exc_subsystem {
__Request__mach_exception_raise_t Request_mach_exception_raise;
__Request__mach_exception_raise_state_t Request_mach_exception_raise_state;
__Request__mach_exception_raise_state_identity_t Request_mach_exception_raise_state_identity;
};
#endif /* !__RequestUnion__mach_exc_subsystem__defined */
/* typedefs for all replies */
#ifndef __Reply__mach_exc_subsystem__defined
#define __Reply__mach_exc_subsystem__defined
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} __Reply__mach_exception_raise_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} __Reply__mach_exception_raise_state_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} __Reply__mach_exception_raise_state_identity_t;
#ifdef __MigPackStructs
#pragma pack()
#endif
#endif /* !__Reply__mach_exc_subsystem__defined */
/* union of all replies */
#ifndef __ReplyUnion__mach_exc_subsystem__defined
#define __ReplyUnion__mach_exc_subsystem__defined
union __ReplyUnion__mach_exc_subsystem {
__Reply__mach_exception_raise_t Reply_mach_exception_raise;
__Reply__mach_exception_raise_state_t Reply_mach_exception_raise_state;
__Reply__mach_exception_raise_state_identity_t Reply_mach_exception_raise_state_identity;
};
#endif /* !__RequestUnion__mach_exc_subsystem__defined */
#ifndef subsystem_to_name_map_mach_exc
#define subsystem_to_name_map_mach_exc \
{ "mach_exception_raise", 2401 },\
{ "mach_exception_raise_state", 2402 },\
{ "mach_exception_raise_state_identity", 2403 }
#endif
#ifdef __AfterMigUserHeader
__AfterMigUserHeader
#endif /* __AfterMigUserHeader */
#endif /* _mach_exc_user_ */

View File

@ -0,0 +1,768 @@
/*
* IDENTIFICATION:
* stub generated Sat Feb 21 18:10:52 2015
* with a MiG generated by bootstrap_cmds-91
* OPTIONS:
*/
#define __MIG_check__Reply__mach_exc_subsystem__ 1
#include "mach_exc.h"
#ifndef mig_internal
#define mig_internal static __inline__
#endif /* mig_internal */
#ifndef mig_external
#define mig_external
#endif /* mig_external */
#if !defined(__MigTypeCheck) && defined(TypeCheck)
#define __MigTypeCheck TypeCheck /* Legacy setting */
#endif /* !defined(__MigTypeCheck) */
#if !defined(__MigKernelSpecificCode) && defined(_MIG_KERNEL_SPECIFIC_CODE_)
#define __MigKernelSpecificCode _MIG_KERNEL_SPECIFIC_CODE_ /* Legacy setting */
#endif /* !defined(__MigKernelSpecificCode) */
#ifndef LimitCheck
#define LimitCheck 0
#endif /* LimitCheck */
#ifndef min
#define min(a,b) ( ((a) < (b))? (a): (b) )
#endif /* min */
#if !defined(_WALIGN_)
#define _WALIGN_(x) (((x) + 3) & ~3)
#endif /* !defined(_WALIGN_) */
#if !defined(_WALIGNSZ_)
#define _WALIGNSZ_(x) _WALIGN_(sizeof(x))
#endif /* !defined(_WALIGNSZ_) */
#ifndef UseStaticTemplates
#define UseStaticTemplates 0
#endif /* UseStaticTemplates */
#ifndef __MachMsgErrorWithTimeout
#define __MachMsgErrorWithTimeout(_R_) { \
switch (_R_) { \
case MACH_SEND_INVALID_DATA: \
case MACH_SEND_INVALID_DEST: \
case MACH_SEND_INVALID_HEADER: \
mig_put_reply_port(InP->Head.msgh_reply_port); \
break; \
case MACH_SEND_TIMED_OUT: \
case MACH_RCV_TIMED_OUT: \
default: \
mig_dealloc_reply_port(InP->Head.msgh_reply_port); \
} \
}
#endif /* __MachMsgErrorWithTimeout */
#ifndef __MachMsgErrorWithoutTimeout
#define __MachMsgErrorWithoutTimeout(_R_) { \
switch (_R_) { \
case MACH_SEND_INVALID_DATA: \
case MACH_SEND_INVALID_DEST: \
case MACH_SEND_INVALID_HEADER: \
mig_put_reply_port(InP->Head.msgh_reply_port); \
break; \
default: \
mig_dealloc_reply_port(InP->Head.msgh_reply_port); \
} \
}
#endif /* __MachMsgErrorWithoutTimeout */
#ifndef __DeclareSendRpc
#define __DeclareSendRpc(_NUM_, _NAME_)
#endif /* __DeclareSendRpc */
#ifndef __BeforeSendRpc
#define __BeforeSendRpc(_NUM_, _NAME_)
#endif /* __BeforeSendRpc */
#ifndef __AfterSendRpc
#define __AfterSendRpc(_NUM_, _NAME_)
#endif /* __AfterSendRpc */
#ifndef __DeclareSendSimple
#define __DeclareSendSimple(_NUM_, _NAME_)
#endif /* __DeclareSendSimple */
#ifndef __BeforeSendSimple
#define __BeforeSendSimple(_NUM_, _NAME_)
#endif /* __BeforeSendSimple */
#ifndef __AfterSendSimple
#define __AfterSendSimple(_NUM_, _NAME_)
#endif /* __AfterSendSimple */
#define msgh_request_port msgh_remote_port
#define msgh_reply_port msgh_local_port
#if ( __MigTypeCheck )
#if __MIG_check__Reply__mach_exc_subsystem__
#if !defined(__MIG_check__Reply__mach_exception_raise_t__defined)
#define __MIG_check__Reply__mach_exception_raise_t__defined
mig_internal kern_return_t __MIG_check__Reply__mach_exception_raise_t(__Reply__mach_exception_raise_t *Out0P)
{
typedef __Reply__mach_exception_raise_t __Reply;
if (Out0P->Head.msgh_id != 2505) {
if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE)
{ return MIG_SERVER_DIED; }
else
{ return MIG_REPLY_MISMATCH; }
}
#if __MigTypeCheck
if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) ||
(Out0P->Head.msgh_size != (mach_msg_size_t)sizeof(__Reply)))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
{
return Out0P->RetCode;
}
}
#endif /* !defined(__MIG_check__Reply__mach_exception_raise_t__defined) */
#endif /* __MIG_check__Reply__mach_exc_subsystem__ */
#endif /* ( __MigTypeCheck ) */
/* Routine mach_exception_raise */
mig_external kern_return_t mach_exception_raise
(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt
)
{
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
} Request;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
mach_msg_trailer_t trailer;
} Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} __Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
union {
Request In;
Reply Out;
} Mess;
Request *InP = &Mess.In;
Reply *Out0P = &Mess.Out;
mach_msg_return_t msg_result;
unsigned int msgh_size;
#ifdef __MIG_check__Reply__mach_exception_raise_t__defined
kern_return_t check_result;
#endif /* __MIG_check__Reply__mach_exception_raise_t__defined */
__DeclareSendRpc(2405, "mach_exception_raise")
#if UseStaticTemplates
const static mach_msg_port_descriptor_t threadTemplate = {
/* name = */ MACH_PORT_NULL,
/* pad1 = */ 0,
/* pad2 = */ 0,
/* disp = */ 19,
/* type = */ MACH_MSG_PORT_DESCRIPTOR,
};
#endif /* UseStaticTemplates */
#if UseStaticTemplates
const static mach_msg_port_descriptor_t taskTemplate = {
/* name = */ MACH_PORT_NULL,
/* pad1 = */ 0,
/* pad2 = */ 0,
/* disp = */ 19,
/* type = */ MACH_MSG_PORT_DESCRIPTOR,
};
#endif /* UseStaticTemplates */
InP->msgh_body.msgh_descriptor_count = 2;
#if UseStaticTemplates
InP->thread = threadTemplate;
InP->thread.name = thread;
#else /* UseStaticTemplates */
InP->thread.name = thread;
InP->thread.disposition = 19;
InP->thread.type = MACH_MSG_PORT_DESCRIPTOR;
#endif /* UseStaticTemplates */
#if UseStaticTemplates
InP->task = taskTemplate;
InP->task.name = task;
#else /* UseStaticTemplates */
InP->task.name = task;
InP->task.disposition = 19;
InP->task.type = MACH_MSG_PORT_DESCRIPTOR;
#endif /* UseStaticTemplates */
InP->NDR = NDR_record;
InP->exception = exception;
if (codeCnt > 2) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->code, (const char *) code, 8 * codeCnt);
InP->codeCnt = codeCnt;
msgh_size = (mach_msg_size_t)(sizeof(Request) - 16) + ((8 * codeCnt));
InP->Head.msgh_bits = MACH_MSGH_BITS_COMPLEX|
MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE);
/* msgh_size passed as argument */
InP->Head.msgh_request_port = exception_port;
InP->Head.msgh_reply_port = mig_get_reply_port();
InP->Head.msgh_id = 2405;
/* BEGIN VOUCHER CODE */
#ifdef USING_VOUCHERS
if (voucher_mach_msg_set != NULL) {
voucher_mach_msg_set(&InP->Head);
}
#endif // USING_VOUCHERS
/* END VOUCHER CODE */
__BeforeSendRpc(2405, "mach_exception_raise")
msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, msgh_size, (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
__AfterSendRpc(2405, "mach_exception_raise")
if (msg_result != MACH_MSG_SUCCESS) {
__MachMsgErrorWithoutTimeout(msg_result);
{ return msg_result; }
}
#if defined(__MIG_check__Reply__mach_exception_raise_t__defined)
check_result = __MIG_check__Reply__mach_exception_raise_t((__Reply__mach_exception_raise_t *)Out0P);
if (check_result != MACH_MSG_SUCCESS)
{ return check_result; }
#endif /* defined(__MIG_check__Reply__mach_exception_raise_t__defined) */
return KERN_SUCCESS;
}
#if ( __MigTypeCheck )
#if __MIG_check__Reply__mach_exc_subsystem__
#if !defined(__MIG_check__Reply__mach_exception_raise_state_t__defined)
#define __MIG_check__Reply__mach_exception_raise_state_t__defined
mig_internal kern_return_t __MIG_check__Reply__mach_exception_raise_state_t(__Reply__mach_exception_raise_state_t *Out0P)
{
typedef __Reply__mach_exception_raise_state_t __Reply;
#if __MigTypeCheck
unsigned int msgh_size;
#endif /* __MigTypeCheck */
if (Out0P->Head.msgh_id != 2506) {
if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE)
{ return MIG_SERVER_DIED; }
else
{ return MIG_REPLY_MISMATCH; }
}
#if __MigTypeCheck
msgh_size = Out0P->Head.msgh_size;
if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) ||
((msgh_size > (mach_msg_size_t)sizeof(__Reply) || msgh_size < (mach_msg_size_t)(sizeof(__Reply) - 896)) &&
(msgh_size != (mach_msg_size_t)sizeof(mig_reply_error_t) ||
Out0P->RetCode == KERN_SUCCESS)))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
if (Out0P->RetCode != KERN_SUCCESS) {
return ((mig_reply_error_t *)Out0P)->RetCode;
}
#if __MigTypeCheck
if ( Out0P->new_stateCnt > 224 )
return MIG_TYPE_ERROR;
if (((msgh_size - (mach_msg_size_t)(sizeof(__Reply) - 896)) / 4< Out0P->new_stateCnt) ||
(msgh_size != (mach_msg_size_t)(sizeof(__Reply) - 896) + Out0P->new_stateCnt * 4))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
return MACH_MSG_SUCCESS;
}
#endif /* !defined(__MIG_check__Reply__mach_exception_raise_state_t__defined) */
#endif /* __MIG_check__Reply__mach_exc_subsystem__ */
#endif /* ( __MigTypeCheck ) */
/* Routine mach_exception_raise_state */
mig_external kern_return_t mach_exception_raise_state
(
mach_port_t exception_port,
exception_type_t exception,
const mach_exception_data_t code,
mach_msg_type_number_t codeCnt,
int *flavor,
const thread_state_t old_state,
mach_msg_type_number_t old_stateCnt,
thread_state_t new_state,
mach_msg_type_number_t *new_stateCnt
)
{
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} Request;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
mach_msg_trailer_t trailer;
} Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} __Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
union {
Request In;
Reply Out;
} Mess;
Request *InP = &Mess.In;
Reply *Out0P = &Mess.Out;
mach_msg_return_t msg_result;
unsigned int msgh_size;
unsigned int msgh_size_delta;
#ifdef __MIG_check__Reply__mach_exception_raise_state_t__defined
kern_return_t check_result;
#endif /* __MIG_check__Reply__mach_exception_raise_state_t__defined */
__DeclareSendRpc(2406, "mach_exception_raise_state")
InP->NDR = NDR_record;
InP->exception = exception;
if (codeCnt > 2) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->code, (const char *) code, 8 * codeCnt);
InP->codeCnt = codeCnt;
msgh_size_delta = (8 * codeCnt);
msgh_size = (mach_msg_size_t)(sizeof(Request) - 912) + msgh_size_delta;
InP = (Request *) ((pointer_t) InP + msgh_size_delta - 16);
InP->flavor = *flavor;
if (old_stateCnt > 224) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->old_state, (const char *) old_state, 4 * old_stateCnt);
InP->old_stateCnt = old_stateCnt;
msgh_size += (4 * old_stateCnt);
InP = &Mess.In;
InP->Head.msgh_bits =
MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE);
/* msgh_size passed as argument */
InP->Head.msgh_request_port = exception_port;
InP->Head.msgh_reply_port = mig_get_reply_port();
InP->Head.msgh_id = 2406;
/* BEGIN VOUCHER CODE */
#ifdef USING_VOUCHERS
if (voucher_mach_msg_set != NULL) {
voucher_mach_msg_set(&InP->Head);
}
#endif // USING_VOUCHERS
/* END VOUCHER CODE */
__BeforeSendRpc(2406, "mach_exception_raise_state")
msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, msgh_size, (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
__AfterSendRpc(2406, "mach_exception_raise_state")
if (msg_result != MACH_MSG_SUCCESS) {
__MachMsgErrorWithoutTimeout(msg_result);
{ return msg_result; }
}
#if defined(__MIG_check__Reply__mach_exception_raise_state_t__defined)
check_result = __MIG_check__Reply__mach_exception_raise_state_t((__Reply__mach_exception_raise_state_t *)Out0P);
if (check_result != MACH_MSG_SUCCESS)
{ return check_result; }
#endif /* defined(__MIG_check__Reply__mach_exception_raise_state_t__defined) */
*flavor = Out0P->flavor;
if (Out0P->new_stateCnt > 224) {
(void)memcpy((char *) new_state, (const char *) Out0P->new_state, 4 * 224);
*new_stateCnt = Out0P->new_stateCnt;
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) new_state, (const char *) Out0P->new_state, 4 * Out0P->new_stateCnt);
*new_stateCnt = Out0P->new_stateCnt;
return KERN_SUCCESS;
}
#if ( __MigTypeCheck )
#if __MIG_check__Reply__mach_exc_subsystem__
#if !defined(__MIG_check__Reply__mach_exception_raise_state_identity_t__defined)
#define __MIG_check__Reply__mach_exception_raise_state_identity_t__defined
mig_internal kern_return_t __MIG_check__Reply__mach_exception_raise_state_identity_t(__Reply__mach_exception_raise_state_identity_t *Out0P)
{
typedef __Reply__mach_exception_raise_state_identity_t __Reply;
#if __MigTypeCheck
unsigned int msgh_size;
#endif /* __MigTypeCheck */
if (Out0P->Head.msgh_id != 2507) {
if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE)
{ return MIG_SERVER_DIED; }
else
{ return MIG_REPLY_MISMATCH; }
}
#if __MigTypeCheck
msgh_size = Out0P->Head.msgh_size;
if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) ||
((msgh_size > (mach_msg_size_t)sizeof(__Reply) || msgh_size < (mach_msg_size_t)(sizeof(__Reply) - 896)) &&
(msgh_size != (mach_msg_size_t)sizeof(mig_reply_error_t) ||
Out0P->RetCode == KERN_SUCCESS)))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
if (Out0P->RetCode != KERN_SUCCESS) {
return ((mig_reply_error_t *)Out0P)->RetCode;
}
#if __MigTypeCheck
if ( Out0P->new_stateCnt > 224 )
return MIG_TYPE_ERROR;
if (((msgh_size - (mach_msg_size_t)(sizeof(__Reply) - 896)) / 4< Out0P->new_stateCnt) ||
(msgh_size != (mach_msg_size_t)(sizeof(__Reply) - 896) + Out0P->new_stateCnt * 4))
{ return MIG_TYPE_ERROR ; }
#endif /* __MigTypeCheck */
return MACH_MSG_SUCCESS;
}
#endif /* !defined(__MIG_check__Reply__mach_exception_raise_state_identity_t__defined) */
#endif /* __MIG_check__Reply__mach_exc_subsystem__ */
#endif /* ( __MigTypeCheck ) */
/* Routine mach_exception_raise_state_identity */
mig_external kern_return_t mach_exception_raise_state_identity
(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt,
int *flavor,
thread_state_t old_state,
mach_msg_type_number_t old_stateCnt,
thread_state_t new_state,
mach_msg_type_number_t *new_stateCnt
)
{
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} Request;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
mach_msg_trailer_t trailer;
} Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} __Reply;
#ifdef __MigPackStructs
#pragma pack()
#endif
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
union {
Request In;
Reply Out;
} Mess;
Request *InP = &Mess.In;
Reply *Out0P = &Mess.Out;
mach_msg_return_t msg_result;
unsigned int msgh_size;
unsigned int msgh_size_delta;
#ifdef __MIG_check__Reply__mach_exception_raise_state_identity_t__defined
kern_return_t check_result;
#endif /* __MIG_check__Reply__mach_exception_raise_state_identity_t__defined */
__DeclareSendRpc(2407, "mach_exception_raise_state_identity")
#if UseStaticTemplates
const static mach_msg_port_descriptor_t threadTemplate = {
/* name = */ MACH_PORT_NULL,
/* pad1 = */ 0,
/* pad2 = */ 0,
/* disp = */ 19,
/* type = */ MACH_MSG_PORT_DESCRIPTOR,
};
#endif /* UseStaticTemplates */
#if UseStaticTemplates
const static mach_msg_port_descriptor_t taskTemplate = {
/* name = */ MACH_PORT_NULL,
/* pad1 = */ 0,
/* pad2 = */ 0,
/* disp = */ 19,
/* type = */ MACH_MSG_PORT_DESCRIPTOR,
};
#endif /* UseStaticTemplates */
InP->msgh_body.msgh_descriptor_count = 2;
#if UseStaticTemplates
InP->thread = threadTemplate;
InP->thread.name = thread;
#else /* UseStaticTemplates */
InP->thread.name = thread;
InP->thread.disposition = 19;
InP->thread.type = MACH_MSG_PORT_DESCRIPTOR;
#endif /* UseStaticTemplates */
#if UseStaticTemplates
InP->task = taskTemplate;
InP->task.name = task;
#else /* UseStaticTemplates */
InP->task.name = task;
InP->task.disposition = 19;
InP->task.type = MACH_MSG_PORT_DESCRIPTOR;
#endif /* UseStaticTemplates */
InP->NDR = NDR_record;
InP->exception = exception;
if (codeCnt > 2) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->code, (const char *) code, 8 * codeCnt);
InP->codeCnt = codeCnt;
msgh_size_delta = (8 * codeCnt);
msgh_size = (mach_msg_size_t)(sizeof(Request) - 912) + msgh_size_delta;
InP = (Request *) ((pointer_t) InP + msgh_size_delta - 16);
InP->flavor = *flavor;
if (old_stateCnt > 224) {
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) InP->old_state, (const char *) old_state, 4 * old_stateCnt);
InP->old_stateCnt = old_stateCnt;
msgh_size += (4 * old_stateCnt);
InP = &Mess.In;
InP->Head.msgh_bits = MACH_MSGH_BITS_COMPLEX|
MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE);
/* msgh_size passed as argument */
InP->Head.msgh_request_port = exception_port;
InP->Head.msgh_reply_port = mig_get_reply_port();
InP->Head.msgh_id = 2407;
/* BEGIN VOUCHER CODE */
#ifdef USING_VOUCHERS
if (voucher_mach_msg_set != NULL) {
voucher_mach_msg_set(&InP->Head);
}
#endif // USING_VOUCHERS
/* END VOUCHER CODE */
__BeforeSendRpc(2407, "mach_exception_raise_state_identity")
msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, msgh_size, (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
__AfterSendRpc(2407, "mach_exception_raise_state_identity")
if (msg_result != MACH_MSG_SUCCESS) {
__MachMsgErrorWithoutTimeout(msg_result);
{ return msg_result; }
}
#if defined(__MIG_check__Reply__mach_exception_raise_state_identity_t__defined)
check_result = __MIG_check__Reply__mach_exception_raise_state_identity_t((__Reply__mach_exception_raise_state_identity_t *)Out0P);
if (check_result != MACH_MSG_SUCCESS)
{ return check_result; }
#endif /* defined(__MIG_check__Reply__mach_exception_raise_state_identity_t__defined) */
*flavor = Out0P->flavor;
if (Out0P->new_stateCnt > 224) {
(void)memcpy((char *) new_state, (const char *) Out0P->new_state, 4 * 224);
*new_stateCnt = Out0P->new_stateCnt;
{ return MIG_ARRAY_TOO_LARGE; }
}
(void)memcpy((char *) new_state, (const char *) Out0P->new_state, 4 * Out0P->new_stateCnt);
*new_stateCnt = Out0P->new_stateCnt;
return KERN_SUCCESS;
}

57
vendor/github.com/derekparker/delve/proc/mem.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
package proc
const cacheEnabled = true
type memoryReadWriter interface {
readMemory(addr uintptr, size int) (data []byte, err error)
writeMemory(addr uintptr, data []byte) (written int, err error)
}
type memCache struct {
cacheAddr uintptr
cache []byte
mem memoryReadWriter
}
func (m *memCache) contains(addr uintptr, size int) bool {
return addr >= m.cacheAddr && addr <= (m.cacheAddr+uintptr(len(m.cache)-size))
}
func (m *memCache) readMemory(addr uintptr, size int) (data []byte, err error) {
if m.contains(addr, size) {
d := make([]byte, size)
copy(d, m.cache[addr-m.cacheAddr:])
return d, nil
}
return m.mem.readMemory(addr, size)
}
func (m *memCache) writeMemory(addr uintptr, data []byte) (written int, err error) {
return m.mem.writeMemory(addr, data)
}
func cacheMemory(mem memoryReadWriter, addr uintptr, size int) memoryReadWriter {
if !cacheEnabled {
return mem
}
if size <= 0 {
return mem
}
if cacheMem, isCache := mem.(*memCache); isCache {
if cacheMem.contains(addr, size) {
return mem
} else {
cache, err := cacheMem.mem.readMemory(addr, size)
if err != nil {
return mem
}
return &memCache{addr, cache, mem}
}
}
cache, err := mem.readMemory(addr, size)
if err != nil {
return mem
}
return &memCache{addr, cache, mem}
}

189
vendor/github.com/derekparker/delve/proc/moduledata.go generated vendored Normal file
View File

@ -0,0 +1,189 @@
package proc
import (
"go/constant"
"unsafe"
)
// delve counterpart to runtime.moduledata
type moduleData struct {
types, etypes uintptr
typemapVar *Variable
}
func (dbp *Process) loadModuleData() (err error) {
dbp.loadModuleDataOnce.Do(func() {
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
var md *Variable
md, err = scope.packageVarAddr("runtime.firstmoduledata")
if err != nil {
return
}
for md.Addr != 0 {
var typesVar, etypesVar, nextVar, typemapVar *Variable
var types, etypes uint64
if typesVar, err = md.structMember("types"); err != nil {
return
}
if etypesVar, err = md.structMember("etypes"); err != nil {
return
}
if nextVar, err = md.structMember("next"); err != nil {
return
}
if typemapVar, err = md.structMember("typemap"); err != nil {
return
}
if types, err = typesVar.asUint(); err != nil {
return
}
if etypes, err = etypesVar.asUint(); err != nil {
return
}
dbp.moduleData = append(dbp.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar})
md = nextVar.maybeDereference()
if md.Unreadable != nil {
err = md.Unreadable
return
}
}
})
return
}
func (dbp *Process) resolveTypeOff(typeAddr uintptr, off uintptr) (*Variable, error) {
// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go
if err := dbp.loadModuleData(); err != nil {
return nil, err
}
var md *moduleData
for i := range dbp.moduleData {
if typeAddr >= dbp.moduleData[i].types && typeAddr < dbp.moduleData[i].etypes {
md = &dbp.moduleData[i]
}
}
rtyp, err := dbp.findType("runtime._type")
if err != nil {
return nil, err
}
if md == nil {
v, err := dbp.reflectOffsMapAccess(off)
if err != nil {
return nil, err
}
v.loadValue(LoadConfig{false, 1, 0, 0, -1})
addr, _ := constant.Int64Val(v.Value)
return v.newVariable(v.Name, uintptr(addr), rtyp), nil
}
if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread)); t != nil {
return t, nil
}
res := md.types + uintptr(off)
return dbp.CurrentThread.newVariable("", res, rtyp), nil
}
func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (name, tag string, pkgpathoff int32, err error) {
// See runtime.resolveNameOff in $GOROOT/src/runtime/type.go
if err = dbp.loadModuleData(); err != nil {
return "", "", 0, err
}
for _, md := range dbp.moduleData {
if typeAddr >= md.types && typeAddr < md.etypes {
return dbp.loadName(md.types + off)
}
}
v, err := dbp.reflectOffsMapAccess(off)
if err != nil {
return "", "", 0, err
}
resv := v.maybeDereference()
if resv.Unreadable != nil {
return "", "", 0, resv.Unreadable
}
return dbp.loadName(resv.Addr)
}
func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) {
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs")
if err != nil {
return nil, err
}
reflectOffsm, err := reflectOffs.structMember("m")
if err != nil {
return nil, err
}
return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread))
}
const (
// flags for the name struct (see 'type name struct' in $GOROOT/src/reflect/type.go)
nameflagExported = 1 << 0
nameflagHasTag = 1 << 1
nameflagHasPkg = 1 << 2
)
func (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32, err error) {
off := addr
namedata, err := dbp.CurrentThread.readMemory(off, 3)
off += 3
if err != nil {
return "", "", 0, err
}
namelen := uint16(namedata[1]<<8) | uint16(namedata[2])
rawstr, err := dbp.CurrentThread.readMemory(off, int(namelen))
off += uintptr(namelen)
if err != nil {
return "", "", 0, err
}
name = string(rawstr)
if namedata[0]&nameflagHasTag != 0 {
taglendata, err := dbp.CurrentThread.readMemory(off, 2)
off += 2
if err != nil {
return "", "", 0, err
}
taglen := uint16(taglendata[0]<<8) | uint16(taglendata[1])
rawstr, err := dbp.CurrentThread.readMemory(off, int(taglen))
off += uintptr(taglen)
if err != nil {
return "", "", 0, err
}
tag = string(rawstr)
}
if namedata[0]&nameflagHasPkg != 0 {
pkgdata, err := dbp.CurrentThread.readMemory(off, 4)
if err != nil {
return "", "", 0, err
}
// see func pkgPath in $GOROOT/src/reflect/type.go
copy((*[4]byte)(unsafe.Pointer(&pkgpathoff))[:], pkgdata)
}
return name, tag, pkgpathoff, nil
}

942
vendor/github.com/derekparker/delve/proc/proc.go generated vendored Normal file
View File

@ -0,0 +1,942 @@
package proc
import (
"debug/gosym"
"encoding/binary"
"errors"
"fmt"
"go/ast"
"go/constant"
"go/token"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
"github.com/derekparker/delve/dwarf/reader"
"golang.org/x/debug/dwarf"
)
// Process represents all of the information the debugger
// is holding onto regarding the process we are debugging.
type Process struct {
Pid int // Process Pid
Process *os.Process // Pointer to process struct for the actual process we are debugging
LastModified time.Time // Time the executable of this process was last modified
// Breakpoint table, holds information on breakpoints.
// Maps instruction address to Breakpoint struct.
Breakpoints map[uint64]*Breakpoint
// List of threads mapped as such: pid -> *Thread
Threads map[int]*Thread
// Active thread
CurrentThread *Thread
// Goroutine that will be used by default to set breakpoint, eval variables, etc...
// Normally SelectedGoroutine is CurrentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
SelectedGoroutine *G
// Maps package names to package paths, needed to lookup types inside DWARF info
packageMap map[string]string
allGCache []*G
dwarf *dwarf.Data
goSymTable *gosym.Table
frameEntries frame.FrameDescriptionEntries
lineInfo line.DebugLines
os *OSProcessDetails
arch Arch
breakpointIDCounter int
internalBreakpointIDCounter int
firstStart bool
halt bool
exited bool
ptraceChan chan func()
ptraceDoneChan chan interface{}
types map[string]dwarf.Offset
loadModuleDataOnce sync.Once
moduleData []moduleData
nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry
}
var NotExecutableErr = errors.New("not an executable file")
// New returns an initialized Process struct. Before returning,
// it will also launch a goroutine in order to handle ptrace(2)
// functions. For more information, see the documentation on
// `handlePtraceFuncs`.
func New(pid int) *Process {
dbp := &Process{
Pid: pid,
Threads: make(map[int]*Thread),
Breakpoints: make(map[uint64]*Breakpoint),
firstStart: true,
os: new(OSProcessDetails),
ptraceChan: make(chan func()),
ptraceDoneChan: make(chan interface{}),
nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry),
}
// TODO: find better way to determine proc arch (perhaps use executable file info)
switch runtime.GOARCH {
case "amd64":
dbp.arch = AMD64Arch()
}
go dbp.handlePtraceFuncs()
return dbp
}
// ProcessExitedError indicates that the process has exited and contains both
// process id and exit status.
type ProcessExitedError struct {
Pid int
Status int
}
func (pe ProcessExitedError) Error() string {
return fmt.Sprintf("Process %d has exited with status %d", pe.Pid, pe.Status)
}
// Detach from the process being debugged, optionally killing it.
func (dbp *Process) Detach(kill bool) (err error) {
if dbp.Running() {
if err = dbp.Halt(); err != nil {
return
}
}
if !kill {
// Clean up any breakpoints we've set.
for _, bp := range dbp.Breakpoints {
if bp != nil {
_, err := dbp.ClearBreakpoint(bp.Addr)
if err != nil {
return err
}
}
}
}
dbp.execPtraceFunc(func() {
err = PtraceDetach(dbp.Pid, 0)
if err != nil {
return
}
if kill {
err = killProcess(dbp.Pid)
}
})
return
}
// Exited returns whether the debugged
// process has exited.
func (dbp *Process) Exited() bool {
return dbp.exited
}
// Running returns whether the debugged
// process is currently executing.
func (dbp *Process) Running() bool {
for _, th := range dbp.Threads {
if th.running {
return true
}
}
return false
}
// LoadInformation finds the executable and then uses it
// to parse the following information:
// * Dwarf .debug_frame section
// * Dwarf .debug_line section
// * Go symbol table.
func (dbp *Process) LoadInformation(path string) error {
var wg sync.WaitGroup
exe, path, err := dbp.findExecutable(path)
if err != nil {
return err
}
fi, err := os.Stat(path)
if err == nil {
dbp.LastModified = fi.ModTime()
}
wg.Add(5)
go dbp.loadProcessInformation(&wg)
go dbp.parseDebugFrame(exe, &wg)
go dbp.obtainGoSymbols(exe, &wg)
go dbp.parseDebugLineInfo(exe, &wg)
go dbp.loadTypeMap(&wg)
wg.Wait()
return nil
}
// FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normailzed to lower case and '/' on Windows.
func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) {
pc, fn, err := dbp.goSymTable.LineToPC(fileName, lineno)
if err != nil {
return 0, err
}
if fn.Entry == pc {
pc, _ = dbp.FirstPCAfterPrologue(fn, true)
}
return pc, nil
}
// FindFunctionLocation finds address of a function's line
// If firstLine == true is passed FindFunctionLocation will attempt to find the first line of the function
// If lineOffset is passed FindFunctionLocation will return the address of that line
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
// Note that setting breakpoints at that address will cause surprising behavior:
// https://github.com/derekparker/delve/issues/170
func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) {
origfn := dbp.goSymTable.LookupFunc(funcName)
if origfn == nil {
return 0, fmt.Errorf("Could not find function %s\n", funcName)
}
if firstLine {
return dbp.FirstPCAfterPrologue(origfn, false)
} else if lineOffset > 0 {
filename, lineno, _ := dbp.goSymTable.PCToLine(origfn.Entry)
breakAddr, _, err := dbp.goSymTable.LineToPC(filename, lineno+lineOffset)
return breakAddr, err
}
return origfn.Entry, nil
}
// CurrentLocation returns the location of the current thread.
func (dbp *Process) CurrentLocation() (*Location, error) {
return dbp.CurrentThread.Location()
}
// RequestManualStop sets the `halt` flag and
// sends SIGSTOP to all threads.
func (dbp *Process) RequestManualStop() error {
if dbp.exited {
return &ProcessExitedError{}
}
dbp.halt = true
return dbp.requestManualStop()
}
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
// break point table. Setting a break point must be thread specific due to
// ptrace actions needing the thread to be in a signal-delivery-stop.
func (dbp *Process) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error) {
tid := dbp.CurrentThread.ID
if bp, ok := dbp.FindBreakpoint(addr); ok {
return nil, BreakpointExistsError{bp.File, bp.Line, bp.Addr}
}
f, l, fn := dbp.goSymTable.PCToLine(uint64(addr))
if fn == nil {
return nil, InvalidAddressError{address: addr}
}
newBreakpoint := &Breakpoint{
FunctionName: fn.Name,
File: f,
Line: l,
Addr: addr,
Kind: kind,
Cond: cond,
HitCount: map[int]uint64{},
}
if kind != UserBreakpoint {
dbp.internalBreakpointIDCounter++
newBreakpoint.ID = dbp.internalBreakpointIDCounter
} else {
dbp.breakpointIDCounter++
newBreakpoint.ID = dbp.breakpointIDCounter
}
thread := dbp.Threads[tid]
originalData, err := thread.readMemory(uintptr(addr), dbp.arch.BreakpointSize())
if err != nil {
return nil, err
}
if err := dbp.writeSoftwareBreakpoint(thread, addr); err != nil {
return nil, err
}
newBreakpoint.OriginalData = originalData
dbp.Breakpoints[addr] = newBreakpoint
return newBreakpoint, nil
}
// ClearBreakpoint clears the breakpoint at addr.
func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
if dbp.exited {
return nil, &ProcessExitedError{}
}
bp, ok := dbp.FindBreakpoint(addr)
if !ok {
return nil, NoBreakpointError{addr: addr}
}
if _, err := bp.Clear(dbp.CurrentThread); err != nil {
return nil, err
}
delete(dbp.Breakpoints, addr)
return bp, nil
}
// Status returns the status of the current main thread context.
func (dbp *Process) Status() *WaitStatus {
return dbp.CurrentThread.Status
}
// Next continues execution until the next source line.
func (dbp *Process) Next() (err error) {
if dbp.exited {
return &ProcessExitedError{}
}
for i := range dbp.Breakpoints {
if dbp.Breakpoints[i].Internal() {
return fmt.Errorf("next while nexting")
}
}
if err = dbp.next(false); err != nil {
switch err.(type) {
case ThreadBlockedError, NoReturnAddr: // Noop
default:
dbp.ClearInternalBreakpoints()
return
}
}
return dbp.Continue()
}
// Continue continues execution of the debugged
// process. It will continue until it hits a breakpoint
// or is otherwise stopped.
func (dbp *Process) Continue() error {
if dbp.exited {
return &ProcessExitedError{}
}
for {
if err := dbp.resume(); err != nil {
return err
}
dbp.allGCache = nil
for _, th := range dbp.Threads {
th.clearBreakpointState()
}
trapthread, err := dbp.trapWait(-1)
if err != nil {
return err
}
if err := dbp.Halt(); err != nil {
return dbp.exitGuard(err)
}
if err := dbp.setCurrentBreakpoints(trapthread); err != nil {
return err
}
if err := dbp.pickCurrentThread(trapthread); err != nil {
return err
}
switch {
case dbp.CurrentThread.CurrentBreakpoint == nil:
// runtime.Breakpoint or manual stop
if dbp.CurrentThread.onRuntimeBreakpoint() {
for i := 0; i < 2; i++ {
if err = dbp.CurrentThread.StepInstruction(); err != nil {
return err
}
}
}
return dbp.conditionErrors()
case dbp.CurrentThread.onTriggeredInternalBreakpoint():
if dbp.CurrentThread.CurrentBreakpoint.Kind == StepBreakpoint {
// See description of proc.(*Process).next for the meaning of StepBreakpoints
if err := dbp.conditionErrors(); err != nil {
return err
}
pc, err := dbp.CurrentThread.PC()
if err != nil {
return err
}
text, err := dbp.CurrentThread.Disassemble(pc, pc+maxInstructionLength, true)
if err != nil {
return err
}
// here we either set a breakpoint into the destination of the CALL
// instruction or we determined that the called function is hidden,
// either way we need to resume execution
if err = dbp.setStepIntoBreakpoint(text, sameGoroutineCondition(dbp.SelectedGoroutine)); err != nil {
return err
}
} else {
if err := dbp.ClearInternalBreakpoints(); err != nil {
return err
}
return dbp.conditionErrors()
}
case dbp.CurrentThread.onTriggeredBreakpoint():
onNextGoroutine, err := dbp.CurrentThread.onNextGoroutine()
if err != nil {
return err
}
if onNextGoroutine {
err := dbp.ClearInternalBreakpoints()
if err != nil {
return err
}
}
return dbp.conditionErrors()
default:
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
}
}
}
func (dbp *Process) conditionErrors() error {
var condErr error
for _, th := range dbp.Threads {
if th.CurrentBreakpoint != nil && th.BreakpointConditionError != nil {
if condErr == nil {
condErr = th.BreakpointConditionError
} else {
return fmt.Errorf("multiple errors evaluating conditions")
}
}
}
return condErr
}
// pick a new dbp.CurrentThread, with the following priority:
// - a thread with onTriggeredInternalBreakpoint() == true
// - a thread with onTriggeredBreakpoint() == true (prioritizing trapthread)
// - trapthread
func (dbp *Process) pickCurrentThread(trapthread *Thread) error {
for _, th := range dbp.Threads {
if th.onTriggeredInternalBreakpoint() {
return dbp.SwitchThread(th.ID)
}
}
if trapthread.onTriggeredBreakpoint() {
return dbp.SwitchThread(trapthread.ID)
}
for _, th := range dbp.Threads {
if th.onTriggeredBreakpoint() {
return dbp.SwitchThread(th.ID)
}
}
return dbp.SwitchThread(trapthread.ID)
}
// Step will continue until another source line is reached.
// Will step into functions.
func (dbp *Process) Step() (err error) {
if dbp.exited {
return &ProcessExitedError{}
}
for i := range dbp.Breakpoints {
if dbp.Breakpoints[i].Internal() {
return fmt.Errorf("next while nexting")
}
}
if err = dbp.next(true); err != nil {
switch err.(type) {
case ThreadBlockedError, NoReturnAddr: // Noop
default:
dbp.ClearInternalBreakpoints()
return
}
}
return dbp.Continue()
}
// Returns an expression that evaluates to true when the current goroutine is g
func sameGoroutineCondition(g *G) ast.Expr {
if g == nil {
return nil
}
return &ast.BinaryExpr{
Op: token.EQL,
X: &ast.SelectorExpr{
X: &ast.SelectorExpr{
X: &ast.Ident{Name: "runtime"},
Sel: &ast.Ident{Name: "curg"},
},
Sel: &ast.Ident{Name: "goid"},
},
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(g.ID)},
}
}
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// asssociated with the selected goroutine. All other
// threads will remain stopped.
func (dbp *Process) StepInstruction() (err error) {
if dbp.SelectedGoroutine == nil {
return errors.New("cannot single step: no selected goroutine")
}
if dbp.SelectedGoroutine.thread == nil {
// Step called on parked goroutine
if _, err := dbp.SetBreakpoint(dbp.SelectedGoroutine.PC, NextBreakpoint, sameGoroutineCondition(dbp.SelectedGoroutine)); err != nil {
return err
}
return dbp.Continue()
}
dbp.allGCache = nil
if dbp.exited {
return &ProcessExitedError{}
}
dbp.SelectedGoroutine.thread.clearBreakpointState()
err = dbp.SelectedGoroutine.thread.StepInstruction()
if err != nil {
return err
}
return dbp.SelectedGoroutine.thread.SetCurrentBreakpoint()
}
// StepOut will continue until the current goroutine exits the
// function currently being executed or a deferred function is executed
func (dbp *Process) StepOut() error {
cond := sameGoroutineCondition(dbp.SelectedGoroutine)
topframe, err := topframe(dbp.SelectedGoroutine, dbp.CurrentThread)
if err != nil {
return err
}
pcs := []uint64{}
var deferpc uint64 = 0
if filepath.Ext(topframe.Current.File) == ".go" {
if dbp.SelectedGoroutine != nil && dbp.SelectedGoroutine.DeferPC != 0 {
_, _, deferfn := dbp.goSymTable.PCToLine(dbp.SelectedGoroutine.DeferPC)
deferpc, err = dbp.FirstPCAfterPrologue(deferfn, false)
if err != nil {
return err
}
pcs = append(pcs, deferpc)
}
}
if topframe.Ret == 0 && deferpc == 0 {
return errors.New("nothing to stepout to")
}
if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond)
if err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
return err
}
}
if bp != nil {
// For StepOut we do not want to step into the deferred function
// when it's called by runtime.deferreturn so we do not populate
// DeferReturns.
bp.DeferReturns = []uint64{}
}
}
if topframe.Ret != 0 {
if err := dbp.setInternalBreakpoints(topframe.Current.PC, []uint64{topframe.Ret}, NextBreakpoint, cond); err != nil {
return err
}
}
return dbp.Continue()
}
// SwitchThread changes from current thread to the thread specified by `tid`.
func (dbp *Process) SwitchThread(tid int) error {
if dbp.exited {
return &ProcessExitedError{}
}
if th, ok := dbp.Threads[tid]; ok {
dbp.CurrentThread = th
dbp.SelectedGoroutine, _ = dbp.CurrentThread.GetG()
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// SwitchGoroutine changes from current thread to the thread
// running the specified goroutine.
func (dbp *Process) SwitchGoroutine(gid int) error {
if dbp.exited {
return &ProcessExitedError{}
}
g, err := dbp.FindGoroutine(gid)
if err != nil {
return err
}
if g == nil {
// user specified -1 and SelectedGoroutine is nil
return nil
}
if g.thread != nil {
return dbp.SwitchThread(g.thread.ID)
}
dbp.SelectedGoroutine = g
return nil
}
// GoroutinesInfo returns an array of G structures representing the information
// Delve cares about from the internal runtime G structure.
func (dbp *Process) GoroutinesInfo() ([]*G, error) {
if dbp.exited {
return nil, &ProcessExitedError{}
}
if dbp.allGCache != nil {
return dbp.allGCache, nil
}
var (
threadg = map[int]*Thread{}
allg []*G
rdr = dbp.DwarfReader()
)
for i := range dbp.Threads {
if dbp.Threads[i].blocked() {
continue
}
g, _ := dbp.Threads[i].GetG()
if g != nil {
threadg[g.ID] = dbp.Threads[i]
}
}
addr, err := rdr.AddrFor("runtime.allglen")
if err != nil {
return nil, err
}
allglenBytes, err := dbp.CurrentThread.readMemory(uintptr(addr), 8)
if err != nil {
return nil, err
}
allglen := binary.LittleEndian.Uint64(allglenBytes)
rdr.Seek(0)
allgentryaddr, err := rdr.AddrFor("runtime.allgs")
if err != nil {
// try old name (pre Go 1.6)
allgentryaddr, err = rdr.AddrFor("runtime.allg")
if err != nil {
return nil, err
}
}
faddr, err := dbp.CurrentThread.readMemory(uintptr(allgentryaddr), dbp.arch.PtrSize())
allgptr := binary.LittleEndian.Uint64(faddr)
for i := uint64(0); i < allglen; i++ {
gvar, err := dbp.CurrentThread.newGVariable(uintptr(allgptr+(i*uint64(dbp.arch.PtrSize()))), true)
if err != nil {
return nil, err
}
g, err := gvar.parseG()
if err != nil {
return nil, err
}
if thread, allocated := threadg[g.ID]; allocated {
loc, err := thread.Location()
if err != nil {
return nil, err
}
g.thread = thread
// Prefer actual thread location information.
g.CurrentLoc = *loc
}
if g.Status != Gdead {
allg = append(allg, g)
}
}
dbp.allGCache = allg
return allg, nil
}
func (g *G) Thread() *Thread {
return g.thread
}
// Halt stops all threads.
func (dbp *Process) Halt() (err error) {
if dbp.exited {
return &ProcessExitedError{}
}
for _, th := range dbp.Threads {
if err := th.Halt(); err != nil {
return err
}
}
return nil
}
// Registers obtains register values from the
// "current" thread of the traced process.
func (dbp *Process) Registers() (Registers, error) {
return dbp.CurrentThread.Registers(false)
}
// PC returns the PC of the current thread.
func (dbp *Process) PC() (uint64, error) {
return dbp.CurrentThread.PC()
}
// CurrentBreakpoint returns the breakpoint the current thread
// is stopped at.
func (dbp *Process) CurrentBreakpoint() *Breakpoint {
return dbp.CurrentThread.CurrentBreakpoint
}
// DwarfReader returns a reader for the dwarf data
func (dbp *Process) DwarfReader() *reader.Reader {
return reader.New(dbp.dwarf)
}
// Sources returns list of source files that comprise the debugged binary.
func (dbp *Process) Sources() map[string]*gosym.Obj {
return dbp.goSymTable.Files
}
// Funcs returns list of functions present in the debugged program.
func (dbp *Process) Funcs() []gosym.Func {
return dbp.goSymTable.Funcs
}
// Types returns list of types present in the debugged program.
func (dbp *Process) Types() ([]string, error) {
types := make([]string, 0, len(dbp.types))
for k := range dbp.types {
types = append(types, k)
}
return types, nil
}
// PCToLine converts an instruction address to a file/line/function.
func (dbp *Process) PCToLine(pc uint64) (string, int, *gosym.Func) {
return dbp.goSymTable.PCToLine(pc)
}
// FindBreakpointByID finds the breakpoint for the given ID.
func (dbp *Process) FindBreakpointByID(id int) (*Breakpoint, bool) {
for _, bp := range dbp.Breakpoints {
if bp.ID == id {
return bp, true
}
}
return nil, false
}
// FindBreakpoint finds the breakpoint for the given pc.
func (dbp *Process) FindBreakpoint(pc uint64) (*Breakpoint, bool) {
// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
if bp, ok := dbp.Breakpoints[pc-uint64(dbp.arch.BreakpointSize())]; ok {
return bp, true
}
// Directly use addr to lookup breakpoint.
if bp, ok := dbp.Breakpoints[pc]; ok {
return bp, true
}
return nil, false
}
// Returns a new Process struct.
func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, error) {
if attach {
var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.Pid) })
if err != nil {
return nil, err
}
_, _, err = dbp.wait(dbp.Pid, 0)
if err != nil {
return nil, err
}
}
proc, err := os.FindProcess(dbp.Pid)
if err != nil {
return nil, err
}
dbp.Process = proc
err = dbp.LoadInformation(path)
if err != nil {
return nil, err
}
if err := dbp.updateThreadList(); err != nil {
return nil, err
}
ver, isextld, err := dbp.getGoInformation()
if err != nil {
return nil, err
}
dbp.arch.SetGStructOffset(ver, isextld)
// SelectedGoroutine can not be set correctly by the call to updateThreadList
// because without calling SetGStructOffset we can not read the G struct of CurrentThread
// but without calling updateThreadList we can not examine memory to determine
// the offset of g struct inside TLS
dbp.SelectedGoroutine, _ = dbp.CurrentThread.GetG()
panicpc, err := dbp.FindFunctionLocation("runtime.startpanic", true, 0)
if err == nil {
bp, err := dbp.SetBreakpoint(panicpc, UserBreakpoint, nil)
if err == nil {
bp.Name = "unrecovered-panic"
bp.ID = -1
dbp.breakpointIDCounter--
}
}
return dbp, nil
}
func (dbp *Process) ClearInternalBreakpoints() error {
for _, bp := range dbp.Breakpoints {
if !bp.Internal() {
continue
}
if _, err := dbp.ClearBreakpoint(bp.Addr); err != nil {
return err
}
}
for i := range dbp.Threads {
if dbp.Threads[i].CurrentBreakpoint != nil && dbp.Threads[i].CurrentBreakpoint.Internal() {
dbp.Threads[i].CurrentBreakpoint = nil
}
}
return nil
}
func (dbp *Process) handlePtraceFuncs() {
// We must ensure here that we are running on the same thread during
// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
// all commands after PTRACE_ATTACH to come from the same thread.
runtime.LockOSThread()
for fn := range dbp.ptraceChan {
fn()
dbp.ptraceDoneChan <- nil
}
}
func (dbp *Process) execPtraceFunc(fn func()) {
dbp.ptraceChan <- fn
<-dbp.ptraceDoneChan
}
func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) {
vv, err := dbp.EvalPackageVariable("runtime.buildVersion", LoadConfig{true, 0, 64, 0, 0})
if err != nil {
err = fmt.Errorf("Could not determine version number: %v\n", err)
return
}
if vv.Unreadable != nil {
err = fmt.Errorf("Unreadable version number: %v\n", vv.Unreadable)
return
}
ver, ok := ParseVersionString(constant.StringVal(vv.Value))
if !ok {
err = fmt.Errorf("Could not parse version number: %v\n", vv.Value)
return
}
rdr := dbp.DwarfReader()
rdr.Seek(0)
for entry, err := rdr.NextCompileUnit(); entry != nil; entry, err = rdr.NextCompileUnit() {
if err != nil {
return ver, isextld, err
}
if prod, ok := entry.Val(dwarf.AttrProducer).(string); ok && (strings.HasPrefix(prod, "GNU AS")) {
isextld = true
break
}
}
return
}
// FindGoroutine returns a G struct representing the goroutine
// specified by `gid`.
func (dbp *Process) FindGoroutine(gid int) (*G, error) {
if gid == -1 {
return dbp.SelectedGoroutine, nil
}
gs, err := dbp.GoroutinesInfo()
if err != nil {
return nil, err
}
for i := range gs {
if gs[i].ID == gid {
return gs[i], nil
}
}
return nil, fmt.Errorf("Unknown goroutine %d", gid)
}
// ConvertEvalScope returns a new EvalScope in the context of the
// specified goroutine ID and stack frame.
func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) {
if dbp.exited {
return nil, &ProcessExitedError{}
}
g, err := dbp.FindGoroutine(gid)
if err != nil {
return nil, err
}
if g == nil {
return dbp.CurrentThread.Scope()
}
var out EvalScope
if g.thread == nil {
out.Thread = dbp.CurrentThread
} else {
out.Thread = g.thread
}
locs, err := g.Stacktrace(frame)
if err != nil {
return nil, err
}
if frame >= len(locs) {
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
}
out.PC, out.CFA = locs[frame].Current.PC, locs[frame].CFA
return &out, nil
}
func (dbp *Process) postExit() {
dbp.exited = true
close(dbp.ptraceChan)
close(dbp.ptraceDoneChan)
}

231
vendor/github.com/derekparker/delve/proc/proc_darwin.c generated vendored Normal file
View File

@ -0,0 +1,231 @@
#include "proc_darwin.h"
static const unsigned char info_plist[]
__attribute__ ((section ("__TEXT,__info_plist"),used)) =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\""
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
"<dict>\n"
" <key>CFBundleIdentifier</key>\n"
" <string>org.dlv</string>\n"
" <key>CFBundleName</key>\n"
" <string>delve</string>\n"
" <key>CFBundleVersion</key>\n"
" <string>1.0</string>\n"
" <key>SecTaskAccess</key>\n"
" <array>\n"
" <string>allowed</string>\n"
" <string>debug</string>\n"
" </array>\n"
"</dict>\n"
"</plist>\n";
kern_return_t
acquire_mach_task(int tid,
task_t *task,
mach_port_t *port_set,
mach_port_t *exception_port,
mach_port_t *notification_port)
{
kern_return_t kret;
mach_port_t prev_not;
mach_port_t self = mach_task_self();
kret = task_for_pid(self, tid, task);
if (kret != KERN_SUCCESS) return kret;
// Allocate exception port.
kret = mach_port_allocate(self, MACH_PORT_RIGHT_RECEIVE, exception_port);
if (kret != KERN_SUCCESS) return kret;
kret = mach_port_insert_right(self, *exception_port, *exception_port, MACH_MSG_TYPE_MAKE_SEND);
if (kret != KERN_SUCCESS) return kret;
kret = task_set_exception_ports(*task, EXC_MASK_BREAKPOINT|EXC_MASK_SOFTWARE, *exception_port,
EXCEPTION_DEFAULT, THREAD_STATE_NONE);
if (kret != KERN_SUCCESS) return kret;
// Allocate notification port to alert of when the process dies.
kret = mach_port_allocate(self, MACH_PORT_RIGHT_RECEIVE, notification_port);
if (kret != KERN_SUCCESS) return kret;
kret = mach_port_insert_right(self, *notification_port, *notification_port, MACH_MSG_TYPE_MAKE_SEND);
if (kret != KERN_SUCCESS) return kret;
kret = mach_port_request_notification(self, *task, MACH_NOTIFY_DEAD_NAME, 0, *notification_port,
MACH_MSG_TYPE_MAKE_SEND_ONCE, &prev_not);
if (kret != KERN_SUCCESS) return kret;
// Create port set.
kret = mach_port_allocate(self, MACH_PORT_RIGHT_PORT_SET, port_set);
if (kret != KERN_SUCCESS) return kret;
// Move exception and notification ports to port set.
kret = mach_port_move_member(self, *exception_port, *port_set);
if (kret != KERN_SUCCESS) return kret;
return mach_port_move_member(self, *notification_port, *port_set);
}
kern_return_t
reset_exception_ports(task_t task, mach_port_t *exception_port, mach_port_t *notification_port) {
kern_return_t kret;
mach_port_t prev_not;
mach_port_t self = mach_task_self();
kret = task_set_exception_ports(task, EXC_MASK_BREAKPOINT|EXC_MASK_SOFTWARE, *exception_port,
EXCEPTION_DEFAULT, THREAD_STATE_NONE);
if (kret != KERN_SUCCESS) return kret;
kret = mach_port_request_notification(self, task, MACH_NOTIFY_DEAD_NAME, 0, *notification_port,
MACH_MSG_TYPE_MAKE_SEND_ONCE, &prev_not);
if (kret != KERN_SUCCESS) return kret;
return KERN_SUCCESS;
}
char *
find_executable(int pid) {
static char pathbuf[PATH_MAX];
proc_pidpath(pid, pathbuf, PATH_MAX);
return pathbuf;
}
kern_return_t
get_threads(task_t task, void *slice, int limit) {
kern_return_t kret;
thread_act_array_t list;
mach_msg_type_number_t count;
kret = task_threads(task, &list, &count);
if (kret != KERN_SUCCESS) {
return kret;
}
if (count > limit) {
vm_deallocate(mach_task_self(), (vm_address_t) list, count * sizeof(list[0]));
return -2;
}
memcpy(slice, (void*)list, count*sizeof(list[0]));
kret = vm_deallocate(mach_task_self(), (vm_address_t) list, count * sizeof(list[0]));
if (kret != KERN_SUCCESS) return kret;
return (kern_return_t)0;
}
int
thread_count(task_t task) {
kern_return_t kret;
thread_act_array_t list;
mach_msg_type_number_t count;
kret = task_threads(task, &list, &count);
if (kret != KERN_SUCCESS) return -1;
kret = vm_deallocate(mach_task_self(), (vm_address_t) list, count * sizeof(list[0]));
if (kret != KERN_SUCCESS) return -1;
return count;
}
mach_port_t
mach_port_wait(mach_port_t port_set, task_t *task, int nonblocking) {
kern_return_t kret;
thread_act_t thread;
NDR_record_t *ndr;
integer_t *data;
union
{
mach_msg_header_t hdr;
char data[256];
} msg;
mach_msg_option_t opts = MACH_RCV_MSG|MACH_RCV_INTERRUPT;
if (nonblocking) {
opts |= MACH_RCV_TIMEOUT;
}
// Wait for mach msg.
kret = mach_msg(&msg.hdr, opts,
0, sizeof(msg.data), port_set, 10, MACH_PORT_NULL);
if (kret == MACH_RCV_INTERRUPTED) return kret;
if (kret != MACH_MSG_SUCCESS) return 0;
switch (msg.hdr.msgh_id) {
case 2401: { // Exception
// 2401 is the exception_raise event, defined in:
// http://opensource.apple.com/source/xnu/xnu-2422.1.72/osfmk/mach/exc.defs?txt
// compile this file with mig to get the C version of the description
mach_msg_body_t *bod = (mach_msg_body_t*)(&msg.hdr + 1);
mach_msg_port_descriptor_t *desc = (mach_msg_port_descriptor_t *)(bod + 1);
thread = desc[0].name;
*task = desc[1].name;
ndr = (NDR_record_t *)(desc + 2);
data = (integer_t *)(ndr + 1);
if (thread_suspend(thread) != KERN_SUCCESS) return 0;
// Send our reply back so the kernel knows this exception has been handled.
kret = mach_send_reply(msg.hdr);
if (kret != MACH_MSG_SUCCESS) return 0;
if (data[2] == EXC_SOFT_SIGNAL) {
if (data[3] != SIGTRAP) {
if (thread_resume(thread) != KERN_SUCCESS) return 0;
return mach_port_wait(port_set, task, nonblocking);
}
}
return thread;
}
case 72: { // Death
// 72 is mach_notify_dead_name, defined in:
// https://opensource.apple.com/source/xnu/xnu-1228.7.58/osfmk/mach/notify.defs?txt
// compile this file with mig to get the C version of the description
ndr = (NDR_record_t *)(&msg.hdr + 1);
*task = *((mach_port_name_t *)(ndr + 1));
return msg.hdr.msgh_local_port;
}
}
return 0;
}
kern_return_t
mach_send_reply(mach_msg_header_t hdr) {
mig_reply_error_t reply;
mach_msg_header_t *rh = &reply.Head;
rh->msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(hdr.msgh_bits), 0);
rh->msgh_remote_port = hdr.msgh_remote_port;
rh->msgh_size = (mach_msg_size_t) sizeof(mig_reply_error_t);
rh->msgh_local_port = MACH_PORT_NULL;
rh->msgh_id = hdr.msgh_id + 100;
reply.NDR = NDR_record;
reply.RetCode = KERN_SUCCESS;
return mach_msg(&reply.Head, MACH_SEND_MSG|MACH_SEND_INTERRUPT, rh->msgh_size, 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
}
kern_return_t
raise_exception(mach_port_t task, mach_port_t thread, mach_port_t exception_port, exception_type_t exception) {
return exception_raise(exception_port, thread, task, exception, 0, 0);
}
task_t
get_task_for_pid(int pid) {
task_t task = 0;
mach_port_t self = mach_task_self();
task_for_pid(self, pid, &task);
return task;
}
int
task_is_valid(task_t task) {
struct task_basic_info info;
mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
return task_info(task, TASK_BASIC_INFO, (task_info_t)&info, &count) == KERN_SUCCESS;
}

511
vendor/github.com/derekparker/delve/proc/proc_darwin.go generated vendored Normal file
View File

@ -0,0 +1,511 @@
package proc
// #include "proc_darwin.h"
// #include "threads_darwin.h"
// #include "exec_darwin.h"
// #include <stdlib.h>
import "C"
import (
"debug/gosym"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"unsafe"
"golang.org/x/debug/macho"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
sys "golang.org/x/sys/unix"
)
// OSProcessDetails holds Darwin specific information.
type OSProcessDetails struct {
task C.task_t // mach task for the debugged process.
exceptionPort C.mach_port_t // mach port for receiving mach exceptions.
notificationPort C.mach_port_t // mach port for dead name notification (process exit).
initialized bool
// the main port we use, will return messages from both the
// exception and notification ports.
portSet C.mach_port_t
}
// Launch creates and begins debugging a new process. Uses a
// custom fork/exec process in order to take advantage of
// PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions.
func Launch(cmd []string, wd string) (*Process, error) {
// check that the argument to Launch is an executable file
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
return nil, NotExecutableErr
}
argv0Go, err := filepath.Abs(cmd[0])
if err != nil {
return nil, err
}
// Make sure the binary exists.
if filepath.Base(cmd[0]) == cmd[0] {
if _, err := exec.LookPath(cmd[0]); err != nil {
return nil, err
}
}
if _, err := os.Stat(argv0Go); err != nil {
return nil, err
}
argv0 := C.CString(argv0Go)
argvSlice := make([]*C.char, 0, len(cmd)+1)
for _, arg := range cmd {
argvSlice = append(argvSlice, C.CString(arg))
}
// argv array must be null terminated.
argvSlice = append(argvSlice, nil)
dbp := New(0)
var pid int
dbp.execPtraceFunc(func() {
ret := C.fork_exec(argv0, &argvSlice[0], C.int(len(argvSlice)),
C.CString(wd),
&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
&dbp.os.notificationPort)
pid = int(ret)
})
if pid <= 0 {
return nil, fmt.Errorf("could not fork/exec")
}
dbp.Pid = pid
for i := range argvSlice {
C.free(unsafe.Pointer(argvSlice[i]))
}
// Initialize enough of the Process state so that we can use resume and
// trapWait to wait until the child process calls execve.
for {
err = dbp.updateThreadListForTask(C.get_task_for_pid(C.int(dbp.Pid)))
if err == nil {
break
}
if err != couldNotGetThreadCount && err != couldNotGetThreadList {
return nil, err
}
}
if err := dbp.resume(); err != nil {
return nil, err
}
dbp.allGCache = nil
for _, th := range dbp.Threads {
th.clearBreakpointState()
}
trapthread, err := dbp.trapWait(-1)
if err != nil {
return nil, err
}
if err := dbp.Halt(); err != nil {
return nil, dbp.exitGuard(err)
}
_, err = dbp.waitForStop()
if err != nil {
return nil, err
}
dbp.os.initialized = true
dbp, err = initializeDebugProcess(dbp, argv0Go, false)
if err != nil {
return nil, err
}
if err := dbp.SwitchThread(trapthread.ID); err != nil {
return nil, err
}
return dbp, err
}
// Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) {
dbp := New(pid)
kret := C.acquire_mach_task(C.int(pid),
&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
&dbp.os.notificationPort)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not attach to %d", pid)
}
dbp.os.initialized = true
return initializeDebugProcess(dbp, "", true)
}
// Kill kills the process.
func (dbp *Process) Kill() (err error) {
if dbp.exited {
return nil
}
err = sys.Kill(-dbp.Pid, sys.SIGKILL)
if err != nil {
return errors.New("could not deliver signal: " + err.Error())
}
for port := range dbp.Threads {
if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS {
return errors.New("could not resume task")
}
}
for {
var task C.task_t
port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
if port == dbp.os.notificationPort {
break
}
}
dbp.postExit()
return
}
func (dbp *Process) requestManualStop() (err error) {
var (
task = C.mach_port_t(dbp.os.task)
thread = C.mach_port_t(dbp.CurrentThread.os.threadAct)
exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
)
kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not raise mach exception")
}
return nil
}
var couldNotGetThreadCount = errors.New("could not get thread count")
var couldNotGetThreadList = errors.New("could not get thread list")
func (dbp *Process) updateThreadList() error {
return dbp.updateThreadListForTask(dbp.os.task)
}
func (dbp *Process) updateThreadListForTask(task C.task_t) error {
var (
err error
kret C.kern_return_t
count C.int
list []uint32
)
for {
count = C.thread_count(task)
if count == -1 {
return couldNotGetThreadCount
}
list = make([]uint32, count)
// TODO(dp) might be better to malloc mem in C and then free it here
// instead of getting count above and passing in a slice
kret = C.get_threads(task, unsafe.Pointer(&list[0]), count)
if kret != -2 {
break
}
}
if kret != C.KERN_SUCCESS {
return couldNotGetThreadList
}
for _, thread := range dbp.Threads {
thread.os.exists = false
}
for _, port := range list {
thread, ok := dbp.Threads[int(port)]
if !ok {
thread, err = dbp.addThread(int(port), false)
if err != nil {
return err
}
}
thread.os.exists = true
}
for threadID, thread := range dbp.Threads {
if !thread.os.exists {
delete(dbp.Threads, threadID)
}
}
return nil
}
func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
if thread, ok := dbp.Threads[port]; ok {
return thread, nil
}
thread := &Thread{
ID: port,
dbp: dbp,
os: new(OSSpecificDetails),
}
dbp.Threads[port] = thread
thread.os.threadAct = C.thread_act_t(port)
if dbp.CurrentThread == nil {
dbp.SwitchThread(thread.ID)
}
return thread, nil
}
func (dbp *Process) parseDebugFrame(exe *macho.File, wg *sync.WaitGroup) {
defer wg.Done()
debugFrameSec := exe.Section("__debug_frame")
debugInfoSec := exe.Section("__debug_info")
if debugFrameSec != nil && debugInfoSec != nil {
debugFrame, err := exe.Section("__debug_frame").Data()
if err != nil {
fmt.Println("could not get __debug_frame section", err)
os.Exit(1)
}
dat, err := debugInfoSec.Data()
if err != nil {
fmt.Println("could not get .debug_info section", err)
os.Exit(1)
}
dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat))
} else {
fmt.Println("could not find __debug_frame section in binary")
os.Exit(1)
}
}
func (dbp *Process) obtainGoSymbols(exe *macho.File, wg *sync.WaitGroup) {
defer wg.Done()
var (
symdat []byte
pclndat []byte
err error
)
if sec := exe.Section("__gosymtab"); sec != nil {
symdat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gosymtab section", err)
os.Exit(1)
}
}
if sec := exe.Section("__gopclntab"); sec != nil {
pclndat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gopclntab section", err)
os.Exit(1)
}
}
pcln := gosym.NewLineTable(pclndat, exe.Section("__text").Addr)
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
fmt.Println("could not get initialize line table", err)
os.Exit(1)
}
dbp.goSymTable = tab
}
func (dbp *Process) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGroup) {
defer wg.Done()
if sec := exe.Section("__debug_line"); sec != nil {
debugLine, err := exe.Section("__debug_line").Data()
if err != nil {
fmt.Println("could not get __debug_line section", err)
os.Exit(1)
}
dbp.lineInfo = line.Parse(debugLine)
} else {
fmt.Println("could not find __debug_line section in binary")
os.Exit(1)
}
}
var UnsupportedArchErr = errors.New("unsupported architecture - only darwin/amd64 is supported")
func (dbp *Process) findExecutable(path string) (*macho.File, string, error) {
if path == "" {
path = C.GoString(C.find_executable(C.int(dbp.Pid)))
}
exe, err := macho.Open(path)
if err != nil {
return nil, path, err
}
if exe.Cpu != macho.CpuAmd64 {
return nil, path, UnsupportedArchErr
}
dbp.dwarf, err = exe.DWARF()
if err != nil {
return nil, path, err
}
return exe, path, nil
}
func (dbp *Process) trapWait(pid int) (*Thread, error) {
for {
task := dbp.os.task
port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
switch port {
case dbp.os.notificationPort:
// on macOS >= 10.12.1 the task_t changes after an execve, we could
// receive the notification for the death of the pre-execve task_t,
// this could also happen *before* we are notified that our task_t has
// changed.
if dbp.os.task != task {
continue
}
if !dbp.os.initialized {
if pidtask := C.get_task_for_pid(C.int(dbp.Pid)); pidtask != 0 && dbp.os.task != pidtask {
continue
}
}
_, status, err := dbp.wait(dbp.Pid, 0)
if err != nil {
return nil, err
}
dbp.postExit()
return nil, ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()}
case C.MACH_RCV_INTERRUPTED:
if !dbp.halt {
// Call trapWait again, it seems
// MACH_RCV_INTERRUPTED is emitted before
// process natural death _sometimes_.
continue
}
return nil, nil
case 0:
return nil, fmt.Errorf("error while waiting for task")
}
// In macOS 10.12.1 if we received a notification for a task other than
// the inferior's task and the inferior's task is no longer valid, this
// means inferior called execve and its task_t changed.
if dbp.os.task != task && C.task_is_valid(dbp.os.task) == 0 {
dbp.os.task = task
kret := C.reset_exception_ports(dbp.os.task, &dbp.os.exceptionPort, &dbp.os.notificationPort)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not follow task across exec: %d\n", kret)
}
}
// Since we cannot be notified of new threads on OS X
// this is as good a time as any to check for them.
dbp.updateThreadList()
th, ok := dbp.Threads[int(port)]
if !ok {
if dbp.halt {
dbp.halt = false
return th, nil
}
if dbp.firstStart || th.singleStepping {
dbp.firstStart = false
return th, nil
}
if err := th.Continue(); err != nil {
return nil, err
}
continue
}
return th, nil
}
}
func (dbp *Process) waitForStop() ([]int, error) {
ports := make([]int, 0, len(dbp.Threads))
count := 0
for {
var task C.task_t
port := C.mach_port_wait(dbp.os.portSet, &task, C.int(1))
if port != 0 && port != dbp.os.notificationPort && port != C.MACH_RCV_INTERRUPTED {
count = 0
ports = append(ports, int(port))
} else {
n := C.num_running_threads(dbp.os.task)
if n == 0 {
return ports, nil
} else if n < 0 {
return nil, fmt.Errorf("error waiting for thread stop %d", n)
} else if count > 16 {
return nil, fmt.Errorf("could not stop process %d", n)
}
}
}
}
func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
ports, err := dbp.waitForStop()
if err != nil {
return err
}
trapthread.SetCurrentBreakpoint()
for _, port := range ports {
if th, ok := dbp.Threads[port]; ok {
err := th.SetCurrentBreakpoint()
if err != nil {
return err
}
}
}
return nil
}
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done()
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
var status sys.WaitStatus
wpid, err := sys.Wait4(pid, &status, options, nil)
return wpid, &status, err
}
func killProcess(pid int) error {
return sys.Kill(pid, sys.SIGINT)
}
func (dbp *Process) exitGuard(err error) error {
if err != ErrContinueThread {
return err
}
_, status, werr := dbp.wait(dbp.Pid, sys.WNOHANG)
if werr == nil && status.Exited() {
dbp.postExit()
return ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()}
}
return err
}
func (dbp *Process) resume() error {
// all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.Threads {
if thread.CurrentBreakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
}
}
// everything is resumed
for _, thread := range dbp.Threads {
if err := thread.resume(); err != nil {
return dbp.exitGuard(err)
}
}
return nil
}

54
vendor/github.com/derekparker/delve/proc/proc_darwin.h generated vendored Normal file
View File

@ -0,0 +1,54 @@
#include <sys/types.h>
#include <libproc.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include "mach_exc.h"
#include "exc.h"
#ifdef mig_external
mig_external
#else
extern
#endif /* mig_external */
boolean_t exc_server(
mach_msg_header_t *InHeadP,
mach_msg_header_t *OutHeadP);
#ifdef mig_external
mig_external
#else
extern
#endif /* mig_external */
boolean_t mach_exc_server(
mach_msg_header_t *InHeadP,
mach_msg_header_t *OutHeadP);
kern_return_t
acquire_mach_task(int, task_t*, mach_port_t*, mach_port_t*, mach_port_t*);
char *
find_executable(int pid);
kern_return_t
get_threads(task_t task, void *data,int limit);
int
thread_count(task_t task);
mach_port_t
mach_port_wait(mach_port_t, task_t*, int);
kern_return_t
mach_send_reply(mach_msg_header_t);
kern_return_t
raise_exception(mach_port_t, mach_port_t, mach_port_t, exception_type_t);
kern_return_t
reset_exception_ports(task_t task, mach_port_t *exception_port, mach_port_t *notification_port);
task_t
get_task_for_pid(int pid);
int
task_is_valid(task_t task);

482
vendor/github.com/derekparker/delve/proc/proc_linux.go generated vendored Normal file
View File

@ -0,0 +1,482 @@
package proc
import (
"bytes"
"debug/gosym"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"syscall"
"time"
sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
"golang.org/x/debug/elf"
)
// Process statuses
const (
StatusSleeping = 'S'
StatusRunning = 'R'
StatusTraceStop = 't'
StatusZombie = 'Z'
// Kernel 2.6 has TraceStop as T
// TODO(derekparker) Since this means something different based on the
// version of the kernel ('T' is job control stop on modern 3.x+ kernels) we
// may want to differentiate at some point.
StatusTraceStopT = 'T'
)
// OSProcessDetails contains Linux specific
// process details.
type OSProcessDetails struct {
comm string
}
// Launch creates and begins debugging a new process. First entry in
// `cmd` is the program to run, and then rest are the arguments
// to be supplied to that process. `wd` is working directory of the program.
func Launch(cmd []string, wd string) (*Process, error) {
var (
proc *exec.Cmd
err error
)
// check that the argument to Launch is an executable file
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
return nil, NotExecutableErr
}
dbp := New(0)
dbp.execPtraceFunc(func() {
proc = exec.Command(cmd[0])
proc.Args = cmd
proc.Stdout = os.Stdout
proc.Stderr = os.Stderr
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true}
if wd != "" {
proc.Dir = wd
}
err = proc.Start()
})
if err != nil {
return nil, err
}
dbp.Pid = proc.Process.Pid
_, _, err = dbp.wait(proc.Process.Pid, 0)
if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
}
return initializeDebugProcess(dbp, proc.Path, false)
}
// Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) {
return initializeDebugProcess(New(pid), "", true)
}
// Kill kills the target process.
func (dbp *Process) Kill() (err error) {
if dbp.exited {
return nil
}
if !dbp.Threads[dbp.Pid].Stopped() {
return errors.New("process must be stopped in order to kill it")
}
if err = sys.Kill(-dbp.Pid, sys.SIGKILL); err != nil {
return errors.New("could not deliver signal " + err.Error())
}
if _, _, err = dbp.wait(dbp.Pid, 0); err != nil {
return
}
dbp.postExit()
return
}
func (dbp *Process) requestManualStop() (err error) {
return sys.Kill(dbp.Pid, sys.SIGTRAP)
}
// Attach to a newly created thread, and store that thread in our list of
// known threads.
func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
if thread, ok := dbp.Threads[tid]; ok {
return thread, nil
}
var err error
if attach {
dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) })
if err != nil && err != sys.EPERM {
// Do not return err if err == EPERM,
// we may already be tracing this thread due to
// PTRACE_O_TRACECLONE. We will surely blow up later
// if we truly don't have permissions.
return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err)
}
pid, status, err := dbp.wait(tid, 0)
if err != nil {
return nil, err
}
if status.Exited() {
return nil, fmt.Errorf("thread already exited %d", pid)
}
}
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
if err == syscall.ESRCH {
if _, _, err = dbp.wait(tid, 0); err != nil {
return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
}
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
if err == syscall.ESRCH {
return nil, err
}
if err != nil {
return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err)
}
}
dbp.Threads[tid] = &Thread{
ID: tid,
dbp: dbp,
os: new(OSSpecificDetails),
}
if dbp.CurrentThread == nil {
dbp.SwitchThread(tid)
}
return dbp.Threads[tid], nil
}
func (dbp *Process) updateThreadList() error {
tids, _ := filepath.Glob(fmt.Sprintf("/proc/%d/task/*", dbp.Pid))
for _, tidpath := range tids {
tidstr := filepath.Base(tidpath)
tid, err := strconv.Atoi(tidstr)
if err != nil {
return err
}
if _, err := dbp.addThread(tid, tid != dbp.Pid); err != nil {
return err
}
}
return nil
}
var UnsupportedArchErr = errors.New("unsupported architecture - only linux/amd64 is supported")
func (dbp *Process) findExecutable(path string) (*elf.File, string, error) {
if path == "" {
path = fmt.Sprintf("/proc/%d/exe", dbp.Pid)
}
f, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil {
return nil, path, err
}
elfFile, err := elf.NewFile(f)
if err != nil {
return nil, path, err
}
if elfFile.Machine != elf.EM_X86_64 {
return nil, path, UnsupportedArchErr
}
dbp.dwarf, err = elfFile.DWARF()
if err != nil {
return nil, path, err
}
return elfFile, path, nil
}
func (dbp *Process) parseDebugFrame(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
debugFrameSec := exe.Section(".debug_frame")
debugInfoSec := exe.Section(".debug_info")
if debugFrameSec != nil && debugInfoSec != nil {
debugFrame, err := exe.Section(".debug_frame").Data()
if err != nil {
fmt.Println("could not get .debug_frame section", err)
os.Exit(1)
}
dat, err := debugInfoSec.Data()
if err != nil {
fmt.Println("could not get .debug_info section", err)
os.Exit(1)
}
dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat))
} else {
fmt.Println("could not find .debug_frame section in binary")
os.Exit(1)
}
}
func (dbp *Process) obtainGoSymbols(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
var (
symdat []byte
pclndat []byte
err error
)
if sec := exe.Section(".gosymtab"); sec != nil {
symdat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gosymtab section", err)
os.Exit(1)
}
}
if sec := exe.Section(".gopclntab"); sec != nil {
pclndat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gopclntab section", err)
os.Exit(1)
}
}
pcln := gosym.NewLineTable(pclndat, exe.Section(".text").Addr)
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
fmt.Println("could not get initialize line table", err)
os.Exit(1)
}
dbp.goSymTable = tab
}
func (dbp *Process) parseDebugLineInfo(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
if sec := exe.Section(".debug_line"); sec != nil {
debugLine, err := exe.Section(".debug_line").Data()
if err != nil {
fmt.Println("could not get .debug_line section", err)
os.Exit(1)
}
dbp.lineInfo = line.Parse(debugLine)
} else {
fmt.Println("could not find .debug_line section in binary")
os.Exit(1)
}
}
func (dbp *Process) trapWait(pid int) (*Thread, error) {
for {
wpid, status, err := dbp.wait(pid, 0)
if err != nil {
return nil, fmt.Errorf("wait err %s %d", err, pid)
}
if wpid == 0 {
continue
}
th, ok := dbp.Threads[wpid]
if ok {
th.Status = (*WaitStatus)(status)
}
if status.Exited() {
if wpid == dbp.Pid {
dbp.postExit()
return nil, ProcessExitedError{Pid: wpid, Status: status.ExitStatus()}
}
delete(dbp.Threads, wpid)
continue
}
if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
// A traced thread has cloned a new thread, grab the pid and
// add it to our list of traced threads.
var cloned uint
dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) })
if err != nil {
if err == sys.ESRCH {
// thread died while we were adding it
continue
}
return nil, fmt.Errorf("could not get event message: %s", err)
}
th, err = dbp.addThread(int(cloned), false)
if err != nil {
if err == sys.ESRCH {
// thread died while we were adding it
continue
}
return nil, err
}
if err = th.Continue(); err != nil {
if err == sys.ESRCH {
// thread died while we were adding it
delete(dbp.Threads, th.ID)
continue
}
return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
}
if err = dbp.Threads[int(wpid)].Continue(); err != nil {
if err != sys.ESRCH {
return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err)
}
}
continue
}
if th == nil {
// Sometimes we get an unknown thread, ignore it?
continue
}
if status.StopSignal() == sys.SIGTRAP && dbp.halt {
th.running = false
dbp.halt = false
return th, nil
}
if status.StopSignal() == sys.SIGTRAP {
th.running = false
return th, nil
}
if th != nil {
// TODO(dp) alert user about unexpected signals here.
if err := th.resumeWithSig(int(status.StopSignal())); err != nil {
if err == sys.ESRCH {
return nil, ProcessExitedError{Pid: dbp.Pid}
}
return nil, err
}
}
}
}
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
defer wg.Done()
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.Pid))
if err == nil {
// removes newline character
comm = bytes.TrimSuffix(comm, []byte("\n"))
}
if comm == nil || len(comm) <= 0 {
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.Pid))
if err != nil {
fmt.Printf("Could not read proc stat: %v\n", err)
os.Exit(1)
}
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.Pid)
rexp, err := regexp.Compile(expr)
if err != nil {
fmt.Printf("Regexp compile error: %v\n", err)
os.Exit(1)
}
match := rexp.FindSubmatch(stat)
if match == nil {
fmt.Printf("No match found using regexp '%s' in /proc/%d/stat\n", expr, dbp.Pid)
os.Exit(1)
}
comm = match[1]
}
dbp.os.comm = strings.Replace(string(comm), "%", "%%", -1)
}
func status(pid int, comm string) rune {
f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
if err != nil {
return '\000'
}
defer f.Close()
var (
p int
state rune
)
// The second field of /proc/pid/stat is the name of the task in parenthesis.
// The name of the task is the base name of the executable for this process limited to TASK_COMM_LEN characters
// Since both parenthesis and spaces can appear inside the name of the task and no escaping happens we need to read the name of the executable first
// See: include/linux/sched.c:315 and include/linux/sched.c:1510
fmt.Fscanf(f, "%d ("+comm+") %c", &p, &state)
return state
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
var s sys.WaitStatus
if (pid != dbp.Pid) || (options != 0) {
wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil)
return wpid, &s, err
}
// If we call wait4/waitpid on a thread that is the leader of its group,
// with options == 0, while ptracing and the thread leader has exited leaving
// zombies of its own then waitpid hangs forever this is apparently intended
// behaviour in the linux kernel because it's just so convenient.
// Therefore we call wait4 in a loop with WNOHANG, sleeping a while between
// calls and exiting when either wait4 succeeds or we find out that the thread
// has become a zombie.
// References:
// https://sourceware.org/bugzilla/show_bug.cgi?id=12702
// https://sourceware.org/bugzilla/show_bug.cgi?id=10095
// https://sourceware.org/bugzilla/attachment.cgi?id=5685
for {
wpid, err := sys.Wait4(pid, &s, sys.WNOHANG|sys.WALL|options, nil)
if err != nil {
return 0, nil, err
}
if wpid != 0 {
return wpid, &s, err
}
if status(pid, dbp.os.comm) == StatusZombie {
return pid, nil, nil
}
time.Sleep(200 * time.Millisecond)
}
}
func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
for _, th := range dbp.Threads {
if th.CurrentBreakpoint == nil {
err := th.SetCurrentBreakpoint()
if err != nil {
return err
}
}
}
return nil
}
func (dbp *Process) exitGuard(err error) error {
if err != sys.ESRCH {
return err
}
if status(dbp.Pid, dbp.os.comm) == StatusZombie {
_, err := dbp.trapWait(-1)
return err
}
return err
}
func (dbp *Process) resume() error {
// all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.Threads {
if thread.CurrentBreakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
}
}
// everything is resumed
for _, thread := range dbp.Threads {
if err := thread.resume(); err != nil && err != sys.ESRCH {
return err
}
}
return nil
}
func killProcess(pid int) error {
return sys.Kill(pid, sys.SIGINT)
}

View File

@ -0,0 +1,643 @@
package proc
import (
"debug/gosym"
"debug/pe"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"syscall"
"unsafe"
sys "golang.org/x/sys/windows"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
"golang.org/x/debug/dwarf"
)
// OSProcessDetails holds Windows specific information.
type OSProcessDetails struct {
hProcess syscall.Handle
breakThread int
}
// Launch creates and begins debugging a new process.
func Launch(cmd []string, wd string) (*Process, error) {
argv0Go, err := filepath.Abs(cmd[0])
if err != nil {
return nil, err
}
// Make sure the binary exists and is an executable file
if filepath.Base(cmd[0]) == cmd[0] {
if _, err := exec.LookPath(cmd[0]); err != nil {
return nil, err
}
}
peFile, err := openExecutablePath(argv0Go)
if err != nil {
return nil, NotExecutableErr
}
peFile.Close()
// Duplicate the stdin/stdout/stderr handles
files := []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)}
p, _ := syscall.GetCurrentProcess()
fd := make([]syscall.Handle, len(files))
for i := range files {
err := syscall.DuplicateHandle(p, syscall.Handle(files[i]), p, &fd[i], 0, true, syscall.DUPLICATE_SAME_ACCESS)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(syscall.Handle(fd[i]))
}
argv0, err := syscall.UTF16PtrFromString(argv0Go)
if err != nil {
return nil, err
}
// create suitable command line for CreateProcess
// see https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L326
// adapted from standard library makeCmdLine
// see https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L86
var cmdLineGo string
if len(cmd) >= 1 {
for _, v := range cmd {
if cmdLineGo != "" {
cmdLineGo += " "
}
cmdLineGo += syscall.EscapeArg(v)
}
}
var cmdLine *uint16
if cmdLineGo != "" {
if cmdLine, err = syscall.UTF16PtrFromString(cmdLineGo); err != nil {
return nil, err
}
}
var workingDir *uint16
if wd != "" {
if workingDir, err = syscall.UTF16PtrFromString(wd); err != nil {
return nil, err
}
}
// Initialize the startup info and create process
si := new(sys.StartupInfo)
si.Cb = uint32(unsafe.Sizeof(*si))
si.Flags = syscall.STARTF_USESTDHANDLES
si.StdInput = sys.Handle(fd[0])
si.StdOutput = sys.Handle(fd[1])
si.StdErr = sys.Handle(fd[2])
pi := new(sys.ProcessInformation)
dbp := New(0)
dbp.execPtraceFunc(func() {
if wd == "" {
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, nil, si, pi)
} else {
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, workingDir, si, pi)
}
})
if err != nil {
return nil, err
}
sys.CloseHandle(sys.Handle(pi.Process))
sys.CloseHandle(sys.Handle(pi.Thread))
dbp.Pid = int(pi.ProcessId)
return newDebugProcess(dbp, argv0Go)
}
// newDebugProcess prepares process pid for debugging.
func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
// It should not actually be possible for the
// call to waitForDebugEvent to fail, since Windows
// will always fire a CREATE_PROCESS_DEBUG_EVENT event
// immediately after launching under DEBUG_ONLY_THIS_PROCESS.
// Attaching with DebugActiveProcess has similar effect.
var err error
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
})
if err != nil {
return nil, err
}
if tid == 0 {
dbp.postExit()
return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode}
}
// Suspend all threads so that the call to _ContinueDebugEvent will
// not resume the target.
for _, thread := range dbp.Threads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return nil, err
}
}
dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.Pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
})
if err != nil {
return nil, err
}
return initializeDebugProcess(dbp, exepath, false)
}
// findExePath searches for process pid, and returns its executable path.
func findExePath(pid int) (string, error) {
// Original code suggested different approach (see below).
// Maybe it could be useful in the future.
//
// Find executable path from PID/handle on Windows:
// https://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx
p, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(pid))
if err != nil {
return "", err
}
defer syscall.CloseHandle(p)
n := uint32(128)
for {
buf := make([]uint16, int(n))
err = _QueryFullProcessImageName(p, 0, &buf[0], &n)
switch err {
case syscall.ERROR_INSUFFICIENT_BUFFER:
// try bigger buffer
n *= 2
// but stop if it gets too big
if n > 10000 {
return "", err
}
case nil:
return syscall.UTF16ToString(buf[:n]), nil
default:
return "", err
}
}
}
// Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) {
// TODO: Probably should have SeDebugPrivilege before starting here.
err := _DebugActiveProcess(uint32(pid))
if err != nil {
return nil, err
}
exepath, err := findExePath(pid)
if err != nil {
return nil, err
}
return newDebugProcess(New(pid), exepath)
}
// Kill kills the process.
func (dbp *Process) Kill() error {
if dbp.exited {
return nil
}
if !dbp.Threads[dbp.Pid].Stopped() {
return errors.New("process must be stopped in order to kill it")
}
// TODO: Should not have to ignore failures here,
// but some tests appear to Kill twice causing
// this to fail on second attempt.
_ = syscall.TerminateProcess(dbp.os.hProcess, 1)
dbp.exited = true
return nil
}
func (dbp *Process) requestManualStop() error {
return _DebugBreakProcess(dbp.os.hProcess)
}
func (dbp *Process) updateThreadList() error {
// We ignore this request since threads are being
// tracked as they are created/killed in waitForDebugEvent.
return nil
}
func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, suspendNewThreads bool) (*Thread, error) {
if thread, ok := dbp.Threads[threadID]; ok {
return thread, nil
}
thread := &Thread{
ID: threadID,
dbp: dbp,
os: new(OSSpecificDetails),
}
thread.os.hThread = hThread
dbp.Threads[threadID] = thread
if dbp.CurrentThread == nil {
dbp.SwitchThread(thread.ID)
}
if suspendNewThreads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return nil, err
}
}
return thread, nil
}
func (dbp *Process) parseDebugFrame(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
debugFrameSec := exe.Section(".debug_frame")
debugInfoSec := exe.Section(".debug_info")
if debugFrameSec != nil && debugInfoSec != nil {
debugFrame, err := debugFrameSec.Data()
if err != nil && uint32(len(debugFrame)) < debugFrameSec.Size {
fmt.Println("could not get .debug_frame section", err)
os.Exit(1)
}
if 0 < debugFrameSec.VirtualSize && debugFrameSec.VirtualSize < debugFrameSec.Size {
debugFrame = debugFrame[:debugFrameSec.VirtualSize]
}
dat, err := debugInfoSec.Data()
if err != nil {
fmt.Println("could not get .debug_info section", err)
os.Exit(1)
}
dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat))
} else {
fmt.Println("could not find .debug_frame section in binary")
os.Exit(1)
}
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
for _, s := range f.Symbols {
if s.Name != name {
continue
}
if s.SectionNumber <= 0 {
return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber)
}
if len(f.Sections) < int(s.SectionNumber) {
return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections))
}
return s, nil
}
return nil, fmt.Errorf("no %s symbol found", name)
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
func loadPETable(f *pe.File, sname, ename string) ([]byte, error) {
ssym, err := findPESymbol(f, sname)
if err != nil {
return nil, err
}
esym, err := findPESymbol(f, ename)
if err != nil {
return nil, err
}
if ssym.SectionNumber != esym.SectionNumber {
return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename)
}
sect := f.Sections[ssym.SectionNumber-1]
data, err := sect.Data()
if err != nil {
return nil, err
}
return data[ssym.Value:esym.Value], nil
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
func pcln(exe *pe.File) (textStart uint64, symtab, pclntab []byte, err error) {
var imageBase uint64
switch oh := exe.OptionalHeader.(type) {
case *pe.OptionalHeader32:
imageBase = uint64(oh.ImageBase)
case *pe.OptionalHeader64:
imageBase = oh.ImageBase
default:
return 0, nil, nil, fmt.Errorf("pe file format not recognized")
}
if sect := exe.Section(".text"); sect != nil {
textStart = imageBase + uint64(sect.VirtualAddress)
}
if pclntab, err = loadPETable(exe, "runtime.pclntab", "runtime.epclntab"); err != nil {
// We didn't find the symbols, so look for the names used in 1.3 and earlier.
// TODO: Remove code looking for the old symbols when we no longer care about 1.3.
var err2 error
if pclntab, err2 = loadPETable(exe, "pclntab", "epclntab"); err2 != nil {
return 0, nil, nil, err
}
}
if symtab, err = loadPETable(exe, "runtime.symtab", "runtime.esymtab"); err != nil {
// Same as above.
var err2 error
if symtab, err2 = loadPETable(exe, "symtab", "esymtab"); err2 != nil {
return 0, nil, nil, err
}
}
return textStart, symtab, pclntab, nil
}
func (dbp *Process) obtainGoSymbols(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
_, symdat, pclndat, err := pcln(exe)
if err != nil {
fmt.Println("could not get Go symbols", err)
os.Exit(1)
}
pcln := gosym.NewLineTable(pclndat, uint64(exe.Section(".text").Offset))
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
fmt.Println("could not get initialize line table", err)
os.Exit(1)
}
dbp.goSymTable = tab
}
func (dbp *Process) parseDebugLineInfo(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
if sec := exe.Section(".debug_line"); sec != nil {
debugLine, err := sec.Data()
if err != nil && uint32(len(debugLine)) < sec.Size {
fmt.Println("could not get .debug_line section", err)
os.Exit(1)
}
if 0 < sec.VirtualSize && sec.VirtualSize < sec.Size {
debugLine = debugLine[:sec.VirtualSize]
}
dbp.lineInfo = line.Parse(debugLine)
} else {
fmt.Println("could not find .debug_line section in binary")
os.Exit(1)
}
}
var UnsupportedArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported")
func (dbp *Process) findExecutable(path string) (*pe.File, string, error) {
peFile, err := openExecutablePath(path)
if err != nil {
return nil, path, err
}
if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 {
return nil, path, UnsupportedArchErr
}
dbp.dwarf, err = dwarfFromPE(peFile)
if err != nil {
return nil, path, err
}
return peFile, path, nil
}
func openExecutablePath(path string) (*pe.File, error) {
f, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil {
return nil, err
}
return pe.NewFile(f)
}
// Adapted from src/debug/pe/file.go: pe.(*File).DWARF()
func dwarfFromPE(f *pe.File) (*dwarf.Data, error) {
// There are many other DWARF sections, but these
// are the ones the debug/dwarf package uses.
// Don't bother loading others.
var names = [...]string{"abbrev", "info", "line", "str"}
var dat [len(names)][]byte
for i, name := range names {
name = ".debug_" + name
s := f.Section(name)
if s == nil {
continue
}
b, err := s.Data()
if err != nil && uint32(len(b)) < s.Size {
return nil, err
}
if 0 < s.VirtualSize && s.VirtualSize < s.Size {
b = b[:s.VirtualSize]
}
dat[i] = b
}
abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3]
return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str)
}
type waitForDebugEventFlags int
const (
waitBlocking waitForDebugEventFlags = 1 << iota
waitSuspendNewThreads
)
func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) {
var debugEvent _DEBUG_EVENT
shouldExit := false
for {
continueStatus := uint32(_DBG_CONTINUE)
var milliseconds uint32 = 0
if flags&waitBlocking != 0 {
milliseconds = syscall.INFINITE
}
// Wait for a debug event...
err := _WaitForDebugEvent(&debugEvent, milliseconds)
if err != nil {
return 0, 0, err
}
// ... handle each event kind ...
unionPtr := unsafe.Pointer(&debugEvent.U[0])
switch debugEvent.DebugEventCode {
case _CREATE_PROCESS_DEBUG_EVENT:
debugInfo := (*_CREATE_PROCESS_DEBUG_INFO)(unionPtr)
hFile := debugInfo.File
if hFile != 0 && hFile != syscall.InvalidHandle {
err = syscall.CloseHandle(hFile)
if err != nil {
return 0, 0, err
}
}
dbp.os.hProcess = debugInfo.Process
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
if err != nil {
return 0, 0, err
}
break
case _CREATE_THREAD_DEBUG_EVENT:
debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr)
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
if err != nil {
return 0, 0, err
}
break
case _EXIT_THREAD_DEBUG_EVENT:
delete(dbp.Threads, int(debugEvent.ThreadId))
break
case _OUTPUT_DEBUG_STRING_EVENT:
//TODO: Handle debug output strings
break
case _LOAD_DLL_DEBUG_EVENT:
debugInfo := (*_LOAD_DLL_DEBUG_INFO)(unionPtr)
hFile := debugInfo.File
if hFile != 0 && hFile != syscall.InvalidHandle {
err = syscall.CloseHandle(hFile)
if err != nil {
return 0, 0, err
}
}
break
case _UNLOAD_DLL_DEBUG_EVENT:
break
case _RIP_EVENT:
break
case _EXCEPTION_DEBUG_EVENT:
exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr)
if code := exception.ExceptionRecord.ExceptionCode; code == _EXCEPTION_BREAKPOINT || code == _EXCEPTION_SINGLE_STEP {
tid := int(debugEvent.ThreadId)
dbp.os.breakThread = tid
return tid, 0, nil
} else {
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
}
case _EXIT_PROCESS_DEBUG_EVENT:
debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr)
exitCode = int(debugInfo.ExitCode)
shouldExit = true
default:
return 0, 0, fmt.Errorf("unknown debug event code: %d", debugEvent.DebugEventCode)
}
// .. and then continue unless we received an event that indicated we should break into debugger.
err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus)
if err != nil {
return 0, 0, err
}
if shouldExit {
return 0, exitCode, nil
}
}
}
func (dbp *Process) trapWait(pid int) (*Thread, error) {
var err error
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
})
if err != nil {
return nil, err
}
if tid == 0 {
dbp.postExit()
return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode}
}
th := dbp.Threads[tid]
return th, nil
}
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done()
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
return 0, nil, fmt.Errorf("not implemented: wait")
}
func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
// While the debug event that stopped the target was being propagated
// other target threads could generate other debug events.
// After this function we need to know about all the threads
// stopped on a breakpoint. To do that we first suspend all target
// threads and then repeatedly call _ContinueDebugEvent followed by
// waitForDebugEvent in non-blocking mode.
// We need to explicitly call SuspendThread because otherwise the
// call to _ContinueDebugEvent will resume execution of some of the
// target threads.
err := trapthread.SetCurrentBreakpoint()
if err != nil {
return err
}
for _, thread := range dbp.Threads {
thread.running = false
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return err
}
}
for {
var err error
var tid int
dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.Pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
if err == nil {
tid, _, _ = dbp.waitForDebugEvent(waitSuspendNewThreads)
}
})
if err != nil {
return err
}
if tid == 0 {
break
}
err = dbp.Threads[tid].SetCurrentBreakpoint()
if err != nil {
return err
}
}
return nil
}
func (dbp *Process) exitGuard(err error) error {
return err
}
func (dbp *Process) resume() error {
for _, thread := range dbp.Threads {
if thread.CurrentBreakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
}
}
for _, thread := range dbp.Threads {
thread.running = true
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
return err
}
}
return nil
}
func killProcess(pid int) error {
p, err := os.FindProcess(pid)
if err != nil {
return err
}
return p.Kill()
}

View File

@ -0,0 +1,28 @@
package proc
import sys "golang.org/x/sys/unix"
// PtraceAttach executes the sys.PtraceAttach call.
func PtraceAttach(pid int) error {
return sys.PtraceAttach(pid)
}
// PtraceDetach executes the PT_DETACH ptrace call.
func PtraceDetach(tid, sig int) error {
return ptrace(sys.PT_DETACH, tid, 1, uintptr(sig))
}
// PtraceCont executes the PTRACE_CONT ptrace call.
func PtraceCont(tid, sig int) error {
return ptrace(sys.PTRACE_CONT, tid, 1, 0)
}
// PtraceSingleStep returns PT_STEP ptrace call.
func PtraceSingleStep(tid int) error {
return ptrace(sys.PT_STEP, tid, 1, 0)
}
func ptrace(request, pid int, addr uintptr, data uintptr) (err error) {
_, _, err = sys.Syscall6(sys.SYS_PTRACE, uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
return
}

View File

@ -0,0 +1,96 @@
package proc
import (
"encoding/binary"
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
)
// PtraceAttach executes the sys.PtraceAttach call.
func PtraceAttach(pid int) error {
return sys.PtraceAttach(pid)
}
// PtraceDetach calls ptrace(PTRACE_DETACH).
func PtraceDetach(tid, sig int) error {
_, _, err := sys.Syscall6(sys.SYS_PTRACE, sys.PTRACE_DETACH, uintptr(tid), 1, uintptr(sig), 0, 0)
if err != syscall.Errno(0) {
return err
}
return nil
}
// PtraceCont executes ptrace PTRACE_CONT
func PtraceCont(tid, sig int) error {
return sys.PtraceCont(tid, sig)
}
// PtraceSingleStep executes ptrace PTRACE_SINGLE_STEP.
func PtraceSingleStep(tid int) error {
return sys.PtraceSingleStep(tid)
}
// PtracePokeUser execute ptrace PTRACE_POKE_USER.
func PtracePokeUser(tid int, off, addr uintptr) error {
_, _, err := sys.Syscall6(sys.SYS_PTRACE, sys.PTRACE_POKEUSR, uintptr(tid), uintptr(off), uintptr(addr), 0, 0)
if err != syscall.Errno(0) {
return err
}
return nil
}
// PtracePeekUser execute ptrace PTRACE_PEEK_USER.
func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
var val uintptr
_, _, err := syscall.Syscall6(syscall.SYS_PTRACE, syscall.PTRACE_PEEKUSR, uintptr(tid), uintptr(off), uintptr(unsafe.Pointer(&val)), 0, 0)
if err != syscall.Errno(0) {
return 0, err
}
return val, nil
}
// PtraceGetRegset returns floating point registers of the specified thread
// using PTRACE.
// See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html
// and amd64_supply_xsave in gdb/amd64-tdep.c.html
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developers Manual, Volume 1: Basic Architecture
func PtraceGetRegset(tid int) (regset PtraceXsave, err error) {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.PtraceFpRegs)), 0, 0)
if err == syscall.Errno(0) {
err = nil
}
var xstateargs [_X86_XSTATE_MAX_SIZE]byte
iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE}
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
if err != syscall.Errno(0) {
return
} else {
err = nil
}
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= iov.Len {
return
}
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
if xcomp_bv&(1<<63) != 0 {
// compact format not supported
return
}
if xstate_bv&(1<<2) == 0 {
// AVX state not present
return
}
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:iov.Len]
regset.AvxState = true
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
return
}

View File

@ -0,0 +1,13 @@
package proc
import (
"fmt"
)
func PtraceAttach(pid int) error {
return fmt.Errorf("not implemented: PtraceAttach")
}
func PtraceDetach(tid, sig int) error {
return _DebugActiveProcessStop(uint32(tid))
}

230
vendor/github.com/derekparker/delve/proc/registers.go generated vendored Normal file
View File

@ -0,0 +1,230 @@
package proc
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"os"
"strings"
)
// Registers is an interface for a generic register type. The
// interface encapsulates the generic values / actions
// we need independent of arch. The concrete register types
// will be different depending on OS/Arch.
type Registers interface {
PC() uint64
SP() uint64
CX() uint64
TLS() uint64
Get(int) (uint64, error)
SetPC(*Thread, uint64) error
Slice() []Register
}
type Register struct {
Name string
Value string
}
func appendWordReg(regs []Register, name string, value uint16) []Register {
return append(regs, Register{name, fmt.Sprintf("%#04x", value)})
}
func appendDwordReg(regs []Register, name string, value uint32) []Register {
return append(regs, Register{name, fmt.Sprintf("%#08x", value)})
}
func appendQwordReg(regs []Register, name string, value uint64) []Register {
return append(regs, Register{name, fmt.Sprintf("%#016x", value)})
}
func appendFlagReg(regs []Register, name string, value uint64, descr flagRegisterDescr, size int) []Register {
return append(regs, Register{name, descr.Describe(value, size)})
}
func appendX87Reg(regs []Register, index int, exponent uint16, mantissa uint64) []Register {
var f float64
fset := false
const (
_SIGNBIT = 1 << 15
_EXP_BIAS = (1 << 14) - 1 // 2^(n-1) - 1 = 16383
_SPECIALEXP = (1 << 15) - 1 // all bits set
_HIGHBIT = 1 << 63
_QUIETBIT = 1 << 62
)
sign := 1.0
if exponent&_SIGNBIT != 0 {
sign = -1.0
}
exponent &= ^uint16(_SIGNBIT)
NaN := math.NaN()
Inf := math.Inf(+1)
switch exponent {
case 0:
switch {
case mantissa == 0:
f = sign * 0.0
fset = true
case mantissa&_HIGHBIT != 0:
f = NaN
fset = true
}
case _SPECIALEXP:
switch {
case mantissa&_HIGHBIT == 0:
f = sign * Inf
fset = true
default:
f = NaN // signaling NaN
fset = true
}
default:
if mantissa&_HIGHBIT == 0 {
f = NaN
fset = true
}
}
if !fset {
significand := float64(mantissa) / (1 << 63)
f = sign * math.Ldexp(significand, int(exponent-_EXP_BIAS))
}
return append(regs, Register{fmt.Sprintf("ST(%d)", index), fmt.Sprintf("%#04x%016x\t%g", exponent, mantissa, f)})
}
func appendSSEReg(regs []Register, name string, xmm []byte) []Register {
buf := bytes.NewReader(xmm)
var out bytes.Buffer
var vi [16]uint8
for i := range vi {
binary.Read(buf, binary.LittleEndian, &vi[i])
}
fmt.Fprintf(&out, "0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8], vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0])
fmt.Fprintf(&out, "\tv2_int={ %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x%02x%02x%02x%02x }", vi[7], vi[6], vi[5], vi[4], vi[3], vi[2], vi[1], vi[0], vi[15], vi[14], vi[13], vi[12], vi[11], vi[10], vi[9], vi[8])
fmt.Fprintf(&out, "\tv4_int={ %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x }", vi[3], vi[2], vi[1], vi[0], vi[7], vi[6], vi[5], vi[4], vi[11], vi[10], vi[9], vi[8], vi[15], vi[14], vi[13], vi[12])
fmt.Fprintf(&out, "\tv8_int={ %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x }", vi[1], vi[0], vi[3], vi[2], vi[5], vi[4], vi[7], vi[6], vi[9], vi[8], vi[11], vi[10], vi[13], vi[12], vi[15], vi[14])
fmt.Fprintf(&out, "\tv16_int={ %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x }", vi[0], vi[1], vi[2], vi[3], vi[4], vi[5], vi[6], vi[7], vi[8], vi[9], vi[10], vi[11], vi[12], vi[13], vi[14], vi[15])
buf.Seek(0, os.SEEK_SET)
var v2 [2]float64
for i := range v2 {
binary.Read(buf, binary.LittleEndian, &v2[i])
}
fmt.Fprintf(&out, "\tv2_float={ %g %g }", v2[0], v2[1])
buf.Seek(0, os.SEEK_SET)
var v4 [4]float32
for i := range v4 {
binary.Read(buf, binary.LittleEndian, &v4[i])
}
fmt.Fprintf(&out, "\tv4_float={ %g %g %g %g }", v4[0], v4[1], v4[2], v4[3])
return append(regs, Register{name, out.String()})
}
var UnknownRegisterError = errors.New("unknown register")
// Registers obtains register values from the debugged process.
func (t *Thread) Registers(floatingPoint bool) (Registers, error) {
return registers(t, floatingPoint)
}
// PC returns the current PC for this thread.
func (t *Thread) PC() (uint64, error) {
regs, err := t.Registers(false)
if err != nil {
return 0, err
}
return regs.PC(), nil
}
type flagRegisterDescr []flagDescr
type flagDescr struct {
name string
mask uint64
}
var mxcsrDescription flagRegisterDescr = []flagDescr{
{"FZ", 1 << 15},
{"RZ/RN", 1<<14 | 1<<13},
{"PM", 1 << 12},
{"UM", 1 << 11},
{"OM", 1 << 10},
{"ZM", 1 << 9},
{"DM", 1 << 8},
{"IM", 1 << 7},
{"DAZ", 1 << 6},
{"PE", 1 << 5},
{"UE", 1 << 4},
{"OE", 1 << 3},
{"ZE", 1 << 2},
{"DE", 1 << 1},
{"IE", 1 << 0},
}
var eflagsDescription flagRegisterDescr = []flagDescr{
{"CF", 1 << 0},
{"", 1 << 1},
{"PF", 1 << 2},
{"AF", 1 << 4},
{"ZF", 1 << 6},
{"SF", 1 << 7},
{"TF", 1 << 8},
{"IF", 1 << 9},
{"DF", 1 << 10},
{"OF", 1 << 11},
{"IOPL", 1<<12 | 1<<13},
{"NT", 1 << 14},
{"RF", 1 << 16},
{"VM", 1 << 17},
{"AC", 1 << 18},
{"VIF", 1 << 19},
{"VIP", 1 << 20},
{"ID", 1 << 21},
}
func (descr flagRegisterDescr) Mask() uint64 {
var r uint64
for _, f := range descr {
r = r | f.mask
}
return r
}
func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string {
var r []string
for _, f := range descr {
if f.name == "" {
continue
}
// rbm is f.mask with only the right-most bit set:
// 0001 1100 -> 0000 0100
rbm := f.mask & -f.mask
if rbm == f.mask {
if reg&f.mask != 0 {
r = append(r, f.name)
}
} else {
x := (reg & f.mask) >> uint64(math.Log2(float64(rbm)))
r = append(r, fmt.Sprintf("%s=%x", f.name, x))
}
}
if reg & ^descr.Mask() != 0 {
r = append(r, fmt.Sprintf("unknown_flags=%x", reg&^descr.Mask()))
}
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
}

View File

@ -0,0 +1,363 @@
package proc
// #include "threads_darwin.h"
import "C"
import (
"encoding/binary"
"fmt"
"rsc.io/x86/x86asm"
"unsafe"
)
// Regs represents CPU registers on an AMD64 processor.
type Regs struct {
rax uint64
rbx uint64
rcx uint64
rdx uint64
rdi uint64
rsi uint64
rbp uint64
rsp uint64
r8 uint64
r9 uint64
r10 uint64
r11 uint64
r12 uint64
r13 uint64
r14 uint64
r15 uint64
rip uint64
rflags uint64
cs uint64
fs uint64
gs uint64
gsBase uint64
fpregs []Register
}
func (r *Regs) Slice() []Register {
var regs = []struct {
k string
v uint64
}{
{"Rip", r.rip},
{"Rsp", r.rsp},
{"Rax", r.rax},
{"Rbx", r.rbx},
{"Rcx", r.rcx},
{"Rdx", r.rdx},
{"Rdi", r.rdi},
{"Rsi", r.rsi},
{"Rbp", r.rbp},
{"R8", r.r8},
{"R9", r.r9},
{"R10", r.r10},
{"R11", r.r11},
{"R12", r.r12},
{"R13", r.r13},
{"R14", r.r14},
{"R15", r.r15},
{"Rflags", r.rflags},
{"Cs", r.cs},
{"Fs", r.fs},
{"Gs", r.gs},
{"Gs_base", r.gsBase},
}
out := make([]Register, 0, len(regs)+len(r.fpregs))
for _, reg := range regs {
if reg.k == "Rflags" {
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64)
} else {
out = appendQwordReg(out, reg.k, reg.v)
}
}
out = append(out, r.fpregs...)
return out
}
// PC returns the current program counter
// i.e. the RIP CPU register.
func (r *Regs) PC() uint64 {
return r.rip
}
// SP returns the stack pointer location,
// i.e. the RSP register.
func (r *Regs) SP() uint64 {
return r.rsp
}
// CX returns the value of the RCX register.
func (r *Regs) CX() uint64 {
return r.rcx
}
// TLS returns the value of the register
// that contains the location of the thread
// local storage segment.
func (r *Regs) TLS() uint64 {
return r.gsBase
}
// SetPC sets the RIP register to the value specified by `pc`.
func (r *Regs) SetPC(thread *Thread, pc uint64) error {
kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not set pc")
}
return nil
}
func (r *Regs) Get(n int) (uint64, error) {
reg := x86asm.Reg(n)
const (
mask8 = 0x000f
mask16 = 0x00ff
mask32 = 0xffff
)
switch reg {
// 8-bit
case x86asm.AL:
return r.rax & mask8, nil
case x86asm.CL:
return r.rcx & mask8, nil
case x86asm.DL:
return r.rdx & mask8, nil
case x86asm.BL:
return r.rbx & mask8, nil
case x86asm.AH:
return (r.rax >> 8) & mask8, nil
case x86asm.CH:
return (r.rax >> 8) & mask8, nil
case x86asm.DH:
return (r.rdx >> 8) & mask8, nil
case x86asm.BH:
return (r.rbx >> 8) & mask8, nil
case x86asm.SPB:
return r.rsp & mask8, nil
case x86asm.BPB:
return r.rbp & mask8, nil
case x86asm.SIB:
return r.rsi & mask8, nil
case x86asm.DIB:
return r.rdi & mask8, nil
case x86asm.R8B:
return r.r8 & mask8, nil
case x86asm.R9B:
return r.r9 & mask8, nil
case x86asm.R10B:
return r.r10 & mask8, nil
case x86asm.R11B:
return r.r11 & mask8, nil
case x86asm.R12B:
return r.r12 & mask8, nil
case x86asm.R13B:
return r.r13 & mask8, nil
case x86asm.R14B:
return r.r14 & mask8, nil
case x86asm.R15B:
return r.r15 & mask8, nil
// 16-bit
case x86asm.AX:
return r.rax & mask16, nil
case x86asm.CX:
return r.rcx & mask16, nil
case x86asm.DX:
return r.rdx & mask16, nil
case x86asm.BX:
return r.rbx & mask16, nil
case x86asm.SP:
return r.rsp & mask16, nil
case x86asm.BP:
return r.rbp & mask16, nil
case x86asm.SI:
return r.rsi & mask16, nil
case x86asm.DI:
return r.rdi & mask16, nil
case x86asm.R8W:
return r.r8 & mask16, nil
case x86asm.R9W:
return r.r9 & mask16, nil
case x86asm.R10W:
return r.r10 & mask16, nil
case x86asm.R11W:
return r.r11 & mask16, nil
case x86asm.R12W:
return r.r12 & mask16, nil
case x86asm.R13W:
return r.r13 & mask16, nil
case x86asm.R14W:
return r.r14 & mask16, nil
case x86asm.R15W:
return r.r15 & mask16, nil
// 32-bit
case x86asm.EAX:
return r.rax & mask32, nil
case x86asm.ECX:
return r.rcx & mask32, nil
case x86asm.EDX:
return r.rdx & mask32, nil
case x86asm.EBX:
return r.rbx & mask32, nil
case x86asm.ESP:
return r.rsp & mask32, nil
case x86asm.EBP:
return r.rbp & mask32, nil
case x86asm.ESI:
return r.rsi & mask32, nil
case x86asm.EDI:
return r.rdi & mask32, nil
case x86asm.R8L:
return r.r8 & mask32, nil
case x86asm.R9L:
return r.r9 & mask32, nil
case x86asm.R10L:
return r.r10 & mask32, nil
case x86asm.R11L:
return r.r11 & mask32, nil
case x86asm.R12L:
return r.r12 & mask32, nil
case x86asm.R13L:
return r.r13 & mask32, nil
case x86asm.R14L:
return r.r14 & mask32, nil
case x86asm.R15L:
return r.r15 & mask32, nil
// 64-bit
case x86asm.RAX:
return r.rax, nil
case x86asm.RCX:
return r.rcx, nil
case x86asm.RDX:
return r.rdx, nil
case x86asm.RBX:
return r.rbx, nil
case x86asm.RSP:
return r.rsp, nil
case x86asm.RBP:
return r.rbp, nil
case x86asm.RSI:
return r.rsi, nil
case x86asm.RDI:
return r.rdi, nil
case x86asm.R8:
return r.r8, nil
case x86asm.R9:
return r.r9, nil
case x86asm.R10:
return r.r10, nil
case x86asm.R11:
return r.r11, nil
case x86asm.R12:
return r.r12, nil
case x86asm.R13:
return r.r13, nil
case x86asm.R14:
return r.r14, nil
case x86asm.R15:
return r.r15, nil
}
return 0, UnknownRegisterError
}
func registers(thread *Thread, floatingPoint bool) (Registers, error) {
var state C.x86_thread_state64_t
var identity C.thread_identifier_info_data_t
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &state)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not get registers")
}
kret = C.get_identity(C.mach_port_name_t(thread.os.threadAct), &identity)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not get thread identity informations")
}
/*
thread_identifier_info::thread_handle contains the base of the
thread-specific data area, which on x86 and x86_64 is the threads base
address of the %gs segment. 10.9.2 xnu-2422.90.20/osfmk/kern/thread.c
thread_info_internal() gets the value from
machine_thread::cthread_self, which is the same value used to set the
%gs base in xnu-2422.90.20/osfmk/i386/pcb_native.c
act_machine_switch_pcb().
--
comment copied from chromium's crashpad
https://chromium.googlesource.com/crashpad/crashpad/+/master/snapshot/mac/process_reader.cc
*/
regs := &Regs{
rax: uint64(state.__rax),
rbx: uint64(state.__rbx),
rcx: uint64(state.__rcx),
rdx: uint64(state.__rdx),
rdi: uint64(state.__rdi),
rsi: uint64(state.__rsi),
rbp: uint64(state.__rbp),
rsp: uint64(state.__rsp),
r8: uint64(state.__r8),
r9: uint64(state.__r9),
r10: uint64(state.__r10),
r11: uint64(state.__r11),
r12: uint64(state.__r12),
r13: uint64(state.__r13),
r14: uint64(state.__r14),
r15: uint64(state.__r15),
rip: uint64(state.__rip),
rflags: uint64(state.__rflags),
cs: uint64(state.__cs),
fs: uint64(state.__fs),
gs: uint64(state.__gs),
gsBase: uint64(identity.thread_handle),
}
if floatingPoint {
// https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/thread_status.h?txt
var fpstate C.x86_float_state64_t
kret = C.get_fpu_registers(C.mach_port_name_t(thread.os.threadAct), &fpstate)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not get floating point registers")
}
regs.fpregs = appendWordReg(regs.fpregs, "CW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fcw))))
regs.fpregs = appendWordReg(regs.fpregs, "SW", *((*uint16)(unsafe.Pointer(&fpstate.__fpu_fsw))))
regs.fpregs = appendWordReg(regs.fpregs, "TW", uint16(fpstate.__fpu_ftw))
regs.fpregs = appendWordReg(regs.fpregs, "FOP", uint16(fpstate.__fpu_fop))
regs.fpregs = appendQwordReg(regs.fpregs, "FIP", uint64(fpstate.__fpu_cs)<<32|uint64(fpstate.__fpu_ip))
regs.fpregs = appendQwordReg(regs.fpregs, "FDP", uint64(fpstate.__fpu_ds)<<32|uint64(fpstate.__fpu_dp))
for i, st := range []*C.char{&fpstate.__fpu_stmm0.__mmst_reg[0], &fpstate.__fpu_stmm1.__mmst_reg[0], &fpstate.__fpu_stmm2.__mmst_reg[0], &fpstate.__fpu_stmm3.__mmst_reg[0], &fpstate.__fpu_stmm4.__mmst_reg[0], &fpstate.__fpu_stmm5.__mmst_reg[0], &fpstate.__fpu_stmm6.__mmst_reg[0], &fpstate.__fpu_stmm7.__mmst_reg[0]} {
stb := C.GoBytes(unsafe.Pointer(st), 10)
mantissa := binary.LittleEndian.Uint64(stb[:8])
exponent := binary.LittleEndian.Uint16(stb[8:])
regs.fpregs = appendX87Reg(regs.fpregs, i, exponent, mantissa)
}
regs.fpregs = appendFlagReg(regs.fpregs, "MXCSR", uint64(fpstate.__fpu_mxcsr), mxcsrDescription, 32)
regs.fpregs = appendDwordReg(regs.fpregs, "MXCSR_MASK", uint32(fpstate.__fpu_mxcsrmask))
for i, xmm := range []*C.char{&fpstate.__fpu_xmm0.__xmm_reg[0], &fpstate.__fpu_xmm1.__xmm_reg[0], &fpstate.__fpu_xmm2.__xmm_reg[0], &fpstate.__fpu_xmm3.__xmm_reg[0], &fpstate.__fpu_xmm4.__xmm_reg[0], &fpstate.__fpu_xmm5.__xmm_reg[0], &fpstate.__fpu_xmm6.__xmm_reg[0], &fpstate.__fpu_xmm7.__xmm_reg[0], &fpstate.__fpu_xmm8.__xmm_reg[0], &fpstate.__fpu_xmm9.__xmm_reg[0], &fpstate.__fpu_xmm10.__xmm_reg[0], &fpstate.__fpu_xmm11.__xmm_reg[0], &fpstate.__fpu_xmm12.__xmm_reg[0], &fpstate.__fpu_xmm13.__xmm_reg[0], &fpstate.__fpu_xmm14.__xmm_reg[0], &fpstate.__fpu_xmm15.__xmm_reg[0]} {
regs.fpregs = appendSSEReg(regs.fpregs, fmt.Sprintf("XMM%d", i), C.GoBytes(unsafe.Pointer(xmm), 16))
}
}
return regs, nil
}
func (thread *Thread) saveRegisters() (Registers, error) {
kret := C.get_registers(C.mach_port_name_t(thread.os.threadAct), &thread.os.registers)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not save register contents")
}
return &Regs{rip: uint64(thread.os.registers.__rip), rsp: uint64(thread.os.registers.__rsp)}, nil
}
func (thread *Thread) restoreRegisters() error {
kret := C.set_registers(C.mach_port_name_t(thread.os.threadAct), &thread.os.registers)
if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not save register contents")
}
return nil
}

View File

@ -0,0 +1,325 @@
package proc
import (
"fmt"
"rsc.io/x86/x86asm"
sys "golang.org/x/sys/unix"
)
// Regs is a wrapper for sys.PtraceRegs.
type Regs struct {
regs *sys.PtraceRegs
fpregs []Register
}
func (r *Regs) Slice() []Register {
var regs = []struct {
k string
v uint64
}{
{"Rip", r.regs.Rip},
{"Rsp", r.regs.Rsp},
{"Rax", r.regs.Rax},
{"Rbx", r.regs.Rbx},
{"Rcx", r.regs.Rcx},
{"Rdx", r.regs.Rdx},
{"Rdi", r.regs.Rdi},
{"Rsi", r.regs.Rsi},
{"Rbp", r.regs.Rbp},
{"R8", r.regs.R8},
{"R9", r.regs.R9},
{"R10", r.regs.R10},
{"R11", r.regs.R11},
{"R12", r.regs.R12},
{"R13", r.regs.R13},
{"R14", r.regs.R14},
{"R15", r.regs.R15},
{"Orig_rax", r.regs.Orig_rax},
{"Cs", r.regs.Cs},
{"Eflags", r.regs.Eflags},
{"Ss", r.regs.Ss},
{"Fs_base", r.regs.Fs_base},
{"Gs_base", r.regs.Gs_base},
{"Ds", r.regs.Ds},
{"Es", r.regs.Es},
{"Fs", r.regs.Fs},
{"Gs", r.regs.Gs},
}
out := make([]Register, 0, len(regs)+len(r.fpregs))
for _, reg := range regs {
if reg.k == "Eflags" {
out = appendFlagReg(out, reg.k, reg.v, eflagsDescription, 64)
} else {
out = appendQwordReg(out, reg.k, reg.v)
}
}
out = append(out, r.fpregs...)
return out
}
// PC returns the value of RIP register.
func (r *Regs) PC() uint64 {
return r.regs.PC()
}
// SP returns the value of RSP register.
func (r *Regs) SP() uint64 {
return r.regs.Rsp
}
// CX returns the value of RCX register.
func (r *Regs) CX() uint64 {
return r.regs.Rcx
}
// TLS returns the address of the thread
// local storage memory segment.
func (r *Regs) TLS() uint64 {
return r.regs.Fs_base
}
// SetPC sets RIP to the value specified by 'pc'.
func (r *Regs) SetPC(thread *Thread, pc uint64) (err error) {
r.regs.SetPC(pc)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
return
}
func (r *Regs) Get(n int) (uint64, error) {
reg := x86asm.Reg(n)
const (
mask8 = 0x000f
mask16 = 0x00ff
mask32 = 0xffff
)
switch reg {
// 8-bit
case x86asm.AL:
return r.regs.Rax & mask8, nil
case x86asm.CL:
return r.regs.Rcx & mask8, nil
case x86asm.DL:
return r.regs.Rdx & mask8, nil
case x86asm.BL:
return r.regs.Rbx & mask8, nil
case x86asm.AH:
return (r.regs.Rax >> 8) & mask8, nil
case x86asm.CH:
return (r.regs.Rax >> 8) & mask8, nil
case x86asm.DH:
return (r.regs.Rdx >> 8) & mask8, nil
case x86asm.BH:
return (r.regs.Rbx >> 8) & mask8, nil
case x86asm.SPB:
return r.regs.Rsp & mask8, nil
case x86asm.BPB:
return r.regs.Rbp & mask8, nil
case x86asm.SIB:
return r.regs.Rsi & mask8, nil
case x86asm.DIB:
return r.regs.Rdi & mask8, nil
case x86asm.R8B:
return r.regs.R8 & mask8, nil
case x86asm.R9B:
return r.regs.R9 & mask8, nil
case x86asm.R10B:
return r.regs.R10 & mask8, nil
case x86asm.R11B:
return r.regs.R11 & mask8, nil
case x86asm.R12B:
return r.regs.R12 & mask8, nil
case x86asm.R13B:
return r.regs.R13 & mask8, nil
case x86asm.R14B:
return r.regs.R14 & mask8, nil
case x86asm.R15B:
return r.regs.R15 & mask8, nil
// 16-bit
case x86asm.AX:
return r.regs.Rax & mask16, nil
case x86asm.CX:
return r.regs.Rcx & mask16, nil
case x86asm.DX:
return r.regs.Rdx & mask16, nil
case x86asm.BX:
return r.regs.Rbx & mask16, nil
case x86asm.SP:
return r.regs.Rsp & mask16, nil
case x86asm.BP:
return r.regs.Rbp & mask16, nil
case x86asm.SI:
return r.regs.Rsi & mask16, nil
case x86asm.DI:
return r.regs.Rdi & mask16, nil
case x86asm.R8W:
return r.regs.R8 & mask16, nil
case x86asm.R9W:
return r.regs.R9 & mask16, nil
case x86asm.R10W:
return r.regs.R10 & mask16, nil
case x86asm.R11W:
return r.regs.R11 & mask16, nil
case x86asm.R12W:
return r.regs.R12 & mask16, nil
case x86asm.R13W:
return r.regs.R13 & mask16, nil
case x86asm.R14W:
return r.regs.R14 & mask16, nil
case x86asm.R15W:
return r.regs.R15 & mask16, nil
// 32-bit
case x86asm.EAX:
return r.regs.Rax & mask32, nil
case x86asm.ECX:
return r.regs.Rcx & mask32, nil
case x86asm.EDX:
return r.regs.Rdx & mask32, nil
case x86asm.EBX:
return r.regs.Rbx & mask32, nil
case x86asm.ESP:
return r.regs.Rsp & mask32, nil
case x86asm.EBP:
return r.regs.Rbp & mask32, nil
case x86asm.ESI:
return r.regs.Rsi & mask32, nil
case x86asm.EDI:
return r.regs.Rdi & mask32, nil
case x86asm.R8L:
return r.regs.R8 & mask32, nil
case x86asm.R9L:
return r.regs.R9 & mask32, nil
case x86asm.R10L:
return r.regs.R10 & mask32, nil
case x86asm.R11L:
return r.regs.R11 & mask32, nil
case x86asm.R12L:
return r.regs.R12 & mask32, nil
case x86asm.R13L:
return r.regs.R13 & mask32, nil
case x86asm.R14L:
return r.regs.R14 & mask32, nil
case x86asm.R15L:
return r.regs.R15 & mask32, nil
// 64-bit
case x86asm.RAX:
return r.regs.Rax, nil
case x86asm.RCX:
return r.regs.Rcx, nil
case x86asm.RDX:
return r.regs.Rdx, nil
case x86asm.RBX:
return r.regs.Rbx, nil
case x86asm.RSP:
return r.regs.Rsp, nil
case x86asm.RBP:
return r.regs.Rbp, nil
case x86asm.RSI:
return r.regs.Rsi, nil
case x86asm.RDI:
return r.regs.Rdi, nil
case x86asm.R8:
return r.regs.R8, nil
case x86asm.R9:
return r.regs.R9, nil
case x86asm.R10:
return r.regs.R10, nil
case x86asm.R11:
return r.regs.R11, nil
case x86asm.R12:
return r.regs.R12, nil
case x86asm.R13:
return r.regs.R13, nil
case x86asm.R14:
return r.regs.R14, nil
case x86asm.R15:
return r.regs.R15, nil
}
return 0, UnknownRegisterError
}
func registers(thread *Thread, floatingPoint bool) (Registers, error) {
var (
regs sys.PtraceRegs
err error
)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, &regs) })
if err != nil {
return nil, err
}
r := &Regs{&regs, nil}
if floatingPoint {
r.fpregs, err = thread.fpRegisters()
if err != nil {
return nil, err
}
}
return r, nil
}
// tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
type PtraceFpRegs struct {
Cwd uint16
Swd uint16
Ftw uint16
Fop uint16
Rip uint64
Rdp uint64
Mxcsr uint32
MxcrMask uint32
StSpace [32]uint32
XmmSpace [256]byte
padding [24]uint32
}
type PtraceXsave struct {
PtraceFpRegs
AvxState bool // contains AVX state
YmmSpace [256]byte
}
const (
_X86_XSTATE_MAX_SIZE = 2688
_NT_X86_XSTATE = 0x202
_XSAVE_HEADER_START = 512
_XSAVE_HEADER_LEN = 64
_XSAVE_EXTENDED_REGION_START = 576
_XSAVE_SSE_REGION_LEN = 416
)
func (thread *Thread) fpRegisters() (regs []Register, err error) {
var fpregs PtraceXsave
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
// x87 registers
regs = appendWordReg(regs, "CW", fpregs.Cwd)
regs = appendWordReg(regs, "SW", fpregs.Swd)
regs = appendWordReg(regs, "TW", fpregs.Ftw)
regs = appendWordReg(regs, "FOP", fpregs.Fop)
regs = appendQwordReg(regs, "FIP", fpregs.Rip)
regs = appendQwordReg(regs, "FDP", fpregs.Rdp)
for i := 0; i < len(fpregs.StSpace); i += 4 {
regs = appendX87Reg(regs, i/4, uint16(fpregs.StSpace[i+2]), uint64(fpregs.StSpace[i+1])<<32|uint64(fpregs.StSpace[i]))
}
// SSE registers
regs = appendFlagReg(regs, "MXCSR", uint64(fpregs.Mxcsr), mxcsrDescription, 32)
regs = appendDwordReg(regs, "MXCSR_MASK", fpregs.MxcrMask)
for i := 0; i < len(fpregs.XmmSpace); i += 16 {
regs = appendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), fpregs.XmmSpace[i:i+16])
if fpregs.AvxState {
regs = appendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), fpregs.YmmSpace[i:i+16])
}
}
return
}

Some files were not shown because too many files have changed in this diff Show More