diff --git a/CHANGELOG.md b/CHANGELOG.md index f13b0a1c5..eb5d81c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ FEATURES: IMPROVEMENTS: +* core: packer help output now loads much faster. * builder/virtualbox: Do not check for VirtualBox as part of template validation; only check at execution. * builder/vmware: Do not check for VMware as part of template validation; diff --git a/packer/environment.go b/packer/environment.go index f641774ec..34dd8ba7d 100644 --- a/packer/environment.go +++ b/packer/environment.go @@ -8,6 +8,7 @@ import ( "os" "sort" "strings" + "sync" ) // The function type used to lookup Builder implementations. @@ -68,6 +69,12 @@ type EnvironmentConfig struct { Ui Ui } +type helpCommandEntry struct { + i int + key string + synopsis string +} + // DefaultEnvironmentConfig returns a default EnvironmentConfig that can // be used to create a new enviroment with NewEnvironment with sane defaults. func DefaultEnvironmentConfig() *EnvironmentConfig { @@ -276,29 +283,69 @@ func (e *coreEnvironment) printHelp() { // Sort the keys sort.Strings(e.commands) + // Create the communication/sync mechanisms to get the synopsis' of + // the various commands. We do this in parallel since the overhead + // of the subprocess underneath is very expensive and this speeds things + // up an incredible amount. + var wg sync.WaitGroup + ch := make(chan *helpCommandEntry) + + for i, key := range e.commands { + wg.Add(1) + + // Get the synopsis in a goroutine since it may take awhile + // to subprocess out. + go func(i int, key string) { + defer wg.Done() + var synopsis string + command, err := e.components.Command(key) + if err != nil { + synopsis = fmt.Sprintf("Error loading command: %s", err.Error()) + } else if command == nil { + return + } else { + synopsis = command.Synopsis() + } + + // Pad the key with spaces so that they're all the same width + key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key))) + + // Output the command and the synopsis + ch <- &helpCommandEntry{ + i: i, + key: key, + synopsis: synopsis, + } + }(i, key) + } + e.ui.Say("usage: packer [--version] [--help] []\n") e.ui.Say("Available commands are:") - for _, key := range e.commands { - var synopsis string - command, err := e.components.Command(key) - if err != nil { - synopsis = fmt.Sprintf("Error loading command: %s", err.Error()) - } else if command == nil { - continue - } else { - synopsis = command.Synopsis() + // Make a goroutine that just waits for all the synopsis gathering + // to complete, and then output it. + synopsisDone := make(chan struct{}) + go func() { + defer close(synopsisDone) + entries := make([]string, len(e.commands)) + + for entry := range ch { + e.ui.Machine("command", entry.key, entry.synopsis) + message := fmt.Sprintf(" %s %s", entry.key, entry.synopsis) + entries[entry.i] = message } - // Machine-readable output of the available command - e.ui.Machine("command", key, synopsis) - - // Pad the key with spaces so that they're all the same width - key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key))) + for _, message := range entries { + if message != "" { + e.ui.Say(message) + } + } + }() - // Output the command and the synopsis - e.ui.Say(fmt.Sprintf(" %s %s", key, synopsis)) - } + // Wait to complete getting the synopsis' then close the channel + wg.Wait() + close(ch) + <-synopsisDone e.ui.Say("\nGlobally recognized options:") e.ui.Say(" -machine-readable Machine-readable output format.")