From 5332d93c45202e7466abe9c5a11156e6343483ca 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 01/28] 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 3e71883f0f4a..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" -#ifndef _WIN32 #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(const 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 b2a50e3b74ec..4c6117b58627 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 180994d50f5a2f2b589e2ed9d05314a004cbdd4b Mon Sep 17 00:00:00 2001 From: Florian Angeletti Date: Fri, 28 Nov 2025 17:40:42 +0100 Subject: [PATCH 02/28] Merge pull request PR#14243 from dra27/ld.conf Relocatable OCaml - explicit-relative paths in `ld.conf` (cherry picked from commit 1e05f341eaf6a7e953332e6081c86b275959191d) --- Changes | 30 +++++ 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 | 176 +++++++++++++++++++++------- runtime/startup_byt.c | 13 +- testsuite/in_prefix/README.md | 7 +- 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 +- 19 files changed, 332 insertions(+), 212 deletions(-) diff --git a/Changes b/Changes index 4ad8737bb097..0ec41c31dbd8 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,33 @@ +OCaml 5.4 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: + +- #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: - #14574, #14577, #14589: Fix wrong assembly code generated for ARM64 diff --git a/Makefile b/Makefile index 82bbb8db00ee..635c8ba74ac4 100644 --- a/Makefile +++ b/Makefile @@ -1268,8 +1268,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) @@ -1357,10 +1356,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) @@ -1606,7 +1601,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) @@ -2667,7 +2662,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 @@ -2688,13 +2683,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 b1fba2b0b4c2..d0580680c06f 100755 --- a/configure +++ b/configure @@ -1032,6 +1032,7 @@ enable_flambda enable_flambda_invariants enable_cmm_invariants with_target_sh +with_additional_stublibsdir enable_reserved_header_bits enable_stdlib_manpages enable_warn_error @@ -1763,6 +1764,9 @@ Optional Packages: --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-odoc build documentation with odoc --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 @@ -3912,6 +3916,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 @@ -4191,6 +4204,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 : @@ -23773,6 +23803,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 @@ -24896,6 +24930,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 @@ -24935,6 +24974,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 @@ -26075,6 +26115,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 0bb4eda5d4c1..2a7e29ac5c1e 100644 --- a/configure.ac +++ b/configure.ac @@ -378,6 +378,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]) @@ -564,6 +575,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])], @@ -2900,6 +2922,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 4a058e6bb6ee..1a0138c43417 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 016a35cc72b6..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 const 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 0f5636481b35..b030bfdfdd78 100644 --- a/runtime/caml/osdeps.h +++ b/runtime/caml/osdeps.h @@ -138,6 +138,14 @@ CAMLextern value caml_win32_xdg_defaults(void); CAMLextern value caml_win32_get_temp_path(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 d3e6b1b5345e..041aa69844af 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,24 +78,51 @@ static c_primitive lookup_primitive(const 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 const 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) { - 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; + 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) { - const char_os * stdlib; - char_os * 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; @@ -103,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) @@ -175,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: @@ -184,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 ee9cfc63d4a3..f2fc29a4f07a 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -376,6 +376,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) @@ -387,7 +396,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); @@ -424,7 +433,7 @@ static void do_print_config(void) puts("shared_libs_path:"); 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/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/testLinkModes.ml b/testsuite/tools/testLinkModes.ml index 4c6117b58627..41cdccb6926c 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -603,14 +603,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 643f985ad3e5..8f3fd45aaf30 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -160,9 +160,7 @@ let libdir_rules config file = ~ocaml_debug:has_ocaml_debug_info, ~c_debug:has_c_debug_info, ~s: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 *) (~stdlib:true, ~ocaml_debug:false, ~c_debug:false, ~s: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 a6d9eacebde2..f200e2fcc8a0 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 3e61d6e66b2a..482c3f7dc1d8 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 c297da1b66595eb85b8da41fd9ef533b91726381 Mon Sep 17 00:00:00 2001 From: Gabriel Scherer Date: Mon, 8 Dec 2025 10:21:19 +0100 Subject: [PATCH 03/28] Merge pull request PR#14244 from dra27/enable-relative Relocatable OCaml - `--with-relative-libdir` (cherry picked from commit cfbf2105cfed30ddea8383b7f2bf054c8c28a5e8) --- .depend | 4 + .github/workflows/build-msvc.yml | 16 +- .github/workflows/build.yml | 31 +- Changes | 24 ++ Makefile | 29 +- Makefile.build_config.in | 15 +- Makefile.common | 23 ++ Makefile.cross | 9 +- appveyor.yml | 1 + asmcomp/asmlink.ml | 12 + asmcomp/asmpackager.ml | 4 + asmcomp/cmm_helpers.ml | 3 + asmcomp/cmm_helpers.mli | 7 + bytecomp/bytegen.ml | 3 +- bytecomp/bytelink.ml | 125 ++++-- bytecomp/bytesections.ml | 3 + bytecomp/bytesections.mli | 1 + configure | 377 ++++++++++++++++-- configure.ac | 233 +++++++++-- 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 | 42 +- 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 | 76 +++- runtime/sys.c | 41 ++ runtime/unix.c | 70 ++++ runtime/win32.c | 86 ++++ testsuite/in_prefix/Makefile.test | 4 + testsuite/in_prefix/README.md | 9 +- .../tests/native-debugger/linux-lldb-amd64.ml | 1 + .../tests/native-debugger/linux-lldb-arm64.ml | 1 + .../tool-debugger/find-artifacts/debuggee.ml | 1 + testsuite/tools/cmdline.ml | 2 +- testsuite/tools/harness.mli | 3 +- testsuite/tools/testBytecodeBinaries.ml | 11 +- testsuite/tools/testDynlink.ml | 35 +- testsuite/tools/testLinkModes.ml | 67 +++- testsuite/tools/testRelocation.ml | 68 ++-- 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 | 35 +- utils/config.fixed.ml | 2 +- utils/config.generated.ml.in | 3 +- utils/config.mli | 43 +- 76 files changed, 1626 insertions(+), 271 deletions(-) diff --git a/.depend b/.depend index 11fc65c10980..0b649d84808f 100644 --- a/.depend +++ b/.depend @@ -5235,6 +5235,7 @@ middle_end/flambda/closure_conversion.cmo : \ lambda/debuginfo.cmi \ middle_end/convert_primitives.cmi \ utils/config.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 \ @@ -5264,6 +5265,7 @@ middle_end/flambda/closure_conversion.cmx : \ lambda/debuginfo.cmx \ middle_end/convert_primitives.cmx \ utils/config.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 \ @@ -10790,11 +10792,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 57e9d59b6d9d..b16ae5badd88 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-codegen-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-codegen-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, @@ -258,6 +277,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 @@ -290,4 +313,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 0ec41c31dbd8..f3f92e4fda95 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,22 @@ OCaml 5.4 maintenance version normalised on both Windows and Unix. (David Allsopp, review by Jonah Beckford, Damien Doligez and Hugo Heuzard) +- #14244: 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 @@ -28,6 +44,14 @@ OCaml 5.4 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: - #14574, #14577, #14589: Fix wrong assembly code generated for ARM64 diff --git a/Makefile b/Makefile index 635c8ba74ac4..a0b2badcb56d 100644 --- a/Makefile +++ b/Makefile @@ -483,9 +483,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 $< > $@ @@ -882,7 +884,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) @@ -959,6 +962,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 @@ -1384,7 +1391,8 @@ $(SAK): 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' \ @@ -1392,6 +1400,8 @@ runtime/build_config.h: $(ROOTDIR)/Makefile.config $(SAK) echo '#define HOST "$(HOST)"'; \ } > $@ +runtime/prims.$(O): runtime/build_config.h + ## Runtime libraries and programs runtime/ocamlrun$(EXE): runtime/prims.$(O) runtime/libcamlrun.$(A) @@ -1691,6 +1701,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) \ @@ -2006,6 +2020,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) ocamltest_BYTECODE_LINKFLAGS = -custom -g diff --git a/Makefile.build_config.in b/Makefile.build_config.in index df3593bb52cb..2c96e5917020 100644 --- a/Makefile.build_config.in +++ b/Makefile.build_config.in @@ -75,14 +75,22 @@ INSTALL_OCAMLNAT = @install_ocamlnat@ DEP_CC=@DEP_CC@ -MM COMPUTE_DEPS=@compute_deps@ +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)/, \ @@ -140,6 +148,7 @@ 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 99ea5fe12a03..cbae9a1e1071 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,26 @@ 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" + +# Allow Makefile.cross to override the Standard Library default for the compiler +# itself. +HOST_LIBDIR ?= $(TARGET_LIBDIR) + +OC_COMMON_LINKFLAGS += \ + -set-runtime-default \ + $(call QUOTE_SINGLE,standard_library_default=$(HOST_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/Makefile.cross b/Makefile.cross index b0a21c3d7c45..6411104fb78e 100644 --- a/Makefile.cross +++ b/Makefile.cross @@ -40,8 +40,15 @@ VPATH := + $(VPATH) CROSS_OVERRIDES=OCAMLRUN=ocamlrun NEW_OCAMLRUN=ocamlrun \ BOOT_OCAMLLEX=ocamllex OCAMLYACC=ocamlyacc +# The cross-compiler is linked as build/bin/ocamlopt -o cross/bin/ocamlopt +# Config.standard_library_default for cross/bin/ocamlopt would by default be the +# value for build/bin/ocamlopt (i.e. build/lib/ocaml), which is not what is +# wanted. When linking the cross-compiler itself, therefore, this default must +# be overridden with -set-runtime-default so that cross/bin/ocamlopt instead has +# cross/lib/ocaml for Config.standard_library_default CROSS_COMPILER_OVERRIDES=$(CROSS_OVERRIDES) CAMLC=ocamlc CAMLOPT=ocamlopt \ - BEST_OCAMLC=ocamlc BEST_OCAMLOPT=ocamlopt BEST_OCAMLLEX=ocamllex + BEST_OCAMLC=ocamlc BEST_OCAMLOPT=ocamlopt BEST_OCAMLLEX=ocamllex \ + HOST_LIBDIR="$(LIBDIR)" CROSS_COMPILERLIBS_OVERRIDES=$(CROSS_OVERRIDES) CAMLC=ocamlc \ CAMLOPT="$(ROOTDIR)/ocamlopt.opt$(EXE) $(STDLIBFLAGS)" 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 9ec1b88812fc..836df2ae6426 100644 --- a/asmcomp/cmm_helpers.ml +++ b/asmcomp/cmm_helpers.ml @@ -2695,6 +2695,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 5c325bd42716..cd45be0e81db 100644 --- a/asmcomp/cmm_helpers.mli +++ b/asmcomp/cmm_helpers.mli @@ -624,7 +624,14 @@ val code_segment_table: string list -> phrase (** Generate data for a predefined exception *) val predef_exception: int -> string -> phrase +<<<<<<< HEAD val plugin_header: (Cmx_format.unit_infos * Digest.t) list -> phrase +======= +(** Generate data for a global string constant *) +val emit_global_string_constant: string -> string -> phrase + +val plugin_header: (Cmx_format.unit_infos * Digest.BLAKE128.t) list -> phrase +>>>>>>> cfbf2105cfe (** Emit constant symbols *) diff --git a/bytecomp/bytegen.ml b/bytecomp/bytegen.ml index 86fc68d83439..fd32dda10526 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 c55224fab8e8..9d46fc8a7feb 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 @@ -201,7 +202,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 = @@ -227,46 +228,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: @@ -345,6 +344,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 @@ -497,7 +501,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; @@ -520,6 +526,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; @@ -591,6 +609,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 = @@ -614,6 +675,8 @@ extern "C" { #include #include +const enum caml_byte_program_mode caml_byte_program_mode = EMBEDDED; + static int caml_code[] = { |}; Symtable.init(); @@ -623,7 +686,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 *) @@ -651,6 +714,7 @@ static char caml_sections[] = { }; |}; + emit_runtime_standard_library_default outchan; (* The table of primitives *) Symtable.output_primitive_table outchan; (* The entry point *) @@ -658,7 +722,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), @@ -800,11 +863,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 d0580680c06f..7c9406d02d2d 100755 --- a/configure +++ b/configure @@ -796,11 +796,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 @@ -847,6 +852,7 @@ build_ocamldoc build_ocamltex build_ocamldebug with_debugger +as_is_cc as_has_debug_prefix_map cc_has_debug_prefix_map unix_directory @@ -1040,6 +1046,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 @@ -1767,6 +1774,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 @@ -3358,6 +3368,14 @@ ocamltest_libunix=None ocamltest_unix_impl="dummy" unix_library="" unix_directory="" +<<<<<<< HEAD +======= +diff_supports_color=false +target_libdir_is_relative=false +srcdir_abs='' +srcdir_abs_real='' +build_map_flags='' +>>>>>>> cfbf2105cfe # Information about the package @@ -3510,6 +3528,7 @@ LINEAR_MAGIC_NUMBER=Caml1999L036 + # TODO: rename this variable @@ -3558,6 +3577,11 @@ LINEAR_MAGIC_NUMBER=Caml1999L036 + + + + + @@ -3761,12 +3785,47 @@ then : ac_tool_prefix=$target_alias- fi +# $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" ;; #( *) : @@ -4291,6 +4350,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 : @@ -14973,11 +15049,11 @@ 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} -case $host in #( - *-w64-mingw32*|*-pc-windows) : +case $cygwin_build_env,$host in #( + true,*-w64-mingw32*|true,*-pc-windows) : { 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; } @@ -14991,9 +15067,10 @@ fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ln" >&5 printf "%s\n" "$ln" >&6; } - ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$ocamlsrcdir")" ;; #( + ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$srcdir_abs")" ;; #( *) : - ln='ln -sf' ;; + ln='ln -sf' + ocamlsrcdir="$srcdir_abs" ;; esac # Whether ar supports @FILE arguments @@ -15647,16 +15724,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|*-*-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 @@ -17334,6 +17411,13 @@ fi # Checks for header files +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 : @@ -19015,30 +19099,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 @@ -20979,8 +21078,6 @@ fi ## -fdebug-prefix-map support by the C compiler case $ocaml_cc_vendor,$target in #( - *,*-w64-mingw32*) : - cc_has_debug_prefix_map=false ;; #( *,*-pc-windows) : cc_has_debug_prefix_map=false ;; #( xlc*,powerpc-ibm-aix*) : @@ -21030,6 +21127,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 @@ -23735,11 +23926,6 @@ then : libdir="$libdir"/ocaml fi -if test x"$TARGET_LIBDIR" = x -then : - TARGET_LIBDIR="$libdir" -fi - if test x"$mandir" = x'${datarootdir}/man' then : mandir='${prefix}/man' @@ -23760,13 +23946,95 @@ then : *) : ;; esac -else $as_nop - case $build,$host in #( - *-*-cygwin,*-w64-mingw32*|*-*-cygwin,*-pc-windows) : - prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")" ;; #( +fi + +# Normalise $prefix, if necessary, on Windows. There are 9 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. +# - $TARGET_BINDIR and $TARGET_LIBDIR. These are both precious environment +# variables (meaning their value is recorded by configure) but they default to +# $ocaml_bindir and $ocaml_libdir respectively. +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" = x'${exec_prefix}/lib/ocaml' +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 + +if test x"$bindir_to_libdir" != 'x' && test x"$TARGET_LIBDIR" != 'x' +then : + as_fn_error $? "--with-relative-libdir and TARGET_LIBDIR cannot both be specified" "$LINENO" 5 fi # Define a few macros that were defined in config/m-nt.h @@ -23951,6 +24219,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" @@ -23960,11 +24229,43 @@ then : exec_prefix="$prefix" fi eval "exec_prefix=\"$exec_prefix\"" - eval "ocaml_bindir=\"$bindir\"" - eval "ocaml_libdir=\"$libdir\"" + # 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 : + ocaml_bindir="$HOST_BINDIR" +else $as_nop + ocaml_bindir='' +fi if test x"$TARGET_BINDIR" = 'x' then : - TARGET_BINDIR="$ocaml_bindir" + 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"$TARGET_LIBDIR" = 'x' +then : + if test "x$bindir_to_libdir" = 'x' +then : + TARGET_LIBDIR="$ocaml_libdir" +else $as_nop + TARGET_LIBDIR="$bindir_to_libdir" +fi fi if test x"$target_launch_method" = 'x' then : @@ -24928,7 +25229,7 @@ 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")' + 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")' @@ -26111,7 +26412,7 @@ 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" \ > stdlib/target_runtime.info ;; diff --git a/configure.ac b/configure.ac index 2a7e29ac5c1e..a4917f34b8c9 100644 --- a/configure.ac +++ b/configure.ac @@ -83,6 +83,14 @@ ocamltest_libunix=None ocamltest_unix_impl="dummy" unix_library="" unix_directory="" +<<<<<<< HEAD +======= +diff_supports_color=false +target_libdir_is_relative=false +srcdir_abs='' +srcdir_abs_real='' +build_map_flags='' +>>>>>>> cfbf2105cfe # Information about the package @@ -215,6 +223,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]) @@ -261,11 +270,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 @@ -318,12 +332,45 @@ AS_IF([test x"$target_alias" != x], AS_IF([test -n "$target_alias"], [ac_tool_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. +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"])]) @@ -630,6 +677,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])]) @@ -813,14 +873,15 @@ 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} -AS_CASE([$host], - [*-w64-mingw32*|*-pc-windows], +AS_CASE([$cygwin_build_env,$host], + [true,*-w64-mingw32*|true,*-pc-windows], [OCAML_CHECK_LN_ON_WINDOWS - ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$ocamlsrcdir")"], - [ln='ln -sf']) + ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$srcdir_abs")"], + [ln='ln -sf' + ocamlsrcdir="$srcdir_abs"]) # Whether ar supports @FILE arguments @@ -899,7 +960,7 @@ 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" \ > stdlib/target_runtime.info], @@ -910,7 +971,7 @@ 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")' + HOST_BINDIR='$(echo "$HOST_BINDIR" | sed -e "s/'/'\"'\"'/g")' TARGET_BINDIR='$(echo "$TARGET_BINDIR" | sed -e "s/'/'\"'\"'/g")']) # Checks for programs @@ -1123,12 +1184,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|*-*-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])) + ]) + ]) ]) ]) @@ -1279,6 +1341,7 @@ AC_SEARCH_LIBS([cos], [m], # Checks for header files +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 ]) @@ -1753,24 +1816,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]) @@ -2377,7 +2449,6 @@ AC_CHECK_FUNC([pwrite], [AC_DEFINE([HAS_PWRITE], [1])]) ## -fdebug-prefix-map support by the C compiler AS_CASE([$ocaml_cc_vendor,$target], - [*,*-w64-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], @@ -2385,6 +2456,20 @@ AS_CASE([$ocaml_cc_vendor,$target], [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 @@ -2869,9 +2954,6 @@ AC_CONFIG_COMMANDS_PRE([cclibs="$cclibs $mathlib $DLLIBS $PTHREAD_LIBS"]) AS_IF([test x"$libdir" = x'${exec_prefix}/lib'], [libdir="$libdir"/ocaml]) -AS_IF([test x"$TARGET_LIBDIR" = x], - [TARGET_LIBDIR="$libdir"]) - AS_IF([test x"$mandir" = x'${datarootdir}/man'], [mandir='${prefix}/man']) @@ -2881,10 +2963,72 @@ 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_CASE([$build,$host], - [*-*-cygwin,*-w64-mingw32*|*-*-cygwin,*-pc-windows], - [prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")"])]) + [x86_64-pc-windows], [prefix='C:/ocamlms64'])]) + +# Normalise $prefix, if necessary, on Windows. There are 9 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. +# - $TARGET_BINDIR and $TARGET_LIBDIR. These are both precious environment +# variables (meaning their value is recorded by configure) but they default to +# $ocaml_bindir and $ocaml_libdir respectively. +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" = x'${exec_prefix}/lib/ocaml'], + [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]))])]) + +AS_IF([test x"$bindir_to_libdir" != 'x' && test x"$TARGET_LIBDIR" != 'x'], + [AC_MSG_ERROR(m4_normalize([--with-relative-libdir and TARGET_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 @@ -2911,12 +3055,35 @@ 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"$TARGET_LIBDIR" = 'x'], + [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 3fb7ad770a98..10ac71317494 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 -> @@ -762,3 +764,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 393f9fe3837a..e3229f92dd5a 100644 --- a/driver/main_args.ml +++ b/driver/main_args.ml @@ -163,6 +163,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" @@ -893,6 +897,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 @@ -1121,6 +1126,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; @@ -1346,6 +1352,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; @@ -1839,6 +1846,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 96cce1ca5386..d285214bf572 100644 --- a/driver/main_args.mli +++ b/driver/main_args.mli @@ -119,6 +119,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 45c16b0973ff..ba2f7df2aea4 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 0a17deea5ac2..df83223b9e4e 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 484c2ce26d3a..c3bcd9229c4c 100644 --- a/lambda/printlambda.ml +++ b/lambda/printlambda.ml @@ -270,7 +270,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 52f39e061495..255ee1f365f2 100644 --- a/lambda/translprim.ml +++ b/lambda/translprim.ml @@ -166,6 +166,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 8a74047dfba4..5785f482bfcd 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 8b35606c272e..520dfbec4012 100644 --- a/middle_end/closure/closure.ml +++ b/middle_end/closure/closure.ml @@ -1058,22 +1058,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 (Config.target_os_type = "Unix") - | Ostype_win32 -> make_const_bool (Config.target_os_type = "Win32") - | Ostype_cygwin -> make_const_bool (Config.target_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 (Config.target_os_type = "Unix") + | Ostype_win32 -> cst make_const_bool (Config.target_os_type = "Win32") + | Ostype_cygwin -> cst make_const_bool (Config.target_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 @@ -1465,7 +1472,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 231abfc19477..5349e775e196 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 linuxlike_mangling = match Config.system with | "macosx" @@ -137,6 +138,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 af1596dbd812..a89a66b2c41a 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 1e2fdd6c7737..655dc4de238a 100644 --- a/middle_end/flambda/closure_conversion.ml +++ b/middle_end/flambda/closure_conversion.ml @@ -390,26 +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 Config.target_os_type "Unix") - | Ostype_win32 -> - lambda_const_bool (String.equal Config.target_os_type "Win32") - | Ostype_cygwin -> - lambda_const_bool (String.equal Config.target_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 Config.target_os_type "Unix") + | Ostype_win32 -> + cst lambda_const_bool (String.equal Config.target_os_type "Win32") + | Ostype_cygwin -> + cst lambda_const_bool (String.equal Config.target_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 fbef17b375f7..4c19aa09afb8 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 68b572deff05..5f68a3596a5a 100644 --- a/ocamltest/ocamltest_config.ml.in +++ b/ocamltest/ocamltest_config.ml.in @@ -100,3 +100,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 55bc657c5e0d..06d9872422f3 100644 --- a/ocamltest/ocamltest_config.mli +++ b/ocamltest/ocamltest_config.mli @@ -142,3 +142,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 13d2c7591b88..27fe41b800ef 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 b030bfdfdd78..3eb89486c8e4 100644 --- a/runtime/caml/osdeps.h +++ b/runtime/caml/osdeps.h @@ -96,6 +96,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 @@ -159,6 +175,18 @@ CAMLextern 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 efdbefb059ae..cb1efb072783 100644 --- a/runtime/caml/s.h.in +++ b/runtime/caml/s.h.in @@ -118,6 +118,10 @@ #undef HAS_DECL_SETTHREADDESCRIPTION +#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 49fc5b9d77b4..628e06c627c1 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, const 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 563ffd4b2f48..558a1161e7ac 100644 --- a/runtime/caml/sys.h +++ b/runtime/caml/sys.h @@ -33,6 +33,10 @@ CAMLextern void caml_sys_init (const 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 041aa69844af..d43caf0a6362 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 f2fc29a4f07a..5181cf856be1 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) { @@ -381,7 +381,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; } @@ -394,7 +394,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)); @@ -433,7 +433,8 @@ static void do_print_config(void) puts("shared_libs_path:"); 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_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) @@ -462,13 +463,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(); @@ -489,24 +490,57 @@ 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(); + +<<<<<<< HEAD +======= + /* 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); + } +>>>>>>> cfbf2105cfe /* Little grasshopper wonders why we do that at all, since "The current executable is ocamlrun itself, it's never a bytecode program". Little grasshopper "ocamlc -custom" in mind should keep. 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. */ +<<<<<<< HEAD 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)); +>>>>>>> cfbf2105cfe } + 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); } @@ -537,6 +571,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 (); @@ -635,6 +687,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 60df7828f466..10308c8f3e8c 100644 --- a/runtime/sys.c +++ b/runtime/sys.c @@ -731,6 +731,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 e58c77cf5170..68a53db8441e 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 @@ -542,3 +545,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 8a5c8ff4b087..346db16a5305 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" @@ -1342,3 +1343,88 @@ value caml_win32_get_temp_path(void) caml_win32_sys_error(GetLastError()); CAMLreturn(caml_copy_string_of_utf16(buf)); } + +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 4bb46a5c4db1..49688d2d774b 100644 --- a/testsuite/in_prefix/Makefile.test +++ b/testsuite/in_prefix/Makefile.test @@ -23,8 +23,12 @@ DRIVER = ../tools/test_in_prefix$(EXE) endif DRIVER_ARGS = \ +<<<<<<< HEAD $(VERBOSE_FLAG) --bindir "$(BINDIR)" --libdir "$(LIBDIR)" \ $(call bool_to_with, shebangscripts, $(SHEBANGSCRIPTS)) \ +======= + $(VERBOSE_FLAG) --bindir "$(BINDIR)" --libdir '$(TARGET_LIBDIR)' \ +>>>>>>> cfbf2105cfe $(call bool_to_with, ocamlnat, $(INSTALL_OCAMLNAT)) \ $(call bool_to_with, ocamlopt, $(NATIVE_COMPILER)) \ $(OTHERLIBRARIES) --pwd "$(SRCDIR_ABS)/testsuite/in_prefix" 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/native-debugger/linux-lldb-amd64.ml b/testsuite/tests/native-debugger/linux-lldb-amd64.ml index d14567ed5e7c..6bfa4608d108 100644 --- a/testsuite/tests/native-debugger/linux-lldb-amd64.ml +++ b/testsuite/tests/native-debugger/linux-lldb-amd64.ml @@ -1,4 +1,5 @@ (* TEST + unset BUILD_PATH_PREFIX_MAP; native-compiler; no-tsan; (* Skip, TSan inserts extra frames into backtraces *) linux; diff --git a/testsuite/tests/native-debugger/linux-lldb-arm64.ml b/testsuite/tests/native-debugger/linux-lldb-arm64.ml index 3294e8af62af..e66d2320dd5a 100644 --- a/testsuite/tests/native-debugger/linux-lldb-arm64.ml +++ b/testsuite/tests/native-debugger/linux-lldb-arm64.ml @@ -1,4 +1,5 @@ (* TEST + unset BUILD_PATH_PREFIX_MAP; native-compiler; no-tsan; (* Skip, TSan inserts extra frames into backtraces *) linux; diff --git a/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml b/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml index f9fb5fa7fdca..442d600a303b 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; debugger_script = "${test_source_directory}/input_script"; debugger; shared-libraries; diff --git a/testsuite/tools/cmdline.ml b/testsuite/tools/cmdline.ml index 62d71c3f16e6..3453d64a63d6 100644 --- a/testsuite/tools/cmdline.ml +++ b/testsuite/tools/cmdline.ml @@ -180,7 +180,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/harness.mli b/testsuite/tools/harness.mli index 8126915cfdba..1a3a50c2993b 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 41cdccb6926c..7f666cc11271 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 @@ -577,6 +580,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 @@ -604,8 +622,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; @@ -633,9 +652,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 8f3fd45aaf30..db861de76220 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -24,12 +24,17 @@ end) (* 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 @@ -59,12 +64,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 @@ -124,7 +131,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) @@ -160,17 +167,26 @@ let libdir_rules config file = ~ocaml_debug:has_ocaml_debug_info, ~c_debug:has_c_debug_info, ~s: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 *) (~stdlib:true, ~ocaml_debug:false, ~c_debug:false, ~s:false) else if basename = "config.cmx" then (* config.cmx contains Config.standard_library for inlining *) - (~stdlib:true, ~ocaml_debug:false, ~c_debug:false, ~s:false) + let stdlib = + config.has_relative_libdir = None && not Config.flambda in + (~stdlib, ~ocaml_debug:false, ~c_debug:false, ~s: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, ~ocaml_debug:true, ~c_debug:false, ~s: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, ~ocaml_debug:false, ~c_debug:false, ~s:false) else if ext = ".cmxs" then (* All the .cmxs files built by the distribution at present include C objects and obviously contain assembled objects. *) @@ -185,16 +201,6 @@ let libdir_rules config file = not (is_ocaml || String.starts_with ~prefix:"flexdll_" basename) in (~stdlib:false, ~ocaml_debug:false, ~c_debug, ~s: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 = @@ -202,14 +208,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, ~ocaml_debug:false, ~c_debug:(not is_ocaml), ~s:is_ocaml) else (* DLLs are either the shared versions of the runtime libraries or C stubs. All of these are compiled with -g *) - (~stdlib:is_camlrun, ~ocaml_debug:false, ~c_debug:true, ~s:false) + (~stdlib:false, ~ocaml_debug:false, ~c_debug:true, ~s:false) else (~stdlib:false, ~ocaml_debug:false, ~c_debug:false, ~s:false) in @@ -227,7 +232,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 @@ -239,6 +244,13 @@ let libdir_rules config file = else LocationSet.empty in + let prefix = + if config.has_relative_libdir <> None + && basename = "Makefile.config" then + LocationSet.add Relative prefix + else + prefix + in if contains_build_path then LocationSet.add Build prefix else 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 a7fde41a37df..c373c7f054c4 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 f200e2fcc8a0..81bbc96e8a2b 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 ~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 bf79b30b7e4b..852e21079781 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 3e54d98ad514..f147ad8d3662 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 0f956d2fbc5f..48ebde875a7f 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,11 +75,16 @@ 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_unix = (target_os_type = "Unix") +let target_win32 = (target_os_type = "Win32") +let target_cygwin = (target_os_type = "Cygwin") let default_executable_name = - match target_os_type with - "Unix" -> "a.out" - | "Win32" | "Cygwin" -> "camlprog.exe" - | _ -> "camlprog" + if target_unix then + "a.out" + else if target_win32 || target_cygwin then + "camlprog.exe" + else + "camlprog" type configuration_value = | String of string | Int of int @@ -71,9 +94,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 a334b1d76f5d..05be4b9e2a11 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 e5e1b15d319d..b516759e1be1 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@|@ccomp_type@|@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 482c3f7dc1d8..c1b22ac0905c 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 *) @@ -184,7 +202,24 @@ val target_os_type: string (** Operating system targetted by the native-code compiler. One of - ["Unix"] (for all Unix versions, including Linux and macOS), - ["Win32"] (for MS-Windows, OCaml compiled with MSVC++ or MinGW-w64), -- ["Cygwin"] (for MS-Windows, OCaml compiled with Cygwin). *) +- ["Cygwin"] (for MS-Windows, OCaml compiled with Cygwin). + + @since 5.4 *) + +val target_unix: bool +(** True if [target_os_type = "Unix"] + + @since 5.5 *) + +val target_win32: bool +(** True if [target_os_type = "Win32"] + + @since 5.5 *) + +val target_cygwin: bool +(** True if [target_os_type = "Cygwin"] + + @since 5.5 *) val asm: string (** The assembler (and flags) to use for assembling From 296c53478d27473f5d684b4248d5e607e849f0e9 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Mon, 8 Dec 2025 10:21:19 +0100 Subject: [PATCH 04/28] Resolve conflicts --- asmcomp/cmm_helpers.mli | 6 +----- configure | 4 ---- configure.ac | 4 ---- runtime/startup_byt.c | 9 --------- testsuite/in_prefix/Makefile.test | 6 +----- 5 files changed, 2 insertions(+), 27 deletions(-) diff --git a/asmcomp/cmm_helpers.mli b/asmcomp/cmm_helpers.mli index cd45be0e81db..00847f643bad 100644 --- a/asmcomp/cmm_helpers.mli +++ b/asmcomp/cmm_helpers.mli @@ -624,14 +624,10 @@ val code_segment_table: string list -> phrase (** Generate data for a predefined exception *) val predef_exception: int -> string -> phrase -<<<<<<< HEAD -val plugin_header: (Cmx_format.unit_infos * Digest.t) list -> phrase -======= (** Generate data for a global string constant *) val emit_global_string_constant: string -> string -> phrase -val plugin_header: (Cmx_format.unit_infos * Digest.BLAKE128.t) list -> phrase ->>>>>>> cfbf2105cfe +val plugin_header: (Cmx_format.unit_infos * Digest.t) list -> phrase (** Emit constant symbols *) diff --git a/configure b/configure index 7c9406d02d2d..794bd467df40 100755 --- a/configure +++ b/configure @@ -3368,14 +3368,10 @@ ocamltest_libunix=None ocamltest_unix_impl="dummy" unix_library="" unix_directory="" -<<<<<<< HEAD -======= -diff_supports_color=false target_libdir_is_relative=false srcdir_abs='' srcdir_abs_real='' build_map_flags='' ->>>>>>> cfbf2105cfe # Information about the package diff --git a/configure.ac b/configure.ac index a4917f34b8c9..52b9677b0d6b 100644 --- a/configure.ac +++ b/configure.ac @@ -83,14 +83,10 @@ ocamltest_libunix=None ocamltest_unix_impl="dummy" unix_library="" unix_directory="" -<<<<<<< HEAD -======= -diff_supports_color=false target_libdir_is_relative=false srcdir_abs='' srcdir_abs_real='' build_map_flags='' ->>>>>>> cfbf2105cfe # Information about the package diff --git a/runtime/startup_byt.c b/runtime/startup_byt.c index 5181cf856be1..515dbf267f2d 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -492,8 +492,6 @@ CAMLexport void caml_main(char_os **argv) argv0 = proc_self_exe = caml_executable_name(); -<<<<<<< HEAD -======= /* 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 @@ -508,18 +506,12 @@ CAMLexport void caml_main(char_os **argv) fd = caml_attempt_open(&exe_name, &trail, 0); } ->>>>>>> cfbf2105cfe /* Little grasshopper wonders why we do that at all, since "The current executable is ocamlrun itself, it's never a bytecode program". Little grasshopper "ocamlc -custom" in mind should keep. 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. */ -<<<<<<< HEAD - 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; @@ -527,7 +519,6 @@ CAMLexport void caml_main(char_os **argv) } if (fd < 0 && caml_byte_program_mode == APPENDED) error("unable to open file '%s'", caml_stat_strdup_of_os(exe_name)); ->>>>>>> cfbf2105cfe } if (argv0 == NULL) diff --git a/testsuite/in_prefix/Makefile.test b/testsuite/in_prefix/Makefile.test index 49688d2d774b..9b619b00377e 100644 --- a/testsuite/in_prefix/Makefile.test +++ b/testsuite/in_prefix/Makefile.test @@ -23,12 +23,8 @@ DRIVER = ../tools/test_in_prefix$(EXE) endif DRIVER_ARGS = \ -<<<<<<< HEAD - $(VERBOSE_FLAG) --bindir "$(BINDIR)" --libdir "$(LIBDIR)" \ - $(call bool_to_with, shebangscripts, $(SHEBANGSCRIPTS)) \ -======= $(VERBOSE_FLAG) --bindir "$(BINDIR)" --libdir '$(TARGET_LIBDIR)' \ ->>>>>>> cfbf2105cfe + $(call bool_to_with, shebangscripts, $(SHEBANGSCRIPTS)) \ $(call bool_to_with, ocamlnat, $(INSTALL_OCAMLNAT)) \ $(call bool_to_with, ocamlopt, $(NATIVE_COMPILER)) \ $(OTHERLIBRARIES) --pwd "$(SRCDIR_ABS)/testsuite/in_prefix" From 52ebaad5fe65121c92d73b60dcc7d811476b3233 Mon Sep 17 00:00:00 2001 From: Gabriel Scherer Date: Thu, 14 May 2026 21:41:13 +0200 Subject: [PATCH 05/28] Merge pull request PR#14802 from dra27/fix-libdir-detection Fix detection of default arguments in configure (cherry picked from commit d315509e7eb6a386e0148b8d0e4290685b528d9a) --- Changes | 5 +++++ configure | 28 +++++++++++++++++++++++++--- configure.ac | 28 +++++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index f3f92e4fda95..c888a01a5e39 100644 --- a/Changes +++ b/Changes @@ -88,6 +88,11 @@ OCaml 5.4.1 (17 February 2026) (Xavier Leroy and NicolΓ‘s Ojeda BΓ€r, review by Olivier Nicole, Mindy Preston, and Edwin TΓΆrΓΆk) +- #14760, #14802: Correct the detection of argument defaults in configure, + fixing an incorrect error message when installing OCaml through opam on + OpenSUSE with the site-config package installed. + (David Allsopp, report by Edwin TΓΆrΓΆk, review by ???) + OCaml 5.4.0 (9 October 2025) ---------------------------- diff --git a/configure b/configure index 794bd467df40..7aff1cb13433 100755 --- a/configure +++ b/configure @@ -3310,6 +3310,28 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.4.2+dev0-2026-02-17" >&5 printf "%s\n" "$as_me: Configuring OCaml version 5.4.2+dev0-2026-02-17" >&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 + break ;; + -libdir=* | --libdir=* | --libdi=* | --libd=* | \ + -libdir | --libdir | --libdi | --libd) + libdir_given=yes + break ;; + esac +done + # Configuration variables ## Command-line arguments passed to configure @@ -23917,12 +23939,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 @@ -23999,7 +24021,7 @@ fi ;; #( ;; esac -if test x"$libdir" = x'${exec_prefix}/lib/ocaml' +if test x"$libdir_given" = 'xno' then : if test x"$bindir_to_libdir" != 'x' then : diff --git a/configure.ac b/configure.ac index 52b9677b0d6b..f00e0a5c46dc 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,28 @@ 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 + break ;; + -libdir=* | --libdir=* | --libdi=* | --libd=* | \ + -libdir | --libdir | --libdi | --libd) + libdir_given=yes + break ;; + esac +done + # Configuration variables ## Command-line arguments passed to configure @@ -2947,10 +2969,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 @@ -3004,7 +3026,7 @@ AS_CASE([$cygwin_build_env,$host], [ocaml_libdir='${exec_prefix}\lib\ocaml'], [ocaml_libdir="$libdir"])])]) -AS_IF([test x"$libdir" = x'${exec_prefix}/lib/ocaml'], +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 From fd46f79579b4a2aff8de95bb6fdc5d6a4c60fc7f Mon Sep 17 00:00:00 2001 From: Gabriel Scherer Date: Fri, 5 Jun 2026 14:44:18 +0200 Subject: [PATCH 06/28] Merge pull request PR#14846 from dra27/fix-configure Remove incorrect breaks in configure.ac (cherry picked from commit 1ecddf15a4cae8541784ee72d033430f503dad8a) --- Changes | 17 ++++++----------- configure | 6 ++---- configure.ac | 6 ++---- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Changes b/Changes index c888a01a5e39..92758b4d2ac4 100644 --- a/Changes +++ b/Changes @@ -14,12 +14,12 @@ OCaml 5.4 maintenance version normalised on both Windows and Unix. (David Allsopp, review by Jonah Beckford, Damien Doligez and Hugo Heuzard) -- #14244: 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]`. +- #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) @@ -88,11 +88,6 @@ OCaml 5.4.1 (17 February 2026) (Xavier Leroy and NicolΓ‘s Ojeda BΓ€r, review by Olivier Nicole, Mindy Preston, and Edwin TΓΆrΓΆk) -- #14760, #14802: Correct the detection of argument defaults in configure, - fixing an incorrect error message when installing OCaml through opam on - OpenSUSE with the site-config package installed. - (David Allsopp, report by Edwin TΓΆrΓΆk, review by ???) - OCaml 5.4.0 (9 October 2025) ---------------------------- diff --git a/configure b/configure index 7aff1cb13433..fb3a625b7e3f 100755 --- a/configure +++ b/configure @@ -3323,12 +3323,10 @@ do case $ac_arg in -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=* | \ -mandir | --mandir | --mandi | --mand | --man | --ma | --m) - mandir_given=yes - break ;; + mandir_given=yes;; -libdir=* | --libdir=* | --libdi=* | --libd=* | \ -libdir | --libdir | --libdi | --libd) - libdir_given=yes - break ;; + libdir_given=yes;; esac done diff --git a/configure.ac b/configure.ac index f00e0a5c46dc..a35507be0fe4 100644 --- a/configure.ac +++ b/configure.ac @@ -38,12 +38,10 @@ do case $ac_arg in -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=* | \ -mandir | --mandir | --mandi | --mand | --man | --ma | --m) - mandir_given=yes - break ;; + mandir_given=yes;; -libdir=* | --libdir=* | --libdi=* | --libd=* | \ -libdir | --libdir | --libdi | --libd) - libdir_given=yes - break ;; + libdir_given=yes;; esac done From aa220eb42e5a65d058a5db481e6f5a6589b65462 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Fri, 12 Jun 2026 21:14:04 +0100 Subject: [PATCH 07/28] 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 cbae9a1e1071..fb6bd9868715 100644 --- a/Makefile.common +++ b/Makefile.common @@ -195,10 +195,6 @@ endif # ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" # itself. HOST_LIBDIR ?= $(TARGET_LIBDIR) -OC_COMMON_LINKFLAGS += \ - -set-runtime-default \ - $(call QUOTE_SINGLE,standard_library_default=$(HOST_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 7f666cc11271..7168e234546b 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 48ebde875a7f..87d2ed3a6ee8 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 05be4b9e2a11..e234fa40ff31 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 b516759e1be1..d44bd049e849 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@|@ccomp_type@|@QS@} let c_compiler = {@QS@|@CC@|@QS@} let c_output_obj = {@QS@|@outputobj@|@QS@} From cb8765ba0dbeaf2f777e0a7c04e6ef6d95ed77a5 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Fri, 12 Jun 2026 21:13:52 +0100 Subject: [PATCH 08/28] Adapt to OCaml 5.4 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 | 12 ++++++------ 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.depend b/.depend index 0b649d84808f..78f95060cbbd 100644 --- a/.depend +++ b/.depend @@ -10732,6 +10732,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 \ @@ -10739,6 +10740,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 9d46fc8a7feb..503dad2b123e 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 @@ -609,6 +608,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. *) @@ -640,7 +649,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 d9ef6e62bcb4..9644b9b740ce 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 db861de76220..21587f727fc1 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -297,18 +297,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 c1b22ac0905c..7153f3c2ac03 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.4.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.4.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.4.2 *) val bytecode_cflags : string (** The flags ocamlc should pass to the C compiler *) @@ -209,17 +209,17 @@ val target_os_type: string val target_unix: bool (** True if [target_os_type = "Unix"] - @since 5.5 *) + @since 5.4.2 *) val target_win32: bool (** True if [target_os_type = "Win32"] - @since 5.5 *) + @since 5.4.2 *) val target_cygwin: bool (** True if [target_os_type = "Cygwin"] - @since 5.5 *) + @since 5.4.2 *) val asm: string (** The assembler (and flags) to use for assembling From 58f8ad38a58dde168791717234bb06afa783748e 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 09/28] Merge pull request PR#14245 from dra27/runtime-searching Relocatable OCaml - Searching and Suffixing (cherry picked from commit da60a2e7920fd441decc950de55c4d10a63f5878) --- .depend | 33 +- .github/workflows/build-msvc.yml | 1 + .github/workflows/build.yml | 14 +- .gitignore | 5 +- Changes | 32 ++ Makefile | 125 ++++++-- Makefile.build_config.in | 9 + Makefile.common | 22 +- Makefile.config.in | 4 + VERSION | 2 +- asmcomp/asmlink.ml | 18 +- build-aux/ocaml_version.m4 | 10 +- bytecomp/bytelink.ml | 324 +++++++++++++------- bytecomp/bytelink.mli | 3 + bytecomp/byterntm.mli | 33 ++ bytecomp/byterntm.mll | 123 ++++++++ bytecomp/dll.ml | 17 +- bytecomp/dll.mli | 7 +- configure | 278 +++++++++++++---- configure.ac | 171 ++++++++--- 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 | 24 +- testsuite/in_prefix/README.md | 19 +- testsuite/tools/cmdline.ml | 17 +- testsuite/tools/environment.ml | 80 ++--- testsuite/tools/environment.mli | 2 +- testsuite/tools/harness.ml | 16 +- testsuite/tools/harness.mli | 31 +- testsuite/tools/poisonedruntime.c | 27 ++ testsuite/tools/testBytecodeBinaries.ml | 220 +++++++++---- testsuite/tools/testLinkModes.ml | 103 +++++-- testsuite/tools/testRelocation.ml | 34 +- testsuite/tools/test_in_prefix.ml | 52 +++- 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 | 34 ++ utils/config.fixed.ml | 13 + utils/config.generated.ml.in | 15 + utils/config.mli | 74 +++++ utils/misc.ml | 131 ++++++++ utils/misc.mli | 104 +++++++ 71 files changed, 2512 insertions(+), 546 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 78f95060cbbd..121fbaae3d0f 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 : \ @@ -2480,6 +2481,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 @@ -8070,6 +8082,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 \ @@ -8077,6 +8090,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 : \ @@ -8093,6 +8107,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 \ @@ -8100,6 +8115,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 : @@ -10524,9 +10540,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 @@ -10562,6 +10580,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 \ @@ -10572,6 +10591,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 @@ -10615,13 +10635,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 \ @@ -10683,12 +10710,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 b16ae5badd88..2791a03a3046 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-codegen-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-codegen-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; @@ -278,7 +282,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 25d60d3aa363..bf260f27335a 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 @@ -271,6 +269,7 @@ META /testsuite/_retries /testsuite/tools/codegen +/testsuite/tools/poisonedruntime /testsuite/tools/expect /testsuite/tools/lexcmm.ml /testsuite/tools/parsecmm.ml diff --git a/Changes b/Changes index 92758b4d2ac4..e718ab0661f7 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,23 @@ OCaml 5.4 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.4 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 @@ -52,6 +78,12 @@ OCaml 5.4 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: - #14574, #14577, #14589: Fix wrong assembly code generated for ARM64 diff --git a/Makefile b/Makefile index a0b2badcb56d..d1069fd91c24 100644 --- a/Makefile +++ b/Makefile @@ -207,6 +207,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 \ @@ -644,7 +645,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) @@ -1102,12 +1106,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 @@ -1274,7 +1278,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 = \ @@ -1282,13 +1286,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 @@ -1296,9 +1300,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 @@ -1349,13 +1353,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) @@ -1389,15 +1395,16 @@ runtime/caml/jumptbl.h : runtime/caml/instruct.h $(SAK): runtime/sak.c runtime/caml/misc.h runtime/caml/config.h $(V_MKEXE)$(call SAK_BUILD,$@,$<) -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 @@ -1864,6 +1871,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 @@ -2031,6 +2045,9 @@ endif testsuite/tools/test_in_prefi%: CAMLOPT = $(BEST_OCAMLOPT) $(STDLIBFLAGS) +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 @@ -2077,6 +2094,7 @@ partialclean:: rm -f $(addprefix testsuite/lib/*.,cm* o obj a lib) rm -f $(addprefix testsuite/tools/*.,cm* o obj a lib) rm -f testsuite/tools/codegen testsuite/tools/codegen.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 \ @@ -2232,6 +2250,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 @@ -2500,6 +2525,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 @@ -2702,8 +2734,9 @@ endif INSTALL_LIBDIR_DYNLINK = $(INSTALL_LIBDIR)/dynlink # Installation + .PHONY: install -install: +install:: $(MKDIR) "$(INSTALL_BINDIR)" $(MKDIR) "$(INSTALL_LIBDIR)" ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" @@ -2713,13 +2746,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)" @@ -2871,11 +2946,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 2c96e5917020..c8df31978d76 100644 --- a/Makefile.build_config.in +++ b/Makefile.build_config.in @@ -109,6 +109,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@ + # How to build sak SAK_BUILD=@SAK_BUILD@ # How to invoke sak @@ -190,6 +193,10 @@ OC_NATIVE_LINKFLAGS = -g BUILD_TRIPLET = @build@ +# Zinc Runtime ID is needed for installation only +ZINC_RUNTIME_ID_HI = @zinc_runtime_id_hi@ +ZINC_RUNTIME_ID = @zinc_runtime_id_lo@$(ZINC_RUNTIME_ID_HI) + # Platform-dependent command to create symbolic links LN = @ln@ @@ -207,3 +214,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 fb6bd9868715..578f5d65f6ad 100644 --- a/Makefile.common +++ b/Makefile.common @@ -338,8 +338,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 @@ -363,8 +375,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 f6473a490bdb..cc7d9134f6ee 100644 --- a/Makefile.config.in +++ b/Makefile.config.in @@ -220,6 +220,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/VERSION b/VERSION index 1e4285bfa39e..e83ad3d60af9 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -5.4.2+dev0-2026-02-17 +5.5.0+dev0-2026-02-17 # 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/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 8e644e064ed2..452767825c3b 100644 --- a/build-aux/ocaml_version.m4 +++ b/build-aux/ocaml_version.m4 @@ -29,11 +29,19 @@ 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]) +<<<<<<< HEAD m4_define([OCAML__VERSION_MINOR], [4]) m4_define([OCAML__VERSION_PATCHLEVEL], [2]) +======= +m4_define([OCAML__VERSION_MINOR], [5]) +m4_define([OCAML__RELEASE_NUMBER], [21]) +m4_define([OCAML__VERSION_PATCHLEVEL], [0]) + +>>>>>>> da60a2e7920 # Note that the OCAML__VERSION_EXTRA string defined below is always empty # for officially-released versions of OCaml. m4_define([OCAML__VERSION_EXTRA], [dev0-2026-02-17]) diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index 503dad2b123e..7f1bff0f5544 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -297,13 +297,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 @@ -316,65 +309,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 "" @@ -384,75 +335,194 @@ 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 = + 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_zinc_hi (String.sub data 0 2) in + if Option.fold ~none:false ~some:Misc.RuntimeID.is_zinc zinc then + zinc, 2 + 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, search = if String.length !Clflags.use_runtime > 0 then +<<<<<<< HEAD (true, make_absolute !Clflags.use_runtime) +======= + (* Do not use BUILD_PATH_PREFIX_MAP mapping for this. *) + let runtime = !Clflags.use_runtime in + if Filename.is_relative runtime then + Filename.concat (Sys.getcwd ()) runtime, Config.Disable + else + runtime, Config.Disable +>>>>>>> da60a2e7920 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)) - 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 - 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 @@ -488,13 +558,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 = @@ -511,11 +596,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; @@ -788,13 +882,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" @@ -937,7 +1038,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/bytelink.mli b/bytecomp/bytelink.mli index 9644b9b740ce..912f98432111 100644 --- a/bytecomp/bytelink.mli +++ b/bytecomp/bytelink.mli @@ -30,8 +30,11 @@ val linkdeps_unit : val extract_crc_interfaces: unit -> crcs +<<<<<<< HEAD val to_utf_8_seq : string -> Uchar.t Seq.t +======= +>>>>>>> da60a2e7920 type error = | File_not_found of filepath | Not_an_object_file of filepath 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 fb3a625b7e3f..86183ba45dbb 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.4.2+dev0-2026-02-17. +# Generated by GNU Autoconf 2.71 for OCaml 5.5.0+dev0-2026-02-17. # # Report bugs to . # @@ -677,8 +677,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='OCaml' PACKAGE_TARNAME='ocaml' -PACKAGE_VERSION='5.4.2+dev0-2026-02-17' -PACKAGE_STRING='OCaml 5.4.2+dev0-2026-02-17' +PACKAGE_VERSION='5.5.0+dev0-2026-02-17' +PACKAGE_STRING='OCaml 5.5.0+dev0-2026-02-17' PACKAGE_BUGREPORT='caml-list@inria.fr' PACKAGE_URL='http://www.ocaml.org' @@ -796,6 +796,13 @@ build_os build_vendor build_cpu build +runtime_search_target +runtime_search +suffixing +native_runtime_id +bytecode_runtime_id +zinc_runtime_id_hi +zinc_runtime_id_lo build_map_flags srcdir_abs_real srcdir_abs @@ -880,6 +887,8 @@ natdynlink supports_shared_libraries mklib AR +launch_method_target +launch_method shebangscripts winpthreads_source_include_dir winpthreads_source_dir @@ -954,6 +963,7 @@ CMO_MAGIC_NUMBER CMI_MAGIC_NUMBER EXEC_MAGIC_NUMBER MAGIC_LENGTH +OCAML_RELEASE_NUMBER OCAML_VERSION_SHORT OCAML_VERSION_EXTRA OCAML_VERSION_PATCHLEVEL @@ -1047,6 +1057,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 @@ -1634,7 +1647,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.4.2+dev0-2026-02-17 to adapt to many kinds of systems. +\`configure' configures OCaml 5.5.0+dev0-2026-02-17 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1701,7 +1714,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of OCaml 5.4.2+dev0-2026-02-17:";; + short | recursive ) echo "Configuration of OCaml 5.5.0+dev0-2026-02-17:";; esac cat <<\_ACEOF @@ -1755,6 +1768,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-pic[=PKGS] try to use only PIC/non-PIC objects [default=use @@ -1894,7 +1914,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -OCaml configure 5.4.2+dev0-2026-02-17 +OCaml configure 5.5.0+dev0-2026-02-17 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2551,7 +2571,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.4.2+dev0-2026-02-17, which was +It was created by OCaml $as_me 5.5.0+dev0-2026-02-17, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3307,8 +3327,8 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.4.2+dev0-2026-02-17" >&5 -printf "%s\n" "$as_me: Configuring OCaml version 5.4.2+dev0-2026-02-17" >&6;} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.5.0+dev0-2026-02-17" >&5 +printf "%s\n" "$as_me: Configuring OCaml version 5.5.0+dev0-2026-02-17" >&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. @@ -3392,6 +3412,8 @@ target_libdir_is_relative=false srcdir_abs='' srcdir_abs_real='' build_map_flags='' +runtime_search='' +runtime_search_target='' # Information about the package @@ -3406,7 +3428,7 @@ build_map_flags='' -VERSION=5.4.2+dev0-2026-02-17 +VERSION=5.5.0+dev0-2026-02-17 OCAML_DEVELOPMENT_VERSION=true @@ -3414,13 +3436,15 @@ OCAML_RELEASE_EXTRA='Some (Plus, "dev0-2026-02-17")' OCAML_VERSION_MAJOR=5 -OCAML_VERSION_MINOR=4 +OCAML_VERSION_MINOR=5 -OCAML_VERSION_PATCHLEVEL=2 +OCAML_VERSION_PATCHLEVEL=0 OCAML_VERSION_EXTRA=dev0-2026-02-17 -OCAML_VERSION_SHORT=5.4 +OCAML_VERSION_SHORT=5.5 + +OCAML_RELEASE_NUMBER=21 printf "%s\n" "#define MAGIC_NUMBER_PREFIX \"Caml1999\"" >>confdefs.h @@ -3544,6 +3568,8 @@ LINEAR_MAGIC_NUMBER=Caml1999L036 + + @@ -3596,6 +3622,13 @@ LINEAR_MAGIC_NUMBER=Caml1999L036 + + + + + + + @@ -3646,17 +3679,19 @@ ac_config_files="$ac_config_files testsuite/tools/toolchain.ml" # Definitions related to the version of OCaml printf "%s\n" "#define OCAML_VERSION_MAJOR 5" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_MINOR 4" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_MINOR 5" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_PATCHLEVEL 2" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_PATCHLEVEL 0" >>confdefs.h printf "%s\n" "#define OCAML_VERSION_ADDITIONAL \"dev0-2026-02-17\"" >>confdefs.h printf "%s\n" "#define OCAML_VERSION_EXTRA \"dev0-2026-02-17\"" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION 50402" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION 50500" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_STRING \"5.4.2+dev0-2026-02-17\"" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_STRING \"5.5.0+dev0-2026-02-17\"" >>confdefs.h + +printf "%s\n" "#define OCAML_RELEASE_NUMBER 21" >>confdefs.h # Works out how many "o"s are needed in quoted strings @@ -4270,12 +4305,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 @@ -4382,6 +4417,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} @@ -4474,13 +4564,13 @@ else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if the installed OCaml compiler can build the cross compiler" >&5 printf %s "checking if the installed OCaml compiler can build the cross compiler... " >&6; } already_installed_version="$(ocamlc -vnum)" - if test x"5.4.2+dev0-2026-02-17" = x"$already_installed_version" + if test x"5.5.0+dev0-2026-02-17" = x"$already_installed_version" then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes (5.4.2+dev0-2026-02-17)" >&5 -printf "%s\n" "yes (5.4.2+dev0-2026-02-17)" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes (5.5.0+dev0-2026-02-17)" >&5 +printf "%s\n" "yes (5.5.0+dev0-2026-02-17)" >&6; } else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no (5.4.2+dev0-2026-02-17 vs $already_installed_version)" >&5 -printf "%s\n" "no (5.4.2+dev0-2026-02-17 vs $already_installed_version)" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no (5.5.0+dev0-2026-02-17 vs $already_installed_version)" >&5 +printf "%s\n" "no (5.5.0+dev0-2026-02-17 vs $already_installed_version)" >&6; } as_fn_error $? "exiting" "$LINENO" 5 fi cross_compiler=true @@ -15184,9 +15274,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 ;; #( *-w64-mingw32*|*-pc-windows) : ;; #( @@ -15198,12 +15288,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 @@ -19499,6 +19583,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" @@ -24080,6 +24173,89 @@ case $target 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_lo="b1" +zinc_runtime_id_hi="${quintet2_zinc}${quintet3_zinc}" +bytecode_runtime_id="b${quintet1}${quintet2_byte}${quintet3}" +native_runtime_id="b${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 @@ -24245,18 +24421,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 : @@ -24283,9 +24457,9 @@ else $as_nop TARGET_LIBDIR="$bindir_to_libdir" fi 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" @@ -24764,7 +24938,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.4.2+dev0-2026-02-17, which was +This file was extended by OCaml $as_me 5.5.0+dev0-2026-02-17, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -24837,7 +25011,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.4.2+dev0-2026-02-17 +OCaml config.status 5.5.0+dev0-2026-02-17 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" @@ -25242,11 +25416,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")' @@ -25288,7 +25457,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" ;; @@ -26428,10 +26596,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 a35507be0fe4..34a072fff4d9 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]) @@ -212,6 +215,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]) @@ -296,6 +301,13 @@ 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_lo]) +AC_SUBST([zinc_runtime_id_hi]) +AC_SUBST([bytecode_runtime_id]) +AC_SUBST([native_runtime_id]) +AC_SUBST([suffixing]) +AC_SUBST([runtime_search]) +AC_SUBST([runtime_search_target]) ## Generated files @@ -327,6 +339,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) @@ -634,9 +647,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], @@ -706,6 +719,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])]) @@ -964,32 +1008,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'])], [*-w64-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")']) - # Checks for programs ## Check for the C compiler: done by libtool @@ -1918,6 +1944,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" @@ -3063,6 +3092,80 @@ AS_CASE([$target], # 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_lo="QUINTET0[]QUINTET1_ZINC" +zinc_runtime_id_hi="${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 @@ -3075,18 +3178,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"], @@ -3100,8 +3201,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 10ac71317494..a2f9da0db756 100644 --- a/driver/compenv.ml +++ b/driver/compenv.ml @@ -626,7 +626,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 @@ -659,8 +659,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 @@ -673,7 +673,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 e3229f92dd5a..157a4417d8d8 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" @@ -524,6 +529,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" @@ -938,10 +962,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 @@ -1072,6 +1099,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; @@ -1139,6 +1167,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; @@ -1976,7 +2006,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 @@ -1990,6 +2022,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 d285214bf572..2f0a1e184115 100644 --- a/driver/main_args.mli +++ b/driver/main_args.mli @@ -161,10 +161,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 5785f482bfcd..8f476b1637e2 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 1a0138c43417..bf2178089e30 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 de8bf0ba8ccf..1de924d9a91e 100644 --- a/ocamltest/ocaml_actions.ml +++ b/ocamltest/ocaml_actions.ml @@ -598,7 +598,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 5f68a3596a5a..c56188c0d478 100644 --- a/ocamltest/ocamltest_config.ml.in +++ b/ocamltest/ocamltest_config.ml.in @@ -102,3 +102,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 06d9872422f3..f534493e937f 100644 --- a/ocamltest/ocamltest_config.mli +++ b/ocamltest/ocamltest_config.mli @@ -145,3 +145,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 231bda97d209..67b588024695 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 = {magic|@CMO_MAGIC_NUMBER@|magic} and cma_magic_number = {magic|@CMA_MAGIC_NUMBER@|magic} and cmxs_magic_number = {magic|@CMXS_MAGIC_NUMBER@|magic} + +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 4b4395f26543..518aa1f60aeb 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. tools/autogen # 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 cb1efb072783..7be4010a2b37 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 d43caf0a6362..53251d3ab9cf 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(STARTUP, "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 515dbf267f2d..0aee158dcd4c 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -401,6 +401,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..22614ad0372d 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_HI),\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 9b619b00377e..30c31c32d9ee 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,29 @@ else VERBOSE_FLAG = endif -test-in-prefix: $(DRIVER) ../tools/main_in_c.$(O) +# 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/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 3453d64a63d6..17af7fd0bf98 100644 --- a/testsuite/tools/cmdline.ml +++ b/testsuite/tools/cmdline.ml @@ -104,9 +104,13 @@ 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; +<<<<<<< HEAD bytecode_shebangs_by_default = false; shebangscripts = false; +======= + bytecode_shebangs_by_default = false; filename_mangling = false; +>>>>>>> da60a2e7920 libraries = []} in let error fmt = Printf.ksprintf (fun s -> raise (Arg.Bad s)) fmt in @@ -164,16 +168,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 599890349a8e..f104c0539636 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 a2cf3e2e74af..1e8e1554dea6 100644 --- a/testsuite/tools/harness.ml +++ b/testsuite/tools/harness.ml @@ -24,11 +24,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 @@ -36,11 +40,15 @@ 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; +<<<<<<< HEAD shebangscripts: bool; +======= + filename_mangling: bool; +>>>>>>> da60a2e7920 libraries: string list list } @@ -90,7 +98,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 1a3a50c2993b..5f21ae3a0f3c 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,28 @@ 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. *) +<<<<<<< HEAD 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; +>>>>>>> da60a2e7920 (** 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..23cb0653b518 100644 --- a/testsuite/tools/testBytecodeBinaries.ml +++ b/testsuite/tools/testBytecodeBinaries.ml @@ -35,59 +35,42 @@ 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" + || 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 +<<<<<<< HEAD runtime = ocamlrun in let expected_launch_mode = @@ -157,7 +140,140 @@ let run config env = | _ -> if not fails then Harness.fail_because "%s: not expected to have failed" program +======= + 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 + 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 + 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 = 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 = + Environment.run_process + ~fails:true 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 () (* Expected outcome is a zero exit code *) + 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 +>>>>>>> da60a2e7920 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 7168e234546b..220f25e6b541 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 -> @@ -536,8 +591,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:[Config.compression_c_libraries] ["-output-complete-obj"; "-noautolink"; "-cclib"; "-lunixnat"; @@ -694,15 +750,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) @@ -730,5 +784,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 21587f727fc1..b476d28ffdcb 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -77,7 +77,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 @@ -111,7 +111,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. *) @@ -182,11 +182,6 @@ let libdir_rules config file = "ocamlcommon.cma"] in (* The compiler's artefacts are all compiled with -g *) (~stdlib, ~ocaml_debug:true, ~c_debug:false, ~s: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, ~ocaml_debug:false, ~c_debug:false, ~s:false) else if ext = ".cmxs" then (* All the .cmxs files built by the distribution at present include C objects and obviously contain assembled objects. *) @@ -493,17 +488,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 = @@ -622,7 +606,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 c373c7f054c4..0e103a7cad0e 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; @@ -68,6 +68,7 @@ let run_tests ~sh config env = TestBytecodeBinaries.run config env; TestLinkModes.run ~sh config env +<<<<<<< HEAD type launch_method = | Shebang_bin_sh of string | Executable @@ -103,6 +104,11 @@ let read_runtime_launch_info file = {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)) +>>>>>>> da60a2e7920 let () = let ~config, ~pwd, ~prefix, ~bindir:_, ~bindir_suffix, ~libdir, @@ -151,8 +157,9 @@ 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 +<<<<<<< HEAD read_runtime_launch_info file in let header_size = let {buffer; executable_offset; _} = runtime_launch_info in @@ -161,11 +168,23 @@ let () = runtime_launch_info.launcher <> Executable in let launcher_searches_for_ocamlrun = Sys.win32 in let target_launcher_searches_for_ocamlrun = Sys.win32 in +======= + In_channel.with_open_bin file @@ fun ic -> + In_channel.length ic, (input_char ic <> '\000') + in + let bytecode_shebangs_by_default = + 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 +>>>>>>> da60a2e7920 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 +201,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 @@ -193,7 +212,7 @@ let () = && (not Toolchain.c_compiler_always_embeds_build_path || not Toolchain.c_compiler_debug_paths_can_be_absolute) 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 +274,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 852e21079781..4c7e6fd020bb 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 @@ -87,6 +89,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 f147ad8d3662..8d12d167f73c 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 print_variance : bool ref diff --git a/utils/config.common.ml.in b/utils/config.common.ml.in index 87d2ed3a6ee8..295e9c5f307a 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -20,7 +20,19 @@ (* The main OCaml version string has moved to ../build-aux/ocaml_version.m4 *) let version = Sys.ocaml_version +<<<<<<< HEAD let standard_library_default_raw = standard_library_default +======= +(* 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 = 21 + +external standard_library_default : unit -> string = "%standard_library_default" + +let standard_library_default_raw = standard_library_default () +>>>>>>> da60a2e7920 external stdlib_dirs : string -> string * string option = "caml_sys_get_stdlib_dirs" @@ -44,6 +56,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 +79,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 @@ -138,6 +170,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 e234fa40ff31..edc5a8fecd77 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -21,7 +21,11 @@ let boot_cannot_call s = "/ The boot compiler should not call " ^ s let bindir = "/tmp" +<<<<<<< HEAD let standard_library_default = "/tmp" +======= +let target_bindir = bindir +>>>>>>> da60a2e7920 let ccomp_type = "n/a" let c_compiler = boot_cannot_call "the C compiler" let c_output_obj = "" @@ -58,6 +62,8 @@ let align_double = true let align_int64 = 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" @@ -80,3 +86,10 @@ let target = host let systhread_supported = false let flexdll_dirs = [] let ar_supports_response_files = true +<<<<<<< HEAD +======= +let shebangscripts = false +let suffixing = false +let launch_method = "sh" +let search_method = "always" +>>>>>>> da60a2e7920 diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index d44bd049e849..b640f204bd68 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@} @@ -71,6 +72,9 @@ let align_int64 = @align_int64@ 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@} @@ -100,3 +104,14 @@ let flexdll_dirs = [@flexdll_dir@] let ar_supports_response_files = @ar_supports_response_files@ let tsan = @tsan@ +<<<<<<< HEAD +======= + +let shebangscripts = @shebangscripts@ + +let suffixing = @suffixing@ + +let launch_method = {@QS@|@launch_method_target@|@QS@} + +let search_method = {@QS@|@runtime_search_target@|@QS@} +>>>>>>> da60a2e7920 diff --git a/utils/config.mli b/utils/config.mli index 7153f3c2ac03..224e3438bb4e 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.4.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 @@ -326,6 +341,65 @@ val ar_supports_response_files: bool val tsan : bool (** Whether ThreadSanitizer instrumentation is enabled *) +<<<<<<< HEAD +======= +(** 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 shebangscripts : bool +(** Whether the target supports shebang scripts + + @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 *) + +>>>>>>> da60a2e7920 (** Access to configuration values *) val print_config : out_channel -> unit diff --git a/utils/misc.ml b/utils/misc.ml index e769e5ba664c..25f847f7cda4 100644 --- a/utils/misc.ml +++ b/utils/misc.ml @@ -1415,3 +1415,134 @@ 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 of_zinc_hi ?(dev = not Config.is_official_release) + ?(release = Config.release_number) s = + Option.map (fun id -> {id with dev; release}) (of_string ("00" ^ s)) + + 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 7622cc62cc14..b6ad115f7df5 100644 --- a/utils/misc.mli +++ b/utils/misc.mli @@ -868,6 +868,110 @@ 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 of_zinc_hi: ?dev:bool -> ?release:int -> string -> t option + (** Converts hi 2 characters of the representation back to a {!t} (using the + default version information from {!Config}. *) + + 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 ad1cb0563e64fcf2518ae7c8a6cca9f1d46d20d5 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Tue, 24 Mar 2026 15:56:54 +0000 Subject: [PATCH 10/28] Merge pull request PR#14669 from dra27/version-free-bootstrap Allow `Config.is_official_release` to be changed without bootstrap (cherry picked from commit c40b6876b2d92cc3b07c73fed1669a3d485e7bb5) --- Makefile.build_config.in | 3 +-- bytecomp/bytelink.ml | 4 ++-- configure | 7 ++----- configure.ac | 6 ++---- stdlib/Makefile | 2 +- utils/misc.ml | 4 ---- utils/misc.mli | 4 ---- 7 files changed, 8 insertions(+), 22 deletions(-) diff --git a/Makefile.build_config.in b/Makefile.build_config.in index c8df31978d76..b0c1b1da96c3 100644 --- a/Makefile.build_config.in +++ b/Makefile.build_config.in @@ -194,8 +194,7 @@ OC_NATIVE_LINKFLAGS = -g BUILD_TRIPLET = @build@ # Zinc Runtime ID is needed for installation only -ZINC_RUNTIME_ID_HI = @zinc_runtime_id_hi@ -ZINC_RUNTIME_ID = @zinc_runtime_id_lo@$(ZINC_RUNTIME_ID_HI) +ZINC_RUNTIME_ID = @zinc_runtime_id@ # Platform-dependent command to create symbolic links LN = @ln@ diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index 7f1bff0f5544..aff4fffc6be7 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -432,9 +432,9 @@ let write_header outchan = else if data.[0] = '\000' then None, 1 else - let zinc = Misc.RuntimeID.of_zinc_hi (String.sub data 0 2) in + 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, 2 + zinc, 4 else raise (Error (Camlheader ("corrupt header", header))) in diff --git a/configure b/configure index 86183ba45dbb..9af901886168 100755 --- a/configure +++ b/configure @@ -801,8 +801,7 @@ runtime_search suffixing native_runtime_id bytecode_runtime_id -zinc_runtime_id_hi -zinc_runtime_id_lo +zinc_runtime_id build_map_flags srcdir_abs_real srcdir_abs @@ -3634,7 +3633,6 @@ LINEAR_MAGIC_NUMBER=Caml1999L036 - ## Generated files @@ -24246,8 +24244,7 @@ esac quintet3_zinc="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet3_zinc \) + 1))" quintet3="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet3 \) + 1))" -zinc_runtime_id_lo="b1" -zinc_runtime_id_hi="${quintet2_zinc}${quintet3_zinc}" +zinc_runtime_id="b1${quintet2_zinc}${quintet3_zinc}" bytecode_runtime_id="b${quintet1}${quintet2_byte}${quintet3}" native_runtime_id="b${quintet1}${quintet2_native}${quintet3}" diff --git a/configure.ac b/configure.ac index 34a072fff4d9..1e69ec8ea218 100644 --- a/configure.ac +++ b/configure.ac @@ -301,8 +301,7 @@ 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_lo]) -AC_SUBST([zinc_runtime_id_hi]) +AC_SUBST([zinc_runtime_id]) AC_SUBST([bytecode_runtime_id]) AC_SUBST([native_runtime_id]) AC_SUBST([suffixing]) @@ -3152,8 +3151,7 @@ AS_CASE([$target,$windows_unicode], quintet3_zinc=BASE32([$quintet3_zinc]) quintet3=BASE32([$quintet3]) -zinc_runtime_id_lo="QUINTET0[]QUINTET1_ZINC" -zinc_runtime_id_hi="${quintet2_zinc}${quintet3_zinc}" +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}" diff --git a/stdlib/Makefile b/stdlib/Makefile index 22614ad0372d..1e775f4fd17d 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -86,7 +86,7 @@ installopt-default: MANGLING = $(filter true,$(SUFFIXING)) runtime-launch-info: tmpheader.exe - @{ printf '$(if $(MANGLING),$(ZINC_RUNTIME_ID_HI),\000)'; \ + @{ printf '$(if $(MANGLING),$(ZINC_RUNTIME_ID),\000)'; \ cat $^; } > $@ # The mingw-w64 and MSVC versions of tmpheader.exe are linked with special flags diff --git a/utils/misc.ml b/utils/misc.ml index 25f847f7cda4..47c9893b24a5 100644 --- a/utils/misc.ml +++ b/utils/misc.ml @@ -1519,10 +1519,6 @@ module RuntimeID = struct else None - let of_zinc_hi ?(dev = not Config.is_official_release) - ?(release = Config.release_number) s = - Option.map (fun id -> {id with dev; release}) (of_string ("00" ^ s)) - let ocamlrun variant runtime_id = if is_zinc runtime_id then Printf.sprintf "ocamlrun%s-%s" variant (to_string runtime_id) diff --git a/utils/misc.mli b/utils/misc.mli index b6ad115f7df5..64e779438ef5 100644 --- a/utils/misc.mli +++ b/utils/misc.mli @@ -939,10 +939,6 @@ module RuntimeID : sig val of_string: string -> t option (** Converts the 4-character representation back to a {!t} *) - val of_zinc_hi: ?dev:bool -> ?release:int -> string -> t option - (** Converts hi 2 characters of the representation back to a {!t} (using the - default version information from {!Config}. *) - val ocamlrun: string -> t -> string (** [ocamlrun variant runtime_id] returns the name for the runtime for the given Zinc Runtime ID. *) From e18e51bd5a1d05abc7c8e08e0b574b04bb632b19 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sun, 6 Sep 2026 14:52:08 +0100 Subject: [PATCH 11/28] Resolve conflicts --- VERSION | 2 +- build-aux/ocaml_version.m4 | 7 +- bytecomp/bytelink.ml | 7 +- bytecomp/bytelink.mli | 3 - configure | 58 ++++++++-------- testsuite/tools/cmdline.ml | 6 +- testsuite/tools/harness.ml | 3 - testsuite/tools/harness.mli | 4 -- testsuite/tools/testBytecodeBinaries.ml | 89 ++++--------------------- testsuite/tools/test_in_prefix.ml | 15 +---- utils/config.common.ml.in | 10 +-- utils/config.fixed.ml | 9 +-- utils/config.generated.ml.in | 5 -- utils/config.mli | 8 --- 14 files changed, 51 insertions(+), 175 deletions(-) diff --git a/VERSION b/VERSION index e83ad3d60af9..1e4285bfa39e 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -5.5.0+dev0-2026-02-17 +5.4.2+dev0-2026-02-17 # 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 452767825c3b..7038481fd49a 100644 --- a/build-aux/ocaml_version.m4 +++ b/build-aux/ocaml_version.m4 @@ -33,15 +33,10 @@ m4_define([OCAML__DEVELOPMENT_VERSION], [true]) # incremented with each minor release, and likewise must be an unpadded integer. m4_define([OCAML__VERSION_MAJOR], [5]) -<<<<<<< HEAD m4_define([OCAML__VERSION_MINOR], [4]) +m4_define([OCAML__RELEASE_NUMBER], [20]) m4_define([OCAML__VERSION_PATCHLEVEL], [2]) -======= -m4_define([OCAML__VERSION_MINOR], [5]) -m4_define([OCAML__RELEASE_NUMBER], [21]) -m4_define([OCAML__VERSION_PATCHLEVEL], [0]) ->>>>>>> da60a2e7920 # Note that the OCAML__VERSION_EXTRA string defined below is always empty # for officially-released versions of OCaml. m4_define([OCAML__VERSION_EXTRA], [dev0-2026-02-17]) diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index aff4fffc6be7..c0edcb166949 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -446,16 +446,11 @@ let write_header outchan = in let runtime, search = if String.length !Clflags.use_runtime > 0 then -<<<<<<< HEAD - (true, make_absolute !Clflags.use_runtime) -======= - (* Do not use BUILD_PATH_PREFIX_MAP mapping for this. *) let runtime = !Clflags.use_runtime in if Filename.is_relative runtime then Filename.concat (Sys.getcwd ()) runtime, Config.Disable else - runtime, Config.Disable ->>>>>>> da60a2e7920 + make_absolute runtime, Config.Disable else let runtime = let runtime = "ocamlrun" ^ !Clflags.runtime_variant in diff --git a/bytecomp/bytelink.mli b/bytecomp/bytelink.mli index 912f98432111..9644b9b740ce 100644 --- a/bytecomp/bytelink.mli +++ b/bytecomp/bytelink.mli @@ -30,11 +30,8 @@ val linkdeps_unit : val extract_crc_interfaces: unit -> crcs -<<<<<<< HEAD val to_utf_8_seq : string -> Uchar.t Seq.t -======= ->>>>>>> da60a2e7920 type error = | File_not_found of filepath | Not_an_object_file of filepath diff --git a/configure b/configure index 9af901886168..3aafe1cfc588 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.5.0+dev0-2026-02-17. +# Generated by GNU Autoconf 2.71 for OCaml 5.4.2+dev0-2026-02-17. # # Report bugs to . # @@ -677,8 +677,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='OCaml' PACKAGE_TARNAME='ocaml' -PACKAGE_VERSION='5.5.0+dev0-2026-02-17' -PACKAGE_STRING='OCaml 5.5.0+dev0-2026-02-17' +PACKAGE_VERSION='5.4.2+dev0-2026-02-17' +PACKAGE_STRING='OCaml 5.4.2+dev0-2026-02-17' PACKAGE_BUGREPORT='caml-list@inria.fr' PACKAGE_URL='http://www.ocaml.org' @@ -1646,7 +1646,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.5.0+dev0-2026-02-17 to adapt to many kinds of systems. +\`configure' configures OCaml 5.4.2+dev0-2026-02-17 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1713,7 +1713,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of OCaml 5.5.0+dev0-2026-02-17:";; + short | recursive ) echo "Configuration of OCaml 5.4.2+dev0-2026-02-17:";; esac cat <<\_ACEOF @@ -1913,7 +1913,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -OCaml configure 5.5.0+dev0-2026-02-17 +OCaml configure 5.4.2+dev0-2026-02-17 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2570,7 +2570,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.5.0+dev0-2026-02-17, which was +It was created by OCaml $as_me 5.4.2+dev0-2026-02-17, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3326,8 +3326,8 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.5.0+dev0-2026-02-17" >&5 -printf "%s\n" "$as_me: Configuring OCaml version 5.5.0+dev0-2026-02-17" >&6;} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.4.2+dev0-2026-02-17" >&5 +printf "%s\n" "$as_me: Configuring OCaml version 5.4.2+dev0-2026-02-17" >&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. @@ -3427,7 +3427,7 @@ runtime_search_target='' -VERSION=5.5.0+dev0-2026-02-17 +VERSION=5.4.2+dev0-2026-02-17 OCAML_DEVELOPMENT_VERSION=true @@ -3435,15 +3435,15 @@ OCAML_RELEASE_EXTRA='Some (Plus, "dev0-2026-02-17")' OCAML_VERSION_MAJOR=5 -OCAML_VERSION_MINOR=5 +OCAML_VERSION_MINOR=4 -OCAML_VERSION_PATCHLEVEL=0 +OCAML_VERSION_PATCHLEVEL=2 OCAML_VERSION_EXTRA=dev0-2026-02-17 -OCAML_VERSION_SHORT=5.5 +OCAML_VERSION_SHORT=5.4 -OCAML_RELEASE_NUMBER=21 +OCAML_RELEASE_NUMBER=20 printf "%s\n" "#define MAGIC_NUMBER_PREFIX \"Caml1999\"" >>confdefs.h @@ -3677,19 +3677,19 @@ ac_config_files="$ac_config_files testsuite/tools/toolchain.ml" # Definitions related to the version of OCaml printf "%s\n" "#define OCAML_VERSION_MAJOR 5" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_MINOR 5" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_MINOR 4" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_PATCHLEVEL 0" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_PATCHLEVEL 2" >>confdefs.h printf "%s\n" "#define OCAML_VERSION_ADDITIONAL \"dev0-2026-02-17\"" >>confdefs.h printf "%s\n" "#define OCAML_VERSION_EXTRA \"dev0-2026-02-17\"" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION 50500" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION 50402" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_STRING \"5.5.0+dev0-2026-02-17\"" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_STRING \"5.4.2+dev0-2026-02-17\"" >>confdefs.h -printf "%s\n" "#define OCAML_RELEASE_NUMBER 21" >>confdefs.h +printf "%s\n" "#define OCAML_RELEASE_NUMBER 20" >>confdefs.h # Works out how many "o"s are needed in quoted strings @@ -4562,13 +4562,13 @@ else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if the installed OCaml compiler can build the cross compiler" >&5 printf %s "checking if the installed OCaml compiler can build the cross compiler... " >&6; } already_installed_version="$(ocamlc -vnum)" - if test x"5.5.0+dev0-2026-02-17" = x"$already_installed_version" + if test x"5.4.2+dev0-2026-02-17" = x"$already_installed_version" then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes (5.5.0+dev0-2026-02-17)" >&5 -printf "%s\n" "yes (5.5.0+dev0-2026-02-17)" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes (5.4.2+dev0-2026-02-17)" >&5 +printf "%s\n" "yes (5.4.2+dev0-2026-02-17)" >&6; } else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no (5.5.0+dev0-2026-02-17 vs $already_installed_version)" >&5 -printf "%s\n" "no (5.5.0+dev0-2026-02-17 vs $already_installed_version)" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no (5.4.2+dev0-2026-02-17 vs $already_installed_version)" >&5 +printf "%s\n" "no (5.4.2+dev0-2026-02-17 vs $already_installed_version)" >&6; } as_fn_error $? "exiting" "$LINENO" 5 fi cross_compiler=true @@ -24244,9 +24244,9 @@ esac quintet3_zinc="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet3_zinc \) + 1))" quintet3="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet3 \) + 1))" -zinc_runtime_id="b1${quintet2_zinc}${quintet3_zinc}" -bytecode_runtime_id="b${quintet1}${quintet2_byte}${quintet3}" -native_runtime_id="b${quintet1}${quintet2_native}${quintet3}" +zinc_runtime_id="91${quintet2_zinc}${quintet3_zinc}" +bytecode_runtime_id="9${quintet1}${quintet2_byte}${quintet3}" +native_runtime_id="9${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 @@ -24935,7 +24935,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.5.0+dev0-2026-02-17, which was +This file was extended by OCaml $as_me 5.4.2+dev0-2026-02-17, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -25008,7 +25008,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.5.0+dev0-2026-02-17 +OCaml config.status 5.4.2+dev0-2026-02-17 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/testsuite/tools/cmdline.ml b/testsuite/tools/cmdline.ml index 17af7fd0bf98..46b6a9424af7 100644 --- a/testsuite/tools/cmdline.ml +++ b/testsuite/tools/cmdline.ml @@ -106,12 +106,8 @@ let parse argv = ref {has_ocamlnat = false; has_ocamlopt = false; has_relative_libdir = None; has_runtime_search = Disable; launcher_searches_for_ocamlrun = false; target_launcher_searches_for_ocamlrun = false; -<<<<<<< HEAD bytecode_shebangs_by_default = false; shebangscripts = false; -======= - bytecode_shebangs_by_default = false; filename_mangling = false; ->>>>>>> da60a2e7920 - libraries = []} + filename_mangling = false; libraries = []} in let error fmt = Printf.ksprintf (fun s -> raise (Arg.Bad s)) fmt in let check_tree () = diff --git a/testsuite/tools/harness.ml b/testsuite/tools/harness.ml index 1e8e1554dea6..28d555f5f32a 100644 --- a/testsuite/tools/harness.ml +++ b/testsuite/tools/harness.ml @@ -44,11 +44,8 @@ module Import = struct launcher_searches_for_ocamlrun: bool; target_launcher_searches_for_ocamlrun: bool; bytecode_shebangs_by_default: bool; -<<<<<<< HEAD shebangscripts: bool; -======= filename_mangling: bool; ->>>>>>> da60a2e7920 libraries: string list list } diff --git a/testsuite/tools/harness.mli b/testsuite/tools/harness.mli index 5f21ae3a0f3c..c42e8531c2f3 100644 --- a/testsuite/tools/harness.mli +++ b/testsuite/tools/harness.mli @@ -75,15 +75,11 @@ module Import : sig bytecode_shebangs_by_default: bool; (** True if ocamlc uses a shebang-style header rather than an executable header for tendered bytecode executables. *) -<<<<<<< HEAD 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; ->>>>>>> da60a2e7920 (** Sorted list of basenames of libraries to test. Derived from {v [$(OTHERLIBRARIES)] v} - {v Makefile.config v} *) } diff --git a/testsuite/tools/testBytecodeBinaries.ml b/testsuite/tools/testBytecodeBinaries.ml index 23cb0653b518..5bc4a97cc0f3 100644 --- a/testsuite/tools/testBytecodeBinaries.ml +++ b/testsuite/tools/testBytecodeBinaries.ml @@ -70,77 +70,6 @@ let run config env = if Filename.extension binary = ".exe" then Filename.remove_extension binary else -<<<<<<< HEAD - runtime = ocamlrun - in - let expected_launch_mode = - if config.shebangscripts then - Header_shebang - else - Header_exe - 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 '.') - 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 expected to return with a non-zero exit code" - 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 -======= binary in name <> "ocamldoc" && name <> "ocamldebug" @@ -177,7 +106,7 @@ let run config env = None in let expected_launch_mode = - if Config.shebangscripts then + if config.shebangscripts then Header_shebang else Header_exe @@ -233,15 +162,18 @@ let run config env = distribution's tools when called with -M. *) let without_exe = Filename.remove_extension binary in let (this_exit_code, _) as this = - let fails = not (String.contains without_exe '.') in + 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:true env program ~argv0:binary ["-M"] + ~fails env program ~argv0:binary ["-M"] in if this = that then Harness.fail_because @@ -256,7 +188,15 @@ let run config env = "%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 expected to return with a non-zero exit code" + 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 *) end; failed @@ -271,7 +211,6 @@ let run config env = failed else failed ->>>>>>> da60a2e7920 in let binaries = Sys.readdir bindir in Array.sort String.compare binaries; diff --git a/testsuite/tools/test_in_prefix.ml b/testsuite/tools/test_in_prefix.ml index 0e103a7cad0e..12218f000a75 100644 --- a/testsuite/tools/test_in_prefix.ml +++ b/testsuite/tools/test_in_prefix.ml @@ -68,7 +68,6 @@ let run_tests ~sh config env = TestBytecodeBinaries.run config env; TestLinkModes.run ~sh config env -<<<<<<< HEAD type launch_method = | Shebang_bin_sh of string | Executable @@ -104,11 +103,10 @@ let read_runtime_launch_info file = {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)) ->>>>>>> da60a2e7920 let () = let ~config, ~pwd, ~prefix, ~bindir:_, ~bindir_suffix, ~libdir, @@ -159,16 +157,6 @@ let () = in let header_size, filename_mangling = let file = Filename.concat libdir "runtime-launch-info" in -<<<<<<< HEAD - read_runtime_launch_info file in - let header_size = - let {buffer; executable_offset; _} = runtime_launch_info in - String.length buffer - executable_offset 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 -======= In_channel.with_open_bin file @@ fun ic -> In_channel.length ic, (input_char ic <> '\000') in @@ -178,7 +166,6 @@ let () = (config.has_runtime_search <> Config.Disable) in let target_launcher_searches_for_ocamlrun = (Config.search_method <> Config.Disable) in ->>>>>>> da60a2e7920 let config = {config with libraries; launcher_searches_for_ocamlrun; diff --git a/utils/config.common.ml.in b/utils/config.common.ml.in index 295e9c5f307a..e0b743858f43 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -20,19 +20,13 @@ (* The main OCaml version string has moved to ../build-aux/ocaml_version.m4 *) let version = Sys.ocaml_version -<<<<<<< HEAD -let standard_library_default_raw = standard_library_default -======= (* 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 = 21 - -external standard_library_default : unit -> string = "%standard_library_default" +let release_number = 20 -let standard_library_default_raw = standard_library_default () ->>>>>>> da60a2e7920 +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 edc5a8fecd77..a961d7994c28 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -21,11 +21,8 @@ let boot_cannot_call s = "/ The boot compiler should not call " ^ s let bindir = "/tmp" -<<<<<<< HEAD -let standard_library_default = "/tmp" -======= let target_bindir = bindir ->>>>>>> da60a2e7920 +let standard_library_default = "/tmp" let ccomp_type = "n/a" let c_compiler = boot_cannot_call "the C compiler" let c_output_obj = "" @@ -86,10 +83,6 @@ let target = host let systhread_supported = false let flexdll_dirs = [] let ar_supports_response_files = true -<<<<<<< HEAD -======= -let shebangscripts = false let suffixing = false let launch_method = "sh" let search_method = "always" ->>>>>>> da60a2e7920 diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index b640f204bd68..b896825d76cf 100644 --- a/utils/config.generated.ml.in +++ b/utils/config.generated.ml.in @@ -104,14 +104,9 @@ let flexdll_dirs = [@flexdll_dir@] let ar_supports_response_files = @ar_supports_response_files@ let tsan = @tsan@ -<<<<<<< HEAD -======= - -let shebangscripts = @shebangscripts@ let suffixing = @suffixing@ let launch_method = {@QS@|@launch_method_target@|@QS@} let search_method = {@QS@|@runtime_search_target@|@QS@} ->>>>>>> da60a2e7920 diff --git a/utils/config.mli b/utils/config.mli index 224e3438bb4e..2abadf5052fb 100644 --- a/utils/config.mli +++ b/utils/config.mli @@ -341,8 +341,6 @@ val ar_supports_response_files: bool val tsan : bool (** Whether ThreadSanitizer instrumentation is enabled *) -<<<<<<< HEAD -======= (** Launch mechanisms for bytecode executables @since 5.5 *) @@ -378,11 +376,6 @@ val search_method : search_method @since 5.5 *) -val shebangscripts : bool -(** Whether the target supports shebang scripts - - @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}. @@ -399,7 +392,6 @@ val native_runtime_id : string @since 5.5 *) ->>>>>>> da60a2e7920 (** Access to configuration values *) val print_config : out_channel -> unit From 27b8210e3850a47b987ecd32e69fd745866f7ea7 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 11:51:03 +0100 Subject: [PATCH 12/28] 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 bf260f27335a..1f371f88570a 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 578f5d65f6ad..a27933c019df 100644 --- a/Makefile.common +++ b/Makefile.common @@ -341,8 +341,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 c0edcb166949..2cd4f5285f10 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -429,6 +429,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 3aafe1cfc588..4d7dcbabce80 100755 --- a/configure +++ b/configure @@ -15286,6 +15286,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 @@ -25413,6 +25419,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")' @@ -25454,6 +25465,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" ;; @@ -26593,6 +26605,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 1e69ec8ea218..4db5fdc071fc 100644 --- a/configure.ac +++ b/configure.ac @@ -1015,6 +1015,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 9a3c0732a50f27b8f671ebd69e4c2b342cd9d0a5 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 12:28:16 +0100 Subject: [PATCH 13/28] Adapt to OCaml 5.4 Config.shebangscripts was added in PR#14014 for the testsuite, 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_runtime_launch_info, which therefore needs deleting here. Labelled tuples were introduced in OCaml 5.4, and by policy shouldn't be used in the compiler distribution itself, although the principal reason to remove them here is simply because they'd have to be removed in the OCaml 5.3 backport anyway! --- bytecomp/bytelink.ml | 8 ++--- 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 | 2 +- testsuite/tools/test_in_prefix.ml | 36 --------------------- tools/objinfo.ml | 2 +- utils/clflags.ml | 3 +- utils/clflags.mli | 2 +- utils/config.mli | 20 ++++++------ utils/misc.mli | 2 +- 14 files changed, 30 insertions(+), 67 deletions(-) diff --git a/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index 2cd4f5285f10..4fdd27a71db6 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -557,16 +557,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 @@ -600,7 +600,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 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 a2f9da0db756..d8a9fc2d2d69 100644 --- a/driver/compenv.ml +++ b/driver/compenv.ml @@ -660,7 +660,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 @@ -673,7 +673,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 220f25e6b541..11718507406d 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -785,7 +785,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 12218f000a75..dcaee499be3c 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/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 4c7e6fd020bb..2ce5e5d81ffb 100644 --- a/utils/clflags.ml +++ b/utils/clflags.ml @@ -40,8 +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) - (* .so, -dllib -lxxx and +and dllibs = ref ([] : (bool * string) list) (* .so, -dllib -lxxx and -dllib-suffixed -lxxx *) let cmi_file = ref None diff --git a/utils/clflags.mli b/utils/clflags.mli index 8d12d167f73c..88c02b647cd6 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.mli b/utils/config.mli index 2abadf5052fb..011360be48ad 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 @@ -343,7 +343,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 *) @@ -357,11 +357,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 *) @@ -374,23 +374,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.mli b/utils/misc.mli index 64e779438ef5..595ceddc14b3 100644 --- a/utils/misc.mli +++ b/utils/misc.mli @@ -872,7 +872,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.4.2 *) (** Runtime IDs *) type t = private { From c1484cd6233d55dd3cda2c5242fcd45a009703e0 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 15:03:42 +0100 Subject: [PATCH 14/28] 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 --- bytecomp/bytelink.ml | 5 +++-- 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 | 13 ++++------- utils/config.fixed.ml | 1 - utils/config.generated.ml.in | 1 - utils/config.mli | 21 ------------------ utils/misc.ml | 3 ++- 22 files changed, 56 insertions(+), 88 deletions(-) rename {bytecomp => tools}/byterntm.mli (100%) rename {bytecomp => tools}/byterntm.mll (100%) diff --git a/.depend b/.depend index 121fbaae3d0f..cb72ebf3b251 100644 --- a/.depend +++ b/.depend @@ -2481,17 +2481,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 @@ -7987,6 +7976,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 @@ -8090,7 +8090,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 : \ @@ -8115,7 +8115,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 : @@ -10580,7 +10580,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 \ @@ -10591,7 +10591,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 @@ -10637,18 +10637,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 1f371f88570a..2d21dd316d47 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 @@ -282,6 +281,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 d1069fd91c24..c1029c07c8dc 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,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 \ @@ -1109,9 +1108,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 @@ -2009,7 +2008,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 \ @@ -2488,7 +2487,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 836df2ae6426..9ec1b88812fc 100644 --- a/asmcomp/cmm_helpers.ml +++ b/asmcomp/cmm_helpers.ml @@ -2695,9 +2695,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 00847f643bad..5c325bd42716 100644 --- a/asmcomp/cmm_helpers.mli +++ b/asmcomp/cmm_helpers.mli @@ -624,9 +624,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/bytecomp/bytelink.ml b/bytecomp/bytelink.ml index 4fdd27a71db6..298f521f2255 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -713,6 +713,7 @@ 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. *) +let target_win32 = (target_os_type = "Win32") let c_string_literal_of_string s = let b = Buffer.create (String.length s * 2) in let utf16le = Bytes.create 4 in @@ -729,7 +730,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 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 @@ -738,7 +739,7 @@ let c_string_literal_of_string s = | _ -> Buffer.add_utf_8_uchar b u in - if Config.target_win32 then + if target_win32 then Buffer.add_char b 'L'; Buffer.add_char b '"'; Seq.iter escape (to_utf_8_seq s); diff --git a/driver/compenv.ml b/driver/compenv.ml index d8a9fc2d2d69..a9d452807bc3 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 -> @@ -764,14 +762,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 157a4417d8d8..e619fecec121 100644 --- a/driver/main_args.ml +++ b/driver/main_args.ml @@ -1838,6 +1838,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 @@ -1876,7 +1889,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 b476d28ffdcb..0436dc76b8d9 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -33,7 +33,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 e0b743858f43..d7e107fc2b2f 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -99,16 +99,11 @@ 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_unix = (target_os_type = "Unix") -let target_win32 = (target_os_type = "Win32") -let target_cygwin = (target_os_type = "Cygwin") let default_executable_name = - if target_unix then - "a.out" - else if target_win32 || target_cygwin then - "camlprog.exe" - else - "camlprog" + match target_os_type with + | "Unix" -> "a.out" + | "Win32" | "Cygwin" -> "camlprog.exe" + | _ -> "camlprog" type configuration_value = | String of string | Int of int diff --git a/utils/config.fixed.ml b/utils/config.fixed.ml index a961d7994c28..470e6a1cb431 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 b896825d76cf..1a94fbc6b8b4 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 011360be48ad..9659d323782a 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.4.2 *) - val bytecode_cflags : string (** The flags ocamlc should pass to the C compiler *) @@ -221,21 +215,6 @@ val target_os_type: string @since 5.4 *) -val target_unix: bool -(** True if [target_os_type = "Unix"] - - @since 5.4.2 *) - -val target_win32: bool -(** True if [target_os_type = "Win32"] - - @since 5.4.2 *) - -val target_cygwin: bool -(** True if [target_os_type = "Cygwin"] - - @since 5.4.2 *) - val asm: string (** The assembler (and flags) to use for assembling ocamlopt-generated code. *) diff --git a/utils/misc.ml b/utils/misc.ml index 47c9893b24a5..b966b5627e69 100644 --- a/utils/misc.ml +++ b/utils/misc.ml @@ -1430,6 +1430,7 @@ module RuntimeID = struct ansi: bool; } + let target_win32 = (Config.target_os_type = "Win32") let make fn ?(dev = not Config.is_official_release) ?(release = Config.release_number) ?(reserved = Config.reserved_header_bits) @@ -1439,7 +1440,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 = target_win32 && not Config.windows_unicode) () = if release < 0 || release > 63 || reserved < 0 || reserved > 31 then invalid_arg fn else From b74c01acdb84f6c26e4ca451c99aa087b479cc6e Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Thu, 18 Jun 2026 18:39:54 +0100 Subject: [PATCH 15/28] 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 | 17 +++++++++++++---- 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, 102 insertions(+), 68 deletions(-) create mode 100644 runtime/sys_int.h.in diff --git a/.gitignore b/.gitignore index 2d21dd316d47..02abe6729352 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 c1029c07c8dc..b56ea03bb62d 100644 --- a/Makefile +++ b/Makefile @@ -1269,7 +1269,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 298f521f2255..e9a44bec23e0 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -768,6 +768,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 @@ -777,7 +779,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[] = { |}; @@ -975,10 +980,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 4d7dcbabce80..4219d7410de5 100755 --- a/configure +++ b/configure @@ -3661,6 +3661,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" @@ -25450,6 +25452,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 4db5fdc071fc..f0e9132c6d23 100644 --- a/configure.ac +++ b/configure.ac @@ -322,6 +322,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 27fe41b800ef..253f169648b9 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 3eb89486c8e4..0f5636481b35 100644 --- a/runtime/caml/osdeps.h +++ b/runtime/caml/osdeps.h @@ -96,22 +96,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 @@ -154,14 +138,6 @@ CAMLextern value caml_win32_xdg_defaults(void); CAMLextern value caml_win32_get_temp_path(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. @@ -175,18 +151,6 @@ CAMLextern 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 7be4010a2b37..efdbefb059ae 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 @@ -120,10 +118,6 @@ #undef HAS_DECL_SETTHREADDESCRIPTION -#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 628e06c627c1..49fc5b9d77b4 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, const 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 558a1161e7ac..563ffd4b2f48 100644 --- a/runtime/caml/sys.h +++ b/runtime/caml/sys.h @@ -33,10 +33,6 @@ CAMLextern void caml_sys_init (const 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 53251d3ab9cf..c3accde18eab 100644 --- a/runtime/dynlink.c +++ b/runtime/dynlink.c @@ -85,6 +85,18 @@ static c_primitive lookup_primitive(const 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 0aee158dcd4c..1bfc3e180a65 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -72,6 +72,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]; @@ -460,8 +462,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; @@ -501,8 +510,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); } @@ -513,12 +522,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 10308c8f3e8c..61094f163a70 100644 --- a/runtime/sys.c +++ b/runtime/sys.c @@ -741,6 +741,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( @@ -749,6 +751,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 68a53db8441e..82e8d1ad27fd 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 @@ -580,6 +581,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) @@ -587,7 +594,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 346db16a5305..61c70a2fce59 100644 --- a/runtime/win32.c +++ b/runtime/win32.c @@ -1344,6 +1344,14 @@ value caml_win32_get_temp_path(void) CAMLreturn(caml_copy_string_of_utf16(buf)); } +#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) @@ -1371,7 +1379,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 6b5e26155a889c2e4e5a3472ce5dee06ab320810 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Wed, 13 Nov 2024 16:28:48 +0000 Subject: [PATCH 16/28] 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 4219d7410de5..bbd3e0012783 100755 --- a/configure +++ b/configure @@ -4427,7 +4427,7 @@ else $as_nop suffixing=true fi else $as_nop - suffixing=true + suffixing=auto fi @@ -4466,6 +4466,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 f0e9132c6d23..8fc174ffa807 100644 --- a/configure.ac +++ b/configure.ac @@ -723,7 +723,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], @@ -747,6 +747,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 9f6ad470b58076af32822d9586835bd8ce37f68a Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 15:58:47 +0100 Subject: [PATCH 17/28] 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 cb72ebf3b251..8d22d5b2fc77 100644 --- a/.depend +++ b/.depend @@ -2365,6 +2365,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 \ @@ -2377,6 +2378,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 \ @@ -2491,14 +2493,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 \ @@ -8082,6 +8087,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 \ @@ -8107,6 +8113,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 e9a44bec23e0..04938a19163d 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -56,7 +56,7 @@ let lib_ccobjs = ref [] let lib_ccopts = ref [] let lib_dllibs = ref [] -let add_ccobjs obj_name origin l = +let add_ccobjs obj_name origin l dllibs = if not !Clflags.no_auto_link then begin if String.length !Clflags.use_runtime = 0 @@ -70,7 +70,7 @@ let add_ccobjs obj_name origin l = lib_ccopts := List.map replace_origin l.lib_ccopts @ !lib_ccopts; end else if l.lib_custom then raise(Error(Needs_custom_runtime obj_name)); - lib_dllibs := l.lib_dllibs @ !lib_dllibs + lib_dllibs := dllibs @ !lib_dllibs end (* A note on ccobj ordering: @@ -141,8 +141,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 obj_name (Filename.dirname file_name) toc; + add_ccobjs obj_name (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 1f011562bf5e..5fc858812dd7 100644 --- a/otherlibs/dynlink/byte/dynlink.ml +++ b/otherlibs/dynlink/byte/dynlink.ml @@ -199,7 +199,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 9fbba9e9ced8..ad47b00d0f58 100644 --- a/toplevel/byte/topeval.ml +++ b/toplevel/byte/topeval.ml @@ -292,7 +292,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 bf703dd84fb8ab4b9d15d160a5714d5b1e9fbc11 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Thu, 18 Jun 2026 18:04:17 +0100 Subject: [PATCH 18/28] 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 8d22d5b2fc77..ceeb3b4ecabf 100644 --- a/.depend +++ b/.depend @@ -8089,6 +8089,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 \ @@ -8115,6 +8116,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 5349e775e196..7c2438ecfeae 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 linuxlike_mangling = match Config.system with | "macosx" @@ -138,7 +138,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 a89a66b2c41a..134054a24b62 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 c093c03efffb8bd87be7c39ebe04004b4720a6b7 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sun, 6 Sep 2026 14:57:10 +0100 Subject: [PATCH 19/28] Bootstrap --- boot/ocamlc | Bin 3515138 -> 3529844 bytes boot/ocamllex | Bin 415555 -> 415631 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/boot/ocamlc b/boot/ocamlc index 0506501b58ebd994542e46f5f59c964d63d641e6..aec872face41a4567a2938f9971c8db2a9d12f18 100755 GIT binary patch delta 285265 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 delta 275103 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

