This commit is contained in:
265
resources/scripts/ossify.py
Normal file
265
resources/scripts/ossify.py
Normal file
@@ -0,0 +1,265 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user