A powerful Python package for analyzing Fortran codebases. Perfect for understanding legacy code, preparing for modernization, analyzing dependencies, and planning translations to other languages.
Key Use Case: Analyze multiple Fortran projects with a single, flexible tool.
- Universal: Works with any Fortran codebase (F77, F90, F95, F2003, F2008)
- Zero Configuration: Auto-detects project type and analyzes with sensible defaults
- Batch Processing: Analyze multiple codebases efficiently with templates
- Rich Insights: Dependency graphs, translation recommendations, code metrics
- Multiple Outputs: JSON, YAML, GraphML, and interactive HTML visualizations
- Python & CLI: Use as a Python library or command-line tool
- Dependency Analysis: Build module dependency graphs, identify circular dependencies
- Translation Planning: Break large procedures into manageable translation units
- Call Graph Generation: Visualize relationships between modules and procedures
- Project Templates: Pre-configured for CTSM, climate models, numerical libraries, and more
- Extensible: Easy to customize for specific project needs
# Recommended: Install with advanced parsing support
pip install fortran-analyzer[fparser]
# Or just the basics
pip install fortran-analyzer
# Development installation
git clone https://github.com/yourusername/fortran-analyzer.git
cd fortran-analyzer
pip install -e .[dev]Optional Features:
[fparser]- Advanced Fortran parsing (recommended)[interactive]- Interactive visualizations with Plotly[full]- All features included
📖 New to Fortran Analyzer? Check out the Quick Start Guide for a 5-minute tutorial!
Analyze a single Fortran project:
from fortran_analyzer import quick_analyze
# Auto-detect and analyze - that's it!
results = quick_analyze('/path/to/fortran/project')
# Access results
print(f"Files: {results['parsing']['statistics']['total_files']}")
print(f"Modules: {len(results['parsing']['modules'])}")
print(f"Translation units: {results['translation']['units']}")Analyze multiple codebases:
from fortran_analyzer import create_analyzer_for_project
# Analyze multiple projects in a loop
projects = {
'climate_model_a': '/path/to/model_a',
'climate_model_b': '/path/to/model_b',
'library_c': '/path/to/library_c'
}
for name, path in projects.items():
print(f"\nAnalyzing {name}...")
# Auto-detect project type and analyze
analyzer = create_analyzer_for_project(
path,
template='auto',
output_dir=f'analysis_results/{name}'
)
results = analyzer.analyze()
summary = analyzer.get_summary_statistics()
print(f" Files: {summary['files']}")
print(f" Modules: {summary['modules']}")
print(f" Lines: {summary['lines']:,}")Use from command line:
# Auto-detect and analyze
fortran-analyzer analyze /path/to/project --template auto
# Analyze with specific template
fortran-analyzer analyze /path/to/ctsm --template ctsm
# Batch process multiple projects
for project in /path/to/projects/*; do
fortran-analyzer analyze "$project" --template auto \
--output-dir "results/$(basename $project)"
donefrom fortran_analyzer import quick_analyze
# Quick overview of a new project
results = quick_analyze('/path/to/unknown/project', output_dir='./analysis')
# Check the generated files:
# - analysis_summary.txt: Human-readable overview
# - analysis_results.json: Full structured data
# - graphs/: Dependency visualizationsfrom fortran_analyzer import create_analyzer_for_project
# Analyze for translation planning
analyzer = create_analyzer_for_project(
'/path/to/legacy/code',
template='auto',
max_translation_unit_lines=150 # Customize chunk size
)
results = analyzer.analyze()
# Get translation recommendations
recommendations = results['recommendations']
print("Translation Strategy:", recommendations['translation_strategy'])
print("Dependency Issues:", recommendations['dependency_issues'])
# Get suggested order
translation_order = analyzer.get_translation_order()
print(f"Start with: {translation_order[:5]}")from fortran_analyzer import create_analyzer_for_project
import json
# Compare multiple similar projects
codebases = ['codebase_v1', 'codebase_v2', 'codebase_v3']
comparison = {}
for codebase in codebases:
analyzer = create_analyzer_for_project(f'/path/to/{codebase}')
results = analyzer.analyze()
comparison[codebase] = {
'modules': len(results['parsing']['modules']),
'lines': results['parsing']['statistics']['total_lines'],
'dependencies': results['dependencies']['module_graph_summary']['edges'],
'circular_deps': len(results['dependencies']['analysis']['circular_dependencies'])
}
# Save comparison
with open('codebase_comparison.json', 'w') as f:
json.dump(comparison, f, indent=2)
# Print comparison table
for name, stats in comparison.items():
print(f"{name:20} | Modules: {stats['modules']:4} | Lines: {stats['lines']:8,} | Deps: {stats['dependencies']:4}")from fortran_analyzer.config.project_config import FortranProjectConfig
from fortran_analyzer import FortranAnalyzer
# Full control with custom configuration
config = FortranProjectConfig(
project_name="My Legacy System",
project_root="/path/to/project",
source_dirs=["src", "lib", "modules"],
fortran_extensions=[".f90", ".F90", ".f", ".F"],
max_translation_unit_lines=80,
external_libraries=["netcdf", "hdf5", "mpi"],
system_modules=["iso_fortran_env", "iso_c_binding"],
exclude_patterns=["**/test_*", "**/deprecated/**"],
generate_graphs=True
)
analyzer = FortranAnalyzer(config)
results = analyzer.analyze()📚 Comprehensive Guides:
- Quick Start Guide - Get started in 5 minutes
- Batch Analysis Guide - Analyze multiple codebases efficiently
- API Documentation - Complete API reference
- Configuration Guide - Advanced configuration options
- Examples - Working code examples including batch processing
The analyzer comes with predefined templates for common project types:
ctsm: Community Terrestrial Systems Modelscientific_computing: General scientific computing projectsnumerical_library: Numerical libraries and mathematical softwareclimate_model: Climate and atmospheric modelsgeneric: Generic Fortran projects
Key configuration parameters:
# Project identification
project_name: "My Fortran Project"
project_root: "/path/to/project"
# Source code locations
source_dirs:
- "src"
- "lib"
# File patterns
include_patterns:
- "**/*.f90"
- "**/*.F90"
exclude_patterns:
- "**/test_*"
# Fortran settings
fortran_extensions: [".f90", ".F90"]
fortran_standard: "f2003"
# Translation unit settings
max_translation_unit_lines: 150
min_chunk_lines: 50
preserve_interfaces: true
# Dependencies
system_modules:
- "iso_fortran_env"
external_libraries:
- "netcdf"
- "mpi"
# Output settings
output_dir: "analysis_output"
generate_graphs: true
generate_metrics: trueThe analyzer generates several output files:
analysis_results.json: Complete analysis results in JSON formatanalysis_summary.txt: Human-readable summary reporttranslation_units.json: Translation unit decompositiongraphs/: Directory containing dependency graphs in various formatsvisualizations/: Directory containing visualization images and HTML files
Generated visualizations include:
- Module Dependency Graph: Shows relationships between modules
- Translation Units Analysis: Charts showing unit distribution and complexity
- Project Overview: Summary statistics and metrics
- Translation Priority Chart: Recommended translation order
- Interactive Dependency Graph: Web-based interactive visualization
from fortran_analyzer.parser.fortran_parser import FortranParser
class CustomFortranParser(FortranParser):
def parse_custom_construct(self, content):
# Custom parsing logic
passfrom fortran_analyzer.analysis.call_graph_builder import CallGraphBuilder
class CustomCallGraphBuilder(CallGraphBuilder):
def analyze_custom_relationships(self, modules):
# Custom analysis logic
pass# Export graphs for Gephi
analyzer.call_graph_builder.export_graphs(
output_dir, formats=['gexf']
)
# Export for NetworkX processing
module_graph = analyzer.call_graph_builder.get_module_graph()
# Use NetworkX algorithms...# Use in CI/CD pipeline
fortran-analyzer analyze $PROJECT_ROOT --template auto --output-dir ./reports
# Upload reports as artifacts"""
Analyze multiple Fortran codebases from a research organization.
"""
from fortran_analyzer import create_analyzer_for_project
from pathlib import Path
import json
# Define your codebases
codebases = {
'weather_model': {
'path': '/research/weather/model',
'template': 'climate_model'
},
'ocean_dynamics': {
'path': '/research/ocean/dynamics',
'template': 'scientific_computing'
},
'linear_algebra_lib': {
'path': '/research/libs/linalg',
'template': 'numerical_library'
}
}
# Analyze all codebases
results_summary = {}
for name, config in codebases.items():
print(f"\n{'='*60}")
print(f"Analyzing: {name}")
print(f"{'='*60}")
analyzer = create_analyzer_for_project(
config['path'],
template=config['template'],
output_dir=f'batch_analysis/{name}'
)
results = analyzer.analyze()
summary = analyzer.get_summary_statistics()
results_summary[name] = summary
print(f"✓ Completed: {name}")
print(f" Files: {summary['files']}")
print(f" Modules: {summary['modules']}")
print(f" Translation Units: {summary['translation_units']}")
# Save consolidated summary
with open('batch_analysis/summary.json', 'w') as f:
json.dump(results_summary, f, indent=2)
print("\n" + "="*60)
print("Batch analysis complete! Results saved to batch_analysis/")"""
Regular health check for Fortran codebases - detect issues early.
"""
from fortran_analyzer import create_analyzer_for_project
def health_check(project_path, project_name):
"""Run health check on a Fortran codebase."""
print(f"\nHealth Check: {project_name}")
print("-" * 40)
analyzer = create_analyzer_for_project(project_path, template='auto')
results = analyzer.analyze()
# Check for issues
deps = results['dependencies']['analysis']
circular_deps = deps.get('circular_dependencies', [])
orphaned = deps.get('orphaned_modules', [])
issues = []
if circular_deps:
issues.append(f"⚠️ {len(circular_deps)} circular dependencies found")
if orphaned:
issues.append(f"ℹ️ {len(orphaned)} orphaned modules")
external_deps = deps.get('external_dependencies', [])
if len(external_deps) > 10:
issues.append(f"⚠️ High external dependencies: {len(external_deps)}")
if not issues:
print("✓ No issues found - codebase is healthy!")
else:
print("Issues detected:")
for issue in issues:
print(f" {issue}")
return len(issues) == 0
# Run health checks on multiple projects
projects = {
'MainModel': '/path/to/main/model',
'SupportLib': '/path/to/support/lib',
}
all_healthy = all(health_check(path, name) for name, path in projects.items())
if all_healthy:
print("\n✓ All projects are healthy!")
else:
print("\n⚠️ Some projects have issues - review the reports")"""
Generate a comprehensive migration/translation planning report.
"""
from fortran_analyzer import create_analyzer_for_project
import json
def create_migration_plan(project_path, target_language='Python'):
"""Create a detailed migration plan."""
print(f"Creating migration plan for: {project_path}")
print(f"Target language: {target_language}\n")
analyzer = create_analyzer_for_project(
project_path,
template='auto',
max_translation_unit_lines=100, # Smaller units for easier translation
output_dir='migration_plan'
)
results = analyzer.analyze()
# Generate report
report = {
'project_overview': {
'total_files': results['parsing']['statistics']['total_files'],
'total_lines': results['parsing']['statistics']['total_lines'],
'total_modules': len(results['parsing']['modules']),
},
'translation_plan': {
'total_units': results['translation']['units'],
'recommended_order': analyzer.get_translation_order()[:10], # First 10
'effort_estimate': results['translation']['statistics'].get('units_by_effort', {}),
},
'dependencies': {
'external_libraries': results['dependencies']['analysis'].get('external_dependencies', []),
'circular_dependencies': results['dependencies']['analysis'].get('circular_dependencies', []),
},
'recommendations': results['recommendations'],
}
# Save detailed report
with open('migration_plan/detailed_plan.json', 'w') as f:
json.dump(report, f, indent=2)
# Print summary
print("Migration Plan Summary:")
print(f" Total modules to translate: {report['project_overview']['total_modules']}")
print(f" Translation units: {report['translation_plan']['total_units']}")
print(f" Recommended start: {report['translation_plan']['recommended_order'][0]}")
effort = report['translation_plan']['effort_estimate']
print(f"\nEffort Estimate:")
print(f" Low effort units: {effort.get('low', 0)}")
print(f" Medium effort units: {effort.get('medium', 0)}")
print(f" High effort units: {effort.get('high', 0)}")
return report
# Run migration planning
plan = create_migration_plan('/path/to/legacy/fortran', target_language='Python')"""
CI/CD script to ensure code quality standards.
"""
import sys
from fortran_analyzer import create_analyzer_for_project
def ci_quality_check(project_path, max_circular_deps=0, max_orphaned=5):
"""Quality check for CI/CD pipeline."""
analyzer = create_analyzer_for_project(project_path, template='auto')
results = analyzer.analyze()
deps_analysis = results['dependencies']['analysis']
# Check quality metrics
circular_deps = len(deps_analysis.get('circular_dependencies', []))
orphaned = len(deps_analysis.get('orphaned_modules', []))
passed = True
if circular_deps > max_circular_deps:
print(f"❌ FAIL: {circular_deps} circular dependencies (max: {max_circular_deps})")
passed = False
if orphaned > max_orphaned:
print(f"⚠️ WARNING: {orphaned} orphaned modules (max: {max_orphaned})")
if passed:
print("✓ Quality check passed!")
return 0
else:
return 1
# Use in CI pipeline
if __name__ == '__main__':
project_path = sys.argv[1] if len(sys.argv) > 1 else '.'
exit_code = ci_quality_check(project_path)
sys.exit(exit_code)from fortran_analyzer import create_analyzer_for_project
# Analyze CTSM biogeophysics component
analyzer = create_analyzer_for_project(
'/path/to/ctsm',
template='ctsm',
source_dirs=['src/biogeophys'], # Focus on specific component
output_dir='ctsm_biogeophys_analysis'
)
results = analyzer.analyze()
# Get specific recommendations
recommendations = results['recommendations']
for category, items in recommendations.items():
if items:
print(f"\n{category.upper()}:")
for item in items:
print(f" - {item}")- Parser Errors: If fparser2 is not available, the analyzer falls back to regex-based parsing
- Large Projects: For very large projects, consider analyzing subdirectories separately
- Memory Usage: Large dependency graphs may require substantial memory
- Use
exclude_patternsto skip unnecessary files - Focus analysis on specific source directories
- Disable graph generation for faster analysis:
generate_graphs: false
- 📖 Start with the Quick Start Guide
- 🔄 Need batch processing? See the Batch Analysis Guide
- 💻 Check working examples including batch_processing.py
- 📚 Browse the API Documentation
- 🐛 Open an issue on GitHub
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
git clone https://github.com/yourusername/fortran-analyzer.git
cd fortran-analyzer
pip install -e .[dev]
pytest # Run testsThis project is licensed under the MIT License - see the LICENSE file for details.
If you use this tool in research, please cite:
@software{fortran_analyzer,
title={Fortran Analyzer: A Generic Framework for Fortran Code Analysis},
author={Fortran Analyzer Team},
year={2024},
url={https://github.com/yourusername/fortran-analyzer}
}- Built with NetworkX for graph analysis
- Optional integration with fparser2 for advanced parsing
- Visualizations powered by Matplotlib and Plotly
- Inspired by the need to modernize legacy Fortran codebases