tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/engine.go (about) 1 package engine 2 3 import ( 4 "context" 5 "log" 6 "log/slog" 7 "os" 8 "path/filepath" 9 "reflect" 10 11 "tractor.dev/toolkit-go/engine/cli" 12 "tractor.dev/toolkit-go/engine/daemon" 13 ) 14 15 var ( 16 Identifier string 17 ) 18 19 // Initializer provides an initialization hook after assembly. 20 type Initializer interface { 21 Initialize() 22 } 23 24 // PostInitializer provides a hook after initialization. 25 type PostInitializer interface { 26 PostInitialize() 27 } 28 29 // Service is a long-running process managed by daemon.Framework. 30 type Service interface { 31 Serve(ctx context.Context) 32 } 33 34 // Runner is a unit that can take over the program entrypoint. 35 type Runner interface { 36 Run(ctx context.Context) error 37 } 38 39 type Terminator interface { 40 Terminate() 41 } 42 43 type Depender interface { 44 Assembly() []Unit 45 } 46 47 var ( 48 // Main is a global reference to the top-level main unit. 49 Main Unit 50 51 defaultAssembly *Assembly 52 ) 53 54 func typeExists(units []Unit, unit Unit) bool { 55 for _, u := range units { 56 a := reflect.ValueOf(unitFrom(unit)) 57 b := reflect.ValueOf(unitFrom(u)) 58 if a.Type() == b.Type() { 59 return true 60 } 61 } 62 return false 63 } 64 65 func Dependencies(units ...Unit) []Unit { 66 var deps []Unit 67 for _, unit := range units { 68 if d, ok := unit.(Depender); ok { 69 for _, dep := range d.Assembly() { 70 if !typeExists(deps, dep) && !typeExists(units, dep) { 71 deps = append(deps, dep) 72 } 73 } 74 } 75 } 76 return append(units, deps...) 77 } 78 79 func Assemble(units ...Unit) (asm *Assembly, err error) { 80 if asm, err = New(Dependencies(units...)...); err != nil { 81 return 82 } 83 84 // dependency injection 85 if err = asm.SelfAssemble(); err != nil { 86 return 87 } 88 89 // initialize units after DI, in reverse order (main last) 90 for i := len(asm.Units()) - 1; i >= 0; i-- { 91 u := asm.Units()[i] 92 i, ok := u.(Initializer) 93 if ok { 94 i.Initialize() 95 } 96 } 97 98 // postinitialize units, for units depending on main initialization 99 for _, u := range asm.Units() { 100 pi, ok := u.(PostInitializer) 101 if ok { 102 pi.PostInitialize() 103 } 104 } 105 106 return 107 } 108 109 // Init only needs to be explicitly called if you 110 // need to run code before calling Run 111 func Init() { 112 if Identifier == "" { 113 Identifier = filepath.Base(os.Args[0]) 114 } 115 } 116 117 // Run assembles units and starts the program. 118 func Run(units ...Unit) { 119 Init() 120 121 asm, err := New(units...) 122 if err != nil { 123 log.Fatal(err) 124 } 125 126 // add assembly 127 if err := asm.Add(asm); err != nil { 128 panic(err) 129 } 130 131 // add daemon framework 132 d := &daemon.Framework{} 133 if err := asm.Add(d); err != nil { 134 panic(err) 135 } 136 137 // add cli framework 138 c := &cli.Framework{} 139 if err := asm.Add(c); err != nil { 140 panic(err) 141 } 142 143 // add logger 144 if err := asm.Add(slog.Default()); err != nil { 145 panic(err) 146 } 147 148 // re-assemble 149 asm, err = Assemble(asm.Units()...) 150 if err != nil { 151 panic(err) 152 } 153 defaultAssembly = asm 154 155 // make main unit global accessible 156 Main = asm.Main() 157 158 // if main has Run use it 159 if r, ok := asm.Main().(Runner); ok { 160 if err := r.Run(context.Background()); err != nil { 161 log.Fatal(err) 162 } 163 return 164 } 165 166 // find a runner; should always be the cli.Framework 167 for i := len(asm.Units()) - 1; i >= 0; i-- { 168 u := asm.Units()[i] 169 r, ok := u.(Runner) 170 if ok { 171 if err := r.Run(context.Background()); err != nil { 172 log.Fatal(err) 173 } 174 return 175 } 176 } 177 178 panic("nothing to run") 179 } 180 181 func Terminate() { 182 if defaultAssembly == nil { 183 panic("no active default assembly") 184 } 185 for i := len(defaultAssembly.Units()) - 1; i >= 0; i-- { 186 u := defaultAssembly.Units()[i] 187 if t, ok := u.(Terminator); ok { 188 t.Terminate() 189 return 190 } 191 } 192 }