From db550f81cdacd6448197f264d09a7954aafe9f61 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 16 Dec 2018 18:28:18 +1100 Subject: [PATCH 01/12] Initial commit of the Setup command --- cmd/cli.go | 268 +++++++++++++++++++++++++++++++++++++++++++ cmd/fs.go | 130 +++++++++++++++++++++ cmd/log.go | 82 +++++++++++++ cmd/program.go | 43 +++++++ cmd/setup.go | 1 + cmd/system.go | 200 ++++++++++++++++++++++++++++++++ cmd/version.go | 5 + cmd/wails/0_setup.go | 60 ++++++++++ cmd/wails/main.go | 26 +++++ go.mod | 14 +++ go.sum | 21 ++++ 11 files changed, 850 insertions(+) create mode 100644 cmd/cli.go create mode 100644 cmd/fs.go create mode 100644 cmd/log.go create mode 100644 cmd/program.go create mode 100644 cmd/setup.go create mode 100644 cmd/system.go create mode 100644 cmd/version.go create mode 100644 cmd/wails/0_setup.go create mode 100644 cmd/wails/main.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/cmd/cli.go b/cmd/cli.go new file mode 100644 index 000000000..3577d5d8d --- /dev/null +++ b/cmd/cli.go @@ -0,0 +1,268 @@ +package cmd + +import ( + "flag" + "fmt" + "os" + "strings" +) + +// NewCli - Creates a new Cli application object +func NewCli(name, description string) *Cli { + result := &Cli{} + result.rootCommand = NewCommand(name, description, result, "") + result.log = NewLogger() + return result +} + +// Cli - The main application object +type Cli struct { + rootCommand *Command + defaultCommand *Command + preRunCommand func(*Cli) error + log *Logger +} + +// Version - Set the Application version string +func (c *Cli) Version(version string) { + c.rootCommand.AppVersion = version +} + +// PrintHelp - Prints the application's help +func (c *Cli) PrintHelp() { + c.rootCommand.PrintHelp() +} + +// Run - Runs the application with the given arguments +func (c *Cli) Run(args ...string) error { + if c.preRunCommand != nil { + err := c.preRunCommand(c) + if err != nil { + return err + } + } + if len(args) == 0 { + args = os.Args[1:] + } + return c.rootCommand.Run(args) +} + +// DefaultCommand - Sets the given command as the command to run when +// no other commands given +func (c *Cli) DefaultCommand(defaultCommand *Command) *Cli { + c.defaultCommand = defaultCommand + return c +} + +// Command - Adds a command to the application +func (c *Cli) Command(name, description string) *Command { + return c.rootCommand.Command(name, description) +} + +// PreRun - Calls the given function before running the specific command +func (c *Cli) PreRun(callback func(*Cli) error) { + c.preRunCommand = callback +} + +// BoolFlag - Adds a boolean flag to the root command +func (c *Cli) BoolFlag(name, description string, variable *bool) *Command { + c.rootCommand.BoolFlag(name, description, variable) + return c.rootCommand +} + +// StringFlag - Adds a string flag to the root command +func (c *Cli) StringFlag(name, description string, variable *string) *Command { + c.rootCommand.StringFlag(name, description, variable) + return c.rootCommand +} + +// Action represents a function that gets calls when the command is called by +// the user +type Action func() error + +// Command represents a command that may be run by the user +type Command struct { + Name string + CommandPath string + Shortdescription string + Longdescription string + AppVersion string + SubCommands []*Command + SubCommandsMap map[string]*Command + longestSubcommand int + ActionCallback Action + App *Cli + Flags *flag.FlagSet + flagCount int + log *Logger + helpFlag bool +} + +// NewCommand creates a new Command +func NewCommand(name string, description string, app *Cli, parentCommandPath string) *Command { + result := &Command{ + Name: name, + Shortdescription: description, + SubCommandsMap: make(map[string]*Command), + App: app, + } + + // Set up command path + if parentCommandPath != "" { + result.CommandPath += parentCommandPath + " " + } + result.CommandPath += name + + // Set up flag set + result.Flags = flag.NewFlagSet(result.CommandPath, flag.ContinueOnError) + result.BoolFlag("help", "Get help on the '"+result.CommandPath+"' command.", &result.helpFlag) + + // result.Flags.Usage = result.PrintHelp + + return result +} + +// parseFlags parses the given flags +func (c *Command) parseFlags(args []string) error { + // Parse flags + tmp := os.Stderr + os.Stderr = nil + err := c.Flags.Parse(args) + os.Stderr = tmp + if err != nil { + fmt.Printf("Error: %s\n\n", err.Error()) + c.PrintHelp() + } + return err +} + +// Run - Runs the Command with the given arguments +func (c *Command) Run(args []string) error { + + // If we have arguments, process them + if len(args) > 0 { + // Check for subcommand + subcommand := c.SubCommandsMap[args[0]] + if subcommand != nil { + return subcommand.Run(args[1:]) + } + + // Parse flags + err := c.parseFlags(args) + if err != nil { + fmt.Printf("Error: %s\n\n", err.Error()) + c.PrintHelp() + return err + } + + // Help takes precedence + if c.helpFlag { + c.PrintHelp() + return nil + } + } + + // Do we have an action? + if c.ActionCallback != nil { + return c.ActionCallback() + } + + // If we haven't specified a subcommand + // check for an app level default command + if c.App.defaultCommand != nil { + // Prevent recursion! + if c.App.defaultCommand != c { + return c.App.defaultCommand.Run(args) + } + } + + // Nothing left we can do + c.PrintHelp() + return nil +} + +// Action - Define an action from this command +func (c *Command) Action(callback Action) *Command { + c.ActionCallback = callback + return c +} + +// PrintHelp - Output the help text for this command +func (c *Command) PrintHelp() { + versionString := c.AppVersion + if versionString != "" { + versionString = " " + versionString + } + commandTitle := c.CommandPath + if c.Shortdescription != "" { + commandTitle += " - " + c.Shortdescription + } + // Ignore root command + if c.CommandPath != c.Name { + c.log.Yellow(commandTitle) + } + if c.Longdescription != "" { + fmt.Println() + fmt.Println(c.Longdescription + "\n") + } + if len(c.SubCommands) > 0 { + fmt.Println("") + c.log.White("Available commands:") + fmt.Println("") + for _, subcommand := range c.SubCommands { + spacer := strings.Repeat(" ", 3+c.longestSubcommand-len(subcommand.Name)) + isDefault := "" + if subcommand.isDefaultCommand() { + isDefault = "[default]" + } + fmt.Printf(" %s%s%s %s\n", subcommand.Name, spacer, subcommand.Shortdescription, isDefault) + } + } + if c.flagCount > 0 { + fmt.Println("") + c.log.White("Flags:") + fmt.Println() + c.Flags.SetOutput(os.Stdout) + c.Flags.PrintDefaults() + c.Flags.SetOutput(os.Stderr) + + } + fmt.Println() +} + +// isDefaultCommand returns true if called on the default command +func (c *Command) isDefaultCommand() bool { + return c.App.defaultCommand == c +} + +// Command - Defines a subcommand +func (c *Command) Command(name, description string) *Command { + result := NewCommand(name, description, c.App, c.CommandPath) + result.log = c.log + c.SubCommands = append(c.SubCommands, result) + c.SubCommandsMap[name] = result + if len(name) > c.longestSubcommand { + c.longestSubcommand = len(name) + } + return result +} + +// BoolFlag - Adds a boolean flag to the command +func (c *Command) BoolFlag(name, description string, variable *bool) *Command { + c.Flags.BoolVar(variable, name, *variable, description) + c.flagCount++ + return c +} + +// StringFlag - Adds a string flag to the command +func (c *Command) StringFlag(name, description string, variable *string) *Command { + c.Flags.StringVar(variable, name, *variable, description) + c.flagCount++ + return c +} + +// LongDescription - Sets the long description for the command +func (c *Command) LongDescription(Longdescription string) *Command { + c.Longdescription = Longdescription + return c +} diff --git a/cmd/fs.go b/cmd/fs.go new file mode 100644 index 000000000..d517f3284 --- /dev/null +++ b/cmd/fs.go @@ -0,0 +1,130 @@ +package cmd + +import ( + "crypto/md5" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" +) + +type FSHelper struct { +} + +func NewFSHelper() *FSHelper { + result := &FSHelper{} + return result +} + +// Returns true if the given path resolves to a directory on the filesystem +func (fs *FSHelper) DirExists(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + + return fi.Mode().IsDir() +} + +// FileExists returns a boolean value indicating whether +// the given file exists +func (fs *FSHelper) FileExists(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + + return fi.Mode().IsRegular() +} + +// MkDirs creates the given nested directories. +// Returns error on failure +func (fs *FSHelper) MkDirs(fullPath string, mode ...os.FileMode) error { + var perms os.FileMode + perms = 0700 + if len(mode) == 1 { + perms = mode[0] + } + return os.MkdirAll(fullPath, perms) +} + +// CopyFile from source to target +func (fs *FSHelper) CopyFile(source, target string) error { + s, err := os.Open(source) + if err != nil { + return err + } + defer s.Close() + d, err := os.Create(target) + if err != nil { + return err + } + if _, err := io.Copy(d, s); err != nil { + d.Close() + return err + } + return d.Close() +} + +// Cwd returns the current working directory +// Aborts on Failure +func (fs *FSHelper) Cwd() string { + cwd, err := os.Getwd() + if err != nil { + log.Fatal("Unable to get working directory!") + } + return cwd +} + +// GetSubdirs will return a list of FQPs to subdirectories in the given directory +func (fs *FSHelper) GetSubdirs(dir string) (map[string]string, error) { + + // Read in the directory information + fileInfo, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + // Allocate space for the list + subdirs := make(map[string]string) + + // Pull out the directories and store in the map as + // map["directoryName"] = "path/to/directoryName" + for _, file := range fileInfo { + if file.IsDir() { + subdirs[file.Name()] = filepath.Join(dir, file.Name()) + } + } + return subdirs, nil +} + +// MkDir creates the given directory. +// Returns error on failure +func (fs *FSHelper) MkDir(dir string) error { + return os.Mkdir(dir, 0700) +} + +// LoadAsString will attempt to load the given file and return +// its contents as a string +func (fs *FSHelper) LoadAsString(filename string) (string, error) { + bytes, err := ioutil.ReadFile(filename) + return string(bytes), err +} + +// FileMD5 returns the md5sum of the given file +func (fs *FSHelper) FileMD5(filename string) (string, error) { + f, err := os.Open(filename) + if err != nil { + return "", err + } + defer f.Close() + + h := md5.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + + return fmt.Sprintf("%x", h.Sum(nil)), nil +} diff --git a/cmd/log.go b/cmd/log.go new file mode 100644 index 000000000..3da1f7d2e --- /dev/null +++ b/cmd/log.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" +) + +// Logger struct +type Logger struct { +} + +// NewLogger creates a new logger! +func NewLogger() *Logger { + return &Logger{} +} + +// Yellow - Outputs yellow text +func (l *Logger) Yellow(format string, a ...interface{}) { + color.New(color.FgHiYellow).PrintfFunc()(format+"\n", a...) +} + +// Yellowf - Outputs yellow text without the newline +func (l *Logger) Yellowf(format string, a ...interface{}) { + color.New(color.FgHiYellow).PrintfFunc()(format, a...) +} + +// Green - Outputs Green text +func (l *Logger) Green(format string, a ...interface{}) { + color.New(color.FgHiGreen).PrintfFunc()(format+"\n", a...) +} + +// White - Outputs White text +func (l *Logger) White(format string, a ...interface{}) { + color.New(color.FgHiWhite).PrintfFunc()(format+"\n", a...) +} + +// WhiteUnderline - Outputs White text with underline +func (l *Logger) WhiteUnderline(format string, a ...interface{}) { + l.White(format, a...) + l.White(l.underline(format)) +} + +// YellowUnderline - Outputs Yellow text with underline +func (l *Logger) YellowUnderline(format string, a ...interface{}) { + l.Yellow(format, a...) + l.Yellow(l.underline(format)) +} + +// underline returns a string of a line, the length of the message given to it +func (l *Logger) underline(message string) string { + return strings.Repeat("-", len(message)) +} + +// Red - Outputs Red text +func (l *Logger) Red(format string, a ...interface{}) { + color.New(color.FgHiRed).PrintfFunc()(format+"\n", a...) +} + +// Error - Outputs an Error message +func (l *Logger) Error(format string, a ...interface{}) { + color.New(color.FgHiRed).PrintfFunc()("Error: "+format+"\n", a...) +} + +// PrintBanner prints the Wails banner before running commands +func (l *Logger) PrintBanner() error { + banner1 := ` _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ ` + "`" + `/ / / ___/ +| |/ |/ / /_/ / / (__ ) ` + banner2 := `|__/|__/\__,_/_/_/____/ ` + + l.Yellowf(banner1) + l.Red(Version) + l.Yellowf(banner2) + l.Green("https://wails.app") + l.White("The lightweight framework for web-like apps") + fmt.Println() + + return nil +} diff --git a/cmd/program.go b/cmd/program.go new file mode 100644 index 000000000..c19ed989d --- /dev/null +++ b/cmd/program.go @@ -0,0 +1,43 @@ +package cmd + +import ( + "os/exec" + "path/filepath" +) + +// ProgramHelper - Utility functions around installed applications +type ProgramHelper struct{} + +// NewProgramHelper - Creates a new ProgramHelper +func NewProgramHelper() *ProgramHelper { + return &ProgramHelper{} +} + +// IsInstalled tries to determine if the given binary name is installed +func (p *ProgramHelper) IsInstalled(programName string) bool { + _, err := exec.LookPath(programName) + return err == nil +} + +// Program - A struct to define an installed application/binary +type Program struct { + Name string `json:"name"` + Path string `json:"path"` +} + +// FindProgram attempts to find the given program on the system.FindProgram +// Returns a struct with the name and path to the program +func (p *ProgramHelper) FindProgram(programName string) *Program { + path, err := exec.LookPath(programName) + if err != nil { + return nil + } + path, err = filepath.Abs(path) + if err != nil { + return nil + } + return &Program{ + Name: programName, + Path: path, + } +} diff --git a/cmd/setup.go b/cmd/setup.go new file mode 100644 index 000000000..1d619dd05 --- /dev/null +++ b/cmd/setup.go @@ -0,0 +1 @@ +package cmd diff --git a/cmd/system.go b/cmd/system.go new file mode 100644 index 000000000..1e657ab87 --- /dev/null +++ b/cmd/system.go @@ -0,0 +1,200 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "path/filepath" + "strconv" + "time" + + "github.com/AlecAivazis/survey" + homedir "github.com/mitchellh/go-homedir" +) + +type SystemHelper struct { + log *Logger + fs *FSHelper + configFilename string + homeDir string + wailsSystemDir string + wailsSystemConfig string +} + +func NewSystemHelper() *SystemHelper { + result := &SystemHelper{ + fs: NewFSHelper(), + log: NewLogger(), + configFilename: "wails.json", + } + result.setSystemDirs() + return result +} + +// Internal +// setSystemDirs calculates the system directories it is interested in +func (s *SystemHelper) setSystemDirs() { + var err error + s.homeDir, err = homedir.Dir() + if err != nil { + log.Fatal("Cannot find home directory! Please file a bug report!") + } + // TODO: A better config system + s.wailsSystemDir = filepath.Join(s.homeDir, ".wails") + s.wailsSystemConfig = filepath.Join(s.wailsSystemDir, s.configFilename) +} + +// ConfigFileExists - Returns true if it does! +func (s *SystemHelper) ConfigFileExists() bool { + return s.fs.FileExists(s.wailsSystemConfig) +} + +// SystemDirExists - Returns true if it does! +func (s *SystemHelper) systemDirExists() bool { + return s.fs.DirExists(s.wailsSystemDir) +} + +// LoadConfig attempts to load the Wails system config +func (s *SystemHelper) LoadConfig() (*SystemConfig, error) { + return NewSystemConfig(s.wailsSystemConfig) +} + +// ConfigFileIsValid checks if the config file is valid +func (s *SystemHelper) ConfigFileIsValid() bool { + _, err := NewSystemConfig(s.wailsSystemConfig) + return err == nil +} + +// BackupConfig attempts to backup the system config file +func (s *SystemHelper) BackupConfig() (string, error) { + now := strconv.FormatInt(time.Now().UTC().UnixNano(), 10) + backupFilename := s.wailsSystemConfig + "." + now + err := s.fs.CopyFile(s.wailsSystemConfig, backupFilename) + if err != nil { + return "", err + } + return backupFilename, nil +} + +func (s *SystemHelper) setup() error { + + // Answers. We all need them. + answers := &SystemConfig{} + + // Try to load current values - ignore errors + config, err := s.LoadConfig() + defaultName := "" + defaultEmail := "" + if config != nil { + defaultName = config.Name + defaultEmail = config.Email + } + // Questions + var simpleQs = []*survey.Question{ + { + Name: "Name", + Prompt: &survey.Input{ + Message: "What is your name:", + Default: defaultName, + }, + Validate: survey.Required, + }, + { + Name: "Email", + Prompt: &survey.Input{ + Message: "What is your email address:", + Default: defaultEmail, + }, + Validate: survey.Required, + }, + } + + // ask the questions + err = survey.Ask(simpleQs, answers) + if err != nil { + return err + } + + // Create the directory + err = s.fs.MkDirs(s.wailsSystemDir) + if err != nil { + return err + } + + fmt.Println() + s.log.White("Wails config saved to: " + s.wailsSystemConfig) + s.log.White("Feel free to customise these settings.") + fmt.Println() + + return answers.Save(s.wailsSystemConfig) +} + +// Initialise attempts to set up the Wails system. +// An error is returns if there is a problem +func (s *SystemHelper) Initialise() error { + + // System dir doesn't exist + if !s.systemDirExists() { + s.log.Green("Welcome to Wails!") + s.log.Green("To get you set up, I'll need to ask you a few things...") + return s.setup() + } + + // Config doesn't exist + if !s.ConfigFileExists() { + s.log.Green("Looks like the system config is missing.") + s.log.Green("To get you back on track, I'll need to ask you a few things...") + return s.setup() + } + + // Config exists but isn't valid. + if !s.ConfigFileIsValid() { + s.log.Green("Looks like the system config got corrupted.") + backupFile, err := s.BackupConfig() + if err != nil { + s.log.Green("I tried to backup your config file but got this error: %s", err.Error()) + } else { + s.log.Green("Just in case you needed it, I backed up your config file here: %s", backupFile) + } + s.log.Green("To get you back on track, I'll need to ask you a few things...") + return s.setup() + } + + return s.setup() +} + +type SystemConfig struct { + Name string `json:"name"` + Email string `json:"email"` +} + +func NewSystemConfig(filename string) (*SystemConfig, error) { + result := &SystemConfig{} + err := result.load(filename) + return result, err +} + +func (sc *SystemConfig) Save(filename string) error { + // Convert config to JSON string + theJSON, err := json.MarshalIndent(sc, "", " ") + if err != nil { + return err + } + + // Write it out to the config file + return ioutil.WriteFile(filename, theJSON, 0644) +} + +func (sc *SystemConfig) load(filename string) error { + configData, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + // Load and unmarshall! + err = json.Unmarshal(configData, &sc) + if err != nil { + return err + } + return nil +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 000000000..31f1d5364 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,5 @@ +package cmd + +// Version - Wails version +// ...oO(There must be a better way) +const Version = "v0.5 Alpha" diff --git a/cmd/wails/0_setup.go b/cmd/wails/0_setup.go new file mode 100644 index 000000000..141c19506 --- /dev/null +++ b/cmd/wails/0_setup.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "runtime" + + "github.com/wailsapp/wails/cmd" +) + +func init() { + + commandDescription := `Sets up your local environment to develop Wails apps.` + + initCommand := app.Command("setup", "Setup the Wails environment"). + LongDescription(commandDescription) + + initCommand.Action(func() error { + + system := cmd.NewSystemHelper() + err := system.Initialise() + if err != nil { + return err + } + + var successMessage string + + logger.Yellow("Checking for prerequisites...") + // Check we have a cgo capable environment + programHelper := cmd.NewProgramHelper() + prerequisites := make(map[string]map[string]string) + prerequisites["darwin"] = make(map[string]string) + prerequisites["darwin"]["clang"] = "Please install with `xcode-select --install` and try again" + prerequisites["darwin"]["npm"] = "Please download and install npm + node from here: https://nodejs.org/en/" + switch runtime.GOOS { + case "darwin": + successMessage = "🚀 Awesome! We are going to the moon! 🚀" + default: + return fmt.Errorf("platform '%s' is unsupported at this time", runtime.GOOS) + } + + errors := false + for name, help := range prerequisites[runtime.GOOS] { + bin := programHelper.FindProgram(name) + if bin == nil { + errors = true + logger.Red("Unable to find '%s' - %s", name, help) + } else { + logger.Green("Found program '%s' at '%s'", name, bin.Path) + } + } + + if errors { + err = fmt.Errorf("There were missing dependencies") + } else { + logger.Yellow(successMessage) + } + + return err + }) +} diff --git a/cmd/wails/main.go b/cmd/wails/main.go new file mode 100644 index 000000000..62258d618 --- /dev/null +++ b/cmd/wails/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/wailsapp/wails/cmd" +) + +// Create Logger +var logger = cmd.NewLogger() + +// Create main app +var app = cmd.NewCli("wails", "A cli tool for building Wails applications.") + +// Prints the cli banner +func printBanner(app *cmd.Cli) error { + logger.PrintBanner() + return nil +} + +// Main! +func main() { + app.PreRun(printBanner) + err := app.Run() + if err != nil { + logger.Error(err.Error()) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..088821717 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/wailsapp/wails + +require ( + github.com/AlecAivazis/survey v1.7.1 + github.com/fatih/color v1.7.0 + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/leaanthony/spinner v0.5.0 + github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0 // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mitchellh/go-homedir v1.0.0 + gopkg.in/AlecAivazis/survey.v1 v1.7.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..f2b808ff3 --- /dev/null +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/AlecAivazis/survey v1.7.1 h1:a84v5MG2296rBkTP0e+dd4l7NxFQ69v4jzMpErkjVxc= +github.com/AlecAivazis/survey v1.7.1/go.mod h1:MVECab6WqEH1aXhj8nKIwF7HEAJAj2bhhGiSjNy3wII= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/leaanthony/spinner v0.5.0 h1:OJKn+0KP6ilHxwCEOv5Lo0wPM4PgWZWLJTeUprGJK0g= +github.com/leaanthony/spinner v0.5.0/go.mod h1:2Mmv+8Brcw3NwPT1DdOLmW6+zWpSamDDFFsUvVHo2cc= +github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0 h1:1bGojw4YacLY5bqQalojiQ7mSfQbe4WIWCEgPZagowU= +github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0/go.mod h1:Iz7eybeeG8bdq640iR+CwYb8p+9EOsgMWghkSRyZcqs= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/wailsapp/wails v0.0.0-20181215232634-5de8efff325d h1:lk91T4sKD98eGcaz/xC6ER+3o9Kaun7Mk8e/cNZOPMc= +gopkg.in/AlecAivazis/survey.v1 v1.7.1 h1:mzQIVyOPSXJaQWi1m6AFCjrCEPIwQBSOn48Ri8ZpzAg= +gopkg.in/AlecAivazis/survey.v1 v1.7.1/go.mod h1:2Ehl7OqkBl3Xb8VmC4oFW2bItAhnUfzIjrOzwRxCrOU= From 04c64c1beac5ea19d0396e4c12131fc682314b3a Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 16 Dec 2018 18:31:49 +1100 Subject: [PATCH 02/12] docs: Updated code comments --- cmd/fs.go | 4 +++- cmd/system.go | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/fs.go b/cmd/fs.go index d517f3284..8eea0d3de 100644 --- a/cmd/fs.go +++ b/cmd/fs.go @@ -10,15 +10,17 @@ import ( "path/filepath" ) +// FSHelper - Wrapper struct for File System utility commands type FSHelper struct { } +// NewFSHelper - Returns a new FSHelper func NewFSHelper() *FSHelper { result := &FSHelper{} return result } -// Returns true if the given path resolves to a directory on the filesystem +// DirExists - Returns true if the given path resolves to a directory on the filesystem func (fs *FSHelper) DirExists(path string) bool { fi, err := os.Lstat(path) if err != nil { diff --git a/cmd/system.go b/cmd/system.go index 1e657ab87..c1d1a2a30 100644 --- a/cmd/system.go +++ b/cmd/system.go @@ -13,6 +13,7 @@ import ( homedir "github.com/mitchellh/go-homedir" ) +// SystemHelper - Defines everything related to the system type SystemHelper struct { log *Logger fs *FSHelper @@ -22,6 +23,7 @@ type SystemHelper struct { wailsSystemConfig string } +// NewSystemHelper - Creates a new System Helper func NewSystemHelper() *SystemHelper { result := &SystemHelper{ fs: NewFSHelper(), @@ -164,17 +166,20 @@ func (s *SystemHelper) Initialise() error { return s.setup() } +// SystemConfig - Defines system wode configuration data type SystemConfig struct { Name string `json:"name"` Email string `json:"email"` } +// NewSystemConfig - Creates a new SystemConfig helper object func NewSystemConfig(filename string) (*SystemConfig, error) { result := &SystemConfig{} err := result.load(filename) return result, err } +// Save - Saves the system config to the given filename func (sc *SystemConfig) Save(filename string) error { // Convert config to JSON string theJSON, err := json.MarshalIndent(sc, "", " ") From 6edd941c1a09f842a2688eb19babc3d15ae6a077 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 16 Dec 2018 20:16:40 +1100 Subject: [PATCH 03/12] Initial Linux distro detection --- cmd/linux.go | 34 ++++++++++++++++++++++++++++++++++ cmd/program.go | 23 +++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 cmd/linux.go diff --git a/cmd/linux.go b/cmd/linux.go new file mode 100644 index 000000000..067e3068c --- /dev/null +++ b/cmd/linux.go @@ -0,0 +1,34 @@ +package cmd + +import "fmt" + +// LinuxDistribution is of type int +type LinuxDistribution int + +const ( + // Ubuntu distro + Ubuntu LinuxDistribution = 0 +) + +// DistroInfo contains all the information relating to a linux distribution +type DistroInfo struct { + distribution LinuxDistribution + name string + release string +} + +func getLinuxDistroInfo() *DistroInfo { + result := &DistroInfo{} + program := NewProgramHelper() + // Does lsb_release exist? + + lsbRelease := program.FindProgram("lsb_release") + if lsbRelease != nil { + stdout, _, err := lsbRelease.Run("-a") + if err != nil { + return nil + } + fmt.Println(stdout) + } + return result +} diff --git a/cmd/program.go b/cmd/program.go index c19ed989d..836b5ad63 100644 --- a/cmd/program.go +++ b/cmd/program.go @@ -1,6 +1,7 @@ package cmd import ( + "bytes" "os/exec" "path/filepath" ) @@ -41,3 +42,25 @@ func (p *ProgramHelper) FindProgram(programName string) *Program { Path: path, } } + +func (p *Program) GetFullPathToBinary() (string, error) { + result := filepath.Join(p.Path, p.Name) + return filepath.Abs(result) +} + +// Run will execute the program with the given parameters +// Returns stdout + stderr as strings and an error if one occured +func (p *Program) Run(vars ...string) (stdout, stderr string, err error) { + command, err := p.GetFullPathToBinary() + if err != nil { + return "", "", err + } + cmd := exec.Command(command, vars...) + var stdo, stde bytes.Buffer + cmd.Stdout = &stdo + cmd.Stderr = &stde + err = cmd.Run() + stdout = string(stdo.Bytes()) + stderr = string(stde.Bytes()) + return +} From 54a88ae3d9e9b74fa9bea7e53573c160ab8d598f Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Thu, 20 Dec 2018 06:58:06 +1100 Subject: [PATCH 04/12] feat: debug config --- .vscode/launch.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..bc0d6013e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/wails/main.go", + "env": {}, + "args": [ + "setup" + ] + } + ] +} \ No newline at end of file From ea5e06aabd98ceb0c11f3da79fd45e74bcfba271 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Thu, 20 Dec 2018 06:58:55 +1100 Subject: [PATCH 05/12] feat: prerequisite checking (osx) --- cmd/prerequisites.go | 79 ++++++++++++++++++++++++++++++++++++++++++++ cmd/wails/0_setup.go | 50 ++++++++++++++-------------- 2 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 cmd/prerequisites.go diff --git a/cmd/prerequisites.go b/cmd/prerequisites.go new file mode 100644 index 000000000..bdc8c5731 --- /dev/null +++ b/cmd/prerequisites.go @@ -0,0 +1,79 @@ +package cmd + +import ( + "fmt" + "runtime" +) + +// binaryPrerequisite defines a binaryPrerequisite +type binaryPrerequisite struct { + Name string + Help string + Path string +} + +func newBinaryPrerequisite(name, help string) *binaryPrerequisite { + return &binaryPrerequisite{Name: name, Help: help} +} + +// binaryPrerequisites is a list of binaryPrerequisites +type binaryPrerequisites []*binaryPrerequisite + +// Add given prereq object to list +func (p *binaryPrerequisites) Add(prereq *binaryPrerequisite) { + *p = append(*p, prereq) +} + +func (p *binaryPrerequisites) check() (success *binaryPrerequisites, failed *binaryPrerequisites) { + success = &binaryPrerequisites{} + failed = &binaryPrerequisites{} + programHelper := NewProgramHelper() + for _, prereq := range *p { + bin := programHelper.FindProgram(prereq.Name) + if bin == nil { + failed.Add(prereq) + } else { + path, err := bin.GetFullPathToBinary() + if err != nil { + failed.Add(prereq) + } else { + prereq.Path = path + success.Add(prereq) + } + } + } + + return success, failed +} + +var platformbinaryPrerequisites = make(map[string]*binaryPrerequisites) + +func init() { + platformbinaryPrerequisites["darwin"] = &binaryPrerequisites{} + newDarwinbinaryPrerequisite("clang", "Please install with `xcode-select --install` and try again") +} + +func newDarwinbinaryPrerequisite(name, help string) { + prereq := newBinaryPrerequisite(name, help) + platformbinaryPrerequisites["darwin"].Add(prereq) +} + +func CheckBinaryPrerequisites() (*binaryPrerequisites, *binaryPrerequisites, error) { + platformPreReqs := platformbinaryPrerequisites[runtime.GOOS] + if platformPreReqs == nil { + return nil, nil, fmt.Errorf("Platform '%s' is not supported at this time", runtime.GOOS) + } + success, failed := platformPreReqs.check() + return success, failed, nil +} + +func CheckNonBinaryPrerequisites() error { + + var err error + + // Check non-binaries + if runtime.GOOS == "linux" { + + } + return err +} diff --git a/cmd/wails/0_setup.go b/cmd/wails/0_setup.go index 141c19506..27d0b118f 100644 --- a/cmd/wails/0_setup.go +++ b/cmd/wails/0_setup.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "runtime" "github.com/wailsapp/wails/cmd" @@ -22,36 +21,37 @@ func init() { return err } - var successMessage string - + var successMessage = `Ready for take off! +Create your first project by running 'wails init'.` + if runtime.GOOS != "windows" { + successMessage = "🚀 " + successMessage + } logger.Yellow("Checking for prerequisites...") // Check we have a cgo capable environment - programHelper := cmd.NewProgramHelper() - prerequisites := make(map[string]map[string]string) - prerequisites["darwin"] = make(map[string]string) - prerequisites["darwin"]["clang"] = "Please install with `xcode-select --install` and try again" - prerequisites["darwin"]["npm"] = "Please download and install npm + node from here: https://nodejs.org/en/" - switch runtime.GOOS { - case "darwin": - successMessage = "🚀 Awesome! We are going to the moon! 🚀" - default: - return fmt.Errorf("platform '%s' is unsupported at this time", runtime.GOOS) + + successDeps, failedDeps, err := cmd.CheckBinaryPrerequisites() + if err != nil { + return err } - errors := false - for name, help := range prerequisites[runtime.GOOS] { - bin := programHelper.FindProgram(name) - if bin == nil { - errors = true - logger.Red("Unable to find '%s' - %s", name, help) - } else { - logger.Green("Found program '%s' at '%s'", name, bin.Path) - } + for _, dep := range *successDeps { + logger.Green("Found '%s' at '%s'", dep.Name, dep.Path) } - if errors { - err = fmt.Errorf("There were missing dependencies") - } else { + logger.White("") + + for _, dep := range *failedDeps { + logger.Red("PreRequisite '%s' missing. %s", dep.Name, dep.Help) + } + + // Check non-binary prerequisites + err = cmd.CheckNonBinaryPrerequisites() + + if err != nil { + return err + } + + if len(*failedDeps) == 0 { logger.Yellow(successMessage) } From 6ecdb43148b623367ccf5bc90810464a2cac748a Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Thu, 20 Dec 2018 07:10:49 +1100 Subject: [PATCH 06/12] feature: show detected os --- cmd/wails/0_setup.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/wails/0_setup.go b/cmd/wails/0_setup.go index 27d0b118f..9af45f971 100644 --- a/cmd/wails/0_setup.go +++ b/cmd/wails/0_setup.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "runtime" "github.com/wailsapp/wails/cmd" @@ -26,6 +27,17 @@ Create your first project by running 'wails init'.` if runtime.GOOS != "windows" { successMessage = "🚀 " + successMessage } + switch runtime.GOOS { + case "darwin": + logger.Yellow("Detected Platform: OSX") + case "windows": + logger.Yellow("Detected Platform: Windows") + case "linux": + logger.Yellow("Detected Platform: Linux") + default: + return fmt.Errorf("Platform %s is currently not supported", runtime.GOOS) + } + logger.Yellow("Checking for prerequisites...") // Check we have a cgo capable environment From 5e42d67570183c68125339a6a34c522a9ef27cce Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 19 Dec 2018 12:49:04 -0800 Subject: [PATCH 07/12] feat: add linux prereq checking --- cmd/linux.go | 38 +++++++++++++++++++++++++++++++------- cmd/prerequisites.go | 13 +++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/cmd/linux.go b/cmd/linux.go index 067e3068c..baecd48e4 100644 --- a/cmd/linux.go +++ b/cmd/linux.go @@ -1,6 +1,8 @@ package cmd -import "fmt" +import ( + "strings" +) // LinuxDistribution is of type int type LinuxDistribution int @@ -12,23 +14,45 @@ const ( // DistroInfo contains all the information relating to a linux distribution type DistroInfo struct { - distribution LinuxDistribution - name string - release string + Distribution LinuxDistribution + Description string + Release string + Codename string + DistributorId string } -func getLinuxDistroInfo() *DistroInfo { +func GetLinuxDistroInfo() *DistroInfo { result := &DistroInfo{} program := NewProgramHelper() // Does lsb_release exist? lsbRelease := program.FindProgram("lsb_release") if lsbRelease != nil { - stdout, _, err := lsbRelease.Run("-a") + stdout, _, err, _ := lsbRelease.Run("-a") if err != nil { return nil } - fmt.Println(stdout) + + for _, line := range strings.Split(stdout, "\n") { + if strings.Contains(line, ":") { + // Iterate lines a + details := strings.Split(line, ":") + key := strings.TrimSpace(details[0]) + value := strings.TrimSpace(details[1]) + switch key { + case "Distributor ID": + result.DistributorId = value + case "Description": + result.Description = value + case "Release": + result.Release = value + case "Codename": + result.Codename = value + + } + } + } + } return result } diff --git a/cmd/prerequisites.go b/cmd/prerequisites.go index bdc8c5731..d5dcd2c78 100644 --- a/cmd/prerequisites.go +++ b/cmd/prerequisites.go @@ -51,6 +51,14 @@ var platformbinaryPrerequisites = make(map[string]*binaryPrerequisites) func init() { platformbinaryPrerequisites["darwin"] = &binaryPrerequisites{} newDarwinbinaryPrerequisite("clang", "Please install with `xcode-select --install` and try again") + platformbinaryPrerequisites["linux"] = &binaryPrerequisites{} + linuxInfo := GetLinuxDistroInfo() + switch linuxInfo.Distribution { + case Ubuntu: + newLinuxbinaryPrerequisite("gcc", "Please install with 'sudo apt install build-essential' ") + default: + newLinuxbinaryPrerequisite("gcc", "Please install with your system package manager.") + } } func newDarwinbinaryPrerequisite(name, help string) { @@ -58,6 +66,11 @@ func newDarwinbinaryPrerequisite(name, help string) { platformbinaryPrerequisites["darwin"].Add(prereq) } +func newLinuxbinaryPrerequisite(name, help string) { + prereq := newBinaryPrerequisite(name, help) + platformbinaryPrerequisites["linux"].Add(prereq) +} + func CheckBinaryPrerequisites() (*binaryPrerequisites, *binaryPrerequisites, error) { platformPreReqs := platformbinaryPrerequisites[runtime.GOOS] if platformPreReqs == nil { From 3e97a4cc4d5d0cc83e10335501db3279362fb26f Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 19 Dec 2018 12:49:21 -0800 Subject: [PATCH 08/12] feat: add exit code to run --- cmd/program.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/cmd/program.go b/cmd/program.go index 836b5ad63..c579c0e2c 100644 --- a/cmd/program.go +++ b/cmd/program.go @@ -4,6 +4,7 @@ import ( "bytes" "os/exec" "path/filepath" + "syscall" ) // ProgramHelper - Utility functions around installed applications @@ -44,16 +45,15 @@ func (p *ProgramHelper) FindProgram(programName string) *Program { } func (p *Program) GetFullPathToBinary() (string, error) { - result := filepath.Join(p.Path, p.Name) - return filepath.Abs(result) + return filepath.Abs(p.Path) } // Run will execute the program with the given parameters // Returns stdout + stderr as strings and an error if one occured -func (p *Program) Run(vars ...string) (stdout, stderr string, err error) { +func (p *Program) Run(vars ...string) (stdout, stderr string, err error, exitCode int) { command, err := p.GetFullPathToBinary() if err != nil { - return "", "", err + return "", "", err, 1 } cmd := exec.Command(command, vars...) var stdo, stde bytes.Buffer @@ -62,5 +62,23 @@ func (p *Program) Run(vars ...string) (stdout, stderr string, err error) { err = cmd.Run() stdout = string(stdo.Bytes()) stderr = string(stde.Bytes()) + + // https://stackoverflow.com/questions/10385551/get-exit-code-go + if err != nil { + // try to get the exit code + if exitError, ok := err.(*exec.ExitError); ok { + ws := exitError.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } else { + exitCode = 1 + if stderr == "" { + stderr = err.Error() + } + } + } else { + // success, exitCode should be 0 if go is ok + ws := cmd.ProcessState.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } return } From 807f77205594435d1627a07dc6f92816d62964aa Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 22 Dec 2018 18:40:36 -0800 Subject: [PATCH 09/12] feat: support checking prerequisites --- cmd/linux.go | 32 ++++++++-- cmd/prerequisites.go | 140 +++++++++++++++++++++++-------------------- cmd/wails/0_setup.go | 57 +++++++++++++----- 3 files changed, 144 insertions(+), 85 deletions(-) diff --git a/cmd/linux.go b/cmd/linux.go index baecd48e4..b812d3fb6 100644 --- a/cmd/linux.go +++ b/cmd/linux.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "strings" ) @@ -8,8 +9,9 @@ import ( type LinuxDistribution int const ( - // Ubuntu distro - Ubuntu LinuxDistribution = 0 + // Unknown is the catch-all distro + Unknown LinuxDistribution = 0 + Ubuntu LinuxDistribution = 1 ) // DistroInfo contains all the information relating to a linux distribution @@ -18,11 +20,12 @@ type DistroInfo struct { Description string Release string Codename string - DistributorId string + DistributorID string } +// GetLinuxDistroInfo returns information about the running linux distribution func GetLinuxDistroInfo() *DistroInfo { - result := &DistroInfo{} + result := &DistroInfo{Distribution: Unknown} program := NewProgramHelper() // Does lsb_release exist? @@ -30,7 +33,7 @@ func GetLinuxDistroInfo() *DistroInfo { if lsbRelease != nil { stdout, _, err, _ := lsbRelease.Run("-a") if err != nil { - return nil + return result } for _, line := range strings.Split(stdout, "\n") { @@ -41,7 +44,11 @@ func GetLinuxDistroInfo() *DistroInfo { value := strings.TrimSpace(details[1]) switch key { case "Distributor ID": - result.DistributorId = value + result.DistributorID = value + switch value { + case "Ubuntu": + result.Distribution = Ubuntu + } case "Description": result.Description = value case "Release": @@ -56,3 +63,16 @@ func GetLinuxDistroInfo() *DistroInfo { } return result } + +// DpkgInstalled uses dpkg to see if a package is installed +func DpkgInstalled(packageName string) (bool, error) { + result := false + program := NewProgramHelper() + dpkg := program.FindProgram("dpkg") + if dpkg == nil { + return false, fmt.Errorf("cannot check dependencies: dpkg not found") + } + _, _, _, exitCode := dpkg.Run("-L", packageName) + result = exitCode == 0 + return result, nil +} diff --git a/cmd/prerequisites.go b/cmd/prerequisites.go index d5dcd2c78..9b78bc8b2 100644 --- a/cmd/prerequisites.go +++ b/cmd/prerequisites.go @@ -5,88 +5,100 @@ import ( "runtime" ) -// binaryPrerequisite defines a binaryPrerequisite -type binaryPrerequisite struct { +// Prerequisite defines a Prerequisite! +type Prerequisite struct { Name string Help string Path string } -func newBinaryPrerequisite(name, help string) *binaryPrerequisite { - return &binaryPrerequisite{Name: name, Help: help} +func newPrerequisite(name, help string) *Prerequisite { + return &Prerequisite{Name: name, Help: help} } -// binaryPrerequisites is a list of binaryPrerequisites -type binaryPrerequisites []*binaryPrerequisite +// Prerequisites is a list of things required to use Wails +type Prerequisites []*Prerequisite // Add given prereq object to list -func (p *binaryPrerequisites) Add(prereq *binaryPrerequisite) { +func (p *Prerequisites) Add(prereq *Prerequisite) { *p = append(*p, prereq) } -func (p *binaryPrerequisites) check() (success *binaryPrerequisites, failed *binaryPrerequisites) { - success = &binaryPrerequisites{} - failed = &binaryPrerequisites{} - programHelper := NewProgramHelper() - for _, prereq := range *p { - bin := programHelper.FindProgram(prereq.Name) - if bin == nil { - failed.Add(prereq) - } else { - path, err := bin.GetFullPathToBinary() - if err != nil { - failed.Add(prereq) - } else { - prereq.Path = path - success.Add(prereq) - } - } - } - - return success, failed -} - -var platformbinaryPrerequisites = make(map[string]*binaryPrerequisites) - -func init() { - platformbinaryPrerequisites["darwin"] = &binaryPrerequisites{} - newDarwinbinaryPrerequisite("clang", "Please install with `xcode-select --install` and try again") - platformbinaryPrerequisites["linux"] = &binaryPrerequisites{} - linuxInfo := GetLinuxDistroInfo() - switch linuxInfo.Distribution { - case Ubuntu: - newLinuxbinaryPrerequisite("gcc", "Please install with 'sudo apt install build-essential' ") +// GetRequiredPrograms returns a list of programs required for the platform +func GetRequiredPrograms() (*Prerequisites, error) { + switch runtime.GOOS { + case "darwin": + return getRequiredProgramsOSX(), nil + case "linux": + return getRequiredProgramsLinux(), nil + case "windows": + return getRequiredProgramsWindows(), nil default: - newLinuxbinaryPrerequisite("gcc", "Please install with your system package manager.") + return nil, fmt.Errorf("platform '%s' not supported at this time", runtime.GOOS) } } -func newDarwinbinaryPrerequisite(name, help string) { - prereq := newBinaryPrerequisite(name, help) - platformbinaryPrerequisites["darwin"].Add(prereq) +func getRequiredProgramsOSX() *Prerequisites { + result := &Prerequisites{} + result.Add(newPrerequisite("clang", "Please install with `xcode-select --install` and try again")) + return result } -func newLinuxbinaryPrerequisite(name, help string) { - prereq := newBinaryPrerequisite(name, help) - platformbinaryPrerequisites["linux"].Add(prereq) -} - -func CheckBinaryPrerequisites() (*binaryPrerequisites, *binaryPrerequisites, error) { - platformPreReqs := platformbinaryPrerequisites[runtime.GOOS] - if platformPreReqs == nil { - return nil, nil, fmt.Errorf("Platform '%s' is not supported at this time", runtime.GOOS) - } - success, failed := platformPreReqs.check() - return success, failed, nil -} - -func CheckNonBinaryPrerequisites() error { - - var err error - - // Check non-binaries - if runtime.GOOS == "linux" { +func getRequiredProgramsLinux() *Prerequisites { + result := &Prerequisites{} + distroInfo := GetLinuxDistroInfo() + switch distroInfo.Distribution { + case Ubuntu: + result.Add(newPrerequisite("gcc", "Please install with `sudo apt install build-essentials` and try again")) + result.Add(newPrerequisite("pkg-config", "Please install with `sudo apt install pkg-config` and try again")) + default: + result.Add(newPrerequisite("gcc", "Please install with your system package manager and try again")) + result.Add(newPrerequisite("pkg-config", "Please install with your system package manager and try again")) } - return err + return result +} + +// TODO: Test this on Windows +func getRequiredProgramsWindows() *Prerequisites { + result := &Prerequisites{} + return result +} + +// GetRequiredLibraries returns a list of libraries (packages) required for the platform +func GetRequiredLibraries() (*Prerequisites, error) { + switch runtime.GOOS { + case "darwin": + return getRequiredLibrariesOSX() + case "linux": + return getRequiredLibrariesLinux() + case "windows": + return getRequiredLibrariesWindows() + default: + return nil, fmt.Errorf("platform '%s' not supported at this time", runtime.GOOS) + } +} + +func getRequiredLibrariesOSX() (*Prerequisites, error) { + result := &Prerequisites{} + return result, nil +} + +func getRequiredLibrariesLinux() (*Prerequisites, error) { + result := &Prerequisites{} + distroInfo := GetLinuxDistroInfo() + switch distroInfo.Distribution { + case Ubuntu: + result.Add(newPrerequisite("libgtk-3-dev", "Please install with `sudo apt install libgtk-3-dev` and try again")) + result.Add(newPrerequisite("libwebkit2gtk-4.0-dev", "Please install with `sudo apt install libwebkit2gtk-4.0-dev` and try again")) + default: + result.Add(newPrerequisite("libgtk-3-dev", "Please install with your system package manager and try again")) + result.Add(newPrerequisite("libwebkit2gtk-4.0-dev", "Please install with your system package manager and try again")) + } + return result, nil +} + +func getRequiredLibrariesWindows() (*Prerequisites, error) { + result := &Prerequisites{} + return result, nil } diff --git a/cmd/wails/0_setup.go b/cmd/wails/0_setup.go index 9af45f971..d90328ed4 100644 --- a/cmd/wails/0_setup.go +++ b/cmd/wails/0_setup.go @@ -4,6 +4,7 @@ import ( "fmt" "runtime" + "github.com/leaanthony/spinner" "github.com/wailsapp/wails/cmd" ) @@ -41,29 +42,55 @@ Create your first project by running 'wails init'.` logger.Yellow("Checking for prerequisites...") // Check we have a cgo capable environment - successDeps, failedDeps, err := cmd.CheckBinaryPrerequisites() + requiredPrograms, err := cmd.GetRequiredPrograms() if err != nil { return err } + errors := false + spinner := spinner.New() + programHelper := cmd.NewProgramHelper() + for _, program := range *requiredPrograms { + spinner.Start("Looking for program '%s'", program.Name) + bin := programHelper.FindProgram(program.Name) + if bin == nil { + errors = true + spinner.Errorf("Program '%s' not found. %s", program.Name, program.Help) + } else { + spinner.Successf("Program '%s' found: %s", program.Name, bin.Path) + } + } - for _, dep := range *successDeps { - logger.Green("Found '%s' at '%s'", dep.Name, dep.Path) + // Linux has library deps + if runtime.GOOS == "linux" { + // Check library prerequisites + requiredLibraries, err := cmd.GetRequiredLibraries() + if err != nil { + return err + } + distroInfo := cmd.GetLinuxDistroInfo() + for _, library := range *requiredLibraries { + spinner.Start() + switch distroInfo.Distribution { + case cmd.Ubuntu: + installed, err := cmd.DpkgInstalled(library.Name) + if err != nil { + return err + } + if !installed { + errors = true + spinner.Errorf("Library '%s' not found. %s", library.Name, library.Help) + } else { + spinner.Successf("Library '%s' installed.", library.Name) + } + default: + return fmt.Errorf("unable to check libraries on distribution '%s'. Please ensure that the '%s' equivalent is installed", distroInfo.DistributorID, library.Name) + } + } } logger.White("") - for _, dep := range *failedDeps { - logger.Red("PreRequisite '%s' missing. %s", dep.Name, dep.Help) - } - - // Check non-binary prerequisites - err = cmd.CheckNonBinaryPrerequisites() - - if err != nil { - return err - } - - if len(*failedDeps) == 0 { + if !errors { logger.Yellow(successMessage) } From 5493e1d4acd195e0de96998844fc0a0bfc129e82 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 23 Dec 2018 15:37:51 +1100 Subject: [PATCH 10/12] feat: add windows dependencies --- cmd/linux.go | 5 +++-- cmd/prerequisites.go | 2 ++ cmd/program.go | 5 +++-- go.mod | 3 ++- go.sum | 4 ++++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/linux.go b/cmd/linux.go index b812d3fb6..2f033f41a 100644 --- a/cmd/linux.go +++ b/cmd/linux.go @@ -11,6 +11,7 @@ type LinuxDistribution int const ( // Unknown is the catch-all distro Unknown LinuxDistribution = 0 + // Ubuntu distribution Ubuntu LinuxDistribution = 1 ) @@ -31,7 +32,7 @@ func GetLinuxDistroInfo() *DistroInfo { lsbRelease := program.FindProgram("lsb_release") if lsbRelease != nil { - stdout, _, err, _ := lsbRelease.Run("-a") + stdout, _, _, err := lsbRelease.Run("-a") if err != nil { return result } @@ -72,7 +73,7 @@ func DpkgInstalled(packageName string) (bool, error) { if dpkg == nil { return false, fmt.Errorf("cannot check dependencies: dpkg not found") } - _, _, _, exitCode := dpkg.Run("-L", packageName) + _, _, exitCode, _ := dpkg.Run("-L", packageName) result = exitCode == 0 return result, nil } diff --git a/cmd/prerequisites.go b/cmd/prerequisites.go index 9b78bc8b2..108d39985 100644 --- a/cmd/prerequisites.go +++ b/cmd/prerequisites.go @@ -62,6 +62,8 @@ func getRequiredProgramsLinux() *Prerequisites { // TODO: Test this on Windows func getRequiredProgramsWindows() *Prerequisites { result := &Prerequisites{} + result.Add(newPrerequisite("gcc", "Please install gcc from here and try again: http://tdm-gcc.tdragon.net/download. You will need to add the bin directory to your path, EG: C:\\TDM-GCC-64\\bin\\")) + result.Add(newPrerequisite("npm", "Please install node/npm from here and try again: https://nodejs.org/en/download/")) return result } diff --git a/cmd/program.go b/cmd/program.go index c579c0e2c..c92d6b382 100644 --- a/cmd/program.go +++ b/cmd/program.go @@ -44,16 +44,17 @@ func (p *ProgramHelper) FindProgram(programName string) *Program { } } +// GetFullPathToBinary returns the full path the the current binary func (p *Program) GetFullPathToBinary() (string, error) { return filepath.Abs(p.Path) } // Run will execute the program with the given parameters // Returns stdout + stderr as strings and an error if one occured -func (p *Program) Run(vars ...string) (stdout, stderr string, err error, exitCode int) { +func (p *Program) Run(vars ...string) (stdout, stderr string, exitCode int, err error) { command, err := p.GetFullPathToBinary() if err != nil { - return "", "", err, 1 + return "", "", 1, err } cmd := exec.Command(command, vars...) var stdo, stde bytes.Buffer diff --git a/go.mod b/go.mod index 088821717..d171210a6 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ require ( github.com/AlecAivazis/survey v1.7.1 github.com/fatih/color v1.7.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/leaanthony/spinner v0.5.0 + github.com/leaanthony/spinner v0.4.0 github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0 // indirect + github.com/leaanthony/wincursor v0.0.0-20180705115120-056510f32d15 // indirect github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.4 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect diff --git a/go.sum b/go.sum index f2b808ff3..1df5885c4 100644 --- a/go.sum +++ b/go.sum @@ -4,10 +4,14 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/leaanthony/spinner v0.4.0 h1:y/7FqQqqObRKYI+33bg9DGhHIY7cQHicm+Vz0Uda0Ik= +github.com/leaanthony/spinner v0.4.0/go.mod h1:2Mmv+8Brcw3NwPT1DdOLmW6+zWpSamDDFFsUvVHo2cc= github.com/leaanthony/spinner v0.5.0 h1:OJKn+0KP6ilHxwCEOv5Lo0wPM4PgWZWLJTeUprGJK0g= github.com/leaanthony/spinner v0.5.0/go.mod h1:2Mmv+8Brcw3NwPT1DdOLmW6+zWpSamDDFFsUvVHo2cc= github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0 h1:1bGojw4YacLY5bqQalojiQ7mSfQbe4WIWCEgPZagowU= github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0/go.mod h1:Iz7eybeeG8bdq640iR+CwYb8p+9EOsgMWghkSRyZcqs= +github.com/leaanthony/wincursor v0.0.0-20180705115120-056510f32d15 h1:166LIty6ldcyOc7tbgfu5smsGATvEo0JZV6bnbzyEc4= +github.com/leaanthony/wincursor v0.0.0-20180705115120-056510f32d15/go.mod h1:7TVwwrzSH/2Y9gLOGH+VhA+bZhoWXBRgbGNTMk+yimE= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= From 76a4c3c171dc351164b35dafaaf24eaaba22f6e9 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 23 Dec 2018 17:10:32 +1100 Subject: [PATCH 11/12] feat: add npm prerequisite for linux and osx --- cmd/prerequisites.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/prerequisites.go b/cmd/prerequisites.go index 108d39985..972ebd25f 100644 --- a/cmd/prerequisites.go +++ b/cmd/prerequisites.go @@ -41,6 +41,7 @@ func GetRequiredPrograms() (*Prerequisites, error) { func getRequiredProgramsOSX() *Prerequisites { result := &Prerequisites{} result.Add(newPrerequisite("clang", "Please install with `xcode-select --install` and try again")) + result.Add(newPrerequisite("npm", "Please install from https://nodejs.org/en/download/ and try again")) return result } @@ -51,9 +52,12 @@ func getRequiredProgramsLinux() *Prerequisites { case Ubuntu: result.Add(newPrerequisite("gcc", "Please install with `sudo apt install build-essentials` and try again")) result.Add(newPrerequisite("pkg-config", "Please install with `sudo apt install pkg-config` and try again")) + result.Add(newPrerequisite("npm", "Please install from https://nodejs.org/en/download/ and try again")) + default: result.Add(newPrerequisite("gcc", "Please install with your system package manager and try again")) result.Add(newPrerequisite("pkg-config", "Please install with your system package manager and try again")) + result.Add(newPrerequisite("npm", "Please install from https://nodejs.org/en/download/ and try again")) } return result From 35d4c4b99c4e7286a40358491bef2768b7a647af Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 23 Dec 2018 17:33:44 +1100 Subject: [PATCH 12/12] feat: make setup the default command docs: better help text --- cmd/system.go | 7 ++++++- cmd/wails/0_setup.go | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cmd/system.go b/cmd/system.go index c1d1a2a30..c8dd6a35a 100644 --- a/cmd/system.go +++ b/cmd/system.go @@ -132,6 +132,11 @@ func (s *SystemHelper) setup() error { return answers.Save(s.wailsSystemConfig) } +const introText = ` +Wails is a lightweight framework for creating web-like desktop apps in Go. +I'll need to ask you a few questions so I can fill in your project templates and then I will try and see if you have the correct dependencies installed. If you don't have the right tools installed, I'll try and suggest how to install them. +` + // Initialise attempts to set up the Wails system. // An error is returns if there is a problem func (s *SystemHelper) Initialise() error { @@ -139,7 +144,7 @@ func (s *SystemHelper) Initialise() error { // System dir doesn't exist if !s.systemDirExists() { s.log.Green("Welcome to Wails!") - s.log.Green("To get you set up, I'll need to ask you a few things...") + s.log.Green(introText) return s.setup() } diff --git a/cmd/wails/0_setup.go b/cmd/wails/0_setup.go index d90328ed4..790c646a7 100644 --- a/cmd/wails/0_setup.go +++ b/cmd/wails/0_setup.go @@ -12,10 +12,12 @@ func init() { commandDescription := `Sets up your local environment to develop Wails apps.` - initCommand := app.Command("setup", "Setup the Wails environment"). + setupCommand := app.Command("setup", "Setup the Wails environment"). LongDescription(commandDescription) - initCommand.Action(func() error { + app.DefaultCommand(setupCommand) + + setupCommand.Action(func() error { system := cmd.NewSystemHelper() err := system.Initialise()