This guide is for developers who want to contribute to Gopher or understand its internal architecture.
Gopher follows a modular architecture with clear separation of concerns:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ CLI Layer │ │ Manager Layer │ │ Storage Layer │
│ │ │ │ │ │
│ cmd/gopher/ │◄──►│ internal/runtime│◄──►│ internal/installer│
│ main.go │ │ manager.go │ │ installer.go │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ ┌─────────────────┐ ┌─────────────────┐
│ │ System Layer │ │ Download Layer │
└──────────────►│ │ │ │
│ internal/runtime│ │ internal/downloader│
│ system.go │ │ downloader.go │
└─────────────────┘ └─────────────────┘
│
┌─────────────────┐
│ Config Layer │
│ │
│ internal/config │
│ config.go │
└─────────────────┘
cmd/gopher/): Command-line interface and argument parsinginternal/runtime/): Core version management logicinternal/runtime/system.go): System Go detection and managementinternal/downloader/): Go version downloading and verificationinternal/installer/): Go version installation and extractioninternal/config/): Configuration managementinternal/env/): Environment variable managementinternal/progress/): Progress bars and spinnersinternal/runtime/alias*.go): Version alias managementgopher/
├── cmd/
│ └── gopher/
│ ├── main.go # CLI entry point
│ ├── setup.go # Shell integration setup
│ ├── helpers.go # CLI helper functions
│ └── interactive.go # Interactive mode handling
├── internal/
│ ├── runtime/
│ │ ├── types.go # Core data structures
│ │ ├── manager.go # Main manager implementation
│ │ ├── list.go # List installed/available versions
│ │ ├── install.go # Install/uninstall operations
│ │ ├── switch.go # Version switching
│ │ ├── system.go # System Go detection
│ │ ├── environment.go # Environment setup
│ │ ├── alias*.go # Alias management (5 files)
│ │ └── *_test.go # Comprehensive test suite
│ ├── config/
│ │ ├── config.go # Configuration management
│ │ ├── env_test.go # Environment tests
│ │ └── config_test.go # Config tests
│ ├── downloader/
│ │ ├── downloader.go # Download and verification
│ │ └── downloader_test.go # Download tests
│ ├── installer/
│ │ ├── installer.go # Installation and extraction
│ │ └── installer_test.go # Installer tests
│ ├── env/
│ │ ├── env.go # Environment variable provider
│ │ └── env_test.go # Env tests
│ ├── progress/
│ │ ├── bar.go # Progress bars
│ │ ├── spinner.go # Spinners
│ │ └── terminal.go # Terminal handling
│ ├── errors/
│ │ └── errors.go # Error handling
│ └── security/
│ └── checksum.go # Checksum verification
├── test/
│ ├── e2e.sh # E2E test suite (19 tests)
│ └── integration_test.go # Integration tests
├── docs/
│ ├── USER_GUIDE.md # User documentation
│ ├── DEVELOPER_GUIDE.md # This file
│ ├── MAKEFILE_GUIDE.md # Makefile reference
│ └── ROADMAP.md # Future plans
├── go.mod # Go module definition
├── go.sum # Go module checksums
├── Makefile # Build and development commands
└── README.md # Project overview
# Clone the repository
git clone https://github.com/molmedoz/gopher.git
cd gopher
# Install dependencies
go mod tidy
# Build the project
make build
# Run tests
make test
Install recommended development tools:
# Install development tools
make install-tools
# This installs:
# - golangci-lint (linting)
# - goimports (import formatting)
# - air (hot reloading)
# Build for current platform
make build
# Build for all platforms
make build-all
# Build development version
make build-dev
# Clean build artifacts
make clean
# Run all tests
make test
# Run tests with verbose output
make test-verbose
# Run tests with coverage
make test-coverage
# Run specific package tests
go test ./internal/version -v
# Format code
make format
# Run linter
make lint
# Run go vet
make vet
# Run all checks
make check
# Start development cycle
make dev
# This runs:
# 1. Format code
# 2. Run linter
# 3. Run tests
# 4. Build binary
Follow standard Go conventions:
// Package comment
package version
import (
"fmt"
"time"
)
// Interface comment
type ManagerInterface interface {
// Method comment
ListInstalled() ([]Version, error)
}
// Struct comment
type Version struct {
Version string `json:"version"` // Field comment
OS string `json:"os"` // Field comment
Arch string `json:"arch"` // Field comment
InstalledAt time.Time `json:"installed_at"`
IsActive bool `json:"is_active"`
IsSystem bool `json:"is_system"`
Path string `json:"path"`
}
// Method comment
func (v Version) String() string {
if v.IsSystem {
return fmt.Sprintf("%s (%s/%s) [system]", v.Version, v.OS, v.Arch)
}
return fmt.Sprintf("%s (%s/%s)", v.Version, v.OS, v.Arch)
}
version, config)-er (e.g., ManagerInterface)Version, SystemDetector)versionString, configPath)Always handle errors explicitly:
// Good
func (m *Manager) Install(version string) error {
if err := ValidateVersion(version); err != nil {
return fmt.Errorf("invalid version: %w", err)
}
// ... rest of implementation
}
// Bad
func (m *Manager) Install(version string) error {
ValidateVersion(version) // Error ignored
// ... rest of implementation
}
Write comprehensive tests:
func TestValidateVersion(t *testing.T) {
tests := []struct {
name string
version string
wantErr bool
}{
{"valid version", "1.21.0", false},
{"invalid version", "invalid", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateVersion(tt.version)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateVersion() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
If new data is needed, update the relevant structs:
// Add new fields to existing structs
type Version struct {
// ... existing fields
NewField string `json:"new_field"`
}
// Or create new structs
type NewFeature struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
Add new methods to interfaces:
type ManagerInterface interface {
// ... existing methods
NewMethod() (NewFeature, error)
}
Implement the feature in the appropriate layer:
func (m *Manager) NewMethod() (NewFeature, error) {
// Implementation
}
Add new commands or options:
case "new-command":
return newCommand(manager, args)
Write comprehensive tests:
func TestNewMethod(t *testing.T) {
// Test implementation
}
Enable debug output:
export GOPHER_DEBUG=1
gopher list
Add debug logging:
import "log"
func (m *Manager) Install(version string) error {
if os.Getenv("GOPHER_DEBUG") == "1" {
log.Printf("Installing version: %s", version)
}
// ... implementation
}
# Test specific functionality
go test -run TestSystemDetector ./internal/version -v
# Test with race detection
go test -race ./internal/version
# Test with coverage
go test -cover ./internal/version
// Add debug logging to system detection
func (sd *SystemDetector) DetectSystemGo() (*Version, error) {
goPath, err := exec.LookPath("go")
if err != nil {
log.Printf("DEBUG: go not found in PATH: %v", err)
return nil, err
}
log.Printf("DEBUG: found go at: %s", goPath)
// ... rest of implementation
}
// Add debug logging to downloader
func (d *Downloader) Download(version, downloadDir string) (string, error) {
log.Printf("DEBUG: downloading version %s to %s", version, downloadDir)
// ... implementation
}
Update version in cmd/gopher/main.go:
const versionString = "gopher v1.0.0"
Create or update CHANGELOG.md:
## [0.2.0] - 2024-01-01
### Added
- New feature X
- New command Y
### Changed
- Improved performance
- Updated dependencies
### Fixed
- Bug fix A
- Bug fix B
# Run all tests
make test
# Run with race detection
make race-test
# Run linting
make lint
# Run security checks
make security
# Build for all platforms
make build-all
# Create distribution packages
make dist
# Tag the release
git tag v1.0.0
git push origin v1.0.0
# Create GitHub release
gh release create v1.0.0 \
--title "Gopher v1.0.0" \
--notes "$(cat CHANGELOG.md)" \
build/release/*
Use conventional commits:
feat: add system Go detection
fix: resolve download timeout issue
docs: update API reference
test: add tests for system detection
refactor: simplify version parsing
# Clean and rebuild
make clean
make build
# Check Go version
go version
# Update dependencies
go mod tidy
# Run tests with verbose output
go test -v ./...
# Run specific test
go test -run TestSpecific ./internal/version -v
# Check for race conditions
go test -race ./...
# Fix formatting
go fmt ./...
# Fix imports
goimports -w .
# Run linter
golangci-lint run
This developer guide provides comprehensive information for contributing to Gopher. For more specific questions, see the API Reference or open an issue on GitHub.