i%2A-;i;8H!(7hPQW(O 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 DFwvfw diff --git a/boot/ocamllex b/boot/ocamllex index f1ed7c02697021d6f22c7937f3a10610472984f1..471c004f022e1b39b7adfcb32c8885e6dbccd9a5 100755 GIT binary patch delta 8127 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 Nl=SrUiOH$={5J^3cHaO1 From 64f85d5de57d5f0d6465560fa913fb3bf8c5d842 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 13 Jun 2026 12:37:26 +0100 Subject: [PATCH 20/28] 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 02abe6729352..2bd0a75a64f4 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 a27933c019df..8173a3b2ab7b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -195,6 +195,10 @@ endif # ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" # itself. HOST_LIBDIR ?= $(TARGET_LIBDIR) +OC_COMMON_LINKFLAGS += \ + -set-runtime-default \ + $(call QUOTE_SINGLE,standard_library_default=$(HOST_LIBDIR)) + # The rule to compile C files # This rule is similar to GNU make's implicit rule, except that it is more @@ -341,44 +345,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 04938a19163d..10493cc524bd 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -430,9 +430,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 bbd3e0012783..012f1d92c556 100755 --- a/configure +++ b/configure @@ -15292,12 +15292,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 @@ -25425,11 +25419,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")' @@ -25472,7 +25461,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" ;; @@ -26612,10 +26600,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 8fc174ffa807..6f43beb3f1a2 100644 --- a/configure.ac +++ b/configure.ac @@ -1018,24 +1018,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 11718507406d..d4093589efb3 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 d7e107fc2b2f..a00e3ae2488d 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 = 20 -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 470e6a1cb431..96886995ddb8 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 1a94fbc6b8b4..6b7e8b82dae1 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@|@ccomp_type@|@QS@} let c_compiler = {@QS@|@CC@|@QS@} let c_output_obj = {@QS@|@outputobj@|@QS@} From a8aea6497341cb1991b3139339812a2de2b8fa7d Mon Sep 17 00:00:00 2001 From: Nick Barnes Date: Wed, 17 Dec 2025 17:27:04 +0000 Subject: [PATCH 21/28] Merge pull request PR#14246 from dra27/opam-install-file Relocatable OCaml - to opam and beyond (cherry picked from commit f6c8af418c3a4dfd43372ec75100e86baffea92a) --- .gitattributes | 1 + .github/workflows/build-msvc.yml | 45 ++- .github/workflows/build.yml | 15 +- .gitignore | 1 + Makefile | 531 +++++++++++++-------------- Makefile.common | 365 +++++++++++++++++- 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 | 40 +- tools/opam/generate.ml | 235 ++++++++++++ tools/opam/process.sh | 190 ++++++++++ 20 files changed, 1243 insertions(+), 399 deletions(-) create mode 100644 tools/opam/generate.ml create mode 100644 tools/opam/process.sh diff --git a/.gitattributes b/.gitattributes index 73dc7d1325b2..b9eb1432aab7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -109,6 +109,7 @@ otherlibs/unix/symlink_win32.c typo.long-line # Some Unicode characters here and there utils/misc.ml typo.non-ascii runtime/sak.c typo.non-ascii +tools/opam/process.sh typo.non-ascii testsuite/tests/** typo.missing-header typo.long-line=may testsuite/tests/lib-bigarray-2/bigarrf.f typo.tab linguist-language=Fortran diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index 685c0a333b93..280d86b65dae 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,11 @@ jobs: - name: Install Cygwin uses: cygwin/cygwin-install-action@v3 with: +<<<<<<< HEAD packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-fortran,' }}gcc-core +======= + packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-g++,gcc-fortran,' }}gcc-core,rsync,unzip +>>>>>>> f6c8af418c3 install-dir: 'D:\cygwin' - name: Save Cygwin cache @@ -123,6 +129,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 +154,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 +214,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 2791a03a3046..6ab57c0ee1d9 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 gdb lldb + - 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 2bd0a75a64f4..5c4ced4f28ef 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ *.cmx[as] *.cmti *.annot +*.stripped *.exe *.exe.manifest .DS_Store diff --git a/Makefile b/Makefile index b56ea03bb62d..23fce770e41c 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) @@ -917,8 +917,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=\ @@ -970,7 +968,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 @@ -981,7 +979,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 @@ -2730,21 +2728,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) @@ -2755,341 +2801,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)" -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, \ + $(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 + $(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)" -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)" -endif - $(INSTALL_DATA) \ - $(ocamlopt_CMO_FILES) \ - "$(INSTALL_COMPLIBDIR)" -ifeq "$(build_ocamldoc)" "true" - $(MKDIR) "$(INSTALL_LIBDIR)/ocamldoc" - $(INSTALL_PROG) $(OCAMLDOC_OPT) "$(INSTALL_BINDIR)" - $(INSTALL_DATA) \ - $(OCAMLDOC_LIBCMIS) \ - "$(INSTALL_LIBDIR)/ocamldoc" + $(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) \ - $(OCAMLDOC_LIBMLIS) $(OCAMLDOC_LIBCMTS) \ - "$(INSTALL_LIBDIR)/ocamldoc" + $(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) \ - ocamldoc/ocamldoc.hva ocamldoc/*.cmx ocamldoc/odoc_info.$(A) \ - ocamldoc/odoc_info.cmxa \ - "$(INSTALL_LIBDIR)/ocamldoc" + $(call INSTALL_ITEMS, $(ocamlopt_CMO_FILES), \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) +ifeq "$(build_ocamldoc)" "true" + $(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) -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_ITEM, flexlink.opt$(EXE), bin, , , flexlink$(EXE)) +endif + $(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 8173a3b2ab7b..aae90f627fe1 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) @@ -510,12 +846,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 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 058f88c26d95..f173b7690001 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) @@ -121,7 +122,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 bf2178089e30..c3065e3510e2 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..e289b138b44b 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 ~/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..7730987eb0dc 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,38 @@ 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" "$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..d0084d9b2ee8 --- /dev/null +++ b/tools/opam/generate.ml @@ -0,0 +1,235 @@ +(**************************************************************************) +(* *) +(* 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. *) +let process_clone oc 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 "$1"'/%s'|} 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] 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. *) +let process_symlinks oc ~mkdir = + 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 "$1"|}; + 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 "$1/__ln_test"; then|}; + 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 + {|dest="$1"'/%s' xargs sh "$1/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 copy_files; + process_symlinks oc ~mkdir:true + 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 "$1" +rm -f "$1/__cp_test" "$1/__ln_test" +if cp --reflink=always doc/ocaml/LICENSE "$1/__cp_test" 2>/dev/null; then + rm -f "$1/__cp_test" + CP='cp --reflink=always -%sf' + if ! test -e "$1/clone-files"; then + echo "$CP"' "$@" "$dest/"' > "$1/clone-files" + fi +else + CP='cp -%sf' + if ! test -e "$1/clone-files"; then + if ln -f doc/ocaml/LICENSE "$1/__ln_test" 2>/dev/null; then + rm -f "$1/__ln_test" + echo 'ln -f "$@" "$dest/"' > "$1/clone-files" + else + echo "$CP"' "$@" "$dest/"' > "$1/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 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 lib/ocaml/ld.conf "$1/lib/ocaml/ld.conf" +cat > "$1/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' lib/ocaml/Makefile.config)" +for file in lib/ocaml/Makefile.config share/ocaml/config.status; do + O="$prefix" N="$1" awk -f "$1/prefix.awk" "$file" > "$1/$file" +done +rm -f "$1/clone-files" "$1/prefix.awk"|}; + process_symlinks oc ~mkdir:false + end diff --git a/tools/opam/process.sh b/tools/opam/process.sh new file mode 100644 index 000000000000..bab1444e2d91 --- /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" + ( cd "$origin_prefix" && sh ./share/ocaml/clone "$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, 25 Jul 2026 16:24:51 +0100 Subject: [PATCH 22/28] Merge pull request PR#14914 from dra27/opam-generate-clean-env Fix installation in opam with invalid `OCAMLTOP_INCLUDE_PATH` (cherry picked from commit 63b0c81c38e8affcda002e125fad805efbb709a1) --- Changes | 3 +++ Makefile.common | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Changes b/Changes index e718ab0661f7..a6ca596da9d7 100644 --- a/Changes +++ b/Changes @@ -90,6 +90,9 @@ OCaml 5.4 maintenance version with frame pointer support. (Xavier Leroy, review by Vincent Laviron, report by Richard Jones) +- #14871, #14914: Ignore OCAMLTOP_INCLUDE_PATH during the build. + (David Allsopp, report by Andreas Rossberg, review by Florian Angeletti) + OCaml 5.4.1 (17 February 2026) ------------------------------ diff --git a/Makefile.common b/Makefile.common index aae90f627fe1..b97a6b5f8656 100644 --- a/Makefile.common +++ b/Makefile.common @@ -870,3 +870,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 From cfb776db675026f31f05b2945f62c59fdc996b8d Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Fri, 31 Jul 2026 09:54:00 +0100 Subject: [PATCH 23/28] Merge pull request PR#14923 from dra27/explicit-clone Fix compiler cloning in the opam sandbox on macOS (cherry picked from commit 31d08a35cf3705ef1a37a1caa708b41a1d1e0f91) --- tools/ci/actions/runner.sh | 2 +- tools/ci/appveyor/appveyor_build.sh | 3 +- tools/opam/generate.ml | 62 +++++++++++++++-------------- tools/opam/process.sh | 2 +- 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/tools/ci/actions/runner.sh b/tools/ci/actions/runner.sh index e289b138b44b..05805ae7e20c 100755 --- a/tools/ci/actions/runner.sh +++ b/tools/ci/actions/runner.sh @@ -143,7 +143,7 @@ Install () { mkdir -p "share/ocaml" cp "$ret/config.status" "$ret/config.cache" "share/ocaml" cp "$ret/ocaml-compiler-clone.sh" "share/ocaml/clone" - sh $script ~/local/_opam + sh $script "$PWD" ~/local/_opam cd "$ret" rm -rf install rm ocaml-compiler-clone.sh diff --git a/tools/ci/appveyor/appveyor_build.sh b/tools/ci/appveyor/appveyor_build.sh index 7730987eb0dc..3d0f21981996 100755 --- a/tools/ci/appveyor/appveyor_build.sh +++ b/tools/ci/appveyor/appveyor_build.sh @@ -216,7 +216,8 @@ case "$1" in cp "$FULL_BUILD_PREFIX-$PORT/ocaml-compiler-clone.sh" \ 'destdir/share/ocaml/clone' cd destdir - sh "$FULL_BUILD_PREFIX-$PORT/ocaml-compiler-clone.sh" "$OCAMLROOT/_opam" + 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 \ diff --git a/tools/opam/generate.ml b/tools/opam/generate.ml index d0084d9b2ee8..5c8d78e69f24 100644 --- a/tools/opam/generate.ml +++ b/tools/opam/generate.ml @@ -81,8 +81,9 @@ let generate_install file = (* [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. *) -let process_clone oc process = + 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 = @@ -90,7 +91,7 @@ let process_clone oc process = (String.sub file 6 (String.length file - 6)) |> valid_section in - output_endline oc {|mkdir -p "$1"'/%s'|} dir; + output_endline oc {|mkdir -p "%s"'/%s'|} prefix dir; In_channel.with_open_text file @@ process oc dir; remove_file file end @@ -99,11 +100,12 @@ let process_clone oc process = Array.sort String.compare files; Array.iter process_file files -(* [process_symlinks oc ~mkdir] processes create-symlinks, if it exists, writing - any required mkdir commands to [oc] if [~mkdir = true] and also the +(* [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. *) -let process_symlinks oc ~mkdir = + 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 @@ -118,7 +120,7 @@ let process_symlinks oc ~mkdir = In_channel.with_open_text file @@ fun ic -> List.rev (In_channel.fold_lines parse [] ic) in - output_endline oc {|cd "$1"|}; + output_endline oc {|cd "%s"|} prefix; let _ = let create_dir seen (dir, _, _) = if not (StringSet.mem dir seen) && String.contains dir '/' then @@ -144,7 +146,7 @@ let process_symlinks oc ~mkdir = 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 "$1/__ln_test"; then|}; + output_endline oc {|if test -L "%s/__ln_test"; then|} prefix; List.iter mklink lines; output_endline oc {|else|}; List.iter cp lines; @@ -165,7 +167,7 @@ let copy_files oc dir = let clone_files oc dir ic = output_endline oc - {|dest="$1"'/%s' xargs sh "$1/clone-files" <<'EOF'|} dir; + {|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|} @@ -176,8 +178,8 @@ let () = Out_channel.with_open_bin (package ^ "-fixup.sh") @@ fun oc -> output_endline oc {|#!/bin/sh set -eu|}; - process_clone oc copy_files; - process_symlinks oc ~mkdir:true + 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 @@ -188,22 +190,22 @@ set -eu|}; Out_channel.with_open_bin (package ^ "-clone.sh") @@ fun oc -> output_endline oc {|#!/bin/sh set -eu -mkdir -p "$1" -rm -f "$1/__cp_test" "$1/__ln_test" -if cp --reflink=always doc/ocaml/LICENSE "$1/__cp_test" 2>/dev/null; then - rm -f "$1/__cp_test" +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 "$1/clone-files"; then - echo "$CP"' "$@" "$dest/"' > "$1/clone-files" + if ! test -e "$2/clone-files"; then + echo 'cd "$src" && '"$CP"' "$@" "$dest/"' > "$2/clone-files" fi else CP='cp -%sf' - if ! test -e "$1/clone-files"; then - if ln -f doc/ocaml/LICENSE "$1/__ln_test" 2>/dev/null; then - rm -f "$1/__ln_test" - echo 'ln -f "$@" "$dest/"' > "$1/clone-files" + 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 "$CP"' "$@" "$dest/"' > "$1/clone-files" + echo 'cd "$src" && '"$CP"' "$@" "$dest/"' > "$2/clone-files" fi fi fi|} preserve preserve; @@ -211,12 +213,12 @@ fi|} preserve preserve; output_endline oc "share/ocaml/clone"; if Sys.file_exists "config.cache" then output_endline oc "share/ocaml/config.cache"); - process_clone oc clone_files; + 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 lib/ocaml/ld.conf "$1/lib/ocaml/ld.conf" -cat > "$1/prefix.awk" <<'ENDAWK' + 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"]))) { @@ -226,10 +228,10 @@ cat > "$1/prefix.awk" <<'ENDAWK' print rest } ENDAWK -prefix="$(sed -ne 's/^prefix *= *//p' lib/ocaml/Makefile.config)" +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="$1" awk -f "$1/prefix.awk" "$file" > "$1/$file" + O="$prefix" N="$2" awk -f "$2/prefix.awk" "$1/$file" > "$2/$file" done -rm -f "$1/clone-files" "$1/prefix.awk"|}; - process_symlinks oc ~mkdir:false +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 index bab1444e2d91..e529259c150d 100644 --- a/tools/opam/process.sh +++ b/tools/opam/process.sh @@ -60,7 +60,7 @@ if [ x"$make_args" = 'xinstall' ]; then origin="$(tail -n 1 build-id)" origin_prefix="$(opam var --safe --switch="$origin" prefix | tr -d '\r')" echo "πŸͺ„ Duplicating $origin_prefix" - ( cd "$origin_prefix" && sh ./share/ocaml/clone "$prefix" ) + sh "$origin_prefix/share/ocaml/clone" "$origin_prefix" "$prefix" fi exit 0 From c0605895343887232cde6ae6544df28b94a49145 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sun, 6 Sep 2026 15:03:02 +0100 Subject: [PATCH 24/28] Resolve conflicts --- .github/workflows/build-msvc.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index 280d86b65dae..cb96bacc5749 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -109,11 +109,7 @@ jobs: - name: Install Cygwin uses: cygwin/cygwin-install-action@v3 with: -<<<<<<< HEAD - packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-fortran,' }}gcc-core -======= - packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-g++,gcc-fortran,' }}gcc-core,rsync,unzip ->>>>>>> f6c8af418c3 + packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-fortran,' }}gcc-core,rsync,unzip install-dir: 'D:\cygwin' - name: Save Cygwin cache From c2a7bd73a53243433036dbbc3f2dea8039aec3ca Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sat, 19 Jul 2025 17:55:17 +0100 Subject: [PATCH 25/28] 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 b9eb1432aab7..73dc7d1325b2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -109,7 +109,6 @@ otherlibs/unix/symlink_win32.c typo.long-line # Some Unicode characters here and there utils/misc.ml typo.non-ascii runtime/sak.c typo.non-ascii -tools/opam/process.sh typo.non-ascii testsuite/tests/** typo.missing-header typo.long-line=may testsuite/tests/lib-bigarray-2/bigarrf.f typo.tab linguist-language=Fortran 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 26/28] 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 | 23 +++++++++++++++++------ configure.ac | 13 +++++++++++-- testsuite/Makefile | 28 +++++++++++++++++++++++++--- 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/.gitattributes b/.gitattributes index 73dc7d1325b2..3d4cd2c32d2a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -44,6 +44,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 23fce770e41c..946a811c4647 100644 --- a/Makefile +++ b/Makefile @@ -940,11 +940,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 @@ -2705,7 +2708,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 012f1d92c556..30103d4551a5 100755 --- a/configure +++ b/configure @@ -3643,12 +3643,13 @@ 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" - +<<<<<<< HEAD +dnlAC_CONFIG_FILES(manual/src/version.tex) +dnlAC_CONFIG_FILES(manual/src/html_processing/src/common.ml) ac_config_files="$ac_config_files ocamltest/ocamltest_config.ml" +======= +>>>>>>> 172b5c5d074 ac_config_files="$ac_config_files otherlibs/dynlink/dynlink_config.ml" ac_config_files="$ac_config_files utils/config.common.ml" @@ -3673,8 +3674,18 @@ ac_config_files="$ac_config_files otherlibs/runtime_events/META" ac_config_files="$ac_config_files stdlib/META" +<<<<<<< HEAD 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 +>>>>>>> 172b5c5d074 # Definitions related to the version of OCaml printf "%s\n" "#define OCAML_VERSION_MAJOR 5" >>confdefs.h @@ -25436,8 +25447,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" ;; @@ -25452,6 +25461,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 6f43beb3f1a2..01aaa13ba131 100644 --- a/configure.ac +++ b/configure.ac @@ -313,9 +313,12 @@ 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]) +<<<<<<< HEAD +dnl AC_CONFIG_FILES([manual/src/version.tex]) +dnl AC_CONFIG_FILES([manual/src/html_processing/src/common.ml]) AC_CONFIG_FILES([ocamltest/ocamltest_config.ml]) +======= +>>>>>>> 172b5c5d074 AC_CONFIG_FILES([otherlibs/dynlink/dynlink_config.ml]) AC_CONFIG_FILES([utils/config.common.ml]) AC_CONFIG_FILES([utils/config.generated.ml]) @@ -328,7 +331,13 @@ AC_CONFIG_FILES([compilerlibs/META]) AC_CONFIG_FILES([otherlibs/dynlink/META]) AC_CONFIG_FILES([otherlibs/runtime_events/META]) AC_CONFIG_FILES([stdlib/META]) +<<<<<<< HEAD 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])]) +>>>>>>> 172b5c5d074 # 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 ff79c6f15106dc77f7768032f1573d65d7d76884 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sun, 6 Sep 2026 15:05:32 +0100 Subject: [PATCH 27/28] Resolve conflicts --- configure | 8 -------- configure.ac | 8 -------- 2 files changed, 16 deletions(-) diff --git a/configure b/configure index 30103d4551a5..8a4290caf81a 100755 --- a/configure +++ b/configure @@ -3643,13 +3643,8 @@ ac_config_files="$ac_config_files Makefile.config" ac_config_files="$ac_config_files stdlib/sys.ml" -<<<<<<< HEAD -dnlAC_CONFIG_FILES(manual/src/version.tex) -dnlAC_CONFIG_FILES(manual/src/html_processing/src/common.ml) ac_config_files="$ac_config_files ocamltest/ocamltest_config.ml" -======= ->>>>>>> 172b5c5d074 ac_config_files="$ac_config_files otherlibs/dynlink/dynlink_config.ml" ac_config_files="$ac_config_files utils/config.common.ml" @@ -3674,10 +3669,8 @@ ac_config_files="$ac_config_files otherlibs/runtime_events/META" ac_config_files="$ac_config_files stdlib/META" -<<<<<<< HEAD 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" @@ -3685,7 +3678,6 @@ then : ac_config_files="$ac_config_files manual/src/html_processing/src/common.ml" fi ->>>>>>> 172b5c5d074 # Definitions related to the version of OCaml printf "%s\n" "#define OCAML_VERSION_MAJOR 5" >>confdefs.h diff --git a/configure.ac b/configure.ac index 01aaa13ba131..d65f5e28c5d9 100644 --- a/configure.ac +++ b/configure.ac @@ -313,12 +313,7 @@ AC_SUBST([runtime_search_target]) AC_CONFIG_FILES([Makefile.build_config]) AC_CONFIG_FILES([Makefile.config]) AC_CONFIG_FILES([stdlib/sys.ml]) -<<<<<<< HEAD -dnl AC_CONFIG_FILES([manual/src/version.tex]) -dnl AC_CONFIG_FILES([manual/src/html_processing/src/common.ml]) AC_CONFIG_FILES([ocamltest/ocamltest_config.ml]) -======= ->>>>>>> 172b5c5d074 AC_CONFIG_FILES([otherlibs/dynlink/dynlink_config.ml]) AC_CONFIG_FILES([utils/config.common.ml]) AC_CONFIG_FILES([utils/config.generated.ml]) @@ -331,13 +326,10 @@ AC_CONFIG_FILES([compilerlibs/META]) AC_CONFIG_FILES([otherlibs/dynlink/META]) AC_CONFIG_FILES([otherlibs/runtime_events/META]) AC_CONFIG_FILES([stdlib/META]) -<<<<<<< HEAD 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])]) ->>>>>>> 172b5c5d074 # Definitions related to the version of OCaml AC_DEFINE([OCAML_VERSION_MAJOR], [OCAML__VERSION_MAJOR]) From 8fa734eb5fa46f44526657220d26c2b9fbd18b2e Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Fri, 27 Sep 2024 11:28:27 +0100 Subject: [PATCH 28/28] OCaml 5.4.2+relocatable base version commit --- VERSION | 2 +- build-aux/ocaml_version.m4 | 2 +- configure | 44 +++++++++++++++++++------------------- ocaml-variants.opam | 4 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/VERSION b/VERSION index 1e4285bfa39e..5c4684282fb1 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -5.4.2+dev0-2026-02-17 +5.4.2+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 7038481fd49a..60582959484a 100644 --- a/build-aux/ocaml_version.m4 +++ b/build-aux/ocaml_version.m4 @@ -39,7 +39,7 @@ m4_define([OCAML__VERSION_PATCHLEVEL], [2]) # Note that the OCAML__VERSION_EXTRA string defined below is always empty # for officially-released versions of OCaml. -m4_define([OCAML__VERSION_EXTRA], [dev0-2026-02-17]) +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 8a4290caf81a..457f1d460561 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.4.2+dev0-2026-02-17. +# Generated by GNU Autoconf 2.71 for OCaml 5.4.2+relocatable. # # Report bugs to . # @@ -677,8 +677,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='OCaml' PACKAGE_TARNAME='ocaml' -PACKAGE_VERSION='5.4.2+dev0-2026-02-17' -PACKAGE_STRING='OCaml 5.4.2+dev0-2026-02-17' +PACKAGE_VERSION='5.4.2+relocatable' +PACKAGE_STRING='OCaml 5.4.2+relocatable' PACKAGE_BUGREPORT='caml-list@inria.fr' PACKAGE_URL='http://www.ocaml.org' @@ -1646,7 +1646,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.4.2+dev0-2026-02-17 to adapt to many kinds of systems. +\`configure' configures OCaml 5.4.2+relocatable to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1713,7 +1713,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of OCaml 5.4.2+dev0-2026-02-17:";; + short | recursive ) echo "Configuration of OCaml 5.4.2+relocatable:";; esac cat <<\_ACEOF @@ -1913,7 +1913,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -OCaml configure 5.4.2+dev0-2026-02-17 +OCaml configure 5.4.2+relocatable generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2570,7 +2570,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.4.2+dev0-2026-02-17, which was +It was created by OCaml $as_me 5.4.2+relocatable, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3326,8 +3326,8 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.4.2+dev0-2026-02-17" >&5 -printf "%s\n" "$as_me: Configuring OCaml version 5.4.2+dev0-2026-02-17" >&6;} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.4.2+relocatable" >&5 +printf "%s\n" "$as_me: Configuring OCaml version 5.4.2+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. @@ -3427,11 +3427,11 @@ runtime_search_target='' -VERSION=5.4.2+dev0-2026-02-17 +VERSION=5.4.2+relocatable OCAML_DEVELOPMENT_VERSION=true -OCAML_RELEASE_EXTRA='Some (Plus, "dev0-2026-02-17")' +OCAML_RELEASE_EXTRA='Some (Plus, "relocatable")' OCAML_VERSION_MAJOR=5 @@ -3439,7 +3439,7 @@ OCAML_VERSION_MINOR=4 OCAML_VERSION_PATCHLEVEL=2 -OCAML_VERSION_EXTRA=dev0-2026-02-17 +OCAML_VERSION_EXTRA=relocatable OCAML_VERSION_SHORT=5.4 @@ -3686,13 +3686,13 @@ printf "%s\n" "#define OCAML_VERSION_MINOR 4" >>confdefs.h printf "%s\n" "#define OCAML_VERSION_PATCHLEVEL 2" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_ADDITIONAL \"dev0-2026-02-17\"" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_ADDITIONAL \"relocatable\"" >>confdefs.h - printf "%s\n" "#define OCAML_VERSION_EXTRA \"dev0-2026-02-17\"" >>confdefs.h + printf "%s\n" "#define OCAML_VERSION_EXTRA \"relocatable\"" >>confdefs.h printf "%s\n" "#define OCAML_VERSION 50402" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_STRING \"5.4.2+dev0-2026-02-17\"" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_STRING \"5.4.2+relocatable\"" >>confdefs.h printf "%s\n" "#define OCAML_RELEASE_NUMBER 20" >>confdefs.h @@ -4571,13 +4571,13 @@ else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if the installed OCaml compiler can build the cross compiler" >&5 printf %s "checking if the installed OCaml compiler can build the cross compiler... " >&6; } already_installed_version="$(ocamlc -vnum)" - if test x"5.4.2+dev0-2026-02-17" = x"$already_installed_version" + if test x"5.4.2+relocatable" = x"$already_installed_version" then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes (5.4.2+dev0-2026-02-17)" >&5 -printf "%s\n" "yes (5.4.2+dev0-2026-02-17)" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes (5.4.2+relocatable)" >&5 +printf "%s\n" "yes (5.4.2+relocatable)" >&6; } else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no (5.4.2+dev0-2026-02-17 vs $already_installed_version)" >&5 -printf "%s\n" "no (5.4.2+dev0-2026-02-17 vs $already_installed_version)" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no (5.4.2+relocatable vs $already_installed_version)" >&5 +printf "%s\n" "no (5.4.2+relocatable vs $already_installed_version)" >&6; } as_fn_error $? "exiting" "$LINENO" 5 fi cross_compiler=true @@ -24944,7 +24944,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.4.2+dev0-2026-02-17, which was +This file was extended by OCaml $as_me 5.4.2+relocatable, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -25017,7 +25017,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.4.2+dev0-2026-02-17 +OCaml config.status 5.4.2+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 c3065e3510e2..9337b2c4a1b8 100644 --- a/ocaml-variants.opam +++ b/ocaml-variants.opam @@ -1,7 +1,7 @@ opam-version: "2.0" -version: "5.4.2+trunk" +version: "5.4.2+relocatable" license: "LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception" -synopsis: "OCaml 5.4 development version" +synopsis: "Relocatable OCaml 5.4 branch" maintainer: "caml-list@inria.fr" authors: [ "Xavier Leroy"