diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aed048653..0fb44b8089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ language runtime. The main focus is on user-observable behavior of the engine. * Allocation reporting via Truffle has been removed. Python object sizes were never reported correctly, so the data was misleading and there was a non-neglible overhead for object allocations even when reporting was inactive. * Better `readline` support via JLine. Autocompletion and history now works in `pdb` * Remove the intrinsified _ctypes module in favor of the native CPython version. This makes GraalPy's ctypes implementation more compatible and reduces the memory footprint of using ctypes. +* Add a new, more natural style of subclassing Java classes from Python by passing the `new_style=True` keyword. Multiple levels of inheritance are supported, and `super()` calls both in the constructor override via `__new__` as well as in Java method overrides work as expected. ## Version 25.0.1 * Allow users to keep going on unsupported JDK/OS/ARCH combinations at their own risk by opting out of early failure using `-Dtruffle.UseFallbackRuntime=true`, `-Dpolyglot.engine.userResourceCache=/set/to/a/writeable/dir`, `-Dpolyglot.engine.allowUnsupportedPlatform=true`, and `-Dpolyglot.python.UnsupportedPlatformEmulates=[linux|macos|windows]` and `-Dorg.graalvm.python.resources.exclude=native.files`. diff --git a/docs/user/Native-Extensions.md b/docs/user/Native-Extensions.md index 5746d1372a..ecbeb12830 100644 --- a/docs/user/Native-Extensions.md +++ b/docs/user/Native-Extensions.md @@ -50,5 +50,6 @@ The implementation also relies on `venv` to work, even if you are not using exte To support creating multiple GraalPy contexts that access native modules within the same JVM or Native Image, we need to isolate them from each other. The current strategy for this is to copy the libraries and modify them such that the dynamic library loader of the operating system will isolate them for us. To do this, all GraalPy contexts in the same process (not just those in the same engine!) must set the `python.IsolateNativeModules` option to `true`. +You should test your applications thoroughly if you want to use this feature, as there are many possiblities for native code to sidestep the library isolation through other process-wide global state. For more details on this, see [our implementation details](https://github.com/oracle/graalpython/blob/master/docs/contributor/IMPLEMENTATION_DETAILS.md#c-extension-copying). diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py b/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py index 63e26b2579..48cdf8b582 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py @@ -720,6 +720,13 @@ def test_super(self): super(list, l).remove(0) # ArrayList#remove(int index) assert l == [6] + def test_issubclass_isinstance(self): + from java.util import ArrayList, List + assert issubclass(ArrayList, List) + assert issubclass(ArrayList, ArrayList) + assert isinstance(ArrayList(), List) + assert isinstance(ArrayList(), ArrayList) + def test_java_array(self): import java il = java.type("int[]")(20) @@ -1409,6 +1416,68 @@ def test_foreign_repl(self): assert repr(Integer).startswith(') { // we want to subclass a Java class - return buildJavaClass(frame, inliningTarget, language, (PFunction) function, arguments, invokeBody, name); + return buildJavaClass(frame, inliningTarget, language, (PFunction) function, arguments, keywords, invokeBody, name); } class InitializeBuildClass { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignAbstractClassBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignAbstractClassBuiltins.java index ad90c12136..f951adf58a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignAbstractClassBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignAbstractClassBuiltins.java @@ -36,20 +36,25 @@ import com.oracle.graal.python.builtins.CoreFunctions; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; +import com.oracle.graal.python.lib.PyObjectGetIter; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.runtime.GilNode; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateNodeFactory; import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; /* * NOTE: We are not using IndirectCallContext here in this file @@ -67,10 +72,25 @@ protected List> getNodeFa @Builtin(name = J___BASES__, minNumOfPositionalArgs = 1, isGetter = true, isSetter = false) @GenerateNodeFactory abstract static class BasesNode extends PythonUnaryBuiltinNode { - @Specialization - static Object getBases(Object self, + @Specialization(limit = "2") + static Object getBases(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @CachedLibrary("self") InteropLibrary lib, + @Cached PyObjectGetIter getIter, + @Cached SequenceStorageNodes.CreateStorageFromIteratorNode createStorageFromIteratorNode, @Bind PythonLanguage language) { - return PFactory.createEmptyTuple(language); + if (lib.hasMetaParents(self)) { + try { + Object parents = lib.getMetaParents(self); + Object iterObj = getIter.execute(frame, inliningTarget, parents); + SequenceStorage storage = createStorageFromIteratorNode.execute(frame, iterObj); + return PFactory.createTuple(language, storage); + } catch (UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } else { + return PFactory.createEmptyTuple(language); + } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java index deb731b8e3..9da09488f9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java @@ -222,6 +222,7 @@ import com.oracle.truffle.api.CompilerDirectives.ValueType; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.TruffleLanguage.Env; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Exclusive; @@ -237,6 +238,8 @@ import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.ControlFlowException; import com.oracle.truffle.api.nodes.Node; @@ -1482,23 +1485,23 @@ static boolean doManaged(PythonManagedClass left, PythonManagedClass right) { } @Specialization - static boolean doManaged(PythonBuiltinClassType left, PythonBuiltinClassType right) { + static boolean doTypeType(PythonBuiltinClassType left, PythonBuiltinClassType right) { return left == right; } @Specialization - static boolean doManaged(PythonBuiltinClassType left, PythonBuiltinClass right) { + static boolean doTypeClass(PythonBuiltinClassType left, PythonBuiltinClass right) { return left == right.getType(); } @Specialization - static boolean doManaged(PythonBuiltinClass left, PythonBuiltinClassType right) { + static boolean doClassType(PythonBuiltinClass left, PythonBuiltinClassType right) { return left.getType() == right; } @Specialization @InliningCutoff - static boolean doNativeSingleContext(PythonAbstractNativeObject left, PythonAbstractNativeObject right, + static boolean doNative(PythonAbstractNativeObject left, PythonAbstractNativeObject right, @CachedLibrary(limit = "1") InteropLibrary lib) { if (left == right) { return true; @@ -1509,6 +1512,44 @@ static boolean doNativeSingleContext(PythonAbstractNativeObject left, PythonAbst return lib.isIdentical(left.getPtr(), right.getPtr(), lib); } + @Specialization(guards = {"isForeignObject(left)", "isForeignObject(right)"}) + @InliningCutoff + static boolean doOther(Object left, Object right, + @Bind PythonContext context, + @CachedLibrary(limit = "2") InteropLibrary lib) { + if (lib.isMetaObject(left) && lib.isMetaObject(right)) { + if (left == right) { + return true; + } + // *sigh*... Host classes have split personality with a "static" and a "class" + // side, and that affects identity comparisons. And they report their "class" sides + // as bases, but importing from Java gives you the "static" side. + Env env = context.getEnv(); + if (env.isHostObject(left) && env.isHostObject(right)) { + // the activation of isMemberReadable and later readMember serves as branch + // profile + boolean leftIsStatic = lib.isMemberReadable(left, "class"); + boolean rightIsStatic = lib.isMemberReadable(right, "class"); + if (leftIsStatic != rightIsStatic) { + try { + if (leftIsStatic) { + left = lib.readMember(left, "class"); + } else { + assert rightIsStatic; + right = lib.readMember(right, "class"); + } + } catch (UnsupportedMessageException | UnknownIdentifierException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + } + if (lib.isIdentical(left, right, lib)) { + return true; + } + } + return false; + } + @Fallback static boolean doOther(@SuppressWarnings("unused") Object left, @SuppressWarnings("unused") Object right) { return false; diff --git a/graalpython/lib-graalpython/__graalpython__.py b/graalpython/lib-graalpython/__graalpython__.py index 1f9cf47c7f..c88326028b 100644 --- a/graalpython/lib-graalpython/__graalpython__.py +++ b/graalpython/lib-graalpython/__graalpython__.py @@ -142,7 +142,17 @@ def import_current_as_named_module_with_delegate(module, module_name, delegate_n @builtin -def build_java_class(module, ns, name, base): +def build_java_class(module, ns, name, base, new_style=False): + if new_style: + return build_new_style_java_class(ns, name, base) + import warnings + warnings.warn("Subclassing Java classes is going to change " + "to a new instance layout that is hopefully " + "more intuitive. Pass the keyword new_style=True " + "to your class definition to try the new style. " + "The new style will become the default in the next " + "release and the old style will be removed soon after.", DeprecationWarning, 1) + ns['__super__'] = None # place where store the original java class when instance is created ExtenderClass = type("PythonJavaExtenderClass", (object, ), ns) HostAdapter = __graalpython__.extend(base) @@ -158,3 +168,115 @@ def factory (cls, *args): resultClass.__new__ = classmethod(factory) return resultClass + + +_CUSTOM_JAVA_SUBCLASS_BACKSTOPS = {} + + +@builtin +def build_new_style_java_class(module, ns, name, base): + import polyglot + import types + + JavaClass = __graalpython__.extend(base) + if JavaClass not in _CUSTOM_JAVA_SUBCLASS_BACKSTOPS: + class MroClass: + def __getattr__(self, name): + sentinel = object() + # An attribute access on the Java instance failed, check the + # delegate and then the static Java members + result = getattr(self.this, name, sentinel) + if result is sentinel: + return getattr(self.getClass().static, name) + else: + return result + + def __setattr__(self, name, value): + # An attribute access on the Java instance failed, use the delegate + setattr(self.this, name, value) + + def __delattr__(self, name): + # An attribute access on the Java instance failed, use the delegate + delattr(self.this, name) + + # This may race, so we allow_method_overwrites, at the only danger to + # insert a few useless classes into the MRO + polyglot.register_interop_type(JavaClass, MroClass, allow_method_overwrites=True) + + # A class to make sure that the returned Python class can be used for + # issubclass and isinstance checks with the Java instances, and to wrap all + # methods in created subclasses to provide the proper `self` (the Java + # instance) and still make `super()` work. + class JavaSubclassMeta(type): + @property + def __bases__(self): + return (JavaClass,) + + def __instancecheck__(cls, obj): + return isinstance(obj, JavaClass) + + def __subclasscheck__(cls, derived): + return cls is derived or issubclass(derived, JavaClass) + + def __new__(mcls, name, bases, namespace): + if bases: + new_class = None + + class custom_super(): + def __init__(self, start_type=None, object_or_type=None): + assert start_type is None and object_or_type is None, "super() calls in Python class inheriting from Java must not receive arguments" + f = sys._getframe(1) + self.self = f.f_locals[f.f_code.co_varnames[0]] + + def __getattribute__(self, name): + if name == "__class__": + return __class__ + if name == "self": + return object.__getattribute__(self, "self") + for t in new_class.mro()[1:]: + if t == DelegateSuperclass: + break + if name in t.__dict__: + value = t.__dict__[name] + if get := getattr(value, "__get__", None): + return get(self.self.this) + return value + return getattr(__graalpython__.super(self.self), name) + + # Wrap all methods so that the `self` inside is always a Java object, and + # adapt the globals in the functions to provide a custom super() if + # necessary + def self_as_java_wrapper(k, value): + if type(value) is not types.FunctionType: + return value + if k in ("__new__", "__class_getitem__"): + return value + if "super" in value.__code__.co_names: + value = types.FunctionType( + value.__code__, + value.__globals__ | {"super": custom_super}, + name=value.__name__, + argdefs=value.__defaults__, + closure=value.__closure__, + ) + return lambda self, *args, **kwds: value(self.__this__, *args, **kwds) + namespace = {k: self_as_java_wrapper(k, v) for k, v in namespace.items()} + new_class = type.__new__(mcls, name, bases, namespace) + return new_class + return type.__new__(mcls, name, bases, namespace) + + def __getattr__(self, name): + return getattr(JavaClass, name) + + # A class that defines the required construction for the Java instances, so + # the Python code can actually override __new__ to affect the construction + # of the Java object + class DelegateSuperclass(metaclass=JavaSubclassMeta): + def __new__(cls, *args, **kwds): + delegate = object.__new__(cls) + java_object = polyglot.__new__(JavaClass, *(args + (delegate,))) + delegate.__this__ = java_object + delegate.__init__(*args, **kwds) + return java_object + + return type(name, (DelegateSuperclass,), ns) diff --git a/scripts/wheelbuilder/darwin/gensim.sh b/scripts/wheelbuilder/darwin/gensim.sh new file mode 100755 index 0000000000..80409d25c4 --- /dev/null +++ b/scripts/wheelbuilder/darwin/gensim.sh @@ -0,0 +1,42 @@ +# Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +if [ -n "$GITHUB_RUN_ID" ]; then + brew install gcc openblas +fi diff --git a/scripts/wheelbuilder/darwin/numpy.2.0.2.sh b/scripts/wheelbuilder/darwin/numpy.2.0.2.sh new file mode 100644 index 0000000000..0e9b3baec4 --- /dev/null +++ b/scripts/wheelbuilder/darwin/numpy.2.0.2.sh @@ -0,0 +1,45 @@ +# Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +if [ -n "$GITHUB_RUN_ID" ]; then + brew install gcc openblas +fi + +export CC="gcc-15" +export CXX="g++-15" diff --git a/scripts/wheelbuilder/darwin/scipy.sh b/scripts/wheelbuilder/darwin/scipy.sh index 5b6e8e24a8..6185dd4615 100755 --- a/scripts/wheelbuilder/darwin/scipy.sh +++ b/scripts/wheelbuilder/darwin/scipy.sh @@ -1,4 +1,4 @@ -# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # The Universal Permissive License (UPL), Version 1.0 @@ -38,10 +38,17 @@ # SOFTWARE. if [ -n "$GITHUB_RUN_ID" ]; then - brew install gcc openblas pkg-config - export PKG_CONFIG_PATH=/opt/homebrew/opt/openblas/lib/pkgconfig + brew install lld gcc pkg-config openblas fi -export FFLAGS=-fallow-argument-mismatch + +export CC="gcc-15" +export CXX="g++-15" +export CXXFLAGS="-fuse-ld=lld" +export CFLAGS="-fuse-ld=lld" +export FFLAGS="-fuse-ld=lld" +export LDFLAGS="-Wl,-platform_version,macos,15.0.0,15.0.0" +export PKG_CONFIG_PATH="/opt/homebrew/opt/openblas/lib/pkgconfig" + if [ -n "$1" ]; then pip wheel "scipy==$1" else diff --git a/scripts/wheelbuilder/linux/gensim.sh b/scripts/wheelbuilder/linux/gensim.sh new file mode 100755 index 0000000000..d977ffc283 --- /dev/null +++ b/scripts/wheelbuilder/linux/gensim.sh @@ -0,0 +1,47 @@ +# Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +if [ -n "$GITHUB_RUN_ID" ]; then + dnf install -y gcc-toolset-12-gcc-gfortran gcc-toolset-12-gcc-c++ openblas-devel + if [ -n "$1" ]; then + scl enable gcc-toolset-12 "pip wheel \"gensim==$1\"" + else + scl enable gcc-toolset-12 "pip wheel gensim" + fi +fi diff --git a/scripts/wheelbuilder/linux/numpy.sh b/scripts/wheelbuilder/linux/numpy.sh index 8cd8c11077..40a64fa1ca 100755 --- a/scripts/wheelbuilder/linux/numpy.sh +++ b/scripts/wheelbuilder/linux/numpy.sh @@ -39,4 +39,9 @@ if [ -n "$GITHUB_RUN_ID" ]; then dnf install -y gcc-toolset-12-gcc-gfortran openblas-devel + if [ -n "$1" ]; then + scl enable gcc-toolset-12 "pip wheel \"numpy==$1\"" + else + scl enable gcc-toolset-12 "pip wheel numpy" + fi fi