From 36e86cf97f8edc2ce710ae9fd23ae5fb396a9373 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Tue, 22 Mar 2022 12:00:56 -0700 Subject: [PATCH] Embed data files in the binary (#180) Now that we're on Go 1.16+ we can do this easily! The main advantage is it means users can build a genqlient binary and use that portably (or we could distribute one, or whatever). Plus the code is marginally simpler; the `embed` API is really quite nice. Fixes #9. Test plan: ``` make check go build . rm -rf generate # pretend we have no checkout ./genqlient ./internal/integration/genqlient.yaml ./genqlient --init # fails after generating a default config ``` --- docs/CHANGELOG.md | 2 ++ generate/config.go | 21 +++++---------------- generate/generate_test.go | 4 ++-- generate/template.go | 16 +++++----------- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 90e0363f..0da5596b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -26,6 +26,8 @@ When releasing a new version: ### New features: +- genqlient can now run as a portable binary (i.e. without a local checkout of the repository or `go run`). + ### Bug fixes: ## v0.4.0 diff --git a/generate/config.go b/generate/config.go index 60fb2c32..2b63f553 100644 --- a/generate/config.go +++ b/generate/config.go @@ -1,8 +1,8 @@ package generate import ( + _ "embed" "go/token" - "io" "io/ioutil" "os" "path/filepath" @@ -123,22 +123,11 @@ func ReadAndValidateConfigFromDefaultLocations() (*Config, error) { return ReadAndValidateConfig(cfgFile) } +//go:embed default_genqlient.yaml +var defaultConfig []byte + func initConfig(filename string) error { - // TODO(benkraft): Embed this config file into the binary, see - // https://github.com/Khan/genqlient/issues/9. - r, err := os.Open(filepath.Join(thisDir, "default_genqlient.yaml")) - if err != nil { - return err - } - w, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644) - if err != nil { - return errorf(nil, "unable to write default genqlient.yaml: %v", err) - } - _, err = io.Copy(w, r) - if err != nil { - return errorf(nil, "unable to write default genqlient.yaml: %v", err) - } - return nil + return os.WriteFile(filename, defaultConfig, 0o644) } // findCfg searches for the config file in this directory and all parents up the tree diff --git a/generate/generate_test.go b/generate/generate_test.go index f12c31c8..6a26e869 100644 --- a/generate/generate_test.go +++ b/generate/generate_test.go @@ -124,7 +124,7 @@ func TestGenerate(t *testing.T) { } } -func defaultConfig(t *testing.T) *Config { +func getDefaultConfig(t *testing.T) *Config { // Parse the config that `genqlient --init` generates, to make sure that // works. var config Config @@ -151,7 +151,7 @@ func TestGenerateWithConfig(t *testing.T) { baseDir string // relative to dataDir config *Config // omits Schema and Operations, set below. }{ - {"DefaultConfig", "", defaultConfig(t)}, + {"DefaultConfig", "", getDefaultConfig(t)}, {"Subpackage", "", &Config{ Generated: "mypkg/myfile.go", }}, diff --git a/generate/template.go b/generate/template.go index 6e3ab438..e120e273 100644 --- a/generate/template.go +++ b/generate/template.go @@ -1,19 +1,14 @@ package generate import ( + "embed" "io" - "path/filepath" - "runtime" "strings" "text/template" ) -var ( - // TODO(benkraft): Embed templates into the binary, see - // https://github.com/Khan/genqlient/issues/9. - _, thisFilename, _, _ = runtime.Caller(0) - thisDir = filepath.Dir(thisFilename) -) +//go:embed *.tmpl +var templates embed.FS func repeat(n int, s string) string { var builder strings.Builder @@ -37,7 +32,6 @@ func sub(x, y int) int { return x - y } func (g *generator) render(tmplRelFilename string, w io.Writer, data interface{}) error { tmpl := g.templateCache[tmplRelFilename] if tmpl == nil { - absFilename := filepath.Join(thisDir, tmplRelFilename) funcMap := template.FuncMap{ "ref": g.ref, "repeat": repeat, @@ -45,9 +39,9 @@ func (g *generator) render(tmplRelFilename string, w io.Writer, data interface{} "sub": sub, } var err error - tmpl, err = template.New(tmplRelFilename).Funcs(funcMap).ParseFiles(absFilename) + tmpl, err = template.New(tmplRelFilename).Funcs(funcMap).ParseFS(templates, tmplRelFilename) if err != nil { - return errorf(nil, "could not load template %v: %v", absFilename, err) + return errorf(nil, "could not load template %v: %v", tmplRelFilename, err) } g.templateCache[tmplRelFilename] = tmpl }