From ba1b6499aaa01f0df942a0b7977be89c1304fa3a Mon Sep 17 00:00:00 2001 From: Anindhiths Date: Wed, 19 Mar 2025 18:11:07 +0530 Subject: [PATCH] Adding Documentation to amalgamate.py Adding Documentation to amalgamate.py --- amalgamate.py | 296 +++++++++++++++++++++++++++++++------------------- 1 file changed, 182 insertions(+), 114 deletions(-) diff --git a/amalgamate.py b/amalgamate.py index 0464c506..f23f77df 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -5,11 +5,16 @@ import sys import time from typing import List, Dict +# Run the prebuild script before starting the amalgamation process assert os.system("python prebuild.py") == 0 +# Define the root directory containing headers ROOT = 'include/pocketpy' + +# List of public headers that will be included in the final amalgamated header PUBLIC_HEADERS = ['config.h', 'export.h', 'linalg.h', 'pocketpy.h'] +# Copyright notice to be added at the beginning of amalgamated files COPYRIGHT = '''/* * Copyright (c) 2025 blueloveTH * Distributed Under The MIT License @@ -18,142 +23,205 @@ COPYRIGHT = '''/* ''' def read_file(path): - with open(path, 'rt', encoding='utf-8') as f: - return f.read() + """Read the content of a file with UTF-8 encoding.""" + with open(path, 'rt', encoding='utf-8') as f: + return f.read() def write_file(path, content): - with open(path, 'wt', encoding='utf-8', newline='\n') as f: - f.write(content) - -if os.path.exists('amalgamated'): - shutil.rmtree('amalgamated') - time.sleep(0.5) + """Write content to a file with UTF-8 encoding and Unix line endings.""" + with open(path, 'wt', encoding='utf-8', newline='\n') as f: + f.write(content) +# Remove existing amalgamated directory if it exists and create a new one +if os.path.exists('amalgamated'): + shutil.rmtree('amalgamated') + time.sleep(0.5) # Wait briefly to ensure directory is fully removed os.mkdir('amalgamated') class Header: - path: str - content: str # header source - dependencies: List[str] - - def __init__(self, path: str): - self.path = path - self.dependencies = [] - self.content = read_file(f'{ROOT}/{path}') - - # process raw content and get dependencies - self.content = self.content.replace('#pragma once', '') - def _replace(m): - path = m.group(1) - if path.startswith('xmacros/'): - return read_file(f'{ROOT}/{path}') + '\n' - if path in PUBLIC_HEADERS: - return '' # remove include - if path != self.path: - self.dependencies.append(path) - return '' # remove include - - self.content = re.sub( - r'#include\s+"pocketpy/(.+)"\s*', - _replace, - self.content - ) - - def __repr__(self): - return f'Header({self.path!r}, dependencies={self.dependencies})' - - def text(self): - return f'// {self.path}\n{self.content}\n' - + """ + Class to represent and process a header file. + + Attributes: + path: Relative path to the header file + content: Processed content of the header file + dependencies: List of other headers this header depends on + """ + path: str + content: str + dependencies: List[str] + + def __init__(self, path: str): + """ + Initialize a Header object. + + Args: + path: Relative path to the header file + """ + self.path = path + self.dependencies = [] + self.content = read_file(f'{ROOT}/{path}') + + # Process raw content and extract dependencies + self.content = self.content.replace('#pragma once', '') # Remove pragma once directives + + def _replace(m): + """ + Process #include directives in the header. + + Returns: + - For xmacros: the content of the included file + - For public headers: empty string (removed) + - For other headers: empty string and adds the header to dependencies + """ + path = m.group(1) + if path.startswith('xmacros/'): + return read_file(f'{ROOT}/{path}') + '\n' # Inline xmacros directly + if path in PUBLIC_HEADERS: + return '' # Remove includes of public headers + if path != self.path: + self.dependencies.append(path) # Add to dependencies + return '' # Remove include directive + + # Process all include directives + self.content = re.sub( + r'#include\s+"pocketpy/(.+)"\s*', + _replace, + self.content + ) + + def __repr__(self): + """Return string representation of the Header object.""" + return f'Header({self.path!r}, dependencies={self.dependencies})' + + def text(self): + """Return the processed header content with a comment indicating its source.""" + return f'// {self.path}\n{self.content}\n' +# Dictionary to store all header objects headers: Dict[str, Header] = {} +# Process all header files in the ROOT directory for entry in os.listdir(ROOT): - if os.path.isdir(f'{ROOT}/{entry}'): - if entry == 'xmacros' or entry in PUBLIC_HEADERS: - continue - files = os.listdir(f'{ROOT}/{entry}') - for file in sorted(files): - assert file.endswith('.h') - if entry in PUBLIC_HEADERS: - continue - headers[f'{entry}/{file}'] = Header(f'{entry}/{file}') + if os.path.isdir(f'{ROOT}/{entry}'): + if entry == 'xmacros' or entry in PUBLIC_HEADERS: + continue # Skip xmacros directory and public headers + files = os.listdir(f'{ROOT}/{entry}') + for file in sorted(files): + assert file.endswith('.h') # Ensure we're only processing header files + if entry in PUBLIC_HEADERS: + continue + headers[f'{entry}/{file}'] = Header(f'{entry}/{file}') def merge_c_files(): - c_files = [COPYRIGHT, '\n', '#include "pocketpy.h"', '\n'] - - # merge internal headers - internal_h = [] - while True: - for h in headers.values(): - if not h.dependencies: - break - else: - if headers: - print(headers) - raise RuntimeError("Circular dependencies detected") - break - # print(h.path) - internal_h.append(h.text()) - del headers[h.path] - for h2 in headers.values(): - h2.dependencies = [d for d in h2.dependencies if d != h.path] - - c_files.extend(internal_h) - - def _replace(m): - path = m.group(1) - if path.startswith('xmacros/'): - return read_file(f'{ROOT}/{path}') + '\n' - return '' # remove include - - for root, _, files in os.walk('src/'): - for file in files: - if file.endswith('.c'): - path = os.path.join(root, file) - c_files.append(f'// {path}\n') - content = read_file(path) - content = re.sub( - r'#include\s+"pocketpy/(.+)"\s*', - _replace, - content, - ) - c_files.append(content) - c_files.append('\n') - return ''.join(c_files) + """ + Merge all C source files into a single file. + + Uses a topological sort to ensure headers are included in the correct order + based on their dependencies. + + Returns: + String containing the amalgamated C source + """ + c_files = [COPYRIGHT, '\n', '#include "pocketpy.h"', '\n'] # Start with copyright and main include + + # Merge internal headers in dependency order + internal_h = [] + while True: + # Find a header with no dependencies + for h in headers.values(): + if not h.dependencies: + break + else: + # If no headers without dependencies were found and headers still remain, + # there must be a circular dependency + if headers: + print(headers) + raise RuntimeError("Circular dependencies detected") + break # No headers left, we're done + + # Add this header to the amalgamated file + internal_h.append(h.text()) + del headers[h.path] + + # Remove this header from the dependencies of other headers + for h2 in headers.values(): + h2.dependencies = [d for d in h2.dependencies if d != h.path] + + c_files.extend(internal_h) + + def _replace(m): + """Process include directives in C files.""" + path = m.group(1) + if path.startswith('xmacros/'): + return read_file(f'{ROOT}/{path}') + '\n' # Inline xmacros + return '' # Remove other includes + + # Process all C source files + for root, _, files in os.walk('src/'): + for file in files: + if file.endswith('.c'): + path = os.path.join(root, file) + c_files.append(f'// {path}\n') # Add comment with original file path + content = read_file(path) + + # Process include directives + content = re.sub( + r'#include\s+"pocketpy/(.+)"\s*', + _replace, + content, + ) + c_files.append(content) + c_files.append('\n') + + return ''.join(c_files) def merge_h_files(): - h_files = [COPYRIGHT, '#pragma once'] - - def _replace(m): - path = m.group(1) - if path.startswith('xmacros/'): - return read_file(f'{ROOT}/{path}') + '\n' - return '' # remove include - - for path in PUBLIC_HEADERS: - content = read_file(f'{ROOT}/{path}') - content = content.replace('#pragma once', '') - content = re.sub( - r'#include\s+"pocketpy/(.+)"\s*', - _replace, - content, - ) - h_files.append(content) - return '\n'.join(h_files) - + """ + Merge all public header files into a single header file. + + Returns: + String containing the amalgamated header + """ + h_files = [COPYRIGHT, '#pragma once'] # Start with copyright and pragma once + + def _replace(m): + """Process include directives in header files.""" + path = m.group(1) + if path.startswith('xmacros/'): + return read_file(f'{ROOT}/{path}') + '\n' # Inline xmacros + return '' # Remove other includes + + # Process all public headers + for path in PUBLIC_HEADERS: + content = read_file(f'{ROOT}/{path}') + content = content.replace('#pragma once', '') # Remove pragma once directives + + # Process include directives + content = re.sub( + r'#include\s+"pocketpy/(.+)"\s*', + _replace, + content, + ) + h_files.append(content) + + return '\n'.join(h_files) +# Generate amalgamated files write_file('amalgamated/pocketpy.c', merge_c_files()) write_file('amalgamated/pocketpy.h', merge_h_files()) +# Copy the main.c file to the amalgamated directory shutil.copy("src2/main.c", "amalgamated/main.c") +# Test compile on Linux or macOS if sys.platform in ['linux', 'darwin']: - ok = os.system("gcc -o main amalgamated/pocketpy.c amalgamated/main.c -O1 --std=c11 -lm -ldl") - if ok == 0: - print("Test build success!") + ok = os.system("gcc -o main amalgamated/pocketpy.c amalgamated/main.c -O1 --std=c11 -lm -ldl") + if ok == 0: + print("Test build success!") print("amalgamated/pocketpy.h") +# Copy the amalgamated files to the Flutter plugin directory shutil.copy("amalgamated/pocketpy.h", "plugins/flutter/pocketpy/src/pocketpy.h") -shutil.copy("amalgamated/pocketpy.c", "plugins/flutter/pocketpy/src/pocketpy.c") \ No newline at end of file +shutil.copy("amalgamated/pocketpy.c", "plugins/flutter/pocketpy/src/pocketpy.c")