Skip to content

Commit

Permalink
Merge pull request #2495 from changkun/iossimulator
Browse files Browse the repository at this point in the history
cmd/fyne: support -os iossimulator
  • Loading branch information
changkun committed Dec 7, 2021
2 parents 27c4563 + 3c04df9 commit 784b67d
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 31 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ single codebase.
Version 2.1 is the current release of the Fyne API, it introduced RichText
and the DocTabs container, as well as the document storage API and FyneApp.toml
metadata support.
We are now working towards the next big release, codenamed
We are now working towards the next big release, codenamed
[bowmore](https://github.com/fyne-io/fyne/milestone/15)
and more news will follow in our news feeds and GitHub project.

Expand Down Expand Up @@ -60,7 +60,7 @@ And even running on a mobile device:

Fyne is designed to be really easy to code with.
If you have followed the prerequisite steps above then all you need is a
Go IDE (or a text editor).
Go IDE (or a text editor).

Open a new file and you're ready to write your first app!

Expand Down Expand Up @@ -114,6 +114,8 @@ There is a helpful mobile simulation mode that gives a hint of how your app woul

$ go run -tags mobile main.go

Another option is to use `fyne` command, see [Packaging for mobile](#packaging-for-mobile).

# Installing

Using `go install` will copy the executable into your go `bin` dir.
Expand All @@ -133,6 +135,14 @@ Once packaged you can install using the platform development tools or the fyne "
$ fyne package -os android -appID my.domain.appname
$ fyne install -os android

The built Android application can run either in a real device or an Android emulator.
However, building for iOS is slightly different.
If the "-os" argument is "ios", it is build only for a real iOS device.
Specify "-os" to "iossimulator" allows the application be able to run in an iOS simulator:

$ fyne package -os ios -appID my.domain.appname
$ fyne package -os iossimulator -appID my.domain.appname

# Preparing a release

Using the fyne utility "release" subcommand you can package up your app for release
Expand All @@ -142,7 +152,7 @@ Then you can execute something like the following, notice the `-os ios` paramete
building an iOS app from macOS computer. Other combinations work as well :)

$ fyne release -os ios -certificate "Apple Distribution" -profile "My App Distribution" -appID "com.example.myapp"

The above command will create a '.ipa' file that can then be uploaded to the iOS App Store.

# Documentation
Expand Down
48 changes: 39 additions & 9 deletions cmd/fyne/internal/commands/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/cmd/fyne/internal/mobile"
"fyne.io/fyne/v2/cmd/fyne/internal/util"

"github.com/urfave/cli/v2"
"golang.org/x/sys/execabs"
Expand All @@ -28,7 +29,7 @@ func Install() *cli.Command {
&cli.StringFlag{
Name: "target",
Aliases: []string{"os"},
Usage: "The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios).",
Usage: "The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios, iossimulator).",
Destination: &i.os,
},
&cli.StringFlag{
Expand Down Expand Up @@ -130,7 +131,7 @@ func (i *Installer) install() error {
p := i.Packager

if i.os != "" {
if i.os == "ios" {
if util.IsIOS(i.os) {
return i.installIOS()
} else if strings.Index(i.os, "android") == 0 {
return i.installAndroid()
Expand Down Expand Up @@ -186,15 +187,21 @@ func (i *Installer) installAndroid() error {

func (i *Installer) installIOS() error {
target := mobile.AppOutputName(i.os, i.Packager.name)
_, err := os.Stat(target)
if os.IsNotExist(err) {
err := i.Packager.doPackage()
if err != nil {
return nil
}

// Always redo the package because the codesign for ios and iossimulator
// must be different.
if err := i.Packager.doPackage(); err != nil {
return nil
}

return i.runMobileInstall("ios-deploy", target, "--bundle")
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 {
Expand All @@ -219,3 +226,26 @@ func (i *Installer) validate() error {
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
}
4 changes: 2 additions & 2 deletions cmd/fyne/internal/commands/package-mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func (p *Packager) packageAndroid(arch string) error {
return mobile.RunNewBuild(arch, p.appID, p.icon, p.name, p.appVersion, p.appBuild, p.release, "", "")
}

func (p *Packager) packageIOS() error {
err := mobile.RunNewBuild("ios", p.appID, p.icon, p.name, p.appVersion, p.appBuild, p.release, p.certificate, p.profile)
func (p *Packager) packageIOS(target string) error {
err := mobile.RunNewBuild(target, p.appID, p.icon, p.name, p.appVersion, p.appBuild, p.release, p.certificate, p.profile)
if err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/fyne/internal/commands/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func Package() *cli.Command {
&cli.StringFlag{
Name: "target",
Aliases: []string{"os"},
Usage: "The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios).",
Usage: "The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios, iossimulator).",
Destination: &p.os,
},
&cli.StringFlag{
Expand Down Expand Up @@ -231,8 +231,8 @@ func (p *Packager) doPackage() error {
return p.packageWindows()
case "android/arm", "android/arm64", "android/amd64", "android/386", "android":
return p.packageAndroid(p.os)
case "ios":
return p.packageIOS()
case "ios", "iossimulator":
return p.packageIOS(p.os)
default:
return fmt.Errorf("unsupported target operating system \"%s\"", p.os)
}
Expand Down
23 changes: 21 additions & 2 deletions cmd/fyne/internal/mobile/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import (
"io"
"os"
"regexp"
"runtime"
"strings"

"fyne.io/fyne/v2/cmd/fyne/internal/util"

"golang.org/x/sys/execabs"
"golang.org/x/tools/go/packages"
)
Expand Down Expand Up @@ -80,7 +83,7 @@ func AppOutputName(os, name string) string {
switch os {
case "android":
return androidPkgName(name) + ".apk"
case "ios":
case "ios", "iossimulator":
return rfc1034Label(name) + ".app"
}

Expand Down Expand Up @@ -372,9 +375,12 @@ func parseBuildTarget(buildTarget string) (os string, archs []string, _ error) {
archNames := []string{}
for i, p := range strings.Split(buildTarget, ",") {
osarch := strings.SplitN(p, "/", 2) // len(osarch) > 0
if osarch[0] != "android" && osarch[0] != "ios" {
if !util.IsAndroid(osarch[0]) && !util.IsIOS(osarch[0]) {
return "", nil, fmt.Errorf(`unsupported os`)
}
if osarch[0] == "iossimulator" {
osarch[0] = "ios"
}

if i == 0 {
os = osarch[0]
Expand Down Expand Up @@ -418,6 +424,19 @@ func parseBuildTarget(buildTarget string) (os string, archs []string, _ error) {
if os == "ios" {
targetOS = "darwin"
}

if buildTarget == "iossimulator" {
if before116 {
// If the build target is iossimulator, and the go distribution also before
// 1.16, then we can only build amd64 arch app for simulators. Because
// arm64 simulators is only supported after go 1.16.
allArchs[os] = []string{"amd64"}
} else {
// Otherwise, the iossimulator arch is depending on the host arch.
allArchs[os] = []string{runtime.GOARCH}
}
}

if all {
return targetOS, allArchs[os], nil
}
Expand Down
10 changes: 10 additions & 0 deletions cmd/fyne/internal/mobile/build_iosapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ func goIOSBuild(pkg *packages.Package, bundleID string, archs []string,
return nil, err
}
}

// Use codesign to remove the codesign certificate for the built application
// so that it can run in iOS simulator.
if buildTarget == "iossimulator" {
if out, err := execabs.Command("codesign", "--force", "--sign", "-", buildO).CombinedOutput(); err != nil {
printcmd("codesign --force --sign --keychain %s\n%s", buildO, out)
return nil, err
}
}

return nmpkgs, nil
}

Expand Down
31 changes: 21 additions & 10 deletions cmd/fyne/internal/mobile/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import (
"strings"

"fyne.io/fyne/v2"
"golang.org/x/mod/semver"
"fyne.io/fyne/v2/cmd/fyne/internal/util"

"golang.org/x/mod/semver"
"golang.org/x/sys/execabs"
)

Expand All @@ -27,8 +28,10 @@ var (
darwinArmNM string

allArchs = map[string][]string{
"android": {"arm", "arm64", "386", "amd64"},
"ios": {"arm64", "amd64"}}
"android": {"arm", "arm64", "386", "amd64"},
"ios": {"arm64"},
"iossimulator": {"arm64", "amd64"},
}

bitcodeEnabled bool
)
Expand Down Expand Up @@ -67,7 +70,7 @@ func buildEnvInit() (cleanup func(), err error) {
tmpdir = "$WORK"
cleanupFn = func() {}
} else {
tmpdir, err = ioutil.TempDir("", "gomobile-work-")
tmpdir, err = ioutil.TempDir("", "fyne-work-")
if err != nil {
return nil, err
}
Expand All @@ -83,14 +86,17 @@ func buildEnvInit() (cleanup func(), err error) {
return cleanupFn, nil
}

var (
before115 = false
before116 = false
)

func envInit() (err error) {
// Check the current Go version by go-list.
// An arbitrary standard package ('runtime' here) is given to go-list.
// This is because go-list tries to analyze the module at the current directory if no packages are given,
// and if the module doesn't have any Go file, go-list fails. See golang/go#36668.

before115 := false
before116 := false
ver, err := exec.Command("go", "version").Output()
if err == nil && string(ver) != "" {
fields := strings.Split(string(ver), " ")
Expand Down Expand Up @@ -168,13 +174,13 @@ func envInit() (err error) {
}
}

if !xcodeAvailable() {
if !xcodeAvailable() || !util.IsIOS(buildTarget) {
return nil
}

darwinArmNM = "nm"
darwinEnv = make(map[string][]string)
for _, arch := range allArchs["ios"] {
for _, arch := range allArchs[buildTarget] {
var env []string
var err error
var clang, cflags string
Expand All @@ -183,8 +189,13 @@ func envInit() (err error) {
env = append(env, "GOARM=7")
fallthrough
case "arm64":
clang, cflags, err = envClang("iphoneos")
cflags += " -miphoneos-version-min=" + buildIOSVersion
if buildTarget == "ios" {
clang, cflags, err = envClang("iphoneos")
cflags += " -miphoneos-version-min=" + buildIOSVersion
} else { // iossimulator
clang, cflags, err = envClang("iphonesimulator")
cflags += " -mios-simulator-version-min=" + buildIOSVersion
}
case "386", "amd64":
clang, cflags, err = envClang("iphonesimulator")
cflags += " -mios-simulator-version-min=" + buildIOSVersion
Expand Down
7 changes: 6 additions & 1 deletion cmd/fyne/internal/util/mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ func IsAndroid(os string) bool {
return strings.HasPrefix(os, "android")
}

// IsIOS returns true if the given os parameter represents one of the iOS targets (ios, iossimulator)
func IsIOS(os string) bool {
return strings.HasPrefix(os, "ios")
}

// IsMobile returns true if the given os parameter represents a platform handled by gomobile.
func IsMobile(os string) bool {
return os == "ios" || IsAndroid(os)
return IsIOS(os) || IsAndroid(os)
}

// RequireAndroidSDK will return an error if it cannot establish the location of a valid Android SDK installation.
Expand Down
3 changes: 2 additions & 1 deletion internal/driver/mobile/gl/work.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build darwin || linux || openbsd || freebsd
//go:build (darwin || linux || openbsd || freebsd) && go115
// +build darwin linux openbsd freebsd
// +build go115

package gl

Expand Down

0 comments on commit 784b67d

Please sign in to comment.