Skip to content

Commit d3e2b78

Browse files
authored
Merge pull request #23 from jld3103/feature/cross-compiling
Initial cross-compiling support
2 parents 71e0793 + 56091f4 commit d3e2b78

File tree

4 files changed

+204
-38
lines changed

4 files changed

+204
-38
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,15 @@ If you want to integrate go-flutter with VSCode, read this [issue](https://githu
8181

8282
### Build standalone application
8383

84-
To create a standalone debug build run this command:
84+
To create a standalone release (JIT mode) build run this command:
8585

8686
```bash
8787
hover build linux # or darwin or windows
8888
```
89+
You can create a build for any of the supported OSs using cross-compiling which needs [Docker to be installed](https://docs.docker.com/install/).
90+
Then just run the command from above and it will do everything for you.
8991

90-
The output will be in `go/build/outputs/linux` or windows or darwin depending on your OS. Hover does not yet support cross-compilation.
92+
The output will be in `go/build/outputs/linux` or windows or darwin.
9193

9294
To start the binary: (replace `yourApplicationName` with your app name)
9395

cmd/build.go

Lines changed: 183 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"os/exec"
7+
"os/user"
78
"path/filepath"
89
"regexp"
910
"runtime"
@@ -27,16 +28,24 @@ var (
2728
buildCachePath string
2829
buildOmitEmbedder bool
2930
buildOmitFlutterBundle bool
31+
buildDocker bool
3032
)
3133

3234
const buildPath = "go"
3335

36+
const mingwGccBinName = "x86_64-w64-mingw32-gcc"
37+
const clangBinName = "o32-clang"
38+
39+
var crossCompile = false
40+
var engineCachePath string
41+
3442
func init() {
3543
buildCmd.PersistentFlags().StringVarP(&buildTarget, "target", "t", "lib/main_desktop.dart", "The main entry-point file of the application.")
3644
buildCmd.PersistentFlags().StringVarP(&buildManifest, "manifest", "m", "pubspec.yaml", "Flutter manifest file of the application.")
3745
buildCmd.PersistentFlags().StringVarP(&buildBranch, "branch", "b", "", "The 'go-flutter' version to use. (@master or @v0.20.0 for example)")
3846
buildCmd.PersistentFlags().BoolVar(&buildDebug, "debug", false, "Build a debug version of the app.")
3947
buildCmd.PersistentFlags().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)")
48+
buildCmd.PersistentFlags().BoolVar(&buildDocker, "docker", false, "Compile in Docker container only. No need to install go")
4049
buildCmd.AddCommand(buildLinuxCmd)
4150
buildCmd.AddCommand(buildLinuxSnapCmd)
4251
buildCmd.AddCommand(buildLinuxDebCmd)
@@ -146,12 +155,107 @@ func outputBinaryPath(projectName string, targetOS string) string {
146155
return outputBinaryPath
147156
}
148157

149-
func build(projectName string, targetOS string, vmArguments []string) {
150-
if targetOS != runtime.GOOS {
151-
fmt.Println("hover: Cross-compiling is currently not supported")
158+
func dockerBuild(projectName string, targetOS string, vmArguments []string) {
159+
crossCompilingDir, err := filepath.Abs(filepath.Join(buildPath, "cross-compiling"))
160+
err = os.MkdirAll(crossCompilingDir, 0755)
161+
if err != nil {
162+
fmt.Printf("hover: Cannot create the cross-compiling directory: %v\n", err)
163+
os.Exit(1)
164+
}
165+
userCacheDir, err := os.UserCacheDir()
166+
if err != nil {
167+
fmt.Printf("hover: Cannot get the path for the system cache directory: %v\n", err)
168+
os.Exit(1)
169+
}
170+
goPath := filepath.Join(userCacheDir, "hover-cc")
171+
err = os.MkdirAll(goPath, 0755)
172+
if err != nil {
173+
fmt.Printf("hover: Cannot create the hover-cc GOPATH under the system cache directory: %v\n", err)
174+
os.Exit(1)
175+
}
176+
wd, err := os.Getwd()
177+
if err != nil {
178+
fmt.Printf("hover: Cannot get the path for current directory %s", err)
152179
os.Exit(1)
153180
}
154-
var engineCachePath string
181+
dockerFilePath, err := filepath.Abs(filepath.Join(crossCompilingDir, "Dockerfile"))
182+
if err != nil {
183+
fmt.Printf("hover: Failed to resolve absolute path for Dockerfile %s: %v\n", dockerFilePath, err)
184+
os.Exit(1)
185+
}
186+
if _, err := os.Stat(dockerFilePath); os.IsNotExist(err) {
187+
dockerFile, err := os.Create(dockerFilePath)
188+
if err != nil {
189+
fmt.Printf("hover: Failed to create Dockerfile %s: %v\n", dockerFilePath, err)
190+
os.Exit(1)
191+
}
192+
dockerFileContent := []string{
193+
"FROM dockercore/golang-cross",
194+
"RUN apt-get install libgl1-mesa-dev xorg-dev -y",
195+
}
196+
197+
for _, line := range dockerFileContent {
198+
if _, err := dockerFile.WriteString(line + "\n"); err != nil {
199+
fmt.Printf("hover: Could not write Dockerfile: %v\n", err)
200+
os.Exit(1)
201+
}
202+
}
203+
err = dockerFile.Close()
204+
if err != nil {
205+
fmt.Printf("hover: Could not close Dockerfile: %v\n", err)
206+
os.Exit(1)
207+
}
208+
fmt.Printf("hover: A Dockerfile for cross-compiling for %s has been created at %s. You can add it to git.\n", targetOS, filepath.Join(buildPath, "cross-compiling", targetOS))
209+
}
210+
dockerBuildCmd := exec.Command(dockerBin, "build", "-t", "hover-build-cc", ".")
211+
dockerBuildCmd.Stderr = os.Stderr
212+
dockerBuildCmd.Dir = crossCompilingDir
213+
err = dockerBuildCmd.Run()
214+
if err != nil {
215+
fmt.Printf("hover: Docker build failed: %v\n", err)
216+
os.Exit(1)
217+
}
218+
219+
fmt.Println("hover: Cross-Compiling 'go-flutter' and plugins using docker")
220+
221+
u, err := user.Current()
222+
if err != nil {
223+
fmt.Printf("hover: Couldn't get current user: %v\n", err)
224+
os.Exit(1)
225+
}
226+
args := []string{
227+
"run",
228+
"-w", "/app/go",
229+
"-v", goPath + ":/go",
230+
"-v", wd + ":/app",
231+
"-v", engineCachePath + ":/engine",
232+
"-v", filepath.Join(userCacheDir, "go-build") + ":/cache",
233+
}
234+
for _, env := range buildEnv(targetOS, "/engine") {
235+
args = append(args, "-e", env)
236+
}
237+
args = append(args, "hover-build-cc")
238+
chownStr := ""
239+
if runtime.GOOS != "windows" {
240+
chownStr = fmt.Sprintf(" && chown %s:%s build/ -R", u.Uid, u.Gid)
241+
}
242+
args = append(args, "bash", "-c", fmt.Sprintf("%s%s", strings.Join(buildCommand(targetOS, vmArguments, "build/outputs/"+targetOS+"/"+outputBinaryName(projectName, targetOS)), " "), chownStr))
243+
dockerRunCmd := exec.Command(dockerBin, args...)
244+
dockerRunCmd.Stderr = os.Stderr
245+
dockerRunCmd.Stdout = os.Stdout
246+
dockerRunCmd.Dir = crossCompilingDir
247+
err = dockerRunCmd.Run()
248+
if err != nil {
249+
fmt.Printf("hover: Docker run failed: %v\n", err)
250+
os.Exit(1)
251+
}
252+
fmt.Println("hover: Successfully cross-compiled for " + targetOS)
253+
}
254+
255+
func build(projectName string, targetOS string, vmArguments []string) {
256+
crossCompile = targetOS != runtime.GOOS
257+
buildDocker = crossCompile || buildDocker
258+
155259
if buildCachePath != "" {
156260
engineCachePath = enginecache.ValidateOrUpdateEngineAtPath(targetOS, buildCachePath)
157261
} else {
@@ -265,19 +369,6 @@ func build(projectName string, targetOS string, vmArguments []string) {
265369
return
266370
}
267371

268-
var cgoLdflags string
269-
switch targetOS {
270-
case "darwin":
271-
cgoLdflags = fmt.Sprintf("-F%s -Wl,-rpath,@executable_path", engineCachePath)
272-
case "linux":
273-
cgoLdflags = fmt.Sprintf("-L%s", engineCachePath)
274-
case "windows":
275-
cgoLdflags = fmt.Sprintf("-L%s", engineCachePath)
276-
default:
277-
fmt.Printf("hover: Target platform %s is not supported, cgo_ldflags not implemented.\n", targetOS)
278-
os.Exit(1)
279-
}
280-
281372
wd, err := os.Getwd()
282373
if err != nil {
283374
fmt.Printf("hover: Failed to get working dir: %v\n", err)
@@ -323,28 +414,19 @@ func build(projectName string, targetOS string, vmArguments []string) {
323414
}
324415
}
325416

326-
var ldflags []string
327-
if !buildDebug {
328-
vmArguments = append(vmArguments, "--disable-dart-asserts")
329-
vmArguments = append(vmArguments, "--disable-observatory")
330-
331-
if targetOS == "windows" {
332-
ldflags = append(ldflags, "-H=windowsgui")
417+
if buildDocker {
418+
if crossCompile {
419+
fmt.Printf("hover: Because %s is not able to compile for %s out of the box, a cross-compiling container is used\n", runtime.GOOS, targetOS)
333420
}
334-
ldflags = append(ldflags, "-s")
335-
ldflags = append(ldflags, "-w")
421+
dockerBuild(projectName, targetOS, vmArguments)
422+
return
336423
}
337-
ldflags = append(ldflags, fmt.Sprintf("-X main.vmArguments=%s", strings.Join(vmArguments, ";")))
338424

339-
cmdGoBuild := exec.Command(goBin, "build",
340-
"-o", outputBinaryPath(projectName, targetOS),
341-
fmt.Sprintf("-ldflags=%s", strings.Join(ldflags, " ")),
342-
dotSlash+"cmd",
343-
)
425+
buildCommandString := buildCommand(targetOS, vmArguments, outputBinaryPath(projectName, targetOS))
426+
cmdGoBuild := exec.Command(buildCommandString[0], buildCommandString[1:]...)
344427
cmdGoBuild.Dir = filepath.Join(wd, buildPath)
345428
cmdGoBuild.Env = append(os.Environ(),
346-
"GO111MODULE=on",
347-
"CGO_LDFLAGS="+cgoLdflags,
429+
buildEnv(targetOS, engineCachePath)...,
348430
)
349431

350432
cmdGoBuild.Stderr = os.Stderr
@@ -356,4 +438,71 @@ func build(projectName string, targetOS string, vmArguments []string) {
356438
fmt.Printf("hover: Go build failed: %v\n", err)
357439
os.Exit(1)
358440
}
441+
fmt.Println("hover: Successfully compiled")
442+
}
443+
444+
func buildEnv(targetOS string, engineCachePath string) []string {
445+
var cgoLdflags string
446+
switch targetOS {
447+
case "darwin":
448+
cgoLdflags = fmt.Sprintf("-F%s -Wl,-rpath,@executable_path", engineCachePath)
449+
case "linux":
450+
cgoLdflags = fmt.Sprintf("-L%s", engineCachePath)
451+
case "windows":
452+
cgoLdflags = fmt.Sprintf("-L%s", engineCachePath)
453+
default:
454+
fmt.Printf("hover: Target platform %s is not supported, cgo_ldflags not implemented.\n", targetOS)
455+
os.Exit(1)
456+
}
457+
env := []string{
458+
"GO111MODULE=on",
459+
"CGO_LDFLAGS=" + cgoLdflags,
460+
"GOOS=" + targetOS,
461+
"GOARCH=amd64",
462+
"CGO_ENABLED=1",
463+
}
464+
if buildDocker {
465+
env = append(env,
466+
"GOCACHE=/cache",
467+
)
468+
if targetOS == "windows" {
469+
env = append(env,
470+
"CC="+mingwGccBinName,
471+
)
472+
}
473+
if targetOS == "darwin" {
474+
env = append(env,
475+
"CC="+clangBinName,
476+
)
477+
}
478+
}
479+
return env
480+
}
481+
482+
func buildCommand(targetOS string, vmArguments []string, outputBinaryPath string) []string {
483+
var ldflags []string
484+
if !buildDebug {
485+
vmArguments = append(vmArguments, "--disable-dart-asserts")
486+
vmArguments = append(vmArguments, "--disable-observatory")
487+
488+
if targetOS == "windows" {
489+
ldflags = append(ldflags, "-H=windowsgui")
490+
}
491+
ldflags = append(ldflags, "-s")
492+
ldflags = append(ldflags, "-w")
493+
}
494+
ldflags = append(ldflags, fmt.Sprintf("-X main.vmArguments=%s", strings.Join(vmArguments, ";")))
495+
outputCommand := []string{
496+
"go",
497+
"build",
498+
"-o", outputBinaryPath,
499+
"-v",
500+
}
501+
if buildDocker {
502+
outputCommand = append(outputCommand, fmt.Sprintf("-ldflags=\"%s\"", strings.Join(ldflags, " ")))
503+
} else {
504+
outputCommand = append(outputCommand, fmt.Sprintf("-ldflags=%s", strings.Join(ldflags, " ")))
505+
}
506+
outputCommand = append(outputCommand, dotSlash+"cmd")
507+
return outputCommand
359508
}

cmd/common.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,27 @@ func init() {
1919
var (
2020
goBin string
2121
flutterBin string
22+
dockerBin string
2223
)
2324

2425
func initBinaries() {
2526
var err error
27+
goAvailable := false
28+
dockerAvailable := false
2629
goBin, err = exec.LookPath("go")
27-
if err != nil {
28-
fmt.Println("hover: Failed to lookup `go` executable. Please install Go.\nhttps://golang.org/doc/install")
30+
if err == nil {
31+
goAvailable = true
32+
}
33+
dockerBin, err = exec.LookPath("docker")
34+
if err == nil {
35+
dockerAvailable = true
36+
}
37+
if !dockerAvailable && !goAvailable {
38+
fmt.Println("hover: Failed to lookup `go` and `docker` executable. Please install one of them:\nGo: https://golang.org/doc/install\nDocker: https://docs.docker.com/install")
39+
os.Exit(1)
40+
}
41+
if dockerAvailable && !goAvailable && !buildDocker {
42+
fmt.Println("hover: Failed to lookup `go` executable. Please install go or add '--docker' to force running in Docker container.\nhttps://golang.org/doc/install")
2943
os.Exit(1)
3044
}
3145
flutterBin, err = exec.LookPath("flutter")

cmd/run.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func init() {
2323
runCmd.Flags().StringVarP(&runObservatoryPort, "observatory-port", "", "50300", "The observatory port used to connect hover to VM services (hot-reload/debug/..)")
2424
runCmd.Flags().BoolVar(&buildOmitEmbedder, "omit-embedder", false, "Don't (re)compile 'go-flutter' source code, useful when only working with Dart code")
2525
runCmd.Flags().BoolVar(&buildOmitFlutterBundle, "omit-flutter", false, "Don't (re)compile the current Flutter project, useful when only working with Golang code (plugin)")
26+
runCmd.PersistentFlags().BoolVar(&buildDocker, "docker", false, "Compile and run in Docker container only. No need to install go")
2627
rootCmd.AddCommand(runCmd)
2728
}
2829

0 commit comments

Comments
 (0)