diff --git a/snapcraft/internal/parser.py b/snapcraft/internal/parser.py index cc0fa1c57d9..5af2a4f9665 100644 --- a/snapcraft/internal/parser.py +++ b/snapcraft/internal/parser.py @@ -39,6 +39,7 @@ import yaml from docopt import docopt +from collections import OrderedDict from snapcraft.internal import log, sources @@ -56,6 +57,23 @@ class InvalidEntryError(Exception): PARTS_FILE = "snap-parts.yaml" +# yaml OrderedDict loading and dumping +# from http://stackoverflow.com/a/21048064 Wed Jun 22 16:05:34 UTC 2016 +_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG + + +def dict_representer(dumper, data): + return dumper.represent_dict(data.items()) + + +def dict_constructor(loader, node): + return OrderedDict(loader.construct_pairs(node)) + + +yaml.add_representer(OrderedDict, dict_representer) +yaml.add_constructor(_mapping_tag, dict_constructor) + + def _get_version(): try: return pkg_resources.require('snapcraft-parser')[0].version @@ -146,7 +164,7 @@ def _process_subparts(project_part, subparts, parts, origin, maintainer, def _process_entry(data): key = data.get('project-part') - parts_list = {} + parts_list = OrderedDict() # Store all the parts listed in 'after' for each included part so that # we can check later that we aren't missing any parts. # XXX: What do we do about 'after' parts that should be looked for in @@ -210,7 +228,7 @@ class Options: def _process_index(output): # XXX: This can't remain in memory if the list gets very large, but it # should be okay for now. - master_parts_list = {} + master_parts_list = OrderedDict() output = output.strip() diff --git a/snapcraft/tests/test_parser.py b/snapcraft/tests/test_parser.py index 4da74831992..41c09fa2f8c 100644 --- a/snapcraft/tests/test_parser.py +++ b/snapcraft/tests/test_parser.py @@ -19,6 +19,7 @@ from unittest import mock import yaml +from collections import OrderedDict from snapcraft.internal.parser import ( _get_namespaced_partname, @@ -556,3 +557,53 @@ def test_missing_fields(self, mock_get, mock_get_origin_data): } main(['--debug', '--index', TEST_OUTPUT_PATH]) self.assertEqual(0, _get_part_list_count()) + + @mock.patch('snapcraft.internal.parser._get_origin_data') + @mock.patch('snapcraft.internal.sources.get') + def test_parsed_output_matches_wiki_order( + self, mock_get, mock_get_origin_data): + _create_example_output(""" +--- +maintainer: John Doe +origin: lp:snapcraft-parser-example +description: example main +project-part: main +--- +maintainer: Jim Doe +origin: lp:snapcraft-parser-example +description: example main2 +project-part: main2 +--- +maintainer: Jim Doe +origin: lp:snapcraft-parser-example +description: example main2 +project-part: app1 +""") + parts = OrderedDict() + + parts_main = OrderedDict() + parts_main['source'] = 'lp:project' + parts_main['plugin'] = 'copy' + parts_main['files'] = ['file1', 'file2'] + parts['main'] = parts_main + + parts_main2 = OrderedDict() + parts_main2['source'] = 'lp:project' + parts_main2['plugin'] = 'copy' + parts_main2['files'] = ['file1', 'file2'] + parts['main2'] = parts_main2 + + parts_app1 = OrderedDict() + parts_app1['source'] = 'lp:project' + parts_app1['plugin'] = 'copy' + parts_app1['files'] = ['file1', 'file2'] + parts['app1'] = parts_app1 + + mock_get_origin_data.return_value = { + 'parts': parts, + } + main(['--index', TEST_OUTPUT_PATH]) + self.assertEqual(3, _get_part_list_count()) + + self.assertEqual(parts, + _get_part_list()) diff --git a/snapcraft/tests/test_pluginhandler.py b/snapcraft/tests/test_pluginhandler.py index aa75572fb8e..d9537364eda 100644 --- a/snapcraft/tests/test_pluginhandler.py +++ b/snapcraft/tests/test_pluginhandler.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from collections import OrderedDict import copy import logging import os @@ -599,9 +600,9 @@ def test_pull_state(self, ubuntu_mock): self.assertTrue(state, 'Expected pull to save state YAML') self.assertTrue(type(state) is states.PullState) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertEqual(0, len(state.properties)) - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertTrue('deb_arch' in state.project_options) @patch('importlib.import_module') @@ -621,10 +622,10 @@ def test_pull_state_with_properties(self, plugin_mock, local_load_mock, self.assertTrue(state, 'Expected pull to save state YAML') self.assertTrue(type(state) is states.PullState) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertTrue('foo' in state.properties) self.assertEqual(state.properties['foo'], 'bar') - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertTrue('deb_arch' in state.project_options) @patch.object(nil.NilPlugin, 'clean_pull') @@ -650,9 +651,9 @@ def test_build_state(self): self.assertTrue(state, 'Expected build to save state YAML') self.assertTrue(type(state) is states.BuildState) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertEqual(0, len(state.properties)) - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertTrue('deb_arch' in state.project_options) @patch('importlib.import_module') @@ -672,10 +673,10 @@ def test_build_state_with_properties(self, plugin_mock, local_load_mock, self.assertTrue(state, 'Expected build to save state YAML') self.assertTrue(type(state) is states.BuildState) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertTrue('foo' in state.properties) self.assertEqual(state.properties['foo'], 'bar') - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertTrue('deb_arch' in state.project_options) @patch.object(nil.NilPlugin, 'clean_build') @@ -710,7 +711,7 @@ def test_stage_state(self): self.assertTrue(type(state) is states.StageState) self.assertTrue(type(state.files) is set) self.assertTrue(type(state.directories) is set) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertEqual(2, len(state.files)) self.assertTrue('bin/1' in state.files) self.assertTrue('bin/2' in state.files) @@ -718,7 +719,7 @@ def test_stage_state(self): self.assertTrue('bin' in state.directories) self.assertTrue('stage' in state.properties) self.assertEqual(state.properties['stage'], ['*']) - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertEqual(0, len(state.project_options)) def test_stage_state_with_stage_keyword(self): @@ -741,14 +742,14 @@ def test_stage_state_with_stage_keyword(self): self.assertTrue(type(state) is states.StageState) self.assertTrue(type(state.files) is set) self.assertTrue(type(state.directories) is set) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertEqual(1, len(state.files)) self.assertTrue('bin/1' in state.files) self.assertEqual(1, len(state.directories)) self.assertTrue('bin' in state.directories) self.assertTrue('stage' in state.properties) self.assertEqual(state.properties['stage'], ['bin/1']) - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertEqual(0, len(state.project_options)) self.assertEqual('stage', self.handler.last_step()) @@ -850,7 +851,7 @@ def test_prime_state(self, mock_copy, mock_find_dependencies): self.assertTrue(type(state.files) is set) self.assertTrue(type(state.directories) is set) self.assertTrue(type(state.dependency_paths) is set) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertEqual(2, len(state.files)) self.assertTrue('bin/1' in state.files) self.assertTrue('bin/2' in state.files) @@ -859,7 +860,7 @@ def test_prime_state(self, mock_copy, mock_find_dependencies): self.assertEqual(0, len(state.dependency_paths)) self.assertTrue('snap' in state.properties) self.assertEqual(state.properties['snap'], ['*']) - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertEqual(0, len(state.project_options)) @patch('snapcraft.internal.pluginhandler._find_dependencies') @@ -898,7 +899,7 @@ def test_prime_state_with_dependencies(self, mock_migrate_files, self.assertTrue(type(state.files) is set) self.assertTrue(type(state.directories) is set) self.assertTrue(type(state.dependency_paths) is set) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertEqual(2, len(state.files)) self.assertTrue('bin/1' in state.files) self.assertTrue('bin/2' in state.files) @@ -910,7 +911,7 @@ def test_prime_state_with_dependencies(self, mock_migrate_files, self.assertTrue('lib2' in state.dependency_paths) self.assertTrue('snap' in state.properties) self.assertEqual(state.properties['snap'], ['*']) - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertEqual(0, len(state.project_options)) @patch('snapcraft.internal.pluginhandler._find_dependencies') @@ -941,7 +942,7 @@ def test_prime_state_with_snap_keyword(self, mock_copy, self.assertTrue(type(state.files) is set) self.assertTrue(type(state.directories) is set) self.assertTrue(type(state.dependency_paths) is set) - self.assertTrue(type(state.properties) is dict) + self.assertTrue(type(state.properties) is OrderedDict) self.assertEqual(1, len(state.files)) self.assertTrue('bin/1' in state.files) self.assertEqual(1, len(state.directories)) @@ -949,7 +950,7 @@ def test_prime_state_with_snap_keyword(self, mock_copy, self.assertEqual(0, len(state.dependency_paths)) self.assertTrue('snap' in state.properties) self.assertEqual(state.properties['snap'], ['bin/1']) - self.assertTrue(type(state.project_options) is dict) + self.assertTrue(type(state.project_options) is OrderedDict) self.assertEqual(0, len(state.project_options)) def test_clean_prime_state(self):