Skip to content

feature plugin dependency management #33

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

Merged
merged 10 commits into from
Oct 3, 2019
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions assets/app/main_desktop.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:flutter/foundation.dart'
show debugDefaultTargetPlatformOverride;
import 'package:flutter/material.dart';

import './main.dart';

void main() {
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
runApp(MyApp());
}
17 changes: 17 additions & 0 deletions assets/plugin/README.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# {{.pluginName}}

This Go package implements the host-side of the Flutter [{{.pluginName}}](https://{{.urlVSCRepo}}) plugin.

## Usage

Import as:

```go
import {{.pluginName}} "{{.urlVSCRepo}}/go"
```

Then add the following option to your go-flutter [application options](https://github.com/go-flutter-desktop/go-flutter/wiki/Plugin-info):

```go
flutter.AddPlugin(&{{.pluginName}}.{{.structName}}{}),
```
13 changes: 13 additions & 0 deletions assets/plugin/import.go.tmpl.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

// DO NOT EDIT, this file is generated by hover at compile-time for the {{.pluginName}} plugin.

import (
flutter "github.com/go-flutter-desktop/go-flutter"
{{.pluginName}} "{{.urlVSCRepo}}/go"
)

func init() {
// Only the init function can be tweaked by plugin maker.
options = append(options, flutter.AddPlugin(&{{.pluginName}}.{{.structName}}{}))
}
24 changes: 24 additions & 0 deletions assets/plugin/plugin.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package {{.pluginName}}

import (
flutter "github.com/go-flutter-desktop/go-flutter"
"github.com/go-flutter-desktop/go-flutter/plugin"
)

const channelName = "{{.pluginName}}"

// {{.structName}} implements flutter.Plugin and handles method.
type {{.structName}} struct{}

var _ flutter.Plugin = &{{.structName}}{} // compile-time type check

// InitPlugin initializes the plugin.
func (p *{{.structName}}) InitPlugin(messenger plugin.BinaryMessenger) error {
channel := plugin.NewMethodChannel(messenger, channelName, plugin.StandardMethodCodec{})
channel.HandleFunc("getPlatformVersion", p.handlePlatformVersion)
return nil
}

func (p *{{.structName}}) handlePlatformVersion(arguments interface{}) (reply interface{}, err error) {
return "go-flutter " + flutter.PlatformVersion, nil
}
45 changes: 45 additions & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"

"github.com/go-flutter-desktop/hover/internal/enginecache"
"github.com/go-flutter-desktop/hover/internal/fileutils"
"github.com/go-flutter-desktop/hover/internal/log"
"github.com/go-flutter-desktop/hover/internal/versioncheck"
"github.com/hashicorp/go-version"
Expand Down Expand Up @@ -118,6 +119,31 @@ var buildWindowsCmd = &cobra.Command{
},
}

// checkForMainDesktop checks and adds the lib/main_desktop.dart dart entry
// point if needed
func checkForMainDesktop() {
if buildTarget != "lib/main_desktop.dart" {
return
}
_, err := os.Stat("lib/main_desktop.dart")
if os.IsNotExist(err) {
log.Warnf("Target file \"lib/main_desktop.dart\" not found.")
log.Warnf("Let hover add the \"lib/main_desktop.dart\" file? ")
if askForConfirmation() {
fileutils.CopyAsset("app/main_desktop.dart", filepath.Join("lib", "main_desktop.dart"), assetsBox)
log.Infof("Target file \"lib/main_desktop.dart\" has been created.")
log.Infof(" Depending on your project, you might want to tweak it.")
return
}
log.Printf("You can define a custom traget by using the %s flag.", log.Au().Magenta("--target"))
os.Exit(1)
}
if err != nil {
log.Errorf("Failed to stat lib/main_desktop.dart: %v\n", err)
os.Exit(1)
}
}

func outputDirectoryPath(targetOS string) string {
outputDirectoryPath, err := filepath.Abs(filepath.Join(buildPath, "build", "outputs", targetOS))
if err != nil {
Expand Down Expand Up @@ -253,6 +279,7 @@ func dockerBuild(projectName string, targetOS string, vmArguments []string) {
}

func build(projectName string, targetOS string, vmArguments []string) {
checkForMainDesktop()
crossCompile = targetOS != runtime.GOOS
buildDocker = crossCompile || buildDocker

Expand Down Expand Up @@ -301,6 +328,14 @@ func build(projectName string, targetOS string, vmArguments []string) {
trackWidgetCreation = "--track-widget-creation"
}

// must be run before `flutter build bundle`
// because `build bundle` will update the file timestamp
runPluginGet, err := shouldRunPluginGet()
if err != nil {
log.Errorf("Failed to check if plugin get should be run: %v.\n", err)
os.Exit(1)
}

cmdFlutterBuild := exec.Command(flutterBin, "build", "bundle",
"--asset-dir", filepath.Join(outputDirectoryPath(targetOS), "flutter_assets"),
"--target", buildTarget,
Expand All @@ -318,6 +353,16 @@ func build(projectName string, targetOS string, vmArguments []string) {
}
}

if runPluginGet {
log.Printf("listing available plugins:")
if hoverPluginGet(true) {
log.Infof(fmt.Sprintf("run `%s`? ", log.Au().Magenta("hover plugins get")))
if askForConfirmation() {
hoverPluginGet(false)
}
}
}

var engineFile string
switch targetOS {
case "darwin":
Expand Down
175 changes: 146 additions & 29 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,31 @@ package cmd
import (
"bufio"
"encoding/xml"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"

"github.com/go-flutter-desktop/hover/internal/log"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
)

var (
goBin string
flutterBin string
dockerBin string
gitBin string
)

// initBinaries is used to ensure go and flutter exec are found in the
// user's path
func initBinaries() {
var err error
goAvailable := false
Expand All @@ -44,58 +53,78 @@ func initBinaries() {
log.Errorf("Failed to lookup 'flutter' executable. Please install flutter.\nhttps://flutter.dev/docs/get-started/install")
os.Exit(1)
}
gitBin, err = exec.LookPath("git")
if err != nil {
log.Warnf("Failed to lookup 'git' executable.")
}
}

// PubSpec basic model pubspec
// PubSpec contains the parsed contents of pubspec.yaml
type PubSpec struct {
Name string
Description string
Version string
Author string
Dependencies map[string]interface{}
Flutter map[string]interface{}
}

var pubspec = PubSpec{}

// getPubSpec returns the working directory pubspec.yaml as a PubSpec
func getPubSpec() PubSpec {
{
if pubspec.Name == "" {
file, err := os.Open("pubspec.yaml")
if err != nil {
if os.IsNotExist(err) {
log.Errorf("Error: No pubspec.yaml file found.")
goto Fail
}
log.Errorf("Failed to open pubspec.yaml: %v", err)
os.Exit(1)
}
defer file.Close()

err = yaml.NewDecoder(file).Decode(&pubspec)
if err != nil {
log.Errorf("Failed to decode pubspec.yaml: %v", err)
goto Fail
}
if _, exists := pubspec.Dependencies["flutter"]; !exists {
log.Errorf("Missing 'flutter' in pubspec.yaml dependencies list.")
goto Fail
}
if pubspec.Name == "" {
pub, err := readPubSpecFile("pubspec.yaml")
if err != nil {
log.Errorf("%v", err)
log.Errorf("This command should be run from the root of your Flutter project.")
os.Exit(1)
}
pubspec = *pub
}
return pubspec
}

return pubspec
// readPubSpecFile reads a .yaml file at a path and return a correspond
// PubSpec struct
func readPubSpecFile(pubSpecPath string) (*PubSpec, error) {
file, err := os.Open(pubSpecPath)
if err != nil {
if os.IsNotExist(err) {
return nil, errors.Wrap(err, "Error: No pubspec.yaml file found")
}
return nil, errors.Wrap(err, "Failed to open pubspec.yaml")
}
defer file.Close()

Fail:
log.Errorf("This command should be run from the root of your Flutter project.")
os.Exit(1)
return PubSpec{}
var pub PubSpec
err = yaml.NewDecoder(file).Decode(&pub)
if err != nil {
return nil, errors.Wrap(err, "Failed to decode pubspec.yaml")
}
// avoid checking for the flutter dependencies for out of ws directories
if pubSpecPath != "pubspec.yaml" {
return &pub, nil
}
if _, exists := pub.Dependencies["flutter"]; !exists {
return nil, errors.New(fmt.Sprintf("Missing `flutter` in %s dependencies list", pubSpecPath))
}
return &pub, nil
}

// assertInFlutterProject asserts this command is executed in a flutter project
func assertInFlutterProject() {
getPubSpec()
}

// assertInFlutterPluginProject asserts this command is executed in a flutter plugin project
func assertInFlutterPluginProject() {
if _, ok := getPubSpec().Flutter["plugin"]; !ok {
log.Errorf("The directory doesn't appear to contain a plugin package.\nTo create a new plugin, first run `%s`, then run `%s`.", log.Au().Magenta("flutter create --template=plugin"), log.Au().Magenta("hover init-plugin"))
os.Exit(1)
}
}

func assertHoverInitialized() {
_, err := os.Stat(buildPath)
if os.IsNotExist(err) {
Expand All @@ -111,6 +140,7 @@ func assertHoverInitialized() {
}
}

// hoverMigration migrates from old hover buildPath directory to the new one ("desktop" -> "go")
func hoverMigration() bool {
oldBuildPath := "desktop"
file, err := os.Open(filepath.Join(oldBuildPath, "go.mod"))
Expand Down Expand Up @@ -138,7 +168,7 @@ func hoverMigration() bool {

// askForConfirmation asks the user for confirmation.
func askForConfirmation() bool {
log.Printf("[y/N]: ")
fmt.Print(log.Au().Bold(log.Au().Cyan("hover: ")).String() + "[y/N]? ")
in := bufio.NewReader(os.Stdin)
s, err := in.ReadString('\n')
if err != nil {
Expand Down Expand Up @@ -197,3 +227,90 @@ func androidOrganizationName() string {
}
return orgName
}

var camelcaseRegex = regexp.MustCompile("(^[A-Za-z])|_([A-Za-z])")

// toCamelCase take a snake_case string and converts it to camelcase
func toCamelCase(str string) string {
return camelcaseRegex.ReplaceAllStringFunc(str, func(s string) string {
return strings.ToUpper(strings.Replace(s, "_", "", -1))
})
}

// initializeGoModule uses the golang binary to initialize the go module
func initializeGoModule(projectPath string) {
wd, err := os.Getwd()
if err != nil {
log.Errorf("Failed to get working dir: %v\n", err)
os.Exit(1)
}

cmdGoModInit := exec.Command(goBin, "mod", "init", projectPath+"/"+buildPath)
cmdGoModInit.Dir = filepath.Join(wd, buildPath)
cmdGoModInit.Env = append(os.Environ(),
"GO111MODULE=on",
)
cmdGoModInit.Stderr = os.Stderr
cmdGoModInit.Stdout = os.Stdout
err = cmdGoModInit.Run()
if err != nil {
log.Errorf("Go mod init failed: %v\n", err)
os.Exit(1)
}

cmdGoModTidy := exec.Command(goBin, "mod", "tidy")
cmdGoModTidy.Dir = filepath.Join(wd, buildPath)
log.Infof("You can add the '%s' directory to git.", cmdGoModTidy.Dir)
cmdGoModTidy.Env = append(os.Environ(),
"GO111MODULE=on",
)
cmdGoModTidy.Stderr = os.Stderr
cmdGoModTidy.Stdout = os.Stdout
err = cmdGoModTidy.Run()
if err != nil {
log.Errorf("Go mod tidy failed: %v\n", err)
os.Exit(1)
}
}

// findPubcachePath returns the absolute path for the pub-cache or an error.
func findPubcachePath() (string, error) {
var path string
switch runtime.GOOS {
case "darwin", "linux":
home, err := homedir.Dir()
if err != nil {
return "", errors.Wrap(err, "failed to resolve user home dir")
}
path = filepath.Join(home, ".pub-cache")
case "windows":
path = filepath.Join(os.Getenv("APPDATA"), "Pub", "Cache")
}
return path, nil
}

// shouldRunPluginGet checks if the pubspec.yaml file is older than the
// .packages file, if it is the case, prompt the user for a hover plugin get.
func shouldRunPluginGet() (bool, error) {
file1Info, err := os.Stat("pubspec.yaml")
if err != nil {
return false, err
}

file2Info, err := os.Stat(".packages")
if err != nil {
if os.IsNotExist(err) {
return true, nil
}
return false, err
}
modTime1 := file1Info.ModTime()
modTime2 := file2Info.ModTime()

diff := modTime1.Sub(modTime2)

if diff > (time.Duration(0) * time.Second) {
return true, nil
}
return false, nil
}
Loading