Repository: pantsbuild/pants

Issue:
jvm_exclude with Group Only Fails Parsing by Coursier
**Describe the bug**
Running `pants generate-lockfiles` when a `jvm_artifact` contains a `jvm_exclude` that only specifies a group will fail with a "Failed to parse [group-name]" message from Coursier.  This is contrary to the documentation for `jvm_exclude` which states "`jvm_exclude`: Exclude the given `artifact` and `group`, or all artifacts from the given `group`."

**Pants version**
2.20.0rc2

**OS**
MacOS

**Additional info**
Example Repo https://github.com/NGustafson/pants-examples/blob/main/3rdparty/jvm/BUILD
This repo has a single jvm_artifact with nothing else configured.  Attempting to run `pants generate-lockfiles` will cause this error:
```
pants generate-lockfiles
[ERROR] 1 Exception encountered:

Engine traceback:
  in `generate-lockfiles` goal

ProcessExecutionFailure: Process 'Running `coursier fetch` against 1 requirement: org.slf4j:slf4j-log4j12:2.0.12' failed with exit code 1.
stdout:

stderr:
+ coursier_exe=__coursier/./cs-aarch64-apple-darwin
+ shift
+ json_output_file=coursier_report.json
+ shift
++ pwd
+ working_dir=/private/var/folders/cm/gmrdwxcn7tv_cct4dzg38w91kjyl1q/T/pants-sandbox-aM4FVB
+ __coursier/./cs-aarch64-apple-darwin fetch -r=https://maven-central.storage-download.googleapis.com/maven2 -r=https://repo1.maven.org/maven2 --no-default --json-output-file=coursier_report.json org.slf4j:slf4j-log4j12:2.0.12 --local-exclude-file PANTS_RESOLVE_EXCLUDES
Failed to parse org.slf4j
Failed to parse org.slf4j
```



Discussion:
Thanks for filing an issue with a reproducer and sorry for the trouble!

I did an initial triage to check priority/regression: it looks like this behaviour has existed since 2.18.0.dev0 (`PANTS_VERSION=2.18.0.dev0 pants generate-lockfiles`), which seems to be the first release that had this `jvm_exclude` https://github.com/pantsbuild/pants/pull/19128, so not a regression.

@NGustafson is this something you might have a chance at trying to fix? @alonsodomin is something you can provide hints or guidance about?
@huonw Thanks for checking!  Yes I'd like to try fixing this.
It seems like Coursier supports group-only excludes if you set the artifact to `*`.  Something like `--exclude 'org.slf4j:*'`.  But this Pants syntax causes a different error `jvm_exclude(group="org.slf4j", artifact="*")`, so I'm going to try getting that to work first.
sorry for the trouble, it's true that initial implementation was wrong. This should be easy to fix, the point in which the exclusion is transformed into a coordinate string is in here:
https://github.com/pantsbuild/pants/blob/b7b0e9ca349bc15c619c62c4ef9ba7787bf2b9d9/src/python/pants/jvm/target_types.py#L308

Resolving patch:
diff --git a/src/python/pants/jvm/target_types.py b/src/python/pants/jvm/target_types.py
--- a/src/python/pants/jvm/target_types.py
+++ b/src/python/pants/jvm/target_types.py
@@ -309,6 +309,8 @@ def to_coord_str(self) -> str:
         result = self.group
         if self.artifact:
             result += f":{self.artifact}"
+        else:
+            result += ":*"
         return result
 
 


Test patch:
diff --git a/src/python/pants/jvm/resolve/coursier_fetch_integration_test.py b/src/python/pants/jvm/resolve/coursier_fetch_integration_test.py
--- a/src/python/pants/jvm/resolve/coursier_fetch_integration_test.py
+++ b/src/python/pants/jvm/resolve/coursier_fetch_integration_test.py
@@ -18,7 +18,11 @@
 from pants.jvm.resolve.coordinate import Coordinate, Coordinates
 from pants.jvm.resolve.coursier_fetch import CoursierLockfileEntry, CoursierResolvedLockfile
 from pants.jvm.resolve.coursier_fetch import rules as coursier_fetch_rules
-from pants.jvm.target_types import JvmArtifactJarSourceField, JvmArtifactTarget
+from pants.jvm.target_types import (
+    JvmArtifactExclusion,
+    JvmArtifactJarSourceField,
+    JvmArtifactTarget,
+)
 from pants.jvm.testutil import maybe_skip_jdk_test
 from pants.jvm.util_rules import ExtractFileDigest
 from pants.jvm.util_rules import rules as util_rules
@@ -662,6 +666,25 @@ def test_transitive_excludes(rule_runner: RuleRunner) -> None:
     assert not any(i for i in entries if i.coord.artifact == "jackson-core")
 
 
+@maybe_skip_jdk_test
+def test_transitive_group_only_excludes(rule_runner: RuleRunner) -> None:
+    group_only_excludes = JvmArtifactExclusion(group="com.fasterxml.jackson.core", artifact=None)
+
+    requirement = ArtifactRequirement(
+        coordinate=Coordinate(
+            group="com.fasterxml.jackson.module",
+            artifact="jackson-module-jaxb-annotations",
+            version="2.17.1",
+        ),
+        excludes=frozenset([group_only_excludes.to_coord_str()]),
+    )
+
+    resolve = rule_runner.request(CoursierResolvedLockfile, [ArtifactRequirements([requirement])])
+
+    entries = resolve.entries
+    assert not any(i for i in entries if i.coord.group == "com.fasterxml.jackson.core")
+
+
 @maybe_skip_jdk_test
 def test_missing_entry_for_transitive_dependency(rule_runner: RuleRunner) -> None:
     requirement = ArtifactRequirement(