Merge pull request #167 from mappu/miqt-uic-rcc-updates

uic, rcc: Add -Qt6 flag, fix relative paths for embedding files
This commit is contained in:
mappu 2025-02-08 10:37:30 +13:00 committed by GitHub
commit 12df341a57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 291 additions and 23 deletions

24
cmd/miqt-rcc/README.md Normal file
View File

@ -0,0 +1,24 @@
# miqt-rcc
The miqt-rcc program generates the necessary wrappers to use a Qt Designer `.qrc`
resource pack in MIQT `.go` files.
## Usage
```
Usage of ./miqt-rcc:
-Input string
Path to .qrc input file
-OutputGo string
(Optional) Path to .go output file. If omitted, interred from the input file path
-OutputRcc string
(Optional) Path to .rcc output file. If omitted, inferred from the output Go file path
-Package string
Package to use in generated Go files (default "main")
-Qt6
Use Qt 6 instead of Qt 5
-RccBinary string
(Optional) Custom path to the Qt rcc program (default "rcc")
-VariableName string
Temporary global variable name for loading embedded data (default "_resourceRcc")
```

View File

@ -0,0 +1,168 @@
package main
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestRcc(t *testing.T) {
// Temporary directory
td := t.TempDir() // The go test package auto cleans this up
// Simulate working out of a Go module that has this miqt checkout as a
// local dependency
RccDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
fakeGoMod := `module miqt-rcc-integration-test
go 1.19
require (
github.com/mappu/miqt v1.0.0
)
replace github.com/mappu/miqt => ` + filepath.Clean(RccDir+`/../../`) + `
`
err = ioutil.WriteFile(filepath.Join(td, `go.mod`), []byte(fakeGoMod), 0644)
if err != nil {
t.Fatal(err)
}
// Add some test content
err = ioutil.WriteFile(filepath.Join(td, "sample.data"), []byte("the quick brown fox jumps over the lazy dog"), 0644)
if err != nil {
t.Fatal(err)
}
// Make a .qrc that includes this file
err = ioutil.WriteFile(filepath.Join(td, "resources.qrc"), []byte(
`<RCC>
<qresource prefix="/">
<file>sample.data</file>
</qresource>
</RCC>`), 0644)
if err != nil {
t.Fatal(err)
}
// Build miqt-rcc and add it to our fake tools path
toolsDir := t.TempDir()
newPathEnv := "PATH=" + toolsDir + `:` + os.Getenv(`PATH`)
if runtime.GOOS == "windows" { // uses a different separator
newPathEnv = "PATH=" + toolsDir + `;` + os.Getenv(`PATH`)
}
t.Run("Compile miqt-rcc", func(t *testing.T) {
buildCmd := exec.Command(`go`, `build`, `-o`, filepath.Join(toolsDir, `miqt-rcc`))
buildCmd.Stderr = os.Stderr
buildCmd.Stdout = os.Stdout
err = buildCmd.Run()
if err != nil {
t.Fatal(err)
}
})
t.Run("Initial build", func(t *testing.T) {
// Run miqt-rcc
rccCmd := exec.Command(filepath.Join(toolsDir, `miqt-rcc`), `-Input`, filepath.Join(td, `resources.qrc`), `-OutputGo`, filepath.Join(td, `resources.go`))
rccCmd.Stderr = os.Stderr
err = rccCmd.Run()
if err != nil {
t.Fatal(err)
}
// Verify that files are created
_, err = os.Stat(filepath.Join(td, `resources.rcc`))
if err != nil {
t.Errorf("resources.rcc should exist: %v", err)
}
goResult, err := ioutil.ReadFile(filepath.Join(td, `resources.go`))
if err != nil {
t.Fatal(err)
}
// Verify the go:embed line accurately used a relative path
if !strings.Contains(string(goResult), "//go:embed resources.rcc\n") {
t.Fatal("missing expected embed line")
}
// Verify the go:generate line matches our expectation
expect := `//go:generate miqt-rcc -Input "` + filepath.Join(td, `resources.qrc`) + `" -OutputGo "resources.go" -OutputRcc "resources.rcc"`
if !strings.Contains(string(goResult), expect) {
t.Fatal("missing expected generate line")
}
})
t.Run("Go generate", func(t *testing.T) {
// Check timestamp before generation
fiBefore, err := os.Stat(filepath.Join(td, `resources.go`))
if err != nil {
t.Fatal(err)
}
// Verify that `go generate` works
regenCmd := exec.Command(`go`, `generate`)
regenCmd.Env = []string{newPathEnv}
regenCmd.Dir = td
regenCmd.Stderr = os.Stderr
err = regenCmd.Run()
if err != nil {
t.Fatal(err)
}
goResult, err := ioutil.ReadFile(filepath.Join(td, `resources.go`))
if err != nil {
t.Fatal(err)
}
// Verify that the resources.go file was actually replaced
fiAfter, err := os.Stat(filepath.Join(td, `resources.go`))
if err != nil {
t.Fatal(err)
}
if !fiAfter.ModTime().After(fiBefore.ModTime()) {
t.Errorf("expected mtime %v to be after original mtime %v", fiAfter.ModTime(), fiBefore.ModTime())
}
// Verify the go:embed line accurately used a relative path
if !strings.Contains(string(goResult), "//go:embed resources.rcc\n") {
t.Fatal("missing expected embed line")
}
// Verify the go:generate line matches our expectation
expect := `//go:generate miqt-rcc -Input "` + filepath.Join(td, `resources.qrc`) + `" -OutputGo "resources.go" -OutputRcc "resources.rcc"`
if !strings.Contains(string(goResult), expect) {
t.Fatal("missing expected generate line")
}
})
}

