Skip to content

Commit 5014a98

Browse files
author
jld3103
authored
Merge pull request #177 from go-flutter-desktop/feature/aot-cross-compilation
Add AOT cross-compilation
2 parents 799cf24 + 1af7302 commit 5014a98

File tree

5 files changed

+176
-51
lines changed

5 files changed

+176
-51
lines changed

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ RUN apt-get update \
6060
&& apt-get install -y \
6161
# dependencies for compiling linux
6262
libgl1-mesa-dev xorg-dev \
63+
# dependencies for compiling windows
64+
wine \
6365
# dependencies for darwin-dmg
6466
genisoimage \
6567
# dependencies for darwin-pkg

cmd/build.go

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/go-flutter-desktop/hover/internal/androidmanifest"
1717
"github.com/go-flutter-desktop/hover/internal/build"
1818
"github.com/go-flutter-desktop/hover/internal/config"
19+
"github.com/go-flutter-desktop/hover/internal/darwinhacks"
1920
"github.com/go-flutter-desktop/hover/internal/enginecache"
2021
"github.com/go-flutter-desktop/hover/internal/fileutils"
2122
"github.com/go-flutter-desktop/hover/internal/log"
@@ -216,16 +217,14 @@ func subcommandBuild(targetOS string, packagingTask packaging.Task, vmArguments
216217
if !buildOrRunDocker {
217218
packagingTask.AssertSupported()
218219
}
219-
220-
if !buildOrRunSkipFlutter {
221-
cleanBuildOutputsDir(targetOS)
222-
buildFlutterBundle(targetOS)
223-
}
224220
if buildOrRunDocker {
221+
removeBrokenBundleFilesForDocker()
225222
var buildFlags []string
226223
buildFlags = append(buildFlags, commonFlags()...)
227-
buildFlags = append(buildFlags, "--skip-flutter")
228224
buildFlags = append(buildFlags, "--skip-engine-download")
225+
if buildOrRunSkipFlutter {
226+
buildFlags = append(buildFlags, "--skip-flutter")
227+
}
229228
if buildOrRunSkipEmbedder {
230229
buildFlags = append(buildFlags, "--skip-embedder")
231230
}
@@ -242,7 +241,12 @@ func subcommandBuild(targetOS string, packagingTask packaging.Task, vmArguments
242241
buildFlags = append(buildFlags, "--profile")
243242
}
244243
dockerHoverBuild(targetOS, packagingTask, buildFlags, nil)
244+
removeBrokenBundleFilesForDocker()
245245
} else {
246+
if !buildOrRunSkipFlutter {
247+
cleanBuildOutputsDir(targetOS)
248+
buildFlutterBundle(targetOS)
249+
}
246250
if !buildOrRunSkipEmbedder {
247251
buildGoBinary(targetOS, vmArguments)
248252
}
@@ -254,6 +258,19 @@ func subcommandBuild(targetOS string, packagingTask packaging.Task, vmArguments
254258
}
255259
}
256260

261+
// removeBrokenBundleFilesForDocker removes some files, because they don't work in the container or after something ran in the container
262+
func removeBrokenBundleFilesForDocker() {
263+
for _, file := range []string{".packages", ".dart_tool"} {
264+
if _, err := os.Stat(file); err == nil || os.IsExist(err) {
265+
err := os.RemoveAll(file)
266+
if err != nil {
267+
log.Errorf("Failed to remove %s: %v", file, err)
268+
os.Exit(1)
269+
}
270+
}
271+
}
272+
}
273+
257274
// initBuildParameters is used to initialize all the build parameters. It sets
258275
// fallback values based on config or defaults for values that have not
259276
// explicitly been set through flags.
@@ -313,17 +330,39 @@ func initBuildParameters(targetOS string, defaultBuildOrRunMode build.Mode) {
313330
buildOrRunMode = build.ProfileMode
314331
}
315332

316-
if buildOrRunMode.IsAot && targetOS != runtime.GOOS && !buildIgnoreHostOS {
317-
log.Errorf("AOT builds currently only work on their host OS")
318-
os.Exit(1)
319-
}
333+
validateBuildParameters(targetOS)
320334

321335
engineCachePath = enginecache.EngineCachePath(targetOS, buildOrRunCachePath, buildOrRunMode)
322336
if !buildSkipEngineDownload {
323337
enginecache.ValidateOrUpdateEngine(targetOS, buildOrRunCachePath, buildOrRunEngineVersion, buildOrRunMode)
324338
}
325339
}
326340

