Sitelet https://github.com/apache/maven/pull/13152
Skip to content

Lower-case the OS family before matching it against the family names - #13152

Merged
gnodet merged 2 commits into
apache:masterfrom
Dev-next-gen:os-family-lowercase
Sep 16, 2026
Merged

gnodet merged 2 commits into
apache:masterfrom
Dev-next-gen:os-family-lowercase

Conversation

@Dev-next-gen

Copy link
Copy Markdown
Contributor

I was comparing the Maven 3 profile activators in compat/maven-model-builder with their Maven 4 counterparts in impl/maven-impl and found one line that did not come across: OperatingSystemProfileActivator.determineFamilyMatch lower-cases the configured family in the Maven 3 copy, but not in the Maven 4 one.

The Maven 3 side got that in 0456c7c ("Caplital OS name can not activate profile"), which lower-cased name, arch and family together. The Maven 4 activator introduced in d075fe7 was written from the state before that commit; name and arch were later brought in line, family was not.

It matters because Os.isFamily switches on the family string against lower-case constants — "windows", "winnt", "win9x", "unix", "dos", "mac", "tandem", and so on — and only the default branch does actualOsName.contains(family.toLowerCase(...)). So a capitalised family never reaches its own case and silently degrades into a substring test on os.name. Some families survive that by luck, because their name is a substring of the OS name: <family>Mac</family> still matches mac os x, which is exactly the case the existing testCapitalOsName covers, so the test passes while the code is wrong. The families that actually need the switch do not survive it:

  • <family>WinNT</family> does not activate on Windows — "windows 11" does not contain "winnt".
  • <family>Mac</family> does not activate on a JDK that reports os.name=darwin, which is the case the DARWIN constant in Os exists for.
  • <family>Unix</family>, <family>DOS</family> and <family>Tandem</family> are wrong the same way, and a negated form such as <family>!Unix</family> flips the wrong way, i.e. it activates a profile that was written to be excluded.

Same POM, activates under Maven 3, does not under Maven 4.

The change is the single toLowerCase(Locale.ENGLISH) call, so the family follows the same rule as name, arch and version in the same class. Locale is already imported.

The new testCapitalFamily fails before the change and passes after:

[ERROR] OperatingSystemProfileActivatorTest.testCapitalFamily:153
        ->AbstractProfileActivatorTest.assertActivation:77 expected: <true> but was: <false>
[ERROR] Tests run: 11, Failures: 1, Errors: 0, Skipped: 0

and with the one-line change applied:

[INFO] Tests run: 11, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

I avoided <family>Unix</family> in the test on purpose: the unix branch of Os.isFamily reads File.pathSeparator of the running JVM, so it would assert different things on the Linux and Windows CI runners. winnt and the darwin case depend only on the os.name the test feeds in.

The whole impl/maven-impl suite is green with the change (703 tests, 0 failures, JDK 21). I have not run the Core ITs.

  • Your pull request should address just one issue, without pulling in other changes.

  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.

  • Each commit in the pull request should have a meaningful subject line and body.

  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied.

  • Run mvn verify to make sure basic checks pass.

  • You have run the Core IT successfully.

  • I hereby declare this contribution to be licenced under the Apache License Version 2.0, January 2004

Found by a defect-hunting pipeline I build and run (Dev-next-gen), using Claude Code with Anthropic's Claude Opus 5.

The Maven 3 activator lower-cases activation/os/family before handing it
to Os.isFamily (0456c7c, "Caplital OS name can not activate profile"),
as it does for name and arch. The Maven 4 activator lower-cases name and
arch but passes family through untouched.

Os.isFamily switches on the family string against lower-case constants
such as "winnt", "unix" and "dos"; anything that does not match falls
through to the default branch, which is a plain
actualOsName.contains(family). Families whose name is a substring of the
OS name, like "Mac" on "mac os x", still work by accident, which is why
the existing capital-name test did not catch this. The ones that rely on
the switch do not: <family>WinNT</family> no longer activates on
Windows, and <family>Mac</family> no longer activates on a JDK reporting
os.name=darwin.

