blob: 3e291184317152f8c8b077668e67d8db6cc984d4 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2021 The Cobalt Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Formats ninja compilation databases to be easily diffed against each other.
Primarily used for looking at the differences between GYP and GN builds during
the GN migration.
To test, first generate the ".ninja" build files, then use
ninja -t compdb > out.json
in the build directory to generate a JSON file (out.json) containing all actions
ninja would run. Then, run this script on the out.json file to generate the
normalized_out.json file. Diff this normalized_out.json file with another
generated from GYP/GN to see differences in actions, build flags, etc.
"""
import argparse
import json
import os
from typing import List, Tuple
_ACTION_COMPONENTS = ['directory', 'command', 'file', 'output']
STRIP = 0
def make_path_absolute(path: str, directory: str) -> str:
if os.path.isabs(path):
return path
return os.path.normpath(os.path.join(directory, path))
def remove_directory_path(path: str, directory: str) -> str:
if STRIP:
dirsplit = directory.split(os.path.sep)
directory = os.path.sep.join(dirsplit[:-(STRIP)])
if os.path.commonpath([path, directory]) != directory:
return path
return os.path.relpath(path, directory)
def normalize_if_pathlike(path: str, directory: str) -> str:
# Fixes an issue on windows where paths are parsed incorrectly.
if path == 'nul':
return path
absolute_path = make_path_absolute(path, directory)
if os.path.exists(absolute_path):
return remove_directory_path(absolute_path, directory)
return path
def relativize_path(path: str, directory: str) -> str:
path = make_path_absolute(path, directory)
return remove_directory_path(path, directory)
def validate_json_database(json_content: object):
for entry in json_content:
for component in _ACTION_COMPONENTS:
if component not in entry:
raise ValueError(f'Ninja json entry is missing {component} field.')
def normalize_command(command: str, directory: str) -> Tuple[List[str], object]:
command_list = command.split()
command_with_normalized_paths = [
normalize_if_pathlike(e, directory) for e in command_list
]
# command_parser = argparse.ArgumentParser()
# command_parser.add_argument('-x', type=str)
# command_parser.add_argument('-MF', type=str)
# command_parser.add_argument('-o', type=str)
# command_parser.add_argument('-c', type=str)
# parsed, unknown = command_parser.parse_known_args(
# command_with_normalized_paths)
# return (parsed, sorted(unknown, reverse=True))
return sorted(command_with_normalized_paths, reverse=True)
def normalize_all_paths(json_content: List[object]) -> List[object]:
for entry in json_content:
directory = entry['directory']
command = normalize_command(entry['command'], directory)
file_entry = relativize_path(entry['file'], directory)
output_entry = entry['output']
yield {'command': command, 'file': file_entry, 'output': output_entry}
def normalize_json_file(filename: str) -> List[object]:
with open(filename, encoding='utf-8') as f:
content = json.load(f)
validate_json_database(content)
normalized_content = normalize_all_paths(content)
return sorted(normalized_content, key=lambda x: x['output'])
def main(json_database: str, output_filename: str):
json_content = normalize_json_file(json_database)
with open(output_filename, 'w', encoding='utf-8') as f:
json.dump(json_content, f, indent=4, sort_keys=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('json_filename', type=str)
parser.add_argument('-o', '--output', type=str)
parser.add_argument('-p', '--strip', type=int)
args = parser.parse_args()
if args.strip:
STRIP = int(args.strip)
output = args.output if args.output else 'normalized_' + os.path.basename(
args.json_filename)
main(args.json_filename, output)