341+
func validateBuildParameters(targetOS string) {
342+
if buildOrRunMode.IsAot && targetOS != runtime.GOOS && !buildIgnoreHostOS {
343+
if targetOS == "windows" && runtime.GOOS != targetOS {
344+
if path, err := exec.LookPath("wine"); (err != nil || len(path) == 0) && !buildOrRunDocker {
345+
// Skip checking for wine if using docker, but still being on host system
346+
log.Errorf("To cross-compile AOT apps for windows on %s install wine from your package manager or https://www.winehq.org/ or use the `--docker` flag", runtime.GOOS)
347+
os.Exit(1)
348+
}
349+
} else if targetOS == "darwin" && runtime.GOOS == "linux" {
350+
if buildOrRunDocker {
351+
// Darling doesn't work in a docker container so it should fail when trying to use docker
352+
log.Errorf("It is not possible to cross-compile AOT apps for darwin using docker")
353+
log.Errorf("To cross-compile AOT apps for darwin on %s install darling from your package manager or https://www.darlinghq.org/", runtime.GOOS)
354+
os.Exit(1)
355+
} else if path, err := exec.LookPath("darling"); err != nil || len(path) == 0 {
356+
log.Errorf("To cross-compile AOT apps for darwin on %s install darling from your package manager or https://www.darlinghq.org/", runtime.GOOS)
357+
os.Exit(1)
358+
}
359+
} else {
360+
log.Errorf("AOT builds currently only work on their host OS")
361+
os.Exit(1)
362+
}
363+
}
364+
}
365+
327366
func commonFlags() []string {
328367
var f []string
329368
if buildOrRunFlutterTarget != config.BuildTargetDefault {
@@ -434,52 +473,82 @@ func buildFlutterBundle(targetOS string) {
434473
log.Errorf("Failed to remove unused kernel_blob.bin: %v", err)
435474
os.Exit(1)
436475
}
476+
477+
useWine := targetOS == "windows" && runtime.GOOS != targetOS
478+
useDarling := targetOS == "darwin" && runtime.GOOS != targetOS
479+
480+
if useDarling {
481+
darwinhacks.ChangePackagesFilePath(true)
482+
}
483+
437484
dart := filepath.Join(engineCachePath, "dart"+build.ExecutableExtension(targetOS))
438485
genSnapshot := filepath.Join(engineCachePath, "gen_snapshot"+build.ExecutableExtension(targetOS))
439486
kernelSnapshot := filepath.Join(build.OutputDirectoryPath(targetOS, buildOrRunMode), "kernel_snapshot.dill")
440487
elfSnapshot := filepath.Join(build.OutputDirectoryPath(targetOS, buildOrRunMode), "libapp.so")
441-
cmdGenerateKernelSnapshot := exec.Command(
442-
dart,
443-
filepath.Join(engineCachePath, "gen", "frontend_server.dart.snapshot"),
444-
"--sdk-root="+filepath.Join(engineCachePath, "flutter_patched_sdk"),
488+
frontendServerSnapshot := filepath.Join(engineCachePath, "gen", "frontend_server.dart.snapshot")
489+
flutterPatchedSdk := filepath.Join(engineCachePath, "flutter_patched_sdk")
490+
generateKernelSnapshotCommand := []string{
491+
darwinhacks.RewriteDarlingPath(useDarling, dart),
492+
darwinhacks.RewriteDarlingPath(useDarling, frontendServerSnapshot),
493+
"--sdk-root=" + darwinhacks.RewriteDarlingPath(useDarling, flutterPatchedSdk),
445494
"--target=flutter",
446495
"--aot",
447496
"--tfa",
448497
"-Ddart.vm.product=true",
449498
"--packages=.packages",
450-
"--output-dill="+kernelSnapshot,
499+
"--output-dill=" + darwinhacks.RewriteDarlingPath(useDarling, kernelSnapshot),
451500
buildOrRunFlutterTarget,
501+
}
502+
if useWine {
503+
generateKernelSnapshotCommand = append([]string{"wine"}, generateKernelSnapshotCommand...)
504+
}
505+
if useDarling {
506+
generateKernelSnapshotCommand = append([]string{"darling", "shell"}, generateKernelSnapshotCommand...)
507+
}
508+
cmdGenerateKernelSnapshot := exec.Command(
509+
generateKernelSnapshotCommand[0],
510+
generateKernelSnapshotCommand[1:]...,
452511
)
512+
cmdGenerateKernelSnapshot.Stdout = os.Stdout
453513
cmdGenerateKernelSnapshot.Stderr = os.Stderr
454514
log.Infof("Generating kernel snapshot")
455-
output, err := cmdGenerateKernelSnapshot.Output()
515+
err = cmdGenerateKernelSnapshot.Run()
516+
if useDarling {
517+
// Change back paths even if the command failed
518+
darwinhacks.ChangePackagesFilePath(false)
519+
}
456520
if err != nil {
457521
log.Errorf("Generating kernel snapshot failed: %v", err)
458-
log.Errorf(string(output))
459522
os.Exit(1)
460523
}
461524
generateAotSnapshotCommand := []string{
462-
genSnapshot,
525+
darwinhacks.RewriteDarlingPath(useDarling, genSnapshot),
463526
"--no-causal-async-stacks",
464527
"--lazy-async-stacks",
465528
"--deterministic",
466529
"--snapshot_kind=app-aot-elf",
467-
"--elf=" + elfSnapshot,
530+
"--elf=" + darwinhacks.RewriteDarlingPath(useDarling, elfSnapshot),
531+
}
532+
if useWine {
533+
generateAotSnapshotCommand = append([]string{"wine"}, generateAotSnapshotCommand...)
534+
}
535+
if useDarling {
536+
generateAotSnapshotCommand = append([]string{"darling", "shell"}, generateAotSnapshotCommand...)
468537
}
469538
if buildOrRunMode == build.ReleaseMode {
470539
generateAotSnapshotCommand = append(generateAotSnapshotCommand, "--strip")
471540
}
472-
generateAotSnapshotCommand = append(generateAotSnapshotCommand, kernelSnapshot)
541+
generateAotSnapshotCommand = append(generateAotSnapshotCommand, darwinhacks.RewriteDarlingPath(useDarling, kernelSnapshot))
473542
cmdGenerateAotSnapshot := exec.Command(
474543
generateAotSnapshotCommand[0],
475544
generateAotSnapshotCommand[1:]...,
476545
)
546+
cmdGenerateAotSnapshot.Stdout = os.Stdout
477547
cmdGenerateAotSnapshot.Stderr = os.Stderr
478548
log.Infof("Generating ELF snapshot")
479-
output, err = cmdGenerateAotSnapshot.Output()
549+
err = cmdGenerateAotSnapshot.Run()
480550
if err != nil {
481551
log.Errorf("Generating AOT snapshot failed: %v", err)
482-
log.Errorf(string(output))
483552
os.Exit(1)
484553
}
485554
err = os.Remove(kernelSnapshot)
@@ -594,10 +663,6 @@ func buildGoBinary(targetOS string, vmArguments []string) {
594663
log.Warnf("The '--opengl=none' flag makes go-flutter incompatible with texture plugins!")
595664
}
596665

597-
if targetOS == "darwin" && buildOrRunMode != build.DebugMode {
598-
darwinDyldHack(filepath.Join(build.OutputDirectoryPath(targetOS, buildOrRunMode), build.EngineFiles(targetOS, buildOrRunMode)[0]))
599-
}
600-
601666
buildCommandString := buildCommand(targetOS, vmArguments, build.OutputBinaryPath(config.GetConfig().GetExecutableName(pubspec.GetPubSpec().Name), targetOS, buildOrRunMode))
602667
cmdGoBuild := exec.Command(buildCommandString[0], buildCommandString[1:]...)
603668
cmdGoBuild.Dir = filepath.Join(wd, build.BuildPath)
@@ -616,29 +681,7 @@ func buildGoBinary(targetOS string, vmArguments []string) {
616681
}
617682
log.Infof("Successfully compiled executable binary for %s", targetOS)
618683
if targetOS == "darwin" && buildOrRunMode != build.DebugMode {
619-
darwinDyldHack(build.OutputBinaryPath(config.GetConfig().GetExecutableName(pubspec.GetPubSpec().Name), targetOS, buildOrRunMode))
620-
}
621-
}
622-
623-
// darwinDyldHack is a nasty hack to get the linking working. After fiddling a lot of hours with CGO linking
624-
// this was the only solution I could come up with and it works. I guess something would need to be changed in the engine
625-
// builds to make this obsolete, but this hack does it for now.
626-
func darwinDyldHack(path string) {
627-
cmdInstallNameTool := exec.Command(
628-
"install_name_tool",
629-
"-change",
630-
"./libflutter_engine.dylib",
631-
"@executable_path/libflutter_engine.dylib",
632-
"-id",
633-
"@executable_path/libflutter_engine.dylib",
634-
path,
635-
)
636-
cmdInstallNameTool.Stderr = os.Stderr
637-
output, err := cmdInstallNameTool.Output()
638-
if err != nil {
639-
log.Errorf("install_name_tool failed: %v", err)
640-
log.Errorf(string(output))
641-
os.Exit(1)
684+
darwinhacks.DyldHack(build.OutputBinaryPath(config.GetConfig().GetExecutableName(pubspec.GetPubSpec().Name), targetOS, buildOrRunMode))
642685
}
643686
}
644687

cmd/docker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func dockerHoverBuild(targetOS string, packagingTask packaging.Task, buildFlags
4141
log.Errorf("Cannot get the path for current directory %s", err)
4242
os.Exit(1)
4343
}
44-
log.Infof("Compiling go binary using docker container")
44+
log.Infof("Building using docker container")
4545

4646
dockerArgs := []string{
4747
"run",
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package darwinhacks
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"runtime"
10+
"strings"
11+
12+
"github.com/go-flutter-desktop/hover/internal/log"
13+
)
14+
15+
// DyldHack is a nasty hack to get the linking working. After fiddling a lot of hours with CGO linking
16+
// this was the only solution I could come up with and it works. I guess something would need to be changed in the engine
17+
// builds to make this obsolete, but this hack does it for now.
18+
func DyldHack(path string) {
19+
installNameToolCommand := []string{
20+
"install_name_tool",
21+
"-change",
22+
"./libflutter_engine.dylib",
23+
"@executable_path/libflutter_engine.dylib",
24+
"-id",
25+
"@executable_path/libflutter_engine.dylib",
26+
RewriteDarlingPath(runtime.GOOS != "darwin", path),
27+
}
28+
if runtime.GOOS != "darwin" {
29+
installNameToolCommand = append([]string{"darling", "shell"}, installNameToolCommand...)
30+
}
31+
cmdInstallNameTool := exec.Command(
32+
installNameToolCommand[0],
33+
installNameToolCommand[1:]...,
34+
)
35+
cmdInstallNameTool.Stderr = os.Stderr
36+
output, err := cmdInstallNameTool.Output()
37+
if err != nil {
38+
log.Errorf("install_name_tool failed: %v", err)
39+
log.Errorf(string(output))
40+
os.Exit(1)
41+
}
42+
}
43+
44+
func RewriteDarlingPath(useDarling bool, path string) string {
45+
if useDarling {
46+
return filepath.Join("/", "Volumes", "SystemRoot", path)
47+
}
48+
return path
49+
}
50+
51+
func ChangePackagesFilePath(isInsert bool) {
52+
for _, path := range []string{".packages", filepath.Join(".dart_tool", "package_config.json")} {
53+
content, err := ioutil.ReadFile(path)
54+
if err != nil {
55+
log.Errorf("Failed to read %s file: %v", path, err)
56+
os.Exit(1)
57+
}
58+
lines := strings.Split(string(content), "\n")
59+
for i := range lines {
60+
if strings.Contains(lines[i], "file://") {
61+
parts := strings.Split(lines[i], "file://")
62+
if isInsert && !strings.Contains(lines[i], "/Volumes/SystemRoot") {
63+
lines[i] = fmt.Sprintf("%sfile:///Volumes/SystemRoot%s", parts[0], parts[1])
64+
} else {
65+
lines[i] = fmt.Sprintf("%sfile://%s", parts[0], strings.ReplaceAll(parts[1], "/Volumes/SystemRoot", ""))
66+
}
67+
}
68+
}
69+
err = ioutil.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644)
70+
if err != nil {
71+
log.Errorf("Failed to write %s file: %v", path, err)
72+
os.Exit(1)
73+
}
74+
}
75+
}

internal/enginecache/cache.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import (
1313
"strings"
1414
"time"
1515

16-
copy "github.com/otiai10/copy"
16+
"github.com/otiai10/copy"
1717
"github.com/pkg/errors"
1818

1919
"github.com/go-flutter-desktop/hover/internal/build"
20+
"github.com/go-flutter-desktop/hover/internal/darwinhacks"
2021
"github.com/go-flutter-desktop/hover/internal/log"
2122
"github.com/go-flutter-desktop/hover/internal/version"
2223
)
@@ -345,6 +346,10 @@ func ValidateOrUpdateEngine(targetOS, cachePath, requiredEngineVersion string, m
345346
}
346347
}
347348

349+
if targetOS == "darwin" && mode != build.DebugMode {
350+
darwinhacks.DyldHack(filepath.Join(engineCachePath, build.EngineFiles(targetOS, mode)[0]))
351+
}
352+
348353
files := []string{
349354
"icudtl.dat",
350355
}

0 commit comments

Comments
 (0)