cutego/internal/binding/templater/template_cgo_qmake.go
2017-04-12 18:56:24 +02:00

426 lines
13 KiB
Go

package templater
import (
"bytes"
"fmt"
"go/format"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/therecipe/qt/internal/binding/parser"
"github.com/therecipe/qt/internal/utils"
)
const (
NONE = iota
MOC
MINIMAL
RCC
)
func CgoTemplate(module, path, target string, mode int, ipkg string) (o string) {
utils.Log.WithField("module", module).WithField("path", path).WithField("target", target).WithField("mode", mode).WithField("pkg", ipkg)
switch module {
case "AndroidExtras":
if target != "android" {
return
}
case "Sailfish":
if !strings.HasPrefix(target, "sailfish") {
return
}
}
if path == "" {
path = utils.GoQtPkgPath(strings.ToLower(module))
}
//TODO:
if !(target == "sailfish" || target == "sailfish-emulator") {
if !parser.ShouldBuildForTarget(module, target) ||
isAlreadyCached(module, path, target, mode) {
utils.Log.Debugf("skipping cgo generation")
return
}
}
switch target {
case "sailfish", "sailfish-emulator":
cgoSailfish(module, path, mode, ipkg) //TODO:
case "asteroid":
cgoAsteroid(module, path, mode, ipkg) //TODO:
default:
createProject(module, path, target, mode)
createMakefile(module, path, target, mode)
createCgo(module, path, target, mode, ipkg)
}
utils.RemoveAll(filepath.Join(path, "Makefile"))
utils.RemoveAll(filepath.Join(path, "Makefile.Release"))
return
}
//TODO: use qmake props ?
func isAlreadyCached(module, path, target string, mode int) bool {
for _, file := range cgoFileNames(module, path, target, mode) {
file = filepath.Join(path, file)
if utils.ExistsFile(file) {
file = utils.Load(file)
for _, dep := range parser.LibDeps[module] {
if !strings.Contains(strings.ToLower(file), strings.ToLower(dep)) {
utils.Log.Debugln("cgo does not contain:", strings.ToLower(dep))
return false
}
}
switch target {
case "darwin", "linux", "windows":
//TODO msys pkg-config mxe brew
return strings.Contains(file, utils.QT_DIR()) || strings.Contains(file, utils.QT_DARWIN_DIR()) ||
strings.Contains(file, utils.QT_MSYS2_DIR()) || strings.Contains(file, utils.QT_MXE_TRIPLET())
case "android":
return strings.Contains(file, utils.QT_DIR()) && strings.Contains(file, utils.ANDROID_NDK_DIR())
case "ios", "ios-simulator":
return strings.Contains(file, utils.QT_DIR()) || strings.Contains(file, utils.QT_DARWIN_DIR())
case "sailfish", "sailfish-emulator", "asteroid":
case "rpi1", "rpi2", "rpi3":
return strings.Contains(file, strings.TrimSpace(utils.RunCmd(exec.Command(utils.ToolPath("qmake", target), "-query", "QT_INSTALL_LIBS"), fmt.Sprintf("query lib path for %v on %v", target, runtime.GOOS))))
}
}
}
return false
}
func createProject(module, path, target string, mode int) {
var out []string
switch {
case mode == RCC:
out = []string{"Core"}
case mode == MOC, module == "build_ios":
out = parser.LibDeps[module]
case mode == MINIMAL, mode == NONE:
out = append([]string{module}, parser.LibDeps[module]...)
}
if strings.HasPrefix(target, "rpi") && module == "Core" {
out = append([]string{module}, []string{"Widgets", "Gui"}...)
}
for i, v := range out {
if v == "Speech" {
out[i] = "TextToSpeech"
}
out[i] = strings.ToLower(out[i])
}
utils.Save(filepath.Join(path, "..", fmt.Sprintf("%v.pro", strings.ToLower(module))), fmt.Sprintf("QT += %v", strings.Join(out, " ")))
}
func createMakefile(module, path, target string, mode int) {
cmd := exec.Command(utils.ToolPath("qmake", target), filepath.Join(path, "..", fmt.Sprintf("%v.pro", strings.ToLower(module))))
cmd.Dir = path
switch target {
case "darwin":
cmd.Args = append(cmd.Args, []string{"-spec", "macx-clang", "CONFIG+=x86_64"}...)
case "windows":
cmd.Args = append(cmd.Args, []string{"-spec", "win32-g++"}...)
case "linux":
cmd.Args = append(cmd.Args, []string{"-spec", "linux-g++"}...)
case "ios":
cmd.Args = append(cmd.Args, []string{"-spec", "macx-ios-clang", "CONFIG+=release", "CONFIG+=iphoneos", "CONFIG+=device"}...)
case "ios-simulator":
cmd.Args = append(cmd.Args, []string{"-spec", "macx-ios-clang", "CONFIG+=release", "CONFIG+=iphonesimulator", "CONFIG+=simulator"}...)
case "android":
cmd.Args = append(cmd.Args, []string{"-spec", "android-g++"}...)
cmd.Env = []string{fmt.Sprintf("ANDROID_NDK_ROOT=%v", utils.ANDROID_NDK_DIR())}
case "sailfish", "sailfish-emulator":
cmd.Args = append(cmd.Args, []string{"-spec", "linux-g++"}...)
cmd.Env = []string{
"MER_SSH_PORT=2222",
fmt.Sprintf("MER_SSH_PRIVATE_KEY=%v", filepath.Join(utils.SAILFISH_DIR(), "vmshare", "ssh", "private_keys", "engine", "mersdk")),
fmt.Sprintf("MER_SSH_PROJECT_PATH=%v", cmd.Dir),
fmt.Sprintf("MER_SSH_SDK_TOOLS=%v/.config/SailfishOS-SDK/mer-sdk-tools/MerSDK/SailfishOS-armv7hl", os.Getenv("HOME")),
fmt.Sprintf("MER_SSH_SHARED_HOME=%v", os.Getenv("HOME")),
fmt.Sprintf("MER_SSH_SHARED_SRC=%v", utils.MustGoPath()),
"MER_SSH_SHARED_TARGET=/opt/SailfishOS/mersdk/targets",
"MER_SSH_TARGET_NAME=SailfishOS-armv7hl",
"MER_SSH_USERNAME=mersdk",
}
case "asteroid":
case "rpi1":
cmd.Args = append(cmd.Args, []string{"-spec", "devices/linux-rasp-pi-g++"}...)
case "rpi2":
cmd.Args = append(cmd.Args, []string{"-spec", "devices/linux-rasp-pi2-g++"}...)
case "rpi3":
cmd.Args = append(cmd.Args, []string{"-spec", "devices/linux-rpi3-g++"}...)
}
if target == "android" && runtime.GOOS == "windows" {
//TODO: -->
utils.SaveExec(filepath.Join(cmd.Dir, "qmake.bat"), fmt.Sprintf("set ANDROID_NDK_ROOT=%v\r\n%v", utils.ANDROID_NDK_DIR(), strings.Join(cmd.Args, " ")))
cmd = exec.Command(".\\qmake.bat")
cmd.Dir = path
utils.RunCmdOptional(cmd, fmt.Sprintf("run qmake for %v on %v", target, runtime.GOOS))
utils.RemoveAll(filepath.Join(cmd.Dir, "qmake.bat"))
//<--
} else {
utils.RunCmdOptional(cmd, fmt.Sprintf("run qmake for %v on %v", target, runtime.GOOS))
}
utils.RemoveAll(filepath.Join(path, "..", fmt.Sprintf("%v.pro", strings.ToLower(module))))
utils.RemoveAll(filepath.Join(path, ".qmake.stash"))
switch target {
case "darwin":
case "windows":
for _, suf := range []string{"_plugin_import", "_qml_plugin_import"} {
pPath := filepath.Join(path, fmt.Sprintf("%v%v.cpp", strings.ToLower(module), suf))
if (utils.QT_MXE_STATIC() || utils.QT_MSYS2_STATIC()) && utils.ExistsFile(pPath) {
if content := utils.Load(pPath); !strings.Contains(content, "+build windows") {
utils.Save(pPath, "// +build windows\n"+content)
}
}
if mode == MOC || mode == RCC || !(utils.QT_MXE_STATIC() || utils.QT_MSYS2_STATIC()) {
utils.RemoveAll(pPath)
}
}
for _, n := range []string{"Makefile", "Makefile.Debug", "release", "debug"} {
utils.RemoveAll(filepath.Join(path, n))
}
case "linux":
case "ios", "ios-simulator":
for _, suf := range []string{"_plugin_import", "_qml_plugin_import"} {
pPath := filepath.Join(path, fmt.Sprintf("%v%v.cpp", strings.ToLower(module), suf))
if utils.QT_VERSION_MAJOR() == "5.9" && utils.ExistsFile(pPath) {
if content := utils.Load(pPath); !strings.Contains(content, "+build ios") {
utils.Save(pPath, "// +build ios\n"+utils.Load(pPath))
}
}
if utils.QT_VERSION_MAJOR() != "5.9" || mode == MOC || mode == RCC {
utils.RemoveAll(pPath)
}
}
for _, n := range []string{"Info.plist", "qt.conf"} {
utils.RemoveAll(filepath.Join(path, n))
}
utils.RemoveAll(filepath.Join(path, fmt.Sprintf("%v.xcodeproj", strings.ToLower(module))))
case "android":
utils.RemoveAll(filepath.Join(path, fmt.Sprintf("android-lib%v.so-deployment-settings.json", strings.ToLower(module))))
case "sailfish", "sailfish-emulator":
case "asteroid":
case "rpi1", "rpi2", "rpi3":
}
}
func createCgo(module, path, target string, mode int, ipkg string) string {
bb := new(bytes.Buffer)
defer bb.Reset()
guards := "// +build "
switch target {
case "darwin":
guards += "!ios"
case "android":
guards += "android"
case "ios", "ios-simulator":
guards += "ios"
case "sailfish", "sailfish-emulator":
guards += strings.Replace(target, "-", "_", -1)
case "asteroid":
guards += target
case "rpi1", "rpi2", "rpi3":
guards += target
}
switch mode {
case NONE:
if len(guards) > 10 {
guards += ","
}
guards += "!minimal"
case MINIMAL:
if len(guards) > 10 {
guards += ","
}
guards += "minimal"
}
if len(guards) > 10 {
bb.WriteString(guards + "\n\n")
}
pkg := strings.ToLower(module)
if mode == MOC {
pkg = ipkg
}
fmt.Fprintf(bb, "package %v\n\n/*\n", pkg)
//
file := "Makefile"
if target == "windows" {
file += ".Release"
}
content := utils.Load(filepath.Join(path, file))
for _, l := range strings.Split(content, "\n") {
switch {
case strings.HasPrefix(l, "CFLAGS"):
fmt.Fprintf(bb, "#cgo CFLAGS: %v\n", strings.Split(l, " = ")[1])
case strings.HasPrefix(l, "CXXFLAGS"), strings.HasPrefix(l, "INCPATH"):
fmt.Fprintf(bb, "#cgo CXXFLAGS: %v\n", strings.Split(l, " = ")[1])
case strings.HasPrefix(l, "LFLAGS"), strings.HasPrefix(l, "LIBS"):
if target == "windows" && !(utils.QT_MXE_STATIC() || utils.QT_MSYS2_STATIC()) {
pFix := []string{
filepath.Join(utils.QT_DIR(), utils.QT_VERSION_MAJOR(), "mingw53_32"),
filepath.Join(utils.QT_MXE_DIR(), "usr", utils.QT_MXE_TRIPLET(), "qt5"),
utils.QT_MSYS2_DIR(),
}
for _, pFix := range pFix {
pFix = strings.Replace(filepath.Join(pFix, "lib", "lib"), "\\", "/", -1)
if strings.Contains(l, pFix) {
var cleaned []string
for _, s := range strings.Split(l, " ") {
if strings.HasPrefix(s, pFix) && (strings.HasSuffix(s, ".a") || strings.HasSuffix(s, ".dll")) {
s = strings.Replace(s, pFix, "-l", -1)
s = strings.TrimSuffix(s, ".a")
s = strings.TrimSuffix(s, ".dll")
}
cleaned = append(cleaned, s)
}
l = strings.Join(cleaned, " ")
}
}
}
fmt.Fprintf(bb, "#cgo LDFLAGS: %v\n", strings.Split(l, " = ")[1])
}
}
switch target {
case "android":
fmt.Fprint(bb, "#cgo LDFLAGS: -Wl,--allow-shlib-undefined\n")
case "windows":
fmt.Fprint(bb, "#cgo LDFLAGS: -Wl,--allow-multiple-definition\n")
}
fmt.Fprint(bb, "*/\nimport \"C\"\n")
out, err := format.Source(bb.Bytes())
if err != nil {
utils.Log.WithError(err).Panicln("failed to format:", module)
}
tmp := string(out)
switch target {
case "darwin":
tmp = strings.Replace(tmp, "$(EXPORT_ARCH_ARGS)", "-arch x86_64", -1)
case "ios":
tmp = strings.Replace(tmp, "$(EXPORT_ARCH_ARGS)", "-arch arm64", -1)
tmp = strings.Replace(tmp, "$(EXPORT_QMAKE_XARCH_CFLAGS)", "", -1)
tmp = strings.Replace(tmp, "$(EXPORT_QMAKE_XARCH_LFLAGS)", "", -1)
case "ios-simulator":
tmp = strings.Replace(tmp, "$(EXPORT_ARCH_ARGS)", "-arch x86_64", -1)
tmp = strings.Replace(tmp, "$(EXPORT_QMAKE_XARCH_CFLAGS)", "", -1)
tmp = strings.Replace(tmp, "$(EXPORT_QMAKE_XARCH_LFLAGS)", "", -1)
case "android":
tmp = strings.Replace(tmp, fmt.Sprintf("-Wl,-soname,lib%v.so", strings.ToLower(module)), "", -1)
tmp = strings.Replace(tmp, "-shared", "", -1)
}
for _, variable := range []string{"DEFINES", "SUBLIBS", "EXPORT_QMAKE_XARCH_CFLAGS", "EXPORT_QMAKE_XARCH_LFLAGS", "EXPORT_ARCH_ARGS", "-fvisibility=hidden", "-fembed-bitcode"} {
for _, l := range strings.Split(content, "\n") {
if strings.HasPrefix(l, variable+" ") {
if strings.Contains(l, "-DQT_TESTCASE_BUILDDIR") {
l = strings.Split(l, "-DQT_TESTCASE_BUILDDIR")[0]
}
tmp = strings.Replace(tmp, fmt.Sprintf("$(%v)", variable), strings.Split(l, " = ")[1], -1)
}
}
tmp = strings.Replace(tmp, fmt.Sprintf("$(%v)", variable), "", -1)
tmp = strings.Replace(tmp, variable, "", -1)
}
tmp = strings.Replace(tmp, "\\", "/", -1)
if module == "build_ios" {
return tmp
}
for _, file := range cgoFileNames(module, path, target, mode) {
switch target {
case "darwin":
for _, lib := range []string{"WebKitWidgets", "WebKit"} {
tmp = strings.Replace(tmp, "-lQt5"+lib, "-framework Qt"+lib, -1)
}
case "windows":
if utils.QT_MSYS2() && utils.QT_MSYS2_ARCH() == "amd64" {
tmp = strings.Replace(tmp, " -Wa,-mbig-obj ", " ", -1)
}
if (utils.QT_MSYS2() && utils.QT_MSYS2_ARCH() == "amd64") || utils.QT_MXE_ARCH() == "amd64" {
tmp = strings.Replace(tmp, " -Wl,-s ", " ", -1)
}
case "ios":
if strings.HasSuffix(file, "darwin_arm.go") {
tmp = strings.Replace(tmp, "arm64", "armv7", -1)
}
case "ios-simulator":
if strings.HasSuffix(file, "darwin_386.go") {
tmp = strings.Replace(tmp, "x86_64", "i386", -1)
}
}
utils.Save(filepath.Join(path, file), tmp)
}
return ""
}
func cgoFileNames(module, path, target string, mode int) []string {
var pFix string
switch mode {
case RCC:
pFix = "rcc_"
case MOC:
pFix = "moc_"
case MINIMAL:
pFix = "minimal_"
}
var sFixes []string
switch target {
case "darwin":
sFixes = []string{"darwin_amd64"}
case "linux":
sFixes = []string{"linux_amd64"}
case "windows":
if utils.QT_MXE_ARCH() == "amd64" || (utils.QT_MSYS2() && utils.QT_MSYS2_ARCH() == "amd64") {
sFixes = []string{"windows_amd64"}
} else {
sFixes = []string{"windows_386"}
}
case "android":
sFixes = []string{"linux_arm"}
case "ios":
sFixes = []string{"darwin_arm64", "darwin_arm"}
case "ios-simulator":
sFixes = []string{"darwin_amd64", "darwin_386"}
case "sailfish":
sFixes = []string{"linux_arm"}
case "sailfish-emulator":
sFixes = []string{"linux_386"}
case "asteroid":
sFixes = []string{"linux_arm"}
case "rpi1", "rpi2", "rpi3":
sFixes = []string{"linux_arm"}
}
var o []string
for _, sFix := range sFixes {
o = append(o, fmt.Sprintf("%vcgo_%v_%v.go", pFix, strings.Replace(target, "-", "_", -1), sFix))
}
return o
}