Add the toLowerCase call so the family follows the same rule as the
other three fields, and cover the two cases above.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is correct and the root cause analysis in the description is thorough. One test coverage gap below.

Category: bug — correctness regression vs Maven 3 behaviour.
Milestone suggestion: 4.1.0 (this is a Maven 4 regression; the compat layer already has the fix).

This review was generated by an AI agent, Hermès on behalf of @gnodet.

assertActivation(false, winnt, newContext(null, newProperties("linux", "6.5.0-1014-aws", "amd64")));

Profile mac = newProfile(ActivationOS.newBuilder().family("Mac"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing negation test for capitalised family.

The negation path (!WinNT) is untested. Before this fix, determineFamilyMatch("!WinNT", ...) would check "!WinNT".startsWith("!") → true, strip to "WinNT", then call Os.isFamily("WinNT", ...) → hits the default branch (substring test on lowercased form) rather than the case FAMILY_NT switch arm — so the result was wrong, and the negation then inverts the wrong result. The fix resolves this, but there is no test to pin it.

Suggest extending testCapitalFamily with a negated assertion:

Suggested change
assertActivation(true, winnt, newContext(null, newProperties("windows 11", "10.0", "amd64")));
assertActivation(false, winnt, newContext(null, newProperties("linux", "6.5.0-1014-aws", "amd64")));
Profile notWinnt = newProfile(ActivationOS.newBuilder().family("!WinNT"));
assertActivation(false, notWinnt, newContext(null, newProperties("windows 11", "10.0", "amd64")));
assertActivation(true, notWinnt, newContext(null, newProperties("linux", "6.5.0-1014-aws", "amd64")));
Profile mac = newProfile(ActivationOS.newBuilder().family("Mac"));
assertActivation(true, mac, newContext(null, newProperties("darwin", "24.6.0", "aarch64")));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6711e72.

@gnodet gnodet added bug Something isn't working backport-to-4.0.x labels Sep 16, 2026
@gnodet gnodet added this to the 4.1.0 milestone Sep 16, 2026

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review after second commit.

The prior finding (missing negation test for capitalised family) has been addressed in 6711e72: notWinnt cases and the mac/darwin assertion are now present and correct.

The fix itself is correct — determineFamilyMatch now matches the compat layer (maven-model-builder) byte-for-byte. Locale.ENGLISH is already imported. The !WinNT path lowers to !winnt before the startsWith("!") strip, so the lowercase is applied before negation stripping — same behaviour as name/arch/version. All test assertions verified against Os.isFamily internals (FAMILY_NT="winnt", DARWIN="darwin").

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet
gnodet merged commit 5d36a54 into apache:master Sep 16, 2026
20 checks passed
gnodet added a commit that referenced this pull request Sep 16, 2026
…13152) (#13154)

* Lower-case the OS family before matching it against the family names

The Maven 3 activator lower-cases activation/os/family before handing it
to Os.isFamily (0456c7c, "Caplital OS name can not activate profile"),
as it does for name and arch. The Maven 4 activator lower-cases name and
arch but passes family through untouched.

Os.isFamily switches on the family string against lower-case constants
such as "winnt", "unix" and "dos"; anything that does not match falls
through to the default branch, which is a plain
actualOsName.contains(family). Families whose name is a substring of the
OS name, like "Mac" on "mac os x", still work by accident, which is why
the existing capital-name test did not catch this. The ones that rely on
the switch do not: <family>WinNT</family> no longer activates on
Windows, and <family>Mac</family> no longer activates on a JDK reporting
os.name=darwin.

Add the toLowerCase call so the family follows the same rule as the
other three fields, and cover the two cases above.

* Add negation test for capitalised OS family

---------

Co-authored-by: Leo Camus <leo.camus23@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-to-4.0.x bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants