We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Role search path precedence change in v2
In 1.9 the path precedence appears to be:
1. playbook_dir/roles/
2. playbook_dir/

In 2.0 the path precedence has changed to what appears to be
1. playbook_dir/
2. playbook_dir/roles/

To validate this, the file tree looks like:

```
.
├── ansible.cfg
├── roles
│   └── test
│       └── tasks
│           └── main.yml
├── test
│   └── tasks
│       └── main.yml
└── test.yml
```

ansible.cfg in this case is completely empty.
#### test.yml

```

---
- hosts: all
  gather_facts: false
  roles:
    - test
```

Each `main.yml` should contain a single debug, each with different message such as:

```
- debug: msg=in_roles_dir
```

```
- debug: msg=in_playbook_dir
```

This causes a completely non-role related directory in the `playbook_dir` to potentially, override an actual role of the same name in the `roles` directory.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/ansible/playbook/role/definition.py`
Content:
```
1 # (c) 2014 Michael DeHaan, <michael@ansible.com>
2 #
3 # This file is part of Ansible
4 #
5 # Ansible is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Ansible is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
17 
18 # Make coding more python3-ish
19 from __future__ import (absolute_import, division, print_function)
20 __metaclass__ = type
21 
22 from ansible.compat.six import iteritems, string_types
23 
24 import os
25 
26 from ansible import constants as C
27 from ansible.errors import AnsibleError
28 from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
29 from ansible.playbook.attribute import Attribute, FieldAttribute
30 from ansible.playbook.base import Base
31 from ansible.playbook.become import Become
32 from ansible.playbook.conditional import Conditional
33 from ansible.playbook.taggable import Taggable
34 from ansible.template import Templar
35 from ansible.utils.path import unfrackpath
36 
37 
38 __all__ = ['RoleDefinition']
39 
40 
41 class RoleDefinition(Base, Become, Conditional, Taggable):
42 
43     _role = FieldAttribute(isa='string')
44 
45     def __init__(self, play=None, role_basedir=None, variable_manager=None, loader=None):
46         self._play             = play
47         self._variable_manager = variable_manager
48         self._loader           = loader
49 
50         self._role_path    = None
51         self._role_basedir = role_basedir
52         self._role_params  = dict()
53         super(RoleDefinition, self).__init__()
54 
55     #def __repr__(self):
56     #    return 'ROLEDEF: ' + self._attributes.get('role', '<no name set>')
57 
58     @staticmethod
59     def load(data, variable_manager=None, loader=None):
60         raise AnsibleError("not implemented")
61 
62     def preprocess_data(self, ds):
63         # role names that are simply numbers can be parsed by PyYAML
64         # as integers even when quoted, so turn it into a string type
65         if isinstance(ds, int):
66             ds = "%s" % ds
67 
68         assert isinstance(ds, dict) or isinstance(ds, string_types) or isinstance(ds, AnsibleBaseYAMLObject)
69 
70         if isinstance(ds, dict):
71             ds = super(RoleDefinition, self).preprocess_data(ds)
72 
73         # save the original ds for use later
74         self._ds = ds
75 
76         # we create a new data structure here, using the same
77         # object used internally by the YAML parsing code so we
78         # can preserve file:line:column information if it exists
79         new_ds = AnsibleMapping()
80         if isinstance(ds, AnsibleBaseYAMLObject):
81             new_ds.ansible_pos = ds.ansible_pos
82 
83         # first we pull the role name out of the data structure,
84         # and then use that to determine the role path (which may
85         # result in a new role name, if it was a file path)
86         role_name = self._load_role_name(ds)
87         (role_name, role_path) = self._load_role_path(role_name)
88 
89         # next, we split the role params out from the valid role
90         # attributes and update the new datastructure with that
91         # result and the role name
92         if isinstance(ds, dict):
93             (new_role_def, role_params) = self._split_role_params(ds)
94             new_ds.update(new_role_def)
95             self._role_params = role_params
96 
97         # set the role name in the new ds
98         new_ds['role'] = role_name
99 
100         # we store the role path internally
101         self._role_path = role_path
102 
103         # and return the cleaned-up data structure
104         return new_ds
105 
106     def _load_role_name(self, ds):
107         '''
108         Returns the role name (either the role: or name: field) from
109         the role definition, or (when the role definition is a simple
110         string), just that string
111         '''
112 
113         if isinstance(ds, string_types):
114             return ds
115 
116         role_name = ds.get('role', ds.get('name'))
117         if not role_name or not isinstance(role_name, string_types):
118             raise AnsibleError('role definitions must contain a role name', obj=ds)
119 
120         # if we have the required datastructures, and if the role_name
121         # contains a variable, try and template it now
122         if self._variable_manager:
123             all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)
124             templar = Templar(loader=self._loader, variables=all_vars)
125             if templar._contains_vars(role_name):
126                 role_name = templar.template(role_name)
127 
128         return role_name
129 
130     def _load_role_path(self, role_name):
131         '''
132         the 'role', as specified in the ds (or as a bare string), can either
133         be a simple name or a full path. If it is a full path, we use the
134         basename as the role name, otherwise we take the name as-given and
135         append it to the default role path
136         '''
137 
138         role_path = unfrackpath(role_name)
139 
140         if self._loader.path_exists(role_path):
141             role_name = os.path.basename(role_name)
142             return (role_name, role_path)
143         else:
144             # we always start the search for roles in the base directory of the playbook
145             role_search_paths = [
146                 os.path.join(self._loader.get_basedir(), u'roles'),
147                 u'./roles',
148                 self._loader.get_basedir(),
149                 u'./'
150             ]
151 
152             # also search in the configured roles path
153             if C.DEFAULT_ROLES_PATH:
154                 configured_paths = C.DEFAULT_ROLES_PATH.split(os.pathsep)
155                 role_search_paths.extend(configured_paths)
156 
157             # finally, append the roles basedir, if it was set, so we can
158             # search relative to that directory for dependent roles
159             if self._role_basedir:
160                 role_search_paths.append(self._role_basedir)
161 
162             # create a templar class to template the dependency names, in
163             # case they contain variables
164             if self._variable_manager is not None:
165                 all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)
166             else:
167                 all_vars = dict()
168 
169             templar = Templar(loader=self._loader, variables=all_vars)
170             role_name = templar.template(role_name)
171 
172             # now iterate through the possible paths and return the first one we find
173             for path in role_search_paths:
174                 path = templar.template(path)
175                 role_path = unfrackpath(os.path.join(path, role_name))
176                 if self._loader.path_exists(role_path):
177                     return (role_name, role_path)
178 
179         raise AnsibleError("the role '%s' was not found in %s" % (role_name, ":".join(role_search_paths)), obj=self._ds)
180 
181     def _split_role_params(self, ds):
182         '''
183         Splits any random role params off from the role spec and store
184         them in a dictionary of params for parsing later
185         '''
186 
187         role_def = dict()
188         role_params = dict()
189         base_attribute_names = frozenset(self._get_base_attributes().keys())
190         for (key, value) in iteritems(ds):
191             # use the list of FieldAttribute values to determine what is and is not
192             # an extra parameter for this role (or sub-class of this role)
193             if key not in base_attribute_names:
194                 # this key does not match a field attribute, so it must be a role param
195                 role_params[key] = value
196             else:
197                 # this is a field attribute, so copy it over directly
198                 role_def[key] = value
199 
200         return (role_def, role_params)
201 
202     def get_role_params(self):
203         return self._role_params.copy()
204 
205     def get_role_path(self):
206         return self._role_path
207
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/ansible/playbook/role/definition.py b/lib/ansible/playbook/role/definition.py
--- a/lib/ansible/playbook/role/definition.py
+++ b/lib/ansible/playbook/role/definition.py
@@ -135,46 +135,44 @@
         append it to the default role path
         '''
 
-        role_path = unfrackpath(role_name)
+        # we always start the search for roles in the base directory of the playbook
+        role_search_paths = [
+            os.path.join(self._loader.get_basedir(), u'roles'),
+            self._loader.get_basedir(),
+        ]
+
+        # also search in the configured roles path
+        if C.DEFAULT_ROLES_PATH:
+            configured_paths = C.DEFAULT_ROLES_PATH.split(os.pathsep)
+            role_search_paths.extend(configured_paths)
+
+        # finally, append the roles basedir, if it was set, so we can
+        # search relative to that directory for dependent roles
+        if self._role_basedir:
+            role_search_paths.append(self._role_basedir)
+
+        # create a templar class to template the dependency names, in
+        # case they contain variables
+        if self._variable_manager is not None:
+            all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)
+        else:
+            all_vars = dict()
+
+        templar = Templar(loader=self._loader, variables=all_vars)
+        role_name = templar.template(role_name)
+
+        # now iterate through the possible paths and return the first one we find
+        for path in role_search_paths:
+            path = templar.template(path)
+            role_path = unfrackpath(os.path.join(path, role_name))
+            if self._loader.path_exists(role_path):
+                return (role_name, role_path)
 
+        # if not found elsewhere try to extract path from name
+        role_path = unfrackpath(role_name)
         if self._loader.path_exists(role_path):
             role_name = os.path.basename(role_name)
             return (role_name, role_path)
-        else:
-            # we always start the search for roles in the base directory of the playbook
-            role_search_paths = [
-                os.path.join(self._loader.get_basedir(), u'roles'),
-                u'./roles',
-                self._loader.get_basedir(),
-                u'./'
-            ]
-
-            # also search in the configured roles path
-            if C.DEFAULT_ROLES_PATH:
-                configured_paths = C.DEFAULT_ROLES_PATH.split(os.pathsep)
-                role_search_paths.extend(configured_paths)
-
-            # finally, append the roles basedir, if it was set, so we can
-            # search relative to that directory for dependent roles
-            if self._role_basedir:
-                role_search_paths.append(self._role_basedir)
-
-            # create a templar class to template the dependency names, in
-            # case they contain variables
-            if self._variable_manager is not None:
-                all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)
-            else:
-                all_vars = dict()
-
-            templar = Templar(loader=self._loader, variables=all_vars)
-            role_name = templar.template(role_name)
-
-            # now iterate through the possible paths and return the first one we find
-            for path in role_search_paths:
-                path = templar.template(path)
-                role_path = unfrackpath(os.path.join(path, role_name))
-                if self._loader.path_exists(role_path):
-                    return (role_name, role_path)
 
         raise AnsibleError("the role '%s' was not found in %s" % (role_name, ":".join(role_search_paths)), obj=self._ds)

{"golden_diff": "diff --git a/lib/ansible/playbook/role/definition.py b/lib/ansible/playbook/role/definition.py\n--- a/lib/ansible/playbook/role/definition.py\n+++ b/lib/ansible/playbook/role/definition.py\n@@ -135,46 +135,44 @@\n         append it to the default role path\n         '''\n \n-        role_path = unfrackpath(role_name)\n+        # we always start the search for roles in the base directory of the playbook\n+        role_search_paths = [\n+            os.path.join(self._loader.get_basedir(), u'roles'),\n+            self._loader.get_basedir(),\n+        ]\n+\n+        # also search in the configured roles path\n+        if C.DEFAULT_ROLES_PATH:\n+            configured_paths = C.DEFAULT_ROLES_PATH.split(os.pathsep)\n+            role_search_paths.extend(configured_paths)\n+\n+        # finally, append the roles basedir, if it was set, so we can\n+        # search relative to that directory for dependent roles\n+        if self._role_basedir:\n+            role_search_paths.append(self._role_basedir)\n+\n+        # create a templar class to template the dependency names, in\n+        # case they contain variables\n+        if self._variable_manager is not None:\n+            all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)\n+        else:\n+            all_vars = dict()\n+\n+        templar = Templar(loader=self._loader, variables=all_vars)\n+        role_name = templar.template(role_name)\n+\n+        # now iterate through the possible paths and return the first one we find\n+        for path in role_search_paths:\n+            path = templar.template(path)\n+            role_path = unfrackpath(os.path.join(path, role_name))\n+            if self._loader.path_exists(role_path):\n+                return (role_name, role_path)\n \n+        # if not found elsewhere try to extract path from name\n+        role_path = unfrackpath(role_name)\n         if self._loader.path_exists(role_path):\n             role_name = os.path.basename(role_name)\n             return (role_name, role_path)\n-        else:\n-            # we always start the search for roles in the base directory of the playbook\n-            role_search_paths = [\n-                os.path.join(self._loader.get_basedir(), u'roles'),\n-                u'./roles',\n-                self._loader.get_basedir(),\n-                u'./'\n-            ]\n-\n-            # also search in the configured roles path\n-            if C.DEFAULT_ROLES_PATH:\n-                configured_paths = C.DEFAULT_ROLES_PATH.split(os.pathsep)\n-                role_search_paths.extend(configured_paths)\n-\n-            # finally, append the roles basedir, if it was set, so we can\n-            # search relative to that directory for dependent roles\n-            if self._role_basedir:\n-                role_search_paths.append(self._role_basedir)\n-\n-            # create a templar class to template the dependency names, in\n-            # case they contain variables\n-            if self._variable_manager is not None:\n-                all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)\n-            else:\n-                all_vars = dict()\n-\n-            templar = Templar(loader=self._loader, variables=all_vars)\n-            role_name = templar.template(role_name)\n-\n-            # now iterate through the possible paths and return the first one we find\n-            for path in role_search_paths:\n-                path = templar.template(path)\n-                role_path = unfrackpath(os.path.join(path, role_name))\n-                if self._loader.path_exists(role_path):\n-                    return (role_name, role_path)\n \n         raise AnsibleError(\"the role '%s' was not found in %s\" % (role_name, \":\".join(role_search_paths)), obj=self._ds)\n", "issue": "Role search path precedence change in v2\nIn 1.9 the path precedence appears to be:\n1. playbook_dir/roles/\n2. playbook_dir/\n\nIn 2.0 the path precedence has changed to what appears to be\n1. playbook_dir/\n2. playbook_dir/roles/\n\nTo validate this, the file tree looks like:\n\n```\n.\n\u251c\u2500\u2500 ansible.cfg\n\u251c\u2500\u2500 roles\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 test\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 tasks\n\u2502\u00a0\u00a0         \u2514\u2500\u2500 main.yml\n\u251c\u2500\u2500 test\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 tasks\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 main.yml\n\u2514\u2500\u2500 test.yml\n```\n\nansible.cfg in this case is completely empty.\n#### test.yml\n\n```\n\n---\n- hosts: all\n  gather_facts: false\n  roles:\n    - test\n```\n\nEach `main.yml` should contain a single debug, each with different message such as:\n\n```\n- debug: msg=in_roles_dir\n```\n\n```\n- debug: msg=in_playbook_dir\n```\n\nThis causes a completely non-role related directory in the `playbook_dir` to potentially, override an actual role of the same name in the `roles` directory.\n\n", "before_files": [{"content": "# (c) 2014 Michael DeHaan, <michael@ansible.com>\n#\n# This file is part of Ansible\n#\n# Ansible is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Ansible is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n\n# Make coding more python3-ish\nfrom __future__ import (absolute_import, division, print_function)\n__metaclass__ = type\n\nfrom ansible.compat.six import iteritems, string_types\n\nimport os\n\nfrom ansible import constants as C\nfrom ansible.errors import AnsibleError\nfrom ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping\nfrom ansible.playbook.attribute import Attribute, FieldAttribute\nfrom ansible.playbook.base import Base\nfrom ansible.playbook.become import Become\nfrom ansible.playbook.conditional import Conditional\nfrom ansible.playbook.taggable import Taggable\nfrom ansible.template import Templar\nfrom ansible.utils.path import unfrackpath\n\n\n__all__ = ['RoleDefinition']\n\n\nclass RoleDefinition(Base, Become, Conditional, Taggable):\n\n    _role = FieldAttribute(isa='string')\n\n    def __init__(self, play=None, role_basedir=None, variable_manager=None, loader=None):\n        self._play             = play\n        self._variable_manager = variable_manager\n        self._loader           = loader\n\n        self._role_path    = None\n        self._role_basedir = role_basedir\n        self._role_params  = dict()\n        super(RoleDefinition, self).__init__()\n\n    #def __repr__(self):\n    #    return 'ROLEDEF: ' + self._attributes.get('role', '<no name set>')\n\n    @staticmethod\n    def load(data, variable_manager=None, loader=None):\n        raise AnsibleError(\"not implemented\")\n\n    def preprocess_data(self, ds):\n        # role names that are simply numbers can be parsed by PyYAML\n        # as integers even when quoted, so turn it into a string type\n        if isinstance(ds, int):\n            ds = \"%s\" % ds\n\n        assert isinstance(ds, dict) or isinstance(ds, string_types) or isinstance(ds, AnsibleBaseYAMLObject)\n\n        if isinstance(ds, dict):\n            ds = super(RoleDefinition, self).preprocess_data(ds)\n\n        # save the original ds for use later\n        self._ds = ds\n\n        # we create a new data structure here, using the same\n        # object used internally by the YAML parsing code so we\n        # can preserve file:line:column information if it exists\n        new_ds = AnsibleMapping()\n        if isinstance(ds, AnsibleBaseYAMLObject):\n            new_ds.ansible_pos = ds.ansible_pos\n\n        # first we pull the role name out of the data structure,\n        # and then use that to determine the role path (which may\n        # result in a new role name, if it was a file path)\n        role_name = self._load_role_name(ds)\n        (role_name, role_path) = self._load_role_path(role_name)\n\n        # next, we split the role params out from the valid role\n        # attributes and update the new datastructure with that\n        # result and the role name\n        if isinstance(ds, dict):\n            (new_role_def, role_params) = self._split_role_params(ds)\n            new_ds.update(new_role_def)\n            self._role_params = role_params\n\n        # set the role name in the new ds\n        new_ds['role'] = role_name\n\n        # we store the role path internally\n        self._role_path = role_path\n\n        # and return the cleaned-up data structure\n        return new_ds\n\n    def _load_role_name(self, ds):\n        '''\n        Returns the role name (either the role: or name: field) from\n        the role definition, or (when the role definition is a simple\n        string), just that string\n        '''\n\n        if isinstance(ds, string_types):\n            return ds\n\n        role_name = ds.get('role', ds.get('name'))\n        if not role_name or not isinstance(role_name, string_types):\n            raise AnsibleError('role definitions must contain a role name', obj=ds)\n\n        # if we have the required datastructures, and if the role_name\n        # contains a variable, try and template it now\n        if self._variable_manager:\n            all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)\n            templar = Templar(loader=self._loader, variables=all_vars)\n            if templar._contains_vars(role_name):\n                role_name = templar.template(role_name)\n\n        return role_name\n\n    def _load_role_path(self, role_name):\n        '''\n        the 'role', as specified in the ds (or as a bare string), can either\n        be a simple name or a full path. If it is a full path, we use the\n        basename as the role name, otherwise we take the name as-given and\n        append it to the default role path\n        '''\n\n        role_path = unfrackpath(role_name)\n\n        if self._loader.path_exists(role_path):\n            role_name = os.path.basename(role_name)\n            return (role_name, role_path)\n        else:\n            # we always start the search for roles in the base directory of the playbook\n            role_search_paths = [\n                os.path.join(self._loader.get_basedir(), u'roles'),\n                u'./roles',\n                self._loader.get_basedir(),\n                u'./'\n            ]\n\n            # also search in the configured roles path\n            if C.DEFAULT_ROLES_PATH:\n                configured_paths = C.DEFAULT_ROLES_PATH.split(os.pathsep)\n                role_search_paths.extend(configured_paths)\n\n            # finally, append the roles basedir, if it was set, so we can\n            # search relative to that directory for dependent roles\n            if self._role_basedir:\n                role_search_paths.append(self._role_basedir)\n\n            # create a templar class to template the dependency names, in\n            # case they contain variables\n            if self._variable_manager is not None:\n                all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)\n            else:\n                all_vars = dict()\n\n            templar = Templar(loader=self._loader, variables=all_vars)\n            role_name = templar.template(role_name)\n\n            # now iterate through the possible paths and return the first one we find\n            for path in role_search_paths:\n                path = templar.template(path)\n                role_path = unfrackpath(os.path.join(path, role_name))\n                if self._loader.path_exists(role_path):\n                    return (role_name, role_path)\n\n        raise AnsibleError(\"the role '%s' was not found in %s\" % (role_name, \":\".join(role_search_paths)), obj=self._ds)\n\n    def _split_role_params(self, ds):\n        '''\n        Splits any random role params off from the role spec and store\n        them in a dictionary of params for parsing later\n        '''\n\n        role_def = dict()\n        role_params = dict()\n        base_attribute_names = frozenset(self._get_base_attributes().keys())\n        for (key, value) in iteritems(ds):\n            # use the list of FieldAttribute values to determine what is and is not\n            # an extra parameter for this role (or sub-class of this role)\n            if key not in base_attribute_names:\n                # this key does not match a field attribute, so it must be a role param\n                role_params[key] = value\n            else:\n                # this is a field attribute, so copy it over directly\n                role_def[key] = value\n\n        return (role_def, role_params)\n\n    def get_role_params(self):\n        return self._role_params.copy()\n\n    def get_role_path(self):\n        return self._role_path\n", "path": "lib/ansible/playbook/role/definition.py"}], "after_files": [{"content": "# (c) 2014 Michael DeHaan, <michael@ansible.com>\n#\n# This file is part of Ansible\n#\n# Ansible is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Ansible is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n\n# Make coding more python3-ish\nfrom __future__ import (absolute_import, division, print_function)\n__metaclass__ = type\n\nfrom ansible.compat.six import iteritems, string_types\n\nimport os\n\nfrom ansible import constants as C\nfrom ansible.errors import AnsibleError\nfrom ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping\nfrom ansible.playbook.attribute import Attribute, FieldAttribute\nfrom ansible.playbook.base import Base\nfrom ansible.playbook.become import Become\nfrom ansible.playbook.conditional import Conditional\nfrom ansible.playbook.taggable import Taggable\nfrom ansible.template import Templar\nfrom ansible.utils.path import unfrackpath\n\n\n__all__ = ['RoleDefinition']\n\n\nclass RoleDefinition(Base, Become, Conditional, Taggable):\n\n    _role = FieldAttribute(isa='string')\n\n    def __init__(self, play=None, role_basedir=None, variable_manager=None, loader=None):\n        self._play             = play\n        self._variable_manager = variable_manager\n        self._loader           = loader\n\n        self._role_path    = None\n        self._role_basedir = role_basedir\n        self._role_params  = dict()\n        super(RoleDefinition, self).__init__()\n\n    #def __repr__(self):\n    #    return 'ROLEDEF: ' + self._attributes.get('role', '<no name set>')\n\n    @staticmethod\n    def load(data, variable_manager=None, loader=None):\n        raise AnsibleError(\"not implemented\")\n\n    def preprocess_data(self, ds):\n        # role names that are simply numbers can be parsed by PyYAML\n        # as integers even when quoted, so turn it into a string type\n        if isinstance(ds, int):\n            ds = \"%s\" % ds\n\n        assert isinstance(ds, dict) or isinstance(ds, string_types) or isinstance(ds, AnsibleBaseYAMLObject)\n\n        if isinstance(ds, dict):\n            ds = super(RoleDefinition, self).preprocess_data(ds)\n\n        # save the original ds for use later\n        self._ds = ds\n\n        # we create a new data structure here, using the same\n        # object used internally by the YAML parsing code so we\n        # can preserve file:line:column information if it exists\n        new_ds = AnsibleMapping()\n        if isinstance(ds, AnsibleBaseYAMLObject):\n            new_ds.ansible_pos = ds.ansible_pos\n\n        # first we pull the role name out of the data structure,\n        # and then use that to determine the role path (which may\n        # result in a new role name, if it was a file path)\n        role_name = self._load_role_name(ds)\n        (role_name, role_path) = self._load_role_path(role_name)\n\n        # next, we split the role params out from the valid role\n        # attributes and update the new datastructure with that\n        # result and the role name\n        if isinstance(ds, dict):\n            (new_role_def, role_params) = self._split_role_params(ds)\n            new_ds.update(new_role_def)\n            self._role_params = role_params\n\n        # set the role name in the new ds\n        new_ds['role'] = role_name\n\n        # we store the role path internally\n        self._role_path = role_path\n\n        # and return the cleaned-up data structure\n        return new_ds\n\n    def _load_role_name(self, ds):\n        '''\n        Returns the role name (either the role: or name: field) from\n        the role definition, or (when the role definition is a simple\n        string), just that string\n        '''\n\n        if isinstance(ds, string_types):\n            return ds\n\n        role_name = ds.get('role', ds.get('name'))\n        if not role_name or not isinstance(role_name, string_types):\n            raise AnsibleError('role definitions must contain a role name', obj=ds)\n\n        # if we have the required datastructures, and if the role_name\n        # contains a variable, try and template it now\n        if self._variable_manager:\n            all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)\n            templar = Templar(loader=self._loader, variables=all_vars)\n            if templar._contains_vars(role_name):\n                role_name = templar.template(role_name)\n\n        return role_name\n\n    def _load_role_path(self, role_name):\n        '''\n        the 'role', as specified in the ds (or as a bare string), can either\n        be a simple name or a full path. If it is a full path, we use the\n        basename as the role name, otherwise we take the name as-given and\n        append it to the default role path\n        '''\n\n        # we always start the search for roles in the base directory of the playbook\n        role_search_paths = [\n            os.path.join(self._loader.get_basedir(), u'roles'),\n            self._loader.get_basedir(),\n        ]\n\n        # also search in the configured roles path\n        if C.DEFAULT_ROLES_PATH:\n            configured_paths = C.DEFAULT_ROLES_PATH.split(os.pathsep)\n            role_search_paths.extend(configured_paths)\n\n        # finally, append the roles basedir, if it was set, so we can\n        # search relative to that directory for dependent roles\n        if self._role_basedir:\n            role_search_paths.append(self._role_basedir)\n\n        # create a templar class to template the dependency names, in\n        # case they contain variables\n        if self._variable_manager is not None:\n            all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)\n        else:\n            all_vars = dict()\n\n        templar = Templar(loader=self._loader, variables=all_vars)\n        role_name = templar.template(role_name)\n\n        # now iterate through the possible paths and return the first one we find\n        for path in role_search_paths:\n            path = templar.template(path)\n            role_path = unfrackpath(os.path.join(path, role_name))\n            if self._loader.path_exists(role_path):\n                return (role_name, role_path)\n\n        # if not found elsewhere try to extract path from name\n        role_path = unfrackpath(role_name)\n        if self._loader.path_exists(role_path):\n            role_name = os.path.basename(role_name)\n            return (role_name, role_path)\n\n        raise AnsibleError(\"the role '%s' was not found in %s\" % (role_name, \":\".join(role_search_paths)), obj=self._ds)\n\n    def _split_role_params(self, ds):\n        '''\n        Splits any random role params off from the role spec and store\n        them in a dictionary of params for parsing later\n        '''\n\n        role_def = dict()\n        role_params = dict()\n        base_attribute_names = frozenset(self._get_base_attributes().keys())\n        for (key, value) in iteritems(ds):\n            # use the list of FieldAttribute values to determine what is and is not\n            # an extra parameter for this role (or sub-class of this role)\n            if key not in base_attribute_names:\n                # this key does not match a field attribute, so it must be a role param\n                role_params[key] = value\n            else:\n                # this is a field attribute, so copy it over directly\n                role_def[key] = value\n\n        return (role_def, role_params)\n\n    def get_role_params(self):\n        return self._role_params.copy()\n\n    def get_role_path(self):\n        return self._role_path\n", "path": "lib/ansible/playbook/role/definition.py"}]}