github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/command/compile.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 "github.com/hashicorp/otto/appfile" 12 "github.com/hashicorp/otto/appfile/detect" 13 appfileLoad "github.com/hashicorp/otto/appfile/load" 14 "github.com/hashicorp/otto/ui" 15 ) 16 17 // CompileCommand is the command that is responsible for "compiling" the 18 // Appfile into a set of data that is used by the other commands for 19 // execution. 20 type CompileCommand struct { 21 Meta 22 23 // Detectors to use for compilation. These will be overridden by any 24 // plugins. 25 Detectors []*detect.Detector 26 } 27 28 func (c *CompileCommand) Run(args []string) int { 29 var flagAppfile string 30 fs := c.FlagSet("compile", FlagSetNone) 31 fs.Usage = func() { c.Ui.Error(c.Help()) } 32 fs.StringVar(&flagAppfile, "appfile", "", "") 33 if err := fs.Parse(args); err != nil { 34 return 1 35 } 36 37 // Load all the plugins, we use all the plugins for compilation only 38 // so we have full access to detectors and app types. 39 pluginMgr, err := c.PluginManager() 40 if err != nil { 41 c.Ui.Error(fmt.Sprintf( 42 "Error initializing plugin manager: %s", err)) 43 return 1 44 } 45 if err := pluginMgr.LoadAll(); err != nil { 46 c.Ui.Error(fmt.Sprintf( 47 "Error loading plugins: %s", err)) 48 return 1 49 } 50 51 // Load the detectors from the plugins 52 detectors := make([]*detect.Detector, 0, 20) 53 detectors = append(detectors, c.Detectors...) 54 for _, p := range pluginMgr.Plugins() { 55 detectors = append(detectors, p.AppMeta.Detectors...) 56 } 57 58 // Sort the detectors so we try highest priority first 59 sort.Sort(detect.DetectorList(detectors)) 60 61 // Parse the detectors 62 dataDir, err := c.DataDir() 63 if err != nil { 64 c.Ui.Error(err.Error()) 65 return 1 66 } 67 detectorDir := filepath.Join(dataDir, DefaultLocalDataDetectorDir) 68 log.Printf("[DEBUG] loading detectors from: %s", detectorDir) 69 detectConfig, err := detect.ParseDir(detectorDir) 70 if err != nil { 71 c.Ui.Error(err.Error()) 72 return 1 73 } 74 if detectConfig == nil { 75 detectConfig = &detect.Config{} 76 } 77 err = detectConfig.Merge(&detect.Config{Detectors: detectors}) 78 if err != nil { 79 c.Ui.Error(err.Error()) 80 return 1 81 } 82 83 // Load a UI 84 ui := c.OttoUi() 85 ui.Header("Loading Appfile...") 86 87 app, appPath, err := loadAppfile(flagAppfile) 88 if err != nil { 89 c.Ui.Error(err.Error()) 90 return 1 91 } 92 93 // Tell the user what is happening if they have no Appfile 94 if app == nil { 95 ui.Header("No Appfile found! Detecting project information...") 96 ui.Message(fmt.Sprintf( 97 "No Appfile was found. If there is no Appfile, Otto will do its best\n" + 98 "to detect the type of application this is and set reasonable defaults.\n" + 99 "This is a good way to get started with Otto, but over time we recommend\n" + 100 "writing a real Appfile since this will allow more complex customizations,\n" + 101 "the ability to reference dependencies, versioning, and more.")) 102 } 103 104 // Build the appfile compiler 105 var loader appfileLoad.Loader 106 compiler, err := appfile.NewCompiler(&appfile.CompileOpts{ 107 Dir: filepath.Join( 108 appPath, DefaultOutputDir, DefaultOutputDirCompiledAppfile), 109 Loader: loader.Load, 110 Callback: c.compileCallback(ui), 111 }) 112 if err != nil { 113 c.Ui.Error(fmt.Sprintf( 114 "Error initializing Appfile compiler: %s", err)) 115 return 1 116 } 117 118 // Create the Appfile loader 119 if err := pluginMgr.ConfigureCore(c.CoreConfig); err != nil { 120 panic(err) 121 } 122 loader.Detector = detectConfig 123 loader.Compiler = compiler 124 loader.Apps = c.CoreConfig.Apps 125 126 // Load the complete Appfile 127 app, err = loader.Load(app, appPath) 128 if err != nil { 129 c.Ui.Error(err.Error()) 130 return 1 131 } 132 133 // If there was no loaded Appfile and we don't have an application 134 // type then we weren't able to detect the type. Error. 135 if app == nil || app.Application.Type == "" { 136 c.Ui.Error(strings.TrimSpace(errCantDetectType)) 137 return 1 138 } 139 140 // Compile the Appfile 141 ui.Header("Fetching all Appfile dependencies...") 142 capp, err := compiler.Compile(app) 143 if err != nil { 144 c.Ui.Error(fmt.Sprintf( 145 "Error compiling Appfile: %s", err)) 146 return 1 147 } 148 149 // Get a core 150 core, err := c.Core(capp) 151 if err != nil { 152 c.Ui.Error(fmt.Sprintf( 153 "Error loading core: %s", err)) 154 return 1 155 } 156 157 // Get the active infrastructure just for UI reasons 158 infra := app.ActiveInfrastructure() 159 160 // Before the compilation, output to the user what is going on 161 ui.Header("Compiling...") 162 ui.Message(fmt.Sprintf( 163 "Application: %s (%s)", 164 app.Application.Name, 165 app.Application.Type)) 166 ui.Message(fmt.Sprintf("Project: %s", app.Project.Name)) 167 ui.Message(fmt.Sprintf( 168 "Infrastructure: %s (%s)", 169 infra.Type, 170 infra.Flavor)) 171 ui.Message("") 172 173 // Compile! 174 if err := core.Compile(); err != nil { 175 c.Ui.Error(fmt.Sprintf( 176 "Error compiling: %s", err)) 177 return 1 178 } 179 180 // Store the used plugins so later calls don't have to load everything 181 usedPath, err := c.AppfilePluginsPath(capp) 182 if err != nil { 183 c.Ui.Error(fmt.Sprintf( 184 "Error compiling: %s", err)) 185 return 1 186 } 187 if err := pluginMgr.StoreUsed(usedPath); err != nil { 188 c.Ui.Error(fmt.Sprintf( 189 "Error compiling plugin data: %s", err)) 190 return 1 191 } 192 193 // Success! 194 ui.Header("[green]Compilation success!") 195 ui.Message(fmt.Sprintf( 196 "[green]This means that Otto is now ready to start a development environment,\n" + 197 "deploy this application, build the supporting infrastructure, and\n" + 198 "more. See the help for more information.\n\n" + 199 "Supporting files to enable Otto to manage your application from\n" + 200 "development to deployment have been placed in the output directory.\n" + 201 "These files can be manually inspected to determine what Otto will do.")) 202 203 return 0 204 } 205 206 func (c *CompileCommand) Synopsis() string { 207 return "Prepares your project for being run" 208 } 209 210 func (c *CompileCommand) Help() string { 211 helpText := ` 212 Usage: otto [options] [path] 213 214 Compiles the Appfile into the set of supporting files used for 215 development, deploy, etc. If path is not specified, the current directory 216 is assumed. 217 218 This command will download and update any dependencies as well as 219 the import statements in your Appfile. This process only happens during 220 compilation so that every other Otto operation begins executing much 221 more quickly. 222 223 ` 224 225 return strings.TrimSpace(helpText) 226 } 227 228 func (c *CompileCommand) compileCallback(ui ui.Ui) func(appfile.CompileEvent) { 229 return func(raw appfile.CompileEvent) { 230 switch e := raw.(type) { 231 case *appfile.CompileEventDep: 232 ui.Message(fmt.Sprintf( 233 "Fetching dependency: %s", e.Source)) 234 case *appfile.CompileEventImport: 235 ui.Message(fmt.Sprintf( 236 "Fetching import: %s", e.Source)) 237 } 238 } 239 } 240 241 // Returns a loaded copy of any appfile.File we find, otherwise returns nil, 242 // which is valid, since Otto can detect app type and calculate defaults. 243 // Also returns the base dir of the appfile, which is the current WD in the 244 // case of a nil appfile. 245 func loadAppfile(flagAppfile string) (*appfile.File, string, error) { 246 appfilePath, err := findAppfile(flagAppfile) 247 if err != nil { 248 return nil, "", err 249 } 250 if appfilePath == "" { 251 wd, err := os.Getwd() 252 if err != nil { 253 return nil, "", err 254 } 255 return nil, wd, nil 256 } 257 app, err := appfile.ParseFile(appfilePath) 258 if err != nil { 259 return nil, "", err 260 } 261 return app, filepath.Dir(app.Path), nil 262 } 263 264 // findAppfile returns the path to an existing Appfile by checking the optional 265 // flag value and the current directory. It returns blank if it does not find 266 // any Appfiles 267 func findAppfile(flag string) (string, error) { 268 // First, if an Appfile was specified on the command-line, it must 269 // exist so we validate that it exists. 270 if flag != "" { 271 fi, err := os.Stat(flag) 272 if err != nil { 273 return "", fmt.Errorf("Error loading Appfile: %s", err) 274 } 275 276 if fi.IsDir() { 277 return findAppfileInDir(flag), nil 278 } else { 279 return flag, nil 280 } 281 } 282 283 // Otherwise we search through our current directory 284 wd, err := os.Getwd() 285 if err != nil { 286 return "", fmt.Errorf("Error loading working directory: %s", err) 287 } 288 return findAppfileInDir(wd), nil 289 } 290 291 // findAppfileInDir takes a path to a directory returns the path to the first 292 // existing Appfile by first looking for the DefaultAppfile and then looking 293 // for any AltAppfiles in the dir 294 func findAppfileInDir(path string) string { 295 if fi, err := os.Stat(filepath.Join(path, DefaultAppfile)); err == nil && !fi.IsDir() { 296 return filepath.Join(path, DefaultAppfile) 297 } 298 for _, aaf := range AltAppfiles { 299 if fi, err := os.Stat(filepath.Join(path, aaf)); err == nil && !fi.IsDir() { 300 return filepath.Join(path, aaf) 301 } 302 } 303 return "" 304 } 305 306 const errCantDetectType = ` 307 No Appfile is present and Otto couldn't detect the project type automatically. 308 Otto does its best without an Appfile to detect what kind of project this is 309 automatically, but sometimes this fails if the project is in a structure 310 Otto doesn't recognize or its a project type that Otto doesn't yet support. 311 312 Please create an Appfile and specify at a minimum the project name and type. Below 313 is an example minimal Appfile specifying the "my-app" application name and "go" 314 project type: 315 316 application { 317 name = "my-app" 318 type = "go" 319 } 320 321 If you believe Otto should've been able to automatically detect your 322 project type, then please open an issue with the Otto project. 323 `