depfind

package module
v0.0.20 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 24, 2026 License: MIT Imports: 9 Imported by: 1

README

godepfind

Project Badges

A Go library for finding reverse dependencies and determining which main packages are affected by file changes.

Overview

godepfind helps you identify which packages import your target packages (reverse dependency analysis) and, more importantly, which main packages need to be recompiled when you modify a specific Go file.

This is particularly useful for:

  • Build systems that need to know which applications to rebuild when files change
  • Development tools that optimize compilation by only rebuilding affected main packages
  • CI/CD pipelines that want to minimize build time by targeting only affected applications

Installation

go get github.com/tinywasm/depfind

Features

1. Cached Dependency Analysis

NEW: Intelligent caching system for performance in development environments where files change regularly.

2. Handler-Based File Ownership

NEW: Determine which handler should process a file change using smart dependency analysis.

3. Find Reverse Dependencies

Find which packages import specified target packages.

4. File-to-Main Mapping

Main feature: Given a modified file name, find which main packages depend on it (directly or transitively).

Usage

Basic Setup
import "github.com/tinywasm/depfind"

// Create a new finder instance for your project
finder := godepfind.New("/path/to/your/go/project")

// Optional: Include test imports in dependency analysis
finder.SetTestImports(true)
Find Which Main Packages Use a File

Primary use case: When you modify a file, find which main packages need recompilation.

// After modifying "database.go", find affected main packages
mains, err := finder.GoFileComesFromMain("database.go")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Main packages affected by database.go changes: %v\n", mains)
// Output: [myproject/cmd/server myproject/cmd/cli]
File Ownership (NEW)

For development tools: Determine which handler should process a file change.

// Check if a file change belongs to this handler
mainInputFileRelativePath := "app/server/main.go"
isMine, err := finder.ThisFileIsMine(mainInputFileRelativePath, "./internal/db/database.go", "write")
if err != nil {
    log.Fatal(err)
}

if isMine {
    fmt.Println("This handler should process the file change")
    // Process the file change...
}
Find Reverse Dependencies
// Find packages that import "fmt" or "os"
deps, err := finder.FindReverseDeps("./...", []string{"fmt", "os"})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Packages importing fmt or os: %v\n", deps)

Real-World Example

Imagine you have a Go project with multiple applications:

myproject/
├── go.mod
├── cmd/
│   ├── server/main.go      (web server)
│   ├── cli/main.go         (command line tool)  
│   └── worker/main.go      (background worker)
├── internal/
│   ├── database/db.go      (shared database package)
│   ├── auth/auth.go        (authentication)
│   └── utils/helpers.go    (utilities)

Scenario: You modify internal/database/db.go

finder := godepfind.New("/path/to/myproject")

// Traditional approach: Find which main packages are affected
affected, err := finder.GoFileComesFromMain("db.go")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Need to rebuild: %v\n", affected)
// Output: [myproject/cmd/server myproject/cmd/worker]
// Note: cmd/cli is NOT affected because it doesn't import the database package

// NEW: Handler-based approach for development tools
// Check if server handler should process this change
mainInputFileRelativePath := "cmd/server/main.go"
shouldProcess, err := finder.ThisFileIsMine(mainInputFileRelativePath, "./internal/database/db.go", "write")
if err != nil {
    log.Fatal(err)
}

if shouldProcess {
    fmt.Println("Server handler will process this database change")
    // Automatically compile server, restart, etc.
}

Now your build system knows to only recompile the server and worker applications, not the cli tool.

API Reference

Core Functions
New(rootDir string) *GoDepFind

Creates a new GoDepFind instance with intelligent caching.

  • rootDir: Path to the Go module root directory (where go.mod is located)
SetTestImports(enabled bool)

Enable/disable inclusion of test imports in dependency analysis.

GoFileComesFromMain(fileName string) ([]string, error)

Main function: Find which main packages depend on the given file.

  • fileName: Name of the file (e.g., "database.go", "helpers.go")
  • Returns: Slice of main package paths that depend on this file
FindReverseDeps(sourcePath string, targetPaths []string) ([]string, error)

Find packages in sourcePath that import any of the targetPaths.

  • sourcePath: Path pattern to search (e.g., "./...", "./cmd/...")
  • targetPaths: Packages to find dependencies for
  • Returns: Slice of packages that import the targets
New Cache-Enabled Functions
ThisFileIsMine(mainInputFileRelativePath, filePath, event string) (bool, error)

