DRYな備忘録

Don't Repeat Yourself.

Revelのコマンドを読む[第2回:run]

revel

読む場所

% cd $GOPATH/src/github.com/revel/cmd

revel run hoge/fuga prod

めんどいからprodモード起動の場合を読む

cmdのrevel/run.go

package main

import (
    "github.com/revel/revel"
    "github.com/revel/revel/harness"
    "strconv"

    "fmt"
)

var cmdRun = &Command{
    UsageLine: "run [import path] [run mode] [port]",
    Short:     "run a Revel application",
    Long:      `略`,
}

func init() {
    cmdRun.Run = runApp
}

func runApp(args []string) {
    if len(args) == 0 {
        errorf("No import path given.\nRun 'revel help run' for usage.\n")
    }

    // Determine the run mode.
    mode := "dev"
    if len(args) >= 2 {
        mode = args[1]
    }
    // ここでmodeはprodになる

    // Find and parse app.conf
    revel.Init(mode, args[0], "")
    revel.LoadMimeConfig()
    // revel.Init, revel.LoadMimeConfigの中を見ないと分からないけど、
    // "prod"と"hoge/fuga"を受けて、revelのグローバルなvarを
    // 上書きしているっぽい

    // Determine the override port, if any.
    // 三番目の引数はポート指定
    // 指定があればですけど
    port := revel.HttpPort
    if len(args) == 3 {
        var err error
        if port, err = strconv.Atoi(args[2]); err != nil {
            errorf("Failed to parse port as integer: %s", args[2])
        }
    }
    // このへんただのstdoutなので無視る
    revel.INFO.Printf("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, mode)
    revel.TRACE.Println("Base path:", revel.BasePath)

    // If the app is run in "watched" mode, use the harness to run it.
    // revel.Configはgithub.com/robfig/configでいうところの
    // sectionにrevel.RunModeを渡しているので、app.confの[prod]を読む
    // で、[prod]の場合これはデフォでfalse
    if revel.Config.BoolDefault("watch", true) && revel.Config.BoolDefault("watch.code", true) {
        revel.HttpPort = port
        harness.NewHarness().Run() // Never returns.
    }

    // Else, just build and run the app.
    // 最も読むべきなのはここのharness.Buildだと思われ
    app, err := harness.Build()
    if err != nil {
        errorf("Failed to build app: %s", err)
    }
    app.Port = port
    app.Cmd().Run()
}

ちなみにrevel.Init()で決定されるrevel直下のvarは、

$GOPATH/src/github.com/revel/revel/revel.go

var (
    // App details
    AppName    string // e.g. "sample"
    BasePath   string // e.g. "/Users/robfig/gocode/src/corp/sample"
    AppPath    string // e.g. "/Users/robfig/gocode/src/corp/sample/app"
    ViewsPath  string // e.g. "/Users/robfig/gocode/src/corp/sample/app/views"
    ImportPath string // e.g. "corp/sample"
    SourcePath string // e.g. "/Users/robfig/gocode/src"

    Config  *MergedConfig
    RunMode string // Application-defined (by default, "dev" or "prod")
    DevMode bool   // if true, RunMode is a development mode.

    // Revel installation details
    RevelPath string // e.g. "/Users/robfig/gocode/src/revel"

    // Where to look for templates and configuration.
    // Ordered by priority.  (Earlier paths take precedence over later paths.)
    CodePaths     []string
    ConfPaths     []string
    TemplatePaths []string

    Modules []Module

    // Server config.
    //
    // Alert: This is how the app is configured, which may be different from
    // the current process reality.  For example, if the app is configured for
    // port 9000, HttpPort will always be 9000, even though in dev mode it is
    // run on a random port and proxied.
    HttpPort    int    // e.g. 9000
    HttpAddr    string // e.g. "", "127.0.0.1"
    HttpSsl     bool   // e.g. true if using ssl
    HttpSslCert string // e.g. "/path/to/cert.pem"
    HttpSslKey  string // e.g. "/path/to/key.pem"

    // All cookies dropped by the framework begin with this prefix.
    CookiePrefix string

    // Cookie flags
    CookieHttpOnly bool
    CookieSecure   bool

    // Delimiters to use when rendering templates
    TemplateDelims string

    Initialized bool

    // Private
    secretKey []byte // Key used to sign cookies. An empty key disables signing.
    packaged  bool   // If true, this is running from a pre-built package.
)

$GOPATH/src/github.com/revel/revel/harness/build.go

package harness

import (
    "fmt"
    "github.com/revel/revel"
    "go/build"
    "os"
    "os/exec"
    "path"
    "path/filepath"
    "regexp"
    "runtime"
    "strconv"
    "strings"
    "text/template"
)

var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")

// Build the app:
// 1. Generate the the main.go file.
// 2. Run the appropriate "go build" command.
// Requires that revel.Init has been called previously.
// Returns the path to the built binary, and an error if there was a problem building it.
func Build() (app *App, compileError *revel.Error) {
    // First, clear the generated files (to avoid them messing with ProcessSource).
    cleanSource("tmp", "routes")

    sourceInfo, compileError := ProcessSource(revel.CodePaths)
    if compileError != nil {
        return nil, compileError
    }

    // Add the db.import to the import paths.
    if dbImportPath, found := revel.Config.String("db.import"); found {
        sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, dbImportPath)
    }

    // Generate two source files.
    templateArgs := map[string]interface{}{
        "Controllers":    sourceInfo.ControllerSpecs(),
        "ValidationKeys": sourceInfo.ValidationKeys,
        "ImportPaths":    calcImportAliases(sourceInfo),
        "TestSuites":     sourceInfo.TestSuites(),
    }
    genSource("tmp", "main.go", MAIN, templateArgs)
    genSource("routes", "routes.go", ROUTES, templateArgs)

    // Read build config.
    buildTags := revel.Config.StringDefault("build.tags", "")

    // Build the user program (all code under app).
    // It relies on the user having "go" installed.
    goPath, err := exec.LookPath("go")
    if err != nil {
        revel.ERROR.Fatalf("Go executable not found in PATH.")
    }

    // このアプリケーションのbinへのパスを決定する
    pkg, err := build.Default.Import(revel.ImportPath, "", build.FindOnly)
    if err != nil {
        revel.ERROR.Fatalln("Failure importing", revel.ImportPath)
    }
    binName := path.Join(pkg.BinDir, path.Base(revel.BasePath))
    if runtime.GOOS == "windows" {
        binName += ".exe"
    }
    fmt.Println("binNameとは?", binName)

    gotten := make(map[string]struct{})
    for {
        appVersion := getAppVersion()
        versionLinkerFlags := fmt.Sprintf("-X %s/app.APP_VERSION \"%s\"", revel.ImportPath, appVersion)

                // ここでアプリケーションのbin ($GOPATH/bin/fuga) をつくってる
        buildCmd := exec.Command(goPath, "build",
            "-ldflags", versionLinkerFlags,
            "-tags", buildTags,
            "-o", binName, path.Join(revel.ImportPath, "app", "tmp"))
        revel.TRACE.Println("Exec:", buildCmd.Args)
        output, err := buildCmd.CombinedOutput()

        // If the build succeeded, we're done.
        if err == nil {
            return NewApp(binName), nil
        }
        revel.ERROR.Println(string(output))

        // See if it was an import error that we can go get.
        matches := importErrorPattern.FindStringSubmatch(string(output))
        if matches == nil {
            return nil, newCompileError(output)
        }

        // Ensure we haven't already tried to go get it.
        pkgName := matches[1]
        if _, alreadyTried := gotten[pkgName]; alreadyTried {
            return nil, newCompileError(output)
        }
        gotten[pkgName] = struct{}{}

        // Execute "go get <pkg>"
        getCmd := exec.Command(goPath, "get", pkgName)
        revel.TRACE.Println("Exec:", getCmd.Args)
        getOutput, err := getCmd.CombinedOutput()
        if err != nil {
            revel.ERROR.Println(string(getOutput))
            return nil, newCompileError(output)
        }

        // Success getting the import, attempt to build again.
    }
    revel.ERROR.Fatalf("Not reachable")
    return nil, nil
}

