tool/util/shared.go (149 lines of code) (raw):
// Copyright (c) 2024 Alibaba Group Holding Ltd.
//
// 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 util
import (
"encoding/json"
"fmt"
"hash/fnv"
"path/filepath"
"regexp"
"strings"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc"
"golang.org/x/mod/module"
)
const (
GoBuildIgnoreComment = "//go:build ignore"
GoModFile = "go.mod"
GoSumFile = "go.sum"
GoWorkSumFile = "go.work.sum"
DebugLogFile = "debug.log"
TempBuildDir = ".otel-build"
)
const (
BuildPattern = "-p"
BuildGoVer = "-goversion"
BuildPgoProfile = "-pgoprofile"
BuildModeVendor = "-mod=vendor"
BuildModeMod = "-mod=mod"
BuildWork = "-work"
)
func AssertGoBuild(args []string) {
if len(args) < 2 {
Assert(false, "empty go build command")
}
if !strings.Contains(args[0], "go") {
Assert(false, "invalid go build command %v", args)
}
if args[1] != "build" {
Assert(false, "invalid go build command %v", args)
}
}
func IsCompileCommand(line string) bool {
check := []string{"-o", "-p", "-buildid"}
if IsWindows() {
check = append(check, "compile.exe")
} else if IsUnix() {
check = append(check, "compile")
} else {
ShouldNotReachHere()
}
// Check if the line contains all the required fields
for _, id := range check {
if !strings.Contains(line, id) {
return false
}
}
// @@PGO compile command is different from normal compile command, we
// should skip it, otherwise the same package will be compiled twice
// (one for PGO and one for normal), which finally leads to the same
// rule being applied twice.
if strings.Contains(line, BuildPgoProfile) {
return false
}
return true
}
func GetTempBuildDir() string {
return filepath.Join(TempBuildDir, GetRunPhase().String())
}
func GetTempBuildDirWith(name string) string {
return filepath.Join(TempBuildDir, name)
}
func GetLogPath(name string) string {
return filepath.Join(GetTempBuildDir(), name)
}
func GetInstrumentLogPath(name string) string {
return filepath.Join(TempBuildDir, PInstrument, name)
}
func GetPreprocessLogPath(name string) string {
return filepath.Join(TempBuildDir, PPreprocess, name)
}
func GetVarNameOfFunc(fn string) string {
const varDeclSuffix = "Impl"
fn = strings.Title(fn)
return fn + varDeclSuffix
}
var packageRegexp = regexp.MustCompile(`(?m)^package\s+\w+`)
func RenamePackage(source, newPkgName string) string {
source = packageRegexp.ReplaceAllString(source,
fmt.Sprintf("package %s\n", newPkgName))
return source
}
func RemoveGoBuildComment(text string) string {
text = strings.ReplaceAll(text, GoBuildIgnoreComment, "")
return text
}
func HasGoBuildComment(text string) bool {
return strings.Contains(text, GoBuildIgnoreComment)
}
// IsModPath checks if the provided module path is valid.
func IsModPath(path string) bool {
if strings.Contains(path, "@") {
pathOnly := strings.Split(path, "@")[0]
return module.CheckPath(pathOnly) == nil
}
return module.CheckPath(path) == nil
}
func IsGoFile(path string) bool {
return strings.HasSuffix(path, ".go")
}
func IsGoModFile(path string) bool {
return strings.HasSuffix(path, GoModFile)
}
func IsGoSumFile(path string) bool {
return strings.HasSuffix(path, "go.sum")
}
func IsGoTestFile(path string) bool {
return strings.HasSuffix(path, "_test.go")
}
func HashStruct(st interface{}) (uint64, error) {
bs, err := json.Marshal(st)
if err != nil {
return 0, errc.New(errc.ErrInvalidJSON, err.Error())
}
hasher := fnv.New64a()
_, err = hasher.Write(bs)
if err != nil {
return 0, errc.New(errc.ErrInternal, err.Error())
}
return hasher.Sum64(), nil
}
func MakePublic(name string) string {
return strings.Title(name)
}
// SplitCmds splits the command line by space, but keep the quoted part as a
// whole. For example, "a b" c will be split into ["a b", "c"].
func SplitCmds(input string) []string {
var args []string
var inQuotes bool
var arg strings.Builder
for i := 0; i < len(input); i++ {
c := input[i]
if c == '"' {
inQuotes = !inQuotes
continue
}
if c == ' ' && !inQuotes {
if arg.Len() > 0 {
args = append(args, arg.String())
arg.Reset()
}
continue
}
arg.WriteByte(c)
}
if arg.Len() > 0 {
args = append(args, arg.String())
}
// Fix the escaped backslashes on Windows
if IsWindows() {
for i, arg := range args {
args[i] = strings.ReplaceAll(arg, `\\`, `\`)
}
}
return args
}