NEW: Determine if a file change belongs to a specific handler using intelligent dependency analysis.

  • mainInputFileRelativePath: Path to the main file that this handler is responsible for managing.
  • filePath: Full path to the changed file (e.g., "./internal/db/database.go") - filePath must include directory separators
  • event: Type of change ("write", "create", "remove", "rename")
  • Returns: (true if handler should process, error if any)

Important: filePath must be a complete path with directory separators (e.g., "./internal/db/database.go"). Simple filenames like "database.go" are not allowed and will return an error.

API Requirements & Validation

File Path Requirements

The ThisFileIsMine function has strict validation requirements for the filePath parameter:

✅ Valid Paths:
  • "./internal/db/database.go" - Relative path with directory
  • "app/web/main.go" - Relative path with subdirectory
  • "/absolute/path/main.go" - Absolute path
  • "pwa/main.server.go" - Path with filename containing dots
❌ Invalid Paths:
  • "" - Empty string
  • "database.go" - Filename only (no directory separators)
  • "file.go" - Filename only (no directory separators)
Validation Rules:
  • filePath cannot be empty
  • filePath must contain at least one directory separator (/ or \)
  • Simple filenames without directory paths will return an error

This validation ensures deterministic file ownership by preventing ambiguity between files with the same name in different directories.

Performance & Caching

godepfind now includes an intelligent caching system that dramatically improves performance in development environments:

📊 Benchmark Summary
Scenario Without Cache With Cache Speedup
GoFileComesFromMain ~14,000,000 ns/op ~194 ns/op ~72,000x
ThisFileIsMine ~13,000,000 ns/op ~22,000 ns/op ~590x
Real-World Scenario ~8,500,000 ns/op ~310 ns/op* ~27,000x*
Multiple Files ~55,000,000 ns/op ~860 ns/op ~64,000x
Cache Invalidation N/A ~305 ns/op -

See docs/BENCHMARK.md for full results and details.

Note: Real-World Scenario with cache is extremely fast; actual value is similar to other cached operations.

Expert Note: GoDepFind's cache system achieves real-time performance (from ~14,000,000 ns/op to ~194 ns/op, ~72,000x faster) and reduces memory allocations to nearly zero in repeated queries. This makes it highly suitable for modern development environments, file watchers, and incremental build systems.

  • Lazy Loading: Cache is built only when needed
  • Selective Invalidation: Only affected packages are re-analyzed when files change
  • Memory Efficient: Cache is stored in memory and cleaned up automatically
  • Event-Driven: Cache updates automatically based on file change events

This makes godepfind suitable for real-time file watching in development tools.

Use Cases

  1. Smart Build Systems: Only rebuild applications affected by code changes with automatic cache management
  2. Development Tools: IDE extensions that show which apps are affected by current changes
  3. File Watchers: Real-time development environments that respond to file changes intelligently
  4. CI/CD Optimization: Reduce build time by targeting only affected main packages
  5. Dependency Analysis: Understand how your modules are interconnected
  6. Refactoring Safety: Know the blast radius of changes before making them

Requirements

  • Go 1.19+
  • Valid Go module (go.mod file)
  • Project must be buildable with go list ./...

Acknowledgments

This library is based on the excellent work of Andrew Wilkins and his rdep tool. The core reverse dependency detection logic was adapted and extended from his implementation to provide file-to-main package mapping functionality.

Special thanks to Andrew for creating the foundational reverse dependency analysis that made this library possible.

Contributing

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type FileImpactResult

type FileImpactResult struct {
	Status           string   `json:"status"`
	Reason           string   `json:"reason,omitempty"`
	BelongsToHandler bool     `json:"belongs_to_handler"`
	AffectedMains    []string `json:"affected_mains"`
	Impact           string   `json:"impact"`
}

FileImpactResult represents the result of file impact analysis

type GoDepFind

type GoDepFind struct {
	// contains filtered or unexported fields
}

func New

func New(rootDir string) *GoDepFind

New creates a new GoDepFind instance with the specified root directory

func (*GoDepFind) AnalyzeFileImpact

func (g *GoDepFind) AnalyzeFileImpact(mainInputFileRelativePath, fileName, filePath, event string) (*FileImpactResult, error)

AnalyzeFileImpact analyzes the impact of a file change with validation Yet another example showing reusability

func (*GoDepFind) CheckFileOwnership

func (g *GoDepFind) CheckFileOwnership(mainInputFileRelativePath, fileName, filePath string) (string, error)

CheckFileOwnership checks if a file belongs to a handler with validation Another example of reusing the validation function

func (*GoDepFind) DebugThisFileIsMine

func (g *GoDepFind) DebugThisFileIsMine(mainInputFileRelativePath, fileAbsPath, event string) (bool, error)

