Developer Onboarding
Complete guide for new developers joining the govman project.
Welcome! š
Thank you for your interest in contributing to govman! This guide will help you get started quickly and effectively.
What is govman?
govman is a Go Version Manager that allows developers to:
- Install and manage multiple Go versions
- Quickly switch between Go versions
- Automatically switch versions based on project requirements
- Work across Linux, macOS, and Windows
Prerequisites
Before you begin, ensure you have:
Required
- Go 1.20+: Download
- Git: Download
- Make: Usually pre-installed on Linux/macOS; Windows users can use Make for Windows
Recommended
- VS Code with Go extension
- delve debugger:
go install github.com/go-delve/delve/cmd/dlv@latest - Terminal with shell support (Bash, Zsh, Fish, or PowerShell)
Check Your Setup
# Verify Go
go version # Should be 1.20 or higher
# Verify Git
git --version
# Verify Make
make --version
Quick Start (5 Minutes)
1. Clone the Repository
# Clone
git clone https://github.com/justjundana/govman.git
cd govman
# Fork first if you plan to contribute
# Then clone your fork:
# git clone https://github.com/YOUR_USERNAME/govman.git
2. Build the Project
# Download dependencies
go mod download
# Build binary
make build
# Verify build
./build/govman --version
3. Run Tests
# Run all tests
make test
# Run with coverage
make test-coverage
# Run specific package tests
go test ./internal/manager
4. Try It Out
# Install locally (optional)
make install
# Or run directly
./build/govman list
# Try installing a Go version
./build/govman install 1.21.5
Project Structure
govman/
āāā cmd/
ā āāā govman/ # Main application entry point
ā āāā main.go # CLI initialization
ā
āāā internal/ # Private application code
ā āāā cli/ # CLI commands (10 files)
ā āāā config/ # Configuration management
ā āāā downloader/ # Download & extraction
ā āāā golang/ # Go releases API
ā āāā logger/ # Logging system
ā āāā manager/ # Core business logic
ā āāā progress/ # Progress bars
ā āāā shell/ # Shell integration
ā āāā symlink/ # Symlink management
ā āāā util/ # Utilities
ā āāā version/ # Version information
ā
āāā docs/ # Documentation (you're here!)
āāā scripts/ # Installation scripts
āāā build/ # Build output (ignored by git)
āāā Makefile # Build automation
āāā go.mod # Go module definition
āāā go.sum # Dependency checksums
Key Files to Know
| File | Purpose |
|---|---|
cmd/govman/main.go | Application entry point |
internal/cli/cli.go | Root command & CLI setup |
internal/manager/manager.go | Core business logic |
internal/config/config.go | Configuration management |
Makefile | Build commands |
Development Workflow
1. Create a Branch
# Update main
git checkout main
git pull origin main
# Create feature branch
git checkout -b feature/my-new-feature
# Or for bug fixes
git checkout -b fix/issue-123
2. Make Changes
# Edit files
vim internal/cli/install.go
# Build frequently
make build
# Test your changes
go test ./internal/cli
# Run the binary
./build/govman install 1.21.5
3. Write Tests
// internal/cli/install_test.go
func TestInstall(t *testing.T) {
tests := []struct {
name string
version string
wantErr bool
}{
{"valid version", "1.21.5", false},
{"invalid version", "invalid", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := runInstall(tt.version)
if (err != nil) != tt.wantErr {
t.Errorf("runInstall() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
4. Format and Lint
# Format code
go fmt ./...
# Run linter (install first: go install golang.org/x/lint/golint@latest)
golint ./...
# Run vet
go vet ./...
5. Commit Your Changes
# Stage changes
git add .
# Commit with descriptive message
git commit -m "feat: add support for arm64 architecture"
# Or for bug fixes
git commit -m "fix: resolve symlink issue on Windows"
Commit Message Format:
feat:New featurefix:Bug fixdocs:Documentation changestest:Test additions/changesrefactor:Code refactoringchore:Maintenance tasks
6. Push and Create PR
# Push to your fork
git push origin feature/my-new-feature
# Create Pull Request on GitHub
# Go to https://github.com/justjundana/govman/pulls
# Click "New Pull Request"
Common Development Tasks
Adding a New CLI Command
- Create command file:
touch internal/cli/mycommand.go
- Implement command:
// internal/cli/mycommand.go
package cli
import (
"github.com/spf13/cobra"
)
var myCmd = &cobra.Command{
Use: "mycommand <arg>",
Short: "Description of my command",
Long: "Longer description...",
Args: cobra.ExactArgs(1),
Run: runMyCommand,
}
func runMyCommand(cmd *cobra.Command, args []string) {
arg := args[0]
// Use manager
mgr := getManager()
if err := mgr.MyOperation(arg); err != nil {
_logger.Error("Operation failed: %v", err)
os.Exit(1)
}
_logger.Success("Operation completed!")
}
func init() {
// Add flags if needed
myCmd.Flags().StringP("option", "o", "", "option description")
}
- Register command:
// internal/cli/command.go
func init() {
rootCmd.AddCommand(
// ... existing commands
myCmd, // Add your command
)
}
- Add tests:
// internal/cli/mycommand_test.go
func TestMyCommand(t *testing.T) {
// Test implementation
}
Adding a Configuration Option
- Update Config struct:
// internal/config/config.go
type Config struct {
// Existing fields...
MyNewOption string `mapstructure:"my_new_option"`
}
- Set default value:
func (c *Config) setDefaults() {
// Existing defaults...
viper.SetDefault("my_new_option", "default_value")
}
- Update example config:
# config.yaml.example
my_new_option: "default_value" # Description
- Document in docs:
<!-- docs/configuration.md -->
### my_new_option
Description of the option.
Adding Shell Support
- Implement Shell interface:
// internal/shell/shell.go
type MyShell struct{}
func (s *MyShell) Name() string {
return "myshell"
}
func (s *MyShell) ConfigFile() string {
return filepath.Join(os.Getenv("HOME"), ".myshellrc")
}
func (s *MyShell) PathCommand(binPath string) string {
return fmt.Sprintf(`export PATH="%s:$PATH"`, binPath)
}
func (s *MyShell) SetupCommands(binPath string) []string {
return []string{
"# govman initialization",
fmt.Sprintf(`export PATH="%s:$PATH"`, binPath),
// Auto-switch hook
`govman_auto_switch() {`,
` if [[ -f .govman-version ]]; then`,
` local required_version=$(cat .govman-version 2>/dev/null)`,
` govman use "$required_version" >/dev/null 2>&1`,
` fi`,
`}`,
`PROMPT_COMMAND="govman_auto_switch; $PROMPT_COMMAND"`,
}
}
- Register shell:
// internal/shell/shell.go
func Detect() Shell {
shell := os.Getenv("SHELL")
switch {
// Existing cases...
case strings.Contains(shell, "myshell"):
return &MyShell{}
}
}
Testing Guidelines
Unit Tests
Test individual functions:
func TestNormalizeVersion(t *testing.T) {
tests := []struct {
input string
want string
}{
{"1.21.5", "go1.21.5"},
{"go1.21.5", "go1.21.5"},
{"1.21", "go1.21.0"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got := normalizeVersion(tt.input)
if got != tt.want {
t.Errorf("normalizeVersion(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
Integration Tests
Test component interactions:
//go:build integration
func TestRealInstall(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}
// Real test with actual downloads
mgr := setupTestManager(t)
err := mgr.Install("1.21.5")
if err != nil {
t.Fatalf("Install failed: %v", err)
}
}
Run:
# Unit tests only
go test -short ./...
# Integration tests
go test -tags=integration ./...
Debugging
VS Code Debug Configuration
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug govman",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/cmd/govman",
"args": ["install", "1.21.5"],
"env": {
"GOVMAN_CONFIG": "${workspaceFolder}/test-config.yaml"
}
}
]
}
Command Line Debugging
# Using delve
dlv debug ./cmd/govman -- install 1.21.5
# Set breakpoint
(dlv) break internal/manager/manager.go:45
(dlv) continue
# Inspect
(dlv) print version
(dlv) locals
Print Debugging
func (m *Manager) Install(version string) error {
fmt.Printf("DEBUG: Install called with version=%q\n", version)
fmt.Printf("DEBUG: config=%+v\n", m.config)
// ... rest of function
}
Code Style
Follow Effective Go
Key Conventions
- Naming:
// Good
func GetVersion() string
var maxRetries int
// Bad
func get_version() string
var MAX_RETRIES int
- Error handling:
// Good
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
// Bad
if err != nil {
panic(err)
}
- Comments:
// Good: Comment explains why
// Use larger buffer for better performance with large files
const bufferSize = 64 * 1024
// Bad: Comment states the obvious
// Set buffer size
const bufferSize = 64 * 1024
Getting Help
Documentation
- Quick Start - User guide
- Architecture - System design
- Troubleshooting - Debugging guide
Community
- GitHub Issues: govman/issues
- Discussions: govman/discussions
- Email: Contact maintainers
Before Asking
- Check existing documentation
- Search closed issues
- Try debugging yourself
- Prepare a minimal reproduction
When Asking
Include:
- What you're trying to do
- What you've tried
- Relevant code snippets
- Error messages
- Environment details (OS, Go version)
Contribution Checklist
Before submitting a PR:
- Code builds successfully:
make build - Tests pass:
make test - Code is formatted:
go fmt ./... - Code is vetted:
go vet ./... - New features have tests
- Documentation is updated
- Commit messages are clear
- Branch is up to date with main
Learning Path
Week 1: Familiarization
- Clone and build project
- Run all tests
- Read architecture documentation
- Try using govman yourself
- Explore codebase structure
Week 2: Small Changes
- Fix a typo in documentation
- Add a test case
- Improve an error message
- Submit your first PR
Week 3: Feature Development
- Pick a "good first issue"
- Implement the feature
- Write comprehensive tests
- Update documentation
Week 4+: Core Contributor
- Review others' PRs
- Help with issues
- Propose new features
- Improve architecture
Best Practices
1. Write Tests First
// Write the test
func TestNewFeature(t *testing.T) {
got := newFeature()
want := "expected"
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// Then implement
func newFeature() string {
return "expected"
}
2. Keep Changes Small
- One PR per feature/fix
- Easy to review
- Faster to merge
3. Document As You Go
- Update docs with code
- Add code comments
- Write clear commit messages
4. Communicate Early
- Open issue before big changes
- Ask questions in discussions
- Request feedback early
Next Steps
Now that you're set up:
- Explore: Browse the codebase, read the architecture docs
- Build: Make a small change and see it work
- Contribute: Pick an issue and submit a PR
- Learn: Review others' code, ask questions
Welcome to the team! š
See Also
- Getting Started - Quick development setup
- Architecture - System design
- Project Structure - Code organization
- Contributing Guide - Contribution guidelines
Happy coding! We're excited to have you here! š»āØ