diff --git a/.gitignore b/.gitignore index dde3895f..ffa778bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store *.pyc +.vscode \ No newline at end of file diff --git a/solutions/build-automation-tool/build.py b/solutions/build-automation-tool/build.py new file mode 100644 index 00000000..2e56b241 --- /dev/null +++ b/solutions/build-automation-tool/build.py @@ -0,0 +1,120 @@ +import enum +import json +import os +import subprocess +import sys + + +class Build(object): + """ + The class builds the dependencies, checks for the existence of required + files and executes the build command for the specified name. + """ + + class BuildSpecs(enum.Enum): + BUILD = 'build.json' + NAME = 'name' + DEPENDENCIES = 'dependencies' + FILES = 'files' + COMMAND = 'command' + + def __init__(self, build_file=os.path.join(os.getcwd(), 'build.json')): + """ + :param build_file: Path of the build file. + :type build_file: str + """ + + self.build_instructions = None + with open(build_file, 'r') as build_file: + self.build_instructions = json.load(build_file) + + def _getBuildInstruction(self, name): + """ + Returns the build instruction available for name. + + :param name: The name of the build instruction. + :type name: str + """ + + for build_instruction in self.build_instructions: + if build_instruction[self.BuildSpecs.NAME.value] == name: + return build_instruction + return None + + def _buildDependencies(self, dependencies): + """ + Builds the dependencies for the current build instruction. + + :param dependencies: The dependecnies to build. + :type depenedecies: list + """ + + for dependency in dependencies: + build_directory, name = os.path.split(dependency) + if build_directory: + current_directory = os.getcwd() + try: + os.chdir(build_directory) + build_dependency = Build( + os.path.join(os.getcwd(), self.BuildSpecs.BUILD.value)) + build_dependency.build(name) + finally: + os.chdir(current_directory) + else: + build_dependency = Build( + os.path.join(os.getcwd(), self.BuildSpecs.BUILD.value)) + build_dependency.build(name) + + def _ensureFiles(self, files): + """ + Asserts if all the files are exists or not + + :param files: Files to check for existence + :type files: list + """ + + for file in files: + assert os.path.isfile(file) + + def _executeCommand(self, command): + """ + Executes the command. + + :param command: Command to execute + :type command: str + """ + + completed_process = subprocess.run( + command, + shell=True, + check=True, + universal_newlines=True, + ) + return completed_process.returncode + + def build(self, name): + build_instruction = self._getBuildInstruction(name) + if build_instruction is None: + raise NameError(f'No build instruction for "{name}".') + + self._buildDependencies( + build_instruction.get(self.BuildSpecs.DEPENDENCIES.value, [])) + self._ensureFiles( + build_instruction.get(self.BuildSpecs.FILES.value, [])) + self._executeCommand( + build_instruction.get(self.BuildSpecs.COMMAND.value, "")) + + +def main(): + if len(sys.argv) <= 1: + sys.stdout.write("No build name given.") + sys.exit(0) + build = Build() + try: + build.build(name=sys.argv[1]) + except NameError as e: + sys.stdout.write(str(e)) + + +if __name__ == '__main__': + main() diff --git a/solutions/build-automation-tool/build_test.py b/solutions/build-automation-tool/build_test.py new file mode 100644 index 00000000..ed869ccf --- /dev/null +++ b/solutions/build-automation-tool/build_test.py @@ -0,0 +1,106 @@ +import contextlib +import json +import os +import tempfile +import textwrap +import unittest + +from build import Build + + +@contextlib.contextmanager +def create_and_change_to_tmpdir(working_directory): + """Creates and switches directory to the a new temporary directory.""" + + try: + tmpdir = tempfile.TemporaryDirectory() + os.chdir(tmpdir.name) + yield tmpdir + finally: + os.chdir(working_directory) + + +class BuildTest(unittest.TestCase): + + def test_build(self): + with create_and_change_to_tmpdir(os.getcwd()): + build_file_1 = [{ + "name": "clean", + "deps": ["algorithms/clean"], + "command": "rm -f test.o && rm -f test.exe" + }, { + "name": "test", + "files": ["test.cpp"], + "command": "g++ -std=c++11 -c test.cpp" + }, { + "name": "run", + "dependencies": [ + "test", "algorithms/sort_bubble", "algorithms/sort_merge" + ], + "command": "g++ algorithms/sort_bubble.o\ + algorithms/sort_merge.o test.o -o test.exe && ./test.exe" + }] + + build_file_2 = [{ + "name": "clean", + "command": "rm -f *.o" + }, { + "name": "sort_bubble", + "files": ["sort_bubble.cpp"], + "command": "g++ -c sort_bubble.cpp" + }, { + "name": "sort_merge", + "files": ["sort_merge.cpp"], + "command": "g++ -c sort_merge.cpp" + }] + + with open('build.json', 'w') as build: + json.dump(build_file_1, build) + + with open('test.cpp', 'w') as test: + test.write(textwrap.dedent("""\ + #include + using namespace std; + + int main() { + return 0; + }""")) + + os.mkdir('algorithms') + + with open('algorithms/build.json', 'w') as build: + json.dump(build_file_2, build) + + with open('algorithms/sort_bubble.cpp', 'w') as sort_bubble: + sort_bubble.write(textwrap.dedent(""" + #include + using namespace std; + + int bubble() { + return 0; + }""")) + + with open('algorithms/sort_merge.cpp', 'w') as sort_merge: + sort_merge.write(textwrap.dedent(""" + #include + using namespace std; + + int merge() { + return 0; + }""")) + + build = Build(os.path.join(os.getcwd(), 'build.json')) + + build.build("test") + assert os.path.exists("test.o") + + build.build("run") + assert os.path.exists("test.exe") + + build.build("clean") + assert not os.path.exists("test.o") + assert not os.path.exists("test.exe") + + +if __name__ == '__main__': + unittest.main()