diff --git a/cmd/weaver/main.go b/cmd/weaver/main.go index 1841db48e..020234ef5 100644 --- a/cmd/weaver/main.go +++ b/cmd/weaver/main.go @@ -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" @@ -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. diff --git a/godeps.txt b/godeps.txt index 46d6f696a..a7823c058 100644 --- a/godeps.txt +++ b/godeps.txt @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/internal/envelope/conn/envelope_conn.go b/internal/envelope/conn/envelope_conn.go index 2de419e79..a28e92c2c 100644 --- a/internal/envelope/conn/envelope_conn.go +++ b/internal/envelope/conn/envelope_conn.go @@ -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 } diff --git a/internal/envelope/conn/weavelet_conn.go b/internal/envelope/conn/weavelet_conn.go index 3aa5222dd..8d88e378d 100644 --- a/internal/envelope/conn/weavelet_conn.go +++ b/internal/envelope/conn/weavelet_conn.go @@ -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 { diff --git a/internal/tool/generate/generator.go b/internal/tool/generate/generator.go index 59ffda35a..81b26e1ff 100644 --- a/internal/tool/generate/generator.go +++ b/internal/tool/generate/generator.go @@ -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" @@ -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), ) } diff --git a/internal/tool/multi/multi.go b/internal/tool/multi/multi.go index ea203ed78..f9b7deaef 100644 --- a/internal/tool/multi/multi.go +++ b/internal/tool/multi/multi.go @@ -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" @@ -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"), } ) diff --git a/internal/tool/single/single.go b/internal/tool/single/single.go index 50ebf1ec9..f87459489 100644 --- a/internal/tool/single/single.go +++ b/internal/tool/single/single.go @@ -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" ) @@ -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"), } ) diff --git a/internal/tool/ssh/ssh.go b/internal/tool/ssh/ssh.go index f8819472c..256e373d8 100644 --- a/internal/tool/ssh/ssh.go +++ b/internal/tool/ssh/ssh.go @@ -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" ) @@ -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, diff --git a/internal/tool/version.go b/internal/tool/version.go new file mode 100644 index 000000000..480bbe96b --- /dev/null +++ b/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 + }, + } +} diff --git a/runtime/bin/bin.go b/runtime/bin/bin.go index 8e14363e9..96a17447c 100644 --- a/runtime/bin/bin.go +++ b/runtime/bin/bin.go @@ -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 ( @@ -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 @@ -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 } @@ -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 +} diff --git a/runtime/bin/bin_test.go b/runtime/bin/bin_test.go index 575dd53e3..b54eac60a 100644 --- a/runtime/bin/bin_test.go +++ b/runtime/bin/bin_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package bin_test +package bin import ( "fmt" @@ -21,18 +21,13 @@ import ( "path/filepath" "testing" - "github.com/ServiceWeaver/weaver/runtime/bin" "github.com/ServiceWeaver/weaver/runtime/codegen" + "github.com/ServiceWeaver/weaver/runtime/version" "github.com/google/go-cmp/cmp" ) func TestReadComponentGraph(t *testing.T) { - type testCase struct { - os string - arch string - } - - for _, test := range []testCase{ + for _, test := range []struct{ os, arch string }{ {"linux", "amd64"}, {"windows", "amd64"}, {"darwin", "arm64"}, @@ -43,13 +38,12 @@ func TestReadComponentGraph(t *testing.T) { binary := filepath.Join(d, "bin") cmd := exec.Command("go", "build", "-o", binary, "./testprogram") cmd.Env = append(os.Environ(), "GOOS="+test.os, "GOARCH="+test.arch) - err := cmd.Run() - if err != nil { + if err := cmd.Run(); err != nil { t.Fatal(err) } // Read edges. - edges, err := bin.ReadComponentGraph(binary) + edges, err := ReadComponentGraph(binary) if err != nil { t.Fatal(err) } @@ -87,12 +81,7 @@ func TestReadComponentGraph(t *testing.T) { } func TestReadListeners(t *testing.T) { - type testCase struct { - os string - arch string - } - - for _, test := range []testCase{ + for _, test := range []struct{ os, arch string }{ {"linux", "amd64"}, {"windows", "amd64"}, {"darwin", "arm64"}, @@ -109,7 +98,7 @@ func TestReadListeners(t *testing.T) { } // Read listeners. - actual, err := bin.ReadListeners(binary) + actual, err := ReadListeners(binary) if err != nil { t.Fatal(err) } @@ -131,3 +120,55 @@ func TestReadListeners(t *testing.T) { }) } } + +func TestExtractDeployerVersion(t *testing.T) { + for _, want := range []version.SemVer{ + {Major: 0, Minor: 0, Patch: 0}, + {Major: 10, Minor: 10, Patch: 10}, + {Major: 123, Minor: 4567, Patch: 891011}, + } { + t.Run(want.String(), func(t *testing.T) { + // Embed the version string inside a big array of bytes. + var bytes [10000]byte + embedded := fmt.Sprintf("⟦wEaVeRdEpLoYeRvErSiOn:%s⟧", want) + copy(bytes[1234:], []byte(embedded)) + + // Extract the version string. + got, err := extractDeployerVersion(bytes[:]) + if err != nil { + t.Fatal(err) + } + if got != want { + t.Fatalf("bad deployer API version: got %s, want %s", got, want) + } + }) + } +} + +func TestReadVersion(t *testing.T) { + for _, test := range []struct{ os, arch string }{ + {"linux", "amd64"}, + {"windows", "amd64"}, + {"darwin", "arm64"}, + } { + t.Run(fmt.Sprintf("%s/%s", test.os, test.arch), func(t *testing.T) { + // Build the binary for os/arch. + d := t.TempDir() + binary := filepath.Join(d, "bin") + cmd := exec.Command("go", "build", "-o", binary, "./testprogram") + cmd.Env = append(os.Environ(), "GOOS="+test.os, "GOARCH="+test.arch) + if err := cmd.Run(); err != nil { + t.Fatal(err) + } + + // Read version. + got, err := ReadDeployerVersion(binary) + if err != nil { + t.Fatal(err) + } + if got != version.DeployerVersion { + t.Fatalf("bad dipe version: got %s, want %s", got, version.DeployerVersion) + } + }) + } +} diff --git a/runtime/codegen/version.go b/runtime/codegen/version.go index 761b3e1b3..f3bfebe97 100644 --- a/runtime/codegen/version.go +++ b/runtime/codegen/version.go @@ -14,49 +14,7 @@ package codegen -const ( - // The version of the codegen API in semantic version format (Major.Minor). - // - // We need to make sure that the code generated by 'weaver generate' is in - // sync with the weaver module version compiled into a developer's binary. - // For example, 'weaver generate' may generate code using a function in the - // codegen package that doesn't yet exist in the version of the codegen - // package linked into the developer's binary. - // - // We also need to make sure that all generated weaver_gen.go files are - // compatible. For example, if we change the RPC wire protocol, we have to - // ensure that every weaver_gen.go file is re-generated to use the new wire - // protocol. - // - // To address these issues, we introduce a codegen version. Every time we - // make a change to how code is generated---e.g., we change our wire - // protocol, we change which codegen functions get called---we increment - // the codegen version. - // - // In the future, we have to be careful to make backwards compatible - // changes and ensure different versions work with each other. In the short - // term, we make the restriction that all weaver_gen.go files use the same - // codegen API version linked into the developer's binary. - // - // We could assign the codegen API versions v1, v2, v3, and so on. - // However, this makes it hard to understand the relationship between the - // codegen API version and the version of the Service Weaver module. (What - // version of Service Weaver do I need to install to get version 7 of the - // codegen API?) - // - // Instead, we use Service Weaver module versions as codegen API versions. - // For example, if we change the codegen API in v0.12.0 of Service Weaver, - // then we update the codegen API version to v0.12.0. If we don't change - // the codegen API in v0.13.0 of Service Weaver, then we leave the codegen - // API at v0.12.0. - // - // TODO(mwhittaker): Write a doc explaining this version as well as the - // module version and deployer API version. - Major = 0 - Minor = 17 - // NOTE: Patch is omitted because all API changes should bump the major or - // minor version. Patch is assumed to be 0. -) +import "github.com/ServiceWeaver/weaver/runtime/version" // The following types are used to check, at compile time, that every // weaver_gen.go file uses the codegen API version that is linked into the @@ -77,4 +35,4 @@ const ( // (this is "..." above). Again note that we ignore the patch number. type Version[_ any] string -type LatestVersion = Version[[Major][Minor]struct{}] +type LatestVersion = Version[[version.CodegenMajor][version.CodegenMinor]struct{}] diff --git a/runtime/tool/version.go b/runtime/tool/version.go deleted file mode 100644 index 0a4885335..000000000 --- a/runtime/tool/version.go +++ /dev/null @@ -1,59 +0,0 @@ -// 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" - "runtime/debug" - - "github.com/ServiceWeaver/weaver/runtime/codegen" - "github.com/ServiceWeaver/weaver/runtime/version" -) - -// VersionCmd returns a command to show a deployer's version. -func VersionCmd(tool string) *Command { - return &Command{ - Name: "version", - Flags: flag.NewFlagSet("version", flag.ContinueOnError), - Description: fmt.Sprintf("Show %q version", tool), - Help: fmt.Sprintf("Usage:\n %s version", tool), - Fn: func(context.Context, []string) error { - deployerAPI := fmt.Sprintf("%d.%d.%d", version.Major, version.Minor, version.Patch) - codegenAPI := fmt.Sprintf("%d.%d.0", codegen.Major, codegen.Minor) - commit := "?" - if info, ok := debug.ReadBuildInfo(); ok { - for _, setting := range info.Settings { - // vcs.revision stores the commit at which the weaver tool - // was built. See [1] for more information. - // - // [1]: https://pkg.go.dev/runtime/debug#BuildSetting - if setting.Key == "vcs.revision" { - commit = setting.Value - break - } - } - } - fmt.Printf("tool: %s\n", tool) - fmt.Printf("commit: %s\n", commit) - fmt.Printf("target: %s/%s\n", runtime.GOOS, runtime.GOARCH) - fmt.Printf("deployer API: %s\n", deployerAPI) - fmt.Printf("codegen API: %s\n", codegenAPI) - return nil - }, - } -} diff --git a/runtime/version/testprogram/main.go b/runtime/version/testprogram/main.go deleted file mode 100644 index 48168070d..000000000 --- a/runtime/version/testprogram/main.go +++ /dev/null @@ -1,32 +0,0 @@ -// 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. - -// testprogram is used by version tests. -package main - -import ( - "context" - - "github.com/ServiceWeaver/weaver" -) - -//go:generate ../../../cmd/weaver/weaver generate - -type app struct { - weaver.Implements[weaver.Main] -} - -func (*app) Main(context.Context) error { return nil } - -func main() {} diff --git a/runtime/version/testprogram/weaver_gen.go b/runtime/version/testprogram/weaver_gen.go deleted file mode 100644 index 345b97367..000000000 --- a/runtime/version/testprogram/weaver_gen.go +++ /dev/null @@ -1,72 +0,0 @@ -// Code generated by "weaver generate". DO NOT EDIT. -//go:build !ignoreWeaverGen - -package main - -import ( - "context" - "github.com/ServiceWeaver/weaver" - "github.com/ServiceWeaver/weaver/runtime/codegen" - "go.opentelemetry.io/otel/trace" - "reflect" -) -var _ codegen.LatestVersion = codegen.Version[[0][17]struct{}]("You used 'weaver generate' codegen version 0.17.0, but you built your code with an incompatible weaver module version. Try upgrading 'weaver generate' and re-running it.") - -func init() { - codegen.Register(codegen.Registration{ - Name: "github.com/ServiceWeaver/weaver/Main", - Iface: reflect.TypeOf((*weaver.Main)(nil)).Elem(), - Impl: reflect.TypeOf(app{}), - LocalStubFn: func(impl any, tracer trace.Tracer) any { - return main_local_stub{impl: impl.(weaver.Main), tracer: tracer} - }, - ClientStubFn: func(stub codegen.Stub, caller string) any { return main_client_stub{stub: stub} }, - ServerStubFn: func(impl any, addLoad func(uint64, float64)) codegen.Server { - return main_server_stub{impl: impl.(weaver.Main), addLoad: addLoad} - }, - RefData: "", - }) -} - -// weaver.Instance checks. -var _ weaver.InstanceOf[weaver.Main] = (*app)(nil) - -// weaver.Router checks. -var _ weaver.Unrouted = (*app)(nil) - -// Local stub implementations. - -type main_local_stub struct { - impl weaver.Main - tracer trace.Tracer -} - -// Check that main_local_stub implements the weaver.Main interface. -var _ weaver.Main = (*main_local_stub)(nil) - -// Client stub implementations. - -type main_client_stub struct { - stub codegen.Stub -} - -// Check that main_client_stub implements the weaver.Main interface. -var _ weaver.Main = (*main_client_stub)(nil) - -// Server stub implementations. - -type main_server_stub struct { - impl weaver.Main - addLoad func(key uint64, load float64) -} - -// Check that main_server_stub implements the codegen.Server interface. -var _ codegen.Server = (*main_server_stub)(nil) - -// GetStubFn implements the codegen.Server interface. -func (s main_server_stub) GetStubFn(method string) func(ctx context.Context, args []byte) ([]byte, error) { - switch method { - default: - return nil - } -} diff --git a/runtime/version/version.go b/runtime/version/version.go index 5263f1638..5226ccc03 100644 --- a/runtime/version/version.go +++ b/runtime/version/version.go @@ -12,90 +12,72 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package version contains code related to deployer API versioning. +// Package version contains the version of the weaver module and its +// constituent APIs (e.g., the pipe API, the codegen API). package version import ( "fmt" - "regexp" - "strconv" - - "github.com/ServiceWeaver/weaver/runtime/bin" ) +// TODO(mwhittaker): Write a doc explaining versioning in detail. Include +// Srdjan's comments in PR #219. + const ( - // The version of the deployer API---aka pipe API---in semantic version - // format (Major.Minor.Patch). + // The weaver module semantic version [1]. + // + // [1]: https://go.dev/doc/modules/version-numbers + ModuleMajor = 0 + ModuleMinor = 17 + ModulePatch = 0 + + // Note that there is currently no way to programmatically get the version + // of the current module [1], so we have to manually update the module + // version here whenever we release a new version. // - // Every time we make a change to deployer API, we assign it a new version. - // When an envelope spawns a weavelet, the weavelet reports this version to - // the envelope. The envelope then errors out if it is not compatible with - // the reported version. + // [1]: https://github.com/golang/go/issues/29228 + + // The version of the deployer API. // - // We could assign the deployer API versions v1, v2, v3, and so on. - // However, this makes it hard to understand the relationship between the - // deployer API version and the version of the Service Weaver module. (What - // version of Service Weaver do I need to install to get version 7 of the - // pipe?) + // Every time we make a change to the deployer API, we assign it a new + // version. We could assign the deployer API versions v1, v2, v3, and so + // on. However, this makes it hard to understand the relationship between + // the deployer API version and the version of the Service Weaver module. // // Instead, we use Service Weaver module versions as deployer API versions. // For example, if we change the deployer API in v0.12.0 of Service Weaver, // then we update the deployer API version to v0.12.0. If we don't change // the deployer API in v0.13.0 of Service Weaver, then we leave the // deployer API at v0.12.0. - // - // TODO(mwhittaker): Write a doc explaining versioning in detail. Include - // Srdjan's comments in PR #219. - Major = 0 - Minor = 14 - Patch = 0 + DeployerMajor = 0 + DeployerMinor = 14 + + // The version of the codegen API. As with the deployer API, we assign a + // new version every time we change how code is generated, and we use + // weaver module versions. + CodegenMajor = 0 + CodegenMinor = 17 ) -// version 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. -// -// NOTE that version should be initialized with a hardcoded string that should -// reflect the values of Major, Minor and Patch. -// -//nolint:unused -var version string +var ( + // The weaver module version. + ModuleVersion = SemVer{ModuleMajor, ModuleMinor, ModulePatch} -func init() { - // Make sure that the hardcoded string reflects the values of Major, Minor and - // Patch. - version = "⟦wEaVeRvErSiOn:0.14.0⟧" -} + // The deployer API version. + DeployerVersion = SemVer{DeployerMajor, DeployerMinor, 0} + + // The codegen API version. + CodegenVersion = SemVer{CodegenMajor, CodegenMinor, 0} +) -// ReadVersion reads version (major, minor, patch) from the specified binary. -func ReadVersion(filename string) (int, int, int, error) { - data, err := bin.ROData(filename) - if err != nil { - return 0, 0, 0, err - } - return extractVersion(data) +// SemVer is a semantic version. See https://go.dev/doc/modules/version-numbers +// for details. +type SemVer struct { + Major int + Minor int + Patch int } -// extractVersion returns the version (major, minor, patch) corresponding to -// MakeVersionString() embedded in data. -func extractVersion(data []byte) (int, int, int, error) { - re := regexp.MustCompile(`⟦wEaVeRvErSiOn:([0-9]*?)\.([0-9]*?)\.([0-9]*?)⟧`) - m := re.FindSubmatch(data) - if m == nil { - return 0, 0, 0, fmt.Errorf("version not found") - } - major, minor, patch := string(m[1]), string(m[2]), string(m[3]) - ma, err := strconv.Atoi(major) - if err != nil { - return 0, 0, 0, fmt.Errorf("invalid major %q: %w", major, err) - } - mi, err := strconv.Atoi(minor) - if err != nil { - return 0, 0, 0, fmt.Errorf("invalid minor %q: %w", minor, err) - } - pa, err := strconv.Atoi(patch) - if err != nil { - return 0, 0, 0, fmt.Errorf("invalid patch %q: %w", patch, err) - } - return ma, mi, pa, nil +func (s SemVer) String() string { + return fmt.Sprintf("v%d.%d.%d", s.Major, s.Minor, s.Patch) } diff --git a/runtime/version/version_test.go b/runtime/version/version_test.go deleted file mode 100644 index 6cb67f30d..000000000 --- a/runtime/version/version_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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 version - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "testing" -) - -func TestExtractVersion(t *testing.T) { - for _, test := range []struct{ major, minor, patch int }{ - {0, 0, 0}, - {10, 10, 10}, - {123, 4567, 891011}, - } { - name := fmt.Sprintf("%d.%d.%d", test.major, test.minor, test.patch) - t.Run(name, func(t *testing.T) { - // Embed the version string inside a big array of bytes. - var bytes [10000]byte - v := fmt.Sprintf("⟦wEaVeRvErSiOn:%d.%d.%d⟧", test.major, test.minor, test.patch) - copy(bytes[1234:], []byte(v)) - - // Extract the version string. - major, minor, patch, err := extractVersion(bytes[:]) - if err != nil { - t.Fatal(err) - } - if major != test.major { - t.Errorf("major: got %d, want %d", major, test.major) - } - if minor != test.minor { - t.Errorf("minor: got %d, want %d", minor, test.minor) - } - if patch != test.patch { - t.Errorf("patch: got %d, want %d", patch, test.patch) - } - }) - } -} - -func TestReadVersion(t *testing.T) { - for _, test := range []struct{ os, arch string }{ - {"linux", "amd64"}, - {"windows", "amd64"}, - {"darwin", "arm64"}, - } { - t.Run(fmt.Sprintf("%s/%s", test.os, test.arch), func(t *testing.T) { - // Build the binary for os/arch. - d := t.TempDir() - binary := filepath.Join(d, "bin") - cmd := exec.Command("go", "build", "-o", binary, "./testprogram") - cmd.Env = append(os.Environ(), "GOOS="+test.os, "GOARCH="+test.arch) - err := cmd.Run() - if err != nil { - t.Fatal(err) - } - - // Read version. - major, minor, patch, err := ReadVersion(binary) - if err != nil { - t.Fatal(err) - } - if major != Major { - t.Errorf("major: got %d, want %d", major, Major) - } - if minor != Minor { - t.Errorf("minor: got %d, want %d", minor, Minor) - } - if patch != Patch { - t.Errorf("patch: got %d, want %d", patch, Patch) - } - }) - } -}