From c1a728dc2ffb69c5bbac55fdb29b97af2c819f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 25 Jul 2022 14:45:06 +0200 Subject: [PATCH] Version updates via Makefile Flamenco now no longer uses the Git tags + hash for the application version, but an explicit `VERSION` variable in the `Makefile`. After changing the `VERSION` variable in the `Makefile`, run `make update-version`. Not every part of Flamenco looks at this variable, though. Most importantly: the Blender add-on needs special handling, because that doesn't just take a version string but a tuple of integers. Running `make update-version` updates the add-on's `bl_info` dict with the new version. If the version has any `-blabla` suffix (like `3.0-beta0`) it will also set the `warning` field to explain that it's not a stable release. --- Makefile | 43 ++++++++++----- addon/flamenco/__init__.py | 1 + cmd/flamenco-manager/main.go | 1 + cmd/flamenco-worker/main.go | 1 + cmd/update-version/addon.go | 65 ++++++++++++++++++++++ cmd/update-version/linereplacer.go | 63 ++++++++++++++++++++++ cmd/update-version/main.go | 87 ++++++++++++++++++++++++++++++ cmd/update-version/makefile.go | 27 ++++++++++ internal/appinfo/appinfo.go | 11 ++-- 9 files changed, 283 insertions(+), 16 deletions(-) create mode 100644 cmd/update-version/addon.go create mode 100644 cmd/update-version/linereplacer.go create mode 100644 cmd/update-version/main.go create mode 100644 cmd/update-version/makefile.go diff --git a/Makefile b/Makefile index bba6f8ef..77187590 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,11 @@ PKG := git.blender.org/flamenco -VERSION := $(shell git describe --tags --dirty --always) -# Version used in the OpenAPI-generated code shouldn't contain the '-dirty' -# suffix. In the common development workflow, those files will always be dirty -# (because they're only committed after locally working, which means the -# implementation has already been written). -OAPI_VERSION := $(shell git describe --tags --always) -LDFLAGS := -X ${PKG}/internal/appinfo.ApplicationVersion=${VERSION} +# To update the version number in all the relevant places, update the VERSION +# variable below and run `make update-version`. +VERSION := 3.0-dev0 + +GITHASH := $(shell git describe --dirty --always) +LDFLAGS := -X ${PKG}/internal/appinfo.ApplicationVersion=${VERSION} -X ${PKG}/internal/appinfo.ApplicationGitHash=${GITHASH} BUILD_FLAGS = -ldflags="${LDFLAGS}" # Package name of the generated Python/JavaScript code for the Flamenco API. @@ -76,7 +75,10 @@ webapp-static: addon-packer ./addon-packer -filename ${WEB_STATIC}/flamenco3-addon.zip @echo "Web app has been installed into ${WEB_STATIC}" -generate: generate-go generate-py generate-js +generate: + $(MAKE) generate-go + $(MAKE) generate-py + $(MAKE) generate-js generate-go: go generate ./pkg/api/... @@ -99,10 +101,10 @@ generate-py: -g python \ -o addon/ \ --package-name "${PY_API_PKG_NAME}" \ - --http-user-agent "Flamenco/${OAPI_VERSION} (Blender add-on)" \ + --http-user-agent "Flamenco/${VERSION} (Blender add-on)" \ -p generateSourceCodeOnly=true \ -p projectName=Flamenco \ - -p packageVersion="${OAPI_VERSION}" > .openapi-generator-py.log + -p packageVersion="${VERSION}" > .openapi-generator-py.log # The generator outputs files so that we can write our own tests. We don't, # though, so it's better to just remove those placeholders. @@ -130,7 +132,7 @@ generate-js: -i pkg/api/flamenco-openapi.yaml \ -g javascript \ -o web/_tmp-manager-api-javascript \ - --http-user-agent "Flamenco/${OAPI_VERSION} / webbrowser" \ + --http-user-agent "Flamenco/${VERSION} / webbrowser" \ -p projectName=flamenco-manager \ -p projectVersion="0.0.0" \ -p apiPackage="${JS_API_PKG_NAME}" \ @@ -149,11 +151,26 @@ ifeq ($(OS),Windows_NT) git status --porcelain | grep '^ M web/app/src/manager-api' | cut -d' ' -f3 | xargs unix2dos --keepdate endif +.PHONY: +update-version: + @echo "--- Updating Flamenco version to ${VERSION}" + @echo "--- If this stops with exit status 42, it was already at that version." + @echo + go run ./cmd/update-version ${VERSION} + $(MAKE) generate-py + $(MAKE) generate-js + @echo + @echo 'File replacement done, commit with:' + @echo + @echo 'git commit -m "Bumped version to ${VERSION}" Makefile addon/flamenco/__init__.py' + @echo 'git tag -a -m "Tagged version ${VERSION}" v${VERSION}' + version: - @echo "OS : ${OS}" @echo "Package : ${PKG}" @echo "Version : ${VERSION}" - @echo "OAPI Version: ${OAPI_VERSION}" + @echo "Git Hash : ${GITHASH}" + @echo -n "GOOS : "; go env GOOS + @echo -n "GOARCH : "; go env GOARCH @echo @env | grep GO diff --git a/addon/flamenco/__init__.py b/addon/flamenco/__init__.py index 4396772a..d712c960 100644 --- a/addon/flamenco/__init__.py +++ b/addon/flamenco/__init__.py @@ -12,6 +12,7 @@ bl_info = { "doc_url": "https://www.flamenco.io/", "category": "System", "support": "COMMUNITY", + "warning": "This is version 3.0-dev0 of the add-on, which is not a stable release", } from pathlib import Path diff --git a/cmd/flamenco-manager/main.go b/cmd/flamenco-manager/main.go index ac4850ac..4866730a 100644 --- a/cmd/flamenco-manager/main.go +++ b/cmd/flamenco-manager/main.go @@ -72,6 +72,7 @@ func main() { log.Logger = log.Output(output) log.Info(). Str("version", appinfo.ApplicationVersion). + Str("git", appinfo.ApplicationGitHash). Str("os", runtime.GOOS). Str("arch", runtime.GOARCH). Msgf("starting %v", appinfo.ApplicationName) diff --git a/cmd/flamenco-worker/main.go b/cmd/flamenco-worker/main.go index 9ee593b6..58e8d3d8 100644 --- a/cmd/flamenco-worker/main.go +++ b/cmd/flamenco-worker/main.go @@ -61,6 +61,7 @@ func main() { log.Info(). Str("version", appinfo.ApplicationVersion). + Str("git", appinfo.ApplicationGitHash). Str("OS", runtime.GOOS). Str("ARCH", runtime.GOARCH). Int("pid", os.Getpid()). diff --git a/cmd/update-version/addon.go b/cmd/update-version/addon.go new file mode 100644 index 00000000..8018c25e --- /dev/null +++ b/cmd/update-version/addon.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/rs/zerolog/log" +) + +const addonVersionFile = "addon/flamenco/__init__.py" + +// updateAddon changes the version number in the Blender add-on. +// Returns whether the file actually changed. +func updateAddon() bool { + // The add-on needs a (x, y, z) tuple as version, and doesn't support suffixes + // like `-dev0` or `-beta3`. + splitOnDash := strings.SplitN(cliArgs.newVersion, "-", 2) + versionWithoutSuffix := splitOnDash[0] + + var warning string + if len(splitOnDash) >= 2 { + log.Warn().Msg("versions of form `x.y.z-something` will put a warning in the bl_info about the `-something`") + warning = fmt.Sprintf("This is version %s of the add-on, which is not a stable release", cliArgs.newVersion) + } + + versionParts := strings.Split(versionWithoutSuffix, ".") + var versionTuple string + switch len(versionParts) { + case 0: + log.Fatal().Str("versionWithoutSuffix", versionWithoutSuffix).Msg("no dot-separated version number found") + case 1: + log.Warn().Strs("versionParts", versionParts).Msg("only a major version found, may be the wrong syntax") + versionTuple = fmt.Sprintf("(%s, 0)", versionParts[0]) + case 2: + log.Debug().Strs("versionParts", versionParts).Msg("major.minor version found, this is expected") + versionTuple = fmt.Sprintf("(%s, %s)", versionParts[0], versionParts[1]) + case 3: + log.Debug().Strs("versionParts", versionParts).Msg("major.minor.micro version found, this is expected") + versionTuple = fmt.Sprintf("(%s, %s, %s)", versionParts[0], versionParts[1], versionParts[2]) + default: + log.Warn().Strs("versionParts", versionParts).Msg("more than three (major, minor, micro) version parts found, using only the first three") + versionTuple = fmt.Sprintf("(%s, %s, %s)", versionParts[0], versionParts[1], versionParts[2]) + } + + var blinfoOpened, blinfoClosed bool + replacer := func(line string) string { + switch { + case !blinfoOpened && strings.HasPrefix(line, "bl_info = {"): + blinfoOpened = true + case blinfoOpened && strings.HasPrefix(line, "}"): + blinfoClosed = true + case blinfoOpened && !blinfoClosed && strings.HasPrefix(line, " \"version\":"): + return fmt.Sprintf(" \"version\": %s,", versionTuple) + case blinfoOpened && !blinfoClosed && strings.HasPrefix(line, " \"warning\":"): + return fmt.Sprintf(" \"warning\": %q,", warning) + } + return line + } + + fileWasChanged, err := updateLines(addonVersionFile, replacer) + if err != nil { + log.Fatal().Err(err).Msg("error updating add-on") + } + return fileWasChanged +} diff --git a/cmd/update-version/linereplacer.go b/cmd/update-version/linereplacer.go new file mode 100644 index 00000000..8ccfdd7b --- /dev/null +++ b/cmd/update-version/linereplacer.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/rs/zerolog/log" +) + +// updateLines calls replacer() on each line of the given file, and replaces it +// with the returned value. +// Returns whether the file changed at all. +func updateLines(filename string, replacer func(string) string) (bool, error) { + logger := log.With().Str("filename", filename).Logger() + logger.Info().Msg("updating file") + + // Read the file contents: + input, err := os.ReadFile(filename) + if err != nil { + return false, fmt.Errorf("reading from %s: %w", filename, err) + } + + // Replace the lines: + anythingChanged := false + lines := strings.Split(string(input), "\n") + for idx := range lines { + replaced := replacer(lines[idx]) + if replaced == lines[idx] { + continue + } + + logger.Info(). + Str("old", strings.TrimSpace(lines[idx])). + Str("new", strings.TrimSpace(replaced)). + Msg("replacing line") + lines[idx] = replaced + anythingChanged = true + } + + if !anythingChanged { + logger.Info().Msg("file did not change, will not touch it") + return false, nil + } + + // Write the file contents to a temporary location: + output := strings.Join(lines, "\n") + tempname := filename + "~" + err = os.WriteFile(tempname, []byte(output), 0644) + if err != nil { + return false, fmt.Errorf("writing to %s: %w", tempname, err) + } + + // Move the temporary file onto the input filename: + if err := os.Remove(filename); err != nil { + return false, fmt.Errorf("removing %s: %w", filename, err) + } + if err := os.Rename(tempname, filename); err != nil { + return false, fmt.Errorf("renaming %s to %s: %w", tempname, filename, err) + } + + return true, nil +} diff --git a/cmd/update-version/main.go b/cmd/update-version/main.go new file mode 100644 index 00000000..5b5a54a4 --- /dev/null +++ b/cmd/update-version/main.go @@ -0,0 +1,87 @@ +package main + +// SPDX-License-Identifier: GPL-3.0-or-later + +import ( + "flag" + "fmt" + "os" + "time" + + "github.com/mattn/go-colorable" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +var cliArgs struct { + // Logging level flags. + quiet, debug, trace bool + + newVersion string + updateMakefile bool +} + +func main() { + parseCliArgs() + output := zerolog.ConsoleWriter{Out: colorable.NewColorableStdout(), TimeFormat: time.RFC3339} + log.Logger = log.Output(output) + configLogLevel() + + log.Info().Str("version", cliArgs.newVersion).Msg("updating Flamenco version") + + var anyFileWasChanged bool + if cliArgs.updateMakefile { + anyFileWasChanged = anyFileWasChanged || updateMakefile() + } + anyFileWasChanged = anyFileWasChanged || updateAddon() + + if !anyFileWasChanged { + log.Warn().Msg("nothing changed") + os.Exit(42) + return + } + + // Lot the result & some easy-to-copy Git commands: + commitMsg := fmt.Sprintf("Bumped version to %s", cliArgs.newVersion) + tagMsg := fmt.Sprintf("Tagged version %s", cliArgs.newVersion) + log.Info().Msg("file replacement done, commit with:") + log.Info().Msgf("git commit -m %q %s %s", commitMsg, makefileFile, addonVersionFile) + log.Info().Msgf("git tag -a -m %q v%s", tagMsg, cliArgs.newVersion) +} + +func parseCliArgs() { + flag.BoolVar(&cliArgs.quiet, "quiet", false, "Only log warning-level and worse.") + flag.BoolVar(&cliArgs.debug, "debug", false, "Enable debug-level logging.") + flag.BoolVar(&cliArgs.trace, "trace", false, "Enable trace-level logging.") + flag.BoolVar(&cliArgs.updateMakefile, "makefile", false, + "Also update the Makefile. Normally this application is invoked from the Makefile itself, "+ + "and thus it does not change that file without this CLI argument.") + + flag.Parse() + + cliArgs.newVersion = flag.Arg(0) + if cliArgs.newVersion == "" { + os.Stderr.WriteString(fmt.Sprintf("Usage: %s [-quiet|-debug|-trace] {new Flamenco version number}\n", os.Args[0])) + os.Stderr.WriteString("\n") + flag.PrintDefaults() + os.Stderr.WriteString("\n") + os.Stderr.WriteString("This program updates Makefile and some other files to set the new Flamenco version.\n") + os.Stderr.WriteString("\n") + os.Exit(47) + } +} + +func configLogLevel() { + var logLevel zerolog.Level + switch { + case cliArgs.trace: + logLevel = zerolog.TraceLevel + case cliArgs.debug: + logLevel = zerolog.DebugLevel + case cliArgs.quiet: + logLevel = zerolog.WarnLevel + default: + logLevel = zerolog.InfoLevel + } + zerolog.SetGlobalLevel(logLevel) +} diff --git a/cmd/update-version/makefile.go b/cmd/update-version/makefile.go new file mode 100644 index 00000000..3911469d --- /dev/null +++ b/cmd/update-version/makefile.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/rs/zerolog/log" +) + +const makefileFile = "Makefile" + +// updateMakefile changes the version number in Makefile. +// Returns whether the file actually changed. +func updateMakefile() bool { + replacer := func(line string) string { + if !strings.HasPrefix(line, "VERSION := ") { + return line + } + return fmt.Sprintf("VERSION := %q", cliArgs.newVersion) + } + + fileWasChanged, err := updateLines(makefileFile, replacer) + if err != nil { + log.Fatal().Err(err).Msg("error updating Makefile") + } + return fileWasChanged +} diff --git a/internal/appinfo/appinfo.go b/internal/appinfo/appinfo.go index d0c123c9..7d927e24 100644 --- a/internal/appinfo/appinfo.go +++ b/internal/appinfo/appinfo.go @@ -5,11 +5,16 @@ package appinfo import "fmt" // ApplicationName contains the application name. -const ApplicationName = "Flamenco 3" +const ApplicationName = "Flamenco" -// ApplicationVersion has the version number, and is set during the build. +// ApplicationVersion is the version number of the application. +// It is set during the build. var ApplicationVersion = "set-during-build" +// ApplicationGitHash has the Git hash of the commit used to create this build. +// It is set during the build. +var ApplicationGitHash = "set-during-build" + // FormattedApplicationInfo returns the application name & version as single string. func FormattedApplicationInfo() string { return fmt.Sprintf("%s %s", ApplicationName, ApplicationVersion) @@ -17,5 +22,5 @@ func FormattedApplicationInfo() string { // UserAgent returns the application name & version suitable for the HTTP User-Agent header. func UserAgent() string { - return fmt.Sprintf("%s/%s", ApplicationName, ApplicationVersion) + return fmt.Sprintf("%s/%s (%s)", ApplicationName, ApplicationVersion, ApplicationGitHash) }