DebugThisFileIsMine provides detailed debugging for production issues with ThisFileIsMine returning unexpected results

func (*GoDepFind) FindReverseDeps

func (g *GoDepFind) FindReverseDeps(sourcePath string, targetPaths []string) ([]string, error)

FindReverseDeps finds packages in sourcePath that import any of the targetPaths

func (*GoDepFind) FindReverseDepsForFile

func (g *GoDepFind) FindReverseDepsForFile(mainInputFileRelativePath, fileName, filePath string) ([]string, error)

FindReverseDepsForFile finds reverse dependencies for a specific file with validation This is an example of how to reuse the validation function in other public APIs

func (*GoDepFind) GoFileComesFromMain

func (g *GoDepFind) GoFileComesFromMain(fileName string) ([]string, error)

GoFileComesFromMain finds which main packages depend on the given file (cached version) fileName: the name of the file to check (e.g., "module3.go") Returns: slice of main package paths that depend on this file

func (*GoDepFind) SetTestImports

func (g *GoDepFind) SetTestImports(enabled bool)

SetTestImports enables or disables inclusion of test imports

func (*GoDepFind) ThisFileIsMine

func (g *GoDepFind) ThisFileIsMine(mainInputFileRelativePath, fileAbsPath, event string) (bool, error)

ThisFileIsMine decides whether the provided handler (identified by its main file path relative to the module root) should handle an event for the given file. It normalizes paths, validates the handler main file exists, updates internal caches when a main file is written (so dynamic import changes are recognized), and finally uses package dependency analysis to decide file ownership.

Important: preference is given to checking the specific handler main-file imports (not just directory-level mains). That lets the finder disambiguate multiple `main` packages that may live in the same directory but are selected via build tags (for example: two mains in one folder with `//go:build wasm` vs `//go:build !wasm`). In those cases the exact main file (and its build tags/imports) determines ownership.

Inputs:

  • mainInputFileRelativePath: handler main file (e.g. "pwa/main.server.go")
  • fileAbsPath: target file path (absolute or relative to module root)
  • event: one of "write","create","remove","rename" (drives cache ops)

Returns: (bool, error) — true when the handler should process the file.

func (*GoDepFind) ValidateInputForProcessing

func (g *GoDepFind) ValidateInputForProcessing(mainInputFileRelativePath, fileName, filePath string) (bool, error)

ValidateInputForProcessing validates handler and file before processing This function provides centralized validation that can be reused across multiple API endpoints.

It performs the following validations: 1. Handler validation (nil check and main file path validation) 2. Go file validation (syntax, completeness, and write-in-progress detection)

Returns:

  • shouldProcess: true if processing should continue, false if file should be skipped
  • error: validation error that should be returned to caller, or nil if validation passed

Usage patterns:

  • shouldProcess=true, error=nil: Continue with normal processing
  • shouldProcess=false, error=nil: Skip processing (file is being written, empty, etc.)
  • shouldProcess=false, error!=nil: Return error to caller (invalid handler, etc.)

ValidateInputForProcessing validates handler and file before processing This function provides centralized validation that can be reused across multiple API endpoints.

It performs the following validations: 1. Handler validation (non-empty main file path) 2. Go file validation (syntax, completeness, and write-in-progress detection)

Returns:

  • shouldProcess: true if processing should continue, false if file should be skipped
  • error: validation error that should be returned to caller, or nil if validation passed

Usage patterns:

  • shouldProcess=true, error=nil: Continue with normal processing
  • shouldProcess=false, error=nil: Skip processing (file is being written, empty, etc.)
  • shouldProcess=false, error!=nil: Return error to caller (invalid handler, etc.)

type GoFileValidator

type GoFileValidator struct{}

GoFileValidator provides methods to validate Go files before processing

func NewGoFileValidator

func NewGoFileValidator() *GoFileValidator

NewGoFileValidator creates a new validator instance

func (*GoFileValidator) HasMinimumGoContent

func (v *GoFileValidator) HasMinimumGoContent(filePath string) (bool, error)

HasMinimumGoContent checks if file has at least a package declaration

func (*GoFileValidator) IsFileBeingWritten

func (v *GoFileValidator) IsFileBeingWritten(filePath string) (bool, error)

IsFileBeingWritten tries to detect if a file is currently being written by checking for incomplete content patterns

func (*GoFileValidator) IsValidGoFile

func (v *GoFileValidator) IsValidGoFile(filePath string) (bool, error)

IsValidGoFile checks if a Go file is valid and safe to process

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL