forked from fyne-io/fyne
/
install.go
263 lines (229 loc) · 7.06 KB
/
install.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
package commands
import (
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/unix-world/smart-fyne"
"github.com/unix-world/smart-fyne/cmd/fyne/internal/mobile"
"github.com/urfave/cli/v2"
"golang.org/x/sys/execabs"
)
// Install returns the cli command for installing fyne applications
func Install() *cli.Command {
i := NewInstaller()
return &cli.Command{
Name: "install",
Usage: "Packages an application and installs an application.",
Description: `The install command packages an application for the current platform and copies it
into the system location for applications. This can be overridden with installDir`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "target",
Aliases: []string{"os"},
Usage: "The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios, iossimulator).",
Destination: &i.os,
},
&cli.StringFlag{
Name: "installDir",
Aliases: []string{"o"},
Usage: "A specific location to install to, rather than the OS default.",
Destination: &i.installDir,
},
&cli.StringFlag{
Name: "icon",
Usage: "The name of the application icon file.",
Value: "",
Destination: &i.icon,
},
&cli.BoolFlag{
Name: "use-raw-icon",
Usage: "Skip any OS-specific icon pre-processing",
Value: false,
Destination: &i.rawIcon,
},
&cli.StringFlag{
Name: "appID",
Aliases: []string{"id"},
Usage: "For Android, darwin, iOS and Windows targets an appID in the form of a reversed domain name is required, for ios this must match a valid provisioning profile",
Destination: &i.AppID,
},
&cli.BoolFlag{
Name: "release",
Usage: "Enable installation in release mode (disable debug, etc).",
Destination: &i.release,
},
},
Action: i.bundleAction,
}
}
// Installer installs locally built Fyne apps.
type Installer struct {
*appData
installDir, srcDir, os string
Packager *Packager
release bool
}
// NewInstaller returns a command that can install a GUI apps built using Fyne from local source code.
func NewInstaller() *Installer {
return &Installer{appData: &appData{}}
}
// AddFlags adds the flags for interacting with the Installer.
//
// Deprecated: Access to the individual cli commands are being removed.
func (i *Installer) AddFlags() {
flag.StringVar(&i.os, "os", "", "The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios)")
flag.StringVar(&i.installDir, "installDir", "", "A specific location to install to, rather than the OS default")
flag.StringVar(&i.icon, "icon", "Icon.png", "The name of the application icon file")
flag.StringVar(&i.AppID, "appID", "", "For ios or darwin targets an appID is required, for ios this must \nmatch a valid provisioning profile")
flag.BoolVar(&i.release, "release", false, "Should this package be installed in release mode? (disable debug etc)")
}
// PrintHelp prints the help for the install command.
//
// Deprecated: Access to the individual cli commands are being removed.
func (i *Installer) PrintHelp(indent string) {
fmt.Println(indent, "The install command packages an application for the current platform and copies it")
fmt.Println(indent, "into the system location for applications. This can be overridden with installDir")
fmt.Println(indent, "Command usage: fyne install [parameters]")
}
// Run runs the install command.
//
// Deprecated: A better version will be exposed in the future.
func (i *Installer) Run(args []string) {
if len(args) != 0 {
fyne.LogError("Unexpected parameter after flags", nil)
return
}
err := i.validate()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
err = i.install()
if err != nil {
fyne.LogError("Unable to install application", err)
os.Exit(1)
}
}
func (i *Installer) bundleAction(ctx *cli.Context) error {
if ctx.Args().Len() != 0 {
return errors.New("unexpected parameter after flags")
}
err := i.validate()
if err != nil {
return err
}
err = i.install()
if err != nil {
return err
}
return nil
}
func (i *Installer) install() error {
p := i.Packager
if i.os != "" {
if util.IsIOS(i.os) {
return i.installIOS()
} else if strings.Index(i.os, "android") == 0 {
return i.installAndroid()
}
return errors.New("Unsupported target operating system \"" + i.os + "\"")
}
if i.installDir == "" {
switch p.os {
case "darwin":
i.installDir = "/Applications"
case "linux", "openbsd", "freebsd", "netbsd":
i.installDir = "/" // the tarball contains the structure starting at usr/local
case "windows":
dirName := p.Name
if filepath.Ext(p.Name) == ".exe" {
dirName = p.Name[:len(p.Name)-4]
}
i.installDir = filepath.Join(os.Getenv("ProgramFiles"), dirName)
err := runAsAdminWindows("mkdir", i.installDir)
if err != nil {
fyne.LogError("Failed to run as windows administrator", err)
return err
}
default:
return errors.New("Unsupported target operating system \"" + p.os + "\"")
}
}
p.dir = i.installDir
err := p.doPackage(nil)
if err != nil {
return err
}
return postInstall(i)
}
func (i *Installer) installAndroid() error {
target := mobile.AppOutputName(i.os, i.Packager.Name, i.release)
_, err := os.Stat(target)
if os.IsNotExist(err) {
err := i.Packager.doPackage(nil)
if err != nil {
return nil
}
}
return i.runMobileInstall("adb", target, "install")
}
func (i *Installer) installIOS() error {
target := mobile.AppOutputName(i.os, i.Packager.Name, i.release)
// Always redo the package because the codesign for ios and iossimulator
// must be different.
if err := i.Packager.doPackage(nil); err != nil {
return nil
}
switch i.os {
case "ios":
return i.runMobileInstall("ios-deploy", target, "--bundle")
case "iossimulator":
return i.installToIOSSimulator(target)
default:
return fmt.Errorf("unsupported install target: %s", target)
}
}
func (i *Installer) runMobileInstall(tool, target string, args ...string) error {
_, err := execabs.LookPath(tool)
if err != nil {
return err
}
cmd := execabs.Command(tool, append(args, target)...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
return cmd.Run()
}
func (i *Installer) validate() error {
os := i.os
if os == "" {
os = targetOS()
}
i.Packager = &Packager{appData: i.appData, os: os, install: true, srcDir: i.srcDir}
i.Packager.AppID = i.AppID
i.Packager.icon = i.icon
i.Packager.release = i.release
return i.Packager.validate()
}
func (i *Installer) installToIOSSimulator(target string) error {
cmd := execabs.Command(
"xcrun", "simctl", "install",
"booted", // Install to the booted simulator.
target)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("Install to a simulator error: %s%s", out, err)
}
i.runInIOSSimulator()
return nil
}
func (i *Installer) runInIOSSimulator() error {
cmd := execabs.Command("xcrun", "simctl", "launch", "booted", i.Packager.AppID)
out, err := cmd.CombinedOutput()
if err != nil {
os.Stderr.Write(out)
return err
}
return nil
}