Repository: pantsbuild/pants

Issue:
Unmatched glob when files matches filename with square brackets
**Describe the bug**

When a `files` target matches a source with square brackets in the filename, such as `[test].txt`, this error is thrown:

```
22:15:10.35 [WARN] Unmatched glob from test/[test].txt:files's `source` field: "test/[test].txt"

Do the file(s) exist? If so, check if the file(s) are in your `.gitignore` or the global `pants_ignore` option, which may result in Pants not being able to see the file(s) even though they exist on disk. Refer to https://www.pantsbuild.org/v2.11/docs/troubleshooting#pants-cannot-find-a-file-in-your-project.
22:15:10.35 [ERROR] 1 Exception encountered:

  InvalidFieldException: The 'source' field in target test/[test].txt:files must have 1 file, but it had 0 files.
```

**Pants version**
2.11

**OS**
Linux

**Additional info**
Full reproduction of bug: https://github.com/caleblloyd/pants-square-bracket-repro



Discussion:

Resolving patch:
diff --git a/src/python/pants/engine/target.py b/src/python/pants/engine/target.py
--- a/src/python/pants/engine/target.py
+++ b/src/python/pants/engine/target.py
@@ -6,6 +6,7 @@
 import collections.abc
 import dataclasses
 import enum
+import glob as glob_stdlib
 import itertools
 import logging
 import os.path
@@ -1130,6 +1131,10 @@ def _generate_file_level_targets(
      as the key.
     """
 
+    # Paths will have already been globbed, so they should be escaped. See
+    # https://github.com/pantsbuild/pants/issues/15381.
+    paths = [glob_stdlib.escape(path) for path in paths]
+
     def generate_address(base_address: Address, relativized_fp: str) -> Address:
         return (
             base_address.create_generated(relativized_fp)


Test patch:
diff --git a/src/python/pants/core/target_types_test.py b/src/python/pants/core/target_types_test.py
--- a/src/python/pants/core/target_types_test.py
+++ b/src/python/pants/core/target_types_test.py
@@ -332,6 +332,7 @@ def test_generate_file_and_resource_targets() -> None:
             ),
             "assets/f1.ext": "",
             "assets/f2.ext": "",
+            "assets/f[3].ext": "",
             "assets/subdir/f.ext": "",
         }
     )
@@ -360,10 +361,12 @@ def gen_resource_tgt(rel_fp: str, tags: list[str] | None = None) -> ResourceTarg
     assert set(generated_files.values()) == {
         gen_file_tgt("f1.ext", tags=["overridden"]),
         gen_file_tgt("f2.ext"),
+        gen_file_tgt("f[[]3].ext"),
         gen_file_tgt("subdir/f.ext"),
     }
     assert set(generated_resources.values()) == {
         gen_resource_tgt("f1.ext", tags=["overridden"]),
         gen_resource_tgt("f2.ext"),
+        gen_resource_tgt("f[[]3].ext"),
         gen_resource_tgt("subdir/f.ext"),
     }
diff --git a/src/python/pants/engine/internals/build_files_test.py b/src/python/pants/engine/internals/build_files_test.py
--- a/src/python/pants/engine/internals/build_files_test.py
+++ b/src/python/pants/engine/internals/build_files_test.py
@@ -328,6 +328,7 @@ def test_address_specs_literals_vs_globs(address_specs_rule_runner: RuleRunner)
             ),
             "demo/f1.txt": "",
             "demo/f2.txt": "",
+            "demo/f[3].txt": "",
             "demo/subdir/f.txt": "",
             "demo/subdir/f.another_ext": "",
             "demo/subdir/BUILD": "mock_tgt(name='another_ext', sources=['f.another_ext'])",
@@ -363,6 +364,8 @@ def assert_resolved(spec: AddressSpec, expected: set[Address]) -> None:
             Address("demo", generated_name="f1.txt"),
             Address("demo", relative_file_path="f2.txt"),
             Address("demo", generated_name="f2.txt"),
+            Address("demo", relative_file_path="f[[]3].txt"),
+            Address("demo", generated_name="f[[]3].txt"),
         },
     )
     assert_resolved(
@@ -382,6 +385,8 @@ def assert_resolved(spec: AddressSpec, expected: set[Address]) -> None:
         Address("demo", generated_name="f1.txt"),
         Address("demo", relative_file_path="f2.txt"),
         Address("demo", generated_name="f2.txt"),
+        Address("demo", relative_file_path="f[[]3].txt"),
+        Address("demo", generated_name="f[[]3].txt"),
         Address("demo", relative_file_path="subdir/f.txt"),
         Address("demo", generated_name="subdir/f.txt"),
         Address("demo/subdir", target_name="another_ext"),
@@ -396,6 +401,8 @@ def assert_resolved(spec: AddressSpec, expected: set[Address]) -> None:
             Address("demo", generated_name="f1.txt"),
             Address("demo", relative_file_path="f2.txt"),
             Address("demo", generated_name="f2.txt"),
+            Address("demo", relative_file_path="f[[]3].txt"),
+            Address("demo", generated_name="f[[]3].txt"),
         },
     )