mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
commit
0bb11079af
14 changed files with 1158 additions and 0 deletions
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
268
cmd/cli.go
Normal file
268
cmd/cli.go
Normal file
|
|
@ -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
|
||||
}
|
||||
132
cmd/fs.go
Normal file
132
cmd/fs.go
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
79
cmd/linux.go
Normal file
79
cmd/linux.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LinuxDistribution is of type int
|
||||
type LinuxDistribution int
|
||||
|
||||
const (
|
||||
// Unknown is the catch-all distro
|
||||
Unknown LinuxDistribution = 0
|
||||
// Ubuntu distribution
|
||||
Ubuntu LinuxDistribution = 1
|
||||
)
|
||||
|
||||
// DistroInfo contains all the information relating to a linux distribution
|
||||
type DistroInfo struct {
|
||||
Distribution LinuxDistribution
|
||||
Description string
|
||||
Release string
|
||||
Codename string
|
||||
DistributorID string
|
||||
}
|
||||
|
||||
// GetLinuxDistroInfo returns information about the running linux distribution
|
||||
func GetLinuxDistroInfo() *DistroInfo {
|
||||
result := &DistroInfo{Distribution: Unknown}
|
||||
program := NewProgramHelper()
|
||||
// Does lsb_release exist?
|
||||
|
||||
lsbRelease := program.FindProgram("lsb_release")
|
||||
if lsbRelease != nil {
|
||||
stdout, _, _, err := lsbRelease.Run("-a")
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
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
|
||||
switch value {
|
||||
case "Ubuntu":
|
||||
result.Distribution = Ubuntu
|
||||
}
|
||||
case "Description":
|
||||
result.Description = value
|
||||
case "Release":
|
||||
result.Release = value
|
||||
case "Codename":
|
||||
result.Codename = value
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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
|
||||
}
|
||||
82
cmd/log.go
Normal file
82
cmd/log.go
Normal file
|
|
@ -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
|
||||
}
|
||||
110
cmd/prerequisites.go
Normal file
110
cmd/prerequisites.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Prerequisite defines a Prerequisite!
|
||||
type Prerequisite struct {
|
||||
Name string
|
||||
Help string
|
||||
Path string
|
||||
}
|
||||
|
||||
func newPrerequisite(name, help string) *Prerequisite {
|
||||
return &Prerequisite{Name: name, Help: help}
|
||||
}
|
||||
|
||||
// Prerequisites is a list of things required to use Wails
|
||||
type Prerequisites []*Prerequisite
|
||||
|
||||
// Add given prereq object to list
|
||||
func (p *Prerequisites) Add(prereq *Prerequisite) {
|
||||
*p = append(*p, prereq)
|
||||
}
|
||||
|
||||
// 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:
|
||||
return nil, fmt.Errorf("platform '%s' not supported at this time", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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"))
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
85
cmd/program.go
Normal file
85
cmd/program.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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, exitCode int, err error) {
|
||||
command, err := p.GetFullPathToBinary()
|
||||
if err != nil {
|
||||
return "", "", 1, 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())
|
||||
|
||||
// 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
|
||||
}
|
||||
1
cmd/setup.go
Normal file
1
cmd/setup.go
Normal file
|
|
@ -0,0 +1 @@
|
|||
package cmd
|
||||
210
cmd/system.go
Normal file
210
cmd/system.go
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// SystemHelper - Defines everything related to the system
|
||||
type SystemHelper struct {
|
||||
log *Logger
|
||||
fs *FSHelper
|
||||
configFilename string
|
||||
homeDir string
|
||||
wailsSystemDir string
|
||||
wailsSystemConfig string
|
||||
}
|
||||
|
||||
// NewSystemHelper - Creates a new System Helper
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
// System dir doesn't exist
|
||||
if !s.systemDirExists() {
|
||||
s.log.Green("Welcome to Wails!")
|
||||
s.log.Green(introText)
|
||||
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()
|
||||
}
|
||||
|
||||
// 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, "", " ")
|
||||
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
|
||||
}
|
||||
5
cmd/version.go
Normal file
5
cmd/version.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package cmd
|
||||
|
||||
// Version - Wails version
|
||||
// ...oO(There must be a better way)
|
||||
const Version = "v0.5 Alpha"
|
||||
101
cmd/wails/0_setup.go
Normal file
101
cmd/wails/0_setup.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/leaanthony/spinner"
|
||||
"github.com/wailsapp/wails/cmd"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
commandDescription := `Sets up your local environment to develop Wails apps.`
|
||||
|
||||
setupCommand := app.Command("setup", "Setup the Wails environment").
|
||||
LongDescription(commandDescription)
|
||||
|
||||
app.DefaultCommand(setupCommand)
|
||||
|
||||
setupCommand.Action(func() error {
|
||||
|
||||
system := cmd.NewSystemHelper()
|
||||
err := system.Initialise()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var successMessage = `Ready for take off!
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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("")
|
||||
|
||||
if !errors {
|
||||
logger.Yellow(successMessage)
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
26
cmd/wails/main.go
Normal file
26
cmd/wails/main.go
Normal file
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
15
go.mod
Normal file
15
go.mod
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
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.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
|
||||
github.com/mitchellh/go-homedir v1.0.0
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.7.1 // indirect
|
||||
)
|
||||
25
go.sum
Normal file
25
go.sum
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
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.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=
|
||||
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=
|
||||
Loading…
Add table
Add a link
Reference in a new issue