Skip to content

Commit

Permalink
Added hardcoded weaver module version.
Browse files Browse the repository at this point in the history
> Module Version

We want a `weaver version` command that prints out the weaver module
version the `weaver` binary was built with, or failing that, the commit
at which the binary was built. Unfortunately, both of these things are
hard.

There is currently no nice way to automatically get the version of the
main module in a go program [1]. There is a way to get the git commit
using `debug.ReadBuildInfo()` [2], but when `go install`ing a binary,
the version control information is stripped.

Browsing existing open source projects, it seems the standard practice
is to hard code the module version in the code. This PR does that and
updates the `weaver version` command to use it:

```
$ weaver version
weaver v0.17.0 linux/amd64
```

> Other Versions

The weaver repo has two other versioned APIs: the deployer API version
and the codegen version. Currently, the deployer API version is the
latest module version where the deployer API changed (and the same for
the codegen version).

We discussed offline the idea of replacing the three versions (module,
deployer API, codegen) with just the module version. Then, we could
write additional code to check version compatibility. Is codegen v0.17.3
incompatible with v0.12.0, for example?

When trying to implement this, however, I ran into some problems. For
example, let's say a deployer is at version v0.10.0 and tries to deploy
an app at version v0.12.0. Is deployer API version v0.12.0 compatible
with version v0.10.0? Well, the deployer was written before v0.12.0 was
even created, so it doesn't have a good way to know.

The codegen version is also tricky because it relies on some compiler
tricks to prevent an app from compiling if it has code generated with a
stale version of `weaver generate`. I'm not sure how to implement these
tricks without hardcoding a codegen version.

Because of these challenges, I decided to stick with our current
approach to versioning, for now at least. To clean things up a bit
though, I moved all versioning related code to `runtime/version.go`. I
also moved some code to the `bin` package because it felt more
appropriate there.

[1]: golang/go#29228
[2]: https://pkg.go.dev/runtime/debug#ReadBuildInfo
  • Loading branch information