genSourceで生成されるGoコード

tmp

// GENERATED CODE - DO NOT EDIT
package main

import (
    "flag"
    "reflect"
    "github.com/revel/revel"
    controllers0 "github.com/revel/revel/modules/static/app/controllers"
    _ "hoge/fuga/app"
    controllers "hoge/fuga/app/controllers"
)

var (
    runMode    *string = flag.String("runMode", "", "Run mode.")
    port       *int    = flag.Int("port", 0, "By default, read from app.conf")
    importPath *string = flag.String("importPath", "", "Go Import Path for the app.")
    srcPath    *string = flag.String("srcPath", "", "Path to the source root.")

    // So compiler won't complain if the generated code doesn't reference reflect package...
    _ = reflect.Invalid
)

func main() {
    flag.Parse()
    revel.Init(*runMode, *importPath, *srcPath)
    revel.INFO.Println("Running revel server")

    revel.RegisterController((*controllers.App)(nil),
        []*revel.MethodType{
            &revel.MethodType{
                Name: "Index",
                Args: []*revel.MethodArg{
                },
                RenderArgNames: map[int][]string{
                    10: []string{
                    },
                },
            },

        })

    revel.RegisterController((*controllers0.Static)(nil),
        []*revel.MethodType{
            &revel.MethodType{
                Name: "Serve",
                Args: []*revel.MethodArg{
                    &revel.MethodArg{Name: "prefix", Type: reflect.TypeOf((*string)(nil)) },
                    &revel.MethodArg{Name: "filepath", Type: reflect.TypeOf((*string)(nil)) },
                },
                RenderArgNames: map[int][]string{
                },
            },
            &revel.MethodType{
                Name: "ServeModule",
                Args: []*revel.MethodArg{
                    &revel.MethodArg{Name: "moduleName", Type: reflect.TypeOf((*string)(nil)) },
                    &revel.MethodArg{Name: "prefix", Type: reflect.TypeOf((*string)(nil)) },
                    &revel.MethodArg{Name: "filepath", Type: reflect.TypeOf((*string)(nil)) },
                },
                RenderArgNames: map[int][]string{
                },
            },

        })

    revel.DefaultValidationKeys = map[string]map[int]string{
    }
    revel.TestSuites = []interface{}{
    }

    revel.Run(*port)
}

