diff --git a/recipes/recipes_emscripten/scipy_flang/build.sh b/recipes/recipes_emscripten/scipy_flang/build.sh new file mode 100755 index 00000000000..560dd64e2a3 --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/build.sh @@ -0,0 +1,54 @@ +set -ex + +LLVM_PKG_URL="https://github.com/IsabelParedes/llvm-project/releases/download/v20.1.7_emscripten-wasm32/llvm_emscripten-wasm32-20.1.7-h2e33cc4_5.tar.bz2" +LLVM_PKG="llvm.tar.bz2" +export LLVM_DIR=$HOME/llvm + +if [ ! -d "$LLVM_DIR" ]; then + wget -O "$LLVM_PKG" "$LLVM_PKG_URL" + mkdir -p $LLVM_DIR + tar -xjvf $LLVM_PKG -C $LLVM_DIR --exclude='info/*' --exclude='share/*' --exclude='libexec/*' +fi + +export EM_LLVM_ROOT=$LLVM_DIR + +echo "LLVM installation complete." + +# https://github.com/mesonbuild/meson/blob/e542901af6e30865715d3c3c18f703910a096ec0/mesonbuild/backend/ninjabackend.py#L94 +# Prevent from using response file. The response file that meson generates is not compatible to pyodide-build +export MESON_RSP_THRESHOLD=131072 + +export CFLAGS="-I$PREFIX/include/python3.13 $CFLAGS" +export CXXFLAGS="-I$PREFIX/include/python3.13 $CXXFLAGS" + +# Patch mesonbuild/linkers/linkers.py - not sure what version we are patching? +# meson 1.9.1 conda-forge, meson-python 0.18.0 conda-forge +cp $RECIPE_DIR/meson_linkers.py $BUILD_PREFIX/lib/python3.13/site-packages/mesonbuild/linkers/linkers.py + +# Remove whitespace after '-s' in LDFLAGS +export LDFLAGS="$(echo "${LDFLAGS}" | sed -E 's/-s +/-s/g')" + +# Use local flang-new-wrapper that does some arg mangling. +cp $RECIPE_DIR/flang-new-wrapper $LLVM_DIR/bin/flang-new-wrapper + +export FC=$LLVM_DIR/bin/flang-new-wrapper +export FFLAGS="-g --target=wasm32-unknown-emscripten -fPIC" +export FCLIBS="-lFortranRuntime" + +export CFLAGS="$CFLAGS -fwasm-exceptions -sSUPPORT_LONGJMP=wasm" +export CXXFLAGS="$CXXFLAGS -fwasm-exceptions -sSUPPORT_LONGJMP=wasm" + +# Note bracket after function name is important or 'sgesv' will also match 'sgesvd' +cat void_to_int_return.txt | xargs -i sed -i -r 's/^void ({})\(/int \1(/g' scipy/linalg/cython_lapack_signatures.txt +# Underscore after name is important +cat void_to_int_return.txt | xargs -i sed -i -r 's/^void ({})_/int \1_/g' scipy/linalg/_matfuncs_expm.h + +# Add -Wl,--fatal-warnings to flags - this does not work +export LDFLAGS="$LDFLAGS -Wl,--no-fatal-warnings" + +${PYTHON} -m pip install . ${PIP_ARGS} --no-build-isolation \ + -Csetup-args="--cross-file=$RECIPE_DIR/emscripten.meson.cross" \ + -Csetup-args="-Dfortran_std=none" \ + -Csetup-args="-Duse-pythran=false" \ + -Cbuild-dir="_build" \ + -Ccompile-args="--verbose" diff --git a/recipes/recipes_emscripten/scipy_flang/emscripten.meson.cross b/recipes/recipes_emscripten/scipy_flang/emscripten.meson.cross new file mode 100755 index 00000000000..c05192830b9 --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/emscripten.meson.cross @@ -0,0 +1,14 @@ +[properties] +needs_exe_wrapper = true +skip_sanity_check = true +longdouble_format = 'IEEE_QUAD_LE' # for numpy + +[host_machine] +system = 'emscripten' +cpu_family = 'wasm32' +cpu = 'wasm' +endian = 'little' + +[binaries] +exe_wrapper = 'node' +pkgconfig = 'pkg-config' diff --git a/recipes/recipes_emscripten/scipy_flang/flang-new-wrapper b/recipes/recipes_emscripten/scipy_flang/flang-new-wrapper new file mode 100755 index 00000000000..85087d3063a --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/flang-new-wrapper @@ -0,0 +1,10 @@ +#!/bin/bash + +args=() +for arg in "$@"; do + if [[ "${arg}" != -s* ]]; then + args+=("${arg}") + fi +done + +exec "$LLVM_DIR/bin/flang-new" "-v" "${args[@]}" diff --git a/recipes/recipes_emscripten/scipy_flang/meson_linkers.py b/recipes/recipes_emscripten/scipy_flang/meson_linkers.py new file mode 100644 index 00000000000..34243063d8c --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/meson_linkers.py @@ -0,0 +1,1775 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2012-2022 The Meson development team +# Copyright © 2023 Intel Corporation + +from __future__ import annotations + +import abc +import os +import typing as T +import re + +from .base import ArLikeLinker, RSPFileSyntax +from .. import mesonlib +from ..mesonlib import EnvironmentException, MesonException +from ..arglist import CompilerArgs + +if T.TYPE_CHECKING: + from ..environment import Environment + from ..mesonlib import MachineChoice + from ..build import BuildTarget + from ..compilers import Compiler + + +class StaticLinker: + + id: str + + def __init__(self, exelist: T.List[str]): + self.exelist = exelist + + def get_id(self) -> str: + return self.id + + def get_exe(self) -> str: + return self.exelist[0] + + def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> CompilerArgs: + return CompilerArgs(self, args) + + def can_linker_accept_rsp(self) -> bool: + """ + Determines whether the linker can accept arguments using the @rsp syntax. + """ + return mesonlib.is_windows() + + def get_base_link_args(self, + target: 'BuildTarget', + linker: 'Compiler', + env: 'Environment') -> T.List[str]: + """Like compilers.get_base_link_args, but for the static linker.""" + return [] + + def get_exelist(self) -> T.List[str]: + return self.exelist.copy() + + def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]: + return [] + + def get_optimization_link_args(self, optimization_level: str) -> T.List[str]: + return [] + + def get_output_args(self, target: str) -> T.List[str]: + return [] + + def get_coverage_link_args(self) -> T.List[str]: + return [] + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str = '', + install_rpath: str='') -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) + + def thread_link_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def openmp_flags(self, env: Environment) -> T.List[str]: + return [] + + def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + return [] + + @classmethod + def unix_args_to_native(cls, args: T.List[str]) -> T.List[str]: + return args[:] + + @classmethod + def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: + return args[:] + + def get_link_debugfile_name(self, targetfile: str) -> T.Optional[str]: + return None + + def get_link_debugfile_args(self, targetfile: str) -> T.List[str]: + # Static libraries do not have PDB files + return [] + + def get_always_args(self) -> T.List[str]: + return [] + + def get_linker_always_args(self) -> T.List[str]: + return [] + + def rsp_file_syntax(self) -> RSPFileSyntax: + """The format of the RSP file that this compiler supports. + + If `self.can_linker_accept_rsp()` returns True, then this needs to + be implemented + """ + assert not self.can_linker_accept_rsp(), f'{self.id} linker accepts RSP, but doesn\' provide a supported format, this is a bug' + raise EnvironmentException(f'{self.id} does not implement rsp format, this shouldn\'t be called') + + +class DynamicLinker(metaclass=abc.ABCMeta): + + """Base class for dynamic linkers.""" + + _OPTIMIZATION_ARGS: T.Dict[str, T.List[str]] = { + 'plain': [], + '0': [], + 'g': [], + '1': [], + '2': [], + '3': [], + 's': [], + } + + @abc.abstractproperty + def id(self) -> str: + pass + + def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]: + args = [arg] if isinstance(arg, str) else arg + if self.prefix_arg is None: + return args + elif isinstance(self.prefix_arg, str): + return [self.prefix_arg + arg for arg in args] + ret: T.List[str] = [] + for arg in args: + ret += self.prefix_arg + [arg] + return ret + + def __init__(self, exelist: T.List[str], + for_machine: mesonlib.MachineChoice, prefix_arg: T.Union[str, T.List[str]], + always_args: T.List[str], *, system: str = 'unknown system', + version: str = 'unknown version'): + self.exelist = exelist + self.for_machine = for_machine + self.system = system + self.version = version + self.prefix_arg = prefix_arg + self.always_args = always_args + self.machine: T.Optional[str] = None + + def __repr__(self) -> str: + return '<{}: v{} `{}`>'.format(type(self).__name__, self.version, ' '.join(self.exelist)) + + def get_id(self) -> str: + return self.id + + def get_exe(self) -> str: + return self.exelist[0] + + def get_version_string(self) -> str: + return f'({self.id} {self.version})' + + def get_exelist(self) -> T.List[str]: + return self.exelist.copy() + + def get_accepts_rsp(self) -> bool: + # rsp files are only used when building on Windows because we want to + # avoid issues with quoting and max argument length + return mesonlib.is_windows() + + def rsp_file_syntax(self) -> RSPFileSyntax: + """The format of the RSP file that this compiler supports. + + If `self.can_linker_accept_rsp()` returns True, then this needs to + be implemented + """ + return RSPFileSyntax.GCC + + def get_always_args(self) -> T.List[str]: + return self.always_args.copy() + + def get_lib_prefix(self) -> str: + return '' + + # XXX: is use_ldflags a compiler or a linker attribute? + + def get_option_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + return [] + + def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + return [] + + def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + raise EnvironmentException(f'Language {self.id} does not support has_multi_link_arguments.') + + def get_debugfile_name(self, targetfile: str) -> T.Optional[str]: + '''Name of debug file written out (see below)''' + return None + + def get_debugfile_args(self, targetfile: str) -> T.List[str]: + """Some compilers (MSVC) write debug into a separate file. + + This method takes the target object path and returns a list of + commands to append to the linker invocation to control where that + file is written. + """ + return [] + + def get_optimization_link_args(self, optimization_level: str) -> T.List[str]: + # We can override these in children by just overriding the + # _OPTIMIZATION_ARGS value. + return mesonlib.listify([self._apply_prefix(a) for a in self._OPTIMIZATION_ARGS[optimization_level]]) + + def get_std_shared_lib_args(self) -> T.List[str]: + return [] + + def get_std_shared_module_args(self, Target: 'BuildTarget') -> T.List[str]: + return self.get_std_shared_lib_args() + + def get_pie_args(self) -> T.List[str]: + # TODO: this really needs to take a boolean and return the args to + # disable pie, otherwise it only acts to enable pie if pie *isn't* the + # default. + raise EnvironmentException(f'Linker {self.id} does not support position-independent executable') + + def get_lto_args(self) -> T.List[str]: + return [] + + def get_thinlto_cache_args(self, path: str) -> T.List[str]: + return [] + + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: + return [] + + def get_asneeded_args(self) -> T.List[str]: + return [] + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + raise EnvironmentException( + f'Linker {self.id} does not support link_whole') + + def get_allow_undefined_args(self) -> T.List[str]: + raise EnvironmentException( + f'Linker {self.id} does not support allow undefined') + + @abc.abstractmethod + def get_output_args(self, outputname: str) -> T.List[str]: + pass + + def get_coverage_args(self) -> T.List[str]: + raise EnvironmentException(f"Linker {self.id} doesn't implement coverage data generation.") + + @abc.abstractmethod + def get_search_args(self, dirname: str) -> T.List[str]: + pass + + def export_dynamic_args(self, env: 'Environment') -> T.List[str]: + return [] + + def import_library_args(self, implibname: str) -> T.List[str]: + """The name of the outputted import library. + + This implementation is used only on Windows by compilers that use GNU ld + """ + return [] + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def no_undefined_args(self) -> T.List[str]: + """Arguments to error if there are any undefined symbols at link time. + + This is the inverse of get_allow_undefined_args(). + + TODO: A future cleanup might merge this and + get_allow_undefined_args() into a single method taking a + boolean + """ + return [] + + def fatal_warnings(self) -> T.List[str]: + """Arguments to make all warnings errors.""" + return [] + + def headerpad_args(self) -> T.List[str]: + # Only used by the Apple linker + return [] + + def get_win_subsystem_args(self, value: str) -> T.List[str]: + # Only used if supported by the dynamic linker and + # only when targeting Windows + return [] + + def bitcode_args(self) -> T.List[str]: + raise MesonException('This linker does not support bitcode bundles') + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + return [] + + def get_archive_name(self, filename: str) -> str: + #Only used by AIX. + return str() + + def get_command_to_archive_shlib(self) -> T.List[str]: + #Only used by AIX. + return [] + + +if T.TYPE_CHECKING: + StaticLinkerBase = StaticLinker + DynamicLinkerBase = DynamicLinker +else: + StaticLinkerBase = DynamicLinkerBase = object + + +class VisualStudioLikeLinker(StaticLinkerBase): + always_args = ['/NOLOGO'] + + def __init__(self, machine: str): + self.machine = machine + + def get_always_args(self) -> T.List[str]: + return self.always_args.copy() + + def get_linker_always_args(self) -> T.List[str]: + return self.always_args.copy() + + def get_output_args(self, target: str) -> T.List[str]: + args: T.List[str] = [] + if self.machine: + args += ['/MACHINE:' + self.machine] + args += ['/OUT:' + target] + return args + + @classmethod + def unix_args_to_native(cls, args: T.List[str]) -> T.List[str]: + from ..compilers.c import VisualStudioCCompiler + return VisualStudioCCompiler.unix_args_to_native(args) + + @classmethod + def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: + from ..compilers.c import VisualStudioCCompiler + return VisualStudioCCompiler.native_args_to_unix(args) + + def rsp_file_syntax(self) -> RSPFileSyntax: + return RSPFileSyntax.MSVC + + +class VisualStudioLinker(VisualStudioLikeLinker, StaticLinker): + + """Microsoft's lib static linker.""" + + id = 'lib' + + def __init__(self, exelist: T.List[str], machine: str): + StaticLinker.__init__(self, exelist) + VisualStudioLikeLinker.__init__(self, machine) + + +class IntelVisualStudioLinker(VisualStudioLikeLinker, StaticLinker): + + """Intel's xilib static linker.""" + + id = 'xilib' + + def __init__(self, exelist: T.List[str], machine: str): + StaticLinker.__init__(self, exelist) + VisualStudioLikeLinker.__init__(self, machine) + + +class ArLinker(ArLikeLinker, StaticLinker): + id = 'ar' + + def __init__(self, for_machine: mesonlib.MachineChoice, exelist: T.List[str]): + super().__init__(exelist) + stdo = mesonlib.Popen_safe(self.exelist + ['-h'])[1] + # Enable deterministic builds if they are available. + stdargs = 'csr' + thinargs = '' + if '[D]' in stdo: + stdargs += 'D' + if '[T]' in stdo: + thinargs = 'T' + self.std_args = [stdargs] + self.std_thin_args = [stdargs + thinargs] + self.can_rsp = '@<' in stdo + self.for_machine = for_machine + + def can_linker_accept_rsp(self) -> bool: + return self.can_rsp + + def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]: + # Thin archives are a GNU extension not supported by the system linkers + # on Mac OS X, Solaris, or illumos, so don't build them on those OSes. + # OS X ld rejects with: "file built for unknown-unsupported file format" + # illumos/Solaris ld rejects with: "unknown file type" + if is_thin and not env.machines[self.for_machine].is_darwin() \ + and not env.machines[self.for_machine].is_sunos(): + return self.std_thin_args + else: + return self.std_args + + +class AppleArLinker(ArLinker): + + # mostly this is used to determine that we need to call ranlib + + id = 'applear' + + +class ArmarLinker(ArLikeLinker, StaticLinker): + id = 'armar' + + +class DLinker(StaticLinker): + def __init__(self, exelist: T.List[str], arch: str, *, rsp_syntax: RSPFileSyntax = RSPFileSyntax.GCC): + super().__init__(exelist) + self.id = exelist[0] + self.arch = arch + self.__rsp_syntax = rsp_syntax + + def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]: + return ['-lib'] + + def get_output_args(self, target: str) -> T.List[str]: + return ['-of=' + target] + + def get_linker_always_args(self) -> T.List[str]: + if mesonlib.is_windows(): + if self.arch == 'x86_64': + return ['-m64'] + elif self.arch == 'x86_mscoff' and self.id == 'dmd': + return ['-m32mscoff'] + return ['-m32'] + return [] + + def rsp_file_syntax(self) -> RSPFileSyntax: + return self.__rsp_syntax + + +class CcrxLinker(StaticLinker): + + def __init__(self, exelist: T.List[str]): + super().__init__(exelist) + self.id = 'rlink' + + def can_linker_accept_rsp(self) -> bool: + return False + + def get_output_args(self, target: str) -> T.List[str]: + return [f'-output={target}'] + + def get_linker_always_args(self) -> T.List[str]: + return ['-nologo', '-form=library'] + + +class Xc16Linker(StaticLinker): + + def __init__(self, exelist: T.List[str]): + super().__init__(exelist) + self.id = 'xc16-ar' + + def can_linker_accept_rsp(self) -> bool: + return False + + def get_output_args(self, target: str) -> T.List[str]: + return [f'{target}'] + + def get_linker_always_args(self) -> T.List[str]: + return ['rcs'] + +class CompCertLinker(StaticLinker): + + def __init__(self, exelist: T.List[str]): + super().__init__(exelist) + self.id = 'ccomp' + + def can_linker_accept_rsp(self) -> bool: + return False + + def get_output_args(self, target: str) -> T.List[str]: + return [f'-o{target}'] + + +class TILinker(StaticLinker): + + def __init__(self, exelist: T.List[str]): + super().__init__(exelist) + self.id = 'ti-ar' + + def can_linker_accept_rsp(self) -> bool: + return False + + def get_output_args(self, target: str) -> T.List[str]: + return [f'{target}'] + + def get_linker_always_args(self) -> T.List[str]: + return ['-r'] + + +class C2000Linker(TILinker): + # Required for backwards compat with projects created before ti-cgt support existed + id = 'ar2000' + +class C6000Linker(TILinker): + id = 'ar6000' + + +class AIXArLinker(ArLikeLinker, StaticLinker): + id = 'aixar' + std_args = ['-csr', '-Xany'] + + +class MetrowerksStaticLinker(StaticLinker): + + def can_linker_accept_rsp(self) -> bool: + return True + + def get_linker_always_args(self) -> T.List[str]: + return ['-library'] + + def get_output_args(self, target: str) -> T.List[str]: + return ['-o', target] + + def rsp_file_syntax(self) -> RSPFileSyntax: + return RSPFileSyntax.GCC + + +class MetrowerksStaticLinkerARM(MetrowerksStaticLinker): + id = 'mwldarm' + + +class MetrowerksStaticLinkerEmbeddedPowerPC(MetrowerksStaticLinker): + id = 'mwldeppc' + +class TaskingStaticLinker(StaticLinker): + id = 'tasking' + + def __init__(self, exelist: T.List[str]): + super().__init__(exelist) + + def can_linker_accept_rsp(self) -> bool: + return True + + def rsp_file_syntax(self) -> RSPFileSyntax: + return RSPFileSyntax.TASKING + + def get_output_args(self, target: str) -> T.List[str]: + return ['-n', target] + + def get_linker_always_args(self) -> T.List[str]: + return ['-r'] + +def prepare_rpaths(raw_rpaths: T.Tuple[str, ...], build_dir: str, from_dir: str) -> T.List[str]: + # The rpaths we write must be relative if they point to the build dir, + # because otherwise they have different length depending on the build + # directory. This breaks reproducible builds. + internal_format_rpaths = [evaluate_rpath(p, build_dir, from_dir) for p in raw_rpaths] + ordered_rpaths = order_rpaths(internal_format_rpaths) + return ordered_rpaths + + +def order_rpaths(rpath_list: T.List[str]) -> T.List[str]: + # We want rpaths that point inside our build dir to always override + # those pointing to other places in the file system. This is so built + # binaries prefer our libraries to the ones that may lie somewhere + # in the file system, such as /lib/x86_64-linux-gnu. + # + # The correct thing to do here would be C++'s std::stable_partition. + # Python standard library does not have it, so replicate it with + # sort, which is guaranteed to be stable. + return sorted(rpath_list, key=os.path.isabs) + + +def evaluate_rpath(p: str, build_dir: str, from_dir: str) -> str: + if p == from_dir: + return '' # relpath errors out in this case + elif os.path.isabs(p): + return p # These can be outside of build dir. + else: + return os.path.relpath(os.path.join(build_dir, p), os.path.join(build_dir, from_dir)) + + +class PosixDynamicLinkerMixin(DynamicLinkerBase): + + """Mixin class for POSIX-ish linkers. + + This is obviously a pretty small subset of the linker interface, but + enough dynamic linkers that meson supports are POSIX-like but not + GNU-like that it makes sense to split this out. + """ + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + def get_std_shared_lib_args(self) -> T.List[str]: + return ['-shared'] + + def get_search_args(self, dirname: str) -> T.List[str]: + return ['-L' + dirname] + + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: + return [] + + +class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): + + """Mixin class for dynamic linkers that provides gnu-like interface. + + This acts as a base for the GNU linkers (bfd and gold), LLVM's lld, and + other linkers like GNU-ld. + """ + + if T.TYPE_CHECKING: + for_machine = MachineChoice.HOST + def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]: ... + + _OPTIMIZATION_ARGS: T.Dict[str, T.List[str]] = { + 'plain': [], + '0': [], + 'g': [], + '1': [], + '2': [], + '3': ['-O1'], + 's': [], + } + + _SUBSYSTEMS: T.Dict[str, str] = { + "native": "1", + "windows": "windows", + "console": "console", + "posix": "7", + "efi_application": "10", + "efi_boot_service_driver": "11", + "efi_runtime_driver": "12", + "efi_rom": "13", + "boot_application": "16", + } + + def get_accepts_rsp(self) -> bool: + return True + + def get_pie_args(self) -> T.List[str]: + return ['-pie'] + + def get_asneeded_args(self) -> T.List[str]: + return self._apply_prefix('--as-needed') + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + if not args: + return args + return self._apply_prefix('--whole-archive') + args + self._apply_prefix('--no-whole-archive') + + def get_allow_undefined_args(self) -> T.List[str]: + return self._apply_prefix('--allow-shlib-undefined') + + def get_lto_args(self) -> T.List[str]: + return ['-flto'] + + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: + if not value: + return value + return [f'-fsanitize={",".join(value)}'] + + def get_coverage_args(self) -> T.List[str]: + return ['--coverage'] + + def export_dynamic_args(self, env: 'Environment') -> T.List[str]: + m = env.machines[self.for_machine] + if m.is_windows() or m.is_cygwin(): + return self._apply_prefix('--export-all-symbols') + return self._apply_prefix('-export-dynamic') + + def import_library_args(self, implibname: str) -> T.List[str]: + return self._apply_prefix('--out-implib=' + implibname) + + def thread_flags(self, env: 'Environment') -> T.List[str]: + if env.machines[self.for_machine].is_haiku(): + return [] + return ['-pthread'] + + def no_undefined_args(self) -> T.List[str]: + return self._apply_prefix('--no-undefined') + + def fatal_warnings(self) -> T.List[str]: + # return self._apply_prefix('--fatal-warnings') + return [] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + m = env.machines[self.for_machine] + if m.is_windows() or m.is_cygwin(): + # For PE/COFF the soname argument has no effect + return [] + sostr = '' if soversion is None else '.' + soversion + return self._apply_prefix(f'-soname,{prefix}{shlib_name}.{suffix}{sostr}') + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + m = env.machines[self.for_machine] + if m.is_windows() or m.is_cygwin(): + return ([], set()) + if not rpath_paths and not install_rpath and not build_rpath: + return ([], set()) + args: T.List[str] = [] + origin_placeholder = '$ORIGIN' + processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) + # Need to deduplicate rpaths, as macOS's install_name_tool + # is *very* allergic to duplicate -delete_rpath arguments + # when calling depfixer on installation. + all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) + rpath_dirs_to_remove: T.Set[bytes] = set() + for p in all_paths: + rpath_dirs_to_remove.add(p.encode('utf8')) + # Build_rpath is used as-is (it is usually absolute). + if build_rpath != '': + all_paths.add(build_rpath) + for p in build_rpath.split(':'): + rpath_dirs_to_remove.add(p.encode('utf8')) + + # TODO: should this actually be "for (dragonfly|open)bsd"? + if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd(): + # This argument instructs the compiler to record the value of + # ORIGIN in the .dynamic section of the elf. On Linux this is done + # by default, but is not on dragonfly/openbsd for some reason. Without this + # $ORIGIN in the runtime path will be undefined and any binaries + # linked against local libraries will fail to resolve them. + args.extend(self._apply_prefix('-z,origin')) + + # In order to avoid relinking for RPATH removal, the binary needs to contain just + # enough space in the ELF header to hold the final installation RPATH. + paths = ':'.join(all_paths) + paths_length = len(paths.encode('utf-8')) + install_rpath_length = len(install_rpath.encode('utf-8')) + if paths_length < install_rpath_length: + padding = 'X' * (install_rpath_length - paths_length) + if not paths: + paths = padding + else: + paths = paths + ':' + padding + args.extend(self._apply_prefix('-rpath,' + paths)) + + # TODO: should this actually be "for solaris/sunos"? + # NOTE: Remove the zigcc check once zig support "-rpath-link" + # See https://github.com/ziglang/zig/issues/18713 + if mesonlib.is_sunos() or self.id == 'ld.zigcc': + return (args, rpath_dirs_to_remove) + + # Rpaths to use while linking must be absolute. These are not + # written to the binary. Needed only with GNU ld, and only for + # versions before 2.28: + # https://sourceware.org/bugzilla/show_bug.cgi?id=20535 + # https://sourceware.org/bugzilla/show_bug.cgi?id=16936 + # Not needed on Windows or other platforms that don't use RPATH + # https://github.com/mesonbuild/meson/issues/1897 + # + # In 2.28 and on, $ORIGIN tokens inside of -rpath are respected, + # so we do not need to duplicate it in -rpath-link. + # + # In addition, this linker option tends to be quite long and some + # compilers have trouble dealing with it. That's why we will include + # one option per folder, like this: + # + # -Wl,-rpath-link,/path/to/folder1 -Wl,-rpath,/path/to/folder2 ... + # + # ...instead of just one single looooong option, like this: + # + # -Wl,-rpath-link,/path/to/folder1:/path/to/folder2:... + if self.id in {'ld.bfd', 'ld.gold'} and mesonlib.version_compare(self.version, '<2.28'): + for p in rpath_paths: + args.extend(self._apply_prefix('-rpath-link,' + os.path.join(build_dir, p))) + + return (args, rpath_dirs_to_remove) + + def get_win_subsystem_args(self, value: str) -> T.List[str]: + # MinGW only directly supports a couple of the possible + # PE application types. The raw integer works as an argument + # as well, and is always accepted, so we manually map the + # other types here. List of all types: + # https://github.com/wine-mirror/wine/blob/3ded60bd1654dc689d24a23305f4a93acce3a6f2/include/winnt.h#L2492-L2507 + versionsuffix = None + if ',' in value: + value, versionsuffix = value.split(',', 1) + newvalue = self._SUBSYSTEMS.get(value) + if newvalue is not None: + if versionsuffix is not None: + newvalue += f':{versionsuffix}' + args = [f'--subsystem,{newvalue}'] + else: + raise mesonlib.MesonBugException(f'win_subsystem: {value!r} not handled in MinGW linker. This should not be possible.') + + return self._apply_prefix(args) + + +class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): + + """Apple's ld implementation.""" + + id = 'ld64' + + def get_asneeded_args(self) -> T.List[str]: + return self._apply_prefix('-dead_strip_dylibs') + + def get_allow_undefined_args(self) -> T.List[str]: + # iOS doesn't allow undefined symbols when linking + if self.system == 'ios': + return [] + else: + return self._apply_prefix('-undefined,dynamic_lookup') + + def get_std_shared_module_args(self, target: 'BuildTarget') -> T.List[str]: + if self.system == 'ios': + return ['-dynamiclib'] + else: + return ['-bundle'] + self.get_allow_undefined_args() + + def get_pie_args(self) -> T.List[str]: + return [] + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + result: T.List[str] = [] + for a in args: + result.extend(self._apply_prefix('-force_load')) + result.append(a) + return result + + def get_coverage_args(self) -> T.List[str]: + return ['--coverage'] + + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: + if not value: + return value + return [f'-fsanitize={",".join(value)}'] + + def no_undefined_args(self) -> T.List[str]: + # We used to emit -undefined,error, but starting with Xcode 15 / + # Sonoma, doing so triggers "ld: warning: -undefined error is + # deprecated". Given that "-undefined error" is documented to be the + # linker's default behaviour, this warning seems ill advised. However, + # it does create a lot of noise. As "-undefined error" is the default + # behaviour, the least bad way to deal with this seems to be to just + # not emit anything here. Of course that only works as long as nothing + # else injects -undefined dynamic_lookup, or such. Complain to Apple. + return [] + + def headerpad_args(self) -> T.List[str]: + return self._apply_prefix('-headerpad_max_install_names') + + def bitcode_args(self) -> T.List[str]: + return self._apply_prefix('-bitcode_bundle') + + def fatal_warnings(self) -> T.List[str]: + return self._apply_prefix('-fatal_warnings') + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + install_name = ['@rpath/', prefix, shlib_name] + if soversion is not None: + install_name.append('.' + soversion) + install_name.append('.' + suffix) + args = ['-install_name', ''.join(install_name)] + if darwin_versions: + args.extend(['-compatibility_version', darwin_versions[0], + '-current_version', darwin_versions[1]]) + return args + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + if not rpath_paths and not install_rpath and not build_rpath: + return ([], set()) + args: T.List[str] = [] + rpath_dirs_to_remove: T.Set[bytes] = set() + # @loader_path is the equivalent of $ORIGIN on macOS + # https://stackoverflow.com/q/26280738 + origin_placeholder = '@loader_path' + processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) + all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) + if build_rpath != '': + all_paths.update(build_rpath.split(':')) + for rp in all_paths: + rpath_dirs_to_remove.add(rp.encode('utf8')) + args.extend(self._apply_prefix('-rpath,' + rp)) + + return (args, rpath_dirs_to_remove) + + def get_thinlto_cache_args(self, path: str) -> T.List[str]: + return ["-Wl,-cache_path_lto," + path] + + def export_dynamic_args(self, env: 'Environment') -> T.List[str]: + if mesonlib.version_compare(self.version, '>=224.1'): + return self._apply_prefix('-export_dynamic') + return [] + + +class LLVMLD64DynamicLinker(AppleDynamicLinker): + + id = 'ld64.lld' + + +class GnuDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): + + """Representation of GNU ld.bfd and ld.gold.""" + + +class GnuGoldDynamicLinker(GnuDynamicLinker): + + id = 'ld.gold' + + def get_thinlto_cache_args(self, path: str) -> T.List[str]: + return ['-Wl,-plugin-opt,cache-dir=' + path] + + +class GnuBFDDynamicLinker(GnuDynamicLinker): + + id = 'ld.bfd' + + +class MoldDynamicLinker(GnuDynamicLinker): + + id = 'ld.mold' + + def get_thinlto_cache_args(self, path: str) -> T.List[str]: + return ['-Wl,--thinlto-cache-dir=' + path] + + +class LLVMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): + + """Representation of LLVM's ld.lld linker. + + This is only the gnu-like linker, not the apple like or link.exe like + linkers. + """ + + id = 'ld.lld' + + def __init__(self, exelist: T.List[str], + for_machine: mesonlib.MachineChoice, prefix_arg: T.Union[str, T.List[str]], + always_args: T.List[str], *, system: str = 'unknown system', + version: str = 'unknown version'): + super().__init__(exelist, for_machine, prefix_arg, always_args, system=system, version=version) + + # Some targets don't seem to support this argument (windows, wasm, ...) + self.has_allow_shlib_undefined = self._supports_flag('--allow-shlib-undefined', always_args) + # These aren't supported by TI Arm Clang + self.has_as_needed = self._supports_flag('--as-needed', always_args) + self.has_no_undefined = self._supports_flag('--no-undefined', always_args) + + def _supports_flag(self, flag: str, always_args: T.List[str]) -> bool: + _, _, e = mesonlib.Popen_safe(self.exelist + always_args + self._apply_prefix(flag)) + return ( + # Versions < 9 do not have a quoted argument + (f'unknown argument: {flag}' not in e) and + (f"unknown argument: '{flag}'" not in e) and + # TI Arm Clang uses a different message + (f'invalid option: {flag}' not in e) + ) + + def get_allow_undefined_args(self) -> T.List[str]: + if self.has_allow_shlib_undefined: + return self._apply_prefix('--allow-shlib-undefined') + return [] + + def get_asneeded_args(self) -> T.List[str]: + if self.has_as_needed: + return self._apply_prefix('--as-needed') + return [] + + def no_undefined_args(self) -> T.List[str]: + if self.has_no_undefined: + return self._apply_prefix('--no-undefined') + return [] + + def get_thinlto_cache_args(self, path: str) -> T.List[str]: + return ['-Wl,--thinlto-cache-dir=' + path] + + def get_win_subsystem_args(self, value: str) -> T.List[str]: + # lld does not support a numeric subsystem value + version = None + if ',' in value: + value, version = value.split(',', 1) + if value in self._SUBSYSTEMS: + if version is not None: + value += f':{version}' + return self._apply_prefix([f'--subsystem,{value}']) + else: + raise mesonlib.MesonBugException(f'win_subsystem: {value} not handled in lld linker. This should not be possible.') + + +class ZigCCDynamicLinker(LLVMDynamicLinker): + id = 'ld.zigcc' + + def get_thinlto_cache_args(self, path: str) -> T.List[str]: + return [] + + +class WASMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): + + """Emscripten's wasm-ld.""" + + id = 'ld.wasm' + + def get_allow_undefined_args(self) -> T.List[str]: + return ['-sERROR_ON_UNDEFINED_SYMBOLS=0'] + + def no_undefined_args(self) -> T.List[str]: + return ['-sERROR_ON_UNDEFINED_SYMBOLS=1'] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + # raise MesonException(f'{self.id} WHO does not support shared libraries.') + return ['-sSIDE_MODULE=1','--no-fatal-warnings','-Wl,--no-fatal-warnings'] + + def get_asneeded_args(self) -> T.List[str]: + return [] + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str="", + install_rpath: str="") -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) + + +class CcrxDynamicLinker(DynamicLinker): + + """Linker for Renesas CCrx compiler.""" + + id = 'rlink' + + def __init__(self, for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + super().__init__(['rlink.exe'], for_machine, '', [], + version=version) + + def get_accepts_rsp(self) -> bool: + return False + + def get_lib_prefix(self) -> str: + return '-lib=' + + def get_std_shared_lib_args(self) -> T.List[str]: + return [] + + def get_output_args(self, outputname: str) -> T.List[str]: + return [f'-output={outputname}'] + + def get_search_args(self, dirname: str) -> 'T.NoReturn': + raise OSError('rlink.exe does not have a search dir argument') + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + return [] + + +class Xc16DynamicLinker(DynamicLinker): + + """Linker for Microchip XC16 compiler.""" + + id = 'xc16-gcc' + + def __init__(self, for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + super().__init__(['xc16-gcc'], for_machine, '', [], + version=version) + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + if len(args) < 2: + return args + return self._apply_prefix('--start-group') + args + self._apply_prefix('--end-group') + + def get_accepts_rsp(self) -> bool: + return False + + def get_lib_prefix(self) -> str: + return '' + + def get_std_shared_lib_args(self) -> T.List[str]: + return [] + + def get_output_args(self, outputname: str) -> T.List[str]: + return [f'-o{outputname}'] + + def get_search_args(self, dirname: str) -> 'T.NoReturn': + raise OSError('xc16-gcc does not have a search dir argument') + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + return [] + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) + +class CompCertDynamicLinker(DynamicLinker): + + """Linker for CompCert C compiler.""" + + id = 'ccomp' + + def __init__(self, for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + super().__init__(['ccomp'], for_machine, '', [], + version=version) + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + if not args: + return args + return self._apply_prefix('-Wl,--whole-archive') + args + self._apply_prefix('-Wl,--no-whole-archive') + + def get_accepts_rsp(self) -> bool: + return False + + def get_lib_prefix(self) -> str: + return '' + + def get_std_shared_lib_args(self) -> T.List[str]: + return [] + + def get_output_args(self, outputname: str) -> T.List[str]: + return [f'-o{outputname}'] + + def get_search_args(self, dirname: str) -> T.List[str]: + return [f'-L{dirname}'] + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + raise MesonException(f'{self.id} ARE does not support shared libraries.') + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) + +class TIDynamicLinker(DynamicLinker): + + """Linker for Texas Instruments compiler family.""" + + id = 'ti' + + def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + super().__init__(exelist, for_machine, '', [], + version=version) + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + if len(args) < 2: + return args + return self._apply_prefix('--start-group') + args + self._apply_prefix('--end-group') + + def get_accepts_rsp(self) -> bool: + return False + + def get_lib_prefix(self) -> str: + return '-l=' + + def get_std_shared_lib_args(self) -> T.List[str]: + return [] + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-z', f'--output_file={outputname}'] + + def get_search_args(self, dirname: str) -> 'T.NoReturn': + raise OSError('TI compilers do not have a search dir argument') + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_always_args(self) -> T.List[str]: + return [] + + +class C2000DynamicLinker(TIDynamicLinker): + # Required for backwards compat with projects created before ti-cgt support existed + id = 'cl2000' + +class C6000DynamicLinker(TIDynamicLinker): + id = 'cl6000' + + +class ArmDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): + + """Linker for the ARM compiler.""" + + id = 'armlink' + + def __init__(self, for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + super().__init__(['armlink'], for_machine, '', [], + version=version) + + def get_accepts_rsp(self) -> bool: + return False + + def get_std_shared_lib_args(self) -> 'T.NoReturn': + raise MesonException('The Arm Linkers do not support shared libraries') + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + +class ArmClangDynamicLinker(ArmDynamicLinker): + + """Linker used with ARM's clang fork. + + The interface is similar enough to the old ARM ld that it inherits and + extends a few things as needed. + """ + + def export_dynamic_args(self, env: 'Environment') -> T.List[str]: + return ['--export_dynamic'] + + def import_library_args(self, implibname: str) -> T.List[str]: + return ['--symdefs=' + implibname] + +class QualcommLLVMDynamicLinker(LLVMDynamicLinker): + + """ARM Linker from Snapdragon LLVM ARM Compiler.""" + + id = 'ld.qcld' + + +class NAGDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): + + """NAG Fortran linker, ld via gcc indirection. + + Using nagfor -Wl,foo passes option foo to a backend gcc invocation. + (This linking gathers the correct objects needed from the nagfor runtime + system.) + To pass gcc -Wl,foo options (i.e., to ld) one must apply indirection + again: nagfor -Wl,-Wl,,foo + """ + + id = 'nag' + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + if not rpath_paths and not install_rpath and not build_rpath: + return ([], set()) + args: T.List[str] = [] + origin_placeholder = '$ORIGIN' + processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) + all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) + if build_rpath != '': + all_paths.add(build_rpath) + for rp in all_paths: + args.extend(self._apply_prefix('-Wl,-Wl,,-rpath,,' + rp)) + + return (args, set()) + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_std_shared_lib_args(self) -> T.List[str]: + from ..compilers.fortran import NAGFortranCompiler + return NAGFortranCompiler.get_nagfor_quiet(self.version) + ['-Wl,-shared'] + + +class PGIDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): + + """PGI linker.""" + + id = 'pgi' + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + return [] + + def get_std_shared_lib_args(self) -> T.List[str]: + # PGI -shared is Linux only. + if mesonlib.is_windows(): + return ['-Bdynamic', '-Mmakedll'] + elif mesonlib.is_linux(): + return ['-shared'] + return [] + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + if not env.machines[self.for_machine].is_windows(): + return (['-R' + os.path.join(build_dir, p) for p in rpath_paths], set()) + return ([], set()) + +NvidiaHPC_DynamicLinker = PGIDynamicLinker + + +class PGIStaticLinker(StaticLinker): + def __init__(self, exelist: T.List[str]): + super().__init__(exelist) + self.id = 'ar' + self.std_args = ['-r'] + + def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]: + return self.std_args + + def get_output_args(self, target: str) -> T.List[str]: + return [target] + +NvidiaHPC_StaticLinker = PGIStaticLinker + + +class VisualStudioLikeLinkerMixin(DynamicLinkerBase): + + """Mixin class for dynamic linkers that act like Microsoft's link.exe.""" + + if T.TYPE_CHECKING: + for_machine = MachineChoice.HOST + def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]: ... + + _OPTIMIZATION_ARGS: T.Dict[str, T.List[str]] = { + 'plain': [], + '0': [], + 'g': [], + '1': [], + '2': [], + # The otherwise implicit REF and ICF linker optimisations are disabled by + # /DEBUG. REF implies ICF. + '3': ['/OPT:REF'], + 's': ['/INCREMENTAL:NO', '/OPT:REF'], + } + + def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + prefix_arg: T.Union[str, T.List[str]], always_args: T.List[str], *, + version: str = 'unknown version', direct: bool = True, machine: str = 'x86', + rsp_syntax: RSPFileSyntax = RSPFileSyntax.MSVC): + # There's no way I can find to make mypy understand what's going on here + super().__init__(exelist, for_machine, prefix_arg, always_args, version=version) + self.machine = machine + self.direct = direct + self.rsp_syntax = rsp_syntax + + def invoked_by_compiler(self) -> bool: + return not self.direct + + def get_output_args(self, outputname: str) -> T.List[str]: + return self._apply_prefix(['/MACHINE:' + self.machine, '/OUT:' + outputname]) + + def get_always_args(self) -> T.List[str]: + parent = super().get_always_args() + return self._apply_prefix('/nologo') + parent + + def get_search_args(self, dirname: str) -> T.List[str]: + return self._apply_prefix('/LIBPATH:' + dirname) + + def get_std_shared_lib_args(self) -> T.List[str]: + return self._apply_prefix('/DLL') + + def get_debugfile_name(self, targetfile: str) -> str: + return targetfile + + def get_debugfile_args(self, targetfile: str) -> T.List[str]: + return self._apply_prefix(['/DEBUG', '/PDB:' + self.get_debugfile_name(targetfile)]) + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + # Only since VS2015 + args = mesonlib.listify(args) + l: T.List[str] = [] + for a in args: + l.extend(self._apply_prefix('/WHOLEARCHIVE:' + a)) + return l + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + return [] + + def import_library_args(self, implibname: str) -> T.List[str]: + """The command to generate the import library.""" + return self._apply_prefix(['/IMPLIB:' + implibname]) + + def rsp_file_syntax(self) -> RSPFileSyntax: + return self.rsp_syntax + + def get_pie_args(self) -> T.List[str]: + return [] + + +class MSVCDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): + + """Microsoft's Link.exe.""" + + id = 'link' + + def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, + exelist: T.Optional[T.List[str]] = None, + prefix: T.Union[str, T.List[str]] = '', + machine: str = 'x86', version: str = 'unknown version', + direct: bool = True, rsp_syntax: RSPFileSyntax = RSPFileSyntax.MSVC): + super().__init__(exelist or ['link.exe'], for_machine, + prefix, always_args, machine=machine, version=version, direct=direct, + rsp_syntax=rsp_syntax) + + def get_always_args(self) -> T.List[str]: + return self._apply_prefix(['/release']) + super().get_always_args() + + def get_win_subsystem_args(self, value: str) -> T.List[str]: + return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}']) + + def fatal_warnings(self) -> T.List[str]: + return ['-WX'] + + +class ClangClDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): + + """Clang's lld-link.exe.""" + + id = 'lld-link' + + def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, + exelist: T.Optional[T.List[str]] = None, + prefix: T.Union[str, T.List[str]] = '', + machine: str = 'x86', version: str = 'unknown version', + direct: bool = True, rsp_syntax: RSPFileSyntax = RSPFileSyntax.MSVC): + super().__init__(exelist or ['lld-link.exe'], for_machine, + prefix, always_args, machine=machine, version=version, direct=direct, + rsp_syntax=rsp_syntax) + + def get_output_args(self, outputname: str) -> T.List[str]: + # If we're being driven indirectly by clang just skip /MACHINE + # as clang's target triple will handle the machine selection + if self.machine is None: + return self._apply_prefix([f"/OUT:{outputname}"]) + + return super().get_output_args(outputname) + + def get_win_subsystem_args(self, value: str) -> T.List[str]: + return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}']) + + def get_thinlto_cache_args(self, path: str) -> T.List[str]: + return ["/lldltocache:" + path] + + def fatal_warnings(self) -> T.List[str]: + return ['-WX'] + + +class XilinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): + + """Intel's Xilink.exe.""" + + id = 'xilink' + + def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, + exelist: T.Optional[T.List[str]] = None, + prefix: T.Union[str, T.List[str]] = '', + machine: str = 'x86', version: str = 'unknown version', + direct: bool = True): + super().__init__(['xilink.exe'], for_machine, '', always_args, version=version) + + def get_win_subsystem_args(self, value: str) -> T.List[str]: + return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}']) + + +class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): + + """Sys-V derived linker used on Solaris and OpenSolaris.""" + + id = 'ld.solaris' + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + if not args: + return args + return self._apply_prefix('--whole-archive') + args + self._apply_prefix('--no-whole-archive') + + def get_pie_args(self) -> T.List[str]: + # Available in Solaris 11.2 and later + pc, stdo, stde = mesonlib.Popen_safe(self.exelist + self._apply_prefix('-zhelp')) + for line in (stdo + stde).split('\n'): + if '-z type' in line: + if 'pie' in line: + return ['-z', 'type=pie'] + break + return [] + + def get_asneeded_args(self) -> T.List[str]: + return self._apply_prefix(['-z', 'ignore']) + + def no_undefined_args(self) -> T.List[str]: + return ['-z', 'defs'] + + def get_allow_undefined_args(self) -> T.List[str]: + return ['-z', 'nodefs'] + + def fatal_warnings(self) -> T.List[str]: + return ['-z', 'fatal-warnings'] + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + if not rpath_paths and not install_rpath and not build_rpath: + return ([], set()) + processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) + all_paths = mesonlib.OrderedSet([os.path.join('$ORIGIN', p) for p in processed_rpaths]) + rpath_dirs_to_remove: T.Set[bytes] = set() + for p in all_paths: + rpath_dirs_to_remove.add(p.encode('utf8')) + if build_rpath != '': + all_paths.add(build_rpath) + for p in build_rpath.split(':'): + rpath_dirs_to_remove.add(p.encode('utf8')) + + # In order to avoid relinking for RPATH removal, the binary needs to contain just + # enough space in the ELF header to hold the final installation RPATH. + paths = ':'.join(all_paths) + paths_length = len(paths.encode('utf-8')) + install_rpath_length = len(install_rpath.encode('utf-8')) + if paths_length < install_rpath_length: + padding = 'X' * (install_rpath_length - paths_length) + if not paths: + paths = padding + else: + paths = paths + ':' + padding + return (self._apply_prefix(f'-rpath,{paths}'), rpath_dirs_to_remove) + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + sostr = '' if soversion is None else '.' + soversion + return self._apply_prefix(f'-soname,{prefix}{shlib_name}.{suffix}{sostr}') + + +class AIXDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): + + """Sys-V derived linker used on AIX""" + + id = 'ld.aix' + + def get_always_args(self) -> T.List[str]: + return self._apply_prefix(['-bnoipath', '-bbigtoc']) + super().get_always_args() + + def no_undefined_args(self) -> T.List[str]: + return self._apply_prefix(['-bernotok']) + + def get_allow_undefined_args(self) -> T.List[str]: + return self._apply_prefix(['-berok']) + + def get_archive_name(self, filename: str) -> str: + # In AIX we allow the shared library name to have the lt_version and so_version. + # But the archive name must just be .a . + # For Example shared object can have the name libgio.so.0.7200.1 but the archive + # must have the name libgio.a having libgio.a (libgio.so.0.7200.1) in the + # archive. This regular expression is to do the same. + filename = re.sub('[.][a]([.]?([0-9]+))*([.]?([a-z]+))*', '.a', filename.replace('.so', '.a')) + return filename + + def get_command_to_archive_shlib(self) -> T.List[str]: + # Archive shared library object and remove the shared library object, + # since it already exists in the archive. + command = ['ar', '-r', '-s', '-v', '$out', '$in', '&&', 'rm', '-f', '$in'] + return command + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + # AIX's linker always links the whole archive: "The ld command + # processes all input files in the same manner, whether they are + # archives or not." + return args + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + all_paths: mesonlib.OrderedSet[str] = mesonlib.OrderedSet() + # install_rpath first, followed by other paths, and the system path last + if install_rpath != '': + all_paths.add(install_rpath) + if build_rpath != '': + all_paths.add(build_rpath) + for p in rpath_paths: + all_paths.add(os.path.join(build_dir, p)) + # We should consider allowing the $LIBPATH environment variable + # to override sys_path. + sys_path = env.get_compiler_system_lib_dirs(self.for_machine) + if len(sys_path) == 0: + # get_compiler_system_lib_dirs doesn't support our compiler. + # Use the default system library path + all_paths.update(['/usr/lib', '/lib']) + else: + # Include the compiler's default library paths, but filter out paths that don't exist + for p in sys_path: + if os.path.isdir(p): + all_paths.add(p) + return (self._apply_prefix('-blibpath:' + ':'.join(all_paths)), set()) + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return ['-pthread'] + + +class OptlinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): + + """Digital Mars dynamic linker for windows.""" + + id = 'optlink' + + def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + # Use optlink instead of link so we don't interfere with other link.exe + # implementations. + super().__init__(exelist, for_machine, '', [], version=version) + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_debugfile_args(self, targetfile: str) -> T.List[str]: + # Optlink does not generate pdb files. + return [] + + def get_always_args(self) -> T.List[str]: + return [] + + +class CudaLinker(PosixDynamicLinkerMixin, DynamicLinker): + """Cuda linker (nvlink)""" + + id = 'nvlink' + + @staticmethod + def parse_version() -> str: + version_cmd = ['nvlink', '--version'] + try: + _, out, _ = mesonlib.Popen_safe(version_cmd) + except OSError: + return 'unknown version' + # Output example: + # nvlink: NVIDIA (R) Cuda linker + # Copyright (c) 2005-2018 NVIDIA Corporation + # Built on Sun_Sep_30_21:09:22_CDT_2018 + # Cuda compilation tools, release 10.0, V10.0.166 + # we need the most verbose version output. Luckily starting with V + return out.strip().rsplit('V', maxsplit=1)[-1] + + def get_accepts_rsp(self) -> bool: + # nvcc does not support response files + return False + + def get_lib_prefix(self) -> str: + # nvcc doesn't recognize Meson's default .a extension for static libraries on + # Windows and passes it to cl as an object file, resulting in 'warning D9024 : + # unrecognized source file type 'xxx.a', object file assumed'. + # + # nvcc's --library= option doesn't help: it takes the library name without the + # extension and assumes that the extension on Windows is .lib; prefixing the + # library with -Xlinker= seems to work. + # + # On Linux, we have to use rely on -Xlinker= too, since nvcc/nvlink chokes on + # versioned shared libraries: + # + # nvcc fatal : Don't know what to do with 'subprojects/foo/libbar.so.0.1.2' + # + from ..compilers.cuda import CudaCompiler + return CudaCompiler.LINKER_PREFIX + + def fatal_warnings(self) -> T.List[str]: + return ['--warning-as-error'] + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + return [] + + +class MetrowerksLinker(DynamicLinker): + + def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + super().__init__(exelist, for_machine, '', [], + version=version) + + def fatal_warnings(self) -> T.List[str]: + return ['-w', 'error'] + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_accepts_rsp(self) -> bool: + return True + + def get_linker_always_args(self) -> T.List[str]: + return [] + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + def get_search_args(self, dirname: str) -> T.List[str]: + return self._apply_prefix('-L' + dirname) + + def invoked_by_compiler(self) -> bool: + return False + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + raise MesonException(f'{self.id} YOU does not support shared libraries.') + + +class MetrowerksLinkerARM(MetrowerksLinker): + id = 'mwldarm' + + +class MetrowerksLinkerEmbeddedPowerPC(MetrowerksLinker): + id = 'mwldeppc' + +class TaskingLinker(DynamicLinker): + id = 'tasking' + + _OPTIMIZATION_ARGS: T.Dict[str, T.List[str]] = { + 'plain': [], + '0': ['-O0'], + 'g': ['-O1'], # There is no debug specific level, O1 is recommended by the compiler + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O2'], # There is no 3rd level optimization for the linker + 's': ['-Os'], + } + + def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + super().__init__(exelist, for_machine, '', [], + version=version) + + def get_accepts_rsp(self) -> bool: + return True + + def get_lib_prefix(self) -> str: + return "" + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def invoked_by_compiler(self) -> bool: + return True + + def get_search_args(self, dirname: str) -> T.List[str]: + return self._apply_prefix('-L' + dirname) + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + def get_lto_args(self) -> T.List[str]: + return ['--mil-link'] + + def rsp_file_syntax(self) -> RSPFileSyntax: + return RSPFileSyntax.TASKING + + def fatal_warnings(self) -> T.List[str]: + """Arguments to make all warnings errors.""" + return self._apply_prefix('--warnings-as-errors') + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + args = mesonlib.listify(args) + l: T.List[str] = [] + for a in args: + l.extend(self._apply_prefix('-Wl--whole-archive=' + a)) + return l diff --git a/recipes/recipes_emscripten/scipy_flang/patches/0001-Remove-duplicate-symbol.patch b/recipes/recipes_emscripten/scipy_flang/patches/0001-Remove-duplicate-symbol.patch new file mode 100644 index 00000000000..b9cbb0652b6 --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/patches/0001-Remove-duplicate-symbol.patch @@ -0,0 +1,62 @@ +From 2d7b53cb55f5bd63c67284094e11079468618a34 Mon Sep 17 00:00:00 2001 +From: Isabel Paredes +Date: Wed, 7 May 2025 17:10:59 +0200 +Subject: [PATCH 1/4] Remove duplicate symbol + +This symbol is defined in openblas +--- + .../linalg/_dsolve/SuperLU/SRC/dcomplex.c | 40 +++++++++---------- + 1 file changed, 20 insertions(+), 20 deletions(-) + +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dcomplex.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dcomplex.c +index 0da48a9..bd4f74a 100644 +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dcomplex.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dcomplex.c +@@ -57,27 +57,27 @@ void z_div(doublecomplex *c, doublecomplex *a, doublecomplex *b) + c->i = ci; + } + +- +-/*! \brief Returns sqrt(z.r^2 + z.i^2) */ +-double z_abs(doublecomplex *z) +-{ +- double temp; +- double real = z->r; +- double imag = z->i; +- +- if (real < 0) real = -real; +- if (imag < 0) imag = -imag; +- if (imag > real) { +- temp = real; +- real = imag; +- imag = temp; +- } +- if ((real+imag) == real) return(real); ++// NOTE: Already defined in openblas ++// /*! \brief Returns sqrt(z.r^2 + z.i^2) */ ++// double z_abs(doublecomplex *z) ++// { ++// double temp; ++// double real = z->r; ++// double imag = z->i; ++ ++// if (real < 0) real = -real; ++// if (imag < 0) imag = -imag; ++// if (imag > real) { ++// temp = real; ++// real = imag; ++// imag = temp; ++// } ++// if ((real+imag) == real) return(real); + +- temp = imag/real; +- temp = real*sqrt(1.0 + temp*temp); /*overflow!!*/ +- return (temp); +-} ++// temp = imag/real; ++// temp = real*sqrt(1.0 + temp*temp); /*overflow!!*/ ++// return (temp); ++// } + + + /*! \brief Approximates the abs. Returns abs(z.r) + abs(z.i) */ diff --git a/recipes/recipes_emscripten/scipy_flang/patches/0002-Set-fortran-linker-to-emcc.patch b/recipes/recipes_emscripten/scipy_flang/patches/0002-Set-fortran-linker-to-emcc.patch new file mode 100644 index 00000000000..434d8daa48c --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/patches/0002-Set-fortran-linker-to-emcc.patch @@ -0,0 +1,174 @@ +From c656ea5647b17ef480ec0b28a73a7837ad91515b Mon Sep 17 00:00:00 2001 +From: Isabel Paredes +Date: Wed, 7 May 2025 17:13:16 +0200 +Subject: [PATCH 2/4] Set fortran linker to emcc + +--- + scipy/integrate/meson.build | 10 +++++----- + scipy/interpolate/meson.build | 4 ++-- + scipy/io/meson.build | 2 +- + scipy/odr/meson.build | 2 +- + scipy/optimize/meson.build | 4 ++-- + scipy/sparse/linalg/_eigen/arpack/meson.build | 2 +- + scipy/sparse/linalg/_propack/meson.build | 2 +- + scipy/stats/meson.build | 2 +- + 8 files changed, 14 insertions(+), 14 deletions(-) + +diff --git a/scipy/integrate/meson.build b/scipy/integrate/meson.build +index 206d504..4c6b36e 100644 +--- a/scipy/integrate/meson.build ++++ b/scipy/integrate/meson.build +@@ -75,7 +75,7 @@ py3.extension_module('_odepack', + link_args: version_link_args, + dependencies: [lapack_dep, np_dep], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/integrate' + ) + +@@ -86,7 +86,7 @@ py3.extension_module('_vode', + link_args: version_link_args, + dependencies: [lapack_dep, blas_dep, fortranobject_dep], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/integrate' + ) + +@@ -97,7 +97,7 @@ py3.extension_module('_lsoda', + dependencies: [lapack_dep, fortranobject_dep], + link_args: version_link_args, + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/integrate' + ) + +@@ -108,7 +108,7 @@ py3.extension_module('_dop', + dependencies: [fortranobject_dep], + link_args: version_link_args, + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/integrate' + ) + +@@ -127,7 +127,7 @@ py3.extension_module('_test_odeint_banded', + link_args: version_link_args, + dependencies: [lapack_dep, fortranobject_dep], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/integrate', + install_tag: 'tests' + ) +diff --git a/scipy/interpolate/meson.build b/scipy/interpolate/meson.build +index 72e2d20..5d54dbf 100644 +--- a/scipy/interpolate/meson.build ++++ b/scipy/interpolate/meson.build +@@ -157,7 +157,7 @@ py3.extension_module('_fitpack', + include_directories: 'src/', + dependencies: np_dep, + link_args: version_link_args, +- link_language: 'fortran', ++ link_language: 'c', + install: true, + subdir: 'scipy/interpolate' + ) +@@ -171,7 +171,7 @@ py3.extension_module('_dfitpack', + link_with: [fitpack_lib], + override_options: ['b_lto=false'], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/interpolate' + ) + +diff --git a/scipy/io/meson.build b/scipy/io/meson.build +index 60f71c6..abdf285 100644 +--- a/scipy/io/meson.build ++++ b/scipy/io/meson.build +@@ -8,7 +8,7 @@ py3.extension_module('_test_fortran', + link_args: version_link_args, + dependencies: [lapack_dep, fortranobject_dep], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/io', + install_tag: 'tests' + ) +diff --git a/scipy/odr/meson.build b/scipy/odr/meson.build +index c7f53bc..eedf439 100644 +--- a/scipy/odr/meson.build ++++ b/scipy/odr/meson.build +@@ -16,7 +16,7 @@ py3.extension_module('__odrpack', + link_args: version_link_args, + dependencies: [blas_dep, np_dep], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/odr' + ) + +diff --git a/scipy/optimize/meson.build b/scipy/optimize/meson.build +index 519755e..1792f21 100644 +--- a/scipy/optimize/meson.build ++++ b/scipy/optimize/meson.build +@@ -92,7 +92,7 @@ py3.extension_module('_cobyla', + link_args: version_link_args, + dependencies: [fortranobject_dep], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/optimize' + ) + +@@ -102,7 +102,7 @@ py3.extension_module('_slsqp', + link_args: version_link_args, + dependencies: [fortranobject_dep], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/optimize' + ) + +diff --git a/scipy/sparse/linalg/_eigen/arpack/meson.build b/scipy/sparse/linalg/_eigen/arpack/meson.build +index 52c8ab9..9bbf053 100644 +--- a/scipy/sparse/linalg/_eigen/arpack/meson.build ++++ b/scipy/sparse/linalg/_eigen/arpack/meson.build +@@ -107,7 +107,7 @@ _arpack = py3.extension_module('_arpack', + link_args: version_link_args, + dependencies: [lapack_dep, blas_dep, fortranobject_dep], + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/sparse/linalg/_eigen/arpack' + ) + +diff --git a/scipy/sparse/linalg/_propack/meson.build b/scipy/sparse/linalg/_propack/meson.build +index d33cdc0..75ba1a3 100644 +--- a/scipy/sparse/linalg/_propack/meson.build ++++ b/scipy/sparse/linalg/_propack/meson.build +@@ -107,7 +107,7 @@ foreach ele: elements + dependencies: [lapack_dep, blas_dep, fortranobject_dep], + link_args: version_link_args, + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/sparse/linalg/_propack' + ) + endforeach +diff --git a/scipy/stats/meson.build b/scipy/stats/meson.build +index a9f9365..d99a944 100644 +--- a/scipy/stats/meson.build ++++ b/scipy/stats/meson.build +@@ -39,7 +39,7 @@ py3.extension_module('_mvn', + dependencies: [fortranobject_dep], + link_args: version_link_args, + install: true, +- link_language: 'fortran', ++ link_language: 'c', + subdir: 'scipy/stats' + ) + diff --git a/recipes/recipes_emscripten/scipy_flang/patches/0003-Disable-threads.patch b/recipes/recipes_emscripten/scipy_flang/patches/0003-Disable-threads.patch new file mode 100644 index 00000000000..d608fd2e66c --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/patches/0003-Disable-threads.patch @@ -0,0 +1,98 @@ +From b56dd0a61a8634e5d0beab5babcbdac302c82b58 Mon Sep 17 00:00:00 2001 +From: Isabel Paredes +Date: Wed, 7 May 2025 17:34:52 +0200 +Subject: [PATCH 3/4] Disable threads + +--- + scipy/meson.build | 22 +++++++++---------- + .../tests/_cython_examples/meson.build | 2 +- + subprojects/highs/meson.build | 2 +- + 3 files changed, 13 insertions(+), 13 deletions(-) + +diff --git a/scipy/meson.build b/scipy/meson.build +index 73168d8..3771c7c 100644 +--- a/scipy/meson.build ++++ b/scipy/meson.build +@@ -27,7 +27,7 @@ if is_mingw + add_project_arguments('-fno-optimize-sibling-calls', language: ['fortran']) + endif + +-thread_dep = dependency('threads', required: false) ++thread_dep = dependency('threads_DONTUSE', required: false) + + + # Uses the `numpy-config` executable (or a user's numpy.pc pkg-config file). +@@ -36,11 +36,11 @@ thread_dep = dependency('threads', required: false) + # version easily for >=2.0. + _numpy_dep = dependency('numpy', required: false) + f2py_freethreading_arg = [] +-if _numpy_dep.found() +- if _numpy_dep.version().version_compare('>=2.1.0') +- f2py_freethreading_arg = ['--free-threading'] +- endif +-endif ++# if _numpy_dep.found() ++# if _numpy_dep.version().version_compare('>=2.1.0') ++# f2py_freethreading_arg = ['--free-threading'] ++# endif ++# endif + + # NumPy include directory - needed in all submodules + # The chdir is needed because within numpy there's an `import signal` +@@ -155,9 +155,9 @@ cdata = configuration_data() + # Test variable attribute to use for thread-local storage; + # Adapted from `numpy/_core/meson.build`. + check_tls_attrs = [ +- ['thread_local', 'HAVE_THREAD_LOCAL'], # C23 +- ['_Thread_local', 'HAVE__THREAD_LOCAL'], # C11/C17 +- ['__thread', 'HAVE__THREAD'], ++ ['thread_local_DONTUSE', 'HAVE_THREAD_LOCAL_DONTUSE'], # C23 ++ ['_Thread_local_DONTUSE', 'HAVE__THREAD_LOCAL_DONTUSE'], # C11/C17 ++ ['__thread_DONTUSE', 'HAVE__THREAD_DONTUSE'], + ] + if is_windows and not is_mingw + check_tls_attrs += ['__declspec(thread)', 'HAVE___DECLSPEC_THREAD_'] +@@ -193,7 +193,7 @@ scipy_config_h = configure_file( + install: false + ) + +-_f2py_c_args = [f'-DF2PY_THREAD_LOCAL_DECL=@f2py_tls_define@'] ++_f2py_c_args = [] + fortranobject_dep = declare_dependency( + dependencies: fortranobject_dep, + compile_args: _f2py_c_args, +@@ -379,7 +379,7 @@ _cython_tree = [fs.copyfile('__init__.py')] + + cython_args = ['-3', '--fast-fail', '--output-file', '@OUTPUT@', '--include-dir', '@BUILD_ROOT@', '@INPUT@'] + if cy.version().version_compare('>=3.1.0') +- cython_args += ['-Xfreethreading_compatible=True'] ++ cython_args += ['-Xfreethreading_compatible=False'] + endif + cython_cplus_args = ['--cplus'] + cython_args + +diff --git a/scipy/optimize/tests/_cython_examples/meson.build b/scipy/optimize/tests/_cython_examples/meson.build +index 1fb210f..02e0b2c 100644 +--- a/scipy/optimize/tests/_cython_examples/meson.build ++++ b/scipy/optimize/tests/_cython_examples/meson.build +@@ -12,7 +12,7 @@ endif + + cython_args = [] + if cy.version().version_compare('>=3.1.0') +- cython_args += ['-Xfreethreading_compatible=True'] ++ cython_args += ['-Xfreethreading_compatible=False'] + endif + + py3.extension_module( +diff --git a/subprojects/highs/meson.build b/subprojects/highs/meson.build +index 8cb61f8..bae6f05 100644 +--- a/subprojects/highs/meson.build ++++ b/subprojects/highs/meson.build +@@ -110,7 +110,7 @@ if is_mingw + endif + + # --------------------- Dependencies +-threads_dep = dependency('threads', required: false) ++threads_dep = dependency('threads_DONTUSE', required: false) + _deps += threads_dep + + # Determine whether it is necessary to link libatomic. This could be the case diff --git a/recipes/recipes_emscripten/scipy_flang/patches/0004-Replace-dtype-with-numpy-int32.patch b/recipes/recipes_emscripten/scipy_flang/patches/0004-Replace-dtype-with-numpy-int32.patch new file mode 100644 index 00000000000..a501159b9d9 --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/patches/0004-Replace-dtype-with-numpy-int32.patch @@ -0,0 +1,78 @@ +From 88d9f88a40ae0aee5db0d5318a6a1abe42e2bb14 Mon Sep 17 00:00:00 2001 +From: Isabel Paredes +Date: Thu, 5 Jun 2025 14:11:26 +0200 +Subject: [PATCH 4/4] Replace dtype with numpy int32 + +--- + scipy/integrate/_ode.py | 8 ++++---- + scipy/interpolate/_fitpack2.py | 4 ++-- + scipy/interpolate/_fitpack_impl.py | 4 ++-- + 3 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/scipy/integrate/_ode.py b/scipy/integrate/_ode.py +index 72d9da2..86ed061 100644 +--- a/scipy/integrate/_ode.py ++++ b/scipy/integrate/_ode.py +@@ -84,16 +84,16 @@ import re + import threading + import warnings + +-from numpy import asarray, array, zeros, isscalar, real, imag, vstack ++from numpy import asarray, array, zeros, isscalar, real, imag, vstack, int32 + + from . import _vode + from . import _dop + from . import _lsoda + + +-_dop_int_dtype = _dop.types.intvar.dtype +-_vode_int_dtype = _vode.types.intvar.dtype +-_lsoda_int_dtype = _lsoda.types.intvar.dtype ++_dop_int_dtype = int32 # _dop.types.intvar.dtype ++_vode_int_dtype = int32 # _vode.types.intvar.dtype ++_lsoda_int_dtype = int32 # _lsoda.types.intvar.dtype + + + # lsoda, vode and zvode are not thread-safe. VODE_LOCK protects both vode and +diff --git a/scipy/interpolate/_fitpack2.py b/scipy/interpolate/_fitpack2.py +index daa7773..5514a0e 100644 +--- a/scipy/interpolate/_fitpack2.py ++++ b/scipy/interpolate/_fitpack2.py +@@ -22,14 +22,14 @@ __all__ = [ + import warnings + from threading import Lock + +-from numpy import zeros, concatenate, ravel, diff, array ++from numpy import zeros, concatenate, ravel, diff, array, int32 + import numpy as np + + from . import _fitpack_impl + from . import _dfitpack as dfitpack + + +-dfitpack_int = dfitpack.types.intvar.dtype ++dfitpack_int = int32 # dfitpack.types.intvar.dtype + FITPACK_LOCK = Lock() + + +diff --git a/scipy/interpolate/_fitpack_impl.py b/scipy/interpolate/_fitpack_impl.py +index a00ca10..9b4d0f6 100644 +--- a/scipy/interpolate/_fitpack_impl.py ++++ b/scipy/interpolate/_fitpack_impl.py +@@ -28,14 +28,14 @@ import warnings + import numpy as np + from . import _fitpack + from numpy import (atleast_1d, array, ones, zeros, sqrt, ravel, transpose, +- empty, iinfo, asarray) ++ empty, iinfo, asarray, int32) + + # Try to replace _fitpack interface with + # f2py-generated version + from . import _dfitpack as dfitpack + + +-dfitpack_int = dfitpack.types.intvar.dtype ++dfitpack_int = int32 # dfitpack.types.intvar.dtype + + + def _int_overflow(x, exception, msg=None): diff --git a/recipes/recipes_emscripten/scipy_flang/patches/0006-Remove-unsupported-version-script.patch b/recipes/recipes_emscripten/scipy_flang/patches/0006-Remove-unsupported-version-script.patch new file mode 100644 index 00000000000..5de6317709e --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/patches/0006-Remove-unsupported-version-script.patch @@ -0,0 +1,36 @@ +From 23b7bebe8ddf10e87b2f582a84c4840b05d6209c Mon Sep 17 00:00:00 2001 +From: Isabel Paredes +Date: Thu, 13 Nov 2025 13:09:41 +0100 +Subject: [PATCH 1/2] Remove unsupported version script + +--- + meson.build | 15 ++++++++------- + 1 file changed, 8 insertions(+), 7 deletions(-) + +diff --git a/meson.build b/meson.build +index 94ffaeb..12c61f0 100644 +--- a/meson.build ++++ b/meson.build +@@ -133,13 +133,14 @@ add_project_arguments(_intel_fflags, language: 'fortran') + # Meson using `-fvisibility=hidden` for C and `-fvisibility-inlines-hidden` for + # C++ code. See gh-15996 for details. + _linker_script = meson.project_source_root() / 'scipy/_build_utils/link-version-pyinit.map' +-version_link_args = ['-Wl,--version-script=' + _linker_script] +-# Note that FreeBSD only accepts version scripts when -shared is passed, +-# hence we need to pass that to `cc.links` explicitly (flag is already +-# present for `extension_module` invocations). +-if not cc.links('', name: '-Wl,--version-script', args: ['-shared', version_link_args]) +- version_link_args = [] +-endif ++# version_link_args = ['-Wl,--version-script=' + _linker_script] ++# # Note that FreeBSD only accepts version scripts when -shared is passed, ++# # hence we need to pass that to `cc.links` explicitly (flag is already ++# # present for `extension_module` invocations). ++# if not cc.links('', name: '-Wl,--version-script', args: ['-shared', version_link_args]) ++# version_link_args = [] ++# endif ++version_link_args = [] + + generate_f2pymod = find_program('tools/generate_f2pymod.py') + tempita = find_program('scipy/_build_utils/tempita.py') + \ No newline at end of file diff --git a/recipes/recipes_emscripten/scipy_flang/patches/0007-Fix-function-args-and-returns.patch b/recipes/recipes_emscripten/scipy_flang/patches/0007-Fix-function-args-and-returns.patch new file mode 100644 index 00000000000..1dad1c34909 --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/patches/0007-Fix-function-args-and-returns.patch @@ -0,0 +1,218 @@ +From c0bdcc99457462fa11f9d65632926c043dd8c1bb Mon Sep 17 00:00:00 2001 +From: Ian Thomas +Date: Mon, 24 Nov 2025 07:41:34 +0000 +Subject: [PATCH] Fix function args and returns + +--- + no_extra_args_for_char.txt | 37 +++++++++++++++++++++++++ + scipy/linalg/_generate_pyx.py | 48 +++++++++++++++++++++++++++++++++ + scipy/meson.build | 2 +- + scipy/optimize/__lbfgsb.h | 4 +-- + scipy/sparse/linalg/meson.build | 8 +++--- + void_to_int_return.txt | 13 +++++++++ + 6 files changed, 105 insertions(+), 7 deletions(-) + create mode 100644 no_extra_args_for_char.txt + create mode 100644 void_to_int_return.txt + +diff --git a/no_extra_args_for_char.txt b/no_extra_args_for_char.txt +new file mode 100644 +index 0000000000..818f19618b +--- /dev/null ++++ b/no_extra_args_for_char.txt +@@ -0,0 +1,37 @@ ++# Add an extra length arg for each char arg passed to fortran functions except for those ++# whose names match these regexes. ++[cdsz]gbmv ++[cdsz]gem[mv] ++[cdsz]getrs ++[cdsz]lauu[2m] ++[cdsz]potf2 ++[cdsz]potrf ++[cdsz]spmv ++[cdsz]spr ++[cdsz]sym[mv] ++[cdsz]syr ++[cdsz]syr2k ++[cdsz]tbmv ++[cdsz]tbsv ++[cdsz]tpmv ++[cdsz]tpsv ++[cdsz]trm[mv] ++[cdsz]trs[mv] ++[cdsz]trti2 ++[cdsz]trtr[is] ++[cz]hbmv ++[cz]hem[mv] ++[cz]her ++[cz]her2k ++[cz]her[k2] ++[cz]hpmv ++[cz]hpr ++[cz]hpr2 ++[cz]syrk ++[ds]sbmv ++[ds]spr2 ++[ds]syr ++[ds]syr[2k] ++[ds]trti2 ++[ds]trtr[is] ++lsame +\ No newline at end of file +diff --git a/scipy/linalg/_generate_pyx.py b/scipy/linalg/_generate_pyx.py +index bdecd5f90b..d43907d8b3 100644 +--- a/scipy/linalg/_generate_pyx.py ++++ b/scipy/linalg/_generate_pyx.py +@@ -11,6 +11,7 @@ NOTE: Must add scipy/_build_utils to PYTHONPATH for _wrappers_common + import argparse + import importlib.util + import os ++import re + + + def import_wrappers_common(): +@@ -385,6 +386,15 @@ def arg_casts(argtype): + return '' + + ++def prepare_regex(filename): ++ with open(filename) as f: ++ lines = filter(lambda s: len(s) > 0 and s[0] != "#", f.read().splitlines()) ++ return re.compile("^(" + "|".join(lines) + ")$") ++ ++void_to_int_return_regex = prepare_regex("../void_to_int_return.txt") ++no_extra_args_for_char_regex = prepare_regex("../no_extra_args_for_char.txt") ++ ++ + def generate_decl_pyx(name, return_type, argnames, argtypes, accelerate, header_name): + """Create Cython declaration for BLAS/LAPACK function.""" + pyx_input_args = ', '.join([' *'.join(arg) for arg in zip(argtypes, argnames)]) +@@ -392,7 +402,11 @@ def generate_decl_pyx(name, return_type, argnames, argtypes, accelerate, header_ + init_return_var = '' + return_kw = '' + return_var = '' ++ + blas_return_type = 'void' ++ if void_to_int_return_regex.match(name): ++ blas_return_type = 'int' ++ + # For functions with complex return type, use 'wrp'-suffixed wrappers + # that take a "return" variable as their first argument and return void + if name in WRAPPED_FUNCS: +@@ -411,6 +425,21 @@ def generate_decl_pyx(name, return_type, argnames, argtypes, accelerate, header_ + pyx_call_args[0] = ''.join([arg_casts(c_argtypes[0]), '&', argnames[0]]) + pyx_call_args = ', '.join(pyx_call_args) + blas_macro, blas_name = get_blas_macro_and_name(name, accelerate) ++ ++ if not no_extra_args_for_char_regex.match(name): ++ for argtype, argname in zip(argtypes, argnames): ++ if argtype == 'char': ++ c_proto += f', int {argname}_len' ++ pyx_call_args += ', 1' ++ ++ if return_type == 'char': ++ c_proto += ', int *in_len, long long ret_arg, int *ret_arg_len' ++ # WIP - this is not correct ++ init_return_var = 'cdef int in_len = 1, ret_arg_len\n cdef long long ret_arg' ++ pyx_call_args += ', &in_len, ret_arg, &ret_arg_len' ++ return_kw = '' ++ return_var = 'return 23 # should come from arg1' ++ + return f""" + cdef extern from "{header_name}": + {blas_return_type} _fortran_{name} "{blas_macro}({blas_name})"({c_proto}) nogil +@@ -521,8 +550,27 @@ def generate_decl_c(name, return_type, argnames, argtypes, accelerate): + argnames = ['out'] + argnames + c_argtypes = [c_return_type] + c_argtypes + c_return_type = 'void' ++ ++ extra_c_argtypes = [] ++ extra_argnames = [] ++ ++ if not no_extra_args_for_char_regex.match(name): ++ for c_argtype, argname in zip(c_argtypes, argnames): ++ if c_argtype == 'char': ++ extra_c_argtypes.append("int") ++ extra_argnames.append(f"{argname}_len") ++ ++ c_argtypes = c_argtypes + extra_c_argtypes ++ argnames = argnames + extra_argnames ++ + blas_macro, blas_name = get_blas_macro_and_name(name, accelerate) + c_args = ', '.join(f'{t} *{n}' for t, n in zip(c_argtypes, argnames)) ++ ++ if c_return_type == "char": ++ c_return_type = "void" ++ # Note second arg is not a pointer??? ++ c_args += ", int *in_len, long long ret_arg, int* ret_arg_len" ++ + return f"{c_return_type} {blas_macro}({blas_name})({c_args});\n" + + +diff --git a/scipy/meson.build b/scipy/meson.build +index 73168d859f..29aa4d23cb 100644 +--- a/scipy/meson.build ++++ b/scipy/meson.build +@@ -643,7 +643,7 @@ subdir('spatial') + subdir('cluster') + subdir('constants') + subdir('fftpack') +-subdir('integrate') ++#subdir('integrate') + subdir('differentiate') + subdir('signal') + subdir('interpolate') +diff --git a/scipy/optimize/__lbfgsb.h b/scipy/optimize/__lbfgsb.h +index 1ac42a4e49..dd36c259ac 100644 +--- a/scipy/optimize/__lbfgsb.h ++++ b/scipy/optimize/__lbfgsb.h +@@ -21,8 +21,8 @@ double dnrm2_(int* n, double* x, int* incx); + double ddot_(int* n, double* x, int* incx, double* y, int* incy); + + // LAPACK +-void dpotrf_(char* uplo, int* n, double* a, int* lda, int* info); +-void dtrtrs_(char* uplo, char* trans, char* diag, int* n, int* nrhs, double* a, int* lda, double* b, int* ldb, int* info); ++int dpotrf_(char* uplo, int* n, double* a, int* lda, int* info); ++int dtrtrs_(char* uplo, char* trans, char* diag, int* n, int* nrhs, double* a, int* lda, double* b, int* ldb, int* info); + + static PyObject* lbfgsb_error; + +diff --git a/scipy/sparse/linalg/meson.build b/scipy/sparse/linalg/meson.build +index e201ae1094..8822f5533b 100644 +--- a/scipy/sparse/linalg/meson.build ++++ b/scipy/sparse/linalg/meson.build +@@ -16,8 +16,8 @@ py3.install_sources([ + subdir: 'scipy/sparse/linalg' + ) + +-subdir('_propack') ++#subdir('_propack') + subdir('_isolve') +-subdir('_dsolve') +-subdir('_eigen') +-subdir('tests') ++#subdir('_dsolve') ++#subdir('_eigen') ++#subdir('tests') +diff --git a/void_to_int_return.txt b/void_to_int_return.txt +new file mode 100644 +index 0000000000..8648461593 +--- /dev/null ++++ b/void_to_int_return.txt +@@ -0,0 +1,13 @@ ++# Regexes for names of fortran functions that return int not void. ++[cdsz]gesv ++[cdsz]getf2 ++[cdsz]getrf ++[cdsz]getrs ++[cdsz]laswp ++[cdsz]lauu2 ++[cdsz]lauum ++[cdsz]potf2 ++[cdsz]potrf ++[cdsz]trti2 ++[cdsz]trtri ++[cdsz]trtrs +\ No newline at end of file +-- +2.43.0 + diff --git a/recipes/recipes_emscripten/scipy_flang/recipe.yaml b/recipes/recipes_emscripten/scipy_flang/recipe.yaml new file mode 100755 index 00000000000..2d1ad85c0d8 --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/recipe.yaml @@ -0,0 +1,73 @@ +context: + name: scipy-flang + version: 1.15.2 + +package: + name: ${{ name }} + version: ${{ version }} + +source: + - url: https://pypi.org/packages/source/s/scipy/scipy-${{ version }}.tar.gz + sha256: cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec + patches: + - patches/0001-Remove-duplicate-symbol.patch + - patches/0002-Set-fortran-linker-to-emcc.patch + - patches/0003-Disable-threads.patch + - patches/0004-Replace-dtype-with-numpy-int32.patch + - patches/0006-Remove-unsupported-version-script.patch + - patches/0007-Fix-function-args-and-returns.patch + +build: + number: 1 + +requirements: + build: + - python + - cross-python_${{ target_platform }} + - cython + # we need gcc and gfortran for the preprocessor! + - gcc_impl_linux-64 + # - llvm_${{ target_platform }} # Installing custom LLVM in build.sh + - pip !=25.1.1 + - numpy + - pybind11 + - setuptools + - meson-python + - ${{ compiler('c') }} + - ${{ compiler('cxx') }} + - pkg-config + host: + - openblas-flang=0.3.30 + - python + - pybind11 + - numpy + - pip !=25.1.1 + - libflang + run: + - openblas-flang + - python + - numpy + +#tests: +#- script: pytester +# requirements: +# build: +# - pytester +# - playwright == 1.50.0 +# run: +# - pytester-run +# - openblas-flang +# files: +# recipe: +# - test_scipy.py + +about: + homepage: http://www.scipy.org/ + license: BSD-3-Clause + license_file: LICENSE.txt + summary: Scientific Library for Python + description: | + SciPy is a Python-based ecosystem of open-source software for mathematics, + science, and engineering. + + diff --git a/recipes/recipes_emscripten/scipy_flang/test_scipy.py b/recipes/recipes_emscripten/scipy_flang/test_scipy.py new file mode 100755 index 00000000000..fdc54d0d9eb --- /dev/null +++ b/recipes/recipes_emscripten/scipy_flang/test_scipy.py @@ -0,0 +1,27 @@ +# def test_scipy_linalg(): +# import scipy.linalg + + +# N = 10 +# X = np.random.RandomState(42).rand(N, N) + +# X_inv = scipy.linalg.inv(X) + +# res = X.dot(X_inv) + +# assert_allclose(res, np.identity(N), rtol=1e-07, atol=1e-9) + + +def test_brentq(): + from scipy.optimize import brentq + + brentq(lambda x: x, -1, 1) + +def test_dlamch(): + from scipy.linalg import lapack + print(lapack.dlamch("Epsilon-Machine")) + +def test_binom_ppf(): + from scipy.stats import binom + + assert binom.ppf(0.9, 1000, 0.1) == 112.0 \ No newline at end of file