| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- package main
- import (
- "encoding/json"
- "flag"
- "fmt"
- "go/parser"
- "go/token"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "sync"
- )
- // DependencyGraph 表示依赖关系图
- type DependencyGraph struct {
- Packages map[string]*PackageInfo
- Mutex sync.RWMutex
- }
- // PackageInfo 表示包的信息
- type PackageInfo struct {
- Name string
- Path string
- Imports []string
- ImportedBy []string
- IsExternal bool
- IsStandard bool
- }
- // DependencyEdge 表示依赖边
- type DependencyEdge struct {
- From string `json:"from"`
- To string `json:"to"`
- }
- // DependencyOutput 输出结构
- type DependencyOutput struct {
- Packages []PackageOutput `json:"packages"`
- Edges []DependencyEdge `json:"edges"`
- }
- // PackageOutput 包输出结构
- type PackageOutput struct {
- Name string `json:"name"`
- Path string `json:"path"`
- Imports []string `json:"imports"`
- ImportedBy []string `json:"imported_by,omitempty"`
- IsExternal bool `json:"is_external"`
- IsStandard bool `json:"is_standard"`
- }
- func main() {
- // 解析命令行参数
- rootDir := flag.String("dir", ".", "要扫描的Go工程目录")
- outputFormat := flag.String("format", "text", "输出格式: text, json, dot, csv")
- outputFile := flag.String("output", "", "输出文件路径(默认输出到控制台)")
- ignoreStdLib := flag.Bool("ignore-std", false, "忽略标准库依赖")
- includeTests := flag.Bool("include-tests", false, "包含测试文件")
- flag.Parse()
- // 检查目录是否存在
- if _, err := os.Stat(*rootDir); os.IsNotExist(err) {
- fmt.Printf("错误: 目录不存在: %s\n", *rootDir)
- os.Exit(1)
- }
- // 构建依赖图
- graph := &DependencyGraph{
- Packages: make(map[string]*PackageInfo),
- }
- fmt.Printf("正在扫描目录: %s\n", *rootDir)
- // 扫描Go文件
- err := scanDirectory(*rootDir, graph, *includeTests)
- if err != nil {
- fmt.Printf("扫描错误: %v\n", err)
- os.Exit(1)
- }
- // 计算导入关系
- calculateImportedBy(graph)
- // 生成输出
- output := generateOutput(graph, *ignoreStdLib)
- // 输出结果
- err = outputResult(output, *outputFormat, *outputFile)
- if err != nil {
- fmt.Printf("输出错误: %v\n", err)
- os.Exit(1)
- }
- fmt.Printf("\n扫描完成! 共发现 %d 个包,%d 个依赖关系\n",
- len(output.Packages), len(output.Edges))
- }
- // scanDirectory 递归扫描目录
- func scanDirectory(dir string, graph *DependencyGraph, includeTests bool) error {
- return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- // 跳过vendor目录和隐藏目录
- if info.IsDir() {
- if strings.Contains(path, "vendor") || strings.HasPrefix(filepath.Base(path), ".") {
- return filepath.SkipDir
- }
- return nil
- }
- // 只处理.go文件
- if filepath.Ext(path) != ".go" {
- return nil
- }
- // 可选跳过测试文件
- if !includeTests && strings.HasSuffix(path, "_test.go") {
- return nil
- }
- // 解析Go文件
- analyzeGoFile(path, graph)
- return nil
- })
- }
- // analyzeGoFile 分析单个Go文件
- func analyzeGoFile(filePath string, graph *DependencyGraph) {
- fset := token.NewFileSet()
- node, err := parser.ParseFile(fset, filePath, nil, parser.ImportsOnly)
- if err != nil {
- fmt.Printf("警告: 无法解析文件 %s: %v\n", filePath, err)
- return
- }
- // 获取包名和相对路径
- relPath, _ := filepath.Rel(".", filepath.Dir(filePath))
- if relPath == "." {
- relPath = ""
- }
- packagePath := relPath
- if packagePath == "" {
- packagePath = "."
- }
- // 创建或获取包信息
- graph.Mutex.Lock()
- packageInfo, exists := graph.Packages[packagePath]
- if !exists {
- packageInfo = &PackageInfo{
- Name: node.Name.Name,
- Path: packagePath,
- Imports: []string{},
- ImportedBy: []string{},
- }
- graph.Packages[packagePath] = packageInfo
- }
- graph.Mutex.Unlock()
- // 收集导入
- var imports []string
- for _, imp := range node.Imports {
- importPath := strings.Trim(imp.Path.Value, `"`)
- imports = append(imports, importPath)
- }
- // 更新导入列表(去重)
- graph.Mutex.Lock()
- existingImports := make(map[string]bool)
- for _, imp := range packageInfo.Imports {
- existingImports[imp] = true
- }
- for _, imp := range imports {
- if !existingImports[imp] {
- packageInfo.Imports = append(packageInfo.Imports, imp)
- }
- }
- graph.Mutex.Unlock()
- }
- // calculateImportedBy 计算每个包被哪些包导入
- func calculateImportedBy(graph *DependencyGraph) {
- graph.Mutex.Lock()
- defer graph.Mutex.Unlock()
- // 清空现有的importedBy
- for _, pkg := range graph.Packages {
- pkg.ImportedBy = []string{}
- pkg.IsExternal = false
- pkg.IsStandard = true // 先假设是标准库,后续会修正
- }
- // 遍历所有包,构建导入关系
- for pkgPath, pkg := range graph.Packages {
- for _, imp := range pkg.Imports {
- // 标记外部包和标准库
- if isStandardPackage(imp) {
- // 如果是标准库,检查是否在已有包中
- if _, exists := graph.Packages[imp]; !exists {
- graph.Packages[imp] = &PackageInfo{
- Name: filepath.Base(imp),
- Path: imp,
- IsStandard: true,
- }
- }
- } else {
- // 外部包
- if importedPkg, exists := graph.Packages[imp]; exists {
- importedPkg.IsExternal = true
- importedPkg.IsStandard = false
- }
- }
- // 添加importedBy关系
- if importedPkg, exists := graph.Packages[imp]; exists {
- importedPkg.ImportedBy = append(importedPkg.ImportedBy, pkgPath)
- }
- }
- }
- // 标记项目内部的包
- for pkgPath, pkg := range graph.Packages {
- if _, exists := graph.Packages[pkgPath]; exists && !pkg.IsExternal {
- // 如果路径中包含.或者没有/,可能是本地包
- if strings.Contains(pkgPath, ".") || !strings.Contains(pkgPath, "/") {
- pkg.IsStandard = true
- } else {
- pkg.IsStandard = false
- }
- }
- }
- }
- // isStandardPackage 检查是否是标准库包
- func isStandardPackage(pkgPath string) bool {
- // 标准库通常不包含域名
- if strings.Contains(pkgPath, ".") {
- return false
- }
- // 常见的一级标准库目录
- stdLibs := map[string]bool{
- "fmt": true,
- "io": true,
- "net": true,
- "http": true,
- "os": true,
- "strings": true,
- "strconv": true,
- "encoding": true,
- "json": true,
- "xml": true,
- "time": true,
- "sync": true,
- "math": true,
- "sort": true,
- "container": true,
- "crypto": true,
- "database": true,
- "debug": true,
- "embed": true,
- "errors": true,
- "expvar": true,
- "flag": true,
- "go": true,
- "hash": true,
- "html": true,
- "image": true,
- "index": true,
- "log": true,
- "mime": true,
- "path": true,
- "reflect": true,
- "regexp": true,
- "runtime": true,
- "testing": true,
- "text": true,
- "unicode": true,
- "unsafe": true,
- }
- // 获取第一级目录
- firstPart := pkgPath
- if idx := strings.Index(pkgPath, "/"); idx != -1 {
- firstPart = pkgPath[:idx]
- }
- return stdLibs[firstPart]
- }
- // generateOutput 生成输出数据
- func generateOutput(graph *DependencyGraph, ignoreStdLib bool) *DependencyOutput {
- graph.Mutex.RLock()
- defer graph.Mutex.RUnlock()
- output := &DependencyOutput{
- Packages: []PackageOutput{},
- Edges: []DependencyEdge{},
- }
- // 收集包信息
- var packagePaths []string
- for pkgPath := range graph.Packages {
- packagePaths = append(packagePaths, pkgPath)
- }
- sort.Strings(packagePaths)
- for _, pkgPath := range packagePaths {
- pkg := graph.Packages[pkgPath]
- // 如果忽略标准库且当前包是标准库,则跳过
- if ignoreStdLib && pkg.IsStandard {
- continue
- }
- // 添加包信息
- pkgOutput := PackageOutput{
- Name: pkg.Name,
- Path: pkg.Path,
- Imports: make([]string, len(pkg.Imports)),
- ImportedBy: make([]string, len(pkg.ImportedBy)),
- IsExternal: pkg.IsExternal,
- IsStandard: pkg.IsStandard,
- }
- copy(pkgOutput.Imports, pkg.Imports)
- copy(pkgOutput.ImportedBy, pkg.ImportedBy)
- output.Packages = append(output.Packages, pkgOutput)
- // 添加依赖边(排除指向被忽略的标准库的边)
- for _, imp := range pkg.Imports {
- if importedPkg, exists := graph.Packages[imp]; exists {
- if ignoreStdLib && importedPkg.IsStandard {
- continue
- }
- output.Edges = append(output.Edges, DependencyEdge{
- From: pkgPath,
- To: imp,
- })
- }
- }
- }
- return output
- }
- // outputResult 输出结果
- func outputResult(output *DependencyOutput, format string, outputFile string) error {
- var result string
- var err error
- switch format {
- case "json":
- result, err = outputJSON(output)
- case "dot":
- result, err = outputDOT(output)
- case "csv":
- result, err = outputCSV(output)
- default: // text
- result, err = outputText(output)
- }
- if err != nil {
- return err
- }
- // 输出到文件或控制台
- if outputFile != "" {
- err = os.WriteFile(outputFile, []byte(result), 0644)
- if err != nil {
- return err
- }
- fmt.Printf("结果已保存到: %s\n", outputFile)
- } else {
- fmt.Println(result)
- }
- return nil
- }
- // outputText 文本格式输出
- func outputText(output *DependencyOutput) (string, error) {
- var builder strings.Builder
- builder.WriteString("=== 包依赖分析报告 ===\n\n")
- // 按类型分组
- var stdPackages, localPackages, extPackages []PackageOutput
- for _, pkg := range output.Packages {
- if pkg.IsStandard {
- stdPackages = append(stdPackages, pkg)
- } else if pkg.IsExternal {
- extPackages = append(extPackages, pkg)
- } else {
- localPackages = append(localPackages, pkg)
- }
- }
- // 输出本地包
- if len(localPackages) > 0 {
- builder.WriteString("本地包:\n")
- for _, pkg := range localPackages {
- builder.WriteString(fmt.Sprintf(" %s (%s)\n", pkg.Path, pkg.Name))
- if len(pkg.Imports) > 0 {
- builder.WriteString(" 导入:\n")
- for _, imp := range pkg.Imports {
- builder.WriteString(fmt.Sprintf(" - %s\n", imp))
- }
- }
- if len(pkg.ImportedBy) > 0 {
- builder.WriteString(" 被以下包导入:\n")
- for _, by := range pkg.ImportedBy {
- builder.WriteString(fmt.Sprintf(" - %s\n", by))
- }
- }
- builder.WriteString("\n")
- }
- }
- // 输出外部包
- if len(extPackages) > 0 {
- builder.WriteString("外部依赖:\n")
- for _, pkg := range extPackages {
- builder.WriteString(fmt.Sprintf(" %s\n", pkg.Path))
- if len(pkg.ImportedBy) > 0 {
- builder.WriteString(" 被以下包导入:\n")
- for _, by := range pkg.ImportedBy {
- builder.WriteString(fmt.Sprintf(" - %s\n", by))
- }
- }
- builder.WriteString("\n")
- }
- }
- // 输出标准库(简略)
- if len(stdPackages) > 0 {
- builder.WriteString("标准库依赖:\n")
- libs := make(map[string]bool)
- for _, pkg := range stdPackages {
- libs[pkg.Path] = true
- }
- var libList []string
- for lib := range libs {
- libList = append(libList, lib)
- }
- sort.Strings(libList)
- for _, lib := range libList {
- builder.WriteString(fmt.Sprintf(" %s\n", lib))
- }
- }
- // 依赖统计
- builder.WriteString("\n=== 依赖统计 ===\n")
- builder.WriteString(fmt.Sprintf("总包数: %d\n", len(output.Packages)))
- builder.WriteString(fmt.Sprintf("本地包: %d\n", len(localPackages)))
- builder.WriteString(fmt.Sprintf("外部包: %d\n", len(extPackages)))
- builder.WriteString(fmt.Sprintf("标准库: %d\n", len(stdPackages)))
- builder.WriteString(fmt.Sprintf("依赖关系数: %d\n", len(output.Edges)))
- return builder.String(), nil
- }
- // outputJSON JSON格式输出
- func outputJSON(output *DependencyOutput) (string, error) {
- data, err := json.MarshalIndent(output, "", " ")
- if err != nil {
- return "", err
- }
- return string(data), nil
- }
- // outputDOT DOT格式输出(用于Graphviz)
- func outputDOT(output *DependencyOutput) (string, error) {
- var builder strings.Builder
- builder.WriteString("digraph GoDependencies {\n")
- builder.WriteString(" rankdir=LR;\n")
- builder.WriteString(" node [shape=box, style=filled];\n\n")
- // 定义节点
- for _, pkg := range output.Packages {
- color := "lightblue"
- if pkg.IsExternal {
- color = "lightcoral"
- } else if pkg.IsStandard {
- color = "lightgrey"
- }
- builder.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\", fillcolor=\"%s\"];\n",
- pkg.Path, pkg.Path, color))
- }
- builder.WriteString("\n")
- // 定义边
- for _, edge := range output.Edges {
- builder.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\";\n", edge.From, edge.To))
- }
- builder.WriteString("}\n")
- return builder.String(), nil
- }
- // outputCSV CSV格式输出
- func outputCSV(output *DependencyOutput) (string, error) {
- var builder strings.Builder
- // 写表头
- builder.WriteString("From,To,Type\n")
- // 写数据
- for _, edge := range output.Edges {
- toPkg := findPackage(output.Packages, edge.To)
- depType := "local"
- if toPkg != nil {
- if toPkg.IsExternal {
- depType = "external"
- } else if toPkg.IsStandard {
- depType = "standard"
- }
- }
- builder.WriteString(fmt.Sprintf("%s,%s,%s\n", edge.From, edge.To, depType))
- }
- return builder.String(), nil
- }
- func findPackage(packages []PackageOutput, path string) *PackageOutput {
- for _, pkg := range packages {
- if pkg.Path == path {
- return &pkg
- }
- }
- return nil
- }
|