1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
// Package entc provides an interface for interacting with
// entc (ent codegen) as a package rather than an executable.
package entc
import (
"errors"
"fmt"
"go/token"
"path"
"path/filepath"
"strings"
"github.com/facebook/ent/entc/gen"
"github.com/facebook/ent/entc/internal"
"github.com/facebook/ent/entc/load"
"golang.org/x/tools/go/packages"
)
// LoadGraph loads the schema package from the given schema path,
// and constructs a *gen.Graph.
func LoadGraph(schemaPath string, cfg *gen.Config) (*gen.Graph, error) {
spec, err := (&load.Config{Path: schemaPath}).Load()
if err != nil {
return nil, err
}
cfg.Schema = spec.PkgPath
if cfg.Package == "" {
// default package-path for codegen is one package
// before the schema package (`<project>/ent/schema`).
cfg.Package = path.Dir(spec.PkgPath)
}
return gen.NewGraph(cfg, spec.Schemas...)
}
// Generate runs the codegen on the schema path. The default target
// directory for the assets, is one directory above the schema path.
// Hence, if the schema package resides in "<project>/ent/schema",
// the base directory for codegen will be "<project>/ent".
//
// If no storage driver provided by option, SQL driver will be used.
//
// entc.Generate("./ent/path", &gen.Config{
// Header: "// Custom header",
// IDType: &field.TypeInfo{Type: field.TypeInt},
// })
//
func Generate(schemaPath string, cfg *gen.Config, options ...Option) (err error) {
if cfg.Target == "" {
abs, err := filepath.Abs(schemaPath)
if err != nil {
return err
}
// default target-path for codegen is one dir above
// the schema.
cfg.Target = filepath.Dir(abs)
}
for _, opt := range options {
if err := opt(cfg); err != nil {
return err
}
}
if cfg.Storage == nil {
driver, err := gen.NewStorage("sql")
if err != nil {
return err
}
cfg.Storage = driver
}
undo, err := gen.PrepareEnv(cfg)
if err != nil {
return err
}
defer func() {
if err != nil {
_ = undo()
}
}()
return generate(schemaPath, cfg)
}
func normalizePkg(c *gen.Config) error {
base := path.Base(c.Package)
if strings.ContainsRune(base, '-') {
base = strings.ReplaceAll(base, "-", "_")
c.Package = path.Join(path.Dir(c.Package), base)
}
if !token.IsIdentifier(base) {
return fmt.Errorf("invalid package identifier: %q", base)
}
return nil
}
// Option allows for managing codegen configuration using functional options.
type Option func(*gen.Config) error
// Storage sets the storage-driver type to support by the codegen.
func Storage(typ string) Option {
return func(cfg *gen.Config) error {
storage, err := gen.NewStorage(typ)
if err != nil {
return err
}
cfg.Storage = storage
return nil
}
}
// FeatureNames enables sets of features by their names.
func FeatureNames(names ...string) Option {
return func(cfg *gen.Config) error {
for _, name := range names {
for _, feat := range gen.AllFeatures {
if name == feat.Name {
cfg.Features = append(cfg.Features, feat)
}
}
}
return nil
}
}
// TemplateFiles parses the named files and associates the resulting templates
// with codegen templates.
func TemplateFiles(filenames ...string) Option {
return templateOption(func(t *gen.Template) (*gen.Template, error) {
return t.ParseFiles(filenames...)
})
}
// TemplateGlob parses the template definitions from the files identified
// by the pattern and associates the resulting templates with codegen templates.
func TemplateGlob(pattern string) Option {
return templateOption(func(t *gen.Template) (*gen.Template, error) {
return t.ParseGlob(pattern)
})
}
// TemplateDir parses the template definitions from the files in the directory
// and associates the resulting templates with codegen templates.
func TemplateDir(path string) Option {
return templateOption(func(t *gen.Template) (*gen.Template, error) {
return t.ParseDir(path)
})
}
// templateOption ensures the template instantiate
// once for config and execute the given Option.
func templateOption(next func(t *gen.Template) (*gen.Template, error)) Option {
return func(cfg *gen.Config) (err error) {
tmpl, err := next(gen.NewTemplate("external"))
if err != nil {
return err
}
cfg.Templates = append(cfg.Templates, tmpl)
return nil
}
}
// generate loads the given schema and run codegen.
func generate(schemaPath string, cfg *gen.Config) error {
graph, err := LoadGraph(schemaPath, cfg)
if err != nil {
if err := mayRecover(err, schemaPath, cfg); err != nil {
return err
}
if graph, err = LoadGraph(schemaPath, cfg); err != nil {
return err
}
}
if err := normalizePkg(cfg); err != nil {
return err
}
return graph.Gen()
}
func mayRecover(err error, schemaPath string, cfg *gen.Config) error {
if enabled, _ := cfg.FeatureEnabled(gen.FeatureSnapshot.Name); !enabled {
return err
}
if !errors.As(err, &packages.Error{}) && !internal.IsBuildError(err) {
return err
}
// If the build error comes from the schema package.
if err := internal.CheckDir(schemaPath); err != nil {
return fmt.Errorf("schema failure: %w", err)
}
target := filepath.Join(cfg.Target, "internal/schema.go")
return (&internal.Snapshot{Path: target, Config: cfg}).Restore()
}
|