blob: 5545e3d2610b945be01ef54d78de0ec6720ce246 [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Prints all of the Chrome tracing stdlib as an amalgamation (a single
# PerfettoSQL source which includes all transitive dependencies).
#
# All "CREATE PERFETTO" instructions are also going to be replaced with
# "CREATE OR REPLACE PERFETTO" instructions to ensure that the amalgamation
# can be used on top of existing stdlib.
#
# Usage: print_stdlib_amalgamation.py [chrome.module1 chrome.module2 ...]
from dataclasses import dataclass
from typing import Collection, Dict, List, Set
from pathlib import Path
import os
import re
import sys
CHROME_STDLIB_DIR = Path(
os.path.dirname(__file__)) / '../../base/tracing/stdlib'
def ReadStdlib(path: Path) -> Dict[str, str]:
"""Reads all of the SQL modules in the given directory.
Args:
path: The path to the directory containing the SQL modules.
Returns:
A dictionary mapping module names to their contents.
"""
result = {}
for root, _, files in os.walk(path):
for file in files:
if not file.endswith('.sql'):
continue
with open(os.path.join(root, file)) as f:
module = os.path.join(os.path.relpath(root, path),
file.removesuffix('.sql')).replace(
os.path.sep, '.')
result[module] = f.read()
return result
@dataclass
class Module:
"""A class representing a parsed SQL module."""
name: str
includes: List[str]
content: str
def ParseModule(name: str, content: str,
known_stdlib_modules: Collection[str]) -> Module:
"""
Parses a given SQL modules, resolving includes and updating
CREATE PERFETTO ... statements to CREATE OR REPLACE.
Args:
name: name of the module.
content: raw contents of the module.
known_stdlib_modules: a list of known stdlib modules.
Returns:
A parsed Module object.
"""
includes = []
def ReplaceInclude(match):
included_module = match.group(1)
if included_module in known_stdlib_modules:
includes.append(included_module)
return ''
return match.group(0)
content = re.sub(r'INCLUDE PERFETTO MODULE ([\w\.]*);', ReplaceInclude,
content)
content = content.replace('CREATE PERFETTO', 'CREATE OR REPLACE PERFETTO')
# If the module doesn't end with a semicolon, add one to terminate the statement.
if not content.strip().endswith(';'):
content += ';'
return Module(name, includes, content)
def ParseModules(stdlib: Dict[str, str]) -> List[Module]:
"""
Parses the given SQL modules, resolving includes and updating
CREATE PERFETTO ... statements to CREATE OR REPLACE.
Args:
stdlib: A dictionary mapping module names to their contents.
Returns:
A list of Module objects, each representing a parsed module.
"""
return [
ParseModule(name, content, stdlib.keys())
for name, content in stdlib.items()
]
def SortModules(stdlib: List[Module],
targets: List[str] | None = None) -> List[Module]:
"""
Topologically sorts the given modules based on their includes.
Assumes that there are no circular includes -- otherwise the behaviour is undefined.
Args:
stdlib: A list of Module objects.
targets: A list of module names to sort. If None, all modules will be sorted.
Returns:
A list of Module objects, sorted in topological order.
"""
modules_by_name = {m.name: m for m in stdlib}
result = []
visited = set()
def AddModule(module: Module):
if module.name in visited:
return
visited.add(module.name)
for include in module.includes:
AddModule(modules_by_name[include])
result.append(module)
if targets is None:
targets = stdlib.keys()
for target in targets:
AddModule(modules_by_name[target])
return result
def PrintModules(modules: List[Module]):
for m in modules:
print()
print()
print('--')
print('--', m.name)
print('--')
print()
print()
print(m.content)
if __name__ == '__main__':
chrome_stdlib = ReadStdlib(CHROME_STDLIB_DIR)
PrintModules(SortModules(ParseModules(chrome_stdlib), sys.argv[1:] or None))