@@ -20,6 +20,7 @@ import (
20
20
"log"
21
21
"os"
22
22
"path/filepath"
23
+ "sort"
23
24
"strings"
24
25
25
26
"github.com/bazelbuild/bazel-gazelle/config"
@@ -89,9 +90,9 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
89
90
pyTestFilenames := treeset .NewWith (godsutils .StringComparator )
90
91
pyFileNames := treeset .NewWith (godsutils .StringComparator )
91
92
92
- // hasPyBinary controls whether a py_binary target should be generated for
93
+ // hasPyBinaryEntryPointFile controls whether a single py_binary target should be generated for
93
94
// this package or not.
94
- hasPyBinary := false
95
+ hasPyBinaryEntryPointFile := false
95
96
96
97
// hasPyTestEntryPointFile and hasPyTestEntryPointTarget control whether a py_test target should
97
98
// be generated for this package or not.
@@ -106,8 +107,8 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
106
107
ext := filepath .Ext (f )
107
108
if ext == ".py" {
108
109
pyFileNames .Add (f )
109
- if ! hasPyBinary && f == pyBinaryEntrypointFilename {
110
- hasPyBinary = true
110
+ if ! hasPyBinaryEntryPointFile && f == pyBinaryEntrypointFilename {
111
+ hasPyBinaryEntryPointFile = true
111
112
} else if ! hasPyTestEntryPointFile && f == pyTestEntrypointFilename {
112
113
hasPyTestEntryPointFile = true
113
114
} else if f == conftestFilename {
@@ -219,7 +220,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
219
220
collisionErrors := singlylinkedlist .New ()
220
221
221
222
appendPyLibrary := func (srcs * treeset.Set , pyLibraryTargetName string ) {
222
- deps , err := parser .parse (srcs )
223
+ deps , mainModules , err := parser .parse (srcs )
223
224
if err != nil {
224
225
log .Fatalf ("ERROR: %v\n " , err )
225
226
}
@@ -228,16 +229,33 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
228
229
// exists, and if it is of a different kind from the one we are
229
230
// generating. If so, we have to throw an error since Gazelle won't
230
231
// generate it correctly.
231
- if args .File != nil {
232
- for _ , t := range args .File .Rules {
233
- if t .Name () == pyLibraryTargetName && t .Kind () != actualPyLibraryKind {
234
- fqTarget := label .New ("" , args .Rel , pyLibraryTargetName )
235
- err := fmt .Errorf ("failed to generate target %q of kind %q: " +
236
- "a target of kind %q with the same name already exists. " +
237
- "Use the '# gazelle:%s' directive to change the naming convention." ,
238
- fqTarget .String (), actualPyLibraryKind , t .Kind (), pythonconfig .LibraryNamingConvention )
239
- collisionErrors .Add (err )
232
+ if err := ensureNoCollision (args .File , pyLibraryTargetName , actualPyLibraryKind ); err != nil {
233
+ fqTarget := label .New ("" , args .Rel , pyLibraryTargetName )
234
+ err := fmt .Errorf ("failed to generate target %q of kind %q: %w. " +
235
+ "Use the '# gazelle:%s' directive to change the naming convention." ,
236
+ fqTarget .String (), actualPyLibraryKind , err , pythonconfig .LibraryNamingConvention )
237
+ collisionErrors .Add (err )
238
+ }
239
+
240
+ if ! hasPyBinaryEntryPointFile {
241
+ sort .Strings (mainModules )
242
+ // Creating one py_binary target per main module when __main__.py doesn't exist.
243
+ for _ , filename := range mainModules {
244
+ pyBinaryTargetName := strings .TrimSuffix (filepath .Base (filename ), ".py" )
245
+ if err := ensureNoCollision (args .File , pyBinaryTargetName , actualPyBinaryKind ); err != nil {
246
+ fqTarget := label .New ("" , args .Rel , pyBinaryTargetName )
247
+ log .Printf ("failed to generate target %q of kind %q: %v" ,
248
+ fqTarget .String (), actualPyBinaryKind , err )
249
+ continue
240
250
}
251
+ srcs .Remove (filename )
252
+ pyBinary := newTargetBuilder (pyBinaryKind , pyBinaryTargetName , pythonProjectRoot , args .Rel , pyFileNames ).
253
+ addVisibility (visibility ).
254
+ addSrc (filename ).
255
+ addModuleDependencies (deps ).
256
+ generateImportsAttribute ().build ()
257
+ result .Gen = append (result .Gen , pyBinary )
258
+ result .Imports = append (result .Imports , pyBinary .PrivateAttr (config .GazelleImportsKey ))
241
259
}
242
260
}
243
261
@@ -270,8 +288,8 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
270
288
appendPyLibrary (pyLibraryFilenames , cfg .RenderLibraryName (packageName ))
271
289
}
272
290
273
- if hasPyBinary {
274
- deps , err := parser .parseSingle (pyBinaryEntrypointFilename )
291
+ if hasPyBinaryEntryPointFile {
292
+ deps , _ , err := parser .parseSingle (pyBinaryEntrypointFilename )
275
293
if err != nil {
276
294
log .Fatalf ("ERROR: %v\n " , err )
277
295
}
@@ -282,17 +300,12 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
282
300
// exists, and if it is of a different kind from the one we are
283
301
// generating. If so, we have to throw an error since Gazelle won't
284
302
// generate it correctly.
285
- if args .File != nil {
286
- for _ , t := range args .File .Rules {
287
- if t .Name () == pyBinaryTargetName && t .Kind () != actualPyBinaryKind {
288
- fqTarget := label .New ("" , args .Rel , pyBinaryTargetName )
289
- err := fmt .Errorf ("failed to generate target %q of kind %q: " +
290
- "a target of kind %q with the same name already exists. " +
291
- "Use the '# gazelle:%s' directive to change the naming convention." ,
292
- fqTarget .String (), actualPyBinaryKind , t .Kind (), pythonconfig .BinaryNamingConvention )
293
- collisionErrors .Add (err )
294
- }
295
- }
303
+ if err := ensureNoCollision (args .File , pyBinaryTargetName , actualPyBinaryKind ); err != nil {
304
+ fqTarget := label .New ("" , args .Rel , pyBinaryTargetName )
305
+ err := fmt .Errorf ("failed to generate target %q of kind %q: %w. " +
306
+ "Use the '# gazelle:%s' directive to change the naming convention." ,
307
+ fqTarget .String (), actualPyBinaryKind , err , pythonconfig .BinaryNamingConvention )
308
+ collisionErrors .Add (err )
296
309
}
297
310
298
311
pyBinaryTarget := newTargetBuilder (pyBinaryKind , pyBinaryTargetName , pythonProjectRoot , args .Rel , pyFileNames ).
@@ -310,7 +323,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
310
323
311
324
var conftest * rule.Rule
312
325
if hasConftestFile {
313
- deps , err := parser .parseSingle (conftestFilename )
326
+ deps , _ , err := parser .parseSingle (conftestFilename )
314
327
if err != nil {
315
328
log .Fatalf ("ERROR: %v\n " , err )
316
329
}
@@ -319,16 +332,11 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
319
332
// exists, and if it is of a different kind from the one we are
320
333
// generating. If so, we have to throw an error since Gazelle won't
321
334
// generate it correctly.
322
- if args .File != nil {
323
- for _ , t := range args .File .Rules {
324
- if t .Name () == conftestTargetname && t .Kind () != actualPyLibraryKind {
325
- fqTarget := label .New ("" , args .Rel , conftestTargetname )
326
- err := fmt .Errorf ("failed to generate target %q of kind %q: " +
327
- "a target of kind %q with the same name already exists." ,
328
- fqTarget .String (), actualPyLibraryKind , t .Kind ())
329
- collisionErrors .Add (err )
330
- }
331
- }
335
+ if err := ensureNoCollision (args .File , conftestTargetname , actualPyLibraryKind ); err != nil {
336
+ fqTarget := label .New ("" , args .Rel , conftestTargetname )
337
+ err := fmt .Errorf ("failed to generate target %q of kind %q: %w. " ,
338
+ fqTarget .String (), actualPyLibraryKind , err )
339
+ collisionErrors .Add (err )
332
340
}
333
341
334
342
conftestTarget := newTargetBuilder (pyLibraryKind , conftestTargetname , pythonProjectRoot , args .Rel , pyFileNames ).
@@ -346,25 +354,20 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
346
354
347
355
var pyTestTargets []* targetBuilder
348
356
newPyTestTargetBuilder := func (srcs * treeset.Set , pyTestTargetName string ) * targetBuilder {
349
- deps , err := parser .parse (srcs )
357
+ deps , _ , err := parser .parse (srcs )
350
358
if err != nil {
351
359
log .Fatalf ("ERROR: %v\n " , err )
352
360
}
353
361
// Check if a target with the same name we are generating already
354
362
// exists, and if it is of a different kind from the one we are
355
363
// generating. If so, we have to throw an error since Gazelle won't
356
364
// generate it correctly.
357
- if args .File != nil {
358
- for _ , t := range args .File .Rules {
359
- if t .Name () == pyTestTargetName && t .Kind () != actualPyTestKind {
360
- fqTarget := label .New ("" , args .Rel , pyTestTargetName )
361
- err := fmt .Errorf ("failed to generate target %q of kind %q: " +
362
- "a target of kind %q with the same name already exists. " +
363
- "Use the '# gazelle:%s' directive to change the naming convention." ,
364
- fqTarget .String (), actualPyTestKind , t .Kind (), pythonconfig .TestNamingConvention )
365
- collisionErrors .Add (err )
366
- }
367
- }
365
+ if err := ensureNoCollision (args .File , pyTestTargetName , actualPyTestKind ); err != nil {
366
+ fqTarget := label .New ("" , args .Rel , pyTestTargetName )
367
+ err := fmt .Errorf ("failed to generate target %q of kind %q: %w. " +
368
+ "Use the '# gazelle:%s' directive to change the naming convention." ,
369
+ fqTarget .String (), actualPyTestKind , err , pythonconfig .TestNamingConvention )
370
+ collisionErrors .Add (err )
368
371
}
369
372
return newTargetBuilder (pyTestKind , pyTestTargetName , pythonProjectRoot , args .Rel , pyFileNames ).
370
373
addSrcs (srcs ).
@@ -476,3 +479,15 @@ func isEntrypointFile(path string) bool {
476
479
return false
477
480
}
478
481
}
482
+
483
+ func ensureNoCollision (file * rule.File , targetName , kind string ) error {
484
+ if
10000
file == nil {
485
+ return nil
486
+ }
487
+ for _ , t := range file .Rules {
488
+ if t .Name () == targetName && t .Kind () != kind {
489
+ return fmt .Errorf ("a target of kind %q with the same name already exists" , t .Kind ())
490
+ }
491
+ }
492
+ return nil
493
+ }
0 commit comments