From 990a0538e796f86dcb9a86e0276b7fdf1bf44c18 Mon Sep 17 00:00:00 2001 From: Zhongpeng Lin Date: Fri, 19 Jul 2024 00:49:29 -0700 Subject: [PATCH] feat!: Following generation mode when generating test targets (#2044) When `python_generation_mode` is `project` or `file` , the generated `py_test` targets are consistent with `py_library`. However, when `python_generation_mode` is `package`, it becomes inconsistent: it requires either `__test__` target or `__test__.py` file to generate `py_test` in package mode, otherwise it will fall back to file mode. This PR relaxes this requirement with a new directive `gazelle:python_generation_mode_per_package_require_test_entry_point`. Whent it's set to false, Gazelle and generates one `py_test` target per package in package mode even without entry points. This allows people to use `gazelle:map_kind` to map `py_test` to a macro that sets a default test runner, such as [rules_python_pytest](https://github.com/caseyduquettesc/rules_python_pytest) or [pytest-bazel](https://pypi.org/project/pytest-bazel/), and generate one test target per package. The behavior when `gazelle:python_generation_mode` is "file" or "project" remains the same. This fixes #1972 for supporting pytest from Gazelle. With this approach, people can define a thin macro like this to use py_pytest_main: ``` load("@aspect_rules_py//py:defs.bzl", "py_pytest_main") def py_test(name, **kwargs): py_pytest_main( name = "__test__", deps = ["@pip_pytest//:pkg"], # change this to the pytest target in your repo. ) deps = kwargs.pop("deps", []) deps.append(":__test__") py_test( name = name, main = ":__test__.py", deps = deps, **kwargs, ) ``` BREAKING CHANGES: Without `gazelle:map_kind` or `__test__` target or `__test__.py`, the package mode will now generate `py_test` without `main` attribute, which may not be runnable. However, this is already an issue with "python_generation_mode:project" before this PR. The default value of `gazelle:python_generation_mode_per_package_require_test_entry_point` is true to preserve the current behavior. We will flip that default value in the future. --------- Co-authored-by: aignas <240938+aignas@users.noreply.github.com> --- CHANGELOG.md | 11 +- gazelle/README.md | 99 +++++++++++------ gazelle/python/configure.go | 9 ++ gazelle/python/generate.go | 7 +- .../BUILD.in | 2 + .../BUILD.out | 18 ++++ .../README.md | 3 + .../WORKSPACE | 1 + .../__init__.py | 0 .../bar_test.py | 24 +++++ .../foo_test.py | 24 +++++ .../test.yaml | 17 +++ gazelle/pythonconfig/pythonconfig.go | 101 ++++++++++-------- 13 files changed, 235 insertions(+), 81 deletions(-) create mode 100644 gazelle/python/testdata/per_package_test_target_without_entry_point/BUILD.in create mode 100644 gazelle/python/testdata/per_package_test_target_without_entry_point/BUILD.out create mode 100644 gazelle/python/testdata/per_package_test_target_without_entry_point/README.md create mode 100644 gazelle/python/testdata/per_package_test_target_without_entry_point/WORKSPACE create mode 100644 gazelle/python/testdata/per_package_test_target_without_entry_point/__init__.py create mode 100644 gazelle/python/testdata/per_package_test_target_without_entry_point/bar_test.py create mode 100644 gazelle/python/testdata/per_package_test_target_without_entry_point/foo_test.py create mode 100644 gazelle/python/testdata/per_package_test_target_without_entry_point/test.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index d924f650ca..560c04ea8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,16 @@ A brief description of the categories of changes: containing ">" sign ### Added -* Nothing yet +* (gazelle) Added `python_generation_mode_per_package_require_test_entry_point` + in order to better accommodate users who use a custom macro, + [`pytest-bazel`][pytest_bazel], [rules_python_pytest] or `rules_py` + [py_test_main] in order to integrate with `pytest`. Currently the default + flag value is set to `true` for backwards compatible behaviour, but in the + future the flag will be flipped be `false` by default. + +[rules_python_pytest]: https://github.com/caseyduquettesc/rules_python_pytest +[py_test_main]: https://docs.aspect.build/rulesets/aspect_rules_py/docs/rules/#py_pytest_main +[pytest_bazel]: https://pypi.org/project/pytest-bazel ### Removed * Nothing yet diff --git a/gazelle/README.md b/gazelle/README.md index d68b94de26..c0494d141b 100644 --- a/gazelle/README.md +++ b/gazelle/README.md @@ -172,42 +172,44 @@ Examples of these directives in use can be found in the Python-specific directives are as follows: -| **Directive** | **Default value** | -|--------------------------------------|-------------------| -| `# gazelle:python_extension` | `enabled` | -| Controls whether the Python extension is enabled or not. Sub-packages inherit this value. Can be either "enabled" or "disabled". | | -| [`# gazelle:python_root`](#directive-python_root) | n/a | -| Sets a Bazel package as a Python root. This is used on monorepos with multiple Python projects that don't share the top-level of the workspace as the root. See [Directive: `python_root`](#directive-python_root) below. | | -| `# gazelle:python_manifest_file_name`| `gazelle_python.yaml` | -| Overrides the default manifest file name. | | -| `# gazelle:python_ignore_files` | n/a | -| Controls the files which are ignored from the generated targets. | | -| `# gazelle:python_ignore_dependencies`| n/a | -| Controls the ignored dependencies from the generated targets. | | -| `# gazelle:python_validate_import_statements`| `true` | -| Controls whether the Python import statements should be validated. Can be "true" or "false" | | -| `# gazelle:python_generation_mode`| `package` | -| Controls the target generation mode. Can be "file", "package", or "project" | | -| `# gazelle:python_generation_mode_per_file_include_init`| `false` | -| Controls whether `__init__.py` files are included as srcs in each generated target when target generation mode is "file". Can be "true", or "false" | | -| `# gazelle:python_library_naming_convention`| `$package_name$` | -| Controls the `py_library` naming convention. It interpolates `$package_name$` with the Bazel package name. E.g. if the Bazel package name is `foo`, setting this to `$package_name$_my_lib` would result in a generated target named `foo_my_lib`. | | -| `# gazelle:python_binary_naming_convention` | `$package_name$_bin` | -| Controls the `py_binary` naming convention. Follows the same interpolation rules as `python_library_naming_convention`. | | -| `# gazelle:python_test_naming_convention` | `$package_name$_test` | -| Controls the `py_test` naming convention. Follows the same interpolation rules as `python_library_naming_convention`. | | -| `# gazelle:resolve py ...` | n/a | +| **Directive** | **Default value** | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| +| `# gazelle:python_extension` | `enabled` | +| Controls whether the Python extension is enabled or not. Sub-packages inherit this value. Can be either "enabled" or "disabled". | | +| [`# gazelle:python_root`](#directive-python_root) | n/a | +| Sets a Bazel package as a Python root. This is used on monorepos with multiple Python projects that don't share the top-level of the workspace as the root. See [Directive: `python_root`](#directive-python_root) below. | | +| `# gazelle:python_manifest_file_name` | `gazelle_python.yaml` | +| Overrides the default manifest file name. | | +| `# gazelle:python_ignore_files` | n/a | +| Controls the files which are ignored from the generated targets. | | +| `# gazelle:python_ignore_dependencies` | n/a | +| Controls the ignored dependencies from the generated targets. | | +| `# gazelle:python_validate_import_statements` | `true` | +| Controls whether the Python import statements should be validated. Can be "true" or "false" | | +| `# gazelle:python_generation_mode` | `package` | +| Controls the target generation mode. Can be "file", "package", or "project" | | +| `# gazelle:python_generation_mode_per_file_include_init` | `false` | +| Controls whether `__init__.py` files are included as srcs in each generated target when target generation mode is "file". Can be "true", or "false" | | +| [`# gazelle:python_generation_mode_per_package_require_test_entry_point`](#directive-python_generation_mode_per_package_require_test_entry_point) | `true` | +| Controls whether a file called `__test__.py` or a target called `__test__` is required to generate one test target per package in package mode. || +| `# gazelle:python_library_naming_convention` | `$package_name$` | +| Controls the `py_library` naming convention. It interpolates `$package_name$` with the Bazel package name. E.g. if the Bazel package name is `foo`, setting this to `$package_name$_my_lib` would result in a generated target named `foo_my_lib`. | | +| `# gazelle:python_binary_naming_convention` | `$package_name$_bin` | +| Controls the `py_binary` naming convention. Follows the same interpolation rules as `python_library_naming_convention`. | | +| `# gazelle:python_test_naming_convention` | `$package_name$_test` | +| Controls the `py_test` naming convention. Follows the same interpolation rules as `python_library_naming_convention`. | | +| `# gazelle:resolve py ...` | n/a | | Instructs the plugin what target to add as a dependency to satisfy a given import statement. The syntax is `# gazelle:resolve py import-string label` where `import-string` is the symbol in the python `import` statement, and `label` is the Bazel label that Gazelle should write in `deps`. | | -| [`# gazelle:python_default_visibility labels`](#directive-python_default_visibility) | | -| Instructs gazelle to use these visibility labels on all python targets. `labels` is a comma-separated list of labels (without spaces). | `//$python_root$:__subpackages__` | -| [`# gazelle:python_visibility label`](#directive-python_visibility) | | -| Appends additional visibility labels to each generated target. This directive can be set multiple times. | | -| [`# gazelle:python_test_file_pattern`](#directive-python_test_file_pattern) | `*_test.py,test_*.py` | -| Filenames matching these comma-separated `glob`s will be mapped to `py_test` targets. | -| `# gazelle:python_label_convention` | `$distribution_name$` | -| Defines the format of the distribution name in labels to third-party deps. Useful for using Gazelle plugin with other rules with different repository conventions (e.g. `rules_pycross`). Full label is always prepended with (pip) repository name, e.g. `@pip//numpy`. | -| `# gazelle:python_label_normalization` | `snake_case` | -| Controls how distribution names in labels to third-party deps are normalized. Useful for using Gazelle plugin with other rules with different label conventions (e.g. `rules_pycross` uses PEP-503). Can be "snake_case", "none", or "pep503". | +| [`# gazelle:python_default_visibility labels`](#directive-python_default_visibility) | | +| Instructs gazelle to use these visibility labels on all python targets. `labels` is a comma-separated list of labels (without spaces). | `//$python_root$:__subpackages__` | +| [`# gazelle:python_visibility label`](#directive-python_visibility) | | +| Appends additional visibility labels to each generated target. This directive can be set multiple times. | | +| [`# gazelle:python_test_file_pattern`](#directive-python_test_file_pattern) | `*_test.py,test_*.py` | +| Filenames matching these comma-separated `glob`s will be mapped to `py_test` targets. | +| `# gazelle:python_label_convention` | `$distribution_name$` | +| Defines the format of the distribution name in labels to third-party deps. Useful for using Gazelle plugin with other rules with different repository conventions (e.g. `rules_pycross`). Full label is always prepended with (pip) repository name, e.g. `@pip//numpy`. | +| `# gazelle:python_label_normalization` | `snake_case` | +| Controls how distribution names in labels to third-party deps are normalized. Useful for using Gazelle plugin with other rules with different label conventions (e.g. `rules_pycross` uses PEP-503). Can be "snake_case", "none", or "pep503". | #### Directive: `python_root`: @@ -440,6 +442,33 @@ py_library( [issue-1826]: https://github.com/bazelbuild/rules_python/issues/1826 +#### Directive: `python_generation_mode_per_package_require_test_entry_point`: +When `# gazelle:python_generation_mode package`, whether a file called `__test__.py` or a target called `__test__`, a.k.a., entry point, is required to generate one test target per package. If this is set to true but no entry point is found, Gazelle will fall back to file mode and generate one test target per file. Setting this directive to false forces Gazelle to generate one test target per package even without entry point. However, this means the `main` attribute of the `py_test` will not be set and the target will not be runnable unless either: +1. there happen to be a file in the `srcs` with the same name as the `py_test` target, or +2. a macro populating the `main` attribute of `py_test` is configured with `gazelle:map_kind` to replace `py_test` when Gazelle is generating Python test targets. For example, user can provide such a macro to Gazelle: + +```starlark +load("@rules_python//python:defs.bzl", _py_test="py_test") +load("@aspect_rules_py//py:defs.bzl", "py_pytest_main") + +def py_test(name, main=None, **kwargs): + deps = kwargs.pop("deps", []) + if not main: + py_pytest_main( + name = "__test__", + deps = ["@pip_pytest//:pkg"], # change this to the pytest target in your repo. + ) + + deps.append(":__test__") + main = ":__test__.py" + + _py_test( + name = name, + main = main, + deps = deps, + **kwargs, +) +``` ### Annotations diff --git a/gazelle/python/configure.go b/gazelle/python/configure.go index b82dd81f8f..a369a64b8e 100644 --- a/gazelle/python/configure.go +++ b/gazelle/python/configure.go @@ -61,6 +61,7 @@ func (py *Configurer) KnownDirectives() []string { pythonconfig.ValidateImportStatementsDirective, pythonconfig.GenerationMode, pythonconfig.GenerationModePerFileIncludeInit, + pythonconfig.GenerationModePerPackageRequireTestEntryPoint, pythonconfig.LibraryNamingConvention, pythonconfig.BinaryNamingConvention, pythonconfig.TestNamingConvention, @@ -163,6 +164,14 @@ func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) { log.Fatal(err) } config.SetPerFileGenerationIncludeInit(v) + case pythonconfig.GenerationModePerPackageRequireTestEntryPoint: + v, err := strconv.ParseBool(strings.TrimSpace(d.Value)) + if err != nil { + log.Printf("invalid value for gazelle:%s in %q: %q", + pythonconfig.GenerationModePerPackageRequireTestEntryPoint, rel, d.Value) + } else { + config.SetPerPackageGenerationRequireTestEntryPoint(v) + } case pythonconfig.LibraryNamingConvention: config.SetLibraryNamingConvention(strings.TrimSpace(d.Value)) case pythonconfig.BinaryNamingConvention: diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go index ef49dd74c4..c563b47bf3 100644 --- a/gazelle/python/generate.go +++ b/gazelle/python/generate.go @@ -421,7 +421,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes addResolvedDependencies(annotations.includeDeps). generateImportsAttribute() } - if (hasPyTestEntryPointFile || hasPyTestEntryPointTarget || cfg.CoarseGrainedGeneration()) && !cfg.PerFileGeneration() { + if (!cfg.PerPackageGenerationRequireTestEntryPoint() || hasPyTestEntryPointFile || hasPyTestEntryPointTarget || cfg.CoarseGrainedGeneration()) && !cfg.PerFileGeneration() { // Create one py_test target per package if hasPyTestEntryPointFile { // Only add the pyTestEntrypointFilename to the pyTestFilenames if @@ -441,7 +441,10 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes setMain(main) } else if hasPyTestEntryPointFile { pyTestTarget.setMain(pyTestEntrypointFilename) - } + } /* else: + main is not set, assuming there is a test file with the same name + as the target name, or there is a macro wrapping py_test and setting its main attribute. + */ pyTestTargets = append(pyTestTargets, pyTestTarget) } } else { diff --git a/gazelle/python/testdata/per_package_test_target_without_entry_point/BUILD.in b/gazelle/python/testdata/per_package_test_target_without_entry_point/BUILD.in new file mode 100644 index 0000000000..27120f3255 --- /dev/null +++ b/gazelle/python/testdata/per_package_test_target_without_entry_point/BUILD.in @@ -0,0 +1,2 @@ +# gazelle:python_generation_mode package +# gazelle:python_generation_mode_per_package_require_test_entry_point false \ No newline at end of file diff --git a/gazelle/python/testdata/per_package_test_target_without_entry_point/BUILD.out b/gazelle/python/testdata/per_package_test_target_without_entry_point/BUILD.out new file mode 100644 index 0000000000..c4ec331583 --- /dev/null +++ b/gazelle/python/testdata/per_package_test_target_without_entry_point/BUILD.out @@ -0,0 +1,18 @@ +load("@rules_python//python:defs.bzl", "py_library", "py_test") + +# gazelle:python_generation_mode package +# gazelle:python_generation_mode_per_package_require_test_entry_point false + +py_library( + name = "per_package_test_target_without_entry_point", + srcs = ["__init__.py"], + visibility = ["//:__subpackages__"], +) + +py_test( + name = "per_package_test_target_without_entry_point_test", + srcs = [ + "bar_test.py", + "foo_test.py", + ], +) diff --git a/gazelle/python/testdata/per_package_test_target_without_entry_point/README.md b/gazelle/python/testdata/per_package_test_target_without_entry_point/README.md new file mode 100644 index 0000000000..8decb00cfa --- /dev/null +++ b/gazelle/python/testdata/per_package_test_target_without_entry_point/README.md @@ -0,0 +1,3 @@ +# One test target per package without entry point + +This test case asserts that one test target is generated per package without entry point when `gazelle:python_generation_mode_per_package_require_test_entry_point false` diff --git a/gazelle/python/testdata/per_package_test_target_without_entry_point/WORKSPACE b/gazelle/python/testdata/per_package_test_target_without_entry_point/WORKSPACE new file mode 100644 index 0000000000..faff6af87a --- /dev/null +++ b/gazelle/python/testdata/per_package_test_target_without_entry_point/WORKSPACE @@ -0,0 +1 @@ +# This is a Bazel workspace for the Gazelle test data. diff --git a/gazelle/python/testdata/per_package_test_target_without_entry_point/__init__.py b/gazelle/python/testdata/per_package_test_target_without_entry_point/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gazelle/python/testdata/per_package_test_target_without_entry_point/bar_test.py b/gazelle/python/testdata/per_package_test_target_without_entry_point/bar_test.py new file mode 100644 index 0000000000..9948f1ccd4 --- /dev/null +++ b/gazelle/python/testdata/per_package_test_target_without_entry_point/bar_test.py @@ -0,0 +1,24 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + + +class BarTest(unittest.TestCase): + def test_foo(self): + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/gazelle/python/testdata/per_package_test_target_without_entry_point/foo_test.py b/gazelle/python/testdata/per_package_test_target_without_entry_point/foo_test.py new file mode 100644 index 0000000000..a128adf67f --- /dev/null +++ b/gazelle/python/testdata/per_package_test_target_without_entry_point/foo_test.py @@ -0,0 +1,24 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + + +class FooTest(unittest.TestCase): + def test_foo(self): + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/gazelle/python/testdata/per_package_test_target_without_entry_point/test.yaml b/gazelle/python/testdata/per_package_test_target_without_entry_point/test.yaml new file mode 100644 index 0000000000..2410223e59 --- /dev/null +++ b/gazelle/python/testdata/per_package_test_target_without_entry_point/test.yaml @@ -0,0 +1,17 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +expect: + exit_code: 0 diff --git a/gazelle/pythonconfig/pythonconfig.go b/gazelle/pythonconfig/pythonconfig.go index 41a470a940..a24a90efeb 100644 --- a/gazelle/pythonconfig/pythonconfig.go +++ b/gazelle/pythonconfig/pythonconfig.go @@ -55,6 +55,10 @@ const ( // the "per_file" GenerationMode by including the package's __init__.py file. // This is a boolean directive. GenerationModePerFileIncludeInit = "python_generation_mode_per_file_include_init" + // GenerationModePerPackageRequireTestEntryPoint represents the directive that + // requires a test entry point to generate test targets in "package" GenerationMode. + // This is a boolean directive. + GenerationModePerPackageRequireTestEntryPoint = "python_generation_mode_per_package_require_test_entry_point" // LibraryNamingConvention represents the directive that controls the // py_library naming convention. It interpolates $package_name$ with the // Bazel package name. E.g. if the Bazel package name is `foo`, setting this @@ -148,21 +152,22 @@ type Config struct { pythonProjectRoot string gazelleManifest *manifest.Manifest - excludedPatterns *singlylinkedlist.List - ignoreFiles map[string]struct{} - ignoreDependencies map[string]struct{} - validateImportStatements bool - coarseGrainedGeneration bool - perFileGeneration bool - perFileGenerationIncludeInit bool - libraryNamingConvention string - binaryNamingConvention string - testNamingConvention string - defaultVisibility []string - visibility []string - testFilePattern []string - labelConvention string - labelNormalization LabelNormalizationType + excludedPatterns *singlylinkedlist.List + ignoreFiles map[string]struct{} + ignoreDependencies map[string]struct{} + validateImportStatements bool + coarseGrainedGeneration bool + perFileGeneration bool + perFileGenerationIncludeInit bool + perPackageGenerationRequireTestEntryPoint bool + libraryNamingConvention string + binaryNamingConvention string + testNamingConvention string + defaultVisibility []string + visibility []string + testFilePattern []string + labelConvention string + labelNormalization LabelNormalizationType } type LabelNormalizationType int @@ -179,24 +184,25 @@ func New( pythonProjectRoot string, ) *Config { return &Config{ - extensionEnabled: true, - repoRoot: repoRoot, - pythonProjectRoot: pythonProjectRoot, - excludedPatterns: singlylinkedlist.New(), - ignoreFiles: make(map[string]struct{}), - ignoreDependencies: make(map[string]struct{}), - validateImportStatements: true, - coarseGrainedGeneration: false, - perFileGeneration: false, - perFileGenerationIncludeInit: false, - libraryNamingConvention: packageNameNamingConventionSubstitution, - binaryNamingConvention: fmt.Sprintf("%s_bin", packageNameNamingConventionSubstitution), - testNamingConvention: fmt.Sprintf("%s_test", packageNameNamingConventionSubstitution), - defaultVisibility: []string{fmt.Sprintf(DefaultVisibilityFmtString, "")}, - visibility: []string{}, - testFilePattern: strings.Split(DefaultTestFilePatternString, ","), - labelConvention: DefaultLabelConvention, - labelNormalization: DefaultLabelNormalizationType, + extensionEnabled: true, + repoRoot: repoRoot, + pythonProjectRoot: pythonProjectRoot, + excludedPatterns: singlylinkedlist.New(), + ignoreFiles: make(map[string]struct{}), + ignoreDependencies: make(map[string]struct{}), + validateImportStatements: true, + coarseGrainedGeneration: false, + perFileGeneration: false, + perFileGenerationIncludeInit: false, + perPackageGenerationRequireTestEntryPoint: true, + libraryNamingConvention: packageNameNamingConventionSubstitution, + binaryNamingConvention: fmt.Sprintf("%s_bin", packageNameNamingConventionSubstitution), + testNamingConvention: fmt.Sprintf("%s_test", packageNameNamingConventionSubstitution), + defaultVisibility: []string{fmt.Sprintf(DefaultVisibilityFmtString, "")}, + visibility: []string{}, + testFilePattern: strings.Split(DefaultTestFilePatternString, ","), + labelConvention: DefaultLabelConvention, + labelNormalization: DefaultLabelNormalizationType, } } @@ -220,14 +226,15 @@ func (c *Config) NewChild() *Config { coarseGrainedGeneration: c.coarseGrainedGeneration, perFileGeneration: c.perFileGeneration, perFileGenerationIncludeInit: c.perFileGenerationIncludeInit, - libraryNamingConvention: c.libraryNamingConvention, - binaryNamingConvention: c.binaryNamingConvention, - testNamingConvention: c.testNamingConvention, - defaultVisibility: c.defaultVisibility, - visibility: c.visibility, - testFilePattern: c.testFilePattern, - labelConvention: c.labelConvention, - labelNormalization: c.labelNormalization, + perPackageGenerationRequireTestEntryPoint: c.perPackageGenerationRequireTestEntryPoint, + libraryNamingConvention: c.libraryNamingConvention, + binaryNamingConvention: c.binaryNamingConvention, + testNamingConvention: c.testNamingConvention, + defaultVisibility: c.defaultVisibility, + visibility: c.visibility, + testFilePattern: c.testFilePattern, + labelConvention: c.labelConvention, + labelNormalization: c.labelNormalization, } } @@ -398,6 +405,14 @@ func (c *Config) PerFileGenerationIncludeInit() bool { return c.perFileGenerationIncludeInit } +func (c *Config) SetPerPackageGenerationRequireTestEntryPoint(perPackageGenerationRequireTestEntryPoint bool) { + c.perPackageGenerationRequireTestEntryPoint = perPackageGenerationRequireTestEntryPoint +} + +func (c *Config) PerPackageGenerationRequireTestEntryPoint() bool { + return c.perPackageGenerationRequireTestEntryPoint +} + // SetLibraryNamingConvention sets the py_library target naming convention. func (c *Config) SetLibraryNamingConvention(libraryNamingConvention string) { c.libraryNamingConvention = libraryNamingConvention @@ -494,9 +509,9 @@ func (c *Config) FormatThirdPartyDependency(repositoryName string, distributionN normConventionalDistributionName = strings.Trim(normConventionalDistributionName, "_") case Pep503LabelNormalizationType: // See https://packaging.python.org/en/latest/specifications/name-normalization/#name-format - normConventionalDistributionName = strings.ToLower(conventionalDistributionName) // ... "should be lowercased" + normConventionalDistributionName = strings.ToLower(conventionalDistributionName) // ... "should be lowercased" normConventionalDistributionName = regexp.MustCompile(`[-_.]+`).ReplaceAllString(normConventionalDistributionName, "-") // ... "all runs of the characters ., -, or _ replaced with a single -" - normConventionalDistributionName = strings.Trim(normConventionalDistributionName, "-") // ... "must start and end with a letter or number" + normConventionalDistributionName = strings.Trim(normConventionalDistributionName, "-") // ... "must start and end with a letter or number" default: fallthrough case NoLabelNormalizationType: