From 78c1178a62441cb932fae123288175b19722d8f9 Mon Sep 17 00:00:00 2001 From: mappu Date: Thu, 6 Feb 2025 18:55:45 +1300 Subject: [PATCH 01/11] rcc: add -Qt6 flag --- cmd/miqt-rcc/main.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/miqt-rcc/main.go b/cmd/miqt-rcc/main.go index c9fe8ffe..0535adbe 100644 --- a/cmd/miqt-rcc/main.go +++ b/cmd/miqt-rcc/main.go @@ -21,6 +21,7 @@ func main() { 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") + useQt6 := flag.Bool("Qt6", false, "Use Qt 6 instead of Qt 5") flag.Parse() // Check if input file exists @@ -52,6 +53,11 @@ func main() { // Create Go file that loads the resource + miqtImport := `"github.com/mappu/miqt/qt"` + if *useQt6 { + miqtImport = `qt "github.com/mappu/miqt/qt6"` + } + goSrcData := ` package ` + *packageName + ` @@ -60,7 +66,7 @@ package ` + *packageName + ` import ( "embed" - "github.com/mappu/miqt/qt" + ` + miqtImport + ` ) //go:embed ` + *outputRcc + ` From adc3f08d4dd53e6f8a7564428560f0860d91b5f6 Mon Sep 17 00:00:00 2001 From: mappu Date: Thu, 6 Feb 2025 19:16:56 +1300 Subject: [PATCH 02/11] rcc: infer embed data rcc filename from go file instead of qrc file --- cmd/miqt-rcc/main.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/miqt-rcc/main.go b/cmd/miqt-rcc/main.go index 0535adbe..cfdcb91c 100644 --- a/cmd/miqt-rcc/main.go +++ b/cmd/miqt-rcc/main.go @@ -17,8 +17,8 @@ func main() { // 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") + outputRcc := flag.String("OutputRcc", "", "(Optional) Path to .rcc output file. If omitted, inferred from the output Go 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") useQt6 := flag.Bool("Qt6", false, "Use Qt 6 instead of Qt 5") @@ -33,12 +33,13 @@ func main() { // 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` + } // Compile qrc to binary resource file From b1c757cd3c32e61a99507c425321a72a45f58f02 Mon Sep 17 00:00:00 2001 From: mappu Date: Thu, 6 Feb 2025 19:17:13 +1300 Subject: [PATCH 03/11] rcc: allow specifying a custom rcc binary --- cmd/miqt-rcc/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/miqt-rcc/main.go b/cmd/miqt-rcc/main.go index cfdcb91c..e0e64389 100644 --- a/cmd/miqt-rcc/main.go +++ b/cmd/miqt-rcc/main.go @@ -22,6 +22,7 @@ func main() { 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") useQt6 := flag.Bool("Qt6", false, "Use Qt 6 instead of Qt 5") + rccBinary := flag.String("RccBinary", "rcc", "(Optional) Custom path to the Qt rcc program") flag.Parse() // Check if input file exists @@ -43,7 +44,7 @@ func main() { // 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() From 0da95c13f8f0d28f2e06451f2638565fa0e5ee5a Mon Sep 17 00:00:00 2001 From: mappu Date: Thu, 6 Feb 2025 19:17:44 +1300 Subject: [PATCH 04/11] rcc: use simpler error handling with a wrapper main --- cmd/miqt-rcc/main.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cmd/miqt-rcc/main.go b/cmd/miqt-rcc/main.go index e0e64389..5d8b972d 100644 --- a/cmd/miqt-rcc/main.go +++ b/cmd/miqt-rcc/main.go @@ -12,7 +12,7 @@ import ( "strings" ) -func main() { +func RccExec() error { // Parse arguments @@ -28,8 +28,7 @@ func main() { // 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 @@ -49,8 +48,7 @@ func main() { rccCmd.Stdout = os.Stdout 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 @@ -83,12 +81,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) } From d8cb5494a0eb6f4729c549dc798065b3ed5deddd Mon Sep 17 00:00:00 2001 From: mappu Date: Thu, 6 Feb 2025 19:18:03 +1300 Subject: [PATCH 05/11] rcc: use a relative path in the go:embed line --- cmd/miqt-rcc/main.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmd/miqt-rcc/main.go b/cmd/miqt-rcc/main.go index 5d8b972d..b2c48fe3 100644 --- a/cmd/miqt-rcc/main.go +++ b/cmd/miqt-rcc/main.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "strings" ) @@ -41,12 +42,19 @@ func RccExec() error { *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(*rccBinary, `--binary`, `-o`, *outputRcc, *input) rccCmd.Stderr = os.Stderr rccCmd.Stdout = os.Stdout - err := rccCmd.Run() + err = rccCmd.Run() if err != nil { return err } @@ -69,7 +77,7 @@ import ( ` + miqtImport + ` ) -//go:embed ` + *outputRcc + ` +//go:embed ` + embedPath + ` var ` + *variableName + ` []byte func init() { From 730153c7f29cf17bb82043d803a0a9fba15cf933 Mon Sep 17 00:00:00 2001 From: mappu Date: Thu, 6 Feb 2025 19:18:37 +1300 Subject: [PATCH 06/11] rcc: construct a more accurate go:generate line for rebuilding --- cmd/miqt-rcc/main.go | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/cmd/miqt-rcc/main.go b/cmd/miqt-rcc/main.go index b2c48fe3..7e35f5ce 100644 --- a/cmd/miqt-rcc/main.go +++ b/cmd/miqt-rcc/main.go @@ -10,9 +10,17 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" ) +const ( + DefaultPackageName string = "main" + DefaultVariableName string = "_resourceRcc" + DefaultIsQt6 bool = false + DefaultRccBinary string = "rcc" +) + func RccExec() error { // Parse arguments @@ -20,10 +28,10 @@ func RccExec() error { input := flag.String("Input", "", "Path to .qrc input file") outputGo := flag.String("OutputGo", "", "(Optional) Path to .go output file. If omitted, interred from the input file path") outputRcc := flag.String("OutputRcc", "", "(Optional) Path to .rcc output file. If omitted, inferred from the output Go 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") - useQt6 := flag.Bool("Qt6", false, "Use Qt 6 instead of Qt 5") - rccBinary := flag.String("RccBinary", "rcc", "(Optional) Custom path to the Qt rcc program") + 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 @@ -59,17 +67,38 @@ func RccExec() error { 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" From 8b20ff1070a58e2272cdaf431ba818c7c63af978 Mon Sep 17 00:00:00 2001 From: mappu Date: Fri, 7 Feb 2025 17:51:33 +1300 Subject: [PATCH 07/11] rcc: add an integration test --- cmd/miqt-rcc/integration_test.go | 145 +++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 cmd/miqt-rcc/integration_test.go diff --git a/cmd/miqt-rcc/integration_test.go b/cmd/miqt-rcc/integration_test.go new file mode 100644 index 00000000..6c3ccbd6 --- /dev/null +++ b/cmd/miqt-rcc/integration_test.go @@ -0,0 +1,145 @@ +package main + +import ( + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "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( + ` + + sample.data + +`), 0644) + if err != nil { + t.Fatal(err) + } + + // Build miqt-rcc and add it to our fake tools path + + toolsDir := t.TempDir() + + 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) { + + // Verify that `go generate` works + + regenCmd := exec.Command(`go`, `generate`) + regenCmd.Env = []string{"PATH=" + toolsDir + ":" + os.Getenv("PATH")} + 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 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") + } + }) +} From 7bacd37161428c8417589066d0502093308532af Mon Sep 17 00:00:00 2001 From: mappu Date: Fri, 7 Feb 2025 17:58:12 +1300 Subject: [PATCH 08/11] uic: add -Qt6 argument --- cmd/miqt-uic/main.go | 7 ++++++- cmd/miqt-uic/ui2go.go | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cmd/miqt-uic/main.go b/cmd/miqt-uic/main.go index 6059ba46..a683b15f 100644 --- a/cmd/miqt-uic/main.go +++ b/cmd/miqt-uic/main.go @@ -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) } diff --git a/cmd/miqt-uic/ui2go.go b/cmd/miqt-uic/ui2go.go index 1473b451..69089e93 100644 --- a/cmd/miqt-uic/ui2go.go +++ b/cmd/miqt-uic/ui2go.go @@ -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 { From dfbe5a08356c6b0007b13662ca2fa16b0efd195d Mon Sep 17 00:00:00 2001 From: mappu Date: Fri, 7 Feb 2025 18:01:45 +1300 Subject: [PATCH 09/11] rcc, uic: add the -Help output to package README.md files --- cmd/miqt-rcc/README.md | 24 ++++++++++++++++++++++++ cmd/miqt-uic/README.md | 16 +++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 cmd/miqt-rcc/README.md diff --git a/cmd/miqt-rcc/README.md b/cmd/miqt-rcc/README.md new file mode 100644 index 00000000..b11cdc87 --- /dev/null +++ b/cmd/miqt-rcc/README.md @@ -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") +``` diff --git a/cmd/miqt-uic/README.md b/cmd/miqt-uic/README.md index 5a80143d..427de7a3 100644 --- a/cmd/miqt-uic/README.md +++ b/cmd/miqt-uic/README.md @@ -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 From d8c52499c196646fe58923d6209043a82d820a1e Mon Sep 17 00:00:00 2001 From: mappu Date: Fri, 7 Feb 2025 19:11:25 +1300 Subject: [PATCH 10/11] rcc/test: support PATH separator difference on Windows --- cmd/miqt-rcc/integration_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/miqt-rcc/integration_test.go b/cmd/miqt-rcc/integration_test.go index 6c3ccbd6..fd495a08 100644 --- a/cmd/miqt-rcc/integration_test.go +++ b/cmd/miqt-rcc/integration_test.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" ) @@ -60,6 +61,10 @@ replace github.com/mappu/miqt => ` + filepath.Clean(RccDir+`/../../`) + ` // 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) { @@ -116,7 +121,7 @@ replace github.com/mappu/miqt => ` + filepath.Clean(RccDir+`/../../`) + ` // Verify that `go generate` works regenCmd := exec.Command(`go`, `generate`) - regenCmd.Env = []string{"PATH=" + toolsDir + ":" + os.Getenv("PATH")} + regenCmd.Env = []string{newPathEnv} regenCmd.Dir = td regenCmd.Stderr = os.Stderr err = regenCmd.Run() From 5eed43069e5d41c1e0ac825496b4d076bc92d34b Mon Sep 17 00:00:00 2001 From: mappu Date: Fri, 7 Feb 2025 19:11:30 +1300 Subject: [PATCH 11/11] rcc/test: verify mtime changes --- cmd/miqt-rcc/integration_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmd/miqt-rcc/integration_test.go b/cmd/miqt-rcc/integration_test.go index fd495a08..48225883 100644 --- a/cmd/miqt-rcc/integration_test.go +++ b/cmd/miqt-rcc/integration_test.go @@ -118,6 +118,13 @@ replace github.com/mappu/miqt => ` + filepath.Clean(RccDir+`/../../`) + ` 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`) @@ -134,6 +141,17 @@ replace github.com/mappu/miqt => ` + filepath.Clean(RccDir+`/../../`) + ` 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") {