View File

@ -9,61 +9,104 @@ import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
func main() {
const (
DefaultPackageName string = "main"
DefaultVariableName string = "_resourceRcc"
DefaultIsQt6 bool = false
DefaultRccBinary string = "rcc"
)
func RccExec() error {
// Parse arguments
input := flag.String("Input", "", "Path to .qrc input file")
outputRcc := flag.String("OutputRcc", "", "(Optional) Path to .rcc output file. If omitted, inferred from the input file path")
outputGo := flag.String("OutputGo", "", "(Optional) Path to .go output file. If omitted, interred from the input file path")
packageName := flag.String("Package", "main", "Package to use in generated Go files")
variableName := flag.String("VariableName", "_resourceRcc", "Temporary global variable name for loading embedded data")
outputRcc := flag.String("OutputRcc", "", "(Optional) Path to .rcc output file. If omitted, inferred from the output Go file path")
packageName := flag.String("Package", DefaultPackageName, "Package to use in generated Go files")
variableName := flag.String("VariableName", DefaultVariableName, "Temporary global variable name for loading embedded data")
useQt6 := flag.Bool("Qt6", DefaultIsQt6, "Use Qt 6 instead of Qt 5")
rccBinary := flag.String("RccBinary", DefaultRccBinary, "(Optional) Custom path to the Qt rcc program")
flag.Parse()
// Check if input file exists
if _, err := os.Stat(*input); os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Input file '%s' not found\n", *input)
os.Exit(1)
return fmt.Errorf("Input file '%s' not found\n", *input)
}
// Fill in default output names, if not specified
if *outputRcc == "" {
*outputRcc = strings.TrimSuffix(*input, `.qrc`) + `.rcc`
}
if *outputGo == "" {
*outputGo = strings.TrimSuffix(*input, `.qrc`) + `.go`
}
if *outputRcc == "" {
// Base this on the outputGo filename, not the input filename
*outputRcc = strings.TrimSuffix(*outputGo, `.go`) + `.rcc`
}
// The rcc output path must be relative to the output go file path
embedPath, err := filepath.Rel(filepath.Dir(*outputGo), *outputRcc)
if err != nil {
return err
}
// Compile qrc to binary resource file
rccCmd := exec.Command(`rcc`, `--binary`, `-o`, *outputRcc, *input)
rccCmd := exec.Command(*rccBinary, `--binary`, `-o`, *outputRcc, *input)
rccCmd.Stderr = os.Stderr
rccCmd.Stdout = os.Stdout
err := rccCmd.Run()
err = rccCmd.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "rcc: %s\n", err.Error())
os.Exit(1)
return err
}
// Create Go file that loads the resource
// Figure out import statement
miqtImport := `"github.com/mappu/miqt/qt"`
if *useQt6 {
miqtImport = `qt "github.com/mappu/miqt/qt6"`
}
// Figure out regeneration command
generate := `miqt-rcc` +
` -Input ` + strconv.Quote(*input) +
` -OutputGo ` + strconv.Quote(filepath.Base(*outputGo)) +
` -OutputRcc ` + strconv.Quote(embedPath)
if *packageName != DefaultPackageName {
generate += ` -Package ` + strconv.Quote(*packageName)
}
if *variableName != DefaultVariableName {
generate += ` -Variable ` + strconv.Quote(*variableName)
}
if *useQt6 != DefaultIsQt6 {
generate += ` -Qt6`
}
if *rccBinary != DefaultRccBinary {
generate += ` -RccBinary ` + strconv.Quote(*rccBinary)
}
// Create Go file that loads the resource
goSrcData := `
package ` + *packageName + `
//go:generate miqt-rcc "` + strings.Join(os.Args[1:], `" "`) + `"
//go:generate ` + generate + `
import (
"embed"
"github.com/mappu/miqt/qt"
` + miqtImport + `
)
//go:embed ` + *outputRcc + `
//go:embed ` + embedPath + `
var ` + *variableName + ` []byte
func init() {
@ -75,12 +118,21 @@ func init() {
outputData, err := format.Source([]byte(goSrcData))
if err != nil {
panic(err)
return err
}
err = ioutil.WriteFile(*outputGo, outputData, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "Writing to '%s': %s\n", *outputGo, err.Error())
return fmt.Errorf("Writing to '%s': %w", *outputGo, err)
}
return nil
}
func main() {
err := RccExec()
if err != nil {
fmt.Fprintf(os.Stderr, "rcc: %s\n", err.Error())
os.Exit(1)
}

View File

@ -2,7 +2,21 @@
The miqt-uic program compiles Qt Designer `.ui` files into MIQT `.go` files.
For usage information, see the `examples/uidesigner` folder.
## Usage
For example usage information, see the `examples/uidesigner` folder.
```
Usage of ./miqt-uic:
-InFile string
Input .ui file
-OutFile string
Output .go file, or - for stdout (default "-")
-Package string
Custom package name (default "main")
-Qt6
Use Qt 6 instead of Qt 5
```
## Architecture design

View File

@ -9,9 +9,14 @@ import (
"strings"
)
const (
DefaultIsQt6 = false
)
func main() {
inFile := flag.String("InFile", "", "Input .ui file")
outFile := flag.String("OutFile", "-", "Output .go file, or - for stdout")
useQt6 := flag.Bool("Qt6", DefaultIsQt6, "Use Qt 6 instead of Qt 5")
packageName := flag.String("Package", "main", "Custom package name")
flag.Parse()
@ -31,7 +36,7 @@ func main() {
panic(err)
}
gosrc, err := generate(*packageName, strings.Join(os.Args[1:], " "), parsed)
gosrc, err := generate(*packageName, strings.Join(os.Args[1:], " "), parsed, *useQt6)
if err != nil {
panic(err)
}

View File

@ -490,7 +490,7 @@ func generateWidget(w UiWidget, parentName string, parentClass string) (string,
return ret.String(), nil
}
func generate(packageName string, goGenerateArgs string, u UiFile) ([]byte, error) {
func generate(packageName string, goGenerateArgs string, u UiFile, useQt6 bool) ([]byte, error) {
ret := strings.Builder{}
@ -507,6 +507,11 @@ func generate(packageName string, goGenerateArgs string, u UiFile) ([]byte, erro
// Header
importPackage := `"github.com/mappu/miqt/qt"`
if useQt6 {
importPackage = `qt "github.com/mappu/miqt/qt6"`
}
ret.WriteString(`// Generated by miqt-uic. To update this file, edit the .ui file in
// Qt Designer, and then run 'go generate'.
//
@ -515,7 +520,7 @@ func generate(packageName string, goGenerateArgs string, u UiFile) ([]byte, erro
package ` + packageName + `
import (
"github.com/mappu/miqt/qt"
` + importPackage + `
)
type ` + u.Class + `Ui struct {