@@ -140,99 +140,122 @@ func (py *Resolver) Resolve(
140
140
it := modules .Iterator ()
141
141
explainDependency := os .Getenv ("EXPLAIN_DEPENDENCY" )
142
142
hasFatalError := false
143
- MODULE_LOOP :
143
+ MODULES_LOOP :
144
144
for it .Next () {
145
145
mod := it .Value ().(module )
146
- imp := resolve.ImportSpec {Lang : languageName , Imp : mod .Name }
147
- if override , ok := resolve .FindRuleWithOverride (c , imp , languageName ); ok {
148
- if override .Repo == "" {
149
- override .Repo = from .Repo
150
- }
151
- if ! override .Equal (from ) {
152
- if override .Repo == from .Repo {
153
- override .Repo = ""
154
- }
155
- dep := override .String ()
156
- deps .Add (dep )
157
- if explainDependency == dep {
158
- log .Printf ("Explaining dependency (%s): " +
159
- "in the target %q, the file %q imports %q at line %d, " +
160
- "which resolves using the \" gazelle:resolve\" directive.\n " ,
161
- explainDependency , from .String (), mod .Filepath , mod .Name , mod .LineNumber )
146
+ moduleParts := strings .Split (mod .Name , "." )
147
+ possibleModules := []string {mod .Name }
148
+ for len (moduleParts ) > 1 {
149
+ // Iterate back through the possible imports until
150
+ // a match is found.
151
+ // For example, "from foo.bar import baz" where bar is a variable, we should try
152
+ // `foo.bar.baz` first, then `foo.bar`, then `foo`. In the first case, the import could be file `baz.py`
153
+ // in the directory `foo/bar`.
154
+ // Or, the import could be variable `bar` in file `foo/bar.py`.
155
+ // The import could also be from a standard module, e.g. `six.moves`, where
156
+ // the dependency is actually `six`.
157
+ moduleParts = moduleParts [:len (moduleParts )- 1 ]
158
+ possibleModules = append (possibleModules , strings .Join (moduleParts , "." ))
159
+ }
160
+ errs := []error {}
161
+ POSSIBLE_MODULE_LOOP:
162
+ for _ , moduleName := range possibleModules {
163
+ imp := resolve.ImportSpec {Lang : languageName , Imp : moduleName }
164
+ if override , ok := resolve .FindRuleWithOverride (c , imp , languageName ); ok {
165
+ if override .Repo == "" {
166
+ override .Repo = from .Repo
162
167
}
163
- }
164
- } else {
165
- if dep , ok := cfg .FindThirdPartyDependency (mod .Name ); ok {
166
- deps .Add (dep )
167
- if explainDependency == dep {
168
- log .Printf ("Explaining dependency (%s): " +
169
- "in the target %q, the file %q imports %q at line %d, " +
170
- "which resolves from the third-party module %q from the wheel %q.\n " ,
171
- explainDependency , from .String (), mod .Filepath , mod .Name , mod .LineNumber , mod .Name , dep )
168
+ if ! override .Equal (from ) {
169
+ if override .Repo == from .Repo {
170
+ override .Repo = ""
171
+ }
172
+ dep := override .String ()
173
+ deps .Add (dep )
174
+ if explainDependency == dep {
175
+ log .Printf ("Explaining dependency (%s): " +
176
+ "in the target %q, the file %q imports %q at line %d, " +
177
+ "which resolves using the \" gazelle:resolve\" directive.\n " ,
178
+ explainDependency , from .String (), mod .Filepath , moduleName , mod .LineNumber )
179
+ }
180
+ continue MODULES_LOOP
172
181
}
173
182
} else {
174
- matches := ix .FindRulesByImportWithConfig (c , imp , languageName )
175
- if len (matches ) == 0 {
176
- // Check if the imported module is part of the standard library.
177
- if isStd , err := isStdModule (mod ); err != nil {
178
- log .Println ("ERROR: " , err )
179
- hasFatalError = true
180
- continue MODULE_LOOP
181
- } else if isStd {
182
- continue MODULE_LOOP
183
+ if dep , ok := cfg .FindThirdPartyDependency (moduleName ); ok {
184
+ deps .Add (dep )
185
+ if explainDependency == dep {
186
+ log .Printf ("Explaining dependency (%s): " +
187
+ "in the target %q, the file %q imports %q at line %d, " +
188
+ "which resolves from the third-party module %q from the wheel %q.\n " ,
189
+ explainDependency , from .String (), mod .Filepath , moduleName , mod .LineNumber , mod .Name , dep )
190
+ }
191
+ continue MODULES_LOOP
192
+ } else {
193
+ matches := ix .FindRulesByImportWithConfig (c , imp , languageName )
194
+ if len (matches ) == 0 {
195
+ // Check if the imported module is part of the standard library.
196
+ if isStd , err := isStdModule (module {Name : moduleName }); err != nil {
197
+ log .Println ("Error checking if standard module: " , err )
198
+ hasFatalError = true
199
+ continue POSSIBLE_MODULE_LOOP
200
+ } else if isStd {
201
+ continue MODULES_LOOP
202
+ } else if cfg .ValidateImportStatements () {
203
+ err := fmt .Errorf (
204
+ "%[1]q at line %[2]d from %[3]q is an invalid dependency: possible solutions:\n " +
205
+ "\t 1. Add it as a dependency in the requirements.txt file.\n " +
206
+ "\t 2. Instruct Gazelle to resolve to a known dependency using the gazelle:resolve directive.\n " +
207
+ "\t 3. Ignore it with a comment '# gazelle:ignore %[1]s' in the Python file.\n " ,
208
+ moduleName , mod .LineNumber , mod .Filepath ,
209
+ )
210
+ errs = append (errs , err )
211
+ continue POSSIBLE_MODULE_LOOP
212
+ }
183
213
}
184
- if cfg .ValidateImportStatements () {
185
- err := fmt .Errorf (
186
- "%[1]q at line %[2]d from %[3]q is an invalid dependency: possible solutions:\n " +
187
- "\t 1. Add it as a dependency in the requirements.txt file.\n " +
188
- "\t 2. Instruct Gazelle to resolve to a known dependency using the gazelle:resolve directive.\n " +
189
- "\t 3. Ignore it with a comment '# gazelle:ignore %[1]s' in the Python file.\n " ,
190
- mod .Name , mod .LineNumber , mod .Filepath ,
191
- )
192
- log .Printf ("ERROR: failed to validate dependencies for target %q: %v\n " , from .String (), err )
193
- hasFatalError = true
194
- continue MODULE_LOOP
214
+ filteredMatches := make ([]resolve.FindResult , 0 , len (matches ))
215
+ for _ , match := range matches {
216
+ if match .IsSelfImport (from ) {
217
+ // Prevent from adding itself as a dependency.
218
+ continue MODULES_LOOP
219
+ }
220
+ filteredMatches = append (filteredMatches , match )
195
221
}
196
- }
197
- filteredMatches := make ([]resolve.FindResult , 0 , len (matches ))
198
- for _ , match := range matches {
199
- if match .IsSelfImport (from ) {
200
- // Prevent from adding itself as a dependency.
201
- continue MODULE_LOOP
222
+ if len (filteredMatches ) == 0 {
223
+ continue POSSIBLE_MODULE_LOOP
202
224
}
203
- filteredMatches = append (filteredMatches , match )
204
- }
205
- if len (filteredMatches ) == 0 {
206
- continue
207
- }
208
- if len (filteredMatches ) > 1 {
209
- sameRootMatches := make ([]resolve.FindResult , 0 , len (filteredMatches ))
210
- for _ , match := range filteredMatches {
211
- if strings .HasPrefix (match .Label .Pkg , pythonProjectRoot ) {
212
- sameRootMatches = append (sameRootMatches , match )
225
+ if len (filteredMatches ) > 1 {
226
+ sameRootMatches := make ([]resolve.FindResult , 0 , len (filteredMatches ))
227
+ for _ , match := range filteredMatches {
228
+ if strings .HasPrefix (match .Label .Pkg , pythonProjectRoot ) {
229
+ sameRootMatches = append (sameRootMatches , match )
230
+ }
231
+ }
232
+ if len (sameRootMatches ) != 1 {
233
+ err := fmt .Errorf (
234
+ "multiple targets (%s) may be imported with %q at line %d in %q " +
235
+ "- this must be fixed using the \" gazelle:resolve\" directive" ,
236
+ targetListFromResults (filteredMatches ), moduleName , mod .LineNumber , mod .Filepath )
237
+ errs = append (errs , err )
238
+ continue POSSIBLE_MODULE_LOOP
213
239
}
240
+ filteredMatches = sameRootMatches
214
241
}
215
- if len ( sameRootMatches ) != 1 {
216
- err := fmt . Errorf (
217
- "multiple targets (%s) may be imported with %q at line %d in %q " +
218
- "- this must be fixed using the \" gazelle:resolve \" directive" ,
219
- targetListFromResults ( filteredMatches ), mod . Name , mod . LineNumber , mod . Filepath )
220
- log . Println ( "ERROR: " , err )
221
- hasFatalError = true
222
- continue MODULE_LOOP
242
+ matchLabel := filteredMatches [ 0 ]. Label . Rel ( from . Repo , from . Pkg )
243
+ dep := matchLabel . String ()
244
+ deps . Add ( dep )
245
+ if explainDependency == dep {
246
+ log . Printf ( "Explaining dependency (%s): " +
247
+ "in the target %q, the file %q imports %q at line %d, " +
248
+ "which resolves from the first-party indexed labels. \n " ,
249
+ explainDependency , from . String (), mod . Filepath , moduleName , mod . LineNumber )
223
250
}
224
- filteredMatches = sameRootMatches
225
- }
226
- matchLabel := filteredMatches [0 ].Label .Rel (from .Repo , from .Pkg )
227
- dep := matchLabel .String ()
228
- deps .Add (dep )
229
- if explainDependency == dep {
230
- log .Printf ("Explaining dependency (%s): " +
231
- "in the target %q, the file %q imports %q at line %d, " +
232
- "which resolves from the first-party indexed labels.\n " ,
233
- explainDependency , from .String (), mod .Filepath , mod .Name , mod .LineNumber )
251
+ continue MODULES_LOOP
234
252
}
235
253
}
254
+ } // End possible modules loop.
255
+ if len (errs ) > 0 {
256
+ // If, after trying all possible modules, we still haven't found anything, error out.
257
+ log .Printf ("Failed to find any valid modules from %v. Errors: %v\n " , possibleModules , errs )
258
+ hasFatalError = true
236
259
}
237
260
}
238
261
if hasFatalError {
0 commit comments