diff --git a/scripts/TouchinBuild/Core/BuildConfigProviderBase.py b/scripts/TouchinBuild/Core/BuildConfigProviderBase.py new file mode 100644 index 0000000..b874565 --- /dev/null +++ b/scripts/TouchinBuild/Core/BuildConfigProviderBase.py @@ -0,0 +1,6 @@ +class BuildConfigProviderBase: + def __init__(self): + pass + + def getConfigs(self, rootConfig): + pass \ No newline at end of file diff --git a/scripts/TouchinBuild/Core/DependencyResolver/DependencyResolver.py b/scripts/TouchinBuild/Core/DependencyResolver/DependencyResolver.py new file mode 100644 index 0000000..42dfbc2 --- /dev/null +++ b/scripts/TouchinBuild/Core/DependencyResolver/DependencyResolver.py @@ -0,0 +1,38 @@ +class DependencyResolver: + def __init__(self): + pass + + def resolve(self, unresolved): + assert unresolved is not None + resolved = [] + + while len(unresolved) > 0: + node = unresolved[0] + self.resolveNode(node, resolved, unresolved, []) + + return resolved + + def resolveNode(self, node, resolved, unresolved, seen): + assert node is not None + assert resolved is not None + assert seen is not None + + seen.append(node) + + for dependency in node.edges: + if dependency not in resolved: + self.guardNotCircularReference(node, dependency, seen) + self.resolveNode(dependency, resolved, unresolved, seen) + + resolved.append(node) + unresolved.remove(node) + seen.remove(node) + + def guardNotCircularReference(self, start, dependency, seen): + assert start is not None + assert dependency is not None + assert seen is not None + + if dependency in seen: + raise Exception('Circular reference detected: {0} -> {1}'.format(start.name, dependency.name)) + diff --git a/scripts/TouchinBuild/Core/DependencyResolver/Node.py b/scripts/TouchinBuild/Core/DependencyResolver/Node.py new file mode 100644 index 0000000..06e1726 --- /dev/null +++ b/scripts/TouchinBuild/Core/DependencyResolver/Node.py @@ -0,0 +1,12 @@ +class Node: + def __init__(self, name): + assert name is not None + + self.name = name + self.edges = [] + + def addEdge(self, node): + assert node is not None + assert node not in self.edges + + self.edges.append(node) \ No newline at end of file diff --git a/scripts/TouchinBuild/Core/DependencyResolver/SettingsResolver.py b/scripts/TouchinBuild/Core/DependencyResolver/SettingsResolver.py new file mode 100644 index 0000000..3b3c50f --- /dev/null +++ b/scripts/TouchinBuild/Core/DependencyResolver/SettingsResolver.py @@ -0,0 +1,60 @@ +from Core.DependencyResolver.DependencyResolver import DependencyResolver +from Core.DependencyResolver.Node import Node +from Core.LineConveyor.MacroResolver import MacroResolver +from commands.ValueProvider import ValueProvider +from utils.MacroProcessor import MacroProcessor + + +class SettingsResolver: + def __init__(self, settingsDictionary): + assert settingsDictionary is not None + + self.settings = settingsDictionary.copy() + self.nodeStorage = {} + self.macroProcessor = MacroProcessor() + + self.valueProvider = ValueProvider() + self.valueProvider.setConfig(self.settings) + + + def resolveSettings(self): + + self.fillNodesStorage() + unresolved = self.nodeStorage.values() + + dependencyResolver = DependencyResolver() + resolved = dependencyResolver.resolve(unresolved) + + self.resolveSettingValues(resolved) + return self.settings + + def fillNodesStorage(self): + + for key in self.settings: + node = self.fetchNodeByKey(key) + + value = self.settings[key] + macroNames = self.macroProcessor.getSymbols(value) + + for symbol in macroNames: + name = self.macroProcessor.getNameByMacroName(symbol) + dependency = self.fetchNodeByKey(name) + + node.addEdge(dependency) + + def fetchNodeByKey(self, key): + assert key is not Node + + node = self.nodeStorage.get(key, Node(key)) + self.nodeStorage[key] = node + + return node + + def resolveSettingValues(self, resolvedDependencies): + macroResolver = MacroResolver(self.macroProcessor, self.valueProvider) + + for node in resolvedDependencies: + unresolvedSettingValue = self.settings[node.name] + resolvedSettingValue = macroResolver.processText(unresolvedSettingValue) + + self.settings[node.name] = resolvedSettingValue \ No newline at end of file diff --git a/scripts/TouchinBuild/Core/DependencyResolver/__init__.py b/scripts/TouchinBuild/Core/DependencyResolver/__init__.py new file mode 100644 index 0000000..cc31abc --- /dev/null +++ b/scripts/TouchinBuild/Core/DependencyResolver/__init__.py @@ -0,0 +1 @@ +__author__ = 'rzaitov' diff --git a/scripts/TouchinBuild/Tests/ManualTests/resolve_settings.py b/scripts/TouchinBuild/Tests/ManualTests/resolve_settings.py new file mode 100644 index 0000000..9bf097a --- /dev/null +++ b/scripts/TouchinBuild/Tests/ManualTests/resolve_settings.py @@ -0,0 +1,44 @@ +from Core.ContentProviderBase import ContentProviderBase +from Tests.Common.SettingsProviderStub import SettingsProviderStub +from taskRunner import TaskRunner +from utils.BuildConfigProvider.BuildConfigProvider import BuildConfigProvider +from utils.BuildConfigProvider.ResolvedBuildConfigProvider import ResolvedBuildConfigProvider + +settingsText = """ +build_tool = '/Applications/Xamarin\ Studio.app/Contents/MacOS/mdtool' +major_minor = '1.2' +build = '345' + +configs = 'config1, config2' +steps = 'main_steps' + +config1.version = '{@major_minor}' +config2.version = '{@major_minor}.{@build}' +""" + +stepsFileContent = """ +sh echo {@version} +""" + +class ContentProviderMock(ContentProviderBase): + def __init__(self): + ContentProviderBase.__init__(self) + + def fetchContent(self, key): + if key == 'main_steps': + return stepsFileContent + else: + raise Exception(key) + + +settingsProvider = SettingsProviderStub(settingsText) + +buildConfigProvider = BuildConfigProvider() +resolvedBuildConfigProvider = ResolvedBuildConfigProvider(buildConfigProvider) + +contentProvider = ContentProviderMock() + +taskRunner = TaskRunner(settingsProvider, contentProvider, resolvedBuildConfigProvider) + +taskRunner.run() + diff --git a/scripts/TouchinBuild/Tests/UnitTests/BuildConfigProvider/test_BuildConfigProvider.py b/scripts/TouchinBuild/Tests/UnitTests/BuildConfigProvider/test_BuildConfigProvider.py index 4906c83..befc479 100644 --- a/scripts/TouchinBuild/Tests/UnitTests/BuildConfigProvider/test_BuildConfigProvider.py +++ b/scripts/TouchinBuild/Tests/UnitTests/BuildConfigProvider/test_BuildConfigProvider.py @@ -1,5 +1,5 @@ import unittest -from utils.BuildConfigProvider import BuildConfigProvider +from utils.BuildConfigProvider.BuildConfigProvider import BuildConfigProvider class TestBuildConfigProvider(unittest.TestCase): diff --git a/scripts/TouchinBuild/Tests/UnitTests/DependencyResolver/__init__.py b/scripts/TouchinBuild/Tests/UnitTests/DependencyResolver/__init__.py new file mode 100644 index 0000000..cc31abc --- /dev/null +++ b/scripts/TouchinBuild/Tests/UnitTests/DependencyResolver/__init__.py @@ -0,0 +1 @@ +__author__ = 'rzaitov' diff --git a/scripts/TouchinBuild/Tests/UnitTests/DependencyResolver/test_resolver.py b/scripts/TouchinBuild/Tests/UnitTests/DependencyResolver/test_resolver.py new file mode 100644 index 0000000..ff9b717 --- /dev/null +++ b/scripts/TouchinBuild/Tests/UnitTests/DependencyResolver/test_resolver.py @@ -0,0 +1,63 @@ +import unittest +from Core.DependencyResolver.DependencyResolver import DependencyResolver +from Core.DependencyResolver.Node import Node + + +class TestDependencyResolver(unittest.TestCase): + def setUp(self): + self.resolver = DependencyResolver() + + def test_OneConnectedness(self): + node1 = Node('node1') + node2 = Node('node2') + + node3 = Node('node3') + node3.addEdge(node1) + node3.addEdge(node2) + + node4 = Node('node4') + node4.addEdge(node3) + node4.addEdge(node1) + + unresolved = [node4, node3, node2, node1] + resolved = self.resolver.resolve(unresolved) + + self.assertEqual(4, len(resolved)) + + self.assertEqual(node1, resolved[0]) + self.assertEqual(node2, resolved[1]) + self.assertEqual(node3, resolved[2]) + self.assertEqual(node4, resolved[3]) + + def test_TwoConnectedness(self): + # first + node1 = Node('node1') + node2 = Node('node2') + + node3 = Node('node3') + node3.addEdge(node1) + node3.addEdge(node2) + + node4 = Node('node4') + node4.addEdge(node3) + node4.addEdge(node1) + + # second + node5 = Node('node5') + + node6 = Node('node6') + node6.addEdge(node5) + + unresolved = [node4, node3, node2, node1, node6, node5] + resolved = self.resolver.resolve(unresolved) + + self.assertEqual(6, len(resolved)) + + self.assertEqual(node1, resolved[0]) + self.assertEqual(node2, resolved[1]) + self.assertEqual(node3, resolved[2]) + self.assertEqual(node4, resolved[3]) + self.assertEqual(node5, resolved[4]) + self.assertEqual(node6, resolved[5]) + + diff --git a/scripts/TouchinBuild/Tests/UnitTests/SettingsResolver/__init__.py b/scripts/TouchinBuild/Tests/UnitTests/SettingsResolver/__init__.py new file mode 100644 index 0000000..cc31abc --- /dev/null +++ b/scripts/TouchinBuild/Tests/UnitTests/SettingsResolver/__init__.py @@ -0,0 +1 @@ +__author__ = 'rzaitov' diff --git a/scripts/TouchinBuild/Tests/UnitTests/SettingsResolver/test_settingsResolver.py b/scripts/TouchinBuild/Tests/UnitTests/SettingsResolver/test_settingsResolver.py new file mode 100644 index 0000000..0a6cc15 --- /dev/null +++ b/scripts/TouchinBuild/Tests/UnitTests/SettingsResolver/test_settingsResolver.py @@ -0,0 +1,26 @@ +import unittest +from Core.DependencyResolver.SettingsResolver import SettingsResolver + + +class TestSettingsResolver(unittest.TestCase): + def test_resolveSettings(self): + unresolvedSettings = { + 'key1': 'value1', + 'key2': 'value2', + 'key3': '{@key1} {@key2}', + 'key4': '{@key1} {@key3}', + + 'key5': 'value5', + 'key6': '{@key5} value6' + } + + settingsResolver = SettingsResolver(unresolvedSettings) + resolvedSettings = settingsResolver.resolveSettings() + + self.assertEqual('value1', resolvedSettings['key1']) + self.assertEqual('value2', resolvedSettings['key2']) + self.assertEqual('value1 value2', resolvedSettings['key3']) + self.assertEqual('value1 value1 value2', resolvedSettings['key4']) + + self.assertEqual('value5', resolvedSettings['key5']) + self.assertEqual('value5 value6', resolvedSettings['key6']) \ No newline at end of file diff --git a/scripts/TouchinBuild/run_manual_tests.py b/scripts/TouchinBuild/run_manual_tests.py index e3aaf88..bee8872 100644 --- a/scripts/TouchinBuild/run_manual_tests.py +++ b/scripts/TouchinBuild/run_manual_tests.py @@ -20,5 +20,6 @@ os.chdir(baseDir) #import ManualTests.clean_test #import Tests.ManualTests.testflight_test #import Tests.ManualTests.install_profile +#import Tests.ManualTests.macros_include_test -import Tests.ManualTests.macros_include_test \ No newline at end of file +import Tests.ManualTests.resolve_settings \ No newline at end of file diff --git a/scripts/TouchinBuild/taskRunner.py b/scripts/TouchinBuild/taskRunner.py index 8650cfe..8fdbf27 100755 --- a/scripts/TouchinBuild/taskRunner.py +++ b/scripts/TouchinBuild/taskRunner.py @@ -8,7 +8,8 @@ from Core.LineConveyor.MacroResolver import MacroResolver from Core.LineConveyor.Stripper import Stripper from Core.LineConveyor.TextInclude import TextInclude from commands.ValueProvider import ValueProvider -from utils.BuildConfigProvider import BuildConfigProvider +from utils.BuildConfigProvider.BuildConfigProvider import BuildConfigProvider +from utils.BuildConfigProvider.ResolvedBuildConfigProvider import ResolvedBuildConfigProvider from utils.IncludeProcessor import IncludeProcessor from utils.MacroProcessor import MacroProcessor from utils.SettingsProvider.CmdArgsOverriderSettingsProvider import CmdArgsOverriderSettingsProvider @@ -23,15 +24,15 @@ from Core.StepsRunner import StepsRunner #os.chdir(baseDir) - class TaskRunner: - def __init__(self, settingsProvider, fileContentProvider): + def __init__(self, settingsProvider, fileContentProvider, buildConfigProvider): assert settingsProvider is not None assert fileContentProvider is not None + assert buildConfigProvider is not None self.settingsProvider = settingsProvider self.fileContentProvider = fileContentProvider - self.configsProvider = BuildConfigProvider() + self.configsProvider = buildConfigProvider lineStripper = Stripper() commentRemover = CommentRemover() @@ -84,8 +85,12 @@ if __name__ == "__main__": # TODO: перенести в корень комапановки fromFileSettingsProvider = FromFileSettingsProvider() overrideWithCmdSetProvider = CmdArgsOverriderSettingsProvider(fromFileSettingsProvider, overrideArgs) + #resolvedSettingsProvider = ResolvedSettingsProvider(CmdArgsOverriderSettingsProvider) fContentProvider = FileContentProvider() - runner = TaskRunner(overrideWithCmdSetProvider, fContentProvider) + buildConfigProvider = BuildConfigProvider() + resolvedBuildConfigProvider = ResolvedBuildConfigProvider(buildConfigProvider) + + runner = TaskRunner(overrideWithCmdSetProvider, fContentProvider, resolvedBuildConfigProvider) runner.run() \ No newline at end of file diff --git a/scripts/TouchinBuild/utils/BuildConfigProvider.py b/scripts/TouchinBuild/utils/BuildConfigProvider/BuildConfigProvider.py similarity index 89% rename from scripts/TouchinBuild/utils/BuildConfigProvider.py rename to scripts/TouchinBuild/utils/BuildConfigProvider/BuildConfigProvider.py index 4a37303..fb5ecd0 100644 --- a/scripts/TouchinBuild/utils/BuildConfigProvider.py +++ b/scripts/TouchinBuild/utils/BuildConfigProvider/BuildConfigProvider.py @@ -1,6 +1,9 @@ -class BuildConfigProvider: +from Core.BuildConfigProviderBase import BuildConfigProviderBase + + +class BuildConfigProvider(BuildConfigProviderBase): def __init__(self): - pass + BuildConfigProviderBase.__init__(self) def getConfigs(self, rootConfig): buildReadyConfigNames = self.fetchBuildReadyConfigNames(rootConfig) diff --git a/scripts/TouchinBuild/utils/BuildConfigProvider/ResolvedBuildConfigProvider.py b/scripts/TouchinBuild/utils/BuildConfigProvider/ResolvedBuildConfigProvider.py new file mode 100644 index 0000000..3202ccb --- /dev/null +++ b/scripts/TouchinBuild/utils/BuildConfigProvider/ResolvedBuildConfigProvider.py @@ -0,0 +1,22 @@ +from Core.BuildConfigProviderBase import BuildConfigProviderBase +from Core.DependencyResolver.SettingsResolver import SettingsResolver + + +class ResolvedBuildConfigProvider(BuildConfigProviderBase): + def __init__(self, buildConfigProvider): + BuildConfigProviderBase.__init__(self) + assert buildConfigProvider is not None + + self.inner = buildConfigProvider + + def getConfigs(self, rootConfig): + unresolvedBuildConfigs = self.inner.getConfigs(rootConfig) + resolvedBuildConfigs = [] + + for bc in unresolvedBuildConfigs: + resolver = SettingsResolver(bc) + resolvedBuildConfig = resolver.resolveSettings() + + resolvedBuildConfigs.append(resolvedBuildConfig) + + return resolvedBuildConfigs \ No newline at end of file diff --git a/scripts/TouchinBuild/utils/BuildConfigProvider/__init__.py b/scripts/TouchinBuild/utils/BuildConfigProvider/__init__.py new file mode 100644 index 0000000..cc31abc --- /dev/null +++ b/scripts/TouchinBuild/utils/BuildConfigProvider/__init__.py @@ -0,0 +1 @@ +__author__ = 'rzaitov' diff --git a/scripts/TouchinBuild/utils/MacroProcessor.py b/scripts/TouchinBuild/utils/MacroProcessor.py index 911647d..614d6ff 100644 --- a/scripts/TouchinBuild/utils/MacroProcessor.py +++ b/scripts/TouchinBuild/utils/MacroProcessor.py @@ -16,6 +16,11 @@ class MacroProcessor: return macro[1:-1] + def getNameByMacroName(self, macroName): + assert macroName.startswith('@') + + return macroName[1:] + def getSymbols(self, line): assert line is not None