From f447c9f06b00a680be3d369a054c83ff2b7a34cd Mon Sep 17 00:00:00 2001 From: jld3103 Date: Sun, 29 Sep 2019 18:31:54 +0200 Subject: [PATCH] Move packaging to containers for cross-packaging --- cmd/build.go | 139 +++++------- cmd/common.go | 68 +----- cmd/init.go | 25 ++- cmd/packaging.go | 432 +----------------------------------- cmd/packaging/linux-deb.go | 166 ++++++++++++++ cmd/packaging/linux-snap.go | 132 +++++++++++ cmd/packaging/linux.go | 42 ++++ cmd/packaging/packaging.go | 165 ++++++++++++++ cmd/run.go | 12 +- cmd/upgrade.go | 14 +- internal/build/binaries.go | 7 + internal/build/build.go | 47 ++++ internal/pubspec/pubspec.go | 53 +++++ 13 files changed, 710 insertions(+), 592 deletions(-) create mode 100644 cmd/packaging/linux-deb.go create mode 100644 cmd/packaging/linux-snap.go create mode 100644 cmd/packaging/linux.go create mode 100644 cmd/packaging/packaging.go create mode 100644 internal/build/binaries.go create mode 100644 internal/build/build.go create mode 100644 internal/pubspec/pubspec.go diff --git a/cmd/build.go b/cmd/build.go index 781325d8..c93027dc 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -10,12 +10,16 @@ import ( "runtime" "strings" - "github.com/go-flutter-desktop/hover/internal/enginecache" - "github.com/go-flutter-desktop/hover/internal/log" - "github.com/go-flutter-desktop/hover/internal/versioncheck" "github.com/hashicorp/go-version" "github.com/otiai10/copy" "github.com/spf13/cobra" + + "github.com/go-flutter-desktop/hover/internal/enginecache" + "github.com/go-flutter-desktop/hover/internal/log" + "github.com/go-flutter-desktop/hover/internal/versioncheck" + "github.com/go-flutter-desktop/hover/internal/build" + "github.com/go-flutter-desktop/hover/internal/pubspec" + "github.com/go-flutter-desktop/hover/cmd/packaging" ) var dotSlash = string([]byte{'.', filepath.Separator}) @@ -31,8 +35,6 @@ var ( buildDocker bool ) -const buildPath = "go" - const mingwGccBinName = "x86_64-w64-mingw32-gcc" const clangBinName = "o32-clang" @@ -63,10 +65,9 @@ var buildLinuxCmd = &cobra.Command{ Use: "linux", Short: "Build a desktop release for linux", Run: func(cmd *cobra.Command, args []string) { - projectName := getPubSpec().Name assertHoverInitialized() - build(projectName, "linux", nil) + buildNormal("linux", nil) }, } @@ -74,12 +75,15 @@ var buildLinuxSnapCmd = &cobra.Command{ Use: "linux-snap", Short: "Build a desktop release for linux and package it for snap", Run: func(cmd *cobra.Command, args []string) { - projectName := getPubSpec().Name assertHoverInitialized() - assertPackagingFormatInitialized("linux-snap") + packaging.AssertPackagingFormatInitialized("linux-snap") - build(projectName, "linux", nil) - buildLinuxSnap(projectName) + if !packaging.DockerInstalled() { + os.Exit(1) + } + + buildNormal("linux", nil) + packaging.BuildLinuxSnap() }, } @@ -87,12 +91,15 @@ var buildLinuxDebCmd = &cobra.Command{ Use: "linux-deb", Short: "Build a desktop release for linux and package it for deb", Run: func(cmd *cobra.Command, args []string) { - projectName := getPubSpec().Name assertHoverInitialized() - assertPackagingFormatInitialized("linux-deb") + packaging.AssertPackagingFormatInitialized("linux-deb") + + if !packaging.DockerInstalled() { + os.Exit(1) + } - build(projectName, "linux", nil) - buildLinuxDeb(projectName) + buildNormal("linux", nil) + packaging.BuildLinuxDeb() }, } @@ -100,10 +107,9 @@ var buildDarwinCmd = &cobra.Command{ Use: "darwin", Short: "Build a desktop release for darwin", Run: func(cmd *cobra.Command, args []string) { - projectName := getPubSpec().Name assertHoverInitialized() - build(projectName, "darwin", nil) + buildNormal("darwin", nil) }, } @@ -111,52 +117,14 @@ var buildWindowsCmd = &cobra.Command{ Use: "windows", Short: "Build a desktop release for windows", Run: func(cmd *cobra.Command, args []string) { - projectName := getPubSpec().Name assertHoverInitialized() - build(projectName, "windows", nil) + buildNormal("windows", nil) }, } -func outputDirectoryPath(targetOS string) string { - outputDirectoryPath, err := filepath.Abs(filepath.Join(buildPath, "build", "outputs", targetOS)) - if err != nil { - log.Errorf("Failed to resolve absolute path for output directory: %v", err) - os.Exit(1) - } - if _, err := os.Stat(outputDirectoryPath); os.IsNotExist(err) { - err = os.MkdirAll(outputDirectoryPath, 0775) - if err != nil { - log.Errorf("Failed to create output directory %s: %v", outputDirectoryPath, err) - os.Exit(1) - } - } - return outputDirectoryPath -} - -func outputBinaryName(projectName string, targetOS string) string { - var outputBinaryName = projectName - switch targetOS { - case "darwin": - // no special filename - case "linux": - // no special filename - case "windows": - outputBinaryName += ".exe" - default: - log.Errorf("Target platform %s is not supported.", targetOS) - os.Exit(1) - } - return outputBinaryName -} - -func outputBinaryPath(projectName string, targetOS string) string { - outputBinaryPath := filepath.Join(outputDirectoryPath(targetOS), outputBinaryName(projectName, targetOS)) - return outputBinaryPath -} - -func dockerBuild(projectName string, targetOS string, vmArguments []string) { - crossCompilingDir, err := filepath.Abs(filepath.Join(buildPath, "cross-compiling")) +func buildInDocker(targetOS string, vmArguments []string) { + crossCompilingDir, err := filepath.Abs(filepath.Join(build.BuildPath, "cross-compiling")) err = os.MkdirAll(crossCompilingDir, 0755) if err != nil { log.Errorf("Cannot create the cross-compiling directory: %v", err) @@ -191,7 +159,7 @@ func dockerBuild(projectName string, targetOS string, vmArguments []string) { } dockerFileContent := []string{ "FROM dockercore/golang-cross", - "RUN apt-get install libgl1-mesa-dev xorg-dev -y", + "RUN apt-get update && apt-get install libgl1-mesa-dev xorg-dev -y", } for _, line := range dockerFileContent { @@ -205,9 +173,10 @@ func dockerBuild(projectName string, targetOS string, vmArguments []string) { log.Errorf("Could not close Dockerfile: %v", err) os.Exit(1) } - log.Infof("A Dockerfile for cross-compiling for %s has been created at %s. You can add it to git.", targetOS, filepath.Join(buildPath, "cross-compiling", targetOS)) + log.Infof("A Dockerfile for cross-compiling for %s has been created at %s. You can add it to git.", targetOS, filepath.Join(build.BuildPath, "cross-compiling", targetOS)) } - dockerBuildCmd := exec.Command(dockerBin, "build", "-t", "hover-build-cc", ".") + dockerBuildCmd := exec.Command(build.DockerBin, "build", "-t", "hover-build-cc", ".") + dockerBuildCmd.Stdout = os.Stdout dockerBuildCmd.Stderr = os.Stderr dockerBuildCmd.Dir = crossCompilingDir err = dockerBuildCmd.Run() @@ -237,10 +206,10 @@ func dockerBuild(projectName string, targetOS string, vmArguments []string) { args = append(args, "hover-build-cc") chownStr := "" if runtime.GOOS != "windows" { - chownStr = fmt.Sprintf(" && chown %s:%s ./ -R", u.Uid, u.Gid) + chownStr = fmt.Sprintf(" && chown %s:%s build/ -R", u.Uid, u.Gid) } - args = append(args, "bash", "-c", fmt.Sprintf("%s%s", strings.Join(buildCommand(targetOS, vmArguments, "build/outputs/"+targetOS+"/"+outputBinaryName(projectName, targetOS)), " "), chownStr)) - dockerRunCmd := exec.Command(dockerBin, args...) + args = append(args, "bash", "-c", fmt.Sprintf("%s%s", strings.Join(buildCommand(targetOS, vmArguments, "build/outputs/"+targetOS+"/"+build.OutputBinaryName(pubspec.GetPubSpec().Name, targetOS)), " "), chownStr)) + dockerRunCmd := exec.Command(build.DockerBin, args...) dockerRunCmd.Stderr = os.Stderr dockerRunCmd.Stdout = os.Stdout dockerRunCmd.Dir = crossCompilingDir @@ -252,7 +221,7 @@ func dockerBuild(projectName string, targetOS string, vmArguments []string) { log.Infof("Successfully cross-compiled for " + targetOS) } -func build(projectName string, targetOS string, vmArguments []string) { +func buildNormal(targetOS string, vmArguments []string) { crossCompile = targetOS != runtime.GOOS buildDocker = crossCompile || buildDocker @@ -263,21 +232,21 @@ func build(projectName string, targetOS string, vmArguments []string) { } if !buildOmitFlutterBundle && !buildOmitEmbedder { - err := os.RemoveAll(outputDirectoryPath(targetOS)) + err := os.RemoveAll(build.OutputDirectoryPath(targetOS)) log.Printf("Cleaning the build directory") if err != nil { - log.Errorf("Failed to clean output directory %s: %v", outputDirectoryPath(targetOS), err) + log.Errorf("Failed to clean output directory %s: %v", build.OutputDirectoryPath(targetOS), err) os.Exit(1) } } - err := os.MkdirAll(outputDirectoryPath(targetOS), 0775) + err := os.MkdirAll(build.OutputDirectoryPath(targetOS), 0775) if err != nil { - log.Errorf("Failed to create output directory %s: %v", outputDirectoryPath(targetOS), err) + log.Errorf("Failed to create output directory %s: %v", build.OutputDirectoryPath(targetOS), err) os.Exit(1) } - cmdCheckFlutter := exec.Command(flutterBin, "--version") + cmdCheckFlutter := exec.Command(build.FlutterBin, "--version") cmdCheckFlutterOut, err := cmdCheckFlutter.Output() if err != nil { log.Warnf("Failed to check your flutter channel: %v", err) @@ -301,8 +270,8 @@ func build(projectName string, targetOS string, vmArguments []string) { trackWidgetCreation = "--track-widget-creation" } - cmdFlutterBuild := exec.Command(flutterBin, "build", "bundle", - "--asset-dir", filepath.Join(outputDirectoryPath(targetOS), "flutter_assets"), + cmdFlutterBuild := exec.Command(build.FlutterBin, "build", "bundle", + "--asset-dir", filepath.Join(build.OutputDirectoryPath(targetOS), "flutter_assets"), "--target", buildTarget, trackWidgetCreation, ) @@ -328,7 +297,7 @@ func build(projectName string, targetOS string, vmArguments []string) { engineFile = "flutter_engine.dll" } - outputEngineFile := filepath.Join(outputDirectoryPath(targetOS), engineFile) + outputEngineFile := filepath.Join(build.OutputDirectoryPath(targetOS), engineFile) err = copy.Copy( filepath.Join(engineCachePath, engineFile), outputEngineFile, @@ -347,7 +316,7 @@ func build(projectName string, targetOS string, vmArguments []string) { err = copy.Copy( filepath.Join(engineCachePath, "artifacts", "icudtl.dat"), - filepath.Join(outputDirectoryPath(targetOS), "icudtl.dat"), + filepath.Join(build.OutputDirectoryPath(targetOS), "icudtl.dat"), ) if err != nil { log.Errorf("Failed to copy icudtl.dat: %v", err) @@ -355,11 +324,11 @@ func build(projectName string, targetOS string, vmArguments []string) { } err = copy.Copy( - filepath.Join(buildPath, "assets"), - filepath.Join(outputDirectoryPath(targetOS), "assets"), + filepath.Join(build.BuildPath, "assets"), + filepath.Join(build.OutputDirectoryPath(targetOS), "assets"), ) if err != nil { - log.Errorf("Failed to copy %s/assets: %v", buildPath, err) + log.Errorf("Failed to copy %s/assets: %v", build.BuildPath, err) os.Exit(1) } @@ -375,7 +344,7 @@ func build(projectName string, targetOS string, vmArguments []string) { } if buildBranch == "" { - currentTag, err := versioncheck.CurrentGoFlutterTag(filepath.Join(wd, buildPath)) + currentTag, err := versioncheck.CurrentGoFlutterTag(filepath.Join(wd, build.BuildPath)) if err != nil { log.Errorf("%v", err) os.Exit(1) @@ -399,7 +368,7 @@ func build(projectName string, targetOS string, vmArguments []string) { } else { // when the buildBranch is empty and the currentTag is a release. // Check if the 'go-flutter' needs updates. - versioncheck.CheckForGoFlutterUpdate(filepath.Join(wd, buildPath), currentTag) + versioncheck.CheckForGoFlutterUpdate(filepath.Join(wd, build.BuildPath), currentTag) } } else { @@ -420,13 +389,13 @@ func build(projectName string, targetOS string, vmArguments []string) { if crossCompile { log.Infof("Because %s is not able to compile for %s out of the box, a cross-compiling container is used", runtime.GOOS, targetOS) } - dockerBuild(projectName, targetOS, vmArguments) + buildInDocker(targetOS, vmArguments) return } - buildCommandString := buildCommand(targetOS, vmArguments, outputBinaryPath(projectName, targetOS)) + buildCommandString := buildCommand(targetOS, vmArguments, build.OutputBinaryPath(pubspec.GetPubSpec().Name, targetOS)) cmdGoBuild := exec.Command(buildCommandString[0], buildCommandString[1:]...) - cmdGoBuild.Dir = filepath.Join(wd, buildPath) + cmdGoBuild.Dir = filepath.Join(wd, build.BuildPath) cmdGoBuild.Env = append(os.Environ(), buildEnv(targetOS, engineCachePath)..., ) @@ -482,7 +451,7 @@ func buildEnv(targetOS string, engineCachePath string) []string { } func buildCommand(targetOS string, vmArguments []string, outputBinaryPath string) []string { - currentTag, err := versioncheck.CurrentGoFlutterTag(buildPath) + currentTag, err := versioncheck.CurrentGoFlutterTag(build.BuildPath) if err != nil { log.Errorf("%v", err) os.Exit(1) @@ -506,9 +475,9 @@ func buildCommand(targetOS string, vmArguments []string, outputBinaryPath string " -X github.com/go-flutter-desktop/go-flutter.PlatformVersion=%s "+ " -X github.com/go-flutter-desktop/go-flutter.ProjectName=%s "+ " -X github.com/go-flutter-desktop/go-flutter.ProjectOrganizationName=%s", - getPubSpec().Version, + pubspec.GetPubSpec().Version, currentTag, - getPubSpec().Name, + pubspec.GetPubSpec().Name, androidOrganizationName())) outputCommand := []string{ diff --git a/cmd/common.go b/cmd/common.go index b36e1b1c..9738baad 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -9,25 +9,20 @@ import ( "path/filepath" "strings" + "github.com/go-flutter-desktop/hover/internal/pubspec" + "github.com/go-flutter-desktop/hover/internal/build" "github.com/go-flutter-desktop/hover/internal/log" - "gopkg.in/yaml.v2" -) - -var ( - goBin string - flutterBin string - dockerBin string ) func initBinaries() { var err error goAvailable := false dockerAvailable := false - goBin, err = exec.LookPath("go") + build.GoBin, err = exec.LookPath("go") if err == nil { goAvailable = true } - dockerBin, err = exec.LookPath("docker") + build.DockerBin, err = exec.LookPath("docker") if err == nil { dockerAvailable = true } @@ -39,70 +34,25 @@ func initBinaries() { log.Errorf("Failed to lookup `go` executable. Please install go or add '--docker' to force running in Docker container.\nhttps://golang.org/doc/install") os.Exit(1) } - flutterBin, err = exec.LookPath("flutter") + build.FlutterBin, err = exec.LookPath("flutter") if err != nil { log.Errorf("Failed to lookup 'flutter' executable. Please install flutter.\nhttps://flutter.dev/docs/get-started/install") os.Exit(1) } } -// PubSpec basic model pubspec -type PubSpec struct { - Name string - Description string - Version string - Author string - Dependencies map[string]interface{} -} - -var pubspec = 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 - } - } - - return pubspec - } - -Fail: - log.Errorf("This command should be run from the root of your Flutter project.") - os.Exit(1) - return PubSpec{} -} - // assertInFlutterProject asserts this command is executed in a flutter project func assertInFlutterProject() { - getPubSpec() + pubspec.GetPubSpec() } func assertHoverInitialized() { - _, err := os.Stat(buildPath) + _, err := os.Stat(build.BuildPath) if os.IsNotExist(err) { if hoverMigration() { return } - log.Errorf("Directory '%s' is missing. Please init go-flutter first: %s", buildPath, log.Au().Magenta("hover init")) + log.Errorf("Directory '%s' is missing. Please init go-flutter first: %s", build.BuildPath, log.Au().Magenta("hover init")) os.Exit(1) } if err != nil { @@ -124,7 +74,7 @@ func hoverMigration() bool { log.Warnf(" Let hover do the migration? ") if askForConfirmation() { - err := os.Rename(oldBuildPath, buildPath) + err := os.Rename(oldBuildPath, build.BuildPath) if err != nil { log.Warnf("Migration failed: %v", err) return false diff --git a/cmd/init.go b/cmd/init.go index 64674ba5..ab5381db 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -7,8 +7,11 @@ import ( "os/exec" "path/filepath" - "github.com/go-flutter-desktop/hover/internal/log" "github.com/spf13/cobra" + + "github.com/go-flutter-desktop/hover/internal/build" + "github.com/go-flutter-desktop/hover/internal/log" + "github.com/go-flutter-desktop/hover/internal/pubspec" ) func init() { @@ -29,27 +32,27 @@ var initCmd = &cobra.Command{ var projectPath string if len(args) == 0 || args[0] == "." { - projectPath = getPubSpec().Name + projectPath = pubspec.GetPubSpec().Name } else { projectPath = args[0] } - err := os.Mkdir(buildPath, 0775) + err := os.Mkdir(build.BuildPath, 0775) if err != nil { if os.IsExist(err) { - log.Errorf("A file or directory named '%s' already exists. Cannot continue init.", buildPath) + log.Errorf("A file or directory named '%s' already exists. Cannot continue init.", build.BuildPath) os.Exit(1) } } - desktopCmdPath := filepath.Join(buildPath, "cmd") + desktopCmdPath := filepath.Join(build.BuildPath, "cmd") err = os.Mkdir(desktopCmdPath, 0775) if err != nil { log.Errorf("Failed to create '%s': %v", desktopCmdPath, err) os.Exit(1) } - desktopAssetsPath := filepath.Join(buildPath, "assets") + desktopAssetsPath := filepath.Join(build.BuildPath, "assets") err = os.Mkdir(desktopAssetsPath, 0775) if err != nil { log.Errorf("Failed to create '%s': %v", desktopAssetsPath, err) @@ -59,7 +62,7 @@ var initCmd = &cobra.Command{ copyAsset("app/main.go", filepath.Join(desktopCmdPath, "main.go")) copyAsset("app/options.go", filepath.Join(desktopCmdPath, "options.go")) copyAsset("app/icon.png", filepath.Join(desktopAssetsPath, "icon.png")) - copyAsset("app/gitignore", filepath.Join(buildPath, ".gitignore")) + copyAsset("app/gitignore", filepath.Join(build.BuildPath, ".gitignore")) wd, err := os.Getwd() if err != nil { @@ -67,8 +70,8 @@ var initCmd = &cobra.Command{ os.Exit(1) } - cmdGoModInit := exec.Command(goBin, "mod", "init", projectPath+"/"+buildPath) - cmdGoModInit.Dir = filepath.Join(wd, buildPath) + cmdGoModInit := exec.Command(build.GoBin, "mod", "init", projectPath+"/"+build.BuildPath) + cmdGoModInit.Dir = filepath.Join(wd, build.BuildPath) cmdGoModInit.Env = append(os.Environ(), "GO111MODULE=on", ) @@ -80,8 +83,8 @@ var initCmd = &cobra.Command{ os.Exit(1) } - cmdGoModTidy := exec.Command(goBin, "mod", "tidy") - cmdGoModTidy.Dir = filepath.Join(wd, buildPath) + cmdGoModTidy := exec.Command(build.GoBin, "mod", "tidy") + cmdGoModTidy.Dir = filepath.Join(wd, build.BuildPath) log.Printf(cmdGoModTidy.Dir) cmdGoModTidy.Env = append(os.Environ(), "GO111MODULE=on", diff --git a/cmd/packaging.go b/cmd/packaging.go index a9c1f9b2..4c307d79 100644 --- a/cmd/packaging.go +++ b/cmd/packaging.go @@ -1,21 +1,10 @@ package cmd import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "os/user" - "path/filepath" - "runtime" - "strings" - - "github.com/go-flutter-desktop/hover/internal/log" - "github.com/otiai10/copy" "github.com/spf13/cobra" -) -var packagingPath = filepath.Join(buildPath, "packaging") + "github.com/go-flutter-desktop/hover/cmd/packaging" +) func init() { initPackagingCmd.AddCommand(initLinuxSnapCmd) @@ -32,10 +21,10 @@ var initLinuxSnapCmd = &cobra.Command{ Use: "linux-snap", Short: "Create configuration files for snap packaging", Run: func(cmd *cobra.Command, args []string) { - projectName := getPubSpec().Name assertHoverInitialized() + packaging.DockerInstalled() - initLinuxSnap(projectName) + packaging.InitLinuxSnap() }, } @@ -43,418 +32,9 @@ var initLinuxDebCmd = &cobra.Command{ Use: "linux-deb", Short: "Create configuration files for deb packaging", Run: func(cmd *cobra.Command, args []string) { - projectName := getPubSpec().Name assertHoverInitialized() + packaging.DockerInstalled() - initLinuxDeb(projectName) + packaging.InitLinuxDeb() }, } - -var linuxPackagingDependencies = []string{"libx11-6", "libxrandr2", "libxcursor1", "libxinerama1"} - -func packagingFormatPath(packagingFormat string) string { - directoryPath, err := filepath.Abs(filepath.Join(packagingPath, packagingFormat)) - if err != nil { - log.Errorf("Failed to resolve absolute path for %s directory: %v", packagingFormat, err) - os.Exit(1) - } - return directoryPath -} - -func createPackagingFormatDirectory(packagingFormat string) { - if _, err := os.Stat(packagingFormatPath(packagingFormat)); !os.IsNotExist(err) { - log.Errorf("A file or directory named '%s' already exists. Cannot continue packaging init for %s.", packagingFormat, packagingFormat) - os.Exit(1) - } - err := os.MkdirAll(packagingFormatPath(packagingFormat), 0775) - if err != nil { - log.Errorf("Failed to create %s directory %s: %v", packagingFormat, packagingFormatPath(packagingFormat), err) - os.Exit(1) - } -} - -func assertPackagingFormatInitialized(packagingFormat string) { - if _, err := os.Stat(packagingFormatPath(packagingFormat)); os.IsNotExist(err) { - log.Errorf("%s is not initialized for packaging. Please init packaging for %s first: %s", packagingFormat, packagingFormat, log.Au().Magenta(fmt.Sprintf("hover init-packaging %s", packagingFormat))) - os.Exit(1) - } -} - -func assertCorrectOS(packagingFormat string) { - if runtime.GOOS != strings.Split(packagingFormat, "-")[0] { - log.Errorf("%s only works on %s", packagingFormat, strings.Split(packagingFormat, "-")[0]) - os.Exit(1) - } -} - -func removeDashesAndUnderscores(projectName string) string { - return strings.ReplaceAll(strings.ReplaceAll(projectName, "-", ""), "_", "") -} - -func printInitFinished(packagingFormat string) { - log.Infof("go/packaging/%s has been created. You can modify the configuration files and add them to git.", packagingFormat) - log.Infof("You now can package the %s: %s", strings.Split(packagingFormat, "-")[1], fmt.Sprintf(log.Au().Magenta("hover build %s").String(), packagingFormat)) -} - -func getTemporaryBuildDirectory(projectName string, packagingFormat string) string { - tmpPath, err := ioutil.TempDir("", "hover-build-"+projectName+"-"+packagingFormat) - if err != nil { - log.Errorf("Couldn't get temporary build directory: %v", err) - os.Exit(1) - } - return tmpPath -} - -func initLinuxSnap(projectName string) { - packagingFormat := "linux-snap" - assertCorrectOS(packagingFormat) - createPackagingFormatDirectory(packagingFormat) - snapDirectoryPath := packagingFormatPath(packagingFormat) - - snapLocalDirectoryPath, err := filepath.Abs(filepath.Join(snapDirectoryPath, "snap", "local")) - if err != nil { - log.Errorf("Failed to resolve absolute path for snap local directory: %v", err) - os.Exit(1) - } - err = os.MkdirAll(snapLocalDirectoryPath, 0775) - if err != nil { - log.Errorf("Failed to create snap local directory %s: %v", snapDirectoryPath, err) - os.Exit(1) - } - - snapcraftFilePath, err := filepath.Abs(filepath.Join(snapDirectoryPath, "snap", "snapcraft.yaml")) - if err != nil { - log.Errorf("Failed to resolve absolute path for snapcraft.yaml file %s: %v", snapcraftFilePath, err) - os.Exit(1) - } - - snapcraftFile, err := os.Create(snapcraftFilePath) - if err != nil { - log.Errorf("Failed to create snapcraft.yaml file %s: %v", snapcraftFilePath, err) - os.Exit(1) - } - snapcraftFileContent := []string{ - "name: " + removeDashesAndUnderscores(projectName), - "base: core18", - "version: '" + getPubSpec().Version + "'", - "summary: " + getPubSpec().Description, - "description: |", - " " + getPubSpec().Description, - "confinement: devmode", - "grade: devel", - "apps:", - " " + removeDashesAndUnderscores(projectName) + ":", - " command: " + projectName, - " desktop: local/" + projectName + ".desktop", - "parts:", - " desktop:", - " plugin: dump", - " source: snap", - " assets:", - " plugin: dump", - " source: assets", - " app:", - " plugin: dump", - " source: build", - " stage-packages:", - } - for _, dependency := range linuxPackagingDependencies { - snapcraftFileContent = append(snapcraftFileContent, " - "+dependency) - } - - for _, line := range snapcraftFileContent { - if _, err := snapcraftFile.WriteString(line + "\n"); err != nil { - log.Errorf("Could not write snapcraft.yaml: %v", err) - os.Exit(1) - } - } - err = snapcraftFile.Close() - if err != nil { - log.Errorf("Could not close snapcraft.yaml: %v", err) - os.Exit(1) - } - - desktopFilePath, err := filepath.Abs(filepath.Join(snapLocalDirectoryPath, projectName+".desktop")) - if err != nil { - log.Errorf("Failed to resolve absolute path for desktop file %s: %v", desktopFilePath, err) - os.Exit(1) - } - desktopFile, err := os.Create(desktopFilePath) - if err != nil { - log.Errorf("Failed to create desktop file %s: %v", desktopFilePath, err) - os.Exit(1) - } - desktopFileContent := []string{ - "[Desktop Entry]", - "Encoding=UTF-8", - "Version=" + getPubSpec().Version, - "Type=Application", - "Terminal=false", - "Exec=/" + projectName, - "Name=" + projectName, - "Icon=/icon.png", - } - - for _, line := range desktopFileContent { - if _, err := desktopFile.WriteString(line + "\n"); err != nil { - log.Errorf("Could not write %s.desktop: %v", projectName, err) - os.Exit(1) - } - } - err = desktopFile.Close() - if err != nil { - log.Errorf("Could not close %s.desktop: %v", projectName, err) - os.Exit(1) - } - - printInitFinished(packagingFormat) -} - -func buildLinuxSnap(projectName string) { - packagingFormat := "linux-snap" - assertCorrectOS(packagingFormat) - snapcraftBin, err := exec.LookPath("snapcraft") - if err != nil { - log.Errorf("Failed to lookup 'snapcraft' executable. Please install snapcraft.\nhttps://tutorials.ubuntu.com/tutorial/create-your-first-snap#1") - os.Exit(1) - } - tmpPath := getTemporaryBuildDirectory(projectName, packagingFormat) - log.Infof("Packaging snap in %s", tmpPath) - - err = copy.Copy(filepath.Join(buildPath, "assets"), filepath.Join(tmpPath, "assets")) - if err != nil { - log.Errorf("Could not copy assets folder: %v", err) - os.Exit(1) - } - err = copy.Copy(outputDirectoryPath("linux"), filepath.Join(tmpPath, "build")) - if err != nil { - log.Errorf("Could not copy build folder: %v", err) - os.Exit(1) - } - err = copy.Copy(packagingFormatPath(packagingFormat), filepath.Join(tmpPath)) - if err != nil { - log.Errorf("Could not copy packaging configuration folder: %v", err) - os.Exit(1) - } - - cmdBuildSnap := exec.Command(snapcraftBin) - cmdBuildSnap.Dir = tmpPath - cmdBuildSnap.Stdout = os.Stdout - cmdBuildSnap.Stderr = os.Stderr - cmdBuildSnap.Stdin = os.Stdin - err = cmdBuildSnap.Run() - if err != nil { - log.Errorf("Failed to package snap: %v", err) - os.Exit(1) - } - outputFilePath := filepath.Join(outputDirectoryPath("linux-snap"), removeDashesAndUnderscores(projectName)+"_"+runtime.GOARCH+".snap") - err = os.Rename(filepath.Join(tmpPath, removeDashesAndUnderscores(projectName)+"_"+getPubSpec().Version+"_"+runtime.GOARCH+".snap"), outputFilePath) - if err != nil { - log.Errorf("Could not move snap file: %v", err) - os.Exit(1) - } - err = os.RemoveAll(tmpPath) - if err != nil { - log.Errorf("Could not remove packaging configuration folder: %v", err) - os.Exit(1) - } -} - -func initLinuxDeb(projectName string) { - packagingFormat := "linux-deb" - assertCorrectOS(packagingFormat) - author := getPubSpec().Author - if author == "" { - log.Warnf("Missing author field in pubspec.yaml") - u, err := user.Current() - if err != nil { - log.Errorf("Couldn't get current user: %v", err) - os.Exit(1) - } - author = u.Username - log.Printf("Using this username from system instead: %s", author) - } - createPackagingFormatDirectory(packagingFormat) - debDirectoryPath := packagingFormatPath(packagingFormat) - debDebianDirectoryPath, err := filepath.Abs(filepath.Join(debDirectoryPath, "DEBIAN")) - if err != nil { - log.Errorf("Failed to resolve absolute path for DEBIAN directory: %v", err) - os.Exit(1) - } - err = os.MkdirAll(debDebianDirectoryPath, 0775) - if err != nil { - log.Errorf("Failed to create DEBIAN directory %s: %v", debDebianDirectoryPath, err) - os.Exit(1) - } - - binDirectoryPath, err := filepath.Abs(filepath.Join(debDirectoryPath, "usr", "bin")) - if err != nil { - log.Errorf("Failed to resolve absolute path for bin directory: %v", err) - os.Exit(1) - } - err = os.MkdirAll(binDirectoryPath, 0775) - if err != nil { - log.Errorf("Failed to create bin directory %s: %v", binDirectoryPath, err) - os.Exit(1) - } - applicationsDirectoryPath, err := filepath.Abs(filepath.Join(debDirectoryPath, "usr", "share", "applications")) - if err != nil { - log.Errorf("Failed to resolve absolute path for applications directory: %v", err) - os.Exit(1) - } - err = os.MkdirAll(applicationsDirectoryPath, 0775) - if err != nil { - log.Errorf("Failed to create applications directory %s: %v", applicationsDirectoryPath, err) - os.Exit(1) - } - - controlFilePath, err := filepath.Abs(filepath.Join(debDebianDirectoryPath, "control")) - if err != nil { - log.Errorf("Failed to resolve absolute path for control file %s: %v", controlFilePath, err) - os.Exit(1) - } - - controlFile, err := os.Create(controlFilePath) - if err != nil { - log.Errorf("Failed to create control file %s: %v", controlFilePath, err) - os.Exit(1) - } - controlFileContent := []string{ - "Package: " + removeDashesAndUnderscores(projectName), - "Architecture: " + runtime.GOARCH, - "Maintainer: @" + getPubSpec().Author, - "Priority: optional", - "Version: " + getPubSpec().Version, - "Description: " + getPubSpec().Description, - "Depends: " + strings.Join(linuxPackagingDependencies, ","), - } - - for _, line := range controlFileContent { - if _, err := controlFile.WriteString(line + "\n"); err != nil { - log.Errorf("Could not write control file: %v", err) - os.Exit(1) - } - } - err = controlFile.Close() - if err != nil { - log.Errorf("Could not close control file: %v", err) - os.Exit(1) - } - - binFilePath, err := filepath.Abs(filepath.Join(binDirectoryPath, removeDashesAndUnderscores(projectName))) - if err != nil { - log.Errorf("Failed to resolve absolute path for bin file %s: %v", binFilePath, err) - os.Exit(1) - } - - binFile, err := os.Create(binFilePath) - if err != nil { - log.Errorf("Failed to create bin file %s: %v", controlFilePath, err) - os.Exit(1) - } - binFileContent := []string{ - "#!/bin/sh", - "/usr/lib/" + projectName + "/" + projectName, - } - for _, line := range binFileContent { - if _, err := binFile.WriteString(line + "\n"); err != nil { - log.Errorf("Could not write bin file: %v", err) - os.Exit(1) - } - } - err = binFile.Close() - if err != nil { - log.Errorf("Could not close bin file: %v", err) - os.Exit(1) - } - err = os.Chmod(binFilePath, 0777) - if err != nil { - log.Errorf("Failed to change file permissions for bin file: %v", err) - os.Exit(1) - } - - desktopFilePath, err := filepath.Abs(filepath.Join(applicationsDirectoryPath, projectName+".desktop")) - if err != nil { - log.Errorf("Failed to resolve absolute path for desktop file %s: %v", desktopFilePath, err) - os.Exit(1) - } - desktopFile, err := os.Create(desktopFilePath) - if err != nil { - log.Errorf("Failed to create desktop file %s: %v", desktopFilePath, err) - os.Exit(1) - } - desktopFileContent := []string{ - "[Desktop Entry]", - "Encoding=UTF-8", - "Version=" + getPubSpec().Version, - "Type=Application", - "Terminal=false", - "Exec=/usr/bin/" + projectName, - "Name=" + projectName, - "Icon=/usr/lib/" + projectName + "/assets/icon.png", - } - for _, line := range desktopFileContent { - if _, err := desktopFile.WriteString(line + "\n"); err != nil { - log.Errorf("Could not write %s.desktop file: %v", projectName, err) - os.Exit(1) - } - } - err = desktopFile.Close() - if err != nil { - log.Errorf("Could not close %s.desktop file: %v", projectName, err) - os.Exit(1) - } - - printInitFinished(packagingFormat) -} - -func buildLinuxDeb(projectName string) { - packagingFormat := "linux-deb" - assertCorrectOS(packagingFormat) - dpkgDebBin, err := exec.LookPath("dpkg-deb") - if err != nil { - log.Errorf("Failed to lookup 'dpkg-deb' executable. Please install dpkg-deb.") - os.Exit(1) - } - tmpPath := getTemporaryBuildDirectory(projectName, packagingFormat) - log.Infof("Packaging deb in %s", tmpPath) - - libDirectoryPath, err := filepath.Abs(filepath.Join(tmpPath, "usr", "lib")) - if err != nil { - log.Errorf("Failed to resolve absolute path for bin directory: %v", err) - os.Exit(1) - } - err = copy.Copy(outputDirectoryPath("linux"), filepath.Join(libDirectoryPath, projectName)) - if err != nil { - log.Errorf("Could not copy build folder: %v", err) - os.Exit(1) - } - err = copy.Copy(packagingFormatPath(packagingFormat), filepath.Join(tmpPath)) - if err != nil { - log.Errorf("Could not copy packaging configuration folder: %v", err) - os.Exit(1) - } - outputFileName := removeDashesAndUnderscores(projectName) + "_" + runtime.GOARCH + ".deb" - outputFilePath := filepath.Join(outputDirectoryPath("linux-deb"), outputFileName) - - cmdBuildDeb := exec.Command(dpkgDebBin, "--build", ".", outputFileName) - cmdBuildDeb.Dir = tmpPath - cmdBuildDeb.Stdout = os.Stdout - cmdBuildDeb.Stderr = os.Stderr - cmdBuildDeb.Stdin = os.Stdin - err = cmdBuildDeb.Run() - if err != nil { - log.Errorf("Failed to package deb: %v", err) - os.Exit(1) - } - err = os.Rename(filepath.Join(tmpPath, outputFileName), outputFilePath) - if err != nil { - log.Errorf("Could not move deb file: %v", err) - os.Exit(1) - } - err = os.RemoveAll(tmpPath) - if err != nil { - log.Errorf("Could not remove packaging configuration folder: %v", err) - os.Exit(1) - } -} diff --git a/cmd/packaging/linux-deb.go b/cmd/packaging/linux-deb.go new file mode 100644 index 00000000..7a4ed47b --- /dev/null +++ b/cmd/packaging/linux-deb.go @@ -0,0 +1,166 @@ +package packaging + +import ( + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/otiai10/copy" + + "github.com/go-flutter-desktop/hover/internal/build" + "github.com/go-flutter-desktop/hover/internal/pubspec" + "github.com/go-flutter-desktop/hover/internal/log" +) + +func InitLinuxDeb() { + projectName := pubspec.GetPubSpec().Name + packagingFormat := "linux-deb" + createPackagingFormatDirectory(packagingFormat) + debDirectoryPath := packagingFormatPath(packagingFormat) + debDebianDirectoryPath, err := filepath.Abs(filepath.Join(debDirectoryPath, "DEBIAN")) + if err != nil { + log.Errorf("Failed to resolve absolute path for DEBIAN directory: %v", err) + os.Exit(1) + } + err = os.MkdirAll(debDebianDirectoryPath, 0775) + if err != nil { + log.Errorf("Failed to create DEBIAN directory %s: %v", debDebianDirectoryPath, err) + os.Exit(1) + } + + binDirectoryPath, err := filepath.Abs(filepath.Join(debDirectoryPath, "usr", "bin")) + if err != nil { + log.Errorf("Failed to resolve absolute path for bin directory: %v", err) + os.Exit(1) + } + err = os.MkdirAll(binDirectoryPath, 0775) + if err != nil { + log.Errorf("Failed to create bin directory %s: %v", binDirectoryPath, err) + os.Exit(1) + } + applicationsDirectoryPath, err := filepath.Abs(filepath.Join(debDirectoryPath, "usr", "share", "applications")) + if err != nil { + log.Errorf("Failed to resolve absolute path for applications directory: %v", err) + os.Exit(1) + } + err = os.MkdirAll(applicationsDirectoryPath, 0775) + if err != nil { + log.Errorf("Failed to create applications directory %s: %v", applicationsDirectoryPath, err) + os.Exit(1) + } + + controlFilePath, err := filepath.Abs(filepath.Join(debDebianDirectoryPath, "control")) + if err != nil { + log.Errorf("Failed to resolve absolute path for control file %s: %v", controlFilePath, err) + os.Exit(1) + } + + controlFile, err := os.Create(controlFilePath) + if err != nil { + log.Errorf("Failed to create control file %s: %v", controlFilePath, err) + os.Exit(1) + } + controlFileContent := []string{ + "Package: " + removeDashesAndUnderscores(projectName), + "Architecture: amd64", + "Maintainer: @" + getAuthor(), + "Priority: optional", + "Version: " + pubspec.GetPubSpec().Version, + "Description: " + pubspec.GetPubSpec().Description, + "Depends: " + strings.Join(linuxPackagingDependencies, ","), + } + + for _, line := range controlFileContent { + if _, err := controlFile.WriteString(line + "\n"); err != nil { + log.Errorf("Could not write control file: %v", err) + os.Exit(1) + } + } + err = controlFile.Close() + if err != nil { + log.Errorf("Could not close control file: %v", err) + os.Exit(1) + } + + binFilePath, err := filepath.Abs(filepath.Join(binDirectoryPath, removeDashesAndUnderscores(projectName))) + if err != nil { + log.Errorf("Failed to resolve absolute path for bin file %s: %v", binFilePath, err) + os.Exit(1) + } + + binFile, err := os.Create(binFilePath) + if err != nil { + log.Errorf("Failed to create bin file %s: %v", controlFilePath, err) + os.Exit(1) + } + binFileContent := []string{ + "#!/bin/sh", + "/usr/lib/" + projectName + "/" + projectName, + } + for _, line := range binFileContent { + if _, err := binFile.WriteString(line + "\n"); err != nil { + log.Errorf("Could not write bin file: %v", err) + os.Exit(1) + } + } + err = binFile.Close() + if err != nil { + log.Errorf("Could not close bin file: %v", err) + os.Exit(1) + } + err = os.Chmod(binFilePath, 0777) + if err != nil { + log.Errorf("Failed to change file permissions for bin file: %v", err) + os.Exit(1) + } + + desktopFilePath, err := filepath.Abs(filepath.Join(applicationsDirectoryPath, projectName+".desktop")) + if err != nil { + log.Errorf("Failed to resolve absolute path for desktop file %s: %v", desktopFilePath, err) + os.Exit(1) + } + createLinuxDesktopFile(desktopFilePath, packagingFormat, "/usr/bin/"+projectName, "/usr/lib/"+projectName+"/assets/icon.png") + createDockerfile(packagingFormat) + + printInitFinished(packagingFormat) +} + +func BuildLinuxDeb() { + projectName := pubspec.GetPubSpec().Name + packagingFormat := "linux-deb" + tmpPath := getTemporaryBuildDirectory(projectName, packagingFormat) + log.Infof("Packaging deb in %s", tmpPath) + + libDirectoryPath, err := filepath.Abs(filepath.Join(tmpPath, "usr", "lib")) + if err != nil { + log.Errorf("Failed to resolve absolute path for lib directory: %v", err) + os.Exit(1) + } + err = copy.Copy(build.OutputDirectoryPath("linux"), filepath.Join(libDirectoryPath, projectName)) + if err != nil { + log.Errorf("Could not copy build folder: %v", err) + os.Exit(1) + } + err = copy.Copy(packagingFormatPath(packagingFormat), filepath.Join(tmpPath)) + if err != nil { + log.Errorf("Could not copy packaging configuration folder: %v", err) + os.Exit(1) + } + + outputFileName := removeDashesAndUnderscores(projectName) + "_" + runtime.GOARCH + ".deb" + outputFilePath := filepath.Join(build.OutputDirectoryPath("linux-deb"), outputFileName) + runDockerPackaging(tmpPath, packagingFormat, []string{"dpkg-deb", "--build", ".", outputFileName}) + + err = os.Rename(filepath.Join(tmpPath, outputFileName), outputFilePath) + if err != nil { + log.Errorf("Could not move deb file: %v", err) + os.Exit(1) + } + err = os.RemoveAll(tmpPath) + if err != nil { + log.Errorf("Could not remove temporary build directory: %v", err) + os.Exit(1) + } + printPackagingFinished(packagingFormat) +} diff --git a/cmd/packaging/linux-snap.go b/cmd/packaging/linux-snap.go new file mode 100644 index 00000000..a220dddd --- /dev/null +++ b/cmd/packaging/linux-snap.go @@ -0,0 +1,132 @@ +package packaging + +import ( + "os" + "path/filepath" + "runtime" + + "github.com/otiai10/copy" + + "github.com/go-flutter-desktop/hover/internal/build" + "github.com/go-flutter-desktop/hover/internal/pubspec" + "github.com/go-flutter-desktop/hover/internal/log" +) + +func InitLinuxSnap() { + projectName := pubspec.GetPubSpec().Name + packagingFormat := "linux-snap" + createPackagingFormatDirectory(packagingFormat) + snapDirectoryPath := packagingFormatPath(packagingFormat) + + snapLocalDirectoryPath, err := filepath.Abs(filepath.Join(snapDirectoryPath, "snap", "local")) + if err != nil { + log.Errorf("Failed to resolve absolute path for snap local directory: %v", err) + os.Exit(1) + } + err = os.MkdirAll(snapLocalDirectoryPath, 0775) + if err != nil { + log.Errorf("Failed to create snap local directory %s: %v", snapDirectoryPath, err) + os.Exit(1) + } + + snapcraftFilePath, err := filepath.Abs(filepath.Join(snapDirectoryPath, "snap", "snapcraft.yaml")) + if err != nil { + log.Errorf("Failed to resolve absolute path for snapcraft.yaml file %s: %v", snapcraftFilePath, err) + os.Exit(1) + } + + snapcraftFile, err := os.Create(snapcraftFilePath) + if err != nil { + log.Errorf("Failed to create snapcraft.yaml file %s: %v", snapcraftFilePath, err) + os.Exit(1) + } + snapcraftFileContent := []string{ + "name: " + removeDashesAndUnderscores(projectName), + "base: core18", + "version: '" + pubspec.GetPubSpec().Version + "'", + "summary: " + pubspec.GetPubSpec().Description, + "description: |", + " " + pubspec.GetPubSpec().Description, + "confinement: devmode", + "grade: devel", + "apps:", + " " + removeDashesAndUnderscores(projectName) + ":", + " command: " + projectName, + " desktop: local/" + projectName + ".desktop", + "parts:", + " desktop:", + " plugin: dump", + " source: snap", + " assets:", + " plugin: dump", + " source: assets", + " app:", + " plugin: dump", + " source: build", + " stage-packages:", + } + for _, dependency := range linuxPackagingDependencies { + snapcraftFileContent = append(snapcraftFileContent, " - "+dependency) + } + + for _, line := range snapcraftFileContent { + if _, err := snapcraftFile.WriteString(line + "\n"); err != nil { + log.Errorf("Could not write snapcraft.yaml: %v", err) + os.Exit(1) + } + } + err = snapcraftFile.Close() + if err != nil { + log.Errorf("Could not close snapcraft.yaml: %v", err) + os.Exit(1) + } + + desktopFilePath, err := filepath.Abs(filepath.Join(snapLocalDirectoryPath, projectName+".desktop")) + if err != nil { + log.Errorf("Failed to resolve absolute path for desktop file %s: %v", desktopFilePath, err) + os.Exit(1) + } + createLinuxDesktopFile(desktopFilePath, packagingFormat, "/"+projectName, "/icon.png") + createDockerfile(packagingFormat) + + printInitFinished(packagingFormat) +} + +func BuildLinuxSnap() { + projectName := pubspec.GetPubSpec().Name + packagingFormat := "linux-snap" + tmpPath := getTemporaryBuildDirectory(projectName, packagingFormat) + log.Infof("Packaging snap in %s", tmpPath) + + err := copy.Copy(filepath.Join(build.BuildPath, "assets"), filepath.Join(tmpPath, "assets")) + if err != nil { + log.Errorf("Could not copy assets folder: %v", err) + os.Exit(1) + } + err = copy.Copy(build.OutputDirectoryPath("linux"), filepath.Join(tmpPath, "build")) + if err != nil { + log.Errorf("Could not copy build folder: %v", err) + os.Exit(1) + } + err = copy.Copy(packagingFormatPath(packagingFormat), filepath.Join(tmpPath)) + if err != nil { + log.Errorf("Could not copy packaging configuration folder: %v", err) + os.Exit(1) + } + + outputFileName := removeDashesAndUnderscores(projectName) + "_" + pubspec.GetPubSpec().Version + "_" + runtime.GOARCH + ".snap" + outputFilePath := filepath.Join(build.OutputDirectoryPath("linux-snap"), outputFileName) + runDockerPackaging(tmpPath, packagingFormat, []string{"snapcraft"}) + + err = os.Rename(filepath.Join(tmpPath, outputFileName), outputFilePath) + if err != nil { + log.Errorf("Could not move snap file: %v", err) + os.Exit(1) + } + err = os.RemoveAll(tmpPath) + if err != nil { + log.Errorf("Could not remove temporary build directory: %v", err) + os.Exit(1) + } + printPackagingFinished(packagingFormat) +} diff --git a/cmd/packaging/linux.go b/cmd/packaging/linux.go new file mode 100644 index 00000000..c3a5381e --- /dev/null +++ b/cmd/packaging/linux.go @@ -0,0 +1,42 @@ +package packaging + +import ( + "os" + + "github.com/go-flutter-desktop/hover/internal/log" + "github.com/go-flutter-desktop/hover/internal/pubspec" +) + +var linuxPackagingDependencies = []string{"libx11-6", "libxrandr2", "libxcursor1", "libxinerama1", "libglu1-mesa", "libgcc1", "libstdc++6", "libtinfo5", "zlib1g"} + +func createLinuxDesktopFile(desktopFilePath string, packagingFormat string, exec string, icon string) { + desktopFile, err := os.Create(desktopFilePath) + if err != nil { + log.Errorf("Failed to create %s.desktop %s: %v", pubspec.GetPubSpec().Name, desktopFilePath, err) + os.Exit(1) + } + desktopFileContent := []string{ + "[Desktop Entry]", + "Version=1.0", + "Type=Application", + "Terminal=false", + "Name=" + pubspec.GetPubSpec().Name, + "Icon=" + icon, + "Categories=", + } + if exec != "" { + desktopFileContent = append(desktopFileContent, "Exec="+exec) + } + + for _, line := range desktopFileContent { + if _, err := desktopFile.WriteString(line + "\n"); err != nil { + log.Errorf("Could not write %s.desktop: %v", pubspec.GetPubSpec().Name, err) + os.Exit(1) + } + } + err = desktopFile.Close() + if err != nil { + log.Errorf("Could not close %s.desktop: %v", pubspec.GetPubSpec().Name, err) + os.Exit(1) + } +} diff --git a/cmd/packaging/packaging.go b/cmd/packaging/packaging.go new file mode 100644 index 00000000..84cef80b --- /dev/null +++ b/cmd/packaging/packaging.go @@ -0,0 +1,165 @@ +package packaging + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "os/user" + "path/filepath" + "runtime" + "strings" + + "github.com/go-flutter-desktop/hover/internal/build" + "github.com/go-flutter-desktop/hover/internal/log" + "github.com/go-flutter-desktop/hover/internal/pubspec" +) + +var packagingPath = filepath.Join(build.BuildPath, "packaging") + +func packagingFormatPath(packagingFormat string) string { + directoryPath, err := filepath.Abs(filepath.Join(packagingPath, packagingFormat)) + if err != nil { + log.Errorf("Failed to resolve absolute path for %s directory: %v", packagingFormat, err) + os.Exit(1) + } + return directoryPath +} + +func createPackagingFormatDirectory(packagingFormat string) { + if _, err := os.Stat(packagingFormatPath(packagingFormat)); !os.IsNotExist(err) { + log.Errorf("A file or directory named `%s` already exists. Cannot continue packaging init for %s.", packagingFormat, packagingFormat) + os.Exit(1) + } + err := os.MkdirAll(packagingFormatPath(packagingFormat), 0775) + if err != nil { + log.Errorf("Failed to create %s directory %s: %v", packagingFormat, packagingFormatPath(packagingFormat), err) + os.Exit(1) + } +} + +func AssertPackagingFormatInitialized(packagingFormat string) { + if _, err := os.Stat(packagingFormatPath(packagingFormat)); os.IsNotExist(err) { + log.Errorf("%s is not initialized for packaging. Please run `hover init-packaging %s` first.", packagingFormat, packagingFormat) + os.Exit(1) + } +} + +func removeDashesAndUnderscores(projectName string) string { + return strings.ReplaceAll(strings.ReplaceAll(projectName, "-", ""), "_", "") +} + +func printInitFinished(packagingFormat string) { + log.Infof("go/packaging/%s has been created. You can modify the configuration files and add it to git.", packagingFormat) + log.Infof("You now can package the %s using `hover build %s`", strings.Split(packagingFormat, "-")[0], packagingFormat) +} + +func printPackagingFinished(packagingFormat string) { + log.Infof("Successfully packaged %s", strings.Split(packagingFormat, "-")[1]) +} + +func getTemporaryBuildDirectory(projectName string, packagingFormat string) string { + tmpPath, err := ioutil.TempDir("", "hover-build-"+projectName+"-"+packagingFormat) + if err != nil { + log.Errorf("Couldn't get temporary build directory: %v", err) + os.Exit(1) + } + return tmpPath +} + +func DockerInstalled() bool { + if build.DockerBin == "" { + log.Warnf("To use packaging, Docker needs to be installed.\nhttps://docs.docker.com/install") + } + return build.DockerBin != "" +} + +func getAuthor() string { + author := pubspec.GetPubSpec().Author + if author == "" { + log.Warnf("Missing author field in pubspec.yaml") + u, err := user.Current() + if err != nil { + log.Errorf("Couldn't get current user: %v", err) + os.Exit(1) + } + author = u.Username + log.Printf("Using this username from system instead: %s", author) + } + return author +} + +func createDockerfile(packagingFormat string) { + dockerFilePath, err := filepath.Abs(filepath.Join(packagingFormatPath(packagingFormat), "Dockerfile")) + if err != nil { + log.Errorf("Failed to resolve absolute path for Dockerfile %s: %v", dockerFilePath, err) + os.Exit(1) + } + dockerFile, err := os.Create(dockerFilePath) + if err != nil { + log.Errorf("Failed to create Dockerfile %s: %v", dockerFilePath, err) + os.Exit(1) + } + dockerFileContent := []string{} + if packagingFormat == "linux-snap" { + dockerFileContent = []string{ + "FROM snapcore/snapcraft", + } + } else if packagingFormat == "linux-deb" { + dockerFileContent = []string{ + "FROM ubuntu:bionic", + } + } else { + log.Errorf("Tried to create Dockerfile for unknown packaging format %s", packagingFormat) + os.Exit(1) + } + + for _, line := range dockerFileContent { + if _, err := dockerFile.WriteString(line + "\n"); err != nil { + log.Errorf("Could not write Dockerfile: %v", err) + os.Exit(1) + } + } + err = dockerFile.Close() + if err != nil { + log.Errorf("Could not close Dockerfile: %v", err) + os.Exit(1) + } +} + +func runDockerPackaging(path string, packagingFormat string, command []string) { + dockerBuildCmd := exec.Command(build.DockerBin, "build", "-t", "hover-build-packaging-"+packagingFormat, ".") + dockerBuildCmd.Stdout = os.Stdout + dockerBuildCmd.Stderr = os.Stderr + dockerBuildCmd.Dir = packagingFormatPath(packagingFormat) + err := dockerBuildCmd.Run() + if err != nil { + log.Errorf("Docker build failed: %v", err) + os.Exit(1) + } + u, err := user.Current() + if err != nil { + log.Errorf("Couldn't get current user: %v", err) + os.Exit(1) + } + args := []string{ + "run", + "-w", "/app", + "-v", path + ":/app", + } + args = append(args, "hover-build-packaging-"+packagingFormat) + chownStr := "" + if runtime.GOOS != "windows" { + chownStr = fmt.Sprintf(" && chown %s:%s * -R", u.Uid, u.Gid) + } + args = append(args, "bash", "-c", fmt.Sprintf("%s%s", strings.Join(command, " "), chownStr)) + dockerRunCmd := exec.Command(build.DockerBin, args...) + dockerRunCmd.Stderr = os.Stderr + dockerRunCmd.Stdout = os.Stdout + dockerRunCmd.Dir = path + err = dockerRunCmd.Run() + if err != nil { + log.Errorf("Docker run failed: %v", err) + os.Exit(1) + } +} diff --git a/cmd/run.go b/cmd/run.go index 064e2557..b8b4e5d4 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -10,8 +10,11 @@ import ( "regexp" "runtime" - "github.com/go-flutter-desktop/hover/internal/log" "github.com/spf13/cobra" + + "github.com/go-flutter-desktop/hover/internal/log" + "github.com/go-flutter-desktop/hover/internal/pubspec" + "github.com/go-flutter-desktop/hover/internal/build" ) var runObservatoryPort string @@ -20,7 +23,6 @@ func init() { runCmd.Flags().StringVarP(&buildTarget, "target", "t", "lib/main_desktop.dart", "The main entry-point file of the application.") runCmd.Flags().StringVarP(&buildBranch, "branch", "b", "", "The 'go-flutter' version to use. (@master or @v0.20.0 for example)") runCmd.Flags().StringVarP(&buildCachePath, "cache-path", "", "", "The path that hover uses to cache dependencies such as the Flutter engine .so/.dll (defaults to the standard user cache directory)") - runCmd.Flags().StringVar(&buildOpenGlVersion, "opengl", "3.3", "The OpenGL version specified here is only relevant for external texture plugin (i.e. video_plugin).\nIf 'none' is provided, texture won't be supported. Note: the Flutter Engine still needs a OpenGL compatible context.") runCmd.Flags().StringVarP(&runObservatoryPort, "observatory-port", "", "50300", "The observatory port used to connect hover to VM services (hot-reload/debug/..)") runCmd.Flags().BoolVar(&buildOmitEmbedder, "omit-embedder", false, "Don't (re)compile 'go-flutter' source code, useful when only working with Dart code") runCmd.Flags().BoolVar(&buildOmitFlutterBundle, "omit-flutter", false, "Don't (re)compile the current Flutter project, useful when only working with Golang code (plugin)") @@ -32,7 +34,7 @@ var runCmd = &cobra.Command{ Use: "run", Short: "Build and start a desktop release, with hot-reload support", Run: func(cmd *cobra.Command, args []string) { - projectName := getPubSpec().Name + projectName := pubspec.GetPubSpec().Name assertHoverInitialized() // ensure we have something to build @@ -47,14 +49,14 @@ var runCmd = &cobra.Command{ // forcefully enable --debug (which is not an option for 'hover run') buildDebug = true - build(projectName, targetOS, []string{"--observatory-port=" + runObservatoryPort}) + buildNormal(targetOS, []string{"--observatory-port=" + runObservatoryPort}) log.Infof("Build finished, starting app...") runAndAttach(projectName, targetOS) }, } func runAndAttach(projectName string, targetOS string) { - cmdApp := exec.Command(dotSlash + filepath.Join(buildPath, "build", "outputs", targetOS, projectName)) + cmdApp := exec.Command(dotSlash + filepath.Join(build.BuildPath, "build", "outputs", targetOS, projectName)) cmdFlutterAttach := exec.Command("flutter", "attach") stdoutApp, err := cmdApp.StdoutPipe() diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 15ca3a0e..86e46a06 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -7,10 +7,12 @@ import ( "path/filepath" "runtime" + "github.com/spf13/cobra" + "github.com/go-flutter-desktop/hover/internal/enginecache" "github.com/go-flutter-desktop/hover/internal/log" "github.com/go-flutter-desktop/hover/internal/versioncheck" - "github.com/spf13/cobra" + "github.com/go-flutter-desktop/hover/internal/build" ) func init() { @@ -68,8 +70,8 @@ func upgradeGoFlutter(targetOS string, engineCachePath string) (err error) { buildBranch = "@latest" } - cmdGoGetU := exec.Command(goBin, "get", "-u", "github.com/go-flutter-desktop/go-flutter"+buildBranch) - cmdGoGetU.Dir = filepath.Join(wd, buildPath) + cmdGoGetU := exec.Command(build.GoBin, "get", "-u", "github.com/go-flutter-desktop/go-flutter"+buildBranch) + cmdGoGetU.Dir = filepath.Join(wd, build.BuildPath) cmdGoGetU.Env = append(os.Environ(), "GOPROXY=direct", // github.com/golang/go/issues/32955 (allows '/' in branch name) "GO111MODULE=on", @@ -84,8 +86,8 @@ func upgradeGoFlutter(targetOS string, engineCachePath string) (err error) { return } - cmdGoModDownload := exec.Command(goBin, "mod", "download") - cmdGoModDownload.Dir = filepath.Join(wd, buildPath) + cmdGoModDownload := exec.Command(build.GoBin, "mod", "download") + cmdGoModDownload.Dir = filepath.Join(wd, build.BuildPath) cmdGoModDownload.Env = append(os.Environ(), "GO111MODULE=on", ) @@ -98,7 +100,7 @@ func upgradeGoFlutter(targetOS string, engineCachePath string) (err error) { return } - currentTag, err := versioncheck.CurrentGoFlutterTag(filepath.Join(wd, buildPath)) + currentTag, err := versioncheck.CurrentGoFlutterTag(filepath.Join(wd, build.BuildPath)) if err != nil { log.Errorf("%v", err) os.Exit(1) diff --git a/internal/build/binaries.go b/internal/build/binaries.go new file mode 100644 index 00000000..be21cda8 --- /dev/null +++ b/internal/build/binaries.go @@ -0,0 +1,7 @@ +package build + +var ( + GoBin string + FlutterBin string + DockerBin string +) diff --git a/internal/build/build.go b/internal/build/build.go new file mode 100644 index 00000000..99b863e8 --- /dev/null +++ b/internal/build/build.go @@ -0,0 +1,47 @@ +package build + +import ( + "os" + "path/filepath" + + "github.com/go-flutter-desktop/hover/internal/log" +) + +const BuildPath = "go" + +func OutputDirectoryPath(targetOS string) string { + outputDirectoryPath, err := filepath.Abs(filepath.Join(BuildPath, "build", "outputs", targetOS)) + if err != nil { + log.Errorf("Failed to resolve absolute path for output directory: %v", err) + os.Exit(1) + } + if _, err := os.Stat(outputDirectoryPath); os.IsNotExist(err) { + err = os.MkdirAll(outputDirectoryPath, 0775) + if err != nil { + log.Errorf("Failed to create output directory %s: %v", outputDirectoryPath, err) + os.Exit(1) + } + } + return outputDirectoryPath +} + +func OutputBinaryName(projectName string, targetOS string) string { + var outputBinaryName = projectName + switch targetOS { + case "darwin": + // no special filename + case "linux": + // no special filename + case "windows": + outputBinaryName += ".exe" + default: + log.Errorf("Target platform %s is not supported.", targetOS) + os.Exit(1) + } + return outputBinaryName +} + +func OutputBinaryPath(projectName string, targetOS string) string { + outputBinaryPath := filepath.Join(OutputDirectoryPath(targetOS), OutputBinaryName(projectName, targetOS)) + return outputBinaryPath +} diff --git a/internal/pubspec/pubspec.go b/internal/pubspec/pubspec.go new file mode 100644 index 00000000..f168a505 --- /dev/null +++ b/internal/pubspec/pubspec.go @@ -0,0 +1,53 @@ +package pubspec + +import ( + "os" + + "gopkg.in/yaml.v2" + + "github.com/go-flutter-desktop/hover/internal/log" +) + +type PubSpec struct { + Name string + Description string + Version string + Author string + Dependencies map[string]interface{} +} + +var pubspec = 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 + } + } + + return pubspec + } + +Fail: + log.Errorf("This command should be run from the root of your Flutter project.") + os.Exit(1) + return PubSpec{} +}