Skip to content

Commit 7c22c22

Browse files
committed
parse AST tocompute app version
1 parent 7c7dc42 commit 7c22c22

File tree

1 file changed

+92
-30
lines changed

1 file changed

+92
-30
lines changed

dbos/dbos.go

Lines changed: 92 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package dbos
22

33
import (
4+
"bytes"
45
"context"
56
"crypto/sha256"
67
"encoding/hex"
78
"fmt"
9+
"go/ast"
10+
"go/format"
11+
"go/parser"
12+
"go/token"
813
"log/slog"
914
"net/url"
1015
"os"
@@ -23,36 +28,6 @@ var (
2328
_DEFAULT_ADMIN_SERVER_PORT = 3001
2429
)
2530

26-
func computeApplicationVersion() string {
27-
if len(registry) == 0 {
28-
fmt.Println("DBOS: No registered workflows found, cannot compute application version")
29-
return ""
30-
}
31-
32-
// Collect all function names and sort them for consistent hashing
33-
var functionNames []string
34-
for fqn := range registry {
35-
functionNames = append(functionNames, fqn)
36-
}
37-
sort.Strings(functionNames)
38-
39-
hasher := sha256.New()
40-
41-
for _, fqn := range functionNames {
42-
workflowEntry := registry[fqn]
43-
44-
// Try to get function source location and other identifying info
45-
if pc := runtime.FuncForPC(reflect.ValueOf(workflowEntry.wrappedFunction).Pointer()); pc != nil {
46-
// Get the function's entry point - this reflects the actual compiled code
47-
entry := pc.Entry()
48-
fmt.Fprintf(hasher, "%x", entry)
49-
}
50-
}
51-
52-
return hex.EncodeToString(hasher.Sum(nil))
53-
54-
}
55-
5631
var workflowScheduler *cron.Cron // Global because accessed during workflow registration before the dbos singleton is initialized
5732

5833
var logger *slog.Logger // Global because accessed everywhere inside the library
@@ -273,3 +248,90 @@ func Shutdown() {
273248
}
274249
dbos = nil
275250
}
251+
252+
func computeApplicationVersion() string {
253+
if len(registry) == 0 {
254+
fmt.Println("DBOS: No registered workflows found, cannot compute application version")
255+
return ""
256+
}
257+
258+
// Create a file set for parsing Go source files
259+
fset := token.NewFileSet()
260+
261+
// Collect function hashes instead of names for more precise versioning
262+
var functionHashes []string
263+
264+
// Iterate through all registered workflow functions
265+
for fqn := range registry {
266+
workflowEntry := registry[fqn]
267+
268+
// Get runtime program counter for the function to find its source location
269+
if pc := runtime.FuncForPC(reflect.ValueOf(workflowEntry.wrappedFunction).Pointer()); pc != nil {
270+
// Get the source file path and line number where function is defined
271+
file, line := pc.FileLine(pc.Entry())
272+
273+
// Parse the Go source file containing this function
274+
src, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
275+
if err != nil {
276+
// If parsing fails, fallback to using function name as identifier
277+
getLogger().Warn("Failed to parse source file, using function name", "file", file, "function", fqn, "error", err)
278+
functionHashes = append(functionHashes, fqn)
279+
continue
280+
}
281+
282+
// Extract the actual source code of the function
283+
funcSource := extractFunctionSource(src, fset, line)
284+
if funcSource != "" {
285+
// Hash the function's source code
286+
hasher := sha256.New()
287+
hasher.Write([]byte(funcSource))
288+
functionHashes = append(functionHashes, hex.EncodeToString(hasher.Sum(nil)))
289+
} else {
290+
// If we can't find the function source, use the function name
291+
getLogger().Warn("Could not extract function source, using function name", "function", fqn, "line", line)
292+
functionHashes = append(functionHashes, fqn)
293+
}
294+
} else {
295+
// If we can't get runtime info, use the function name
296+
functionHashes = append(functionHashes, fqn)
297+
}
298+
}
299+
300+
// Sort hashes for consistent ordering across runs
301+
sort.Strings(functionHashes)
302+
303+
// Create final application version hash from all function hashes
304+
finalHasher := sha256.New()
305+
for _, hash := range functionHashes {
306+
fmt.Fprintf(finalHasher, "%s", hash)
307+
}
308+
309+
return hex.EncodeToString(finalHasher.Sum(nil))
310+
}
311+
312+
// extractFunctionSource finds and returns the source code of a function at the given line
313+
func extractFunctionSource(file *ast.File, fset *token.FileSet, targetLine int) string {
314+
// Walk through all declarations in the file
315+
for _, decl := range file.Decls {
316+
// Check if this declaration is a function declaration
317+
if fn, ok := decl.(*ast.FuncDecl); ok {
318+
// Get the position information for this function
319+
startPos := fset.Position(fn.Pos())
320+
endPos := fset.Position(fn.End())
321+
322+
// Check if the target line falls within this function's range
323+
if startPos.Line <= targetLine && targetLine <= endPos.Line {
324+
// Format the function AST back to source code
325+
var buf bytes.Buffer
326+
err := format.Node(&buf, fset, fn)
327+
if err != nil {
328+
getLogger().Warn("Failed to format function source", "function", fn.Name, "error", err)
329+
return ""
330+
}
331+
return buf.String()
332+
}
333+
}
334+
}
335+
// Function not found at the target line
336+
return ""
337+
}

0 commit comments

Comments
 (0)