Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/metadataapi #2813

Merged
merged 16 commits into from Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@ More detailed release notes can be found on the [releases page](https://github.c
### Added

* Add SetIcon method on ToolbarAction (#2475)
* Access compiled app metadata using new `App.Metadata()` method

### Changed

Expand All @@ -19,7 +20,7 @@ More detailed release notes can be found on the [releases page](https://github.c

### Fixed

*
* SendNotification does not show app name on Windows (#1940)


## 2.1.3 - Ongoing
Expand Down
21 changes: 21 additions & 0 deletions app.go
Expand Up @@ -62,6 +62,11 @@ type App interface {
//
// Since: 2.1
Lifecycle() Lifecycle

// Metadata returns the application metadata that was set at compile time.
//
// Since: 2.2
Metadata() AppMetadata
}

// app contains an App variable, but due to atomic.Value restrictions on
Expand Down Expand Up @@ -90,6 +95,22 @@ func CurrentApp() App {
return (val).(appContainer).current
}

// AppMetadata captures the build metadata for an application.
//
// Since: 2.2
type AppMetadata struct {
// ID is the unique ID of this application, used by many distribution platforms.
ID string
// Name is the human friendly name of this app.
Name string
// Version represents the version of this application, normally following semantic versioning.
Version string
// Build is the build number of this app, some times appended to the version number.
Build int
// Icon contains, if present, a resource of the icon that was bundled at build time.
Icon Resource
Jacalz marked this conversation as resolved.
Show resolved Hide resolved
}

// Lifecycle represents the various phases that an app can transition through.
//
// Since: 2.1
Expand Down
9 changes: 8 additions & 1 deletion app/app.go
Expand Up @@ -35,7 +35,11 @@ type fyneApp struct {
}

func (a *fyneApp) Icon() fyne.Resource {
return a.icon
if a.icon != nil {
return a.icon
}

return a.Metadata().Icon
}

func (a *fyneApp) SetIcon(icon fyne.Resource) {
Expand All @@ -46,6 +50,9 @@ func (a *fyneApp) UniqueID() string {
if a.uniqueID != "" {
return a.uniqueID
}
if a.Metadata().ID != "" {
return a.Metadata().ID
}

fyne.LogError("Preferences API requires a unique ID, use app.NewWithID()", nil)
a.uniqueID = "missing-id-" + strconv.FormatInt(time.Now().Unix(), 10) // This is a fake unique - it just has to not be reused...
Expand Down
4 changes: 2 additions & 2 deletions app/app_windows.go
Expand Up @@ -74,9 +74,9 @@ var scriptNum = 0
func (a *fyneApp) SendNotification(n *fyne.Notification) {
title := escapeNotificationString(n.Title)
content := escapeNotificationString(n.Content)
appID := a.UniqueID() // TODO once we have an app name compiled in this could be improved
appID := a.UniqueID()
if appID == "" || strings.Index(appID, "missing-id") == 0 {
appID = "Fyne app"
appID = a.Metadata().Name
}

script := fmt.Sprintf(notificationTemplate, title, content, appID)
Expand Down
48 changes: 48 additions & 0 deletions app/meta.go
@@ -0,0 +1,48 @@
package app

import (
"encoding/base64"
"io/ioutil"
"strconv"
"strings"

"fyne.io/fyne/v2"
intapp "fyne.io/fyne/v2/internal/app"
)

var (
meta fyne.AppMetadata
)

func init() {
build, err := strconv.Atoi(intapp.MetaBuild)
andydotxyz marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
build = 1
}

meta = fyne.AppMetadata{
Icon: convertIcon(intapp.MetaIcon),
ID: intapp.MetaID,
Name: intapp.MetaName,
Version: intapp.MetaVersion,
Build: build,
}
}

func (a *fyneApp) Metadata() fyne.AppMetadata {
return meta
}

func convertIcon(bytes string) fyne.Resource {
data := base64.NewDecoder(base64.StdEncoding, strings.NewReader(bytes))
img, err := ioutil.ReadAll(data)
if err != nil {
fyne.LogError("Failed to decode icon from embedded data", err)
return nil
}

return &fyne.StaticResource{
StaticName: "FyneAppIcon",
StaticContent: img,
}
}
4 changes: 4 additions & 0 deletions app_test.go
Expand Up @@ -57,6 +57,10 @@ func (dummyApp) Lifecycle() Lifecycle {
return nil
}

func (dummyApp) Metadata() AppMetadata {
return AppMetadata{}
}

func TestSetCurrentApp(t *testing.T) {
a := &dummyApp{}
SetCurrentApp(a)
Expand Down
55 changes: 52 additions & 3 deletions cmd/fyne/internal/commands/build.go
@@ -1,11 +1,14 @@
package commands

import (
"encoding/base64"
"fmt"
"os"
"runtime"
"strconv"
"strings"

"fyne.io/fyne/v2"
version "github.com/mcuadros/go-version"
)

Expand All @@ -15,6 +18,9 @@ type builder struct {
tags []string

runner runner

icon, id, name, version string
buildNum int
}

func checkVersion(output string, versionConstraint *version.ConstraintGroup) error {
Expand Down Expand Up @@ -73,18 +79,23 @@ func (b *builder) build() error {
env = append(env, "CGO_CFLAGS=-mmacosx-version-min=10.11", "CGO_LDFLAGS=-mmacosx-version-min=10.11")
}

meta := b.generateMetaLDFlags()
if !isWeb(goos) {
env = append(env, "CGO_ENABLED=1") // in case someone is trying to cross-compile...

if goos == "windows" {
if b.release {
args = append(args, "-ldflags", "-s -w -H=windowsgui")
args = append(args, "-ldflags", "-s -w -H=windowsgui "+meta)
} else {
args = append(args, "-ldflags", "-H=windowsgui")
args = append(args, "-ldflags", "-H=windowsgui "+meta)
}
} else if b.release {
args = append(args, "-ldflags", "-s -w")
args = append(args, "-ldflags", "-s -w "+meta)
} else if meta != "" {
args = append(args, "-ldflags", meta)
}
} else if meta != "" {
args = append(args, "-ldflags", meta)
}

if b.target != "" {
Expand Down Expand Up @@ -126,6 +137,44 @@ func (b *builder) build() error {
return err
}

func (b *builder) generateMetaLDFlags() string {
buildIDString := ""
if b.buildNum > 0 {
buildIDString = strconv.Itoa(b.buildNum)
}
iconBytes := ""
if b.icon != "" {
res, err := fyne.LoadResourceFromPath(b.icon)
if err != nil {
fyne.LogError("Unable to load file "+b.icon, err)
} else {
staticRes, ok := res.(*fyne.StaticResource)
if !ok {
fyne.LogError("Unable to format resource", fmt.Errorf("unexpected resource type %T", res))
} else {
iconBytes = base64.StdEncoding.EncodeToString(staticRes.StaticContent)
}
}
}

inserts := [][2]string{
{"MetaID", b.id},
{"MetaName", b.name},
{"MetaVersion", b.version},
{"MetaBuild", buildIDString},
{"MetaIcon", iconBytes},
}

var vars []string
for _, item := range inserts {
if item[1] != "" {
vars = append(vars, "-X 'fyne.io/fyne/v2/internal/app."+item[0]+"="+item[1]+"'")
}
}

return strings.Join(vars, " ")
}

func targetOS() string {
osEnv, ok := os.LookupEnv("GOOS")
if ok {
Expand Down
11 changes: 11 additions & 0 deletions cmd/fyne/internal/commands/build_test.go
Expand Up @@ -8,6 +8,17 @@ import (
"github.com/stretchr/testify/assert"
)

func TestBuildGenerateMetaLDFlags(t *testing.T) {
b := &builder{}
assert.Equal(t, "", b.generateMetaLDFlags())

b.id = "com.example"
assert.Equal(t, "-X 'fyne.io/fyne/v2/internal/app.MetaID=com.example'", b.generateMetaLDFlags())

b.version = "1.2.3"
assert.Equal(t, "-X 'fyne.io/fyne/v2/internal/app.MetaID=com.example' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.2.3'", b.generateMetaLDFlags())
}

func Test_CheckGoVersionNoGo(t *testing.T) {
commandNil := &testCommandRuns{runs: []mockRunner{}, t: t}
assert.Nil(t, checkGoVersion(commandNil, nil))
Expand Down
18 changes: 18 additions & 0 deletions cmd/fyne/internal/commands/package.go
Expand Up @@ -196,6 +196,12 @@ func (p *Packager) buildPackage(runner runner) ([]string, error) {
release: p.release,
tags: tags,
runner: runner,

icon: p.icon,
id: p.appID,
name: p.name,
version: p.appVersion,
buildNum: p.appBuild,
}

return []string{p.exe}, b.build()
Expand All @@ -208,6 +214,12 @@ func (p *Packager) buildPackage(runner runner) ([]string, error) {
release: p.release,
tags: tags,
runner: runner,

icon: p.icon,
id: p.appID,
name: p.name,
version: p.appVersion,
buildNum: p.appBuild,
}

err := bWasm.build()
Expand All @@ -222,6 +234,12 @@ func (p *Packager) buildPackage(runner runner) ([]string, error) {
release: p.release,
tags: tags,
runner: runner,

icon: p.icon,
id: p.appID,
name: p.name,
version: p.appVersion,
buildNum: p.appBuild,
}

err = bGopherJS.build()
Expand Down
16 changes: 12 additions & 4 deletions cmd/fyne/internal/commands/package_test.go
Expand Up @@ -119,7 +119,9 @@ func Test_PackageWasm(t *testing.T) {
},
{
expectedValue: expectedValue{
args: []string{"build", "-o", "myTest.wasm"},
args: []string{"build", "-ldflags",
"-X 'fyne.io/fyne/v2/internal/app.MetaName=myTest.wasm' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.0.0' -X 'fyne.io/fyne/v2/internal/app.MetaBuild=1'",
"-o", "myTest.wasm"},
env: []string{"GOARCH=wasm", "GOOS=js"},
osEnv: true,
dir: "myTest",
Expand Down Expand Up @@ -226,7 +228,9 @@ func Test_PackageGopherJS(t *testing.T) {
expected := []mockRunner{
{
expectedValue: expectedValue{
args: []string{"build", "-o", "myTest.js"},
args: []string{"build", "-ldflags",
"-X 'fyne.io/fyne/v2/internal/app.MetaName=myTest.js' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.0.0' -X 'fyne.io/fyne/v2/internal/app.MetaBuild=1'",
"-o", "myTest.js"},
osEnv: true,
dir: "myTest",
},
Expand Down Expand Up @@ -354,7 +358,9 @@ func Test_PackageWeb(t *testing.T) {
},
{
expectedValue: expectedValue{
args: []string{"build", "-o", "myTest.wasm"},
args: []string{"build", "-ldflags",
"-X 'fyne.io/fyne/v2/internal/app.MetaName=myTest' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.0.0' -X 'fyne.io/fyne/v2/internal/app.MetaBuild=1'",
"-o", "myTest.wasm"},
env: []string{"GOARCH=wasm", "GOOS=js"},
osEnv: true,
dir: "myTest",
Expand All @@ -365,7 +371,9 @@ func Test_PackageWeb(t *testing.T) {
},
{
expectedValue: expectedValue{
args: []string{"build", "-o", "myTest.js"},
args: []string{"build", "-ldflags",
"-X 'fyne.io/fyne/v2/internal/app.MetaName=myTest' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.0.0' -X 'fyne.io/fyne/v2/internal/app.MetaBuild=1'",
"-o", "myTest.js"},
osEnv: true,
dir: "myTest",
},
Expand Down
10 changes: 10 additions & 0 deletions internal/app/meta.go
@@ -0,0 +1,10 @@
package app

// these internal variables are set by the fyne build command so that the "FyneApp.toml" data is readable at runtime.
var (
MetaIcon = "" // this will contain base64 encoded icon bytes
MetaID = "com.example"
MetaName = "Fyne App"
MetaVersion = "1.0.0"
MetaBuild = "1"
)
4 changes: 4 additions & 0 deletions test/testapp.go
Expand Up @@ -86,6 +86,10 @@ func (a *testApp) Lifecycle() fyne.Lifecycle {
return a.lifecycle
}

func (a *testApp) Metadata() fyne.AppMetadata {
return fyne.AppMetadata{} // just dummy data
}

func (a *testApp) lastAppliedTheme() fyne.Theme {
a.propertyLock.Lock()
defer a.propertyLock.Unlock()
Expand Down
4 changes: 4 additions & 0 deletions theme/themedtestapp.go
Expand Up @@ -64,6 +64,10 @@ func (t *themedApp) Lifecycle() fyne.Lifecycle {
return nil
}

func (t *themedApp) Metadata() fyne.AppMetadata {
return fyne.AppMetadata{}
}

func (t *themedApp) PrimaryColor() string {
return ColorBlue
}
Expand Down