mwhittaker committed Jun 28, 2023
1 parent 3d6954f commit 51bb741
Show file tree
Hide file tree
Showing 17 changed files with 241 additions and 415 deletions.
4 changes: 3 additions & 1 deletion cmd/weaver/main.go
Expand Up @@ -25,6 +25,7 @@ import (
"os/exec"
"strings"

itool "github.com/ServiceWeaver/weaver/internal/tool"
"github.com/ServiceWeaver/weaver/internal/tool/callgraph"
"github.com/ServiceWeaver/weaver/internal/tool/generate"
"github.com/ServiceWeaver/weaver/internal/tool/multi"
Expand Down Expand Up @@ -84,11 +85,12 @@ func main() {
return

case "version":
cmd := tool.VersionCmd("weaver")
cmd := itool.VersionCmd("weaver")
if err := cmd.Fn(context.Background(), flag.Args()[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
return

case "callgraph":
const usage = `Generate component callgraphs.
Expand Down
29 changes: 16 additions & 13 deletions godeps.txt
Expand Up @@ -59,6 +59,7 @@ github.com/ServiceWeaver/weaver/cmd/weaver
errors
flag
fmt
github.com/ServiceWeaver/weaver/internal/tool
github.com/ServiceWeaver/weaver/internal/tool/callgraph
github.com/ServiceWeaver/weaver/internal/tool/generate
github.com/ServiceWeaver/weaver/internal/tool/multi
Expand Down Expand Up @@ -517,6 +518,13 @@ github.com/ServiceWeaver/weaver/internal/status
syscall
text/template
time
github.com/ServiceWeaver/weaver/internal/tool
context
flag
fmt
github.com/ServiceWeaver/weaver/runtime/tool
github.com/ServiceWeaver/weaver/runtime/version
runtime
github.com/ServiceWeaver/weaver/internal/tool/callgraph
fmt
github.com/ServiceWeaver/weaver/runtime/bin
Expand Down Expand Up @@ -550,6 +558,7 @@ github.com/ServiceWeaver/weaver/internal/tool/generate
github.com/ServiceWeaver/weaver/internal/files
github.com/ServiceWeaver/weaver/runtime/codegen
github.com/ServiceWeaver/weaver/runtime/colors
github.com/ServiceWeaver/weaver/runtime/version
go/ast
go/format
go/parser
Expand Down Expand Up @@ -589,6 +598,7 @@ github.com/ServiceWeaver/weaver/internal/tool/multi
github.com/ServiceWeaver/weaver/internal/proxy
github.com/ServiceWeaver/weaver/internal/routing
github.com/ServiceWeaver/weaver/internal/status
github.com/ServiceWeaver/weaver/internal/tool
github.com/ServiceWeaver/weaver/internal/tool/certs
github.com/ServiceWeaver/weaver/internal/tool/config
github.com/ServiceWeaver/weaver/runtime
Expand Down Expand Up @@ -629,6 +639,7 @@ github.com/ServiceWeaver/weaver/internal/tool/single
fmt
github.com/ServiceWeaver/weaver/internal/must
github.com/ServiceWeaver/weaver/internal/status
github.com/ServiceWeaver/weaver/internal/tool
github.com/ServiceWeaver/weaver/internal/tool/config
github.com/ServiceWeaver/weaver/runtime
github.com/ServiceWeaver/weaver/runtime/codegen
Expand All @@ -650,6 +661,7 @@ github.com/ServiceWeaver/weaver/internal/tool/ssh
flag
fmt
github.com/ServiceWeaver/weaver/internal/status
github.com/ServiceWeaver/weaver/internal/tool
github.com/ServiceWeaver/weaver/internal/tool/config
github.com/ServiceWeaver/weaver/internal/tool/ssh/impl
github.com/ServiceWeaver/weaver/runtime
Expand Down Expand Up @@ -743,7 +755,10 @@ github.com/ServiceWeaver/weaver/runtime/bin
debug/pe
fmt
github.com/ServiceWeaver/weaver/runtime/codegen
github.com/ServiceWeaver/weaver/runtime/version
os
regexp
strconv
github.com/ServiceWeaver/weaver/runtime/bin/testprogram
context
github.com/ServiceWeaver/weaver
Expand All @@ -762,6 +777,7 @@ github.com/ServiceWeaver/weaver/runtime/codegen
github.com/ServiceWeaver/weaver/metrics
github.com/ServiceWeaver/weaver/runtime
github.com/ServiceWeaver/weaver/runtime/protos
github.com/ServiceWeaver/weaver/runtime/version
go.opentelemetry.io/otel/trace
google.golang.org/protobuf/proto
math
Expand Down Expand Up @@ -909,30 +925,17 @@ github.com/ServiceWeaver/weaver/runtime/tool
errors
flag
fmt
github.com/ServiceWeaver/weaver/runtime/codegen
github.com/ServiceWeaver/weaver/runtime/colors
github.com/ServiceWeaver/weaver/runtime/logging
github.com/ServiceWeaver/weaver/runtime/version
io
os
os/exec
runtime
runtime/debug
sort
strings
text/template
time
github.com/ServiceWeaver/weaver/runtime/version
fmt
github.com/ServiceWeaver/weaver/runtime/bin
regexp
strconv
github.com/ServiceWeaver/weaver/runtime/version/testprogram
context
github.com/ServiceWeaver/weaver
github.com/ServiceWeaver/weaver/runtime/codegen
go.opentelemetry.io/otel/trace
reflect
github.com/ServiceWeaver/weaver/weavertest
context
errors
Expand Down
15 changes: 5 additions & 10 deletions internal/envelope/conn/envelope_conn.go
Expand Up @@ -381,18 +381,13 @@ func verifyWeaveletInfo(wlet *protos.WeaveletInfo) error {
// checkVersion checks that the deployer API version the deployer was built
// with is compatible with the deployer API version the app was built with,
// erroring out if they are not compatible.
func checkVersion(appVersion *protos.SemVer) error {
if appVersion == nil {
func checkVersion(v *protos.SemVer) error {
if v == nil {
return fmt.Errorf("version mismatch: nil app version")
}
if appVersion.Major != version.Major ||
appVersion.Minor != version.Minor ||
appVersion.Patch != version.Patch {
return fmt.Errorf(
"version mismatch: deployer version %d.%d.%d is incompatible with app version %d.%d.%d.",
version.Major, version.Minor, version.Patch,
appVersion.Major, appVersion.Minor, appVersion.Patch,
)
got := version.SemVer{Major: int(v.Major), Minor: int(v.Minor), Patch: int(v.Patch)}
if got != version.DeployerVersion {
return fmt.Errorf("version mismatch: deployer's deployer API version %s is incompatible with app' deployer API version %s.", version.DeployerVersion, got)
}
return nil
}
6 changes: 3 additions & 3 deletions internal/envelope/conn/weavelet_conn.go
Expand Up @@ -102,9 +102,9 @@ func NewWeaveletConn(r io.ReadCloser, w io.WriteCloser, h WeaveletHandler) (*Wea
DialAddr: dialAddr,
Pid: int64(os.Getpid()),
Version: &protos.SemVer{
Major: version.Major,
Minor: version.Minor,
Patch: version.Patch,
Major: version.DeployerMajor,
Minor: version.DeployerMinor,
Patch: 0,
},
}
if err := wc.conn.send(&protos.WeaveletMsg{WeaveletInfo: wc.winfo}); err != nil {
Expand Down
5 changes: 3 additions & 2 deletions internal/tool/generate/generator.go
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/ServiceWeaver/weaver/internal/files"
"github.com/ServiceWeaver/weaver/runtime/codegen"
"github.com/ServiceWeaver/weaver/runtime/colors"
"github.com/ServiceWeaver/weaver/runtime/version"
"golang.org/x/exp/maps"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/types/typeutil"
Expand Down Expand Up @@ -928,8 +929,8 @@ func (g *generator) generateVersionCheck(p printFn) {
// var _ codegen.LatestVersion = codegen.Version[[0][1]struct{}]("You used ...")
p(`var _ %s = %s[[%d][%d]struct{}](%q)`,
g.codegen().qualify("LatestVersion"), g.codegen().qualify("Version"),
codegen.Major, codegen.Minor,
fmt.Sprintf(`You used 'weaver generate' codegen version %d.%d.0, but you built your code with an incompatible weaver module version. Try upgrading 'weaver generate' and re-running it.`, codegen.Major, codegen.Minor),
version.CodegenMajor, version.CodegenMinor,
fmt.Sprintf(`You used 'weaver generate' codegen version %d.%d.0, but you built your code with an incompatible weaver module version. Try upgrading 'weaver generate' and re-running it.`, version.CodegenMajor, version.CodegenMinor),
)
}

Expand Down
3 changes: 2 additions & 1 deletion internal/tool/multi/multi.go
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/ServiceWeaver/weaver/internal/must"
"github.com/ServiceWeaver/weaver/internal/status"
itool "github.com/ServiceWeaver/weaver/internal/tool"
"github.com/ServiceWeaver/weaver/runtime"
"github.com/ServiceWeaver/weaver/runtime/logging"
"github.com/ServiceWeaver/weaver/runtime/tool"
Expand Down Expand Up @@ -66,6 +67,6 @@ var (
"metrics": status.MetricsCommand("weaver multi", defaultRegistry),
"profile": status.ProfileCommand("weaver multi", defaultRegistry),
"purge": tool.PurgeCmd(purgeSpec),
"version": tool.VersionCmd("weaver multi"),
"version": itool.VersionCmd("weaver multi"),
}
)
3 changes: 2 additions & 1 deletion internal/tool/single/single.go
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/ServiceWeaver/weaver/internal/must"
"github.com/ServiceWeaver/weaver/internal/status"
itool "github.com/ServiceWeaver/weaver/internal/tool"
"github.com/ServiceWeaver/weaver/runtime"
"github.com/ServiceWeaver/weaver/runtime/tool"
)
Expand Down Expand Up @@ -55,7 +56,7 @@ var (
"metrics": status.MetricsCommand("weaver single", defaultRegistry),
"profile": status.ProfileCommand("weaver single", defaultRegistry),
"purge": tool.PurgeCmd(purgeSpec),
"version": tool.VersionCmd("weaver single"),
"version": itool.VersionCmd("weaver single"),
}
)

Expand Down
3 changes: 2 additions & 1 deletion internal/tool/ssh/ssh.go
Expand Up @@ -16,6 +16,7 @@ package ssh

import (
"github.com/ServiceWeaver/weaver/internal/status"
itool "github.com/ServiceWeaver/weaver/internal/tool"
"github.com/ServiceWeaver/weaver/runtime/tool"
)

Expand All @@ -24,7 +25,7 @@ var (
"deploy": &deployCmd,
"logs": tool.LogsCmd(&logsSpec),
"dashboard": status.DashboardCommand(dashboardSpec),
"version": tool.VersionCmd("weaver ssh"),
"version": itool.VersionCmd("weaver ssh"),

// Hidden commands.
"babysitter": &babysitterCmd,
Expand Down
39 changes: 39 additions & 0 deletions internal/tool/version.go
@@ -0,0 +1,39 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tool

import (
"context"
"flag"
"fmt"
"runtime"

"github.com/ServiceWeaver/weaver/runtime/tool"
"github.com/ServiceWeaver/weaver/runtime/version"
)

// VersionCmd returns a command to show a deployer's version.
func VersionCmd(toolname string) *tool.Command {
return &tool.Command{
Name: "version",
Flags: flag.NewFlagSet("version", flag.ContinueOnError),
Description: fmt.Sprintf("Show %q version", toolname),
Help: fmt.Sprintf("Usage:\n %s version", toolname),
Fn: func(context.Context, []string) error {
fmt.Printf("%s %s %s/%s\n", toolname, version.ModuleVersion, runtime.GOOS, runtime.GOARCH)
return nil
},
}
}
62 changes: 58 additions & 4 deletions runtime/bin/bin.go
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package bin contains code to extract data from a Service Weaver binary.
package bin

import (
Expand All @@ -21,12 +22,30 @@ import (
"debug/pe"
"fmt"
"os"
"regexp"
"strconv"

"github.com/ServiceWeaver/weaver/runtime/codegen"
"github.com/ServiceWeaver/weaver/runtime/version"
)

// ROData returns the read-only data section of the provided binary.
func ROData(file string) ([]byte, error) {
// deployerVersion exists to embed the deployer API version into a Service
// Weaver binary. We split declaring and assigning version to prevent the
// compiler from erasing it.
//
//nolint:unused
var deployerVersion string

func init() {
// NOTE that deployerVersion must be assigned a string constant that
// reflects the values of version.DeployerMajor and version.DeployerMinor.
// If the string is not a constant---if we try to use fmt.Sprintf, for
// example---it will not be embedded in a Service Weaver binary.
deployerVersion = "⟦wEaVeRdEpLoYeRvErSiOn:v0.14.0⟧"
}

// rodata returns the read-only data section of the provided binary.
func rodata(file string) ([]byte, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
Expand Down Expand Up @@ -76,7 +95,7 @@ func ROData(file string) ([]byte, error) {
//
// github.com/ServiceWeaver/weaver/Main
func ReadComponentGraph(file string) ([][2]string, error) {
data, err := ROData(file)
data, err := rodata(file)
if err != nil {
return nil, err
}
Expand All @@ -86,9 +105,44 @@ func ReadComponentGraph(file string) ([][2]string, error) {
// ReadListeners reads the sets of listeners associated with each component
// in the specified binary.
func ReadListeners(file string) ([]codegen.ComponentListeners, error) {
data, err := ROData(file)
data, err := rodata(file)
if err != nil {
return nil, err
}
return codegen.ExtractListeners(data), nil
}

// ReadDeployerVersion reads the deployer API version from the specified binary.
func ReadDeployerVersion(filename string) (version.SemVer, error) {
data, err := rodata(filename)
if err != nil {
return version.SemVer{}, err
}
return extractDeployerVersion(data)
}

// extractDeployerVersion returns the deployer API version embedded in data.
func extractDeployerVersion(data []byte) (version.SemVer, error) {
re := regexp.MustCompile(`⟦wEaVeRdEpLoYeRvErSiOn:v([0-9]*?)\.([0-9]*?)\.([0-9]*?)⟧`)
m := re.FindSubmatch(data)
if m == nil {
return version.SemVer{}, fmt.Errorf("embedded deployer API version not found")
}
major, minor, patch := string(m[1]), string(m[2]), string(m[3])

v := version.SemVer{}
var err error
v.Major, err = strconv.Atoi(major)
if err != nil {
return version.SemVer{}, fmt.Errorf("invalid embedded deployer API major %q: %w", major, err)
}
v.Minor, err = strconv.Atoi(minor)
if err != nil {
return version.SemVer{}, fmt.Errorf("invalid embedded deployer API minor %q: %w", minor, err)
}
v.Patch, err = strconv.Atoi(patch)
if err != nil {
return version.SemVer{}, fmt.Errorf("invalid embedded deployer API patch %q: %w", patch, err)
}
return v, nil
}

0 comments on commit 51bb741

Please sign in to comment.