diff --git a/CHANGELOG.md b/CHANGELOG.md index 08d20567..06e90e9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog - Fyne.io fyne-cross +## Unreleased +- Add support for "fyne release" #3 +- Add support for creating packaged .tar.gz bundles on freebsd #6 +- Update fyne cli to v1.4.2-0.20201125075943-97ad77d2abe0 (fyne-io#1538 fyne-io#1527) + ## [0.9.0] - Releaseing under project namespace with previous 2.2.1 becoming 0.9.0 in fyne-io namespace diff --git a/README.md b/README.md index e502524f..377501e0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,10 @@ Supported targets are: - android - ios -> Note: iOS compilation is supported only on darwin hosts. See [fyne README mobile](https://github.com/fyne-io/fyne/blob/v1.2.4/README-mobile.md#ios) for pre-requisites. +> Note: +> - iOS compilation is supported only on darwin hosts. See [fyne pre-requisites](https://developer.fyne.io/started/#prerequisites) for details. +> - macOS packaging for public distrubution (release mode) is supported only on darwin hosts. +> - windows packaging for public distrubution (release mode) is supported only on windows hosts. ## Requirements diff --git a/docker/CHANGELOG.md b/docker/CHANGELOG.md index 7243a63a..0fd3df11 100644 --- a/docker/CHANGELOG.md +++ b/docker/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to the fyne-cross docker images will be documented in this f Release cycle won't follow the fyne-cross one, so the images will be tagged using the label year.month.day along with the latest one. +# Release 20.11.28 +- Update fyne cli to v1.4.2-0.20201127180716-f9f91c194737 fyne-io#1609 + +# Release 20.11.25 +- Update fyne cli to v1.4.2-0.20201125075943-97ad77d2abe0 fyne-io#1538 + +# Release 20.11.23 +- Update fyne cli to v1.4.2-0.20201122132119-67b762f56dc0 fyne-io#1527 + +# Release 20.11.04 +- fyne cli updated to v1.4.0 + # Archive These releases occurred in the original namspace, lucor/fyne-cross diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 1ed40de3..a5d88758 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -1,7 +1,7 @@ # docker cross 1.13.15 ARG DOCKER_CROSS_VERSION=sha256:11a04661d910f74c419623ef7880024694f9151c17578af15e86c45cdf6c8588 # fyne stable branch -ARG FYNE_VERSION=v1.3.3 +ARG FYNE_VERSION=f9f91c1947370189c73384fb12ee799b1cf68638 # Build the fyne command utility FROM dockercore/golang-cross@${DOCKER_CROSS_VERSION} AS fyne diff --git a/internal/command/android.go b/internal/command/android.go index 821ba5a6..0a32d07e 100644 --- a/internal/command/android.go +++ b/internal/command/android.go @@ -42,6 +42,10 @@ func (cmd *Android) Parse(args []string) error { CommonFlags: commonFlags, } + flagSet.StringVar(&flags.Keystore, "keystore", "", "The location of .keystore file containing signing information") + flagSet.StringVar(&flags.KeystorePass, "keystore-pass", "", "Password for the .keystore file") + flagSet.StringVar(&flags.KeyPass, "key-pass", "", "Password for the signer's private key, which is needed if the private key is password-protected") + flagSet.Usage = cmd.Usage flagSet.Parse(args) @@ -95,13 +99,33 @@ func (cmd *Android) Run() error { return err } - err = fynePackage(ctx) + if ctx.Release { + err = fyneRelease(ctx) + } else { + err = fynePackage(ctx) + } if err != nil { return fmt.Errorf("could not package the Fyne app: %v", err) } // move the dist package into the "dist" folder - srcFile := volume.JoinPathHost(ctx.WorkDirHost(), ctx.Package, packageName) + // The fyne tool sanitizes the package name to be acceptable as a + // android package name. For details, see: + // https://github.com/fyne-io/fyne/blob/v1.4.0/cmd/fyne/internal/mobile/build_androidapp.go#L297 + // To avoid to duplicate the fyne tool sanitize logic here, the location of + // the dist package to move will be detected using a matching pattern + apkFilePattern := volume.JoinPathHost(ctx.WorkDirHost(), ctx.Package, "*.apk") + apks, err := filepath.Glob(apkFilePattern) + if err != nil { + return fmt.Errorf("could not find any apk file matching %q: %v", apkFilePattern, err) + } + if apks == nil { + return fmt.Errorf("could not find any apk file matching %q", apkFilePattern) + } + if len(apks) > 1 { + return fmt.Errorf("multiple apk files matching %q: %v. Please remove and build again", apkFilePattern, apks) + } + srcFile := apks[0] distFile := volume.JoinPathHost(ctx.DistDirHost(), ctx.ID, packageName) err = os.MkdirAll(filepath.Dir(distFile), 0755) if err != nil { @@ -142,6 +166,10 @@ Options: // androidFlags defines the command-line flags for the android command type androidFlags struct { *CommonFlags + + Keystore string //Keystore represents the location of .keystore file containing signing information + KeystorePass string //Password for the .keystore file + KeyPass string //Password for the signer's private key, which is needed if the private key is password-protected } // makeAndroidContext returns the command context for an android target @@ -159,6 +187,10 @@ func makeAndroidContext(flags *androidFlags, args []string) (Context, error) { ctx.OS = androidOS ctx.ID = androidOS + ctx.Keystore = flags.Keystore + ctx.KeystorePass = flags.KeystorePass + ctx.KeyPass = flags.KeyPass + // set context based on command-line flags if flags.DockerImage == "" { ctx.DockerImage = androidImage diff --git a/internal/command/command.go b/internal/command/command.go index 933ce96c..6b7fedcf 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -4,6 +4,8 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" + "strings" "github.com/fyne-io/fyne-cross/internal/icon" "github.com/fyne-io/fyne-cross/internal/log" @@ -102,3 +104,117 @@ func prepareIcon(ctx Context) error { func printUsage(template string, data interface{}) { log.PrintTemplate(os.Stderr, template, data) } + +// checkFyneBinHost checks if the fyne cli tool is installed on the host +func checkFyneBinHost(ctx Context) (string, error) { + fyne, err := exec.LookPath("fyne") + if err != nil { + return "", fmt.Errorf("missed requirement: fyne. To install: `go get fyne.io/fyne/cmd/fyne` and add $GOPATH/bin to $PATH") + } + + if ctx.Debug { + out, err := exec.Command(fyne, "version").Output() + if err != nil { + return fyne, fmt.Errorf("could not get fyne cli %s version: %v", fyne, err) + } + log.Debugf("%s", out) + } + + return fyne, nil +} + +// fynePackageHost package the application using the fyne cli tool from the host +// Note: at the moment this is used only for the ios builds +func fynePackageHost(ctx Context) error { + + fyne, err := checkFyneBinHost(ctx) + if err != nil { + return err + } + + args := []string{ + "package", + "-os", ctx.OS, + "-name", ctx.Output, + "-icon", volume.JoinPathContainer(ctx.TmpDirHost(), ctx.ID, icon.Default), + "-appID", ctx.AppID, + "-appBuild", ctx.AppBuild, + "-appVersion", ctx.AppVersion, + } + + // add tags to command, if any + tags := ctx.Tags + if len(tags) > 0 { + args = append(args, "-tags", fmt.Sprintf("'%s'", strings.Join(tags, ","))) + } + + // run the command from the host + fyneCmd := exec.Command(fyne, args...) + fyneCmd.Dir = ctx.WorkDirHost() + fyneCmd.Stdout = os.Stdout + fyneCmd.Stderr = os.Stderr + + if ctx.Debug { + log.Debug(fyneCmd) + } + + err = fyneCmd.Run() + if err != nil { + return fmt.Errorf("could not package the Fyne app: %v", err) + } + return nil +} + +// fyneReleaseHost package and release the application using the fyne cli tool from the host +// Note: at the moment this is used only for the ios and windows builds +func fyneReleaseHost(ctx Context) error { + + fyne, err := checkFyneBinHost(ctx) + if err != nil { + return err + } + + args := []string{ + "release", + "-os", ctx.OS, + "-name", ctx.Output, + "-icon", volume.JoinPathContainer(ctx.TmpDirHost(), ctx.ID, icon.Default), + "-appID", ctx.AppID, + "-appBuild", ctx.AppBuild, + "-appVersion", ctx.AppVersion, + } + + // add tags to command, if any + tags := ctx.Tags + if len(tags) > 0 { + args = append(args, "-tags", fmt.Sprintf("'%s'", strings.Join(tags, ","))) + } + + switch ctx.OS { + case darwinOS: + args = append(args, "-category", ctx.Category) + case iosOS: + args = append(args, "-certificate", ctx.Certificate) + args = append(args, "-profile", ctx.Profile) + case windowsOS: + args = append(args, "-certificate", ctx.Certificate) + args = append(args, "-developer", ctx.Developer) + args = append(args, "-password", ctx.Password) + } + + // run the command from the host + fyneCmd := exec.Command(fyne, args...) + fyneCmd.Dir = ctx.WorkDirHost() + fyneCmd.Stdout = os.Stdout + fyneCmd.Stderr = os.Stderr + + if ctx.Debug { + log.Debug(fyneCmd) + } + + err = fyneCmd.Run() + if err != nil { + return fmt.Errorf("could not package the Fyne app: %v", err) + } + return nil +} diff --git a/internal/command/context.go b/internal/command/context.go index 27fd7f1f..568e09a0 100644 --- a/internal/command/context.go +++ b/internal/command/context.go @@ -2,9 +2,11 @@ package command import ( "bytes" + "errors" "fmt" "path/filepath" "runtime" + "strconv" "strings" "github.com/fyne-io/fyne-cross/internal/log" @@ -41,15 +43,28 @@ type Context struct { OS string // OS defines the target OS Tags []string // Tags defines the tags to use + AppBuild string // Build number AppID string // AppID is the appID to use for distribution + AppVersion string // AppVersion is the version number in the form x, x.y or x.y.z semantic version CacheEnabled bool // CacheEnabled if true enable go build cache DockerImage string // DockerImage defines the docker image used to build Icon string // Icon is the optional icon in png format to use for distribution Output string // Output is the name output Package string // Package is the package to build named by the import path as per 'go build' + Release bool // Enable release mode. If true, prepares an application for public distribution StripDebug bool // StripDebug if true, strips binary output Debug bool // Debug if true enable debug log Pull bool // Pull if true attempts to pull a newer version of the docker image + + // Release context + Category string //Category represents the category of the app for store listing [macOS] + Certificate string //Certificate represents the name of the certificate to sign the build [iOS, Windows] + Developer string //Developer represents the developer identity for your Microsoft store account [Windows] + Keystore string //Keystore represents the location of .keystore file containing signing information [Android] + KeystorePass string //KeystorePass represents the password for the .keystore file [Android] + KeyPass string //KeyPass represents the assword for the signer's private key, which is needed if the private key is password-protected [Android] + Password string //Password represents the password for the certificate used to sign the build [Windows] + Profile string //Profile represents the name of the provisioning profile for this release build [iOS] } // String implements the Stringer interface @@ -76,6 +91,7 @@ func makeDefaultContext(flags *CommonFlags, args []string) (Context, error) { // set context based on command-line flags ctx := Context{ AppID: flags.AppID, + AppVersion: flags.AppVersion, CacheEnabled: !flags.NoCache, DockerImage: flags.DockerImage, Env: flags.Env, @@ -86,8 +102,15 @@ func makeDefaultContext(flags *CommonFlags, args []string) (Context, error) { Debug: flags.Debug, Volume: vol, Pull: flags.Pull, + Release: flags.Release, + } + + if flags.AppBuild <= 0 { + return ctx, errors.New("build number should be greater than 0") } + ctx.AppBuild = strconv.Itoa(flags.AppBuild) + ctx.Package, err = packageFromArgs(args, vol) if err != nil { return ctx, err diff --git a/internal/command/context_test.go b/internal/command/context_test.go index aee06882..abda740b 100644 --- a/internal/command/context_test.go +++ b/internal/command/context_test.go @@ -27,9 +27,12 @@ func Test_makeDefaultContext(t *testing.T) { { name: "default", args: args{ - flags: &CommonFlags{}, + flags: &CommonFlags{ + AppBuild: 1, + }, }, want: Context{ + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -41,10 +44,12 @@ func Test_makeDefaultContext(t *testing.T) { name: "custom env", args: args{ flags: &CommonFlags{ - Env: envFlag{"TEST=true"}, + AppBuild: 1, + Env: envFlag{"TEST=true"}, }, }, want: Context{ + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -57,10 +62,12 @@ func Test_makeDefaultContext(t *testing.T) { name: "custom ldflags", args: args{ flags: &CommonFlags{ - Ldflags: "-X main.version=1.2.3", + AppBuild: 1, + Ldflags: "-X main.version=1.2.3", }, }, want: Context{ + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -72,9 +79,12 @@ func Test_makeDefaultContext(t *testing.T) { { name: "package default", args: args{ - flags: &CommonFlags{}, + flags: &CommonFlags{ + AppBuild: 1, + }, }, want: Context{ + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -85,10 +95,13 @@ func Test_makeDefaultContext(t *testing.T) { { name: "package dot", args: args{ - flags: &CommonFlags{}, - args: []string{"."}, + flags: &CommonFlags{ + AppBuild: 1, + }, + args: []string{"."}, }, want: Context{ + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -99,10 +112,13 @@ func Test_makeDefaultContext(t *testing.T) { { name: "package relative", args: args{ - flags: &CommonFlags{}, - args: []string{"./cmd/command"}, + flags: &CommonFlags{ + AppBuild: 1, + }, + args: []string{"./cmd/command"}, }, want: Context{ + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -113,10 +129,13 @@ func Test_makeDefaultContext(t *testing.T) { { name: "package absolute", args: args{ - flags: &CommonFlags{}, - args: []string{volume.JoinPathHost(vol.WorkDirHost(), "cmd/command")}, + flags: &CommonFlags{ + AppBuild: 1, + }, + args: []string{volume.JoinPathHost(vol.WorkDirHost(), "cmd/command")}, }, want: Context{ + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -127,8 +146,10 @@ func Test_makeDefaultContext(t *testing.T) { { name: "package absolute outside work dir", args: args{ - flags: &CommonFlags{}, - args: []string{os.TempDir()}, + flags: &CommonFlags{ + AppBuild: 1, + }, + args: []string{os.TempDir()}, }, wantErr: true, }, @@ -136,10 +157,12 @@ func Test_makeDefaultContext(t *testing.T) { name: "custom tags", args: args{ flags: &CommonFlags{ - Tags: tagsFlag{"hints", "gles"}, + AppBuild: 1, + Tags: tagsFlag{"hints", "gles"}, }, }, want: Context{ + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -148,6 +171,54 @@ func Test_makeDefaultContext(t *testing.T) { }, wantErr: false, }, + { + name: "invalid app build", + args: args{ + flags: &CommonFlags{ + AppBuild: 0, + }, + }, + want: Context{}, + wantErr: true, + }, + { + name: "release mode enabled", + args: args{ + flags: &CommonFlags{ + AppBuild: 1, + Release: true, + }, + }, + want: Context{ + AppBuild: "1", + Volume: vol, + CacheEnabled: true, + StripDebug: true, + Package: ".", + Release: true, + }, + wantErr: false, + }, + { + name: "app version", + args: args{ + flags: &CommonFlags{ + AppBuild: 1, + AppVersion: "1.0", + Release: true, + }, + }, + want: Context{ + AppBuild: "1", + AppVersion: "1.0", + Volume: vol, + CacheEnabled: true, + StripDebug: true, + Package: ".", + Release: true, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/command/darwin.go b/internal/command/darwin.go index d753eacb..bae823de 100644 --- a/internal/command/darwin.go +++ b/internal/command/darwin.go @@ -48,7 +48,10 @@ func (cmd *Darwin) Parse(args []string) error { CommonFlags: commonFlags, TargetArch: &targetArchFlag{runtime.GOARCH}, } - flagSet.Var(flags.TargetArch, "arch", fmt.Sprintf(`List of target architecture to build separated by comma. Supported arch: %s`, windowsArchSupported)) + flagSet.Var(flags.TargetArch, "arch", fmt.Sprintf(`List of target architecture to build separated by comma. Supported arch: %s`, darwinArchSupported)) + + // flags used only in release mode + flagSet.StringVar(&flags.Category, "category", "", "The name of the certificate to sign the build") flagAppID := flagSet.Lookup("app-id") flagAppID.Usage = fmt.Sprintf("%s [required]", flagAppID.Usage) @@ -93,10 +96,7 @@ func (cmd *Darwin) Run() error { return err } - // - // build - // - err = goBuild(ctx) + err = prepareIcon(ctx) if err != nil { return err } @@ -106,20 +106,36 @@ func (cmd *Darwin) Run() error { // log.Info("[i] Packaging app...") - packageName := fmt.Sprintf("%s.app", ctx.Output) - - err = prepareIcon(ctx) - if err != nil { - return err - } - - err = fynePackage(ctx) - if err != nil { - return fmt.Errorf("could not package the Fyne app: %v", err) + var packageName string + var srcFile string + if ctx.Release { + if runtime.GOOS != darwinOS { + return fmt.Errorf("darwin release build is supported only on darwin hosts") + } + + packageName = fmt.Sprintf("%s.pkg", ctx.Output) + srcFile = volume.JoinPathHost(ctx.WorkDirHost(), packageName) + + err = fyneReleaseHost(ctx) + if err != nil { + return fmt.Errorf("could not package the Fyne app: %v", err) + } + } else { + err = goBuild(ctx) + if err != nil { + return err + } + + packageName = fmt.Sprintf("%s.app", ctx.Output) + srcFile = volume.JoinPathHost(ctx.TmpDirHost(), ctx.ID, packageName) + + err = fynePackage(ctx) + if err != nil { + return fmt.Errorf("could not package the Fyne app: %v", err) + } } - // move the dist package into the "dist" folder - srcFile := volume.JoinPathHost(ctx.TmpDirHost(), ctx.ID, packageName) + // move the package into the "dist" folder distFile := volume.JoinPathHost(ctx.DistDirHost(), ctx.ID, packageName) err = os.MkdirAll(filepath.Dir(distFile), 0755) if err != nil { @@ -163,6 +179,9 @@ Options: type darwinFlags struct { *CommonFlags + //Category represents the category of the app for store listing + Category string + // TargetArch represents a list of target architecture to build on separated by comma TargetArch *targetArchFlag } @@ -186,6 +205,7 @@ func darwinContext(flags *darwinFlags, args []string) ([]Context, error) { ctx.Architecture = arch ctx.OS = darwinOS ctx.ID = fmt.Sprintf("%s-%s", ctx.OS, ctx.Architecture) + ctx.Category = flags.Category switch arch { case ArchAmd64: diff --git a/internal/command/docker.go b/internal/command/docker.go index 996de5f5..40ec774e 100644 --- a/internal/command/docker.go +++ b/internal/command/docker.go @@ -190,12 +190,25 @@ func fynePackage(ctx Context) error { "-name", ctx.Output, "-icon", volume.JoinPathContainer(ctx.TmpDirContainer(), ctx.ID, icon.Default), "-appID", ctx.AppID, + "-appBuild", ctx.AppBuild, + "-appVersion", ctx.AppVersion, + } + + // add tags to command, if any + tags := ctx.Tags + if len(tags) > 0 { + args = append(args, "-tags", fmt.Sprintf("'%s'", strings.Join(tags, ","))) + } + + // Enable release mode, if specified + if ctx.Release { + args = append(args, "-release") } // workDir default value workDir := ctx.WorkDirContainer() - if ctx.OS == androidOS || ctx.OS == iosOS { + if ctx.OS == androidOS { workDir = volume.JoinPathContainer(workDir, ctx.Package) } @@ -210,6 +223,66 @@ func fynePackage(ctx Context) error { CacheEnabled: ctx.CacheEnabled, WorkDir: workDir, Debug: ctx.Debug, + Env: ctx.Env, + } + + err := Run(ctx.DockerImage, ctx.Volume, runOpts, args) + if err != nil { + return fmt.Errorf("could not package the Fyne app: %v", err) + } + return nil +} + +// fyneRelease package and release the application using the fyne cli tool +// Note: at the moment this is used only for the android builds +func fyneRelease(ctx Context) error { + + if ctx.Debug { + err := Run(ctx.DockerImage, ctx.Volume, Options{Debug: ctx.Debug}, []string{fyneBin, "version"}) + if err != nil { + return fmt.Errorf("could not get fyne cli %s version: %v", fyneBin, err) + } + } + + args := []string{ + fyneBin, "release", + "-os", ctx.OS, + "-name", ctx.Output, + "-icon", volume.JoinPathContainer(ctx.TmpDirContainer(), ctx.ID, icon.Default), + "-appID", ctx.AppID, + "-appBuild", ctx.AppBuild, + "-appVersion", ctx.AppVersion, + } + + // add tags to command, if any + tags := ctx.Tags + if len(tags) > 0 { + args = append(args, "-tags", fmt.Sprintf("'%s'", strings.Join(tags, ","))) + } + + // workDir default value + workDir := ctx.WorkDirContainer() + + switch ctx.OS { + case androidOS: + workDir = volume.JoinPathContainer(workDir, ctx.Package) + args = append(args, "-keyStore", ctx.Keystore) + args = append(args, "-keyStorePass", ctx.KeystorePass) + args = append(args, "-keyPass", ctx.KeyPass) + case iosOS: + args = append(args, "-certificate", ctx.Certificate) + args = append(args, "-profile", ctx.Profile) + case windowsOS: + args = append(args, "-certificate", ctx.Certificate) + args = append(args, "-developer", ctx.Developer) + args = append(args, "-password", ctx.Password) + } + + runOpts := Options{ + CacheEnabled: ctx.CacheEnabled, + WorkDir: workDir, + Debug: ctx.Debug, + Env: ctx.Env, } err := Run(ctx.DockerImage, ctx.Volume, runOpts, args) diff --git a/internal/command/flag.go b/internal/command/flag.go index 2f227d68..dcba8ee8 100644 --- a/internal/command/flag.go +++ b/internal/command/flag.go @@ -15,8 +15,13 @@ var flagSet = flag.NewFlagSet("fyne-cross", flag.ExitOnError) // CommonFlags holds the flags shared between all commands type CommonFlags struct { + // AppBuild represents the build number, should be greater than 0 and + // incremented for each build + AppBuild int // AppID represents the application ID used for distribution AppID string + // AppVersion represents the version number in the form x, x.y or x.y.z semantic version + AppVersion string // CacheDir is the directory used to share/cache sources and dependencies. // Default to system cache directory (i.e. $HOME/.cache/fyne-cross) CacheDir string @@ -36,6 +41,8 @@ type CommonFlags struct { NoStripDebug bool // Output represents the named output file Output string + // Release represents if the package should be prepared for release (disable debug etc) + Release bool // RootDir represents the project root directory RootDir string // Silent enables the silent mode @@ -67,7 +74,9 @@ func newCommonFlags() (*CommonFlags, error) { } flags := &CommonFlags{} + flagSet.IntVar(&flags.AppBuild, "app-build", 1, "Build number, should be greater than 0 and incremented for each build") flagSet.StringVar(&flags.AppID, "app-id", output, "Application ID used for distribution") + flagSet.StringVar(&flags.AppVersion, "app-version", "1.0", "Version number in the form x, x.y or x.y.z semantic version") flagSet.StringVar(&flags.CacheDir, "cache", cacheDir, "Directory used to share/cache sources and dependencies") flagSet.BoolVar(&flags.NoCache, "no-cache", false, "Do not use the go build cache") flagSet.Var(&flags.Env, "env", "List of additional env variables specified as KEY=VALUE and separated by comma") @@ -77,6 +86,7 @@ func newCommonFlags() (*CommonFlags, error) { flagSet.Var(&flags.Tags, "tags", "List of additional build tags separated by comma") flagSet.BoolVar(&flags.NoStripDebug, "no-strip-debug", false, "Do not strip debug information from binaries") flagSet.StringVar(&flags.Output, "output", output, "Named output file") + flagSet.BoolVar(&flags.Release, "release", false, "Release mode. Prepares the application for public distribution") flagSet.StringVar(&flags.RootDir, "dir", rootDir, "Fyne app root directory") flagSet.BoolVar(&flags.Silent, "silent", false, "Silent mode") flagSet.BoolVar(&flags.Debug, "debug", false, "Debug mode") diff --git a/internal/command/ios.go b/internal/command/ios.go index 6897ea31..0d2cbba7 100644 --- a/internal/command/ios.go +++ b/internal/command/ios.go @@ -3,11 +3,9 @@ package command import ( "fmt" "os" - "os/exec" "path/filepath" "runtime" - "github.com/fyne-io/fyne-cross/internal/icon" "github.com/fyne-io/fyne-cross/internal/log" "github.com/fyne-io/fyne-cross/internal/volume" ) @@ -45,6 +43,10 @@ func (cmd *IOS) Parse(args []string) error { CommonFlags: commonFlags, } + // flags used only in release mode + flagSet.StringVar(&flags.Certificate, "certificate", "", "The name of the certificate to sign the build") + flagSet.StringVar(&flags.Profile, "profile", "", "The name of the provisioning profile for this release build") + flagAppID := flagSet.Lookup("app-id") flagAppID.Usage = fmt.Sprintf("%s. Must match a valid provisioning profile [required]", flagAppID.Usage) @@ -66,28 +68,10 @@ func (cmd *IOS) Run() error { log.Infof("[i] Target: %s", ctx.OS) log.Debugf("%#v", ctx) - // - // check requirements - // until a docker image for iOS will be available the packaging of iOS app - // is supported only on darwin hosts via the fyne command. - // Requirements: - // - fyne binary - // - XCode - // - fyne, err := exec.LookPath("fyne") - if err != nil { - return fmt.Errorf("missed requirement: fyne. To install: `go get fyne.io/fyne/cmd/fyne` and add $GOPATH/bin to $PATH") - } - - err = exec.Command("xcrun", "xcodebuild", "-version").Run() - if err != nil { - return fmt.Errorf("missed requirement: XCode") - } - // // pull image, if requested // - err = pullImage(ctx) + err := pullImage(ctx) if err != nil { return err } @@ -105,38 +89,24 @@ func (cmd *IOS) Run() error { return err } - // - // package - // - - log.Info("[i] Packaging app...") - - packageName := fmt.Sprintf("%s.app", ctx.Output) - err = prepareIcon(ctx) if err != nil { return err } - args := []string{ - "package", - "-os", ctx.OS, - "-name", ctx.Output, - "-icon", volume.JoinPathContainer(ctx.TmpDirHost(), ctx.ID, icon.Default), - "-appID", ctx.AppID, - } - - // run the command inside the container - fynePackageCmd := exec.Command(fyne, args...) - fynePackageCmd.Dir = ctx.WorkDirHost() - fynePackageCmd.Stdout = os.Stdout - fynePackageCmd.Stderr = os.Stderr + log.Info("[i] Packaging app...") - if ctx.Debug { - log.Debug(fynePackageCmd) + var packageName string + if ctx.Release { + // Release mode + packageName = fmt.Sprintf("%s.ipa", ctx.Output) + err = fyneReleaseHost(ctx) + } else { + // Build mode + packageName = fmt.Sprintf("%s.app", ctx.Output) + err = fynePackageHost(ctx) } - err = fynePackageCmd.Run() if err != nil { return fmt.Errorf("could not package the Fyne app: %v", err) } @@ -185,6 +155,12 @@ Options: // iosFlags defines the command-line flags for the ios command type iosFlags struct { *CommonFlags + + //Certificate represents the name of the certificate to sign the build + Certificate string + + //Profile represents the name of the provisioning profile for this release build + Profile string } // makeIOSContext returns the command context for an iOS target @@ -206,6 +182,8 @@ func makeIOSContext(flags *iosFlags, args []string) (Context, error) { ctx.OS = iosOS ctx.ID = iosOS + ctx.Certificate = flags.Certificate + ctx.Profile = flags.Profile // set context based on command-line flags if flags.DockerImage == "" { diff --git a/internal/command/windows.go b/internal/command/windows.go index ce03f883..6b5677a3 100644 --- a/internal/command/windows.go +++ b/internal/command/windows.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "github.com/fyne-io/fyne-cross/internal/log" "github.com/fyne-io/fyne-cross/internal/volume" @@ -52,6 +53,11 @@ func (cmd *Windows) Parse(args []string) error { flagSet.Var(flags.TargetArch, "arch", fmt.Sprintf(`List of target architecture to build separated by comma. Supported arch: %s`, windowsArchSupported)) flagSet.BoolVar(&flags.Console, "console", false, "If set writes a 'console binary' instead of 'GUI binary'") + // flags used only in release mode + flagSet.StringVar(&flags.Certificate, "certificate", "", "The name of the certificate to sign the build") + flagSet.StringVar(&flags.Developer, "developer", "", "The developer identity for your Microsoft store account") + flagSet.StringVar(&flags.Password, "password", "", "The password for the certificate used to sign the build") + // Add exe extension to default output flagOutput := flagSet.Lookup("output") flagOutput.DefValue = fmt.Sprintf("%s.exe", flagOutput.DefValue) @@ -102,6 +108,38 @@ func (cmd *Windows) Run() error { return err } + // Release mode + if ctx.Release { + if runtime.GOOS != windowsOS { + return fmt.Errorf("windows release build is supported only on windows hosts") + } + + err = fyneReleaseHost(ctx) + if err != nil { + return fmt.Errorf("could not package the Fyne app: %v", err) + } + + packageName := ctx.Output + ".appx" + if pos := strings.LastIndex(ctx.Output, ".exe"); pos > 0 { + packageName = ctx.Output[:pos] + ".appx" + } + + // move the dist package into the "dist" folder + srcFile := volume.JoinPathHost(ctx.WorkDirHost(), packageName) + distFile := volume.JoinPathHost(ctx.DistDirHost(), ctx.ID, packageName) + err = os.MkdirAll(filepath.Dir(distFile), 0755) + if err != nil { + return fmt.Errorf("could not create the dist package dir: %v", err) + } + + err = os.Rename(srcFile, distFile) + if err != nil { + return err + } + return nil + } + + // Build mode windres, err := WindowsResource(ctx) if err != nil { return err @@ -174,6 +212,13 @@ type windowsFlags struct { // Console defines if the Windows app will build as "console binary" instead of "GUI binary" Console bool + + //Certificate represents the name of the certificate to sign the build + Certificate string + //Developer represents the developer identity for your Microsoft store account + Developer string + //Password represents the password for the certificate used to sign the build [Windows] + Password string } // makeWindowsContext returns the command context for a windows target @@ -195,6 +240,10 @@ func makeWindowsContext(flags *windowsFlags, args []string) ([]Context, error) { ctx.OS = windowsOS ctx.ID = fmt.Sprintf("%s-%s", ctx.OS, ctx.Architecture) + ctx.Certificate = flags.Certificate + ctx.Developer = flags.Developer + ctx.Password = flags.Password + switch arch { case ArchAmd64: ctx.Env = append(ctx.Env, "GOOS=windows", "GOARCH=amd64", "CC=x86_64-w64-mingw32-gcc") diff --git a/internal/command/windows_test.go b/internal/command/windows_test.go index 4722b5cc..11314405 100644 --- a/internal/command/windows_test.go +++ b/internal/command/windows_test.go @@ -25,12 +25,15 @@ func Test_makeWindowsContext(t *testing.T) { name: "default", args: args{ flags: &windowsFlags{ - CommonFlags: &CommonFlags{}, - TargetArch: &targetArchFlag{"amd64"}, + CommonFlags: &CommonFlags{ + AppBuild: 1, + }, + TargetArch: &targetArchFlag{"amd64"}, }, }, want: []Context{ { + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -48,13 +51,16 @@ func Test_makeWindowsContext(t *testing.T) { name: "console", args: args{ flags: &windowsFlags{ - CommonFlags: &CommonFlags{}, - TargetArch: &targetArchFlag{"386"}, - Console: true, + CommonFlags: &CommonFlags{ + AppBuild: 1, + }, + TargetArch: &targetArchFlag{"386"}, + Console: true, }, }, want: []Context{ { + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -72,13 +78,15 @@ func Test_makeWindowsContext(t *testing.T) { args: args{ flags: &windowsFlags{ CommonFlags: &CommonFlags{ - Ldflags: "-X main.version=1.2.3", + AppBuild: 1, + Ldflags: "-X main.version=1.2.3", }, TargetArch: &targetArchFlag{"amd64"}, }, }, want: []Context{ { + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true, @@ -97,6 +105,7 @@ func Test_makeWindowsContext(t *testing.T) { args: args{ flags: &windowsFlags{ CommonFlags: &CommonFlags{ + AppBuild: 1, DockerImage: "test", }, TargetArch: &targetArchFlag{"amd64"}, @@ -104,6 +113,7 @@ func Test_makeWindowsContext(t *testing.T) { }, want: []Context{ { + AppBuild: "1", Volume: vol, CacheEnabled: true, StripDebug: true,