Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/patch generator #427

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions discopop_library/PatchGenerator/PatchGeneratorArguments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
from dataclasses import dataclass


@dataclass
class PatchGeneratorArguments(object):
"""Container Class for the arguments passed to the discopop_patch_generator"""

verbose: bool

def __post_init__(self):
self.__validate()

def __validate(self):
"""Validate the arguments passed to the discopop_explorer, e.g check if given files exist"""
pass
Empty file.
41 changes: 41 additions & 0 deletions discopop_library/PatchGenerator/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.

from argparse import ArgumentParser

from discopop_library.PatchGenerator.PatchGeneratorArguments import PatchGeneratorArguments
from discopop_library.PatchGenerator.patch_generator import run


def parse_args() -> PatchGeneratorArguments:
"""Parse the arguments passed to the discopop_explorer"""
parser = ArgumentParser(description="DiscoPoP Patch Generator")
# all flags that are not considered stable should be added to the experimental_parser
experimental_parser = parser.add_argument_group(
"EXPERIMENTAL",
"Arguments for the task pattern detector and other experimental features. These flags are considered EXPERIMENTAL and they may or may not be removed or changed in the near future.",
)

# fmt: off
parser.add_argument("--verbose", action="store_true",
help="Enable verbose output.")
# EXPERIMENTAL FLAGS:
# fmt: on

arguments = parser.parse_args()

return PatchGeneratorArguments(verbose=arguments.verbose)


def main():
arguments = parse_args()
run(arguments)


if __name__ == "__main__":
main()
64 changes: 64 additions & 0 deletions discopop_library/PatchGenerator/diffs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
import os.path
import subprocess
from pathlib import Path
from typing import Dict

from discopop_library.PatchGenerator.PatchGeneratorArguments import PatchGeneratorArguments


def get_diffs_from_modified_code(
file_mapping: Dict[int, Path], file_id_to_modified_code: Dict[int, str], arguments: PatchGeneratorArguments
) -> Dict[int, str]:
patches: Dict[int, str] = dict()
for file_id in file_id_to_modified_code:
# get path to original code
original_file_path = file_mapping[file_id]
# create temporary modified code
modified_file_path = original_file_path.parent / (original_file_path.name + ".discopop_patch_generator.temp")
if arguments.verbose:
print("Original: ", original_file_path)
print("Modified: ", modified_file_path)

with open(modified_file_path, "w") as f:
f.write(file_id_to_modified_code[file_id])

# calculate diff
diff_name = original_file_path.parent / (original_file_path.name + ".discopop_patch_generator.diff")
command = [
"diff",
"-Naru",
original_file_path.as_posix(),
modified_file_path.as_posix(),
]
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
cwd=os.getcwd(),
)
if result.returncode != 0:
if arguments.verbose:
print("RESULT: ", result.returncode)
print("STDERR:")
print(result.stderr)
print("STDOUT: ")
print(result.stdout)

# save diff
patches[file_id] = result.stdout

# cleanup environment
if os.path.exists(modified_file_path):
os.remove(modified_file_path)
if os.path.exists(diff_name):
os.remove(diff_name)

return patches
83 changes: 83 additions & 0 deletions discopop_library/PatchGenerator/patch_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# This file is part of the DiscoPoP software (http://www.discopop.tu-darmstadt.de)
#
# Copyright (c) 2020, Technische Universitaet Darmstadt, Germany
#
# This software may be modified and distributed under the terms of
# the 3-Clause BSD License. See the LICENSE file in the package base
# directory for details.
import json
import os.path
import shutil
from typing import Dict

from discopop_library.CodeGenerator.CodeGenerator import from_json_strings
from discopop_library.JSONHandler.JSONHandler import read_patterns_from_json_to_json
from discopop_library.PatchGenerator.PatchGeneratorArguments import PatchGeneratorArguments
from discopop_library.PatchGenerator.diffs import get_diffs_from_modified_code
from discopop_library.PathManagement.PathManagement import load_file_mapping


def run(arguments: PatchGeneratorArguments):
if arguments.verbose:
print("Started DiscoPoP Patch Generator...")
if arguments.verbose:
print("Creating patch_generator directory...")
patch_generator_dir = os.path.join(os.getcwd(), "patch_generator")
if not os.path.exists(patch_generator_dir):
os.mkdir(patch_generator_dir)

pattern_file_path = os.path.join(os.getcwd(), "explorer", "patterns.json")
if not os.path.exists(pattern_file_path):
raise FileNotFoundError(
"No pattern file found. Please execute the discopop_explorer in advance."
+ "\nExpected pattern file: "
+ pattern_file_path
)
file_mapping_path = os.path.join(os.getcwd(), "FileMapping.txt")
if not os.path.exists(file_mapping_path):
raise FileNotFoundError(
"No file mapping found. Please execute the discopop_explorer in advance."
+ "\nExpected file: "
+ file_mapping_path
)

if arguments.verbose:
print("Loading file mapping...")
file_mapping = load_file_mapping(file_mapping_path)

if arguments.verbose:
print("Loading patterns...")
patterns_by_type = read_patterns_from_json_to_json(pattern_file_path, [])
if arguments.verbose:
print("Patterns: ", patterns_by_type)

# generate code modifications from each suggestion, create a patch and store the patch
# using the suggestions unique id
if arguments.verbose:
print("Generating modified code...")
for suggestion_type in patterns_by_type:
for suggestion in patterns_by_type[suggestion_type]:
if arguments.verbose:
print("Suggestion: ", suggestion)
file_id_to_modified_code: Dict[int, str] = from_json_strings(file_mapping, {suggestion_type: [suggestion]})
# create patches from the modified codes
file_id_to_patches: Dict[int, str] = get_diffs_from_modified_code(
file_mapping, file_id_to_modified_code, arguments
)
if arguments.verbose:
print("Patches: ", file_id_to_patches)
# clear old results and save patches
suggestion_dict = json.loads(suggestion)
suggestion_id = suggestion_dict["pattern_id"]
suggestion_folder_path = os.path.join(patch_generator_dir, str(suggestion_id))
if arguments.verbose:
print("Saving patches for suggestion: ", suggestion_id)
if os.path.exists(suggestion_folder_path):
shutil.rmtree(suggestion_folder_path)
os.mkdir(suggestion_folder_path)
for file_id in file_id_to_patches:
patch_path = os.path.join(suggestion_folder_path, str(file_id) + ".patch")
with open(patch_path, "w") as f:
f.write(file_id_to_patches[file_id])
if arguments.verbose:
print("Done.")
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"discopop_wizard=discopop_wizard.__main__:main",
"discopop_code_generator=discopop_library.CodeGenerator.__main__:main",
"discopop_optimizer=discopop_library.discopop_optimizer.__main__:main",
"discopop_patch_generator=discopop_library.PatchGenerator.__main__:main",
]
},
zip_safe=True,
Expand Down