mail-server/resources/scripts/ossify.py

265 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""
Stalwart SEL code remover
This script removes SEL code from the Stalwart codebase by:
1. Removing entire .rs files that contain "SPDX-License-Identifier: LicenseRef-SEL" in their first comment
2. Removing SEL snippets marked with SPDX-SnippetBegin/End from mixed files
Usage: python ossify.py <stalwart_repository>/crates
"""
import os
import sys
import re
import argparse
from pathlib import Path
from typing import List, Tuple, Optional
def find_first_comment_block(content: str) -> Optional[str]:
"""
Find the first comment block in a Rust file.
Returns the comment content or None if no comment block is found.
"""
# Remove leading whitespace and find the first comment
lines = content.strip().split('\n')
if not lines:
return None
first_line = lines[0].strip()
# Check for block comment starting with /*
if first_line.startswith('/*'):
comment_lines = []
in_comment = True
for line in lines:
if in_comment:
comment_lines.append(line)
if '*/' in line:
break
return '\n'.join(comment_lines)
# Check for line comments starting with //
elif first_line.startswith('//'):
comment_lines = []
for line in lines:
stripped = line.strip()
if stripped.startswith('//'):
comment_lines.append(line)
elif stripped == '':
comment_lines.append(line) # Keep empty lines within comment block
else:
break # Stop at first non-comment, non-empty line
return '\n'.join(comment_lines)
return None
def should_remove_file(file_path: str) -> bool:
"""
Check if a .rs file should be completely removed based on its first comment.
Returns True if the file contains "SPDX-License-Identifier: LicenseRef-SEL" in the first comment.
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
first_comment = find_first_comment_block(content)
if first_comment and 'SPDX-License-Identifier: LicenseRef-SEL' in first_comment:
return True
except Exception as e:
print(f"Error reading file {file_path}: {e}")
return False
def remove_proprietary_snippets(content: str) -> Tuple[str, int]:
"""
Remove proprietary snippets from file content.
Returns tuple of (modified_content, number_of_snippets_removed)
"""
snippets_removed = 0
# Pattern to match SPDX snippets that contain LicenseRef-SEL
# We look for SPDX-SnippetBegin, then check if the snippet contains LicenseRef-SEL,
# and if so, remove everything until SPDX-SnippetEnd
lines = content.split('\n')
result_lines = []
i = 0
while i < len(lines):
line = lines[i]
# Check if this line starts a snippet
if '// SPDX-SnippetBegin' in line:
# Look ahead to see if this snippet contains LicenseRef-SEL
snippet_start = i
snippet_lines = []
j = i
# Collect the snippet lines until we find SnippetEnd or reach end of file
while j < len(lines):
snippet_lines.append(lines[j])
if '// SPDX-SnippetEnd' in lines[j]:
break
j += 1
# Check if this snippet contains LicenseRef-SEL
snippet_content = '\n'.join(snippet_lines)
if 'SPDX-License-Identifier: LicenseRef-SEL' in snippet_content:
# Remove this snippet
snippets_removed += 1
i = j + 1 # Skip past the SnippetEnd line
continue
else:
# Keep this snippet as it's not proprietary
result_lines.append(line)
i += 1
else:
result_lines.append(line)
i += 1
return '\n'.join(result_lines), snippets_removed
def process_rust_file(file_path: str, dry_run: bool = False) -> dict:
"""
Process a single Rust file, removing proprietary content.
Returns a dictionary with processing results.
"""
result = {
'file': file_path,
'action': 'none',
'snippets_removed': 0,
'error': None
}
try:
# Check if the entire file should be removed
if should_remove_file(file_path):
result['action'] = 'file_removed'
if not dry_run:
os.remove(file_path)
return result
# Process snippets in the file
with open(file_path, 'r', encoding='utf-8') as f:
original_content = f.read()
modified_content, snippets_removed = remove_proprietary_snippets(original_content)
if snippets_removed > 0:
result['action'] = 'snippets_removed'
result['snippets_removed'] = snippets_removed
if not dry_run:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(modified_content)
except Exception as e:
result['error'] = str(e)
return result
def find_rust_files(directory: str) -> List[str]:
"""Find all .rs files in the given directory recursively."""
rust_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.rs'):
rust_files.append(os.path.join(root, file))
return rust_files
def main():
parser = argparse.ArgumentParser(
description='Remove Enterprise licensed code from Stalwart codebase'
)
parser.add_argument(
'directory',
help='Directory containing Stalwart code to process'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without making changes'
)
parser.add_argument(
'--verbose',
action='store_true',
help='Show detailed output for each file'
)
args = parser.parse_args()
if not os.path.isdir(args.directory):
print(f"Error: {args.directory} is not a valid directory")
sys.exit(1)
print(f"Processing Rust files in: {args.directory}")
if args.dry_run:
print("DRY RUN MODE - No changes will be made")
print()
rust_files = find_rust_files(args.directory)
if not rust_files:
print("No .rs files found in the specified directory")
return
print(f"Found {len(rust_files)} Rust files")
print()
files_removed = 0
files_with_snippets_removed = 0
total_snippets_removed = 0
errors = []
for file_path in rust_files:
result = process_rust_file(file_path, args.dry_run)
if result['error']:
errors.append(f"{file_path}: {result['error']}")
continue
if result['action'] == 'file_removed':
files_removed += 1
if args.verbose or args.dry_run:
action_text = "Would remove" if args.dry_run else "Removed"
print(f"{action_text} file: {file_path}")
elif result['action'] == 'snippets_removed':
files_with_snippets_removed += 1
total_snippets_removed += result['snippets_removed']
if args.verbose or args.dry_run:
action_text = "Would remove" if args.dry_run else "Removed"
print(f"{action_text} {result['snippets_removed']} snippet(s) from: {file_path}")
# Summary
print("\nSummary:")
action_text = "Would be" if args.dry_run else "Were"
print(f"- {files_removed} files {action_text.lower()} completely removed")
print(f"- {total_snippets_removed} proprietary snippets {action_text.lower()} removed from {files_with_snippets_removed} files")
if errors:
print(f"- {len(errors)} errors occurred:")
for error in errors:
print(f" {error}")
if args.dry_run:
print("\nRun without --dry-run to apply changes")
if __name__ == '__main__':
main()