Thank you for your interest in contributing to Emu! This document provides guidelines and information for contributors.
- Code of Conduct
- How to Contribute
- Development Setup
- Project Structure
- Coding Standards
- Testing Guidelines
- Submitting Changes
- Review Process
This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code.
We welcome various types of contributions:
- Bug Reports: Help us identify and fix issues
- Feature Requests: Suggest new features or improvements
- Code Contributions: Bug fixes, new features, performance improvements
- Documentation: Improve or expand documentation
- Testing: Add or improve test coverage
- Platform Support: Enhance Windows/Linux support
- Check existing issues: Look for existing bug reports or feature requests
- Create an issue: For new features or significant changes, create an issue first to discuss
- Fork the repository: Create a personal fork to work on changes
- Create a branch: Use descriptive branch names like
feature/device-creationorfix/android-state-detection
- asdf: Version manager for tool versions
- Git: For version control
- Android SDK: For Android development testing
- Xcode (macOS): For iOS development testing
# Clone your fork
git clone https://github.com/your-username/emu.git
cd emu
# Add upstream remote
git remote add upstream https://github.com/wasabeef/emu.git
# Install asdf (if not already installed)
# macOS
brew install asdf
# Linux - see https://asdf-vm.com/guide/getting-started.html
# Add asdf plugins
asdf plugin add rust
asdf plugin add bun
# Install the tool versions specified in .tool-versions
asdf install
# Install development dependencies (including lefthook)
bun install
# Build and test
cargo build
cargo test --bins --tests # Recommended: excludes doctests
# cargo test # Optional: includes doctests (may have import issues)
# Try running the application
cargo run# Install cargo-watch for live reload during development
cargo install cargo-watch
# Install clippy for linting
rustup component add clippy
# Install rustfmt for formatting
rustup component add rustfmt# Run with live reload
cargo watch -x run
# Run tests with live reload
cargo watch -x test
# Format code
cargo fmt
# Lint code
cargo clippy
# Run specific test
cargo test test_name
# Run test with output
cargo test test_name -- --nocapturesrc/
├── app/ # Application core
│ ├── mod.rs # Main app logic, event loop
│ ├── state.rs # AppState, device state management
│ ├── events.rs # Event type definitions
│ └── actions.rs # User action handlers
├── managers/ # Platform-specific device management
│ ├── common.rs # DeviceManager trait
│ ├── android.rs # Android AVD management
│ └── ios.rs # iOS Simulator management
├── models/ # Data structures and types
│ ├── device.rs # Device models (AndroidDevice, IosDevice)
│ ├── error.rs # Error types and handling
│ └── platform.rs # Platform enums
├── ui/ # Terminal user interface
│ ├── render.rs # Main rendering logic
│ ├── theme.rs # Color themes and styling
│ └── widgets.rs # Custom UI widgets
└── utils/ # Shared utilities
├── command.rs # Command execution helpers
└── logger.rs # Logging utilities
All device managers implement the DeviceManager trait with async methods:
#[async_trait]
pub trait DeviceManager: Send + Sync + Clone {
async fn list_devices(&self) -> Result<Vec<Device>>;
async fn start_device(&self, id: &str) -> Result<()>;
// ... other methods
}Centralized state with thread-safe access:
pub struct App {
state: Arc<Mutex<AppState>>,
// ... other fields
}Use anyhow for error propagation and thiserror for custom errors:
#[derive(thiserror::Error, Debug)]
pub enum DeviceError {
#[error("Device not found: {name}")]
NotFound { name: String },
// ... other variants
}- Use
cargo fmtfor automatic formatting - Follow standard Rust naming conventions
- Use 4 spaces for indentation
- Run
cargo clippyand fix all warnings - Use meaningful variable and function names
- Add documentation comments for public APIs
- Keep functions focused and reasonably sized
- Use
Result<T, E>for fallible operations - Provide helpful error messages with context
- Use
anyhow::Contextto add context to errors
// Good
fn parse_device_config(content: &str) -> Result<DeviceConfig> {
serde_json::from_str(content)
.with_context(|| format!("Failed to parse device config: {}", content))
}
// Avoid
fn parse_device_config(content: &str) -> DeviceConfig {
serde_json::from_str(content).unwrap()
}- Use
async/awaitconsistently - Avoid blocking operations in async contexts
- Use
tokio::spawnfor background tasks - Handle task cancellation properly
// Good
let handle = tokio::spawn(async move {
// Long-running background task
});
// Cancel task when needed
if let Some(handle) = self.background_task.take() {
handle.abort();
}- Use
///for public API documentation - Use
//for implementation comments - Include examples for complex functions
/// Creates a new Android Virtual Device with the specified configuration.
///
/// # Arguments
/// * `config` - Device configuration including name, API level, and hardware specs
///
/// # Returns
/// * `Ok(())` - Device created successfully
/// * `Err(DeviceError)` - Creation failed
///
/// # Example
/// ```rust
/// let config = DeviceConfig::new("Pixel_7_API_31", "pixel_7", "31");
/// manager.create_device(&config).await?;
/// ```
pub async fn create_device(&self, config: &DeviceConfig) -> Result<()> {
// Implementation
}Use conventional commit format:
type(scope): description
[optional body]
[optional footer]
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Formatting changesrefactor: Code refactoringtest: Adding or modifying testsperf: Performance improvements
Examples:
feat(android): add device creation with custom RAM/storage
fix(ios): resolve simulator state detection issue
docs(readme): update installation instructions
test(device): add comprehensive device lifecycle tests
Located in the same file as the code being tested:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_device_name_validation() {
assert!(is_valid_device_name("Valid_Name_123"));
assert!(!is_valid_device_name("Invalid Name!"));
}
}Located in tests/ directory:
// tests/device_creation_test.rs
use emu::managers::AndroidManager;
#[tokio::test]
async fn test_complete_device_lifecycle() {
let manager = AndroidManager::new().unwrap();
// Test device creation, start, stop, delete
}Validate performance requirements:
#[tokio::test]
async fn test_startup_performance() {
let start = std::time::Instant::now();
let app = App::new().await?;
let duration = start.elapsed();
assert!(duration < std::time::Duration::from_millis(150));
}- Write tests first for new features (TDD)
- Test error conditions as well as success paths
- Use meaningful test names that describe what is being tested
- Mock external dependencies using the
mockallcrate - Test async code using
#[tokio::test] - Validate performance for critical paths
# Run all tests (recommended - excludes doctests)
cargo test --bins --tests
# Run all tests including doctests (may have import issues in examples)
cargo test
# Run specific test file
cargo test --test device_creation_test
# Run with output
cargo test -- --nocapture
# Run performance tests
cargo test responsiveness_validation_test -- --nocapture
# Run tests for specific module
cargo test android::-
Update your fork:
git fetch upstream git checkout main git merge upstream/main
-
Create a feature branch:
git checkout -b feature/your-feature-name
-
Make your changes following the coding standards
-
Test your changes:
cargo test --bins --tests cargo clippy cargo fmt --check -
Commit your changes:
git add . git commit -m "feat(scope): description of changes"
-
Push to your fork:
git push origin feature/your-feature-name
-
Create a Pull Request on GitHub
- Code follows the style guidelines
- Self-review of the code
- Tests added for new functionality
- All tests pass
- Documentation updated (if applicable)
- No clippy warnings
- Code is formatted with
cargo fmt
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
Describe the tests you ran to verify your changes
## Screenshots (if applicable)
Add screenshots for UI changes
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Tests added/updated
- [ ] All tests pass
- [ ] Documentation updated-
Code Quality
- Clean, readable code
- Proper error handling
- Adequate test coverage
- Performance considerations
-
Design Consistency
- Follows existing patterns
- Maintains architectural principles
- Proper separation of concerns
-
User Experience
- Intuitive interfaces
- Clear error messages
- Responsive performance
- Initial Response: Within 2-3 days
- Full Review: Within 1 week
- Follow-up: Within 2-3 days of updates
- Read feedback carefully and ask questions if unclear
- Make requested changes in additional commits
- Update tests if needed
- Respond to comments when changes are made
- Request re-review when ready
- Issues: For bug reports and feature requests
- Discussions: For questions and general discussion
- Pull Requests: For code review discussions
If you need help with:
- Setup Issues: Create an issue with the "help wanted" label
- Architecture Questions: Start a discussion
- Code Review: Ask in the PR comments
Contributors are recognized in several ways:
- Listed in the project's contributors
- Mentioned in release notes for significant contributions
- Added to the CONTRIBUTORS.md file
Thank you for contributing to Emu! 🎉