From 87dc419c9517f20d3606f043bb1b567a96f4d697 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Mon, 29 Aug 2022 10:34:52 +0100 Subject: [PATCH 01/25] Create symlinks on Windows when available Previously Windows unconditionally uses `cp`, doubling the size required for the OCaml binaries. `configure` now determines if `ln` creates native symlinks and only uses `cp` if that fails. Users of the compiler are simply required to enable Developer Mode (or build OCaml using an elevated shell). (cherry picked from commit b082fd17f080c7fb7e13f2bbb57edb06cd1e81c8) --- Changes | 14 +++++++++++--- aclocal.m4 | 15 +++++++++++++++ configure | 23 ++++++++++++++++++++++- configure.ac | 2 +- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 486c5313a290..c6a36a911575 100644 --- a/Changes +++ b/Changes @@ -1,14 +1,22 @@ OCaml 5.3 maintenance version ----------------------------- -- #14007, #14015: Fix memory corruption when an exception is raised during - demarshaling. - (Benoît Vaugon, review by David Allsopp and Gabriel Scherer) +### Build system: + +- #13494: Use native symlinks on Windows for the OCaml installation, reducing + disk usage considerably. + (David Allsopp, review by Nicolás Ojeda Bär and Gabriel Scherer) + +### Bug fixes: - #13790: Fix bytecode-only build of Cygwin when flexlink is being bootstrapped with the compiler. (David Allsopp, review by Antonin Décimo and Miod Vallat) +- #14007, #14015: Fix memory corruption when an exception is raised during + demarshaling. + (Benoît Vaugon, review by David Allsopp and Gabriel Scherer) + OCaml 5.3.0 (8 January 2025) ---------------------------- diff --git a/aclocal.m4 b/aclocal.m4 index 7353e09a96ef..e3afac44d768 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -543,3 +543,18 @@ AC_DEFUN([OCAML_CC_SUPPORTS_LABELS_AS_VALUES], [ [Define if the C compiler supports the labels as values extension.]) fi ]) + +AC_DEFUN([OCAML_CHECK_LN_ON_WINDOWS], [ + AC_MSG_CHECKING([for a workable solution for ln -sf]) + ln -sf configure conftestLink + AS_IF([test -z "$(cmd /c dir conftestLink 2>/dev/null | grep -F SYMLINK)"], + [rm -f conftestLink + CYGWIN=winsymlinks:native ln -sf configure conftestLink + AS_IF([test -z "$(cmd /c dir conftestLink 2>/dev/null | grep -F SYMLINK)"], + [ln='cp -pf'], + [ln='CYGWIN=winsymlinks:native ln -sf'] + )], + [ln='ln -sf'] + ) + AC_MSG_RESULT([$ln]) +]) diff --git a/configure b/configure index 2b8e81bc3d2b..41fcc335bfd2 100755 --- a/configure +++ b/configure @@ -13833,7 +13833,28 @@ esac case $host in #( *-*-mingw32*|*-pc-windows) : unix_or_win32="win32" - ln='cp -pf' + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a workable solution for ln -sf" >&5 +printf %s "checking for a workable solution for ln -sf... " >&6; } + ln -sf configure conftestLink + if test -z "$(cmd /c dir conftestLink 2>/dev/null | grep -F SYMLINK)" +then : + rm -f conftestLink + CYGWIN=winsymlinks:native ln -sf configure conftestLink + if test -z "$(cmd /c dir conftestLink 2>/dev/null | grep -F SYMLINK)" +then : + ln='cp -pf' +else $as_nop + ln='CYGWIN=winsymlinks:native ln -sf' + +fi +else $as_nop + ln='ln -sf' + +fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ln" >&5 +printf "%s\n" "$ln" >&6; } + ocamltest_libunix="Some false" ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$ocamlsrcdir")" ocamlyacc_wstr_module="yacc/wstr" ;; #( diff --git a/configure.ac b/configure.ac index a1ce09dc58bf..144a6836ec4a 100644 --- a/configure.ac +++ b/configure.ac @@ -727,7 +727,7 @@ AS_CASE([$lt_cv_ar_at_file], AS_CASE([$host], [*-*-mingw32*|*-pc-windows], [unix_or_win32="win32" - ln='cp -pf' + OCAML_CHECK_LN_ON_WINDOWS ocamltest_libunix="Some false" ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$ocamlsrcdir")" ocamlyacc_wstr_module="yacc/wstr"], From 2f03ad61b1a69639551bdfcc9e8af93df51cc42f Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Wed, 17 May 2023 13:57:57 +0100 Subject: [PATCH 02/25] Set MSYS and CYGWIN permanently Slightly easier detection, and a somewhat hardened implementation. (cherry picked from commit e0467cc670b51d29942556a028313b6d260344da) --- Makefile.common | 9 +++++++++ aclocal.m4 | 14 +++++--------- configure | 15 +++------------ 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Makefile.common b/Makefile.common index 1e6a7eff8903..76d348cf22cc 100644 --- a/Makefile.common +++ b/Makefile.common @@ -467,3 +467,12 @@ endef # INSTALL_STRIPPED_BYTE_PROG # boot/ as part of coldstart. See read_runtime_launch_info in # bytecomp/bytelink.ml for further details. HEADER_NAME = runtime-launch-info + +ifeq "$(UNIX_OR_WIN32)" "win32" +# Ensure that no command can create Cygwin symbolic links by ensuring that +# symlink(2) will fail if native NTFS symlinks aren't available. +export CYGWIN := $(strip \ + $(filter-out winsymlinks winsymlinks:%, $(CYGWIN)) winsymlinks:nativestrict) +export MSYS := $(strip \ + $(filter-out winsymlinks winsymlinks:%, $(MSYS)) winsymlinks:nativestrict) +endif diff --git a/aclocal.m4 b/aclocal.m4 index e3afac44d768..791a49432ed5 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -546,15 +546,11 @@ AC_DEFUN([OCAML_CC_SUPPORTS_LABELS_AS_VALUES], [ AC_DEFUN([OCAML_CHECK_LN_ON_WINDOWS], [ AC_MSG_CHECKING([for a workable solution for ln -sf]) - ln -sf configure conftestLink - AS_IF([test -z "$(cmd /c dir conftestLink 2>/dev/null | grep -F SYMLINK)"], - [rm -f conftestLink - CYGWIN=winsymlinks:native ln -sf configure conftestLink - AS_IF([test -z "$(cmd /c dir conftestLink 2>/dev/null | grep -F SYMLINK)"], - [ln='cp -pf'], - [ln='CYGWIN=winsymlinks:native ln -sf'] - )], - [ln='ln -sf'] + AS_IF([m4_normalize(MSYS=winsymlinks:nativestrict + CYGWIN=winsymlinks:nativestrict + ln -sf configure conftestLink 2>/dev/null)], + [ln='ln -sf'], + [ln='cp -pf'] ) AC_MSG_RESULT([$ln]) ]) diff --git a/configure b/configure index 41fcc335bfd2..82ed2188d856 100755 --- a/configure +++ b/configure @@ -13836,20 +13836,11 @@ case $host in #( { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a workable solution for ln -sf" >&5 printf %s "checking for a workable solution for ln -sf... " >&6; } - ln -sf configure conftestLink - if test -z "$(cmd /c dir conftestLink 2>/dev/null | grep -F SYMLINK)" + if MSYS=winsymlinks:nativestrict CYGWIN=winsymlinks:nativestrict ln -sf configure conftestLink 2>/dev/null then : - rm -f conftestLink - CYGWIN=winsymlinks:native ln -sf configure conftestLink - if test -z "$(cmd /c dir conftestLink 2>/dev/null | grep -F SYMLINK)" -then : - ln='cp -pf' -else $as_nop - ln='CYGWIN=winsymlinks:native ln -sf' - -fi -else $as_nop ln='ln -sf' +else $as_nop + ln='cp -pf' fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ln" >&5 From 7f569b0cdb483534ccb44b049fab1cfc5db779ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonin=20D=C3=A9cimo?= Date: Sat, 6 Sep 2025 16:22:15 +0200 Subject: [PATCH 03/25] Avoid an undefined variable warning from Make Co-authored-by: David Allsopp (cherry picked from commit 49755dbc183b5f5b5a0c3466fe8cfd4eb728ae03) --- Makefile.common | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile.common b/Makefile.common index 76d348cf22cc..8e977db95f5a 100644 --- a/Makefile.common +++ b/Makefile.common @@ -471,6 +471,8 @@ HEADER_NAME = runtime-launch-info ifeq "$(UNIX_OR_WIN32)" "win32" # Ensure that no command can create Cygwin symbolic links by ensuring that # symlink(2) will fail if native NTFS symlinks aren't available. +CYGWIN ?= +MSYS ?= export CYGWIN := $(strip \ $(filter-out winsymlinks winsymlinks:%, $(CYGWIN)) winsymlinks:nativestrict) export MSYS := $(strip \ From 6ff87293e5c0d62284197192c495e35fd1cc7880 Mon Sep 17 00:00:00 2001 From: Samuel Hym Date: Tue, 1 Oct 2024 18:18:41 +0200 Subject: [PATCH 04/25] Add the missing `$(EXE)` for `stripdebug` invocations (cherry picked from commit c815cfa9dbe07b98417102609200463ef728fc67) --- Makefile | 6 +++--- Makefile.common | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 02e6f79e7e99..1733be7db587 100644 --- a/Makefile +++ b/Makefile @@ -716,9 +716,9 @@ compare: # The core system has to be rebuilt after bootstrap anyway, so strip ocamlc # and ocamllex, which means the artefacts should be identical. mv ocamlc$(EXE) ocamlc.tmp - $(OCAMLRUN) tools/stripdebug -all ocamlc.tmp ocamlc$(EXE) + $(OCAMLRUN) tools/stripdebug$(EXE) -all ocamlc.tmp ocamlc$(EXE) mv lex/ocamllex$(EXE) ocamllex.tmp - $(OCAMLRUN) tools/stripdebug -all ocamllex.tmp lex/ocamllex$(EXE) + $(OCAMLRUN) tools/stripdebug$(EXE) -all ocamllex.tmp lex/ocamllex$(EXE) rm -f ocamllex.tmp ocamlc.tmp @if $(CMPCMD) boot/ocamlc ocamlc$(EXE) \ && $(CMPCMD) boot/ocamllex lex/ocamllex$(EXE); \ @@ -746,7 +746,7 @@ promote-cross: promote-common # Promote the newly compiled system to the rank of bootstrap compiler # (Runs on the new runtime, produces code for the new runtime) .PHONY: promote -promote: PROMOTE = $(OCAMLRUN) tools/stripdebug -all +promote: PROMOTE = $(OCAMLRUN) tools/stripdebug$(EXE) -all promote: promote-common rm -f boot/ocamlrun$(EXE) cp runtime/ocamlrun$(EXE) boot/ocamlrun$(EXE) diff --git a/Makefile.common b/Makefile.common index 8e977db95f5a..417d47c4a45d 100644 --- a/Makefile.common +++ b/Makefile.common @@ -455,7 +455,7 @@ endef # OCAML_LIBRARY # Installing a bytecode executable, with debug information removed define INSTALL_STRIPPED_BYTE_PROG -$(OCAMLRUN) $(ROOTDIR)/tools/stripdebug $(1) $(1).tmp \ +$(OCAMLRUN) $(ROOTDIR)/tools/stripdebug$(EXE) $(1) $(1).tmp \ && $(INSTALL_PROG) $(1).tmp $(2) \ && rm $(1).tmp endef # INSTALL_STRIPPED_BYTE_PROG From 52adfb2d947819084ae0c119017ab4b57aa58459 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 8 Feb 2025 16:36:20 +0000 Subject: [PATCH 05/25] Fix backslashes in runtime/build_config.h echo cannot portably be used to display strings containing backslashes and the echo builtin in dash in particular always transforms \\ to \ which breaks the transformation in sak. Use printf instead. Co-authored-by: Samuel Hym (cherry picked from commit 9d3164630236aa4e6a070966b934474f5fd1a3f8) --- Changes | 5 +++++ Makefile | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index c6a36a911575..483c1ec262a5 100644 --- a/Changes +++ b/Changes @@ -7,6 +7,11 @@ OCaml 5.3 maintenance version disk usage considerably. (David Allsopp, review by Nicolás Ojeda Bär and Gabriel Scherer) +- #13789: Harden the escaping of backslashes when generating + runtime/build_config.h (in particular, if the underlying implementation of sh + is dash, rather than bash, this now works correctly). + (David Allsopp and Samuel Hym, review by Antonin Décimo and Gabriel Scherer) + ### Bug fixes: - #13790: Fix bytecode-only build of Cygwin when flexlink is being bootstrapped diff --git a/Makefile b/Makefile index 1733be7db587..0de15fbde851 100644 --- a/Makefile +++ b/Makefile @@ -1399,9 +1399,11 @@ runtime/sak.$(O): runtime/sak.c runtime/caml/misc.h runtime/caml/config.h C_LITERAL = $(shell $(SAK) encode-C-literal '$(1)') runtime/build_config.h: $(ROOTDIR)/Makefile.config $(SAK) - $(V_GEN)echo '/* This file is generated from $(ROOTDIR)/Makefile.config */' > $@ && \ - echo '#define OCAML_STDLIB_DIR $(call C_LITERAL,$(LIBDIR))' >> $@ && \ - echo '#define HOST "$(HOST)"' >> $@ + $(V_GEN){ \ + echo '/* This file is generated from $(ROOTDIR)/Makefile.config */'; \ + printf '#define OCAML_STDLIB_DIR %s\n' '$(call C_LITERAL,$(LIBDIR))'; \ + echo '#define HOST "$(HOST)"'; \ + } > $@ ## Runtime libraries and programs From a94915c7f4bfe0595acc5ea80f73fb99a1d3768f Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Thu, 9 Jan 2025 15:00:26 +0000 Subject: [PATCH 06/25] Set FLEXDIR when bootstrapping flexlink Previously, the flexdll support objects were placed in both byte/bin and opt/bin with the copy of flexlink.exe when flexlink was being bootstrapped with OCaml. The objects are small, so the copying was not particulary onerous. However, if opt/bin/flexlink.exe is a native Windows symlink (pointing to ../../flexlink.opt.exe) then Sys.executable_name when flexlink runs will point to the wrong place. While flexlink ought to be checking Sys.argv.(0) rather than Sys.executable_name, a better hardening is to be explicit and set the FLEXDIR environment variable to point to the directory containing the support objects. This also allows byte/bin/flexlink.exe and opt/bin/flexlink.exe to share the same copy of the objects. (cherry picked from commit 84474967c692e5fb37e697ed9257fdc6495691d9) --- Makefile | 8 +++----- Makefile.build_config.in | 2 ++ Makefile.common | 22 +++++++++++++++++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 0de15fbde851..e5ea64f02b88 100644 --- a/Makefile +++ b/Makefile @@ -644,9 +644,10 @@ flexlink.byte$(EXE): $(FLEXDLL_SOURCES) OCAMLOPT='$(value BOOT_OCAMLC) $(USE_RUNTIME_PRIMS) $(USE_STDLIB)' \ flexlink.exe support cp $(FLEXDLL_SOURCE_DIR)/flexlink.exe $@ + cp $(addprefix $(FLEXDLL_SOURCE_DIR)/, $(FLEXDLL_OBJECTS)) $(ROOTDIR) partialclean:: - rm -f flexlink.byte flexlink.byte.exe + rm -f flexlink.byte flexlink.byte.exe flexdll_*.o flexdll_*.obj $(BYTE_BINDIR)/flexlink$(EXE): \ boot/ocamlrun$(EXE) flexlink.byte$(EXE) | $(BYTE_BINDIR) @@ -654,7 +655,6 @@ $(BYTE_BINDIR)/flexlink$(EXE): \ # Start with a copy to ensure that the result is always executable cp boot/ocamlrun$(EXE) $@ cat flexlink.byte$(EXE) >> $@ - cp $(addprefix $(FLEXDLL_SOURCE_DIR)/, $(FLEXDLL_OBJECTS)) $(BYTE_BINDIR) partialclean:: rm -f $(BYTE_BINDIR)/flexlink $(BYTE_BINDIR)/flexlink.exe @@ -885,7 +885,6 @@ flexlink.opt$(EXE): \ cp $(FLEXDLL_SOURCE_DIR)/flexlink.exe $@ rm -f $(OPT_BINDIR)/flexlink$(EXE) cd $(OPT_BINDIR); $(LN) $(call ROOT_FROM, $(OPT_BINDIR))/$@ flexlink$(EXE) - cp $(addprefix $(BYTE_BINDIR)/, $(FLEXDLL_OBJECTS)) $(OPT_BINDIR) else @@ -2841,8 +2840,7 @@ ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" flexlink.byte$(EXE) "$(INSTALL_BINDIR)" endif # ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" $(MKDIR) "$(INSTALL_FLEXDLLDIR)" - $(INSTALL_DATA) $(addprefix $(BYTE_BINDIR)/, $(FLEXDLL_OBJECTS)) \ - "$(INSTALL_FLEXDLLDIR)" + $(INSTALL_DATA) $(FLEXDLL_OBJECTS) "$(INSTALL_FLEXDLLDIR)" endif # ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" $(INSTALL_DATA) Makefile.config "$(INSTALL_LIBDIR)" $(INSTALL_DATA) $(DOC_FILES) "$(INSTALL_DOCDIR)" diff --git a/Makefile.build_config.in b/Makefile.build_config.in index 006eb2fc59a2..7d8056cd091f 100644 --- a/Makefile.build_config.in +++ b/Makefile.build_config.in @@ -171,6 +171,8 @@ OC_NATIVE_COMPFLAGS = @oc_native_compflags@ OC_NATIVE_LINKFLAGS = -g +BUILD_TRIPLET = @build@ + # Platform-dependent command to create symbolic links LN = @ln@ diff --git a/Makefile.common b/Makefile.common index 417d47c4a45d..83ee360ce903 100644 --- a/Makefile.common +++ b/Makefile.common @@ -141,9 +141,29 @@ ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" ifeq "$(filter $(REAL_ROOT_DIR)/$(BYTE_BINDIR), $(subst :, ,$(PATH)))" "" export PATH := \ $(REAL_ROOT_DIR)/$(OPT_BINDIR):$(REAL_ROOT_DIR)/$(BYTE_BINDIR):$(PATH) + # $(BUILD_COMPANY_SYSTEM) is the last two parts of $(BUILD_TRIPLET) + BUILD_TRIPLET_FIELDS := $(subst -,$(SPACE),$(BUILD_TRIPLET)) + BUILD_COMPANY_SYSTEM := $(subst $(SPACE),-,$\ + $(wordlist 2, $(words $(BUILD_TRIPLET_FIELDS)), $(BUILD_TRIPLET_FIELDS))) + # Use the FLEXDIR environment variable to tell flexlink where the support + # objects are located. Passing this location using -I would defeat the whole + # purpose of the PATH-trick (bootstrapped flexlink is indistinguishable from + # an installed flexlink). flexlink also looks for the objects in the same + # directory as the executable, but this is slightly irritating as it requires + # copying them to both byte/bin/ and opt/bin/ but also doesn't work if + # opt/bin/flexlink.exe is a symlink to flexlink.opt.exe, as flexlink can end + # up looking in the directory for the target of the symlink, rather than the + # symlink itself. +ifeq "" "$(filter pc-msys pc-cygwin%, $(BUILD_COMPANY_SYSTEM))" + export FLEXDIR := $(REAL_ROOT_DIR) +else + export FLEXDIR := $(shell cygpath -w "$(REAL_ROOT_DIR)") endif - undefine REAL_ROOT_DIR + undefine BUILD_TRIPLET_FIELDS + undefine BUILD_COMPANY_SYSTEM endif + undefine REAL_ROOT_DIR +endif # ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" # List of other libraries ALL_OTHERLIBS = dynlink str systhreads unix runtime_events From cb48ea0637e3497b1263046cc5932fa6ae5dc4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Ojeda=20B=C3=A4r?= Date: Sat, 28 Jun 2025 10:07:54 +0200 Subject: [PATCH 07/25] Merge pull request PR#13988 from dra27/unified-header Unify the two executable header implementations (cherry picked from commit 076b435f9d1ccc36ba499c19c1f5b46ab1f35913) --- runtime/caml/exec.h.in | 2 + stdlib/Makefile | 47 +++++-- stdlib/header.c | 217 ++++++++++++++++++++++++------- stdlib/headernt.c | 169 ------------------------ testsuite/in_prefix/README.md | 8 +- testsuite/tools/testLinkModes.ml | 6 +- 6 files changed, 217 insertions(+), 232 deletions(-) delete mode 100644 stdlib/headernt.c diff --git a/runtime/caml/exec.h.in b/runtime/caml/exec.h.in index 333af64ddede..9572dd88e1ac 100644 --- a/runtime/caml/exec.h.in +++ b/runtime/caml/exec.h.in @@ -20,6 +20,8 @@ #ifdef CAML_INTERNALS +#include + /* Executable bytecode files are composed of a number of sections, identified by 4-character names. A table of contents at the end of the file lists the section names along with their sizes, diff --git a/stdlib/Makefile b/stdlib/Makefile index a7f2e60a7aa0..3f831625292e 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -84,19 +84,48 @@ installopt-default: stdlib.cmxa stdlib.$(A) std_exit.$(O) *.cmx \ "$(INSTALL_LIBDIR)" -ifeq "$(UNIX_OR_WIN32)" "unix" -HEADERPROGRAM = header -else # Windows -HEADERPROGRAM = headernt -endif - %-launch-info: %.info tmpheader.exe - @cat $^ >> $@ + @cat $^ > $@ + +# The mingw-w64 and MSVC versions of tmpheader.exe are linked with special flags +# to reduce their size (considerably). In particular, the entry point is +# overridden, which prevents the linking of crt0. +ifeq "$(TOOLCHAIN)" "mingw" +# mingw-w64: optimise header.o for space and remove all unused sections during +# linking. +header.o: OC_CFLAGS += -Os +ifeq "$(SYSTEM)" "mingw" + ENTRYPOINT = _wmainCRTStartup +else + ENTRYPOINT = wmainCRTStartup +endif +# Certainly for GCC, -nostdlib automatically adds -nostartfiles (clang's +# documentation is not explicit on this point, but it would be unusual for clang +# and GCC to differ). --gc-sections removes unreferenced code and data sections. +tmpheader.exe: OC_LDFLAGS += \ + -nostdlib -nostartfiles -Wl,--gc-sections -Wl,-e,$(ENTRYPOINT) +HEADERLIBS = -lkernel32 +else ifeq "$(TOOLCHAIN)" "msvc" +# MSVC: Optimise header.obj for space. cl doesn't have an equivalent of +# -nostdlib, however it also doesn't have default libraries in the same way as +# GCC - the equivalent of crt0.o is linked because it provides the entry point +# symbol, rather than by being explicitly added to the linking command line. +# /GS- disables compilation of runtime security checks and /NOCOFFGRPINFO is an +# undocumented linker flag which removes the debug directory from the PE+ image. +header.obj: OC_CFLAGS += /GS- /Os +tmpheader.exe: OC_LDFLAGS += /NOCOFFGRPINFO /subsystem:console +HEADERLIBS = kernel32.lib +else +HEADERLIBS = +endif .INTERMEDIATE: tmpheader.exe -tmpheader.exe: $(HEADERPROGRAM).$(O) - $(V_MKEXE)$(call MKEXE_VIA_CC,$@,$^) +tmpheader.exe: header.$(O) + $(V_MKEXE)$(call MKEXE_VIA_CC,$@,$^ $(HEADERLIBS)) +# Do not strip the header produced by cl +ifneq "$(TOOLCHAIN)" "msvc" $(STRIP) $@ +endif stdlib.cma: $(OBJS) $(V_LINKC)$(CAMLC) -a -o $@ $^ diff --git a/stdlib/header.c b/stdlib/header.c index 164bf9de2f56..8ef80b057037 100644 --- a/stdlib/header.c +++ b/stdlib/header.c @@ -13,34 +13,98 @@ /* */ /**************************************************************************/ -#define CAML_INTERNALS +/* The launcher for bytecode executables (if #! is not available) */ + +/* C11's _Noreturn is deprecated in C23 in favour of attributes */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L + #define NORETURN [[noreturn]] +#else + #define NORETURN _Noreturn +#endif + +#ifdef _WIN32 + +#define STRICT +#define WIN32_LEAN_AND_MEAN +#include + +#if WINDOWS_UNICODE +#define CP CP_UTF8 +#else +#define CP CP_ACP +#endif + +/* mingw-w64 has a limits.h which defines PATH_MAX as an alias for MAX_PATH */ +#if !defined(PATH_MAX) +#define PATH_MAX MAX_PATH +#endif + +#define SEEK_END FILE_END + +#define lseek(h, offset, origin) SetFilePointer((h), (offset), NULL, (origin)) + +typedef HANDLE file_descriptor; + +static int read(HANDLE h, LPVOID buffer, DWORD buffer_size) +{ + DWORD nread = 0; + ReadFile(h, buffer, buffer_size, &nread, NULL); + return nread; +} + +static BOOL WINAPI ctrl_handler(DWORD event) +{ + if (event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT) + return TRUE; /* pretend we've handled them */ + else + return FALSE; +} -/* The launcher for bytecode executables (if #! is not working) */ +static void write_error(const wchar_t *wstr, HANDLE hOut) +{ + DWORD consoleMode, numwritten, len; + char str[MAX_PATH]; + + if (GetConsoleMode(hOut, &consoleMode) != 0) { + /* The output stream is a Console */ + WriteConsole(hOut, wstr, lstrlen(wstr), &numwritten, NULL); + } else { /* The output stream is redirected */ + len = + WideCharToMultiByte(CP, 0, wstr, lstrlen(wstr), str, sizeof(str), + NULL, NULL); + WriteFile(hOut, str, len, &numwritten, NULL); + } +} + +NORETURN static void exit_with_error(const wchar_t *wstr1, + const wchar_t *wstr2, + const wchar_t *wstr3) +{ + HANDLE hOut = GetStdHandle(STD_ERROR_HANDLE); + if (wstr1) write_error(wstr1, hOut); + if (wstr2) write_error(wstr2, hOut); + if (wstr3) write_error(wstr3, hOut); + write_error(L"\r\n", hOut); + ExitProcess(2); +} + +#else #include #include #include -#include "caml/s.h" -#ifdef HAS_UNISTD #include -#endif #include +#include #include #include -#include "caml/mlvalues.h" -#include "caml/exec.h" - -#ifndef MAXPATHLEN -#define MAXPATHLEN 1024 -#endif -#ifndef S_ISREG -#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +/* O_BINARY is defined in Gnulib, but is not POSIX */ +#ifndef O_BINARY +#define O_BINARY 0 #endif -#ifndef SEEK_END -#define SEEK_END 2 -#endif +typedef int file_descriptor; #ifndef __CYGWIN__ @@ -48,7 +112,7 @@ static char * searchpath(char * name) { - static char fullname[MAXPATHLEN + 1]; + static char fullname[PATH_MAX + 1]; char * path; struct stat st; @@ -60,11 +124,11 @@ static char * searchpath(char * name) while(1) { char * p; for (p = fullname; *path != 0 && *path != ':'; p++, path++) - if (p < fullname + MAXPATHLEN) *p = *path; - if (p != fullname && p < fullname + MAXPATHLEN) + if (p < fullname + PATH_MAX) *p = *path; + if (p != fullname && p < fullname + PATH_MAX) *p++ = '/'; for (char *q = name; *q != 0; p++, q++) - if (p < fullname + MAXPATHLEN) *p = *q; + if (p < fullname + PATH_MAX) *p = *q; *p = 0; if (stat(fullname, &st) == 0 && S_ISREG(st.st_mode)) break; if (*path == 0) return name; @@ -123,26 +187,42 @@ static char * searchpath(char * name) #endif -static unsigned long read_size(char * ptr) +NORETURN static void exit_with_error(const char *str1, + const char *str2, + const char *str3) { - unsigned char * p = (unsigned char *) ptr; - return ((unsigned long) p[0] << 24) + ((unsigned long) p[1] << 16) + - ((unsigned long) p[2] << 8) + p[3]; + if (str1) fputs(str1, stderr); + if (str2) fputs(str2, stderr); + if (str3) fputs(str3, stderr); + fputs("\n", stderr); + exit(2); } -static char * read_runtime_path(int fd) +#endif /* defined(_WIN32) */ + +#define CAML_INTERNALS +#include "caml/exec.h" + +static uint32_t read_size(const char *ptr) +{ + const unsigned char *p = (const unsigned char *)ptr; + return ((uint32_t) p[0] << 24) | ((uint32_t) p[1] << 16) | + ((uint32_t) p[2] << 8) | p[3]; +} + +static char * read_runtime_path(file_descriptor fd) { char buffer[TRAILER_SIZE]; - static char runtime_path[MAXPATHLEN]; + static char runtime_path[PATH_MAX]; int num_sections; uint32_t path_size; long ofs; - lseek(fd, (long) -TRAILER_SIZE, SEEK_END); + if (lseek(fd, -TRAILER_SIZE, SEEK_END) == -1) return NULL; if (read(fd, buffer, TRAILER_SIZE) < TRAILER_SIZE) return NULL; num_sections = read_size(buffer); ofs = TRAILER_SIZE + num_sections * 8; - lseek(fd, -ofs, SEEK_END); + if (lseek(fd, -ofs, SEEK_END) == -1) return NULL; path_size = 0; for (int i = 0; i < num_sections; i++) { if (read(fd, buffer, 8) < 8) return NULL; @@ -154,37 +234,80 @@ static char * read_runtime_path(int fd) ofs += read_size(buffer + 4); } if (path_size == 0) return NULL; - if (path_size >= MAXPATHLEN) return NULL; - lseek(fd, -ofs, SEEK_END); + if (path_size >= PATH_MAX) return NULL; + if (lseek(fd, -ofs, SEEK_END) == -1) return NULL; if (read(fd, runtime_path, path_size) != path_size) return NULL; return runtime_path; } -static void errwrite(char * msg) +#ifdef _WIN32 + +NORETURN void __cdecl wmainCRTStartup(void) { - fputs(msg, stderr); + wchar_t truename[MAX_PATH]; + char *runtime_path; + wchar_t wruntime_path[MAX_PATH]; + HANDLE h; + STARTUPINFO stinfo; + PROCESS_INFORMATION procinfo; + DWORD retcode; + + if (GetModuleFileName(NULL, truename, sizeof(truename)/sizeof(wchar_t)) == 0) + exit_with_error(L"Out of memory", NULL, NULL); + + h = CreateFile(truename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (h == INVALID_HANDLE_VALUE || + (runtime_path = read_runtime_path(h)) == NULL || + !MultiByteToWideChar(CP, 0, runtime_path, -1, wruntime_path, + sizeof(wruntime_path)/sizeof(wchar_t))) + exit_with_error(NULL, truename, + L" not found or is not a bytecode executable file"); + CloseHandle(h); + if (SearchPath(NULL, wruntime_path, L".exe", sizeof(truename)/sizeof(wchar_t), + truename, NULL)) { + /* Need to ignore ctrl-C and ctrl-break, otherwise we'll die and take + the underlying OCaml program with us! */ + SetConsoleCtrlHandler(ctrl_handler, TRUE); + + stinfo.cb = sizeof(stinfo); + stinfo.lpReserved = NULL; + stinfo.lpDesktop = NULL; + stinfo.lpTitle = NULL; + stinfo.dwFlags = 0; + stinfo.cbReserved2 = 0; + stinfo.lpReserved2 = NULL; + if (CreateProcess(truename, GetCommandLine(), NULL, NULL, TRUE, 0, + NULL, NULL, &stinfo, &procinfo)) { + CloseHandle(procinfo.hThread); + WaitForSingleObject(procinfo.hProcess, INFINITE); + GetExitCodeProcess(procinfo.hProcess, &retcode); + CloseHandle(procinfo.hProcess); + ExitProcess(retcode); + } + } + + exit_with_error(L"Cannot exec ", wruntime_path, NULL); } -#ifndef O_BINARY -#define O_BINARY 0 -#endif +#else -int main(int argc, char ** argv) +int main(int argc, char *argv[]) { - char * truename, * runtime_path; + char *truename, *runtime_path; int fd; truename = searchpath(argv[0]); fd = open(truename, O_RDONLY | O_BINARY); - if (fd == -1 || (runtime_path = read_runtime_path(fd)) == NULL) { - errwrite(truename); - errwrite(" not found or is not a bytecode executable file\n"); - return 2; - } + if (fd == -1 || (runtime_path = read_runtime_path(fd)) == NULL) + exit_with_error(NULL, truename, + " not found or is not a bytecode executable file"); + close(fd); + argv[0] = truename; - execv(runtime_path, argv); - errwrite("Cannot exec "); - errwrite(runtime_path); - errwrite("\n"); - return 2; + execvp(runtime_path, argv); + + exit_with_error("Cannot exec ", runtime_path, NULL); } + +#endif /* defined(_WIN32) */ diff --git a/stdlib/headernt.c b/stdlib/headernt.c deleted file mode 100644 index 9815f0415b51..000000000000 --- a/stdlib/headernt.c +++ /dev/null @@ -1,169 +0,0 @@ -/**************************************************************************/ -/* */ -/* OCaml */ -/* */ -/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */ -/* */ -/* Copyright 1998 Institut National de Recherche en Informatique et */ -/* en Automatique. */ -/* */ -/* All rights reserved. This file is distributed under the terms of */ -/* the GNU Lesser General Public License version 2.1, with the */ -/* special exception on linking described in the file LICENSE. */ -/* */ -/**************************************************************************/ - -#define CAML_INTERNALS - -#define STRICT -#define WIN32_LEAN_AND_MEAN - -#include -#include "caml/mlvalues.h" -#include "caml/exec.h" - -#ifndef __MINGW32__ -#pragma comment(linker , "/subsystem:console") -#pragma comment(lib , "kernel32") -#ifdef _UCRT -#pragma comment(lib , "ucrt.lib") -#pragma comment(lib , "vcruntime.lib") -#endif -#endif - -Caml_inline unsigned long read_size(const char * const ptr) -{ - const unsigned char * const p = (const unsigned char * const) ptr; - return ((unsigned long) p[0] << 24) | ((unsigned long) p[1] << 16) | - ((unsigned long) p[2] << 8) | p[3]; -} - -Caml_inline char * read_runtime_path(HANDLE h) -{ - char buffer[TRAILER_SIZE]; - static char runtime_path[MAX_PATH]; - DWORD nread; - int num_sections, path_size; - long ofs; - - if (SetFilePointer(h, -TRAILER_SIZE, NULL, FILE_END) == -1) return NULL; - if (! ReadFile(h, buffer, TRAILER_SIZE, &nread, NULL)) return NULL; - if (nread != TRAILER_SIZE) return NULL; - num_sections = read_size(buffer); - ofs = TRAILER_SIZE + num_sections * 8; - if (SetFilePointer(h, - ofs, NULL, FILE_END) == -1) return NULL; - path_size = 0; - for (int i = 0; i < num_sections; i++) { - if (! ReadFile(h, buffer, 8, &nread, NULL) || nread != 8) return NULL; - if (buffer[0] == 'R' && buffer[1] == 'N' && - buffer[2] == 'T' && buffer[3] == 'M') { - path_size = read_size(buffer + 4); - ofs += path_size; - } else if (path_size > 0) - ofs += read_size(buffer + 4); - } - if (path_size == 0) return NULL; - if (path_size >= MAX_PATH) return NULL; - if (SetFilePointer(h, -ofs, NULL, FILE_END) == -1) return NULL; - if (! ReadFile(h, runtime_path, path_size, &nread, NULL)) return NULL; - if (nread != path_size) return NULL; - return runtime_path; -} - -static BOOL WINAPI ctrl_handler(DWORD event) -{ - if (event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT) - return TRUE; /* pretend we've handled them */ - else - return FALSE; -} - -#if WINDOWS_UNICODE -#define CP CP_UTF8 -#else -#define CP CP_ACP -#endif - -static void write_console(HANDLE hOut, WCHAR *wstr) -{ - DWORD consoleMode, numwritten, len; - static char str[MAX_PATH]; - - if (GetConsoleMode(hOut, &consoleMode) != 0) { - /* The output stream is a Console */ - WriteConsole(hOut, wstr, wcslen(wstr), &numwritten, NULL); - } else { /* The output stream is redirected */ - len = - WideCharToMultiByte(CP, 0, wstr, wcslen(wstr), str, sizeof(str), - NULL, NULL); - WriteFile(hOut, str, len, &numwritten, NULL); - } -} - -CAMLnoret Caml_inline void run_runtime(wchar_t * runtime, - wchar_t * const cmdline) -{ - wchar_t path[MAX_PATH]; - STARTUPINFO stinfo; - PROCESS_INFORMATION procinfo; - DWORD retcode; - if (SearchPath(NULL, runtime, L".exe", sizeof(path)/sizeof(wchar_t), - path, NULL) == 0) { - HANDLE errh; - errh = GetStdHandle(STD_ERROR_HANDLE); - write_console(errh, L"Cannot exec "); - write_console(errh, runtime); - write_console(errh, L"\r\n"); - ExitProcess(2); - } - /* Need to ignore ctrl-C and ctrl-break, otherwise we'll die and take - the underlying OCaml program with us! */ - SetConsoleCtrlHandler(ctrl_handler, TRUE); - - stinfo.cb = sizeof(stinfo); - stinfo.lpReserved = NULL; - stinfo.lpDesktop = NULL; - stinfo.lpTitle = NULL; - stinfo.dwFlags = 0; - stinfo.cbReserved2 = 0; - stinfo.lpReserved2 = NULL; - if (!CreateProcess(path, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, - &stinfo, &procinfo)) { - HANDLE errh; - errh = GetStdHandle(STD_ERROR_HANDLE); - write_console(errh, L"Cannot exec "); - write_console(errh, runtime); - write_console(errh, L"\r\n"); - ExitProcess(2); - } - CloseHandle(procinfo.hThread); - WaitForSingleObject(procinfo.hProcess , INFINITE); - GetExitCodeProcess(procinfo.hProcess , &retcode); - CloseHandle(procinfo.hProcess); - ExitProcess(retcode); -} - -int wmain(void) -{ - wchar_t truename[MAX_PATH]; - wchar_t * cmdline = GetCommandLine(); - char * runtime_path; - wchar_t wruntime_path[MAX_PATH]; - HANDLE h; - - GetModuleFileName(NULL, truename, sizeof(truename)/sizeof(wchar_t)); - h = CreateFile(truename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, NULL); - if (h == INVALID_HANDLE_VALUE || - (runtime_path = read_runtime_path(h)) == NULL) { - HANDLE errh; - errh = GetStdHandle(STD_ERROR_HANDLE); - write_console(errh, truename); - write_console(errh, L" not found or is not a bytecode executable file\r\n"); - ExitProcess(2); - } - CloseHandle(h); - MultiByteToWideChar(CP, 0, runtime_path, -1, wruntime_path, - sizeof(wruntime_path)/sizeof(wchar_t)); - run_runtime(wruntime_path , cmdline); -} diff --git a/testsuite/in_prefix/README.md b/testsuite/in_prefix/README.md index da889954b1d0..513b5d641d99 100644 --- a/testsuite/in_prefix/README.md +++ b/testsuite/in_prefix/README.md @@ -101,10 +101,10 @@ program is executed with `-vnum`. Additionally, for native Windows, executables are additionally called with `-M` as an argument, both with and without the `.exe`. This exercises a known bug in -the hand-off between the executable launcher (`stdlib/headernt.c`) and -`ocamlrun` where, for example, `ocamlc.byte` when resolved in `PATH` just runs -as though it were `ocamlrun`. The test works on the basis that `-M` is only a -valid argument for `ocamlrun` (returning the magic number). +the hand-off between the executable launcher (`stdlib/header.c`) and `ocamlrun` +where, for example, `ocamlc.byte` when resolved in `PATH` just runs as though it +were `ocamlrun`. The test works on the basis that `-M` is only a valid argument +for `ocamlrun` (returning the magic number). Exercises: - Bytecode executable header and logic in `ocamlc` for computing the "shebang" diff --git a/testsuite/tools/testLinkModes.ml b/testsuite/tools/testLinkModes.ml index a7c4d0be1fb5..a21feb923a99 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -313,7 +313,7 @@ let test_runs usr_bin_sh test_program_path test_program | Tendered {header = Header_exe; _} -> if argv0_not_ocaml then if Sys.win32 then - (* stdlib/headernt.c will find ocamlrun (because it effectively + (* stdlib/header.c will find ocamlrun (because it effectively uses caml_executable_name) but fails to hand off the bytecode image, which causes ocamlrun to exit with code 127 *) Fail 127 @@ -324,10 +324,10 @@ let test_runs usr_bin_sh test_program_path test_program executable. Somewhat confusingly, it exits with code 2 *) Fail 2 else if Sys.win32 then - (* stdlib/headernt.c correctly preserves argv[0] *) + (* stdlib/header.c correctly preserves argv[0] for Windows *) Success {executable_name = test_program_path; argv0} else - (* stdlib/header.c does not preserve argv[0] *) + (* stdlib/header.c does not preserve argv[0] for Unix *) Success {executable_name = argv0_resolved; argv0 = argv0_resolved} | Custom -> From 2d3de3ebc11fd33e87177f8a109e1c4670fbb93e Mon Sep 17 00:00:00 2001 From: Florian Angeletti Date: Fri, 28 Nov 2025 17:40:42 +0100 Subject: [PATCH 08/25] Merge pull request PR#14243 from dra27/ld.conf Relocatable OCaml - explicit-relative paths in `ld.conf` (cherry picked from commit 1e05f341eaf6a7e953332e6081c86b275959191d) --- Changes | 25 ++++ Makefile | 15 +-- bytecomp/dll.ml | 19 +-- configure | 47 ++++++++ configure.ac | 37 ++++++ manual/src/cmds/runtime.etex | 4 +- ocaml-variants.opam | 1 + otherlibs/Makefile.otherlibs.common | 14 +-- otherlibs/systhreads/Makefile | 6 +- runtime/caml/dynlink.h | 9 +- runtime/caml/osdeps.h | 8 ++ runtime/dynlink.c | 175 +++++++++++++++++++++------- runtime/startup_byt.c | 13 ++- testsuite/in_prefix/README.md | 7 +- testsuite/tools/dump_load_path.c | 3 +- testsuite/tools/testLinkModes.ml | 6 +- testsuite/tools/testRelocation.ml | 4 +- testsuite/tools/testToplevel.ml | 1 - testsuite/tools/test_ld_conf.ml | 140 +++++----------------- utils/config.mli | 7 +- 20 files changed, 329 insertions(+), 212 deletions(-) diff --git a/Changes b/Changes index 483c1ec262a5..57807de2b376 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,25 @@ OCaml 5.3 maintenance version ----------------------------- +### Runtime system: + +* #14243: Explicit relative paths in ld.conf (".", "..", "./", + "../") are interpreted as being relative to the directory ld.conf + was loaded from, and the default ld.conf now uses relative paths, rather than + embedding the absolute path to the Standard Library. The brave may continue to + put implicit paths in ld.conf. The interpretation of CAML_LD_LIBRARY_PATH is + unaltered. Additionally, ld.conf is loaded from all of $OCAMLLIB/ld.conf, + $CAMLLIB/ld.conf and standard_library_default/ld.conf rather than just the + first one found. ld.conf files with CRLF line endings are now consistently + normalised on both Windows and Unix. + (David Allsopp, review by Jonah Beckford, Damien Doligez and Hugo Heuzard) + +### Internal/compiler-libs changes: + +- #14243: ocamlc now uses the same code as the runtime to parse ld.conf (via a + C primitive), eliminating some highly obscure corner cases. + (David Allsopp, review by Jonah Beckford, Damien Doligez and Hugo Heuzard) + ### Build system: - #13494: Use native symlinks on Windows for the OCaml installation, reducing @@ -12,6 +31,12 @@ OCaml 5.3 maintenance version is dash, rather than bash, this now works correctly). (David Allsopp and Samuel Hym, review by Antonin Décimo and Gabriel Scherer) +- #14243: New configure option --with-additional-stublibsdir allows an + additional directory to be added to the start of ld.conf. Additionally, the + stublibs subdirectory is no longer created, nor added to ld.conf, when + building OCaml with --disable-shared. + (David Allsopp, review by Jonah Beckford, Damien Doligez and Hugo Heuzard) + ### Bug fixes: - #13790: Fix bytecode-only build of Cygwin when flexlink is being bootstrapped diff --git a/Makefile b/Makefile index e5ea64f02b88..a80a4f2b95c5 100644 --- a/Makefile +++ b/Makefile @@ -1267,8 +1267,7 @@ runtime_BUILT_HEADERS = $(addprefix runtime/, \ ## Targets to build and install runtime_PROGRAMS = runtime/ocamlrun$(EXE) -runtime_BYTECODE_STATIC_LIBRARIES = $(addprefix runtime/, \ - ld.conf libcamlrun.$(A)) +runtime_BYTECODE_STATIC_LIBRARIES = runtime/libcamlrun.$(A) runtime_BYTECODE_SHARED_LIBRARIES = runtime_NATIVE_STATIC_LIBRARIES = \ runtime/libasmrun.$(A) runtime/libcomprmarsh.$(A) @@ -1356,10 +1355,6 @@ endif ## Generated non-object files -runtime/ld.conf: $(ROOTDIR)/Makefile.config - $(V_GEN)echo "$(STUBLIBDIR)" > $@ && \ - echo "$(LIBDIR)" >> $@ - runtime/primitives: runtime/gen_primitives.sh $(runtime_BYTECODE_C_SOURCES) $(V_GEN)runtime/gen_primitives.sh $@ $(runtime_BYTECODE_C_SOURCES) @@ -1613,7 +1608,7 @@ makeruntime: runtime-all stdlib/libcamlrun.$(A): runtime-all cd stdlib; $(LN) ../runtime/libcamlrun.$(A) . clean:: - rm -f $(addprefix runtime/, *.o *.obj *.a *.lib *.so *.dll ld.conf) + rm -f $(addprefix runtime/, *.o *.obj *.a *.lib *.so *.dll) rm -f $(addprefix runtime/, ocamlrun ocamlrund ocamlruni ocamlruns sak) rm -f $(addprefix runtime/, \ ocamlrun.exe ocamlrund.exe ocamlruni.exe ocamlruns.exe sak.exe) @@ -2674,7 +2669,7 @@ endif otherlibs/dynlink/dynlink_cmxs_format.mli \ otherlibs/dynlink/dynlink_platform_intf.mli $(MAKE) -C otherlibs distclean - rm -f $(runtime_CONFIGURED_HEADERS) + rm -f $(runtime_CONFIGURED_HEADERS) runtime/ld.conf $(MAKE) -C stdlib distclean $(MAKE) -C testsuite distclean rm -f tools/eventlog_metadata tools/*.bak @@ -2695,13 +2690,15 @@ INSTALL_LIBDIR_DYNLINK = $(INSTALL_LIBDIR)/dynlink install: $(MKDIR) "$(INSTALL_BINDIR)" $(MKDIR) "$(INSTALL_LIBDIR)" +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" $(MKDIR) "$(INSTALL_STUBLIBDIR)" +endif $(MKDIR) "$(INSTALL_COMPLIBDIR)" $(MKDIR) "$(INSTALL_DOCDIR)" $(MKDIR) "$(INSTALL_INCDIR)" $(MKDIR) "$(INSTALL_LIBDIR_PROFILING)" $(INSTALL_PROG) $(runtime_PROGRAMS) "$(INSTALL_BINDIR)" - $(INSTALL_DATA) $(runtime_BYTECODE_STATIC_LIBRARIES) \ + $(INSTALL_DATA) runtime/ld.conf $(runtime_BYTECODE_STATIC_LIBRARIES) \ "$(INSTALL_LIBDIR)" ifneq "$(runtime_BYTECODE_SHARED_LIBRARIES)" "" $(INSTALL_PROG) $(runtime_BYTECODE_SHARED_LIBRARIES) \ diff --git a/bytecomp/dll.ml b/bytecomp/dll.ml index f86efc6e93be..c93d24f5c29d 100644 --- a/bytecomp/dll.ml +++ b/bytecomp/dll.ml @@ -138,22 +138,7 @@ let synchronize_primitive num symb = assert (actual_num = num) end -(* Read the [ld.conf] file and return the corresponding list of directories *) - -let ld_conf_contents () = - let path = ref [] in - begin try - let ic = open_in (Filename.concat Config.standard_library "ld.conf") in - begin try - while true do - path := input_line ic :: !path - done - with End_of_file -> () - end; - close_in ic - with Sys_error _ -> () - end; - List.rev !path +external ld_conf_contents : string -> string list = "caml_dynlink_parse_ld_conf" (* Split the CAML_LD_LIBRARY_PATH environment variable and return the corresponding list of directories. *) @@ -169,7 +154,7 @@ let ld_library_path_contents () = let init_compile nostdlib = search_path := ld_library_path_contents() @ - (if nostdlib then [] else ld_conf_contents()) + (if nostdlib then [] else ld_conf_contents Config.standard_library_default) (* Initialization for linking in core (dynlink or toplevel) *) diff --git a/configure b/configure index 82ed2188d856..a254cb68a0cd 100755 --- a/configure +++ b/configure @@ -1012,6 +1012,7 @@ enable_flambda_invariants enable_cmm_invariants with_target_bindir with_target_sh +with_additional_stublibsdir enable_reserved_header_bits enable_stdlib_manpages enable_warn_error @@ -1734,6 +1735,9 @@ Optional Packages: --with-target-bindir location of the runtime binaries on the target system --with-target-sh location of Posix sh on the target system + --with-additional-stublibsdir + additional directory for searching for bytecode stub + libraries --with-afl use the AFL fuzzer --with-flexdll bootstrap FlexDLL from the given sources --with-winpthreads-msvc build winpthreads (only for the MSVC port) from the @@ -3803,6 +3807,15 @@ printf "%s\n" "$as_me: WARNING: Mono is not yet supported - C sharp tests disabl esac fi +# Environment-specific set-up + +case $target in #( + *-w64-mingw32*|*-pc-windows) : + default_separator='\' ;; #( + *) : + default_separator='/' ;; +esac + # Environment variables that are taken into account @@ -4081,6 +4094,23 @@ else $as_nop fi + +# Check whether --with-additional-stublibsdir was given. +if test ${with_additional_stublibsdir+y} +then : + withval=$with_additional_stublibsdir; case $withval in #( + no) : + ocaml_additional_stublibs_dir='' ;; #( + yes) : + ocaml_additional_stublibs_dir="..${default_separator}stublibs" ;; #( + *) : + ocaml_additional_stublibs_dir="$withval" ;; +esac +else $as_nop + ocaml_additional_stublibs_dir='' +fi + + # Check whether --enable-reserved-header-bits was given. if test ${enable_reserved_header_bits+y} then : @@ -21063,6 +21093,10 @@ unset ac_cv_header_flexdll_h # (this is needed for the OCaml configuration module) +# Create ld.conf +ac_config_commands="$ac_config_commands runtime/ld.conf" + + # Just before config.status is generated, determine the final values for MKEXE, # MKDLL, MKMAINDLL and MKEXE_VIA_CC. The final variables controlling these are: # $mkexe - the linking command and munged CFLAGS + any extra flexlink flags @@ -22184,6 +22218,11 @@ fi '$(echo "$target_launch_method" | sed -e "s/'/'\"'\"'/g")' ocaml_bindir='$(echo "$ocaml_bindir" | sed -e "s/'/'\"'\"'/g")' target_bindir='$(echo "$target_bindir" | sed -e "s/'/'\"'\"'/g")' +ocaml_additional_stublibs_dir=\ +'$(echo "$ocaml_additional_stublibs_dir" | sed -e "s/'/'\"'\"'/g")' + ocaml_libdir='$(echo "$ocaml_libdir" | sed -e "s/'/'\"'\"'/g")' + default_separator='$default_separator' + supports_shared_libraries='$supports_shared_libraries' _ACEOF @@ -22223,6 +22262,7 @@ do "shebang") CONFIG_COMMANDS="$CONFIG_COMMANDS shebang" ;; "otherlibs/systhreads/META") CONFIG_FILES="$CONFIG_FILES otherlibs/systhreads/META" ;; "ocamltest/ocamltest_unix.ml") CONFIG_LINKS="$CONFIG_LINKS ocamltest/ocamltest_unix.ml:${ocamltest_unix_mod}" ;; + "runtime/ld.conf") CONFIG_COMMANDS="$CONFIG_COMMANDS runtime/ld.conf" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac @@ -23357,6 +23397,13 @@ ltmain=$ac_aux_dir/ltmain.sh > stdlib/runtime.info printf '%s\n%s\000\n' "$target_launch_method" "$target_bindir" \ > stdlib/target_runtime.info ;; + "runtime/ld.conf":C) rm -f runtime/ld.conf + test x"$ocaml_additional_stublibs_dir" = 'x' || \ + echo "$ocaml_additional_stublibs_dir" > runtime/ld.conf + if $supports_shared_libraries; then + echo ".${default_separator}stublibs" >> runtime/ld.conf + fi + echo "." >> runtime/ld.conf ;; esac done # for ac_tag diff --git a/configure.ac b/configure.ac index 144a6836ec4a..146cdcd90c94 100644 --- a/configure.ac +++ b/configure.ac @@ -341,6 +341,17 @@ AS_IF([test -n "$csc"], AS_CASE([$host_cpu], [i*86], [CSCFLAGS="$CSCFLAGS /platform:x86"])], [AC_MSG_WARN([Mono is not yet supported - C sharp tests disabled])])]) +# Environment-specific set-up + +dnl The separator should be being treated differently for host/target for +dnl cross-compilers, but the installation layout for cross-compilers is already +dnl incorrect (for example, ld.conf is processed by _both_ the host and target +dnl runtimes), so this is left as target-specific for now. +AS_CASE([$target], + [*-w64-mingw32*|*-pc-windows], + [default_separator='\'], + [default_separator='/']) + # Environment variables that are taken into account AC_ARG_VAR([AS], [which assembler to use]) @@ -522,6 +533,17 @@ AC_ARG_WITH([target-sh], [target_launch_method="$withval"])], [target_launch_method='']) +AC_ARG_WITH([additional-stublibsdir], + [AS_HELP_STRING([--with-additional-stublibsdir], + [additional directory for searching for bytecode stub libraries])], + [AS_CASE([$withval], + [no], + [ocaml_additional_stublibs_dir=''], + [yes], + [ocaml_additional_stublibs_dir="..${default_separator}stublibs"], + [ocaml_additional_stublibs_dir="$withval"])], + [ocaml_additional_stublibs_dir='']) + AC_ARG_ENABLE([reserved-header-bits], [AS_HELP_STRING([--enable-reserved-header-bits=BITS], [reserve BITS (between 0 and 31) bits in block headers])], @@ -2710,6 +2732,21 @@ AC_CONFIG_COMMANDS_PRE([ prefix="$saved_prefix" exec_prefix="$saved_exec_prefix"]) +# Create ld.conf +AC_CONFIG_COMMANDS([runtime/ld.conf], + [rm -f runtime/ld.conf + test x"$ocaml_additional_stublibs_dir" = 'x' || \ + echo "$ocaml_additional_stublibs_dir" > runtime/ld.conf + if $supports_shared_libraries; then + echo ".${default_separator}stublibs" >> runtime/ld.conf + fi + echo "." >> runtime/ld.conf], + [ocaml_additional_stublibs_dir=\ +'$(echo "$ocaml_additional_stublibs_dir" | sed -e "s/'/'\"'\"'/g")' + ocaml_libdir='$(echo "$ocaml_libdir" | sed -e "s/'/'\"'\"'/g")' + default_separator='$default_separator' + supports_shared_libraries='$supports_shared_libraries']) + # Just before config.status is generated, determine the final values for MKEXE, # MKDLL, MKMAINDLL and MKEXE_VIA_CC. The final variables controlling these are: # $mkexe - the linking command and munged CFLAGS + any extra flexlink flags diff --git a/manual/src/cmds/runtime.etex b/manual/src/cmds/runtime.etex index 5f41a2d097b9..ac7e827d2186 100644 --- a/manual/src/cmds/runtime.etex +++ b/manual/src/cmds/runtime.etex @@ -250,7 +250,9 @@ library directory. Users can add there the names of other directories containing frequently-used shared libraries; however, for consistency of installation, we recommend that shared libraries are installed directly in the system "stublibs" directory, rather than adding lines -to the "ld.conf" file. +to the "ld.conf" file. "ocamlrun" will add lines from "ld.conf" files +found in the directories pointed to by "OCAMLLIB", "CAMLLIB" and the +standard library directory, in that order. \item Default directories searched by the system dynamic loader. Under Unix, these generally include "/lib" and "/usr/lib", plus the directories listed in the file "/etc/ld.so.conf" and the environment diff --git a/ocaml-variants.opam b/ocaml-variants.opam index 6787e2bc41bf..f233b07c27f2 100644 --- a/ocaml-variants.opam +++ b/ocaml-variants.opam @@ -75,6 +75,7 @@ build: [ "--host=i686-w64-mingw32" {os-distribution = "cygwin" & system-mingw:installed & arch-x86_32:installed} "--prefix=%{prefix}%" "--docdir=%{doc}%/ocaml" + "--with-additional-stublibsdir" "--with-flexdll=%{flexdll:share}%" {os = "win32" & flexdll:installed} "--with-winpthreads-msvc=%{winpthreads:share}%" {system-msvc:installed} "-C" diff --git a/otherlibs/Makefile.otherlibs.common b/otherlibs/Makefile.otherlibs.common index f08dd13ef8cc..984c2ea64224 100644 --- a/otherlibs/Makefile.otherlibs.common +++ b/otherlibs/Makefile.otherlibs.common @@ -98,10 +98,9 @@ lib$(CLIBNAME_NATIVE).$(A): $(COBJS) INSTALL_LIBDIR_LIBNAME = $(INSTALL_LIBDIR)/$(LIBNAME) install:: - if test -f dll$(CLIBNAME_BYTECODE)$(EXT_DLL); then \ - $(INSTALL_PROG) \ - dll$(CLIBNAME_BYTECODE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)"; \ - fi +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" + $(INSTALL_PROG) dll$(CLIBNAME_BYTECODE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)" +endif ifneq "$(STUBSLIB_BYTECODE)" "" $(INSTALL_DATA) $(STUBSLIB_BYTECODE) "$(INSTALL_LIBDIR)/" endif @@ -132,10 +131,9 @@ installopt: if test -f $(LIBNAME).cmxs; then \ $(INSTALL_PROG) $(LIBNAME).cmxs "$(INSTALL_LIBDIR_LIBNAME)"; \ fi - if test -f dll$(CLIBNAME_NATIVE)$(EXT_DLL); then \ - $(INSTALL_PROG) \ - dll$(CLIBNAME_NATIVE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)"; \ - fi +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" + $(INSTALL_PROG) dll$(CLIBNAME_NATIVE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)" +endif ifneq "$(STUBSLIB_NATIVE)" "" $(INSTALL_DATA) $(STUBSLIB_NATIVE) "$(INSTALL_LIBDIR)/" endif diff --git a/otherlibs/systhreads/Makefile b/otherlibs/systhreads/Makefile index 64d41cc19d66..f48b2cbc1eff 100644 --- a/otherlibs/systhreads/Makefile +++ b/otherlibs/systhreads/Makefile @@ -100,9 +100,9 @@ distclean: clean INSTALL_THREADSLIBDIR=$(INSTALL_LIBDIR)/$(LIBNAME) install: - if test -f dllthreads$(EXT_DLL); then \ - $(INSTALL_PROG) dllthreads$(EXT_DLL) "$(INSTALL_STUBLIBDIR)"; \ - fi +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" + $(INSTALL_PROG) dllthreads$(EXT_DLL) "$(INSTALL_STUBLIBDIR)" +endif $(INSTALL_DATA) libthreads.$(A) "$(INSTALL_LIBDIR)" $(MKDIR) "$(INSTALL_THREADSLIBDIR)" $(INSTALL_DATA) \ diff --git a/runtime/caml/dynlink.h b/runtime/caml/dynlink.h index d5f7170e6fcb..a7441fcc0e4f 100644 --- a/runtime/caml/dynlink.h +++ b/runtime/caml/dynlink.h @@ -41,11 +41,10 @@ extern void caml_build_primitive_table_builtin(void); /* Unload all the previously loaded shared libraries */ extern void caml_free_shared_libs(void); -/* Return the effective location of the standard library */ -extern char_os * caml_get_stdlib_location(void); - -/* Parse ld.conf and add the lines read to caml_shared_libs_path */ -extern char_os * caml_parse_ld_conf(void); +/* If found, parse $OCAMLLIB/ld.conf, $CAMLLIB/ld.conf and stdlib/ld.conf in + that order and add the lines read to table. */ +extern char_os * caml_parse_ld_conf(const char_os * stdlib, + struct ext_table * table); #endif /* CAML_INTERNALS */ diff --git a/runtime/caml/osdeps.h b/runtime/caml/osdeps.h index 7215668c397f..07666f26a6af 100644 --- a/runtime/caml/osdeps.h +++ b/runtime/caml/osdeps.h @@ -135,6 +135,14 @@ CAMLextern clock_t caml_win32_clock(void); CAMLextern value caml_win32_xdg_defaults(void); +#define CAML_DIR_SEP T("\\") +#define Is_separator(c) (c == '\\' || c == '/') + +#else + +#define CAML_DIR_SEP T("/") +#define Is_separator(c) (c == '/') + #endif /* _WIN32 */ /* Returns the current value of a counter that increments once per nanosecond. diff --git a/runtime/dynlink.c b/runtime/dynlink.c index c06f8adb9aac..c367115d059d 100644 --- a/runtime/dynlink.c +++ b/runtime/dynlink.c @@ -43,12 +43,12 @@ #include "build_config.h" -#ifndef NATIVE_CODE - #ifndef O_BINARY #define O_BINARY 0 #endif +#ifndef NATIVE_CODE + /* The table of primitives */ struct ext_table caml_prim_table; @@ -78,23 +78,51 @@ static c_primitive lookup_primitive(char * name) return NULL; } +#endif /* NATIVE_CODE */ + /* Parse the ld.conf file and add the directories listed there to the search path */ #define LD_CONF_NAME T("ld.conf") -CAMLexport char_os * caml_get_stdlib_location(void) +/* Return a copy of [path], interpreting explicit-relative paths relative to + [root]. [root] must not end with a directory separator and is expected to be + absolute. The result of this function can never be ".", ".." or a path + beginning "./" or "../". Note that the function does not necessarily + canonicalise the path. */ +static char_os *make_relative_path_absolute(char_os *path, char_os *root) { - char_os * stdlib; - stdlib = caml_secure_getenv(T("OCAMLLIB")); - if (stdlib == NULL) stdlib = caml_secure_getenv(T("CAMLLIB")); - if (stdlib == NULL) stdlib = OCAML_STDLIB_DIR; - return stdlib; + if (path[0] == '.') { + if (path[1] == '\0') { + /* path is exactly "." => return root */ + return caml_stat_strdup_os(root); + } else if (Is_separator(path[1])) { + /* path is exactly "./" or begins "./". In both cases, replace the "." + with root */ + return caml_stat_strconcat_os(2, root, (path + 1)); + } else if (path[1] == '.' && (path[2] == '\0' || Is_separator(path[2]))) { + /* path is either exactly ".." or begins "../" => prefix it with root + (which has no trailing separator) */ + return caml_stat_strconcat_os(3, root, CAML_DIR_SEP, path); + } else { + /* path is not explicit-relative, but simply begins with a dot + => return a copy */ + return caml_stat_strdup_os(path); + } + } else { + /* path is not explicit-relative => return a copy */ + return caml_stat_strdup_os(path); + } } -CAMLexport char_os * caml_parse_ld_conf(void) +CAMLexport char_os * caml_parse_ld_conf(const char_os * stdlib, + struct ext_table *table) { - char_os * stdlib, * ldconfname, * wconfig, * p, * q; + const char_os * const locations[3] = { + caml_secure_getenv(T("OCAMLLIB")), + caml_secure_getenv(T("CAMLLIB")), + stdlib}; + char_os * libroot, * ldconfname, * wconfig, * p, * q; char * config; #ifdef _WIN32 struct _stati64 st; @@ -102,40 +130,105 @@ CAMLexport char_os * caml_parse_ld_conf(void) struct stat st; #endif int ldconf, nread; + size_t length = 0; + struct ext_table entries; + + /* Use a temporary ext_table to hold the individually-allocated entries */ + caml_ext_table_init(&entries, 8); + for (int i = 0; i < sizeof(locations) / sizeof(locations[0]); i++) { + if (locations[i] != NULL) { + libroot = caml_stat_strdup_os(locations[i]); + size_t libroot_length = strlen_os(libroot); + while (libroot_length > 0 && Is_separator(libroot[libroot_length - 1])) + libroot[--libroot_length] = '\0'; + ldconfname = + caml_stat_strconcat_os(3, libroot, CAML_DIR_SEP, LD_CONF_NAME); + if (stat_os(ldconfname, &st) == -1) { + caml_stat_free(ldconfname); + caml_stat_free(libroot); + continue; + } + ldconf = open_os(ldconfname, O_RDONLY | O_BINARY, 0); + if (ldconf == -1) + caml_fatal_error("cannot read loader config file %s", + caml_stat_strdup_of_os(ldconfname)); + config = caml_stat_alloc(st.st_size + 1); + nread = read(ldconf, config, st.st_size); + if (nread == -1) + caml_fatal_error + ("error while reading loader config file %s", + caml_stat_strdup_of_os(ldconfname)); + close(ldconf); + config[nread] = 0; + wconfig = caml_stat_strdup_to_os(config); + caml_stat_free(config); + caml_stat_free(ldconfname); + + p = wconfig; + while (*p != '\0') { + for (q = p; *q != '\0' && *q != '\n'; q++) /*nothing*/; + char_os *r = q; + if (*q == '\n') { + r++; + /* Ignore any trailing CR characters, so that CR*LF is uniformly + treated as a single LF. */ + while (q > p && *(q - 1) == '\r') + q--; + } + *q = '\0'; + char_os *entry = make_relative_path_absolute(p, libroot); + length += strlen_os(entry) + 1; + caml_ext_table_add(&entries, entry); + p = r; + } - stdlib = caml_get_stdlib_location(); - ldconfname = caml_stat_strconcat_os(3, stdlib, T("/"), LD_CONF_NAME); - if (stat_os(ldconfname, &st) == -1) { - caml_stat_free(ldconfname); - return NULL; - } - ldconf = open_os(ldconfname, O_RDONLY, 0); - if (ldconf == -1) - caml_fatal_error("cannot read loader config file %s", - caml_stat_strdup_of_os(ldconfname)); - config = caml_stat_alloc(st.st_size + 1); - nread = read(ldconf, config, st.st_size); - if (nread == -1) - caml_fatal_error - ("error while reading loader config file %s", - caml_stat_strdup_of_os(ldconfname)); - config[nread] = 0; - wconfig = caml_stat_strdup_to_os(config); - caml_stat_free(config); - q = wconfig; - for (p = wconfig; *p != 0; p++) { - if (*p == '\n') { - *p = 0; - caml_ext_table_add(&caml_shared_libs_path, q); - q = p + 1; + caml_stat_free(wconfig); + caml_stat_free(libroot); } } - if (q < p) caml_ext_table_add(&caml_shared_libs_path, q); - close(ldconf); - caml_stat_free(ldconfname); - return wconfig; + + /* Now concatenate them all and load the search path */ + char_os *result = caml_stat_alloc(length * sizeof(char_os)); + p = result; + for (int i = 0; i < entries.size; i++) { + char_os *entry = entries.contents[i]; + length = strlen_os(entry) + 1; + memcpy(p, entry, length * sizeof(char_os)); + caml_ext_table_add(table, p); + p += length; + } + caml_ext_table_free(&entries, 1); + + return result; +} + +/* Exposes caml_parse_ld_conf as a primitive for the bytecode compiler, saving + the duplication of the logic within the bytecode compiler. */ +CAMLprim value caml_dynlink_parse_ld_conf(value vstdlib) +{ + CAMLparam1(vstdlib); + CAMLlocal2(list, str); + + char_os *stdlib = caml_stat_strdup_to_os(String_val(vstdlib)); + struct ext_table table; + caml_ext_table_init(&table, 8); + char_os *tofree = caml_parse_ld_conf(stdlib, &table); + caml_stat_free(stdlib); + + list = Val_emptylist; + for (int i = table.size - 1; i >= 0; i--) { + str = caml_copy_string_of_os(table.contents[i]); + list = caml_alloc_2(Tag_cons, str, list); + } + + caml_ext_table_free(&table, 0); + caml_stat_free(tofree); + + CAMLreturn(list); } +#ifndef NATIVE_CODE + /* Open the given shared library and add it to shared_libs. Abort on error. */ static void open_shared_lib(char_os * name) @@ -174,6 +267,8 @@ void caml_build_primitive_table(char_os * lib_path, - directories specified on the command line with the -I option - directories specified in the CAML_LD_LIBRARY_PATH - directories specified in the executable + - directories specified in OCAMLLIB/ld.conf + - directories specified in CAMLLIB/ld.conf - directories specified in the file /ld.conf caml_shared_libs_path and caml_prim_name_table are not freed afterwards: @@ -183,7 +278,7 @@ void caml_build_primitive_table(char_os * lib_path, if (lib_path != NULL) for (char_os *p = lib_path; *p != 0; p += strlen_os(p) + 1) caml_ext_table_add(&caml_shared_libs_path, p); - caml_parse_ld_conf(); + caml_parse_ld_conf(OCAML_STDLIB_DIR, &caml_shared_libs_path); /* Open the shared libraries */ caml_ext_table_init(&shared_libs, 8); if (libs != NULL) diff --git a/runtime/startup_byt.c b/runtime/startup_byt.c index ffb4eb0bebdd..bff725886730 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -374,6 +374,15 @@ static int parse_command_line(char_os **argv) return i; } +static const char_os * get_stdlib_location(void) +{ + const char_os * stdlib; + stdlib = caml_secure_getenv(T("OCAMLLIB")); + if (stdlib == NULL) stdlib = caml_secure_getenv(T("CAMLLIB")); + if (stdlib == NULL) stdlib = OCAML_STDLIB_DIR; + return stdlib; +} + /* Print the configuration of the runtime to stdout; memory allocated is not freed, since the runtime will terminate after calling this. */ static void do_print_config(void) @@ -385,7 +394,7 @@ static void do_print_config(void) printf("standard_library_default: %s\n", caml_stat_strdup_of_os(OCAML_STDLIB_DIR)); printf("standard_library: %s\n", - caml_stat_strdup_of_os(caml_get_stdlib_location())); + caml_stat_strdup_of_os(get_stdlib_location())); printf("int_size: %d\n", 8 * (int)sizeof(value)); printf("word_size: %d\n", 8 * (int)sizeof(value) - 1); printf("os_type: %s\n", OCAML_OS_TYPE); @@ -420,7 +429,7 @@ static void do_print_config(void) /* Parse ld.conf and print the effective search path */ puts("shared_libs_path:"); - caml_parse_ld_conf(); + caml_parse_ld_conf(OCAML_STDLIB_DIR, &caml_shared_libs_path); for (int i = 0; i < caml_shared_libs_path.size; i++) { dir = caml_shared_libs_path.contents[i]; if (dir[0] == 0) diff --git a/testsuite/in_prefix/README.md b/testsuite/in_prefix/README.md index 513b5d641d99..ef01209ad976 100644 --- a/testsuite/in_prefix/README.md +++ b/testsuite/in_prefix/README.md @@ -73,8 +73,6 @@ Shims: so must be explicitly invoked via `ocamlrun` - Both toplevels contain the absolute location of the Standard Library, requiring `OCAMLLIB` to be set -- `ld.conf` contains the absolute location of the `stublibs` directory, - requiring `CAML_LD_LIBRARY_PATH` to be adjusted ### Loading archives/plugins (.cma / .cmxs) with `Dynlink` @@ -89,9 +87,8 @@ Shims: requiring `OCAMLLIB` to be set - The executable created by `ocamlc` contains the absolute location of `ocamlrun`, so must be both explicitly invoked via `ocamlrun` and also have - `CAML_LD_LIBRARY_PATH` adjusted, as that `ocamlrun` will either not load - `ld.conf` or (with `OCAMLLIB` set) will be pointed to an `ld.conf` containing - the absolute location of the `stublibs` directory + `CAML_LD_LIBRARY_PATH` or `OCAMLLIB` adjusted, as that `ocamlrun` will not be + able to find `ld.conf` ### Executing installed bytecode binaries with `-vnum` diff --git a/testsuite/tools/dump_load_path.c b/testsuite/tools/dump_load_path.c index 45c70ffc3347..a17253c568ac 100644 --- a/testsuite/tools/dump_load_path.c +++ b/testsuite/tools/dump_load_path.c @@ -22,6 +22,7 @@ #include #include #include +#include int main_os(int argc, char_os **argv) { @@ -30,7 +31,7 @@ int main_os(int argc, char_os **argv) caml_ext_table_init(&caml_shared_libs_path, 8); caml_decompose_path(&caml_shared_libs_path, caml_secure_getenv(T("CAML_LD_LIBRARY_PATH"))); - caml_parse_ld_conf(); + caml_parse_ld_conf(OCAML_STDLIB_DIR, &caml_shared_libs_path); for (int i = 0; i < caml_shared_libs_path.size; i++) { dir = caml_shared_libs_path.contents[i]; if (dir[0] == 0) diff --git a/testsuite/tools/testLinkModes.ml b/testsuite/tools/testLinkModes.ml index a21feb923a99..8c45f84b99eb 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -605,14 +605,10 @@ let compile_test usr_bin_sh config env test test_program description = need to be invoked via ocamlrun in the Renamed phase *) let runtime = mode = Bytecode && Harness.ocamlc_fails_after_rename config in - (* If shared libraries are being used, ocamlc will need to be able to - load the stub libraries to check the primitives table *) - let stubs = with_unix && tendered in (* In the Renamed phase, Config.standard_library will still point to the Original location *) let stdlib = true in - Environment.run_process - ~fails ~runtime ~stubs ~stdlib env compiler args + Environment.run_process ~fails ~runtime ~stdlib env compiler args in Environment.display_output output; exit_code diff --git a/testsuite/tools/testRelocation.ml b/testsuite/tools/testRelocation.ml index aa3439d9228f..2e7edb58dc31 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -161,9 +161,7 @@ let libdir_rules config file = - contains objects which have been created by the assembler *) let (embeds_stdlib_location, has_ocaml_debug_info, has_c_debug_info, contains_assembled_objects) = - if List.mem basename ["Makefile.config"; - "ld.conf"; - "runtime-launch-info"] then + if basename = "Makefile.config" || basename = "runtime-launch-info" then (* These files all embed the Standard Library location *) (true, false, false, false) else if basename = "config.cmx" then diff --git a/testsuite/tools/testToplevel.ml b/testsuite/tools/testToplevel.ml index 665371261129..329aaea872ea 100644 --- a/testsuite/tools/testToplevel.ml +++ b/testsuite/tools/testToplevel.ml @@ -97,7 +97,6 @@ let run config env mode = Environment.run_process ~fails:(expected_exit_code <> 0) ~runtime:(mode = Bytecode && not config.launcher_searches_for_ocamlrun) - ~stubs:(mode = Bytecode && has_c_stubs) ~stdlib:true env toplevel args in Environment.display_output output; diff --git a/testsuite/tools/test_ld_conf.ml b/testsuite/tools/test_ld_conf.ml index c866dde4ca58..3fc0a8951bf7 100644 --- a/testsuite/tools/test_ld_conf.ml +++ b/testsuite/tools/test_ld_conf.ml @@ -69,16 +69,16 @@ let tests _config env = "/", "/", None; "//", "//", None; (* Current and Parent directory names *) - ".", ".", None; - "..", "..", None; + ".", libdir, None; + "..", libdir / "..", None; (* Current and Parent directory names with OS-default trailing separator (i.e. ./ and ../ on Unix and .\ and ..\ on Windows) *) - "." / "", "." / "", None; - ".." / "", ".." / "", None; + "." / "", libdir / "", None; + ".." / "", libdir / ".." / "", None; (* "stublibs" relative to the Current and Parent directory (using OS- default separator) *) - "." / "stublibs", "." / "stublibs", None; - ".." / "stublibs", ".." / "stublibs", None; + "." / "stublibs", libdir / "stublibs", None; + ".." / "stublibs", libdir / ".." / "stublibs", None; (* Other cases - implicit and absolute entries, and entries beginning with the Current and Parent directory names *) "stublibs", "stublibs", None; @@ -88,15 +88,7 @@ let tests _config env = "/lib/ocaml", "/lib/ocaml", Some "/lib/ocaml\r"; ] in let fold (main, main_outcome, main_outcome_cr) (line, outcome, cr) = - let cr = match cr with - | Some cr -> cr - | None -> - (* Windows opens ld.conf in text mode, so the \r are stripped *) - if Sys.win32 then - outcome - else - outcome ^ "\r" - in + let cr = Option.value ~default:outcome cr in line::main, outcome::main_outcome, cr::main_outcome_cr in List.fold_left fold ([], [], []) (List.rev data) @@ -185,12 +177,12 @@ let tests _config env = let tests = (* As first, but with a CR at the end of each line *) let outcome = - (* Windows opens ld.conf in text mode, so the line with just \r is - read as an empty string and consequently stripped *) + (* Known issue: Windows strips out the blank entries in the search + path (somewhat counterintuitively!) *) if Sys.win32 then main_outcome_cr else - "\r" :: main_outcome_cr + "." :: main_outcome_cr in {base with description = "Base ld.conf with CRLF endings"; stdlib = List.map (Fun.flip (^) "\r") ("" :: main); @@ -227,27 +219,28 @@ let tests _config env = stdlib = ["ld.conf"]; outcome = outcome_caml_ld_library_path @ if_ld_conf_found ["ld.conf"]} :: tests in + let ld_conf_outcome = if_ld_conf_found ["masked-stdlib"] in let tests = - (* An empty CAMLLIB should cause ld.conf in the Standard Library to be - ignored, but not CAML_LD_LIBRARY PATH *) + (* An empty CAMLLIB shouldn't hide ld.conf in the Standard Library *) {base with description = "Empty CAMLLIB"; caml_ld_library_path = Set ["env"]; camllib = Empty; stdlib = ["masked-stdlib"]; - outcome = ["env"]} :: tests in + outcome = "env" :: ld_conf_outcome} :: tests in let tests = - (* An empty OCAMLLIB should cause ld.conf in both the Standard Library and - CAMLLIB to be ignored, but not CAML_LD_LIBRARY_PATH *) + (* An empty OCAMLLIB shouldn't hide ld.conf in either the Standard Library + or CAMLLIB\ld.conf *) {description = "Empty OCAMLLIB"; caml_ld_library_path = Set ["env"]; ocamllib = Empty; camllib = Set ["masked-camllib"]; stdlib = ["masked-stdlib"]; - outcome = ["env"]} :: tests in + outcome = ["env"; "masked-camllib"] @ ld_conf_outcome} :: tests in tests in (* Batch 3: load priority, embedded NUL characters, EOL-at-EOF, etc. *) let tests = + let ld_conf_outcome = if_ld_conf_found ["libdir"] in let tests = (* OCAMLLIB should have priority over CAMLLIB and the Standard Library *) {description = "$OCAMLLIB/ld.conf"; @@ -255,19 +248,19 @@ let tests _config env = ocamllib = Set ["ocamllib\000"; "hidden"]; camllib = Set ["camllib\000"; "hidden"]; stdlib = ["libdir"]; - outcome = ["env"; "ocamllib"]} :: tests in + outcome = ["env"; "ocamllib"; "camllib"] @ ld_conf_outcome} :: tests in let tests = (* CAMLLIB should have priority over the Standard Library *) {base with description = "$CAMLLIB/ld.conf"; caml_ld_library_path = Set ["env"]; camllib = Set ["camllib\000"; "hidden"]; stdlib = ["libdir"]; - outcome = ["env"; "camllib"]} :: tests in + outcome = ["env"; "camllib"] @ ld_conf_outcome} :: tests in let tests = (* EOL-at-EOF should not add a blank entry to the search path *) {base with description = "EOF-at-EOF"; stdlib = (if Sys.win32 then ["libdir\r\n"] else ["libdir\n"]); - outcome = if_ld_conf_found ["libdir"]} :: tests in + outcome = ld_conf_outcome} :: tests in tests in tests @@ -359,94 +352,19 @@ let () = in if code = 0 then let lines = - (* Known issue: Sys.getenv processes blank environment variables - differently from _wgetenv which in the tests will cause it load - ld.conf files. The tests have been written to allow for this by - having the lines which are _not_ expected to appear on Unix be - prefixed with "masked-". *) - if Sys.win32 then - if ((test.camllib = Empty - && not (Environment.is_renamed env)) - || test.ocamllib = Empty) then - let unmask s = not (String.starts_with ~prefix:"masked-" s) in - let lines' = List.filter unmask lines in - (* If Windows behaviour has been harmonised, then the filtered - list of lines would be the same as the unfiltered list. If this - happens, insert an extra line to "poison" the test output to - prevent this behaviour from being silently fixed. *) - if lines = lines' then - "poisoned"::lines - else - lines' - else - lines - else - lines - in - let lines = - (* Known issue: ocamlc opens ld.conf in text mode on Cygwin but - ocamlrun opens it in binary mode (the default). This means that - ocamlrun will return lines ending with \r, but ocamlc will both - strip the \r and ignore a line consisting of just \r (because that - appears blank in text mode). This is mitigated by ensuring that the - \r line is always first in the test, and then adding back the \r to - the output on Cygwin. This will clearly fail if the behaviour of - ocamlrun and ocamlc is harmonised. *) - match test.stdlib with - | "\r" :: _ when Sys.cygwin && lines <> [] -> - "\r" :: List.map (Fun.flip (^) "\r") (List.tl lines) - | _ -> - lines - in - let lines = - (* Known issue: Misc.split_path_contents ignores empty strings where - caml_decompose_path does not. Mitigate it by detecting the - environment setting and simulating the line. *) - if test.caml_ld_library_path = Set [] - || test.caml_ld_library_path = Empty then + (* Known issues: + - Misc.split_path_contents ignores empty strings where + caml_decompose_path does not + - Sys.getenv can't return empty environment variables on Windows, + but _wgetenv can + - Windows strips out the blank entries in the search path + (somewhat counterintuitively!) *) + if not Sys.win32 && (test.caml_ld_library_path = Set [] + || test.caml_ld_library_path = Empty) then "." :: lines else lines in - (* Known issue: Windows strips out the blank entries in the search path - (somewhat counterintuitively!) *) - let lines = - if not Sys.win32 then - lines - else - List.drop_while (String.equal ".") lines - in - let lines = - (* Known issue: Dll.ld_conf_contents preserves NUL characters in lines - where caml_parse_ld_conf terminates processing. This is mitigated - in the test by putting a single line "hidden" after the line with - an embedded NUL. *) - let includes_nulls = - let includes_nulls = function - | Unset | Empty -> false - | Set l -> List.exists (Fun.flip String.contains '\000') l - in - includes_nulls test.ocamllib || includes_nulls test.camllib - in - if includes_nulls then - let strip_null s = - match String.index s '\000' with - | index -> - String.sub s 0 index - | exception Not_found -> - s - in - let lines' = List.map strip_null lines in - if lines <> lines' then - List.filter ((<>) "hidden") lines' - else - (* As with empty environment variables above, if this behaviour - appears to have been fixed, poison the output of the test so - that doesn't happen silently. *) - "poisoned" :: lines - else - lines - in description :: lines else Harness.fail_because "%s is expected to exit with code 0" diff --git a/utils/config.mli b/utils/config.mli index 51e31a37294c..9ac111b09ae8 100644 --- a/utils/config.mli +++ b/utils/config.mli @@ -26,8 +26,13 @@ val version: string val bindir: string (** The directory containing the binary programs *) +val standard_library_default: string +(** The configured value for the directory containing the standard libraries + + @since 5.5 *) + val standard_library: string -(** The directory containing the standard libraries *) +(** The effective directory containing the standard libraries *) val ccomp_type: string (** The "kind" of the C compiler, assembler and linker used: one of From c4281e4db85824dbba6eb830913f6cb385d618e5 Mon Sep 17 00:00:00 2001 From: Gabriel Scherer Date: Mon, 8 Dec 2025 10:21:19 +0100 Subject: [PATCH 09/25] Merge pull request PR#14244 from dra27/enable-relative Relocatable OCaml - `--with-relative-libdir` (cherry picked from commit cfbf2105cfed30ddea8383b7f2bf054c8c28a5e8) (cherry picked from commit d315509e7eb6a386e0148b8d0e4290685b528d9a) (cherry picked from commit 1ecddf15a4cae8541784ee72d033430f503dad8a) --- .depend | 4 + .github/workflows/build-msvc.yml | 16 +- .github/workflows/build.yml | 31 +- Changes | 24 ++ Makefile | 32 +- Makefile.build_config.in | 18 +- Makefile.common | 19 + appveyor.yml | 1 + asmcomp/asmlink.ml | 12 + asmcomp/asmpackager.ml | 4 + asmcomp/cmm_helpers.ml | 3 + asmcomp/cmm_helpers.mli | 3 + bytecomp/bytegen.ml | 3 +- bytecomp/bytelink.ml | 125 ++++-- bytecomp/bytesections.ml | 3 + bytecomp/bytesections.mli | 1 + configure | 393 ++++++++++++++++-- configure.ac | 250 +++++++++-- driver/compenv.ml | 13 + driver/compenv.mli | 4 + driver/main_args.ml | 8 + driver/main_args.mli | 1 + driver/maindriver.ml | 2 +- driver/optmaindriver.ml | 2 +- file_formats/cmx_format.mli | 3 +- lambda/lambda.ml | 1 + lambda/lambda.mli | 1 + lambda/printlambda.ml | 3 +- lambda/translprim.ml | 2 + man/ocamlc.1 | 9 + man/ocamlopt.1 | 9 + manual/src/cmds/unified-options.etex | 11 + middle_end/closure/closure.ml | 41 +- middle_end/compilenv.ml | 16 +- middle_end/compilenv.mli | 8 + middle_end/flambda/closure_conversion.ml | 39 +- ocamltest/ocaml_tests.ml | 11 +- ocamltest/ocamltest_config.ml.in | 2 + ocamltest/ocamltest_config.mli | 3 + runtime/backtrace_byt.c | 4 +- runtime/caml/osdeps.h | 28 ++ runtime/caml/s.h.in | 4 + runtime/caml/startup.h | 17 +- runtime/caml/sys.h | 4 + runtime/dynlink.c | 3 +- runtime/gen_primsc.sh | 11 + runtime/startup_byt.c | 73 +++- runtime/sys.c | 41 ++ runtime/unix.c | 70 ++++ runtime/win32.c | 86 ++++ testsuite/in_prefix/Makefile.test | 2 +- testsuite/in_prefix/README.md | 9 +- .../tool-debugger/find-artifacts/debuggee.ml | 1 + testsuite/tools/cmdline.ml | 2 +- testsuite/tools/dump_load_path.c | 11 +- testsuite/tools/harness.mli | 3 +- testsuite/tools/testBytecodeBinaries.ml | 11 +- testsuite/tools/testDynlink.ml | 35 +- testsuite/tools/testLinkModes.ml | 67 ++- testsuite/tools/testRelocation.ml | 82 ++-- testsuite/tools/testToplevel.ml | 2 +- testsuite/tools/test_in_prefix.ml | 10 +- testsuite/tools/test_ld_conf.ml | 26 +- tools/ci/actions/runner.sh | 83 ++++ tools/ci/appveyor/appveyor_build.sh | 4 +- tools/objinfo.ml | 17 +- tools/ocamlmklib.ml | 4 +- utils/ccomp.ml | 8 +- utils/clflags.ml | 1 + utils/clflags.mli | 1 + utils/config.common.ml.in | 23 + utils/config.fixed.ml | 2 +- utils/config.generated.ml.in | 3 +- utils/config.mli | 29 +- 74 files changed, 1633 insertions(+), 275 deletions(-) diff --git a/.depend b/.depend index fe726b33da2b..2ddbecc3d453 100644 --- a/.depend +++ b/.depend @@ -5162,6 +5162,7 @@ middle_end/flambda/closure_conversion.cmo : \ middle_end/flambda/flambda.cmi \ lambda/debuginfo.cmi \ middle_end/convert_primitives.cmi \ + middle_end/compilenv.cmi \ middle_end/compilation_unit.cmi \ middle_end/flambda/base_types/closure_origin.cmi \ middle_end/flambda/base_types/closure_id.cmi \ @@ -5190,6 +5191,7 @@ middle_end/flambda/closure_conversion.cmx : \ middle_end/flambda/flambda.cmx \ lambda/debuginfo.cmx \ middle_end/convert_primitives.cmx \ + middle_end/compilenv.cmx \ middle_end/compilation_unit.cmx \ middle_end/flambda/base_types/closure_origin.cmx \ middle_end/flambda/base_types/closure_id.cmx \ @@ -10640,11 +10642,13 @@ testsuite/tools/test_in_prefix.cmx : \ testsuite/tools/test_in_prefix.cmi testsuite/tools/test_in_prefix.cmi : testsuite/tools/test_ld_conf.cmo : \ + otherlibs/unix/unix.cmi \ testsuite/tools/harness.cmi \ testsuite/tools/environment.cmi \ utils/config.cmi \ testsuite/tools/test_ld_conf.cmi testsuite/tools/test_ld_conf.cmx : \ + otherlibs/unix/unix.cmx \ testsuite/tools/harness.cmx \ testsuite/tools/environment.cmx \ utils/config.cmx \ diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index fb78b02a21bb..0bcbb251d63a 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -36,7 +36,8 @@ jobs: let compilers = ['cl', 'clang-cl']; // # Also test i686 MSVC let include = [ - {cc: 'cl', arch: 'i686'}]; + {cc: 'cl', arch: 'i686', libdir: 'relative'}]; + let libdir = ['absolute']; // # If this is a pull request, see if the PR has the // # 'CI: Full matrix' label. This is done using an API request, // # rather than from context.payload.pull_request.labels, since we @@ -52,10 +53,14 @@ jobs: // # Test Cygwin as well compilers.push('gcc'); // # Test bytecode-only Cygwin - include.push({cc: 'gcc', arch: 'x86_64', config_arg: '--disable-native-toplevel --disable-native-compiler'}); + include.push({cc: 'gcc', arch: 'x86_64', libdir: 'absolute', config_arg: '--disable-native-toplevel --disable-native-compiler'}); + // # Test i686 MSVC absolute + include.push({cc: 'cl', arch: 'i686', libdir: 'absolute'}); + // # Expand the main matrix to include relative testing + libdir.push('relative'); } } - return {config_arg: [''], arch: ['x86_64'], cc: compilers, include: include}; + return {config_arg: [''], arch: ['x86_64'], cc: compilers, libdir: libdir, include: include}; - name: Determine if the testsuite should be skipped id: skip uses: actions/github-script@v7 @@ -79,7 +84,7 @@ jobs: timeout-minutes: ${{ matrix.cc == 'gcc' && 90 || 60 }} - name: ${{ matrix.cc == 'cl' && 'MSVC' || matrix.cc == 'gcc' && 'Cygwin' || 'clang-cl' }} ${{ matrix.arch }} ${{ matrix.config_arg != '' && format('({0})', matrix.config_arg) || '' }} + name: ${{ matrix.cc == 'cl' && 'MSVC' || matrix.cc == 'gcc' && 'Cygwin' || 'clang-cl' }} ${{ matrix.arch }} ${{ matrix.libdir }} ${{ matrix.config_arg != '' && format('({0})', matrix.config_arg) || '' }} strategy: matrix: ${{ fromJSON(needs.config.outputs.matrix) }} @@ -136,11 +141,12 @@ jobs: env: CONFIG_ARGS: >- --cache-file=config.cache - --prefix "${{ matrix.cc != 'gcc' && '$PROGRAMFILES/Бактріан🐫' || '$(cygpath "$PROGRAMFILES/Бактріан🐫")'}}" + --prefix "${{ matrix.cc != 'gcc' && '$PROGRAMFILES\\Бактріан🐫' || '$(cygpath "$PROGRAMFILES/Бактріан🐫")'}}" ${{ matrix.cc != 'gcc' && format('--host={0}-pc-windows', matrix.arch) || '' }} ${{ matrix.cc != 'gcc' && format('CC={0}', matrix.cc) || '' }} --enable-ocamltest ${{ endsWith(matrix.arch, '64') && '--enable-native-toplevel' || '--disable-native-toplevel' }} + ${{ matrix.libdir == 'relative' && '--with-relative-libdir' || '--without-relative-libdir' }} ${{ matrix.config_arg }} run: | eval $(tools/msvs-promote-path) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0eb47cf80be8..17da20c7d797 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: '${{ github.event.repository.full_name }}' - name: Configure tree run: | - MAKE_ARG=-j CONFIG_ARG='--enable-flambda --enable-cmm-invariants --enable-dependency-generation --enable-native-toplevel' OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh configure + MAKE_ARG=-j CONFIG_ARG='--enable-flambda --enable-cmm-invariants --enable-dependency-generation --enable-native-toplevel --with-relative-libdir' OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh configure - name: Build run: | MAKE_ARG=-j bash -xe tools/ci/actions/runner.sh build @@ -139,6 +139,10 @@ jobs: if: matrix.id == 'normal' run: | MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh test-in-prefix + - name: Test in prefix (alternate configuration) + if: matrix.id == 'normal' && needs.config.outputs.full-matrix == 'true' + run: | + MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh re-test-in-prefix - name: Build the manual if: matrix.id == 'normal' && needs.build.outputs.manual_changed == 'true' run: | @@ -152,9 +156,23 @@ jobs: config: runs-on: ubuntu-latest outputs: + full-matrix: ${{ steps.full.outputs.result }} jobs: ${{ steps.jobs.outputs.result }} skip-testsuite: ${{ steps.skip.outputs.result }} steps: + - name: Record if the build matrix is expanded + id: full + uses: actions/github-script@v7 + with: + script: | + let full_matrix = false; + if (context.payload.pull_request) { + const { data: labels } = + await github.rest.issues.listLabelsOnIssue({...context.repo, issue_number: context.payload.pull_request.number}); + full_matrix = labels.some(label => label.name === 'CI: Full matrix'); + } + console.log('Full matrix: ' + full_matrix); + return full_matrix; - name: Compute matrix for the "others" job id: jobs uses: actions/github-script@v7 @@ -169,6 +187,7 @@ jobs: {name: 'macos-x86_64', os: 'macos-15-intel', 'test-in-prefix': true}, {name: 'macos-arm64', os: 'macos-latest', + config_arg: '--with-relative-libdir', 'test-in-prefix': true}]; // # If this is a pull request, see if the PR has the // # 'CI: Full matrix' label. This is done using an API request, @@ -250,6 +269,10 @@ jobs: if: ${{ matrix.test-in-prefix }} run: | MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh test-in-prefix + - name: Test in prefix (alternate configuration) + if: ${{ matrix.test-in-prefix && needs.config.outputs.full-matrix == 'true' }} + run: | + MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh re-test-in-prefix i386: runs-on: ubuntu-latest @@ -282,4 +305,8 @@ jobs: su ocaml -c "bash -xe tools/ci/actions/runner.sh install" - name: Test in prefix run: | - su ocaml -c "bash -xe tools/ci/actions/runner.sh test-in-prefix" + MAKE_ARG=-j su ocaml -c "bash -xe tools/ci/actions/runner.sh test-in-prefix" + - name: Test in prefix (alternate configuration) + if: ${{ needs.config.outputs.full-matrix == 'true' }} + run: | + MAKE_ARG=-j su ocaml -c "bash -xe tools/ci/actions/runner.sh re-test-in-prefix" diff --git a/Changes b/Changes index 57807de2b376..f65ecafafd2d 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,22 @@ OCaml 5.3 maintenance version normalised on both Windows and Unix. (David Allsopp, review by Jonah Beckford, Damien Doligez and Hugo Heuzard) +- #14244, #14802, #14846: Added --with-relative-libdir which allows the runtime + and the compilers to locate the Standard Library relative to where the + binaries themselves are installed, removing the absolute path previously + embedded in caml_standard_library_default. Executables linked with + `ocamlc -custom` now always attempt to load bytecode from the executable + itself, rather than first trying `argv[0]`. + (David Allsopp, review by Jonah Beckford, Antonin Décimo, Damien Doligez, + Samuel Hym and Vincent Laviron) + +### Compiler user-interface and warnings: + +- #14244: Add -set-runtime-default option to the compiler, allowing the default + value of the Standard Library location used by the runtime to be overridden. + (Antonin Décimo, review by David Allsopp, Jonah Beckford, Damien Doligez and + Samuel Hym) + ### Internal/compiler-libs changes: - #14243: ocamlc now uses the same code as the runtime to parse ld.conf (via a @@ -37,6 +53,14 @@ OCaml 5.3 maintenance version building OCaml with --disable-shared. (David Allsopp, review by Jonah Beckford, Damien Doligez and Hugo Heuzard) +- #14244: When targeting native Windows on Cygwin or MSYS2, preserve + backslashes in the supplied `--prefix` (in particular, backslashes instead of + slashes will then be displayed by `ocamlopt -config-var standard_library`). + If the supplied prefix contains a slash, then it is normalised, as + previously. + (David Allsopp, review by Jonah Beckford, Antonin Décimo, Damien Doligez and + Samuel Hym) + ### Bug fixes: - #13790: Fix bytecode-only build of Cygwin when flexlink is being bootstrapped diff --git a/Makefile b/Makefile index a80a4f2b95c5..c1c2ee62eaf2 100644 --- a/Makefile +++ b/Makefile @@ -482,9 +482,11 @@ utils/config_boot.ml: utils/config.fixed.ml utils/config.common.ml utils/config_main.ml: utils/config.generated.ml utils/config.common.ml $(V_GEN)cat $^ > $@ +ADDITIONAL_CONFIGURE_ARGS ?= .PHONY: reconfigure reconfigure: - ac_read_git_config=true ./configure $(CONFIGURE_ARGS) + ac_read_git_config=true ./configure $(CONFIGURE_ARGS) \ + $(ADDITIONAL_CONFIGURE_ARGS) utils/domainstate.ml: utils/domainstate.ml.c runtime/caml/domain_state.tbl $(V_GEN)$(CPP) -I runtime/caml $< > $@ @@ -881,7 +883,8 @@ flexlink.opt$(EXE): \ $(FLEXDLL_SOURCES) | $(BYTE_BINDIR)/flexlink$(EXE) $(OPT_BINDIR) rm -f $(FLEXDLL_SOURCE_DIR)/flexlink.exe $(MAKE) -C $(FLEXDLL_SOURCE_DIR) $(FLEXLINK_BUILD_ENV) \ - OCAMLOPT='$(FLEXLINK_OCAMLOPT) -nostdlib -I ../stdlib' flexlink.exe + OCAMLOPT='$(FLEXLINK_OCAMLOPT) $(USE_STDLIB) $(SET_RELATIVE_STDLIB)' \ + flexlink.exe cp $(FLEXDLL_SOURCE_DIR)/flexlink.exe $@ rm -f $(OPT_BINDIR)/flexlink$(EXE) cd $(OPT_BINDIR); $(LN) $(call ROOT_FROM, $(OPT_BINDIR))/$@ flexlink$(EXE) @@ -958,6 +961,10 @@ ocamlc_SOURCES = driver/main.mli driver/main.ml ocamlc_BYTECODE_LINKFLAGS = -compat-32 -g +ifeq "$(IN_COREBOOT_CYCLE)" "true" +ocamlc_BYTECODE_LINKFLAGS += -set-runtime-default standard_library_default=. +endif + partialclean:: rm -f ocamlc ocamlc.exe ocamlc.opt ocamlc.opt.exe @@ -1392,13 +1399,17 @@ runtime/sak.$(O): runtime/sak.c runtime/caml/misc.h runtime/caml/config.h C_LITERAL = $(shell $(SAK) encode-C-literal '$(1)') -runtime/build_config.h: $(ROOTDIR)/Makefile.config $(SAK) +runtime/build_config.h: $(ROOTDIR)/Makefile.config \ + $(ROOTDIR)/Makefile.build_config $(SAK) $(V_GEN){ \ echo '/* This file is generated from $(ROOTDIR)/Makefile.config */'; \ - printf '#define OCAML_STDLIB_DIR %s\n' '$(call C_LITERAL,$(LIBDIR))'; \ + printf '#define OCAML_STDLIB_DIR %s\n' \ + '$(call C_LITERAL,$(TARGET_LIBDIR))'; \ echo '#define HOST "$(HOST)"'; \ } > $@ +runtime/prims.$(O): runtime/build_config.h + ## Runtime libraries and programs runtime/ocamlrun$(EXE): runtime/prims.$(O) runtime/libcamlrun.$(A) @@ -1698,6 +1709,10 @@ ocamllex.opt: ocamlopt ocamllex_BYTECODE_LINKFLAGS = -compat-32 +ifeq "$(IN_COREBOOT_CYCLE)" "true" +ocamllex_BYTECODE_LINKFLAGS += -set-runtime-default standard_library_default=. +endif + partialclean:: rm -f lex/*.cm* lex/*.o lex/*.obj \ $(ocamllex_PROGRAMS) $(ocamllex_PROGRAMS:=.exe) \ @@ -2010,6 +2025,15 @@ testsuite/tools/test_in_prefi%: CAMLC = $(BEST_OCAMLC) $(STDLIBFLAGS) test_in_prefix_BYTECODE_LINKFLAGS += -custom +ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" +# testsuite/tools/test_in_prefix cannot use a relative stdlib because it is run +# from testsuite/tools, not from the installation tree (the alternative would be +# to compile it directly with the installed compiler) +test_in_prefix_NATIVE_LINKFLAGS = +test_in_prefix_COMMON_LINKFLAGS = \ + -set-runtime-default 'standard_library_default=$(LIBDIR)' +endif + testsuite/tools/test_in_prefi%: CAMLOPT = $(BEST_OCAMLOPT) $(STDLIBFLAGS) testsuite/tools/dump_load_path$(EXE): \ diff --git a/Makefile.build_config.in b/Makefile.build_config.in index 7d8056cd091f..d45ad53e06bd 100644 --- a/Makefile.build_config.in +++ b/Makefile.build_config.in @@ -78,14 +78,22 @@ COMPUTE_DEPS=@compute_deps@ # The C compiler vendor OCAML_CC_VENDOR = @ocaml_cc_vendor@ +BUILD_PATH_LOGICAL = @srcdir_abs@ +BUILD_PATH_PHYSICAL = @srcdir_abs_real@ +BUILD_MAP_FLAGS = @build_map_flags@ +BUILD_MAP_CFLAGS = $(foreach flag, $(BUILD_MAP_FLAGS), \ + $(call QUOTE_SINGLE,$(flag)$(BUILD_PATH_LOGICAL)=+build) \ + $(if $(BUILD_PATH_PHYSICAL), \ + $(call $(QUOTE_SINGLE),$(flag)$(BUILD_PATH_PHYSICAL)=+build))) + # Default flags to use to compile C files -OC_CFLAGS = @oc_cflags@ +OC_CFLAGS = @oc_cflags@ $(BUILD_MAP_CFLAGS) # Flags to use when compiling C files to be linked with bytecode -OC_BYTECODE_CFLAGS = @oc_bytecode_cflags@ +OC_BYTECODE_CFLAGS = @oc_bytecode_cflags@ $(BUILD_MAP_CFLAGS) # Flags to use when compiling C files to be linked with native code -OC_NATIVE_CFLAGS = @oc_native_cflags@ +OC_NATIVE_CFLAGS = @oc_native_cflags@ $(BUILD_MAP_CFLAGS) # The submodules should be searched *before* any other external -I paths OC_INCLUDES = $(addprefix -I $(ROOTDIR)/, \ @@ -133,6 +141,10 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ datarootdir = @datarootdir@ DOCDIR=@docdir@ +### Where to look for the standard library on target +TARGET_LIBDIR=@TARGET_LIBDIR@ +TARGET_LIBDIR_IS_RELATIVE=@target_libdir_is_relative@ + unix_directory = @unix_directory@ unix_library = @unix_library@ diff --git a/Makefile.common b/Makefile.common index 83ee360ce903..8d9f1017e815 100644 --- a/Makefile.common +++ b/Makefile.common @@ -29,6 +29,7 @@ EMPTY := SPACE := $(EMPTY) $(EMPTY) # $( ) suppresses warning from the alignments in the V_ macros below $(SPACE) := +HASH := \# ifeq "$(UNIX_OR_WIN32)" "win32" DIR_SEP := \$ # There must a space following the $ @@ -38,6 +39,8 @@ DIR_SEP = / CONVERT_PATH = $(strip $(1)) endif +QUOTE_SINGLE = '$(subst ','\'',$(1))' + V ?= 0 ifeq "$(V)" "0" @@ -176,6 +179,22 @@ ifeq "$(FUNCTION_SECTIONS)" "true" OPTCOMPFLAGS += -function-sections endif +ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" + SRCDIR_ENCODED = $(subst =,%+,$(subst :,%.,$(subst %,%$(HASH),$(SRCDIR_ABS)))) + SRCDIR_ABS_REAL := $(shell realpath $(SRCDIR_ABS) 2>/dev/null) + SRCDIR_REAL_ENCODED = \ + $(subst =,%+,$(subst :,%.,$(subst %,%$(HASH),$(SRCDIR_ABS_REAL)))) + BUILD_PATH_PREFIX_MAP ?= + export BUILD_PATH_PREFIX_MAP := \ + $(BUILD_PATH_PREFIX_MAP)$\ + :.=$(SRCDIR_ENCODED)$\ + $(if $(SRCDIR_REAL_ENCODED),:.=$(SRCDIR_REAL_ENCODED)) +endif # ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" + +OC_COMMON_LINKFLAGS += \ + -set-runtime-default \ + $(call QUOTE_SINGLE,standard_library_default=$(TARGET_LIBDIR)) + # The rule to compile C files # This rule is similar to GNU make's implicit rule, except that it is more diff --git a/appveyor.yml b/appveyor.yml index ad1426d9bc1b..201a7a363586 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -44,6 +44,7 @@ environment: matrix: - PORT: mingw64 BOOTSTRAP_FLEXDLL: true + RELOCATABLE: true # OCaml 5.0 does not yet support MSVC # - PORT: msvc64 # BOOTSTRAP_FLEXDLL: false diff --git a/asmcomp/asmlink.ml b/asmcomp/asmlink.ml index e8179a729f7c..5614d58ab077 100644 --- a/asmcomp/asmlink.ml +++ b/asmcomp/asmlink.ml @@ -198,6 +198,10 @@ let make_globals_map units_list ~crc_interfaces = crc_interfaces defined let make_startup_file ~ppf_dump units_list ~crc_interfaces = + let need_stdlib = + let needs_stdlib ({ui_need_stdlib; _}, _, _) = ui_need_stdlib in + List.exists needs_stdlib units_list + in let compile_phrase p = Asmgen.compile_phrase ~ppf_dump p in Location.input_name := "caml_startup"; (* set name of "current" input *) Compilenv.reset "_startup"; @@ -224,6 +228,14 @@ let make_startup_file ~ppf_dump units_list ~crc_interfaces = Array.iteri (fun i name -> compile_phrase (Cmm_helpers.predef_exception i name)) Runtimedef.builtin_exceptions; + if need_stdlib then begin + let standard_library_default = + Option.value ~default:Config.standard_library_default + !Clflags.standard_library_default in + compile_phrase + (Cmm_helpers.emit_global_string_constant + "caml_standard_library_nat" standard_library_default) + end; compile_phrase (Cmm_helpers.global_table name_list); let globals_map = make_globals_map units_list ~crc_interfaces in compile_phrase (Cmm_helpers.globals_map globals_map); diff --git a/asmcomp/asmpackager.ml b/asmcomp/asmpackager.ml index f0e2148f447c..3cec13386ce0 100644 --- a/asmcomp/asmpackager.ml +++ b/asmcomp/asmpackager.ml @@ -217,6 +217,9 @@ let build_package_cmx members cmxfile = else Clambda (get_approx ui) in + let ui_need_stdlib = + List.exists (function {ui_need_stdlib; _} -> ui_need_stdlib) units + in Export_info_for_pack.clear_import_state (); let pkg_infos = { ui_name = ui.ui_name; @@ -239,6 +242,7 @@ let build_package_cmx members cmxfile = List.exists (fun info -> info.ui_force_link) units; ui_export_info; ui_for_pack = None; + ui_need_stdlib; } in Compilenv.write_unit_info pkg_infos cmxfile diff --git a/asmcomp/cmm_helpers.ml b/asmcomp/cmm_helpers.ml index c99c39bc0013..c87ecbee155c 100644 --- a/asmcomp/cmm_helpers.ml +++ b/asmcomp/cmm_helpers.ml @@ -2605,6 +2605,9 @@ let predef_exception i name = in Cdata data_items +let emit_global_string_constant name value = + Cdata (emit_string_constant (name, Global) value []) + (* Header for a plugin *) let plugin_header units = diff --git a/asmcomp/cmm_helpers.mli b/asmcomp/cmm_helpers.mli index 513b4adf95c8..2115b42f5cb5 100644 --- a/asmcomp/cmm_helpers.mli +++ b/asmcomp/cmm_helpers.mli @@ -619,6 +619,9 @@ val code_segment_table: string list -> phrase (** Generate data for a predefined exception *) val predef_exception: int -> string -> phrase +(** Generate data for a global string constant *) +val emit_global_string_constant: string -> string -> phrase + val plugin_header: (Cmx_format.unit_infos * Digest.t) list -> phrase (** Emit constant symbols *) diff --git a/bytecomp/bytegen.ml b/bytecomp/bytegen.ml index 4665ac84bcac..ef054c2c9be4 100644 --- a/bytecomp/bytegen.ml +++ b/bytecomp/bytegen.ml @@ -439,7 +439,8 @@ let comp_primitive stack_info p sz args = | Ostype_unix -> "ostype_unix" | Ostype_win32 -> "ostype_win32" | Ostype_cygwin -> "ostype_cygwin" - | Backend_type -> "backend_type" in + | Backend_type -> "backend_type" + | Standard_library_default -> "standard_library_default" in Kccall(Printf.sprintf "caml_sys_const_%s" const_name, 1) | Pisint -> Kisint | Pisout -> Kisout diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index db4fa18e76ce..ebaff66bac28 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -19,6 +19,7 @@ open Misc open Config open Cmo_format +module String = Misc.Stdlib.String module Compunit = Symtable.Compunit module Dep = struct @@ -199,7 +200,7 @@ let debug_info = ref ([] : (int * Instruct.debug_event list * string list) list) (* Link in a compilation unit *) -let link_compunit output_fun currpos_fun inchan file_name compunit = +let link_compunit accu output_fun currpos_fun inchan file_name compunit = check_consistency file_name compunit; seek_in inchan compunit.cu_pos; let code_block = @@ -225,46 +226,44 @@ let link_compunit output_fun currpos_fun inchan file_name compunit = debug_info := (currpos_fun(), debug_event_list, debug_dirs) :: !debug_info end; output_fun code_block; - if !Clflags.link_everything then - List.iter Symtable.require_primitive compunit.cu_primitives + let fold_primitive needs_stdlib name = + if !Clflags.link_everything then + Symtable.require_primitive name; + (needs_stdlib || name = "%standard_library_default") + in + List.fold_left fold_primitive accu compunit.cu_primitives (* Link in a .cmo file *) -let link_object output_fun currpos_fun file_name compunit = - let inchan = open_in_bin file_name in - try - link_compunit output_fun currpos_fun inchan file_name compunit; - close_in inchan - with - Symtable.Error msg -> - close_in inchan; raise(Error(Symbol_error(file_name, msg))) - | x -> - close_in inchan; raise x +let link_object accu output_fun currpos_fun file_name compunit = + In_channel.with_open_bin file_name @@ fun inchan -> + try link_compunit accu output_fun currpos_fun inchan file_name compunit + with Symtable.Error msg -> raise(Error(Symbol_error(file_name, msg))) (* Link in a .cma file *) -let link_archive output_fun currpos_fun file_name units_required = - let inchan = open_in_bin file_name in - try - List.iter - (fun cu -> +let link_archive accu output_fun currpos_fun file_name units_required = + In_channel.with_open_bin file_name @@ fun inchan -> + List.fold_left + (fun accu cu -> let n = Compunit.name cu.cu_name in let name = file_name ^ "(" ^ n ^ ")" in try - link_compunit output_fun currpos_fun inchan name cu + link_compunit accu output_fun currpos_fun inchan name cu with Symtable.Error msg -> raise(Error(Symbol_error(name, msg)))) - units_required; - close_in inchan - with x -> close_in inchan; raise x + accu units_required (* Link in a .cmo or .cma file *) -let link_file output_fun currpos_fun = function +let link_file output_fun currpos_fun accu = function Link_object(file_name, unit) -> - link_object output_fun currpos_fun file_name unit + link_object accu output_fun currpos_fun file_name unit | Link_archive(file_name, units) -> - link_archive output_fun currpos_fun file_name units + link_archive accu output_fun currpos_fun file_name units + +let link_files output_fun currpos_fun = + List.fold_left (link_file output_fun currpos_fun) false (* Output the debugging information *) (* Format is: @@ -343,6 +342,11 @@ let read_runtime_launch_info file = let bindir_start = String.index buffer '\n' + 1 in let bindir_end = String.index_from buffer bindir_start '\000' in let bindir = String.sub buffer bindir_start (bindir_end - bindir_start) in + let bindir = + if bindir = Filename.current_dir_name then + Filename.dirname Sys.executable_name + else + bindir in let executable_offset = bindir_end + 2 in let launcher = let kind = String.sub buffer 0 (bindir_start - 1) in @@ -495,7 +499,9 @@ let link_bytecode ?final_name tolink exec_name standalone = let output_fun buf = Out_channel.output_bigarray outchan buf 0 (Bigarray.Array1.dim buf) and currpos_fun () = pos_out outchan - start_code in - List.iter (link_file output_fun currpos_fun) tolink; + let needs_stdlib = + link_files output_fun currpos_fun tolink + in if check_dlls then Dll.close_all_dlls(); (* The final STOP instruction *) output_byte outchan Opcodes.opSTOP; @@ -518,6 +524,18 @@ let link_bytecode ?final_name tolink exec_name standalone = ~filename:final_name ~kind:"bytecode executable" outchan (Symtable.initial_global_table()); Bytesections.record toc_writer DATA; + (* -custom executables don't need OSLD sections - the correct value is + already included in the runtime. *) + if standalone && needs_stdlib then begin + (* OCaml Standard Library Default location *) + let standard_library_default = + Option.value + ~default:Config.standard_library_default + !Clflags.standard_library_default + in + output_string outchan standard_library_default; + Bytesections.record toc_writer OSLD + end; (* The map of global identifiers *) Symtable.output_global_map outchan; Bytesections.record toc_writer SYMB; @@ -589,6 +607,49 @@ let output_cds_file outfile = Bytesections.write_toc_and_trailer toc_writer; ) +(* [c_string_literal_of_string s] returns the C literal string representation of + [s], suitable for embedding in a C source file with type [char_os *]. The + result includes the quote markers. *) +let c_string_literal_of_string s = + let b = Buffer.create (String.length s * 2) in + let utf16le = Bytes.create 4 in + let escape u = + match Uchar.to_int u with + (* Characters with C escape sequences *) + | 000 (* '\0' *) -> Buffer.add_string b "\\000" + | 009 (* '\t' *) -> Buffer.add_string b "\\t" + | 010 (* '\n' *) -> Buffer.add_string b "\\n" + | 013 (* '\r' *) -> Buffer.add_string b "\\r" + | 034 (* '\"' *) -> Buffer.add_string b "\\\"" + | 092 (* '\\' *) -> Buffer.add_string b "\\\\" + (* Most C compilers will have no problem processing UTF-8 in the strings + with the characters above converted to their C representations. On + Windows, where the string is [wchar_t *], all characters for which + iswprint returns 0 are escaped using the extended [\x] notation. *) + | c when Config.target_win32 && (c < 32 (* ' ' *) || c >= 127) -> + (* Convert u to UTF-16LE, allowing for surrogate pairs *) + let len = Bytes.set_utf_16le_uchar utf16le 0 u in + for i = 1 to len / 2 do + Printf.bprintf b "\\x%04x" (Bytes.get_uint16_le utf16le ((i - 1) * 2)) + done + | _ -> + Buffer.add_utf_8_uchar b u + in + if Config.target_win32 then + Buffer.add_char b 'L'; + Buffer.add_char b '"'; + Seq.iter escape (String.to_utf_8_seq s); + Buffer.add_char b '"'; + Buffer.contents b + +let emit_runtime_standard_library_default outchan = + let stdlib = + let default = Config.standard_library_default in + Option.value ~default !Clflags.standard_library_default in + let literal = c_string_literal_of_string stdlib in + Printf.fprintf outchan + "const char_os * caml_runtime_standard_library_default = %s;\n" literal + (* Output a bytecode executable as a C file *) let link_bytecode_as_c tolink outfile with_main = @@ -612,6 +673,8 @@ extern "C" { #include #include +const enum caml_byte_program_mode caml_byte_program_mode = EMBEDDED; + static int caml_code[] = { |}; Symtable.init(); @@ -621,7 +684,7 @@ static int caml_code[] = { output_code_string outchan code; currpos := !currpos + (Bigarray.Array1.dim code) and currpos_fun () = !currpos in - List.iter (link_file output_fun currpos_fun) tolink; + ignore (link_files output_fun currpos_fun tolink); (* The final STOP instruction *) Printf.fprintf outchan "\n0x%x};\n" Opcodes.opSTOP; (* The table of global data *) @@ -649,6 +712,7 @@ static char caml_sections[] = { }; |}; + emit_runtime_standard_library_default outchan; (* The table of primitives *) Symtable.output_primitive_table outchan; (* The entry point *) @@ -656,7 +720,6 @@ static char caml_sections[] = { output_string outchan {| int main_os(int argc, char_os **argv) { - caml_byte_program_mode = COMPLETE_EXE; caml_startup_code(caml_code, sizeof(caml_code), caml_data, sizeof(caml_data), caml_sections, sizeof(caml_sections), @@ -798,11 +861,17 @@ let link objfiles output_name = extern "C" { #endif +#define CAML_INTERNALS #define CAML_INTERNALS_NO_PRIM_DECLARATIONS + #include +#include + +const enum caml_byte_program_mode caml_byte_program_mode = APPENDED; |}; Symtable.output_primitive_table poc; + emit_runtime_standard_library_default poc; output_string poc {| #ifdef __cplusplus } diff --git a/bytecomp/bytesections.ml b/bytecomp/bytesections.ml index 30a1c0fbc9ec..dd848a7de719 100644 --- a/bytecomp/bytesections.ml +++ b/bytecomp/bytesections.ml @@ -26,6 +26,7 @@ module Name = struct | DBUG (** debug info *) | DLLS (** dll names *) | DLPT (** dll paths *) + | OSLD (** OCaml Standard Library Default location *) | PRIM (** primitives names *) | RNTM (** The path to the bytecode interpreter (use_runtime mode) *) | SYMB (** global identifiers *) @@ -37,6 +38,7 @@ module Name = struct | "DLPT" -> DLPT | "DLLS" -> DLLS | "DATA" -> DATA + | "OSLD" -> OSLD | "PRIM" -> PRIM | "SYMB" -> SYMB | "DBUG" -> DBUG @@ -52,6 +54,7 @@ module Name = struct | DLPT -> "DLPT" | DLLS -> "DLLS" | DATA -> "DATA" + | OSLD -> "OSLD" | PRIM -> "PRIM" | SYMB -> "SYMB" | DBUG -> "DBUG" diff --git a/bytecomp/bytesections.mli b/bytecomp/bytesections.mli index 3d287932ac29..e6f9e6af1d56 100644 --- a/bytecomp/bytesections.mli +++ b/bytecomp/bytesections.mli @@ -27,6 +27,7 @@ module Name : sig | DBUG (** debug info *) | DLLS (** dll names *) | DLPT (** dll paths *) + | OSLD (** OCaml Standard Library Default location *) | PRIM (** primitives names *) | RNTM (** The path to the bytecode interpreter (use_runtime mode) *) | SYMB (** global identifiers *) diff --git a/configure b/configure index a254cb68a0cd..d340888e17dd 100755 --- a/configure +++ b/configure @@ -785,10 +785,16 @@ build_os build_vendor build_cpu build +build_map_flags +srcdir_abs_real +srcdir_abs +target_libdir_is_relative ar_supports_response_files QS +TARGET_LIBDIR ocaml_libdir ocaml_bindir +ocaml_prefix compute_deps build_libraries_manpages PACKLD @@ -832,6 +838,7 @@ build_ocamldoc build_ocamltex build_ocamldebug with_debugger +as_is_cc as_has_debug_prefix_map cc_has_debug_prefix_map unix_directory @@ -1020,6 +1027,7 @@ enable_force_safe_string enable_flat_float_array enable_function_sections enable_mmap_map_stack +with_relative_libdir with_afl with_flexdll with_winpthreads_msvc @@ -1738,6 +1746,9 @@ Optional Packages: --with-additional-stublibsdir additional directory for searching for bytecode stub libraries + --with-relative-libdir location of the Standard Library, specified relative + to --bindir (if no argument is given to + --with-relative-libdir, defaults to ../lib/ocaml) --with-afl use the AFL fuzzer --with-flexdll bootstrap FlexDLL from the given sources --with-winpthreads-msvc build winpthreads (only for the MSVC port) from the @@ -3271,6 +3282,26 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.3.1+dev0-2025-01-06" >&5 printf "%s\n" "$as_me: Configuring OCaml version 5.3.1+dev0-2025-01-06" >&6;} +# It's important for the setting up of defaults and the checking of the +# --with-relative-libdir option to know whether the user specified --libdir. +# Unfortunately, autoconf doesn't provide an easy way to determine this, so we +# simply repeat autoconf's argument parsing for the libdir and mandir options. +# This is done early in the script to ensure that only autoconf and site-config +# scripts have run. +libdir_given=no +mandir_given=no +for ac_arg +do + case $ac_arg in + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=* | \ + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + mandir_given=yes;; + -libdir=* | --libdir=* | --libdi=* | --libd=* | \ + -libdir | --libdir | --libdi | --libd) + libdir_given=yes;; + esac +done + # Configuration variables ## Command-line arguments passed to configure @@ -3329,6 +3360,10 @@ ocamltest_libunix=None ocamltest_unix_impl="dummy" unix_library="" unix_directory="" +target_libdir_is_relative=false +srcdir_abs='' +srcdir_abs_real='' +build_map_flags='' # Information about the package @@ -3476,6 +3511,7 @@ LINEAR_MAGIC_NUMBER=Caml1999L035 + # TODO: rename this variable @@ -3519,6 +3555,12 @@ LINEAR_MAGIC_NUMBER=Caml1999L035 + + + + + + @@ -3703,12 +3745,47 @@ test -n "$target_alias" && NONENONEs,x,x, && program_prefix=${target_alias}- +# $cygwin_build_env=true if the build is taking place in any kind of Cygwin-like +# environment (which may include cross-compiling _from_ Cygwin) +# All patterns end with * (cf. build-aux/config.sub) +# +# In Cygwin itself, the mingw-w64 compilers are cross-compilers +# (host=x86_64-pc-cygwin; target=*-w64-mingw32) and $build when running from +# within Cygwin is always *-pc-cygwin. +# +# In MSYS2, the mingw-w64 compilers are normal host compilers, which MSYS2 makes +# available through different Environments (similar to the Microsoft Visual +# Studio Tools Command Prompts; see https://www.msys2.org/docs/environments/). +# It is possible to use MSYS2's "Cygwin" gcc (the equivalent of compiling native +# Cygwin), in which case $build is *-*-cygwin (some older MSYS2 installations +# may report the legacy *-*-msys*) +# The mingw-w64 Environments manually set $build to *-w64-mingw32, but the +# _native_ value inferred by config.guess (which uses uname -s) will be +# *-pc-mingw32 (for the 32-bit _target_ environments, even though MSYS2 is a +# 64-bit build environment) and *-pc-mingw64 (for the 64-bit _target_ +# environments). +# +# This leads to the four patterns below, all of which imply that the build is +# taking place on a system where Cygwin's utilities (cygpath, etc.) can be +# expected to be found and its semantics (CYGWIN=winsymlinks:native, etc.) be +# expected to apply. +# +# Note that although build=x86_64-pc-mingw64 will be accepted here, it is highly +# likely that that's a misconfigured environment, and the script will +# subsequently fail if host has not been altered to x86_64-w64-mingw32. +case $build in #( + *-*-cygwin*|*-*-msys*|*-*-mingw32*|*-*-mingw64*) : + cygwin_build_env=true ;; #( + *) : + cygwin_build_env=false ;; +esac + # Ensure that AC_CONFIG_LINKS will either create symlinks which are compatible # with native Windows (i.e. NTFS symlinks, not WSL or Cygwin-emulated ones) or # use its fallback mechanisms. Native Windows versions of ocamlc/ocamlopt cannot # interpret either WSL or Cygwin-emulated symlinks. -case $host in #( - *-pc-windows|*-w64-mingw32*) : +case $cygwin_build_env,$host in #( + true,*-pc-windows|true,*-w64-mingw32*) : ac_config_commands="$ac_config_commands native-symlinks" ;; #( *) : @@ -4073,9 +4150,9 @@ fi # Check whether --with-target-bindir was given. if test ${with_target_bindir+y} then : - withval=$with_target_bindir; target_bindir=$withval + withval=$with_target_bindir; TARGET_BINDIR=$withval else $as_nop - target_bindir='' + TARGET_BINDIR='' fi @@ -4181,6 +4258,23 @@ fi +# Check whether --with-relative-libdir was given. +if test ${with_relative_libdir+y} +then : + withval=$with_relative_libdir; case $withval in #( + no) : + bindir_to_libdir='' ;; #( + yes) : + bindir_to_libdir="..${default_separator}lib${default_separator}ocaml" ;; #( + *) : + bindir_to_libdir="$withval" ;; +esac +else $as_nop + bindir_to_libdir='' +fi + + + # Check whether --with-afl was given. if test ${with_afl+y} then : @@ -13846,8 +13940,8 @@ esac # See https://lists.gnu.org/archive/html/autoconf/2019-07/msg00002.html # for the detailed explanation. -ocamlsrcdir=$(unset CDPATH; cd -- "$srcdir" && printf %sX "$PWD") || fail -ocamlsrcdir=${ocamlsrcdir%X} +srcdir_abs=$(unset CDPATH; cd -- "$srcdir" && printf %sX "$PWD") || fail +srcdir_abs=${srcdir_abs%X} # Whether ar supports @FILE arguments @@ -13860,8 +13954,8 @@ esac # Libraries to build depending on the host -case $host in #( - *-*-mingw32*|*-pc-windows) : +case $cygwin_build_env,$host in #( + true,*-*-mingw32*|true,*-pc-windows) : unix_or_win32="win32" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a workable solution for ln -sf" >&5 @@ -13877,12 +13971,13 @@ fi printf "%s\n" "$ln" >&6; } ocamltest_libunix="Some false" - ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$ocamlsrcdir")" + ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$srcdir_abs")" ocamlyacc_wstr_module="yacc/wstr" ;; #( *) : unix_or_win32="unix" ln='ln -sf' ocamltest_libunix="Some true" + ocamlsrcdir="$srcdir_abs" ocamlyacc_wstr_module="" ;; esac @@ -14531,16 +14626,16 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test x"$build" != x"$host" then : - case $build in #( - *-pc-msys|*-pc-cygwin*) : - flexlink_where="$(cmd /c "$flexlink" -where 2>/dev/null)" - if test -z "$flexlink_where" + if $cygwin_build_env +then : + flexlink_where="$(cmd /c "$flexlink" -where 2>/dev/null)" + if test -z "$flexlink_where" then : as_fn_error $? "$flexlink is not executable from a native Win32 process" "$LINENO" 5 -fi ;; #( - *) : - ;; -esac + +fi + +fi fi @@ -15035,6 +15130,13 @@ then : fi +ac_fn_c_check_header_compile "$LINENO" "libgen.h" "ac_cv_header_libgen_h" "$ac_includes_default" +if test "x$ac_cv_header_libgen_h" = xyes +then : + printf "%s\n" "#define HAS_LIBGEN_H 1" >>confdefs.h + +fi + ac_fn_c_check_header_compile "$LINENO" "pthread_np.h" "ac_cv_header_pthread_np_h" "$ac_includes_default" if test "x$ac_cv_header_pthread_np_h" = xyes then : @@ -16610,30 +16712,45 @@ fi # to avoiding forking a C compiler process for each compilation by ocamlopt. # Both AS and ASPP can be overridden by the user. -default_as="$CC -c" -default_aspp="$CC -c" +as_is_cc=true +default_as='' case $as_target,$ocaml_cc_vendor in #( *-*-linux*,gcc-*) : case $as_cpu in #( x86_64|arm*|aarch64*|i[3-6]86|riscv*) : - default_as="${toolpref}as" ;; #( + default_as="${toolpref}as" + as_is_cc=false ;; #( *) : ;; esac ;; #( + *-*-cygwin,gcc-*) : + default_as="${toolpref}as" + as_is_cc=false ;; #( i686-pc-windows,*) : default_as="ml -nologo -coff -Cp -c -Fo" - default_aspp="$default_as" ;; #( + default_aspp="$default_as" + as_is_cc=false ;; #( x86_64-pc-windows,*) : default_as="ml64 -nologo -Cp -c -Fo" - default_aspp="$default_as" ;; #( + default_aspp="$default_as" + as_is_cc=false ;; #( *-*-darwin*,clang-*) : - default_as="$default_as -Wno-trigraphs" + default_as="$CC -c -Wno-trigraphs" default_aspp="$default_as" ;; #( *) : ;; esac +if test -z "$default_as" +then : + default_as="$CC -c" +fi +if test -z "$default_aspp" +then : + default_aspp="$CC -c" +fi + if test "$with_pic" then : fpic=true @@ -18451,8 +18568,6 @@ fi ## -fdebug-prefix-map support by the C compiler case $ocaml_cc_vendor,$host in #( - *,*-*-mingw32*) : - cc_has_debug_prefix_map=false ;; #( *,*-pc-windows) : cc_has_debug_prefix_map=false ;; #( xlc*,powerpc-ibm-aix*) : @@ -18502,6 +18617,100 @@ fi ;; esac +## -ffile-prefix-map support by the C compiler - used in the build, not by +## the compiler +if test x"$bindir_to_libdir" != 'x' +then : + srcdir_abs_real="$(realpath "$srcdir_abs" 2>/dev/null)" + if test x"$srcdir_abs_real" = "x$srcdir_abs" +then : + srcdir_abs_real='' +fi + as_CACHEVAR=`printf "%s\n" "ax_cv_check_cflags_$warn_error_flag_-Wa,--debug-prefix-map=old=new" | $as_tr_sh` +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -Wa,--debug-prefix-map=old=new" >&5 +printf %s "checking whether the C compiler accepts -Wa,--debug-prefix-map=old=new... " >&6; } +if eval test \${$as_CACHEVAR+y} +then : + printf %s "(cached) " >&6 +else $as_nop + + ax_check_save_flags=$CFLAGS + CFLAGS="$CFLAGS $warn_error_flag -Wa,--debug-prefix-map=old=new" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + eval "$as_CACHEVAR=yes" +else $as_nop + eval "$as_CACHEVAR=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CFLAGS=$ax_check_save_flags +fi +eval ac_res=\$$as_CACHEVAR + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } +if eval test \"x\$"$as_CACHEVAR"\" = x"yes" +then : + build_map_flags='-Wa,--debug-prefix-map=' +else $as_nop + : +fi + + as_CACHEVAR=`printf "%s\n" "ax_cv_check_cflags_$warn_error_flag_-ffile-prefix-map=old=new" | $as_tr_sh` +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler accepts -ffile-prefix-map=old=new" >&5 +printf %s "checking whether the C compiler accepts -ffile-prefix-map=old=new... " >&6; } +if eval test \${$as_CACHEVAR+y} +then : + printf %s "(cached) " >&6 +else $as_nop + + ax_check_save_flags=$CFLAGS + CFLAGS="$CFLAGS $warn_error_flag -ffile-prefix-map=old=new" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + eval "$as_CACHEVAR=yes" +else $as_nop + eval "$as_CACHEVAR=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CFLAGS=$ax_check_save_flags +fi +eval ac_res=\$$as_CACHEVAR + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } +if eval test \"x\$"$as_CACHEVAR"\" = x"yes" +then : + build_map_flags="$build_map_flags -ffile-prefix-map=" +else $as_nop + if $cc_has_debug_prefix_map +then : + build_map_flags="$build_map_flags -fdebug-prefix-map=" +fi +fi + +fi + ## Does stat support nanosecond precision stat_has_ns_precision=false @@ -21021,12 +21230,12 @@ esac -if test x"$libdir" = x'${exec_prefix}/lib' +if test x"$libdir_given" = 'xno' then : libdir="$libdir"/ocaml fi -if test x"$mandir" = x'${datarootdir}/man' +if test x"$mandir_given" = 'xno' then : mandir='${prefix}/man' fi @@ -21046,16 +21255,86 @@ then : *) : ;; esac -else $as_nop - if test x"$unix_or_win32" = "xwin32" \ - && test "$host_vendor-$host_os" != "$build_vendor-$build_os" -then : - case $build in #( - *-pc-cygwin) : - prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")" ;; #( +fi + +# Normalise $prefix, if necessary, on Windows. There are 7 variables to be +# considered: +# - $prefix and $exec_prefix. These are autoconf variables containing the values +# specified for --prefix and --exec-prefix respectively, or NONE if these +# flags were not given on the command line. These two variables are written to +# Makefile.config and are in _build_ format. On native Windows, the values +# must be suitable to pass to _both_ native Windows processes and Cygwin/MSYS2 +# commands. The values are ultimately converted to Windows paths using slashes +# (i.e. C:/foo) +# - $bindir and $libdir. These similarly contain the values specified for +# --bindir and --libdir, or defaults relative to $exec_prefix otherwise. +# Unlike --prefix, values specified to configure for --bindir and --libdir are +# assumed to be suitable for both native Windows processes and Cygwin/MSYS2. +# These two variables ultimately end up in Makefile.config as $(BINDIR) and +# $(LIBDIR) and are used for installation commands only. +# - $ocaml_prefix, $ocaml_bindir and $ocaml_libdir are the _expanded_ values of +# $prefix, $bindir and $libdir so that the values can be inserted into OCaml +# strings (typically in utils/config.generated.ml). On Windows, these preserve +# the backslashes present in the value passed to --prefix. +ocaml_bindir="$bindir" +ocaml_libdir="$libdir" +ocaml_prefix="$prefix" +case $cygwin_build_env,$host in #( + true,*-w64-mingw32*|true,*-pc-windows) : + prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")" + if test "x${ocaml_prefix%%/*}" != "x$ocaml_prefix" +then : + # $prefix contained a slash - normalise it with cygpath. The rationale for + # this allows both `./configure --prefix $PWD/install` (which will be a + # Cygwin path) and also systems lazily using slashes instead of + # backslashes (i.e. C:/Backslashes/Scare/Us) to work. + ocaml_prefix="$prefix" +else $as_nop + # $prefix was using backslashes - preserve these in the build, but + # continue to use slashes for the Makefile variables. + if test "x$bindir" = 'x${exec_prefix}/bin' +then : + ocaml_bindir='${exec_prefix}\bin' +else $as_nop + ocaml_bindir="$bindir" +fi + if test "x$libdir" = 'x${exec_prefix}/lib/ocaml' +then : + ocaml_libdir='${exec_prefix}\lib\ocaml' +else $as_nop + ocaml_libdir="$libdir" +fi +fi ;; #( *) : ;; esac + +if test x"$libdir_given" = 'xno' +then : + if test x"$bindir_to_libdir" != 'x' +then : + ocaml_libdir="$bindir_to_libdir" + target_libdir_is_relative=true + case $cygwin_build_env,$host in #( + true,*-w64-mingw32*|true,*-pc-windows) : + build_bindir_to_libdir="$(LC_ALL=C.UTF-8 cygpath \ + "$bindir_to_libdir")" ;; #( + *) : + build_bindir_to_libdir="$bindir_to_libdir" ;; +esac + case $build_bindir_to_libdir in #( + ./*) : + libdir="$bindir${build_bindir_to_libdir#.}" ;; #( + ../*) : + libdir="$bindir/$build_bindir_to_libdir" ;; #( + *) : + as_fn_error $? "--with-relative-libdir requires an explicit relative path" "$LINENO" 5 ;; +esac +fi +else $as_nop + if test x"$bindir_to_libdir" != 'x' +then : + as_fn_error $? "--with-relative-libdir and --libdir cannot both be specified" "$LINENO" 5 fi fi @@ -21241,6 +21520,7 @@ cclibs="$cclibs $mathlib $DLLIBS $PTHREAD_LIBS" saved_exec_prefix="$exec_prefix" saved_prefix="$prefix" + prefix="$ocaml_prefix" if test "x$prefix" = "xNONE" then : prefix="$ac_default_prefix" @@ -21250,11 +21530,40 @@ then : exec_prefix="$prefix" fi eval "exec_prefix=\"$exec_prefix\"" - eval "ocaml_bindir=\"$bindir\"" - eval "ocaml_libdir=\"$libdir\"" - if test x"$target_bindir" = 'x' + # Set variables necessary to create utils/config.generated.ml and the two + # runtime-launch-info templates in stdlib. + # $ocaml_bindir is used for utils/config.generated.ml and is _empty_ if the + # compiler is configured with --with-relative-libdir (otherwise the path + # would be embedded in config.cmo) + # $HOST_BINDIR and $TARGET_BINDIR are used to generate the two + # runtime-launch-info files. $HOST_BINDIR is always the absolute path to the + # binary directory, in host format (i.e. potentially with backslashes on + # Windows). $TARGET_BINDIR can be specified by the caller when building + # cross-compilers, and the value then is used unaltered. Otherwise, + # $TARGET_BINDIR is set to '.' when the compiler is configured with + # --with-relative-libdir or the value of $HOST_BINDIR otherwise. + eval "HOST_BINDIR=\"$ocaml_bindir\"" + if test "x$bindir_to_libdir" = 'x' then : - target_bindir="$ocaml_bindir" + ocaml_bindir="$HOST_BINDIR" +else $as_nop + ocaml_bindir='' +fi + if test x"$TARGET_BINDIR" = 'x' +then : + if test "x$bindir_to_libdir" = 'x' +then : + TARGET_BINDIR="$HOST_BINDIR" +else $as_nop + TARGET_BINDIR='.' +fi +fi + eval "ocaml_libdir=\"$ocaml_libdir\"" + if test "x$bindir_to_libdir" = 'x' +then : + TARGET_LIBDIR="$ocaml_libdir" +else $as_nop + TARGET_LIBDIR="$bindir_to_libdir" fi if test x"$target_launch_method" = 'x' then : @@ -22216,8 +22525,8 @@ fi launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' target_launch_method=\ '$(echo "$target_launch_method" | sed -e "s/'/'\"'\"'/g")' - ocaml_bindir='$(echo "$ocaml_bindir" | sed -e "s/'/'\"'\"'/g")' - target_bindir='$(echo "$target_bindir" | sed -e "s/'/'\"'\"'/g")' + HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' + TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")' ocaml_additional_stublibs_dir=\ '$(echo "$ocaml_additional_stublibs_dir" | sed -e "s/'/'\"'\"'/g")' ocaml_libdir='$(echo "$ocaml_libdir" | sed -e "s/'/'\"'\"'/g")' @@ -23393,9 +23702,9 @@ ltmain=$ac_aux_dir/ltmain.sh chmod +x "$ofile" ;; - "shebang":C) printf '%s\n%s\000\n' "$launch_method" "$ocaml_bindir" \ + "shebang":C) printf '%s\n%s\000\n' "$launch_method" "$HOST_BINDIR" \ > stdlib/runtime.info - printf '%s\n%s\000\n' "$target_launch_method" "$target_bindir" \ + printf '%s\n%s\000\n' "$target_launch_method" "$TARGET_BINDIR" \ > stdlib/target_runtime.info ;; "runtime/ld.conf":C) rm -f runtime/ld.conf test x"$ocaml_additional_stublibs_dir" = 'x' || \ diff --git a/configure.ac b/configure.ac index 146cdcd90c94..155955da6a28 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,26 @@ AC_INIT([OCaml], AC_MSG_NOTICE([Configuring OCaml version AC_PACKAGE_VERSION]) +# It's important for the setting up of defaults and the checking of the +# --with-relative-libdir option to know whether the user specified --libdir. +# Unfortunately, autoconf doesn't provide an easy way to determine this, so we +# simply repeat autoconf's argument parsing for the libdir and mandir options. +# This is done early in the script to ensure that only autoconf and site-config +# scripts have run. +libdir_given=no +mandir_given=no +for ac_arg +do + case $ac_arg in + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=* | \ + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + mandir_given=yes;; + -libdir=* | --libdir=* | --libdi=* | --libd=* | \ + -libdir | --libdir | --libdi | --libd) + libdir_given=yes;; + esac +done + # Configuration variables ## Command-line arguments passed to configure @@ -83,6 +103,10 @@ ocamltest_libunix=None ocamltest_unix_impl="dummy" unix_library="" unix_directory="" +target_libdir_is_relative=false +srcdir_abs='' +srcdir_abs_real='' +build_map_flags='' # Information about the package @@ -210,6 +234,7 @@ AC_SUBST([unix_library]) AC_SUBST([unix_directory]) AC_SUBST([cc_has_debug_prefix_map]) AC_SUBST([as_has_debug_prefix_map]) +AC_SUBST([as_is_cc]) AC_SUBST([with_debugger]) # TODO: rename this variable AC_SUBST([build_ocamldebug]) AC_SUBST([build_ocamltex]) @@ -253,10 +278,16 @@ AC_SUBST([flexdll_chain]) AC_SUBST([PACKLD]) AC_SUBST([build_libraries_manpages]) AC_SUBST([compute_deps]) +AC_SUBST([ocaml_prefix]) AC_SUBST([ocaml_bindir]) AC_SUBST([ocaml_libdir]) +AC_SUBST([TARGET_LIBDIR]) AC_SUBST([QS]) AC_SUBST([ar_supports_response_files]) +AC_SUBST([target_libdir_is_relative]) +AC_SUBST([srcdir_abs]) +AC_SUBST([srcdir_abs_real]) +AC_SUBST([build_map_flags]) ## Generated files @@ -298,12 +329,45 @@ AC_CANONICAL_BUILD AC_CANONICAL_HOST AC_CANONICAL_TARGET +# $cygwin_build_env=true if the build is taking place in any kind of Cygwin-like +# environment (which may include cross-compiling _from_ Cygwin) +# All patterns end with * (cf. build-aux/config.sub) +# +# In Cygwin itself, the mingw-w64 compilers are cross-compilers +# (host=x86_64-pc-cygwin; target=*-w64-mingw32) and $build when running from +# within Cygwin is always *-pc-cygwin. +# +# In MSYS2, the mingw-w64 compilers are normal host compilers, which MSYS2 makes +# available through different Environments (similar to the Microsoft Visual +# Studio Tools Command Prompts; see https://www.msys2.org/docs/environments/). +# It is possible to use MSYS2's "Cygwin" gcc (the equivalent of compiling native +# Cygwin), in which case $build is *-*-cygwin (some older MSYS2 installations +# may report the legacy *-*-msys*) +# The mingw-w64 Environments manually set $build to *-w64-mingw32, but the +# _native_ value inferred by config.guess (which uses uname -s) will be +# *-pc-mingw32 (for the 32-bit _target_ environments, even though MSYS2 is a +# 64-bit build environment) and *-pc-mingw64 (for the 64-bit _target_ +# environments). +# +# This leads to the four patterns below, all of which imply that the build is +# taking place on a system where Cygwin's utilities (cygpath, etc.) can be +# expected to be found and its semantics (CYGWIN=winsymlinks:native, etc.) be +# expected to apply. +# +# Note that although build=x86_64-pc-mingw64 will be accepted here, it is highly +# likely that that's a misconfigured environment, and the script will +# subsequently fail if host has not been altered to x86_64-w64-mingw32. +AS_CASE([$build], + [*-*-cygwin*|*-*-msys*|*-*-mingw32*|*-*-mingw64*], + [cygwin_build_env=true], + [cygwin_build_env=false]) + # Ensure that AC_CONFIG_LINKS will either create symlinks which are compatible # with native Windows (i.e. NTFS symlinks, not WSL or Cygwin-emulated ones) or # use its fallback mechanisms. Native Windows versions of ocamlc/ocamlopt cannot # interpret either WSL or Cygwin-emulated symlinks. -AS_CASE([$host], - [*-pc-windows|*-w64-mingw32*], +AS_CASE([$cygwin_build_env,$host], + [true,*-pc-windows|true,*-w64-mingw32*], [AC_CONFIG_COMMANDS([native-symlinks], [], [export CYGWIN="\$CYGWIN\${CYGWIN:+ }winsymlinks:nativestrict" export MSYS="\$MSYS\${MSYS:+ }winsymlinks:nativestrict"])]) @@ -522,8 +586,8 @@ AC_ARG_ENABLE([cmm-invariants], AC_ARG_WITH([target-bindir], [AS_HELP_STRING([--with-target-bindir], [location of the runtime binaries on the target system])], - [target_bindir=$withval], - [target_bindir='']) + [TARGET_BINDIR=$withval], + [TARGET_BINDIR='']) AC_ARG_WITH([target-sh], [AS_HELP_STRING([--with-target-sh], @@ -588,6 +652,19 @@ AC_ARG_ENABLE([mmap-map-stack], [AS_HELP_STRING([--enable-mmap-map-stack], [use mmap to allocate stacks instead of malloc])]) +AC_ARG_WITH([relative-libdir], + [AS_HELP_STRING([--with-relative-libdir], + m4_normalize([location of the Standard Library, specified relative to + --bindir (if no argument is given to --with-relative-libdir, defaults to + ../lib/ocaml)]))], + [AS_CASE([$withval], + [no], + [bindir_to_libdir=''], + [yes], + [bindir_to_libdir="..${default_separator}lib${default_separator}ocaml"], + [bindir_to_libdir="$withval"])], + [bindir_to_libdir='']) + AC_ARG_WITH([afl], [AS_HELP_STRING([--with-afl], [use the AFL fuzzer])]) @@ -735,8 +812,8 @@ AS_CASE([$ocaml_cc_vendor], # See https://lists.gnu.org/archive/html/autoconf/2019-07/msg00002.html # for the detailed explanation. -ocamlsrcdir=$(unset CDPATH; cd -- "$srcdir" && printf %sX "$PWD") || fail -ocamlsrcdir=${ocamlsrcdir%X} +srcdir_abs=$(unset CDPATH; cd -- "$srcdir" && printf %sX "$PWD") || fail +srcdir_abs=${srcdir_abs%X} # Whether ar supports @FILE arguments @@ -746,16 +823,17 @@ AS_CASE([$lt_cv_ar_at_file], # Libraries to build depending on the host -AS_CASE([$host], - [*-*-mingw32*|*-pc-windows], +AS_CASE([$cygwin_build_env,$host], + [true,*-*-mingw32*|true,*-pc-windows], [unix_or_win32="win32" OCAML_CHECK_LN_ON_WINDOWS ocamltest_libunix="Some false" - ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$ocamlsrcdir")" + ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$srcdir_abs")" ocamlyacc_wstr_module="yacc/wstr"], [unix_or_win32="unix" ln='ln -sf' ocamltest_libunix="Some true" + ocamlsrcdir="$srcdir_abs" ocamlyacc_wstr_module=""]) AS_CASE([$host], @@ -823,9 +901,9 @@ AS_IF([test "x$interpval" = "xyes"], # in config.status, rather than by the .in mechanism, since the latter cannot # reliably process binary files. AC_CONFIG_COMMANDS([shebang], - [printf '%s\n%s\000\n' "$launch_method" "$ocaml_bindir" \ + [printf '%s\n%s\000\n' "$launch_method" "$HOST_BINDIR" \ > stdlib/runtime.info - printf '%s\n%s\000\n' "$target_launch_method" "$target_bindir" \ + printf '%s\n%s\000\n' "$target_launch_method" "$TARGET_BINDIR" \ > stdlib/target_runtime.info], dnl These declarations are put in a here-document in configure, so the command dnl in '$(...)' _is_ evaluated as the content is written to config.status (by @@ -834,8 +912,8 @@ dnl nefarious single quotes which may appear in any of the strings. [launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' target_launch_method=\ '$(echo "$target_launch_method" | sed -e "s/'/'\"'\"'/g")' - ocaml_bindir='$(echo "$ocaml_bindir" | sed -e "s/'/'\"'\"'/g")' - target_bindir='$(echo "$target_bindir" | sed -e "s/'/'\"'\"'/g")']) + HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' + TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")']) # Are we building a cross-compiler @@ -1057,12 +1135,13 @@ AS_IF([test x"$supports_shared_libraries" != 'xfalse'], [ # ensure it can be executed from a native Windows process. The check # is only necessary when cross-compiling. AS_IF([test x"$build" != x"$host"],[ - AS_CASE([$build], - [*-pc-msys|*-pc-cygwin*], - [flexlink_where="$(cmd /c "$flexlink" -where 2>/dev/null)" - AS_IF([test -z "$flexlink_where"], - [AC_MSG_ERROR(m4_normalize([$flexlink is not executable from a - native Win32 process]))])]) + AS_IF([$cygwin_build_env], + [flexlink_where="$(cmd /c "$flexlink" -where 2>/dev/null)" + AS_IF([test -z "$flexlink_where"], + [AC_MSG_ERROR(m4_normalize([$flexlink is not executable from a + native Win32 process])) + ]) + ]) ]) ]) @@ -1194,6 +1273,7 @@ AS_CASE([$host], [AC_CHECK_HEADERS([unistd.h],[AC_DEFINE([HAS_UNISTD], [1])])]) AC_CHECK_HEADER([math.h]) +AC_CHECK_HEADER([libgen.h],[AC_DEFINE([HAS_LIBGEN_H], [1])]) AC_CHECK_HEADER([pthread_np.h],[AC_DEFINE([HAS_PTHREAD_NP_H], [1])]) AC_CHECK_HEADER([dirent.h], [AC_DEFINE([HAS_DIRENT], [1])], [], [#include ]) @@ -1659,24 +1739,33 @@ AS_IF([test -n "$target_alias"], # to avoiding forking a C compiler process for each compilation by ocamlopt. # Both AS and ASPP can be overridden by the user. -default_as="$CC -c" -default_aspp="$CC -c" +as_is_cc=true +default_as='' AS_CASE([$as_target,$ocaml_cc_vendor], [*-*-linux*,gcc-*], [AS_CASE([$as_cpu], [x86_64|arm*|aarch64*|i[[3-6]]86|riscv*], - [default_as="${toolpref}as"])], + [default_as="${toolpref}as" + as_is_cc=false])], + [*-*-cygwin,gcc-*], + [default_as="${toolpref}as" + as_is_cc=false], [i686-pc-windows,*], [default_as="ml -nologo -coff -Cp -c -Fo" - default_aspp="$default_as"], + default_aspp="$default_as" + as_is_cc=false], [x86_64-pc-windows,*], [default_as="ml64 -nologo -Cp -c -Fo" - default_aspp="$default_as"], + default_aspp="$default_as" + as_is_cc=false], [*-*-darwin*,clang-*], - [default_as="$default_as -Wno-trigraphs" + [default_as="$CC -c -Wno-trigraphs" default_aspp="$default_as"]) +AS_IF([test -z "$default_as"],[default_as="$CC -c"]) +AS_IF([test -z "$default_aspp"],[default_aspp="$CC -c"]) + AS_IF([test "$with_pic"], [fpic=true AC_DEFINE([CAML_WITH_FPIC], [1]) @@ -2226,7 +2315,6 @@ AC_CHECK_FUNC([pwrite], [AC_DEFINE([HAS_PWRITE], [1])]) ## -fdebug-prefix-map support by the C compiler AS_CASE([$ocaml_cc_vendor,$host], - [*,*-*-mingw32*], [cc_has_debug_prefix_map=false], [*,*-pc-windows], [cc_has_debug_prefix_map=false], [xlc*,powerpc-ibm-aix*], [cc_has_debug_prefix_map=false], [sunc*,sparc-sun-*], [cc_has_debug_prefix_map=false], @@ -2234,6 +2322,20 @@ AS_CASE([$ocaml_cc_vendor,$host], [cc_has_debug_prefix_map=true], [cc_has_debug_prefix_map=false], [$warn_error_flag])]) +## -ffile-prefix-map support by the C compiler - used in the build, not by +## the compiler +AS_IF([test x"$bindir_to_libdir" != 'x'], + [srcdir_abs_real="$(realpath "$srcdir_abs" 2>/dev/null)" + AS_IF([test x"$srcdir_abs_real" = "x$srcdir_abs"], + [srcdir_abs_real='']) + AX_CHECK_COMPILE_FLAG([-Wa,--debug-prefix-map=old=new], + [build_map_flags='-Wa,--debug-prefix-map='], [], [$warn_error_flag]) + AX_CHECK_COMPILE_FLAG([-ffile-prefix-map=old=new], + [build_map_flags="$build_map_flags -ffile-prefix-map="], + [AS_IF([$cc_has_debug_prefix_map], + [build_map_flags="$build_map_flags -fdebug-prefix-map="])], + [$warn_error_flag])]) + ## Does stat support nanosecond precision stat_has_ns_precision=false @@ -2678,10 +2780,10 @@ shlwapi.lib synchronization.lib"]) AC_CONFIG_COMMANDS_PRE([cclibs="$cclibs $mathlib $DLLIBS $PTHREAD_LIBS"]) -AS_IF([test x"$libdir" = x'${exec_prefix}/lib'], +AS_IF([test x"$libdir_given" = 'xno'], [libdir="$libdir"/ocaml]) -AS_IF([test x"$mandir" = x'${datarootdir}/man'], +AS_IF([test x"$mandir_given" = 'xno'], [mandir='${prefix}/man']) # Define default prefix correctly for the different Windows ports @@ -2690,11 +2792,65 @@ AS_IF([test x"$prefix" = "xNONE"], [i686-w64-mingw32*], [prefix='C:/ocamlmgw'], [x86_64-w64-mingw32*], [prefix='C:/ocamlmgw64'], [i686-pc-windows], [prefix='C:/ocamlms'], - [x86_64-pc-windows], [prefix='C:/ocamlms64'])], - [AS_IF([test x"$unix_or_win32" = "xwin32" \ - && test "$host_vendor-$host_os" != "$build_vendor-$build_os" ], - [AS_CASE([$build], - [*-pc-cygwin], [prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")"])])]) + [x86_64-pc-windows], [prefix='C:/ocamlms64'])]) + +# Normalise $prefix, if necessary, on Windows. There are 7 variables to be +# considered: +# - $prefix and $exec_prefix. These are autoconf variables containing the values +# specified for --prefix and --exec-prefix respectively, or NONE if these +# flags were not given on the command line. These two variables are written to +# Makefile.config and are in _build_ format. On native Windows, the values +# must be suitable to pass to _both_ native Windows processes and Cygwin/MSYS2 +# commands. The values are ultimately converted to Windows paths using slashes +# (i.e. C:/foo) +# - $bindir and $libdir. These similarly contain the values specified for +# --bindir and --libdir, or defaults relative to $exec_prefix otherwise. +# Unlike --prefix, values specified to configure for --bindir and --libdir are +# assumed to be suitable for both native Windows processes and Cygwin/MSYS2. +# These two variables ultimately end up in Makefile.config as $(BINDIR) and +# $(LIBDIR) and are used for installation commands only. +# - $ocaml_prefix, $ocaml_bindir and $ocaml_libdir are the _expanded_ values of +# $prefix, $bindir and $libdir so that the values can be inserted into OCaml +# strings (typically in utils/config.generated.ml). On Windows, these preserve +# the backslashes present in the value passed to --prefix. +ocaml_bindir="$bindir" +ocaml_libdir="$libdir" +ocaml_prefix="$prefix" +AS_CASE([$cygwin_build_env,$host], + [true,*-w64-mingw32*|true,*-pc-windows], + [prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")" + AS_IF([test "x${ocaml_prefix%%/*}" != "x$ocaml_prefix"], + # $prefix contained a slash - normalise it with cygpath. The rationale for + # this allows both `./configure --prefix $PWD/install` (which will be a + # Cygwin path) and also systems lazily using slashes instead of + # backslashes (i.e. C:/Backslashes/Scare/Us) to work. + [ocaml_prefix="$prefix"], + # $prefix was using backslashes - preserve these in the build, but + # continue to use slashes for the Makefile variables. + [AS_IF([test "x$bindir" = 'x${exec_prefix}/bin'], + [ocaml_bindir='${exec_prefix}\bin'], + [ocaml_bindir="$bindir"]) + AS_IF([test "x$libdir" = 'x${exec_prefix}/lib/ocaml'], + [ocaml_libdir='${exec_prefix}\lib\ocaml'], + [ocaml_libdir="$libdir"])])]) + +AS_IF([test x"$libdir_given" = 'xno'], + [AS_IF([test x"$bindir_to_libdir" != 'x'], + [ocaml_libdir="$bindir_to_libdir" + target_libdir_is_relative=true + AS_CASE([$cygwin_build_env,$host], + [true,*-w64-mingw32*|true,*-pc-windows], + [build_bindir_to_libdir="$(LC_ALL=C.UTF-8 cygpath \ + "$bindir_to_libdir")"], + [build_bindir_to_libdir="$bindir_to_libdir"]) + AS_CASE([$build_bindir_to_libdir], + [./*],[libdir="$bindir${build_bindir_to_libdir[#].}"], + [../*],[libdir="$bindir/$build_bindir_to_libdir"], + [AC_MSG_ERROR(m4_normalize([--with-relative-libdir requires an explicit + relative path]))])])], + [AS_IF([test x"$bindir_to_libdir" != 'x'], + [AC_MSG_ERROR(m4_normalize([--with-relative-libdir and --libdir cannot both + be specified]))])]) # Define a few macros that were defined in config/m-nt.h # but whose value is not guessed properly by configure @@ -2721,12 +2877,34 @@ unset ac_cv_header_flexdll_h AC_CONFIG_COMMANDS_PRE([ saved_exec_prefix="$exec_prefix" saved_prefix="$prefix" + prefix="$ocaml_prefix" AS_IF([test "x$prefix" = "xNONE"],[prefix="$ac_default_prefix"]) AS_IF([test "x$exec_prefix" = "xNONE"],[exec_prefix="$prefix"]) eval "exec_prefix=\"$exec_prefix\"" - eval "ocaml_bindir=\"$bindir\"" - eval "ocaml_libdir=\"$libdir\"" - AS_IF([test x"$target_bindir" = 'x'],[target_bindir="$ocaml_bindir"]) + # Set variables necessary to create utils/config.generated.ml and the two + # runtime-launch-info templates in stdlib. + # $ocaml_bindir is used for utils/config.generated.ml and is _empty_ if the + # compiler is configured with --with-relative-libdir (otherwise the path + # would be embedded in config.cmo) + # $HOST_BINDIR and $TARGET_BINDIR are used to generate the two + # runtime-launch-info files. $HOST_BINDIR is always the absolute path to the + # binary directory, in host format (i.e. potentially with backslashes on + # Windows). $TARGET_BINDIR can be specified by the caller when building + # cross-compilers, and the value then is used unaltered. Otherwise, + # $TARGET_BINDIR is set to '.' when the compiler is configured with + # --with-relative-libdir or the value of $HOST_BINDIR otherwise. + eval "HOST_BINDIR=\"$ocaml_bindir\"" + AS_IF([test "x$bindir_to_libdir" = 'x'], + [ocaml_bindir="$HOST_BINDIR"], + [ocaml_bindir='']) + AS_IF([test x"$TARGET_BINDIR" = 'x'], + [AS_IF([test "x$bindir_to_libdir" = 'x'], + [TARGET_BINDIR="$HOST_BINDIR"], + [TARGET_BINDIR='.'])]) + eval "ocaml_libdir=\"$ocaml_libdir\"" + AS_IF([test "x$bindir_to_libdir" = 'x'], + [TARGET_LIBDIR="$ocaml_libdir"], + [TARGET_LIBDIR="$bindir_to_libdir"]) AS_IF([test x"$target_launch_method" = 'x'], [target_launch_method="$launch_method"]) prefix="$saved_prefix" diff --git a/driver/compenv.ml b/driver/compenv.ml index d61d2b19fd0f..a1458e5cdac4 100644 --- a/driver/compenv.ml +++ b/driver/compenv.ml @@ -43,6 +43,8 @@ let fatal err = prerr_endline err; raise (Exit_with_status 2) +let fatalf fmt = Printf.ksprintf fatal fmt + let extract_output = function | Some s -> s | None -> @@ -732,3 +734,14 @@ let parse_arguments ?(current=ref 0) argv f program = Printf.sprintf "Usage: %s \nOptions are:" program in Printf.printf "%s\n%s" help_msg err_msg; raise (Exit_with_status 0) + +let parse_runtime_parameter opt = + let k, setting = + try Misc.cut_at opt '=' + with Not_found -> + fatalf "-set-runtime-default: invalid runtime parameter '%s'. \ + Expected =." opt in + if k = "standard_library_default" then + Clflags.standard_library_default := Some setting + else + fatalf "-set-runtime-default: unrecognized runtime parameter %s." k diff --git a/driver/compenv.mli b/driver/compenv.mli index a5958554e76e..c2bc2dff1dbb 100644 --- a/driver/compenv.mli +++ b/driver/compenv.mli @@ -23,6 +23,7 @@ val print_version_and_library : string -> 'a val print_version_string : unit -> 'a val print_standard_library : unit -> 'a val fatal : string -> 'a +val fatalf : ('a, unit, string, 'b) format4 -> 'a val first_ccopts : string list ref val first_ppx : string list ref @@ -76,3 +77,6 @@ val process_deferred_actions : *) val parse_arguments : ?current:(int ref) -> string array ref -> Arg.anon_fun -> string -> unit + +(** Validate a single -set-runtime-default parameter specification. *) +val parse_runtime_parameter : string -> unit diff --git a/driver/main_args.ml b/driver/main_args.ml index ea07d15cf19f..4cc08062844c 100644 --- a/driver/main_args.ml +++ b/driver/main_args.ml @@ -160,6 +160,10 @@ let mk_H f = " Add to the list of \"hidden\" include directories\n\ \ (Like -I, but the program can not directly reference these dependencies)" +let mk_set_runtime_default f = + "-set-runtime-default", Arg.String f, "= Set the default for \ + runtime parameter to (see the manual for further details)" + let mk_impl f = "-impl", Arg.String f, " Compile as a .ml file" @@ -872,6 +876,7 @@ module type Compiler_options = sig val _runtime_variant : string -> unit val _with_runtime : unit -> unit val _without_runtime : unit -> unit + val _set_runtime_default : string -> unit val _short_paths : unit -> unit val _thread : unit -> unit val _v : unit -> unit @@ -1099,6 +1104,7 @@ struct mk_without_runtime F._without_runtime; mk_safe_string; mk_safer_matching F._safer_matching; + mk_set_runtime_default F._set_runtime_default; mk_short_paths F._short_paths; mk_strict_sequence F._strict_sequence; mk_no_strict_sequence F._no_strict_sequence; @@ -1314,6 +1320,7 @@ struct mk_S F._S; mk_safe_string; mk_safer_matching F._safer_matching; + mk_set_runtime_default F._set_runtime_default; mk_shared F._shared; mk_short_paths F._short_paths; mk_strict_sequence F._strict_sequence; @@ -1794,6 +1801,7 @@ module Default = struct let _plugin _p = plugin := true let _pp s = preprocessor := (Some s) let _runtime_variant s = runtime_variant := s + let _set_runtime_default s = Compenv.parse_runtime_parameter s let _stop_after pass = let module P = Compiler_pass in match P.of_string pass with diff --git a/driver/main_args.mli b/driver/main_args.mli index 554b1fa36bad..4857507214e6 100644 --- a/driver/main_args.mli +++ b/driver/main_args.mli @@ -113,6 +113,7 @@ module type Compiler_options = sig val _runtime_variant : string -> unit val _with_runtime : unit -> unit val _without_runtime : unit -> unit + val _set_runtime_default : string -> unit val _short_paths : unit -> unit val _thread : unit -> unit val _v : unit -> unit diff --git a/driver/maindriver.ml b/driver/maindriver.ml index c008221c54fa..fc27f6be4568 100644 --- a/driver/maindriver.ml +++ b/driver/maindriver.ml @@ -61,7 +61,7 @@ let main argv ppf = "Please specify at most one of -pack, -a, -c, -output-obj"; | Some ((P.Parsing | P.Typing | P.Lambda) as p) -> assert (P.is_compilation_pass p); - Printf.ksprintf Compenv.fatal + Compenv.fatalf "Options -i and -stop-after (%s) \ are incompatible with -pack, -a, -output-obj" (String.concat "|" diff --git a/driver/optmaindriver.ml b/driver/optmaindriver.ml index 0cacd3363a80..124890367ab4 100644 --- a/driver/optmaindriver.ml +++ b/driver/optmaindriver.ml @@ -77,7 +77,7 @@ let main argv ppf = -output-obj"; | Some ((P.Parsing | P.Typing | P.Lambda | P.Scheduling | P.Emit) as p) -> assert (P.is_compilation_pass p); - Printf.ksprintf Compenv.fatal + Compenv.fatalf "Options -i and -stop-after (%s) \ are incompatible with -pack, -a, -shared, -output-obj" (String.concat "|" diff --git a/file_formats/cmx_format.mli b/file_formats/cmx_format.mli index 7a167d0cd4de..711ade43e7e1 100644 --- a/file_formats/cmx_format.mli +++ b/file_formats/cmx_format.mli @@ -46,7 +46,8 @@ type unit_infos = mutable ui_send_fun: int list; (* Send functions needed *) mutable ui_export_info: export_info; mutable ui_force_link: bool; (* Always linked *) - mutable ui_for_pack: string option } (* Part of a pack *) + mutable ui_for_pack: string option; (* Part of a pack *) + mutable ui_need_stdlib: bool} (* caml_standard_library_nat needed *) (* Each .a library has a matching .cmxa file that provides the following infos on the library: *) diff --git a/lambda/lambda.ml b/lambda/lambda.ml index 9fdbc8146e9f..20074a1828f3 100644 --- a/lambda/lambda.ml +++ b/lambda/lambda.ml @@ -25,6 +25,7 @@ type compile_time_constant = | Ostype_win32 | Ostype_cygwin | Backend_type + | Standard_library_default type immediate_or_pointer = | Immediate diff --git a/lambda/lambda.mli b/lambda/lambda.mli index 6f5d9717914b..eb72cf09eaff 100644 --- a/lambda/lambda.mli +++ b/lambda/lambda.mli @@ -26,6 +26,7 @@ type compile_time_constant = | Ostype_win32 | Ostype_cygwin | Backend_type + | Standard_library_default type immediate_or_pointer = | Immediate diff --git a/lambda/printlambda.ml b/lambda/printlambda.ml index 76989a4035f7..c5153580e1ce 100644 --- a/lambda/printlambda.ml +++ b/lambda/printlambda.ml @@ -266,7 +266,8 @@ let primitive ppf = function | Ostype_unix -> "ostype_unix" | Ostype_win32 -> "ostype_win32" | Ostype_cygwin -> "ostype_cygwin" - | Backend_type -> "backend_type" in + | Backend_type -> "backend_type" + | Standard_library_default -> "standard_library_default" in fprintf ppf "sys.constant_%s" const_name | Pisint -> fprintf ppf "isint" | Pisout -> fprintf ppf "isout" diff --git a/lambda/translprim.ml b/lambda/translprim.ml index df46ea3a44af..ca50c898a0a8 100644 --- a/lambda/translprim.ml +++ b/lambda/translprim.ml @@ -148,6 +148,8 @@ let primitives_table = "%ostype_unix", Primitive ((Pctconst Ostype_unix), 1); "%ostype_win32", Primitive ((Pctconst Ostype_win32), 1); "%ostype_cygwin", Primitive ((Pctconst Ostype_cygwin), 1); + "%standard_library_default", + Primitive ((Pctconst Standard_library_default), 1); "%frame_pointers", Frame_pointers; "%negint", Primitive (Pnegint, 1); "%succint", Primitive ((Poffsetint 1), 1); diff --git a/man/ocamlc.1 b/man/ocamlc.1 index f503e948c79b..4adf4d99a370 100644 --- a/man/ocamlc.1 +++ b/man/ocamlc.1 @@ -674,6 +674,15 @@ This allows to detect match failures even if a pattern-matching was wrongly assumed to be exhaustive. This only impacts GADT and polymorphic variant compilation. .TP +.BI \-set\-runtime\-default " setting=value" +When linking an executable, override the default value for a runtime setting. +The only currently supported setting is: + +.B standard_library_default +Specifies the default location used by the executable to locate the Standard +Library. By default, this is the absolute path to the Standard Library the +compiler itself was configured with. +.TP .B \-short\-paths When a type is visible under several module-paths, use the shortest one when printing the type's name in inferred interfaces and error and diff --git a/man/ocamlopt.1 b/man/ocamlopt.1 index 52bed675ef2a..9b84757a95bf 100644 --- a/man/ocamlopt.1 +++ b/man/ocamlopt.1 @@ -593,6 +593,15 @@ Save intermediate representation after the given compilation pass. The currently supported passes are: .BR scheduling . .TP +.BI \-set\-runtime\-default " setting=value" +When linking an executable, override the default value for a runtime setting. +The only currently supported setting is: + +.B standard_library_default +Specifies the default location used by the executable to locate the Standard +Library. By default, this is the absolute path to the Standard Library the +compiler itself was configured with. +.TP .B \-shared Build a plugin (usually .cmxs) that can be dynamically loaded with the diff --git a/manual/src/cmds/unified-options.etex b/manual/src/cmds/unified-options.etex index 96cc29ce3323..b660d01421e8 100644 --- a/manual/src/cmds/unified-options.etex +++ b/manual/src/cmds/unified-options.etex @@ -706,6 +706,17 @@ using "compiler-libs" library (see ). }%nat +\notop{% +\item["-set-runtime-default" \var{name=value}] +When linking an executable, override the default value for a runtime setting. +The currently supported settings are: +\begin{description} + \item["standard_library_default"] Specifies the default location used by the + executable to locate the Standard Library. By default, this is the absolute + path to the Standard Library the compiler itself was configured with. +\end{description} +}%notop + \nat{% \item["-shared"] Build a plugin (usually ".cmxs") that can be dynamically loaded with diff --git a/middle_end/closure/closure.ml b/middle_end/closure/closure.ml index 880773fbac09..26e380767573 100644 --- a/middle_end/closure/closure.ml +++ b/middle_end/closure/closure.ml @@ -1051,22 +1051,29 @@ let rec close ({ backend; fenv; cenv ; mutable_vars } as env) lam = None ubody), approx) (* Compile-time constants *) - | Lprim(Pctconst c, [arg], _loc) -> - let cst, approx = - match c with - | Big_endian -> make_const_bool B.big_endian - | Word_size -> make_const_int (8*B.size_int) - | Int_size -> make_const_int (8*B.size_int - 1) - | Max_wosize -> make_const_int ((1 lsl ((8*B.size_int) - 10)) - 1 ) - | Ostype_unix -> make_const_bool (Sys.os_type = "Unix") - | Ostype_win32 -> make_const_bool (Sys.os_type = "Win32") - | Ostype_cygwin -> make_const_bool (Sys.os_type = "Cygwin") - | Backend_type -> - make_const_int 0 (* tag 0 is the same as Native here *) + | Lprim(Pctconst c, [arg], loc) -> + let cst f v = + let cst, approx = f v in + let arg, _approx = close env arg in + let id = Ident.create_local "dummy" in + Ulet(Immutable, Pgenval, VP.create id, arg, cst), approx in - let arg, _approx = close env arg in - let id = Ident.create_local "dummy" in - Ulet(Immutable, Pgenval, VP.create id, arg, cst), approx + begin match c with + | Big_endian -> cst make_const_bool B.big_endian + | Word_size -> cst make_const_int (8*B.size_int) + | Int_size -> cst make_const_int (8*B.size_int - 1) + | Max_wosize -> cst make_const_int ((1 lsl ((8*B.size_int) - 10)) - 1) + | Ostype_unix -> cst make_const_bool (Sys.os_type = "Unix") + | Ostype_win32 -> cst make_const_bool (Sys.os_type = "Win32") + | Ostype_cygwin -> cst make_const_bool (Sys.os_type = "Cygwin") + | Backend_type -> + cst make_const_int 0 (* tag 0 is the same as Native here *) + | Standard_library_default -> + Compilenv.need_stdlib_location (); + let dbg = Debuginfo.from_location loc in + let id = Ident.name Compilenv.stdlib_symbol_name in + Uprim(P.Pread_symbol id, [], dbg), Value_const (Uconst_ref (id, None)) + end | Lprim(Pignore, [arg], _loc) -> let expr, approx = make_const_int 0 in Usequence(fst (close env arg), expr), approx @@ -1458,7 +1465,9 @@ let collect_exported_structured_constants a = | Uconst_ref (s, (Some c)) -> Compilenv.add_exported_constant s; structured_constant c - | Uconst_ref (_s, None) -> assert false (* Cannot be generated *) + | Uconst_ref (s, None) -> + (* Only generated in one context *) + assert (s = Ident.name Compilenv.stdlib_symbol_name) | Uconst_int _ -> () and structured_constant = function | Uconst_block (_, ul) -> List.iter const ul diff --git a/middle_end/compilenv.ml b/middle_end/compilenv.ml index d975c1781ce6..bda2938c09e5 100644 --- a/middle_end/compilenv.ml +++ b/middle_end/compilenv.ml @@ -88,7 +88,8 @@ let current_unit = ui_send_fun = []; ui_force_link = false; ui_export_info = default_ui_export_info; - ui_for_pack = None } + ui_for_pack = None; + ui_need_stdlib = false } let symbol_separator = match Config.ccomp_type with @@ -128,6 +129,7 @@ let reset ?packname name = current_unit.ui_send_fun <- []; current_unit.ui_force_link <- !Clflags.link_everything; current_unit.ui_for_pack <- packname; + current_unit.ui_need_stdlib <- false; Hashtbl.clear exported_constants; structured_constants := structured_constants_empty; current_unit.ui_export_info <- default_ui_export_info; @@ -258,11 +260,16 @@ let global_approx id = | None -> Clambda.Value_unknown | Some ui -> get_clambda_approx ui +(* The name of the symbol defined globally for %standard_library_default *) +let stdlib_symbol_name = Ident.create_persistent "caml_standard_library_nat" + (* Return the symbol used to refer to a global identifier *) let symbol_for_global id = if Ident.is_predef id then "caml_exn_" ^ Ident.name id + else if Ident.same stdlib_symbol_name id then + Ident.name id else begin let unitname = Ident.name id in match @@ -290,7 +297,7 @@ let is_predefined_exception sym = let symbol_for_global' id = let sym_label = Linkage_name.create (symbol_for_global id) in - if Ident.is_predef id then + if Ident.is_predef id || Ident.same stdlib_symbol_name id then Symbol.of_global_linkage predefined_exception_compilation_unit sym_label else Symbol.of_global_linkage (unit_for_global id) sym_label @@ -348,6 +355,11 @@ let need_send_fun n = if not (List.mem n current_unit.ui_send_fun) then current_unit.ui_send_fun <- n :: current_unit.ui_send_fun +(* Record that caml_standard_library_nat is needed *) + +let need_stdlib_location () = + current_unit.ui_need_stdlib <- true + (* Write the description of the current unit *) let write_unit_info info filename = diff --git a/middle_end/compilenv.mli b/middle_end/compilenv.mli index 2bcbca98b830..33965f425b9c 100644 --- a/middle_end/compilenv.mli +++ b/middle_end/compilenv.mli @@ -104,6 +104,14 @@ val need_send_fun: int -> unit (* Record the need of a currying (resp. application, message sending) function with the given arity *) +val need_stdlib_location: unit -> unit + (* Record that caml_standard_library_nat needs to be initialised if this + unit is linked. *) + +val stdlib_symbol_name: Ident.t + (* The name of the symbol defined globally for + %standard_library_default *) + val new_const_symbol : unit -> string val closure_symbol : Closure_id.t -> Symbol.t (* Symbol of a function if the function is diff --git a/middle_end/flambda/closure_conversion.ml b/middle_end/flambda/closure_conversion.ml index 6b9e8d967019..4c68b6c11e35 100644 --- a/middle_end/flambda/closure_conversion.ml +++ b/middle_end/flambda/closure_conversion.ml @@ -390,23 +390,30 @@ let rec close t env (lam : Lambda.lambda) : Flambda.t = ~name:Names.raise) | Lprim (Pctconst c, [arg], _loc) -> let module Backend = (val t.backend) in - let const = - begin match c with - | Big_endian -> lambda_const_bool Backend.big_endian - | Word_size -> lambda_const_int (8*Backend.size_int) - | Int_size -> lambda_const_int (8*Backend.size_int - 1) - | Max_wosize -> - lambda_const_int ((1 lsl ((8*Backend.size_int) - 10)) - 1) - | Ostype_unix -> lambda_const_bool (String.equal Sys.os_type "Unix") - | Ostype_win32 -> lambda_const_bool (String.equal Sys.os_type "Win32") - | Ostype_cygwin -> lambda_const_bool (String.equal Sys.os_type "Cygwin") - | Backend_type -> - Lambda.const_int 0 (* tag 0 is the same as Native *) - end - in - close t env - (Lambda.Llet(Strict, Pgenval, Ident.create_local "dummy", + let cst f v = + let const = f v in + close t env (Lambda.Llet(Strict, Pgenval, Ident.create_local "dummy", arg, Lconst const)) + in + begin match c with + | Big_endian -> cst lambda_const_bool Backend.big_endian + | Word_size -> cst lambda_const_int (8*Backend.size_int) + | Int_size -> cst lambda_const_int (8*Backend.size_int - 1) + | Max_wosize -> + cst lambda_const_int ((1 lsl ((8*Backend.size_int) - 10)) - 1) + | Ostype_unix -> + cst lambda_const_bool (String.equal Sys.os_type "Unix") + | Ostype_win32 -> + cst lambda_const_bool (String.equal Sys.os_type "Win32") + | Ostype_cygwin -> + cst lambda_const_bool (String.equal Sys.os_type "Cygwin") + | Backend_type -> cst Lambda.const_int 0 (* tag 0 is the same as Native *) + | Standard_library_default -> + Compilenv.need_stdlib_location (); + let symbol = t.symbol_for_global' Compilenv.stdlib_symbol_name in + t.imported_symbols <- Symbol.Set.add symbol t.imported_symbols; + name_expr (Symbol symbol) ~name:Names.pgetglobal + end | Lprim (Pfield _, [Lprim (Pgetglobal id, [],_)], _) when Ident.same id t.current_unit_id -> Misc.fatal_errorf "[Pfield (Pgetglobal ...)] for the current compilation \ diff --git a/ocamltest/ocaml_tests.ml b/ocamltest/ocaml_tests.ml index baf643ec33c9..75930567159d 100644 --- a/ocamltest/ocaml_tests.ml +++ b/ocamltest/ocaml_tests.ml @@ -46,7 +46,16 @@ let bytecode = check_program_output; ] @ (if not Sys.win32 && Ocamltest_config.native_compiler then - opt_build @ [compare_bytecode_programs] + (* If the compiler is configured using --with-relative-libdir then at + present we can't compare the bytecode programs because ocamlc.opt and + ocamlrun are at different levels in the build tree, but they're both + configured with the same relative directory path. + This problem will disappear when ocamltest runs the testsuite against a + compiler in an install-tree like way. *) + if Ocamltest_config.has_relative_libdir then + opt_build + else + opt_build @ [compare_bytecode_programs] else [] ) diff --git a/ocamltest/ocamltest_config.ml.in b/ocamltest/ocamltest_config.ml.in index 2c53e45ce39f..112976238ef0 100644 --- a/ocamltest/ocamltest_config.ml.in +++ b/ocamltest/ocamltest_config.ml.in @@ -95,3 +95,5 @@ let instrumented_runtime = @instrumented_runtime@ let frame_pointers = @frame_pointers@ let tsan = @tsan@ + +let has_relative_libdir = @target_libdir_is_relative@ diff --git a/ocamltest/ocamltest_config.mli b/ocamltest/ocamltest_config.mli index d75ae87fbe39..d4a6276d2227 100644 --- a/ocamltest/ocamltest_config.mli +++ b/ocamltest/ocamltest_config.mli @@ -133,3 +133,6 @@ val frame_pointers : bool val tsan : bool (** Whether ThreadSanitizer support has been enabled at configure time *) + +val has_relative_libdir : bool +(** Whether the compiler has been configured using --with-relative-libdir *) diff --git a/runtime/backtrace_byt.c b/runtime/backtrace_byt.c index e8cb4b3999dd..074dc42274f7 100644 --- a/runtime/backtrace_byt.c +++ b/runtime/backtrace_byt.c @@ -451,12 +451,12 @@ static void read_main_debug_info(struct debug_info *di) CAMLassert(di->already_read == 0); di->already_read = 1; - /* At the moment, bytecode programs built with --output-complete-exe + /* At the moment, bytecode programs built with -output-complete-exe do not contain any debug info. See https://github.com/ocaml/ocaml/issues/9344 for details. */ - if (caml_params->cds_file == NULL && caml_byte_program_mode == COMPLETE_EXE) + if (caml_params->cds_file == NULL && caml_byte_program_mode == EMBEDDED) CAMLreturn0; if (caml_params->cds_file != NULL) { diff --git a/runtime/caml/osdeps.h b/runtime/caml/osdeps.h index 07666f26a6af..60166ad5c64e 100644 --- a/runtime/caml/osdeps.h +++ b/runtime/caml/osdeps.h @@ -95,6 +95,22 @@ void *caml_plat_mem_commit(void *, uintnat); void caml_plat_mem_decommit(void *, uintnat); void caml_plat_mem_unmap(void *, uintnat); +/* caml_locate_standard_library(exe_name, stdlib_default, dirname) returns the + location of the Standard Library. The location returned is absolute, if + stdlib_default is a relative path then the result is computed relative to the + directory portion of exe_name. + + If dirname is not NULL and stdlib_default is a relative path, a copy of the + directory name part of exe_name is returned in dirname. If stdlib_default is + an absolute path, dirname is never changed. + + Both strings are allocated with [caml_stat_alloc], so should be freed using + [caml_stat_free]. +*/ +CAMLextern char_os *caml_locate_standard_library (const char_os *exe_name, + const char_os *stdlib_default, + char_os **dirname); + #ifdef _WIN32 #include @@ -156,6 +172,18 @@ extern uint64_t caml_time_counter(void); extern void caml_init_os_params(void); +/* True if: + - dir equals "." + - dir equals ".." + - dir begins "./" + - dir begins "../" + The tests for null avoid the need to call strlen_os. */ +#define Is_relative_dir(dir) \ + (dir[0] == '.' \ + && (dir[1] == '\0' \ + || Is_separator(dir[1]) \ + || (dir[1] == '.' && (dir[2] == '\0' || Is_separator(dir[2]))))) + #endif /* CAML_INTERNALS */ #ifdef _WIN32 diff --git a/runtime/caml/s.h.in b/runtime/caml/s.h.in index bc6ced458da6..8c6b6b6af999 100644 --- a/runtime/caml/s.h.in +++ b/runtime/caml/s.h.in @@ -112,6 +112,10 @@ /* Define HAS_UNISTD if you have /usr/include/unistd.h. */ +#undef HAS_LIBGEN_H + +/* Define HAS_LIBGEN_H if you have /usr/include/libgen.h. */ + #undef HAS_DIRENT /* Define HAS_DIRENT if you have /usr/include/dirent.h and the result of diff --git a/runtime/caml/startup.h b/runtime/caml/startup.h index 14f81e61d4c6..51fddf0f7a0e 100644 --- a/runtime/caml/startup.h +++ b/runtime/caml/startup.h @@ -48,13 +48,18 @@ extern int32_t caml_seek_optional_section(int fd, struct exec_trailer *trail, extern int32_t caml_seek_section(int fd, struct exec_trailer *trail, char *name); -enum caml_byte_program_mode - { - STANDARD /* normal bytecode program requiring "ocamlrun" */, - COMPLETE_EXE /* embedding the vm, i.e. compiled with --output-complete-exe */ - }; +enum caml_byte_program_mode { + STANDARD, /* Default mode for ocamlrun */ + APPENDED, /* bytecode must be appended (i.e. -custom) */ + EMBEDDED /* bytecode embedded in C (e.g. -output-complete-exe/-output-obj) */ +}; -extern enum caml_byte_program_mode caml_byte_program_mode; +extern const enum caml_byte_program_mode caml_byte_program_mode; + +/* The default location of the Standard Library as used by the runtime to find + ld.conf */ +extern const char_os *caml_runtime_standard_library_default; +extern const char_os *caml_runtime_standard_library_effective; #endif /* CAML_INTERNALS */ diff --git a/runtime/caml/sys.h b/runtime/caml/sys.h index b2fe25a147d9..e422f29d3beb 100644 --- a/runtime/caml/sys.h +++ b/runtime/caml/sys.h @@ -33,6 +33,10 @@ CAMLextern void caml_sys_init (char_os * exe_name, char_os ** argv); CAMLnoret CAMLextern void caml_do_exit (int); +/* The default location of the Standard Library as used by the + %standard_library_default primitive */ +extern char_os *caml_standard_library_default; + #endif /* CAML_INTERNALS */ #endif /* CAML_SYS_H */ diff --git a/runtime/dynlink.c b/runtime/dynlink.c index c367115d059d..460c4385c54d 100644 --- a/runtime/dynlink.c +++ b/runtime/dynlink.c @@ -278,7 +278,8 @@ void caml_build_primitive_table(char_os * lib_path, if (lib_path != NULL) for (char_os *p = lib_path; *p != 0; p += strlen_os(p) + 1) caml_ext_table_add(&caml_shared_libs_path, p); - caml_parse_ld_conf(OCAML_STDLIB_DIR, &caml_shared_libs_path); + caml_parse_ld_conf(caml_runtime_standard_library_effective, + &caml_shared_libs_path); /* Open the shared libraries */ caml_ext_table_init(&shared_libs, 8); if (libs != NULL) diff --git a/runtime/gen_primsc.sh b/runtime/gen_primsc.sh index c18ab95a0987..9630501a7c60 100755 --- a/runtime/gen_primsc.sh +++ b/runtime/gen_primsc.sh @@ -31,6 +31,8 @@ cat <<'EOF' #define CAML_INTERNALS #include "caml/mlvalues.h" #include "caml/prims.h" +#include "caml/startup.h" +#include "build_config.h" EOF @@ -61,3 +63,12 @@ echo echo 'const char * const caml_names_of_builtin_cprim[] = {' sed -e 's/.*/ "&",/' "$primitives" echo ' 0 };' + +# ocamlrun values for symbols which are provided by the bytecode linker +# - ocamlrun is able to use any of the mechanisms to load the bytecode +# - caml_runtime_standard_library_default for bytecode images on this runtime +cat <<'EOF' + +const enum caml_byte_program_mode caml_byte_program_mode = STANDARD; +const char_os *caml_runtime_standard_library_default = OCAML_STDLIB_DIR; +EOF diff --git a/runtime/startup_byt.c b/runtime/startup_byt.c index bff725886730..9e4ebf263120 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -72,6 +72,8 @@ #define SEEK_END 2 #endif +const char_os * caml_runtime_standard_library_effective = NULL; + static char magicstr[EXEC_MAGIC_LENGTH+1]; /* Print the specified error message followed by an end-of-line and exit */ @@ -113,8 +115,6 @@ int caml_read_trailer(int fd, struct exec_trailer *trail) ? 0 : WRONG_MAGIC; } -enum caml_byte_program_mode caml_byte_program_mode = STANDARD; - int caml_attempt_open(char_os **name, struct exec_trailer *trail, int do_open_script) { @@ -379,7 +379,7 @@ static const char_os * get_stdlib_location(void) const char_os * stdlib; stdlib = caml_secure_getenv(T("OCAMLLIB")); if (stdlib == NULL) stdlib = caml_secure_getenv(T("CAMLLIB")); - if (stdlib == NULL) stdlib = OCAML_STDLIB_DIR; + if (stdlib == NULL) stdlib = caml_runtime_standard_library_effective; return stdlib; } @@ -392,7 +392,7 @@ static void do_print_config(void) /* Print the runtime configuration */ printf("version: %s\n", OCAML_VERSION_STRING); printf("standard_library_default: %s\n", - caml_stat_strdup_of_os(OCAML_STDLIB_DIR)); + caml_stat_strdup_of_os(caml_runtime_standard_library_default)); printf("standard_library: %s\n", caml_stat_strdup_of_os(get_stdlib_location())); printf("int_size: %d\n", 8 * (int)sizeof(value)); @@ -429,7 +429,8 @@ static void do_print_config(void) /* Parse ld.conf and print the effective search path */ puts("shared_libs_path:"); - caml_parse_ld_conf(OCAML_STDLIB_DIR, &caml_shared_libs_path); + caml_parse_ld_conf(caml_runtime_standard_library_effective, + &caml_shared_libs_path); for (int i = 0; i < caml_shared_libs_path.size; i++) { dir = caml_shared_libs_path.contents[i]; if (dir[0] == 0) @@ -458,13 +459,13 @@ extern void caml_install_invalid_parameter_handler(void); CAMLexport void caml_main(char_os **argv) { - int fd, pos; + int fd = -1, pos; struct exec_trailer trail; struct channel * chan; value res; char * req_prims; char_os * shared_lib_path, * shared_libs; - char_os * exe_name, * proc_self_exe; + char_os * exe_name, * proc_self_exe, * argv0; /* Determine options */ caml_parse_ocamlrunparam(); @@ -485,9 +486,21 @@ CAMLexport void caml_main(char_os **argv) /* Determine position of bytecode file */ pos = 0; - /* First, try argv[0] (when ocamlrun is called by a bytecode program) */ - exe_name = argv[0]; - fd = caml_attempt_open(&exe_name, &trail, 0); + argv0 = proc_self_exe = caml_executable_name(); + + /* In APPENDED mode (i.e. with -custom), we always want to load the bytecode + from the running executable, and argv[0] should never be used. However, + some platforms still don't implement caml_executable_name, so there is an + escape hatch here to fallback to checking argv[0] if proc_self_exe is + NULL. + For STANDARD mode (i.e. the current executable is ocamlrun), argv[0] is + tried first, as this should be the path to shebang-script/executable + originally executed by the user. */ + CAMLassert(caml_byte_program_mode != EMBEDDED); + if (caml_byte_program_mode != APPENDED || proc_self_exe == NULL) { + exe_name = argv[0]; + fd = caml_attempt_open(&exe_name, &trail, 0); + } /* Little grasshopper wonders why we do that at all, since "The current executable is ocamlrun itself, it's never a bytecode @@ -495,14 +508,26 @@ CAMLexport void caml_main(char_os **argv) With -custom, we have an executable that is ocamlrun itself concatenated with the bytecode. So, if the attempt with argv[0] failed, it is worth trying again with executable_name. */ - if (fd < 0 && (proc_self_exe = caml_executable_name()) != NULL) { - exe_name = proc_self_exe; - fd = caml_attempt_open(&exe_name, &trail, 0); + if (caml_byte_program_mode == APPENDED || fd < 0) { + if (proc_self_exe != NULL) { + exe_name = proc_self_exe; + fd = caml_attempt_open(&exe_name, &trail, 0); + } + if (fd < 0 && caml_byte_program_mode == APPENDED) + error("unable to open file '%s'", caml_stat_strdup_of_os(exe_name)); } + if (argv0 == NULL) + argv0 = caml_search_exe_in_path(exe_name); + if (fd < 0) { pos = parse_command_line(argv); if (caml_params->print_config) { + caml_runtime_standard_library_effective = + caml_locate_standard_library(argv0, + caml_runtime_standard_library_default, + NULL); + do_print_config(); exit(0); } @@ -533,6 +558,24 @@ CAMLexport void caml_main(char_os **argv) } /* Read the table of contents (section descriptors) */ caml_read_section_descriptors(fd, &trail); + + caml_runtime_standard_library_effective = + caml_locate_standard_library(argv0, + caml_runtime_standard_library_default, NULL); + if (argv0 != proc_self_exe) + caml_stat_free(argv0); + + /* Load the embedded overridden caml_standard_library_default value, if one is + available. Note that although -custom executables come through this + mechanism, they don't define OSLD sections because + caml_runtime_standard_library_default and caml_standard_library_default are + fundamentally equal and caml_runtime_standard_library_default is set when + the -custom executable is linked. */ + char_os *image_standard_library_default = + read_section_to_os(fd, &trail, "OSLD"); + if (image_standard_library_default != NULL) + caml_standard_library_default = image_standard_library_default; + /* Initialize the abstract machine */ caml_init_gc (); @@ -631,6 +674,10 @@ CAMLexport value caml_startup_code_exn( exe_name = caml_executable_name(); if (exe_name == NULL) exe_name = caml_search_exe_in_path(argv[0]); + caml_runtime_standard_library_effective = + caml_locate_standard_library(exe_name, + caml_runtime_standard_library_default, NULL); + Caml_state->external_raise = NULL; /* Setup signal handling */ caml_init_signals(); diff --git a/runtime/sys.c b/runtime/sys.c index 3356664ecdbd..c69a54a2b741 100644 --- a/runtime/sys.c +++ b/runtime/sys.c @@ -700,6 +700,47 @@ CAMLprim value caml_sys_const_backend_type(value unit) { return Val_int(1); /* Bytecode backed */ } + +/* The native code linker doesn't synthesise calls to this primitive, instead + putting the required string statically in caml_standard_library_nat if any of + the compilation units use %standard_library_default. The primitive is omitted + completely in libasmrun as there are no other existing instances in the + native runtime where OCAML_STDLIB_DIR ends up being embedded. */ +#ifndef NATIVE_CODE +/* If this remains unset then caml_runtime_standard_library_default is used */ +char_os *caml_standard_library_default = NULL; + +CAMLprim value caml_sys_const_standard_library_default(value unit) +{ + return caml_copy_string_of_os( + caml_standard_library_default ? caml_standard_library_default + : caml_runtime_standard_library_default); +} +#endif + +CAMLprim value caml_sys_get_stdlib_dirs(value vstdlib_default) +{ + CAMLparam1(vstdlib_default); + CAMLlocal3(result, eff, root_dir); + + char_os *stdlib_default = caml_stat_strdup_to_os(String_val(vstdlib_default)); + char_os *root = NULL, *stdlib; + + stdlib = + caml_locate_standard_library(caml_params->exe_name, stdlib_default, &root); + + eff = caml_copy_string_of_os(stdlib); + if (root == NULL) { + root_dir = Val_none; + } else { + root_dir = caml_copy_string_of_os(root); + root_dir = caml_alloc_some(root_dir); + } + result = caml_alloc_2(0, eff, root_dir); + + CAMLreturn(result); +} + CAMLprim value caml_sys_get_config(value unit) { CAMLparam0 (); /* unit is unused */ diff --git a/runtime/unix.c b/runtime/unix.c index e19d5a26a86d..7394bf66ed8d 100644 --- a/runtime/unix.c +++ b/runtime/unix.c @@ -54,6 +54,9 @@ #else #include #endif +#ifdef HAS_LIBGEN_H +#include +#endif #ifdef __APPLE__ #include #endif @@ -541,3 +544,70 @@ void caml_plat_mem_unmap(void* mem, uintnat size) if (munmap(mem, size) != 0) CAMLassert(0); } + +static char * caml_dirname (const char * path) +{ +#ifdef HAS_LIBGEN_H + char *dir, *res; + dir = caml_stat_strdup(path); + res = caml_stat_strdup(dirname(dir)); + caml_stat_free(dir); + return res; +#else + /* See Filename.generic_dirname */ + ptrdiff_t n = strlen(path) - 1; + char *res; + if (n < 0) /* path is "" */ + return caml_stat_strdup("."); + while (n >= 0 && path[n] == '/') + n--; + if (n < 0) /* path is entirely slashes */ + return caml_stat_strdup("/"); + while (n >= 0 && path[n] != '/') + n--; + if (n < 0) /* path is relative */ + return caml_stat_strdup("."); + while (n >= 0 && path[n] == '/') + n--; + if (n < 0) /* path is a file at root */ + return caml_stat_strdup("/"); + /* n is the _index_ of the last character of the dirname */ + res = caml_stat_alloc(n + 2); + memcpy(res, path, n + 1); + res[n + 1] = 0; + return res; +#endif +} + +CAMLextern char_os* caml_locate_standard_library (const char *exe_name, + const char *stdlib_default, + char **dirname) +{ + if (Is_relative_dir(stdlib_default)) { + char * root = caml_dirname(exe_name); + char * candidate = + caml_stat_strconcat(3, root, CAML_DIR_SEP, stdlib_default); + /* In practice, a system which can be configured --with-relative-libdir will + also have realpath. The directory is normalised here for consistency with + the behaviour on Windows, which doesn't have a direct equivalent of + dirname and performs the equivalent of realpath as a side-effect of + determining the root path. */ +#ifdef HAS_REALPATH + char * resolved_candidate = realpath(candidate, NULL); + /* If realpath fails, use the non-normalised path for error messages. */ + if (resolved_candidate != NULL) { + caml_stat_free(candidate); + /* realpath uses malloc */ + candidate = caml_stat_strdup(resolved_candidate); + free(resolved_candidate); + } +#endif + if (dirname == NULL) + caml_stat_free(root); + else + *dirname = root; + return candidate; + } else { + return caml_stat_strdup(stdlib_default); + } +} diff --git a/runtime/win32.c b/runtime/win32.c index 3ec659d42ad9..e7c899bc446d 100644 --- a/runtime/win32.c +++ b/runtime/win32.c @@ -41,6 +41,7 @@ #include #include #include +#include #include "caml/alloc.h" #include "caml/codefrag.h" #include "caml/fail.h" @@ -1272,3 +1273,88 @@ value caml_win32_xdg_defaults(void) CAMLreturn(result); } + +CAMLextern char_os* caml_locate_standard_library (const wchar_t *exe_name, + const wchar_t *stdlib_default, + wchar_t **dirname) +{ + if (Is_relative_dir(stdlib_default)) { + LPWSTR root = NULL, basename; + DWORD l = MAX_PATH + 1, buf_len; + + do { + buf_len = l; + caml_stat_free(root); + root = caml_stat_alloc(buf_len * sizeof(WCHAR)); + l = GetFullPathName(exe_name, buf_len, root, &basename); + } while (l >= buf_len); + /* It should be an Impossible Thing for exe_name (which will have been the + result of GetModuleFileName) to be unparsable by GetFullPathName */ + if (l == 0) { + caml_stat_free(root); + return caml_stat_wcsdup(stdlib_default); + } + + CAMLassert(basename && basename != root && Is_separator(*(basename - 1))); + + /* Make root the dirname portion */ + *(basename - 1) = 0; + + LPWSTR candidate = + caml_stat_wcsconcat(3, root, CAML_DIR_SEP, stdlib_default); + HANDLE h = + CreateFile(candidate, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (h == INVALID_HANDLE_VALUE) { + caml_stat_free(candidate); + return caml_stat_wcsdup(stdlib_default); + } + + LPWSTR resolved_candidate = NULL; + l = MAX_PATH + 1; + do { + buf_len = l; + caml_stat_free(resolved_candidate); + resolved_candidate = caml_stat_alloc(buf_len * sizeof(WCHAR)); + l = GetFinalPathNameByHandle(h, resolved_candidate, buf_len, + FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + } while (l >= buf_len); + + if (l > 0) { + /* GetFinalPathNameByHandle always returns \\?\ which needs stripping. */ + CAMLassert(l > 4 && resolved_candidate[0] == '\\' + && resolved_candidate[1] == '\\' + && resolved_candidate[2] == '?' + && resolved_candidate[3] == '\\'); + + caml_stat_free(candidate); + + if (l >= 8 && resolved_candidate[4] == 'U' + && resolved_candidate[5] == 'N' + && resolved_candidate[6] == 'C' + && resolved_candidate[7] == '\\') { + /* NT native UNC path (\\?\UNC\foo). We change the last C to a backslash + (\\?\UN\\foo) and then include that altered character and the + original final slash to create a normal UNC path. */ + resolved_candidate[6] = '\\'; + candidate = caml_stat_wcsdup(resolved_candidate + 6); + } else { + /* Local device path */ + candidate = caml_stat_wcsdup(resolved_candidate + 4); + } + } + + /* It should be another Impossible Thing for l == 0 in the above. If that + did happen, candidate will _not_ have been freed, and we'll return the + path returned by GetFullPathName */ + caml_stat_free(resolved_candidate); + + if (dirname != NULL) + *dirname = caml_stat_wcsdup(root); + caml_stat_free(root); + return candidate; + } else { + return caml_stat_wcsdup(stdlib_default); + } +} diff --git a/testsuite/in_prefix/Makefile.test b/testsuite/in_prefix/Makefile.test index cf8d2039c77b..7d2a5900a027 100644 --- a/testsuite/in_prefix/Makefile.test +++ b/testsuite/in_prefix/Makefile.test @@ -23,7 +23,7 @@ DRIVER = ../tools/test_in_prefix$(EXE) endif DRIVER_ARGS = \ - $(VERBOSE_FLAG) --bindir "$(BINDIR)" --libdir "$(LIBDIR)" \ + $(VERBOSE_FLAG) --bindir "$(BINDIR)" --libdir '$(TARGET_LIBDIR)' \ $(call bool_to_with, shebangscripts, $(SHEBANGSCRIPTS)) \ $(call bool_to_with, ocamlnat, $(INSTALL_OCAMLNAT)) \ $(call bool_to_with, ocamlopt, $(NATIVE_COMPILER)) \ diff --git a/testsuite/in_prefix/README.md b/testsuite/in_prefix/README.md index ef01209ad976..35080d2da552 100644 --- a/testsuite/in_prefix/README.md +++ b/testsuite/in_prefix/README.md @@ -46,7 +46,8 @@ fifth test are re-run and then the entire battery is executed a second time. During this second execution, the test harness does whatever is physically possible to allow these tests to proceed: - Environment variables `CAML_LD_LIBRARY_PATH` and `OCAMLLIB` are manipulated to - allow the compiler to operate + allow the compiler to operate (unless the compiler has been configured with + `--with-relative-libdir`) - Bytecode executables which will no longer be able to find `ocamlrun` are explicitly passed to `ocamlrun`. The harness always verifies that this step is required by first executing the binary and ensuring that it fails and then @@ -72,7 +73,8 @@ Shims: - On Unix, the bytecode toplevel contains the absolute location of `ocamlrun`, so must be explicitly invoked via `ocamlrun` - Both toplevels contain the absolute location of the Standard Library, - requiring `OCAMLLIB` to be set + requiring `OCAMLLIB` to be set, unless the compiler was configured with + `--with-relative-libdir` ### Loading archives/plugins (.cma / .cmxs) with `Dynlink` @@ -84,7 +86,8 @@ Shims: compiler is available, then both `ocamlc` and `ocamlopt` will be native executables) - Both compilers contain the absolute location of the Standard Library, - requiring `OCAMLLIB` to be set + requiring `OCAMLLIB` to be set, unless the comnpiler was configured with + `--with-relative-libdir` - The executable created by `ocamlc` contains the absolute location of `ocamlrun`, so must be both explicitly invoked via `ocamlrun` and also have `CAML_LD_LIBRARY_PATH` or `OCAMLLIB` adjusted, as that `ocamlrun` will not be diff --git a/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml b/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml index 37544574d91a..eafe82261272 100644 --- a/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml +++ b/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml @@ -1,4 +1,5 @@ (* TEST + unset BUILD_PATH_PREFIX_MAP; ocamldebug_script = "${test_source_directory}/input_script"; debugger; shared-libraries; diff --git a/testsuite/tools/cmdline.ml b/testsuite/tools/cmdline.ml index 874d259692da..cb668079c1f2 100644 --- a/testsuite/tools/cmdline.ml +++ b/testsuite/tools/cmdline.ml @@ -179,7 +179,7 @@ let parse argv = "--pwd", Arg.Set_string pwd, "\tCurrent working directory to use"; "--bindir", Arg.String (check_exists ~absolute:true bindir), "\ \tDirectory containing programs (must share a prefix with --libdir)"; - "--libdir", Arg.String (check_exists ~absolute:true libdir), "\ + "--libdir", Arg.String (check_exists ~absolute:false libdir), "\ \tDirectory containing stdlib.cma (must share a prefix with --bindir)"; "--summary", Arg.Set summary, ""; "--verbose", Arg.Set verbose, ""; diff --git a/testsuite/tools/dump_load_path.c b/testsuite/tools/dump_load_path.c index a17253c568ac..f38a75859b9e 100644 --- a/testsuite/tools/dump_load_path.c +++ b/testsuite/tools/dump_load_path.c @@ -24,6 +24,11 @@ #include #include +extern const char_os *caml_runtime_standard_library_default; +extern const char_os *caml_runtime_standard_library_effective; +CAMLextern char_os *caml_locate_standard_library (const char_os *exe_name, + const char_os *stdlib_default, + char_os **dirname); int main_os(int argc, char_os **argv) { const char_os *dir; @@ -31,7 +36,11 @@ int main_os(int argc, char_os **argv) caml_ext_table_init(&caml_shared_libs_path, 8); caml_decompose_path(&caml_shared_libs_path, caml_secure_getenv(T("CAML_LD_LIBRARY_PATH"))); - caml_parse_ld_conf(OCAML_STDLIB_DIR, &caml_shared_libs_path); + caml_runtime_standard_library_effective = + caml_locate_standard_library(argv[0], + caml_runtime_standard_library_default, NULL); + caml_parse_ld_conf(caml_runtime_standard_library_effective, + &caml_shared_libs_path); for (int i = 0; i < caml_shared_libs_path.size; i++) { dir = caml_shared_libs_path.contents[i]; if (dir[0] == 0) diff --git a/testsuite/tools/harness.mli b/testsuite/tools/harness.mli index 67d8336026cf..77d5d46f0c47 100644 --- a/testsuite/tools/harness.mli +++ b/testsuite/tools/harness.mli @@ -53,7 +53,8 @@ module Import : sig has_ocamlopt: bool; (** {v [$(NATIVE_COMPILER)] v} - {v Makefile.config v} *) has_relative_libdir: string option; - (** Not implemented; always None. *) + (** {v $(TARGET_LIBDIR_IS_RELATIVE) v} and {v $(TARGET_LIBDIR) v} - + {v Makefile.build_config v} *) has_runtime_search: bool option; (** Not implemented; always None. *) launcher_searches_for_ocamlrun: bool; diff --git a/testsuite/tools/testBytecodeBinaries.ml b/testsuite/tools/testBytecodeBinaries.ml index 536483369ffa..2822b351ea35 100644 --- a/testsuite/tools/testBytecodeBinaries.ml +++ b/testsuite/tools/testBytecodeBinaries.ml @@ -44,13 +44,16 @@ let run config env = if classification <> Vanilla then let fails = (* After the prefix has been renamed, bytecode executables compiled - with -custom will still work. Otherwise, only executables where the - header can search for ocamlrun and which do not require any C stubs - to be loaded will still work. *) + with -custom will still work. Otherwise, the header needs to be + able to search for ocamlrun and, if applicable, ocamlrun needs to + be able to load C stubs (which will only happen if the runtime + locates the Standard Library using a relative directory, so that it + can find ld.conf) *) Environment.is_renamed env && match classification with | Tendered {dlls; _} -> - not config.launcher_searches_for_ocamlrun || dlls + not config.launcher_searches_for_ocamlrun + || dlls && config.has_relative_libdir = None | _ -> false in diff --git a/testsuite/tools/testDynlink.ml b/testsuite/tools/testDynlink.ml index d34563ff88e8..ad6611eff343 100644 --- a/testsuite/tools/testDynlink.ml +++ b/testsuite/tools/testDynlink.ml @@ -48,14 +48,25 @@ let () = let compile ?(custom = false) () = if Sys.file_exists test_program then Harness.erase_file test_program; - let args = if custom then "-custom" :: args else args in + let args = + if custom then + "-custom" :: args + else + (* Hardening to ensure that Bytecode Dynlink is using the runtime's + search path, not compiler's (i.e. unix.cma should be located using + Config.standard_library_default but dllunixbyt.so should be located + using caml_runtime_standard_library_default) *) + "-set-runtime-default" :: "standard_library_default=/does-not-exist" + :: args + in (* In the Renamed phase for a bytecode-only build, ocamlc will be ocamlc.byte and will need to be called via ocamlrun *) let runtime = mode = Bytecode && Harness.ocamlc_fails_after_rename config in (* In the Renamed phase, Config.standard_library will still point to the - Original location *) - let stdlib = true in + Original location, unless the compiler has been configured with a + relative libdir *) + let stdlib = (config.has_relative_libdir = None) in let (_, output) = Environment.run_process ~runtime ~stdlib env compiler args in Environment.display_output output @@ -78,12 +89,15 @@ let () = mode = Bytecode && expected_exit_code = None && not config.target_launcher_searches_for_ocamlrun + && config.has_relative_libdir = None in (* If the library needs C stubs to be loaded dynamically, then the runtime will need CAML_LD_LIBRARY_PATH set in the Renamed phase. *) let stubs = has_c_stubs && expected_exit_code = None + && Config.supports_shared_libraries + && config.has_relative_libdir = None in let expected_exit_code = match expected_exit_code with @@ -126,11 +140,16 @@ let () = let not_dynlink l = not (List.mem "dynlink" l) in let files, re_compile = compile_test_program () in let expected_exit_code = - (* Bytecode executables launched using the executable header require - caml_executable_name to know where the runtime is. As the Standard - Library is only stored as an absolute path, this doesn't affect the - execution of the test driver (yet). *) - None in + (* Relocatable OCaml bytecode executables launched using the executable + header require caml_executable_name, or they end up being accidentally + relative, since the exec call leaves argv[0] as being the bytecode image + itself. *) + if mode = Bytecode && config.has_relative_libdir <> None + && Harness.no_caml_executable_name + && Environment.launched_via_stub test_program then + Some 2 + else + None in let libraries = List.filter not_dynlink config.libraries in let () = List.iter (test_libraries_in_prog ?expected_exit_code env) libraries; diff --git a/testsuite/tools/testLinkModes.ml b/testsuite/tools/testLinkModes.ml index 8c45f84b99eb..e471f5d84f6b 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -125,7 +125,7 @@ let () = around some problems with shared runtimes on s390x and riscv which don't reliably fail. *) -let run_program env _config = +let run_program env config = let prefix = Environment.prefix env in let libdir_suffix = Environment.libdir_suffix env in let prefix, libdir_suffix = @@ -142,7 +142,7 @@ let run_program env _config = if Environment.is_renamed env then stdlib_exists_when_renamed else - true in + config.has_relative_libdir <> None in let args = [string_of_bool stdlib_exists; prefix; libdir_suffix] in let argv0 = if argv0 = test_program then @@ -270,7 +270,7 @@ type outcome = - Sys.argv.(0) doesn't equal Sys.argv.(3) - Config.standard_library exists when it shouldn't (or vice versa) *) let test_runs usr_bin_sh test_program_path test_program - _config env ~via_ocamlrun = + config env ~via_ocamlrun = let tests = let test_program_relative = Filename.concat Filename.current_dir_name test_program @@ -326,6 +326,12 @@ let test_runs usr_bin_sh test_program_path test_program else if Sys.win32 then (* stdlib/header.c correctly preserves argv[0] for Windows *) Success {executable_name = test_program_path; argv0} + else if Harness.no_caml_executable_name + && config.has_relative_libdir <> None then + (* Without caml_executable_name, ocamlrun will be forced to + interpret the relative standard library relative to argv[0], + which will fail. *) + Fail 134 else (* stdlib/header.c does not preserve argv[0] for Unix *) Success {executable_name = argv0_resolved; @@ -340,12 +346,8 @@ let test_runs usr_bin_sh test_program_path test_program else Success {executable_name = argv0_resolved; argv0} else - if Sys.win32 || argv0_not_ocaml then - (* SearchPath will resolve the relative/implicit arguments to - absolute paths *) - Success {executable_name = test_program_path; argv0} - else - Success {executable_name = argv0_resolved; argv0} + (* -custom executables use caml_executable_name *) + Success {executable_name = test_program_path; argv0} | Vanilla -> if Harness.no_caml_executable_name then Success {executable_name = argv0_resolved; argv0} @@ -366,11 +368,12 @@ let test_runs usr_bin_sh test_program_path test_program run in the Renamed phase for other reasons. *) let make_test_runner ~stdlib_exists_when_renamed ~may_segfault ~with_unix ~tendered ~target_launcher_searches_for_ocamlrun usr_bin_sh - test_program_path test_program config _env = - (* Bytecode executables with absolute headers will need to be - invoked via ocamlrun after the prefix has been renamed. *) + test_program_path test_program config env = + (* Bytecode executables with absolute headers will need to be invoked via + ocamlrun after the prefix has been renamed. *) let via_ocamlrun = tendered && not target_launcher_searches_for_ocamlrun + && (config.has_relative_libdir = None || not (Environment.is_renamed env)) in let rec run env = let runs = @@ -382,7 +385,7 @@ let make_test_runner ~stdlib_exists_when_renamed ~may_segfault ~with_unix | Fail code -> "", code, "" | Success {executable_name; argv0} -> executable_name, 0, argv0 in - let stubs = tendered && with_unix in + let stubs = tendered && with_unix && config.has_relative_libdir = None in run_program env config ~runtime:via_ocamlrun ~stubs test_program_path ~prefix_path_with_cwd expected_executable_name @@ -579,6 +582,21 @@ let compile_test usr_bin_sh config env test test_program description = else options in + let options = + if Environment.is_renamed env || config.has_relative_libdir <> None then + options + else + let new_libdir = + Filename.concat (Environment.prefix env ^ ".new") + (Environment.libdir_suffix env) in + let stdlib_default = "standard_library_default=" ^ new_libdir in + let options = "-set-runtime-default" :: stdlib_default :: options in + if tendered then + let libdir = Environment.libdir env in + "-dllpath" :: (Filename.concat libdir "stublibs") :: options + else + options + in let args = "-o" :: output :: "test_install_script.ml" :: options @@ -606,8 +624,9 @@ let compile_test usr_bin_sh config env test test_program description = let runtime = mode = Bytecode && Harness.ocamlc_fails_after_rename config in (* In the Renamed phase, Config.standard_library will still point to - the Original location *) - let stdlib = true in + the Original location, unless the compiler has been configured + with a relative libdir *) + let stdlib = (config.has_relative_libdir = None) in Environment.run_process ~fails ~runtime ~stdlib env compiler args in Environment.display_output output; @@ -635,9 +654,21 @@ let compile_test usr_bin_sh config env test test_program description = `None else let stdlib_exists_when_renamed = - (* Config.standard_library is an absolute path, and therefore will - always point to the Original location in the Renamed phase. *) - false + if config.has_relative_libdir = None then + (* In the Original phase, for a compiler with an absolute libdir, + -set-runtime-default is used to set standard_library_default to + the Renamed phase's location. When the tests are recompiled in + the Renamed phase, this is not done. The effect is that if any + test is being run in the Renamed phase, Config.standard_library + will be correct. *) + not (Environment.is_renamed env) + else + (* When the compiler has a relative libdir, -set-runtime-default + is implicitly being tested by the build process, and we wish to + test the opposite in the harness - thus the test programs + compiled in the Original phase will _not_ be able to find the + Standard Library in the Renamed phase. *) + Environment.is_renamed env in make_test_runner ~stdlib_exists_when_renamed ~may_segfault ~with_unix ~tendered ~target_launcher_searches_for_ocamlrun diff --git a/testsuite/tools/testRelocation.ml b/testsuite/tools/testRelocation.ml index 2e7edb58dc31..dd4279b9d2d2 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -27,12 +27,17 @@ module LocationMap = Map.Make(Location) (* Augment toolchain properties with information from the configuration (this essentially goes from "is foo capable of doing bar" to "foo does bar in this context". *) -let effective_toolchain _config = +let effective_toolchain config = let c_compiler_debug_paths_are_absolute = Toolchain.c_compiler_debug_paths_can_be_absolute + && (not Config.c_has_debug_prefix_map || config.has_relative_libdir = None) in let assembler_embeds_build_path = Toolchain.assembler_embeds_build_path + && (not Config.as_has_debug_prefix_map + || Config.architecture = "riscv" + || Config.as_is_cc + || config.has_relative_libdir = None) in c_compiler_debug_paths_are_absolute, assembler_embeds_build_path @@ -62,12 +67,14 @@ let bindir_rules config file = (* Determine if the installation prefix should be found in this file *) let prefix = let code_embeds_stdlib_location = - (* The runtime binaries all contain OCAML_STDLIB_DIR and everything - except flexlink and ocamllex link with the Config module, either - directly or via ocamlcommon *) - not (List.mem basename ["flexlink.byte"; "flexlink.opt"; "flexlink"; - "ocamllex.byte"; "ocamllex.opt"; "ocamllex"; - "ocamlyacc"]) + (* If the compiler is configured with an absolute libdir, the runtime + binaries all contain OCAML_STDLIB_DIR and everything except flexlink + and ocamllex link with the Config module, either directly or via + ocamlcommon *) + config.has_relative_libdir = None + && not (List.mem basename ["flexlink.byte"; "flexlink.opt"; "flexlink"; + "ocamllex.byte"; "ocamllex.opt"; "ocamllex"; + "ocamlyacc"]) in let linker_embeds_stdlib_location = (* If the launcher doesn't search for ocamlrun, then either the #! stub @@ -127,7 +134,7 @@ let bindir_rules config file = stripped. However, since the C objects in libcamlrun are compiled with -g, this will still result in debug information for -custom runtime executables. *) - linked_with_debug + linked_with_debug && config.has_relative_libdir = None || (classification = Custom && Toolchain.linker_propagates_debug_information && c_compiler_debug_paths_are_absolute) @@ -161,17 +168,26 @@ let libdir_rules config file = - contains objects which have been created by the assembler *) let (embeds_stdlib_location, has_ocaml_debug_info, has_c_debug_info, contains_assembled_objects) = - if basename = "Makefile.config" || basename = "runtime-launch-info" then - (* These files all embed the Standard Library location *) + if basename = "Makefile.config" then + (* Embeds the Standard Library location *) (true, false, false, false) else if basename = "config.cmx" then (* config.cmx contains Config.standard_library for inlining *) - (true, false, false, false) + let stdlib = + config.has_relative_libdir = None && not Config.flambda in + (stdlib, false, false, false) else if List.mem ext [".cma"; ".cmo"; ".cmt"; ".cmti"] then let stdlib = (* via Config.standard_library *) - List.mem basename ["config.cmt"; "config_main.cmt"; - "ocamlcommon.cma"] in + config.has_relative_libdir = None + && List.mem basename ["config.cmt"; "config_main.cmt"; + "ocamlcommon.cma"] in + (* The compiler's artefacts are all compiled with -g *) (stdlib, true, false, false) + else if basename = "runtime-launch-info" then + (* When the compiler is configured with a relative libdir, + runtime-launch-info just contains ".", rather than the prefix *) + let stdlib = (config.has_relative_libdir = None) in + (stdlib, false, false, false) else if ext = ".cmxs" then (* All the .cmxs files built by the distribution at present include C objects and obviously contain assembled objects. *) @@ -186,16 +202,6 @@ let libdir_rules config file = not (is_ocaml || String.starts_with ~prefix:"flexdll_" basename) in (false, false, c_debug, is_ocaml) else if ext = Config.ext_lib || ext = Config.ext_dll then - (* Based on the filename, is this one of the bytecode runtime libraries - (libcamlrun.a, libcamlrund.a, libcamlrun_shared.so, etc. - Note that these properties are _not_ used for libasmrun* (see - below) *) - let is_camlrun = - let dir = Filename.basename (Filename.dirname file) in - dir <> "stublibs" - && String.starts_with ~prefix:"libcamlrun" basename - && not (String.starts_with ~prefix:"libcamlruntime" basename) - in if ext = Config.ext_lib then (* Any archive produced by ocamlopt will have a .cmxa file with it *) let is_ocaml = @@ -203,14 +209,13 @@ let libdir_rules config file = (* Config.standard_library is in ocamlcommon and the bytecode runtime embeds the Standard Library location *) let stdlib = - is_camlrun - || Filename.remove_extension basename = "ocamlcommon" - in + config.has_relative_libdir = None + && Filename.remove_extension basename = "ocamlcommon" in (stdlib, false, (not is_ocaml), is_ocaml) else (* DLLs are either the shared versions of the runtime libraries or C stubs. All of these are compiled with -g *) - (is_camlrun, false, true, false) + (false, false, true, false) else (false, false, false, false) in @@ -228,7 +233,7 @@ let libdir_rules config file = || Toolchain.linker_embeds_build_path) then Toolchain.linker_embeds_build_path else - has_ocaml_debug_info + has_ocaml_debug_info && config.has_relative_libdir = None || has_c_debug_info && c_compiler_debug_paths_are_absolute || contains_assembled_objects && assembler_embeds_build_path || ext = Config.ext_obj @@ -240,8 +245,29 @@ let libdir_rules config file = else LocationMap.empty in + let prefix = + if config.has_relative_libdir <> None + && basename = "Makefile.config" then + LocationMap.add Relative false prefix + else + prefix + in if contains_build_path then LocationMap.add Build false prefix + (* Prior to #13828 (OCaml 5.4.0), .cmt and .cmti embed the absolute location + of the compiler without using BUILD_PATH_PREFIX_MAP. However, this + embedding is not entirely predictable, because it only happens when + ocamlc.opt or ocamlopt.opt is used for compilation, rather than when the + bytecode version of the tool is passed is explicitly to ocamlrun (in this + case, Sys.argv.(0) always retains the relative path used in the build + system). For this reason, when configured relatively, on Windows, with + native compilation available, accept that the Build directory may appear + in .cmt/.cmti files. *) + else if config.has_relative_libdir <> None + && Sys.win32 + && config.has_ocamlopt + && (ext = ".cmt" || ext = ".cmti") then + LocationMap.add Build true prefix else prefix diff --git a/testsuite/tools/testToplevel.ml b/testsuite/tools/testToplevel.ml index 329aaea872ea..699ead0dd8b5 100644 --- a/testsuite/tools/testToplevel.ml +++ b/testsuite/tools/testToplevel.ml @@ -97,7 +97,7 @@ let run config env mode = Environment.run_process ~fails:(expected_exit_code <> 0) ~runtime:(mode = Bytecode && not config.launcher_searches_for_ocamlrun) - ~stdlib:true env toplevel args + ~stdlib:(config.has_relative_libdir = None) env toplevel args in Environment.display_output output; if exit_code <> expected_exit_code then diff --git a/testsuite/tools/test_in_prefix.ml b/testsuite/tools/test_in_prefix.ml index 743ea1996505..55ab5dcb4c5f 100644 --- a/testsuite/tools/test_in_prefix.ml +++ b/testsuite/tools/test_in_prefix.ml @@ -180,12 +180,12 @@ let () = For the compiler's files to be reproducible, the compiler needs to be both relocatable and also required support from the assembler and C compiler. *) - let relocatable = false in + let relocatable = + config.has_relative_libdir <> None + && config.launcher_searches_for_ocamlrun + in let reproducible = relocatable - (* At present, the compiler build doesn't actually take advantage of this - configuration, but this does not matter because the compiler cannot yet - be relocatable! *) && (not config.has_ocamlopt || not Toolchain.assembler_embeds_build_path || Config.as_has_debug_prefix_map && Config.architecture <> "riscv") @@ -193,7 +193,7 @@ let () = && (not Toolchain.c_compiler_always_embeds_build_path || not Toolchain.c_compiler_debug_paths_can_be_absolute) in - let target_relocatable = false in + let target_relocatable = config.target_launcher_searches_for_ocamlrun in (* Use Harness.pp_path unless --verbose was specified *) let pp_path = if verbose then diff --git a/testsuite/tools/test_ld_conf.ml b/testsuite/tools/test_ld_conf.ml index 3fc0a8951bf7..9a74ae529bba 100644 --- a/testsuite/tools/test_ld_conf.ml +++ b/testsuite/tools/test_ld_conf.ml @@ -39,12 +39,13 @@ type ld_conf_test = { and var_setting = Unset | Empty | Set of string list (* Set of tests to run in a given environment *) -let tests _config env = +let tests config env = (* Convenience function - [if_ld_conf_found outcome] returns the empty list in the Renamed phase. *) let if_ld_conf_found outcome = - (* ocamlrun can't find ld.conf after the prefix has been renamed *) - if Environment.is_renamed env then + (* ocamlrun can only find ld.conf after the prefix has been renamed if it's + configured with --with-relative-libdir *) + if Environment.is_renamed env && config.has_relative_libdir = None then [] else outcome @@ -63,6 +64,13 @@ let tests _config env = Environment.libdir env else Config.standard_library in + let libdir = + if config.has_relative_libdir = None then + libdir + else + (* Unix.realpath raises Invalid_argument if it's not available *) + try Unix.realpath libdir + with Invalid_argument _ -> libdir in let (/) = Filename.concat in let data = [ (* Root directory (both forms) preserved *) @@ -326,8 +334,9 @@ let () = let runtime = mode = Bytecode && Harness.ocamlc_fails_after_rename config in (* In the Renamed phase, Config.standard_library will still point to the - Original location *) - let stdlib = true in + Original location, unless the compiler has been configured with a + relative libdir *) + let stdlib = (config.has_relative_libdir = None) in let (_, output) = Environment.run_process ~runtime ~stdlib env compiler args in Environment.display_output output; @@ -342,10 +351,13 @@ let () = in (* In the Renamed phase, the test driver will need to be launched with ocamlrun, unless executables produced by the compiler are capable of - searching for the runtime (as the Windows executable launcher does) *) + searching for the runtime (as the Windows executable launcher does) or + the compiler has been configured with a relative libdir (as in this mode + the bytecode header will have the correct location) *) let runtime = mode = Bytecode - && not config.target_launcher_searches_for_ocamlrun in + && not config.target_launcher_searches_for_ocamlrun + && config.has_relative_libdir = None in let run run_process test = let code, lines = run_process ?argv0:None ~runtime test_program [] diff --git a/tools/ci/actions/runner.sh b/tools/ci/actions/runner.sh index 512e57d23829..0f44e0671177 100755 --- a/tools/ci/actions/runner.sh +++ b/tools/ci/actions/runner.sh @@ -128,10 +128,92 @@ Install () { $MAKE install } +target_libdir_is_relative='^ *TARGET_LIBDIR_IS_RELATIVE *= *false' + Test-In-Prefix () { + { set +x + echo 'Checking that compilers invoked with alternate runtimes use their' + echo "configured location, not the alternate runtime's" + expected1="$(realpath "$PREFIX/lib/ocaml")" + } 2>/dev/null + if [[ ! -d "$PREFIX.new" ]]; then + # In Re-Test-In-Prefix, $PREFIX is the original compiler built by the + # workflow and then $PREFIX.new is the "alternate configuration". The first + # time round, we clone whichever compiler has just been built for this test. + cp -a "$PREFIX" "$PREFIX.new" + remove="$PREFIX.new" + if grep -q "$target_libdir_is_relative" Makefile.build_config; then + # Compiler configured absolutely - both should return the same answer + expected2="$expected1" + else + # Compiler configured relatively + expected2="$(realpath "$PREFIX").new/lib/ocaml" + fi + else + # The alternate configuration path should be returned, regardless of whether + # the runtime invoking it is an absolute or a relative one from another + # location. + expected2="$(realpath "$PREFIX").new/lib/ocaml-lib" + remove='' + fi + { set +x + lib1="$($PREFIX.new/bin/ocamlrun $PREFIX/bin/ocamlc.byte -where)" + lib2="$($PREFIX/bin/ocamlrun $PREFIX.new/bin/ocamlc.byte -where)" + echo "$PREFIX/bin/ocamlc.byte OSLD: $($PREFIX/bin/ocamlrun \ + $PREFIX/bin/ocamlobjinfo.byte $PREFIX/bin/ocamlc.byte \ + | sed -ne 's/^caml_standard_library_default: //p')" + echo -n "$PREFIX.new/bin/ocamlrun standard_library_default: " + $PREFIX.new/bin/ocamlrun -config | sed -ne 's/standard_library_default: //p' + echo "$PREFIX.new/bin/ocamlrun $PREFIX/bin/ocamlc.byte -where: $lib1" + if [[ $lib1 != $expected1 ]]; then + echo -e ' \e[31mEXPECTED\e[0m:' "$expected1" + fi + echo + echo "$PREFIX.new/bin/ocamlc.byte OSLD: $($PREFIX.new/bin/ocamlrun \ + $PREFIX.new/bin/ocamlobjinfo.byte $PREFIX.new/bin/ocamlc.byte \ + | sed -ne 's/^caml_standard_library_default: //p')" + echo -n "$PREFIX/bin/ocamlrun standard_library_default: " + $PREFIX/bin/ocamlrun -config | sed -ne 's/standard_library_default: //p' + echo "$PREFIX/bin/ocamlrun $PREFIX.new/bin/ocamlc.byte -where: $lib2" + if [[ $lib2 != $expected2 ]]; then + echo -e ' \e[31mEXPECTED\e[0m:' "$expected2" + fi + [[ $lib1 = $expected1 && $lib2 = $expected2 ]] && echo 'Correct.' || exit 1 + } 2>/dev/null + [[ -z $remove ]] || rm -rf "$remove" $MAKE -C testsuite/in_prefix -f Makefile.test test-in-prefix } +Re-Test-In-Prefix () { + mkdir -p bak + mv Makefile.config Makefile.build_config config.status bak + git clean -dfX &>/dev/null + mv bak/Makefile.config bak/Makefile.build_config bak/config.status . + rmdir bak + # The libdir is configured to be $PREFIX.new/lib/ocaml-lib in order to + # "poison" the cross-runtime test (otherwise if $PREFIX/bin/ocamlc.byte is + # missing OSLD, then $PREFIX.new/bin/ocamlrun would still supply the correct + # ../lib/ocaml. This way, it supplies ../lib/ocaml-lib and the test correctly + # fails) + if grep -q "$target_libdir_is_relative" Makefile.build_config; then + # Compiler configured absolutely - reconfigure relatively + echo '::group::Re-building the compiler with a relative libdir' + $MAKE COMPUTE_DEPS=false reconfigure \ + 'ADDITIONAL_CONFIGURE_ARGS=--with-relative-libdir=../lib/ocaml-lib \ +--prefix='"$PREFIX"'.new' + else + # Compiler configured relatively - reconfigure absolutely + echo '::group::Re-building the compiler with an absolute libdir' + $MAKE COMPUTE_DEPS=false reconfigure \ + 'ADDITIONAL_CONFIGURE_ARGS=--without-relative-libdir \ +--prefix='"$PREFIX"'.new --libdir='"$PREFIX"'.new/lib/ocaml-lib' + fi + $MAKE + $MAKE install + echo '::endgroup::' + Test-In-Prefix +} + Checks () { if fgrep 'SUPPORTS_SHARED_LIBRARIES=true' Makefile.config &>/dev/null ; then echo Check the code examples in the manual @@ -223,6 +305,7 @@ test_prefix) TestPrefix $2;; api-docs) API_Docs;; install) Install;; test-in-prefix) Test-In-Prefix;; +re-test-in-prefix) Re-Test-In-Prefix;; manual) BuildManual;; other-checks) Checks;; basic-compiler) BasicCompiler;; diff --git a/tools/ci/appveyor/appveyor_build.sh b/tools/ci/appveyor/appveyor_build.sh index 5d29916eaf9d..2d7a6bef3fbc 100755 --- a/tools/ci/appveyor/appveyor_build.sh +++ b/tools/ci/appveyor/appveyor_build.sh @@ -87,6 +87,9 @@ function set_configuration { args+=('--host=x86_64-pc-windows' '--enable-dependency-generation' \ '--enable-native-toplevel');; esac + if [[ $RELOCATABLE = 'true' ]]; then + args+=('--with-relative-libdir') + fi # Remove old configure cache if the configure script or the OS # have changed @@ -110,7 +113,6 @@ function set_configuration { PARALLEL_URL='https://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel' APPVEYOR_BUILD_FOLDER=$(echo "$APPVEYOR_BUILD_FOLDER" | cygpath -f -) FLEXDLLROOT="$PROGRAMFILES/flexdll" -OCAMLROOT=$(echo "$OCAMLROOT" | cygpath -f - -m) if [[ $BOOTSTRAP_FLEXDLL = 'false' ]] ; then case "$PORT" in diff --git a/tools/objinfo.ml b/tools/objinfo.ml index 83e36593141b..7186d2747126 100644 --- a/tools/objinfo.ml +++ b/tools/objinfo.ml @@ -35,6 +35,8 @@ let uid_deps = ref false module Magic_number = Misc.Magic_number +let yesno_of_bool oc b = output_string oc (if b then "YES" else "no") + let dummy_crc = String.make 32 '-' let null_crc = String.make 32 '0' @@ -67,13 +69,13 @@ let print_cmo_infos cu = printf "YES\n"; printf "Primitives declared in this module:\n"; List.iter print_line l); - printf "Force link: %s\n" (if cu.cu_force_link then "YES" else "no") + printf "Force link: %a\n" yesno_of_bool cu.cu_force_link let print_spaced_string s = printf " %s" s let print_cma_infos (lib : Cmo_format.library) = - printf "Force custom: %s\n" (if lib.lib_custom then "YES" else "no"); + printf "Force custom: %a\n" yesno_of_bool lib.lib_custom; printf "Extra C object files:"; (* PR#4949: print in linking order *) List.iter print_spaced_string (List.rev lib.lib_ccobjs); @@ -249,11 +251,13 @@ let print_cmx_infos (ui, crc) = printf "Currying functions:%a\n" pr_funs ui.ui_curry_fun; printf "Apply functions:%a\n" pr_funs ui.ui_apply_fun; printf "Send functions:%a\n" pr_funs ui.ui_send_fun; - printf "Force link: %s\n" (if ui.ui_force_link then "YES" else "no"); + printf "Force link: %a\n" yesno_of_bool ui.ui_force_link; printf "For pack: %s\n" (match ui.ui_for_pack with | None -> "no" - | Some pack -> "YES: " ^ pack) + | Some pack -> "YES: " ^ pack); + printf + "Requires caml_standard_library_nat: %a\n" yesno_of_bool ui.ui_need_stdlib let print_cmxa_infos (lib : Cmx_format.library_infos) = printf "Extra C object files:"; @@ -317,6 +321,11 @@ let dump_byte ic = | SYMB -> let symb = Bytesections.read_section_struct toc ic section in print_global_table symb + | OSLD -> + let caml_standard_library_default = + Bytesections.read_section_string toc ic section in + printf "caml_standard_library_default: %s\n" + caml_standard_library_default | _ -> () with _ -> () ) diff --git a/tools/ocamlmklib.ml b/tools/ocamlmklib.ml index f6b2a2d16ec9..1082a208d3c7 100644 --- a/tools/ocamlmklib.ml +++ b/tools/ocamlmklib.ml @@ -25,10 +25,8 @@ let mklib out files opts = Printf.sprintf "link -lib -nologo %s-out:%s %s %s" machine out opts files else Printf.sprintf "%s rcs %s %s %s" Config.ar out opts files -(* PR#4783: under Windows, don't use absolute paths because we do - not know where the binary distribution will be installed. *) let compiler_path name = - if Sys.os_type = "Win32" then name else Filename.concat Config.bindir name + Filename.concat Config.bindir name let bytecode_objs = ref [] (* .cmo,.cma,.ml,.mli files to pass to ocamlc *) and native_objs = ref [] (* .cmx,.ml,.mli files to pass to ocamlopt *) diff --git a/utils/ccomp.ml b/utils/ccomp.ml index defe4d2a4b92..d6818ef093a5 100644 --- a/utils/ccomp.ml +++ b/utils/ccomp.ml @@ -90,7 +90,13 @@ let compile_file ?output ?(opt="") ?stable_name name = ("", "") in let debug_prefix_map = match stable_name with - | Some stable when Config.c_has_debug_prefix_map -> + | Some stable + when Config.c_has_debug_prefix_map + && not (String.starts_with ~prefix:"mingw" Config.system) -> + (* -fdebug-prefix-map exists on mingw-w64 but at present it is not used + for BUILD_PATH_PREFIX_MAP because there isn't yet a good story for how + to deal with Cygwin, where the paths are Cygwin-style paths and MSYS2, + where they are native Windows paths. *) Printf.sprintf " -fdebug-prefix-map=%s=%s" name stable | Some _ | None -> "" in let exit = diff --git a/utils/clflags.ml b/utils/clflags.ml index be10f2352219..1b69e5104f21 100644 --- a/utils/clflags.ml +++ b/utils/clflags.ml @@ -48,6 +48,7 @@ let compile_only = ref false (* -c *) and output_name = ref (None : string option) (* -o *) and include_dirs = ref ([] : string list) (* -I *) and hidden_include_dirs = ref ([] : string list) (* -H *) +and standard_library_default = ref None (* -set-runtime-default *) and no_std_include = ref false (* -nostdlib *) and no_cwd = ref false (* -nocwd *) and print_types = ref false (* -i *) diff --git a/utils/clflags.mli b/utils/clflags.mli index 248a7d86e620..a8ac1d415a1d 100644 --- a/utils/clflags.mli +++ b/utils/clflags.mli @@ -76,6 +76,7 @@ val compile_only : bool ref val output_name : string option ref val include_dirs : string list ref val hidden_include_dirs : string list ref +val standard_library_default : string option ref val no_std_include : bool ref val no_cwd : bool ref val print_types : bool ref diff --git a/utils/config.common.ml.in b/utils/config.common.ml.in index 3603fe6c60c7..80efe1be0444 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -20,6 +20,22 @@ (* The main OCaml version string has moved to ../build-aux/ocaml_version.m4 *) let version = Sys.ocaml_version +external standard_library_default : unit -> string = "%standard_library_default" + +let standard_library_default_raw = standard_library_default () + +external stdlib_dirs : string -> string * string option + = "caml_sys_get_stdlib_dirs" + +let standard_library_default, relative_root_dir = + stdlib_dirs standard_library_default_raw + +let standard_library_relative = + if relative_root_dir = None then + None + else + Some standard_library_default_raw + let standard_library = try Sys.getenv "OCAMLLIB" @@ -29,6 +45,8 @@ let standard_library = with Not_found -> standard_library_default +let bindir = Option.value ~default:bindir relative_root_dir + let exec_magic_number = {magic|@EXEC_MAGIC_NUMBER@|magic} (* exec_magic_number is duplicated in runtime/caml/exec.h *) and cmi_magic_number = {magic|@CMI_MAGIC_NUMBER@|magic} @@ -57,6 +75,7 @@ let lazy_tag = 246 let max_young_wosize = 256 let stack_threshold = 32 (* see runtime/caml/config.h *) let stack_safety_margin = 6 +let target_win32 = Sys.win32 let default_executable_name = match Sys.os_type with "Unix" -> "a.out" @@ -71,9 +90,13 @@ let configuration_variables () = let p x v = (x, String v) in let p_int x v = (x, Int v) in let p_bool x v = (x, Bool v) in + let standard_library_relative = + Option.value ~default:"" standard_library_relative + in [ p "version" version; p "standard_library_default" standard_library_default; + p "standard_library_relative" standard_library_relative; p "standard_library" standard_library; p "ccomp_type" ccomp_type; p "c_compiler" c_compiler; diff --git a/utils/config.fixed.ml b/utils/config.fixed.ml index 807b92935531..f7c0ab70ebe4 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -21,12 +21,12 @@ let boot_cannot_call s = "/ The boot compiler should not call " ^ s let bindir = "/tmp" -let standard_library_default = "/tmp" let ccomp_type = "n/a" let c_compiler = boot_cannot_call "the C compiler" let c_output_obj = "" let c_has_debug_prefix_map = false let as_has_debug_prefix_map = false +let as_is_cc = false let bytecode_cflags = "" let bytecode_cppflags = "" let native_cflags = "" diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index aa0345540968..51e047efcba7 100644 --- a/utils/config.generated.ml.in +++ b/utils/config.generated.ml.in @@ -20,13 +20,12 @@ let bindir = {@QS@|@ocaml_bindir@|@QS@} -let standard_library_default = {@QS@|@ocaml_libdir@|@QS@} - let ccomp_type = {@QS@|@ccomptype@|@QS@} let c_compiler = {@QS@|@CC@|@QS@} let c_output_obj = {@QS@|@outputobj@|@QS@} let c_has_debug_prefix_map = @cc_has_debug_prefix_map@ let as_has_debug_prefix_map = @as_has_debug_prefix_map@ +let as_is_cc = @as_is_cc@ let bytecode_cflags = {@QS@|@bytecode_cflags@|@QS@} let bytecode_cppflags = {@QS@|@bytecode_cppflags@|@QS@} let native_cflags = {@QS@|@native_cflags@|@QS@} diff --git a/utils/config.mli b/utils/config.mli index 9ac111b09ae8..c94489b29033 100644 --- a/utils/config.mli +++ b/utils/config.mli @@ -24,15 +24,27 @@ val version: string (** The current version number of the system *) val bindir: string -(** The directory containing the binary programs *) +(** The directory containing the binary programs. If the compiler was configured + with [--with-relative-libdir] then this will be the directory containing the + currently executing runtime. *) + +val standard_library_relative: string option +(** The explicit relative path from the compiler binaries to the standard + libraries directory if the compiler was configured with + [--with-relative-libdir], or [None] otherwise. + + @since 5.5 *) val standard_library_default: string -(** The configured value for the directory containing the standard libraries +(** The effective value for the default directory containing the standard + libraries. This is always an absolute path, computed using + {!standard_library_relative} if necessary. @since 5.5 *) val standard_library: string -(** The effective directory containing the standard libraries *) +(** The effective directory containing the standard libraries, taking CAMLLIB + and OCAMLLIB into account. *) val ccomp_type: string (** The "kind" of the C compiler, assembler and linker used: one of @@ -52,6 +64,12 @@ val c_has_debug_prefix_map : bool val as_has_debug_prefix_map : bool (** Whether the assembler supports --debug-prefix-map *) +val as_is_cc : bool +(** Whether the assembler is actually an assembler, or whether we are really + assembling files via the C compiler + + @since 5.5 *) + val bytecode_cflags : string (** The flags ocamlc should pass to the C compiler *) @@ -168,6 +186,11 @@ val model: string val system: string (** Name of operating system for the native-code compiler *) +val target_win32: bool +(** True if [target_os_type = "Win32"] + + @since 5.5 *) + val asm: string (** The assembler (and flags) to use for assembling ocamlopt-generated code. *) From 15602d486886feecc1dde2c980176a65c9415074 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Fri, 12 Jun 2026 21:14:04 +0100 Subject: [PATCH 10/25] Adapt the build for lack of bootstrap boot/ocamlc doesn't yet support -set-runtime-default and %standard_library_default. --- Makefile.common | 4 ---- testsuite/tools/testLinkModes.ml | 10 +++------- utils/config.common.ml.in | 4 +--- utils/config.fixed.ml | 1 + utils/config.generated.ml.in | 2 ++ 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Makefile.common b/Makefile.common index 8d9f1017e815..cf6875a53f66 100644 --- a/Makefile.common +++ b/Makefile.common @@ -191,10 +191,6 @@ ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" $(if $(SRCDIR_REAL_ENCODED),:.=$(SRCDIR_REAL_ENCODED)) endif # ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" -OC_COMMON_LINKFLAGS += \ - -set-runtime-default \ - $(call QUOTE_SINGLE,standard_library_default=$(TARGET_LIBDIR)) - # The rule to compile C files # This rule is similar to GNU make's implicit rule, except that it is more diff --git a/testsuite/tools/testLinkModes.ml b/testsuite/tools/testLinkModes.ml index e471f5d84f6b..5f303b3252d5 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -125,7 +125,7 @@ let () = around some problems with shared runtimes on s390x and riscv which don't reliably fail. *) -let run_program env config = +let run_program env _config = let prefix = Environment.prefix env in let libdir_suffix = Environment.libdir_suffix env in let prefix, libdir_suffix = @@ -137,12 +137,8 @@ let run_program env config = in fun ~runtime ~stubs test_program expected_executable_name ~prefix_path_with_cwd expected_exit_code argv0 expected_argv0 - ~may_segfault ~stdlib_exists_when_renamed -> - let stdlib_exists = - if Environment.is_renamed env then - stdlib_exists_when_renamed - else - config.has_relative_libdir <> None in + ~may_segfault ~stdlib_exists_when_renamed:_ -> + let stdlib_exists = not (Environment.is_renamed env) in let args = [string_of_bool stdlib_exists; prefix; libdir_suffix] in let argv0 = if argv0 = test_program then diff --git a/utils/config.common.ml.in b/utils/config.common.ml.in index 80efe1be0444..4f9fa2be90cc 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -20,9 +20,7 @@ (* The main OCaml version string has moved to ../build-aux/ocaml_version.m4 *) let version = Sys.ocaml_version -external standard_library_default : unit -> string = "%standard_library_default" - -let standard_library_default_raw = standard_library_default () +let standard_library_default_raw = standard_library_default external stdlib_dirs : string -> string * string option = "caml_sys_get_stdlib_dirs" diff --git a/utils/config.fixed.ml b/utils/config.fixed.ml index f7c0ab70ebe4..43913bcd2f3d 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -21,6 +21,7 @@ let boot_cannot_call s = "/ The boot compiler should not call " ^ s let bindir = "/tmp" +let standard_library_default = "/tmp" let ccomp_type = "n/a" let c_compiler = boot_cannot_call "the C compiler" let c_output_obj = "" diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index 51e047efcba7..a757acb77171 100644 --- a/utils/config.generated.ml.in +++ b/utils/config.generated.ml.in @@ -20,6 +20,8 @@ let bindir = {@QS@|@ocaml_bindir@|@QS@} +let standard_library_default = {@QS@|@ocaml_libdir@|@QS@} + let ccomp_type = {@QS@|@ccomptype@|@QS@} let c_compiler = {@QS@|@CC@|@QS@} let c_output_obj = {@QS@|@outputobj@|@QS@} From 3e2b0553f5805163dda5ec1083386c6b0c00d485 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Fri, 12 Jun 2026 21:13:52 +0100 Subject: [PATCH 11/25] Adapt to OCaml 5.3 Misc.String.to_utf_8_seq was added in PR#14014 for the testsuite, but isn't part of the backport. It's only needed in Bytelink, so share it from there instead. --- .depend | 2 ++ bytecomp/bytelink.ml | 13 +++++++++++-- bytecomp/bytelink.mli | 2 ++ testsuite/tools/testRelocation.ml | 12 +----------- utils/config.mli | 8 ++++---- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/.depend b/.depend index 2ddbecc3d453..822e0ad3054e 100644 --- a/.depend +++ b/.depend @@ -10582,6 +10582,7 @@ testsuite/tools/testRelocation.cmo : \ testsuite/tools/harness.cmi \ testsuite/tools/environment.cmi \ utils/config.cmi \ + bytecomp/bytelink.cmi \ testsuite/tools/testRelocation.cmi testsuite/tools/testRelocation.cmx : \ otherlibs/unix/unix.cmx \ @@ -10589,6 +10590,7 @@ testsuite/tools/testRelocation.cmx : \ testsuite/tools/harness.cmx \ testsuite/tools/environment.cmx \ utils/config.cmx \ + bytecomp/bytelink.cmx \ testsuite/tools/testRelocation.cmi testsuite/tools/testRelocation.cmi : \ testsuite/tools/harness.cmi \ diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index ebaff66bac28..d020c5035f23 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -19,7 +19,6 @@ open Misc open Config open Cmo_format -module String = Misc.Stdlib.String module Compunit = Symtable.Compunit module Dep = struct @@ -607,6 +606,16 @@ let output_cds_file outfile = Bytesections.write_toc_and_trailer toc_writer; ) +let rec to_utf_8_seq b i () = + if i >= Bytes.length b then + Seq.Nil + else + let next = Bytes.get_utf_8_uchar b i in + let u = Uchar.utf_decode_uchar next in + Seq.Cons(u, to_utf_8_seq b (i + Uchar.utf_decode_length next)) + +let to_utf_8_seq s = to_utf_8_seq (Bytes.unsafe_of_string s) 0 + (* [c_string_literal_of_string s] returns the C literal string representation of [s], suitable for embedding in a C source file with type [char_os *]. The result includes the quote markers. *) @@ -638,7 +647,7 @@ let c_string_literal_of_string s = if Config.target_win32 then Buffer.add_char b 'L'; Buffer.add_char b '"'; - Seq.iter escape (String.to_utf_8_seq s); + Seq.iter escape (to_utf_8_seq s); Buffer.add_char b '"'; Buffer.contents b diff --git a/bytecomp/bytelink.mli b/bytecomp/bytelink.mli index 61dffee8779c..9a8d7a4da47a 100644 --- a/bytecomp/bytelink.mli +++ b/bytecomp/bytelink.mli @@ -30,6 +30,8 @@ val linkdeps_unit : val extract_crc_interfaces: unit -> crcs +val to_utf_8_seq : string -> Uchar.t Seq.t + type error = | File_not_found of filepath | Not_an_object_file of filepath diff --git a/testsuite/tools/testRelocation.ml b/testsuite/tools/testRelocation.ml index dd4279b9d2d2..79d393b0dfcc 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -312,18 +312,8 @@ let rec contains content content_len tests i seen = seen, i in contains content content_len tests (i + 1) seen -let rec to_utf_8_seq b i () = - if i >= Bytes.length b then - Seq.Nil - else - let next = Bytes.get_utf_8_uchar b i in - let u = Uchar.utf_decode_uchar next in - Seq.Cons(u, to_utf_8_seq b (i + Uchar.utf_decode_length next)) - -let to_utf_8_seq s = to_utf_8_seq (Bytes.unsafe_of_string s) 0 - let utf_16le_of_utf_8 s = - let s = to_utf_8_seq s in + let s = Bytelink.to_utf_8_seq s in let utf_16le_length = Seq.fold_left (fun acc u -> acc + Uchar.utf_16_byte_length u) 0 s in let b = Bytes.create utf_16le_length in diff --git a/utils/config.mli b/utils/config.mli index c94489b29033..4823c85f5f1b 100644 --- a/utils/config.mli +++ b/utils/config.mli @@ -33,14 +33,14 @@ val standard_library_relative: string option libraries directory if the compiler was configured with [--with-relative-libdir], or [None] otherwise. - @since 5.5 *) + @since 5.3.2 *) val standard_library_default: string (** The effective value for the default directory containing the standard libraries. This is always an absolute path, computed using {!standard_library_relative} if necessary. - @since 5.5 *) + @since 5.3.2 *) val standard_library: string (** The effective directory containing the standard libraries, taking CAMLLIB @@ -68,7 +68,7 @@ val as_is_cc : bool (** Whether the assembler is actually an assembler, or whether we are really assembling files via the C compiler - @since 5.5 *) + @since 5.3.2 *) val bytecode_cflags : string (** The flags ocamlc should pass to the C compiler *) @@ -189,7 +189,7 @@ val system: string val target_win32: bool (** True if [target_os_type = "Win32"] - @since 5.5 *) + @since 5.3.2 *) val asm: string (** The assembler (and flags) to use for assembling From 1510bd78d4cf5fbc4e6c39046359906fb1bdbd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Ojeda=20B=C3=A4r?= Date: Fri, 12 Dec 2025 05:40:00 +0100 Subject: [PATCH 12/25] Merge pull request PR#14245 from dra27/runtime-searching Relocatable OCaml - Searching and Suffixing (cherry picked from commit da60a2e7920fd441decc950de55c4d10a63f5878) (cherry picked from commit c40b6876b2d92cc3b07c73fed1669a3d485e7bb5) --- .depend | 33 +- .github/workflows/build-msvc.yml | 1 + .github/workflows/build.yml | 14 +- .gitignore | 5 +- Changes | 32 ++ Makefile | 125 ++++++-- Makefile.build_config.in | 8 + Makefile.common | 22 +- Makefile.config.in | 4 + asmcomp/asmlink.ml | 18 +- build-aux/ocaml_version.m4 | 5 +- bytecomp/bytelink.ml | 321 +++++++++++++------- bytecomp/byterntm.mli | 33 ++ bytecomp/byterntm.mll | 123 ++++++++ bytecomp/dll.ml | 17 +- bytecomp/dll.mli | 7 +- configure | 238 ++++++++++++--- configure.ac | 177 ++++++++--- driver/compenv.ml | 8 +- driver/compenv.mli | 2 +- driver/main_args.ml | 62 +++- driver/main_args.mli | 3 + file_formats/cmo_format.mli | 2 +- man/Makefile | 4 +- man/ocamlc.1 | 46 +++ manual/src/cmds/unified-options.etex | 42 +++ ocaml-variants.opam | 4 + ocamltest/ocaml_actions.ml | 4 +- ocamltest/ocamltest_config.ml.in | 2 + ocamltest/ocamltest_config.mli | 4 + otherlibs/Makefile.otherlibs.common | 27 +- otherlibs/dynlink/byte/dynlink_symtable.ml | 30 +- otherlibs/dynlink/byte/dynlink_symtable.mli | 2 +- otherlibs/dynlink/dynlink_config.ml.in | 4 + otherlibs/dynlink/dynlink_config.mli | 4 + otherlibs/systhreads/Makefile | 8 +- release-info/howto.md | 4 + runtime/Mangling.md | 135 ++++++++ runtime/caml/s.h.in | 2 + runtime/caml/version.h.in | 1 + runtime/dynlink.c | 17 +- runtime/startup_byt.c | 1 + stdlib/Makefile | 14 +- stdlib/header.c | 258 +++++++++++++--- testsuite/in_prefix/Makefile.test | 25 +- testsuite/in_prefix/README.md | 19 +- testsuite/tools/cmdline.ml | 15 +- testsuite/tools/environment.ml | 80 ++--- testsuite/tools/environment.mli | 2 +- testsuite/tools/harness.ml | 13 +- testsuite/tools/harness.mli | 29 +- testsuite/tools/poisonedruntime.c | 27 ++ testsuite/tools/testBytecodeBinaries.ml | 283 ++++++++++------- testsuite/tools/testLinkModes.ml | 103 +++++-- testsuite/tools/testRelocation.ml | 34 +-- testsuite/tools/test_in_prefix.ml | 64 +++- tools/ci/actions/runner.sh | 2 + tools/ci/appveyor/appveyor_build.sh | 3 +- tools/objinfo.ml | 60 +++- tools/ocamlmklib.ml | 19 +- tools/ocamlsize | 38 ++- utils/clflags.ml | 14 +- utils/clflags.mli | 5 +- utils/config.common.ml.in | 28 ++ utils/config.fixed.ml | 6 + utils/config.generated.ml.in | 10 + utils/config.mli | 66 ++++ utils/misc.ml | 127 ++++++++ utils/misc.mli | 100 ++++++ 69 files changed, 2436 insertions(+), 609 deletions(-) create mode 100644 bytecomp/byterntm.mli create mode 100644 bytecomp/byterntm.mll create mode 100644 runtime/Mangling.md create mode 100644 testsuite/tools/poisonedruntime.c diff --git a/.depend b/.depend index 822e0ad3054e..09961ad09ddb 100644 --- a/.depend +++ b/.depend @@ -44,7 +44,8 @@ utils/clflags.cmx : \ utils/clflags.cmi utils/clflags.cmi : \ utils/profile.cmi \ - utils/misc.cmi + utils/misc.cmi \ + utils/config.cmi utils/compression.cmo : \ utils/compression.cmi utils/compression.cmx : \ @@ -2420,6 +2421,17 @@ bytecomp/bytepackager.cmi : \ utils/format_doc.cmi \ typing/env.cmi \ file_formats/cmo_format.cmi +bytecomp/byterntm.cmo : \ + utils/misc.cmi \ + bytecomp/bytesections.cmi \ + bytecomp/byterntm.cmi +bytecomp/byterntm.cmx : \ + utils/misc.cmx \ + bytecomp/bytesections.cmx \ + bytecomp/byterntm.cmi +bytecomp/byterntm.cmi : \ + utils/misc.cmi \ + bytecomp/bytesections.cmi bytecomp/bytesections.cmo : \ utils/config.cmi \ bytecomp/bytesections.cmi @@ -7983,6 +7995,7 @@ tools/objinfo.cmo : \ typing/ident.cmi \ utils/format_doc.cmi \ middle_end/flambda/export_info.cmi \ + utils/config.cmi \ middle_end/compilation_unit.cmi \ file_formats/cmxs_format.cmi \ file_formats/cmx_format.cmi \ @@ -7990,6 +8003,7 @@ tools/objinfo.cmo : \ file_formats/cmo_format.cmi \ file_formats/cmi_format.cmi \ bytecomp/bytesections.cmi \ + bytecomp/byterntm.cmi \ utils/binutils.cmi \ tools/objinfo.cmi tools/objinfo.cmx : \ @@ -8006,6 +8020,7 @@ tools/objinfo.cmx : \ typing/ident.cmx \ utils/format_doc.cmx \ middle_end/flambda/export_info.cmx \ + utils/config.cmx \ middle_end/compilation_unit.cmx \ file_formats/cmxs_format.cmi \ file_formats/cmx_format.cmi \ @@ -8013,6 +8028,7 @@ tools/objinfo.cmx : \ file_formats/cmo_format.cmi \ file_formats/cmi_format.cmx \ bytecomp/bytesections.cmx \ + bytecomp/byterntm.cmx \ utils/binutils.cmx \ tools/objinfo.cmi tools/objinfo.cmi : @@ -10377,9 +10393,11 @@ testsuite/lib/testing.cmx : \ testsuite/lib/testing.cmi : testsuite/tools/cmdline.cmo : \ testsuite/tools/harness.cmi \ + utils/config.cmi \ testsuite/tools/cmdline.cmi testsuite/tools/cmdline.cmx : \ testsuite/tools/harness.cmx \ + utils/config.cmx \ testsuite/tools/cmdline.cmi testsuite/tools/cmdline.cmi : \ testsuite/tools/harness.cmi @@ -10415,6 +10433,7 @@ testsuite/tools/environment.cmo : \ file_formats/cmt_format.cmi \ file_formats/cmo_format.cmi \ bytecomp/bytesections.cmi \ + bytecomp/byterntm.cmi \ testsuite/tools/environment.cmi testsuite/tools/environment.cmx : \ otherlibs/unix/unix.cmx \ @@ -10425,6 +10444,7 @@ testsuite/tools/environment.cmx : \ file_formats/cmt_format.cmx \ file_formats/cmo_format.cmi \ bytecomp/bytesections.cmx \ + bytecomp/byterntm.cmx \ testsuite/tools/environment.cmi testsuite/tools/environment.cmi : \ testsuite/tools/harness.cmi @@ -10468,13 +10488,20 @@ testsuite/tools/expect.cmi : \ parsing/location.cmi testsuite/tools/harness.cmo : \ otherlibs/unix/unix.cmi \ + utils/misc.cmi \ utils/config.cmi \ + bytecomp/byterntm.cmi \ testsuite/tools/harness.cmi testsuite/tools/harness.cmx : \ otherlibs/unix/unix.cmx \ + utils/misc.cmx \ utils/config.cmx \ + bytecomp/byterntm.cmx \ testsuite/tools/harness.cmi -testsuite/tools/harness.cmi : +testsuite/tools/harness.cmi : \ + utils/misc.cmi \ + utils/config.cmi \ + bytecomp/byterntm.cmi testsuite/tools/lexcmm.cmo : \ testsuite/tools/parsecmm.cmi \ utils/misc.cmi \ @@ -10533,12 +10560,14 @@ testsuite/tools/parsecmmaux.cmi : \ middle_end/backend_var.cmi testsuite/tools/testBytecodeBinaries.cmo : \ otherlibs/unix/unix.cmi \ + utils/misc.cmi \ testsuite/tools/harness.cmi \ testsuite/tools/environment.cmi \ utils/config.cmi \ testsuite/tools/testBytecodeBinaries.cmi testsuite/tools/testBytecodeBinaries.cmx : \ otherlibs/unix/unix.cmx \ + utils/misc.cmx \ testsuite/tools/harness.cmx \ testsuite/tools/environment.cmx \ utils/config.cmx \ diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index 0bcbb251d63a..685c0a333b93 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -147,6 +147,7 @@ jobs: --enable-ocamltest ${{ endsWith(matrix.arch, '64') && '--enable-native-toplevel' || '--disable-native-toplevel' }} ${{ matrix.libdir == 'relative' && '--with-relative-libdir' || '--without-relative-libdir' }} + ${{ matrix.libdir == 'relative' && '--enable-runtime-search --enable-runtime-search-target=fallback' || '--disable-runtime-search --disable-runtime-search-target' }} ${{ matrix.config_arg }} run: | eval $(tools/msvs-promote-path) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17da20c7d797..d1790b0421bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: '${{ github.event.repository.full_name }}' - name: Configure tree run: | - MAKE_ARG=-j CONFIG_ARG='--enable-flambda --enable-cmm-invariants --enable-dependency-generation --enable-native-toplevel --with-relative-libdir' OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh configure + MAKE_ARG=-j CONFIG_ARG='--enable-flambda --enable-cmm-invariants --enable-dependency-generation --enable-native-toplevel --with-relative-libdir --enable-runtime-search --enable-runtime-search-target=fallback' OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh configure - name: Build run: | MAKE_ARG=-j bash -xe tools/ci/actions/runner.sh build @@ -183,11 +183,12 @@ jobs: {name: 'linux-O0', os: 'ubuntu-latest', config_arg: "CFLAGS='-O0'"}, {name: 'linux-arm64', os: 'ubuntu-24.04-arm', + config_arg: '--with-target-sh=exe --enable-runtime-search-target=fallback', 'test-in-prefix': true}, {name: 'macos-x86_64', os: 'macos-15-intel', 'test-in-prefix': true}, {name: 'macos-arm64', os: 'macos-latest', - config_arg: '--with-relative-libdir', + config_arg: '--with-relative-libdir --enable-runtime-search', 'test-in-prefix': true}]; // # If this is a pull request, see if the PR has the // # 'CI: Full matrix' label. This is done using an API request, @@ -201,13 +202,16 @@ jobs: await github.rest.issues.listLabelsOnIssue({...context.repo, issue_number: context.payload.pull_request.number}); if (labels.some(label => label.name === 'CI: Full matrix')) { console.log('Full matrix requested'); - // # Add "static" and "minimal" jobs + // # Add "static", "minimal" and "unsuffixed" jobs jobs = jobs.concat([ {name: 'static', os: 'ubuntu-latest', config_arg: '--disable-native-toplevel --disable-shared', 'test-in-prefix': true}, {name: 'minimal', os: 'ubuntu-latest', - config_arg: '--disable-native-toplevel --disable-native-compiler --disable-shared --disable-debug-runtime --disable-instrumented-runtime --disable-systhreads --disable-str-lib --disable-unix-lib --disable-ocamldoc'}]); + config_arg: '--disable-native-toplevel --disable-native-compiler --disable-shared --disable-debug-runtime --disable-instrumented-runtime --disable-systhreads --disable-str-lib --disable-unix-lib --disable-ocamldoc'}, + {name: 'unsuffixed', os: 'ubuntu-latest', + config_arg: '--disable-suffixing', + 'test-in-prefix': true}]); } } return jobs; @@ -270,7 +274,7 @@ jobs: run: | MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh test-in-prefix - name: Test in prefix (alternate configuration) - if: ${{ matrix.test-in-prefix && needs.config.outputs.full-matrix == 'true' }} + if: ${{ matrix.test-in-prefix && needs.config.outputs.full-matrix == 'true' && !contains(matrix.config_arg, '--disable-suffixing') }} run: | MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh re-test-in-prefix diff --git a/.gitignore b/.gitignore index 4159c2a270b7..45450d808703 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ META /bytecomp/opcodes.ml /bytecomp/opcodes.mli +/bytecomp/byterntm.ml /debugger/debugger_lexer.ml /debugger/debugger_parser.ml @@ -251,13 +252,10 @@ META /runtime/build_config.h /runtime/sak -/stdlib/runtime.info /stdlib/runtime-launch-info /stdlib/labelled-* /stdlib/caml /stdlib/sys.ml -/stdlib/target_runtime.info -/stdlib/target_runtime-launch-info /testsuite/**/*.result /testsuite/**/*.opt_result @@ -272,6 +270,7 @@ META /testsuite/tools/codegen /testsuite/tools/dump_load_path +/testsuite/tools/poisonedruntime /testsuite/tools/expect /testsuite/tools/lexcmm.ml /testsuite/tools/parsecmm.ml diff --git a/Changes b/Changes index f65ecafafd2d..9fd758c6eebd 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,23 @@ OCaml 5.3 maintenance version (David Allsopp, review by Jonah Beckford, Antonin Décimo, Damien Doligez, Samuel Hym and Vincent Laviron) +- #14245: Introduce Runtime IDs for use in filename mangling to allow different + configurations and different versions of the runtime system to coexist + harmoniously on a single system. The IDs are used, along with the host + triplet, to provide mangled names for the ocamlrun executable and its variants + and the DLL versions of both the bytecode and native runtimes, with symlinks + created for the original names. They are also used to mangle the names of stub + libraries so that stub libraries compiled for a given configuration of the + runtime will only be sought by that runtime. The behaviour is disabled by + configuring with --disable-suffixing. + (David Allsopp, review by Damien Doligez and Samuel Hym) + +### Tools: + +- #14245: ocamlobjinfo now displays the runtime invoked by a bytecode + executable (either from the RNTM section or by analysing the shebang lines) + (David Allsopp, review by Damien Doligez and Samuel Hym) + ### Compiler user-interface and warnings: - #14244: Add -set-runtime-default option to the compiler, allowing the default @@ -30,6 +47,15 @@ OCaml 5.3 maintenance version (Antonin Décimo, review by David Allsopp, Jonah Beckford, Damien Doligez and Samuel Hym) +- #14245: New option -launch-method for ocamlc allows the method used by a + tendered bytecode executable to locate the interpreter to be given explicitly. + In particular, it makes it easier to specify the use of the executable + launcher on Unix. New option -runtime-search extends the bytecode executable + header to be able to search for the runtime interpreter in the directory + containing the executable and in PATH rather than relying on a single + hard-coded path. + (David Allsopp, review by Damien Doligez and Samuel Hym) + ### Internal/compiler-libs changes: - #14243: ocamlc now uses the same code as the runtime to parse ld.conf (via a @@ -61,6 +87,12 @@ OCaml 5.3 maintenance version (David Allsopp, review by Jonah Beckford, Antonin Décimo, Damien Doligez and Samuel Hym) +- #14245: New --enable-runtime-search configure option controls the + -runtime-search option used to build the bytecode binaries in the compiler + distribution. --enable-runtime-search-target controls the default value of + -runtime-search used for bytecode executables produced by the compiler. + (David Allsopp, review by Damien Doligez and Samuel Hym) + ### Bug fixes: - #13790: Fix bytecode-only build of Cygwin when flexlink is being bootstrapped diff --git a/Makefile b/Makefile index c1c2ee62eaf2..abfe1fb166d5 100644 --- a/Makefile +++ b/Makefile @@ -206,6 +206,7 @@ ocamlcommon_SOURCES = \ $(lambda_SOURCES) $(comp_SOURCES) ocamlbytecomp_SOURCES = \ + bytecomp/byterntm.mll \ bytecomp/instruct.mli bytecomp/instruct.ml \ bytecomp/bytegen.mli bytecomp/bytegen.ml \ bytecomp/printinstr.mli bytecomp/printinstr.ml \ @@ -643,7 +644,10 @@ flexlink.byte$(EXE): $(FLEXDLL_SOURCES) rm -f $(FLEXDLL_SOURCE_DIR)/flexlink.exe $(MAKE) -C $(FLEXDLL_SOURCE_DIR) $(FLEXLINK_BUILD_ENV) \ OCAMLRUN='$$(ROOTDIR)/boot/ocamlrun$(EXE)' NATDYNLINK=false \ - OCAMLOPT='$(value BOOT_OCAMLC) $(USE_RUNTIME_PRIMS) $(USE_STDLIB)' \ + OCAMLOPT=$(call QUOTE_SINGLE,$(value BOOT_OCAMLC) \ + $(USE_RUNTIME_PRIMS) \ + $(BYTECODE_LAUNCHER_FLAGS) \ + $(USE_STDLIB)) \ flexlink.exe support cp $(FLEXDLL_SOURCE_DIR)/flexlink.exe $@ cp $(addprefix $(FLEXDLL_SOURCE_DIR)/, $(FLEXDLL_OBJECTS)) $(ROOTDIR) @@ -1101,12 +1105,12 @@ otherlibs/dynlink.depend: beforedepend otherlibs/dynlink/native/dynlink.ml \ >> $@ -# Cleanup the lexer +# Cleanup the lexers partialclean:: - rm -f parsing/lexer.ml + rm -f bytecomp/byterntm.ml parsing/lexer.ml -beforedepend:: parsing/lexer.ml +beforedepend:: bytecomp/byterntm.ml parsing/lexer.ml # The predefined exceptions and primitives @@ -1273,7 +1277,7 @@ runtime_BUILT_HEADERS = $(addprefix runtime/, \ ## Targets to build and install -runtime_PROGRAMS = runtime/ocamlrun$(EXE) +runtime_PROGRAMS = ocamlrun runtime_BYTECODE_STATIC_LIBRARIES = runtime/libcamlrun.$(A) runtime_BYTECODE_SHARED_LIBRARIES = runtime_NATIVE_STATIC_LIBRARIES = \ @@ -1281,13 +1285,13 @@ runtime_NATIVE_STATIC_LIBRARIES = \ runtime_NATIVE_SHARED_LIBRARIES = ifeq "$(RUNTIMED)" "true" -runtime_PROGRAMS += runtime/ocamlrund$(EXE) +runtime_PROGRAMS += ocamlrund runtime_BYTECODE_STATIC_LIBRARIES += runtime/libcamlrund.$(A) runtime_NATIVE_STATIC_LIBRARIES += runtime/libasmrund.$(A) endif ifeq "$(INSTRUMENTED_RUNTIME)" "true" -runtime_PROGRAMS += runtime/ocamlruni$(EXE) +runtime_PROGRAMS += ocamlruni runtime_BYTECODE_STATIC_LIBRARIES += runtime/libcamlruni.$(A) runtime_NATIVE_STATIC_LIBRARIES += runtime/libasmruni.$(A) endif @@ -1295,9 +1299,9 @@ endif ifeq "$(UNIX_OR_WIN32)" "unix" ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" runtime_BYTECODE_STATIC_LIBRARIES += runtime/libcamlrun_pic.$(A) -runtime_BYTECODE_SHARED_LIBRARIES += runtime/libcamlrun_shared.$(SO) +runtime_BYTECODE_SHARED_LIBRARIES += camlrun runtime_NATIVE_STATIC_LIBRARIES += runtime/libasmrun_pic.$(A) -runtime_NATIVE_SHARED_LIBRARIES += runtime/libasmrun_shared.$(SO) +runtime_NATIVE_SHARED_LIBRARIES += asmrun endif endif @@ -1348,13 +1352,15 @@ ocamlruni_CPPFLAGS = $(runtime_CPPFLAGS) -DCAML_INSTR .PHONY: runtime-all runtime-all: \ - $(runtime_BYTECODE_STATIC_LIBRARIES) $(runtime_BYTECODE_SHARED_LIBRARIES) \ - $(runtime_PROGRAMS) $(SAK) + $(runtime_BYTECODE_STATIC_LIBRARIES) \ + $(runtime_BYTECODE_SHARED_LIBRARIES:%=runtime/lib%_shared$(EXT_DLL)) \ + $(runtime_PROGRAMS:%=runtime/%$(EXE)) $(SAK) .PHONY: runtime-allopt ifeq "$(NATIVE_COMPILER)" "true" runtime-allopt: \ - $(runtime_NATIVE_STATIC_LIBRARIES) $(runtime_NATIVE_SHARED_LIBRARIES) + $(runtime_NATIVE_STATIC_LIBRARIES) \ + $(runtime_NATIVE_SHARED_LIBRARIES:%=runtime/lib%_shared$(EXT_DLL)) else runtime-allopt: $(error The build has been configured with --disable-native-compiler) @@ -1397,15 +1403,16 @@ $(SAK): runtime/sak.$(O) runtime/sak.$(O): runtime/sak.c runtime/caml/misc.h runtime/caml/config.h $(V_CC)$(SAK_CC) $(SAK_CFLAGS) $(OUTPUTOBJ)$@ -c $< -C_LITERAL = $(shell $(SAK) encode-C-literal '$(1)') +C_LITERAL = $(shell $(SAK) encode-C-literal $(call QUOTE_SINGLE,$(1))) runtime/build_config.h: $(ROOTDIR)/Makefile.config \ $(ROOTDIR)/Makefile.build_config $(SAK) $(V_GEN){ \ echo '/* This file is generated from $(ROOTDIR)/Makefile.config */'; \ printf '#define OCAML_STDLIB_DIR %s\n' \ - '$(call C_LITERAL,$(TARGET_LIBDIR))'; \ + $(call QUOTE_SINGLE,$(call C_LITERAL,$(TARGET_LIBDIR))); \ echo '#define HOST "$(HOST)"'; \ + echo '#define BYTECODE_RUNTIME_ID "$(BYTECODE_RUNTIME_ID)"'; \ } > $@ runtime/prims.$(O): runtime/build_config.h @@ -1872,6 +1879,13 @@ OCAMLDOC_LIBCMTS=$(OCAMLDOC_LIBMLIS:.mli=.cmt) $(OCAMLDOC_LIBMLIS:.mli=.cmti) ocamldoc/%: CAMLC = $(BEST_OCAMLC) $(STDLIBFLAGS) ocamldoc/%: CAMLOPT = $(BEST_OCAMLOPT) $(STDLIBFLAGS) +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "false" +# ocamldoc needs a custom runtime when building statically owing to the C stubs +# in unix.cma and str.cma. This is specified explicitly to suppress the default +# linking flags (see $(MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS) in Makefile.common) +ocamldoc/ocamldoc$(EXE): ocamldoc_BYTECODE_LINKFLAGS += -custom +endif + .PHONY: ocamldoc ocamldoc: ocamldoc/ocamldoc$(EXE) ocamldoc/odoc_test.cmo @@ -2040,6 +2054,9 @@ testsuite/tools/dump_load_path$(EXE): \ testsuite/tools/dump_load_path.$(O) runtime/prims.$(O) runtime/libcamlrun.$(A) $(V_MKEXE)$(MKEXE) -o $@ $^ $(BYTECCLIBS) +testsuite/tools/poisonedruntime$(EXE): testsuite/tools/poisonedruntime.$(O) + $(V_MKEXE)$(call MKEXE_VIA_CC,$@,$^) + ocamltest_BYTECODE_LINKFLAGS = -custom -g ocamltest/ocamltest$(EXE): ocamlc ocamlyacc ocamllex @@ -2087,6 +2104,7 @@ partialclean:: rm -f $(addprefix testsuite/tools/*.,cm* o obj a lib) rm -f testsuite/tools/codegen testsuite/tools/codegen.exe rm -f testsuite/tools/dump_load_path testsuite/tools/dump_load_path.exe + rm -f testsuite/tools/poisonedruntime testsuite/tools/poisonedruntime.exe rm -f testsuite/tools/expect testsuite/tools/expect.exe rm -f testsuite/tools/test_in_prefix testsuite/tools/test_in_prefix.exe rm -f testsuite/tools/test_in_prefix.opt \ @@ -2240,6 +2258,13 @@ debugger/ocamldebug.cmo: $(ocamldebug_DEBUGGER_OBJECTS) debugger/ocamldebug_entry.cmo: debugger/ocamldebug.cmo +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "false" +# ocamldebug needs a custom runtime when building statically owing to the +# C stubs in unix.cma. This is specified explicitly to suppress the default +# linking flags (see $(MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS) in Makefile.common) +debugger/ocamldebug$(EXE): ocamldebug_BYTECODE_LINKFLAGS += -custom +endif + clean:: rm -f debugger/ocamldebug debugger/ocamldebug.exe rm -f debugger/debugger_lexer.ml @@ -2508,6 +2533,13 @@ $(ocamltex): VPATH += $(addprefix otherlibs/,str unix) tools/ocamltex.cmo: OC_COMMON_COMPFLAGS += -no-alias-deps +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "false" +# ocamltex needs a custom runtime when building statically owing to the C stubs +# in unix.cma and str.cma. This is specified explicitly to suppress the default +# linking flags (see $(MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS) in Makefile.common) +tools/ocamltex$(EXE): ocamltex_BYTECODE_LINKFLAGS += -custom +endif + # we need str and unix which depend on the bytecode version of other tools # thus we use the othertools target ## Test compilation of backend-specific parts @@ -2710,8 +2742,9 @@ endif INSTALL_LIBDIR_DYNLINK = $(INSTALL_LIBDIR)/dynlink # Installation + .PHONY: install -install: +install:: $(MKDIR) "$(INSTALL_BINDIR)" $(MKDIR) "$(INSTALL_LIBDIR)" ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" @@ -2721,13 +2754,55 @@ endif $(MKDIR) "$(INSTALL_DOCDIR)" $(MKDIR) "$(INSTALL_INCDIR)" $(MKDIR) "$(INSTALL_LIBDIR_PROFILING)" - $(INSTALL_PROG) $(runtime_PROGRAMS) "$(INSTALL_BINDIR)" + +ifeq "$(SUFFIXING)" "true" +MANGLE_RUNTIME_NAME = $(TARGET)-$(1)-$(BYTECODE_RUNTIME_ID)$(EXE) +MANGLE_RUNTIME_DLL_NAME = lib$(1)-$(TARGET)-$($(2)_RUNTIME_ID)$(EXT_DLL) +else +MANGLE_RUNTIME_NAME = $(1)$(EXE) +MANGLE_RUNTIME_DLL_NAME = lib$(1)_shared$(EXT_DLL) +endif + +define INSTALL_RUNTIME +install:: + $(INSTALL_PROG) \ + runtime/$(1)$(EXE) \ + "$(INSTALL_BINDIR)/$(call MANGLE_RUNTIME_NAME,$(1))" +ifeq "$(SUFFIXING)" "true" + cd "$(INSTALL_BINDIR)" && \ + $(LN) "$(TARGET)-$(1)-$(BYTECODE_RUNTIME_ID)$(EXE)" "$(1)$(EXE)" + cd "$(INSTALL_BINDIR)" && \ + $(LN) "$(TARGET)-$(1)-$(BYTECODE_RUNTIME_ID)$(EXE)" \ + "$(1)-$(ZINC_RUNTIME_ID)$(EXE)" +endif +endef +define INSTALL_RUNTIME_LIB +ifeq "$(2)" "BYTECODE" +install:: +else +installopt:: +endif + $(INSTALL_PROG) \ + runtime/lib$(1)_shared$(EXT_DLL) \ + "$(INSTALL_LIBDIR)/$(call MANGLE_RUNTIME_DLL_NAME,$(1),$(2))" +ifeq "$(SUFFIXING)" "true" + cd "$(INSTALL_LIBDIR)" && \ + $(LN) "$(call MANGLE_RUNTIME_DLL_NAME,$(1),$(2))" \ + "lib$(1)_shared$(EXT_DLL)" +endif +endef + +$(foreach runtime, $(runtime_PROGRAMS), \ + $(eval $(call INSTALL_RUNTIME,$(runtime)))) + +install:: $(INSTALL_DATA) runtime/ld.conf $(runtime_BYTECODE_STATIC_LIBRARIES) \ "$(INSTALL_LIBDIR)" -ifneq "$(runtime_BYTECODE_SHARED_LIBRARIES)" "" - $(INSTALL_PROG) $(runtime_BYTECODE_SHARED_LIBRARIES) \ - "$(INSTALL_LIBDIR)" -endif + +$(foreach shared_runtime, $(runtime_BYTECODE_SHARED_LIBRARIES), \ + $(eval $(call INSTALL_RUNTIME_LIB,$(shared_runtime),BYTECODE))) + +install:: $(INSTALL_DATA) runtime/caml/domain_state.tbl runtime/caml/*.h \ "$(INSTALL_INCDIR)" $(INSTALL_PROG) ocaml$(EXE) "$(INSTALL_BINDIR)" @@ -2879,11 +2954,13 @@ endif # Installation of the native-code compiler .PHONY: installopt -installopt: +installopt:: $(INSTALL_DATA) $(runtime_NATIVE_STATIC_LIBRARIES) "$(INSTALL_LIBDIR)" -ifneq "$(runtime_NATIVE_SHARED_LIBRARIES)" "" - $(INSTALL_PROG) $(runtime_NATIVE_SHARED_LIBRARIES) "$(INSTALL_LIBDIR)" -endif + +$(foreach shared_runtime, $(runtime_NATIVE_SHARED_LIBRARIES), \ + $(eval $(call INSTALL_RUNTIME_LIB,$(shared_runtime),NATIVE))) + +installopt:: ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" $(call INSTALL_STRIPPED_BYTE_PROG,\ ocamlopt$(EXE),"$(INSTALL_BINDIR)/ocamlopt.byte$(EXE)") diff --git a/Makefile.build_config.in b/Makefile.build_config.in index d45ad53e06bd..c6ed402699ad 100644 --- a/Makefile.build_config.in +++ b/Makefile.build_config.in @@ -112,6 +112,9 @@ OC_DLL_LDFLAGS=@oc_dll_ldflags@ MKEXE_VIA_CC=$(CC) @mkexe_via_cc_ldflags@ @mkexe_via_cc_extra_cmd@ +LAUNCH_METHOD = @launch_method@ +SUFFIXING = @suffixing@ + # Which tool to use to display differences between files DIFF=@DIFF@ # Which flags to pass to the diff tool @@ -185,6 +188,9 @@ OC_NATIVE_LINKFLAGS = -g BUILD_TRIPLET = @build@ +# Zinc Runtime ID is needed for installation only +ZINC_RUNTIME_ID = @zinc_runtime_id@ + # Platform-dependent command to create symbolic links LN = @ln@ @@ -202,3 +208,5 @@ TSAN=@tsan@ # Contains TSan-specific runtime files, or nothing if TSan support is # disabled TSAN_NATIVE_RUNTIME_C_SOURCES = @tsan_native_runtime_c_sources@ + +RUNTIME_SEARCH = @runtime_search@ diff --git a/Makefile.common b/Makefile.common index cf6875a53f66..548f9f6322a9 100644 --- a/Makefile.common +++ b/Makefile.common @@ -336,8 +336,20 @@ $(eval $(call _OCAML_COMMON_BASE,$(1))) $(basename $(notdir $(1)))_COMMON_LINKFLAGS = endef # _OCAML_PROGRAM_BASE +# $(ROOTDIR)/ocamlc needs -launch-method to be given explicitly as its default +# values are those for the target (cf. --with-target-sh and TARGET_BINDIR). +BYTECODE_LAUNCHER_FLAGS = \ + -launch-method $(call QUOTE_SINGLE,$(LAUNCH_METHOD) $(BINDIR)) \ + -runtime-search $(if $(RUNTIME_SEARCH),$(RUNTIME_SEARCH),disable) + +MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS = \ + $(if $(filter -custom, $(1)),,\ + -use-prims $(ROOTDIR)/runtime/primitives $(BYTECODE_LAUNCHER_FLAGS)) + LINK_BYTECODE_PROGRAM =\ - $(CAMLC) $(OC_COMMON_LINKFLAGS) $(OC_BYTECODE_LINKFLAGS) + $(CAMLC) $(OC_COMMON_LINKFLAGS) \ + $(call MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS, \ + $(OC_COMMON_LINKFLAGS) $(OC_BYTECODE_LINKFLAGS)) $(OC_BYTECODE_LINKFLAGS) # The _OCAML_BYTECODE_PROGRAM macro defines a bytecode program but assuming # that _OCAML_PROGRAM_BASE has already been called. Its public counterpart @@ -361,8 +373,12 @@ $(basename $(notdir $(1)))_BYTECODE_LINKFLAGS = $(basename $(notdir $(1)))_BYTECODE_LINKCMD = \ $(strip \ - $$(CAMLC) $$(OC_COMMON_LINKFLAGS) $$(OC_BYTECODE_LINKFLAGS) \ - $$($(basename $(notdir $(1)))_COMMON_LINKFLAGS) \ + $$(CAMLC) $$(OC_COMMON_LINKFLAGS) \ + $$(call MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS, \ + $$(OC_COMMON_LINKFLAGS) $$(OC_BYTECODE_LINKFLAGS) \ + $$($(basename $(notdir $(1)))_COMMON_LINKFLAGS) \ + $$($(basename $(notdir $(1)))_BYTECODE_LINKFLAGS)) \ + $$(OC_BYTECODE_LINKFLAGS) $$($(basename $(notdir $(1)))_COMMON_LINKFLAGS) \ $$($(basename $(notdir $(1)))_BYTECODE_LINKFLAGS)) $(1)$(EXE): $$$$($(basename $(notdir $(1)))_BCOBJS) diff --git a/Makefile.config.in b/Makefile.config.in index e432851cdea5..10b6917ada44 100644 --- a/Makefile.config.in +++ b/Makefile.config.in @@ -219,6 +219,10 @@ FUNCTION_SECTIONS=@function_sections@ AWK=@AWK@ NAKED_POINTERS=false +# Runtime ID values +BYTECODE_RUNTIME_ID=@bytecode_runtime_id@ +NATIVE_RUNTIME_ID=@native_runtime_id@ + # Deprecated variables ## Variables deprecated since OCaml 5.3 diff --git a/asmcomp/asmlink.ml b/asmcomp/asmlink.ml index 5614d58ab077..b2d162ac3d02 100644 --- a/asmcomp/asmlink.ml +++ b/asmcomp/asmlink.ml @@ -105,12 +105,18 @@ let add_ccobjs origin l = end let runtime_lib () = - let libname = "libasmrun" ^ !Clflags.runtime_variant ^ ext_lib in - try - if !Clflags.nopervasives || not !Clflags.with_runtime then [] - else [ Load_path.find libname ] - with Not_found -> - raise(Error(File_not_found libname)) + if !Clflags.runtime_variant = "_shared" then + if Config.suffixing then + [Misc.RuntimeID.shared_runtime Sys.Native] + else + ["-lasmrun_shared"] + else + let libname = "libasmrun" ^ !Clflags.runtime_variant ^ ext_lib in + try + if !Clflags.nopervasives || not !Clflags.with_runtime then [] + else [ Load_path.find libname ] + with Not_found -> + raise(Error(File_not_found libname)) (* First pass: determine which units are needed *) diff --git a/build-aux/ocaml_version.m4 b/build-aux/ocaml_version.m4 index 4079bf3bca87..546e37cefa58 100644 --- a/build-aux/ocaml_version.m4 +++ b/build-aux/ocaml_version.m4 @@ -29,11 +29,14 @@ m4_define([OCAML__DEVELOPMENT_VERSION], [true]) # The three following components (major, minor and patch level) MUST be # integers. They MUST NOT be left-padded with zeros and all of them, -# including the patchlevel, are mandatory. +# including the patchlevel, are mandatory. OCAML__RELEASE_NUMBER must be +# incremented with each minor release, and likewise must be an unpadded integer. m4_define([OCAML__VERSION_MAJOR], [5]) m4_define([OCAML__VERSION_MINOR], [3]) +m4_define([OCAML__RELEASE_NUMBER], [19]) m4_define([OCAML__VERSION_PATCHLEVEL], [1]) + # Note that the OCAML__VERSION_EXTRA string defined below is always empty # for officially-released versions of OCaml. m4_define([OCAML__VERSION_EXTRA], [dev0-2025-01-06]) diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index d020c5035f23..d9592425b5d6 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -295,13 +295,6 @@ type launch_method = | Shebang_runtime | Executable -type runtime_launch_info = { - buffer : string; - bindir : string; - launcher : launch_method; - executable_offset : int -} - (* See https://www.in-ulm.de/~mascheck/various/shebang/#origin for a deep dive into shebangs. - Whitespace (space or horizontal tab) delimits the interpreter from an @@ -314,65 +307,23 @@ let invalid_for_shebang_line path = let invalid_char = function ' ' | '\t' | '\n' -> true | _ -> false in String.length path > 125 || String.exists invalid_char path -(* The runtime-launch-info file consists of two "lines" followed by binary data. - The file is _always_ LF-formatted, even on Windows. The sequence of bytes up - to the first '\n' is interpreted: - - "sh" - use a shebang-style launcher. If sh is needed, determine its - location from [command -p -v sh] - - "exe" - use the executable launcher contained in this runtime-launch-info - file. - - "/" ^ path - use a shebang-style launcher. If sh is needed, path is the - absolute location of sh. path must be valid for a shebang - line. - The second "line" is interpreted as the next "\000\n"-terminated sequence and - is the directory containing the default runtimes (ocamlrun, ocamlrund, etc.). - The null terminator is used since '\n' is valid in a nefarious installation - prefix but Posix forbids filenames including the nul character. - The remainder of the file is then the executable launcher for bytecode - programs (see stdlib/header{,nt}.c). *) - -let read_runtime_launch_info file = - let buffer = - try - In_channel.with_open_bin file In_channel.input_all - with Sys_error msg -> raise (Error (Camlheader (msg, file))) - in - try - let bindir_start = String.index buffer '\n' + 1 in - let bindir_end = String.index_from buffer bindir_start '\000' in - let bindir = String.sub buffer bindir_start (bindir_end - bindir_start) in - let bindir = - if bindir = Filename.current_dir_name then - Filename.dirname Sys.executable_name - else - bindir in - let executable_offset = bindir_end + 2 in - let launcher = - let kind = String.sub buffer 0 (bindir_start - 1) in - if kind = "exe" then - Executable - else if kind <> "" && (kind.[0] = '/' || kind = "sh") then - Shebang_bin_sh kind - else - raise Not_found in - if String.length buffer < executable_offset - || buffer.[executable_offset - 1] <> '\n' then - raise Not_found - else - {bindir; launcher; buffer; executable_offset} - with Not_found -> - raise (Error (Camlheader ("corrupt header", file))) - let find_bin_sh () = let output_file = Filename.temp_file "caml_bin_sh" "" in let result = try - let cmd = - Filename.quote_command ~stdout:output_file "command" ["-p"; "-v"; "sh"] + let run command args = + let cmd = + Filename.quote_command ~stdout:output_file command args + in + if !Clflags.verbose then + Printf.eprintf "+ %s\n" cmd; + (Sys.command cmd = 0) in - if !Clflags.verbose then - Printf.eprintf "+ %s\n" cmd; - if Sys.command cmd = 0 then + (* While [command -v] and [command -p] are long-standing Posix commands, + the ability to combine them as [command -p -v] is actually Posix Issue 7 + and so of course Solaris does not support it *) + if run "command" ["-p"; "-v"; "sh"] || + run "sh" ["-c"; "PATH=\"`getconf PATH`\" command -v sh"] then In_channel.with_open_text output_file input_line else "" @@ -382,75 +333,189 @@ let find_bin_sh () = remove_file output_file; result +(* Writes the shell script version of the bytecode launcher to outchan *) +let write_sh_launcher outchan bin_sh bindir search runtime = + let open struct type tag = DFE | F | FE end in + let l tag fmt = + let output s = + match tag, search with + | DFE, _ + | F, Config.Fallback + | FE, (Config.Fallback | Config.Enable) -> + output_string outchan (String.trim s); + output_char outchan '\n' + | _ -> + () + in + Printf.ksprintf output fmt + in + let runtime = Filename.quote runtime in + let bin = Filename.quote (Filename.concat bindir "") in + let exec = + if search = Config.Disable then + runtime + else + {|"$c"|} + in + let release = + Printf.sprintf "%d.%d" Sys.ocaml_release.major Sys.ocaml_release.minor + in + (* Each of the three search modes requires a slightly different shell script. + However, these shell scripts do have one very useful property: the script + for Fallback adds lines to the script for Enable which adds lines to the + script for Disable, but none of them change lines (apart from a trivial + tweak to the exec line for the Disable script). + The lines below are laid out to reflect this, with the tag letters + D(isable), F(allback) and E(nable) for the lines in each script. If a line + is emitted, it is first passed to String.trim, which allows indentation and + a column-based layout to be used. + + The Disable script just needs to exec the runtime. The two searching modes + do a few more calculations and will ultimately exec the contents of $c + (which is why exec_arg above is set to the literal string {v "$c" v}). + + In the script itself: + - $r is the name of the runtime ('ocamlrun', 'ocamlrund', etc.) + - $d is calculated in the script as $(dirname "$0") - i.e. the directory + containing the bytecode executable itself + - $c will ultimately be the runtime to exec. If it is empty, then the + script displays an error message. For Fallback, $c will be the first + runtime to try (i.e. the runtime in bindir), and the bindir passed must + end with a separator (which is ensured by Filename.concat above) + + The script tries up to three options: + - exec $c, if it exists (prefer the runtime in bindir) + - exec $d/$r, if it exists (prefer a runtime in the same directory + as the bytecode executable) + - otherwise try $(command -v "$r") (search PATH for the runtime) + + If the script fails to find an interpreter, $c will always be empty + (since [command -v] will have returned an empty string) and an + error message can be displayed. *) + l DFE {|#!%s |} bin_sh; + l FE {|r=%s |} runtime; + l F {|c=%s"$r" |} bin; + l F {|if ! test -f "$c"; then |}; + l FE {| d="$(dirname "$0" 2>/dev/null)" |}; + l FE {| test -z "$d" || d="${d%%/}/" |}; + l FE {| c="$(command -v "$d$r")" |}; + l FE {| test -n "$c" || c="$(command -v "$r")" |}; + l F {|fi |}; + l FE {|if test -z "$c"; then |}; + l FE {| echo 'This program requires an OCaml %s interpreter'>&2|} release; + l FE {| echo "$r not found either alongside $0 or in \$PATH">&2|}; + l FE {|else |}; + l DFE {| exec %s "$0" "$@" |} exec; + l FE {|fi |}; + l FE {|exit 126 |} + (* Writes the executable header to outchan and writes the RNTM section, if needed. Returns a toc_writer (i.e. Bytesections.init_record is always called) *) let write_header outchan = - let use_runtime, runtime = - if String.length !Clflags.use_runtime > 0 then - (true, make_absolute !Clflags.use_runtime) - else - (false, "ocamlrun" ^ !Clflags.runtime_variant) - in - (* Write the header *) - let runtime_info = - let header = "runtime-launch-info" in - try read_runtime_launch_info (Load_path.find header) - with Not_found -> raise (Error (File_not_found header)) + let zinc_runtime_id, write_exe_launcher = + let header = + let header = "runtime-launch-info" in + try Load_path.find header + with Not_found -> raise (Error (File_not_found header)) + in + let data = + try In_channel.with_open_bin header In_channel.input_all + with Sys_error msg -> raise (Error (Camlheader (msg, header))) + in + let zinc_runtime_id, offset = + if String.length data < 2 then + raise (Error (Camlheader ("corrupt header", header))) + else if data.[0] = '\000' then + None, 1 + else + let zinc = Misc.RuntimeID.of_string (String.sub data 0 4) in + if Option.fold ~none:false ~some:Misc.RuntimeID.is_zinc zinc then + zinc, 4 + else + raise (Error (Camlheader ("corrupt header", header))) + in + let write_exe_header outchan = + let len = String.length data in + Out_channel.output_substring outchan data offset (len - offset) + in + zinc_runtime_id, write_exe_header in - let runtime = - (* Historically, the native Windows ports are assumed to be finding - ocamlrun using a PATH search. *) - if use_runtime || Sys.win32 then - runtime + let runtime, search = + if String.length !Clflags.use_runtime > 0 then + let runtime = !Clflags.use_runtime in + if Filename.is_relative runtime then + Filename.concat (Sys.getcwd ()) runtime, Config.Disable + else + make_absolute runtime, Config.Disable else - Filename.concat runtime_info.bindir runtime + let runtime = + let runtime = "ocamlrun" ^ !Clflags.runtime_variant in + let some = Misc.RuntimeID.ocamlrun !Clflags.runtime_variant in + Option.fold ~none:runtime ~some zinc_runtime_id + in + let runtime = + if !Clflags.search_method = Config.Disable then + Filename.concat !Clflags.target_bindir runtime + else + runtime + in + runtime, !Clflags.search_method in (* Determine which method will be used for launching the executable: Executable: concatenate the bytecode image to the executable stub Shebang_runtime: #! line with the required runtime Shebang_bin_sh: #! for a shell script calling exec *) let launcher = - if runtime_info.launcher = Executable then - Executable - else - if invalid_for_shebang_line runtime then - match runtime_info.launcher with - | Shebang_bin_sh sh -> - let sh = - if sh = "sh" then - find_bin_sh () - else - sh in - if sh = "" || invalid_for_shebang_line sh then - Executable - else - Shebang_bin_sh sh - | _ -> + match !Clflags.launch_method with + | Config.Executable -> + Executable + | Config.Shebang sh -> + if search <> Config.Disable || invalid_for_shebang_line runtime then + let sh = + match sh with + | Some sh -> sh + | None -> find_bin_sh () + in + if sh = "" || invalid_for_shebang_line sh then Executable - else - Shebang_runtime + else + Shebang_bin_sh sh + else + Shebang_runtime in + (* Write the header *) match launcher with | Shebang_runtime -> + assert (search = Config.Disable); (* Use the runtime directly *) Printf.fprintf outchan "#!%s\n" runtime; Bytesections.init_record outchan | Shebang_bin_sh bin_sh -> - (* exec the runtime using sh *) - Printf.fprintf outchan "\ - #!%s\n\ - exec %s \"$0\" \"$@\"\n" bin_sh (Filename.quote runtime); + (* Use the shebang launcher *) + write_sh_launcher outchan bin_sh bindir search runtime; Bytesections.init_record outchan | Executable -> (* Use the executable stub launcher *) - let pos = runtime_info.executable_offset in - let len = String.length runtime_info.buffer - pos in - Out_channel.output_substring outchan runtime_info.buffer pos len; + write_exe_launcher outchan; (* The runtime name needs recording in RNTM *) let toc_writer = Bytesections.init_record outchan in - Printf.fprintf outchan "%s\000" runtime; + (* stdlib/header.c determines which mode is needed based on whether the + RNTM section contains an embedded NUL character. For Disable, the path + is written verbatim (no extra NUL), otherwise the directory separator + just before the basename is effectively turned into a NUL (for Enable, + there is no dirname, so the string "begins" with a NUL character). *) + if search = Disable then + output_string outchan runtime + else begin + if search = Fallback then + (* Ensure bindir does _not_ end up with a separator *) + output_string outchan + (Filename.(dirname (concat bindir current_dir_name))); + output_char outchan '\000'; + output_string outchan runtime + end; Bytesections.record toc_writer RNTM; toc_writer @@ -486,13 +551,28 @@ let link_bytecode ?final_name tolink exec_name standalone = let start_code = pos_out outchan in Symtable.init(); clear_crc_interfaces (); - let sharedobjs = List.map Dll.extract_dll_name !Clflags.dllibs in + let (tocheck, sharedobjs) = + let process_dllib ((~suffixed, name) as dllib) (tocheck, sharedobjs) = + let resolved_name = Dll.extract_dll_name dllib in + let partial_name = + if suffixed then + if String.starts_with ~prefix:"-l" name then + (~suffixed, "dll" ^ String.sub name 2 (String.length name - 2)) + else + dllib + else + (~suffixed:false, resolved_name) + in + (resolved_name::tocheck, partial_name::sharedobjs) + in + List.fold_right process_dllib !Clflags.dllibs ([], []) + in let check_dlls = standalone && Config.target = Config.host in if check_dlls then begin (* Initialize the DLL machinery *) Dll.init_compile !Clflags.no_std_include; Dll.add_path (Load_path.get_path_list ()); - try Dll.open_dlls Dll.For_checking sharedobjs + try Dll.open_dlls Dll.For_checking tocheck with Failure reason -> raise(Error(Cannot_open_dll reason)) end; let output_fun buf = @@ -509,11 +589,20 @@ let link_bytecode ?final_name tolink exec_name standalone = (* DLL stuff *) if standalone then begin (* The extra search path for DLLs *) - output_string outchan (concat_null_terminated !Clflags.dllpaths); - Bytesections.record toc_writer DLPT; + if !Clflags.dllpaths <> [] then begin + output_string outchan (concat_null_terminated !Clflags.dllpaths); + Bytesections.record toc_writer DLPT + end; (* The names of the DLLs *) - output_string outchan (concat_null_terminated sharedobjs); - Bytesections.record toc_writer DLLS + if sharedobjs <> [] then begin + let output_sharedobj (~suffixed, name) = + output_char outchan (if suffixed then '-' else ':'); + output_string outchan name; + output_byte outchan 0 + in + List.iter output_sharedobj sharedobjs; + Bytesections.record toc_writer DLLS + end end; (* The names of all primitives *) Symtable.output_primitive_names outchan; @@ -786,13 +875,20 @@ value caml_startup_pooled_exn(char_os ** argv) if not with_main && !Clflags.debug then output_cds_file ((Filename.chop_extension outfile) ^ ".cds") +let runtime_library_name runtime_variant = + if runtime_variant = "_shared" && Config.suffixing then + Misc.RuntimeID.shared_runtime Sys.Bytecode + else + "-lcamlrun" ^ runtime_variant + (* Build a custom runtime *) let build_custom_runtime prim_name exec_name = let runtime_lib = if not !Clflags.with_runtime then "" - else "-lcamlrun" ^ !Clflags.runtime_variant in + else runtime_library_name !Clflags.runtime_variant + in let stable_name = if not !Clflags.keep_camlprimc_file then Some "camlprim.c" @@ -935,7 +1031,8 @@ const enum caml_byte_program_mode caml_byte_program_mode = APPENDED; let runtime_lib = if not !Clflags.with_runtime then "" - else "-lcamlrun" ^ !Clflags.runtime_variant in + else runtime_library_name !Clflags.runtime_variant + in Ccomp.call_linker mode output_name ([obj_file] @ List.rev !Clflags.ccobjs @ [runtime_lib]) c_libs = 0 diff --git a/bytecomp/byterntm.mli b/bytecomp/byterntm.mli new file mode 100644 index 000000000000..4a9c206c4eed --- /dev/null +++ b/bytecomp/byterntm.mli @@ -0,0 +1,33 @@ +(**************************************************************************) +(* *) +(* OCaml *) +(* *) +(* David Allsopp, University of Cambridge & Tarides *) +(* *) +(* Copyright 2025 David Allsopp Ltd. *) +(* *) +(* All rights reserved. This file is distributed under the terms of *) +(* the GNU Lesser General Public License version 2.1, with the *) +(* special exception on linking described in the file LICENSE. *) +(* *) +(**************************************************************************) + +(** Parser for RNTM in bytecode executables. Parses both the RNTM section and + the shebang launcher produced by {!Bytelink}. *) + +(** Search methods used by a tendered bytecode image to find a runtime. *) +type search_method = +| Disable of string + (** Check fixed location only *) +| Fallback of string + (** Check given location first then fallback to searching for the + interpreter *) +| Enable + (** Always search for the interpreter *) + +val read_runtime : + Bytesections.section_table -> in_channel + -> (string * Misc.RuntimeID.t option * search_method) option +(** Returns the runtime used by this tendered/standalone image. If the runtime + used cannot be parsed, or the image was linked using -without-runtime, then + [None] is returned. *) diff --git a/bytecomp/byterntm.mll b/bytecomp/byterntm.mll new file mode 100644 index 000000000000..43b43c1806b1 --- /dev/null +++ b/bytecomp/byterntm.mll @@ -0,0 +1,123 @@ +(**************************************************************************) +(* *) +(* OCaml *) +(* *) +(* David Allsopp, University of Cambridge & Tarides *) +(* *) +(* Copyright 2025 David Allsopp Ltd. *) +(* *) +(* All rights reserved. This file is distributed under the terms of *) +(* the GNU Lesser General Public License version 2.1, with the *) +(* special exception on linking described in the file LICENSE. *) +(* *) +(**************************************************************************) + +{ +type search_method = +| Disable of string +| Fallback of string +| Enable + +(* First word of the current line being analysed - [exec ...], [r=...], or + [c=...] *) +type state = Exec | R | C of string + +let cut_runtime_id search name = + let len = String.length name in + let id = + if len < 6 || name.[len - 5] <> '-' then + None + else + Misc.RuntimeID.of_string (String.sub name (len - 4) 4) + in + let name = + if id = None then + name + else + String.sub name 0 (len - 5) + in + Some (name, id, search) +} + +rule analyze = parse +(* RNTM section for -runtime-search absolute or shebang directly to the + runtime *) + | "#!" ([^ ' ' '\n']* as dir) ('/' as sep) ([^ '/' ' ' '\n']+ as runtime) '\n' + | ([^ '\000']* as dir) (['/' '\\' '\000'] as sep) (* Directory portion *) + ([^ '\\' '/' '\000']+ as runtime) eof (* Runtime portion *) + { if sep = '\000' then + if dir = "" then + cut_runtime_id Enable runtime + else + let dir = Filename.concat dir "" in + cut_runtime_id (Fallback dir) runtime + else + let dir = dir ^ String.make 1 sep in + cut_runtime_id (Disable dir) runtime } + +(* Shell script launcher (if it matches, this always matches more than the above + regexp) *) + | "#!" [^ ' ' '\n']+ "/sh\n" (("exec '" | "r='") as next) + { let state = if next.[0] = 'r' then R else Exec in + analyze_sh_launcher state (Buffer.create 1024) lexbuf } + + | _ | eof + { None } + +and analyze_sh_launcher state b = parse +(* An embedded single quote *) + | "'\\''" + { analyze_sh_launcher state (Buffer.add_char b '\''; b) lexbuf } + + | [^ '\'' ]+ as s + { analyze_sh_launcher state (Buffer.add_string b s; b) lexbuf } + +(* exec line for -runtime-search disable *) + | "' \"$0\" \"$@\"\n" + { if state = Exec then + let name = Buffer.contents b in + let runtime = Filename.basename name in + let dir = + String.sub name 0 (String.length name - String.length runtime) + in + cut_runtime_id (Disable dir) runtime + else + None } + +(* r= line for -runtime-search {fallback,enable} *) + | "'\n" ("c='" as c)? + { if state = R then + let runtime = Buffer.contents b in + if c = None then + cut_runtime_id Enable runtime + else + analyze_sh_launcher (C runtime) (Buffer.clear b; b) lexbuf + else + None } + +(* c= line for -runtime-search fallback *) + | "'\"$r\"\n" + { match state with + | C runtime -> + cut_runtime_id (Fallback (Buffer.contents b)) runtime + | _ -> + None } + + | _ | eof + { None } + +{ +let read_runtime t ic = + seek_in ic 0; + let lexbuf = + try + if really_input_string ic 2 = "#!" then + let () = seek_in ic 0 in + Some (Lexing.from_channel ic) + else + let rntm = Bytesections.(read_section_string t ic Name.RNTM) in + Some (Lexing.from_string rntm) + with End_of_file | Not_found -> None + in + Option.bind lexbuf analyze +} diff --git a/bytecomp/dll.ml b/bytecomp/dll.ml index c93d24f5c29d..3443c34df6ba 100644 --- a/bytecomp/dll.ml +++ b/bytecomp/dll.ml @@ -51,13 +51,20 @@ let remove_path dirs = (* Extract the name of a DLLs from its external name (xxx.so or -lxxx) *) -let extract_dll_name file = - if Filename.check_suffix file Config.ext_dll then +let extract_dll_name (~suffixed, file) = + if not suffixed && Filename.check_suffix file Config.ext_dll then Filename.chop_suffix file Config.ext_dll - else if String.length file >= 2 && String.sub file 0 2 = "-l" then - "dll" ^ String.sub file 2 (String.length file - 2) else - file (* will cause error later *) + let file = + if String.starts_with ~prefix:"-l" file then + "dll" ^ String.sub file 2 (String.length file - 2) + else + file + in + if suffixed then + Misc.RuntimeID.stubslib file + else + file (* Open a list of DLLs, adding them to opened_dlls. Raise [Failure msg] in case of error. *) diff --git a/bytecomp/dll.mli b/bytecomp/dll.mli index 216fea828014..132daee8c050 100644 --- a/bytecomp/dll.mli +++ b/bytecomp/dll.mli @@ -15,8 +15,11 @@ (* Handling of dynamically-linked libraries *) -(* Extract the name of a DLLs from its external name (xxx.so or -lxxx) *) -val extract_dll_name: string -> string +(* Extract the name of a DLLs from its mangled or external name. If + [~suffixed:true] then the name is just the undecorated basename of the DLL + (no -l and no .so). If [~suffixed:false] then the external name may include + the DLL extension or linking symbol (xxx.so or -lxxx) *) +val extract_dll_name: (suffixed:bool * string) -> string type dll_mode = | For_checking (* will just check existence of symbols; diff --git a/configure b/configure index d340888e17dd..64da328c17d6 100755 --- a/configure +++ b/configure @@ -785,6 +785,12 @@ build_os build_vendor build_cpu build +runtime_search_target +runtime_search +suffixing +native_runtime_id +bytecode_runtime_id +zinc_runtime_id build_map_flags srcdir_abs_real srcdir_abs @@ -793,6 +799,7 @@ ar_supports_response_files QS TARGET_LIBDIR ocaml_libdir +TARGET_BINDIR ocaml_bindir ocaml_prefix compute_deps @@ -865,6 +872,8 @@ natdynlink supports_shared_libraries mklib AR +launch_method_target +launch_method shebangscripts winpthreads_source_include_dir winpthreads_source_dir @@ -935,6 +944,7 @@ CMO_MAGIC_NUMBER CMI_MAGIC_NUMBER EXEC_MAGIC_NUMBER MAGIC_LENGTH +OCAML_RELEASE_NUMBER OCAML_VERSION_SHORT OCAML_VERSION_EXTRA OCAML_VERSION_PATCHLEVEL @@ -1028,6 +1038,9 @@ enable_flat_float_array enable_function_sections enable_mmap_map_stack with_relative_libdir +enable_suffixing +enable_runtime_search +enable_runtime_search_target with_afl with_flexdll with_winpthreads_msvc @@ -1730,6 +1743,13 @@ Optional Features: --disable-function-sections do not emit each function in a separate section --enable-mmap-map-stack use mmap to allocate stacks instead of malloc + --disable-suffixing disable suffixing of runtime executables and shared + libraries + --enable-runtime-search allow the distribution's bytecode executables to + search for ocamlrun + --enable-runtime-search-target + allow bytecode executables produced by ocamlc to + search for ocamlrun --enable-shared[=PKGS] build shared libraries [default=yes] --enable-static[=PKGS] build static libraries [default=yes] --enable-fast-install[=PKGS] @@ -3364,6 +3384,8 @@ target_libdir_is_relative=false srcdir_abs='' srcdir_abs_real='' build_map_flags='' +runtime_search='' +runtime_search_target='' # Information about the package @@ -3394,6 +3416,8 @@ OCAML_VERSION_EXTRA=dev0-2025-01-06 OCAML_VERSION_SHORT=5.3 +OCAML_RELEASE_NUMBER=19 + printf "%s\n" "#define MAGIC_NUMBER_PREFIX \"Caml1999\"" >>confdefs.h printf "%s\n" "#define MAGIC_NUMBER_VERSION \"035\"" >>confdefs.h @@ -3511,6 +3535,8 @@ LINEAR_MAGIC_NUMBER=Caml1999L035 + + @@ -3560,6 +3586,13 @@ LINEAR_MAGIC_NUMBER=Caml1999L035 + + + + + + + @@ -3622,6 +3655,8 @@ printf "%s\n" "#define OCAML_VERSION 50301" >>confdefs.h printf "%s\n" "#define OCAML_VERSION_STRING \"5.3.1+dev0-2025-01-06\"" >>confdefs.h +printf "%s\n" "#define OCAML_RELEASE_NUMBER 19" >>confdefs.h + # Works out how many "o"s are needed in quoted strings @@ -4162,12 +4197,12 @@ if test ${with_target_sh+y} then : withval=$with_target_sh; if test x"$withval" = 'xno' then : - target_launch_method='exe' + launch_method_target='exe' else $as_nop - target_launch_method="$withval" + launch_method_target="$withval" fi else $as_nop - target_launch_method='' + launch_method_target='' fi @@ -4274,6 +4309,61 @@ else $as_nop fi +# Check whether --enable-suffixing was given. +if test ${enable_suffixing+y} +then : + enableval=$enable_suffixing; if test "x$enableval" = 'xno' +then : + suffixing=false +else $as_nop + suffixing=true +fi +else $as_nop + suffixing=true +fi + + +# Check whether --enable-runtime-search was given. +if test ${enable_runtime_search+y} +then : + enableval=$enable_runtime_search; case $enableval in #( + no) : + ;; #( + yes) : + runtime_search='enable' ;; #( + fallback) : + runtime_search='fallback' ;; #( + *) : + as_fn_error $? "valid values are yes, no or fallback for --enable-runtime-search" "$LINENO" 5 ;; +esac +fi + + +# Check whether --enable-runtime-search-target was given. +if test ${enable_runtime_search_target+y} +then : + enableval=$enable_runtime_search_target; case $enableval in #( + no) : + ;; #( + yes) : + runtime_search_target='enable' ;; #( + fallback) : + runtime_search_target='fallback' ;; #( + *) : + as_fn_error $? "valid values are yes, no or fallback for --enable-runtime-search-target" "$LINENO" 5 ;; +esac +fi + + +case $suffixing,$runtime_search,$runtime_search_target in #( + true,*,*|false,,) : + ;; #( + false,*,*) : + as_fn_error $? "--disable-suffixed cannot be used with --enable-runtime-search or --enable-runtime-search-target" "$LINENO" 5 ;; #( + *) : + ;; +esac + # Check whether --with-afl was given. if test ${with_afl+y} @@ -14061,9 +14151,9 @@ then : # not use shebang scripts shebangscripts=true launch_method='sh' - if test x"$target_launch_method" = 'x' + if test x"$launch_method_target" = 'x' then : - target_launch_method='exe' + launch_method_target='exe' fi ;; #( *-*-mingw32*|*-pc-windows) : ;; #( @@ -14075,21 +14165,6 @@ esac fi -# stdlib/runtime.info and stdlib/target_runtime.info are generated by commands -# in config.status, rather than by the .in mechanism, since the latter cannot -# reliably process binary files. -ac_config_commands="$ac_config_commands shebang" - - -# Are we building a cross-compiler - -if test x"$host" = x"$target" -then : - cross_compiler=false -else $as_nop - cross_compiler=true -fi - # Checks for programs ## Check for the C compiler: done by libtool @@ -17096,6 +17171,15 @@ then : fi +## strlcpy +ac_fn_c_check_func "$LINENO" "strlcpy" "ac_cv_func_strlcpy" +if test "x$ac_cv_func_strlcpy" = xyes +then : + printf "%s\n" "#define HAS_STRLCPY 1" >>confdefs.h + +fi + + ## secure_getenv and __secure_getenv saved_CPPFLAGS="$CPPFLAGS" @@ -21365,6 +21449,88 @@ case $host in #( ;; esac +# Determine the three Runtime IDs (see runtime/Mangling.md) + + + + + +# Bits 0-4 (dev + low 4 bits of release) + + +# Bits 5-6 (high 2 bits of release) + + +# Bits 7-9 (low 3 bits of reserved) +quintet1="1 + $(expr \( $reserved_header_bits \* 4 \) % 32)" +quintet1="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet1 \) + 1))" + +# Bits 10-11 (high 2 bits of reserved) +quintet2_byte="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $reserved_header_bits / 8 \) + 1))" + +# Bit 12 (no-flat-float-array) +if $flat_float_array +then : + quintet2_zinc='0' +else $as_nop + quintet2_byte="4 + $quintet2_byte" + quintet2_zinc='4' +fi +# Bit 13 (fp) +if $frame_pointers +then : + quintet2_native="8 + $quintet2_byte" +else $as_nop + quintet2_native="$quintet2_byte" +fi +# Bit 14 (tsan) +if $tsan +then : + quintet2_native="16 + $quintet2_native" +fi + +quintet2_zinc="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet2_zinc \) + 1))" +quintet2_byte="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet2_byte \) + 1))" +quintet2_native="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet2_native \) + 1))" + +# Bit 15 (int31) +if $arch64 +then : + quintet3_zinc='0' +else $as_nop + quintet3_zinc='1' +fi +# Bit 16 (static) +if ! $supports_shared_libraries +then : + quintet3_zinc="2 + $quintet3_zinc" +fi +# Bit 17 (no-compression) +if test x"$zstd_status" != 'xok' +then : + quintet3_zinc="4 + $quintet3_zinc" +fi +# Bit 18 (ansi) +case $target,$windows_unicode in #( + *-*-mingw32,0|*-pc-windows,0) : + quintet3="8 + $quintet3_zinc" ;; #( + *) : + quintet3="$quintet3_zinc" ;; +esac +# Bit 19 (mutable-string) cannot be set since OCaml 5.0 + +quintet3_zinc="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet3_zinc \) + 1))" +quintet3="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet3 \) + 1))" + +zinc_runtime_id="71${quintet2_zinc}${quintet3_zinc}" +bytecode_runtime_id="7${quintet1}${quintet2_byte}${quintet3}" +native_runtime_id="7${quintet1}${quintet2_native}${quintet3}" + +# Update the values for is_official_release and release_number in +# utils/config.common.ml.in (this is done when tools/autogen is run, not each +# time configure is run!) + + # Do not permanently cache the result of flexdll.h unset ac_cv_header_flexdll_h @@ -21530,18 +21696,16 @@ then : exec_prefix="$prefix" fi eval "exec_prefix=\"$exec_prefix\"" - # Set variables necessary to create utils/config.generated.ml and the two - # runtime-launch-info templates in stdlib. - # $ocaml_bindir is used for utils/config.generated.ml and is _empty_ if the + # Set variables necessary to create utils/config.generated.ml. + # $HOST_BINDIR is always the absolute path to the binary directory, in host + # format (i.e. potentially with backslashes on Windows). + # $TARGET_BINDIR can be specified by the caller when building cross-compilers, + # and the value then is used unaltered. Otherwise, $TARGET_BINDIR is set to + # '.' when the compiler is configured with --with-relative-libdir or the + # value of $HOST_BINDIR otherwise. + # $ocaml_bindir is used in utils/config.generated.ml and is _empty_ if the # compiler is configured with --with-relative-libdir (otherwise the path # would be embedded in config.cmo) - # $HOST_BINDIR and $TARGET_BINDIR are used to generate the two - # runtime-launch-info files. $HOST_BINDIR is always the absolute path to the - # binary directory, in host format (i.e. potentially with backslashes on - # Windows). $TARGET_BINDIR can be specified by the caller when building - # cross-compilers, and the value then is used unaltered. Otherwise, - # $TARGET_BINDIR is set to '.' when the compiler is configured with - # --with-relative-libdir or the value of $HOST_BINDIR otherwise. eval "HOST_BINDIR=\"$ocaml_bindir\"" if test "x$bindir_to_libdir" = 'x' then : @@ -21565,9 +21729,9 @@ then : else $as_nop TARGET_LIBDIR="$bindir_to_libdir" fi - if test x"$target_launch_method" = 'x' + if test x"$launch_method_target" = 'x' then : - target_launch_method="$launch_method" + launch_method_target="$launch_method" fi prefix="$saved_prefix" exec_prefix="$saved_exec_prefix" @@ -22522,11 +22686,6 @@ fi - launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' - target_launch_method=\ -'$(echo "$target_launch_method" | sed -e "s/'/'\"'\"'/g")' - HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' - TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")' ocaml_additional_stublibs_dir=\ '$(echo "$ocaml_additional_stublibs_dir" | sed -e "s/'/'\"'\"'/g")' ocaml_libdir='$(echo "$ocaml_libdir" | sed -e "s/'/'\"'\"'/g")' @@ -22568,7 +22727,6 @@ do "otherlibs/unix/META") CONFIG_FILES="$CONFIG_FILES otherlibs/unix/META" ;; "otherlibs/unix/unix.ml") CONFIG_LINKS="$CONFIG_LINKS otherlibs/unix/unix.ml:otherlibs/unix/unix_${unix_or_win32}.ml" ;; "otherlibs/str/META") CONFIG_FILES="$CONFIG_FILES otherlibs/str/META" ;; - "shebang") CONFIG_COMMANDS="$CONFIG_COMMANDS shebang" ;; "otherlibs/systhreads/META") CONFIG_FILES="$CONFIG_FILES otherlibs/systhreads/META" ;; "ocamltest/ocamltest_unix.ml") CONFIG_LINKS="$CONFIG_LINKS ocamltest/ocamltest_unix.ml:${ocamltest_unix_mod}" ;; "runtime/ld.conf") CONFIG_COMMANDS="$CONFIG_COMMANDS runtime/ld.conf" ;; @@ -23702,10 +23860,6 @@ ltmain=$ac_aux_dir/ltmain.sh chmod +x "$ofile" ;; - "shebang":C) printf '%s\n%s\000\n' "$launch_method" "$HOST_BINDIR" \ - > stdlib/runtime.info - printf '%s\n%s\000\n' "$target_launch_method" "$TARGET_BINDIR" \ - > stdlib/target_runtime.info ;; "runtime/ld.conf":C) rm -f runtime/ld.conf test x"$ocaml_additional_stublibs_dir" = 'x' || \ echo "$ocaml_additional_stublibs_dir" > runtime/ld.conf diff --git a/configure.ac b/configure.ac index 155955da6a28..db57fd1f3b56 100644 --- a/configure.ac +++ b/configure.ac @@ -107,6 +107,8 @@ target_libdir_is_relative=false srcdir_abs='' srcdir_abs_real='' build_map_flags='' +runtime_search='' +runtime_search_target='' # Information about the package @@ -129,6 +131,7 @@ AC_SUBST([OCAML_VERSION_MINOR], [OCAML__VERSION_MINOR]) AC_SUBST([OCAML_VERSION_PATCHLEVEL], [OCAML__VERSION_PATCHLEVEL]) AC_SUBST([OCAML_VERSION_EXTRA], [OCAML__VERSION_EXTRA]) AC_SUBST([OCAML_VERSION_SHORT], [OCAML__VERSION_SHORT]) +AC_SUBST([OCAML_RELEASE_NUMBER], [OCAML__RELEASE_NUMBER]) AC_DEFINE([MAGIC_NUMBER_PREFIX], ["][MAGIC_NUMBER__PREFIX]["]) AC_DEFINE([MAGIC_NUMBER_VERSION], ["][MAGIC_NUMBER__VERSION]["]) AC_DEFINE([EXEC_MAGIC_LENGTH], [MAGIC_NUMBER__LENGTH]) @@ -208,6 +211,8 @@ AC_SUBST([flexdll_dir]) AC_SUBST([winpthreads_source_dir]) AC_SUBST([winpthreads_source_include_dir]) AC_SUBST([shebangscripts]) +AC_SUBST([launch_method]) +AC_SUBST([launch_method_target]) AC_SUBST([AR]) AC_SUBST([mklib]) AC_SUBST([supports_shared_libraries]) @@ -280,6 +285,7 @@ AC_SUBST([build_libraries_manpages]) AC_SUBST([compute_deps]) AC_SUBST([ocaml_prefix]) AC_SUBST([ocaml_bindir]) +AC_SUBST([TARGET_BINDIR]) AC_SUBST([ocaml_libdir]) AC_SUBST([TARGET_LIBDIR]) AC_SUBST([QS]) @@ -288,6 +294,12 @@ AC_SUBST([target_libdir_is_relative]) AC_SUBST([srcdir_abs]) AC_SUBST([srcdir_abs_real]) AC_SUBST([build_map_flags]) +AC_SUBST([zinc_runtime_id]) +AC_SUBST([bytecode_runtime_id]) +AC_SUBST([native_runtime_id]) +AC_SUBST([suffixing]) +AC_SUBST([runtime_search]) +AC_SUBST([runtime_search_target]) ## Generated files @@ -319,6 +331,7 @@ m4_if([OCAML__VERSION_EXTRA],[], [], AC_DEFINE([OCAML_VERSION_EXTRA], ["][OCAML__VERSION_EXTRA]["])]) AC_DEFINE([OCAML_VERSION], [OCAML__VERSION_NUMBER]) AC_DEFINE([OCAML_VERSION_STRING], ["][OCAML__VERSION]["]) +AC_DEFINE([OCAML_RELEASE_NUMBER], [OCAML__RELEASE_NUMBER]) # Works out how many "o"s are needed in quoted strings AC_CONFIG_COMMANDS_PRE(OCAML_QUOTED_STRING_ID) @@ -593,9 +606,9 @@ AC_ARG_WITH([target-sh], [AS_HELP_STRING([--with-target-sh], [location of Posix sh on the target system])], [AS_IF([test x"$withval" = 'xno'], - [target_launch_method='exe'], - [target_launch_method="$withval"])], - [target_launch_method='']) + [launch_method_target='exe'], + [launch_method_target="$withval"])], + [launch_method_target='']) AC_ARG_WITH([additional-stublibsdir], [AS_HELP_STRING([--with-additional-stublibsdir], @@ -665,6 +678,37 @@ AC_ARG_WITH([relative-libdir], [bindir_to_libdir="$withval"])], [bindir_to_libdir='']) +AC_ARG_ENABLE([suffixing], + [AS_HELP_STRING([--disable-suffixing], + [disable suffixing of runtime executables and shared libraries])], + [AS_IF([test "x$enableval" = 'xno'], [suffixing=false], [suffixing=true])], + [suffixing=true]) + +AC_ARG_ENABLE([runtime-search], + [AS_HELP_STRING([--enable-runtime-search], + [allow the distribution's bytecode executables to search for ocamlrun])], + [AS_CASE([$enableval], + [no],[], + [yes],[runtime_search='enable'], + [fallback],[runtime_search='fallback'], + [AC_MSG_ERROR(m4_normalize([valid values are yes, no or fallback for + --enable-runtime-search]))])]) + +AC_ARG_ENABLE([runtime-search-target], + [AS_HELP_STRING([--enable-runtime-search-target], + [allow bytecode executables produced by ocamlc to search for ocamlrun])], + [AS_CASE([$enableval], + [no],[], + [yes],[runtime_search_target='enable'], + [fallback],[runtime_search_target='fallback'], + [AC_MSG_ERROR(m4_normalize([valid values are yes, no or fallback for + --enable-runtime-search-target]))])]) + +AS_CASE([$suffixing,$runtime_search,$runtime_search_target], + [true,*,*|false,,],[], + [false,*,*],[AC_MSG_ERROR(m4_normalize([--disable-suffixed cannot be used with + --enable-runtime-search or --enable-runtime-search-target]))]) + AC_ARG_WITH([afl], [AS_HELP_STRING([--with-afl], [use the AFL fuzzer])]) @@ -889,39 +933,14 @@ AS_IF([test "x$interpval" = "xyes"], # not use shebang scripts shebangscripts=true launch_method='sh' - AS_IF([test x"$target_launch_method" = 'x'], - [target_launch_method='exe'])], + AS_IF([test x"$launch_method_target" = 'x'], + [launch_method_target='exe'])], [*-*-mingw32*|*-pc-windows], [], [shebangscripts=true launch_method='sh'] )] ) -# stdlib/runtime.info and stdlib/target_runtime.info are generated by commands -# in config.status, rather than by the .in mechanism, since the latter cannot -# reliably process binary files. -AC_CONFIG_COMMANDS([shebang], - [printf '%s\n%s\000\n' "$launch_method" "$HOST_BINDIR" \ - > stdlib/runtime.info - printf '%s\n%s\000\n' "$target_launch_method" "$TARGET_BINDIR" \ - > stdlib/target_runtime.info], -dnl These declarations are put in a here-document in configure, so the command -dnl in '$(...)' _is_ evaluated as the content is written to config.status (by -dnl standard interpretation of a here-document). The sed commands quote any -dnl nefarious single quotes which may appear in any of the strings. - [launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' - target_launch_method=\ -'$(echo "$target_launch_method" | sed -e "s/'/'\"'\"'/g")' - HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' - TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")']) - -# Are we building a cross-compiler - -AS_IF( - [test x"$host" = x"$target"], - [cross_compiler=false], - [cross_compiler=true]) - # Checks for programs ## Check for the C compiler: done by libtool @@ -1825,6 +1844,9 @@ AC_CHECK_FUNC([getrusage], [AC_DEFINE([HAS_GETRUSAGE], [1])]) ## times AC_CHECK_FUNC([times], [AC_DEFINE([HAS_TIMES], [1])]) +## strlcpy +AC_CHECK_FUNC([strlcpy], [AC_DEFINE([HAS_STRLCPY], [1])]) + ## secure_getenv and __secure_getenv saved_CPPFLAGS="$CPPFLAGS" @@ -2869,6 +2891,79 @@ AS_CASE([$host], # as "Infinity" and "Inf" instead of the expected "inf" [AC_DEFINE([HAS_BROKEN_PRINTF], [1])]) +# Determine the three Runtime IDs (see runtime/Mangling.md) +m4_define([ALPHABET], [0123456789abcdefghijklmnopqrstuv]) +AC_DEFUN([BASE32], ["$(echo 'ALPHABET' | cut -c $(expr \( $1 \) + 1))"]) + +m4_cond(OCAML__DEVELOPMENT_VERSION, [true], + [m4_define([ID_VERSION], m4_eval((OCAML__RELEASE_NUMBER << 1) + 1))], + [m4_define([ID_VERSION], m4_eval((OCAML__RELEASE_NUMBER << 1)))]) + +# Bits 0-4 (dev + low 4 bits of release) +m4_define([QUINTET0], + [m4_substr(ALPHABET, m4_eval(ID_VERSION & 31), [1])]) + +# Bits 5-6 (high 2 bits of release) +m4_define([QUINTET1_ZINC], + [m4_substr(ALPHABET, m4_eval(ID_VERSION >> 5), [1])]) + +# Bits 7-9 (low 3 bits of reserved) +quintet1="QUINTET1_ZINC + $(expr \( $reserved_header_bits \* 4 \) % 32)" +quintet1=BASE32([$quintet1]) + +# Bits 10-11 (high 2 bits of reserved) +quintet2_byte=BASE32([$reserved_header_bits / 8]) + +# Bit 12 (no-flat-float-array) +AS_IF([$flat_float_array], + [quintet2_zinc='0'], + [quintet2_byte="4 + $quintet2_byte" + quintet2_zinc='4']) +# Bit 13 (fp) +AS_IF([$frame_pointers], + [quintet2_native="8 + $quintet2_byte"], + [quintet2_native="$quintet2_byte"]) +# Bit 14 (tsan) +AS_IF([$tsan], + [quintet2_native="16 + $quintet2_native"]) + +quintet2_zinc=BASE32([$quintet2_zinc]) +quintet2_byte=BASE32([$quintet2_byte]) +quintet2_native=BASE32([$quintet2_native]) + +# Bit 15 (int31) +AS_IF([$arch64], + [quintet3_zinc='0'], + [quintet3_zinc='1']) +# Bit 16 (static) +AS_IF([! $supports_shared_libraries], + [quintet3_zinc="2 + $quintet3_zinc"]) +# Bit 17 (no-compression) +AS_IF([test x"$zstd_status" != 'xok'], + [quintet3_zinc="4 + $quintet3_zinc"]) +# Bit 18 (ansi) +AS_CASE([$target,$windows_unicode], + [*-*-mingw32,0|*-pc-windows,0], + [quintet3="8 + $quintet3_zinc"], + [quintet3="$quintet3_zinc"]) +# Bit 19 (mutable-string) cannot be set since OCaml 5.0 + +quintet3_zinc=BASE32([$quintet3_zinc]) +quintet3=BASE32([$quintet3]) + +zinc_runtime_id="QUINTET0[]QUINTET1_ZINC${quintet2_zinc}${quintet3_zinc}" +bytecode_runtime_id="QUINTET0${quintet1}${quintet2_byte}${quintet3}" +native_runtime_id="QUINTET0${quintet1}${quintet2_native}${quintet3}" + +# Update the values for is_official_release and release_number in +# utils/config.common.ml.in (this is done when tools/autogen is run, not each +# time configure is run!) +m4_syscmd([sed -e '/^let is_official_release =/s/=.*/= ]'\ +'m4_if(OCAML__DEVELOPMENT_VERSION,true,false,true)[/' \ + -e '/^let release_number =/s/=.*/= ]OCAML__RELEASE_NUMBER[/' \ + utils/config.common.ml.in > utils/config.common.ml.in.new +mv -f utils/config.common.ml.in.new utils/config.common.ml.in]) + # Do not permanently cache the result of flexdll.h unset ac_cv_header_flexdll_h @@ -2881,18 +2976,16 @@ AC_CONFIG_COMMANDS_PRE([ AS_IF([test "x$prefix" = "xNONE"],[prefix="$ac_default_prefix"]) AS_IF([test "x$exec_prefix" = "xNONE"],[exec_prefix="$prefix"]) eval "exec_prefix=\"$exec_prefix\"" - # Set variables necessary to create utils/config.generated.ml and the two - # runtime-launch-info templates in stdlib. - # $ocaml_bindir is used for utils/config.generated.ml and is _empty_ if the + # Set variables necessary to create utils/config.generated.ml. + # $HOST_BINDIR is always the absolute path to the binary directory, in host + # format (i.e. potentially with backslashes on Windows). + # $TARGET_BINDIR can be specified by the caller when building cross-compilers, + # and the value then is used unaltered. Otherwise, $TARGET_BINDIR is set to + # '.' when the compiler is configured with --with-relative-libdir or the + # value of $HOST_BINDIR otherwise. + # $ocaml_bindir is used in utils/config.generated.ml and is _empty_ if the # compiler is configured with --with-relative-libdir (otherwise the path # would be embedded in config.cmo) - # $HOST_BINDIR and $TARGET_BINDIR are used to generate the two - # runtime-launch-info files. $HOST_BINDIR is always the absolute path to the - # binary directory, in host format (i.e. potentially with backslashes on - # Windows). $TARGET_BINDIR can be specified by the caller when building - # cross-compilers, and the value then is used unaltered. Otherwise, - # $TARGET_BINDIR is set to '.' when the compiler is configured with - # --with-relative-libdir or the value of $HOST_BINDIR otherwise. eval "HOST_BINDIR=\"$ocaml_bindir\"" AS_IF([test "x$bindir_to_libdir" = 'x'], [ocaml_bindir="$HOST_BINDIR"], @@ -2905,8 +2998,8 @@ AC_CONFIG_COMMANDS_PRE([ AS_IF([test "x$bindir_to_libdir" = 'x'], [TARGET_LIBDIR="$ocaml_libdir"], [TARGET_LIBDIR="$bindir_to_libdir"]) - AS_IF([test x"$target_launch_method" = 'x'], - [target_launch_method="$launch_method"]) + AS_IF([test x"$launch_method_target" = 'x'], + [launch_method_target="$launch_method"]) prefix="$saved_prefix" exec_prefix="$saved_exec_prefix"]) diff --git a/driver/compenv.ml b/driver/compenv.ml index a1458e5cdac4..4774a9adb874 100644 --- a/driver/compenv.ml +++ b/driver/compenv.ml @@ -596,7 +596,7 @@ type deferred_action = | ProcessCFile of string | ProcessOtherFile of string | ProcessObjects of string list - | ProcessDLLs of string list + | ProcessDLLs of bool * string list let c_object_of_filename name = Filename.chop_suffix (Filename.basename name) ".c" ^ Config.ext_obj @@ -629,8 +629,8 @@ let process_action ccobjs := obj_name :: !ccobjs | ProcessObjects names -> ccobjs := names @ !ccobjs - | ProcessDLLs names -> - dllibs := names @ !dllibs + | ProcessDLLs (suffixed, names) -> + dllibs := (List.map (fun n -> (~suffixed, n)) names) @ !dllibs | ProcessOtherFile name -> if Filename.check_suffix name ocaml_mod_ext || Filename.check_suffix name ocaml_lib_ext then @@ -643,7 +643,7 @@ let process_action ccobjs := name :: !ccobjs end else if not !native_code && Filename.check_suffix name Config.ext_dll then - dllibs := name :: !dllibs + dllibs := (~suffixed:false, name) :: !dllibs else match Compiler_pass.of_input_filename name with | Some start_from -> diff --git a/driver/compenv.mli b/driver/compenv.mli index c2bc2dff1dbb..576e8d24fc2a 100644 --- a/driver/compenv.mli +++ b/driver/compenv.mli @@ -53,7 +53,7 @@ type deferred_action = | ProcessCFile of string | ProcessOtherFile of string | ProcessObjects of string list - | ProcessDLLs of string list + | ProcessDLLs of bool * string list val c_object_of_filename : string -> string diff --git a/driver/main_args.ml b/driver/main_args.ml index 4cc08062844c..a0b21a160152 100644 --- a/driver/main_args.ml +++ b/driver/main_args.ml @@ -89,6 +89,11 @@ let mk_custom f = let mk_dllib f = "-dllib", Arg.String f, " Use the dynamically-loaded library " +let mk_dllib_suffixed f = + "-dllib-suffixed", Arg.String f, + " Use the dynamically-loaded library , with the runtime suffix \ + appended to the name" + let mk_dllpath f = "-dllpath", Arg.String f, " Add to the run-time search path for shared libraries" @@ -521,6 +526,25 @@ let mk_unsafe_string = in "-unsafe-string", Arg.Unit err, " (option not available)" +let mk_launch_method f = + "-launch-method", Arg.String f, + " Specify the mechanism for the bytecode launcher:\n\ + \ exe - use the executable launcher in runtime-launch-info\n\ + \ sh - use a #!, using sh if the interpreter path cannot be used\n\ + \ /path/interpreter - use #!, or the given sh-compatible \n\ + \ interpreter if the interpreter path cannot be used" + +let mk_search_method f = + "-runtime-search", Arg.Symbol (["disable"; "fallback"; "enable"], f), + Printf.sprintf + " Control the way the bytecode header searches for the interpreter\n\ + \ The following settings are supported:\n\ + \ disable use a fixed absolute path to the interpreter\n\ + \ fallback search for interpreter only if not found at the absolute \ + path\n\ + \ enable always search for the interpreter\n\ + \ The default setting is 'disable'." + let mk_use_runtime f = "-use-runtime", Arg.String f, " Generate bytecode for the given runtime system" @@ -917,10 +941,13 @@ module type Bytecomp_options = sig val _custom : unit -> unit val _no_check_prims : unit -> unit val _dllib : string -> unit + val _dllib_suffixed : string -> unit val _dllpath : string -> unit val _make_runtime : unit -> unit val _vmthread : unit -> unit val _use_runtime : string -> unit + val _launch_method : string -> unit + val _search_method : string -> unit val _output_complete_exe : unit -> unit val _dinstr : unit -> unit @@ -1051,6 +1078,7 @@ struct mk_config_var F._config_var; mk_custom F._custom; mk_dllib F._dllib; + mk_dllib_suffixed F._dllib_suffixed; mk_dllpath F._dllpath; mk_dtypes F._annot; mk_for_pack_byt F._for_pack; @@ -1117,6 +1145,8 @@ struct mk_unsafe_string; mk_use_runtime F._use_runtime; mk_use_runtime_2 F._use_runtime; + mk_launch_method F._launch_method; + mk_search_method F._search_method; mk_v F._v; mk_verbose F._verbose; mk_version F._version; @@ -1931,7 +1961,9 @@ third-party libraries such as Lwt, but with a different API." let _custom = set custom_runtime let _dcamlprimc = set keep_camlprimc_file let _dinstr = set dump_instr - let _dllib s = Compenv.defer (ProcessDLLs (Misc.rev_split_words s)) + let _dllib s = Compenv.defer (ProcessDLLs (false, Misc.rev_split_words s)) + let _dllib_suffixed s = + Compenv.defer (ProcessDLLs (true, Misc.rev_split_words s)) let _dllpath s = dllpaths := ((!dllpaths) @ [s]) let _make_runtime () = custom_runtime := true; make_runtime := true; link_everything := true @@ -1945,6 +1977,34 @@ third-party libraries such as Lwt, but with a different API." let _output_obj () = output_c_object := true; custom_runtime := true let _use_prims s = use_prims := s let _use_runtime s = use_runtime := s + let _launch_method s = + let setting = + try + let s, bindir = Misc.cut_at s ' ' in + target_bindir := bindir; + s + with Not_found -> + s + in + match setting with + | "exe" -> + launch_method := Config.Executable; + | "sh" -> + launch_method := Config.Shebang None + | s when s <> "" && s.[0] = '/' -> + launch_method := Config.Shebang (Some s) + | _ -> + Compenv.fatal + "-launch-method: expect sh, exe or an absolute path for " + let _search_method = function + | "disable" -> + search_method := Config.Disable + | "fallback" -> + search_method := Config.Fallback + | "enable" -> + search_method := Config.Enable + | _ -> + assert false let _v () = Compenv.print_version_and_library "compiler" let _vmthread () = Compenv.fatal vmthread_removed_message end diff --git a/driver/main_args.mli b/driver/main_args.mli index 4857507214e6..14a69c6f45cc 100644 --- a/driver/main_args.mli +++ b/driver/main_args.mli @@ -155,10 +155,13 @@ module type Bytecomp_options = sig val _custom : unit -> unit val _no_check_prims : unit -> unit val _dllib : string -> unit + val _dllib_suffixed : string -> unit val _dllpath : string -> unit val _make_runtime : unit -> unit val _vmthread : unit -> unit val _use_runtime : string -> unit + val _launch_method : string -> unit + val _search_method : string -> unit val _output_complete_exe : unit -> unit val _dinstr : unit -> unit diff --git a/file_formats/cmo_format.mli b/file_formats/cmo_format.mli index a4dbfce082e9..b38cceab90a4 100644 --- a/file_formats/cmo_format.mli +++ b/file_formats/cmo_format.mli @@ -67,7 +67,7 @@ type library = how they end up being used on the command line. *) lib_ccobjs: string list; (* C object files needed for -custom *) lib_ccopts: string list; (* Extra opts to C compiler *) - lib_dllibs: string list } (* DLLs needed *) + lib_dllibs: (suffixed:bool * string) list } (* DLLs needed *) (* Format of a .cma file: magic number (Config.cma_magic_number) diff --git a/man/Makefile b/man/Makefile index 10cc8bbe417b..05424bab4737 100644 --- a/man/Makefile +++ b/man/Makefile @@ -22,5 +22,5 @@ MANPAGES = $(addsuffix .1,\ .PHONY: install install: - $(MKDIR) '$(INSTALL_PROGRAMS_MAN_DIR)' - $(INSTALL_DATA) $(MANPAGES) '$(INSTALL_PROGRAMS_MAN_DIR)' + $(MKDIR) $(call QUOTE_SINGLE,$(INSTALL_PROGRAMS_MAN_DIR)) + $(INSTALL_DATA) $(MANPAGES) $(call QUOTE_SINGLE,$(INSTALL_PROGRAMS_MAN_DIR)) diff --git a/man/ocamlc.1 b/man/ocamlc.1 index 4adf4d99a370..005a2fe52cad 100644 --- a/man/ocamlc.1 +++ b/man/ocamlc.1 @@ -355,6 +355,15 @@ to be loaded dynamically by the run-time system .BR ocamlrun (1) at program start-up time. .TP +.BI \-dllib\-suffixed\ \-l libname +As for +.BI \-dllib +but the name recorded is mangled by +.BR ocamlrun (1) +at program start-up time. This is used for C stub libraries; see +.B ocamlmklib\ \-suffixed +for further information. +.TP .BI \-dllpath " dir" Adds the directory .I dir @@ -474,6 +483,24 @@ source code. Labels are not ignored in types, labels may be used in applications, and labelled parameters can be given in any order. This is the default. .TP +.BI \-launch\-method " method" +Specifies the mechanism used by normal bytecode executables to find the +interpreter. +The following methods are supported: + +.B exe +A small executable stub launcher contained in the runtime-launch-info file is +prepended to the bytecode image. + +.B sh +A #! header is used. If the full path to the required interpreter is not +suitable for a #! line, a small shell script is generated instead, using the sh +interpreter found in PATH. + +.B /path/interpreter +As for sh, but if the interpreter cannot be used in #! line then +/path/intepreter is used instead of searching for sh in PATH. +.TP .B \-linkall Force all modules contained in libraries to be linked in. If this flag is not given, unreferenced modules are not linked in. When @@ -653,6 +680,25 @@ flag, you must use it again for all dependencies. Do no allow arbitrary recursive types during type-checking. This is the default. .TP +.BI \-runtime\-search " method" +Controls whether the header used by normal bytecode executables is permitted to +search for the interpreter, or requires it to be at a fixed location. +The following methods are supported: + +.B disable +A fixed absolute path to the interpreter is used, and the executable will not +launch if the interpreter is not found at this location. + +.B fallback +A fixed absolute path to the interpreter is used, but if the executable cannot +find the interpreter at this location then it will search first in the directory +containing the executable and then in PATH. + +.B enable +The executable searches for the interpreter first in the directory containing +the executable and then in PATH. No absolute path to the interpreter is +recorded. +.TP .BI \-runtime\-variant " suffix" Add .I suffix diff --git a/manual/src/cmds/unified-options.etex b/manual/src/cmds/unified-options.etex index b660d01421e8..08d2fd3a70f7 100644 --- a/manual/src/cmds/unified-options.etex +++ b/manual/src/cmds/unified-options.etex @@ -229,6 +229,14 @@ Arrange for the C shared library "dll"\var{libname}".so" by the run-time system "ocamlrun" at program start-up time. }%comp +\comp{ +\item["-dllib-suffixed" "-l"\var{libname}] +As for "-dllib" but the name recorded is mangled with the Runtime ID and host of +the interpreter before being loaded by the run-time system "ocamlrun" at program +start-up time. Ths is used for C stub libraries, for example by DLLs produced +with "ocamlmklib -suffixed". +}%comp + \comp{\item["-dllpath" \var{dir}] Adds the directory \var{dir} to the run-time search path for shared C libraries. At link-time, shared libraries are searched in the @@ -381,6 +389,23 @@ source code. Labels are not ignored in types, labels may be used in applications, and labelled parameters can be given in any order. This is the default. +\comp{% +\item["-launch-method" \var{method}] +Specifies the mechanism used by normal bytecode executables to find the +interpreter. +The following methods are supported: +\begin{description} + \item["exe"] A small executable stub launcher contained in the + runtime-launch-info file is prepended to the bytecode image. + \item["sh"] A "#!" header is used. If the full path to the required + interpreter is not suitable for a "#!" line, a small shell script is + generated instead, using the "sh" interpreter found in "PATH". + \item["/path/interpreter"] As for "sh", but if the interpreter cannot be used + in "#!" line then "/path/intepreter" is used instead of searching for "sh" + in "PATH". +\end{description} +}%comp + \notop{% \item["-linkall"] Force all modules contained in libraries to be linked in. If this @@ -663,6 +688,23 @@ only recursive types where the recursion goes through an object type are supported. \notop{Note that once you have created an interface using this flag, you must use it again for all dependencies.} +\comp{% +\item["-runtime-search" \var{method}] +Controls whether the header used by normal bytecode executables is permitted to +search for the interpreter, or requires it to be at a fixed location. +The following methods are supported: +\begin{description} + \item["disable"] A fixed absolute path to the interpreter is used, and the + executable will not launch if the interpreter is not found at this location. + \item["fallback"] A fixed absolute path to the interpreter is used, but if the + executable cannot find the interpreter at this location then it will search + first in the directory containing the executable and then in "PATH". + \item["enable"] The executable searches for the interpreter first in the + directory containing the executable and then in "PATH". No absolute path to + the interpreter is recorded. +\end{description} +}%comp + \notop{% \item["-runtime-variant" \var{suffix}] Add the \var{suffix} string to the name of the runtime library used by diff --git a/ocaml-variants.opam b/ocaml-variants.opam index f233b07c27f2..af4115770a49 100644 --- a/ocaml-variants.opam +++ b/ocaml-variants.opam @@ -9,6 +9,7 @@ authors: [ "Alain Frisch" "Jacques Garrigue" "Didier Rémy" + "KC Sivaramakrishnan" "Jérôme Vouillon" ] homepage: "https://github.com/ocaml/ocaml/" @@ -76,6 +77,9 @@ build: [ "--prefix=%{prefix}%" "--docdir=%{doc}%/ocaml" "--with-additional-stublibsdir" + "--with-relative-libdir" + "--enable-runtime-search" + "--enable-runtime-search-target=fallback" "--with-flexdll=%{flexdll:share}%" {os = "win32" & flexdll:installed} "--with-winpthreads-msvc=%{winpthreads:share}%" {system-msvc:installed} "-C" diff --git a/ocamltest/ocaml_actions.ml b/ocamltest/ocaml_actions.ml index 4c261d93144f..30ef16c15d5a 100644 --- a/ocamltest/ocaml_actions.ml +++ b/ocamltest/ocaml_actions.ml @@ -629,7 +629,9 @@ let mklib log env = Ocaml_commands.ocamlrun_ocamlmklib; "-ocamlc '" ^ ocamlc_command ^ "'"; "-o " ^ program - ] @ modules env in + ] @ (if Ocamltest_config.suffixing then ["-suffixed"] else []) + @ modules env + in let expected_exit_status = 0 in let exit_status = Actions_helpers.run_cmd diff --git a/ocamltest/ocamltest_config.ml.in b/ocamltest/ocamltest_config.ml.in index 112976238ef0..03a3ca7b8052 100644 --- a/ocamltest/ocamltest_config.ml.in +++ b/ocamltest/ocamltest_config.ml.in @@ -97,3 +97,5 @@ let frame_pointers = @frame_pointers@ let tsan = @tsan@ let has_relative_libdir = @target_libdir_is_relative@ + +let suffixing = @suffixing@ diff --git a/ocamltest/ocamltest_config.mli b/ocamltest/ocamltest_config.mli index d4a6276d2227..ec5f97abf468 100644 --- a/ocamltest/ocamltest_config.mli +++ b/ocamltest/ocamltest_config.mli @@ -136,3 +136,7 @@ val tsan : bool val has_relative_libdir : bool (** Whether the compiler has been configured using --with-relative-libdir *) + +val suffixing : bool +(** Whether C stub library filenames are being mangled with the Bytecode + Runtime ID and {!Config.target}. *) diff --git a/otherlibs/Makefile.otherlibs.common b/otherlibs/Makefile.otherlibs.common index 984c2ea64224..4ffb9fd63f8a 100644 --- a/otherlibs/Makefile.otherlibs.common +++ b/otherlibs/Makefile.otherlibs.common @@ -35,6 +35,9 @@ ifeq "$(FLAMBDA)" "true" OPTCOMPFLAGS += -O3 endif MKLIB=$(OCAMLRUN) $(ROOTDIR)/tools/ocamlmklib$(EXE) +ifeq "$(SUFFIXING)" "true" +MKLIB += -suffixed +endif # Variables that must be defined by individual libraries: # LIBNAME @@ -52,8 +55,13 @@ CAMLOBJS_NAT ?= $(CAMLOBJS:.cmo=.cmx) CLIBNAME ?= $(LIBNAME) ifeq "$(C_SOURCES)" "" -STUBSLIB= + +STUBSLIB_BYTECODE= +STUBSLIB_NATIVE= +STUBSDLL= + else + COBJS_BYTECODE = $(C_SOURCES:.c=.b.$(O)) COBJS_NATIVE = $(C_SOURCES:.c=.n.$(O)) COBJS = $(COBJS_BYTECODE) $(COBJS_NATIVE) @@ -62,6 +70,12 @@ CLIBNAME_BYTECODE=$(CLIBNAME)byt CLIBNAME_NATIVE=$(CLIBNAME)nat STUBSLIB_BYTECODE=lib$(CLIBNAME_BYTECODE).$(A) STUBSLIB_NATIVE=lib$(CLIBNAME_NATIVE).$(A) + +ifeq "$(SUFFIXING)" "true" +STUBSDLL=dll$(CLIBNAME_BYTECODE)-$(TARGET)-$(BYTECODE_RUNTIME_ID)$(EXT_DLL) +else +STUBSDLL=dll$(CLIBNAME_BYTECODE)$(EXT_DLL) +endif endif .PHONY: all allopt opt.opt # allopt and opt.opt are synonyms @@ -98,10 +112,10 @@ lib$(CLIBNAME_NATIVE).$(A): $(COBJS) INSTALL_LIBDIR_LIBNAME = $(INSTALL_LIBDIR)/$(LIBNAME) install:: +ifneq "$(STUBSLIB_BYTECODE)" "" ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" - $(INSTALL_PROG) dll$(CLIBNAME_BYTECODE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)" + $(INSTALL_PROG) $(STUBSDLL) "$(INSTALL_STUBLIBDIR)" endif -ifneq "$(STUBSLIB_BYTECODE)" "" $(INSTALL_DATA) $(STUBSLIB_BYTECODE) "$(INSTALL_LIBDIR)/" endif # If installing over a previous OCaml version, ensure the library is removed @@ -131,9 +145,10 @@ installopt: if test -f $(LIBNAME).cmxs; then \ $(INSTALL_PROG) $(LIBNAME).cmxs "$(INSTALL_LIBDIR_LIBNAME)"; \ fi -ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" - $(INSTALL_PROG) dll$(CLIBNAME_NATIVE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)" -endif + if test -f dll$(CLIBNAME_NATIVE)$(EXT_DLL); then \ + $(INSTALL_PROG) \ + dll$(CLIBNAME_NATIVE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)"; \ + fi ifneq "$(STUBSLIB_NATIVE)" "" $(INSTALL_DATA) $(STUBSLIB_NATIVE) "$(INSTALL_LIBDIR)/" endif diff --git a/otherlibs/dynlink/byte/dynlink_symtable.ml b/otherlibs/dynlink/byte/dynlink_symtable.ml index e275a2856466..632df6f774fa 100644 --- a/otherlibs/dynlink/byte/dynlink_symtable.ml +++ b/otherlibs/dynlink/byte/dynlink_symtable.ml @@ -89,14 +89,24 @@ let primitives : (string, int) Hashtbl.t = Hashtbl.create 100 #52 "bytecomp/dll.ml" (* Extract the name of a DLLs from its external name (xxx.so or -lxxx) *) -let extract_dll_name file = - if Filename.check_suffix file Config.ext_dll then +let extract_dll_name (~suffixed, file) = + if not suffixed && Filename.check_suffix file Config.ext_dll then Filename.chop_suffix file Config.ext_dll - else if String.length file >= 2 && String.sub file 0 2 = "-l" then - "dll" ^ String.sub file 2 (String.length file - 2) else - file (* will cause error later *) -#100 "otherlibs/dynlink/byte/dynlink_symtable.ml" + let file = + if String.starts_with ~prefix:"-l" file then + "dll" ^ String.sub file 2 (String.length file - 2) + else + file + in + if suffixed then +#104 "otherlibs/dynlink/byte/dynlink_symtable.ml" + (* This name must be in sync with Misc.RuntimeID.stubslib *) + Printf.sprintf "%s-%s-%s" file Config.target Config.bytecode_runtime_id +#66 "bytecomp/dll.ml" + else + file +#110 "otherlibs/dynlink/byte/dynlink_symtable.ml" (* Specialized version of [Dll.{open_dll,open_dlls,find_primitive}] for the execution mode. *) let open_dll name = @@ -233,12 +243,12 @@ let patch_object buff patchlist = (* Functions for toplevel use *) (* Update the in-core table of globals *) -#237 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#247 "otherlibs/dynlink/byte/dynlink_symtable.ml" module Meta = struct #16 "bytecomp/meta.ml" external global_data : unit -> Obj.t array = "caml_get_global_data" external realloc_global_data : int -> unit = "caml_realloc_global" -#242 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#252 "otherlibs/dynlink/byte/dynlink_symtable.ml" end #332 "bytecomp/symtable.ml" let update_global_table () = @@ -264,7 +274,7 @@ external get_bytecode_sections : unit -> bytecode_sections = let init_toplevel () = let sect = get_bytecode_sections () in global_table := sect.symb; -#268 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#278 "otherlibs/dynlink/byte/dynlink_symtable.ml" Dll.init ~dllpaths:sect.dlpt ~prims:sect.prim; #358 "bytecomp/symtable.ml" sect.crcs @@ -317,7 +327,7 @@ let current_state () = !global_table #412 "bytecomp/symtable.ml" let hide_additions (st : global_map) = if st.cnt > !global_table.cnt then -#321 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#331 "otherlibs/dynlink/byte/dynlink_symtable.ml" failwith "Symtable.hide_additions"; #415 "bytecomp/symtable.ml" global_table := diff --git a/otherlibs/dynlink/byte/dynlink_symtable.mli b/otherlibs/dynlink/byte/dynlink_symtable.mli index 686cb4c44d41..cb3f047d5e20 100644 --- a/otherlibs/dynlink/byte/dynlink_symtable.mli +++ b/otherlibs/dynlink/byte/dynlink_symtable.mli @@ -31,7 +31,7 @@ module Global : sig val description: Format.formatter -> t -> unit end -val open_dlls : string list -> unit +val open_dlls : (suffixed:bool * string) list -> unit val patch_object: (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t -> diff --git a/otherlibs/dynlink/dynlink_config.ml.in b/otherlibs/dynlink/dynlink_config.ml.in index 60132fe50356..e51fa45bc383 100644 --- a/otherlibs/dynlink/dynlink_config.ml.in +++ b/otherlibs/dynlink/dynlink_config.ml.in @@ -24,3 +24,7 @@ let ext_dll = "." ^ {@QS@|@SO@|@QS@} and cmo_magic_number = {@QS@|@CMO_MAGIC_NUMBER@|@QS@} and cma_magic_number = {@QS@|@CMA_MAGIC_NUMBER@|@QS@} and cmxs_magic_number = {@QS@|@CMXS_MAGIC_NUMBER@|@QS@} + +let bytecode_runtime_id = {@QS@|@bytecode_runtime_id@|@QS@} + +let target = {@QS@|@target@|@QS@} diff --git a/otherlibs/dynlink/dynlink_config.mli b/otherlibs/dynlink/dynlink_config.mli index ad44848ebc20..30dbdf4df6bf 100644 --- a/otherlibs/dynlink/dynlink_config.mli +++ b/otherlibs/dynlink/dynlink_config.mli @@ -21,3 +21,7 @@ val ext_dll: string val cmo_magic_number: string val cma_magic_number: string val cmxs_magic_number: string + +val bytecode_runtime_id: string + +val target : string diff --git a/otherlibs/systhreads/Makefile b/otherlibs/systhreads/Makefile index f48b2cbc1eff..2b97c9c62c83 100644 --- a/otherlibs/systhreads/Makefile +++ b/otherlibs/systhreads/Makefile @@ -27,6 +27,12 @@ CAMLC=$(BEST_OCAMLC) $(LIBS) CAMLOPT=$(BEST_OCAMLOPT) $(LIBS) MKLIB=$(OCAMLRUN) $(ROOTDIR)/tools/ocamlmklib$(EXE) +ifeq "$(SUFFIXING)" "true" +MKLIB += -suffixed +DLLTHREADS = dllthreads-$(TARGET)-$(BYTECODE_RUNTIME_ID)$(EXT_DLL) +else +DLLTHREADS = dllthreads$(EXT_DLL) +endif COMPFLAGS=-w +33..39 -warn-error +A -g -bin-annot ifeq "$(FLAMBDA)" "true" OPTCOMPFLAGS += -O3 @@ -101,7 +107,7 @@ INSTALL_THREADSLIBDIR=$(INSTALL_LIBDIR)/$(LIBNAME) install: ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" - $(INSTALL_PROG) dllthreads$(EXT_DLL) "$(INSTALL_STUBLIBDIR)" + $(INSTALL_PROG) $(DLLTHREADS) "$(INSTALL_STUBLIBDIR)" endif $(INSTALL_DATA) libthreads.$(A) "$(INSTALL_LIBDIR)" $(MKDIR) "$(INSTALL_THREADSLIBDIR)" diff --git a/release-info/howto.md b/release-info/howto.md index 3b67474ac676..79b75326e0a8 100644 --- a/release-info/howto.md +++ b/release-info/howto.md @@ -165,6 +165,10 @@ git branch $BRANCH # update build-aux/ocaml_version.m4 with the new future branch, # 4.07.0+dev1-2018-06-26 => 4.08.0+dev0-2018-06-30 +# Also increment OCAML__RELEASE_NUMBER in build-aux/ocaml_version.m4 +# If the major version number is changing, the logic in p_runtime_id in +# tools/objinfo.ml mapping release number to major/minor version will need +# altering. # Update ocaml-variants.opam with new version. make -B configure # Add a "Working version" section" to Changes diff --git a/runtime/Mangling.md b/runtime/Mangling.md new file mode 100644 index 000000000000..fd2f7c30abb0 --- /dev/null +++ b/runtime/Mangling.md @@ -0,0 +1,135 @@ +# Filename Mangling + +## Background + +OCaml compiler installations exist in isolation. When running the compiler, it +is assumed that the caller will have configured the environment of the compiler +such that files and settings related to other compiler installations will not +interfere. + +This is not true of the runtime. Shared libraries are loaded from a global +namespace (dynamically loaded bytecode stub libraries and the shared versions of +both the native and bytecode runtimes) and programs may be searched in a global +PATH. To allow programs compiled against different coinstalled versions of the +runtime to be executed, a name mangling scheme is used for the runtime's +executables and shared libraries. + +## Filename Mangling + +Filenames are mangled using one or both of two pieces of configuration +information. The first is the standard "autoconf" triplet on which the runtime +executes (e.g. `x86_64-pc-linux-gnu`). The other is a summary of the runtime +version and configuration called the Runtime ID. This information is a series of +bits encoded in base32 using the alphabet `[0-9a-v]` and with the quintets laid +out little-endian. + +Mangling is applied to the name of any file which will be searched for at +runtime: + +- `ocamlrun` (and variants) are triplet-prefixed and Bytecode-suffixed. For + example, `x86_64-pc-linux-gnu-ocamlrun-a140` is OCaml 5.5 configured with + `--disable-flat-float-array` on 64-bit Intel/AMD Linux. A symbolic link is + still created for `ocamlrun` pointing to this mangled name. Additionally, a + symbolic link is also created for `ocamlrun-a140`, using the Zinc-suffix. +- C stub libraries loaded by both the bytecode runtime and bytecode `Dynlink` + library are triplet- and Bytecode-suffixed. For example, + `dllunixbyt-x86_64-pc-linux-gnu-a140.so` contains the C stubs for the Unix + library for OCaml 5.5 configured with `--disable-flat-float-array` on 64-bit + Intel/AMD Linux. +- Shared versions of the bytecode and native runtimes (`libcamlrun_shared.so` + and `libasmrun_shared.so`) are triplet- and Bytecode/Native-suffixed + respectively. For example, `libasmrun-x86_64-pc-linux-gnu-a1k0.so` and + `libcamlrun-x86_64-pc-linux-gnu-a140.so` are OCaml 5.5 configured with + `--disable-flat-float-array` and `--enable-tsan` on 64-bit Intel/AMD Linux + (note the **tsan** bit not being set for the name of libcamlrun). + Additionally, symbolic links are also created for `libasmrun_shared.so` and + `libcamlrun_shared.so`. + +## Runtime ID + +A Runtime ID is a bit string describing a given OCaml runtime. At present, +20 bits are used, but the format is intended to be trivially extensible. +Ultimately, the only requirement is that each version and configuration +generates some kind of unique identifier which can then be used in filenames. + +- Bit 0 (**dev**): Development bit. This should be set for development versions + of OCaml or for customised compilers. If it is not set, the compiler should be + an unaltered official release. +- Bits 1-6 (**release**): OCaml release number. This is incremented for each + minor release of the compiler, with OCaml 3.12.0[^1] being release 0. At + present, the ordering of release numbers matches the semantic ordering of the + version numbers, but this is not guaranteed and should not be assumed[^2]. +- Bits 7-11 (**reserved**): Number of reserved bits in the OCaml value header. + This is the number passed to `--enable-reserved-header-bits` when the compiler + distribution was configured. +- Bit 12 (**no-flat-float-array**): Set if the compiler distribution was + configured with `--disable-flat-float-array`. +- Bit 13 (**fp**): Set if the compiler distribution was configured with + `--enable-frame-pointers`. Affects the **native** runtime only. +- Bit 14 (**tsan**): For OCaml 5.2 onwards, set if the compiler distribution was + configured with `--enable-tsan`. Prior to OCaml 5.2, set if the compiler + distribution was configured with `--enable-spacetime` (this option was removed + in OCaml 4.12, meaning this bit is always unset for OCaml 4.12-5.1). Affects + the **native** runtime only. +- Bit 15 (**int31**): Set if the runtime uses 31-bit `int` values (i.e. runtimes + running on 32-bit systems). +- Bit 16 (**static**): Set if the runtime does not support shared libraries, + meaning dynamic loading of C code is not supported in bytecode, and native + dynlink is not supported at all. +- Bit 17 (**no-compression**): For OCaml 5.1 onwards, set if the runtime does + not support compressed marshalling. Prior to OCaml 5.1, set if the compiler + distribution was configured with `--enable-naked-pointers` (this bit was + always unset for OCaml 5.0, since it supports neither naked pointers nor + compressed marshalling). +- Bit 18 (**ansi**): Set if the compiler distribution was configured with the + legacy support `WINDOWS_UNICODE=ansi`. +- Bit 19 (**mutable-string**): Set if the compiler distribution was configured + with `--disable-force-safe-string`. This option was removed in OCaml 5.0, and + the bit is available for re-use. When this bit is unset, strings are + guaranteed to be immutable. + +The bit descriptions are designed such that the default configuration of the +latest version of the compiler has unset bits. The ordering of the bits is +designed to mean ID values in the same version of OCaml will usually have the +same opening sequence of characters (since `--enable-reserved-header-bits` is +now rarely used) and laying out the characters little-endian in the mangling +scheme means that the opening two characters of the Runtime ID define its +version (and consequently its length, should that change in future). + +[^1]: OCaml 3.12.0 was the first version where `ocamlrun` supported the `-vnum` +argument; the original author had a fantasy of backporting the scheme to the +entire 4.x series, but following some therapy stopped at 4.08. The release +numbering persists to allow for future madness. +[^2]: In particular, should there be any additional releases in the OCaml 4.x +series, these will have higher release numbers than releases already made in the +OCaml 5.x series. + +## Masks + +A particular configuration of the compiler has one Runtime ID, but this is used +in three different contexts where certain bits are masked out: + +1. _Bytecode Mask_: masks out bits which are only ever set by the native runtime + (at present, **fp** and **tsan**). +2. _Native Mask_: masks out bits which are only ever set by the bytecode runtime + (at present there aren't any). +3. _Zinc Mask_: masks out bits which are not related to bytecode portability. + Where the _Bytecode_ and _Native_ masks relate to _runtimes_, the _Zinc_ mask + relates to _bytecode images_. The Zinc ID therefore includes: + - **release** and **dev** (a given bytecode image targets a specific version + of OCaml) + - **no-flat-float-array** (code compiled assuming that float arrays are boxed + will segfault on runtimes which unbox them) + - **int31**, **static**, and **no-compression** (a bytecode image using + 63-bit integers, dynamically loaded C stubs and compressed marshalling will + be rejected by an interpreter which doesn't support any of these features) + +Note that the inclusion of a bit in a mask is determined by whether that +property affects the ability to load and execute the code, rather than whether +it is semantically affected by it. For example, the **reserved** bits affects +the value representation, and therefore both runtimes. It does not directly +affect bytecode (although a bytecode program may use unsafe features to observe +it). **reserved** is therefore part of both the _Bytecode_ and _Native Masks_, +but not part of the _Zinc Mask_. Similarly, although **no-flat-float-array** +affects code generation for bytecode, **mutable-string** never did, and so would +not be included in the _Zinc Mask_. diff --git a/runtime/caml/s.h.in b/runtime/caml/s.h.in index 8c6b6b6af999..7945dcd1801c 100644 --- a/runtime/caml/s.h.in +++ b/runtime/caml/s.h.in @@ -72,6 +72,8 @@ #undef HAS_TIMES +#undef HAS_STRLCPY + #undef HAS_SECURE_GETENV #undef HAS___SECURE_GETENV diff --git a/runtime/caml/version.h.in b/runtime/caml/version.h.in index bfe5d70957dc..66c7611bc4a5 100644 --- a/runtime/caml/version.h.in +++ b/runtime/caml/version.h.in @@ -22,3 +22,4 @@ #undef OCAML_VERSION_EXTRA #undef OCAML_VERSION #undef OCAML_VERSION_STRING +#undef OCAML_RELEASE_NUMBER diff --git a/runtime/dynlink.c b/runtime/dynlink.c index 460c4385c54d..9d45f4156d05 100644 --- a/runtime/dynlink.c +++ b/runtime/dynlink.c @@ -233,10 +233,24 @@ CAMLprim value caml_dynlink_parse_ld_conf(value vstdlib) Abort on error. */ static void open_shared_lib(char_os * name) { - char_os * realname; + char_os * realname, * suffixed = NULL; char * u8; void * handle; + if (*name == '\0') + caml_fatal_error("corrupt DLLS section"); + + if (*name == '-') { + char * suffix = + caml_stat_strconcat(4, "-", HOST, "-", BYTECODE_RUNTIME_ID); + char_os * suffix_os = caml_stat_strdup_to_os(suffix); + name = suffixed = caml_stat_strconcat_os(2, name + 1, suffix_os); + caml_stat_free(suffix_os); + caml_stat_free(suffix); + } else { + name++; + } + realname = caml_search_dll_in_path(&caml_shared_libs_path, name); u8 = caml_stat_strdup_of_os(realname); caml_gc_message(0x100, "Loading shared library %s\n", u8); @@ -253,6 +267,7 @@ static void open_shared_lib(char_os * name) caml_dlerror() ); caml_ext_table_add(&shared_libs, handle); + caml_stat_free(suffixed); caml_stat_free(realname); } diff --git a/runtime/startup_byt.c b/runtime/startup_byt.c index 9e4ebf263120..57a0b949dd0a 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -399,6 +399,7 @@ static void do_print_config(void) printf("word_size: %d\n", 8 * (int)sizeof(value) - 1); printf("os_type: %s\n", OCAML_OS_TYPE); printf("host: %s\n", HOST); + printf("bytecode_runtime_id: %s\n", BYTECODE_RUNTIME_ID); printf("flat_float_array: %s\n", #ifdef FLAT_FLOAT_ARRAY "true"); diff --git a/stdlib/Makefile b/stdlib/Makefile index 3f831625292e..1e775f4fd17d 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -54,7 +54,7 @@ NOSTDLIB= camlinternalFormatBasics.cmo stdlib.cmo OTHERS=$(filter-out $(NOSTDLIB),$(OBJS)) .PHONY: all -all: stdlib.cma std_exit.cmo $(HEADER_NAME) target_$(HEADER_NAME) +all: stdlib.cma std_exit.cmo $(HEADER_NAME) .PHONY: allopt opt.opt # allopt and opt.opt are synonyms allopt: stdlib.cmxa std_exit.cmx @@ -73,7 +73,7 @@ ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" *.cmt *.cmti *.mli *.ml *.ml.in \ "$(INSTALL_LIBDIR)" endif - $(INSTALL_DATA) target_$(HEADER_NAME) "$(INSTALL_LIBDIR)/$(HEADER_NAME)" + $(INSTALL_DATA) $(HEADER_NAME) "$(INSTALL_LIBDIR)/$(HEADER_NAME)" .PHONY: installopt installopt: installopt-default @@ -84,8 +84,10 @@ installopt-default: stdlib.cmxa stdlib.$(A) std_exit.$(O) *.cmx \ "$(INSTALL_LIBDIR)" -%-launch-info: %.info tmpheader.exe - @cat $^ > $@ +MANGLING = $(filter true,$(SUFFIXING)) +runtime-launch-info: tmpheader.exe + @{ printf '$(if $(MANGLING),$(ZINC_RUNTIME_ID),\000)'; \ + cat $^; } > $@ # The mingw-w64 and MSVC versions of tmpheader.exe are linked with special flags # to reduce their size (considerably). In particular, the entry point is @@ -135,11 +137,11 @@ stdlib.cmxa: $(OBJS:.cmo=.cmx) .PHONY: distclean distclean: clean - rm -f sys.ml META runtime.info target_runtime.info + rm -f sys.ml META .PHONY: clean clean:: - rm -f $(HEADER_NAME) target_$(HEADER_NAME) + rm -f $(HEADER_NAME) export AWK diff --git a/stdlib/header.c b/stdlib/header.c index 8ef80b057037..e42f9f78d513 100644 --- a/stdlib/header.c +++ b/stdlib/header.c @@ -22,14 +22,28 @@ #define NORETURN _Noreturn #endif +#include + #ifdef _WIN32 #define STRICT #define WIN32_LEAN_AND_MEAN #include +typedef wchar_t char_os; +typedef wchar_t * argv_t; +#define T(x) L ## x +#define Is_separator(c) (c == '\\' || c == '/') +#define Directory_separator_character T('\\') +#define ITOL(i) L ## #i +#define ITOT(i) ITOL(i) +#define PATH_NAME L"%Path%" + #if WINDOWS_UNICODE #define CP CP_UTF8 +/* The characters in RNTM will be converted from UTF-8 to UTF-16. Parasitically, + there could be 4 bytes in RNTM for every wchar_t in the actual value. */ +#define RNTM_ENCODING_LENGTH 4 #else #define CP CP_ACP #endif @@ -45,6 +59,8 @@ typedef HANDLE file_descriptor; +#define unsafe_copy(dst, src, dstsize) lstrcpy(dst, src) + static int read(HANDLE h, LPVOID buffer, DWORD buffer_size) { DWORD nread = 0; @@ -60,6 +76,41 @@ static BOOL WINAPI ctrl_handler(DWORD event) return FALSE; } +static int exec_file(wchar_t *file, wchar_t *cmdline) +{ + wchar_t truename[MAX_PATH]; + STARTUPINFO stinfo; + PROCESS_INFORMATION procinfo; + DWORD retcode; + + if (SearchPath(NULL, file, L".exe", sizeof(truename)/sizeof(wchar_t), + truename, NULL)) { + /* Need to ignore ctrl-C and ctrl-break, otherwise we'll die and take the + underlying OCaml program with us! */ + SetConsoleCtrlHandler(ctrl_handler, TRUE); + + stinfo.cb = sizeof(stinfo); + stinfo.lpReserved = NULL; + stinfo.lpDesktop = NULL; + stinfo.lpTitle = NULL; + stinfo.dwFlags = 0; + stinfo.cbReserved2 = 0; + stinfo.lpReserved2 = NULL; + if (CreateProcess(truename, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, + &stinfo, &procinfo)) { + CloseHandle(procinfo.hThread); + WaitForSingleObject(procinfo.hProcess, INFINITE); + GetExitCodeProcess(procinfo.hProcess, &retcode); + CloseHandle(procinfo.hProcess); + ExitProcess(retcode); + } else { + return ENOEXEC; + } + } else { + return ENOENT; + } +} + static void write_error(const wchar_t *wstr, HANDLE hOut) { DWORD consoleMode, numwritten, len; @@ -90,12 +141,17 @@ NORETURN static void exit_with_error(const wchar_t *wstr1, #else +#include "caml/s.h" + #include #include #include #include #include #include +#ifdef HAS_LIBGEN_H +#include +#endif #include #include @@ -106,6 +162,24 @@ NORETURN static void exit_with_error(const wchar_t *wstr1, typedef int file_descriptor; +typedef char char_os; +typedef char ** argv_t; +#define T(x) x +#define Is_separator(c) (c == '/') +#define Directory_separator_character '/' +#define ITOL(x) #x +#define ITOT(x) ITOL(x) +#define PATH_NAME "$PATH" + +#ifdef HAS_STRLCPY +/* The macro is named unsafe_copy because although it requires a dstsize + argument which _may_ be passed to strlcpy, there are platforms where the + underlying operation is unsafe and will ignore dstsize. */ +#define unsafe_copy strlcpy +#else +#define unsafe_copy(dst, src, dstsize) strcpy(dst, src) +#endif + #ifndef __CYGWIN__ /* Normal Unix search path function */ @@ -198,8 +272,16 @@ NORETURN static void exit_with_error(const char *str1, exit(2); } +static int exec_file(const char *file, char * const argv[]) +{ + return (execvp(file, argv) == -1 ? errno : 0); +} + #endif /* defined(_WIN32) */ +#include "caml/version.h" +#define SHORT_VERSION ITOT(OCAML_VERSION_MAJOR) T(".") ITOT(OCAML_VERSION_MINOR) + #define CAML_INTERNALS #include "caml/exec.h" @@ -210,12 +292,15 @@ static uint32_t read_size(const char *ptr) ((uint32_t) p[2] << 8) | p[3]; } -static char * read_runtime_path(file_descriptor fd) +#ifndef RNTM_ENCODING_LENGTH +#define RNTM_ENCODING_LENGTH 1 +#endif + +static char * read_runtime_path(file_descriptor fd, uint32_t *rntm_strlen) { char buffer[TRAILER_SIZE]; - static char runtime_path[PATH_MAX]; + static char runtime_path[PATH_MAX * RNTM_ENCODING_LENGTH]; int num_sections; - uint32_t path_size; long ofs; if (lseek(fd, -TRAILER_SIZE, SEEK_END) == -1) return NULL; @@ -223,91 +308,172 @@ static char * read_runtime_path(file_descriptor fd) num_sections = read_size(buffer); ofs = TRAILER_SIZE + num_sections * 8; if (lseek(fd, -ofs, SEEK_END) == -1) return NULL; - path_size = 0; + *rntm_strlen = 0; for (int i = 0; i < num_sections; i++) { if (read(fd, buffer, 8) < 8) return NULL; if (buffer[0] == 'R' && buffer[1] == 'N' && buffer[2] == 'T' && buffer[3] == 'M') { - path_size = read_size(buffer + 4); - ofs += path_size; - } else if (path_size > 0) + *rntm_strlen = read_size(buffer + 4); + ofs += *rntm_strlen; + } else if (*rntm_strlen > 0) ofs += read_size(buffer + 4); } - if (path_size == 0) return NULL; - if (path_size >= PATH_MAX) return NULL; + if (*rntm_strlen == 0) return NULL; + /* The last character of runtime_path must be '\0', so RNTM must be strictly + less than PATH_MAX */ + if (*rntm_strlen >= PATH_MAX * RNTM_ENCODING_LENGTH) return NULL; if (lseek(fd, -ofs, SEEK_END) == -1) return NULL; - if (read(fd, runtime_path, path_size) != path_size) return NULL; + if (read(fd, runtime_path, *rntm_strlen) != *rntm_strlen) return NULL; + return runtime_path; } +/* rntm points to a buffer containing rntm_bsz characters consisting of the + decoded content of the RNTM section (which may include NUL characters) and an + additional NUL "terminator". + RNTM is either [\0] or []\0 + Decode rntm and search for a runtime (using argv0_dirname if non-NULL and + required) and exec the first runtime found passing argv. */ +NORETURN void search_and_exec_runtime(char_os *rntm, uint32_t rntm_bsz, + argv_t argv, char_os *argv0_dirname) +{ + /* rntm_end points to the NUL "terminator" of rntm (_not_ the last character + of the RNTM section */ + const char_os *rntm_end = rntm + (rntm_bsz - 1); + + char_os *rntm_bindir_end = rntm; + + /* Scan for the first NUL character in rntm (there is always one) */ + while (*rntm_bindir_end != 0) + rntm_bindir_end++; + + /* The first character of rntm is NUL for Enable mode */ + if (*rntm != 0) { + /* For Disable mode, there is no NUL in RNTM, so rntm_bindir_end points to + the terminator pointed to by rntm_end. For Fallback, there is a NUL in + the middle of the RNTM "string", which rntm_bindir_end points at. Change + that to a directory separator, so that rntm now points to a + NUL-terminated full path we can attempt to exec. */ + if (rntm_bindir_end != rntm_end) + *rntm_bindir_end = Directory_separator_character; + int status = exec_file(rntm, argv); + /* exec failed. For Disable mode, there's nothing else to be tried. For + Fallback, if the failure was for any other reason than ENOENT then there + is also nothing else to be tried. */ + if (rntm_bindir_end == rntm_end || status != ENOENT) + exit_with_error(T("Cannot exec "), rntm, NULL); + } + + /* Shift rntm to point to */ + rntm = rntm_bindir_end + 1; + if (rntm < rntm_end) { + /* Searching takes place first in the directory containing this executable, + if it's known. */ + if (argv0_dirname != NULL) { + char_os root[PATH_MAX]; + unsafe_copy(root, argv0_dirname, PATH_MAX); + + /* Ensure root ends with a directory separator. root_basename points to + the character at which to place */ + char_os *root_basename = root; + while (*root_basename != 0) + root_basename++; + if (root_basename > root && !Is_separator(*(root_basename - 1))) + *root_basename++ = Directory_separator_character; + + /* If there isn't enough space to copy rntm to root then simply skip this + check (e.g. an executable called b.exe in a very long directory name). + (root_basename - root) is strlen_os(root) and likewise + (rntm_end - rntm) is strlen_os(rntm). */ + if ((rntm_end - rntm) <= PATH_MAX - (root_basename - root) - 1) { + unsafe_copy(root_basename, rntm, PATH_MAX - (root_basename - root)); + if (exec_file(root, argv) != ENOENT) + exit_with_error(T("Cannot exec "), root, NULL); + } + } + + /* Otherwise, search in PATH */ + if (exec_file(rntm, argv) != ENOENT) + exit_with_error(T("Cannot exec "), rntm, NULL); + } + + /* If we get here, we've failed... */ + exit_with_error(T("This program requires OCaml ") SHORT_VERSION T("\n") + T("Interpreter ("), (rntm_bindir_end + 1), + T(") not found alongside the program or in " PATH_NAME)); +} + #ifdef _WIN32 NORETURN void __cdecl wmainCRTStartup(void) { + wchar_t module[MAX_PATH]; wchar_t truename[MAX_PATH]; + uint32_t rntm_strlen = 0, rntm_bsz = 0; char *runtime_path; - wchar_t wruntime_path[MAX_PATH]; + wchar_t wruntime_path[MAX_PATH], *dirname; HANDLE h; - STARTUPINFO stinfo; - PROCESS_INFORMATION procinfo; - DWORD retcode; - if (GetModuleFileName(NULL, truename, sizeof(truename)/sizeof(wchar_t)) == 0) + if (GetModuleFileName(NULL, module, sizeof(module)/sizeof(wchar_t)) == 0) exit_with_error(L"Out of memory", NULL, NULL); - h = CreateFile(truename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + h = CreateFile(module, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - if (h == INVALID_HANDLE_VALUE || - (runtime_path = read_runtime_path(h)) == NULL || - !MultiByteToWideChar(CP, 0, runtime_path, -1, wruntime_path, - sizeof(wruntime_path)/sizeof(wchar_t))) + + + /* read_runtime_path returns the actual size of RNTM, but the buffer returned + is guaranteed to have a null character following the final character of + RNTM. */ + if (h == INVALID_HANDLE_VALUE + || (runtime_path = read_runtime_path(h, &rntm_strlen)) == NULL + || (rntm_bsz = + MultiByteToWideChar(CP, 0, runtime_path, rntm_strlen + 1, + wruntime_path, + sizeof(wruntime_path)/sizeof(wchar_t))) == 0 + || GetFullPathName(module, sizeof(truename)/sizeof(wchar_t), truename, + &dirname) >= sizeof(truename)/sizeof(wchar_t)) exit_with_error(NULL, truename, L" not found or is not a bytecode executable file"); CloseHandle(h); - if (SearchPath(NULL, wruntime_path, L".exe", sizeof(truename)/sizeof(wchar_t), - truename, NULL)) { - /* Need to ignore ctrl-C and ctrl-break, otherwise we'll die and take - the underlying OCaml program with us! */ - SetConsoleCtrlHandler(ctrl_handler, TRUE); - stinfo.cb = sizeof(stinfo); - stinfo.lpReserved = NULL; - stinfo.lpDesktop = NULL; - stinfo.lpTitle = NULL; - stinfo.dwFlags = 0; - stinfo.cbReserved2 = 0; - stinfo.lpReserved2 = NULL; - if (CreateProcess(truename, GetCommandLine(), NULL, NULL, TRUE, 0, - NULL, NULL, &stinfo, &procinfo)) { - CloseHandle(procinfo.hThread); - WaitForSingleObject(procinfo.hProcess, INFINITE); - GetExitCodeProcess(procinfo.hProcess, &retcode); - CloseHandle(procinfo.hProcess); - ExitProcess(retcode); - } + if (dirname) { + /* GetFullPathName leaves dirname pointing to the first character of the + basename, so setting that to NUL means the string pointed to by truename + is the dirname of the currently running executable with a trailing + separator (although search_and_exec_runtime will check that anyway) */ + *dirname = 0; + dirname = truename; } - exit_with_error(L"Cannot exec ", wruntime_path, NULL); + search_and_exec_runtime(wruntime_path, rntm_bsz, GetCommandLine(), dirname); } #else int main(int argc, char *argv[]) { - char *truename, *runtime_path; + char *truename, *runtime_path, *argv0_dirname; + uint32_t rntm_strlen = 0; int fd; truename = searchpath(argv[0]); fd = open(truename, O_RDONLY | O_BINARY); - if (fd == -1 || (runtime_path = read_runtime_path(fd)) == NULL) + if (fd == -1 || (runtime_path = read_runtime_path(fd, &rntm_strlen)) == NULL) exit_with_error(NULL, truename, " not found or is not a bytecode executable file"); close(fd); - argv[0] = truename; - execvp(runtime_path, argv); +#ifdef HAS_LIBGEN_H + argv0_dirname = dirname(strdup(truename)); +#else + argv0_dirname = NULL; +#endif - exit_with_error("Cannot exec ", runtime_path, NULL); + argv[0] = truename; + /* read_runtime_path returns the actual size of RNTM, but the buffer returned + is guaranteed to have a null character following the final character of + RNTM. */ + search_and_exec_runtime(runtime_path, rntm_strlen + 1, argv, argv0_dirname); } #endif /* defined(_WIN32) */ diff --git a/testsuite/in_prefix/Makefile.test b/testsuite/in_prefix/Makefile.test index 7d2a5900a027..7c968f8a93d8 100644 --- a/testsuite/in_prefix/Makefile.test +++ b/testsuite/in_prefix/Makefile.test @@ -27,6 +27,7 @@ DRIVER_ARGS = \ $(call bool_to_with, shebangscripts, $(SHEBANGSCRIPTS)) \ $(call bool_to_with, ocamlnat, $(INSTALL_OCAMLNAT)) \ $(call bool_to_with, ocamlopt, $(NATIVE_COMPILER)) \ + $(RUNTIME_SEARCH_FLAG) \ $(OTHERLIBRARIES) --pwd "$(SRCDIR_ABS)/testsuite/in_prefix" default: $(DRIVER) @@ -39,8 +40,30 @@ else VERBOSE_FLAG = endif -test-in-prefix: $(DRIVER) ../tools/main_in_c.$(O) ../tools/dump_load_path$(EXE) +# Generates --without-$(1) if $(2) is empty or --with-$(1)=$(2) otherwise +RUNTIME_SEARCH_FLAG = \ + $(if $(RUNTIME_SEARCH),$\ + --with-runtime-search=$(RUNTIME_SEARCH),$\ + --without-runtime-search) + +export PATH := $(SRCDIR_ABS)/testsuite/in_prefix/poisoned-runtime:$(PATH) + +ifeq "$(SUFFIXING)" "true" +RUNTIME_NAME = ocamlrun-$(ZINC_RUNTIME_ID)$(EXE) +else +RUNTIME_NAME = ocamlrun$(EXE) +endif + +test-in-prefix: $(DRIVER) ../tools/main_in_c.$(O) \ + ../tools/dump_load_path$(EXE) ../tools/poisonedruntime$(EXE) + @rm -f ocamlrun* + @$(LN) $(ROOTDIR)/runtime/ocamlrun$(EXE) test-$(RUNTIME_NAME) + @$(MKDIR) poisoned-runtime + @cd poisoned-runtime \ + && $(LN) ../../tools/poisonedruntime$(EXE) $(RUNTIME_NAME) @$< $(DRIVER_ARGS) + @rm -rf poisoned-runtime + @rm -f test-ocamlrun* SCRUB_ENV = \ CAML_LD_LIBRARY_PATH OCAMLLIB CAMLLIB OCAMLPARAM OCAMLRUNPARAM CAMLRUNPARAM diff --git a/testsuite/in_prefix/README.md b/testsuite/in_prefix/README.md index 35080d2da552..a7a063652a10 100644 --- a/testsuite/in_prefix/README.md +++ b/testsuite/in_prefix/README.md @@ -71,7 +71,8 @@ Exercises: Shims: - On Unix, the bytecode toplevel contains the absolute location of `ocamlrun`, - so must be explicitly invoked via `ocamlrun` + so must be explicitly invoked via `ocamlrun`, unless the compiler is + configured with `--enable-runtime-search` - Both toplevels contain the absolute location of the Standard Library, requiring `OCAMLLIB` to be set, unless the compiler was configured with `--with-relative-libdir` @@ -84,14 +85,13 @@ Shims: - For a bytecode-only build, `ocamlc` contains the absolute location of `ocamlrun`, so must be explicitly invoked via `ocamlrun` (if the native compiler is available, then both `ocamlc` and `ocamlopt` will be native - executables) + executables), unless the compiler is configured with `--enable-runtime-search` - Both compilers contain the absolute location of the Standard Library, requiring `OCAMLLIB` to be set, unless the comnpiler was configured with `--with-relative-libdir` - The executable created by `ocamlc` contains the absolute location of - `ocamlrun`, so must be both explicitly invoked via `ocamlrun` and also have - `CAML_LD_LIBRARY_PATH` or `OCAMLLIB` adjusted, as that `ocamlrun` will not be - able to find `ld.conf` + `ocamlrun`, so must be explicitly invoked via `ocamlrun`, unless the + compiler is configured with `--enable-runtime-search-target` ### Executing installed bytecode binaries with `-vnum` @@ -113,7 +113,8 @@ Exercises: Shims: - On builds with shared library support, all the executables will contain the - absolute location of `ocamlrun` and will fail to execute + absolute location of `ocamlrun` and will fail to execute, unless the compiler + was configured with `--enable-runtime-search` - On builds without shared library support, executables using libraries with C stubs (in particular, `ocamldebug` and `ocamldoc`) are compiled with `-custom` and do succeed @@ -151,6 +152,8 @@ Exercises: Shims: - As with the `Dynlink` test, on bytecode-only builds the compiler must be - explicitly invoked via `ocamlrun` + explicitly invoked via `ocamlrun`, unless the compiler was configured with + `--enable-runtime-search` - The executable produced by `ocamlc` by default contains the absolute location - of `ocamlrun` and so has to be run explicitly via `ocamlrun` + of `ocamlrun` and so has to be run explicitly via `ocamlrun`, unless the + compiler was configured with `--enable-runtime-search-target` diff --git a/testsuite/tools/cmdline.ml b/testsuite/tools/cmdline.ml index cb668079c1f2..b091f9c83177 100644 --- a/testsuite/tools/cmdline.ml +++ b/testsuite/tools/cmdline.ml @@ -104,10 +104,10 @@ let parse argv = in let config = ref {has_ocamlnat = false; has_ocamlopt = false; has_relative_libdir = None; - has_runtime_search = None; launcher_searches_for_ocamlrun = false; + has_runtime_search = Disable; launcher_searches_for_ocamlrun = false; target_launcher_searches_for_ocamlrun = false; bytecode_shebangs_by_default = false; shebangscripts = false; - libraries = []} + filename_mangling = false; libraries = []} in let error fmt = Printf.ksprintf (fun s -> raise (Arg.Bad s)) fmt in let check_tree () = @@ -163,16 +163,15 @@ let parse argv = config := {!config with shebangscripts} in let parse_search = function - | "enable" -> true - | "always" -> false + | Some "fallback" -> Config.Fallback + | Some "enable" -> Config.Enable + | None -> Config.Disable | _ -> raise (Arg.Bad - "--with-runtime-search: argument should be either enable or always") + "--with-runtime-search: argument should be either fallback or enable") in let has_runtime_search arg = - let has_runtime_search = Option.map parse_search arg in - if has_runtime_search <> None then - error "--with-runtime-search is not implemented!"; + let has_runtime_search = parse_search arg in config := {!config with has_runtime_search} in let args = Arg.align [ diff --git a/testsuite/tools/environment.ml b/testsuite/tools/environment.ml index 8f867887a8ae..108f14860c45 100644 --- a/testsuite/tools/environment.ml +++ b/testsuite/tools/environment.ml @@ -46,7 +46,7 @@ let libdir_suffix {libdir_suffix; _} = libdir_suffix (* Derived properties *) -let is_renamed {phase; _} = (phase = Renamed) +let is_renamed {phase; _} = (phase <> Original) let bindir {prefix; bindir_suffix; _} = Filename.concat prefix bindir_suffix @@ -67,19 +67,6 @@ let in_libdir env path = let in_test_root {test_root; _} path = Filename.concat test_root path -(* Reverse the quoting of single quotes done by Filename.quote on Unix (which is - used for the runtime name when embedded in sh-scripts. Any single quote - characters are transformed to "'\\''". If the string is split on the single - quote characters, the sequence ["\\"; ""] is a single quote character in the - unescaped version. *) -let dequote s = - let[@tail_mod_cons] rec loop = function - | "\\" :: "" :: rest -> "'" :: loop rest - | chunk :: rest -> chunk :: loop rest - | [] -> [] - in - String.concat "" (loop (String.split_on_char '\'' s)) - (* [classify_executable file] determines if [file] is : - Tendered bytecode with an executable header - Scripted bytecode invoking ocamlrun with a #! header @@ -91,49 +78,18 @@ let classify_executable file = try In_channel.with_open_bin file (fun ic -> let start = really_input_string ic 2 in - let is_RNTM = function - | Bytesections.{name = Name.RNTM; _} -> true - | _ -> false - in + let toc = Bytesections.read_toc ic in + let sections = Bytesections.all toc in let is_DLLS = function | Bytesections.{name = Name.DLLS; len} when len > 0 -> true | _ -> false in - let toc = Bytesections.read_toc ic in - let sections = Bytesections.all toc in - if start = "#!" then - let runtime = - seek_in ic 2; - let shebang = String.trim (input_line ic) in - if Filename.basename shebang = "sh" then - let exec_line = input_line ic in - if String.starts_with ~prefix:"exec '" exec_line - && String.ends_with ~suffix:"' \"$0\" \"$@\"" exec_line then - (* When the path to the runtime can't be directly used in a - shebang, the shell is used instead, the next line is then: - exec '' "$0" "$@" *) - dequote (String.sub exec_line 6 (String.length exec_line - 17)) - else - Harness.fail_because "%s contains an unexpected exec line: %S" - file exec_line - else - shebang - in - Tendered {header = Header_shebang; - dlls = List.exists is_DLLS sections; - runtime} - else if List.exists is_RNTM sections then - let rntm = - Bytesections.read_section_string toc ic Bytesections.Name.RNTM in - let len = String.length rntm in - if len = 0 || rntm.[len - 1] <> '\000' then - Harness.fail_because "%s contains corrupt RNTM: %S" file rntm; - let runtime = String.sub rntm 0 (len - 1) in - Tendered {header = Header_exe; - dlls = List.exists is_DLLS sections; - runtime} - else - Custom) + let tendered (runtime, id, search) = + let header = if start = "#!" then Header_shebang else Header_exe in + let dlls = List.exists is_DLLS sections in + Tendered {header; dlls; runtime; id; search} + in + Option.fold ~none:Custom ~some:tendered (Byterntm.read_runtime toc ic)) with End_of_file | Bytesections.Bad_magic_number -> Vanilla @@ -224,7 +180,7 @@ let make pp_path ~verbose ~test_root ~test_root_logical let value = String.sub binding (equals + 1) (String.length binding - equals - 1) in - if is_path_env name then + if phase <> Execution && is_path_env name then if Sys.win32 then if String.index_opt bindir ';' <> None then Printf.sprintf "%s=\"%s\";%s" name bindir value @@ -270,7 +226,7 @@ let string_of_process_status = function highlighted. If argv0 is specified, then the original program executable is also shown. *) let display_execution level status pid ~runtime program argv0 args - ({pp_path; verbose; serial; _} as env) = + ({pp_path; verbose; serial; phase; _} as env) = let pp_program style program f = function | Some argv0 -> Format.fprintf f "@{<%s>%s (from %a)@}" @@ -313,9 +269,11 @@ let display_execution level status pid ~runtime program argv0 args if serial <> !last_environment then begin last_environment := serial; Format.printf "\ - @{> @}@{Environment@}\n\ - @{> @} @{PATH=%a:$PATH@}\n" - pp_path (bindir env); + @{> @}@{Environment@}\n"; + if phase <> Execution then + Format.printf "\ + @{> @} @{PATH=%a:$PATH@}\n" + pp_path (bindir env); if not Sys.win32 then Format.printf "\ @{> @} @{%s=%a:$%s@}\n" @@ -472,9 +430,9 @@ let run_process ?(runtime = false) ?(stubs = false) ?(stdlib = false) (* The tests are easier to write with the assumption that shims are simply ignored in the Original phase (otherwise they all begin [Env.is_renamed env && (* ... *)] *) - let runtime = runtime && phase = Renamed in + let runtime = runtime && phase <> Original in let env = - if phase = Renamed && (stubs || stdlib) then + if phase <> Original && (stubs || stdlib) then apply_shims ~stubs ~stdlib env else env @@ -493,7 +451,7 @@ let run_process ?(runtime = false) ?(stubs = false) ?(stdlib = false) fails without each shim in turn. The final entry in the strategy must be the request itself. *) let test_without cond shim strategy = - if phase = Renamed && cond then + if phase <> Original && cond then shim env :: strategy else strategy diff --git a/testsuite/tools/environment.mli b/testsuite/tools/environment.mli index 192718ee3594..217b98f6d56d 100644 --- a/testsuite/tools/environment.mli +++ b/testsuite/tools/environment.mli @@ -37,7 +37,7 @@ val make : (Format.formatter -> string -> unit) -> verbose:bool with [LD_LIBRARY_PATH] / [DYLD_LIBRARY_PATH] set or updated). *) val is_renamed : t -> bool -(** [is_renamed t] if [~phase = Renamed] *) +(** [is_renamed t] if [~phase <> Original] *) val test_root : t -> string (** Retrieves the [~test_root] passed to {!make}. *) diff --git a/testsuite/tools/harness.ml b/testsuite/tools/harness.ml index 6cac18613f36..f57f8e7e1c1d 100644 --- a/testsuite/tools/harness.ml +++ b/testsuite/tools/harness.ml @@ -82,11 +82,15 @@ module Import = struct type launch_mode = Header_exe | Header_shebang type executable = - | Tendered of {header: launch_mode; dlls: bool; runtime: string} + | Tendered of {header: launch_mode; + dlls: bool; + runtime: string; + id: Misc.RuntimeID.t option; + search: Byterntm.search_method} | Custom | Vanilla - type phase = Original | Renamed + type phase = Original | Execution | Renamed type mode = Bytecode | Native @@ -94,11 +98,12 @@ module Import = struct has_ocamlnat: bool; has_ocamlopt: bool; has_relative_libdir: string option; - has_runtime_search: bool option; + has_runtime_search: Config.search_method; launcher_searches_for_ocamlrun: bool; target_launcher_searches_for_ocamlrun: bool; bytecode_shebangs_by_default: bool; shebangscripts: bool; + filename_mangling: bool; libraries: string list list } @@ -151,7 +156,7 @@ let files_for ?(source_and_cmi = true) mode name files = |> add_if source_and_cmi (name ^ ".ml") let fail_because fmt = - Format.ksprintf (fun s -> prerr_endline s; exit 1) fmt + Format.ksprintf (fun s -> flush stdout; prerr_endline s; exit 1) fmt (* ocamlc cannot be directly executed after renaming the prefix if native compilation is disabled (because ocamlc will be ocamlc.byte, since ocamlc.opt diff --git a/testsuite/tools/harness.mli b/testsuite/tools/harness.mli index 77d5d46f0c47..3cdc335fd082 100644 --- a/testsuite/tools/harness.mli +++ b/testsuite/tools/harness.mli @@ -23,7 +23,11 @@ module Import : sig (** Kinds of executable *) type executable = - | Tendered of {header: launch_mode; dlls: bool; runtime: string} + | Tendered of {header: launch_mode; + dlls: bool; + runtime: string; + id: Misc.RuntimeID.t option; + search: Byterntm.search_method} (** Tendered bytecode image. Executable uses the given mechanism to locate a suitable runtime to execute the image. [dlls] is [true] if the bytecode image requires additional C libraries to be loaded. [runtime] @@ -36,8 +40,11 @@ module Import : sig (** Test harness phases. *) type phase = - | Original (* Compiler installed in its original configured prefix. *) - | Renamed (* Compiler moved to a different prefix from its configuration. *) + | Original (* Compiler installed in its original configured prefix. *) + | Execution (* Executing programs built by the compiler installed in its + original prefix after the compiler has been moved to a + different prefix. *) + | Renamed (* Compiler moved to a different prefix from its configuration. *) (* Tooling modes. *) type mode = @@ -55,22 +62,24 @@ module Import : sig has_relative_libdir: string option; (** {v $(TARGET_LIBDIR_IS_RELATIVE) v} and {v $(TARGET_LIBDIR) v} - {v Makefile.build_config v} *) - has_runtime_search: bool option; - (** Not implemented; always None. *) + has_runtime_search: Config.search_method; + (** {v $(RUNTIME_SEARCH) v} - {v Makefile.build_config v} *) launcher_searches_for_ocamlrun: bool; (** Indicates whether bytecode executables in the compiler distribution - use a launcher that is capable of searching PATH to find ocamlrun. At - present, only native Windows has this behaviour. *) + use a launcher that is capable of searching PATH to find ocamlrun. + This used to be the behaviour for native Windows. *) target_launcher_searches_for_ocamlrun: bool; (** Indicates whether the executable launcher used by ocamlc is capable of - searching PATH to find ocamlrun. At present, only native Windows has - this behaviour. *) + searching PATH to find ocamlrun. This used to be the behaviour for + native Windows. *) bytecode_shebangs_by_default: bool; (** True if ocamlc uses a shebang-style header rather than an executable header for tendered bytecode executables. *) shebangscripts: bool; (** {v $(SHEBANGSCRIPTS) v} - {v Makefile.config v} *) - libraries: string list list + filename_mangling: bool; + (** True if the Runtime ID is being used for filename mangling. *) + libraries: string list list; (** Sorted list of basenames of libraries to test. Derived from {v [$(OTHERLIBRARIES)] v} - {v Makefile.config v} *) } diff --git a/testsuite/tools/poisonedruntime.c b/testsuite/tools/poisonedruntime.c new file mode 100644 index 000000000000..ef5f5be95ea0 --- /dev/null +++ b/testsuite/tools/poisonedruntime.c @@ -0,0 +1,27 @@ +/**************************************************************************/ +/* */ +/* OCaml */ +/* */ +/* David Allsopp, University of Cambridge & Tarides */ +/* */ +/* Copyright 2025 David Allsopp Ltd. */ +/* */ +/* All rights reserved. This file is distributed under the terms of */ +/* the GNU Lesser General Public License version 2.1, with the */ +/* special exception on linking described in the file LICENSE. */ +/* */ +/**************************************************************************/ + +/* Micro-program used to sit in PATH to test local path search for bytecode + executables. */ + +#define CAML_INTERNALS +#include +#include + +int main_os(int argc, char_os **argv) +{ + printf("The poisoned runtime has been invoked!\n" + "This suggests something is wrong in stdlib/header.c\n"); + return 1; +} diff --git a/testsuite/tools/testBytecodeBinaries.ml b/testsuite/tools/testBytecodeBinaries.ml index 2822b351ea35..5bc4a97cc0f3 100644 --- a/testsuite/tools/testBytecodeBinaries.ml +++ b/testsuite/tools/testBytecodeBinaries.ml @@ -35,129 +35,184 @@ let run config env = let exec_magic = Environment.run_process env ocamlrun ["-M"] in - let test_binary binary = + let test_binary failed binary = if String.starts_with ~prefix:"ocaml" binary - || String.starts_with ~prefix:"flexlink" binary then - let program = Filename.concat bindir binary in - if is_executable program then - let classification = Environment.classify_executable program in - if classification <> Vanilla then - let fails = - (* After the prefix has been renamed, bytecode executables compiled - with -custom will still work. Otherwise, the header needs to be - able to search for ocamlrun and, if applicable, ocamlrun needs to - be able to load C stubs (which will only happen if the runtime - locates the Standard Library using a relative directory, so that it - can find ld.conf) *) - Environment.is_renamed env - && match classification with - | Tendered {dlls; _} -> - not config.launcher_searches_for_ocamlrun - || dlls && config.has_relative_libdir = None - | _ -> - false - in - match Environment.run_process ~fails env program ["-vnum"] with - | (0, ((output::rest) as all_output)) when not fails -> - if rest <> [] then begin - Environment.display_output all_output; - Harness.fail_because "%s: expected only one line of output" - program - end; - let runtime = - let compiled_by_boot_ocamlc = - let name = - if Filename.extension binary = ".exe" then - Filename.remove_extension binary - else - binary - in - name <> "ocamldoc" && name <> "ocamldebug" - in - match classification with - | Vanilla -> assert false - | Custom -> - if Config.supports_shared_libraries - || compiled_by_boot_ocamlc then - Harness.fail_because "%s: unexpected -custom runtime" - program - else - "compiled with -custom" - | Tendered {runtime; header; _} -> - let is_expected_runtime = - if Sys.win32 then - runtime = "ocamlrun" - else - runtime = ocamlrun - in - let expected_launch_mode = - if config.shebangscripts then - Header_shebang + || String.starts_with ~prefix:"flexlink" binary then + let program = Filename.concat bindir binary in + if is_executable program then + let classification = Environment.classify_executable program in + if classification <> Vanilla then + let fails = + (* After the prefix has been renamed, bytecode executables compiled + with -custom will still work. Otherwise, the header needs to be + able to search for ocamlrun and, if applicable, ocamlrun needs to + be able to load C stubs (which will only happen if the runtime + locates the Standard Library using a relative directory, so that + it can find ld.conf) *) + Environment.is_renamed env + && match classification with + | Tendered {dlls; _} -> + not config.launcher_searches_for_ocamlrun + || dlls && config.has_relative_libdir = None + | _ -> + false + in + match Environment.run_process ~fails env program ["-vnum"] with + | (0, ((output::rest) as all_output)) when not fails -> + if rest <> [] then begin + Environment.display_output all_output; + Harness.fail_because "%s: expected only one line of output" + program + end; + let failed, runtime = + let compiled_by_boot_ocamlc = + let name = + if Filename.extension binary = ".exe" then + Filename.remove_extension binary else - Header_exe + binary in - if is_expected_runtime then - if header = expected_launch_mode then - runtime - else - Harness.fail_because "%s: unexpected launch mode" program - else - Harness.fail_because "%s: unexpected runtime %S" - program runtime - in - Printf.printf " Runtime: %s\n Output: %s\n" runtime output; - if Sys.win32 && Filename.extension binary = ".exe" then - (* This additional part of the test ensures that the executable - launcher on Windows can correctly hand-over to ocamlrun on - Windows. The check is that a binary named ocamlc.byte.exe - can be invoked as ocamlc.byte. -M is used as a previous bug - caused ocamlc.byte to act solely as ocamlrun, the test being - that ocamlrun -M returning the runtime's magic number would - be likely distinct from the behaviour of any of the - distribution's tools when called with -M. *) - let without_exe = Filename.remove_extension binary in - let (this_exit_code, _) as this = - let fails = - without_exe <> "ocamlmklib" - && not (String.contains without_exe '.') + name <> "ocamldoc" && name <> "ocamldebug" in - Environment.run_process - ~fails env program ~argv0:without_exe ["-M"] + match classification with + | Vanilla -> assert false + | Custom -> + if Config.supports_shared_libraries + || compiled_by_boot_ocamlc then + Harness.fail_because "%s: unexpected -custom runtime" + program + else + failed, "compiled with -custom" + | Tendered {runtime; id; header; search; _} -> + let reported_runtime, search = + let id = + Option.map + (fun t -> "-" ^ Misc.RuntimeID.to_string t) id + |> Option.value ~default:"" + in + match search with + | Disable dir -> + dir ^ runtime ^ id, Config.Disable + | Fallback dir -> + Printf.sprintf "[%s]%s%s" dir runtime id, + Config.Fallback + | Enable -> + runtime ^ id, Config.Enable + in + let expected_id = + if config.filename_mangling then + Some (Misc.RuntimeID.make_zinc ()) + else + None + in + let expected_launch_mode = + if config.shebangscripts then + Header_shebang + else + Header_exe + in + let pp_runtime_id f = function + | None -> + Format.pp_print_string f "" + | Some id -> + Format.pp_print_string f (Misc.RuntimeID.to_string id) + in + let pp_search f = function + | Config.Disable -> + Format.pp_print_string f "disable" + | Config.Fallback -> + Format.pp_print_string f "fallback" + | Config.Enable -> + Format.pp_print_string f "enable" + in + let pp_launch f = function + | Header_shebang -> Format.pp_print_string f "shebang" + | Header_exe -> Format.pp_print_string f "executable" + in + let check expected actual description print failed = + if expected = actual then + failed + else + Format.kfprintf (Fun.const true) Format.err_formatter + " *** Unexpected %s (Expected: %a; got %a)\n%!" + description print expected print actual + in + let failed = + failed + |> check config.has_runtime_search search + "search mechanism" pp_search + |> check expected_id id + "runtime ID" pp_runtime_id + |> check "ocamlrun" runtime + "runtime" Format.pp_print_string + |> check expected_launch_mode header + "launch mode" pp_launch + in + failed, reported_runtime in - if this_exit_code = 0 then - if this = exec_magic then - let (that_exit_code, _) as that = - let fails = without_exe <> "ocamlmklib" in - Environment.run_process - ~fails env program ~argv0:binary ["-M"] - in - if this = that then - Harness.fail_because - "Neither %s nor %s seem to load the bytecode image" - without_exe binary - else if that_exit_code = 0 then - Harness.fail_because - "%s is not expected to return with exit code 0" - binary - else if not (String.contains without_exe '.') then + Printf.printf " Runtime: %s\n Output: %s\n" runtime output; + if Sys.win32 && Filename.extension binary = ".exe" then begin + (* This additional part of the test ensures that the executable + launcher on Windows can correctly hand-over to ocamlrun on + Windows. The check is that a binary named ocamlc.byte.exe + can be invoked as ocamlc.byte. -M is used as a previous bug + caused ocamlc.byte to act solely as ocamlrun, the test being + that ocamlrun -M returning the runtime's magic number would + be likely distinct from the behaviour of any of the + distribution's tools when called with -M. *) + let without_exe = Filename.remove_extension binary in + let (this_exit_code, _) as this = + let fails = + without_exe <> "ocamlmklib" + && not (String.contains without_exe '.') in + Environment.run_process + ~fails env program ~argv0:without_exe ["-M"] + in + if this_exit_code = 0 then + if this = exec_magic then + let (that_exit_code, _) as that = + let fails = without_exe <> "ocamlmklib" in + Environment.run_process + ~fails env program ~argv0:binary ["-M"] + in + if this = that then + Harness.fail_because + "Neither %s nor %s seem to load the bytecode image" + without_exe binary + else if that_exit_code = 0 then + Harness.fail_because + "%s is not expected to return with exit code 0" + binary + else if not (String.contains without_exe '.') then + Harness.fail_because + "%s is not expected to return the exec magic number!" + without_exe + else () (* Expected outcome was the exec magic number *) + else if without_exe <> "ocamlmklib" then Harness.fail_because - "%s is not expected to return the exec magic number!" + "%s is expected to return with a non-zero exit code" without_exe - else () (* Expected outcome was the exec magic number *) - else if without_exe <> "ocamlmklib" then + else () (* Expected outcome is a zero exit code *) + else if without_exe = "ocamlmklib" then Harness.fail_because - "%s is expected to return with a non-zero exit code" + "%s is expected to return with exit code 0" without_exe - else () (* Expected outcome is a zero exit code *) - else if without_exe = "ocamlmklib" then - Harness.fail_because - "%s is expected to return with exit code 0" - without_exe - else () (* Expected outcome is a non-zero exit code *) - | _ -> - if not fails then - Harness.fail_because "%s: not expected to have failed" program + else () (* Expected outcome is a non-zero exit code *) + end; + failed + | _ -> + if not fails then + Harness.fail_because "%s: not expected to have failed" program + else + failed + else + failed + else + failed + else + failed in let binaries = Sys.readdir bindir in Array.sort String.compare binaries; - Array.iter test_binary binaries + if Array.fold_left test_binary false binaries then + Harness.fail_because "Binaries didn't all match expectation" diff --git a/testsuite/tools/testLinkModes.ml b/testsuite/tools/testLinkModes.ml index 5f303b3252d5..3e518015333d 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -371,17 +371,46 @@ let make_test_runner ~stdlib_exists_when_renamed ~may_segfault ~with_unix tendered && not target_launcher_searches_for_ocamlrun && (config.has_relative_libdir = None || not (Environment.is_renamed env)) in - let rec run env = + let rec run ~re_executing env = let runs = test_runs usr_bin_sh test_program_path test_program config env ~via_ocamlrun in let execute ({argv0; prefix_path_with_cwd}, outcome) = let expected_executable_name, expected_exit_code, expected_argv0 = match outcome with - | Fail code -> "", code, "" - | Success {executable_name; argv0} -> executable_name, 0, argv0 + | Fail code -> + "", code, "" + | Success {executable_name; argv0} -> + (* Systems which don't have caml_executable_name get particularly + fiddly here, because they can fail for multiple reasons in this + test! Any tendered executable which was expected to succeed is + set to fail here, since the shim for CAML_LD_LIBRARY_PATH will + not be applied. *) + if tendered && with_unix && Harness.no_caml_executable_name + (* Passing the executable directly to ocamlrun will fail if + ocamlrun isn't configured with a relative libdir *) + && (not via_ocamlrun || config.has_relative_libdir = None) + && (re_executing || Environment.is_renamed env + && config.has_relative_libdir = None) then + "", 134, "" + else + executable_name, 0, argv0 + in + let stubs = + tendered && with_unix + (* The programs compiled before the prefix is renamed are intentionally + run without the runtime in PATH in order to test the bytecode + launcher's searching in the image directory before PATH. A side + effect of this is that ld.conf then can't be found, because the + runtime copied to the testsuite directory doesn't have ld.conf in the + correct place. The shim is skipped for systems which don't have + caml_executable_name because otherwise we'd have a test which fails + in the Original phase and succeeds in the Execution phase, which is a + special case too far! *) + && (not Harness.no_caml_executable_name + && (config.has_relative_libdir = None + || not via_ocamlrun && re_executing)) in - let stubs = tendered && with_unix && config.has_relative_libdir = None in run_program env config ~runtime:via_ocamlrun ~stubs test_program_path ~prefix_path_with_cwd expected_executable_name @@ -393,14 +422,14 @@ let make_test_runner ~stdlib_exists_when_renamed ~may_segfault ~with_unix if Environment.is_renamed env then (Harness.erase_file test_program_path; `None) else - `Some run + `Some (run ~re_executing:true) in - `Some run + `Some (run ~re_executing:false) (* Describe the various ways in which executables can be produced by our two compilers... *) type linkage = -| Default_ocamlc of launch_mode +| Default_ocamlc of launch_mode * Config.search_method | Default_ocamlopt | Custom_runtime of runtime_mode | Output_obj of compiler * runtime_mode @@ -469,8 +498,34 @@ let compile_test usr_bin_sh config env test test_program description = 0 in match test with - | Default_ocamlc _launch_method -> - f ~tendered:true [] + | Default_ocamlc(launch_method, search_method) -> + let args = + match launch_method with + | Header_exe when config.bytecode_shebangs_by_default -> + ["-launch-method"; "exe"] + | Header_shebang when not config.bytecode_shebangs_by_default -> + ["-launch-method"; "sh"] + | _ -> + [] in + let target_launcher_searches_for_ocamlrun = + if search_method = Config.search_method then + None + else + Some (search_method <> Config.Disable) + in + let param = + match search_method with + | Disable -> "disable" + | Fallback -> "fallback" + | Enable -> "enable" + in + let args = + if search_method = Config.search_method then + args + else + "-runtime-search" :: param :: args + in + f ?target_launcher_searches_for_ocamlrun ~tendered:true args | Default_ocamlopt -> f ~mode:Native [] | Custom_runtime Static -> @@ -538,8 +593,9 @@ let compile_test usr_bin_sh config env test test_program description = ["-output-complete-obj"; "-noautolink"; "-cclib"; "-lunixnat"; "-cclib"; "-lcomprmarsh"] | Output_complete_obj(C_ocamlopt, Shared) -> - (* ocamlopt doesn't correctly implement -runtime-variant _shared *) - let compilation_exit_code = fails_if true in + (* ocamlopt allows the .so to be passed to the partial linker which + fails with GNU ld, but not with the macOS linker *) + let compilation_exit_code = fails_if (Config.system <> "macosx") in f ~mode:Native ~use_shared_runtime:true ~compilation_exit_code ~clibs:[Toolchain.compression_c_libraries] ["-output-complete-obj"; "-noautolink"; "-cclib"; "-lunixnat"; @@ -696,15 +752,13 @@ let run ~sh config env = Format.printf "ocamlc -where: %a\nocamlopt -where: %a\n%!" pp_path ocamlc_where pp_path ocamlopt_where; let compile_test = compile_test sh config env in - let launch_method = - if config.bytecode_shebangs_by_default then - Header_shebang - else - Header_exe - in let tests = [ - compile_test (Default_ocamlc launch_method) - "byt_default" "with tender"; + compile_test (Default_ocamlc(Header_exe, Disable)) + "byt_default_exe_disable" "with absolute tender"; + compile_test (Default_ocamlc(Header_exe, Fallback)) + "byt_default_exe_fallback" "with fallback tender"; + compile_test (Default_ocamlc(Header_exe, Enable)) + "byt_default_exe_enable" "with relocatable tender"; compile_test (Custom_runtime Static) "custom_static" "-custom static runtime"; compile_test (Custom_runtime Shared) @@ -732,5 +786,16 @@ let run ~sh config env = compile_test (Output_complete_obj(C_ocamlopt, Shared)) "nat_complete_obj_shared" "-output-complete-obj shared runtime"; ] in + let tests = + if Config.shebangscripts then + (compile_test (Default_ocamlc(Header_shebang, Disable)) + "byt_default_sh_disable" "with absolute #!") :: + (compile_test (Default_ocamlc(Header_shebang, Fallback)) + "byt_default_sh_fallback" "with fallback #!") :: + (compile_test (Default_ocamlc(Header_shebang, Enable)) + "byt_default_sh_enable" "with relocatable #!") :: + tests + else + tests in Printf.printf "Running programs\n%!"; List.map (function `Some f -> f env | `None -> `None) tests diff --git a/testsuite/tools/testRelocation.ml b/testsuite/tools/testRelocation.ml index 79d393b0dfcc..256b8025ecbd 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -80,7 +80,7 @@ let bindir_rules config file = (* If the launcher doesn't search for ocamlrun, then either the #! stub will include the absolute path or the RNTM section will *) match classification with - | Tendered _ when not config.launcher_searches_for_ocamlrun -> true + | Tendered _ when config.has_runtime_search <> Config.Enable -> true | _ -> false in if code_embeds_stdlib_location || linker_embeds_stdlib_location then @@ -114,7 +114,7 @@ let bindir_rules config file = else (* Bytecode runtimes and ocamlyacc of which only ocamlrund is linked with -g *) - `Other, (basename = "ocamlrund") + `Other, (List.mem "ocamlrund" (String.split_on_char '-' basename)) in (* Combine this with the properties of the platform to determine whether the executable will contain the build path. *) @@ -183,11 +183,6 @@ let libdir_rules config file = "ocamlcommon.cma"] in (* The compiler's artefacts are all compiled with -g *) (stdlib, true, false, false) - else if basename = "runtime-launch-info" then - (* When the compiler is configured with a relative libdir, - runtime-launch-info just contains ".", rather than the prefix *) - let stdlib = (config.has_relative_libdir = None) in - (stdlib, false, false, false) else if ext = ".cmxs" then (* All the .cmxs files built by the distribution at present include C objects and obviously contain assembled objects. *) @@ -517,17 +512,6 @@ let run ~reproducible config env = |> scan Environment.libdir "$libdir" libdir_rules in flush stderr; - (* Abort the harness if there are files which didn't match a ruleset *) - let () = - if results_are_reproducible && not consistent then - Harness.fail_because - "Internal error: bindir_rules and libdir_rules disagree with \ - reproducible_rules" - else if results_are_reproducible <> reproducible then - Harness.fail_because - "The build is %sexpected to be reproducible" - (if not reproducible then "not " else "") - in (* Summarise the results, using wildcards to bring them to a readable length *) let sections = @@ -646,7 +630,15 @@ let run ~reproducible config env = let pp_results = Format.(pp_print_list ~pp_sep pp_print_string) in Format.printf "@[ %a@]@." pp_results results in + (* Abort the harness if there are files which didn't match a ruleset *) if failed then - Harness.fail_because "Installed files don't match expectation" - else - List.iter display sections + Harness.fail_because "Installed files don't match expectation"; + List.iter display sections; + if results_are_reproducible && not consistent then + Harness.fail_because + "Internal error: bindir_rules and libdir_rules disagree with \ + reproducible_rules" + else if results_are_reproducible <> reproducible then + Harness.fail_because + "The build is %sexpected to be reproducible" + (if not reproducible then "not " else "") diff --git a/testsuite/tools/test_in_prefix.ml b/testsuite/tools/test_in_prefix.ml index 55ab5dcb4c5f..4530c133694f 100644 --- a/testsuite/tools/test_in_prefix.ml +++ b/testsuite/tools/test_in_prefix.ml @@ -49,13 +49,13 @@ let print_summary config header_size ~prefix ~bindir_suffix ~libdir_suffix \ @{libdir@} = [$prefix/]%s\n\ \ - C compiler is %s [%s] for %s\n\ \ - OCaml is %a%a; target binaries by default are %a\n\ - \ - Executable header size is %.2fKiB (%d bytes)\n\ + \ - Executable header size is %.2fKiB (%Ld bytes)\n\ \ - Testing %s\n@?" prefix bindir_suffix libdir_suffix Config.c_compiler Toolchain.c_compiler_vendor Config.target pp_relocatable relocatable pp_reproducible reproducible pp_relocatable target_relocatable - (float_of_int header_size /. 1024.0) header_size summary + (Int64.to_float header_size /. 1024.0) header_size summary let run_tests ~sh config env = TestDynlink.run config env Bytecode; @@ -104,6 +104,10 @@ let read_runtime_launch_info file = with Not_found -> Harness.fail_because "%s: corrupt header" file +let rename_exe_in_test_root env from_base to_base = + Sys.rename (Environment.in_test_root env (Harness.exe from_base)) + (Environment.in_test_root env (Harness.exe to_base)) + let () = let config, pwd, prefix, _, bindir_suffix, libdir, libdir_suffix, summarise_only, verbose = @@ -151,21 +155,23 @@ let () = in List.map add_dependencies libraries in - let runtime_launch_info = + let header_size, filename_mangling = let file = Filename.concat libdir "runtime-launch-info" in - read_runtime_launch_info file in - let header_size = - let {buffer; executable_offset; _} = runtime_launch_info in - String.length buffer - executable_offset in + In_channel.with_open_bin file @@ fun ic -> + In_channel.length ic, (input_char ic <> '\000') + in let bytecode_shebangs_by_default = - runtime_launch_info.launcher <> Executable in - let launcher_searches_for_ocamlrun = Sys.win32 in - let target_launcher_searches_for_ocamlrun = Sys.win32 in + Config.launch_method <> Config.Executable in + let launcher_searches_for_ocamlrun = + (config.has_runtime_search <> Config.Disable) in + let target_launcher_searches_for_ocamlrun = + (Config.search_method <> Config.Disable) in let config = {config with libraries; launcher_searches_for_ocamlrun; target_launcher_searches_for_ocamlrun; - bytecode_shebangs_by_default} + bytecode_shebangs_by_default; + filename_mangling} in (* A compiler distribution is _Relocatable_ if its build, for a given system, satisfies the following three properties: @@ -182,7 +188,7 @@ let () = relocatable and also required support from the assembler and C compiler. *) let relocatable = config.has_relative_libdir <> None - && config.launcher_searches_for_ocamlrun + && config.has_runtime_search <> Config.Disable in let reproducible = relocatable @@ -192,8 +198,19 @@ let () = && not Toolchain.linker_embeds_build_path && (not Toolchain.c_compiler_always_embeds_build_path || not Toolchain.c_compiler_debug_paths_can_be_absolute) + (* Prior to #13828 (OCaml 5.4.0), .cmt and .cmti embed the absolute location + of the compiler without using BUILD_PATH_PREFIX_MAP. However, this + embedding is not entirely predictable, because it only happens when + ocamlc.opt or ocamlopt.opt is used for compilation, rather than when the + bytecode version of the tool is passed is explicitly to ocamlrun (in this + case, Sys.argv.(0) always retains the relative path used in the build + system). For this reason, when configured relatively, on Windows, with + native compilation available, accept that the Build directory may appear + in .cmt/.cmti files, and therefore that the output may not be + reproducible. *) + && (not Sys.win32 || not config.has_ocamlopt) in - let target_relocatable = config.target_launcher_searches_for_ocamlrun in + let target_relocatable = (Config.search_method <> Config.Disable) in (* Use Harness.pp_path unless --verbose was specified *) let pp_path = if verbose then @@ -255,11 +272,26 @@ let () = pp_path prefix; Sys.rename new_prefix prefix); let env = - make_env ~phase:Renamed ~prefix:new_prefix ~bindir_suffix ~libdir_suffix in + make_env ~phase:Execution ~prefix:new_prefix ~bindir_suffix ~libdir_suffix + in (* 3. Re-run the test programs compiled with the normal prefix *) Printf.printf "Re-running test programs\n%!"; - List.iter - (function `Some f -> assert (f env = `None) | `None -> ()) programs; + (* Verify that the searching runtimes are searching the directory containing + the program itself first. *) + let runtime = + if config.filename_mangling then + Misc.RuntimeID.(ocamlrun "" (make_zinc ())) + else + "ocamlrun" + in + rename_exe_in_test_root env ("test-" ^ runtime) runtime; + Fun.protect + ~finally:(fun () -> rename_exe_in_test_root env runtime ("test-" ^ runtime)) + (fun () -> + List.iter + (function `Some f -> assert (f env = `None) | `None -> ()) programs); + let env = + make_env ~phase:Renamed ~prefix:new_prefix ~bindir_suffix ~libdir_suffix in (* 4. Finally re-run the main test battery in the new prefix *) Compmisc.reinit_path ~standard_library:libdir (); let programs = run_tests env in diff --git a/tools/ci/actions/runner.sh b/tools/ci/actions/runner.sh index 0f44e0671177..f3a0f749dfa2 100755 --- a/tools/ci/actions/runner.sh +++ b/tools/ci/actions/runner.sh @@ -200,12 +200,14 @@ Re-Test-In-Prefix () { echo '::group::Re-building the compiler with a relative libdir' $MAKE COMPUTE_DEPS=false reconfigure \ 'ADDITIONAL_CONFIGURE_ARGS=--with-relative-libdir=../lib/ocaml-lib \ +--enable-runtime-search --enable-runtime-search-target=fallback \ --prefix='"$PREFIX"'.new' else # Compiler configured relatively - reconfigure absolutely echo '::group::Re-building the compiler with an absolute libdir' $MAKE COMPUTE_DEPS=false reconfigure \ 'ADDITIONAL_CONFIGURE_ARGS=--without-relative-libdir \ +--disable-runtime-search --disable-runtime-search-target \ --prefix='"$PREFIX"'.new --libdir='"$PREFIX"'.new/lib/ocaml-lib' fi $MAKE diff --git a/tools/ci/appveyor/appveyor_build.sh b/tools/ci/appveyor/appveyor_build.sh index 2d7a6bef3fbc..459945dfe34f 100755 --- a/tools/ci/appveyor/appveyor_build.sh +++ b/tools/ci/appveyor/appveyor_build.sh @@ -88,7 +88,8 @@ function set_configuration { '--enable-native-toplevel');; esac if [[ $RELOCATABLE = 'true' ]]; then - args+=('--with-relative-libdir') + args+=('--with-relative-libdir' \ + '--enable-runtime-search' '--enable-runtime-search-target=fallback') fi # Remove old configure cache if the configure script or the OS diff --git a/tools/objinfo.ml b/tools/objinfo.ml index 7186d2747126..f43c0e850762 100644 --- a/tools/objinfo.ml +++ b/tools/objinfo.ml @@ -74,6 +74,12 @@ let print_cmo_infos cu = let print_spaced_string s = printf " %s" s +let dllib (~suffixed, name) = + if suffixed then + Printf.sprintf "%s--" name + else + name + let print_cma_infos (lib : Cmo_format.library) = printf "Force custom: %a\n" yesno_of_bool lib.lib_custom; printf "Extra C object files:"; @@ -83,7 +89,7 @@ let print_cma_infos (lib : Cmo_format.library) = List.iter print_spaced_string (List.rev lib.lib_ccopts); printf "\n"; print_string "Extra dynamically-loaded libraries:"; - List.iter print_spaced_string (List.rev lib.lib_dllibs); + List.iter print_spaced_string (List.rev_map dllib lib.lib_dllibs); printf "\n"; List.iter print_cmo_infos lib.lib_units @@ -292,9 +298,57 @@ let p_list title print = function p_title title; List.iter print l +let p_runtime_id ({Misc.RuntimeID.dev; release; no_flat_float_array; fp; tsan; + int31; static; no_compression; ansi; reserved} as t) = + let version = + if release > Config.release_number then + "" + else + if release = 0 then + " (Objective Caml 3.12)" + else if release < 16 then + Printf.sprintf " (OCaml 4.%02d)" (release - 1) + else + Printf.sprintf " (OCaml 5.%d)" (release - 16) + in + printf "\t%s = Release %d%s%s\n" + (Misc.RuntimeID.to_string t) + release version (if dev then " - development/altered version" else ""); + if reserved > 0 then + printf "\t - %d reserved header bit%s\n" + reserved (if reserved = 1 then "" else "s"); + if no_flat_float_array then + printf "\t - Flat float array representation disabled\n"; + if fp then + printf "\t - Frame pointers enabled\n"; + if tsan then + printf "\t - TSAN enabled\n"; + if int31 then + printf "\t - Compiled without 64-bit support\n"; + if static then + printf "\t - Compiled without support dynamic loading\n"; + if no_compression then + printf "\t - Compiled without support for compressed marshalling\n"; + if ansi then + printf "\t - Windows Unicode support disabled\n" + +let p_runtime (runtime, id, search) = + let runtime = + let some id = runtime ^ "-" ^ Misc.RuntimeID.to_string id in + Option.fold ~none:runtime ~some id + in + let runtime = + match search with + | Byterntm.Enable -> runtime + | Byterntm.Disable dir -> dir ^ runtime + | Byterntm.Fallback dir -> Printf.sprintf "[%s]%s" dir runtime + in + printf "Runtime:\n\t%s\n" runtime; + Option.iter p_runtime_id id + let dump_byte ic = let toc = Bytesections.read_toc ic in - let all = Bytesections.all toc in + Option.iter p_runtime (Byterntm.read_runtime toc ic); List.iter (fun {Bytesections.name = section; len; _} -> try @@ -329,7 +383,7 @@ let dump_byte ic = | _ -> () with _ -> () ) - all + (Bytesections.all toc) let find_dyn_offset filename = match Binutils.read filename with diff --git a/tools/ocamlmklib.ml b/tools/ocamlmklib.ml index 1082a208d3c7..398d62e6ba64 100644 --- a/tools/ocamlmklib.ml +++ b/tools/ocamlmklib.ml @@ -49,6 +49,7 @@ and output_c = ref "" (* Output name for C part of library *) and rpath = ref [] (* rpath options *) and debug = ref false (* -g option *) and verbose = ref false +and suffixed = ref false (* -suffixed option *) let starts_with s pref = String.length s >= String.length pref && @@ -162,6 +163,10 @@ let parse_arguments argv = c_opts := s :: !c_opts else if s = "-framework" then (let a = next_arg s in c_opts := a :: s :: !c_opts) + else if s = "-suffixed" then + suffixed := true + else if s = "-no-suffixed" then + suffixed := false else if starts_with s "-" then prerr_endline ("Unknown option " ^ s) else @@ -208,6 +213,9 @@ Options are: -oc Generated C library is named dll.so or lib.a -rpath Same as -dllpath -R Same as -rpath + -suffixed Append runtime ID to any generated shared libraries + -no-suffixed Do not append runtime ID to any generated shared libraries + (default) -verbose Print commands before executing them -v same as -verbose -version Print version and exit @@ -284,11 +292,17 @@ let flexdll_dirs = let build_libs () = if !c_objs <> [] then begin if !dynlink then begin + let dllname = + if !suffixed then + Misc.RuntimeID.stubslib !output_c + else + !output_c + in let retcode = command (Printf.sprintf "%s %s -o %s %s %s %s %s %s %s" Config.mkdll (if !debug then "-g" else "") - (prepostfix "dll" !output_c Config.ext_dll) + (prepostfix "dll" dllname Config.ext_dll) (String.concat " " !c_objs) (String.concat " " !c_opts) (String.concat " " !ld_opts) @@ -306,7 +320,7 @@ let build_libs () = end; if !bytecode_objs <> [] then scommand - (sprintf "%s -a %s %s %s -o %s.cma %s %s -dllib -l%s -cclib -l%s \ + (sprintf "%s -a %s %s %s -o %s.cma %s %s -dllib%s -l%s -cclib -l%s \ %s %s %s %s" (transl_path !ocamlc) (if !debug then "-g" else "") @@ -315,6 +329,7 @@ let build_libs () = !output (String.concat " " !caml_opts) (String.concat " " !bytecode_objs) + (if !suffixed then "-suffixed" else "") (Filename.basename !output_c) (Filename.basename !output_c) (String.concat " " (prefix_list "-ccopt " !c_opts)) diff --git a/tools/ocamlsize b/tools/ocamlsize index 783068067f78..dfcc0adbf8fb 100755 --- a/tools/ocamlsize +++ b/tools/ocamlsize @@ -19,12 +19,25 @@ foreach $f (@ARGV) { open(FILE, $f) || die("Cannot open $f"); read(FILE, $header, 2); if ($header eq '#!') { - $path = ; - if ($path = '/bin/sh') { - # exec form of the shebang header - $path = ; + chomp($path = ); + if ($path =~ m/\/sh$/) { + # shell-script form of the shebang header + chomp($path = ); + # exec form - used for -runtime-search absolute when the path to the + # runtime isn't valid as a #! line. if ($path =~ s/^exec '(.*)' "\$0" "\$@\"$/$1/ > 0) { $path =~ s/'\\''/'/g; + # Both -runtime-search fallback and -runtime-search enable define a + # variable r with the name of the runtime (see bytecomp/bytelink.ml) + } elsif ($path =~ s/^r='(.*)'$/$1/ > 0) { + $path =~ s/'\\''/'/g; + chomp($dir = ); + # In -runtime-search fallback, there will also be a path to the + # runtime defined the variable c. + if ($dir =~ s/^c='(.*)'"\$r"$/$1/ > 0) { + $dir =~ s/'\\''/'/g; + $path = "[$dir]$path"; + } } else { undef $path; } @@ -45,10 +58,19 @@ foreach $f (@ARGV) { } print $f, ":\n" if ($#ARGV > 0); if (not defined $path) { - $path = - $length{'RNTM'} > 0 ? - substr(&read_section('RNTM'), 0, -1) : - "(custom runtime)"; + if ($length{'RNTM'} > 0) { + $path = &read_section('RNTM'); + # RNTM is "\0ocamlrun" for -runtime-search enable + if ($path !~ s/^\0//) { + # RNTM is "/path/to/ocamlrun" for -runtime-search disable and + # "/path/to\0ocamlrun" for -runtime-search fallback. Transform the + # embedded "\0" into a directory separator and display the directory + # in square brackets (as above for the sh-case) + $path =~ s/^([^\/\\]*)([\\\/])([^\0]*)\0(.*)$/[$1$2$3$2]$4/ + } + } else { + $path = '(custom runtime)'; + } }; printf ("\tcode: %-7d data: %-7d symbols: %-7d debug: %-7d\n", $length{'CODE'}, $length{'DATA'}, diff --git a/utils/clflags.ml b/utils/clflags.ml index 1b69e5104f21..0b257a0e9d9f 100644 --- a/utils/clflags.ml +++ b/utils/clflags.ml @@ -38,9 +38,11 @@ module Float_arg_helper = Arg_helper.Make (struct end end) -let objfiles = ref ([] : string list) (* .cmo and .cma files *) -and ccobjs = ref ([] : string list) (* .o, .a, .so and -cclib -lxxx *) -and dllibs = ref ([] : string list) (* .so and -dllib -lxxx *) +let objfiles = ref ([] : string list) (* .cmo and .cma files *) +and ccobjs = ref ([] : string list) (* .o, .a, .so and -cclib -lxxx *) +and dllibs = ref ([] : (suffixed:bool * string) list) + (* .so, -dllib -lxxx and + -dllib-suffixed -lxxx *) let cmi_file = ref None @@ -86,6 +88,12 @@ and noinit = ref false (* -noinit *) and open_modules = ref [] (* -open *) and use_prims = ref "" (* -use-prims ... *) and use_runtime = ref "" (* -use-runtime ... *) +and target_bindir = (* -launch-method ... *) + ref Config.target_bindir +and launch_method = + ref Config.launch_method +and search_method = (* -search-method ... *) + ref Config.search_method and plugin = ref false (* -plugin ... *) and principal = ref false (* -principal *) and real_paths = ref true (* -short-paths *) diff --git a/utils/clflags.mli b/utils/clflags.mli index a8ac1d415a1d..7450996f5a08 100644 --- a/utils/clflags.mli +++ b/utils/clflags.mli @@ -70,7 +70,7 @@ val use_inlining_arguments_set : ?round:int -> inlining_arguments -> unit val objfiles : string list ref val ccobjs : string list ref -val dllibs : string list ref +val dllibs : (suffixed:bool * string) list ref val cmi_file : string option ref val compile_only : bool ref val output_name : string option ref @@ -114,6 +114,9 @@ val noinit : bool ref val noversion : bool ref val use_prims : string ref val use_runtime : string ref +val target_bindir : string ref +val launch_method : Config.launch_method ref +val search_method : Config.search_method ref val plugin : bool ref val principal : bool ref val real_paths : bool ref diff --git a/utils/config.common.ml.in b/utils/config.common.ml.in index 4f9fa2be90cc..c2adc27bcd12 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -20,6 +20,12 @@ (* The main OCaml version string has moved to ../build-aux/ocaml_version.m4 *) let version = Sys.ocaml_version +(* is_official_release and release_number are automatically updated autoconf + from values in ../build-aux/ocaml_version.m4 - do not edit these lines + directly. *) +let is_official_release = false +let release_number = 19 + let standard_library_default_raw = standard_library_default external stdlib_dirs : string -> string * string option @@ -44,6 +50,11 @@ let standard_library = standard_library_default let bindir = Option.value ~default:bindir relative_root_dir +let target_bindir = + if target_bindir = Filename.current_dir_name then + Filename.dirname Sys.executable_name + else + target_bindir let exec_magic_number = {magic|@EXEC_MAGIC_NUMBER@|magic} (* exec_magic_number is duplicated in runtime/caml/exec.h *) @@ -62,6 +73,21 @@ let safe_string = true let default_safe_string = true let naked_pointers = false +type launch_method = Executable | Shebang of string option +type search_method = Disable | Fallback | Enable + +let launch_method = + match launch_method with + | "exe" -> Executable + | "sh" -> Shebang None + | _ -> Shebang (Some launch_method) + +let search_method = + match search_method with + | "enable" -> Enable + | "fallback" -> Fallback + | _ -> Disable + let interface_suffix = ref ".mli" let max_tag = 243 @@ -131,6 +157,8 @@ let configuration_variables () = p_bool "systhread_supported" systhread_supported; p "host" host; p "target" target; + p "bytecode_runtime_id" bytecode_runtime_id; + p "native_runtime_id" native_runtime_id; p_bool "flambda" flambda; p_bool "safe_string" safe_string; p_bool "default_safe_string" default_safe_string; diff --git a/utils/config.fixed.ml b/utils/config.fixed.ml index 43913bcd2f3d..26f60e4ff6c4 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -21,6 +21,7 @@ let boot_cannot_call s = "/ The boot compiler should not call " ^ s let bindir = "/tmp" +let target_bindir = bindir let standard_library_default = "/tmp" let ccomp_type = "n/a" let c_compiler = boot_cannot_call "the C compiler" @@ -53,6 +54,8 @@ let windows_unicode = false let flat_float_array = true let function_sections = false let afl_instrument = false +let bytecode_runtime_id = "" +let native_runtime_id = "" let native_compiler = false let tsan = false let architecture = "none" @@ -72,3 +75,6 @@ let target = host let systhread_supported = false let flexdll_dirs = [] let ar_supports_response_files = true +let suffixing = false +let launch_method = "sh" +let search_method = "always" diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index a757acb77171..94c5189d1bb2 100644 --- a/utils/config.generated.ml.in +++ b/utils/config.generated.ml.in @@ -19,6 +19,7 @@ than compiled on its own *) let bindir = {@QS@|@ocaml_bindir@|@QS@} +let target_bindir = {@QS@|@TARGET_BINDIR@|@QS@} let standard_library_default = {@QS@|@ocaml_libdir@|@QS@} @@ -66,6 +67,9 @@ let flat_float_array = @flat_float_array@ let function_sections = @function_sections@ let afl_instrument = @afl@ +let bytecode_runtime_id = {@QS@|@bytecode_runtime_id@|@QS@} +let native_runtime_id = {@QS@|@native_runtime_id@|@QS@} + let native_compiler = @native_compiler@ let architecture = {@QS@|@arch@|@QS@} @@ -93,3 +97,9 @@ let flexdll_dirs = [@flexdll_dir@] let ar_supports_response_files = @ar_supports_response_files@ let tsan = @tsan@ + +let suffixing = @suffixing@ + +let launch_method = {@QS@|@launch_method_target@|@QS@} + +let search_method = {@QS@|@runtime_search_target@|@QS@} diff --git a/utils/config.mli b/utils/config.mli index 4823c85f5f1b..710fcd317876 100644 --- a/utils/config.mli +++ b/utils/config.mli @@ -23,6 +23,16 @@ val version: string (** The current version number of the system *) +val release_number: int +(** The release number for the compiler + + @since 5.5 *) + +val is_official_release: bool +(** True if the compiler is an unmodified official OCaml release + + @since 5.5 *) + val bindir: string (** The directory containing the binary programs. If the compiler was configured with [--with-relative-libdir] then this will be the directory containing the @@ -35,6 +45,11 @@ val standard_library_relative: string option @since 5.3.2 *) +val target_bindir: string +(** The directory containing the runtime binaries on the target system + + @since 5.5 *) + val standard_library_default: string (** The effective value for the default directory containing the standard libraries. This is always an absolute path, computed using @@ -281,6 +296,57 @@ val ar_supports_response_files: bool val tsan : bool (** Whether ThreadSanitizer instrumentation is enabled *) +(** Launch mechanisms for bytecode executables + + @since 5.5 *) +type launch_method = +| Executable + (** Use the executable launcher stub *) +| Shebang of string option + (** Use a shebang-style launcher. Whenever possible, the interpreter will be + the runtime itself, but if the path to the runtime is not valid for a + shebang line, then a shell script is generated. When this is necessary, + the parameter in [Shebang (Some sh)] is the full path to [sh]; if the + parameter is [None], then the linker searches PATH for [sh]. *) + +val launch_method : launch_method +(** Default launch mechanism for bytecode executables + + @since 5.5 *) + +(** Mechanisms used by tendered bytecode executables to locate the interpreter + + @since 5.5 *) +type search_method = +| Disable + (** Interpreter searching disabled - check fixed absolute location only *) +| Fallback + (** Check fixed absolute location first, but fall back to a search if that + fails *) +| Enable + (** Always search for the interpreter *) + +val search_method : search_method +(** Default search mechanism for bytecode executables + + @since 5.5 *) + +val suffixing : bool +(** Whether the runtime executable and shared library filenames and C stub + library filenames are being mangled with Runtime IDs and the {!target}. + + @since 5.5 *) + +val bytecode_runtime_id : string +(** The Runtime ID for this build of the bytecode runtime system + + @since 5.5 *) + +val native_runtime_id : string +(** The Runtime ID for this build of the native runtime system + + @since 5.5 *) + (** Access to configuration values *) val print_config : out_channel -> unit diff --git a/utils/misc.ml b/utils/misc.ml index b3d75dbb8664..1dadd934420b 100644 --- a/utils/misc.ml +++ b/utils/misc.ml @@ -1390,3 +1390,130 @@ module Magic_number = struct | Error err -> Error (Unexpected_error err) | Ok () -> Ok info end + +module RuntimeID = struct + type t = { + dev: bool; + release: int; + reserved: int; + no_flat_float_array: bool; + fp: bool; + tsan: bool; + int31: bool; + static: bool; + no_compression: bool; + ansi: bool; + } + + let make fn ?(dev = not Config.is_official_release) + ?(release = Config.release_number) + ?(reserved = Config.reserved_header_bits) + ?(no_flat_float_array = not Config.flat_float_array) + ?(fp = Config.with_frame_pointers) + ?(tsan = Config.tsan) + ?(int31 = (Sys.int_size = 31)) + ?(static = not Config.supports_shared_libraries) + ?(no_compression = (Config.compression_c_libraries = "")) + ?(ansi = Config.target_win32 && not Config.windows_unicode) () = + if release < 0 || release > 63 || reserved < 0 || reserved > 31 then + invalid_arg fn + else + {dev; release; reserved; no_flat_float_array; fp; tsan; int31; static; + no_compression; ansi} + + let make_zinc = + make "Misc.RuntimeID.make_zinc" + ~reserved:0 ~fp:false ~tsan:false ~ansi:false + + let make_bytecode = + make "Misc.RuntimeID.make_bytecode" ~fp:false ~tsan:false + + let make_native = make "Misc.RuntimeID.make_native" + + let is_zinc = function + | {dev = _; release = _; reserved = 0; no_flat_float_array = _; fp = false; + tsan = false; int31 = _; static = _; no_compression = _; ansi = false} -> + true + | _ -> + false + + let is_bytecode = function + | {dev = _; release = _; reserved = _; no_flat_float_array = _; fp = false; + tsan = false; int31 = _; static = _; no_compression = _; ansi = _} -> true + | _ -> false + + let is_native _ = true + + let to_string t = + let alpha = "0123456789abcdefghijklmnopqrstuv" in + let bit bit cond = if cond then 1 lsl bit else 0 in + let q0 = + (bit 0 t.dev) lor + ((t.release lsl 1) land 0b11110) (* 4 bits *) + in + let q1 = + t.release lsr 4 lor (* 2 bits *) + ((t.reserved lsl 2) land 0b11100) (* 3 bits *) + in + let q2 = + t.reserved lsr 3 lor (* 2 bits *) + bit 2 t.no_flat_float_array lor + bit 3 t.fp lor + bit 4 t.tsan + in + let q3 = + bit 0 t.int31 lor + bit 1 t.static lor + bit 2 t.no_compression lor + bit 3 t.ansi + (* bit 4 is unused *) + in + Printf.sprintf "%c%c%c%c" alpha.[q0] alpha.[q1] alpha.[q2] alpha.[q3] + + let of_string s = + if String.length s <> 4 then + None + else + let convert c = + match c with + | '0'..'9' -> Char.code c - Char.code '0' + | 'a'..'v' -> Char.code c - Char.code 'a' + 10 + | _ -> min_int + in + let set bit q = (q land (1 lsl bit) <> 0) in + let q0 = convert s.[0] in + let q1 = convert s.[1] in + let q2 = convert s.[2] in + let q3 = convert s.[3] in + if q0 + q1 + q2 + q3 >= 0 then + Some {dev = set 0 q0; release = ((q1 land 0b11) lsl 4) lor (q0 lsr 1); + reserved = ((q2 land 0b11) lsl 2) lor (q1 lsr 2); + no_flat_float_array = set 2 q2; fp = set 3 q2; tsan = set 4 q2; + int31 = set 0 q3; static = set 1 q3; no_compression = set 2 q3; + ansi = set 3 q3; (* bit 4 of q3 is unused *)} + else + None + + let ocamlrun variant runtime_id = + if is_zinc runtime_id then + Printf.sprintf "ocamlrun%s-%s" variant (to_string runtime_id) + else + invalid_arg "Misc.RuntimeID.ocamlrun" + + let shared_runtime ?runtime_id ?(host = Config.target) ?(prefix = "-l") + backend_type = + match backend_type with + | Sys.Native -> + let runtime_id = Option.value ~default:(make_native ()) runtime_id in + Printf.sprintf "%sasmrun-%s-%s" prefix host (to_string runtime_id) + | Sys.Bytecode -> + let runtime_id = Option.value ~default:(make_bytecode ()) runtime_id in + Printf.sprintf "%scamlrun-%s-%s" prefix host (to_string runtime_id) + | Sys.Other _ -> + invalid_arg "Misc.RuntimeID.shared_runtime" + + let stubslib ?(runtime_id = make_bytecode ()) + ?(host = Config.target) + name = + Printf.sprintf "%s-%s-%s" name host (to_string runtime_id) +end diff --git a/utils/misc.mli b/utils/misc.mli index 54354eba56ab..51886ee81b1d 100644 --- a/utils/misc.mli +++ b/utils/misc.mli @@ -823,6 +823,106 @@ module Utf8_lexeme: sig are not checked. *) end +module RuntimeID : sig + (** Manipulation of the Runtime ID values used to mangle the filenames of + shared libraries and the bytecode interpreters. + + @since 5.5 *) + + (** Runtime IDs *) + type t = private { + dev: bool; + (** [true] if this not an unaltered official release of OCaml *) + release: int; + (** Release number (OCaml 5.5 is release 21) *) + reserved: int; + (** The number of reserved bits (0-31) in the {v value v} header *) + no_flat_float_array: bool; + (** [true] if float arrays must be boxed (i.e. configured with + {v --disable-flat-float-array v}) *) + fp: bool; + (** [true] if frame pointers are required (i.e. configured with + {v --enable-frame-pointers v} *) + tsan: bool; + (** [true] if ThreadSanitizer (TSAN) is required (i.e. configured with + {v --enable-tsan v}) *) + int31: bool; + (** [true] if the platform has 31-bit [int]s (i.e. 32-bit systems) *) + static: bool; + (** [true] if dynamic loading of libraries is not supported *) + no_compression: bool; + (** [true] if compressed marshalling is not supported *) + ansi: bool; + (** [true] if Unicode support on Windows is disabled *) + } + + val make_zinc: ?dev:bool -> ?release:int + -> ?no_flat_float_array:bool + -> ?int31:bool -> ?static:bool -> ?no_compression:bool + -> unit -> t + (** Returns the Zinc Runtime ID for the given parameters (using default values + from {!Config} and {!Sys} as necessary) *) + + val make_bytecode: ?dev:bool -> ?release:int + -> ?reserved:int -> ?no_flat_float_array:bool + -> ?int31:bool -> ?static:bool -> ?no_compression:bool + -> ?ansi:bool + -> unit -> t + (** Returns the Bytecode Runtime ID for the given parameters (using default + values from {!Config} and {!Sys} as necessary) *) + + val make_native: ?dev:bool -> ?release:int + -> ?reserved:int -> ?no_flat_float_array:bool -> ?fp:bool -> ?tsan:bool + -> ?int31:bool -> ?static:bool -> ?no_compression:bool + -> ?ansi:bool + -> unit -> t + (** Returns the Native Runtime ID for the given parameters (using default + values from {!Config} and {!Sys} as necessary) *) + + val is_zinc: t -> bool + (** [is_zinc t] is true if [t] can be used as a Zinc Runtime ID *) + + val is_bytecode: t -> bool + (** [is_bytecode t] is true if [t] can be used as a Bytecode Runtime ID *) + + val is_native: t -> bool + (** [is_native t] is true if [t] can be used as a Native Runtime ID *) + + val to_string: t -> string + (** Returns the 4-character representation of a {!t} *) + + val of_string: string -> t option + (** Converts the 4-character representation back to a {!t} *) + + val ocamlrun: string -> t -> string + (** [ocamlrun variant runtime_id] returns the name for the runtime for the + given Zinc Runtime ID. *) + + val shared_runtime: ?runtime_id:t -> ?host:string + -> ?prefix:string -> Sys.backend_type -> string + (** [shared_runtime ?runtime_id ?host ?prefix backend] returns the name of the + shared runtime for the given [backend]. [runtime_id] defaults to + {!make_bytecode} if [backend = Sys.Bytecode] and {!make_native} if + [backend = Sys.Native] and [host] to {!Config.target}. [prefix] defaults + to ["-l"] and the function does not append {!Config.ext_dll}. + + e.g. [shared_runtime ~host:"x86_64-pc-linux-gnu" Native + = "-lasmrun-x86_64-pc-linux-gnu-b100"] for a default OCaml 5.5 + build on a 64-bit system with shared library support and compressed + marshalling. *) + + val stubslib: ?runtime_id:t -> ?host:string -> string -> string + (** [stublibs ?runtime_id ?host dllname] returns the name for the given DLL + basename. [dllname] should not include {!Config.ext_dll} (and the result + does not include it either). [host] and [runtime_id] default to + {!Config.target} and {!make_bytecode} respectively. + + e.g. [stubslib ~host:"x86_64-pc-linux-gnu" "dllunixbyt" + = "dllunixbyt-x86_64-pc-linux-gnu-001b"] for a default OCaml 5.5 + build on a 64-bit system with shared library support and compressed + marshalling. *) +end + (** {1 Miscellaneous type aliases} *) type filepath = string From 8d39c7d1b1fa3b242e560658f67bda936c46314e Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 11:51:03 +0100 Subject: [PATCH 13/25] Adapt the build for lack of bootstrap boot/ocamlc still uses the old runtime-launch-info format and doesn't yet support -launch-method or -runtime-search. --- .gitignore | 3 +++ Makefile.common | 40 ++++++++++++++++++++++++++++++++++++++-- bytecomp/bytelink.ml | 3 +++ configure | 16 ++++++++++++++++ configure.ac | 18 ++++++++++++++++++ stdlib/Makefile | 14 +++++++++----- 6 files changed, 87 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 45450d808703..b906cf04a032 100644 --- a/.gitignore +++ b/.gitignore @@ -252,10 +252,13 @@ META /runtime/build_config.h /runtime/sak +/stdlib/runtime.info /stdlib/runtime-launch-info /stdlib/labelled-* /stdlib/caml /stdlib/sys.ml +/stdlib/target_runtime.info +/stdlib/target_runtime-launch-info /testsuite/**/*.result /testsuite/**/*.opt_result diff --git a/Makefile.common b/Makefile.common index 548f9f6322a9..b476ab85caf6 100644 --- a/Makefile.common +++ b/Makefile.common @@ -339,8 +339,44 @@ endef # _OCAML_PROGRAM_BASE # $(ROOTDIR)/ocamlc needs -launch-method to be given explicitly as its default # values are those for the target (cf. --with-target-sh and TARGET_BINDIR). BYTECODE_LAUNCHER_FLAGS = \ - -launch-method $(call QUOTE_SINGLE,$(LAUNCH_METHOD) $(BINDIR)) \ - -runtime-search $(if $(RUNTIME_SEARCH),$(RUNTIME_SEARCH),disable) + -launch-method $(call QUOTE_SINGLE,$(LAUNCH_METHOD) $(BINDIR)) + +# Historically, the native Windows ports are assumed to be finding ocamlrun +# using a PATH search. Since boot/ocamlc has no notion of the target, Windows +# requires -runtime-search to be passed explicitly. +ifeq "$(UNIX_OR_WIN32)" "win32" +BYTECODE_LAUNCHER_FLAGS += -runtime-search enable +endif + +# $(BOOTSTRAPPED) will be non-empty after the compiler has been bootstrapped, as +# the -launch-method string will appear in it. +BOOTSTRAPPED := \ + $(shell grep -Fq 'launch-method' $(ROOTDIR)/boot/ocamlc && echo Bootstrapped) + +# Prior to bootstrapping, boot/ocamlc gets the correct host values from +# boot/runtime-launch-info and doesn't yet recognise -launch-method. After +# bootstrapping, both compilers must be passed -launch-method. +ifeq "$(BOOTSTRAPPED)" "" +ROOT_LINK_FLAGS := $(BYTECODE_LAUNCHER_FLAGS) +BYTECODE_LAUNCHER_FLAGS := +endif + +ifeq "$(SUFFIXING)-$(UNIX_OR_WIN32)" "true-win32" +# Historically, the native Windows ports are assumed to be finding ocamlrun +# using a PATH search. -use-runtime ocamlrun-$(ZINC_RUNTIME_ID) won't work here +# as ocamlc will convert it to an absolute path. Instead, we (ab)use +# -runtime-variant to append the -$(ZINC_RUNTIME_ID) to the default ocamlrun. +BYTECODE_LAUNCHER_FLAGS += -runtime-variant -$(ZINC_RUNTIME_ID) +# boot/ocamlc is built with suffixing disabled, so this works both before and +# after bootstrapping, however this would cause $(ROOTDIR)/ocamlc to add the +# suffix twice. Thwart this by further (ab)using -runtime-variant. +ROOT_LINK_FLAGS += -runtime-variant '' +else ifeq "$(SUFFIXING)" "true" +# boot/ocamlc is built with suffixing disabled or not yet bootstrapped, so +# either way pass the suffixed runtime name explicitly with -use-runtime. +BYTECODE_LAUNCHER_FLAGS += \ + -use-runtime $(call QUOTE_SINGLE,$(BINDIR)/ocamlrun-$(ZINC_RUNTIME_ID)) +endif MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS = \ $(if $(filter -custom, $(1)),,\ diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index d9592425b5d6..75800ab97932 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -427,6 +427,9 @@ let write_header outchan = let zinc_runtime_id, offset = if String.length data < 2 then raise (Error (Camlheader ("corrupt header", header))) + (* Compatibility with previous header format - remove post-bootstrap *) + else if List.mem data.[0] ['/'; 'e'; 's'] then + None, String.index data '\000' + 2 else if data.[0] = '\000' then None, 1 else diff --git a/configure b/configure index 64da328c17d6..d000982fd9c5 100755 --- a/configure +++ b/configure @@ -14165,6 +14165,12 @@ esac fi +# stdlib/runtime.info and stdlib/target_runtime.info are generated by commands +# in config.status, rather than by the .in mechanism, since the latter cannot +# reliably process binary files. +ac_config_commands="$ac_config_commands shebang" + + # Checks for programs ## Check for the C compiler: done by libtool @@ -22686,6 +22692,11 @@ fi + launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' + launch_method_target=\ +'$(echo "$launch_method_target" | sed -e "s/'/'\"'\"'/g")' + HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' + TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")' ocaml_additional_stublibs_dir=\ '$(echo "$ocaml_additional_stublibs_dir" | sed -e "s/'/'\"'\"'/g")' ocaml_libdir='$(echo "$ocaml_libdir" | sed -e "s/'/'\"'\"'/g")' @@ -22727,6 +22738,7 @@ do "otherlibs/unix/META") CONFIG_FILES="$CONFIG_FILES otherlibs/unix/META" ;; "otherlibs/unix/unix.ml") CONFIG_LINKS="$CONFIG_LINKS otherlibs/unix/unix.ml:otherlibs/unix/unix_${unix_or_win32}.ml" ;; "otherlibs/str/META") CONFIG_FILES="$CONFIG_FILES otherlibs/str/META" ;; + "shebang") CONFIG_COMMANDS="$CONFIG_COMMANDS shebang" ;; "otherlibs/systhreads/META") CONFIG_FILES="$CONFIG_FILES otherlibs/systhreads/META" ;; "ocamltest/ocamltest_unix.ml") CONFIG_LINKS="$CONFIG_LINKS ocamltest/ocamltest_unix.ml:${ocamltest_unix_mod}" ;; "runtime/ld.conf") CONFIG_COMMANDS="$CONFIG_COMMANDS runtime/ld.conf" ;; @@ -23860,6 +23872,10 @@ ltmain=$ac_aux_dir/ltmain.sh chmod +x "$ofile" ;; + "shebang":C) printf '%s\n%s\000\n' "$launch_method" "$HOST_BINDIR" \ + > stdlib/runtime.info + printf '%s\n%s\000\n' "$launch_method_target" "$TARGET_BINDIR" \ + > stdlib/target_runtime.info ;; "runtime/ld.conf":C) rm -f runtime/ld.conf test x"$ocaml_additional_stublibs_dir" = 'x' || \ echo "$ocaml_additional_stublibs_dir" > runtime/ld.conf diff --git a/configure.ac b/configure.ac index db57fd1f3b56..f248285715b3 100644 --- a/configure.ac +++ b/configure.ac @@ -941,6 +941,24 @@ AS_IF([test "x$interpval" = "xyes"], )] ) +# stdlib/runtime.info and stdlib/target_runtime.info are generated by commands +# in config.status, rather than by the .in mechanism, since the latter cannot +# reliably process binary files. +AC_CONFIG_COMMANDS([shebang], + [printf '%s\n%s\000\n' "$launch_method" "$HOST_BINDIR" \ + > stdlib/runtime.info + printf '%s\n%s\000\n' "$launch_method_target" "$TARGET_BINDIR" \ + > stdlib/target_runtime.info], +dnl These declarations are put in a here-document in configure, so the command +dnl in '$(...)' _is_ evaluated as the content is written to config.status (by +dnl standard interpretation of a here-document). The sed commands quote any +dnl nefarious single quotes which may appear in any of the strings. + [launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' + launch_method_target=\ +'$(echo "$launch_method_target" | sed -e "s/'/'\"'\"'/g")' + HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' + TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")']) + # Checks for programs ## Check for the C compiler: done by libtool diff --git a/stdlib/Makefile b/stdlib/Makefile index 1e775f4fd17d..1bb0eac35a9a 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -54,7 +54,7 @@ NOSTDLIB= camlinternalFormatBasics.cmo stdlib.cmo OTHERS=$(filter-out $(NOSTDLIB),$(OBJS)) .PHONY: all -all: stdlib.cma std_exit.cmo $(HEADER_NAME) +all: stdlib.cma std_exit.cmo $(HEADER_NAME) target_$(HEADER_NAME) .PHONY: allopt opt.opt # allopt and opt.opt are synonyms allopt: stdlib.cmxa std_exit.cmx @@ -73,7 +73,7 @@ ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" *.cmt *.cmti *.mli *.ml *.ml.in \ "$(INSTALL_LIBDIR)" endif - $(INSTALL_DATA) $(HEADER_NAME) "$(INSTALL_LIBDIR)/$(HEADER_NAME)" + $(INSTALL_DATA) target_$(HEADER_NAME) "$(INSTALL_LIBDIR)/$(HEADER_NAME)" .PHONY: installopt installopt: installopt-default @@ -84,8 +84,12 @@ installopt-default: stdlib.cmxa stdlib.$(A) std_exit.$(O) *.cmx \ "$(INSTALL_LIBDIR)" +runtime-launch-info: runtime.info tmpheader.exe + @{ cat $^; \ + printf '$(if $(filter true,$(SUFFIXING)),$(ZINC_RUNTIME_ID))'; } > $@ + MANGLING = $(filter true,$(SUFFIXING)) -runtime-launch-info: tmpheader.exe +target_runtime-launch-info: tmpheader.exe @{ printf '$(if $(MANGLING),$(ZINC_RUNTIME_ID),\000)'; \ cat $^; } > $@ @@ -137,11 +141,11 @@ stdlib.cmxa: $(OBJS:.cmo=.cmx) .PHONY: distclean distclean: clean - rm -f sys.ml META + rm -f sys.ml META runtime.info target_runtime.info .PHONY: clean clean:: - rm -f $(HEADER_NAME) + rm -f $(HEADER_NAME) target_$(HEADER_NAME) export AWK From 73d01a7712150615cdd879a9e4be337c816d07cc Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 12:28:16 +0100 Subject: [PATCH 14/25] Adapt to OCaml 5.3 Config.shebangscripts was added in PR#14014 for the testuiste, but isn't part of the backport. Similarly, Bytelink.read_runtime_launch_info was exposed, only to be deleted as part of PR#14245, so the backport cuts out this intermediate step by temporarily copying Bytelink.read_rutime_launch_info, which therefore needs deleting here. --- bytecomp/bytelink.ml | 12 +++---- bytecomp/dll.ml | 2 +- bytecomp/dll.mli | 10 +++--- driver/compenv.ml | 4 +-- file_formats/cmo_format.mli | 2 +- otherlibs/dynlink/byte/dynlink_symtable.ml | 2 +- otherlibs/dynlink/byte/dynlink_symtable.mli | 2 +- testsuite/tools/testLinkModes.ml | 10 +++--- testsuite/tools/test_in_prefix.ml | 36 --------------------- testsuite/tools/toolchain.ml.in | 1 - testsuite/tools/toolchain.mli | 3 -- tools/objinfo.ml | 2 +- utils/clflags.ml | 2 +- utils/clflags.mli | 2 +- utils/config.fixed.ml | 1 + utils/config.generated.ml.in | 1 + utils/config.mli | 26 +++++++++------ utils/misc.ml | 2 +- utils/misc.mli | 2 +- 19 files changed, 45 insertions(+), 77 deletions(-) diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index 75800ab97932..4b146b2693fb 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -555,16 +555,16 @@ let link_bytecode ?final_name tolink exec_name standalone = Symtable.init(); clear_crc_interfaces (); let (tocheck, sharedobjs) = - let process_dllib ((~suffixed, name) as dllib) (tocheck, sharedobjs) = + let process_dllib ((suffixed, name) as dllib) (tocheck, sharedobjs) = let resolved_name = Dll.extract_dll_name dllib in let partial_name = if suffixed then if String.starts_with ~prefix:"-l" name then - (~suffixed, "dll" ^ String.sub name 2 (String.length name - 2)) + (suffixed, "dll" ^ String.sub name 2 (String.length name - 2)) else dllib else - (~suffixed:false, resolved_name) + (false, resolved_name) in (resolved_name::tocheck, partial_name::sharedobjs) in @@ -598,7 +598,7 @@ let link_bytecode ?final_name tolink exec_name standalone = end; (* The names of the DLLs *) if sharedobjs <> [] then begin - let output_sharedobj (~suffixed, name) = + let output_sharedobj (suffixed, name) = output_char outchan (if suffixed then '-' else ':'); output_string outchan name; output_byte outchan 0 @@ -727,7 +727,7 @@ let c_string_literal_of_string s = with the characters above converted to their C representations. On Windows, where the string is [wchar_t *], all characters for which iswprint returns 0 are escaped using the extended [\x] notation. *) - | c when Config.target_win32 && (c < 32 (* ' ' *) || c >= 127) -> + | c when Sys.win32 && (c < 32 (* ' ' *) || c >= 127) -> (* Convert u to UTF-16LE, allowing for surrogate pairs *) let len = Bytes.set_utf_16le_uchar utf16le 0 u in for i = 1 to len / 2 do @@ -736,7 +736,7 @@ let c_string_literal_of_string s = | _ -> Buffer.add_utf_8_uchar b u in - if Config.target_win32 then + if Sys.win32 then Buffer.add_char b 'L'; Buffer.add_char b '"'; Seq.iter escape (to_utf_8_seq s); diff --git a/bytecomp/dll.ml b/bytecomp/dll.ml index 3443c34df6ba..0d1f90ee0250 100644 --- a/bytecomp/dll.ml +++ b/bytecomp/dll.ml @@ -51,7 +51,7 @@ let remove_path dirs = (* Extract the name of a DLLs from its external name (xxx.so or -lxxx) *) -let extract_dll_name (~suffixed, file) = +let extract_dll_name (suffixed, file) = if not suffixed && Filename.check_suffix file Config.ext_dll then Filename.chop_suffix file Config.ext_dll else diff --git a/bytecomp/dll.mli b/bytecomp/dll.mli index 132daee8c050..99fd2ed3deed 100644 --- a/bytecomp/dll.mli +++ b/bytecomp/dll.mli @@ -15,11 +15,11 @@ (* Handling of dynamically-linked libraries *) -(* Extract the name of a DLLs from its mangled or external name. If - [~suffixed:true] then the name is just the undecorated basename of the DLL - (no -l and no .so). If [~suffixed:false] then the external name may include - the DLL extension or linking symbol (xxx.so or -lxxx) *) -val extract_dll_name: (suffixed:bool * string) -> string +(* Extract the name of a DLL from its mangled or external name. If the flag is + [true] then the name is just the undecorated basename of the DLL (no -l and + no .so). If the flag is [false] then the external name may include the DLL + extension or linking symbol (xxx.so or -lxxx) *) +val extract_dll_name: (bool * string) -> string type dll_mode = | For_checking (* will just check existence of symbols; diff --git a/driver/compenv.ml b/driver/compenv.ml index 4774a9adb874..10bb1b4e6d7f 100644 --- a/driver/compenv.ml +++ b/driver/compenv.ml @@ -630,7 +630,7 @@ let process_action | ProcessObjects names -> ccobjs := names @ !ccobjs | ProcessDLLs (suffixed, names) -> - dllibs := (List.map (fun n -> (~suffixed, n)) names) @ !dllibs + dllibs := (List.map (fun n -> (suffixed, n)) names) @ !dllibs | ProcessOtherFile name -> if Filename.check_suffix name ocaml_mod_ext || Filename.check_suffix name ocaml_lib_ext then @@ -643,7 +643,7 @@ let process_action ccobjs := name :: !ccobjs end else if not !native_code && Filename.check_suffix name Config.ext_dll then - dllibs := (~suffixed:false, name) :: !dllibs + dllibs := (false, name) :: !dllibs else match Compiler_pass.of_input_filename name with | Some start_from -> diff --git a/file_formats/cmo_format.mli b/file_formats/cmo_format.mli index b38cceab90a4..4a893bf57032 100644 --- a/file_formats/cmo_format.mli +++ b/file_formats/cmo_format.mli @@ -67,7 +67,7 @@ type library = how they end up being used on the command line. *) lib_ccobjs: string list; (* C object files needed for -custom *) lib_ccopts: string list; (* Extra opts to C compiler *) - lib_dllibs: (suffixed:bool * string) list } (* DLLs needed *) + lib_dllibs: (bool * string) list } (* DLLs needed *) (* Format of a .cma file: magic number (Config.cma_magic_number) diff --git a/otherlibs/dynlink/byte/dynlink_symtable.ml b/otherlibs/dynlink/byte/dynlink_symtable.ml index 632df6f774fa..8baea488878e 100644 --- a/otherlibs/dynlink/byte/dynlink_symtable.ml +++ b/otherlibs/dynlink/byte/dynlink_symtable.ml @@ -89,7 +89,7 @@ let primitives : (string, int) Hashtbl.t = Hashtbl.create 100 #52 "bytecomp/dll.ml" (* Extract the name of a DLLs from its external name (xxx.so or -lxxx) *) -let extract_dll_name (~suffixed, file) = +let extract_dll_name (suffixed, file) = if not suffixed && Filename.check_suffix file Config.ext_dll then Filename.chop_suffix file Config.ext_dll else diff --git a/otherlibs/dynlink/byte/dynlink_symtable.mli b/otherlibs/dynlink/byte/dynlink_symtable.mli index cb3f047d5e20..8db93a27de68 100644 --- a/otherlibs/dynlink/byte/dynlink_symtable.mli +++ b/otherlibs/dynlink/byte/dynlink_symtable.mli @@ -31,7 +31,7 @@ module Global : sig val description: Format.formatter -> t -> unit end -val open_dlls : (suffixed:bool * string) list -> unit +val open_dlls : (bool * string) list -> unit val patch_object: (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t -> diff --git a/testsuite/tools/testLinkModes.ml b/testsuite/tools/testLinkModes.ml index 3e518015333d..ed357394a63a 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -547,7 +547,7 @@ let compile_test usr_bin_sh config env test test_program description = | Output_obj(C_ocamlopt, Static) -> f ~mode:Native ~clibs:["-lcomprmarsh"; "-lunixnat"; - Toolchain.compression_c_libraries] + Config.compression_c_libraries] ["-output-obj"] | Output_obj(C_ocamlopt, Shared) -> (* cf. ocaml/ocaml#13693 - on Fedora/RHEL, this executable @@ -558,7 +558,7 @@ let compile_test usr_bin_sh config env test test_program description = let linker_exit_code = fails_if (Sys.win32 || Sys.cygwin) in f ~mode:Native ~use_shared_runtime:true ~may_segfault ~clibs:["-lcomprmarsh"; "-lunixnat"; - Toolchain.compression_c_libraries] + Config.compression_c_libraries] ~linker_exit_code ["-output-obj"] | Output_complete_obj(C_ocamlc, Static) -> (* At the moment, the partial linker will pass -lws2_32 and -ladvapi32 @@ -588,7 +588,7 @@ let compile_test usr_bin_sh config env test test_program description = (* At the moment, the partial linker will pass -lzstd to ld -r which will (normally) fail). Until this is done, pass the libraries manually, using -noautolink. *) - f ~mode:Native ~clibs:[Toolchain.compression_c_libraries] + f ~mode:Native ~clibs:[Config.compression_c_libraries] ~linker_exit_code ["-output-complete-obj"; "-noautolink"; "-cclib"; "-lunixnat"; "-cclib"; "-lcomprmarsh"] @@ -597,7 +597,7 @@ let compile_test usr_bin_sh config env test test_program description = fails with GNU ld, but not with the macOS linker *) let compilation_exit_code = fails_if (Config.system <> "macosx") in f ~mode:Native ~use_shared_runtime:true - ~compilation_exit_code ~clibs:[Toolchain.compression_c_libraries] + ~compilation_exit_code ~clibs:[Config.compression_c_libraries] ["-output-complete-obj"; "-noautolink"; "-cclib"; "-lunixnat"; "-cclib"; "-lcomprmarsh"] | Output_complete_exe Static -> @@ -787,7 +787,7 @@ let run ~sh config env = "nat_complete_obj_shared" "-output-complete-obj shared runtime"; ] in let tests = - if Config.shebangscripts then + if config.shebangscripts then (compile_test (Default_ocamlc(Header_shebang, Disable)) "byt_default_sh_disable" "with absolute #!") :: (compile_test (Default_ocamlc(Header_shebang, Fallback)) diff --git a/testsuite/tools/test_in_prefix.ml b/testsuite/tools/test_in_prefix.ml index 4530c133694f..4764b185d2fa 100644 --- a/testsuite/tools/test_in_prefix.ml +++ b/testsuite/tools/test_in_prefix.ml @@ -68,42 +68,6 @@ let run_tests ~sh config env = TestBytecodeBinaries.run config env; TestLinkModes.run ~sh config env -type launch_method = -| Shebang_bin_sh of string -| Executable - -type runtime_launch_info = { - buffer : string; - launcher : launch_method; - executable_offset : int -} - -let read_runtime_launch_info file = - let buffer = - try - In_channel.with_open_bin file In_channel.input_all - with Sys_error msg -> Harness.fail_because "%s: %s" file msg - in - try - let bindir_start = String.index buffer '\n' + 1 in - let bindir_end = String.index_from buffer bindir_start '\000' in - let executable_offset = bindir_end + 2 in - let launcher = - let kind = String.sub buffer 0 (bindir_start - 1) in - if kind = "exe" then - Executable - else if kind <> "" && (kind.[0] = '/' || kind = "sh") then - Shebang_bin_sh kind - else - raise Not_found in - if String.length buffer < executable_offset - || buffer.[executable_offset - 1] <> '\n' then - raise Not_found - else - {launcher; buffer; executable_offset} - with Not_found -> - Harness.fail_because "%s: corrupt header" file - let rename_exe_in_test_root env from_base to_base = Sys.rename (Environment.in_test_root env (Harness.exe from_base)) (Environment.in_test_root env (Harness.exe to_base)) diff --git a/testsuite/tools/toolchain.ml.in b/testsuite/tools/toolchain.ml.in index e976fe59b46a..394eb856772e 100644 --- a/testsuite/tools/toolchain.ml.in +++ b/testsuite/tools/toolchain.ml.in @@ -15,7 +15,6 @@ open Harness.Import let c_compiler_vendor = {@QS@|@ocaml_cc_vendor@|@QS@} -let compression_c_libraries = {@QS@|@zstd_libs@|@QS@} (* cf. OCAML_CC_VENDOR in aclocal.m4 and utils.config.mli *) let is_clang = diff --git a/testsuite/tools/toolchain.mli b/testsuite/tools/toolchain.mli index ca5a71948cfa..b9a76e06fcea 100644 --- a/testsuite/tools/toolchain.mli +++ b/testsuite/tools/toolchain.mli @@ -49,6 +49,3 @@ val linker_is_flexlink : bool val c_compiler_vendor : string (** The vendor and version of the C compiler, as determined by configure.ac (see Config.c_compiler_vendor in OCaml 5.5) *) - -val compression_c_libraries : string -(** The C libraries needed with -lcomprmarsh *) diff --git a/tools/objinfo.ml b/tools/objinfo.ml index f43c0e850762..aa94d2bd2224 100644 --- a/tools/objinfo.ml +++ b/tools/objinfo.ml @@ -74,7 +74,7 @@ let print_cmo_infos cu = let print_spaced_string s = printf " %s" s -let dllib (~suffixed, name) = +let dllib (suffixed, name) = if suffixed then Printf.sprintf "%s--" name else diff --git a/utils/clflags.ml b/utils/clflags.ml index 0b257a0e9d9f..ab48a33138d1 100644 --- a/utils/clflags.ml +++ b/utils/clflags.ml @@ -40,7 +40,7 @@ end) let objfiles = ref ([] : string list) (* .cmo and .cma files *) and ccobjs = ref ([] : string list) (* .o, .a, .so and -cclib -lxxx *) -and dllibs = ref ([] : (suffixed:bool * string) list) +and dllibs = ref ([] : (bool * string) list) (* .so, -dllib -lxxx and -dllib-suffixed -lxxx *) diff --git a/utils/clflags.mli b/utils/clflags.mli index 7450996f5a08..48fad440b062 100644 --- a/utils/clflags.mli +++ b/utils/clflags.mli @@ -70,7 +70,7 @@ val use_inlining_arguments_set : ?round:int -> inlining_arguments -> unit val objfiles : string list ref val ccobjs : string list ref -val dllibs : (suffixed:bool * string) list ref +val dllibs : (bool * string) list ref val cmi_file : string option ref val compile_only : bool ref val output_name : string option ref diff --git a/utils/config.fixed.ml b/utils/config.fixed.ml index 26f60e4ff6c4..77fef2fc450d 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -37,6 +37,7 @@ let bytecomp_c_libraries = "" let bytecomp_c_compiler = "" let native_c_compiler = c_compiler let native_c_libraries = "" +let compression_c_libraries = "" let native_ldflags = "" let native_pack_linker = boot_cannot_call "the linker" let default_rpath = "" diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index 94c5189d1bb2..7a87638f2373 100644 --- a/utils/config.generated.ml.in +++ b/utils/config.generated.ml.in @@ -46,6 +46,7 @@ let bytecomp_c_compiler = let native_c_compiler = c_compiler ^ " " ^ native_cflags ^ " " ^ native_cppflags let native_c_libraries = {@QS@|@cclibs@|@QS@} +let compression_c_libraries = {@QS@|@zstd_libs@|@QS@} let native_ldflags = {@QS@|@native_ldflags@|@QS@} let native_pack_linker = {@QS@|@PACKLD@|@QS@} let default_rpath = {@QS@|@rpath@|@QS@} diff --git a/utils/config.mli b/utils/config.mli index 710fcd317876..cae3dcb7428f 100644 --- a/utils/config.mli +++ b/utils/config.mli @@ -26,12 +26,12 @@ val version: string val release_number: int (** The release number for the compiler - @since 5.5 *) + @since 5.4.2 *) val is_official_release: bool (** True if the compiler is an unmodified official OCaml release - @since 5.5 *) + @since 5.4.2 *) val bindir: string (** The directory containing the binary programs. If the compiler was configured @@ -48,7 +48,7 @@ val standard_library_relative: string option val target_bindir: string (** The directory containing the runtime binaries on the target system - @since 5.5 *) + @since 5.4.2 *) val standard_library_default: string (** The effective value for the default directory containing the standard @@ -103,6 +103,12 @@ val bytecomp_c_libraries: string val native_c_libraries: string (** The C libraries to link with native-code programs *) +val compression_c_libraries: string +(** The C libraries needed with -lcomprmarsh (should appear before + {!native_c_libraries} in a call to the C compiler) + + @since 5.3.2 *) + val native_ldflags : string (* Flags to pass to the system linker *) @@ -298,7 +304,7 @@ val tsan : bool (** Launch mechanisms for bytecode executables - @since 5.5 *) + @since 5.4.2 *) type launch_method = | Executable (** Use the executable launcher stub *) @@ -312,11 +318,11 @@ type launch_method = val launch_method : launch_method (** Default launch mechanism for bytecode executables - @since 5.5 *) + @since 5.4.2 *) (** Mechanisms used by tendered bytecode executables to locate the interpreter - @since 5.5 *) + @since 5.4.2 *) type search_method = | Disable (** Interpreter searching disabled - check fixed absolute location only *) @@ -329,23 +335,23 @@ type search_method = val search_method : search_method (** Default search mechanism for bytecode executables - @since 5.5 *) + @since 5.4.2 *) val suffixing : bool (** Whether the runtime executable and shared library filenames and C stub library filenames are being mangled with Runtime IDs and the {!target}. - @since 5.5 *) + @since 5.4.2 *) val bytecode_runtime_id : string (** The Runtime ID for this build of the bytecode runtime system - @since 5.5 *) + @since 5.4.2 *) val native_runtime_id : string (** The Runtime ID for this build of the native runtime system - @since 5.5 *) + @since 5.4.2 *) (** Access to configuration values *) val print_config : out_channel -> unit diff --git a/utils/misc.ml b/utils/misc.ml index 1dadd934420b..5d9afaa80bed 100644 --- a/utils/misc.ml +++ b/utils/misc.ml @@ -1414,7 +1414,7 @@ module RuntimeID = struct ?(int31 = (Sys.int_size = 31)) ?(static = not Config.supports_shared_libraries) ?(no_compression = (Config.compression_c_libraries = "")) - ?(ansi = Config.target_win32 && not Config.windows_unicode) () = + ?(ansi = Sys.win32 && not Config.windows_unicode) () = if release < 0 || release > 63 || reserved < 0 || reserved > 31 then invalid_arg fn else diff --git a/utils/misc.mli b/utils/misc.mli index 51886ee81b1d..8bf42334eed9 100644 --- a/utils/misc.mli +++ b/utils/misc.mli @@ -827,7 +827,7 @@ module RuntimeID : sig (** Manipulation of the Runtime ID values used to mangle the filenames of shared libraries and the bytecode interpreters. - @since 5.5 *) + @since 5.3.2 *) (** Runtime IDs *) type t = private { From 1a58be7e765fc8302b7400be99eb48da0c720f81 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 15:03:42 +0100 Subject: [PATCH 15/25] Backporting - reduce interface changes - Byterntm removed from ocamlbytecomp and linked directly in ocamlobjinfo (the test-in-prefix driver likewise links it directly) - Cmm_helpers.emit_global_string_constant inlined into its single use in Asmlink.make_startup_file - Compenv.parse_runtime_parameter inlined into its single use in Main_args and Compenv.fatalf consequently removed - Config.as_is_cc moved to Toolchain.as_is_cc - Config.target_{unix,win32,cygwin} removed and uses of Config.target_win32 inlined --- .depend | 36 +++++++++++++++---------------- .gitignore | 2 +- Makefile | 9 ++++---- asmcomp/asmlink.ml | 4 ++-- asmcomp/cmm_helpers.ml | 3 --- asmcomp/cmm_helpers.mli | 3 --- driver/compenv.ml | 13 ----------- driver/compenv.mli | 4 ---- driver/main_args.ml | 15 ++++++++++++- driver/maindriver.ml | 2 +- driver/optmaindriver.ml | 2 +- testsuite/tools/testRelocation.ml | 2 +- testsuite/tools/toolchain.ml.in | 1 + testsuite/tools/toolchain.mli | 4 ++++ {bytecomp => tools}/byterntm.mli | 0 {bytecomp => tools}/byterntm.mll | 0 utils/config.common.ml.in | 1 - utils/config.fixed.ml | 1 - utils/config.generated.ml.in | 1 - utils/config.mli | 11 ---------- 20 files changed, 47 insertions(+), 67 deletions(-) rename {bytecomp => tools}/byterntm.mli (100%) rename {bytecomp => tools}/byterntm.mll (100%) diff --git a/.depend b/.depend index 09961ad09ddb..8449b91a293b 100644 --- a/.depend +++ b/.depend @@ -2421,17 +2421,6 @@ bytecomp/bytepackager.cmi : \ utils/format_doc.cmi \ typing/env.cmi \ file_formats/cmo_format.cmi -bytecomp/byterntm.cmo : \ - utils/misc.cmi \ - bytecomp/bytesections.cmi \ - bytecomp/byterntm.cmi -bytecomp/byterntm.cmx : \ - utils/misc.cmx \ - bytecomp/bytesections.cmx \ - bytecomp/byterntm.cmi -bytecomp/byterntm.cmi : \ - utils/misc.cmi \ - bytecomp/bytesections.cmi bytecomp/bytesections.cmo : \ utils/config.cmi \ bytecomp/bytesections.cmi @@ -7900,6 +7889,17 @@ lex/table.cmo : \ lex/table.cmx : \ lex/table.cmi lex/table.cmi : +tools/byterntm.cmo : \ + utils/misc.cmi \ + bytecomp/bytesections.cmi \ + tools/byterntm.cmi +tools/byterntm.cmx : \ + utils/misc.cmx \ + bytecomp/bytesections.cmx \ + tools/byterntm.cmi +tools/byterntm.cmi : \ + utils/misc.cmi \ + bytecomp/bytesections.cmi tools/cmpbyt.cmo : \ bytecomp/bytesections.cmi \ tools/cmpbyt.cmi @@ -8003,7 +8003,7 @@ tools/objinfo.cmo : \ file_formats/cmo_format.cmi \ file_formats/cmi_format.cmi \ bytecomp/bytesections.cmi \ - bytecomp/byterntm.cmi \ + tools/byterntm.cmi \ utils/binutils.cmi \ tools/objinfo.cmi tools/objinfo.cmx : \ @@ -8028,7 +8028,7 @@ tools/objinfo.cmx : \ file_formats/cmo_format.cmi \ file_formats/cmi_format.cmx \ bytecomp/bytesections.cmx \ - bytecomp/byterntm.cmx \ + tools/byterntm.cmx \ utils/binutils.cmx \ tools/objinfo.cmi tools/objinfo.cmi : @@ -10433,7 +10433,7 @@ testsuite/tools/environment.cmo : \ file_formats/cmt_format.cmi \ file_formats/cmo_format.cmi \ bytecomp/bytesections.cmi \ - bytecomp/byterntm.cmi \ + tools/byterntm.cmi \ testsuite/tools/environment.cmi testsuite/tools/environment.cmx : \ otherlibs/unix/unix.cmx \ @@ -10444,7 +10444,7 @@ testsuite/tools/environment.cmx : \ file_formats/cmt_format.cmx \ file_formats/cmo_format.cmi \ bytecomp/bytesections.cmx \ - bytecomp/byterntm.cmx \ + tools/byterntm.cmx \ testsuite/tools/environment.cmi testsuite/tools/environment.cmi : \ testsuite/tools/harness.cmi @@ -10490,18 +10490,18 @@ testsuite/tools/harness.cmo : \ otherlibs/unix/unix.cmi \ utils/misc.cmi \ utils/config.cmi \ - bytecomp/byterntm.cmi \ + tools/byterntm.cmi \ testsuite/tools/harness.cmi testsuite/tools/harness.cmx : \ otherlibs/unix/unix.cmx \ utils/misc.cmx \ utils/config.cmx \ - bytecomp/byterntm.cmx \ + tools/byterntm.cmx \ testsuite/tools/harness.cmi testsuite/tools/harness.cmi : \ utils/misc.cmi \ utils/config.cmi \ - bytecomp/byterntm.cmi + tools/byterntm.cmi testsuite/tools/lexcmm.cmo : \ testsuite/tools/parsecmm.cmi \ utils/misc.cmi \ diff --git a/.gitignore b/.gitignore index b906cf04a032..d1f61a77dabe 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,6 @@ META /bytecomp/opcodes.ml /bytecomp/opcodes.mli -/bytecomp/byterntm.ml /debugger/debugger_lexer.ml /debugger/debugger_parser.ml @@ -283,6 +282,7 @@ META /testsuite/tools/test_in_prefix.opt /testsuite/tools/toolchain.ml +/tools/byterntm.ml /tools/ocamldep /tools/ocamldep.opt /tools/ocamlprof diff --git a/Makefile b/Makefile index abfe1fb166d5..27c4d6560ac1 100644 --- a/Makefile +++ b/Makefile @@ -206,7 +206,6 @@ ocamlcommon_SOURCES = \ $(lambda_SOURCES) $(comp_SOURCES) ocamlbytecomp_SOURCES = \ - bytecomp/byterntm.mll \ bytecomp/instruct.mli bytecomp/instruct.ml \ bytecomp/bytegen.mli bytecomp/bytegen.ml \ bytecomp/printinstr.mli bytecomp/printinstr.ml \ @@ -1108,9 +1107,9 @@ otherlibs/dynlink.depend: beforedepend # Cleanup the lexers partialclean:: - rm -f bytecomp/byterntm.ml parsing/lexer.ml + rm -f tools/byterntm.ml parsing/lexer.ml -beforedepend:: bytecomp/byterntm.ml parsing/lexer.ml +beforedepend:: tools/byterntm.ml parsing/lexer.ml # The predefined exceptions and primitives @@ -2014,7 +2013,7 @@ $(asmgen_OBJECT): $(asmgen_SOURCE) $(V_ASM)$(ASPP) $(OC_ASPPFLAGS) -o $@ $< || $(ASPP_ERROR) endif -test_in_prefix_SOURCES = $(addprefix testsuite/tools/,\ +test_in_prefix_SOURCES = tools/byterntm.mll $(addprefix testsuite/tools/,\ stubs.c \ harness.mli harness.ml \ toolchain.mli toolchain.ml \ @@ -2496,7 +2495,7 @@ beforedepend:: $(addprefix tools/,opnames.ml make_opcodes.ml) ocamlobjinfo_LIBRARIES = \ $(addprefix compilerlibs/,ocamlcommon ocamlbytecomp ocamlmiddleend) -ocamlobjinfo_SOURCES = tools/objinfo.mli tools/objinfo.ml +ocamlobjinfo_SOURCES = tools/byterntm.mll tools/objinfo.mli tools/objinfo.ml # Scan object files for required primitives diff --git a/asmcomp/asmlink.ml b/asmcomp/asmlink.ml index b2d162ac3d02..e88944564e94 100644 --- a/asmcomp/asmlink.ml +++ b/asmcomp/asmlink.ml @@ -239,8 +239,8 @@ let make_startup_file ~ppf_dump units_list ~crc_interfaces = Option.value ~default:Config.standard_library_default !Clflags.standard_library_default in compile_phrase - (Cmm_helpers.emit_global_string_constant - "caml_standard_library_nat" standard_library_default) + (Cdata (Cmm_helpers.emit_string_constant + ("caml_standard_library_nat", Global) standard_library_default [])) end; compile_phrase (Cmm_helpers.global_table name_list); let globals_map = make_globals_map units_list ~crc_interfaces in diff --git a/asmcomp/cmm_helpers.ml b/asmcomp/cmm_helpers.ml index c87ecbee155c..c99c39bc0013 100644 --- a/asmcomp/cmm_helpers.ml +++ b/asmcomp/cmm_helpers.ml @@ -2605,9 +2605,6 @@ let predef_exception i name = in Cdata data_items -let emit_global_string_constant name value = - Cdata (emit_string_constant (name, Global) value []) - (* Header for a plugin *) let plugin_header units = diff --git a/asmcomp/cmm_helpers.mli b/asmcomp/cmm_helpers.mli index 2115b42f5cb5..513b4adf95c8 100644 --- a/asmcomp/cmm_helpers.mli +++ b/asmcomp/cmm_helpers.mli @@ -619,9 +619,6 @@ val code_segment_table: string list -> phrase (** Generate data for a predefined exception *) val predef_exception: int -> string -> phrase -(** Generate data for a global string constant *) -val emit_global_string_constant: string -> string -> phrase - val plugin_header: (Cmx_format.unit_infos * Digest.t) list -> phrase (** Emit constant symbols *) diff --git a/driver/compenv.ml b/driver/compenv.ml index 10bb1b4e6d7f..73f24bd605eb 100644 --- a/driver/compenv.ml +++ b/driver/compenv.ml @@ -43,8 +43,6 @@ let fatal err = prerr_endline err; raise (Exit_with_status 2) -let fatalf fmt = Printf.ksprintf fatal fmt - let extract_output = function | Some s -> s | None -> @@ -734,14 +732,3 @@ let parse_arguments ?(current=ref 0) argv f program = Printf.sprintf "Usage: %s \nOptions are:" program in Printf.printf "%s\n%s" help_msg err_msg; raise (Exit_with_status 0) - -let parse_runtime_parameter opt = - let k, setting = - try Misc.cut_at opt '=' - with Not_found -> - fatalf "-set-runtime-default: invalid runtime parameter '%s'. \ - Expected =." opt in - if k = "standard_library_default" then - Clflags.standard_library_default := Some setting - else - fatalf "-set-runtime-default: unrecognized runtime parameter %s." k diff --git a/driver/compenv.mli b/driver/compenv.mli index 576e8d24fc2a..7260eb8affb5 100644 --- a/driver/compenv.mli +++ b/driver/compenv.mli @@ -23,7 +23,6 @@ val print_version_and_library : string -> 'a val print_version_string : unit -> 'a val print_standard_library : unit -> 'a val fatal : string -> 'a -val fatalf : ('a, unit, string, 'b) format4 -> 'a val first_ccopts : string list ref val first_ppx : string list ref @@ -77,6 +76,3 @@ val process_deferred_actions : *) val parse_arguments : ?current:(int ref) -> string array ref -> Arg.anon_fun -> string -> unit - -(** Validate a single -set-runtime-default parameter specification. *) -val parse_runtime_parameter : string -> unit diff --git a/driver/main_args.ml b/driver/main_args.ml index a0b21a160152..7543eb874bea 100644 --- a/driver/main_args.ml +++ b/driver/main_args.ml @@ -1793,6 +1793,19 @@ module Default = struct let _verbose = set verbose end + let fatalf fmt = Printf.ksprintf Compenv.fatal fmt + + let parse_runtime_parameter opt = + let k, setting = + try Misc.cut_at opt '=' + with Not_found -> + fatalf "-set-runtime-default: invalid runtime parameter '%s'. \ + Expected =." opt in + if k = "standard_library_default" then + Clflags.standard_library_default := Some setting + else + fatalf "-set-runtime-default: unrecognized runtime parameter %s." k + module Compiler = struct let _a = set make_archive let _annot = set annotations @@ -1831,7 +1844,7 @@ module Default = struct let _plugin _p = plugin := true let _pp s = preprocessor := (Some s) let _runtime_variant s = runtime_variant := s - let _set_runtime_default s = Compenv.parse_runtime_parameter s + let _set_runtime_default s = parse_runtime_parameter s let _stop_after pass = let module P = Compiler_pass in match P.of_string pass with diff --git a/driver/maindriver.ml b/driver/maindriver.ml index fc27f6be4568..c008221c54fa 100644 --- a/driver/maindriver.ml +++ b/driver/maindriver.ml @@ -61,7 +61,7 @@ let main argv ppf = "Please specify at most one of -pack, -a, -c, -output-obj"; | Some ((P.Parsing | P.Typing | P.Lambda) as p) -> assert (P.is_compilation_pass p); - Compenv.fatalf + Printf.ksprintf Compenv.fatal "Options -i and -stop-after (%s) \ are incompatible with -pack, -a, -output-obj" (String.concat "|" diff --git a/driver/optmaindriver.ml b/driver/optmaindriver.ml index 124890367ab4..0cacd3363a80 100644 --- a/driver/optmaindriver.ml +++ b/driver/optmaindriver.ml @@ -77,7 +77,7 @@ let main argv ppf = -output-obj"; | Some ((P.Parsing | P.Typing | P.Lambda | P.Scheduling | P.Emit) as p) -> assert (P.is_compilation_pass p); - Compenv.fatalf + Printf.ksprintf Compenv.fatal "Options -i and -stop-after (%s) \ are incompatible with -pack, -a, -shared, -output-obj" (String.concat "|" diff --git a/testsuite/tools/testRelocation.ml b/testsuite/tools/testRelocation.ml index 256b8025ecbd..6a2ff3ce6c42 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -36,7 +36,7 @@ let effective_toolchain config = Toolchain.assembler_embeds_build_path && (not Config.as_has_debug_prefix_map || Config.architecture = "riscv" - || Config.as_is_cc + || Toolchain.as_is_cc || config.has_relative_libdir = None) in c_compiler_debug_paths_are_absolute, assembler_embeds_build_path diff --git a/testsuite/tools/toolchain.ml.in b/testsuite/tools/toolchain.ml.in index 394eb856772e..7c8e7a456269 100644 --- a/testsuite/tools/toolchain.ml.in +++ b/testsuite/tools/toolchain.ml.in @@ -15,6 +15,7 @@ open Harness.Import let c_compiler_vendor = {@QS@|@ocaml_cc_vendor@|@QS@} +let as_is_cc = @as_is_cc@ (* cf. OCAML_CC_VENDOR in aclocal.m4 and utils.config.mli *) let is_clang = diff --git a/testsuite/tools/toolchain.mli b/testsuite/tools/toolchain.mli index b9a76e06fcea..625bdf5057c4 100644 --- a/testsuite/tools/toolchain.mli +++ b/testsuite/tools/toolchain.mli @@ -49,3 +49,7 @@ val linker_is_flexlink : bool val c_compiler_vendor : string (** The vendor and version of the C compiler, as determined by configure.ac (see Config.c_compiler_vendor in OCaml 5.5) *) + +val as_is_cc : bool +(** Whether the assembler is actually an assembler, or whether we are really + assembling files via the C compiler (see Config.as_is_cc in OCaml 5.5) *) diff --git a/bytecomp/byterntm.mli b/tools/byterntm.mli similarity index 100% rename from bytecomp/byterntm.mli rename to tools/byterntm.mli diff --git a/bytecomp/byterntm.mll b/tools/byterntm.mll similarity index 100% rename from bytecomp/byterntm.mll rename to tools/byterntm.mll diff --git a/utils/config.common.ml.in b/utils/config.common.ml.in index c2adc27bcd12..5e744ee7437b 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -99,7 +99,6 @@ let lazy_tag = 246 let max_young_wosize = 256 let stack_threshold = 32 (* see runtime/caml/config.h *) let stack_safety_margin = 6 -let target_win32 = Sys.win32 let default_executable_name = match Sys.os_type with "Unix" -> "a.out" diff --git a/utils/config.fixed.ml b/utils/config.fixed.ml index 77fef2fc450d..a6cabaeaaacd 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -28,7 +28,6 @@ let c_compiler = boot_cannot_call "the C compiler" let c_output_obj = "" let c_has_debug_prefix_map = false let as_has_debug_prefix_map = false -let as_is_cc = false let bytecode_cflags = "" let bytecode_cppflags = "" let native_cflags = "" diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index 7a87638f2373..1dcbb9bf3857 100644 --- a/utils/config.generated.ml.in +++ b/utils/config.generated.ml.in @@ -28,7 +28,6 @@ let c_compiler = {@QS@|@CC@|@QS@} let c_output_obj = {@QS@|@outputobj@|@QS@} let c_has_debug_prefix_map = @cc_has_debug_prefix_map@ let as_has_debug_prefix_map = @as_has_debug_prefix_map@ -let as_is_cc = @as_is_cc@ let bytecode_cflags = {@QS@|@bytecode_cflags@|@QS@} let bytecode_cppflags = {@QS@|@bytecode_cppflags@|@QS@} let native_cflags = {@QS@|@native_cflags@|@QS@} diff --git a/utils/config.mli b/utils/config.mli index cae3dcb7428f..7ccbd70f80d4 100644 --- a/utils/config.mli +++ b/utils/config.mli @@ -79,12 +79,6 @@ val c_has_debug_prefix_map : bool val as_has_debug_prefix_map : bool (** Whether the assembler supports --debug-prefix-map *) -val as_is_cc : bool -(** Whether the assembler is actually an assembler, or whether we are really - assembling files via the C compiler - - @since 5.3.2 *) - val bytecode_cflags : string (** The flags ocamlc should pass to the C compiler *) @@ -207,11 +201,6 @@ val model: string val system: string (** Name of operating system for the native-code compiler *) -val target_win32: bool -(** True if [target_os_type = "Win32"] - - @since 5.3.2 *) - val asm: string (** The assembler (and flags) to use for assembling ocamlopt-generated code. *) From ba1eae1631f0c4716ab932dd627f3ef1c7e95f3d Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Thu, 18 Jun 2026 18:39:54 +0100 Subject: [PATCH 16/25] Backporting - reduce runtime header changes Virtually elimates changes to the runtime headers: - caml_parse_ld_conf (protected by CAML_INTERNALS) changes arguments, but this function was already checked for not being used outside the runtime as part of OCaml 5.5 - exec.h now includes (protected by CAML_INTERNALS) - version.h now defines OCAML_RELEASE_NUMBER --- .gitignore | 1 + Makefile | 2 +- bytecomp/bytelink.ml | 14 ++++++++++++-- configure | 3 +++ configure.ac | 1 + runtime/backtrace_byt.c | 2 +- runtime/caml/osdeps.h | 36 ------------------------------------ runtime/caml/s.h.in | 6 ------ runtime/caml/startup.h | 17 ++++++----------- runtime/caml/sys.h | 4 ---- runtime/dynlink.c | 14 ++++++++++++++ runtime/gen_primsc.sh | 5 ++++- runtime/startup_byt.c | 18 ++++++++++++++---- runtime/sys.c | 6 ++++++ runtime/sys_int.h.in | 22 ++++++++++++++++++++++ runtime/unix.c | 9 ++++++++- runtime/win32.c | 10 +++++++++- stdlib/header.c | 1 + 18 files changed, 103 insertions(+), 68 deletions(-) create mode 100644 runtime/sys_int.h.in diff --git a/.gitignore b/.gitignore index d1f61a77dabe..05424e826bb2 100644 --- a/.gitignore +++ b/.gitignore @@ -240,6 +240,7 @@ META /runtime/primitives /runtime/primitives*.new /runtime/prims.c +/runtime/sys_int.h /runtime/caml/exec.h /runtime/caml/opnames.h /runtime/caml/version.h diff --git a/Makefile b/Makefile index 27c4d6560ac1..04a969c976ed 100644 --- a/Makefile +++ b/Makefile @@ -1268,7 +1268,7 @@ runtime_NATIVE_C_SOURCES = \ ## Header files generated by configure runtime_CONFIGURED_HEADERS = \ - $(addprefix runtime/caml/, exec.h m.h s.h version.h) + runtime/sys_int.h $(addprefix runtime/caml/, exec.h m.h s.h version.h) ## Header files generated by make runtime_BUILT_HEADERS = $(addprefix runtime/, \ diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index 4b146b2693fb..c23e4a426455 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -765,6 +765,8 @@ let link_bytecode_as_c tolink outfile with_main = extern "C" { #endif +#include + #define CAML_INTERNALS #define CAMLDLLIMPORT #define CAML_INTERNALS_NO_PRIM_DECLARATIONS @@ -774,7 +776,10 @@ extern "C" { #include #include -const enum caml_byte_program_mode caml_byte_program_mode = EMBEDDED; +extern const char_os *caml_runtime_standard_library_default; + +enum caml_byte_program_mode caml_byte_program_mode = COMPLETE_EXE; +const bool caml_byte_program_mode_custom = false; static int caml_code[] = { |}; @@ -972,10 +977,15 @@ extern "C" { #define CAML_INTERNALS #define CAML_INTERNALS_NO_PRIM_DECLARATIONS +#include + #include #include -const enum caml_byte_program_mode caml_byte_program_mode = APPENDED; +extern const char_os *caml_runtime_standard_library_default; + +enum caml_byte_program_mode caml_byte_program_mode = STANDARD; +const bool caml_byte_program_mode_custom = true; |}; Symtable.output_primitive_table poc; diff --git a/configure b/configure index d000982fd9c5..61e8e1100248 100755 --- a/configure +++ b/configure @@ -3627,6 +3627,8 @@ ac_config_headers="$ac_config_headers runtime/caml/m.h" ac_config_headers="$ac_config_headers runtime/caml/s.h" +ac_config_headers="$ac_config_headers runtime/sys_int.h" + ac_config_headers="$ac_config_headers runtime/caml/version.h" ac_config_files="$ac_config_files compilerlibs/META" @@ -22723,6 +22725,7 @@ do "runtime/caml/exec.h") CONFIG_HEADERS="$CONFIG_HEADERS runtime/caml/exec.h" ;; "runtime/caml/m.h") CONFIG_HEADERS="$CONFIG_HEADERS runtime/caml/m.h" ;; "runtime/caml/s.h") CONFIG_HEADERS="$CONFIG_HEADERS runtime/caml/s.h" ;; + "runtime/sys_int.h") CONFIG_HEADERS="$CONFIG_HEADERS runtime/sys_int.h" ;; "runtime/caml/version.h") CONFIG_HEADERS="$CONFIG_HEADERS runtime/caml/version.h" ;; "compilerlibs/META") CONFIG_FILES="$CONFIG_FILES compilerlibs/META" ;; "otherlibs/dynlink/META") CONFIG_FILES="$CONFIG_FILES otherlibs/dynlink/META" ;; diff --git a/configure.ac b/configure.ac index f248285715b3..0aeac3e28141 100644 --- a/configure.ac +++ b/configure.ac @@ -315,6 +315,7 @@ AC_CONFIG_FILES([utils/config.generated.ml]) AC_CONFIG_HEADERS([runtime/caml/exec.h]) AC_CONFIG_HEADERS([runtime/caml/m.h]) AC_CONFIG_HEADERS([runtime/caml/s.h]) +AC_CONFIG_HEADERS([runtime/sys_int.h]) AC_CONFIG_HEADERS([runtime/caml/version.h]) AC_CONFIG_FILES([compilerlibs/META]) AC_CONFIG_FILES([otherlibs/dynlink/META]) diff --git a/runtime/backtrace_byt.c b/runtime/backtrace_byt.c index 074dc42274f7..982dfe47eba1 100644 --- a/runtime/backtrace_byt.c +++ b/runtime/backtrace_byt.c @@ -456,7 +456,7 @@ static void read_main_debug_info(struct debug_info *di) See https://github.com/ocaml/ocaml/issues/9344 for details. */ - if (caml_params->cds_file == NULL && caml_byte_program_mode == EMBEDDED) + if (caml_params->cds_file == NULL && caml_byte_program_mode == COMPLETE_EXE) CAMLreturn0; if (caml_params->cds_file != NULL) { diff --git a/runtime/caml/osdeps.h b/runtime/caml/osdeps.h index 60166ad5c64e..7215668c397f 100644 --- a/runtime/caml/osdeps.h +++ b/runtime/caml/osdeps.h @@ -95,22 +95,6 @@ void *caml_plat_mem_commit(void *, uintnat); void caml_plat_mem_decommit(void *, uintnat); void caml_plat_mem_unmap(void *, uintnat); -/* caml_locate_standard_library(exe_name, stdlib_default, dirname) returns the - location of the Standard Library. The location returned is absolute, if - stdlib_default is a relative path then the result is computed relative to the - directory portion of exe_name. - - If dirname is not NULL and stdlib_default is a relative path, a copy of the - directory name part of exe_name is returned in dirname. If stdlib_default is - an absolute path, dirname is never changed. - - Both strings are allocated with [caml_stat_alloc], so should be freed using - [caml_stat_free]. -*/ -CAMLextern char_os *caml_locate_standard_library (const char_os *exe_name, - const char_os *stdlib_default, - char_os **dirname); - #ifdef _WIN32 #include @@ -151,14 +135,6 @@ CAMLextern clock_t caml_win32_clock(void); CAMLextern value caml_win32_xdg_defaults(void); -#define CAML_DIR_SEP T("\\") -#define Is_separator(c) (c == '\\' || c == '/') - -#else - -#define CAML_DIR_SEP T("/") -#define Is_separator(c) (c == '/') - #endif /* _WIN32 */ /* Returns the current value of a counter that increments once per nanosecond. @@ -172,18 +148,6 @@ extern uint64_t caml_time_counter(void); extern void caml_init_os_params(void); -/* True if: - - dir equals "." - - dir equals ".." - - dir begins "./" - - dir begins "../" - The tests for null avoid the need to call strlen_os. */ -#define Is_relative_dir(dir) \ - (dir[0] == '.' \ - && (dir[1] == '\0' \ - || Is_separator(dir[1]) \ - || (dir[1] == '.' && (dir[2] == '\0' || Is_separator(dir[2]))))) - #endif /* CAML_INTERNALS */ #ifdef _WIN32 diff --git a/runtime/caml/s.h.in b/runtime/caml/s.h.in index 7945dcd1801c..bc6ced458da6 100644 --- a/runtime/caml/s.h.in +++ b/runtime/caml/s.h.in @@ -72,8 +72,6 @@ #undef HAS_TIMES -#undef HAS_STRLCPY - #undef HAS_SECURE_GETENV #undef HAS___SECURE_GETENV @@ -114,10 +112,6 @@ /* Define HAS_UNISTD if you have /usr/include/unistd.h. */ -#undef HAS_LIBGEN_H - -/* Define HAS_LIBGEN_H if you have /usr/include/libgen.h. */ - #undef HAS_DIRENT /* Define HAS_DIRENT if you have /usr/include/dirent.h and the result of diff --git a/runtime/caml/startup.h b/runtime/caml/startup.h index 51fddf0f7a0e..14f81e61d4c6 100644 --- a/runtime/caml/startup.h +++ b/runtime/caml/startup.h @@ -48,18 +48,13 @@ extern int32_t caml_seek_optional_section(int fd, struct exec_trailer *trail, extern int32_t caml_seek_section(int fd, struct exec_trailer *trail, char *name); -enum caml_byte_program_mode { - STANDARD, /* Default mode for ocamlrun */ - APPENDED, /* bytecode must be appended (i.e. -custom) */ - EMBEDDED /* bytecode embedded in C (e.g. -output-complete-exe/-output-obj) */ -}; +enum caml_byte_program_mode + { + STANDARD /* normal bytecode program requiring "ocamlrun" */, + COMPLETE_EXE /* embedding the vm, i.e. compiled with --output-complete-exe */ + }; -extern const enum caml_byte_program_mode caml_byte_program_mode; - -/* The default location of the Standard Library as used by the runtime to find - ld.conf */ -extern const char_os *caml_runtime_standard_library_default; -extern const char_os *caml_runtime_standard_library_effective; +extern enum caml_byte_program_mode caml_byte_program_mode; #endif /* CAML_INTERNALS */ diff --git a/runtime/caml/sys.h b/runtime/caml/sys.h index e422f29d3beb..b2fe25a147d9 100644 --- a/runtime/caml/sys.h +++ b/runtime/caml/sys.h @@ -33,10 +33,6 @@ CAMLextern void caml_sys_init (char_os * exe_name, char_os ** argv); CAMLnoret CAMLextern void caml_do_exit (int); -/* The default location of the Standard Library as used by the - %standard_library_default primitive */ -extern char_os *caml_standard_library_default; - #endif /* CAML_INTERNALS */ #endif /* CAML_SYS_H */ diff --git a/runtime/dynlink.c b/runtime/dynlink.c index 9d45f4156d05..621b71dea830 100644 --- a/runtime/dynlink.c +++ b/runtime/dynlink.c @@ -85,6 +85,18 @@ static c_primitive lookup_primitive(char * name) #define LD_CONF_NAME T("ld.conf") +#ifdef _WIN32 + +#define CAML_DIR_SEP T("\\") +#define Is_separator(c) (c == '\\' || c == '/') + +#else + +#define CAML_DIR_SEP T("/") +#define Is_separator(c) (c == '/') + +#endif /* _WIN32 */ + /* Return a copy of [path], interpreting explicit-relative paths relative to [root]. [root] must not end with a directory separator and is expected to be absolute. The result of this function can never be ".", ".." or a path @@ -271,6 +283,8 @@ static void open_shared_lib(char_os * name) caml_stat_free(realname); } +extern const char_os *caml_runtime_standard_library_effective; + /* Build the table of primitives, given a search path and a list of shared libraries (both 0-separated in a char array). Abort the runtime system on error. */ diff --git a/runtime/gen_primsc.sh b/runtime/gen_primsc.sh index 9630501a7c60..95c6a7dee98e 100755 --- a/runtime/gen_primsc.sh +++ b/runtime/gen_primsc.sh @@ -28,6 +28,8 @@ esac cat <<'EOF' /* Generated file, do not edit */ +#include + #define CAML_INTERNALS #include "caml/mlvalues.h" #include "caml/prims.h" @@ -69,6 +71,7 @@ echo ' 0 };' # - caml_runtime_standard_library_default for bytecode images on this runtime cat <<'EOF' -const enum caml_byte_program_mode caml_byte_program_mode = STANDARD; +enum caml_byte_program_mode caml_byte_program_mode = STANDARD; +const bool caml_byte_program_mode_custom = false; const char_os *caml_runtime_standard_library_default = OCAML_STDLIB_DIR; EOF diff --git a/runtime/startup_byt.c b/runtime/startup_byt.c index 57a0b949dd0a..f2be3343b73d 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include "caml/config.h" @@ -72,6 +73,8 @@ #define SEEK_END 2 #endif +extern const char_os *caml_runtime_standard_library_default; + const char_os * caml_runtime_standard_library_effective = NULL; static char magicstr[EXEC_MAGIC_LENGTH+1]; @@ -456,8 +459,15 @@ extern void caml_install_invalid_parameter_handler(void); #endif +CAMLextern char_os *caml_locate_standard_library (const char_os *exe_name, + const char_os *stdlib_default, + char_os **dirname); + /* Main entry point when loading code from a file */ +extern const bool caml_byte_program_mode_custom; +extern char_os *caml_standard_library_default; + CAMLexport void caml_main(char_os **argv) { int fd = -1, pos; @@ -497,8 +507,8 @@ CAMLexport void caml_main(char_os **argv) For STANDARD mode (i.e. the current executable is ocamlrun), argv[0] is tried first, as this should be the path to shebang-script/executable originally executed by the user. */ - CAMLassert(caml_byte_program_mode != EMBEDDED); - if (caml_byte_program_mode != APPENDED || proc_self_exe == NULL) { + CAMLassert(caml_byte_program_mode != COMPLETE_EXE); + if (!caml_byte_program_mode_custom || proc_self_exe == NULL) { exe_name = argv[0]; fd = caml_attempt_open(&exe_name, &trail, 0); } @@ -509,12 +519,12 @@ CAMLexport void caml_main(char_os **argv) With -custom, we have an executable that is ocamlrun itself concatenated with the bytecode. So, if the attempt with argv[0] failed, it is worth trying again with executable_name. */ - if (caml_byte_program_mode == APPENDED || fd < 0) { + if (caml_byte_program_mode_custom || fd < 0) { if (proc_self_exe != NULL) { exe_name = proc_self_exe; fd = caml_attempt_open(&exe_name, &trail, 0); } - if (fd < 0 && caml_byte_program_mode == APPENDED) + if (fd < 0 && caml_byte_program_mode_custom) error("unable to open file '%s'", caml_stat_strdup_of_os(exe_name)); } diff --git a/runtime/sys.c b/runtime/sys.c index c69a54a2b741..edb99c8918d4 100644 --- a/runtime/sys.c +++ b/runtime/sys.c @@ -710,6 +710,8 @@ CAMLprim value caml_sys_const_backend_type(value unit) /* If this remains unset then caml_runtime_standard_library_default is used */ char_os *caml_standard_library_default = NULL; +extern const char_os *caml_runtime_standard_library_default; + CAMLprim value caml_sys_const_standard_library_default(value unit) { return caml_copy_string_of_os( @@ -718,6 +720,10 @@ CAMLprim value caml_sys_const_standard_library_default(value unit) } #endif +CAMLextern char_os *caml_locate_standard_library (const char_os *exe_name, + const char_os *stdlib_default, + char_os **dirname); + CAMLprim value caml_sys_get_stdlib_dirs(value vstdlib_default) { CAMLparam1(vstdlib_default); diff --git a/runtime/sys_int.h.in b/runtime/sys_int.h.in new file mode 100644 index 000000000000..d96af93b011f --- /dev/null +++ b/runtime/sys_int.h.in @@ -0,0 +1,22 @@ +/**************************************************************************/ +/* */ +/* OCaml */ +/* */ +/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */ +/* */ +/* Copyright 1996 Institut National de Recherche en Informatique et */ +/* en Automatique. */ +/* */ +/* All rights reserved. This file is distributed under the terms of */ +/* the GNU Lesser General Public License version 2.1, with the */ +/* special exception on linking described in the file LICENSE. */ +/* */ +/**************************************************************************/ + +/* Operating system and standard library configuration. */ + +#undef HAS_STRLCPY + +#undef HAS_LIBGEN_H + +/* Define HAS_LIBGEN_H if you have /usr/include/libgen.h. */ diff --git a/runtime/unix.c b/runtime/unix.c index 7394bf66ed8d..5e62839e143e 100644 --- a/runtime/unix.c +++ b/runtime/unix.c @@ -28,6 +28,7 @@ #include #include #include "caml/config.h" +#include "sys_int.h" #ifdef HAS_GETTIMEOFDAY #include #endif @@ -579,6 +580,12 @@ static char * caml_dirname (const char * path) #endif } +#define Is_relative_dir(dir) \ + (dir[0] == '.' \ + && (dir[1] == '\0' \ + || dir[1] == '/' \ + || (dir[1] == '.' && (dir[2] == '\0' || dir[2] == '/')))) + CAMLextern char_os* caml_locate_standard_library (const char *exe_name, const char *stdlib_default, char **dirname) @@ -586,7 +593,7 @@ CAMLextern char_os* caml_locate_standard_library (const char *exe_name, if (Is_relative_dir(stdlib_default)) { char * root = caml_dirname(exe_name); char * candidate = - caml_stat_strconcat(3, root, CAML_DIR_SEP, stdlib_default); + caml_stat_strconcat(3, root, "/", stdlib_default); /* In practice, a system which can be configured --with-relative-libdir will also have realpath. The directory is normalised here for consistency with the behaviour on Windows, which doesn't have a direct equivalent of diff --git a/runtime/win32.c b/runtime/win32.c index e7c899bc446d..5df267790339 100644 --- a/runtime/win32.c +++ b/runtime/win32.c @@ -1274,6 +1274,14 @@ value caml_win32_xdg_defaults(void) CAMLreturn(result); } +#define Is_separator(c) (c == '\\' || c == '/') + +#define Is_relative_dir(dir) \ + (dir[0] == '.' \ + && (dir[1] == '\0' \ + || Is_separator(dir[1]) \ + || (dir[1] == '.' && (dir[2] == '\0' || Is_separator(dir[2]))))) + CAMLextern char_os* caml_locate_standard_library (const wchar_t *exe_name, const wchar_t *stdlib_default, wchar_t **dirname) @@ -1301,7 +1309,7 @@ CAMLextern char_os* caml_locate_standard_library (const wchar_t *exe_name, *(basename - 1) = 0; LPWSTR candidate = - caml_stat_wcsconcat(3, root, CAML_DIR_SEP, stdlib_default); + caml_stat_wcsconcat(3, root, T("\\"), stdlib_default); HANDLE h = CreateFile(candidate, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, diff --git a/stdlib/header.c b/stdlib/header.c index e42f9f78d513..c194105034ee 100644 --- a/stdlib/header.c +++ b/stdlib/header.c @@ -142,6 +142,7 @@ NORETURN static void exit_with_error(const wchar_t *wstr1, #else #include "caml/s.h" +#include "sys_int.h" #include #include From 7c14d00025ac3adca9306ffd79d2af22a0920f8b Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Wed, 13 Nov 2024 16:28:48 +0000 Subject: [PATCH 17/25] Change the default for --enable-suffixing In OCaml 5.5, the suffixing mode is enabled by default. For compatibility, the backports only enable this mode by default if --enable-runtime-search and/or --enable-runtime-search-target is specified (the usual validation rules for these flags still applies - i.e. --disable-suffixing is still prohibited if either --enable-runtime-search or --enable-runtime-search-target is specified) --- configure | 6 +++++- configure.ac | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 61e8e1100248..b99258152942 100755 --- a/configure +++ b/configure @@ -4321,7 +4321,7 @@ else $as_nop suffixing=true fi else $as_nop - suffixing=true + suffixing=auto fi @@ -4360,6 +4360,10 @@ fi case $suffixing,$runtime_search,$runtime_search_target in #( true,*,*|false,,) : ;; #( + auto,,) : + suffixing=false ;; #( + auto,*,*) : + suffixing=true ;; #( false,*,*) : as_fn_error $? "--disable-suffixed cannot be used with --enable-runtime-search or --enable-runtime-search-target" "$LINENO" 5 ;; #( *) : diff --git a/configure.ac b/configure.ac index 0aeac3e28141..013fb77907d0 100644 --- a/configure.ac +++ b/configure.ac @@ -683,7 +683,7 @@ AC_ARG_ENABLE([suffixing], [AS_HELP_STRING([--disable-suffixing], [disable suffixing of runtime executables and shared libraries])], [AS_IF([test "x$enableval" = 'xno'], [suffixing=false], [suffixing=true])], - [suffixing=true]) + [suffixing=auto]) AC_ARG_ENABLE([runtime-search], [AS_HELP_STRING([--enable-runtime-search], @@ -707,6 +707,8 @@ AC_ARG_ENABLE([runtime-search-target], AS_CASE([$suffixing,$runtime_search,$runtime_search_target], [true,*,*|false,,],[], + [auto,,],[suffixing=false], + [auto,*,*],[suffixing=true], [false,*,*],[AC_MSG_ERROR(m4_normalize([--disable-suffixed cannot be used with --enable-runtime-search or --enable-runtime-search-target]))]) From b67571cedef177c2f40461f27b7a61b32e230eab Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 15:58:47 +0100 Subject: [PATCH 18/25] Revert change to Cmo_format.library Cmo_format.library.lib_dllibs is able to change type without a bootstrap because the bootstrap cycle never reads a .cma which has any entries for dllibs (it fundamentally can't, because that would imply a dependency on dynamic loading, which isn't permitted in ultra-portable bootstrap) However, the format absolutely cannot change without ultimately changing the cma magic number, which is non-trivial, because we don't normally change the magic numbers in maintenance releases, and the numbering scheme consequently doesn't reserve space for them. This is worked around instead by leaving Cmo_format.library unaltered and instead writing a _second_ list after the Cmo_format.library in the cma format. This list is guarded with the cma magic number, but written in reverse. If this magic number is present, then the list must follow and must be the same length as lib_dllibs. If the magic number is not present, then the suffixed member of each lib_dllibs is assumed to be false, which is used also used as an "optimisation" to avoid writing the extra list at all when none of entries have suffixed:true. --- .depend | 9 ++++++- bytecomp/bytelibrarian.ml | 22 ++++++++++++--- bytecomp/bytelink.ml | 7 ++--- bytecomp/dll.ml | 21 +++++++++++++++ bytecomp/dll.mli | 3 +++ file_formats/cmo_format.mli | 2 +- otherlibs/dynlink/byte/dynlink.ml | 2 +- otherlibs/dynlink/byte/dynlink_symtable.ml | 30 ++++++++++++++++----- otherlibs/dynlink/byte/dynlink_symtable.mli | 2 +- tools/objinfo.ml | 7 ++--- toplevel/byte/topeval.ml | 2 +- 11 files changed, 85 insertions(+), 22 deletions(-) diff --git a/.depend b/.depend index 8449b91a293b..2c6f95491c5f 100644 --- a/.depend +++ b/.depend @@ -2305,6 +2305,7 @@ bytecomp/bytelibrarian.cmo : \ utils/linkdeps.cmi \ utils/format_doc.cmi \ bytecomp/emitcode.cmi \ + bytecomp/dll.cmi \ utils/config.cmi \ file_formats/cmo_format.cmi \ utils/clflags.cmi \ @@ -2317,6 +2318,7 @@ bytecomp/bytelibrarian.cmx : \ utils/linkdeps.cmx \ utils/format_doc.cmx \ bytecomp/emitcode.cmx \ + bytecomp/dll.cmx \ utils/config.cmx \ file_formats/cmo_format.cmi \ utils/clflags.cmx \ @@ -2431,14 +2433,17 @@ bytecomp/bytesections.cmi : bytecomp/dll.cmo : \ utils/misc.cmi \ utils/config.cmi \ + file_formats/cmo_format.cmi \ utils/binutils.cmi \ bytecomp/dll.cmi bytecomp/dll.cmx : \ utils/misc.cmx \ utils/config.cmx \ + file_formats/cmo_format.cmi \ utils/binutils.cmx \ bytecomp/dll.cmi -bytecomp/dll.cmi : +bytecomp/dll.cmi : \ + file_formats/cmo_format.cmi bytecomp/emitcode.cmo : \ parsing/unit_info.cmi \ lambda/translmod.cmi \ @@ -7995,6 +8000,7 @@ tools/objinfo.cmo : \ typing/ident.cmi \ utils/format_doc.cmi \ middle_end/flambda/export_info.cmi \ + bytecomp/dll.cmi \ utils/config.cmi \ middle_end/compilation_unit.cmi \ file_formats/cmxs_format.cmi \ @@ -8020,6 +8026,7 @@ tools/objinfo.cmx : \ typing/ident.cmx \ utils/format_doc.cmx \ middle_end/flambda/export_info.cmx \ + bytecomp/dll.cmx \ utils/config.cmx \ middle_end/compilation_unit.cmx \ file_formats/cmxs_format.cmi \ diff --git a/bytecomp/bytelibrarian.ml b/bytecomp/bytelibrarian.ml index 4ff25d2f4c48..138de8a24ccf 100644 --- a/bytecomp/bytelibrarian.ml +++ b/bytecomp/bytelibrarian.ml @@ -48,12 +48,12 @@ let lib_dllibs = ref [] Notice that here we scan .cma files given on the command line from left to right, hence options must be added after. *) -let add_ccobjs l = +let add_ccobjs l dllibs = if not !Clflags.no_auto_link then begin if l.lib_custom then Clflags.custom_runtime := true; lib_ccobjs := !lib_ccobjs @ l.lib_ccobjs; lib_ccopts := !lib_ccopts @ l.lib_ccopts; - lib_dllibs := !lib_dllibs @ l.lib_dllibs + lib_dllibs := !lib_dllibs @ dllibs end let copy_object_file oc name = @@ -79,7 +79,7 @@ let copy_object_file oc name = seek_in ic toc_pos; let toc = (input_value ic : library) in List.iter (Bytelink.check_consistency file_name) toc.lib_units; - add_ccobjs toc; + add_ccobjs toc (Dll.read_suffixed_dllibs_from_channel ic toc); List.iter (copy_compunit ic oc) toc.lib_units; close_in ic; List.map (fun u -> name, u) toc.lib_units @@ -107,16 +107,30 @@ let create_archive file_list lib_name = (match Linkdeps.check ldeps with | None -> () | Some e -> raise (Error (Link_error e))); + let suffixed, lib_dllibs = + let f (suffixed, l) (a, b) = (suffixed::a, l::b) in + List.fold_right f (!Clflags.dllibs @ !lib_dllibs) ([], []) + in let toc = { lib_units = (List.map snd units); lib_custom = !Clflags.custom_runtime; lib_ccobjs = !Clflags.ccobjs @ !lib_ccobjs; lib_ccopts = !Clflags.all_ccopts @ !lib_ccopts; - lib_dllibs = !Clflags.dllibs @ !lib_dllibs } in + lib_dllibs } in let pos_toc = pos_out outchan in Emitcode.marshal_to_channel_with_possibly_32bit_compat ~filename:lib_name ~kind:"bytecode library" outchan toc; + (* If lib_dllibs is empty or none of the entries had suffixed:true then + there's no need to write the suffixed list. Otherwise, output the + reverse cma magic number again and then the suffixed list to be + List.combine'd with lib_dllibs when the library is unmarshalled. *) + if List.mem true suffixed then begin + for i = String.length cma_magic_number - 1 downto 0 do + output_char outchan Config.cma_magic_number.[i] + done; + output_value outchan suffixed + end; seek_out outchan ofs_pos_toc; output_binary_int outchan pos_toc; ) diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index c23e4a426455..298edc42ddec 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -55,7 +55,7 @@ let lib_ccobjs = ref [] let lib_ccopts = ref [] let lib_dllibs = ref [] -let add_ccobjs origin l = +let add_ccobjs origin l dllibs = if not !Clflags.no_auto_link then begin if String.length !Clflags.use_runtime = 0 @@ -68,7 +68,7 @@ let add_ccobjs origin l = in lib_ccopts := List.map replace_origin l.lib_ccopts @ !lib_ccopts; end; - lib_dllibs := l.lib_dllibs @ !lib_dllibs + lib_dllibs := dllibs @ !lib_dllibs end (* A note on ccobj ordering: @@ -139,8 +139,9 @@ let scan_file ldeps obj_name tolink = let pos_toc = input_binary_int ic in (* Go to table of contents *) seek_in ic pos_toc; let toc = (input_value ic : library) in + let dllibs = Dll.read_suffixed_dllibs_from_channel ic toc in close_in ic; - add_ccobjs (Filename.dirname file_name) toc; + add_ccobjs (Filename.dirname file_name) toc dllibs; let required = List.fold_right (fun compunit reqd -> diff --git a/bytecomp/dll.ml b/bytecomp/dll.ml index 0d1f90ee0250..24028f21bd8a 100644 --- a/bytecomp/dll.ml +++ b/bytecomp/dll.ml @@ -66,6 +66,27 @@ let extract_dll_name (suffixed, file) = else file +(* This dance avoids the need to bump the magic number for .cma format, which is + a bit awkward to do with old releases, because we don't reserve indexes for + maintenance releases. In OCaml 5.5+, Cmo_format.lib_dllibs is a + (suffixed:bool * string) list, but for these backports we instead keep the + old string list for Cmo_format.lib_dllibs and append a bool list after the + toc. This value is guarded by the cma magic number written in reverse. *) +let rebmun_cigam_amc = + let magic_length = String.length Config.cma_magic_number in + let init i = Config.cma_magic_number.[magic_length - i - 1] in + String.init magic_length init + +let read_suffixed_dllibs_from_channel ic l = + let open Cmo_format in + let magic_length = String.length Config.cma_magic_number in + match In_channel.really_input_string ic magic_length with + | Some magic when magic = rebmun_cigam_amc -> + let combine suffixed l acc = (suffixed, l)::acc in + List.fold_right2 combine (input_value ic : bool list) l.lib_dllibs [] + | _ -> + List.map (fun l -> (false, l)) l.lib_dllibs + (* Open a list of DLLs, adding them to opened_dlls. Raise [Failure msg] in case of error. *) diff --git a/bytecomp/dll.mli b/bytecomp/dll.mli index 99fd2ed3deed..e92be9f08dbb 100644 --- a/bytecomp/dll.mli +++ b/bytecomp/dll.mli @@ -21,6 +21,9 @@ extension or linking symbol (xxx.so or -lxxx) *) val extract_dll_name: (bool * string) -> string +val read_suffixed_dllibs_from_channel: + in_channel -> Cmo_format.library -> (bool * string) list + type dll_mode = | For_checking (* will just check existence of symbols; no need to do full symbol resolution *) diff --git a/file_formats/cmo_format.mli b/file_formats/cmo_format.mli index 4a893bf57032..a4dbfce082e9 100644 --- a/file_formats/cmo_format.mli +++ b/file_formats/cmo_format.mli @@ -67,7 +67,7 @@ type library = how they end up being used on the command line. *) lib_ccobjs: string list; (* C object files needed for -custom *) lib_ccopts: string list; (* Extra opts to C compiler *) - lib_dllibs: (bool * string) list } (* DLLs needed *) + lib_dllibs: string list } (* DLLs needed *) (* Format of a .cma file: magic number (Config.cma_magic_number) diff --git a/otherlibs/dynlink/byte/dynlink.ml b/otherlibs/dynlink/byte/dynlink.ml index 0f315d3ab0d0..a3c553e8dae5 100644 --- a/otherlibs/dynlink/byte/dynlink.ml +++ b/otherlibs/dynlink/byte/dynlink.ml @@ -194,7 +194,7 @@ module Bytecode = struct let toc_pos = input_binary_int ic in (* Go to table of contents *) seek_in ic toc_pos; let lib = (input_value ic : library) in - Symtable.open_dlls lib.lib_dllibs; + Symtable.open_dlls ic lib; handle, lib.lib_units end else begin raise (DT.Error (Not_a_bytecode_file file_name)) diff --git a/otherlibs/dynlink/byte/dynlink_symtable.ml b/otherlibs/dynlink/byte/dynlink_symtable.ml index 8baea488878e..ec90f2a1c63d 100644 --- a/otherlibs/dynlink/byte/dynlink_symtable.ml +++ b/otherlibs/dynlink/byte/dynlink_symtable.ml @@ -106,7 +106,23 @@ let extract_dll_name (suffixed, file) = #66 "bytecomp/dll.ml" else file -#110 "otherlibs/dynlink/byte/dynlink_symtable.ml" + +#75 "bytecomp/dll.ml" +let rebmun_cigam_amc = + let magic_length = String.length Config.cma_magic_number in + let init i = Config.cma_magic_number.[magic_length - i - 1] in + String.init magic_length init + +let read_suffixed_dllibs_from_channel ic l = +#82 "bytecomp/dll.ml" + let magic_length = String.length Config.cma_magic_number in + match In_channel.really_input_string ic magic_length with + | Some magic when magic = rebmun_cigam_amc -> + let combine suffixed l acc = (suffixed, l)::acc in + List.fold_right2 combine (input_value ic : bool list) l.lib_dllibs [] + | _ -> + List.map (fun l -> (false, l)) l.lib_dllibs +#126 "otherlibs/dynlink/byte/dynlink_symtable.ml" (* Specialized version of [Dll.{open_dll,open_dlls,find_primitive}] for the execution mode. *) let open_dll name = @@ -139,8 +155,8 @@ let open_dll name = (* Open a list of DLLs, adding them to opened_dlls. Raise [Failure msg] in case of error. *) -let open_dlls names = - List.iter open_dll names +let open_dlls ic lib = + List.iter open_dll (read_suffixed_dllibs_from_channel ic lib) let find_primitive prim_name = try Hashtbl.find primitives prim_name @@ -243,12 +259,12 @@ let patch_object buff patchlist = (* Functions for toplevel use *) (* Update the in-core table of globals *) -#247 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#263 "otherlibs/dynlink/byte/dynlink_symtable.ml" module Meta = struct #16 "bytecomp/meta.ml" external global_data : unit -> Obj.t array = "caml_get_global_data" external realloc_global_data : int -> unit = "caml_realloc_global" -#252 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#268 "otherlibs/dynlink/byte/dynlink_symtable.ml" end #332 "bytecomp/symtable.ml" let update_global_table () = @@ -274,7 +290,7 @@ external get_bytecode_sections : unit -> bytecode_sections = let init_toplevel () = let sect = get_bytecode_sections () in global_table := sect.symb; -#278 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#294 "otherlibs/dynlink/byte/dynlink_symtable.ml" Dll.init ~dllpaths:sect.dlpt ~prims:sect.prim; #358 "bytecomp/symtable.ml" sect.crcs @@ -327,7 +343,7 @@ let current_state () = !global_table #412 "bytecomp/symtable.ml" let hide_additions (st : global_map) = if st.cnt > !global_table.cnt then -#331 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#347 "otherlibs/dynlink/byte/dynlink_symtable.ml" failwith "Symtable.hide_additions"; #415 "bytecomp/symtable.ml" global_table := diff --git a/otherlibs/dynlink/byte/dynlink_symtable.mli b/otherlibs/dynlink/byte/dynlink_symtable.mli index 8db93a27de68..1d76e3be42fc 100644 --- a/otherlibs/dynlink/byte/dynlink_symtable.mli +++ b/otherlibs/dynlink/byte/dynlink_symtable.mli @@ -31,7 +31,7 @@ module Global : sig val description: Format.formatter -> t -> unit end -val open_dlls : (bool * string) list -> unit +val open_dlls : in_channel -> library -> unit val patch_object: (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t -> diff --git a/tools/objinfo.ml b/tools/objinfo.ml index aa94d2bd2224..4fc09c55aee7 100644 --- a/tools/objinfo.ml +++ b/tools/objinfo.ml @@ -80,7 +80,7 @@ let dllib (suffixed, name) = else name -let print_cma_infos (lib : Cmo_format.library) = +let print_cma_infos (lib : Cmo_format.library) lib_dllibs = printf "Force custom: %a\n" yesno_of_bool lib.lib_custom; printf "Extra C object files:"; (* PR#4949: print in linking order *) @@ -89,7 +89,7 @@ let print_cma_infos (lib : Cmo_format.library) = List.iter print_spaced_string (List.rev lib.lib_ccopts); printf "\n"; print_string "Extra dynamically-loaded libraries:"; - List.iter print_spaced_string (List.rev_map dllib lib.lib_dllibs); + List.iter print_spaced_string (List.rev_map dllib lib_dllibs); printf "\n"; List.iter print_cmo_infos lib.lib_units @@ -424,8 +424,9 @@ let dump_obj_by_kind filename ic obj_kind = let toc_pos = input_binary_int ic in seek_in ic toc_pos; let toc = (input_value ic : library) in + let lib_dllibs = Dll.read_suffixed_dllibs_from_channel ic toc in close_in ic; - print_cma_infos toc + print_cma_infos toc lib_dllibs | Cmi | Cmt -> close_in ic; let cmi, cmt = Cmt_format.read filename in diff --git a/toplevel/byte/topeval.ml b/toplevel/byte/topeval.ml index 14c605039d56..b3823bd1a985 100644 --- a/toplevel/byte/topeval.ml +++ b/toplevel/byte/topeval.ml @@ -301,7 +301,7 @@ and really_load_file recursive ppf name filename ic = "Cannot load required shared library %s.@.Reason: %s.@." name reason; raise Load_failed) - lib.lib_dllibs; + (Dll.read_suffixed_dllibs_from_channel ic lib); List.iter (load_compunit ic filename ppf) lib.lib_units; true end else begin From 18871618e39b0e258e81fbb7f93a9d3acbd0cc0e Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Thu, 18 Jun 2026 18:04:17 +0100 Subject: [PATCH 19/25] Revert change to Cmx_format.unit_infos As with Cmo_format.library, the type of Cmx_format.unit_infos cannot change without changing the cmx and cmxa magic numbers, which is similarly awkward. The workaround here is slightly simpler, if also somewhat more devious. Cmx_format.unit_infos is handily a fully mutable record, meaning that there are few if any optimisation assumptions which will ever be made around it. For the compiler, the interface is altered to add Compilenv.needs_stdlib_location which takes a Cmx_format.unit_infos and "accesses" its ui_need_stdlib field. In parallel, Compilenv.write_unit_info gains an optional ui_need_stdlib argument which allows the field to be specified when a Cmx_format.unit_infos is written to a file. The devious part is that instead of writing a Cmx_format.unit_infos value, Compilenv.write_unit_info writes a tuple with one extra element for the ui_need_stdlib field to be added in. Compilenv.needs_stdlib_location looks to see if the Cmx_format.unit_infos it has been passed in fact contains an extra field in the block and, if it does, reads it. --- .depend | 2 ++ asmcomp/asmlink.ml | 2 +- asmcomp/asmpackager.ml | 9 +++------ file_formats/cmx_format.mli | 3 +-- middle_end/compilenv.ml | 38 +++++++++++++++++++++++++++++++------ middle_end/compilenv.mli | 5 ++++- tools/objinfo.ml | 3 ++- 7 files changed, 45 insertions(+), 17 deletions(-) diff --git a/.depend b/.depend index 2c6f95491c5f..15c292317e16 100644 --- a/.depend +++ b/.depend @@ -8002,6 +8002,7 @@ tools/objinfo.cmo : \ middle_end/flambda/export_info.cmi \ bytecomp/dll.cmi \ utils/config.cmi \ + middle_end/compilenv.cmi \ middle_end/compilation_unit.cmi \ file_formats/cmxs_format.cmi \ file_formats/cmx_format.cmi \ @@ -8028,6 +8029,7 @@ tools/objinfo.cmx : \ middle_end/flambda/export_info.cmx \ bytecomp/dll.cmx \ utils/config.cmx \ + middle_end/compilenv.cmx \ middle_end/compilation_unit.cmx \ file_formats/cmxs_format.cmi \ file_formats/cmx_format.cmi \ diff --git a/asmcomp/asmlink.ml b/asmcomp/asmlink.ml index e88944564e94..060ae7ac4f3c 100644 --- a/asmcomp/asmlink.ml +++ b/asmcomp/asmlink.ml @@ -205,7 +205,7 @@ let make_globals_map units_list ~crc_interfaces = let make_startup_file ~ppf_dump units_list ~crc_interfaces = let need_stdlib = - let needs_stdlib ({ui_need_stdlib; _}, _, _) = ui_need_stdlib in + let needs_stdlib (ui, _, _) = Compilenv.needs_stdlib_location ui in List.exists needs_stdlib units_list in let compile_phrase p = Asmgen.compile_phrase ~ppf_dump p in diff --git a/asmcomp/asmpackager.ml b/asmcomp/asmpackager.ml index 3cec13386ce0..3745eb0d61f6 100644 --- a/asmcomp/asmpackager.ml +++ b/asmcomp/asmpackager.ml @@ -217,9 +217,7 @@ let build_package_cmx members cmxfile = else Clambda (get_approx ui) in - let ui_need_stdlib = - List.exists (function {ui_need_stdlib; _} -> ui_need_stdlib) units - in + let ui_need_stdlib = List.exists Compilenv.needs_stdlib_location units in Export_info_for_pack.clear_import_state (); let pkg_infos = { ui_name = ui.ui_name; @@ -241,10 +239,9 @@ let build_package_cmx members cmxfile = ui_force_link = List.exists (fun info -> info.ui_force_link) units; ui_export_info; - ui_for_pack = None; - ui_need_stdlib; + ui_for_pack = None } in - Compilenv.write_unit_info pkg_infos cmxfile + Compilenv.write_unit_info ~ui_need_stdlib pkg_infos cmxfile (* Make the .cmx and the .o for the package *) diff --git a/file_formats/cmx_format.mli b/file_formats/cmx_format.mli index 711ade43e7e1..7a167d0cd4de 100644 --- a/file_formats/cmx_format.mli +++ b/file_formats/cmx_format.mli @@ -46,8 +46,7 @@ type unit_infos = mutable ui_send_fun: int list; (* Send functions needed *) mutable ui_export_info: export_info; mutable ui_force_link: bool; (* Always linked *) - mutable ui_for_pack: string option; (* Part of a pack *) - mutable ui_need_stdlib: bool} (* caml_standard_library_nat needed *) + mutable ui_for_pack: string option } (* Part of a pack *) (* Each .a library has a matching .cmxa file that provides the following infos on the library: *) diff --git a/middle_end/compilenv.ml b/middle_end/compilenv.ml index bda2938c09e5..b7287aa67e96 100644 --- a/middle_end/compilenv.ml +++ b/middle_end/compilenv.ml @@ -88,8 +88,8 @@ let current_unit = ui_send_fun = []; ui_force_link = false; ui_export_info = default_ui_export_info; - ui_for_pack = None; - ui_need_stdlib = false } + ui_for_pack = None } +let current_ui_need_stdlib = ref false let symbol_separator = match Config.ccomp_type with @@ -129,7 +129,7 @@ let reset ?packname name = current_unit.ui_send_fun <- []; current_unit.ui_force_link <- !Clflags.link_everything; current_unit.ui_for_pack <- packname; - current_unit.ui_need_stdlib <- false; + current_ui_need_stdlib := false; Hashtbl.clear exported_constants; structured_constants := structured_constants_empty; current_unit.ui_export_info <- default_ui_export_info; @@ -358,13 +358,39 @@ let need_send_fun n = (* Record that caml_standard_library_nat is needed *) let need_stdlib_location () = - current_unit.ui_need_stdlib <- true + current_ui_need_stdlib := true + +(* Relocatable OCaml needs an extra piece of information recorded in cmx format, + but for backporting to previous releases it was desirable not to change the + magic numbers. The additional ui_need_stdlib field is stored, but not + declared in the type, which allows .cmx files produced with earlier versions + of the compiler in the same series still to be loaded. *) +let unit_info_size = Obj.size (Obj.repr current_unit) +let needs_stdlib_location ui = + let ui = Obj.repr ui in + if Obj.size ui <= unit_info_size then + false + else + let field = Obj.field ui unit_info_size in + if Obj.is_int field then + (Obj.obj field : bool) + else + false (* Write the description of the current unit *) -let write_unit_info info filename = +let write_unit_info ?(ui_need_stdlib=false) info filename = let oc = open_out_bin filename in output_string oc cmx_magic_number; + let info = + let[@ocaml.warning "+missing-record-field-pattern"] + {ui_name; ui_symbol; ui_defines; ui_imports_cmi; + ui_imports_cmx; ui_curry_fun; ui_apply_fun; ui_send_fun; + ui_export_info; ui_force_link; ui_for_pack} = info in + ui_name, ui_symbol, ui_defines, ui_imports_cmi, + ui_imports_cmx, ui_curry_fun, ui_apply_fun, ui_send_fun, + ui_export_info, ui_force_link, ui_for_pack, ui_need_stdlib + in output_value oc info; flush oc; let crc = Digest.file filename in @@ -373,7 +399,7 @@ let write_unit_info info filename = let save_unit_info filename = current_unit.ui_imports_cmi <- Env.imports(); - write_unit_info current_unit filename + write_unit_info ~ui_need_stdlib:!current_ui_need_stdlib current_unit filename let current_unit () = match Compilation_unit.get_current () with diff --git a/middle_end/compilenv.mli b/middle_end/compilenv.mli index 33965f425b9c..36d4adc3a0ef 100644 --- a/middle_end/compilenv.mli +++ b/middle_end/compilenv.mli @@ -108,6 +108,9 @@ val need_stdlib_location: unit -> unit (* Record that caml_standard_library_nat needs to be initialised if this unit is linked. *) +val needs_stdlib_location: unit_infos -> bool + (* Accessor for the hidden ui_need_stdlib field *) + val stdlib_symbol_name: Ident.t (* The name of the symbol defined globally for %standard_library_default *) @@ -143,7 +146,7 @@ val backtrack: structured_constants -> unit val read_unit_info: string -> unit_infos * Digest.t (* Read infos and MD5 from a [.cmx] file. *) -val write_unit_info: unit_infos -> string -> unit +val write_unit_info: ?ui_need_stdlib:bool -> unit_infos -> string -> unit (* Save the given infos in the given file *) val save_unit_info: string -> unit (* Save the infos for the current unit in the given file *) diff --git a/tools/objinfo.ml b/tools/objinfo.ml index 4fc09c55aee7..8096e581cac7 100644 --- a/tools/objinfo.ml +++ b/tools/objinfo.ml @@ -263,7 +263,8 @@ let print_cmx_infos (ui, crc) = | None -> "no" | Some pack -> "YES: " ^ pack); printf - "Requires caml_standard_library_nat: %a\n" yesno_of_bool ui.ui_need_stdlib + "Requires caml_standard_library_nat: %a\n" + yesno_of_bool (Compilenv.needs_stdlib_location ui) let print_cmxa_infos (lib : Cmx_format.library_infos) = printf "Extra C object files:"; From ed3645784306e731938aea4dd8142a260d38a386 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 13:07:13 +0100 Subject: [PATCH 20/25] Bootstrap --- boot/ocamlc | Bin 3372764 -> 3388302 bytes boot/ocamllex | Bin 406627 -> 406701 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/boot/ocamlc b/boot/ocamlc index 852a0ec901686d8fb07b5da30307c4a7bc5454d7..e93db9ae174ffbf13239bd738b04e965731f3196 100755 GIT binary patch delta 283430 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
B?~(PEb+Hj0mF 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%0A@1n{fy|EwUKdI&iKZ{I+trp`W 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 K%JNdzg#QQK_dk39 delta 272095 zcmbS!3s_Y}*S~Ylz8&S@IUcS80s;bp3SRPt3ZZEU33)AVkd>M@NXvY^0I6?Y6Flm& zQiHV2(uCJ)TWLaB>6?~xvx_EFmX(%tzv%1#xAxfwjwj#m`=0-Ko?-S{vu4fAnwd3g z*38~K^!3}~)P>`sdQqT=a4!!0OSE^VJK{uFcU@caue-KwynC}VNd$6=JI5I$qTCe@ zm*=m0kt0aFqkr5jcN)03q(CK$kns<*;{U$eZHMb)^Jeg%LEc zL<*tmNL6xIy6#BkL@&ds$krMm0$~!uRT=+RR#CpJZbfz#K|UVUxr6fH@c239%h<8w74qraLw~8eNzbo+7wP5tO=H)hz9W z`)+4Pin$OJ;I0jq#B}toiyC4rlDnXNs>o_C&>i1CUZhY}pcHDPG*=~+Dg&h;cSHLC zk>!q!aETPRh=@jm@ytmY)m{vap}e?Y4aHa`n^!RH-Ng|lqO-dxVn%bVlde%~a5-lU z5NoKhRzy?IlVM4eRwpzYcQv26EUtZv5_?J_ z+?aWNF>gcNMU9fXA!Ubr5Lu>>(}o@{kCent%6e0TQqe{&&3!&KAiRi^O|Dg7=WW#! zZf6%)!oxn8M|_?ueV!k67j_8^f6NE7)#rK3z6D)I3bAe9@veg`?rA*(sOZIZk~^lK zB<6dyhqP3BYaiczsF<|4{{7fV#kZ%_xnVHi;eW{E5$Vb_Bd z(VOLwtBOZG(NId$#3(#BUMF0LYWk!`>-#m4#AJ;&@g}0W0IV4Aw-yKRT+v2MM%2_s z3__G1U_ccC0s>*W`JO0+?ywtNs|I2wov@2Y;EiV~P^_?XPw;}qY7+Q9Vkt$o6{Gy{ zi`xpa*adC-Ah82gk5we9+$ki=aEOl>{q{yNhGsfNjtO+7%1t7YN;Ph&(GE&#CpO^O z87v|w=W!80U$ztX0}*@0>Y^f-*zfb)ht_Nt8s+toBq|LO<&YNg5sP70h``Eku+bf% z;(n_!l%|n5YIW(A?ZshX)ZDwkpc^7Z2{JTAioJ-cqQrw{cDkf!4jR<~waB81Xk)O6 zXt55-El>nCM2mTxe>m!06eCi_a(C5g=)GQ8gkywcWBIX((~(4#z%n0WRJS-5)629x ziq>gV-4VTLa{0Lf;0YM+4W$c(_BwA*bM z-+je%%-DwEw!5FG#WEENH=Pb`?Gx~Y(^`ZdjPh5{lB8qf*u$Zfg=~NaZ zxjY6nWwyp>s~#>gJ^eIDC|KJ?C?GZvskA>vpjxG>AEMl8}c_C_STZIwC#)^gNmxvUs zZ|SEK&Be}rv6q9=cB>LPrDl!fqEkztXZ{;S(^lyJ(h_k8sPltF9HrfZF}s=4XF_uo zm5RZFD0Ts~74SiL7G>fGBsZ0bp~&H!g&~~tfXG8i)lQp>svi)`yw4Z&#QjveR3!Bx z)Rc#X1A#R@uc?6uPK3z_n-TcOv#=RQ;ug_O&sipl1kAz0QBYdzscMvnKsyhP68!=F zF$yYiBc+cP3lZ%Zjj6t#>Iza^6te=_#xVv%U?bH%D6(ZPG5|{{rcLb_jJNevzXGh* zQ^i>E8da~vOj<|9ev^P>#6&BW*lePe=y;W$< zbUd#;bg`?&QZS!*n}{<~xn(U}DsD6C?Oel+f$0N-%`Jvt}z>h?GU(3L)sKO8LT$a;&P)> zqC&U~Lbg?zkh{iH?Z(L%z-V3tgiTadA)XQBE*|fq%=OU5=>0nwLuGEU*T$V{?A53y zUkRnW4PuRf<04m){Vp+=O7DPvi0LC7ls5$eCTe^FbG*SJ(x_+?{vbUS*sST|9YoH% z#T0jS4~Ys0lL`Zh#CDlW380$B2$yV(6e`>-Mta9;-fUw`Uf2vdZl zql$G2F3NZW^9lpFNVp76PAYsHqS#Coj~KM|OEG>+iy>8v_(xk9n}fY@0mNa>1;-Y# z%xTmHHEN5%?Ify?65S}X40FBUQ4G0;Ezna$HFL#Ks*SS*tlu0&1tWw5T&lK;Z10r$ zeJkcTGM|Wsy(fYt8n#XB5D&Q$1gg8>Sfo2!|5U=TJ(m5>MfI&wUuMKUaa@ zI!b;V17K@0L7>lCA@!!}9awmMj7_Te zJ|UX&N}vjBpAh4MUw}|XBk+*IDr8s*4^aA3DC_~sdQy!1FBJK^42pwKiiQ6(Md42I z@_(WDKbKPX)c;lr>Jd$)J4G0s_$3B=obRFOccIHxQR6PG`4dSzjn0_JYqI?%(;(vU zu!Pc|h>W0^na~UcPh-fBr|C~aV=SSKPm5^VMv!{Q4s!k?`rv6XLVQA5>G3X#yfZmK zAN-7%AjCSoYPV2X+wXc-xHVsayBjHjE*=&^_BTy2Gs6>|4o$NdPM1%s!6oFP6UVx^rD%?lwH(k zs814tx061_bBjcrucCnWO@Y#oSFv(6=yk7(Gyg@N!+j>pXmY*|gQI!8b{%C-k6~tf z3u0eUa%Qh6M+=&CNMI#*dmKQ!ySAIFDgRlH}ym;j|nu?Fjlyj~^$XiuLiHVAiY;BhE~}j- z8XFWHNLM#(RI<9kFdJo&^ffnHrnSDBkuFAArfPn;dnZ_Q3A<+J%ThGeO0Xj)6j}m+ zC>)xMt<&%haA&?OIXp=R#wNg7)G0DRW+hSQpQ-MD&ZG~!-I6JbmHNu5mcNAQzZ6}g zvAB)J=JUDfmT^e5-)$Ky;OD&29^ad`%&_dho*`?dWh_-!wv%YfOv^CO)47?JO6(q1 z&a(7?-+-EmEU^EJP<3o!iY(ZK>)0cJli}Nq&=22Zi2p9O12p zbAg2um27)EPo#6;@I|^VhMZ$vVNb(X(ib$aJB^DQ=AxkXlL_0ne z2o5sGps(NG}HLY5U2OB!V9pH5bIRh%lb2L z_g-gpSy82xeXa9RThY(@8*~4ozx6FH?e&44xFX9s6fk|9Z5@dSp6x^$HQ2fc&-H_? zs}a=`PhFI4y8GF7d?-Lbv;4o_*_#oA4v8)sns5#ryiJPoBpo$u9eG_SChg+v| zwQ_T&L}Hq?1QD9!7Aq)tI(jIJ`c8-Bo2Wh^3=Yh0aoD`yZC!ya$I-j35mD>W zA-qI)L*P|>HDIrOyq?4iEAO(RJW*dG(nrs*P8K$HQ_Mt$M9NxV9Z8N^)^S46^jX%i zs6gE;tB#V(4q8# zaF2DI!ctgZUFzgYPeY}7`^rnpO8xi(Yc_X@b0J!~lDaIk4nh=Lii#I3vP$}jh1PQ{ z=;Fm_G*ar9pqE!tlP7YP7*KkN(Vul5sL>ONQclysYO;f6QEu%9u%_HPh{2{x>lx^b z`YLNT=q(aEt>ck8aVLnNHP$|5eFhF=z3*;owvB%O$`wGDszczOk&_{Z^NdxkpD+#dgf9120;OP*#_htdF7zM_;nGu_%UnsTKX_ zfVI615-@DCEV_KaI@HFKa~V)ERIw2j^vr`+xQujkBF6P5z3LS!)~Zdqv(_4lm=KT9 z@2|7=X@e0`bBqUye*Bnqr-&*658g&-2t1~K17F^Vv&2iN;%)0(8!tn^w#ugZKRdYe zn0Ktlteyj^5tQ*BT4S6!jnKEeXFUfGkG}jv>({JsXMJRybM-Pq-+W|^ux$e`UUO~K z1m7AY7yGfbn}kD${L`36)y*dy_s~{G6!WnSZ`&RE`P0^qgs7&%GjKe7bjCVU1k;cP z>meJTPp~vvxzl?=6E;`_D6J7StJbp`txLUf7S)lVdc)_|2^R1WKL*9=1!t}CD%RWO z=dE)r=COvTR$UbEgEiHJD|X?GFb^>Ldhrj|`4(|I{nBK8%*Llo+$|c_jniCu^^eva z!m4jKSt`f64?NW3AmG)2qbjL5OX0}oCuh5?l zQr)jm3W#t9K$*Xx8GLRjRvO0|0fu9;HC6p)9U-8Yf3rRV(yFuA(NzC#jS*E;|GTxf zSWUJ+pe(#QpllS+R*{V0H71^>tUoZMvncjY>rcjvv{A)haMPy#W&PReUKHri)2>)G zc+HI>pEi2n#77nfUCL~&xr?w6mI_HT9K3TV{ZJb?c!g9A-@2YJNh2lqQ(*ymwI+>& zp`zQ`NQanHx}!Js5!m1|I~S$B*HNRiK&fbbgyzm`@7UB@!X7YCx`-@wZKb&uc>UoW z4c5&1`>v=MTkWKwsQ>e@1+Wo|ErA=bz$LMV-f#z>a7jr*HBOhCeY}{*>?s}>ge05c zUUagDm{km$y6p2%xw*}X$BVVm(I6=rrF|15Wn+AI36_52V+62s(TOlP6*I%6MM(D| z_|POOTM_~UGONu31e;yo(w-Y6w-0#ZP^rYp8&R&e*Y16TJ{~GX z$MW8hXY?y(l6QuC@y}EEFlkV@m-lxd^X2jv6%WH%<9TcWY4eRz5;eRdaMJZMTo$!M zCC$l}wlDoD`eqnPJqI+G`aS|z$Xlux?H zqNW?A2-`P6bFtYJe3P`$kPw4^QPa7=M2ac4N+O#b!Z;Y!sC2qTvVV?bt^v#Sb1Lfy z56O9V8P1-J)NyA0ICUJQ4~JiRMDxW3hXV%T=TtXA<~-~&U`#OQ;cD?7sUrgC9YM}w z7o2j#B}ts8nyIq+Jc26z>>RNvN7BUeWXq9yh;uX`M@qzDd&P|qd%;Ec?@M!}81cTl z_y#`Xg^`*g1t9%s4o0NKH-CCA@}HnPbFY>E70yo$xsn5Un{uV>=Hc@lWerJ=)+gL7 zRa)%cVel;~J(r4?!pMA@-pojK{ zgwU}u(m0qwB0CCe{8&u4EPeV|$rUJc%6hIX+_~?-wz#-7R?@5QlrF*PLVIRQBN1WK zlY*#XTrB*cbD%18s;Y@|(X=_zG|Y(lInqhkSNg6+(ntpixV#2s@1$O9QT9$jv%d`L zM5W)t;@YuRs*N%9+B7OmP@}1`0V1q?41da>mC`T+>zWd&*e zS!obmJS#;~&exb9j0GFP2Ub@3$tg%wUVb=yJl%BgF z)uOF9ns(8O?;+T2dgJ%fZ`a!m^BidqhU+Bspm%*NKl znes`Sc!s(xkmqkIP0^@wFB($2K#nA>du)Jy@jkhy#kL1*SUj6`&|&Y9MQazz{i$G~ zoa%X=jOfBbc?Puat&8N|fGQTrBLFonk`2wgSk5*^2hU|g(BL?S>O$a#Ke1TugPyf5 zk+HRc;Z`F5gebO@g+p1MsIXKXPJNyh*w2;9TO?yhmQdkWnEFM_Tp^R4TklmzSmRc}5LrP{E9DtOIK{UrOHeJ|5p+O6QgaoXbNh0_^Bm9wP*$*l zliqd7F*57L6=Yu}^IDa@4%Hg9N{$cqj??}~=AUPX8R@_dgVPppxPgrA6VACiZ9L^Orc zb_s3MIyueLp#IHm`CDE3`&XlB9T%m?mdo!5aBHgITItU9atVVKZljVlZaD?4(NA%k zHpslw%-taOH+F_N+J8{)O*lux)^sQKr!lu+4t%sh&cOT)-YDy0r+$2+yi-7DA?~K{ zAbGb1Qz!oso|CJlsFZ6h@;0n^Q>eaQ zJD7btFfB{BgG(@L3?2pD5qMpUwPJ5bbx+C>a!2E_kssrXN3j!+@y25;KPDKDS^U_^ zcr4_{MB}lHACo-TCuNtN?`1Ln8&?p&4;odKw93{MgHQ6wK*5ymIajEQ%}}|T;%mFD7GLNmED|?3Rpyg4H%UUrAWYH2vt1| z@txQuV?AQT4UE86oQa1T#Ia~B#XloscfU&xkZ&{!Y2*&RsfBqh(+_Wf8yL=MfeUs6 z&TWP_F?e$`NSO?eXn|vP%K^gE3xsD)&TmO8W%!n>;AYxdP8-#d2AI>D^u$o3Q^puc z&EVJ;%FI2;InIFd&v(Y5;~rU(Z#7`?tQ--08$UvzY?fsr@Xssc@djBLkBE0z`q}H@sUX;As5G!&A7mf3lXz*;T=G?1Li3^jTVDt z2hMVrNjlYxry=GuV_PeF zFH6U;%X+u|hZi*Mvm$4tCVEnRCC-&Dp0EZ{(RFZn9^Na*XK@Wm&63N^_yIFsYR1dV zcsXLO2?wqj=lv%g|CgLy@*t9UjpVh>yO4$BnJc&A8fn}(PFv}hHX1k;VH5&4l4(|f zhGE7tyxN2j8SolwD;jiXVmOuV#f^-ZuW<{we3h%*9n)D8qbYp_%!U=WcJ4zL3p$6< z`7T%}(zeS1;XI0%-7A-_T;Za;qpFCUCDE?nu|V^R&S)&+t=a&dM$Qb$70iPo4T0kr zGw$iGPY$&OnNWW!8!v^5?v!5*Khd=R$PVL=2*<6DcIbnr9S+)WOGy(qQ`tZ`;S0ue zcF;#pJHl|}y*3p${2G#z@Yq<;Iof@0Mi}j%5EMpX&&z4#7}XhemjJ^PlR9hRyn*Iw zY%t^75f4Nd=1!mK5^MFW{c;50LA2lJE;1K8cB{`7-ti*VXarbBnLD*q1Ms! zFUeoy?0@Uau>UIPgO}ww@_Mk1pr#`V+!U*DJ)qzKj21Yy4q%ff-1P84*obfk9F()M z{&#r=(FQ7hMV?~VcY)NH3p@7wD{_Fm(O}d#10E&EAt*4|Gl%3uaLAoLWUz>>gN8ow zI<97=jgOZorxvyi(ZO0QcAGuX!*sb824hA0sjAbUcT2W?)7jiT7@t>NViRYnTX7t*M=&UUG|`+zB-k4BRdpXX+4WH?tVlDyqDI7DWT!jCYr^`S-nea4IMqe(>9V@H>9+2_M)LZg{(T z6X})NyH@J@c0zk73MHV+dwz-U`@tUo_71}bX+WeBTJj-Kyf#ik@UHel@yvohd5yFT zGwq{mq_IiE`98izS|>B@ldIEGjKzz`HfQ|w>O>>WVWyq3(%W-`LMW#LPQqfMl(1f> zLCu5RD~iwj;4^-31H+W{1+FKSMqxlUTIuHg$stq;pyYET`pd!F)-SG+=56bj*GTiW z_3Sm$yiNV;>a@03we{~ ztv+nlW0V|Ala*%2DBWe&%&Oa2;c}md!1BKoHvkrmhe;8m6t(^lX@-G|SM1*DaFabA zd{|L4zelJlR+%jS1d<36PlVxuT1O?)V0Qy$byVgUk3%TEKP-ZRQ=M^l?Opt7=&1CB zZ4ex%=!ok3K`}PODSQp0Z#;A*v~_-h!u}}Zjy}#(gA9kmOv-vU3D?;=DFMC?h|Fd1 zzvXqnxx}JQN{szyRE8&wr@gy6DO1}00+g@H#@SPcL?xsbi;BnbAO!vyS%C8u-*14~ z%=0#Rhdw<~>E^x1(KHyQPj!-V$ymR*Sg-Zg26tI)F8xHZ($iwpHjpl)pmA`xq$(;O zjwE-%!nBfxbWsuw0`zYegJ2~Rtn%+DI1&dDp)UPEnj#6YgX+?iILjYa`YK)NVB^Yj zUv#AWe`DQ>>8=ETq)T^YP|2T2^6Cw*lHj4hZT;&SX`RiqOV>z?GSe^(qO-WR4$!gn#6_Ms7?{2^~3-WG@z$4)?$(9`#iP1uu@G=rFT0C zRK80hytJ=W$G11uBwU0C4UQag!vj1Bl@T}&?xiGk)ckVtyNJG=T1)hH2c>ftmV(_c zEzl2c>xC&j7JcGzU|F0J{b!@xA-Nq={pHJ3j5)h}(`f5;=;Np9#p@I|E*a$ZR#Ggu zDKNXr5<=y@m7wqtvm8XN*ki}m19J(bgT0mhFh~FBt<2>KQ8++}cGrFgA9G*yvoUE_ zQ^EbXLzCK1$pOKFehRz4F`1SMK74;VL+MBjDQOBu=? z9@dG7Rt`{lgK^yeV+?*gK!KZg1zjGfJSc~8o2d90=4p8;hGIBX4MMxx)4@SXoIT>I z7GD^oJQE!0i$a@5qtHM~PmGMyPh={Y7Lj9kMDzG0Gm$E@l`!#{apwC4iBb&ahHO|! z1=%R-2%~BLUfi4rD3x&qC0ogWm)@XC8?3-1P16S(^Jw{CB~e6C(^18x*A7;eS;IS^ z7QDS=|CDz}91Wa<@<*U4MZ;hw;y%GJWuK_ghuo;>R%a}dxhK8LD>kv+aj`r;S78r& zey&l{tXzmFicQZO{JJnRSOQdO=gBjKh9dZ->3X0Nj#{8G+s~-XVdiY{1^2Xr>kfM9m2FsE604d?k%Cr(!`} zmZwZ>L5{*BZq&Kl4U=)5YKDfhi+qSQmUoog8unXz&DV~oY4g~nUAHL2o1T%%I~IGK zZ;~E0MtKJZJo<%Om6O6Ak2Kz#vS3zF{dfgO33~bjr9a>BnKeln>N&%xohZWe6O*u7 zTM{Jq%{@b~^;gDOI!V;^^L8CYqCTro3AKnMs+*=n=#^8H6&8_f0C*!U3fsaqxF0s^ zF6FT3Y+&8xKL+S%aj6g~I`ZHe*8;uiZp9_U0+MH<#}`nSnW)SHT00Xvqg2{66MfW$ zj&jtM&dyYZc6bwY=6+!Z))e!38)8;U-Jle{b%jvDEM=zU%-8hxETzNs7r(x8B^8N0 z8~G=nyBbdRgZufxqt5AFXDgp;coD%lU&$8f`sn$J%VO{DD`2{JfudWMoYlMjLs@TW z?X3{*OfH!K_0X_L(fam8219Wp=7}4N!0>f|xnsR>Z@|9pwmy37V&!W~6tC;NsCgGS z&fC}I*^d%Rl{uFF5I|~b2mQHH<(!DpK)@SBHYu4QuYAma;Q;DV*E!nBCyI>1lP3>qWwiuSOPbfi{U}Xxp>Sn%v`b&2J$mrgAByd_X76U)wAr_@SY07CA4{A zFkO~NQZVQ9wt~07p3-mBRlHc?j1P$Q<;$B)hIyUma(LYvLiIn|;>2LuIuG~L9QFf4mg7>9KGPP9!OTxk1(jx9bt#U_Nv0 zSw>vSV4R^Sq3(0ZeVks@Ff(ZwRXi?tqb)bVYqq>+s zsOV8t94pkL&=%KAboWXDA_(`RI)D5U6kIkLfP^?EBhv2o+GN|cEZQf#VOx|5LeJZ!R7+y0E}m6z1#*~f-wPYmcB9$igYL4n5$=ZW0TlTF#D1w-i5E9f zY>o1$eYjZ~3rA4aa=6C6rPDRaWM__V3hK~_s&0h?r{H@WhZp@z$+P8p3Fzp*l$YUn z1jBBW^Dw{UwNJTU+^F|`PMIofXOWrZ{2A?fUP&~-NdV7L&0Z{5=bu+Jhvahb(pa}*nwOvh>KTKm=ZRrGe1;z*l$Nax0nb`A7XLd%a%|N$~mdTzKGbsIIEakUR?#HmY{!dWZ$I5HS zQ217uOTG=IwxZZnxP*&7Q7#I@p$LUx@}6}HwVa}FJOyPT?x2aEDTBnFwDmL0hYWId z434&Oi+I_*i^Lh!V=6~*lb?Y~!@J)6oO1?qID?9RbtY2YQfy}~o>4{tA>MX|Qf5>X zR-|K!CbL;Tor)Wj6#Lz%6t8bA1{hOyIJIlA$(nw+L3v7u8Tzcx6`aA(q`D4@YtxrX zyloaTc!bgAOBg#p;Nlg$1I5vrzWYm9jU}^@-fYxie2)R+E(l!aoNJ_YG}DT%k;V%p z=PSNO8dsXr?zuYcX2ZY_HWTL}(L1!6c%C18uOED$A3UEPNK9 z4_@w90C%ystNwY7G;hTo^h@K4u?fdLv!bPpPy?~D1!2IeB$_lYyc0S0y8=3{_Dfvj z2d|afv(r05!QtgAh;uy7pQ=Y=qn`O4)YEg{D!au)I#k*^tD%N0R9Fb}ulzW6IwyL7 z*71W95WLQZr&l@S%Qini-L5e##^Y+s!p+Q1vm|4OuCKgt?J(J$4rV!Gp;h@$LV27 z-50#-k1JPLrYuLU2QLm2sZdr^0=Ao!I}lUqAlz}hxKK!&6g4vW2{Y+Q#J)$^Km306 zk@mZESyrPHcKVSW02!+oo~7PGmr<6YcAM41kHas&UCr?$z}&j0{8+x@NB0t7F2XC@ zXZ+H)0B%F+6H?(`U#dub7XjwYm6v`7U>=peJZ?5|ZG7nZQQ_|z-cxJB{%?b-hB+Tc zqF0z0yRCkpgQ^w6B@2On z0}=jZJb5-?FXEXuV7tiKR-I~p8a>S0SHn!um$y}M_xTx#p8Y8>L~n4Yqb!!)fL_6o zNRCU@QKvGY{hznccj`D~}b;f?rC19lG8A!;7pED@n9Jc6sKFbon7L$!F`ucoXp zP_EX2oiD=qB@FJL!3f@%Yrm12!c`q#V#+!dh>wCCO$?)I6E@e;ep!OKR zrSiD^UA#!YbWn$im-GSA>I^Ha#9bZL z*C6Rxaj4!#+8C$yw!aK+T=@gcmFm6;glQb7ri!O1DqbBfKaWzAsD6soh5c<=yc#DR z1R**8V~x}g$3slvub2!DnQ^Td*Xj07YFi;*rKm);8@^7Gn}}u}raKeW{+8DF+J7+>o`?VQeBWrzdDO(Qlz4^sH)SQMV{yG2+u4Xs$*Sf)PYxdk8P zp|zdWIAihU8nOW2_2ocEku2V$3!T;8miHyP`E2NQl=fmM&bw07o-qD5rl|dm?Sa?i z|A0=XU=Z#CmkyMksz!(pDHl=j9l-GPk2T9V33xiiextg?M^urjMjLssd%6f;c4;px z!VRfv(g0q``N)I?%bLS$;BbQ($HUQ!<8;Qc)?s0M?AfPP9>7k5oAwD!@1oANe~OZM z;PL8xgudwl6@jbSUDX~@r;x%!mAAWN5QiY}k0qByi@U0;EvI3%%(sP5OgB|?a^=#H z#=YU8!4>=7;xj7lhLL#(6%WQjk@gL)FO*IT!yWo`_+)AicfxkJo60-!E@|qZtAu|B z6=?2STArrHSsEmII!*0plojkP2ld0{kc=d3J) z&tc(b${lIK`|L@ngDP2+u7>sf!juWig~`4I?9GbYS4oB4Uo3L&<5W7HuEyEV;*qm` z#cZhfwa$rDbR`Hc*mYM^Enh=q-PP+`pEHTRfhm7VbfOd8RW0CK6ZsutYIt3aq1xf` zQq&kQW5vLh6*tLS7hVa*P<{_J)N&qPu^#F`%LNFihuSsydvIVfuGSL7JkY!_j}kAN zT_k#{-Td~njYqMc%X4hcFt9T4f^ek1O=6 zM1fxhhS2T}$pH!@@cp}|uTwkPe+Q*^fn84@U#G6d(syTXb)Bsmbx#7NL}gD}@%fXE zyqO6~#eS`i>X83Hz9ec|#D8oi{9h?=|6#EjmIj2f1a6H(N}eaTsE_$1C2(T!y4bN z2n(STebulicDS&>6A;)~;vcvEKfp&&{xW=2E3BXDuw4Pn_*RtOPj%y1wkAh~d%m9< z7H&)nq+}zELEt{&kzpZcf2f*$G`hd~rabxbl`FUfIKkpdkn!ki)ct~Oj4%8Q;MOFr z?-)Vlm)k(5iy_#Cx1Ey6qp}QjI8K3&XQ)$P8l(?^zLm*28a7*Ane4#X@4W+{iB#G; z01K!_X9uW{;l;-Mz985#5LQfUsu>6?25*WEM5O{S9N{orj%yTKWP&%K*fseo+(*}|qeO`VWIQm? zH-;pfesDWK*yRTY`N6?{aEKoqdNo|qD(q@Rt8hQKy&oLm2S@tBQGRd-KR6mNZvl98 zW=POu15JSMAMmh>Md0BRLp4Lx&=MXVv3@){`oVF2aJ(O!;0Jf|gA@JWq!xIf(JRR< z2m|g6xTO*Qy$vbchPW=NAa9<_=;_gD6Ho@LE+%VkI=8i}XQXzc!eMG6o!Ax;6dY_O zrGb>~QkG6SRrJI_ZMYdWd3P!ti|5@pzzpx<$=;J{hGM+;qNW?v@!~ql1?23F{F%th zKjzVg762{xWwun`AtcO`sXu3m?!^=tNcwLTAAmF-xxT7kAjJ-YQ8dU?iA-+8co+~y z>DT*}p6w}puu(dHj}_-IMx}2s%6D@4L(TH}8Ru8cExyrH&P|?jhI2V_hEfgYQN&t> zbL1d553?QQya~d{Woo<_d6SxiGZRA}j9@-^J?|!1xp`E76ZAp8C*>AT2_p@NHr${F zQCcs!7&3?9CCL3ZsA(-A4mAvB<2+ez^+30g7z>p)-ov1JC>S_Ks<>aAqe?hNFmg;{ z)fg=%d!Pc3qAb5RI*ej+)qto%)Zce;yxm6{Q>f`?jE|yRY&Y+qid?ksPG$p}>LYwy z;x5l~L#}$iJQdjjjW6E9!|cpc6F1@X9oGodNc(h7GNpBQi+pLrjjChQ2sIKuV{{34 zj=)>=v$$Up@sT(vvDsWac2SI<(~=Pz2ox}umfd9Jhbm@*dwdAy`Y@blbTsuGt}deL zUts1hV9nS={KFG1^hAp|!s1s6!KeKrL=%fC>lU?l_!2+qlmO;lFQtlGp!mx;8+NX@ zU{XB569lX7-bma$#YJZ4QqX$GE>F5;G+-ob{pEfv|7o(sR{}<2Xsqx=D?QOFYC;}t zV1q{?Z4C_=g{rTm;!)^|hgdu*_%@xf+?*JN)uh}bv7#liV^cnaQo(77un!R~&z=C^DRkx`lu+ePdXcuLT$25OhzjZuzHqK`tUA8B9JvVk1w0HuV zmqoiKV56{7Z=8TU0MjQi$X}I-rWKRaK6tL1goQba#ALNU2nS4N!j-hP06ma}&uU^X ziqDo5s6Qb(Tc~>8E5LWA@SRF3xE&gEB^BR}zFSEfZ&yj|*1Jr>R26&l;ycs_h5Rfk z=A^6>aN$JW1z*KpnspaAS5wtp7+5uQ;Vy{fU!G_mb(yNpfg@n+RCT`Pd6@#g?bMkv zr(t{j0?nFcZ1g!t3ceN;+K6hr>p#-o`ciXz1Z5$x?4># z?(Vgr*fFSV_1);imHP8{t5bz|iK1qrp)c#RX2M`WL!Y0e&P_Og%6k0{yyNAyg{L$d z6yp$#b(X4D;4SW%v*8%QN%m}>xw9dL3?0v(@@ZcEk3rFNzDS+m^BhEriq%>m2Hd00 z6$h#29`)t)S5Tbq%sJ!-*ZRSAls^ySyLK)%trwd_fVsNoK_Br$A2rYk|Mo+_Qm}37S<5J~3brjympoE2Lw(U6ZtViCa;)QB& z+dC$Bf@&73{hjqD_%7m^pc+P5i!kNF7O7#j_sq2SsqZ3Kg&&yUhqRc%lP35P)d94B zY=WO4#>w1+IQYw3gj;Dzi`6jur)J_QBeD1`9=D6t_|~UQ{AV&fv{>!uJY&KQOo!Uw zO4(wyhqKW{e{P~P4Rq@-O!SvB-L^#S*ZQmpeOW=h4#!UOx zOv3^7BJ^t|59DDbYLN3gli<8b(BGfnf=TebNzl)afTk_PknHP^eF|6_d`AqfpcTV{BJ{>;}em@yC7yECv0;D~c8-ktHBc@h6|a*v|gg zEsRa}$DU#=F2|!!H+_XKxer^0)AiEmzAg`B|1PJYNOj7;!Do?>LYA2PBG$T&ab z1RzPOiSYDsO9i$tJ}3wo2mQ`^d!j=O54Act1G75ANg#C;Guj zesHoM-1%y_q*cn*h*qh7a2G$gs~_CW4^H!g)BWJ?SHUH%TJ^XJ(W+HXKe(44e4QWM z+Yj#J2lw@Z`}x9mK;->>5r8xN-~oQ{KtFhpADrn2XHoJB90_FbQkLk6;;7*-J9gD8 z)Te~=deC#%XPfb0+Orte*k?P$rrgqQ;Hw=;tO4y)A8%`0vxp)HtGr9?kc>m$qcNA{G5#sQx zQ1u(sp@yaBn=1z-9gqvE(af3tnhQ5UHmb|)xkzJojfZ3UCN*x;CN}KzW^Au-P`jGQv9>o7Db)t3o~~*&5TDi>lw`aOruZ4pwmYYvWe+86igE6VK{T zwo#xp>HJ! zU7+GU>QFkh$IHLqS@7SCiW>aCG5E*e3OR$6^DM0UFR12O7=>f0{#kVdfj)o^Fn%0m z?Nxh|FR_CheES@K@R3k%{9!zacj<$@ut?vfKlZ8-_FKII#g4#pInW@rPetd9L*U_A zq~}&+Lf~Q&`UE?ONls1wvPL~`731~#=hYFKm_V_wfaed~s%Ur7hmt<+6&088ChB#E z)KBpbTlB4m)xUTyJp6Ct3(+iFzN|yv)zxndc44Gb;E#Rn_CxRONI9 zFC8|il5w z-pBs;E;{}`l)zMyKY;Z=jqd$G)$I-7;uY2Vl;40iLUTS;m*Jl=)PIP%cy+Qksg7?J z!`&=~2)*>AS|ojIkTlQs#`=QI`uZ(?Wprctkt)x_l4z9>KPIlgK7 z@{e(uzBzZo-2+F!SE|dm0 zs`2vu9=L53UarTNBdF*!j?^|cswbMIg-?inu9h@||L}~Vi=T9m^q4O&moUxKzl5g7 zXo%+z+kL6_v8=#g$E@s2pti4pgySA zomaODc?Ht&y7P=Em%iwG6<@$<)RUW3RRpgz*|BzLMBpMnfbxsb@$cbI@#lI6{ET63 zr>#F@pN;>}(iZoszWiB@;g7#Q^mcp*MZybXS*3X1kjnc-4QkJa2CVAuL*NF84g4U#KrO-#@++cpO8*u5sECTc4adKn`BjZ{ zZUakhTLr3~iTHPN_C~v^e^sASaE;*B%j!@`xb-TbU6I8Gy-L;kx5l^E(z|OB?xI7I ze#xofQ&{*v5RYN{RJ*h^W9CoR(2IC%h z*o4^!!SEv{%yMV=Q92%~^|x$MDDbGO11-8M0w*w=+e*gmOL$FgzzdWGTBM{yXHIAh zqoAfcu1jQW(=@EJ^&K^reH%D>4;yz-Q%5b-c*8P*#Gg?ZDREj@ug5^bduoW3Viw?=4W}Yw;DkxiF7aJo+#JR3GDYKS zrQT;O7AdGDS2-yvLf5)z@m5i;cS+M03VAz9u(}I-;VdIvEAj|@s9Eo-TO!bTsW^dS zDz0-Z0*?*DBwz`!k#xMfmT2U#QDM3z!W}bDp@&K%6pHDg?dJ3zS~k94;OwdOmY)P8 zeB)+LFh1+`T7-lDEm@`As_CUYHO%In_)l$GM8Z=bVnxGYG6G+aVX0XVrXcXBKZ3J7 zu-!$$*J(dkcHsLAQ^P|jPl3>XmNC| zpXRXqOQFyDY9U?ybM7;9`r^GV?dLps%t8^(>aT@bo^Qr(1^BnBywF^gExKi=@0PEzzx)w8eG^9A4vHNPxgO=(#o4{L0JbA2NI@->$>rP5G(#3fcMLS^&jQw>n76fi~MgRXN%S%fA($_uU&xWPArZSKA`v zdd!QrXmEb&f7}Aa-T%Xf4Hl9BA*Vvz``@)*f}fZBhn-+A}bd$6#8$ z12lIqPajtH+}0C+Bb`1*d(c^LqWNs!!$FKiRd!I;SWNjWnm88IVI?gdtHp6E#%hU( zj*iv(2fu6PZ$$M0Uh`(fX?5c9T1<;t~<4n0{>crw+EF2u;GflON;03x(ky8 zZ$2c4NAn#r`zgqk2a8A4StT8#$I;L!*hr)e)cKSvS|AeN`6 z8imufL9t%|&3cAcA@1TYO|s^(Ms?G*bqQxpH1BV{q+gl1mH}OV2A>jNakmzh@U@x7 zmVuZ4oQZ3hLa*PgjlA)jYmk2HPs$@6tYQ$n#>ICg4GYF>&w#!7M+RjI4xmoY!DQyEAXUiE9qr*`FVgsp^E-<) zE9SC3{T}T%A@H@&d0J>l5vW+B-T;yDk30H2^#45V1!RHY zwF3s&+AvY6bdi?V{F3eWP}X?sSHHMOE3nvqKqg)vc0<^$sA4?)ouwsOpOA~dPXo#u zxH3N=XNeYKX~H`6Lz|FIsj_DC>bE<{UaAeU{0LLKRJ*~rXdeQz4?C>tlhy#6_cBum z)s<@Okou!ki?IBJUHnVd_LNqpMOuCa_Zn-6k+mz;)Zi7`hB8gE`~rL#O8wQ0fBP?V zznkfQcw+fav>?J=m-FwOQzJ@6S@n-eU%nqxeU>*(z=TO@Mab-hpf_4EmyF;S*1N- z=do=JJe~{GxE*g&Oj`{jB8$S;X!v}(N}sh4?MT&YwEZ@VmloHlw0**=Cp@Gr475u? zH?Ok#o-J66?6NP8s8;sB*(Ta`Mh)q>m zSg;Mryn(qox!7$Dbr+CiYmH>ibpz#BX;ZM>%5taR{JcsV7Tm^6;x6MKixaP5Kdy~% z`l9S0%jC}m>y?jd^K5b;;89e(8C&$s@35`vvs+7R_N=z0;@vPD@p8l>XpYUhwH)Ep z&+gXn|9JwbqDD(h=IpF8xk=u2hH)W)d6D)iblmx>fw*?;g$z7d5fxTzV-wnenzbai znw#dE+p9eT>2x*v=KrV6?ZM2DtL6qbQ%!&0q&PP+rF)Azx62&A?9v}X*V z1X0#2Fy{-NgX#>VSUX3g~q`hTSitPwi7R_UUvT9MAoKj5-_VzAM zN~ST43%#sXn`-$l4Z8fQ_Tc}(HY(Wc+G}zqaGs$LKCGP(|AoCV1}=W8rBU^}T7bUc z4XxJtKNT19zZOTS?`V;F+FRNYPm``Kj;PT8m65*xZ7s7sUPQ_N245{G_&!C_9p7m2 zYBH7LOq-e?=8;>#>U z^6|xj_Ed2@K0dT4-}aoKOJ!OTm6u`EL{V88c03(CQM4zD;Rw}PZox+se#O_{GOMst zOnX9-^iv~kNy1n}1F`WB!>5-<+lCo%TgoqjMcWXAZ#LAfNs?sltsAIfw5^+nrMl6! zLHMsxVhkdj9geXL#knGoE`7@w+j)UCfL4vObrK1B%{W_I3qIXjcbjczLMKy#UITSB zc=FoH25KVG3_Fu5-ok28Io_t(l7RD+TQeRdB~#OQ+uU$A0eB(jiRslHneoY|G@F9SdxCBccAf;Aq-i zVC!S#>_$~7&d^PTwp4FY?M8fgEARi&_C4@5SO5Q?_uU=4_rBlvp7%D}*w`JzFxjGr z$reTVH>JqGCi++zwh(Eul|slg_ELrog(4GqD`bm8p(#rF(^e|-Z;Dc(-}80e@4MYe z-{0f=dpv%7xZdygIj{5YbzbLnUa!|V=XHiVBJ|cbqJj7#7s?y%m;hj(J;+W`k2=_C zDVu{R^-)JMmZj0_t*~aj{ZY_B0#!chSga)?H)hzTV9p+C@=Ltt8G>7BevS%UCctwA zcG+RG+BqO>(~A9J({09CC^cSOsJaaf3f`=7E#lZ?E>dOB`+9*vUQ zNqVEwouHh|4A2O7*=@B0)3MC|bq2Qix33)zJ?#<>Vr9Yx2btF)AS3r1;|P)4pEHmR z@cf^#_%Zj9N-s?v;|Nze(9lWf&b4D4bDH^EUU-VU18wGM#-~X#k)S7rjdk3DEzV|E zJ|cSvacm>!8h5%OjtMY;iX(C3O!-(x&_Bva0z7l}+T|G6$2xvgG!|!h7Uc?c#%)oK zaHa-lmZR}M^11V-`Oy7Yj$2!FxuH&6U*$dT9@T$9+-llbgCQ_6E5kjE#-RS z!wvOGM+G|&J2&3Z3WsU2+>F$*__1nJ4?pf!4(HQngYmQB#}&Wp#_Uo)#ORwm!4Yrk zT9@42gBuv76C76+srEFbvZoyLv=od!?krB~`FZnlL%!P-3I-=I|BL7?%k>;|R6a zzXvfazCR03rK9lVX-INT^UlEj(;QXs_(AihW3Z=9cO)p-nL6Fk6YIlxw8_s|gTea= z{A5MF;OGHwAN^i*;D%OsnkRL5sGvnJIFgi}X6mXJ9BE1~hIjzGQ#nJU^hL)h1=~|o z79;m78Mw)=d?pg`lImiRXHHH7oE&)xOb=H-BA_6r0o1|gz%`R+JN97nDhC?gIS$oE z6?|oRx5bW7I6cDMzEhguf``X!SWsE$h@d$yBUkgH!^}olIH`Eq5n2c9yTxBQLh_5o zVUcnk6e;gQ2b@Lpm$fl+$PpJ{dZ5jijoV8fha2b;an_1DDj#vd7Y#E^7QnMh6 z?~j${t8anFq)|*ca6E9Wqn9#V0#GWKTe{X!5uiM36qh*K+msO;6KPy}$5F)&9XKCe z#oYX^BRc>Kgd8dDFhZ?Qm?tp4GqL|QzxI%q5C@Iru zxX006!BU@WP3B%lI=1Y}_tdpPLHViU>;<1Vf^bW=;}dl11f%yS*y@IrJ>YK@6!g92 zjwUsIPb0|@VR#QZR>Lj%Yj{OtN)>qX24!`n>5D;oa%pDULKv^o>V?2v@A(FHBjSi7 zG=QR(b9AN6ZuB_nSgBxZ(bz^bneoaU8u}N^6c|>|`3(jyvkswN0%{ z!>U?_t%~{q7xTzPy3thH9+ynpRuU=wrd_`&U9gbO~HAU%yn*+Qg?c>Ieag|Dvmqtn;p)K zKxMkwYdLP`N#!2nF^}_T08*DlImcm{%Q;g46(=shifU;k?`p%I}~L#c2h!`D~@uesJLV!(isckKdZeHTQ#3^!crLJTnYT3 zJUH^H714+z=wiHc2Bo!7aSxHx8A~~>UAWFU%Z7RXSL_~3t=GivOXa;4eiFAtI~V=$ zQ}u|EsenI;(O$kRQ}1vtz{-~so}(DKwIflUpp~)CIUL#4x!9Mes3XGDSE0tG9i6L5 z3{+!{@@7sIR}h++RDIyI7u>F%r9G{jyRfLCO)%EBbS5#{(P^Jjn%6y@(k8irjN~@X zL)FR9)zuY4*@XZv9;fY~iVs~0V2di~d$~KEQX|z^N^a+z?yC=AgXm;C=YoGESe1FZ z(?a?ezRLNRg64B6evLEGxNPoLCN%=7l~TeJXle2Z+&i0>{vS?s298nIg%I{b6v>ATI&NNW$Xsb_diUI1-2e`jkTUJwQ9$F<*Ku;0WI@LK^pXHbJOL&i> z4PAL;q{=KLHHiCV&q>sX73wkfY`-*f>QbN6TC;-w5`8zpxmi%F8}ME5kh7YD6b^81 zLv>NsZt_0tR)f`&v{C@Kg3=|(>l?(+lh@jjNIOaWYw~AHZ zVa{|rP9|JjWzoQ7q2$D68b|c|QCnj%6 zkZ+N9B6yEAiV_Ba?6y7SJYq!}$&HhMlFMKxol)R|KU_cW${c2M7!=ID44cL>hT7px%nxczlL)w?%A{`M~Tv zP@9>bvkg0}UUFimh3uBmR5j0a2>z8Jhs>GlOy*!E)&0FuG7kkI&kgl7NObd^R}kSF z07EMSr_kgBdi@y(hA8KsMyp;8QU||br$+_JbYLpf^ z6Og-s>TG0=i04IP>A%A5mcmzM90DtIdhlm9Ab5AJ*b&7+Df&WzfS*jW54 z7CZCi;Y8%VH=X;eiS1XKv)*|X$$+Dcl=%iKU)n%bsj5F_sq#BqQ8cHi8{B)KJ;hd<1bzltbQ9)GiP6QBx!HM1EAShdWMmmm`0$anyFgX>Xko$- zXB1U@qS}qZ_nev4apeo05k}}Xumx?AUkm!e-*#s<@{=$c?okH`R`Ow^rol+K~oF{zUs4yI7Qo z>7b6I)Q%7fikiE4An@c;aLv_@(v~`tq%2{iwQvbr!6^;Hw)y`ik;;P%6xxO7aqz&& z5H^b*)S&XVc6DQl2hnV#wC}+Xb7H;7eH@0Aw~cG9NkQK#MI?f51tRR8$lgi;IwjpUyx&`w~K64ayNjzon7%%l`Wsu7px*P&6Qv?(_ekaS3O_06$4FN_IB-nWX`Gy|HG|A21JL4ol84UKngu4@;Lw`D!& zidL}yXa4gpsCJzBY|5POsxar~d`=7@u;4yWUuG*z5D9Cz;3u0Qs~26_CR8ixF~gOP zN?(ASx>Tt$f0UqTHcsPZM(B+jZg=-zve+TTcl;)%F{UEpb z0mT{F1!$V`0Rc1fOz~`o0a5J{9%sw}D7CpOuAreBLCJ|)5*0oYr!LG?Lkp&;UfR~f ziP6&$lU{0XATXm)w$LX7jYK&wEO8A$MxdY()fpK%OI>rgLRt~%%1tRnE`o0Ob&<;@ z=VBJI)11!9M#)N7j;{v^YG)`_+{aVpV$Aj>VNP}P8W-*Ymy+F4EDAEhNiR^>Q|dYw z4?Re)M`gSfmT9Q(xJoc5n)Q(kr09q%mJ%Y=Br4j29r4MFcrLG_h8kzLxV+Yw7)>Sj zfiSDiK!-}Z8%+j$+~%#WZ5$udNBxvi&o+UUznyVzfV4uC%{x#VvuaY&1wnG5&zQ|9 z`49}LlxZI3>dTn;OLn>nEm`tgs@RE3RulHRPI3l3ds8xQ*T(sJI3E@Pw-?QKf08x`DIap1m z%u`M;RdCI#jx<*bz6NDimwoPQ*JxCH{vfO|l^h2Hjt_8R``)q`PMr7M9;s&s8c5yjuRSmZ^c zRvF?XkcTMW=)x|mAJJq9`v8ewK<$}7x`vpk!i|$Zy0&otfoZ{Y98)T~7>1d%u6;-| z|6X@n!+Xv(gb#W8K;k!Fa&3`o)c2xg`+jq+X4>hiHL+2ztLQ%Q2C97a4~b`d^e6<3&>nj9=Hm{!BF zD&n=P0+>p)tMHE&L7fBDXcHEexyg6IIZ<*`oa&K@-rb;$Lg5&l&X64 zU{zOFtWnZH1{?jN)HBT2z{m4_ToDGmw@_H=oi5DeG3v}Z@M<={B^yphJNqxJfRa-_ z21rS0uI^&P*GMF1%~;uK0BT;4vdaKB`BuRmQsp+4=Pfi~6|qri^X)1&>3_`O-AQD- zM;1H^QKjm3wTLo*g*ah>mUL7L&3B1u7+X523$)#Sn#Uf>N>V?=2Gf+z>M|aDeNdnup zFaUf>mCvb>%2%c(TRg8uVBam)BA6S>q4Tu~@Qok9QGO36^*QwrPUgw<$Ni#D)Jp%Y z2~gn&Q0bTcJ2U!wzvz?x=_#)zOg|@|L%n|VgZatKB58(t2?vQ_>z`(n%v4v~Y^Uqe zftS>mY(HDiGuHF0^*m=i&s)z6*0aibUbLRSm`@UED8Bq9btdKc-fWjGnBT1D?{#VH zc-M<0o^|F?`D-=^de1tCP)QqgvQf1_?ZvW`l&@j-FXt;Ivx+hD%)p++hDq&;-+=go z2Sg-gJnO=p2nSfCT&eB?bj|Sj(+~PDhVD)!Kk;aL10zI|;b(Mp53JhWRqAR4$dQrx zj@qAc-Uj=u7zzp%$M9d~2UyBUyPpf13yID9A~Gqt2J5+Z4fq^VVhGNoqIc9D1wAl@ zls~OT6fCHN<<7TM*8EJtyy^r-XlZ~O;^v!9*tmA6BW+Z^6C9^%tttv^kwFDpfy99u zz}GFo0Xb$^ynP;(dsuO<&Lk&B#qAHfF2gnvHfbgX7_^*q<#lv zma;Ed(l(MmQ@u8=jvr86nWd3)P#wr-^5GXT*MaoxYQE9ysJci|;4kl6b$!7YXAk4p zah|Ik1_GePI4P@AT?JsC5RKCBROpW`%L6;yDSbztA(T4{7tMz9 zqmxnL(0XxObA`5y_Nf{(SY2q5nB_GV#%>ngi_ShIB`GWmjVh?8_4i9S&P?+cvVqCD zIpkxlf=a%Tbb-A5!%qR!%Ao|}DTDK1w4hSzYol&Rh#=0sCJh%6N%2DKL1{~vEktT8 z;(-2O$8M*^7EA$B#bW4O=}}sbP(*>!Cvj9ZRdfcENc#&+q9jq1CS;B?5O+zk1eQF? zuRE|#Dm73jh?JM8MNvsZq=WTXH72yzq*5mPk3kE*h4p&Q9a?sv?4c++sHxcS-9=l; zIZ7E#Jb}t&rf6)SHy~fKV8oTWSy`b4w{h+6(JnE`Ao)$DVq;)0EgfXU>I8NYn}AY( zD7{~c;s73wKAmBu1z|2F7+6Y(<+?#M`_w2Mq`@GK?V3Zhx6JN?ORFIocI#2UyWP=7 z{7?l|DSBGis*zOD*G-3T__!H3QSN~UI+r+v5YM&zS8mmWXIwPze zX#g@|VG3Zd?nd%x6PJt%LyX}MDRx9t_E^mn58X`g(Xg_|fw_->87BE-8?Iyg18-uS z#)2kV@hhtp3$wJ{NbVD`{E|!Q634C!V8N>Zf^r;*tbA8WF;9VR|LrJUiIXE5Bc)bcDM zeI;N$4GXo?G`6Ndd+$P3ylDY(K?*WtdF(np-Q*Xc#A z7mMD>6#qQ9LAVdbZe&l_X3ANE%`B;Bv{YI>Q=6$p_%Yl1%xu>~vWLB=+^)8xAunm$ zxSCdy1$1@=)~_y|(bh81Nb=6b2#KP(2es$@!8E>q%Qtg0s?KyI73G1fto-C+YP27q z*HB*3O$x{U0da7(03&nJ>mW`vr*@QBrKRy0d|T_P z(3Ur~IJOZdkh(@=Nrp85qhyW7Rw4@qdEFcap4AX%WwwF|lv8ispj9YJj8XXx#vZn( z;@O?NaWH^pZjw9?D$G}=b#CChunbu229wv20vl*5!2nbfDn1MIL#hG7H`xM9ZHp+X zD{dY;wndvji}sKb90q^B|Bwq=)fo6Qz+(|f-ntNy@}21c^?DC zY3HG{o-G5D?sfo%N6{N1VBUw^BfRPxpuF-mbhBL=uRPD)g(*eWWq?uf5l@8?)Nr?k zwdaFoOCTIBfDBgnp_XB!?$P=O2DSwGVN!e_yrk$1wEc?x+Agk6HNk7G{0KCjDfw6U zN3!Uhr4WJ?GNj-Oi6Eb>lZrb%-Dx!Ekj83S)?Zo|JU0A=>8ZnEu+18Dr&8vZ8oMCz zhnfDBb_lJTe^qO*Xt($k--e6F66`f?G9`Q?iAmOA-#1znGG&9IG2|EqEI#B60Xye( zDhYQ-U=MBapBh`AYYO^`j#bD!B=wf2W5aab1F&CJRcdisqF=GMa zY9D^&0B-9C*-k=I&I9P9s*_rducd5nE%*_X=wCi=Gy6$9$$9bUxuN{q{Bpg$x_rzy z{$11F52ynJ{c5`-h#Gz5oI+`((tIX6#3~&U!2oO+Og*n<-%yWjsXQnyXr;*Ahqo&? zz?_|_nR|^p()^2>QQbeC{OW*B(^mgXpzrlwQ%o7tsodY5%x ztw8|%9r&(|wu}X+uJ=_qrzpPy?psr5tFOEKv#%jCTlAFdS9nTweYvM#n_5N5TVU}^ zwuAjw1YpgZl^=rg69-GN-ODnlYE=EL!PZc#N(Ri$(h!E}b1~b_YVtUx-Q`zeS6QM_ zWp}5sl!5__P4hKY4y@O&(V_|#K)g!Wh{2w`8PYupjiSu1ZfIdHx83p2_I5Lh>$!X0 zgy`?)2(VzaJ{xw-H4X5%R0E?^m8A4f%A?fxFzaA~!#=qvH@J++^+ZGW5Nx-F6pyv4 zCho}vjbxTJ>g&CJt-Oz0iKQ897)+))(GnVSmfX$7ywt}kj%hvpQuZ=aVyJ~;4-d5D zI9DuHHFL4~OLAXXkKWbw;C`!Cp5!uxQ5EYhRkS{Snf5idH*+tuVdE}@e<*;g2*BBF z_51yP?{GU?9p2yX9oNTJhd<=^4iB`|;RF5N;fuC9e30KeywX;O5Al14U)t*MVSexM zP+J|I=J!6_@BLA~_Yr>YBmLgtqP998xTvjuALaL+?)Uz<-}@7O@1y>8|7}CO>U6(`Qv3!wXRNduW8GMwHa~DW0|=?g zM0#Iz=>%kOlaHOsbfb8(8@rIoC!uJWey8#Rhdts@UNm3>7#*n0sIdhYL)y7}0J>WD zpKa>?bIiwF^YJo2uv(A?`&IIrKu`G`xBcOH7CVt=Z?N0@A?zjc9VX919G_a?BT9Uh zWB&gD`-%n2M`0K?pAzS|S3JDne@L?MhU^Re2Uxlx`$e4nU}@o@in;DRz!JFVBVd70 zluDf{R(IyP;n%|wDx^Hacg6ZNm)8gi2@DTkU=6r5(21=RK8#>rQ%t`5SA{n;se_Ho z1?~)MLV@+h+I|wpTV2aJbKe>hZ%UD&>DU~LHHC^r?sSf4l^=R;4$HEsuepzzVFP?C zMEy4xx?z%+!hiK*dd;$txkHDNAB2mb!gkn^m%0p11^7R=178A(!hSE;S+K)!1&>5y z%nG+$nm{!snUi= zr}7Pw&TC`S08mjqWeE-6oCu~YYfk)nQ% z1MYDO98K@RX4Sj{nCK&@{%7u;zP%G z4VDQ9-D{a09{=3UhLAtZN4+oHedUSVW>|oM52Wlb&_JBu{lfhSO3MAxjf)vx#PMEn z$eo6OtV7tp8bRyK$7%D?^su`-(u_GQGgdgw>cj4&hJ0kW8EVlTKR&R_$AtKthdgIW zfE$HWWna3(&62v*e0_~PYQFTZs=qXD8Rx-y>NI@5Jk0CM<16>`(uQTD+(0-jh&tll zVj8vVba4$_CsiGHZ{ymZKo7u#a{~1ZpnkL2AK7{=9C*KVcax~&Q4fzaa5qSK-@0Q1 z15l`q=9l62uJUi)Gv)EAjx@9a*~qSg-SHZ`=fEk&eD5F-%^u_?uEpY1Uxhozc1N)_ z+;Kila+BrwW-jE!@< z4u+s%Ke@fiI(U9_x4QdnD?OjWW6rqV`u6z;E3v+PTz7-@&9&pBrCjlMXv0sy-A3c$ zPwuvgkNm6N!^N%1r$K)bshz0s1T_4Cr`_08^KA?k98bIBt6@*O?SVSFMWMuNN~96` zvwMOPxCv$9KDImI_N(ZOJ63s@O$Rs}de)tgpO=Ple8wFSxVZ)>^(=ofK9XBb8fZ87Nlc->wk>VcUJa2|2X5pb8ehedf&ui$p!b2 z7F+$}t!c@ZcAI~kaa)ypJ^OEBgBc-h6qQeDWVfzVZTPD@vOc?%oQ`_%@n!A={xeT$ z_WRY{2HT`^e|6`}cBquq8D8ML&%o+<3222y^pcyGJhxwRcaac*RCb96w9)ypyC*Ld zq`V%9yWxL#x5N9v-`xhK?QusKMOWNdT8N;sKjpwt{*sTBza%d}o=`QuK`&MPDGS+- zaI5@W->Z{R3e&|Uca)5(BMYvnLwJ74dF&cW;)8N#E`MBgPpdxvI*oF#xgU@o7<@)^nG!x31@b2XUII z;ZRSwGR?ScsHbgR>`!=nq-Qp^Bgj7R!jGGwKIVxCuSf4sip^` z-{YRHing~_Sn(4coDE8%Yqy_L%%{Q|=&EU7{V3wy1 zrEY?$_}Dm4sIrePj`M8g__s4Xe^$foXSnXl0gi1>$xnI~SRntUr175V%4bH|cn=)J zxM}+&PdFW$=n+_Pw=A&xf*Elb>yjH=$8=9T<#|Iphzc@tWd&*eBu_|ve^5r~=KwL8 zH*#(wkGtBbcs-6vXHJA|_sEz~wSj*c_KaH7Uq;Kxo?il#FN}uSo}>197ZEuKKW0We zS8)M{NXtb}9QM`LZY1S;(iN+u`2jcyoAwsCQt5M^BI#9y8`2a%=Ge@QzBDIF_T>bm z7zNLJ?oqJ5j#(BbQD=BEDg6b{9FFLzvNF85)pCxfL}}#TK&CD(+OLtrUiN$gH=)KI^F7$>@{LhH-%~dLeGyaOiEMb( z!m&gmxRw9PS)nJI@_uRz-q#T3$>g#a4$WFDbLNEj`g*Y} z4xr8J^OVh;oR69CiR5ZO8%9bY(rCxf0H$JYz)DhJG7{C|o(abx5p z+qW1`n?wWa*mU9s5z?>%Q0d5u^F3+8$UK&L!fZd(s>X&?5t7)=s=|*} z6?|3t3E%%b=FU|s+w6sNhKagr6|H}+M+e_{O8!(3EnRNb_Wk9a#{Y;vT`Rs3TtWoe zZ9ms);w@Byjcm3vwc6S59NgDP;Ip-W!zf|7C(3s2pMmN>%06GK?0?qn!v7_{swRHW z3eWAJtNANDAvzP3kIEVO7kxuJ$S7aofqtH3{IL?;3~Vyx4NqIk`H`{-PR8cE;py-A z1sG=nfXQ-|=Mm*sbBv~~^2GXs7OwK}{qGXwk_5qtZY)KQ$8|0;<7k-k>tXZNYEPc+ zauD6M#xuqC8*sSBbKUkko^N_Swp|IL7vJ(saa0d*ikaRBN5$8A;2ibXbPtZfyp40W zlh=7VDAa8RzN>DDfa&p9NGTO>d%S)Li*JdBQ=|2sP>XY2d>BqEQrCNSS>MC6Fm1(` zcy_@{;l&b~k;!nHv2}yxJ z>sX(4&Cej~(_wx(txuQvsal^Je}>bX%^u#KD&N=gyT^Rr$M1sl_W*O2-yzo`n0XwMR1W}Z`1qm9O2}(IZBQrs?1rg1y2q}0Ev#gh6n!p{4tWN5C0B&q% z*Te#DVy10wea2ETCunLWsNe+6NZIa*v~~Iqop{&NfV}S`-#9G%0IY=xmia!wTAKN? z9}KQ$EAuni`h?pQPS9G07HkFlE%MdY{XaBzvj?2T!1U0A=5EIH!0(g+AlkHLclajI7^$$IZb3g6EeWz0ypKYowgaTItSBY(`jlD4M00Ree2%^LTrSn?f7}|V}``wU8&(N z&kCg*mF@EM#N}#uzzzQSV3A*1s8WxQJW*lyAnA<`y}LVTSaWeJE*!;<#FCFZ>47}} zB`9XM2N!{T>{;Nr7vIw7s2NuAF<8cZG=Dd;uh<35Chzv7vWtt|o(JIF;)mUyQSffD z;crj#{M4~_n!E>$u@5W+66GK5@kGNU{sSKM`ypR{OBOo8yhizdVk{C0cR81r=HR9E`gNLnqI)xqe1+>7=9&M@AGtl zJBcy-JR|g>R!PIG=Oa|P&(l6I4WIB!{JR%Bn8(IKT~x5UA*W+w9NFLRSqZNZm-nM> zkJ8Ws5bH+J!~>r33e4T*AdUo&Q1h!`os9YnBHm*r2)g<+&uD#=RZ6<`eB6wE!az#+ zVHi!1AN0(FFNi-5dfLDP1kyyqdo#{s_xjuufveX57Ealp1M9GBb7A5nNcrsN9$qJz z_=RV^l4UeKbDv)wfJ2J;eY9w6F3nSaPdW~u#DWTtDC>_ zj8v4TjK_|6Mko(Y;z(ehr8l2@Pk2O;g6QO%#IX}i7E+(SNgOjMj+;_bV6R4eo8C8` zxTen_kO8=$Y4|;aAAd6&^eH#lH1`|N498S}GO52s8|pU$H6Hb-@Y&w^DCm0{%{mHn zhgJfEGb#65Pv3`U-K1?koV;|CI3G@C-z3h5lQ|q`c0y7G zDm<5-t^ifNOqCU$XK+SamY+xaD?QQqB}>^AJ~+{QGx!w_?yKaRUkkVmZ7A6T&l;jP zT9Ex6@UzftQgWL*@Wb#O+F!tJX-?kDSZXfsCHUGn1hm1`N_MzpSrdhmfqYTiBI^CU zhwqMi{CnUJ=Sey5s`0S>lz)#JEaq%~dbsc=E%RYy$xY&X7+HFgI3Grq)xwBMy?%fw zQAGNWo>hUxh|^(U!Ei4B(X$O5GxsOdayg}(Mw`li@_6BH^x{vRuMo5M6nYewKb{6% zyv_jZ3-!Iz7zwKwqzOg+?CB7=8aZOcU_CrBCp3dQlAJ5pmYDN1CMGP_{_OE6a3@}T z2F&D5++B`=0TcfhD81L&dO=3TSx8*a$sY?Y^%{tg0}ZEm!lo9Nz)o^krS z*5_vHY0%z_o(BTA;M1zckTLMJo&1YOJp3L9A`Abqf6OZL{hP$SVa08|N!+7W+_vh1 zjBUR_wbQpFfb02z_1r<%e)a6sORdi`>-ixayyV%c@3cO5S#SI7v%{5*X+C*|uYbm#$mEzH*eI@D z@w9jBv!dcS3f>+6@ElY28=e33;N18DO8E=Z$7gi>F9_`A)cLAsaJ_>_z-;L?{Cu*| z=d|uBX5TNU-!<@$L&oFRkPNy%rmS#DXaz4~BV);h&_EVK8|C*qA5Q)M_6&hOkEwDt zly|VFwhJ>wn)3C+gJLKAIWJVidfQP4b_fSFFw$%yftxu$K&--6`8(|5dF425w~I5j z6ApSaP%O87>!3b$#1pm(B+*18Dt!?H{8$~a)>i4D^BxgKMRmop|bcscZ?Yyk&Slb2J{6}q7HI2r`4Y$a%U6c^0Ki7Ak8+raB z>pRHyt5xqg9t2);&;gHVWV+zFu9V z+x~FSOS-rPmnfif!VTamNU{Cppl^c3#|^Ij3pTFF<}{$HFB;p8BOyX4w(Ab#=TLDM zjyv1I-`jjFuJl_=D?NH^+F0FM7&}%T1SXcO8o#tF@?T_ zpBFzL*6p;ezR1VD*eMM}B(B89Bb<5-2*hch1|kYRaAkOq8NRQ9D6G%kiEoyZlX4?P z6Wo>^6ND_1M+B*q*%z0{9g7rEfhrP#Yu5*Dh$t}-caJ5u@nXECUgSR8 zD7=nocA`P8#Z&OMc(AoN3Y}-&En)|}DvoI*mcgr{mLN95En-=MI0H3jT_W1nky_sh ztaLJ7xK-3;tH#{6qEldJQ~;XqA$Jq1YAZ6J*bHqa#^Dxd+0JA}#5{1StlQA8yQu!{ z0>0s_I)hwpXNGaR7zsrMQBbRRVCA$IUZ^a^?NQo2^hbL!8y@%Y1!r;{#AN7p_3jYg z!sFiMJ3#;UQA$UkwzL2e#cL2?)ZS zL5laA0N8PpB%%wN!b>tgk=;!ccqDH;~uDdGbktQ8X* z_)x8H7&W>HEZTMv5rG40Vsg5Obf_h#y9fhn3Ho(3G!iXE7_>GKMfSf7?H)qsQp6+B zBYWS4K{AZLU|+aP1lb;O<`3)EjPgowtxeUnx=w1;Nv}gk?h-@vG?d}5JPoJ2yQ1Nb z8uPn~XB22B+TF-(B%QklnU!`26%V{yw1Z|c_ihZ^|Kg%@6gi6Ydq4^43^f|}R>`rP z*)0W4wYeF_IYRY)A05o|N)cKuEoDs0)ute~QdBGsw9YLUoq0 z@Lr62s3+_019^_8sXfshz$_NSmj~cVwVooP{sd&gjHU;EUi^G4Ya&CBSDvDaJw>!K ziS%CR`^nV17e@2bw1mH=FbJ-XIsrcIlZK<7hPc>m#~? zg{JgH${c<)p`yNMaxQ({SF{OyPIg{yUy!Lc6+H2I>YOS@2Tn&YOg{|+aKa~5ltO{X z?FXd1h zQQ!D<+#@0G2MZF*n%Uv15R^WfUhfZxISla?bc4$eik_6w1DNXZ5GZLLZFmTMkcW$x z!4T%-?hSP2E4X_D3?`r69e{zb!1!T+z#jaWRCW|=!Dk=FWU`R1Jq)TSpr;3+vN-iH z2+RU{!12LA&?1X-Juh%}t8p+$ekd4EQJRP~a)t;TUwGA6GK`1+YbNb1roE4#iiMPt z1`1n3kEe+*0+&jx9rU19%V_9u(aKijqz}^Jw!LULkceBQw?8V{2QCMg#X3Y2;ijec zJL*uv2r&Ut{Ocpc0}$W;7y%M_oraG@r@vuRWNKeHm*ttB?2n0(z*VxW`FEo`S2G-z z!XE>U*O=qyO$r<(7AtR2@hDVpE$tlz6s_~iNTh@LyiF6+#W=@$WM$4Km5+&#g2%;4 z+}tCX?FLew0JD9^0`Mp8jgfWO_#@kk?x`1nc^ASUMG$HGB6CMP00Rds%MuSb4p?n4ax*d$S) zd@JJvE0C?A4eojv#goOzz)IgYmQc=Oduz#LNUz^XSljnbIzL$qYjhGY{#ww3=ttke z{|`>Or7WZYO@CUfwEgI$mQ%!2wx67|e2O@4JLRNTo)OR4PCF?qTeMVu=241*KZS8@ zNVXUfcm_E)T9_i57!}z-0dt=%Fv65P3m$*gN$)=^?y{Y8()DK{5c|iSx8g3yxJHyW zq8`?jKk>NfdT>x3s(22*E-R*q2wRnt{+uT6b6iA0k`q%uuO3;4De)I{e~!4t_N$YY z=7^5AOHTSGNA$5>cG7{f_ApA$6^E4HOnjGX3if>3|J`N_ne5cU2)QH&xvNXKQK2vFIoov32@0Kn^5NSV5%!=_(HI{oQ-wt zw!cu~^Wst4Rp9ak)aaU%UVcG@*{;j)X7I`;LT!JWk;;E?iv)0(x-cM0nh`k?EDM2c z6q^~B`2r@i0J`=97`dG)UjXk3G{5Tb7uG0V6jveB_MRbn+Jap4wl}^3rG6MHcJC2Ty4|N!8UlGTY;7CNp0Sv|rS$vd>o^4a7 z0f{XkYD~i$A{*WC9Zi)BM5OM$AvlJ#g_6~SG}@aKf~$S^)luc4eMvEpEEfvZjKglu zW?^pnAiRDZN;?5}qwxh|f!-LI`eY6kZdV$(zqY5MH$k}k<}E?^yN6OPW6n-q1oe3( z?z8}VTuHMQiAYDR50cg`5(5I8;xm$zx$tG5^D4xpip%ikE>)W3S4D)QSvBnQm{>xc807aLcHyn=g6XX2HLy}#(fb;R z7ml@G6K%B?$b@ZTvij6|vDl%tM38UQ8XnbfLoca?B35gKaJH42pq&dv6D_`0(3nEd zb_y*-%)PA<<1;e(EW13PF*)6GL)?Q1^YOekh~sI>XWvU8WeIXkq!vp=lcu*?v5aTV zsjZb09&!+wRm)legs0G~C8B1p53d(N50-z-q1w^*C78=o=s2LDmf#DDRB2G49eLp% z&WEBRDX)xeRZAgrw&(ar8o5-QY|#M#jIm+(`C7*1^ItlCzE;notYxB&c85PWty?C- z>+_}vHbP@X{qJgMggs6DHU~$x=;)sU!8~@^PR)O{3NT`d#0VQreg#zf3e8^*ae5XV z430-bR)CniD=^*E1SH^n{tBpDDRgZmnm&sjTZw6Y7R@pr>sA8KveKa3>n*{PJKAet z8@?`KQ_RO>uh+m9y$(T|msGh!l2CU>Z)fXyr*ZIgvBIWwF($4Sv5p0sh z{JD+7o8I#;pxtYMZrFt#IXN3d|9{+b*aL9f3F%Zh9QKcQM9@F3bG#QI!4wmvhe%~O ziu8^)*nPKPc&5H1%v%`RVt;?*=Kt72aG#5On=#&hN3{G$d`~l8-u~1UwUDMX+VB-N zT=e>vhV}lJ@IEwrqln}!9M5hPEt~qI&WEyOB;`fjyDb#rA_sfJ5a_wSl-DCfxUyhnX>LlduQ&Lk2)pjbnc$KPsBizVc$T*(R`}`>6~`xeg~biAD_` z_?N2oH(PBLQMnwqD5btDL{r`w!GAIM@nrzK0dpCiUGd{C=kFqlsSFP;a z&>r^hz6&wbBtc5pEHWBCXcf%+7IgO~sE%`g_~lvo0mm~b8& zDup5J^&k4T)YRI5{H@b`Lv(3fy(a?s@(3C5Z>f01)5XNbFk@|j_+jmA&ddvk(76R# z1e&o$w2>S8Mj@X*@_rS%v<0@hk0|9m5z_f1Fj^KVxG9Vq#s!D*b|mgw|1gv$VXMPv zKC)=R;E<~1JWw5lFjkOx-)(gm?SD^%2QqkF+VE)05Y*wCFC+lBdB~8~?^`gi8jg@5 zLlFYdiSLW{-2HFAFJ57<{aev74^!?|%uj=8`&P(6gGt#2#cv2*+y(}nw+*K1q5M7? zHjAO#p};&s=e9#LNTd26h)#~-z$({IkkTLwew97|RvxA8A3(huLGwRi`K(m*fYVpl zU-EWM!qhP8ungWn7GLU5VF za9v>0$l8kvWOMym;SOtjwI%T}M(?xL>0&AK6XZONONpj!p8)AOT!~h+_BVkmTR(*o zm`h_mh06LIJ-iPY^#1_uFFD9M|2`PpxAAAx*DwYg+Xq|b3yj#=uyYh21b)lQ0k&tq z$b-eB?0{&GP5h`HObrLntF!nqfO0-V!(SrpYadExehF+;LVIVlKr9Lif|=vuXP}!o zj2GONR}M{KE~l0yWMDBg@e5?0lMZPw4eg_(4@3lvAE?797(Z%d9Y^A2Xd~S|0$Aqf zu#vpNiE%Rf3*ZGOUA{o=7tr=E#9J_f$ZU%a0YL?vSu5IhOBfv392RMmz8j4{b_7JS zuM~(XIgA0nh=E{a-Y0}42eCt_j`Qvm0x@LbVQu&O^el)7;OJJ{% z8-?X*4`j(9UkMx)-)mvFAT}%l2Pe>!CV0iMNAAXD4BnoSk3d=|;<)CNcSN+K!cU-v zPd#kHHAAhV~zW9r#Tea~zhnw`luu7@F4dPzk3i9Z|!X zyHNF&C&WHjQ?kB=0RJ|9^esTwQ@sj&mC)lAFo;)XwExyx&jPiBMq;FjJyf_ zfDL?hiYbc@G0H13Y+*fV@jbNq&9v@&$Uz3}{~prJ7P|K&uoiI=wDlgp4}bxs_@p$T za5E|52jsqurv3n^qKoLVfj?k2*vmE|KnG;S_&QSc2ozYCd=IH0qZ6Gpt$r7&Mwr{w&gL`(3p3wy-c7at7+^0h)hC zflq@<=`lmQ;>y`=HZ<)cPEZ=AZLpFs3!M@(W5ikC6ha$$8Y|u*up&FNpr| zxApi1wCpQJcoVE2UcLahujQBR8yB_g9oB$~sz8uO>0%WCkC{D_aS^#5r>PeKeWE7F z_N|NDzli_ZDqQr`FJid@*ILY@6MhxRf#1m{m-Rz+=lqJX`n{R%$gePloCGah5-%!0 zm>f9jvdFOgD8p{WB^N;OuFD`Yc-gXY!&=;Lz{)8LD~o;;-E5~_bmBMh5?wh35}W)x zR!7d5xy4-(i)?2t(4s#?tnC~GkSpRP+j+l0>UIWH zn|)QNby%>z;-WvViYKW20!rmjs^Ygr*Tg`_UslnOO#VjE>UEK6y9#Vv7cFhqT=eF3 zn2oMmRqyaO>h-saKKWZTqtZPX)BmBpe}hVug)Q_EuyQEBp#w^m>2Z|aR7DrbDbT1b9=GR5$Np?K}dnLx$^-(sh2C_dT2kH?v z_<-@H&4z(Qrcmo3g`m!L^hY2(EvbV_1ap~9NUf{KDbY9hcPn4Js&Cb(~E7gqLtM-OlbkT)H1;Zo;6=sZ>MZ zu`-T?8>nqc{oFcX#qYFRpN$p2i5`6tb+aL_Z36#A2|bPqZRo7h5E#$Wgx(Dcf3jZ@ zpGZ&eg&)7(?j(%#df<& zw~P&gdxtQ+7xeFxa4e3Mh9TPy{N9Q}!-17M47^WLux8k=J};gnVk3Qd1Uj!12jG&| ztT5qVyBCX6NgOr<+ar~$=!~lR`arBA-rGR$8`uSsvcNHI5JyP^Jri1LOr*ZsmVzpb zs@pg}^=_<|MCp^Uuy`?AhiNDZ73@k%Lwy+*6pI^zj_x)yO>3m5V=Yk@P|*mz-JKGn z^&MDCM8fB>mN?O?kHT8waj!la3t18_Ek>V?1;zc1^~i$8dOIvCA~=M4G)6B#No%aP zfyOnzvEB)q7Jyc0td@mYZR$r;o9NF3-j4>T)bAh+rXp4sxMM3O7Ub8TAFyMb1iMHL z!-9KBEXeFZ%8k|ADi86a6;++G*=++r!iU@~Ni@}iAcrFYy9=|!qHGVV)F(UacHYt0 zRBvk=tkQ|>uv@9>C0-oqT-Of491+;5-3%EI<@abRzXpELtC=1O5q)?weKZ}ZkA6Sa zOpgmplZcMFrzu49mRMiB+9MjC51OMVhtrtmdVAZWDjnz%)qqMlYy_Qaj$uDirKR@< zg;7SF-of^mO6UJuw-My?hAjX;%KA(%eW z&UXey9p4Xbsxbkv{Z z=k+NqursKmzAtck4eY+o`Ysanuzj_n61#vilk_NteK|>g0yX#}N#7dyLM`Z1R&)a@ zzY{}C)^d_Q7q&o_@ZmHT>K^@U%)I|0i@aJ{sK$`)`Vj@IyM7t*b)S4A zEw1xh8tkT9PPv!D+a>G}-uv{mpjtmrB?eML>Iq#Gyx6ReN_y%YV)AcNICm7+e?d** zQN8pu;O05 zN!-I$+-qjs{k1afVMQ*!NkTSVab*f`632X=;I`;*w zt)bz4_2ziY!b8Rh%|P!0d@2j;HKC+by~DJnHz|!73YWI*CUJeNxT2fH z-D$-Y-z4r9D{gu1xL|44_R+%%j`Z87ntD&5o#Og|1aL&o8Wa!p(_6~Mw5(~@>ou=k zX@5VxlLWxFB45LAGZOCyABKMF%jVhsdNWBcK~y}ZX*BKW51HZ(y4c@B)$7#nL5yJR z-t~omUvtRnnvmPfkTo?S?ah!ksq#V0*KgHCv@|2u)`WzZA?s>Fl4;#TdL&fa>LQ!a zISyIRA(1#3gF_N6;hnl7EX2OB3f?4=2B6X+LNqhv9W#f4VEG$KA82y9 zdj{$)!J@JTf^2Y{XBc>W)r@d^t?Su0p+}M_F$XJCrBktEIqf{gVBUT^7SsnqMo6Oh z!NK7q9)_&-F7D>*gh~{~={w6S#F84rN4Et7umQM2Sx!p)sR8y{TD`VI2W zsfY%Gb`6#cCF`Xo*ab9>ZSjxba6m#qP&@LT3svdtS@uXh1Z}G>ENdua$~ZPuTUg3D z1OGDb)4?Hni1c#6%TQe@W@S`J!BDf7Lm>+Ir{_9L1S2UraVYrwR){!CvqtdU9c zu{x?~TpXv5S0n&WWOu`xA$mtVsk7V5b6JqhBWU82`aD@K@D|z#w|9?+_5xqPp7{i~2)P>I_Q}muPw?L{~29dZT8w!s18E9H4_!*gE`!k$^QnDep!~I3J z{7Rgvn?891srau}kU6I6If(82tOQ!~tORO3O`qY*yp*-MDHytNN|M7Zq2W0Y^-x*! z%aJRS%*~Z$p3c>uLEK}{L4k-cnm!Nij?doHWr`)!G30=w>9UpcU(gMF4c*Zg=I9q8 zlQC|vA>l2S{%1%U%9>%-qYmZn!-e2trrzHI%UTXP&z>p!_S{V9Qz&$nzQc-5oTj&B zlz^CC(!2R`E=A6nlk|9~0#Js4=h?E++h^-r@%8i^iEd@Ce9W4wU*N!(&A@rw9x9us zPs1Z6Pqt%Wo@}c=UxK_kUv^r`EAngaEBfGSry|`c#)H+gDOp|_LZD)s;{Ev}HXczT7zLhF@QUDfL5 zN6^ZZdPFUkF?HzT3dmq69jPe>&EZWc;bIP~@E+4!}#e=`Z6AnRx0O7B*G#p)Isvl|)_ZRkE^KtE{o7QrT=h#JIXj&$h|B;0!OoH?tfFCWX1asi8)}Z9!oRdl2g+0w=7&bX+w4mF6Xb68qx8!l7ntv zXZ2VZml8==zikjp$K048%ilJ2lJ#;xmaUhB*{?(buJGXv=)wp_eaUw?E zkau8Oh@ivoNFq+$sGBZ$>hbW!thrGy0^w$D(npxAlXo$6!!kfX?XZHWp8`OA zLw}wrJMnk$^^*7XO|r3YE-?kwELvj&A|}CqK}Bg~D4>eB%K9a4lcRRYHjBuxqlsAt z(1N4i$8Gk4u%?(4x5KU#L9cGtXIpvt7>Gn5X5FnI+o;1Jh>Amz(BwX_s0@Wmw)lZm zl3MJ5u_}Vz-J!pQ$M8})R*#p;VR=Ux6kjxA%!k02EF_AuKGdTNT1Q4v(W`neHF^@8 zHpMP$QnFF;>;~1bxF=~BP>8F>^+~Nr?T;C0!N+<=U6ywb=v{3))cih!!;B#Z^g}k- z<`WO07RZK@5UdBpW7|#6=b)o| zjKiO!Esmu~&-v{n{gD2E^t8dByl%=$otb^_r*Nn}?+^yzE_&|}xEybzfU5Ez_^#jE zV1{C(J>lrD(3P)X7P^O89|1eW67Sa-x#ceb+b56cLzIsV1kA33g7<%`_qF5M@Fbpr zyHQIQ^(zeu3EU%Jvpx(7wbulGQuF$0&Fj9J*ZnoG2dZDO5wrDasL)(d)?3(t5Q{KQ zN{PXN{LIt(0!MwG{biUw)CNE3KkJk2SZsLpJZ4)uCn*5=r|rk^K3fI;YXu}-)VJV0<=d8# zhWJHKg9Kt><&uN_kS1PpKJ&0l|4pLrB?{(pKcvuaFUy>x!MRX*s{j3fo&PeQFPW}tKg`$!%6ms0bsfdT}I@o+f8a+J0r`S_N zq8{9p(IQ~XgE3aeRAW+)%wD6WMkAMuU_y>PUWjfYQZHvYGEqME0^bNV}o&( zS$J?}0AOm>Dgyz2)gV;kPPzZtSREBy9$+?z5)Z4#zqIGawJ*LJH_;taL|4g8) zl2G87if7i1rXj@*)%>$5g5saHp{oW!kJ-{XxEG?c<}f2YpsPm0Ehy0}3&))u_2IC; zP4E_+r!!h421nX#U&01-R%vNuwhiu9mrDDD*s;c5S%e)esqf&JT|$T*%eZIZS|&Lk z`h@5doP!1H`JIBtJJ|dXhk8Ly{JUX2dOJ0$4y8U61*1k%aECw{OT|$Q!t;|+ubD}~ zY03pUofJGm`wF;~je&gww{jc}4ly!12QLUvju=V`Xhr$j$hs>SM+U78jG5W>u$HhG zOABcaVNNO;SsxsK1Watlx(0h?Z+9c5TX4AJ8!O|L6ob$DN3A|&7Ih5oY{ukrS5m)j z!Cf53Ye8!#tWP#*cBAc_-?z0O>Q~?s|0D;j#7j+n#Va$gX_^P_XInQ`0l}Q zK=OZx`|`M|s;>Xe<)WZmz4xApnad!9B7!(83eKe{IL|_kp6SvefF^S+H0-7_TFpb zbhek4)!%tjVAH!{?BrC1V)gt1=Mu{WAGSW;GmKS8jc}xfK~}~OaJIAj1l#KajoUKU zG%MXo==8N*tWSs@z7d3dp?#5)b)a*g?PsI1BvAyGKLY+saymz0sc$B(^l*KMBPV$; zVq#h}1{~txK<5I!XO5_LCVux-|LHj$4{GfgmieKtD2&n4Q=L&B)=S72-hm@=&uN-- zv*-Oh`(p24tl}0};b$q`xlQo?1&PS?`ywl5t;E9D8+$RYq0VuZUwqiqq0SM)8tvpz zXJf&#MqnS^5Hb0zhQO*?4lB#GVa_T~`dnry>peuRX1MbQ3w`(bDCbdnt{?4uhn^$G zI3M)9jD$WcHwe7GVhj*+g(E~M9P2!0`4z_Qw8roxj?tW=&$lf8G6UOFmL=-W|c=w*ar-&RhOeu$V&m{QVEfZ~p_FtmqB0!OdFJ zEM5*8qypmVq%Esdf>dwvy*Cq4IJ$U>JdkKXKALs9bFJWQF%tLXc0ldmLy!*+4-<$_ zxXjkxe%RT{VzV~L`PNKlKTl5r={7`&@2j4T8icXLKiioR)7bqxRAo5wr7e!MHIByL zL-A%B=^V&>cS6Wmv>e(d`L*6Cauw`vr!Z+U(a=ZqL{Lb)-y~c(r&}Ju_!EW zo^!3kfp4jbPU9)E){i<5P^n)%>U=mx0+=YbH-7Yw>P@0p!7~&;Q~T`8W?Jr&*w?DL)R4#k84P9&kiisFQ|LEAImIlmD`u@=0{`GK{WAJP%|Q4tv0|6bvI z7%urTS2|%;31BJbobcxrO*08Nk)pBx(JJS^us~k?xU;K+I1XWeszj{`Vky;5wQvO% z+wD(4at5>fCs1Yx%l8)p3RgQvVd0!ou&mWi7yJX|uXgsbgxOfLhazx-nc~CQ{cD^( zgb0di!`JSxP>!;|sn$TSN9yl+{ji*2eG+;`6f3TFiiHOv^kuQ+Cy_@qB?@83o^<-) z>@2+(zSkVLmfmcEYCQ#lY^f*c^pw-iTlADOxS%Qr&VLYD2PnDa#m~M{laYXxJq1Rn z2U;3TsXWh1bxcLO&F{cbxss?+CZPBOG@}xXm5m_&dT0cZAzE3|sC?WVUq}oYXv( zok{dW2g2@|aA)_o!*|qVESLn!6^FZh0&Or^x zd&b$@(#fRBXFznFS*1h7>Uf?r7JC$sat^y#0<-gb@D2?pAK91uaK}fUbAjc4D{Ho` z(ZkxO`ObK2ldeYPK{L$?oi{L1@Mg|doL;6aOM@~PVYV_i-;BXI$VGg$(c_JcFakUjjEL#b!K4} zqlLbTN?QJentaOH-IEq(G2J`mjN*Rgyjgytpcdq`wPI6*$s2qMUx(1`&#J${sLS{l zctuf8gfI3<@C0SiSW(5nYhon)9Y=rbn2P;~Q(`w?hYrs*J(y9-oEuSz+A=3D1Ip5F zz3cqe1N#=vf9U*+t4DOAWG+j251x<*GT{uE%NSN&0gZb48D~7D+*0A}2dxXRa9TRR zltX0QKGSC;ZtvNjp*q{HhhY&!!+WB|I~AxQmJ65EP`)_3^CVbvmi|H{c%TIbbSrMi zxY{B~_0?QoqRH6V!6_rY73`{6XQ7^^vO{N`Djsj2b%t98>r@2BhkJL9U_XWZD`#(P z009exJ84{R#}<4AewEHDzjBVW3^8cVUX8(TFM?yTp)CDt%s%1HYBfG6o7x!rV!n3L z-VZQ6=uNF0QsFsgGB$w#qlv;u>Va@}xn+clpNC3iC(k)m$0(y==t<5!MzbH!Il~>d zF)Zkx$-A*R1WX$Dze0-|cpkJiR?p5}E#YRL^C)H8#?h>|V6U9{Erv-Zv0N-^e2a`zS>=0bM>sQfMg%=b7)CAV90U!> zNeGjYMDLSXXcdq-g>|lSw(*{d7=lh?*;SBDIFaxTX7p867@!a77%kc<;=Bv)&x@^g zO4!Q{G*844bl@rkJGaZLfudPD=g*oAbNT4KJV`*#Vd>u?WBC665wwvNi#|;* zg&(kNR{9-sc!X6_^js>}J$fEX_})1ZlSqh67~LL=zj=pa~un%WnK>^ecyv93226Nr+-nBguZB!EeMi1%a`bHO>+LAo(?1?{}&wF6X zJ>|Odxc3^sgHb`8uKNSKjk?`Hzdp&*Z#V}CPifn4fY8ug!cFI!*mm*uP4w?tcKs$I z*Rejg@L13CZ-GNR!+yNw{0X={^Eb>8dF=Y%0OqsQ+aRtDY|3qf3fQZ+o#Dbp?fh*g z*5NiWn;;IxzMAoZ*bk@O_XuJSgKP^j3gL>}G%{EiXqx>a&$b#2xE4~!)J}K!B#ByeZ@k9F}~KY z^k7lMmVC$`*yyK4U_GOA2h!}{y`r!Vs%Y}$( zmTztBQivGF-U<;TELA)(8yof^*rC(A#NF_0z}0~GP_Z{Q>thVIDa?QoGTsiYv?LU{ z{ZOaAk>2N17$!zz2S1;2;ncjsCWMK>xF(wypS-V?tiP3o;nRy$cSA+2kZ>?0FkF;` zpY;^H+L!85<7VIW;n)=tF53NmX^4{hE+b5w=L!{z#kFwc%_S$)w5g4KgkN=;|C|1( z(S=`DgqSSU>Y(5C#~=FRs{Z)1u3<1ArogClu7%i+{2Nf6;qVVfpz5Iise`5o*XTW* zRYXDPmB25ca9szKtbhtoh40|d3wXq|JPJ7di+CKEo~(GWc~N2~;bz_E5}O<>O7Iya z;yN`3QzLK8--duCHTt&OJKP+!5L>aFjaW}9iUuCx;-Cd7aFh^!4(i@L^n|V$(S>a` zDKTPK?D=0BBhF!`L&a#W%QvPZQT)}5f2n1(6tB`|#^J5S5Ii1jE%pla#zFv%1d~#m z7-3TYHf3j9iz73A46r#MY)04|5Y3FRIUt%FVRJy(jj&ma!w8$jIJNvZ@wk88rnj{7epVD@sy3HVmwtmy_xNn9KhEZS^P_lbZek+LU%CMcnBr1ZuPTiLmOYvh4oi zUqX;J>wXamQz}cz@x@}Ufb;xS_hV=kJ&A2MsVskh7!HF;=_>xPNF@TeRnhVv;_6dKcu z8VU99lMBJn&J7oR^{-)x93f`GFtl`p7y!diIUcO$eyFzG`DhCQv=SnC+wcyToz=<5J2Yl2uo zbxFwt<4)DGGsU^oQpX2=0Y@Jeas3eHi8I8VuwGGDkdJnAcXuaNG80^a*9Qvs7ucsO z%@WrFP&`ZQ=%9N00)sSZBh5XPRHhtY~~m>o+bBzRvDWmb|Qn<)GTof><;Bw z-~=(OI*ZF9M>a<sF7LKu*6}DOzVb)Kj!1w9koXvwV=BvhL80#FHBNgBjJFlXl2-JX znBzeai+KO#E(Sj17lW5qEaDwu0@9EM@6$YEG5DLZ(w-3^1@;UqS1b{`5t;@q5!+Id zCA^t?mZ0X{Sng7BEP&^i8dM1L@OKfASj^>Bc)q1ob1lQTL;_GNOokKvK#5xBa+(OF zY7gb;++$WQ)OqT7d`wqUoy+Coj(=ibfwo}^bi@Nhu!47Y`4ia6kkM6iX=_)A?I=S) zo~CE&)8a~xW@NM@D~fKEW8bV7I|P+MKb`~_vEWwtA$VWWU$<{lnlW0!GiZ{~S{t4x zYGh)uZ4`%cN^Fa0AVa4_EqkLFWrfGVEt|!mwl;v%9EWn)&5EB3#!(bc53Jm70UOz^ zC2bME6&&$ML@P*Soih>^VMXlbYM9|NWEhdhZ54fN33q0Nql8< zBZKkVMAe(-vow7UE6Cr9HSukjU+reww_zA0vew&0)z+@TH|g8OiH`OFxlejJuyRV- z5#aiD&&-XDA;w{vv2_BRu%%bqvDhE>!5!jmTjvHjSMCsBv~>aK4pfxviy+YMQEQ5=$=@!(+M|U{gl+ zK$rwpGMSMz^gG!&&F&c%y`UFf5im2U^_C-hWPP!<&H}O}Tav5XUYt#MT=h*Vl-U zSCITD=C;kS&^mLM=xghP1SEg;-1B#ds~vp-qL$QyV|I(Z9R2PBFWrq2`U6~Fz5Tnz zM92LAktF4L0#9D7^qf^J+=IqEfT;TV1nm*SZ37yB%75?^Z37#Cc$ep_fb-J4J)C=< z+#^QGgOG-5X)dTx7s4lMekj<`UU8To?^48*WqcBT^>ndxuecKU0rmp$2-*i`JD4Ty z6Qd0LJN^LyX$F+kL3# zBk&__i_k+;lA%afj}P9`vRo{T6+JHo+J+$#e@5Tro)kk0(l$#QHp4nPyOp)YaKzDo z>4hInk*K9q_6Wf1`!4%q5jQ+3;Ih;Aivt}a5qS?raBjcY$uY`^t3xTA8G%W3dqGTd zjK1qTI64YvWiN>RZDSBuk6v&f9J)Rr_I8W~+#NaNSndH)j2V9icml#y?}>Oe96=`! zh-2BjVrc5C4v6-c2k-cBlJVi>J3rK>9E6JDm}0=|+q3-;%tja*exYJ05eQklOteik zQrC4E^E)h#^_&KxWwD2C9zggrni6wFTx@$3;d*S99uem{9s|goM&>i&s2C8l z;12LYgo#EL;Yp3a!^n8yE8={|;yaQr;kmG^1EM6;XUp&Utj|#}-yDGJ(NTO<9BIo1 z2!BTAo*LNM3ghE?ywv`Hc8}AqwgRc6t0R~0$($+HDtKy9I>x@k6hz;>3 z6QXV~CEI$WtM3+_361f^Q9~oUXBxz#+pvo}%Z?jVz@zdISC>Dlej6H51+)M*>NRn? zEgzBf_@ylqtm-u}!nUEoM;HNJZ3O_;i;;vkk zJ{)e_+VJC(Ant7d)z_4_ASWF&nk8>z<~jWo%-GwJf;zdLka&B_-UhyQpAx&-b|68$ z3WO}gqpyo^367n{hxOTiRwi~0Dgu~rVA@@WhM}$+6prt0%o5%fUTyRO}6Wxz~uTuQMlpcxWU|Z~PlzHv5cf7%IdYVu9^>fa)uY zh9tJw4-m0ivQ*QYim3jDyD~Qs;^o&H@x*Z`-2tS-pV0vzvM9EoYZ&~ogk#cn^G&f+ z%t1uEvynpx6Is&Nhw7A8uMG28o4bzfzv?~9A?ihYfpeP8T%7x*|!_y8ZBVA&stEANUuNnmJIy#ipU z_z=c|Q_K+wdl>iYnpR2Pl>5P(a$aNF-04O8Z!pY#$g%)F~7i(PH^SfZXwX8e!bdb4(0KIb%eb?C&EZ zY!0oDjj+kPD-fn0u2(b0SBSk@R|1Sbea{loj=;N_<-ZV7$2Ey7Ga(tvM^zQ#Fxw}_ zcY0-^0z}z9Z2$_j2C25s0J@{)C6%JD<@09xX2PmUF}C#=NL0rW4dzc)A+sf4-jxYv z2bc@~3uchB?B{=p;f}A6q+#QNJ`r0vzBa)1Ok?yXnA)8)!1de$x)0{jpMdF~M;!hP zV#iD{fJJ{QF0g$AAhm?Jn1Fkp!F0X_h=NlL@G^G(Q&F{4;Y{^sVpu^2Sa`*LD{K^> zi9VKU4vqBx&Mlw6@FC?%!~c6D{)c~v|IvuQ@DK4nHDkYiCUyt6OQ4M$$)AIuceBx- zi~B4W;T-!*(cktnKEN zF5eBhayRJLyFixTjQZ3d3V-$Wf!Xp)v6HQ~0X+1XU>u=3i^)t1n{pOf)bEIw-P(f*31%BA;j@Uz4}NNx%g$zM8zh+=_njl6VanRsSMR6q2=+%VJlnAhV@4VjJ2; zcDP3T%3DGHgbeH?s};Kv_@i3!S#Pxtoc+5<2YhpvU>p{C&{q4=zBq!Dg*{UhH7&4{ zzh?{Vg>2&mZ%apU;~31We~5D&ap+wdWG11nVZv2WY)!UOP$kzsR^mw{iIVtHC;Egs z+GGo`(O-o|8prbSr6}Nc<$&$0$RUOAU{}WT|2EcfY%DZP zPSkS#BjSc}82hh_bMbJuSts9s?LLkP2b6y7<_*~H%%%_>Q*}~V%wMQ%9J~K7Sex~x zq&%qNQ1D-34h-wX+kxEpgRr6p-V~=J9X591Xm(3KYG?6Hs9FHsgjqk1wY~)_6QPlG zU91-!#SX_ZUl{n~w^}iGxFwE4<;w5J`GJyabb9NScmiLgw~=V;TlwGI_I~|uaU@3> zQD{BNGH-*Pc{4<9+ih``MJUihEmBi~s3OioI_w=-M-{dGA_5An(ki}Bv>AN$2H_j4 zl;(l`I#(J?yYR^OlG@-=?j`j@M&90%C8jUZ39%$t>V_TT+ONTqLLYOW%O#cIz0xHOH9%T&sD!zCscqG4oU8hw~3Dq%>f#vW0}bVmu#F?ug2m)MhpcG`b%iYKlT* z?C_Ju2X6d3jrh=7X({<2YYY?^(_2Z_&^uSP{##kW4a(w6R@+)h zM9zF<42+fXu=6!#3b+&9V9u_^O1_A6v<77ut3TaZOT7WgZH<#PFJ6wo)QrFUE0(A>eK&+DbXzp>@&V%2<>dAU9!C z6Qv1!%ik01{Y0rBe_hSwc2d95VMuOrT2rM6N5Leg!tW7h-VqRSk2rw%cP%Kc?zjim z{dQnu|G9Qjf9?h8K34pdCm8I2SRBb2YvK1+W0KZh(tQZ6X4RfC+|^RE=bX4A_?(}; zuozp*I;d z*-?sMRpAz_x!}#o$Mw>HTI7|m}ZHedf-3& zGlwJ=A^+|rg<*9yw72wyXDg0d;Ry-ul$<`axx+ISAfJN#3``jMNLJ6*JVo}eAgy=@ zAMTlbq#i;NEAAtyE!rTKijBvUYEDOB%+X9OBp5s#^pUvl?;CwMV@mHUseVMFB$dMO zdl)~HJd;>%Ur9ep11QhHq-M&{v|wON_T@wKJTl|suoYtave5phsJ$O5o`5PjaqpQ6 zw}|QRFPc_ubtn-RPAYLXb9ukh+`b%rEe!9KC^fDGm)9;5A!E?(4 z{K={ZNF;@a4+M`))vgSbu=fyrW-xe5>>%k={rwLUF*9wJRc z{Zk5X@qO&Gjl7nVY@zarCP?T~2ew0<+{5h=CiIx`9gI4g;f@FsOEbfr5XN6!xHAHz z@0jsj5T?FExDhX~E2|wUiAmi6C-ux!0H`iW2FxTRf^|1w?se?JQidUmWFv~2WEPiV zz}&0Y(}3Yom#!DfM;0-?jVPmryn=lU7}d_q7Dw-gLG}Zw2_v9W{I4GABc+17QUVB` zHA;$Z7}J@^&b^8Kj7oHIt3-bT*3}JmKMNfVR6JlrCAmcnFks!?U;_;p9iukeMjH%} z#ULXp#Vu;E0qf}oOEX}^v&<~gSvj(hhZs?P+@gkRKaZ9?1bLVdH_$C^I1|RAH zAh)QI25hh!Y?J{@bAye>z8gvO9%DoeVWIHuE|}H6*IX3FYsFKgIf88hszG1V9BjE3G##V4#hpDn5ot)$6aOPZ=S+uH&H(!= zdkCc^vFu~vnCLX}xAKF9Nb+BpVd=4OweY1dtM@}jvQ#bQVX2qkT~{otoeH0!MKdH{ zOI$M+{H$F8>7lq8cqGk)o`KyrGdV})vy!}-oX?bw!`$HXEa-e`!~MMSD{wU7doL?z zqO>4Pm8XAtCJNsRPcG zoSh>@)j8>Ska=feI8I{Qv!t$+D6U;>U*^h|q#$ClwC9DWmI|4SLWq4(rZ^p+Ew#BT z$rL1^^7SO9iN$_bx~UDa!lEX8HBD_~Ey(!W$`4Or=R?;3*fKg{(Z%%$I)6V)dPI8D zMhB~Wkw57W*!dI_8^AqU638U44XpEADLke@N>U3=0 z>KQBElzRD*tA#bln)s?a z_On>-A}N4JQ)k^7olVh;kxAw6Bv}^YJXZW-iCnLkfKhmdzYXZ{i!u9qgr-OXa8fB> zln1b)%0R5tr()}G^irvt_dKI!eYIIjA?|qJM6=9LZ;_uF!|k2Rq$u0NNJXQLaP%k( zT`u)75>}SL|J9Ojp*F8sE~PcNHOK5Gcxdk8rTX28>c@BB76D8}6Irj(5^|*wi$5LtBvS8-A2BO)tlw_f)y#%cd;5BiuU1uaoH7lInF576sQy@%sA! zIs%3%a6ygB>LAHZTqX-YBCl?2^m-Tv?q`SAORee#^HL2Ky{(pi7|r?l(tmJO$denS zu1QadNn0QCb=G$9Pe3jd8#oW7^on2Ju` zBpJSdK9rL%{D}ilpP5L8l3NH<#Tu4>qb0bIf^!HrueQK7I!wB17Iu+Ib~bslv_-75Jvh%?JaT<|vOC9V^g z`XZcb3lN?)+c4TzXfUjOqC4*hM!xX>-UWWe%XUf|I}@tRZ)m_1Dbqi~g*zIFiVxNQ={Ikh1`@KuSf+;QyR=7&K=V8AkrI(& z)*gucB(`pk)Um-SeVwya9ss!OMq;XXPkyI1m&DNH)MJE_O9@p~~D zn#3yiN?Y-mwNH8)`Cs3Mp`65mp8w}!);up|Vc+oe=aF&}3);^=NWsIKdYh;N*I}1I zL=Jpg@?vZEW5Qd98dkj)Cd#t?QZhGQc(VMyP3S)C@=ds%Gxi0TwN}DLis=#t=?hXn z?ybn2Z4hdxwW#C^dL<7?yY7vP8Nw=-bWj@XR!9&bfh-vP@FQ{w$Im2*BvyWqXL;qY z6vccFN%ni^8^wklLJvO99zTQ$WehuWh<{b9KhQT(%>E*zVDjO6=8$<<8p=C^$i}@h zpa+ct0?y$xARm?Zhdz#C<6Z=gU?qJK+Fur{-W3M<;U~G^h&BRdTZ(*wg_gi<3$CL( zTKeBVQr?rpHCL&G(7CAz7DK!J(3H zRhKlfK~0}R$~uW=)Rah&YHGrtWUXJ5ehMO=AOs>rle{KsHL0{OOE`{h@5@r@k)%Cz z94x`8jz5b(*$O=Ld-&tY_?*s_M~7M2i4)R1@2BhP+4-bi&oL*Z2laa5ME6P1j2l{Q zYmuDL$kEDDPJxy<76ZHyVc=!yDd{)VW7g}uI#0e14%JsX`MUJA1sq|^8`8&~laScL zutgX?H{S$P?W-lcg()Q_4PgH0z)ikzWbif^TwVRwz9X&mURPIFkVKT{SOl@SlHe~A zMLzhh)WxiJ*}Dy@jq~;INku5U^gW1{zU=4sqySICr6()*gHb8)eekTl+Pe3p1A-sH z3AaRlc;!A>^o~sb5EfFRc^oeJ6jRBP52blL?8h?014OOcX^ehq_!&&sVzkmTQgZ~g z@{fQdJkNeC{pDp@-@G6$KbYlgh108>UrL)TU=*#-Nl^uPYpw84FU`c42*Vo-m#k4* z-g#&YVI*4VAN4ljp60wHVhBu99%Ogw8!3|Tb>SOnw(S{I(`1S8Gxn{d5{?#qD@7)d zM5Klhi=l?O>)s^b^YAH2F`_|=$Bj|nO0D<_@&=zVTa~0P|DSTpH*zy&FW(}KTwVB4 z5Gr?_GPBD(5#J=GLSEzGwR4=|CZcQFjEsd}hB5ge)PgYA^Wd;x-s6mt9)fbD> zuI~(6Haur6^8|H&Cs|`QBR$pXj;i0c#mL7T!MoX^@1$OiXALk(HoY;z_tIF$)_Z{i z*qZO9WXHC9$6ckk?RUj>{t@ax&8v;D`~F8MmhJulit7$m{-Y#1b{dK6`yM;(Iy#E( zmB(t_)y_Y9?%p5my6dCX7bM#B&Oh37?~nH0^${8$z&iaTbqc)`fBWuAoBxwE^sZ#j z-z!-URsvtMEa#%+W804erX;~wxhS==y>K_^z}=vOcY_Yy4LW=`=*7E1C3k|JP5d;x|nH2-lr)I|0~S@L)sI_-vw^=$^X{c7{VpZm1B^Kp?CgI?qexah z@rTqC+0_1uIl$&eLa6?yGs zSCG5dCQ;ZMKU2*0WoO#SBe|B}p68Fk@605ty&Ov^cC?rM-HMSCUI(&>Moqn&loZqK zSY|LYk}gnmx!A^j+zxVh1MPyO`5UZH2RVwXe;wopfw#&I@?Qehtt=URbU&HJ+oH*&aFWhXGM}_e(vApbaf9KtE}pPT@#Z=~472r= zTUlWA>e3UPvWyMyDTh$-v7T~I1Ybpv+mhfRpb(S)nx4RpVMvVaB}a64i|8B~QrD4f zjHXO7et6}Dl9|oaj7{AK*)QAMygR&A;bvu*#mmdvWKQ6ty?{mdzJ_Iwl^w$=@3K<3 z+|B6%8jRnIjYS!~<&81#+)*ge3khS=3*U8*V?ll7e#WFxSgB%e;^T6$gMDOu^FTD? zGs%jUV6Qiidj;Y$B{|;UzL{6y(xDnLsPPkWDb=VBjWr}Im(l)EV#XzZb3<23%h&9N>nIgBb*4t4wG|`>d-KmOgWv0 z%g^8e%M2_F!{t1@&l&;VnZz6;c}%hXfa&35E{&S7{LA6c6msFF!!=s=LCV&n<)KvD z6{F?82);jB9?PfbL>x4`AcoQxKSCxJ!N!7%q>aHqiea(;veA-L$nh(OuFQ z&X{Y)$Z3>k&#`hG^2A5W-4j;Lg^2JO$46k$IKB8s#_{4`9S0T}!~Dj};dnTnrqagC zov53i8ZW-PrX^~!#KO2GZmPRVe6*KA>3Ak{JHW+-ZpeJQzL0u z!}&H#hNDSVGgW>bF5qbx7NpI=By$?FC}3x%$su@LnI^Z=_rw*H7s6b`uxB=Tx@n~EqvjQhaMV6)x*U!4Tc(4H=zHiAdciYf-YE}UsWu%Qq6SkKV*RjgSuz8I z2N^V*DYr!iDKq7*6#Ql;&(AhXUfJ+79_wD(>DlrEqbG9{{l$Ib=E!`ZctVzJAvv6x zEx(Bex9*!7BV_U;5aWi)KI0J(%Q9{0BXUy<$AYCGdnDYu{D?h!H47tgIpQwtVJ({n zH3Qi_Di6g2k}HH&$yg`2yf_3}%{h2+E~{wiQ}~#?jViq3G0v??nbl4|CLf^>_zBP+<}~WXytK?3)@If^J_V--IZ;vRDql z20!m5;Kf1(Efs~(@Z`jT!e=j)6~kQqae~#QmzH^RFJP6(ky1_$z^5b9^IXQCRT^c_)Q4UI|)HnED%aL`D z*7zwG8)Nt%^c{kp3pOU*`xz`sgV;UweW#8jsOK5pd z$otHM3s%dMVf29s8*J&jag={0<`DzF`k+)2YAv7Lt|FF0`tY3d^wtj8?n%W=9pUKbG1g; zAgj#x^G3we@lGaw4(hgn^I85r+3kJp204&tV`sU0X@;^v_Tu5@6vpY(0*sht?8*kv z|1#`Wk|*(4T=Mcw4BB2GNAR#W%M6BUkRe3sUn{IOGDzo)%UHrj5XUl>xzXVNK3JA+ zr1C{eFt<%I?ORL1x&Wz&BR0uxJ9C}{RFO(#PK60kWYs5~gy0;NfYHBtwF%1$w0KfY zj~LuiAxAan2h$c(&&QE!c%e)t{_TbG(;V*tOId{ZU)5XKxL$;QNIJ%}=#Z3pdLbjcLY}&5++TnxIZ7EazEy6$Kh?l?&;)W2@}1e^)?mr7E_f z&v0esatqF|ycHelW0Avg}*q9xcX(Xf)$_|CN%^Rp5k*;+U_)~Xe4hj4+2 z)gIU>?-hivSZ$F!pDq$F0!if*%j4jt?0m7@3m(csp98=Cra3$Hocx;bt)3!pmz->= z0t|-+Sn@97{vTpw6#0c%E!EB0uilOP;i3$aKB#*M*lzs2K1efm%clu7aeL%9u+UMv zM-HH1v%T^=7<|?Hu~KnuuY3_KLqlKy$8FpJ`3XeUuJFT^_ntWI@Z|w{Gsi17eCL2w zxrRYWD(j9zH+cu;o0Q-BLvl~P5J<{QIDS~D+Yc{N*AB_UkQMIRfCHVw@(G?xI7=9c zWrO4wjv@(#}FIKEi=FszanX<)Yo zuC8g2zk1z)D{z8gqE*W~CVN>7McL6W1{iuxc5$~g^{r*!yarY4$A4%XzoX#Pak*!G zQ=(W}BacQ}+Y|B|9+nHu*}q?xBb`5C%KXgGcn{+Tk*JjNo~h60zD0SuDs~h_#dfvR4x`<6oPz4sXa0SboD&d6^u^ z(w4#P@rgGe(Q4|}>Ah<)t>WwSEb~n{+>c}?{p*JxO~}n@)EKt?OVHn{_!wJ~rlXoBH3qWu;viM?c^izxplA+=e@7ljWb?mY zXEah`xiPVBw?eIc{%GTS@=U&HPq-ok)z?5%-j^di93<w1jzzhYJzROG0GEU3m ztil-WWQF_*JRNKKXJxI4A0?oFBpFEHhM-6Ihg#+jaz~+kGQ@}qxY-q^{kf0v4W%{H z^u{}d!F_t*h9T}(*Y0b9qVKHQ=qnRa@WQufJ2%!ozfHCa65VeX6@>Q$l_vY-K zU*u-Sh(mfNw76f|QccDX5SWWOv+@MTEnG_;&6ayL)g ze}EZNEbeC9F~PM4pwdE&)~c;u*1|ua=x4EY&qMPsquZKX3oSm(rzPC7@;i7Ao^9k) zkd|d{Y8-0S|jcy}WQsQFCR9hj7b4TQ#`)^G;=y@VBmMR?I_%U81s4u-tCWj!H^*3pU#D zEiG`VC@Z}z7CVdc6(d>AFECOR$*43eCbGi4I8L@$?QH$8&M;P_fKAiQCvdNbSa_D} z1JPku6{}|>7%<>rHw2plV{c%WLpN1fU;+Bpp9;Y}*s3zh0>mKzk>`s5j3N_G+Jjkg zA4OuPeU)*RCU*AgNqb96Q#W}L*=Bz@%Zm3`;)P}`(_fj3 zYbfvtXD42_2Qn!@$-sUs04B1G&+K-=!HQ>~@?~ZAG19;}p)8qF%@q`Ec){0egOzxPiiVnf$tMA z5Jc~ZmS8)3?R|S#VHe<}4DI#KH;5{PrH3j*EiNA4lH41HDs$k?5N&!HB8;~+=Usa{ zmh-XQONgLoOustdu+PkJC0b}ffFAJgj!R=B^~khNN+9{>qlEk&4)y3g?|r+(?C;p^ zmKZzx<~@4{RuQ4}_iTwqH)D>o5a#hMlxWXZ2;+Y1N`$jofX-rZ9)lVasl+m$iEw*r ze;w23vKGL6>$<2_n3}yEsT5e+*je{!aO;~DrIZQrEFl``NMQNV%4!^QK&T^2i&474 zli9i$WvkGR#k5o=dba0P$UbjZ+3uFgmqG`&w3U)5bY!Kil#W6tdW18dSae!vy(`l? zf?_gamFc+AAU6TsR~4&F7P``VA}hHHN?y_$RmWD!&Hz-j2ey3TlvU7*^W&5Pp*t(@ z0EExj3nP&(EB zpV(cvY=FOKe1IFYP9!SBgaORaPI+AzNOhcqDKpvx17v%}#TK-OtLZ^?fOhb?MW_Oi z!A3Rq*RQ2;pSDFtiTAY4F@%+))_{fBgxa zl&)~o&mSY{f!pwi$VTb!6`hncVKi0aIJ?+Ni9q?W!dRoE&RBlld=_n)+(lUeC;S*J zU&9GMA9SB~Rd&PwKBup$bLh3aZpviKL_6CN6RR+vB;^EL@$-~bNkDie3+k>679M1? zx+^1TH&%Ca%en4KZvnpcdnn=Xwa-E0dVmEOb!;+d=plL^ z>G?2-4p)1ofVNL3qYq|K9@v+af^MG4@>7sJBL&H4(fc&G+P|KnwDFw7zsmg)qa(Q| znwiCB^;BlS%l_G(Nc0Fx>4kdCWm9?qm-ApUR(ip=J{q_hPV@KoMos3k%HC+i0v6gw zVZuUUY%qZIQIgrgP9PFjUo>Z3-;eL7JdMfY&;5|;a+cX2x#h5R z_bX#rQGbwF&i&}+TmsAxRS8Q{GGl_M1kCZ8K8hvq)l7}#RG(C!eV^HEDRy;;o3HS5o$0D-qGR$oQ#{&MM zj=*hW(XJAT8~}&&^=a%L4qR(7h#EQ;|MBnAJRI%dcwU!|6Hq|SIAtCdxw&-AmG#Rn=EA# zn9Eyw?AsJONqC2~n2fC6Wv@)eC-1T9$-wOUdKYI+0hjpzCT67fSS=tAX z`e`X0`nel))_iUw$rP|KA%uz;IzO%DSile1=Vh+gM^1YpX1XYk#_=4Dyvy}+T4|dkf&(Vby zWh-8mA9s(dON~gt{A6bj_&Wx9UIhN_EMX1Q zv=V=Zi$%{>vMfJC6`ZU1H=&N9x$Gq#4zv8ip9;G+S1A!L>)DpggGzbD&VnCR`odXy zz4Tb}sFG&+4V8XWc?nZfU2p_DB3SY~pttNXWuB$hkS(sGF)$>|SEgHjhvGF~`O5MK zN>amcN#Jy>(w8hyT;Q~o3zX%St5BAfLZewyZMSoK-Ey{cAvC%_q3N8p;Nq`^)OYyL z^6z?xWcd&B3-$=!R0TUl)v7SeVWvfRLXqvVNNH=iZfCO>DHEE}bW1UicEiq=g*z5` z{ssKt7Pm?mBJB|lyX7X}i^h6v0 zVyEdCBQdqD(lb7=)l<6N%dV+jCc zYU40ggWYma+Iiw2{SmA`pq=NUr4_jmm@Te^0uGPlE0h6ziGXIik@%T*mQX6WBUE>! zfG02PDB||eK5Y9+r3X~`^DBY-2eOi)_f7 zhO^|SAQ_jl{ZA>=3bwhtG~cJeI*|~br~}xlwMu_}$e(8F=C(mNd0q>xB9#gKgF@J< zbr}AsYUV-Cm?fiN)7pCRP zePFgFUty6aEf4wzceH^kyz-P-&)+deZiZ8USU4@oQ>y$a>_SH2_|bIQG%>(*_^Zvo zJInKf>7XVbh)QL*@)ece+CnWMJKYHU3h<-eAqxgty#aU!mdM%nk_}34GC{tvLHU55 z>kE`FhOrJV>LCrbDS?F>A);s(1D1%_0~=Yt`C*^aMht~G*zXm%L}&RMq2+PsD=Is^ zQSp_@2SjI-Ml~Qz(OIeXbj>X+hQC7*w{Jpi;WR?V;pe=am_HGDa91y_keVzqH1BlZ z2}?L9)?hXshhR5hB%?Vy&|-=)y&6*4)KatTI26362Sx^k#8oynMwow6f9iU$-ceAlz_kOspp84CAXZU@2p46=z`$PblJ2)!W?ND|i z)Osi8M+SxE>_kOzW57;jt4xGU{Ys@0AydRqoqsWa;i+xb%K;(xpR9Ed&;`u1>WI=g~ zN>1f9zaKStALweHAExn^#KM&l*v4@_AZkVe1?MDfy z*d4BcCbOCsKtbr=1IplgxCY8R0Aolj=OLRius{z#K)sO*AJYFPe<)dpl+Aq5l0lRh zAt@eug@X?BW_CV|QNnTY3`(j!tdOS!?my+)i z;1SMt_zC5-BbW=o)zcAPm4p>=N?-K{#B<3j;HTy(=2w(%yw`B7YA_CWCOfc1AAb~@ zKpg9H6k;3${wSxj%A?8*8cE$X&`g=} zDz8iNt3>$P`By=0h$`)>24Iy8`cU9;WjbdZ-GJ9H%ws*te@*dm!b;tMHUUo_Vs1RG!JVpw9SH$~9vwBrKr?=s4&sK7V4@ z&9K*%1!h%>4natkrK15guX7Zf?2b_tScZuKeCj-?(guX|GKF#t?V-Z7T&4_cLX7XD z50odUL6uYBBx?F;SO?G_T!4}Jwc@l=gUIqTpk@BRk&BO%>71UZ2}DozY&%ItnI#`7 zGkHAS!_**NOZZrsPd7L~IP)RgsR3PL*cxLA?~6R&^ox|QmU|Hcg(K5seD$63lrN8hIlm2Nqgh^azJ2vjJ&fLH9i()swf(1BjQHZa0^D;~qv|Ov=GRsRMP0O~)b7Bx=QM*pr1w(!~^HBU~mQ|C@6uPPlm zj(V`nn;;nbpPXle{;7m>aCerlClvclDsgve#-B8c!(#T)e=Adk`mWc|^&u9%@?^KIPeWVc8u*LfDB5;a*zF->}|cU=+{vRX5F4 zA)a43sct1wu62T$%(SYwxJ1jfs>^Bg3f^h}cbP!C0ToA-Vjfv;fp;!nfg-9>eQ%_C ztKRy^Ow$ZMj5CLXqJaMB%Q`Hx?Xa1hnRHZv&xWI_L>aw%l zP1XL$_exWB8sNiy_~WFH3dyXeijT#nqs=yTI@P_I+LiZ**?pLz8U2yojDO;2&IvuH zxw=&_bjR9g?|@AiO#%xY>U4vuOC8|yISyVGP&KwvIMgi9>(CM~_4+YFWc{6p%VLi@ z)x{_lXw>ie@n(*;P0*khu%$X$R7v3HiYf%WRxGNxG!f%LLc^gAOKK}#(R;F?9!RlN zRul0kmGOC7t&&2VPt&#%tUZiG$GKFML~(+u>XhckQhI>jC*+>8S? z`CC-Oo*3=r4b&KjMrKrhj8@~@z+nPa`UDteRl~~>xY)PwX77iQ8 z$6UJB2Jpf|1#))~KxE1Aj%AbzNXMc8kS>soG`x8stoTvr&9Q-Mf37mp>E=*Yy9u{q zZwv(PK{SDCS3b&xy4rC!Y_6PEx~dJvCj=#GMM0_$4Uj{@=tm-6xQLtp{#+HztDw^i zd2qzMWOs8omIzUk9f@EIMC3$j`j9Vv0oce3QLVhAI4@)6gFK-V1IE2o5fdXFle)kh zI&oNvQa={jY1>`uE+UbXFrpZi8K!=XIwghcA-Yxw{@D<^hl*Gnu8JHd9at?x`zyjh z8TG^#u97O;I)YPFW`sHeq5Q<)sDhdW&=yWdcont)=~Khov)rF8`fexq?rWja{G+sm z+6#}&DCi9KNR=j>QzBLUW~Z)9SYWtxK$k~y+CCYHAr{AKn?Nnf>Ow1|kt#8kKO@cT z5?NXluORZIQT={YU3R6*n}dse6A43fBS`R^ZtyB`;x#xth>upYFye}%RmK^#DW9ne z>coYX9;3c#<(PRR^^b z0Dc`g;!``S{TwDMBnr?cDtkJr%gwJc?#C|Up=@0zwar~!kNdzn@!@9c ztPbGqwzA6hjiXpqawDtvBov~PW`1XNlELm;3n8RVIl8Drz0KPAF-I`ml;w0$b>vwK z%3s7DzaOBL6?IWN@r=D$N&*}J*K|QYK;7=D_TZ`@P2WjM=;eTI5UCkbAL;}IUpLO0 z%M-v-*LG9+oo!=)-Lh^f-PaYAq;k#R*Qk&%7XLREq3qpJ0vKm^mF6SR1ZhrnmlC*D zXwOdeQ2ET5#$8~t8c5ADvi4%p$wXq>lw=iKI6JU)J=KmRV&CbhHuFTngc(GQ+O2-- zUlvjcZakp+Q_lKzS@kkj;RHtPQReM=D40o3UVua-9g=*QCR^^rOibA3!p7=TwdXl2DKEuP3sTD*mzz+Qu8@lO3^v{g>Q!WV&k4-)v~hGHM$aiw@PDs zM12`ZDt|=n_1{KJySZxrMnL+~g`5&h8)`R}mquGZ--NZPY8p1%R2&ZkmsrFlNa!L% z@~LnX8-$}3i#UhLUc@`%$wgeBIJ8I|Ok;hK3JYf(;~hMG36xH7nI$|S9-?+|iRwio zmS10qtz$Xr7wq(MbuvH+baQ2r1`y`YSz3Os+LXwC`wHZU4>CcRUMtlyOwit6srIUy zB}S}P`AmGZs^3cKLgzq5DZre`zD8}wIY2mvmF5M76vSs@1Lhjkt2=|j2QM_A(#f7R zTnSm4=#O>oT9^@P)~Gi*M<4}U=LmG;Ba@y|kDxLz8{lNkQ|d9ibHifn%x0JaJdJEQ z#bDJz1Tj>t<&vDIC}JuWu{Nm?y3eYTma|SRLF;fKA6TT&gO#TEMzNecl6<;wE6P)I zXf_v?uTFI&8+uTk5N2EQIb(vQVl2MEJ%hMNvp06Yr)^Nl9#h#C+FjPuf)6%q=yUZA z>H;K;DNrY2-v$R2-3qa5M+zVa5CIjMd!}Q_n^dd=HP>F)q<%*cRCx`R@2`t(+&VkL zUo3o99caX{jAwa?k3Xxf2FSKmJ-|^Az{+32QN@a_YBIo%Z4lw^^RgCFUH^)pd7 z`-!%#NR9O{3@E}Quu@m;Lg&D^v>SaBr@gvcT}}KTvd=DDS z0ksD2nFm3sNPkdm>_J`k#*1nkq4M_^)vt(L&%CUDfk(_MAiJ_7Tw>#WXM^Ed-@_!} z3XfW>+Rayh{Cg+TMjun>d)zU}E*-9h7oOmhM6ayoFRWgr(V>J+C%``-ApRd`Uji3Z z)yCZ&24s}c83vfU!VI8*E4Z5*glkGcnoEj;mRl~6EoN#^W?EVx$uYOED6O=h?OJI; zTG^sO*S#? z?uE23n_uUsndR*SM2>+)m?LHW!M*@-l{HFT?rS zskMOnds%fYr&Nb(-I-hia~aED4}*;7&yKh}vQa~r#|U_nmDIw3k-iQ_YR40hTnctV za;dm4A;@0>_Lu0>ko2}$nP1a@Zd8BsyXxyXUiHb zh}GwLA|765ixeFHc^6|TfCrt{g^fMkA)@>}LvkX#l=>bvBxc|}cP8IXrY48#12%XM zD4MIzfkNLmL_V6G-tW8fINXOY@8ED4J4fJ1+1~fvJ1?;%Z#m@thvx+b_yKOmC5PPy zFp1ZGKzoEV^j*vr_I`lX2i2{^3}Pbc_@ZRhfdjuN8hzRToU2F`KyyMe%D^`JS74lbQFl8C%XYpqqdw+t+SgxKgkE+Pzp1#^`-cV5#`lj^%u7 z-M8SCy6airL5}1Ym6(rV_|fholYs$bhyKGk_l(ED#(7Sn=U_Kax<~T8icEe_YqA4f z#=IIl?<-EaC;2b~e|MbXWF_~My9=iwomu`V@L0)@K|rlKdHm$AaP!m%&Dcc=Tx z3LUKQUtEmKB)YRT-*a$7%dq^O#Br~pSXis8ir}R0DGvc()5l_30AVx$`CkVdm-++m zZpIJBo~NyG^AGO+e%lLiz{bvm?FGof$lmw^_)&oLY4`7#-gOQMVW!TwPxG1U$O=EU zK>3^@lfEbGtlPM`T4cgtN*;o1#cFQEDUg!0?qR$@0^*@1!bLS*q*0td%TY}rGA?)m zdvnf@*sunpzC9Jnle~ByrXN_OAM-SEj6%r0<3$t|vWuw~2^b8pPI(xPgBlCCy@@ug zzORW?B%o$L5jA5k|KxrUvKzbdXZJG(P_XS2a6Bp9qlNSN7g^&SkZbEYv<3gT80>ob zFD)2m{x9wV+Pz=>;=WE8%Y;I)hd$?5;4!d1&A)*y^ad)3X=Eg<5n!3huI?=55BGH5 zGMMMSDS(}b3aPsv_*AFe^h9qD(v zaVqaT@DsjmxWE-7oUwHL<30(0P{^CghA0uJbVI&qkT)x9ObmxwXv22V#n}tqK~{Lo z>e^5zCS!)s5%|9g;(UR_`b7iK*IC3d24C2j)s}dqQglm{!6~R#G`Bo9z5L{8H zc!w~KQX$ZXiDY|5_W`2Mckr++Rxv_lw0Kh2}4FueNU?sEP3&kdO zU}AUzhl?d_WMeLq7+wVVPXZ5Cxy5k5Hj1)x!x5{m?Fn z0+EBPGKaJb8Ni3_Ba2tF>>m=~q@jo;FsxC;y8%dcRop>ShRbZ=A>6G?6g;58%;R`Q zZEW~UaU#F@yrTzjo~0MkgO>U=uegos{0rEr%7J0z(b2N>haFaZZ#=dRzI~e@J_ZW3 zCJ`&)19x=JqyLg99;ENQi*>g`NA_hW%n^;1_<8@>q3{$Fgo(t`fduRa(HxWn$VbVX zWb^`nos60%u|vtCanE@QN@wC{p!2j8z&mt{DdO8GlHXZ0G`wwD#=SVqTGCl0(p{Y+ zc44Vq#AH78=Bl=IVL=zMAF@5(h0mb3t4Ivd=&l%87CY0GrjHeN!yd?DZ+7E3&vz5Y zL{pGAawnq0{iLKMCz&{w+g$`7rLXBO>NJRqRNsP|Tx5&>i|aogN)>T%51dk(NXjT~ z4{-G4c%fTQ@n`hI+e^F=k9EC7V!vRW0&(swK7_wt>ePsYeYJZVr{n7o!2T{q<{^Gp?+}`sq7k`J> z?c{;D`uJ-)c+!rQo8Z+ShcKOJ4emY80P8?Rm59)U zbg?fzA4nG`*z(XqENTg7wfBKF%n;LglpWexUgouf&P>e^Rk9N<%@F&tNhR)N+^z=I zX+wr+ctl09>ZZoeslJTP=6kUfXAhnbNY4yuq;eYhKm&Ziz6j%$H0=_#b287_9sGX~gWCi6~$8%9%6Xq0e0s~#iX&%ti2cnxxt zjup#jX8Mt{%k`n*Y*?0o=nbJh|5cBRZOJ0efK_LKu3>j)`RHvt^EM(LTk3qM3Dm2^ zl{8CNi9Onry3+(Ip!Bb<5=RhfZ~vwgZ^WaZ*bLli+m7XA!m+dF^Y&nUEAd(a)6= zZ8H`K*H^(tc@1bV*w$;rSAjvbaexB63KK!9r;ZmRjK86M_%;;6#KT}3N=P248ZU~R zcfkck_r)Vz+<4#$9#oQ~oFx2cTM=xSu+8f<$Z!`V6EQKN89IgOKy7TFL6UBRJ_0CQgWj1V*xE6hcqZ9c%0arj6P&$F7jt1N2_zVj9ipkfkc4!x{i|@VG0KkEgv|q_Y@KQF}r07GTgwrO$7m%MURPPC4jA>pCD+b?(ae( ztdI+E#gvhYTSXqcQ9ZxIU1?A*W($}+m(vHY5C@>4r0(p=TtFbH_j$2;7`fQmX`meH zG?7ffW&*7gTm=E8)5Hi)G%q<(L=m}?X4!PHz&8&cqMF(~YzJV*d~qaSnRpn=0RmNF z9$WSo4~HgT zWU&Ra#n-8D_c>zB#h>+S=YR|Fp`*0U;QMOka^B0BgkLFi%r0;`YkzflSOfhvb{^-^ zAD$=nyM#ujaUO>NPSbbYD30P|>C5bkz(Qd6{-hyR*?jDOPU9cMBW;1$gOl25=1p)S zBEgNKW5>n?BJC5SmWARdF2nQ%j1n%K>4Vko%(jSgXsv1;Q(nMvuCqWK_%8+X3&1kZ z;gTP38|P2mVCIX4;NWZCJK-RPf5D0Dh4}DR2DrjPaWf4g$llM@TFiMhbo=Qyfz1J> zy@~Vi>u%zh&%0RM&poE8^9lG-0aM@w<-f349D+iFZw7A!NVx@ohMOfqlTP5OLh@KKrA(o|uFXS4EniZTGJGz4Nz|aapUcVC@cNUv_ zC$FUR&X$G`+{uNDj3UlMaw>|dc2BNVluA+ z!L=N?BB)^%@IPiGZMA6F*b&PmCJCdO1Dl5f%H#6CqgB0OwRnVb@hT49Czf!mlIQ8f zitdLrgSj`&BCP+fSTH(R&y^pLl-B}GkpmYm68BJin>UH2K7d6+l<)_{UY12Tk}#=F zEUU{GRpxn645!#MogWkz@?M6qG%pS&WWN|2?j)pf3zmdHknlX>jqOQ@V}Cy=&iC=T zem_{&P#p~tR3du31%Ofoxk*?v9l=l$r#uu%A_HN7pnzB-Tz1*rcuizEo5JF7(z08r zcs2UCyp;3zm>$5^dI$vo^7Y~lb1wSZVt~5#(5q}O8l#^IBE`f>Vb~=nOe> zmv7*-1U75j&%FW6h|gO$dxLmA{(f--Nk9nL1L>nX8&EE0;<3D(!`tq1&PZchxY*z2 zeE)AhOQfIqpe2L}n{cVgdQgba@3@EqF6^=lh8a*pohjl2<9o_qOW zy@i(3PLig#nxvM`#Se33!^02rZ38==$j&^B-a?LqA5oA7fM(V_pa7J$iBnN>9fxaT zvNh;V&X)RXH;DuNy=qw-zP~-2MZ@Q-zrj1#9D(3Jb+b<}31Me?bWLDuHxtb3p<94U zs8`$2-jc0+@;SDL)ym4YY!&m6>HJoxCiyBxloc{?1LbW(4a8V*4an};C@m2a3h;tn zs5IQ#Dtwu@0?2~mG0{JQ5BeLonmq1;IdzVLxI)tqf286xu^-VM{I5c+qUyF2Vr2Q- zIZl0YJGuao_fd|ZGAhBztbSDNk9@U{0=V(zG4UUC6<;>fc#b{Jkq;_?S@sE_VgTI} zTs@PqL;T$s3u3v$iJ+(}@N(Wxj(=|0$@e$MxRpD_)tp>EDbBdWOk)@PhZD7q7!eT@ za}gkHKIN0zL?nAz&Qk!whNlQ1_47|b`2^!v{Vs51pcFWyRKA-tuQ|K1ffxH3lLfmu z43_Q|cXXufLwCo~i2;(m`rvo7nUfSA_}7tT+!5+3Ybd7e1xM5|tmYYx(#bGgJ`X@2YJo6l%Y~VM@qR)#p=&ADsuDHNqCmhsT z1Px7I4sM{og*Cq*E{-H@2Atzp$AU24`l9$D_X+ODvR(oUaw|*QgEjEzd_xY_NvX-U z>V`s5$KJb*G7#f9cMqTZ(mi}?9^ZqUOX!=cEcI3Z$;&_hw;S&T`3SK~@6u9C15G?3 z$4vYPyol=AnD-~aUFOIGSGUX+;SWs;Z1Sq8nCYQ9{aX)pYxD_O=n;?@3;GWR&yim8?uR`rgU?N40gWQV_Z z#W2siVmQCC&E!pKltdZLZeq&?_G@+UEA9aa{CZ#T>;1v6YlC0cu`}Gzajo8(n_7E%nM!duSx`BnB2zV{0uMpr)h{^sej2${5 zj`Y87WZJjDS>5&KZ^g0@fAYinx>MpEz!|#jG@e{X5`y_AXd>QbK7If=V?*ko!w7Q+ zXNTA(@h&SO5p4fO>_W)(@GqiqZTGL@JRUp>XI`&@1|zkMj-vevwjhI@{}nq3W{2NI zg<={H`3>3t{t9aI8&|C9+kO*owb(YJL9RPt_46U|rJfVj=q*Si(nD+wK|8_6R+fKG z92dF`pPWY4_s@}X(>RpF?H}1`koJnk0d1!LDIPIIld?t~coh|vlcdx`MtuzH@q+1j4%xug0$HiLg&fw zj~0OjTY*v1cBc?lpd;{o+5S!#j_Eo(LKkY0xRJ>W5z>u@2R<$6 zNap+s#?t8hRa}vky1r9zm*j>j7oAgG9$q8bO3KttU)sqD1@∈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 Y;qT#3(A7PsaN&Zq5hF%CoSL5T{}GOGqW}N^ From a0591e9c2856c8d5c133ec9ab6b1a7bb8fe6f061 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 12:37:26 +0100 Subject: [PATCH 21/25] Clean-up post-bootstrap --- .gitignore | 3 --- Makefile.common | 44 +++++--------------------------- bytecomp/bytelink.ml | 3 --- configure | 16 ------------ configure.ac | 18 ------------- stdlib/Makefile | 14 ++++------ testsuite/tools/testLinkModes.ml | 10 +++++--- utils/config.common.ml.in | 4 ++- utils/config.fixed.ml | 1 - utils/config.generated.ml.in | 2 -- 10 files changed, 21 insertions(+), 94 deletions(-) diff --git a/.gitignore b/.gitignore index 05424e826bb2..23b481526669 100644 --- a/.gitignore +++ b/.gitignore @@ -252,13 +252,10 @@ META /runtime/build_config.h /runtime/sak -/stdlib/runtime.info /stdlib/runtime-launch-info /stdlib/labelled-* /stdlib/caml /stdlib/sys.ml -/stdlib/target_runtime.info -/stdlib/target_runtime-launch-info /testsuite/**/*.result /testsuite/**/*.opt_result diff --git a/Makefile.common b/Makefile.common index b476ab85caf6..0b97f3478cd3 100644 --- a/Makefile.common +++ b/Makefile.common @@ -191,6 +191,10 @@ ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" $(if $(SRCDIR_REAL_ENCODED),:.=$(SRCDIR_REAL_ENCODED)) endif # ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" +OC_COMMON_LINKFLAGS += \ + -set-runtime-default \ + $(call QUOTE_SINGLE,standard_library_default=$(TARGET_LIBDIR)) + # The rule to compile C files # This rule is similar to GNU make's implicit rule, except that it is more @@ -339,44 +343,8 @@ endef # _OCAML_PROGRAM_BASE # $(ROOTDIR)/ocamlc needs -launch-method to be given explicitly as its default # values are those for the target (cf. --with-target-sh and TARGET_BINDIR). BYTECODE_LAUNCHER_FLAGS = \ - -launch-method $(call QUOTE_SINGLE,$(LAUNCH_METHOD) $(BINDIR)) - -# Historically, the native Windows ports are assumed to be finding ocamlrun -# using a PATH search. Since boot/ocamlc has no notion of the target, Windows -# requires -runtime-search to be passed explicitly. -ifeq "$(UNIX_OR_WIN32)" "win32" -BYTECODE_LAUNCHER_FLAGS += -runtime-search enable -endif - -# $(BOOTSTRAPPED) will be non-empty after the compiler has been bootstrapped, as -# the -launch-method string will appear in it. -BOOTSTRAPPED := \ - $(shell grep -Fq 'launch-method' $(ROOTDIR)/boot/ocamlc && echo Bootstrapped) - -# Prior to bootstrapping, boot/ocamlc gets the correct host values from -# boot/runtime-launch-info and doesn't yet recognise -launch-method. After -# bootstrapping, both compilers must be passed -launch-method. -ifeq "$(BOOTSTRAPPED)" "" -ROOT_LINK_FLAGS := $(BYTECODE_LAUNCHER_FLAGS) -BYTECODE_LAUNCHER_FLAGS := -endif - -ifeq "$(SUFFIXING)-$(UNIX_OR_WIN32)" "true-win32" -# Historically, the native Windows ports are assumed to be finding ocamlrun -# using a PATH search. -use-runtime ocamlrun-$(ZINC_RUNTIME_ID) won't work here -# as ocamlc will convert it to an absolute path. Instead, we (ab)use -# -runtime-variant to append the -$(ZINC_RUNTIME_ID) to the default ocamlrun. -BYTECODE_LAUNCHER_FLAGS += -runtime-variant -$(ZINC_RUNTIME_ID) -# boot/ocamlc is built with suffixing disabled, so this works both before and -# after bootstrapping, however this would cause $(ROOTDIR)/ocamlc to add the -# suffix twice. Thwart this by further (ab)using -runtime-variant. -ROOT_LINK_FLAGS += -runtime-variant '' -else ifeq "$(SUFFIXING)" "true" -# boot/ocamlc is built with suffixing disabled or not yet bootstrapped, so -# either way pass the suffixed runtime name explicitly with -use-runtime. -BYTECODE_LAUNCHER_FLAGS += \ - -use-runtime $(call QUOTE_SINGLE,$(BINDIR)/ocamlrun-$(ZINC_RUNTIME_ID)) -endif + -launch-method $(call QUOTE_SINGLE,$(LAUNCH_METHOD) $(BINDIR)) \ + -runtime-search $(if $(RUNTIME_SEARCH),$(RUNTIME_SEARCH),disable) MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS = \ $(if $(filter -custom, $(1)),,\ diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index 298edc42ddec..dbc93c0e8b33 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -428,9 +428,6 @@ let write_header outchan = let zinc_runtime_id, offset = if String.length data < 2 then raise (Error (Camlheader ("corrupt header", header))) - (* Compatibility with previous header format - remove post-bootstrap *) - else if List.mem data.[0] ['/'; 'e'; 's'] then - None, String.index data '\000' + 2 else if data.[0] = '\000' then None, 1 else diff --git a/configure b/configure index b99258152942..857629560e96 100755 --- a/configure +++ b/configure @@ -14171,12 +14171,6 @@ esac fi -# stdlib/runtime.info and stdlib/target_runtime.info are generated by commands -# in config.status, rather than by the .in mechanism, since the latter cannot -# reliably process binary files. -ac_config_commands="$ac_config_commands shebang" - - # Checks for programs ## Check for the C compiler: done by libtool @@ -22698,11 +22692,6 @@ fi - launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' - launch_method_target=\ -'$(echo "$launch_method_target" | sed -e "s/'/'\"'\"'/g")' - HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' - TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")' ocaml_additional_stublibs_dir=\ '$(echo "$ocaml_additional_stublibs_dir" | sed -e "s/'/'\"'\"'/g")' ocaml_libdir='$(echo "$ocaml_libdir" | sed -e "s/'/'\"'\"'/g")' @@ -22745,7 +22734,6 @@ do "otherlibs/unix/META") CONFIG_FILES="$CONFIG_FILES otherlibs/unix/META" ;; "otherlibs/unix/unix.ml") CONFIG_LINKS="$CONFIG_LINKS otherlibs/unix/unix.ml:otherlibs/unix/unix_${unix_or_win32}.ml" ;; "otherlibs/str/META") CONFIG_FILES="$CONFIG_FILES otherlibs/str/META" ;; - "shebang") CONFIG_COMMANDS="$CONFIG_COMMANDS shebang" ;; "otherlibs/systhreads/META") CONFIG_FILES="$CONFIG_FILES otherlibs/systhreads/META" ;; "ocamltest/ocamltest_unix.ml") CONFIG_LINKS="$CONFIG_LINKS ocamltest/ocamltest_unix.ml:${ocamltest_unix_mod}" ;; "runtime/ld.conf") CONFIG_COMMANDS="$CONFIG_COMMANDS runtime/ld.conf" ;; @@ -23879,10 +23867,6 @@ ltmain=$ac_aux_dir/ltmain.sh chmod +x "$ofile" ;; - "shebang":C) printf '%s\n%s\000\n' "$launch_method" "$HOST_BINDIR" \ - > stdlib/runtime.info - printf '%s\n%s\000\n' "$launch_method_target" "$TARGET_BINDIR" \ - > stdlib/target_runtime.info ;; "runtime/ld.conf":C) rm -f runtime/ld.conf test x"$ocaml_additional_stublibs_dir" = 'x' || \ echo "$ocaml_additional_stublibs_dir" > runtime/ld.conf diff --git a/configure.ac b/configure.ac index 013fb77907d0..b59234e167b5 100644 --- a/configure.ac +++ b/configure.ac @@ -944,24 +944,6 @@ AS_IF([test "x$interpval" = "xyes"], )] ) -# stdlib/runtime.info and stdlib/target_runtime.info are generated by commands -# in config.status, rather than by the .in mechanism, since the latter cannot -# reliably process binary files. -AC_CONFIG_COMMANDS([shebang], - [printf '%s\n%s\000\n' "$launch_method" "$HOST_BINDIR" \ - > stdlib/runtime.info - printf '%s\n%s\000\n' "$launch_method_target" "$TARGET_BINDIR" \ - > stdlib/target_runtime.info], -dnl These declarations are put in a here-document in configure, so the command -dnl in '$(...)' _is_ evaluated as the content is written to config.status (by -dnl standard interpretation of a here-document). The sed commands quote any -dnl nefarious single quotes which may appear in any of the strings. - [launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' - launch_method_target=\ -'$(echo "$launch_method_target" | sed -e "s/'/'\"'\"'/g")' - HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' - TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")']) - # Checks for programs ## Check for the C compiler: done by libtool diff --git a/stdlib/Makefile b/stdlib/Makefile index 1bb0eac35a9a..1e775f4fd17d 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -54,7 +54,7 @@ NOSTDLIB= camlinternalFormatBasics.cmo stdlib.cmo OTHERS=$(filter-out $(NOSTDLIB),$(OBJS)) .PHONY: all -all: stdlib.cma std_exit.cmo $(HEADER_NAME) target_$(HEADER_NAME) +all: stdlib.cma std_exit.cmo $(HEADER_NAME) .PHONY: allopt opt.opt # allopt and opt.opt are synonyms allopt: stdlib.cmxa std_exit.cmx @@ -73,7 +73,7 @@ ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" *.cmt *.cmti *.mli *.ml *.ml.in \ "$(INSTALL_LIBDIR)" endif - $(INSTALL_DATA) target_$(HEADER_NAME) "$(INSTALL_LIBDIR)/$(HEADER_NAME)" + $(INSTALL_DATA) $(HEADER_NAME) "$(INSTALL_LIBDIR)/$(HEADER_NAME)" .PHONY: installopt installopt: installopt-default @@ -84,12 +84,8 @@ installopt-default: stdlib.cmxa stdlib.$(A) std_exit.$(O) *.cmx \ "$(INSTALL_LIBDIR)" -runtime-launch-info: runtime.info tmpheader.exe - @{ cat $^; \ - printf '$(if $(filter true,$(SUFFIXING)),$(ZINC_RUNTIME_ID))'; } > $@ - MANGLING = $(filter true,$(SUFFIXING)) -target_runtime-launch-info: tmpheader.exe +runtime-launch-info: tmpheader.exe @{ printf '$(if $(MANGLING),$(ZINC_RUNTIME_ID),\000)'; \ cat $^; } > $@ @@ -141,11 +137,11 @@ stdlib.cmxa: $(OBJS:.cmo=.cmx) .PHONY: distclean distclean: clean - rm -f sys.ml META runtime.info target_runtime.info + rm -f sys.ml META .PHONY: clean clean:: - rm -f $(HEADER_NAME) target_$(HEADER_NAME) + rm -f $(HEADER_NAME) export AWK diff --git a/testsuite/tools/testLinkModes.ml b/testsuite/tools/testLinkModes.ml index ed357394a63a..7fbfbf1afdc4 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -125,7 +125,7 @@ let () = around some problems with shared runtimes on s390x and riscv which don't reliably fail. *) -let run_program env _config = +let run_program env config = let prefix = Environment.prefix env in let libdir_suffix = Environment.libdir_suffix env in let prefix, libdir_suffix = @@ -137,8 +137,12 @@ let run_program env _config = in fun ~runtime ~stubs test_program expected_executable_name ~prefix_path_with_cwd expected_exit_code argv0 expected_argv0 - ~may_segfault ~stdlib_exists_when_renamed:_ -> - let stdlib_exists = not (Environment.is_renamed env) in + ~may_segfault ~stdlib_exists_when_renamed -> + let stdlib_exists = + if Environment.is_renamed env then + stdlib_exists_when_renamed + else + config.has_relative_libdir <> None in let args = [string_of_bool stdlib_exists; prefix; libdir_suffix] in let argv0 = if argv0 = test_program then diff --git a/utils/config.common.ml.in b/utils/config.common.ml.in index 5e744ee7437b..c5459b93efc0 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -26,7 +26,9 @@ let version = Sys.ocaml_version let is_official_release = false let release_number = 19 -let standard_library_default_raw = standard_library_default +external standard_library_default : unit -> string = "%standard_library_default" + +let standard_library_default_raw = standard_library_default () external stdlib_dirs : string -> string * string option = "caml_sys_get_stdlib_dirs" diff --git a/utils/config.fixed.ml b/utils/config.fixed.ml index a6cabaeaaacd..02894bc6b3b0 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -22,7 +22,6 @@ let boot_cannot_call s = "/ The boot compiler should not call " ^ s let bindir = "/tmp" let target_bindir = bindir -let standard_library_default = "/tmp" let ccomp_type = "n/a" let c_compiler = boot_cannot_call "the C compiler" let c_output_obj = "" diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index 1dcbb9bf3857..71093a1e5dbb 100644 --- a/utils/config.generated.ml.in +++ b/utils/config.generated.ml.in @@ -21,8 +21,6 @@ let bindir = {@QS@|@ocaml_bindir@|@QS@} let target_bindir = {@QS@|@TARGET_BINDIR@|@QS@} -let standard_library_default = {@QS@|@ocaml_libdir@|@QS@} - let ccomp_type = {@QS@|@ccomptype@|@QS@} let c_compiler = {@QS@|@CC@|@QS@} let c_output_obj = {@QS@|@outputobj@|@QS@} From 47b586814a1721194cb9fbe69f11981b5f8cd284 Mon Sep 17 00:00:00 2001 From: Nick Barnes Date: Wed, 17 Dec 2025 17:27:04 +0000 Subject: [PATCH 22/25] Merge pull request PR#14246 from dra27/opam-install-file Relocatable OCaml - to opam and beyond. Includes subsequent fixes from PR#14914 and PR#14923. (cherry picked from commit f6c8af418c3a4dfd43372ec75100e86baffea92a) (cherry picked from commit 63b0c81c38e8affcda002e125fad805efbb709a1) (cherry picked from commit 31d08a35cf3705ef1a37a1caa708b41a1d1e0f91) --- .gitattributes | 1 + .github/workflows/build-msvc.yml | 43 ++- .github/workflows/build.yml | 15 +- .gitignore | 1 + Changes | 3 + Makefile | 525 +++++++++++++-------------- Makefile.common | 371 ++++++++++++++++++- api_docgen/Makefile | 1 + api_docgen/ocamldoc/Makefile | 6 +- api_docgen/odoc/Makefile | 12 +- man/Makefile | 4 +- ocaml-variants.opam | 19 +- otherlibs/Makefile | 1 + otherlibs/Makefile.otherlibs.common | 53 ++- otherlibs/systhreads/Makefile | 27 +- stdlib/Makefile | 22 +- tools/ci/actions/runner.sh | 52 ++- tools/ci/appveyor/appveyor_build.cmd | 22 +- tools/ci/appveyor/appveyor_build.sh | 41 ++- tools/opam/generate.ml | 237 ++++++++++++ tools/opam/process.sh | 190 ++++++++++ 21 files changed, 1249 insertions(+), 397 deletions(-) create mode 100644 tools/opam/generate.ml create mode 100644 tools/opam/process.sh diff --git a/.gitattributes b/.gitattributes index 1069ce73e3d7..53ae490491f1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -108,6 +108,7 @@ otherlibs/unix/symlink_win32.c typo.long-line # Some Unicode characters here and there utils/misc.ml typo.utf8 runtime/sak.c typo.non-ascii +tools/opam/process.sh typo.non-ascii stdlib/hashbang typo.white-at-eol typo.missing-lf diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index 685c0a333b93..cb96bacc5749 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -33,10 +33,12 @@ jobs: with: script: | // # Always test cl and clang-cl - let compilers = ['cl', 'clang-cl']; + let compilers = ['clang-cl']; // # Also test i686 MSVC let include = [ - {cc: 'cl', arch: 'i686', libdir: 'relative'}]; + {os: 'windows-latest', cc: 'cl', arch: 'i686', opam: 'false', prefix: '$PROGRAMFILES/Бактріан🐫', libdir: 'relative'}, + {os: 'windows-2025', cc: 'cl', arch: 'x86_64', opam: 'true', prefix: 'C:\\\\Бактріан🐫'}, + {os: 'windows-2025', cc: 'cl', arch: 'i686', opam: 'true', prefix: 'C:\\\\Бактріан🐫'}]; let libdir = ['absolute']; // # If this is a pull request, see if the PR has the // # 'CI: Full matrix' label. This is done using an API request, @@ -53,14 +55,14 @@ jobs: // # Test Cygwin as well compilers.push('gcc'); // # Test bytecode-only Cygwin - include.push({cc: 'gcc', arch: 'x86_64', libdir: 'absolute', config_arg: '--disable-native-toplevel --disable-native-compiler'}); + include.push({os: 'windows-latest', prefix: '$PROGRAMFILES/Бактріан🐫', opam: ['false'], cc: 'gcc', arch: 'x86_64', libdir: 'absolute', config_arg: '--disable-native-toplevel --disable-native-compiler'}); // # Test i686 MSVC absolute - include.push({cc: 'cl', arch: 'i686', libdir: 'absolute'}); + include.push({os: 'windows-latest', prefix: '$PROGRAMFILES/Бактріан🐫', opam: ['false'], cc: 'cl', arch: 'i686', libdir: 'absolute'}); // # Expand the main matrix to include relative testing libdir.push('relative'); } } - return {config_arg: [''], arch: ['x86_64'], cc: compilers, libdir: libdir, include: include}; + return {os: ['windows-latest'], prefix: ['$PROGRAMFILES/Бактріан🐫'], opam: ['false'], config_arg: [''], arch: ['x86_64'], cc: compilers, libdir: libdir, include: include}; - name: Determine if the testsuite should be skipped id: skip uses: actions/github-script@v7 @@ -78,7 +80,7 @@ jobs: build: permissions: {} - runs-on: windows-latest + runs-on: ${{ matrix.os }} needs: config @@ -107,7 +109,7 @@ jobs: - name: Install Cygwin uses: cygwin/cygwin-install-action@v3 with: - packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-fortran,' }}gcc-core + packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-fortran,' }}gcc-core,rsync,unzip install-dir: 'D:\cygwin' - name: Save Cygwin cache @@ -123,6 +125,13 @@ jobs: arch: ${{ matrix.arch == 'x86_64' && 'x64' || 'x86' }} if: matrix.cc != 'gcc' + - name: Install opam + if: matrix.opam == 'true' + shell: pwsh + run: | + winget install opam --accept-source-agreements + Add-Content -Path $env:GITHUB_PATH -Value "$env:LOCALAPPDATA\Microsoft\WinGet\Links" + - name: Compute a key to cache configure results id: autoconf-cache-key env: @@ -141,7 +150,8 @@ jobs: env: CONFIG_ARGS: >- --cache-file=config.cache - --prefix "${{ matrix.cc != 'gcc' && '$PROGRAMFILES\\Бактріан🐫' || '$(cygpath "$PROGRAMFILES/Бактріан🐫")'}}" + --prefix ${{ matrix.cc != 'gcc' && format('"{0}/_opam"', matrix.prefix) || format('"$(cygpath "{0}")"', matrix.prefix) }} + --docdir ${{ format((matrix.cc != 'gcc' && '"{0}/_opam/doc/ocaml"' || '"$(cygpath "{0}/doc/ocaml")"'), matrix.prefix) }} ${{ matrix.cc != 'gcc' && format('--host={0}-pc-windows', matrix.arch) || '' }} ${{ matrix.cc != 'gcc' && format('CC={0}', matrix.cc) || '' }} --enable-ocamltest @@ -200,8 +210,25 @@ jobs: make tests - name: Install the compiler + if: matrix.opam != 'true' run: make install + - name: Create opam switch + if: matrix.opam == 'true' + env: + OPAMSWITCH: ${{ matrix.prefix }} + run: | + make OPAM_PACKAGE_NAME=ocaml-variants INSTALL_MODE=opam install + opam init --cli=2.4 --bare --yes --disable-sandboxing --auto-setup --cygwin-local-install + # These commands intentionally run using opam's "default" CLI + opam switch create '${{ env.OPAMSWITCH }}' --empty + opam pin add --no-action --kind=path ocaml-variants . + opam pin add --no-action flexdll flexdll + opam pin add --no-action winpthreads winpthreads + opam install --yes flexdll winpthreads + opam install --yes --assume-built ocaml-variants + opam exec -- ocamlc -v + - name: Test in prefix run: | eval $(tools/msvs-promote-path) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1790b0421bb..a6f30541663e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -78,6 +78,7 @@ jobs: # debug runtime and minor heap verification. # debug-s4096: select testsuite run with the debug runtime and a small # minor heap. +# opam: constructs an opam local switch from the compiler. normal: name: ${{ matrix.name }} needs: [build, config] @@ -88,6 +89,8 @@ jobs: - id: normal name: normal dependencies: texlive-latex-extra texlive-fonts-recommended texlive-luatex hevea sass + - id: opam + name: opam installation - id: debug name: extra (debug) - id: debug-s4096 @@ -134,15 +137,19 @@ jobs: - name: Install if: matrix.id == 'normal' run: | - MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh install + MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh install + - name: Create opam switch + if: matrix.id == 'opam' + run: | + MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh opam - name: Test in prefix - if: matrix.id == 'normal' + if: matrix.id == 'normal' || matrix.id == 'opam' run: | - MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh test-in-prefix + MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh test-in-prefix - name: Test in prefix (alternate configuration) if: matrix.id == 'normal' && needs.config.outputs.full-matrix == 'true' run: | - MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh re-test-in-prefix + MAKE_ARG=-j OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh re-test-in-prefix - name: Build the manual if: matrix.id == 'normal' && needs.build.outputs.manual_changed == 'true' run: | diff --git a/.gitignore b/.gitignore index 23b481526669..c3d7e7a5102a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ *.cmx[as] *.cmti *.annot +*.stripped *.exe *.exe.manifest .DS_Store diff --git a/Changes b/Changes index 9fd758c6eebd..271adfa7c4e0 100644 --- a/Changes +++ b/Changes @@ -103,6 +103,9 @@ OCaml 5.3 maintenance version demarshaling. (Benoît Vaugon, review by David Allsopp and Gabriel Scherer) +- #14871, #14914: Ignore OCAMLTOP_INCLUDE_PATH during the build. + (David Allsopp, report by Andreas Rossberg, review by Florian Angeletti) + OCaml 5.3.0 (8 January 2025) ---------------------------- diff --git a/Makefile b/Makefile index 04a969c976ed..57747404c110 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,8 @@ # The main Makefile ROOTDIR = . +SUBDIR_NAME = + # NOTE: it is important that the OCAMLDEP and OCAMLLEX variables # are defined *before* Makefile.common gets included, so that # their local definitions here take precedence over their @@ -52,8 +54,6 @@ PERVASIVES=$(STDLIB_MODULES) outcometree topprinters topdirs toploop LIBFILES=stdlib.cma std_exit.cmo *.cmi $(HEADER_NAME) -COMPLIBDIR=$(LIBDIR)/compiler-libs - TOPINCLUDES=$(addprefix -I otherlibs/,$(filter-out %threads,$(OTHERLIBRARIES))) expunge := expunge$(EXE) @@ -916,8 +916,6 @@ partialclean:: rm -f flexlink.opt flexlink.opt.exe \ $(OPT_BINDIR)/flexlink $(OPT_BINDIR)/flexlink.exe -INSTALL_COMPLIBDIR = $(DESTDIR)$(COMPLIBDIR) -INSTALL_FLEXDLLDIR = $(INSTALL_LIBDIR)/flexdll FLEXDLL_MANIFEST = default$(filter-out _i386,_$(ARCH)).manifest DOC_FILES=\ @@ -969,7 +967,7 @@ ocamlc_BYTECODE_LINKFLAGS += -set-runtime-default standard_library_default=. endif partialclean:: - rm -f ocamlc ocamlc.exe ocamlc.opt ocamlc.opt.exe + rm -f ocamlc ocamlc.exe ocamlc.opt ocamlc.opt.exe ocamlc*.stripped # The native-code compiler @@ -980,7 +978,7 @@ ocamlopt_SOURCES = driver/optmain.mli driver/optmain.ml ocamlopt_BYTECODE_LINKFLAGS = -g partialclean:: - rm -f ocamlopt ocamlopt.exe ocamlopt.opt ocamlopt.opt.exe + rm -f ocamlopt ocamlopt.exe ocamlopt.opt ocamlopt.opt.exe ocamlopt*.stripped # The toplevel @@ -2738,21 +2736,69 @@ endif $(BYTE_BUILD_TREE) $(OPT_BUILD_TREE) rm -f config.log config.status libtool -INSTALL_LIBDIR_DYNLINK = $(INSTALL_LIBDIR)/dynlink +# COMPILER_ARTEFACT_DIRS adds the common compiler-libs directories as prefixes +# to a sequence of patterns in the first argument, e.g. +# $(call COMPILER_ARTEFACT_DIRS, *.cmi) expands to utils/*.cmi, parsing/*.cmi, +# and so forth. Multiple wildcard patterns may be supplied. An optional second +# argument includes additional directories beyond the common ones (e.g. asmcomp, +# etc.) +COMPILER_ARTEFACT_DIRS = \ + $(foreach dir, \ + utils parsing typing bytecomp file_formats lambda driver toplevel \ + $(if $(filter-out undefined, $(origin 2)), $(2)), \ + $(addprefix $(dir)/, $(1))) +NATIVE_ARTEFACT_DIRS = \ + asmcomp toplevel/native \ + middle_end middle_end/closure middle_end/flambda middle_end/flambda/base_types # Installation +# Historically, the install target dynamically installed what had been built, +# for example, if only world had been built then make install simply didn't +# install the native tools. That infrastructure is potentially convenient when +# working on the compiler, but potentially masks bugs. It is better to have the +# installation targets require everything configure mandated to have built. +# There are three entry points to installation: +# install - installs everything +# installopt - installs the native code compiler _and_ the extra .opt tools +# installoptopt - installs just the extra .opt tools +# The installopt targets have been maintained for now, but may be removed in the +# future. -.PHONY: install -install:: - $(MKDIR) "$(INSTALL_BINDIR)" - $(MKDIR) "$(INSTALL_LIBDIR)" -ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" - $(MKDIR) "$(INSTALL_STUBLIBDIR)" +ifeq "$(NATIVE_COMPILER)" "true" +install: full-installoptopt + $(call INSTALL_END) +else +install: common-install + $(call INSTALL_END) endif - $(MKDIR) "$(INSTALL_COMPLIBDIR)" - $(MKDIR) "$(INSTALL_DOCDIR)" - $(MKDIR) "$(INSTALL_INCDIR)" - $(MKDIR) "$(INSTALL_LIBDIR_PROFILING)" + +# These three targets are the slightly esoteric special sauce that avoid +# recursive make invocations in the install targets. +# There are three basic install recipes: +# - The old install target is available to common-install, but never recurses to +# installopt +# - The old installopt target is available as both full-installopt and +# native-install +# - The old installoptopt target is also available as full-installoptopt and +# installopt +# These sets of recipes are then welded together by these three dependency +# specifications +# - When configured with --disable-native-compiler, the install target simply +# depends on common-install (see above) +# - Otherwise, install depends on full-installoptopt (see above) +# - The recipe for full-installoptopt installs the .opt versions of the tools, +# but it _depends on_ full-installopt. +# - full-installopt installs the native compiler, but it _depends on_ +# common-install +installopt: native-install + +full-installopt:: common-install + +full-installoptopt: full-installopt + +.PHONY: common-install +common-install:: + $(call INSTALL_BEGIN) ifeq "$(SUFFIXING)" "true" MANGLE_RUNTIME_NAME = $(TARGET)-$(1)-$(BYTECODE_RUNTIME_ID)$(EXE) @@ -2763,341 +2809,264 @@ MANGLE_RUNTIME_DLL_NAME = lib$(1)_shared$(EXT_DLL) endif define INSTALL_RUNTIME -install:: - $(INSTALL_PROG) \ - runtime/$(1)$(EXE) \ - "$(INSTALL_BINDIR)/$(call MANGLE_RUNTIME_NAME,$(1))" -ifeq "$(SUFFIXING)" "true" - cd "$(INSTALL_BINDIR)" && \ - $(LN) "$(TARGET)-$(1)-$(BYTECODE_RUNTIME_ID)$(EXE)" "$(1)$(EXE)" - cd "$(INSTALL_BINDIR)" && \ - $(LN) "$(TARGET)-$(1)-$(BYTECODE_RUNTIME_ID)$(EXE)" \ - "$(1)-$(ZINC_RUNTIME_ID)$(EXE)" -endif +common-install:: + $$(call INSTALL_ITEM, runtime/$(1)$(EXE), bin, , \ + $(call MANGLE_RUNTIME_NAME,$(1)), $(if $(filter true, $(SUFFIXING)), \ + $(1)$(EXE) $(1)-$(ZINC_RUNTIME_ID)$(EXE))) endef define INSTALL_RUNTIME_LIB ifeq "$(2)" "BYTECODE" -install:: +common-install:: else -installopt:: -endif - $(INSTALL_PROG) \ - runtime/lib$(1)_shared$(EXT_DLL) \ - "$(INSTALL_LIBDIR)/$(call MANGLE_RUNTIME_DLL_NAME,$(1),$(2))" -ifeq "$(SUFFIXING)" "true" - cd "$(INSTALL_LIBDIR)" && \ - $(LN) "$(call MANGLE_RUNTIME_DLL_NAME,$(1),$(2))" \ - "lib$(1)_shared$(EXT_DLL)" +full-installopt native-install:: endif + $$(call INSTALL_ITEM, runtime/lib$(1)_shared$(EXT_DLL), libexec, , \ + $(call MANGLE_RUNTIME_DLL_NAME,$(1),$(2)), \ + $(if $(filter true, $(SUFFIXING)), lib$(1)_shared$(EXT_DLL))) endef $(foreach runtime, $(runtime_PROGRAMS), \ $(eval $(call INSTALL_RUNTIME,$(runtime)))) -install:: - $(INSTALL_DATA) runtime/ld.conf $(runtime_BYTECODE_STATIC_LIBRARIES) \ - "$(INSTALL_LIBDIR)" +common-install:: + $(call INSTALL_ITEMS, runtime/ld.conf $(runtime_BYTECODE_STATIC_LIBRARIES), \ + lib) $(foreach shared_runtime, $(runtime_BYTECODE_SHARED_LIBRARIES), \ $(eval $(call INSTALL_RUNTIME_LIB,$(shared_runtime),BYTECODE))) -install:: - $(INSTALL_DATA) runtime/caml/domain_state.tbl runtime/caml/*.h \ - "$(INSTALL_INCDIR)" - $(INSTALL_PROG) ocaml$(EXE) "$(INSTALL_BINDIR)" +common-install:: + $(call INSTALL_ITEMS, \ + runtime/caml/domain_state.tbl runtime/caml/*.h, \ + lib, $(INSTALL_LIBDIR_CAML)) + $(call INSTALL_ITEMS, ocaml$(EXE), bin) ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" - $(call INSTALL_STRIPPED_BYTE_PROG,\ - ocamlc$(EXE),"$(INSTALL_BINDIR)/ocamlc.byte$(EXE)") + $(call STRIP_BYTE_PROG, ocamlc$(EXE)) +ifeq "$(NATIVE_COMPILER)" "true" + $(call INSTALL_ITEM, \ + ocamlc$(EXE).stripped, bin, , ocamlc.byte$(EXE)) +else + $(call INSTALL_ITEM, \ + ocamlc$(EXE).stripped, bin, , ocamlc.byte$(EXE), ocamlc$(EXE)) +endif endif $(MAKE) -C stdlib install + +define INSTALL_ONE_NAT_TOOL +common-install:: +ifeq "$(NATIVE_COMPILER)" "true" ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" - $(INSTALL_PROG) lex/ocamllex$(EXE) \ - "$(INSTALL_BINDIR)/ocamllex.byte$(EXE)" - for i in $(TOOLS_TO_INSTALL_NAT); \ - do \ - $(INSTALL_PROG) "tools/$$i$(EXE)" "$(INSTALL_BINDIR)/$$i.byte$(EXE)";\ - if test -f "tools/$$i".opt$(EXE); then \ - $(INSTALL_PROG) "tools/$$i.opt$(EXE)" "$(INSTALL_BINDIR)" && \ - (cd "$(INSTALL_BINDIR)" && $(LN) "$$i.opt$(EXE)" "$$i$(EXE)"); \ - else \ - (cd "$(INSTALL_BINDIR)" && $(LN) "$$i.byte$(EXE)" "$$i$(EXE)"); \ - fi; \ - done + $$(call INSTALL_ITEM, tools/$(1)$(EXE), bin, , $(1).byte$(EXE)) +endif + $$(call INSTALL_ITEM, tools/$(1).opt$(EXE), bin, , , $(1)$(EXE)) else - for i in $(TOOLS_TO_INSTALL_NAT); \ - do \ - if test -f "tools/$$i".opt$(EXE); then \ - $(INSTALL_PROG) "tools/$$i.opt$(EXE)" "$(INSTALL_BINDIR)"; \ - (cd "$(INSTALL_BINDIR)" && $(LN) "$$i.opt$(EXE)" "$$i$(EXE)"); \ - fi; \ - done + $$(call INSTALL_ITEM, tools/$(1)$(EXE), bin, , $(1).byte$(EXE), $(1)$(EXE)) endif - for i in $(TOOLS_TO_INSTALL_BYT); \ - do \ - $(INSTALL_PROG) "tools/$$i$(EXE)" "$(INSTALL_BINDIR)";\ - done - $(INSTALL_PROG) $(ocamlyacc_PROGRAM)$(EXE) "$(INSTALL_BINDIR)" - $(INSTALL_DATA) \ - utils/*.cmi \ - parsing/*.cmi \ - typing/*.cmi \ - bytecomp/*.cmi \ - file_formats/*.cmi \ - lambda/*.cmi \ - driver/*.cmi \ - toplevel/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - toplevel/byte/*.cmi \ - "$(INSTALL_COMPLIBDIR)" +endef + +ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" +common-install:: +ifeq "$(NATIVE_COMPILER)" "true" + $(call INSTALL_ITEM, \ + lex/ocamllex$(EXE), bin, , ocamllex.byte$(EXE)) +else + $(call INSTALL_ITEM, \ + lex/ocamllex$(EXE), bin, , ocamllex.byte$(EXE), ocamllex$(EXE)) +endif +endif + +$(foreach tool, $(TOOLS_TO_INSTALL_NAT), \ + $(eval $(call INSTALL_ONE_NAT_TOOL,$(tool)))) + +define INSTALL_ONE_BYT_TOOL +common-install:: + $$(call INSTALL_ITEMS, tools/$(1)$(EXE), bin) +endef + +$(foreach tool, $(TOOLS_TO_INSTALL_BYT), \ + $(eval $(call INSTALL_ONE_BYT_TOOL,$(tool)))) + +common-install:: + $(call INSTALL_ITEMS, $(ocamlyacc_PROGRAM)$(EXE), bin) + $(call INSTALL_ITEMS, \ + $(call COMPILER_ARTEFACT_DIRS, *.cmi), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - utils/*.cmt utils/*.cmti utils/*.mli \ - parsing/*.cmt parsing/*.cmti parsing/*.mli \ - typing/*.cmt typing/*.cmti typing/*.mli \ - file_formats/*.cmt file_formats/*.cmti file_formats/*.mli \ - lambda/*.cmt lambda/*.cmti lambda/*.mli \ - bytecomp/*.cmt bytecomp/*.cmti bytecomp/*.mli \ - driver/*.cmt driver/*.cmti driver/*.mli \ - toplevel/*.cmt toplevel/*.cmti toplevel/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - toplevel/byte/*.cmt \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - tools/profiling.cmt tools/profiling.cmti \ - "$(INSTALL_LIBDIR_PROFILING)" + $(call INSTALL_ITEMS, \ + $(call COMPILER_ARTEFACT_DIRS, *.cmt *.cmti *.mli), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, toplevel/byte/*.cmt, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, tools/profiling.cmt tools/profiling.cmti, \ + lib, $(INSTALL_LIBDIR_PROFILING)) endif - $(INSTALL_DATA) \ - compilerlibs/*.cma compilerlibs/META \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - $(ocamlc_CMO_FILES) $(ocaml_CMO_FILES) \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_PROG) $(expunge) "$(INSTALL_LIBDIR)" + $(call INSTALL_ITEMS, compilerlibs/*.cma compilerlibs/META, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, $(ocamlc_CMO_FILES) $(ocaml_CMO_FILES), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, $(expunge), libexec) # If installing over a previous OCaml version, ensure some modules are removed # from the previous installation. - rm -f "$(INSTALL_LIBDIR)"/topdirs.cm* "$(INSTALL_LIBDIR)/topdirs.mli" - rm -f "$(INSTALL_LIBDIR)"/profiling.cm* "$(INSTALL_LIBDIR)/profiling.$(O)" - $(INSTALL_DATA) \ - tools/profiling.cmi tools/profiling.cmo \ - "$(INSTALL_LIBDIR_PROFILING)" + $(call INSTALL_RM, \ + "$(INSTALL_LIBDIR)"/topdirs.cm* "$(INSTALL_LIBDIR)/topdirs.mli") + $(call INSTALL_RM, \ + "$(INSTALL_LIBDIR)"/profiling.cm* "$(INSTALL_LIBDIR)/profiling.$(O)") + $(call INSTALL_ITEMS, tools/profiling.cmi tools/profiling.cmo, \ + lib, $(INSTALL_LIBDIR_PROFILING)) ifeq "$(UNIX_OR_WIN32)" "unix" # Install manual pages only on Unix $(MAKE) -C man install endif # For dynlink, if installing over a previous OCaml version, ensure # dynlink is removed from the previous installation. - rm -f "$(INSTALL_LIBDIR)"/dynlink.cm* "$(INSTALL_LIBDIR)/dynlink.mli" \ - "$(INSTALL_LIBDIR)/dynlink.$(A)" \ - $(addprefix "$(INSTALL_LIBDIR)/", $(notdir $(dynlink_CMX_FILES))) - $(MKDIR) "$(INSTALL_LIBDIR_DYNLINK)" - $(INSTALL_DATA) \ - otherlibs/dynlink/dynlink.cmi otherlibs/dynlink/dynlink.cma \ - otherlibs/dynlink/META \ - "$(INSTALL_LIBDIR_DYNLINK)" + $(call INSTALL_RM, \ + "$(INSTALL_LIBDIR)"/dynlink.cm* \ + "$(INSTALL_LIBDIR)/dynlink.mli" \ + "$(INSTALL_LIBDIR)/dynlink.$(A)" \ + $(addprefix "$(INSTALL_LIBDIR)/", $(notdir $(dynlink_CMX_FILES)))) + $(call INSTALL_ITEMS, \ + otherlibs/dynlink/dynlink.cmi otherlibs/dynlink/dynlink.cma \ + otherlibs/dynlink/META, \ + lib, $(INSTALL_LIBDIR_DYNLINK)) ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - otherlibs/dynlink/dynlink.cmti otherlibs/dynlink/dynlink.mli \ - "$(INSTALL_LIBDIR_DYNLINK)" + $(call INSTALL_ITEMS, \ + otherlibs/dynlink/dynlink.cmti otherlibs/dynlink/dynlink.mli, \ + lib, $(INSTALL_LIBDIR_DYNLINK)) endif for i in $(OTHERLIBS); do \ $(MAKE) -C otherlibs/$$i install || exit $$?; \ done ifeq "$(build_ocamldoc)" "true" - $(MKDIR) "$(INSTALL_LIBDIR)/ocamldoc" - $(INSTALL_PROG) $(OCAMLDOC) "$(INSTALL_BINDIR)" - $(INSTALL_DATA) \ - ocamldoc/ocamldoc.hva ocamldoc/*.cmi ocamldoc/odoc_info.cma \ - ocamldoc/META \ - "$(INSTALL_LIBDIR)/ocamldoc" - $(INSTALL_DATA) \ - $(OCAMLDOC_LIBCMIS) \ - "$(INSTALL_LIBDIR)/ocamldoc" + $(call INSTALL_ITEMS, ocamldoc/ocamldoc$(EXE), bin) + $(call INSTALL_ITEMS, \ + ocamldoc/ocamldoc.hva ocamldoc/*.cmi ocamldoc/odoc_info.cma \ + ocamldoc/META, \ + lib, $(INSTALL_LIBDIR_OCAMLDOC)) ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - $(OCAMLDOC_LIBMLIS) $(OCAMLDOC_LIBCMTS) \ - "$(INSTALL_LIBDIR)/ocamldoc" + $(call INSTALL_ITEMS, $(OCAMLDOC_LIBMLIS) $(OCAMLDOC_LIBCMTS), \ + lib, $(INSTALL_LIBDIR_OCAMLDOC)) endif endif ifeq "$(build_libraries_manpages)" "true" $(MAKE) -C api_docgen install endif - if test -n "$(WITH_DEBUGGER)"; then \ - $(INSTALL_PROG) debugger/ocamldebug$(EXE) "$(INSTALL_BINDIR)"; \ - fi +ifneq "$(WITH_DEBUGGER)" "" + $(call INSTALL_ITEMS, debugger/ocamldebug$(EXE), bin) +endif ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" ifeq "$(TOOLCHAIN)" "msvc" - $(INSTALL_DATA) $(FLEXDLL_SOURCE_DIR)/$(FLEXDLL_MANIFEST) \ - "$(INSTALL_BINDIR)/" + # Technically this should not be installed with "executable" + # permissions, but in practice that request will be ignored. + $(call INSTALL_ITEMS, $(FLEXDLL_SOURCE_DIR)/$(FLEXDLL_MANIFEST), bin) endif ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" - $(INSTALL_PROG) \ - flexlink.byte$(EXE) "$(INSTALL_BINDIR)" -endif # ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" - $(MKDIR) "$(INSTALL_FLEXDLLDIR)" - $(INSTALL_DATA) $(FLEXDLL_OBJECTS) "$(INSTALL_FLEXDLLDIR)" -endif # ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" - $(INSTALL_DATA) Makefile.config "$(INSTALL_LIBDIR)" - $(INSTALL_DATA) $(DOC_FILES) "$(INSTALL_DOCDIR)" -ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" - if test -f ocamlopt$(EXE); then $(MAKE) installopt; else \ - cd "$(INSTALL_BINDIR)"; \ - $(LN) ocamlc.byte$(EXE) ocamlc$(EXE); \ - $(LN) ocamllex.byte$(EXE) ocamllex$(EXE); \ - (test -f flexlink.byte$(EXE) && \ - $(LN) flexlink.byte$(EXE) flexlink$(EXE)) || true; \ - fi +ifeq "$(NATIVE_COMPILER)" "true" + $(call INSTALL_ITEMS, flexlink.byte$(EXE), bin) else - if test -f ocamlopt$(EXE); then $(MAKE) installopt; fi + $(call INSTALL_ITEM, flexlink.byte$(EXE), bin, , , flexlink$(EXE)) endif +endif # ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" + $(call INSTALL_ITEMS, $(FLEXDLL_OBJECTS), lib, $(INSTALL_LIBDIR_FLEXDLL)) +endif # ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" + $(call INSTALL_ITEMS, Makefile.config, lib) + $(call INSTALL_ITEMS, $(DOC_FILES), doc) # Installation of the native-code compiler -.PHONY: installopt -installopt:: - $(INSTALL_DATA) $(runtime_NATIVE_STATIC_LIBRARIES) "$(INSTALL_LIBDIR)" +.PHONY: full-installopt native-install +full-installopt native-install:: + $(call INSTALL_ITEMS, $(runtime_NATIVE_STATIC_LIBRARIES), lib) $(foreach shared_runtime, $(runtime_NATIVE_SHARED_LIBRARIES), \ $(eval $(call INSTALL_RUNTIME_LIB,$(shared_runtime),NATIVE))) -installopt:: +full-installopt native-install:: ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" - $(call INSTALL_STRIPPED_BYTE_PROG,\ - ocamlopt$(EXE),"$(INSTALL_BINDIR)/ocamlopt.byte$(EXE)") + $(call STRIP_BYTE_PROG, ocamlopt$(EXE)) + $(call INSTALL_ITEM, ocamlopt$(EXE).stripped, bin, , ocamlopt.byte$(EXE)) endif $(MAKE) -C stdlib installopt - $(INSTALL_DATA) \ - middle_end/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/closure/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/flambda/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/flambda/base_types/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - asmcomp/*.cmi \ - "$(INSTALL_COMPLIBDIR)" + $(call INSTALL_ITEMS, \ + middle_end/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + middle_end/closure/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + middle_end/flambda/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + middle_end/flambda/base_types/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + asmcomp/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - middle_end/*.cmt middle_end/*.cmti \ - middle_end/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/closure/*.cmt middle_end/closure/*.cmti \ - middle_end/closure/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/flambda/*.cmt middle_end/flambda/*.cmti \ - middle_end/flambda/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/flambda/base_types/*.cmt \ - middle_end/flambda/base_types/*.cmti \ - middle_end/flambda/base_types/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - asmcomp/*.cmt asmcomp/*.cmti \ - asmcomp/*.mli \ - "$(INSTALL_COMPLIBDIR)" + $(call INSTALL_ITEMS, \ + $(addprefix middle_end/, *.cmt *.cmti *.mli), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + $(addprefix middle_end/closure/, *.cmt *.cmti *.mli), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + $(addprefix middle_end/flambda/, *.cmt *.cmti *.mli), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + $(addprefix middle_end/flambda/base_types/, *.cmt *.cmti *.mli), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + $(addprefix asmcomp/, *.cmt *.cmti *.mli), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) endif - $(INSTALL_DATA) \ - $(ocamlopt_CMO_FILES) \ - "$(INSTALL_COMPLIBDIR)" + $(call INSTALL_ITEMS, $(ocamlopt_CMO_FILES), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) ifeq "$(build_ocamldoc)" "true" - $(MKDIR) "$(INSTALL_LIBDIR)/ocamldoc" - $(INSTALL_PROG) $(OCAMLDOC_OPT) "$(INSTALL_BINDIR)" - $(INSTALL_DATA) \ - $(OCAMLDOC_LIBCMIS) \ - "$(INSTALL_LIBDIR)/ocamldoc" -ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - $(OCAMLDOC_LIBMLIS) $(OCAMLDOC_LIBCMTS) \ - "$(INSTALL_LIBDIR)/ocamldoc" -endif - $(INSTALL_DATA) \ - ocamldoc/ocamldoc.hva ocamldoc/*.cmx ocamldoc/odoc_info.$(A) \ - ocamldoc/odoc_info.cmxa \ - "$(INSTALL_LIBDIR)/ocamldoc" + $(call INSTALL_ITEMS, ocamldoc/ocamldoc.opt$(EXE), bin) + $(call INSTALL_ITEMS, \ + ocamldoc/*.cmx ocamldoc/odoc_info.$(A) ocamldoc/odoc_info.cmxa, \ + lib, $(INSTALL_LIBDIR_OCAMLDOC)) endif ifeq "$(strip $(NATDYNLINK))" "true" - $(INSTALL_DATA) \ - $(dynlink_CMX_FILES) otherlibs/dynlink/dynlink.cmxa \ - otherlibs/dynlink/dynlink.$(A) \ - "$(INSTALL_LIBDIR_DYNLINK)" + $(call INSTALL_ITEMS, \ + $(dynlink_CMX_FILES) otherlibs/dynlink/dynlink.cmxa \ + otherlibs/dynlink/dynlink.$(A), \ + lib, $(INSTALL_LIBDIR_DYNLINK)) endif for i in $(OTHERLIBS); do \ $(MAKE) -C otherlibs/$$i installopt || exit $$?; \ done -ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" - if test -f ocamlopt.opt$(EXE); then $(MAKE) installoptopt; else \ - cd "$(INSTALL_BINDIR)"; \ - $(LN) ocamlc.byte$(EXE) ocamlc$(EXE); \ - $(LN) ocamlopt.byte$(EXE) ocamlopt$(EXE); \ - $(LN) ocamllex.byte$(EXE) ocamllex$(EXE); \ - (test -f flexlink.byte$(EXE) && \ - $(LN) flexlink.byte$(EXE) flexlink$(EXE)) || true; \ - fi -else - if test -f ocamlopt.opt$(EXE); then $(MAKE) installoptopt; fi -endif - $(INSTALL_DATA) \ - tools/profiling.cmx tools/profiling.$(O) \ - "$(INSTALL_LIBDIR_PROFILING)" - -.PHONY: installoptopt -installoptopt: - $(INSTALL_PROG) ocamlc.opt$(EXE) "$(INSTALL_BINDIR)" - $(INSTALL_PROG) ocamlopt.opt$(EXE) "$(INSTALL_BINDIR)" - $(INSTALL_PROG) lex/ocamllex.opt$(EXE) "$(INSTALL_BINDIR)" - cd "$(INSTALL_BINDIR)"; \ - $(LN) ocamlc.opt$(EXE) ocamlc$(EXE); \ - $(LN) ocamlopt.opt$(EXE) ocamlopt$(EXE); \ - $(LN) ocamllex.opt$(EXE) ocamllex$(EXE) + $(call INSTALL_ITEMS, tools/profiling.cmx tools/profiling.$(O), \ + lib, $(INSTALL_LIBDIR_PROFILING)) + +.PHONY: full-installoptopt installopt installoptopt +full-installoptopt installopt installoptopt: + $(call INSTALL_ITEM, ocamlc.opt$(EXE), bin, , , ocamlc$(EXE)) + $(call INSTALL_ITEM, ocamlopt.opt$(EXE), bin, , , ocamlopt$(EXE)) + $(call INSTALL_ITEM, lex/ocamllex.opt$(EXE), bin, , , ocamllex$(EXE)) ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" - $(INSTALL_PROG) flexlink.opt$(EXE) "$(INSTALL_BINDIR)" - cd "$(INSTALL_BINDIR)"; \ - $(LN) flexlink.opt$(EXE) flexlink$(EXE) + $(call INSTALL_ITEM, flexlink.opt$(EXE), bin, , , flexlink$(EXE)) endif - $(INSTALL_DATA) \ - utils/*.cmx parsing/*.cmx typing/*.cmx bytecomp/*.cmx \ - toplevel/*.cmx toplevel/native/*.cmx \ - toplevel/native/tophooks.cmi \ - file_formats/*.cmx \ - lambda/*.cmx \ - driver/*.cmx asmcomp/*.cmx middle_end/*.cmx \ - middle_end/closure/*.cmx \ - middle_end/flambda/*.cmx \ - middle_end/flambda/base_types/*.cmx \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - compilerlibs/*.cmxa compilerlibs/*.$(A) \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - $(ocamlc_CMX_FILES) $(ocamlc_CMX_FILES:.cmx=.$(O)) \ - $(ocamlopt_CMX_FILES) $(ocamlopt_CMX_FILES:.cmx=.$(O)) \ - $(ocamlnat_CMX_FILES:.cmx=.$(O)) \ - "$(INSTALL_COMPLIBDIR)" + $(call INSTALL_ITEMS, \ + $(call COMPILER_ARTEFACT_DIRS, *.cmx, $(NATIVE_ARTEFACT_DIRS)) \ + toplevel/native/tophooks.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, compilerlibs/*.cmxa compilerlibs/*.$(A), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + $(ocamlc_CMX_FILES:.cmx=.$(O)) \ + $(ocamlopt_CMX_FILES:.cmx=.$(O)) \ + $(ocamlnat_CMX_FILES:.cmx=.$(O)), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) ifeq "$(INSTALL_OCAMLNAT)" "true" - $(INSTALL_PROG) ocamlnat$(EXE) "$(INSTALL_BINDIR)" + $(call INSTALL_ITEMS, ocamlnat$(EXE), bin) endif # Installation of the *.ml sources of compiler-libs .PHONY: install-compiler-sources install-compiler-sources: ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - utils/*.ml parsing/*.ml typing/*.ml bytecomp/*.ml driver/*.ml \ - file_formats/*.ml \ - lambda/*.ml \ - toplevel/*.ml toplevel/byte/*.ml \ - middle_end/*.ml middle_end/closure/*.ml \ - middle_end/flambda/*.ml middle_end/flambda/base_types/*.ml \ - asmcomp/*.ml \ - asmcmp/debug/*.ml \ - "$(INSTALL_COMPLIBDIR)" + $(call INSTALL_ITEMS, \ + $(call COMPILER_ARTEFACT_DIRS, *.ml, $(NATIVE_ARTEFACT_DIRS)) \ + toplevel/byte/*.ml, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) endif include .depend diff --git a/Makefile.common b/Makefile.common index 0b97f3478cd3..192daa858f6b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -86,15 +86,351 @@ V_ODOC = endif DESTDIR ?= -INSTALL_BINDIR := $(DESTDIR)$(BINDIR) -INSTALL_LIBDIR := $(DESTDIR)$(LIBDIR) -INSTALL_INCDIR=$(INSTALL_LIBDIR)/caml -INSTALL_STUBLIBDIR := $(DESTDIR)$(STUBLIBDIR) -INSTALL_LIBDIR_PROFILING = $(INSTALL_LIBDIR)/profiling -INSTALL_MANDIR := $(DESTDIR)$(MANDIR) -INSTALL_PROGRAMS_MAN_DIR := $(DESTDIR)$(PROGRAMS_MAN_DIR) -INSTALL_LIBRARIES_MAN_DIR := $(DESTDIR)$(LIBRARIES_MAN_DIR) -INSTALL_DOCDIR := $(DESTDIR)$(DOCDIR) + +# Augment directories from Makefile.config / Makefile.build_config with +# $(DESTDIR). i.e. each of these 5 directories may be overridden by the user, +# and the compiler distribution makes no assumptions about where they are +# relative to each other. +INSTALL_BINDIR = $(DESTDIR)$(BINDIR) +INSTALL_DOCDIR = $(DESTDIR)$(DOCDIR) +INSTALL_LIBDIR = $(DESTDIR)$(LIBDIR) +INSTALL_MANDIR = $(DESTDIR)$(MANDIR) +INSTALL_STUBLIBDIR = $(DESTDIR)$(STUBLIBDIR) + +# Library subdirectories. The compiler distribution does make assumptions about +# these, and they cannot be freely overridden by the user. +INSTALL_LIBDIR_CAML = caml +INSTALL_LIBDIR_COMPILERLIBS = compiler-libs +INSTALL_LIBDIR_DYNLINK = dynlink +INSTALL_LIBDIR_FLEXDLL = flexdll +INSTALL_LIBDIR_OCAMLDOC = ocamldoc +INSTALL_LIBDIR_PROFILING = profiling +INSTALL_LIBDIR_STDLIB = stdlib +INSTALL_LIBDIR_SYSTHREADS = threads + +INSTALL_MANDIR_PROGRAMS = man1 +INSTALL_MANDIR_LIBRARIES = man3 + +INSTALL_MODE ?= install + +# The scripts and commands generated by this installation system allow the user +# to be installing OCaml to any kind of tortuously difficult path they choose, +# but it is written assuming that the directory and file names which the +# distribution is in control of will follow some more restrictive rules, for +# simplicity. +# Paths in the installation system should always use forward slashes (these will +# be automatically translated to backslashes on Windows where required). Both +# the single and double quote characters are prohibited in all names and paths +# (this vastly simplifies the quoting assumptions between Unix/Windows). The @ +# symbol is not permitted in directory names because it used internally in +# filename mangling to represent forward slashes; it is permitted in filenames. +# Principally owing to escaping limitations of GNU make, it is not possible to +# use spaces in either source or target file names or in subdirectory names. +# tools/opam/generate.ml contains some sanity checking on the paths and names +# generated by these macros - make INSTALL_MODE=clone install is a good +# confidence check that all rules have been adhered to. + +# INSTALL_ITEM installs a single file, possibly with a different name and +# possibly creating additional symlinks/copies +# $1 = source file (may include directories) +# $2 = section (bin, doc, lib, libexec, man, stublibs) +# $3 = directory within section (may be empty) +# $4 = target basename (may be empty) +# $5 = additional basenames (either symlinked or copied, depending on what the +# platform supports) +# The $(origin n) dance is necessary to suppress warnings about undefined +# variables. +INSTALL_ITEM = \ + $(INSTALL_$(INSTALL_MODE)_PREFIX)$(call INSTALL_ENSURE_DIR,$\ + $(strip $(2)),$(if $(filter-out undefined,$(origin 3)),$(strip $(3))))$\ + $(call INSTALL_DESPATCH_$(INSTALL_MODE)_ITEM,$\ + $(strip $(1)),$\ + $(strip $(2)),$\ + $(if $(filter-out undefined,$(origin 3)),$(strip $(3))),$\ + $(if $(filter-out undefined,$(origin 4)),$(strip $(4))),$\ + $(if $(filter-out undefined,$(origin 5)),$(strip $(5)))) + +# INSTALL_ITEMS installs a series of files to a single directory +# $1 = source file(s) (may include directories and glob patterns) +# $2 = section (as for INSTALL_ITEM) +# $3 = directory within section (may be omitted) +# INSTALL_ITEMS is sometimes an alias for INSTALL_ITEM. For simplicity with +# undefined variable warnings, INSTALL_DESPATCH_foo_ITEMS is passed 5 parameters +# but $4 and $5 are always empty. +INSTALL_ITEMS = \ + $(INSTALL_$(INSTALL_MODE)_PREFIX)$(call INSTALL_ENSURE_DIR,$\ + $(strip $(2)),$(if $(filter-out undefined,$(origin 3)),$(strip $(3))))$\ + $(call INSTALL_DESPATCH_$(INSTALL_MODE)_ITEMS,$\ + $(strip $(1)),$\ + $(strip $(2)),$\ + $(if $(filter-out undefined,$(origin 3)),$(strip $(3))),,) + +# INSTALL_ITEMS_OPT is INSTALL_ITEMS, but does nothing if the source file(s) do +# not exist +INSTALL_ITEMS_OPT = \ + $(if $(wildcard $(1)),$(call INSTALL_ITEMS, \ + $(1), $(2), $(if $(filter-out undefined,$(origin 3)), $(3)))) + +INSTALL_ENSURE_DIR = \ + $(if $(filter undefined,$(origin DIR_CREATED_$(subst exec,,$(1))_$(2))),$\ + $(eval DIR_CREATED_$(subst exec,,$(1))_$(2):=)$\ + $(call INSTALL_DESPATCH_$(INSTALL_MODE)_MKDIR,$\ + $(subst exec,,$(1)),$(2))) + +# INSTALL_RM takes a single argument which may include glob patterns of files to +# be removed when performing a physical install. +INSTALL_RM = $(call INSTALL_DESPATCH_$(INSTALL_MODE)_RM,$(strip $(1))) + +# INSTALL_BEGIN and INSTALL_END are used in the root Makefile's install target +INSTALL_BEGIN = $(INSTALL_DESPATCH_$(INSTALL_MODE)_BEGIN) +INSTALL_END = $(INSTALL_DESPATCH_$(INSTALL_MODE)_END) + +# Normal installation +INSTALL_CMD_bin = $(INSTALL_PROG) +INSTALL_CMD_doc = $(INSTALL_DATA) +INSTALL_CMD_lib = $(INSTALL_DATA) +INSTALL_CMD_libexec = $(INSTALL_PROG) +INSTALL_CMD_man = $(INSTALL_DATA) +INSTALL_CMD_stublibs = $(INSTALL_PROG) + +INSTALL_SECTION_bin = $(INSTALL_BINDIR) +INSTALL_SECTION_doc = $(INSTALL_DOCDIR) +INSTALL_SECTION_lib = $(INSTALL_LIBDIR) +INSTALL_SECTION_libexec = $(INSTALL_LIBDIR) +INSTALL_SECTION_man = $(INSTALL_MANDIR) +INSTALL_SECTION_stublibs = $(INSTALL_STUBLIBDIR) + +QUOTE_SINGLE = '$(subst ','\'',$(1))' + +define NEWLINE + + +endef +SH_AND = && \$(NEWLINE) + +INSTALL_install_PREFIX = + +INSTALL_DESPATCH_install_RM = rm -f $(1) + +INSTALL_DESPATCH_install_MKDIR = \ + $(MKDIR) $(call QUOTE_SINGLE,$(INSTALL_SECTION_$(1))$(addprefix /,$(2))) \ + $(SH_AND) + +MK_LINK = \ + (cd "$(INSTALL_SECTION_$(2))$(addprefix /,$(3))" && \ + $(LN) $(call QUOTE_SINGLE,$(1)) $(call QUOTE_SINGLE,$(4))) + +INSTALL_DESPATCH_install_ITEM = \ + $(INSTALL_CMD_$(2)) $(1) \ + $(call QUOTE_SINGLE,$\ + $(INSTALL_SECTION_$(2))$(addprefix /,$(3))$(addprefix /,$(4))) \ + $(foreach link, $(5),$(SH_AND)$\ + $(call MK_LINK,$(if $(4),$(4),$(notdir $(1))),$(2),$(3),$(link))) + +INSTALL_DESPATCH_install_ITEMS = $(INSTALL_DESPATCH_install_ITEM) + +INSTALL_DESPATCH_install_BEGIN = @ +INSTALL_DESPATCH_install_END = @ + +INSTALL_display_PREFIX = @ + +INSTALL_DESPATCH_display_RM = @ + +INSTALL_DESPATCH_display_MKDIR = \ + echo $(call QUOTE_SINGLE,$\ + -> MKDIR $(INSTALL_SECTION_$(1))$(addprefix /,$(2))) $(SH_AND) + +MKLINK_display = \ + echo $(call QUOTE_SINGLE,-> LN \ + $(abspath $(INSTALL_SECTION_$(2))$(addprefix /,$(3))/$(1)) -> \ + $(if $(4),$(4),$(notdir $(1)))) + +INSTALL_DESPATCH_display_ITEM = \ + echo $(call QUOTE_SINGLE,-> INSTALL $(1) \ + $(INSTALL_SECTION_$(2))$(addprefix /,$(3))$(addprefix /,$(4))) \ + $(foreach link, $(5), && \ + $(call MKLINK_display,$(if $(4),$(4),$(notdir $(1))),$(2),$(3),$(link))) + +INSTALL_DESPATCH_display_ITEMS = $(INSTALL_DESPATCH_display_ITEM) + +INSTALL_DESPATCH_display_BEGIN = @ +INSTALL_DESPATCH_display_END = @ + +INSTALL_list_PREFIX = @ + +INSTALL_DESPATCH_list_RM = @ + +INSTALL_DESPATCH_list_MKDIR = + +MKLINK_list = \ + echo $(call QUOTE_SINGLE,-> \ + $(abspath $(INSTALL_SECTION_$(2))$(addprefix /,$(3))/$\ + $(if $(4),$(4),$(notdir $(1))))) + +INSTALL_DESPATCH_list_ITEM = \ + echo $(call QUOTE_SINGLE,-> \ + $(abspath $(INSTALL_SECTION_$(2))$(addprefix /,$(3))/$\ + $(if $(4),$(4),$(notdir $(1))))) \ + $(foreach link, $(5), && \ + $(call MKLINK_list,$(if $(4),$(4),$(notdir $(1))),$(2),$(3),$(link))) + +INSTALL_DESPATCH_list_ITEMS = \ + $(foreach file, $(wildcard $(1)), \ + echo $(call QUOTE_SINGLE,-> \ + $(INSTALL_SECTION_$(2))$(addprefix /,$(3))/$(notdir $(file)));) \ + true + +INSTALL_DESPATCH_list_BEGIN = @ +INSTALL_DESPATCH_list_END = @ + +OPAM_PACKAGE_NAME ?= ocaml-compiler + +# Generate $(OPAM_PACKAGE_NAME).install and $(OPAM_PACKAGE_NAME)-fixup.sh +# (INSTALL_MODE=opam) +# opam's .install format isn't quite rich enough at present to express the +# installation of the compiler. In particular, we can't install the doc files to +# doc/ocaml using a .install and we can't create symlinks. The things which +# can't be handled by the .install file are dealt with by the fixup script +# instead. + +INVOKE = $(strip $(1)) $(call QUOTE_SINGLE,$(strip $(2))) +ADD_LINE = $(call INVOKE, echo, $(2)) >> $(1) + +# RECORD_SYMLINK_TO_INSTALL +# $1 = file to install, implicitly relative to $(ROOTDIR) +# $2 = section +# $3 = subdirectory within $2 (may be empty) +# $4 = name to install $1 (must be specified) +# $5 = single name of symlink +# If symlinks are supported, $1 is ignored and the three pieces of information +# are recorded in create-symlinks: the directory, implicitly relative to the +# prefix, in which the symlink is to be created, the source file and name of the +# symlink. +# These can then be munged to a cd + ln combination in the fixup script. +# If symlinks are not supported, $1 is instead used to create an additional copy +# of the file, using the .install file. +ifeq "$(firstword $(LN))" "ln" +RECORD_SYMLINK_TO_INSTALL = \ + $(call ADD_LINE, $(ROOTDIR)/create-symlinks, \ + $(patsubst lib%,lib,$(2))$(addprefix /,$(3)) $(4) $(5)) +else +# Symlinks aren't available, so copy the file again using the target name +RECORD_SYMLINK_TO_INSTALL = \ + $(call RECORD_$(INSTALL_MODE)_ITEM_TO_INSTALL,$(1),$(2),$(3),$(5)) +endif + +# Process the arguments to pass to RECORD_$(INSTALL_MODE)_ITEM_TO_INSTALL: +# - Items installed to the stublibs section need to be remapped to the stublibs +# subdirectory of libexec (since we install to lib/ocaml/stublibs rather than +# opam's default lib/stublibs) +# - Source files must be given implicitly relative to $(ROOTDIR), so prefix with +# $(SUBDIR_NAME) if necessary +# - Items installed to the lib/libexec sections will in fact be installed to +# lib_root/libexec_root, so remap the installation directory to ocaml (i.e. to +# install to lib/ocaml rather than lib) +# - If no target basename has been explictly given, use the source's basename +RECORD_ITEM_TO_INSTALL = \ + $(if $(filter stublibs,$(2)),\ + $(call RECORD_ITEM_TO_INSTALL,$\ + $(1),libexec,stublibs$(addprefix /,$(3)),$(4),$(5)),\ + $(call RECORD_$(INSTALL_MODE)_ITEM_TO_INSTALL,$\ + $(addsuffix /,$(SUBDIR_NAME))$(1),$\ + $(2),$\ + $(if $(filter doc lib%,$(2)),ocaml$(addprefix /,$(3)),$(3)),$\ + $(if $(4),$(4),$(notdir $(1))),$\ + $(5))) + +# All files must be explicitly installed, so evaluate the wildcards and call +# INSTALL_DESPATCH_opam_ITEM for each file. +INSTALL_EVALUATE_GLOBS = \ + $(foreach file, $(wildcard $(1)), \ + $(call INSTALL_DESPATCH_$(INSTALL_MODE)_ITEM,$(file),$(2),$(3));) \ + true + +# RECORD_FILE_TO_INSTALL +# $1 = file to install, implicitly relative to $(ROOTDIR) +# $2 = bin/lib/libexec/man +# $3 = subdirectory within $2 (may be empty, but otherwise must end with "/") +# $4 = name to install $1 (must be specified) +# Writes an opam .install line to the section file for $(2). Each line consists +# of a double-quoted implicit filename relative to $(ROOTDIR) and optionally a +# second double-quoted implicit filename relative to the $(2) for the name to +# install the file under. +# e.g. "lex/ocamllex" {"ocamllex.byte"} or "expunge" {"ocaml/expunge"} +RECORD_FILE_TO_INSTALL = \ + $(call ADD_LINE, $(ROOTDIR)/opam-$(2), \ + "$(1)" $(if $(3)$(filter-out $(notdir $(1)),$(4)), {"$(3)$(4)"})) + +# RECORD_FILE_TO_CLONE +# $1 = file to install, implicitly relative to $(ROOTDIR) +# $2 = subdirectory (may be empty, but otherwise must end with "/") +# $3 = name to install $1 (must be specified) +# The compiler is installed as the ocaml package in opam, but the actual files +# are installed from other packages (typically ocaml-compiler). For the lib +# directory, the lib_root and libexec_root sections allow files to be installed +# to lib/ocaml, but there's no equivalent mechanism for the doc directory. These +# files are recorded to be copied manually in the fixup script. +RECORD_FILE_TO_CLONE = \ + $(call ADD_LINE, $(ROOTDIR)/clone-$(subst /,@,$(2)), $(1) $(3)) + +# RECORD_opam_ITEM_TO_INSTALL despatches the processed arguments of +# INSTALL_DESPATCH_opam_ITEM to the appropriate RECORD_ macro. +RECORD_opam_ITEM_TO_INSTALL = \ + $(if $(filter doc,$(2)),\ + $(call RECORD_FILE_TO_CLONE,$(1),doc/$(3),$(4)), \ + $(call RECORD_FILE_TO_INSTALL,$(1),$(2),$(addsuffix /,$(3)),$(4))) \ + $(foreach link, $(5), && \ + $(call RECORD_SYMLINK_TO_INSTALL,$(1),$(2),$(3),$(4),$(link))) + +INSTALL_DESPATCH_opam_ITEM = $(RECORD_ITEM_TO_INSTALL) + +INSTALL_DESPATCH_opam_ITEMS = $(INSTALL_EVALUATE_GLOBS) + +INSTALL_opam_PREFIX = @ + +INSTALL_DESPATCH_opam_RM = @ + +# INSTALL_MKDIR is ignored (opam creates them when executing the .install file) +INSTALL_DESPATCH_opam_MKDIR = + +INSTALL_DESPATCH_opam_BEGIN = \ + rm -f opam-bin clone-* opam-lib opam-libexec opam-man create-symlinks + +# Munge opam-bin, opam-lib, opam-libexec and opam-man into a .install file and +# then munge clone-* and create-symlinks into the fixup script. +INSTALL_DESPATCH_opam_END = \ + $(OCAMLRUN) ./ocaml$(EXE) $(STDLIBFLAGS) \ + tools/opam/generate.ml $(INSTALL_MODE) $(OPAM_PACKAGE_NAME) '$(LN)' + +# Generate $(OPAM_PACKAGE_NAME)-clone.sh (INSTALL_MODE=clone) + +# ld.conf is explicitly copied, rather than cloned, to allow (in principle, if +# not in practice) the cloning installation to edit it. +RECORD_clone_ITEM_TO_INSTALL = \ + $(if $(filter runtime/ld.conf Makefile.config, $(1)), true, \ + $(if $(filter libexec,$(2)), \ + $(call RECORD_clone_ITEM_TO_INSTALL,$(1),lib,$(3),$(4),$(5)), \ + $(call ADD_LINE, \ + $(ROOTDIR)/clone-$(2)$(addprefix @,$(subst /,@,$(3))), \ + $(2)$(addprefix /,$(3))/$(if $(4),$(4),$(notdir $(1)))) \ + $(foreach link, $(5), && \ + $(call RECORD_SYMLINK_TO_INSTALL,$(1),$(2),$(3),$(4),$(link))))) + +INSTALL_DESPATCH_clone_ITEM = $(RECORD_ITEM_TO_INSTALL) + +INSTALL_DESPATCH_clone_ITEMS = $(INSTALL_EVALUATE_GLOBS) + +INSTALL_clone_PREFIX = @ + +INSTALL_DESPATCH_clone_RM = @ + +# INSTALL_MKDIR is ignored - INSTALL_DESPATCH_clone_END automatically creates +# directories for each cp file. +INSTALL_DESPATCH_clone_MKDIR = + +INSTALL_DESPATCH_clone_BEGIN = rm -f clone-* create-symlinks + +INSTALL_DESPATCH_clone_END = $(INSTALL_DESPATCH_opam_END) FLEXDLL_SUBMODULE_PRESENT := $(wildcard $(ROOTDIR)/flexdll/Makefile) @@ -508,12 +844,11 @@ $(eval $(call _OCAML_BYTECODE_LIBRARY,$(1))) $(eval $(call _OCAML_NATIVE_LIBRARY,$(1))) endef # OCAML_LIBRARY -# Installing a bytecode executable, with debug information removed -define INSTALL_STRIPPED_BYTE_PROG -$(OCAMLRUN) $(ROOTDIR)/tools/stripdebug$(EXE) $(1) $(1).tmp \ -&& $(INSTALL_PROG) $(1).tmp $(2) \ -&& rm $(1).tmp -endef # INSTALL_STRIPPED_BYTE_PROG +# Strip debug information from a bytecode executable +define STRIP_BYTE_PROG +$(OCAMLRUN) $(ROOTDIR)/tools/stripdebug$(EXE) \ + $(strip $(1)) $(strip $(1)).stripped +endef # STRIP_BYTE_PROG # ocamlc has several mechanisms for linking a bytecode image to the runtime # which executes it. The exact mechanism depends on the platform and the precise @@ -533,3 +868,9 @@ export CYGWIN := $(strip \ export MSYS := $(strip \ $(filter-out winsymlinks winsymlinks:%, $(MSYS)) winsymlinks:nativestrict) endif + +# Invocations of the compilers during the build use -nostdlib which ensures that +# existing installations and the OCAMLLIB/CAMLLIB environment variables don't +# affect the build. There isn't an equivalent flag for the toplevel's +# OCAMLTOP_INCLUDE_PATH, so it's scrubbed from the environment instead. +unexport OCAMLTOP_INCLUDE_PATH diff --git a/api_docgen/Makefile b/api_docgen/Makefile index 07254645812a..4ad898ea6bfd 100644 --- a/api_docgen/Makefile +++ b/api_docgen/Makefile @@ -14,6 +14,7 @@ #************************************************************************** # Used by included Makefiles ROOTDIR = .. +SUBDIR_NAME = api_docgen -include ../Makefile.build_config odoc-%: diff --git a/api_docgen/ocamldoc/Makefile b/api_docgen/ocamldoc/Makefile index 5b1f8946d802..a9781f1aba2d 100644 --- a/api_docgen/ocamldoc/Makefile +++ b/api_docgen/ocamldoc/Makefile @@ -14,6 +14,7 @@ #************************************************************************** # Used by included Makefiles ROOTDIR = ../.. +SUBDIR_NAME = api_docgen/ocamldoc include ../Makefile.common vpath %.mli ../../stdlib $(DOC_COMPILERLIBS_DIRS) $(DOC_STDLIB_DIRS) @@ -120,7 +121,4 @@ build/latex/compilerlibs_input.tex: | build/latex .PHONY: install install: - $(MKDIR) "$(INSTALL_LIBRARIES_MAN_DIR)" - if test -d build/man; then \ - $(INSTALL_DATA) build/man/*.3o "$(INSTALL_LIBRARIES_MAN_DIR)"; \ - fi + $(call INSTALL_ITEMS_OPT, build/man/*.3o, man, $(INSTALL_MANDIR_LIBRARIES)) diff --git a/api_docgen/odoc/Makefile b/api_docgen/odoc/Makefile index c40ed778c41e..8f2c0195571a 100644 --- a/api_docgen/odoc/Makefile +++ b/api_docgen/odoc/Makefile @@ -15,6 +15,7 @@ # Used by included Makefiles ROOTDIR = ../.. +SUBDIR_NAME = api_docgen/odoc include ../Makefile.common @@ -191,13 +192,10 @@ $(ALL_PAGED_DOC:%=build/%.3o.stamp):build/%.3o.stamp:build/%.odocl | build/ # Man pages are the only installed documentation .PHONY: install install: - $(MKDIR) "$(INSTALL_LIBRARIES_MAN_DIR)" - if test -d build/man/libref ; then \ - $(INSTALL_DATA) build/man/libref/* "$(INSTALL_LIBRARIES_MAN_DIR)"; \ - fi - if test -d build/man/compilerlibref ; then \ - $(INSTALL_DATA) build/man/libref/* "$(INSTALL_LIBRARIES_MAN_DIR)"; \ - fi + $(call INSTALL_ITEMS_OPT, \ + build/man/libref/*, man, $(INSTALL_MANDIR_LIBRARIES)) + $(call INSTALL_ITEMS_OPT, \ + build/man/compilerlibref/*, man, $(INSTALL_MANDIR_LIBRARIES)) # Dependencies for stdlib modules. # Use the same dependencies used for compiling .cmx files. diff --git a/man/Makefile b/man/Makefile index 05424bab4737..e43a3451bc66 100644 --- a/man/Makefile +++ b/man/Makefile @@ -14,6 +14,7 @@ #************************************************************************** ROOTDIR = .. +SUBDIR_NAME = man include $(ROOTDIR)/Makefile.common MANPAGES = $(addsuffix .1,\ @@ -22,5 +23,4 @@ MANPAGES = $(addsuffix .1,\ .PHONY: install install: - $(MKDIR) $(call QUOTE_SINGLE,$(INSTALL_PROGRAMS_MAN_DIR)) - $(INSTALL_DATA) $(MANPAGES) $(call QUOTE_SINGLE,$(INSTALL_PROGRAMS_MAN_DIR)) + $(call INSTALL_ITEMS, $(MANPAGES), man, $(INSTALL_MANDIR_PROGRAMS)) diff --git a/ocaml-variants.opam b/ocaml-variants.opam index af4115770a49..4747ba540454 100644 --- a/ocaml-variants.opam +++ b/ocaml-variants.opam @@ -39,18 +39,18 @@ depends: [ # facility is not yet available for other platforms. "host-arch-x86_32" {os != "win32" & arch = "x86_32" & post} ("host-arch-x86_64" {os != "win32" & arch = "x86_64" & post} | - ("host-arch-x86_32" {os != "win32" & arch = "x86_64" & post} & "ocaml-option-32bit" {os != "win32" & arch = "x86_64"})) + ("host-arch-x86_32" {os != "win32" & arch = "x86_64" & post} & "ocaml-option-32bit" {build & os != "win32" & arch = "x86_64"})) "host-arch-unknown" {os != "win32" & arch != "arm32" & arch != "arm64" & arch != "ppc64" & arch != "riscv64" & arch != "s390x" & arch != "x86_32" & arch != "x86_64" & post} # Port selection (Windows) # amd64 mingw-w64 / MSVC - (("arch-x86_64" {os = "win32" & arch = "x86_64"} & - (("system-mingw" & "mingw-w64-shims" {os-distribution = "cygwin" & build}) | - ("system-msvc" & "winpthreads" & "ocaml-option-no-compression" {os = "win32"}))) | + (("arch-x86_64" {build & os = "win32" & arch = "x86_64"} & + (("system-mingw" {build} & "mingw-w64-shims" {os-distribution = "cygwin" & build}) | + ("system-msvc" {build} & "winpthreads" {os = "win32"} & "ocaml-option-no-compression" {build & os = "win32"}))) | # i686 mingw-w64 / MSVC - ("arch-x86_32" {os = "win32"} & "ocaml-option-bytecode-only" {os = "win32"} & - (("system-mingw" & "mingw-w64-shims" {os-distribution = "cygwin" & build}) | - ("system-msvc" & "winpthreads" & "ocaml-option-no-compression" {os = "win32"}))) | + ("arch-x86_32" {build & os = "win32"} & "ocaml-option-bytecode-only" {build & os = "win32"} & + (("system-mingw" {build} & "mingw-w64-shims" {os-distribution = "cygwin" & build}) | + ("system-msvc" {build} & "winpthreads" {os = "win32"} & "ocaml-option-no-compression" {build & os = "win32"}))) | # Non-Windows systems "host-system-other" {os != "win32" & post}) @@ -81,7 +81,7 @@ build: [ "--enable-runtime-search" "--enable-runtime-search-target=fallback" "--with-flexdll=%{flexdll:share}%" {os = "win32" & flexdll:installed} - "--with-winpthreads-msvc=%{winpthreads:share}%" {system-msvc:installed} + "--with-winpthreads-msvc=%{winpthreads:share}%" {winpthreads:installed & system-msvc:installed} "-C" "--with-afl" {ocaml-option-afl:installed} "--disable-native-compiler" {ocaml-option-bytecode-only:installed} @@ -107,8 +107,9 @@ build: [ "--disable-warn-error" ] [make "-j%{jobs}%"] + [make "INSTALL_MODE=opam" "install"] ] -install: [make "install"] +install: ["sh" "./%{name}%-fixup.sh" prefix] depopts: [ "ocaml-option-32bit" "ocaml-option-afl" diff --git a/otherlibs/Makefile b/otherlibs/Makefile index d76643bd297b..83dce3235d50 100644 --- a/otherlibs/Makefile +++ b/otherlibs/Makefile @@ -14,6 +14,7 @@ #************************************************************************** ROOTDIR=.. +SUBDIR_NAME=otherlibs include $(ROOTDIR)/Makefile.common # Although the OTHERLIBS variable is defined in ../Makefile.config, diff --git a/otherlibs/Makefile.otherlibs.common b/otherlibs/Makefile.otherlibs.common index 4ffb9fd63f8a..d679b65f2db8 100644 --- a/otherlibs/Makefile.otherlibs.common +++ b/otherlibs/Makefile.otherlibs.common @@ -16,6 +16,7 @@ # Common Makefile for otherlibs ROOTDIR=../.. +SUBDIR_NAME=otherlibs/$(LIBNAME) include $(ROOTDIR)/Makefile.common include $(ROOTDIR)/Makefile.best_binaries @@ -109,48 +110,40 @@ lib$(CLIBNAME_BYTECODE).$(A): $(COBJS) lib$(CLIBNAME_NATIVE).$(A): $(COBJS) $(V_OCAMLMKLIB)$(MKLIB) -oc $(CLIBNAME_NATIVE) $(COBJS_NATIVE) $(LDOPTS) -INSTALL_LIBDIR_LIBNAME = $(INSTALL_LIBDIR)/$(LIBNAME) - install:: ifneq "$(STUBSLIB_BYTECODE)" "" ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" - $(INSTALL_PROG) $(STUBSDLL) "$(INSTALL_STUBLIBDIR)" + $(call INSTALL_ITEMS, $(STUBSDLL), stublibs) endif - $(INSTALL_DATA) $(STUBSLIB_BYTECODE) "$(INSTALL_LIBDIR)/" + $(call INSTALL_ITEMS, $(STUBSLIB_BYTECODE), lib) endif # If installing over a previous OCaml version, ensure the library is removed # from the previous installation. - rm -f $(addprefix "$(INSTALL_LIBDIR)"/, \ - $(LIBNAME).cma $(CMIFILES) \ - $(CMIFILES:.cmi=.mli) $(CMIFILES:.cmi=.cmti) \ - $(CAMLOBJS_NAT) $(LIBNAME).cmxa $(LIBNAME).cmxs $(LIBNAME).$(A)) - $(MKDIR) "$(INSTALL_LIBDIR_LIBNAME)" - $(INSTALL_DATA) \ - $(LIBNAME).cma $(CMIFILES) META \ - "$(INSTALL_LIBDIR_LIBNAME)/" + $(call INSTALL_RM, \ + $(addprefix "$(INSTALL_LIBDIR)"/, \ + $(LIBNAME).cma $(CMIFILES) \ + $(CMIFILES:.cmi=.mli) $(CMIFILES:.cmi=.cmti) \ + $(CAMLOBJS_NAT) $(LIBNAME).cmxa $(LIBNAME).cmxs $(LIBNAME).$(A))) + $(call INSTALL_ITEMS, $(LIBNAME).cma $(CMIFILES) META, lib, $(LIBNAME)) ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - $(CMIFILES:.cmi=.mli) \ - $(CMIFILES:.cmi=.cmti) \ - "$(INSTALL_LIBDIR_LIBNAME)/" + $(call INSTALL_ITEMS, $(CMIFILES:.cmi=.mli) $(CMIFILES:.cmi=.cmti), \ + lib, $(LIBNAME)) +endif +ifneq "$(HEADERS)" "" + $(call INSTALL_ITEMS, $(HEADERS), lib, $(INSTALL_LIBDIR_CAML)) endif - if test -n "$(HEADERS)"; then \ - $(INSTALL_DATA) $(HEADERS) "$(INSTALL_INCDIR)/"; \ - fi installopt: - $(INSTALL_DATA) \ - $(CAMLOBJS_NAT) $(LIBNAME).cmxa $(LIBNAME).$(A) \ - "$(INSTALL_LIBDIR_LIBNAME)/" - if test -f $(LIBNAME).cmxs; then \ - $(INSTALL_PROG) $(LIBNAME).cmxs "$(INSTALL_LIBDIR_LIBNAME)"; \ - fi - if test -f dll$(CLIBNAME_NATIVE)$(EXT_DLL); then \ - $(INSTALL_PROG) \ - dll$(CLIBNAME_NATIVE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)"; \ - fi + $(call INSTALL_ITEMS, \ + $(CAMLOBJS_NAT) $(LIBNAME).cmxa $(LIBNAME).$(A), lib, $(LIBNAME)) +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" + $(call INSTALL_ITEMS, $(LIBNAME).cmxs, libexec, $(LIBNAME)) +ifeq "$(SUFFIXING)" "false" + $(call INSTALL_ITEMS, dll$(CLIBNAME_NATIVE)$(EXT_DLL), stublibs) +endif +endif ifneq "$(STUBSLIB_NATIVE)" "" - $(INSTALL_DATA) $(STUBSLIB_NATIVE) "$(INSTALL_LIBDIR)/" + $(call INSTALL_ITEMS, $(STUBSLIB_NATIVE), lib) endif partialclean: diff --git a/otherlibs/systhreads/Makefile b/otherlibs/systhreads/Makefile index 2b97c9c62c83..617093bb4ecd 100644 --- a/otherlibs/systhreads/Makefile +++ b/otherlibs/systhreads/Makefile @@ -14,6 +14,7 @@ #************************************************************************** ROOTDIR=../.. +SUBDIR_NAME=otherlibs/systhreads include $(ROOTDIR)/Makefile.common include $(ROOTDIR)/Makefile.best_binaries @@ -103,30 +104,22 @@ clean: partialclean distclean: clean rm -f META -INSTALL_THREADSLIBDIR=$(INSTALL_LIBDIR)/$(LIBNAME) - install: ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" - $(INSTALL_PROG) $(DLLTHREADS) "$(INSTALL_STUBLIBDIR)" + $(call INSTALL_ITEMS, $(DLLTHREADS), stublibs) endif - $(INSTALL_DATA) libthreads.$(A) "$(INSTALL_LIBDIR)" - $(MKDIR) "$(INSTALL_THREADSLIBDIR)" - $(INSTALL_DATA) \ - $(CMIFILES) threads.cma META \ - "$(INSTALL_THREADSLIBDIR)" + $(call INSTALL_ITEMS, libthreads.$(A), lib) + $(call INSTALL_ITEMS, $(CMIFILES) threads.cma META, lib, $(LIBNAME)) ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - $(CMIFILES:.cmi=.cmti) \ - "$(INSTALL_THREADSLIBDIR)" - $(INSTALL_DATA) $(MLIFILES) "$(INSTALL_THREADSLIBDIR)" + $(call INSTALL_ITEMS, $(CMIFILES:.cmi=.cmti), lib, $(LIBNAME)) + $(call INSTALL_ITEMS, $(MLIFILES), lib, $(LIBNAME)) endif - $(INSTALL_DATA) caml/threads.h "$(INSTALL_INCDIR)" + $(call INSTALL_ITEMS, caml/threads.h, lib, $(INSTALL_LIBDIR_CAML)) installopt: - $(INSTALL_DATA) libthreadsnat.$(A) "$(INSTALL_LIBDIR)" - $(INSTALL_DATA) \ - $(THREADS_NCOBJS) threads.cmxa threads.$(A) \ - "$(INSTALL_THREADSLIBDIR)" + $(call INSTALL_ITEMS, libthreadsnat.$(A), lib) + $(call INSTALL_ITEMS, $(THREADS_NCOBJS) threads.cmxa threads.$(A), \ + lib, $(LIBNAME)) %.cmi: %.mli $(V_OCAMLC)$(CAMLC) -c $(COMPFLAGS) $< diff --git a/stdlib/Makefile b/stdlib/Makefile index 1e775f4fd17d..fcd82f18e157 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -14,6 +14,7 @@ #************************************************************************** ROOTDIR = .. +SUBDIR_NAME = stdlib # NOTE: it is important that the OCAMLDEP variable is defined *before* # Makefile.common gets included, so that its local definition here # take precedence over its general shared definitions in Makefile.common. @@ -47,6 +48,9 @@ endif OPTCOMPILER=$(ROOTDIR)/ocamlopt$(EXE) CAMLOPT=$(OCAMLRUN) $(OPTCOMPILER) +# At present, only META is installed to the package directory +LIBNAME = stdlib + include StdlibModules OBJS=$(addsuffix .cmo,$(STDLIB_MODULES)) @@ -60,29 +64,21 @@ all: stdlib.cma std_exit.cmo $(HEADER_NAME) allopt: stdlib.cmxa std_exit.cmx opt.opt: allopt -INSTALL_STDLIB_META_DIR=$(DESTDIR)$(LIBDIR)/stdlib - .PHONY: install install:: - $(INSTALL_DATA) \ - stdlib.cma std_exit.cmo *.cmi "$(INSTALL_LIBDIR)" - $(MKDIR) "$(INSTALL_STDLIB_META_DIR)" - $(INSTALL_DATA) META "$(INSTALL_STDLIB_META_DIR)" + $(call INSTALL_ITEMS, stdlib.cma std_exit.cmo *.cmi, lib) + $(call INSTALL_ITEMS, META, lib, $(LIBNAME)) ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - *.cmt *.cmti *.mli *.ml *.ml.in \ - "$(INSTALL_LIBDIR)" + $(call INSTALL_ITEMS, *.cmt *.cmti *.mli *.ml *.ml.in, lib) endif - $(INSTALL_DATA) $(HEADER_NAME) "$(INSTALL_LIBDIR)/$(HEADER_NAME)" + $(call INSTALL_ITEMS, $(HEADER_NAME), lib) .PHONY: installopt installopt: installopt-default .PHONY: installopt-default installopt-default: - $(INSTALL_DATA) \ - stdlib.cmxa stdlib.$(A) std_exit.$(O) *.cmx \ - "$(INSTALL_LIBDIR)" + $(call INSTALL_ITEMS, stdlib.cmxa stdlib.$(A) std_exit.$(O) *.cmx, lib) MANGLING = $(filter true,$(SUFFIXING)) runtime-launch-info: tmpheader.exe diff --git a/tools/ci/actions/runner.sh b/tools/ci/actions/runner.sh index f3a0f749dfa2..05805ae7e20c 100755 --- a/tools/ci/actions/runner.sh +++ b/tools/ci/actions/runner.sh @@ -16,7 +16,8 @@ set -xe -PREFIX=~/local +# The prefix is designed to be usable as an opam local switch +PREFIX=~/local/_opam MAKE="make $MAKE_ARG" SHELL=dash @@ -56,11 +57,12 @@ EOF # $CONFIG_ARG also appears last to allow settings specified here to be # overridden by the workflows. call-configure --prefix="$PREFIX" \ + --docdir="$PREFIX/doc/ocaml" \ --enable-flambda-invariants \ --enable-ocamltest \ --enable-native-toplevel \ --disable-dependency-generation \ - $CONFIG_ARG + -C $CONFIG_ARG } Build () { @@ -125,7 +127,26 @@ API_Docs () { } Install () { - $MAKE install + $MAKE INSTALL_MODE=list install | grep '^->' | sort | uniq -d > duplicates + if [ -s duplicates ]; then + echo "The installation duplicates targets:" + cat duplicates + exit 1 + fi + rm duplicates + $MAKE DESTDIR="$PWD/install" install + find $PWD/install -name _opam -type d + $MAKE INSTALL_MODE=clone install + ret="$PWD" + script="$PWD/ocaml-compiler-clone.sh" + cd "$(find $PWD/install -name _opam -type d)" + mkdir -p "share/ocaml" + cp "$ret/config.status" "$ret/config.cache" "share/ocaml" + cp "$ret/ocaml-compiler-clone.sh" "share/ocaml/clone" + sh $script "$PWD" ~/local/_opam + cd "$ret" + rm -rf install + rm ocaml-compiler-clone.sh } target_libdir_is_relative='^ *TARGET_LIBDIR_IS_RELATIVE *= *false' @@ -225,7 +246,7 @@ Checks () { # we would need to redo (small parts of) world.opt afterwards to # use the compiler again $MAKE check_all_arches - # Ensure that .gitignore is up-to-date - this will fail if any untreacked or + # Ensure that .gitignore is up-to-date - this will fail if any untracked or # altered files exist. test -z "$(git status --porcelain)" # check that the 'clean' target also works @@ -234,7 +255,9 @@ Checks () { $MAKE -C manual distclean # check that the `distclean` target definitely cleans the tree $MAKE distclean - # Check the working tree is clean + # Check the working tree is clean - config.cache is intentionally not deleted + # by any of the clean targets + rm config.cache test -z "$(git status --porcelain)" # Check that there are no ignored files test -z "$(git ls-files --others -i --exclude-standard)" @@ -298,6 +321,24 @@ BasicCompiler () { ReportBuildStatus 0 } +CreateSwitch () { + # This can be switched to use the Ubuntu package when Ubuntu 26.04 is deployed + # (opam 2.1.5 in Ubuntu 24.04 is too old) + curl -Lo opam \ + 'https://github.com/ocaml/opam/releases/download/2.4.1/opam-2.4.1-x86_64-linux' + chmod +x opam + ./opam init --cli=2.4 --bare --disable-sandboxing --yes --auto-setup + # This is intentionally done before the switch is created - if the install + # target creates _opam then the switch creation will fail. + $MAKE INSTALL_MODE=opam OPAM_PACKAGE_NAME=ocaml-variants install + # These commands intentionally run using opam's "default" CLI + ./opam switch create ~/local --empty + ./opam switch --switch ~/local set-invariant --no-action ocaml-option-flambda + ./opam pin add --switch ~/local --no-action --kind=path ocaml-variants . + ./opam install --switch ~/local --yes --assume-built ocaml-variants + ./opam exec --switch ~/local -- ocamlopt -v +} + case $1 in configure) Configure;; build) Build;; @@ -311,6 +352,7 @@ re-test-in-prefix) Re-Test-In-Prefix;; manual) BuildManual;; other-checks) Checks;; basic-compiler) BasicCompiler;; +opam) CreateSwitch;; *) echo "Unknown CI instruction: $1" exit 1;; esac diff --git a/tools/ci/appveyor/appveyor_build.cmd b/tools/ci/appveyor/appveyor_build.cmd index 5f18b1cffed2..aaf6f929791a 100644 --- a/tools/ci/appveyor/appveyor_build.cmd +++ b/tools/ci/appveyor/appveyor_build.cmd @@ -22,7 +22,7 @@ chcp 65001 > nul set BUILD_PREFIX=🐫реализация -set OCAMLROOT=%PROGRAMFILES%\Бактріан🐫 +set OCAMLROOT=C:\Бактріан🐫 if "%1" neq "install" goto %1 setlocal enabledelayedexpansion @@ -69,7 +69,13 @@ if %CYGWIN_UPGRADE_REQUIRED% equ 1 ( ) ) if "%CYGWIN_INSTALL_PACKAGES%" neq "" "%CYG_ROOT%\setup-x86_64.exe" --quiet-mode --no-shortcuts --no-startmenu --no-desktop --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" %CYGWIN_FLAGS% --packages %CYGWIN_INSTALL_PACKAGES:~1% -for %%P in (%CYGWIN_COMMANDS%) do "%CYG_ROOT%\bin\%%P.exe" --version 2> nul > nul || set CYGWIN_UPGRADE_REQUIRED=1 +for %%P in (%CYGWIN_COMMANDS%) do ( + if %%P equ unzip ( + "%CYG_ROOT%\bin\%%P.exe" -v 2> nul > nul || set CYGWIN_UPGRADE_REQUIRED=1 + ) else ( + "%CYG_ROOT%\bin\%%P.exe" --version 2> nul > nul || set CYGWIN_UPGRADE_REQUIRED=1 + ) +) "%CYG_ROOT%\bin\bash.exe" -lc "cygcheck -dc %CYGWIN_PACKAGES%" if %CYGWIN_UPGRADE_REQUIRED% equ 1 ( echo Cygwin package upgrade required - please go and drink coffee @@ -87,6 +93,11 @@ if not defined SDK ( if "%PORT%" equ "mingw32" set SDK=call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" ) %SDK% +rem The environment block becomes very large on AppVeyor, which can cause +rem problems for xargs in Cygwin. These two environment variables from the SDK +rem infrastructure can be safely junked to reduce the size of the block. +set __VSCMD_PREINIT_PATH= +set EXTERNAL_INCLUDE= goto :EOF :install @@ -103,6 +114,9 @@ if "%BOOTSTRAP_FLEXDLL%" equ "true" ( cd "%APPVEYOR_BUILD_FOLDER%" appveyor DownloadFile "https://github.com/ocaml/flexdll/archive/%FLEXDLL_VERSION%.tar.gz" -FileName "flexdll.tar.gz" || exit /b 1 appveyor DownloadFile "https://github.com/ocaml/flexdll/releases/download/%FLEXDLL_VERSION%/flexdll-bin-%FLEXDLL_VERSION%.zip" -FileName "flexdll.zip" || exit /b 1 +appveyor DownloadFile "https://github.com/ocaml/opam/releases/download/2.4.1/opam-2.4.1-x86_64-windows.exe" -FileName "opam.exe" || exit /b 1 +md "%PROGRAMFILES%\flexdll" +move opam.exe "%PROGRAMFILES%\flexdll" rem flexdll.zip is processed here, rather than in appveyor_build.sh because the rem unzip command comes from MSYS2 (via Git for Windows) and it has to be rem invoked via cmd /c in a bash script which is weird(er). @@ -115,8 +129,8 @@ rem in the list just so that the Cygwin version is always displayed on the log). rem CYGWIN_COMMANDS is a corresponding command to run with --version to test rem whether the package works. This is used to verify whether the installation rem needs upgrading. -set CYGWIN_PACKAGES=cygwin make diffutils -set CYGWIN_COMMANDS=cygcheck make diff +set CYGWIN_PACKAGES=cygwin make diffutils unzip +set CYGWIN_COMMANDS=cygcheck make diff unzip if "%PORT%" equ "mingw32" ( rem mingw64-i686-runtime does not need explicitly installing, but it's useful rem to have the version reported. diff --git a/tools/ci/appveyor/appveyor_build.sh b/tools/ci/appveyor/appveyor_build.sh index 459945dfe34f..3d0f21981996 100755 --- a/tools/ci/appveyor/appveyor_build.sh +++ b/tools/ci/appveyor/appveyor_build.sh @@ -70,7 +70,10 @@ function set_configuration { CACHE_FILE_PREFIX="$CACHE_DIRECTORY/config.cache-$1" CACHE_FILE="$CACHE_FILE_PREFIX-$CACHE_KEY" - args=('--cache-file' "$CACHE_FILE" '--prefix' "$2" '--enable-ocamltest') + args=('--cache-file' "$CACHE_FILE" \ + '--prefix' "$2/_opam" \ + '--docdir' "$2/_opam/doc/ocaml" \ + '--enable-ocamltest') case "$1" in cygwin*) @@ -107,6 +110,8 @@ function set_configuration { if ((failed)) ; then cat config.log ; exit $failed ; fi fi + cp "$CACHE_FILE" config.cache + # FILE=$(pwd | cygpath -f - -m)/Makefile.config # run "Content of $FILE" cat Makefile.config } @@ -114,6 +119,7 @@ function set_configuration { PARALLEL_URL='https://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel' APPVEYOR_BUILD_FOLDER=$(echo "$APPVEYOR_BUILD_FOLDER" | cygpath -f -) FLEXDLLROOT="$PROGRAMFILES/flexdll" +export OPAMSWITCH="$OCAMLROOT" if [[ $BOOTSTRAP_FLEXDLL = 'false' ]] ; then case "$PORT" in @@ -198,6 +204,39 @@ case "$1" in make -C "$FULL_BUILD_PREFIX-$PORT/testsuite" SHOW_TIMINGS=1 all fi run "install $PORT" $MAKE -C "$FULL_BUILD_PREFIX-$PORT" install + make -C "$FULL_BUILD_PREFIX-$PORT" INSTALL_MODE=clone install + ( + cd "$OCAMLROOT" + mv _opam destdir + #ret="$PWD" + #script="$PWD/ocaml-compiler-clone.sh" + #cd "$(find $PWD/install -name _opam -type d)" + mkdir -p "destdir/share/ocaml" + cp "$FULL_BUILD_PREFIX-$PORT/config."{cache,status} 'destdir/share/ocaml/' + cp "$FULL_BUILD_PREFIX-$PORT/ocaml-compiler-clone.sh" \ + 'destdir/share/ocaml/clone' + cd destdir + sh "$FULL_BUILD_PREFIX-$PORT/ocaml-compiler-clone.sh" "$PWD" \ + "$OCAMLROOT/_opam" + ) + rm -rf "$OCAMLROOT" + $MAKE -C "$FULL_BUILD_PREFIX-$PORT" OPAM_PACKAGE_NAME=ocaml-variants \ + INSTALL_MODE=opam install + ( + cd "$FULL_BUILD_PREFIX-$PORT" + export PATH="$FLEXDLLROOT:$PATH" + opam init --cli=2.4 --bare --yes --disable-sandboxing --auto-setup \ + --cygwin-local-install + # These commands intentionally run using opam's "default" CLI + opam switch create "$OPAMSWITCH" --empty + opam pin add --no-action --kind=path ocaml-variants . + opam pin add --no-action flexdll flexdll + opam install --yes flexdll winpthreads + opam install --yes --assume-built ocaml-variants + git checkout -- ocaml-variants.install + rm -f config.cache ocaml-variants-fixup.sh ocaml-compiler-clone.sh + opam exec -- ocamlc -v + ) run "test $PORT in prefix" \ $MAKE -f Makefile.test -C "$FULL_BUILD_PREFIX-$PORT/testsuite/in_prefix" \ test-in-prefix diff --git a/tools/opam/generate.ml b/tools/opam/generate.ml new file mode 100644 index 000000000000..5c8d78e69f24 --- /dev/null +++ b/tools/opam/generate.ml @@ -0,0 +1,237 @@ +(**************************************************************************) +(* *) +(* OCaml *) +(* *) +(* David Allsopp, University of Cambridge & Tarides *) +(* *) +(* Copyright 2025 David Allsopp Ltd. *) +(* *) +(* All rights reserved. This file is distributed under the terms of *) +(* the GNU Lesser General Public License version 2.1, with the *) +(* special exception on linking described in the file LICENSE. *) +(* *) +(**************************************************************************) + +(* This script is called from the root of the repository at the end of + `make INSTALL_MODE= install` and is responsible for converting + the various files generated by the installation backend into final output. + Parameters are the following Makefile variables: + $1 = $(INSTALL_MODE) (opam or clone) + $2 = $(OPAM_PACKAGE_NAME) + $3 = $(LN) *) + +let exit_because fmt = Printf.ksprintf (fun s -> prerr_endline s; exit 1) fmt + +let () = + if Array.length Sys.argv <> 4 + || Sys.argv.(1) <> "clone" && Sys.argv.(1) <> "opam" then begin + exit_because "Invalid command line arguments" + end + +let mode = Sys.argv.(1) +let package = Sys.argv.(2) +let ln_command = Sys.argv.(3) + +let output_endline oc = Printf.kfprintf (fun oc -> output_char oc '\n') oc + +let write_install_lines oc file = + In_channel.with_open_text file @@ + In_channel.fold_lines (fun _ -> output_endline oc " %s") () + +let remove_file = Sys.remove + +let output_section oc section = + let file = "opam-" ^ section in + if Sys.file_exists file then begin + let section = + if section = "lib" || section = "libexec" then + section ^ "_root" + else + section + in + output_endline oc {|%s: [ +%a]|} section write_install_lines file; + remove_file file + end + +(* See note in Makefile.common *) +let valid_in_path = function '\'' | '"' | '\\' -> false | _ -> true +let valid_in_section c = c <> '@' && valid_in_path c +let valid_path path = + if String.for_all valid_in_path path then + path + else + exit_because "%S contains characters invalid in a path" path +let valid_section dir = + if String.for_all valid_in_section dir then + dir + else + exit_because "%S contains characters invalid in a section" dir + +(* [generate_install file] processes then erases opam-bin, opam-lib opam-libexec + and opam-man to produce [file] *) +let generate_install file = + Out_channel.with_open_text file @@ fun oc -> + List.iter (output_section oc) ["bin"; "lib"; "libexec"; "man"]; + output_endline oc {|share_root: [ + "config.cache" {"ocaml/config.cache"} + "config.status" {"ocaml/config.status"} +]|} + +(* [process_clone oc process] processes clone-* in the current directory, + emitting mkdir commands to [oc] and passing the directory name and a channel + set to the start of each clone file to [process]. The clone files are erased + after processing. [prefix] is the literal string to place in double-quotes + for the installation prefix (either ["$1"] or ["$2"]). *) +let process_clone oc ~prefix process = + let process_file file = + if String.starts_with ~prefix:"clone-" file then begin + let dir = + String.map (function '@' -> '/' | c -> c) + (String.sub file 6 (String.length file - 6)) + |> valid_section + in + output_endline oc {|mkdir -p "%s"'/%s'|} prefix dir; + In_channel.with_open_text file @@ process oc dir; + remove_file file + end + in + let files = Sys.readdir Filename.current_dir_name in + Array.sort String.compare files; + Array.iter process_file files + +(* [process_symlinks oc ~mkdir ~prefix] processes create-symlinks, if it exists, + writing any required mkdir commands to [oc] if [mkdir = true] and also the + appropriate ln / mklink commands. create-symlinks is erased after + processing. [prefix] is the literal string to place in double-quotes for the + installation prefix (either ["$1"] or ["$2"]). *) +let process_symlinks oc ~mkdir ~prefix = + let module StringSet = Set.Make(String) in + let file = "create-symlinks" in + if Sys.file_exists file then + let lines = + let parse acc line = + match String.split_on_char ' ' line with + | [dir; target; source] -> + (valid_section dir, valid_path target, valid_path source)::acc + | _ -> + exit_because "Invalid line encountered in create-symlinks" + in + In_channel.with_open_text file @@ fun ic -> + List.rev (In_channel.fold_lines parse [] ic) + in + output_endline oc {|cd "%s"|} prefix; + let _ = + let create_dir seen (dir, _, _) = + if not (StringSet.mem dir seen) && String.contains dir '/' then + output_endline oc {|mkdir -p '%s'|} dir; + StringSet.add dir seen + in + List.fold_left create_dir StringSet.empty (if mkdir then lines else []) + in + if not Sys.win32 then + let ln (dir, target, source) = + output_endline oc {|%s '%s' '%s/%s'|} ln_command target dir source + in + List.iter ln lines + else begin + let mklink (dir, target, source) = + (* Convert all slashes to _two_ backslashes *) + let to_backslashes oc s = + output_string oc (String.concat {|\\|} (String.split_on_char '/' s)) + in + output_endline oc + {| cmd /c "mklink %a\\%s %s"|} to_backslashes dir source target + and cp (dir, target, source) = + output_endline oc {| $CP '%s/%s' '%s/%s'|} dir target dir source + in + output_endline oc {|cmd /c "mklink __ln_test mklink-test"|}; + output_endline oc {|if test -L "%s/__ln_test"; then|} prefix; + List.iter mklink lines; + output_endline oc {|else|}; + List.iter cp lines; + output_endline oc {|fi|}; + output_endline oc {|rm -f __ln_test|} + end; + remove_file file + +let copy_files oc dir = + In_channel.fold_lines (fun _ line -> + match String.split_on_char ' ' line with + | [source; dest] -> + let source = valid_path source in + let dest = valid_path dest in + output_endline oc {|cp '%s' "$1"'/%s/%s'|} source dir dest + | _ -> + exit_because "Invalid line encountered in clone files") () + +let clone_files oc dir ic = + output_endline oc + {|src="$1" dest="$2"'/%s' xargs sh "$2/clone-files" <<'EOF'|} dir; + In_channel.fold_lines (fun _ -> output_endline oc "%s") () ic; + output_endline oc {|EOF|} + +let () = + if mode = "opam" then begin + generate_install (package ^ ".install"); + (* The script must be written with Unix line-endings on Windows *) + Out_channel.with_open_bin (package ^ "-fixup.sh") @@ fun oc -> + output_endline oc {|#!/bin/sh +set -eu|}; + process_clone oc ~prefix:"$1" copy_files; + process_symlinks oc ~mkdir:true ~prefix:"$1" + end else begin + (* Don't pass -p to cp on Windows - it's never going to be relevant (no + execute bit which needs preserving) and there are scenarios in which it's + more likely to fail than add anything useful (especially if copying from + a Cygwin-managed build directory to /cygdrive) *) + let preserve = if Sys.win32 then "" else "p" in + (* The script must be written with Unix line-endings on Windows *) + Out_channel.with_open_bin (package ^ "-clone.sh") @@ fun oc -> + output_endline oc {|#!/bin/sh +set -eu +mkdir -p "$2" +rm -f "$2/__cp_test" "$2/__ln_test" +if cp --reflink=always "$1/doc/ocaml/LICENSE" "$2/__cp_test" 2>/dev/null; then + rm -f "$2/__cp_test" + CP='cp --reflink=always -%sf' + if ! test -e "$2/clone-files"; then + echo 'cd "$src" && '"$CP"' "$@" "$dest/"' > "$2/clone-files" + fi +else + CP='cp -%sf' + if ! test -e "$2/clone-files"; then + if ln -f "$1/doc/ocaml/LICENSE" "$2/__ln_test" 2>/dev/null; then + rm -f "$2/__ln_test" + echo 'cd "$src" && ln -f "$@" "$dest/"' > "$2/clone-files" + else + echo 'cd "$src" && '"$CP"' "$@" "$dest/"' > "$2/clone-files" + fi + fi +fi|} preserve preserve; + Out_channel.with_open_text "clone-share@ocaml" (fun oc -> + output_endline oc "share/ocaml/clone"; + if Sys.file_exists "config.cache" then + output_endline oc "share/ocaml/config.cache"); + process_clone oc ~prefix:"$2" clone_files; + (* ld.conf is a configuration file, so is always copied. + Makefile.config and config.status will both contain the original + prefix, which must be updated. *) + output_endline oc {|cp "$1/lib/ocaml/ld.conf" "$2/lib/ocaml/ld.conf" +cat > "$2/prefix.awk" <<'ENDAWK' +{ + rest = $0 + while ((p = index(rest, ENVIRON["O"]))) { + printf "%%s%%s", substr(rest, 1, p-1), ENVIRON["N"] + rest = substr(rest, p + length(ENVIRON["O"])) + } + print rest +} +ENDAWK +prefix="$(sed -ne 's/^prefix *= *//p' "$1/lib/ocaml/Makefile.config")" +for file in lib/ocaml/Makefile.config share/ocaml/config.status; do + O="$prefix" N="$2" awk -f "$2/prefix.awk" "$1/$file" > "$2/$file" +done +rm -f "$2/clone-files" "$2/prefix.awk"|}; + process_symlinks oc ~mkdir:false ~prefix:"$2" + end diff --git a/tools/opam/process.sh b/tools/opam/process.sh new file mode 100644 index 000000000000..e529259c150d --- /dev/null +++ b/tools/opam/process.sh @@ -0,0 +1,190 @@ +#!/bin/sh +#************************************************************************** +#* * +#* OCaml * +#* * +#* David Allsopp, University of Cambridge & Tarides * +#* * +#* Copyright 2025 David Allsopp Ltd. * +#* * +#* All rights reserved. This file is distributed under the terms of * +#* the GNU Lesser General Public License version 2.1, with the * +#* special exception on linking described in the file LICENSE. * +#* * +#************************************************************************** + +set -eu + +# POSIX.1-2024 (Issue 8) lifts this from being a bashism. The sub-shell dance is +# necessary because set is a builtin and is permitted to abort the script +# unconditionally on error. +if (set -o pipefail 2> /dev/null); then + set -o pipefail +fi + +# This script is responsible for building and cloning OCaml installations. It is +# invoked by both the build and install sections of an opam package. +# $1 = make command (the `make` variable in opam). This should be the path to +# a binary only and is invoked without word-splitting (i.e. any +# additional arguments should be passed in $2 and the command is invoked +# "$1"). +# $2 = additional arguments passed to "$1". This variable will be used +# unquoted - arguments with spaces cannot be passed. From the build +# section, this allows the -j argument to be specified. For the install +# section, this argument must be "install". +# $3 = opam build-id variable of this package. +# $4 = name of the opam package to be used when generating .install and +# .config files. +# The remaining arguments depend on the value of $2. When it is "install": +# $5 = installation prefix, which may be a native Windows path, rather than a +# Cygwin path. +# When $2 is not "install" (the build opam section): +# $5 = "enabled" if cloning the compiler from an existing switch is permitted +# and "disabled" to force the compiler to be built from sources. +# $6, and any further arguments are additional options to pass to `configure` +# if the compiler is built from sources. + +make="$1" +make_args="$2" +build_id="$3" +package_name="$4" + +if [ x"$make_args" = 'xinstall' ]; then + prefix="$5" + + echo "📦 Installing the compiler to $prefix" + if [ -e 'config.status' ]; then + echo "📜 Using make install" + "$make" install + else + origin="$(tail -n 1 build-id)" + origin_prefix="$(opam var --safe --switch="$origin" prefix | tr -d '\r')" + echo "🪄 Duplicating $origin_prefix" + sh "$origin_prefix/share/ocaml/clone" "$origin_prefix" "$prefix" + fi + + exit 0 +fi + +# Build the package + +cloning="$5" +shift 5 +# "$@" now expands to the correctly-quoted arguments to pass to configure + +origin='' +clone_mechanism='' +if [ x"$cloning" = 'xenabled' ]; then + echo "🕵️ Searching for a switch containing build-id $build_id" + + if [ -e "$OPAM_SWITCH_PREFIX/share/ocaml/build-id" ]; then + switch="$(tail -n 1 "$OPAM_SWITCH_PREFIX/share/ocaml/build-id")" + if [ -n "$switch" ]; then + switch_share_dir="$(opam var --safe --switch="$switch" share \ + | tr -d '\r')" + switch_build_id="$switch_share_dir/ocaml/build-id" + if [ -e "$switch_build_id" ]; then + if [ x"$build_id" = x"$(head -n 1 "$switch_build_id")" ]; then + echo "🔁 Prefer to re-clone from $switch" + echo "$switch" > opam-switches + origin="$switch" + if ln "$switch_build_id" __cp_test 2>/dev/null; then + rm __cp_test + clone_mechanism='hard-linking' + fi + fi + fi + fi + fi + + echo "🐫 Requesting list of switches from opam" + opam switch list --safe --short | tr -d '\r' | grep -Fxv "$OPAMSWITCH" \ + >> opam-switches 2> /dev/null || true + + while IFS= read -r switch; do + switch_share_dir="$(opam var --safe --switch="$switch" share | tr -d '\r')" + switch_build_id="$switch_share_dir/ocaml/build-id" + if [ -e "$switch_build_id" ]; then + if [ x"$build_id" = x"$(head -n 1 "$switch_build_id")" ]; then + # There are three ways of cloning a switch: + # - Copy-on-Write (cp --reflink=always) + # - Hard linking + # - Copy + # Copy-on-Write is the ideal - virtually no space overhead, but with + # defence against accidental subsequent alterations. Hard linking is + # preferred over copying for the space-saving, and because the + # compiler should not being subsequently altered. + if cp --reflink=always "$switch_build_id" __cp_test 2>/dev/null; then + rm __cp_test + echo "📝 - can reflink from: $switch" + origin="$switch" + clone_mechanism='copy-on-write' + break + elif ln "$switch_build_id" __cp_test 2>/dev/null; then + rm __cp_test + if [ -z "$clone_mechanism" ]; then + echo "🔗 - can hard link from: $switch" + origin="$switch" + clone_mechanism='hard-linking' + fi + elif [ -z "$origin" ]; then + echo "📄 - can copy from: $switch" + origin="$switch" + fi + elif [ -z "$origin" ]; then + echo "⛔ - different compiler: $switch" + fi + fi + done < opam-switches +fi + +{ echo "$build_id"; echo "$origin" ; } > build-id + +if [ -n "$origin" ]; then + + echo "🧬 Will clone the compiler from $origin" + test -n "$clone_mechanism" || clone_mechanism='copying' + + cloned='true' + clone_source="$(sed -e '1d;s/\\/\\\\/g;s/%/%%/g;s/"/\\"/g' build-id)" + case "$origin" in + */*|*\\*) clone_source="local switch $clone_source";; + *) clone_source="global switch $clone_source";; + esac + + cat > "$package_name.install" <<'EOF' +share_root: [ + "build-id" {"ocaml/build-id"} +] +EOF + +else + + echo "🏗️ Will build the compiler from sources" + + cloned='false' + clone_source='' + + ./configure --cache-file=config.cache "$@" + "$make" $make_args + "$make" OPAM_PACKAGE_NAME=ocaml-compiler INSTALL_MODE=clone install + + cat > "$package_name.install" <<'EOF' +share_root: [ + "build-id" {"ocaml/build-id"} + "ocaml-compiler-clone.sh" {"ocaml/clone"} + "config.cache" {"ocaml/config.cache"} + "config.status" {"ocaml/config.status"} +] +EOF +fi + +# Create the .config file +cat > "$package_name.config" < Date: Sat, 19 Jul 2025 17:55:17 +0100 Subject: [PATCH 23/25] Don't backport process.sh --- .gitattributes | 1 - tools/opam/process.sh | 190 ------------------------------------------ 2 files changed, 191 deletions(-) delete mode 100644 tools/opam/process.sh diff --git a/.gitattributes b/.gitattributes index 53ae490491f1..1069ce73e3d7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -108,7 +108,6 @@ otherlibs/unix/symlink_win32.c typo.long-line # Some Unicode characters here and there utils/misc.ml typo.utf8 runtime/sak.c typo.non-ascii -tools/opam/process.sh typo.non-ascii stdlib/hashbang typo.white-at-eol typo.missing-lf diff --git a/tools/opam/process.sh b/tools/opam/process.sh deleted file mode 100644 index e529259c150d..000000000000 --- a/tools/opam/process.sh +++ /dev/null @@ -1,190 +0,0 @@ -#!/bin/sh -#************************************************************************** -#* * -#* OCaml * -#* * -#* David Allsopp, University of Cambridge & Tarides * -#* * -#* Copyright 2025 David Allsopp Ltd. * -#* * -#* All rights reserved. This file is distributed under the terms of * -#* the GNU Lesser General Public License version 2.1, with the * -#* special exception on linking described in the file LICENSE. * -#* * -#************************************************************************** - -set -eu - -# POSIX.1-2024 (Issue 8) lifts this from being a bashism. The sub-shell dance is -# necessary because set is a builtin and is permitted to abort the script -# unconditionally on error. -if (set -o pipefail 2> /dev/null); then - set -o pipefail -fi - -# This script is responsible for building and cloning OCaml installations. It is -# invoked by both the build and install sections of an opam package. -# $1 = make command (the `make` variable in opam). This should be the path to -# a binary only and is invoked without word-splitting (i.e. any -# additional arguments should be passed in $2 and the command is invoked -# "$1"). -# $2 = additional arguments passed to "$1". This variable will be used -# unquoted - arguments with spaces cannot be passed. From the build -# section, this allows the -j argument to be specified. For the install -# section, this argument must be "install". -# $3 = opam build-id variable of this package. -# $4 = name of the opam package to be used when generating .install and -# .config files. -# The remaining arguments depend on the value of $2. When it is "install": -# $5 = installation prefix, which may be a native Windows path, rather than a -# Cygwin path. -# When $2 is not "install" (the build opam section): -# $5 = "enabled" if cloning the compiler from an existing switch is permitted -# and "disabled" to force the compiler to be built from sources. -# $6, and any further arguments are additional options to pass to `configure` -# if the compiler is built from sources. - -make="$1" -make_args="$2" -build_id="$3" -package_name="$4" - -if [ x"$make_args" = 'xinstall' ]; then - prefix="$5" - - echo "📦 Installing the compiler to $prefix" - if [ -e 'config.status' ]; then - echo "📜 Using make install" - "$make" install - else - origin="$(tail -n 1 build-id)" - origin_prefix="$(opam var --safe --switch="$origin" prefix | tr -d '\r')" - echo "🪄 Duplicating $origin_prefix" - sh "$origin_prefix/share/ocaml/clone" "$origin_prefix" "$prefix" - fi - - exit 0 -fi - -# Build the package - -cloning="$5" -shift 5 -# "$@" now expands to the correctly-quoted arguments to pass to configure - -origin='' -clone_mechanism='' -if [ x"$cloning" = 'xenabled' ]; then - echo "🕵️ Searching for a switch containing build-id $build_id" - - if [ -e "$OPAM_SWITCH_PREFIX/share/ocaml/build-id" ]; then - switch="$(tail -n 1 "$OPAM_SWITCH_PREFIX/share/ocaml/build-id")" - if [ -n "$switch" ]; then - switch_share_dir="$(opam var --safe --switch="$switch" share \ - | tr -d '\r')" - switch_build_id="$switch_share_dir/ocaml/build-id" - if [ -e "$switch_build_id" ]; then - if [ x"$build_id" = x"$(head -n 1 "$switch_build_id")" ]; then - echo "🔁 Prefer to re-clone from $switch" - echo "$switch" > opam-switches - origin="$switch" - if ln "$switch_build_id" __cp_test 2>/dev/null; then - rm __cp_test - clone_mechanism='hard-linking' - fi - fi - fi - fi - fi - - echo "🐫 Requesting list of switches from opam" - opam switch list --safe --short | tr -d '\r' | grep -Fxv "$OPAMSWITCH" \ - >> opam-switches 2> /dev/null || true - - while IFS= read -r switch; do - switch_share_dir="$(opam var --safe --switch="$switch" share | tr -d '\r')" - switch_build_id="$switch_share_dir/ocaml/build-id" - if [ -e "$switch_build_id" ]; then - if [ x"$build_id" = x"$(head -n 1 "$switch_build_id")" ]; then - # There are three ways of cloning a switch: - # - Copy-on-Write (cp --reflink=always) - # - Hard linking - # - Copy - # Copy-on-Write is the ideal - virtually no space overhead, but with - # defence against accidental subsequent alterations. Hard linking is - # preferred over copying for the space-saving, and because the - # compiler should not being subsequently altered. - if cp --reflink=always "$switch_build_id" __cp_test 2>/dev/null; then - rm __cp_test - echo "📝 - can reflink from: $switch" - origin="$switch" - clone_mechanism='copy-on-write' - break - elif ln "$switch_build_id" __cp_test 2>/dev/null; then - rm __cp_test - if [ -z "$clone_mechanism" ]; then - echo "🔗 - can hard link from: $switch" - origin="$switch" - clone_mechanism='hard-linking' - fi - elif [ -z "$origin" ]; then - echo "📄 - can copy from: $switch" - origin="$switch" - fi - elif [ -z "$origin" ]; then - echo "⛔ - different compiler: $switch" - fi - fi - done < opam-switches -fi - -{ echo "$build_id"; echo "$origin" ; } > build-id - -if [ -n "$origin" ]; then - - echo "🧬 Will clone the compiler from $origin" - test -n "$clone_mechanism" || clone_mechanism='copying' - - cloned='true' - clone_source="$(sed -e '1d;s/\\/\\\\/g;s/%/%%/g;s/"/\\"/g' build-id)" - case "$origin" in - */*|*\\*) clone_source="local switch $clone_source";; - *) clone_source="global switch $clone_source";; - esac - - cat > "$package_name.install" <<'EOF' -share_root: [ - "build-id" {"ocaml/build-id"} -] -EOF - -else - - echo "🏗️ Will build the compiler from sources" - - cloned='false' - clone_source='' - - ./configure --cache-file=config.cache "$@" - "$make" $make_args - "$make" OPAM_PACKAGE_NAME=ocaml-compiler INSTALL_MODE=clone install - - cat > "$package_name.install" <<'EOF' -share_root: [ - "build-id" {"ocaml/build-id"} - "ocaml-compiler-clone.sh" {"ocaml/clone"} - "config.cache" {"ocaml/config.cache"} - "config.status" {"ocaml/config.status"} -] -EOF -fi - -# Create the .config file -cat > "$package_name.config" < Date: Tue, 17 Feb 2026 15:39:00 +0000 Subject: [PATCH 24/25] Merge pull request PR#14152 from dra27/export-ignore Improve installation time in opam by shrinking the Git-generated source archives (cherry picked from commit 172b5c5d074d34ea0f21384fcf45660bb641e348) --- .gitattributes | 15 +++++++++++++++ Makefile | 9 +++++++-- configure | 15 +++++++++------ configure.ac | 5 +++-- testsuite/Makefile | 28 +++++++++++++++++++++++++--- 5 files changed, 59 insertions(+), 13 deletions(-) diff --git a/.gitattributes b/.gitattributes index 1069ce73e3d7..de6bb51ad890 100644 --- a/.gitattributes +++ b/.gitattributes @@ -41,6 +41,21 @@ # the lines involved in the conflict, which is arguably worse #/Changes merge=union +testsuite/Makefile export-subst + +# Files and directories excluded from git-generated tarballs. +.github export-ignore +manual export-ignore +release-info export-ignore +testsuite/tests export-ignore +tools/ci export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.gitmodules export-ignore +.mailmap export-ignore +ocaml-variants.install export-ignore +ocaml-variants.opam export-ignore + # No header for text and META files (would be too obtrusive). *.md typo.missing-header README* typo.missing-header diff --git a/Makefile b/Makefile index 57747404c110..2b89f31f5c2f 100644 --- a/Makefile +++ b/Makefile @@ -939,11 +939,14 @@ clean:: # Build the manual latex files from the etex source files # (see manual/README.md) .PHONY: manual-pregen -manual-pregen: opt.opt - cd manual; $(MAKE) clean && $(MAKE) pregen-etex +manual-pregen: opt.opt | manual + $(MAKE) -C manual clean + $(MAKE) -C manual pregen-etex +ifneq "$(wildcard manual)" "" clean:: $(MAKE) -C manual clean +endif # The clean target clean:: partialclean @@ -2713,7 +2716,9 @@ distclean: clean ifneq "$(FLEXDLL_SUBMODULE_PRESENT)" "" $(MAKE) -C flexdll distclean MSVC_DETECT=0 endif +ifneq "$(wildcard manual)" "" $(MAKE) -C manual distclean +endif rm -f ocamldoc/META rm -f $(addprefix ocamltest/,ocamltest_config.ml ocamltest_unix.ml) rm -f testsuite/tools/toolchain.ml diff --git a/configure b/configure index 857629560e96..611026402e9f 100755 --- a/configure +++ b/configure @@ -3609,10 +3609,6 @@ ac_config_files="$ac_config_files Makefile.config" ac_config_files="$ac_config_files stdlib/sys.ml" -ac_config_files="$ac_config_files manual/src/version.tex" - -ac_config_files="$ac_config_files manual/src/html_processing/src/common.ml" - ac_config_files="$ac_config_files ocamltest/ocamltest_config.ml" ac_config_files="$ac_config_files otherlibs/dynlink/dynlink_config.ml" @@ -3641,6 +3637,13 @@ ac_config_files="$ac_config_files stdlib/META" ac_config_files="$ac_config_files testsuite/tools/toolchain.ml" +if test -d manual +then : + ac_config_files="$ac_config_files manual/src/version.tex" + + ac_config_files="$ac_config_files manual/src/html_processing/src/common.ml" + +fi # Definitions related to the version of OCaml printf "%s\n" "#define OCAML_VERSION_MAJOR 5" >>confdefs.h @@ -22709,8 +22712,6 @@ do "Makefile.build_config") CONFIG_FILES="$CONFIG_FILES Makefile.build_config" ;; "Makefile.config") CONFIG_FILES="$CONFIG_FILES Makefile.config" ;; "stdlib/sys.ml") CONFIG_FILES="$CONFIG_FILES stdlib/sys.ml" ;; - "manual/src/version.tex") CONFIG_FILES="$CONFIG_FILES manual/src/version.tex" ;; - "manual/src/html_processing/src/common.ml") CONFIG_FILES="$CONFIG_FILES manual/src/html_processing/src/common.ml" ;; "ocamltest/ocamltest_config.ml") CONFIG_FILES="$CONFIG_FILES ocamltest/ocamltest_config.ml" ;; "otherlibs/dynlink/dynlink_config.ml") CONFIG_FILES="$CONFIG_FILES otherlibs/dynlink/dynlink_config.ml" ;; "utils/config.common.ml") CONFIG_FILES="$CONFIG_FILES utils/config.common.ml" ;; @@ -22725,6 +22726,8 @@ do "otherlibs/runtime_events/META") CONFIG_FILES="$CONFIG_FILES otherlibs/runtime_events/META" ;; "stdlib/META") CONFIG_FILES="$CONFIG_FILES stdlib/META" ;; "testsuite/tools/toolchain.ml") CONFIG_FILES="$CONFIG_FILES testsuite/tools/toolchain.ml" ;; + "manual/src/version.tex") CONFIG_FILES="$CONFIG_FILES manual/src/version.tex" ;; + "manual/src/html_processing/src/common.ml") CONFIG_FILES="$CONFIG_FILES manual/src/html_processing/src/common.ml" ;; "native-symlinks") CONFIG_COMMANDS="$CONFIG_COMMANDS native-symlinks" ;; "ocamldoc/META") CONFIG_FILES="$CONFIG_FILES ocamldoc/META" ;; "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; diff --git a/configure.ac b/configure.ac index b59234e167b5..b3deb70abec6 100644 --- a/configure.ac +++ b/configure.ac @@ -306,8 +306,6 @@ AC_SUBST([runtime_search_target]) AC_CONFIG_FILES([Makefile.build_config]) AC_CONFIG_FILES([Makefile.config]) AC_CONFIG_FILES([stdlib/sys.ml]) -AC_CONFIG_FILES([manual/src/version.tex]) -AC_CONFIG_FILES([manual/src/html_processing/src/common.ml]) AC_CONFIG_FILES([ocamltest/ocamltest_config.ml]) AC_CONFIG_FILES([otherlibs/dynlink/dynlink_config.ml]) AC_CONFIG_FILES([utils/config.common.ml]) @@ -322,6 +320,9 @@ AC_CONFIG_FILES([otherlibs/dynlink/META]) AC_CONFIG_FILES([otherlibs/runtime_events/META]) AC_CONFIG_FILES([stdlib/META]) AC_CONFIG_FILES([testsuite/tools/toolchain.ml]) +AS_IF([test -d manual], + [AC_CONFIG_FILES([manual/src/version.tex]) + AC_CONFIG_FILES([manual/src/html_processing/src/common.ml])]) # Definitions related to the version of OCaml AC_DEFINE([OCAML_VERSION_MAJOR], [OCAML__VERSION_MAJOR]) diff --git a/testsuite/Makefile b/testsuite/Makefile index 4cad7738954a..a8302c6922dc 100644 --- a/testsuite/Makefile +++ b/testsuite/Makefile @@ -166,7 +166,7 @@ all: @$(MAKE) --no-print-directory report .PHONY: new-without-report -new-without-report: +new-without-report: | tests @rm -f $(failstamp) @($(ocamltest) -find-test-dirs tests | while $(IFS_LINE) read -r dir; do \ echo Running tests from \'$$dir\' ... ; \ @@ -185,7 +185,7 @@ check-failstamp: fi .PHONY: all-% -all-%: +all-%: | tests @for dir in tests/$**; do \ $(MAKE) --no-print-directory exec-one DIR=$$dir; \ done 2>&1 | tee $(TESTLOG) @@ -223,7 +223,7 @@ all-%: J_ARGUMENT = $(filter-out -j,$(filter -j%,$(MAKEFLAGS))) .PHONY: parallel-% -parallel-%: +parallel-%: | tests @echo | parallel >/dev/null 2>/dev/null \ || (echo "Unable to run the GNU parallel tool;";\ echo "You should install it before using the parallel* targets.";\ @@ -327,3 +327,25 @@ distclean: clean report: @if [ ! -f $(TESTLOG) ]; then echo "No $(TESTLOG) file."; exit 1; fi @$(AWK) -f ./summarize.awk < $(TESTLOG) + +# When an archive is created by git-archive, this is expanded to the SHA of the +# commit. The filter-out causes this to be blank if it's run when the Format +# tag has not been expanded +GIT_ARCHIVE_SHA = $(filter-out ormat%, $Format:%H$ ) + +tests: + @echo "There are no tests in the tests directory!" + @echo "This happens when the sources of OCaml are extracted from a \ +tarball" + @echo "generated by git-archive (which includes those generated by \ +GitHub)" + @head -n 1 $(ROOTDIR)/VERSION | grep -Fq + || \ + echo "Note that the release tarballs published at \ +https://caml.inria.fr/pub/distrib/ include all the manual and testsuite sources" + @$(if $(GIT_ARCHIVE_SHA),,false) + @echo "The required files are in commit $(GIT_ARCHIVE_SHA), for \ +example:" + @echo " git clone https://github.com/ocaml/ocaml \ +--revision $(GIT_ARCHIVE_SHA) --depth 1 git-sources" + @echo " mv git-sources/$@ ." + @false From cbe13b40c4c242c51620e187db86e56fe92bbd68 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Fri, 27 Sep 2024 11:28:27 +0100 Subject: [PATCH 25/25] OCaml 5.3.1+relocatable base version commit --- VERSION | 2 +- build-aux/ocaml_version.m4 | 2 +- configure | 34 +++++++++++++++++----------------- ocaml-variants.opam | 4 ++-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/VERSION b/VERSION index b562d9f5108f..65b318906844 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -5.3.1+dev0-2025-01-06 +5.3.1+relocatable # Starting with OCaml 4.14, although the version string that appears above is # still correct and this file can thus still be used to figure it out, diff --git a/build-aux/ocaml_version.m4 b/build-aux/ocaml_version.m4 index 546e37cefa58..e689cf8ca302 100644 --- a/build-aux/ocaml_version.m4 +++ b/build-aux/ocaml_version.m4 @@ -39,7 +39,7 @@ m4_define([OCAML__VERSION_PATCHLEVEL], [1]) # Note that the OCAML__VERSION_EXTRA string defined below is always empty # for officially-released versions of OCaml. -m4_define([OCAML__VERSION_EXTRA], [dev0-2025-01-06]) +m4_define([OCAML__VERSION_EXTRA], [relocatable]) # The OCAML__VERSION_EXTRA_PREFIX macro defined below should be a # single character: diff --git a/configure b/configure index 611026402e9f..a763699fcac3 100755 --- a/configure +++ b/configure @@ -56,7 +56,7 @@ if test -e '.git' ; then : fi fi # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for OCaml 5.3.1+dev0-2025-01-06. +# Generated by GNU Autoconf 2.71 for OCaml 5.3.1+relocatable. # # Report bugs to . # @@ -677,8 +677,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='OCaml' PACKAGE_TARNAME='ocaml' -PACKAGE_VERSION='5.3.1+dev0-2025-01-06' -PACKAGE_STRING='OCaml 5.3.1+dev0-2025-01-06' +PACKAGE_VERSION='5.3.1+relocatable' +PACKAGE_STRING='OCaml 5.3.1+relocatable' PACKAGE_BUGREPORT='caml-list@inria.fr' PACKAGE_URL='http://www.ocaml.org' @@ -1624,7 +1624,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures OCaml 5.3.1+dev0-2025-01-06 to adapt to many kinds of systems. +\`configure' configures OCaml 5.3.1+relocatable to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1691,7 +1691,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of OCaml 5.3.1+dev0-2025-01-06:";; + short | recursive ) echo "Configuration of OCaml 5.3.1+relocatable:";; esac cat <<\_ACEOF @@ -1886,7 +1886,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -OCaml configure 5.3.1+dev0-2025-01-06 +OCaml configure 5.3.1+relocatable generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2543,7 +2543,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by OCaml $as_me 5.3.1+dev0-2025-01-06, which was +It was created by OCaml $as_me 5.3.1+relocatable, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3299,8 +3299,8 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.3.1+dev0-2025-01-06" >&5 -printf "%s\n" "$as_me: Configuring OCaml version 5.3.1+dev0-2025-01-06" >&6;} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.3.1+relocatable" >&5 +printf "%s\n" "$as_me: Configuring OCaml version 5.3.1+relocatable" >&6;} # It's important for the setting up of defaults and the checking of the # --with-relative-libdir option to know whether the user specified --libdir. @@ -3400,11 +3400,11 @@ runtime_search_target='' -VERSION=5.3.1+dev0-2025-01-06 +VERSION=5.3.1+relocatable OCAML_DEVELOPMENT_VERSION=true -OCAML_RELEASE_EXTRA='Some (Plus, "dev0-2025-01-06")' +OCAML_RELEASE_EXTRA='Some (Plus, "relocatable")' OCAML_VERSION_MAJOR=5 @@ -3412,7 +3412,7 @@ OCAML_VERSION_MINOR=3 OCAML_VERSION_PATCHLEVEL=1 -OCAML_VERSION_EXTRA=dev0-2025-01-06 +OCAML_VERSION_EXTRA=relocatable OCAML_VERSION_SHORT=5.3 @@ -3652,13 +3652,13 @@ printf "%s\n" "#define OCAML_VERSION_MINOR 3" >>confdefs.h printf "%s\n" "#define OCAML_VERSION_PATCHLEVEL 1" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_ADDITIONAL \"dev0-2025-01-06\"" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_ADDITIONAL \"relocatable\"" >>confdefs.h - printf "%s\n" "#define OCAML_VERSION_EXTRA \"dev0-2025-01-06\"" >>confdefs.h + printf "%s\n" "#define OCAML_VERSION_EXTRA \"relocatable\"" >>confdefs.h printf "%s\n" "#define OCAML_VERSION 50301" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_STRING \"5.3.1+dev0-2025-01-06\"" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_STRING \"5.3.1+relocatable\"" >>confdefs.h printf "%s\n" "#define OCAML_RELEASE_NUMBER 19" >>confdefs.h @@ -22219,7 +22219,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by OCaml $as_me 5.3.1+dev0-2025-01-06, which was +This file was extended by OCaml $as_me 5.3.1+relocatable, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -22292,7 +22292,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -OCaml config.status 5.3.1+dev0-2025-01-06 +OCaml config.status 5.3.1+relocatable configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/ocaml-variants.opam b/ocaml-variants.opam index 4747ba540454..e1c3079357c5 100644 --- a/ocaml-variants.opam +++ b/ocaml-variants.opam @@ -1,7 +1,7 @@ opam-version: "2.0" -version: "5.3.1+trunk" +version: "5.3.1+relocatable" license: "LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception" -synopsis: "OCaml 5.3 development version" +synopsis: "Relocatable OCaml 5.3 branch" maintainer: "caml-list@inria.fr" authors: [ "Xavier Leroy"