diff --git a/.gitignore b/.gitignore index 627ca78..3a53cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ aminal aminal.exe *.syso -.idea \ No newline at end of file +.idea +tmp/ \ No newline at end of file diff --git a/Makefile b/Makefile index 9749f29..349d864 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,11 @@ SHELL := /bin/bash BINARY := aminal FONTPATH := ./gui/packed-fonts +TMPDIR := ./tmp +VERSION_MAJOR := 0 +VERSION_MINOR := 9 +VERSION_PATCH := 0 +VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH} .PHONY: build build: @@ -50,6 +55,22 @@ build-windows: windres -o aminal.syso aminal.rc go build -o ${BINARY}-windows-amd64.exe +.PHONY: launcher-windows +launcher-windows: build-windows + if exist "${TMPDIR}\launcher-src" rmdir /S /Q "${TMPDIR}\launcher-src" + xcopy "windows\launcher\*.*" "${TMPDIR}\launcher-src" /K /H /Y /Q /I + powershell -Command "(gc ${TMPDIR}\launcher-src\versioninfo.json) -replace 'VERSION_MAJOR', '${VERSION_MAJOR}' | Out-File -Encoding default ${TMPDIR}\launcher-src\versioninfo.json" + powershell -Command "(gc ${TMPDIR}\launcher-src\versioninfo.json) -replace 'VERSION_MINOR', '${VERSION_MINOR}' | Out-File -Encoding default ${TMPDIR}\launcher-src\versioninfo.json" + powershell -Command "(gc ${TMPDIR}\launcher-src\versioninfo.json) -replace 'VERSION_PATCH', '${VERSION_PATCH}' | Out-File -Encoding default ${TMPDIR}\launcher-src\versioninfo.json" + powershell -Command "(gc ${TMPDIR}\launcher-src\versioninfo.json) -replace 'VERSION', '${VERSION}' | Out-File -Encoding default ${TMPDIR}\launcher-src\versioninfo.json" + powershell -Command "(gc ${TMPDIR}\launcher-src\versioninfo.json) -replace 'YEAR', (Get-Date -UFormat '%Y') | Out-File -Encoding default ${TMPDIR}\launcher-src\versioninfo.json" + copy aminal.ico "${TMPDIR}\launcher-src" /Y + go generate "${TMPDIR}\launcher-src" + if exist "${TMPDIR}\launcher" rmdir /S /Q "${TMPDIR}\launcher" + mkdir "${TMPDIR}\launcher\Versions\${VERSION}" + go build -o "${TMPDIR}\launcher\${BINARY}.exe" -ldflags "-H windowsgui" "${TMPDIR}\launcher-src" + copy ${BINARY}-windows-amd64.exe "${TMPDIR}\launcher\Versions\${VERSION}\${BINARY}.exe" /Y + .PHONY: build-darwin-native-travis build-darwin-native-travis: mkdir -p bin/darwin diff --git a/windows/launcher/launcher.go b/windows/launcher/launcher.go new file mode 100644 index 0000000..c001126 --- /dev/null +++ b/windows/launcher/launcher.go @@ -0,0 +1,123 @@ +//go:generate goversioninfo -icon=aminal.ico + +/* +Looks at directory "Versions" next to this executable. Finds the latest version +and runs the executable with the same name as this executable in that directory. +Eg.: + Aminal.exe (=launcher.exe) + Versions/ + 1.0.0/ + Aminal.exe + 1.0.1 + Aminal.exe +-> Launches Versions/1.0.1/Aminal.exe. +*/ + +package main + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "sort" + "strconv" + "strings" + "github.com/liamg/aminal/windows/winutil" +) + +type Version struct { + number [3]int + name string +} +type Versions []Version + +func main() { + executable, err := winutil.GetExecutablePath() + check(err) + executableDir, executableName := filepath.Split(executable) + versionsDir := filepath.Join(executableDir, "Versions") + latestVersion, err := getLatestVersion(versionsDir) + check(err) + target := filepath.Join(versionsDir, latestVersion, executableName) + cmd := exec.Command(target, os.Args[1:]...) + check(cmd.Start()) +} + +func getLatestVersion(versionsDir string) (string, error) { + potentialVersions, err := ioutil.ReadDir(versionsDir) + if err != nil { + return "", err + } + var versions Versions + for _, file := range potentialVersions { + if !file.IsDir() { + continue + } + version, err := parseVersionString(file.Name()) + if err != nil { + continue + } + versions = append(versions, version) + } + if len(versions) == 0 { + errMsg := fmt.Sprintf("No valid version in %s.", versionsDir) + return "", errors.New(errMsg) + } + sort.Sort(versions) + return versions[len(versions)-1].String(), nil +} + +func parseVersionString(version string) (Version, error) { + var result Version + result.name = version + err := error(nil) + version = strings.TrimSuffix(version, "-SNAPSHOT") + parts := strings.Split(version, ".") + if len(parts) != len(result.number) { + err = errors.New("Wrong number of parts.") + } else { + for i, partStr := range parts { + result.number[i], err = strconv.Atoi(partStr) + if err != nil { + break + } + } + } + return result, err +} + +func (arr Versions) Len() int { + return len(arr) +} + +func (arr Versions) Less(i, j int) bool { + for k, left := range arr[i].number { + right := arr[j].number[k] + if left > right { + return false + } else if left < right { + return true + } + } + fmt.Printf("%s < %s\n", arr[i], arr[j]) + return true +} + +func (arr Versions) Swap(i, j int) { + tmp := arr[j] + arr[j] = arr[i] + arr[i] = tmp +} + +func (version Version) String() string { + return version.name +} + +func check(e error) { + if e != nil { + panic(e) + } +} \ No newline at end of file diff --git a/windows/launcher/versioninfo.json b/windows/launcher/versioninfo.json new file mode 100644 index 0000000..af8c654 --- /dev/null +++ b/windows/launcher/versioninfo.json @@ -0,0 +1,35 @@ +{ + "FixedFileInfo": + { + "FileVersion": { + "Major": VERSION_MAJOR, + "Minor": VERSION_MINOR, + "Patch": VERSION_PATCH, + "Build": 0 + }, + "ProductVersion": { + "Major": VERSION_MAJOR, + "Minor": VERSION_MINOR, + "Patch": VERSION_PATCH, + "Build": 0 + }, + "FileFlagsMask": "3f", + "FileFlags ": "00", + "FileOS": "040004", + "FileType": "01", + "FileSubType": "00" + }, + "StringFileInfo": + { + "ProductName": "Aminal", + "ProductVersion": "VERSION", + "LegalCopyright": "Copyright 2018-YEAR Liam Galvin" + }, + "VarFileInfo": + { + "Translation": { + "LangID": "0409", + "CharsetID": "04B0" + } + } +} \ No newline at end of file diff --git a/windows/winutil/winutil.go b/windows/winutil/winutil.go new file mode 100644 index 0000000..8b31fa4 --- /dev/null +++ b/windows/winutil/winutil.go @@ -0,0 +1,25 @@ +package winutil + +import ( + "golang.org/x/sys/windows" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = windows.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +func GetExecutablePath() (string, error) { + var n uint32 + b := make([]uint16, windows.MAX_PATH) + size := uint32(len(b)) + bPtr := uintptr(unsafe.Pointer(&b[0])) + r0, _, e1 := getModuleFileNameProc.Call(0, bPtr, uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} \ No newline at end of file