routes

// GENERATED CODE - DO NOT EDIT
package routes

import "github.com/revel/revel"


type tApp struct {}
var App tApp


func (_ tApp) Index(
        ) string {
    args := make(map[string]string)

    return revel.MainRouter.Reverse("App.Index", args).Url
}


type tStatic struct {}
var Static tStatic


func (_ tStatic) Serve(
        prefix string,
        filepath string,
        ) string {
    args := make(map[string]string)

    revel.Unbind(args, "prefix", prefix)
    revel.Unbind(args, "filepath", filepath)
    return revel.MainRouter.Reverse("Static.Serve", args).Url
}

func (_ tStatic) ServeModule(
        moduleName string,
        prefix string,
        filepath string,
        ) string {
    args := make(map[string]string)

    revel.Unbind(args, "moduleName", moduleName)
    revel.Unbind(args, "prefix", prefix)
    revel.Unbind(args, "filepath", filepath)
    return revel.MainRouter.Reverse("Static.ServeModule", args).Url
}

まとめ

今までわかったこと

  • revel new hoge/fuga
    • revelが持ってるskeltonをごっそりそのままhoge/fugaというpathにコピーする
    • あとちょっとしたconfとかいじる
  • revel run hoge/fuga
    • アプリケーションのソースからアプリケーションのtmpディレクトリにbinとして動くmainパッケージのGoソースを自動生成(templateパッケージを利用)
    • アプリケーションbinをgo installする(go/buildパッケージを利用)
    • アプリケーションbinコマンドを実行
      • その中ではrevel.Run()を実行

ということで、revelの役割は

  1. アプリケーションのskelをごっそりつくってあげる
  2. アプリケーション独自のルーチンを書いたら、それを見つけて自動でRegisterControllerとかまでしてくれるGoソースを自動生成してくれる
  3. あとはrevelがそれを動かす

雑感

とりあえず今はこんくらいでいいや

f:id:otiai10:20140721171753j:plain