@@ -42,13 +42,9 @@ def __init__(self, fs: FileSystem, project_root: str,
42
42
log .debug ("Setting Cloned Project path to %s" ,
43
43
self .CLONED_PROJECT_PATH )
44
44
45
- self .PROJECT_HAS_PIPFILE = False
46
-
47
45
# Clone the project from the provided filesystem into the local
48
46
# cache
49
47
for file_path in self .fs .walk (str (self .PROJECT_ROOT )):
50
- if file_path == "/Pipfile" :
51
- self .PROJECT_HAS_PIPFILE = True
52
48
53
49
cache_file_path = self .project_to_cache_path (file_path )
54
50
try :
@@ -63,8 +59,83 @@ def __init__(self, fs: FileSystem, project_root: str,
63
59
else :
64
60
raise e
65
61
62
+ self .project = Project (self .CLONED_PROJECT_PATH ,
63
+ self .CLONED_PROJECT_PATH )
64
+
65
+ def find_project_for_path (self , path ):
66
+ return self .project .find_project_for_path (path )
67
+
68
+ def project_to_cache_path (self , project_path ):
69
+ """
70
+ Translates a path from the root of the project to the equivalent path in
71
+ the local cache.
72
+
73
+ e.x.: '/a/b.py' -> '/python-cloned-projects-cache/project_name/a/b.py'
74
+ """
75
+
76
+ # strip the leading '/' so that we can join it properly
77
+ return self .CLONED_PROJECT_PATH / project_path .lstrip ("/" )
78
+
79
+ def cleanup (self ):
80
+ self .project .cleanup ()
81
+
82
+
83
+ class Project :
84
+ def __init__ (self , workspace_root_dir , project_root_dir ):
85
+ self .WORKSPACE_ROOT_DIR = workspace_root_dir
86
+ self .PROJECT_ROOT_DIR = project_root_dir
87
+ self .sub_projects = self ._find_subprojects (project_root_dir )
66
88
self ._install_external_dependencies ()
67
89
90
+ def _find_subprojects (self , current_dir ):
91
+ '''
92
+ Returns a map containing the top-level subprojects contained inside
93
+ this project, keyed by the absolute path to the subproject.
94
+ '''
95
+ sub_projects = {}
96
+
97
+ top_level_folders = (
98
+ child for child in current_dir .iterdir () if child .is_dir ())
99
+
100
+ for folder in top_level_folders :
101
+
102
+ def gen_len (generator ):
103
+ return sum (1 for _ in generator )
104
+
105
+ # do any subfolders contain installation files?
106
+ if any (gen_len (folder .glob (pattern )) for pattern in INSTALLATION_FILE_PATTERNS ):
107
+ sub_projects [folder ] = Project (self .WORKSPACE_ROOT_DIR , folder )
108
+ else :
109
+ sub_projects .update (self ._find_subprojects (folder ))
110
+
111
+ return sub_projects
112
+
113
+ def find_project_for_path (self , path ):
114
+ '''
115
+ Returns the deepest project instance that contains this path.
116
+
117
+ '''
118
+ if path .is_file ():
119
+ folder = path .parent
120
+ else :
121
+ folder = path
122
+
123
+ if folder == self .PROJECT_ROOT_DIR :
124
+ return self
125
+
126
+ # If the project_dir isn't an ancestor of the folder,
127
+ # there is no way it or any of its subprojects
128
+ # could contain this path
129
+ if self .PROJECT_ROOT_DIR not in folder .parents :
130
+ return None
131
+
132
+ for sub_project in self .sub_projects .values ():
133
+ deepest_project = sub_project .find_project_for_path (path )
134
+ if deepest_project is not None :
135
+ return deepest_project
136
+
137
+ return self
138
+
68
139
def get_module_info (self , raw_jedi_module_path ):
69
140
"""
70
141
Given an absolute module path provided from jedi,
@@ -80,8 +151,8 @@ def get_module_info(self, raw_jedi_module_path):
80
151
81
152
module_path = Path (raw_jedi_module_path )
82
153
83
- if self .CLONED_PROJECT_PATH in module_path .parents :
84
- return (ModuleKind .PROJECT , module_path .relative_to (self .CLONED_PROJECT_PATH ))
154
+ if self .PROJECT_ROOT_DIR in module_path .parents :
155
+ return (ModuleKind .PROJECT , module_path .relative_to (self .WORKSPACE_ROOT_DIR ))
85
156
86
157
if GlobalConfig .PYTHON_PATH in module_path .parents :
87
158
return (ModuleKind .STANDARD_LIBRARY , module_path .relative_to (GlobalConfig .PYTHON_PATH ))
@@ -103,17 +174,6 @@ def get_module_info(self, raw_jedi_module_path):
103
174
104
175
return (ModuleKind .UNKNOWN , module_path )
105
176
106
- def project_to_cache_path (self , project_path ):
107
- """
108
- Translates a path from the root of the project to the equivalent path in
109
- the local cache.
110
-
111
- e.x.: '/a/b.py' -> '/python-cloned-projects-cache/project_name/a/b.py'
112
- """
113
-
114
- # strip the leading '/' so that we can join it properly
115
- return self .CLONED_PROJECT_PATH / project_path .lstrip ("/" )
116
-
117
177
def _install_external_dependencies (self ):
118
178
"""
119
179
Installs the external dependencies for the project.
@@ -128,21 +188,18 @@ def _install_external_dependencies(self):
128
188
self ._install_pipenv ()
129
189
130
190
def _install_pipenv (self ):
131
- # pipenv creates the Pipfile automatically whenever it does anything -
132
- # only install if the project had one to begin with
133
- if self .PROJECT_HAS_PIPFILE :
134
- if (self .CLONED_PROJECT_PATH / "Pipfile.lock" ).exists ():
135
- self .run_command ("pipenv install --dev --ignore-pipfile" )
136
- elif (self .CLONED_PROJECT_PATH / "Pipfile" ).exists ():
137
- self .run_command ("pipenv install --dev" )
191
+ if (self .PROJECT_ROOT_DIR / "Pipfile.lock" ).exists ():
192
+ self .run_command ("pipenv install --dev --ignore-pipfile" )
193
+ elif (self .PROJECT_ROOT_DIR / "Pipfile" ).exists ():
194
+ self .run_command ("pipenv install --dev" )
138
195
139
196
def _install_pip (self ):
140
- for requirements_file in self .CLONED_PROJECT_PATH .glob ("*requirements.txt" ):
197
+ for requirements_file in self .PROJECT_ROOT_DIR .glob (REQUIREMENTS_GLOB_PATTERN ):
141
198
self .run_command (
142
199
"pip install -r {}" .format (str (requirements_file .absolute ())))
143
200
144
201
def _install_setup_py (self ):
145
- if (self .CLONED_PROJECT_PATH / "setup.py" ).exists ():
202
+ if (self .PROJECT_ROOT_DIR / "setup.py" ).exists ():
146
203
self .run_command ("python setup.py install" )
147
204
148
205
@property
@@ -156,11 +213,14 @@ def VENV_PATH(self):
156
213
return Path (venv_path )
157
214
158
215
def cleanup (self ):
216
+ for sub_project in self .sub_projects .values ():
217
+ sub_project .cleanup ()
218
+
159
219
log .info ("Removing project's virtual environment %s" , self .VENV_PATH )
160
220
self .remove_venv ()
161
221
162
- log .info ("Removing cloned project cache %s" , self .CLONED_PROJECT_PATH )
163
- rmtree (self .CLONED_PROJECT_PATH , ignore_errors = True )
222
+ log .info ("Removing cloned project cache %s" , self .PROJECT_ROOT_DIR )
223
+ rmtree (self .PROJECT_ROOT_DIR , ignore_errors = True )
164
224
165
225
def ensure_venv_created (self ):
166
226
'''
@@ -182,7 +242,7 @@ def run_command(self, command, no_prefix=False, **kwargs):
182
242
- the projects virtual environment is loaded into
183
243
the command's environment
184
244
'''
185
- kwargs ["cwd" ] = self .CLONED_PROJECT_PATH
245
+ kwargs ["cwd" ] = self .PROJECT_ROOT_DIR
186
246
187
247
if not no_prefix :
188
248
@@ -203,6 +263,11 @@ def run_command(self, command, no_prefix=False, **kwargs):
203
263
return delegator .run (command , ** kwargs )
204
264
205
265
266
+ REQUIREMENTS_GLOB_PATTERN = "*requirements.txt"
267
+
268
+ INSTALLATION_FILE_PATTERNS = ["Pipfile" , REQUIREMENTS_GLOB_PATTERN , "setup.py" ]
269
+
270
+
206
271
class ModuleKind (Enum ):
207
272
PROJECT = 1
208
273
STANDARD_LIBRARY = 2
0 commit comments