Skip to content

Conversation

@hakre
Copy link
Contributor

@hakre hakre commented Nov 22, 2025

Update the build recipe template as in PHP-8.5 ext/opcache errors on make -C ext/opcache install as modules/* does not resolve to actual filenames any longer,
but to modules/* verbatim which fails the install command yielding the following diagnostics:

cp: cannot stat 'modules/*': No such file or directory

and exit status 1 (resolved by make as build error 2).

Important

Install is a standard target and we keep the invocation unconditional as the user could be currently remaking.

Note

ext/opcache is only exemplary, this can be any ext/* that makes use of the install-modules target to install zero modules. (that is the recipe with the fix here)

update the build recipe template as in PHP-8.5 ext/opcache errors on
`make -C ext/opcache install` as modules/* does not resolve to actual
filenames any longer,
but to `modules/*` verbatim which fails the `install` command yielding
the following diagnostics:

    cp: cannot stat 'modules/*': No such file or directory

and exit status 1 (resolved by make as build error 2).
@devnexen devnexen requested a review from petk November 22, 2025 10:10
@hakre hakre changed the title build make -C ext/* install-modules w/o modules/* GH-20557 Fix GH-20557: Build make -C ext/* install-modules w/o modules/* Nov 22, 2025
@devnexen
Copy link
Member

Note that in fact it might need to target PHP-8.5 branch though.

Copy link
Member

@TimWolla TimWolla left a comment

Choose a reason for hiding this comment

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

After both reading the issue and the description of this PR, it is still not clear to me what this is supposed to fix. Installing OPcache as a separate module is no longer supported and this is documented as part of the migration guide.

@petk
Copy link
Member

petk commented Nov 24, 2025

After both reading the issue and the description of this PR, it is still not clear to me what this is supposed to fix. Installing OPcache as a separate module is no longer supported and this is documented as part of the migration guide.

If anything, it makes sense here to check whether the directory exists and contains files for installation. Otherwise, yes. That type of installation with phpize won't work in any case anymore for opcache extension since PHP-8.5.

@hakre hakre requested a review from TimWolla November 25, 2025 13:25
@hakre
Copy link
Contributor Author

hakre commented Nov 25, 2025

Thanks @petk, I couldn't have put it better myself.

@hakre
Copy link
Contributor Author

hakre commented Nov 25, 2025

Thank you @TimWolla for your candid feedback.

Copy link
Member

@TimWolla TimWolla left a comment

Choose a reason for hiding this comment

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

The PR description is unchanged from my previous review and does not provide an argument or example in favor of this change that doesn't involve trying to install OPcache as a shared module. This makes it impossible to evaluate the correctness of this change.

Should this check $PHP_MODULES instead?

@TimWolla
Copy link
Member

TimWolla commented Nov 25, 2025

If anything, it makes sense here to check whether the directory exists and contains files for installation.

@petk As far as I'm concerned, “no module is available” is an error situation and emitting an error message is the correct consequence of that.

@petk
Copy link
Member

petk commented Nov 25, 2025

Actually, yes. I'm exactly at this point now also in my CMake-based build system. :D Yes, building extensions, such as opcache, json, hash, random, date, etc. in "phpize" style should emit some error, yes. Otherwise, also this bug in Docker image wouldn't be noted. It's only that install modules target in Makefile is probably a bit inconvenient place for detecting and emitting such error, yes.

Edit: Ah, I see. And there is even test -d modules few lines above in that target already, which tests if there is a modules directory present.

@hakre
Copy link
Contributor Author

hakre commented Nov 25, 2025

Edit: Ah, I see. And there is even test -d modules few lines above in that target already, which tests if there is a modules directory present.

Yes, this is parenting in the fix.

Btw. have you figured out in CMake to define all install files for the shared objects? Great project btw..

@petk
Copy link
Member

petk commented Nov 25, 2025

Edit: Ah, I see. And there is even test -d modules few lines above in that target already, which tests if there is a modules directory present.

Yes, this is parenting in the fix.

Btw. have you figured out in CMake to define all install files for the shared objects? Great project btw..

Yes. It felt almost like a rocket science. Very complex but quite neat overall. It's resolved over CMake configuration files in one of my local branches and even with upcoming CPS experimentation. :D

@hakre
Copy link
Contributor Author

hakre commented Nov 25, 2025

The PR description is unchanged from my previous review and does not provide an argument or example in favor of this change that doesn't involve trying to install OPcache as a shared module. This makes it impossible to evaluate the correctness of this change.

Should this check $PHP_MODULES instead?

If anything, then finding no actionable in the change request description for the pull request description. Otherwise, yes, obviously., voiding the invocation voids the exit status and the solution.

Please share more context (What have you tried? What did happen? What did you expect?).

@hakre hakre requested a review from TimWolla November 28, 2025 12:50
@TimWolla
Copy link
Member

TimWolla commented Dec 1, 2025

ext/opcache is only exemplary, this can be any ext/* that makes use of the install-modules target to install zero modules. (that is the recipe with the fix here)

I do not find this convincing. The point of install-modules is to install the module. Silently doing nothing if no module is available (e.g. because the build failed or because it's not a module that can be installed dynamically) is not correct.

If anything, then finding no actionable in the change request description for the pull request description. Otherwise, yes, obviously., voiding the invocation voids the exit status and the solution.

I do not understand what you are trying to say here. Is there a word missing in the first sentence?

Please share more context (What have you tried? What did happen? What did you expect?).

I tried to ask for an explanation justifying this change. I did not receive any useful replies. I expected to receive useful replies.

@TimWolla TimWolla removed their request for review December 1, 2025 14:22
@hakre
Copy link
Contributor Author

hakre commented Dec 7, 2025

I understand you remain unconvinced. Let's focus solely on the code change to resolve this.

  1. On the scope: The fix modifies exactly one line in the install-modules recipe. The specific extension name is irrelevant to the recipe's logic.

  2. On the rationale: The existing recipe already correctly handles the zero-modules case in its preceding lines. The change ensures consistency by having the final line follow the same pattern as the rest of the recipe. This aligns one line with the established behavior of the target.

To move forward, I need specific, actionable feedback on the code. Could you please:

  • Point to the exact line in the diff you believe is incorrect, and
  • Propose the exact change you'd like to see?

If your concern relates to broader behaviors or policies beyond this one-line consistency fix, I recommend filing a separate issue to discuss the overall design. For this specific PR, the technical case is based on the existing code's logic.

@hakre hakre requested a review from TimWolla December 7, 2025 13:26
@hakre
Copy link
Contributor Author

hakre commented Dec 10, 2025

@TimWolla, a quick follow-up here. To move this maintenance fix forward, I need actionable feedback on the code itself.

Could you please, by Dec 13th 2025, either:

  1. Point to the exact line in the diff you believe is incorrect and propose a specific change, or
  2. Confirm you have no code-level objections to this one-line consistency fix?

If I don't hear back, I'll assume there are no substantive code issues and will seek a maintainer's approval to merge based on the existing approvals and technical rationale.

Thank you.

@iluuu1994
Copy link
Member

@hakre FWIU, Tim wasn't suggesting the fix is "wrong", but harmful. He was asking why install-modules was invoked on an uninstallable extension in the first place. For somebody who is relying on this in their build is surely better off knowing this won't work anymore?

Copy link
Member

@TimWolla TimWolla left a comment

Choose a reason for hiding this comment

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

If I don't hear back, I'll assume there are no substantive code issues and will seek a maintainer's approval to merge based on the existing approvals and technical rationale.

That is not how it works. If you want a change included, it is your job to explain:

(1) Why it makes the situation better rather than just different or worse.
(2) Why the selected change is the correct change and not any other change.

I - a maintainer of PHP - have repeatedly explained why I believe this PR fails both (1) and (2) and just reiterating the same points or trying to enforce an arbitrary deadline is certainly not going to change my opinion.

Tim wasn't suggesting the fix is "wrong", but harmful

Yes.

@hakre
Copy link
Contributor Author

hakre commented Dec 10, 2025

Let me address both points with concrete technical facts:

1. Why this is better, not just different

The recipe currently has inconsistent behavior for the same condition:

  • Lines 1-3: Handle zero modules without failure
  • Line 4: Fails for zero modules

This inconsistency is objectively a bug. Making line 4 consistent with lines 1-3 fixes that bug. "Better" means:

  • Eliminating the bug (inconsistent behavior)
  • Following the recipe's own established design pattern

2. Why this specific change is correct

This change is correct because it respects the existing architecture. The recipe's design (established in lines 1-3) is to handle zero modules without failure. The fix aligns line 4 with that design.

If the concern is that the design itself is wrong (zero modules should fail), then:

  1. That's a separate design discussion
  2. It would require changing lines 1-3, not just line 4
  3. It would be a breaking change needing justification

The "harmful" claim

The claim that consistent behavior is "harmful" contradicts:

  • The recipe's own established behavior (lines 1-3)
  • The principle that bugs (inconsistencies) should be fixed

If silent handling of zero modules is harmful, then lines 1-3 have been harmful since they were written. This PR doesn't introduce new behavior—it removes an inconsistency.

Specific code feedback requested

To have a technical discussion, I need:

  1. Which character in the diff is wrong?
  2. What should it be instead, and why?

Without specific code feedback, this remains a subjective objection to fixing an objective inconsistency.

@hakre hakre requested a review from TimWolla December 10, 2025 21:39
@iluuu1994
Copy link
Member

This inconsistency is objectively a bug.

The inconsistency may be considered a bug that can be aligned in two ways, i.e. handle non-existent modules, or don't. But who says which is the desired behavior? That's the more relevant question. IMO, it's "the user should be informed when trying to install something that can't be installed", in which case the more reasonable change is to drop test ... && mkdir ... to align the behavior.

Which character in the diff is wrong?

A PR can choose the wrong appraoch, rather than being otherwise incorrect.

What should it be instead, and why?

Drop test ... && mkdir ... instead.

I would also like to hear from @petk whether he agrees. Without consensus we can't proceed here anyway.

@hakre
Copy link
Contributor Author

hakre commented Dec 10, 2025

@iluuu1994 , @TimWolla , let's clarify the exact nature of the objection, because I believe we are discussing two different layers of the build system.

1. The Layer This PR Addresses: The Recipe's Internal Contract

The install-modules recipe is a low-level building block. Its contract is simple and functional:

  • Input: A list of module names.
  • Output: Those modules are installed.
  • Edge Case (Empty List): By the principle of least astonishment and robustness, the correct behavior is to do nothing successfully. This is standard with the development tools, e.g. make install yields make: Nothing to be done for 'install', the earlier recipe lines check for the modules directory existence, or always output that modules are being installed even if none.

The bug is that the recipe violates its own contract inconsistently:

  • Lines 1-3: Correctly implement "do nothing successfully" for an empty list.
  • Line 4: Incorrectly fails.

This PR fixes that internal inconsistency. It ensures the recipe is a reliable, predictable component.

2. The Layer of the Reviewer's Concern: User-Facing Process Feedback

The concern about "harm" seems to stem from a different, higher-level layer: the user's experience when they run make install.

If the overall installation process results in zero modules being installed, it might indeed be helpful to surface a message like make: Nothing to be done for 'install'. However:

  • That is not the responsibility of the install-modules recipe. Its job is to install modules, not to govern the entire make install flow.
  • That feedback should be handled at the target level (e.g., the install target itself,, here configured by the $(installation_targets)), not by having a low-level component fail.
  • Failing here is the wrong mechanism. A build failure (make: *** [install-modules] Error 1) is a hard error, not a helpful informational message. It breaks scripts and automation.

3. The Category Error

The objection "it should not silently do nothing" is applying a user-experience requirement to an internal implementation component. It's like demanding that a single printf function call must also check if the entire program has any output and complain if it doesn't. That's not its job.

If the project's policy is that make install must warn when no modules are installed, that policy should be implemented at the appropriate architectural level, not by making a utility recipe fail.

4. The Path Forward

We have two orthogonal issues:

  1. Bug: The install-modules recipe is internally inconsistent. (This PR fixes it.)
  2. Feature/Policy: Should the build system provide explicit feedback when an installation results in zero module changes? (This is a separate discussion.)

Mixing these two concerns is harmful to the codebase. It would:

  • Corrupt a clean, functional recipe with policy logic.
  • Use a hard error for a soft notification.
  • Break the recipe's reusability for other contexts.

5. Final, Direct Questions

To resolve this, I need clear answers on principle, not feelings:

Question for the Project:

Is it acceptable for a low-level, functional recipe like install-modules to successfully do nothing when given an empty list as input?

If No, then the recipe's design is flawed and lines 1-3 must also be changed to fail. This is a separate, breaking change that requires an RFC.

If Yes, then the inconsistency is a bug, and this PR fixes it.

Question for the Reviewers:

Can you point to the specific character in the diff of this PR that is incorrect, given the recipe's existing design (where lines 1-3 already do nothing successfully)?

If you cannot, then the objection is not about this code change, but about a separate design policy. Let's file an issue for that policy and settle this bug fix on its own technical merits.

@iluuu1994
Copy link
Member

iluuu1994 commented Dec 10, 2025

  • Bug: The install-modules recipe is internally inconsistent. (This PR fixes it.)
  • Feature/Policy: Should the build system provide explicit feedback when an installation results in zero module changes? (This is a separate discussion.)

They're not really two separate discussions though. Not printing a proper error message is bad, agreed. However, this PR changes behavior, rather than just fixing the error message. Sometimes it's ok to merge a solution in multiple PRs, but no indication was made here that this is the intent.

@hakre
Copy link
Contributor Author

hakre commented Dec 11, 2025

I appreciate the technical discussion about aligning the recipe's behavior. However, I'd like to refocus on what I believe is the fundamental user experience principle at stake:

The build system should guide developers, not just pass or fail.

Currently, we're debating between two incomplete solutions:

  1. Silent success (my fix) - which hides potential configuration issues -- NOTE: my fix does not remove the existing echo line of the recipe.
  2. Hard failure (the alternative) - which breaks builds for a non-error condition

Both options miss the ideal: success with clear feedback.

My fix changes the observable behavior from 'failing inconsistently' to 'succeeding consistently'—this aligns the implementation with what appears to be the recipe's original design (based on lines 1-3).

The install-modules recipe uses @ (silent execution), which is a separate design choice. If we want developers to know when they're attempting to install zero modules, we should consider:

  1. First: Fix the inconsistency so the recipe behaves predictably (this PR)
  2. Then: Discuss whether the recipe should provide informational output (removing @ or adding echo -- NOTE: my fix does not remove the existing echo line of the recipe)

The 'nothing to do' message you mentioned should come from the install target itself, not necessarily from this low-level recipe.

$ make install
make: Nothing to be done for 'install'.

But that's a separate, higher-level design discussion, e.g. whether or not install-modules is listed as installation target.

For this PR: Can we agree that predictable behavior (consistency) is prerequisite to any higher-level improvements? Once the recipe is consistent, we can have a separate discussion about output messaging (and/or from my perspective preferable automated validation / property based tests across ext/*).

I find the idea good to ask @petk for re-validation. We discussed the parenting lines 1-3 quite early IIRC. Maybe he also has some more insights for the different discussions, e.g. what might make it hard for ext/* users to configure the correct installation targets.

@petk
Copy link
Member

petk commented Dec 11, 2025

I'd suggest something like this instead to throw error sooner in the build process - at the phpize step (this should be refactored further though):

Emit error when running phpize inside ext/{date,lexbor,pcre,opcache,json,random,hash,uri,reflection,standard}
diff --git a/build/php.m4 b/build/php.m4
index 0a2169f6f51..812ec77acfe 100644
--- a/build/php.m4
+++ b/build/php.m4
@@ -2542,3 +2542,7 @@ AS_VAR_IF([php_cv_c_standard_library], [unknown],
 [php_cv_c_standard_library=unknown])])])
 ])
 ])
+
+AC_DEFUN([PHP_ALWAYS_ENABLED_EXTENSION], [m4_ifdef([PHP_ALWAYS_SHARED],
+[m4_fatal(m4_text_wrap([The PHP $1 extension is intended to be built only
+  within the PHP source tree (php-src).]))])])
diff --git a/ext/date/config0.m4 b/ext/date/config0.m4
index c78fcb78e15..e40b69b44f7 100644
--- a/ext/date/config0.m4
+++ b/ext/date/config0.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([date])
+
 dnl Check for headers needed by timelib
 AC_CHECK_HEADERS([io.h])
 
diff --git a/ext/hash/config.m4 b/ext/hash/config.m4
index 2da44c503a6..c815d43651e 100644
--- a/ext/hash/config.m4
+++ b/ext/hash/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([hash])
+
 PHP_ARG_WITH([mhash],
   [for mhash support],
   [AS_HELP_STRING([--with-mhash],
diff --git a/ext/json/config.m4 b/ext/json/config.m4
index 5697dbff8d2..72106f3deca 100644
--- a/ext/json/config.m4
+++ b/ext/json/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([json])
+
 PHP_NEW_EXTENSION([json], m4_normalize([
     json_encoder.c
     json_parser.tab.c
diff --git a/ext/lexbor/config.m4 b/ext/lexbor/config.m4
index 3e67c10fdfc..5e6ef16630d 100644
--- a/ext/lexbor/config.m4
+++ b/ext/lexbor/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([lexbor])
+
 PHP_LEXBOR_CFLAGS="-I@ext_srcdir@/"
 LEXBOR_DIR="lexbor"
 
diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4
index 70138726c56..d48e29d9e1e 100644
--- a/ext/opcache/config.m4
+++ b/ext/opcache/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([opcache])
+
 PHP_ARG_ENABLE([huge-code-pages],
   [whether to enable copying PHP CODE pages into HUGE PAGES],
   [AS_HELP_STRING([--disable-huge-code-pages],
diff --git a/ext/pcre/config0.m4 b/ext/pcre/config0.m4
index e71d9a79557..46f431550db 100644
--- a/ext/pcre/config0.m4
+++ b/ext/pcre/config0.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([pcre])
+
 dnl By default we'll compile and link against the bundled PCRE library. If
 dnl --with-external-pcre is supplied, we'll use that for linking.
 PHP_ARG_WITH([external-pcre],,
diff --git a/ext/random/config.m4 b/ext/random/config.m4
index ff43b856acf..2daa87c020b 100644
--- a/ext/random/config.m4
+++ b/ext/random/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([random])
+
 AC_CHECK_DECL([arc4random_buf],
   [AC_DEFINE([HAVE_ARC4RANDOM_BUF], [1],
     [Define to 1 if you have the 'arc4random_buf' function.])])
diff --git a/ext/reflection/config.m4 b/ext/reflection/config.m4
index 10ce256f01a..5596f541912 100644
--- a/ext/reflection/config.m4
+++ b/ext/reflection/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([reflection])
+
 PHP_NEW_EXTENSION([reflection],
   [php_reflection.c],
   [no],,
diff --git a/ext/spl/config.m4 b/ext/spl/config.m4
index f15e124ba3f..3d2ebfc7b40 100644
--- a/ext/spl/config.m4
+++ b/ext/spl/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([spl])
+
 PHP_NEW_EXTENSION([spl], m4_normalize([
     php_spl.c
     spl_array.c
diff --git a/ext/standard/config.m4 b/ext/standard/config.m4
index ef6b3c5a010..8d986b48b91 100644
--- a/ext/standard/config.m4
+++ b/ext/standard/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([standard])
+
 dnl
 dnl Check if flush should be called explicitly after buffered io
 dnl
diff --git a/ext/uri/config.m4 b/ext/uri/config.m4
index 99e0d6b4767..d98257713a9 100644
--- a/ext/uri/config.m4
+++ b/ext/uri/config.m4
@@ -1,3 +1,5 @@
+PHP_ALWAYS_ENABLED_EXTENSION([uri])
+
 dnl Configure options
 dnl

@hakre
Copy link
Contributor Author

hakre commented Dec 12, 2025

@petk Thank you for proposing this comprehensive solution. I agree that failing at phpize stage for extensions never intended to be built separately is architecturally cleaner and provides better user guidance.

However, this is a significant policy change affecting many extensions and will require careful review and consensus.

In the meantime, the inconsistency in install-modules remains a bug in the current codebase. My one-line fix addresses this specific inconsistency immediately, regardless of what broader policy we adopt later.

Could you support merging this minimal bug fix now? This would:

  1. Fix the current inconsistency immediately
  2. Not conflict with your proposed phpize validation (they're orthogonal)
  3. Demonstrate progress while the larger policy discussion continues

Your phpize proposal is the right long-term solution, but bugs in the current system should still be fixed as we identify them.

@TimWolla TimWolla removed their request for review December 12, 2025 06:29
@TimWolla
Copy link
Member

This patch changes behavior that has been standing for 20 years. I'm hereby requesting a RFC to be written to decide if changing installation of zero modules to silently fail is a desired change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants