diff --git a/.depend b/.depend index fe726b33da2b..15c292317e16 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 : \ @@ -2304,6 +2305,7 @@ bytecomp/bytelibrarian.cmo : \ utils/linkdeps.cmi \ utils/format_doc.cmi \ bytecomp/emitcode.cmi \ + bytecomp/dll.cmi \ utils/config.cmi \ file_formats/cmo_format.cmi \ utils/clflags.cmi \ @@ -2316,6 +2318,7 @@ bytecomp/bytelibrarian.cmx : \ utils/linkdeps.cmx \ utils/format_doc.cmx \ bytecomp/emitcode.cmx \ + bytecomp/dll.cmx \ utils/config.cmx \ file_formats/cmo_format.cmi \ utils/clflags.cmx \ @@ -2430,14 +2433,17 @@ bytecomp/bytesections.cmi : bytecomp/dll.cmo : \ utils/misc.cmi \ utils/config.cmi \ + file_formats/cmo_format.cmi \ utils/binutils.cmi \ bytecomp/dll.cmi bytecomp/dll.cmx : \ utils/misc.cmx \ utils/config.cmx \ + file_formats/cmo_format.cmi \ utils/binutils.cmx \ bytecomp/dll.cmi -bytecomp/dll.cmi : +bytecomp/dll.cmi : \ + file_formats/cmo_format.cmi bytecomp/emitcode.cmo : \ parsing/unit_info.cmi \ lambda/translmod.cmi \ @@ -5162,6 +5168,7 @@ middle_end/flambda/closure_conversion.cmo : \ middle_end/flambda/flambda.cmi \ lambda/debuginfo.cmi \ middle_end/convert_primitives.cmi \ + middle_end/compilenv.cmi \ middle_end/compilation_unit.cmi \ middle_end/flambda/base_types/closure_origin.cmi \ middle_end/flambda/base_types/closure_id.cmi \ @@ -5190,6 +5197,7 @@ middle_end/flambda/closure_conversion.cmx : \ middle_end/flambda/flambda.cmx \ lambda/debuginfo.cmx \ middle_end/convert_primitives.cmx \ + middle_end/compilenv.cmx \ middle_end/compilation_unit.cmx \ middle_end/flambda/base_types/closure_origin.cmx \ middle_end/flambda/base_types/closure_id.cmx \ @@ -7886,6 +7894,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 @@ -7981,6 +8000,9 @@ tools/objinfo.cmo : \ typing/ident.cmi \ utils/format_doc.cmi \ 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 \ @@ -7988,6 +8010,7 @@ tools/objinfo.cmo : \ file_formats/cmo_format.cmi \ file_formats/cmi_format.cmi \ bytecomp/bytesections.cmi \ + tools/byterntm.cmi \ utils/binutils.cmi \ tools/objinfo.cmi tools/objinfo.cmx : \ @@ -8004,6 +8027,9 @@ tools/objinfo.cmx : \ typing/ident.cmx \ utils/format_doc.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 \ @@ -8011,6 +8037,7 @@ tools/objinfo.cmx : \ file_formats/cmo_format.cmi \ file_formats/cmi_format.cmx \ bytecomp/bytesections.cmx \ + tools/byterntm.cmx \ utils/binutils.cmx \ tools/objinfo.cmi tools/objinfo.cmi : @@ -10375,9 +10402,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 @@ -10413,6 +10442,7 @@ testsuite/tools/environment.cmo : \ file_formats/cmt_format.cmi \ file_formats/cmo_format.cmi \ bytecomp/bytesections.cmi \ + tools/byterntm.cmi \ testsuite/tools/environment.cmi testsuite/tools/environment.cmx : \ otherlibs/unix/unix.cmx \ @@ -10423,6 +10453,7 @@ testsuite/tools/environment.cmx : \ file_formats/cmt_format.cmx \ file_formats/cmo_format.cmi \ bytecomp/bytesections.cmx \ + tools/byterntm.cmx \ testsuite/tools/environment.cmi testsuite/tools/environment.cmi : \ testsuite/tools/harness.cmi @@ -10466,13 +10497,20 @@ testsuite/tools/expect.cmi : \ parsing/location.cmi testsuite/tools/harness.cmo : \ otherlibs/unix/unix.cmi \ + utils/misc.cmi \ utils/config.cmi \ + tools/byterntm.cmi \ testsuite/tools/harness.cmi testsuite/tools/harness.cmx : \ otherlibs/unix/unix.cmx \ + utils/misc.cmx \ utils/config.cmx \ + tools/byterntm.cmx \ testsuite/tools/harness.cmi -testsuite/tools/harness.cmi : +testsuite/tools/harness.cmi : \ + utils/misc.cmi \ + utils/config.cmi \ + tools/byterntm.cmi testsuite/tools/lexcmm.cmo : \ testsuite/tools/parsecmm.cmi \ utils/misc.cmi \ @@ -10531,12 +10569,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 \ @@ -10580,6 +10620,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 \ @@ -10587,6 +10628,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 \ @@ -10640,11 +10682,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/.gitattributes b/.gitattributes index 1069ce73e3d7..de6bb51ad890 100644 --- a/.gitattributes +++ b/.gitattributes @@ -41,6 +41,21 @@ # the lines involved in the conflict, which is arguably worse #/Changes merge=union +testsuite/Makefile export-subst + +# Files and directories excluded from git-generated tarballs. +.github export-ignore +manual export-ignore +release-info export-ignore +testsuite/tests export-ignore +tools/ci export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.gitmodules export-ignore +.mailmap export-ignore +ocaml-variants.install export-ignore +ocaml-variants.opam export-ignore + # No header for text and META files (would be too obtrusive). *.md typo.missing-header README* typo.missing-header diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index fb78b02a21bb..cb96bacc5749 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -33,10 +33,13 @@ 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'}]; + {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, // # rather than from context.payload.pull_request.labels, since we @@ -52,10 +55,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({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({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, 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 @@ -73,13 +80,13 @@ jobs: build: permissions: {} - runs-on: windows-latest + runs-on: ${{ matrix.os }} needs: config 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) }} @@ -102,7 +109,7 @@ jobs: - name: Install Cygwin uses: cygwin/cygwin-install-action@v3 with: - packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-fortran,' }}gcc-core + packages: make,${{ matrix.cc != 'gcc' && 'mingw64-x86_64-' || 'gcc-fortran,' }}gcc-core,rsync,unzip install-dir: 'D:\cygwin' - name: Save Cygwin cache @@ -118,6 +125,13 @@ jobs: arch: ${{ matrix.arch == 'x86_64' && 'x64' || 'x86' }} if: matrix.cc != 'gcc' + - name: Install opam + if: matrix.opam == 'true' + shell: pwsh + run: | + winget install opam --accept-source-agreements + Add-Content -Path $env:GITHUB_PATH -Value "$env:LOCALAPPDATA\Microsoft\WinGet\Links" + - name: Compute a key to cache configure results id: autoconf-cache-key env: @@ -136,11 +150,14 @@ 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 ${{ 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) @@ -193,8 +210,25 @@ jobs: make tests - name: Install the compiler + if: matrix.opam != 'true' run: make install + - name: Create opam switch + if: matrix.opam == 'true' + env: + OPAMSWITCH: ${{ matrix.prefix }} + run: | + make OPAM_PACKAGE_NAME=ocaml-variants INSTALL_MODE=opam install + opam init --cli=2.4 --bare --yes --disable-sandboxing --auto-setup --cygwin-local-install + # These commands intentionally run using opam's "default" CLI + opam switch create '${{ env.OPAMSWITCH }}' --empty + opam pin add --no-action --kind=path ocaml-variants . + opam pin add --no-action flexdll flexdll + opam pin add --no-action winpthreads winpthreads + opam install --yes flexdll winpthreads + opam install --yes --assume-built ocaml-variants + opam exec -- ocamlc -v + - name: Test in prefix run: | eval $(tools/msvs-promote-path) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0eb47cf80be8..a6f30541663e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: '${{ github.event.repository.full_name }}' - name: Configure tree run: | - MAKE_ARG=-j CONFIG_ARG='--enable-flambda --enable-cmm-invariants --enable-dependency-generation --enable-native-toplevel' OCAMLRUNPARAM=b,v=0 bash -xe tools/ci/actions/runner.sh configure + MAKE_ARG=-j CONFIG_ARG='--enable-flambda --enable-cmm-invariants --enable-dependency-generation --enable-native-toplevel --with-relative-libdir --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 @@ -78,6 +78,7 @@ jobs: # debug runtime and minor heap verification. # debug-s4096: select testsuite run with the debug runtime and a small # minor heap. +# opam: constructs an opam local switch from the compiler. normal: name: ${{ matrix.name }} needs: [build, config] @@ -88,6 +89,8 @@ jobs: - id: normal name: normal dependencies: texlive-latex-extra texlive-fonts-recommended texlive-luatex hevea sass + - id: opam + name: opam installation - id: debug name: extra (debug) - id: debug-s4096 @@ -134,11 +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 - name: Build the manual if: matrix.id == 'normal' && needs.build.outputs.manual_changed == 'true' run: | @@ -152,9 +163,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 @@ -165,10 +190,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 --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, @@ -182,13 +209,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; @@ -250,6 +280,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' && !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 i386: runs-on: ubuntu-latest @@ -282,4 +316,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/.gitignore b/.gitignore index 4159c2a270b7..c3d7e7a5102a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ *.cmx[as] *.cmti *.annot +*.stripped *.exe *.exe.manifest .DS_Store @@ -240,6 +241,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 @@ -251,13 +253,10 @@ META /runtime/build_config.h /runtime/sak -/stdlib/runtime.info /stdlib/runtime-launch-info /stdlib/labelled-* /stdlib/caml /stdlib/sys.ml -/stdlib/target_runtime.info -/stdlib/target_runtime-launch-info /testsuite/**/*.result /testsuite/**/*.opt_result @@ -272,6 +271,7 @@ META /testsuite/tools/codegen /testsuite/tools/dump_load_path +/testsuite/tools/poisonedruntime /testsuite/tools/expect /testsuite/tools/lexcmm.ml /testsuite/tools/parsecmm.ml @@ -281,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/Changes b/Changes index 486c5313a290..271adfa7c4e0 100644 --- a/Changes +++ b/Changes @@ -1,14 +1,111 @@ OCaml 5.3 maintenance version ----------------------------- -- #14007, #14015: Fix memory corruption when an exception is raised during - demarshaling. - (Benoît Vaugon, review by David Allsopp and Gabriel Scherer) +### 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) + +- #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) + +- #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 + 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) + +- #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 + C primitive), eliminating some highly obscure corner cases. + (David Allsopp, review by Jonah Beckford, Damien Doligez and Hugo Heuzard) + +### Build system: + +- #13494: Use native symlinks on Windows for the OCaml installation, reducing + disk usage considerably. + (David Allsopp, review by Nicolás Ojeda Bär and Gabriel Scherer) + +- #13789: Harden the escaping of backslashes when generating + runtime/build_config.h (in particular, if the underlying implementation of sh + is dash, rather than bash, this now works correctly). + (David Allsopp and Samuel Hym, review by Antonin Décimo and Gabriel Scherer) + +- #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) + +- #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) + +- #14245: New --enable-runtime-search configure option controls the + -runtime-search option used to build the bytecode binaries in the compiler + distribution. --enable-runtime-search-target controls the default value of + -runtime-search used for bytecode executables produced by the compiler. + (David Allsopp, review by Damien Doligez and Samuel Hym) + +### Bug fixes: - #13790: Fix bytecode-only build of Cygwin when flexlink is being bootstrapped with the compiler. (David Allsopp, review by Antonin Décimo and Miod Vallat) +- #14007, #14015: Fix memory corruption when an exception is raised during + demarshaling. + (Benoît Vaugon, review by David Allsopp and Gabriel Scherer) + +- #14871, #14914: Ignore OCAMLTOP_INCLUDE_PATH during the build. + (David Allsopp, report by Andreas Rossberg, review by Florian Angeletti) + OCaml 5.3.0 (8 January 2025) ---------------------------- diff --git a/Makefile b/Makefile index 02e6f79e7e99..2b89f31f5c2f 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) @@ -482,9 +482,11 @@ utils/config_boot.ml: utils/config.fixed.ml utils/config.common.ml utils/config_main.ml: utils/config.generated.ml utils/config.common.ml $(V_GEN)cat $^ > $@ +ADDITIONAL_CONFIGURE_ARGS ?= .PHONY: reconfigure reconfigure: - ac_read_git_config=true ./configure $(CONFIGURE_ARGS) + ac_read_git_config=true ./configure $(CONFIGURE_ARGS) \ + $(ADDITIONAL_CONFIGURE_ARGS) utils/domainstate.ml: utils/domainstate.ml.c runtime/caml/domain_state.tbl $(V_GEN)$(CPP) -I runtime/caml $< > $@ @@ -641,12 +643,16 @@ 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) partialclean:: - rm -f flexlink.byte flexlink.byte.exe + rm -f flexlink.byte flexlink.byte.exe flexdll_*.o flexdll_*.obj $(BYTE_BINDIR)/flexlink$(EXE): \ boot/ocamlrun$(EXE) flexlink.byte$(EXE) | $(BYTE_BINDIR) @@ -654,7 +660,6 @@ $(BYTE_BINDIR)/flexlink$(EXE): \ # Start with a copy to ensure that the result is always executable cp boot/ocamlrun$(EXE) $@ cat flexlink.byte$(EXE) >> $@ - cp $(addprefix $(FLEXDLL_SOURCE_DIR)/, $(FLEXDLL_OBJECTS)) $(BYTE_BINDIR) partialclean:: rm -f $(BYTE_BINDIR)/flexlink $(BYTE_BINDIR)/flexlink.exe @@ -716,9 +721,9 @@ compare: # The core system has to be rebuilt after bootstrap anyway, so strip ocamlc # and ocamllex, which means the artefacts should be identical. mv ocamlc$(EXE) ocamlc.tmp - $(OCAMLRUN) tools/stripdebug -all ocamlc.tmp ocamlc$(EXE) + $(OCAMLRUN) tools/stripdebug$(EXE) -all ocamlc.tmp ocamlc$(EXE) mv lex/ocamllex$(EXE) ocamllex.tmp - $(OCAMLRUN) tools/stripdebug -all ocamllex.tmp lex/ocamllex$(EXE) + $(OCAMLRUN) tools/stripdebug$(EXE) -all ocamllex.tmp lex/ocamllex$(EXE) rm -f ocamllex.tmp ocamlc.tmp @if $(CMPCMD) boot/ocamlc ocamlc$(EXE) \ && $(CMPCMD) boot/ocamllex lex/ocamllex$(EXE); \ @@ -746,7 +751,7 @@ promote-cross: promote-common # Promote the newly compiled system to the rank of bootstrap compiler # (Runs on the new runtime, produces code for the new runtime) .PHONY: promote -promote: PROMOTE = $(OCAMLRUN) tools/stripdebug -all +promote: PROMOTE = $(OCAMLRUN) tools/stripdebug$(EXE) -all promote: promote-common rm -f boot/ocamlrun$(EXE) cp runtime/ocamlrun$(EXE) boot/ocamlrun$(EXE) @@ -881,11 +886,11 @@ 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) - cp $(addprefix $(BYTE_BINDIR)/, $(FLEXDLL_OBJECTS)) $(OPT_BINDIR) else @@ -911,8 +916,6 @@ partialclean:: rm -f flexlink.opt flexlink.opt.exe \ $(OPT_BINDIR)/flexlink $(OPT_BINDIR)/flexlink.exe -INSTALL_COMPLIBDIR = $(DESTDIR)$(COMPLIBDIR) -INSTALL_FLEXDLLDIR = $(INSTALL_LIBDIR)/flexdll FLEXDLL_MANIFEST = default$(filter-out _i386,_$(ARCH)).manifest DOC_FILES=\ @@ -936,11 +939,14 @@ clean:: # Build the manual latex files from the etex source files # (see manual/README.md) .PHONY: manual-pregen -manual-pregen: opt.opt - cd manual; $(MAKE) clean && $(MAKE) pregen-etex +manual-pregen: opt.opt | manual + $(MAKE) -C manual clean + $(MAKE) -C manual pregen-etex +ifneq "$(wildcard manual)" "" clean:: $(MAKE) -C manual clean +endif # The clean target clean:: partialclean @@ -959,8 +965,12 @@ 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 + rm -f ocamlc ocamlc.exe ocamlc.opt ocamlc.opt.exe ocamlc*.stripped # The native-code compiler @@ -971,7 +981,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 @@ -1095,12 +1105,12 @@ otherlibs/dynlink.depend: beforedepend otherlibs/dynlink/native/dynlink.ml \ >> $@ -# Cleanup the lexer +# Cleanup the lexers partialclean:: - rm -f parsing/lexer.ml + rm -f tools/byterntm.ml parsing/lexer.ml -beforedepend:: parsing/lexer.ml +beforedepend:: tools/byterntm.ml parsing/lexer.ml # The predefined exceptions and primitives @@ -1259,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/, \ @@ -1267,22 +1277,21 @@ 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_PROGRAMS = ocamlrun +runtime_BYTECODE_STATIC_LIBRARIES = runtime/libcamlrun.$(A) runtime_BYTECODE_SHARED_LIBRARIES = runtime_NATIVE_STATIC_LIBRARIES = \ runtime/libasmrun.$(A) runtime/libcomprmarsh.$(A) 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 @@ -1290,9 +1299,9 @@ endif ifeq "$(UNIX_OR_WIN32)" "unix" ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" runtime_BYTECODE_STATIC_LIBRARIES += runtime/libcamlrun_pic.$(A) -runtime_BYTECODE_SHARED_LIBRARIES += runtime/libcamlrun_shared.$(SO) +runtime_BYTECODE_SHARED_LIBRARIES += camlrun runtime_NATIVE_STATIC_LIBRARIES += runtime/libasmrun_pic.$(A) -runtime_NATIVE_SHARED_LIBRARIES += runtime/libasmrun_shared.$(SO) +runtime_NATIVE_SHARED_LIBRARIES += asmrun endif endif @@ -1343,13 +1352,15 @@ ocamlruni_CPPFLAGS = $(runtime_CPPFLAGS) -DCAML_INSTR .PHONY: runtime-all runtime-all: \ - $(runtime_BYTECODE_STATIC_LIBRARIES) $(runtime_BYTECODE_SHARED_LIBRARIES) \ - $(runtime_PROGRAMS) $(SAK) + $(runtime_BYTECODE_STATIC_LIBRARIES) \ + $(runtime_BYTECODE_SHARED_LIBRARIES:%=runtime/lib%_shared$(EXT_DLL)) \ + $(runtime_PROGRAMS:%=runtime/%$(EXE)) $(SAK) .PHONY: runtime-allopt ifeq "$(NATIVE_COMPILER)" "true" runtime-allopt: \ - $(runtime_NATIVE_STATIC_LIBRARIES) $(runtime_NATIVE_SHARED_LIBRARIES) + $(runtime_NATIVE_STATIC_LIBRARIES) \ + $(runtime_NATIVE_SHARED_LIBRARIES:%=runtime/lib%_shared$(EXT_DLL)) else runtime-allopt: $(error The build has been configured with --disable-native-compiler) @@ -1357,10 +1368,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) @@ -1396,12 +1403,19 @@ $(SAK): runtime/sak.$(O) runtime/sak.$(O): runtime/sak.c runtime/caml/misc.h runtime/caml/config.h $(V_CC)$(SAK_CC) $(SAK_CFLAGS) $(OUTPUTOBJ)$@ -c $< -C_LITERAL = $(shell $(SAK) encode-C-literal '$(1)') +C_LITERAL = $(shell $(SAK) encode-C-literal $(call QUOTE_SINGLE,$(1))) + +runtime/build_config.h: $(ROOTDIR)/Makefile.config \ + $(ROOTDIR)/Makefile.build_config $(SAK) + $(V_GEN){ \ + echo '/* This file is generated from $(ROOTDIR)/Makefile.config */'; \ + printf '#define OCAML_STDLIB_DIR %s\n' \ + $(call QUOTE_SINGLE,$(call C_LITERAL,$(TARGET_LIBDIR))); \ + echo '#define HOST "$(HOST)"'; \ + echo '#define BYTECODE_RUNTIME_ID "$(BYTECODE_RUNTIME_ID)"'; \ + } > $@ -runtime/build_config.h: $(ROOTDIR)/Makefile.config $(SAK) - $(V_GEN)echo '/* This file is generated from $(ROOTDIR)/Makefile.config */' > $@ && \ - echo '#define OCAML_STDLIB_DIR $(call C_LITERAL,$(LIBDIR))' >> $@ && \ - echo '#define HOST "$(HOST)"' >> $@ +runtime/prims.$(O): runtime/build_config.h ## Runtime libraries and programs @@ -1612,7 +1626,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) @@ -1702,6 +1716,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) \ @@ -1861,6 +1879,13 @@ OCAMLDOC_LIBCMTS=$(OCAMLDOC_LIBMLIS:.mli=.cmt) $(OCAMLDOC_LIBMLIS:.mli=.cmti) ocamldoc/%: CAMLC = $(BEST_OCAMLC) $(STDLIBFLAGS) ocamldoc/%: CAMLOPT = $(BEST_OCAMLOPT) $(STDLIBFLAGS) +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "false" +# ocamldoc needs a custom runtime when building statically owing to the C stubs +# in unix.cma and str.cma. This is specified explicitly to suppress the default +# linking flags (see $(MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS) in Makefile.common) +ocamldoc/ocamldoc$(EXE): ocamldoc_BYTECODE_LINKFLAGS += -custom +endif + .PHONY: ocamldoc ocamldoc: ocamldoc/ocamldoc$(EXE) ocamldoc/odoc_test.cmo @@ -1989,7 +2014,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 \ @@ -2014,12 +2039,24 @@ testsuite/tools/test_in_prefi%: CAMLC = $(BEST_OCAMLC) $(STDLIBFLAGS) test_in_prefix_BYTECODE_LINKFLAGS += -custom +ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" +# testsuite/tools/test_in_prefix cannot use a relative stdlib because it is run +# from testsuite/tools, not from the installation tree (the alternative would be +# to compile it directly with the installed compiler) +test_in_prefix_NATIVE_LINKFLAGS = +test_in_prefix_COMMON_LINKFLAGS = \ + -set-runtime-default 'standard_library_default=$(LIBDIR)' +endif + testsuite/tools/test_in_prefi%: CAMLOPT = $(BEST_OCAMLOPT) $(STDLIBFLAGS) testsuite/tools/dump_load_path$(EXE): \ testsuite/tools/dump_load_path.$(O) runtime/prims.$(O) runtime/libcamlrun.$(A) $(V_MKEXE)$(MKEXE) -o $@ $^ $(BYTECCLIBS) +testsuite/tools/poisonedruntime$(EXE): testsuite/tools/poisonedruntime.$(O) + $(V_MKEXE)$(call MKEXE_VIA_CC,$@,$^) + ocamltest_BYTECODE_LINKFLAGS = -custom -g ocamltest/ocamltest$(EXE): ocamlc ocamlyacc ocamllex @@ -2067,6 +2104,7 @@ partialclean:: rm -f $(addprefix testsuite/tools/*.,cm* o obj a lib) rm -f testsuite/tools/codegen testsuite/tools/codegen.exe rm -f testsuite/tools/dump_load_path testsuite/tools/dump_load_path.exe + rm -f testsuite/tools/poisonedruntime testsuite/tools/poisonedruntime.exe rm -f testsuite/tools/expect testsuite/tools/expect.exe rm -f testsuite/tools/test_in_prefix testsuite/tools/test_in_prefix.exe rm -f testsuite/tools/test_in_prefix.opt \ @@ -2220,6 +2258,13 @@ debugger/ocamldebug.cmo: $(ocamldebug_DEBUGGER_OBJECTS) debugger/ocamldebug_entry.cmo: debugger/ocamldebug.cmo +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "false" +# ocamldebug needs a custom runtime when building statically owing to the +# C stubs in unix.cma. This is specified explicitly to suppress the default +# linking flags (see $(MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS) in Makefile.common) +debugger/ocamldebug$(EXE): ocamldebug_BYTECODE_LINKFLAGS += -custom +endif + clean:: rm -f debugger/ocamldebug debugger/ocamldebug.exe rm -f debugger/debugger_lexer.ml @@ -2451,7 +2496,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 @@ -2488,6 +2533,13 @@ $(ocamltex): VPATH += $(addprefix otherlibs/,str unix) tools/ocamltex.cmo: OC_COMMON_COMPFLAGS += -no-alias-deps +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "false" +# ocamltex needs a custom runtime when building statically owing to the C stubs +# in unix.cma and str.cma. This is specified explicitly to suppress the default +# linking flags (see $(MAYBE_ADD_BYTECODE_LAUNCHER_FLAGS) in Makefile.common) +tools/ocamltex$(EXE): ocamltex_BYTECODE_LINKFLAGS += -custom +endif + # we need str and unix which depend on the bytecode version of other tools # thus we use the othertools target ## Test compilation of backend-specific parts @@ -2664,7 +2716,9 @@ distclean: clean ifneq "$(FLEXDLL_SUBMODULE_PRESENT)" "" $(MAKE) -C flexdll distclean MSVC_DETECT=0 endif +ifneq "$(wildcard manual)" "" $(MAKE) -C manual distclean +endif rm -f ocamldoc/META rm -f $(addprefix ocamltest/,ocamltest_config.ml ocamltest_unix.ml) rm -f testsuite/tools/toolchain.ml @@ -2673,7 +2727,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 @@ -2687,320 +2741,337 @@ 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 -.PHONY: install -install: - $(MKDIR) "$(INSTALL_BINDIR)" - $(MKDIR) "$(INSTALL_LIBDIR)" - $(MKDIR) "$(INSTALL_STUBLIBDIR)" - $(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_LIBDIR)" -ifneq "$(runtime_BYTECODE_SHARED_LIBRARIES)" "" - $(INSTALL_PROG) $(runtime_BYTECODE_SHARED_LIBRARIES) \ - "$(INSTALL_LIBDIR)" -endif - $(INSTALL_DATA) runtime/caml/domain_state.tbl runtime/caml/*.h \ - "$(INSTALL_INCDIR)" - $(INSTALL_PROG) ocaml$(EXE) "$(INSTALL_BINDIR)" +# 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. + +ifeq "$(NATIVE_COMPILER)" "true" +install: full-installoptopt + $(call INSTALL_END) +else +install: common-install + $(call INSTALL_END) +endif + +# 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) +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 +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" +common-install:: +else +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)))) + +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))) + +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) $(addprefix $(BYTE_BINDIR)/, $(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)" -ifneq "$(runtime_NATIVE_SHARED_LIBRARIES)" "" - $(INSTALL_PROG) $(runtime_NATIVE_SHARED_LIBRARIES) "$(INSTALL_LIBDIR)" -endif +.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))) + +full-installopt native-install:: ifeq "$(INSTALL_BYTECODE_PROGRAMS)" "true" - $(call INSTALL_STRIPPED_BYTE_PROG,\ - ocamlopt$(EXE),"$(INSTALL_BINDIR)/ocamlopt.byte$(EXE)") + $(call STRIP_BYTE_PROG, ocamlopt$(EXE)) + $(call INSTALL_ITEM, ocamlopt$(EXE).stripped, bin, , ocamlopt.byte$(EXE)) endif $(MAKE) -C stdlib installopt - $(INSTALL_DATA) \ - middle_end/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/closure/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/flambda/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/flambda/base_types/*.cmi \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - asmcomp/*.cmi \ - "$(INSTALL_COMPLIBDIR)" + $(call INSTALL_ITEMS, \ + middle_end/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + middle_end/closure/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + middle_end/flambda/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + middle_end/flambda/base_types/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) + $(call INSTALL_ITEMS, \ + asmcomp/*.cmi, \ + lib, $(INSTALL_LIBDIR_COMPILERLIBS)) ifeq "$(INSTALL_SOURCE_ARTIFACTS)" "true" - $(INSTALL_DATA) \ - middle_end/*.cmt middle_end/*.cmti \ - middle_end/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/closure/*.cmt middle_end/closure/*.cmti \ - middle_end/closure/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/flambda/*.cmt middle_end/flambda/*.cmti \ - middle_end/flambda/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - middle_end/flambda/base_types/*.cmt \ - middle_end/flambda/base_types/*.cmti \ - middle_end/flambda/base_types/*.mli \ - "$(INSTALL_COMPLIBDIR)" - $(INSTALL_DATA) \ - asmcomp/*.cmt asmcomp/*.cmti \ - asmcomp/*.mli \ - "$(INSTALL_COMPLIBDIR)" -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" -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.build_config.in b/Makefile.build_config.in index 006eb2fc59a2..c6ed402699ad 100644 --- a/Makefile.build_config.in +++ b/Makefile.build_config.in @@ -78,14 +78,22 @@ COMPUTE_DEPS=@compute_deps@ # The C compiler vendor OCAML_CC_VENDOR = @ocaml_cc_vendor@ +BUILD_PATH_LOGICAL = @srcdir_abs@ +BUILD_PATH_PHYSICAL = @srcdir_abs_real@ +BUILD_MAP_FLAGS = @build_map_flags@ +BUILD_MAP_CFLAGS = $(foreach flag, $(BUILD_MAP_FLAGS), \ + $(call QUOTE_SINGLE,$(flag)$(BUILD_PATH_LOGICAL)=+build) \ + $(if $(BUILD_PATH_PHYSICAL), \ + $(call $(QUOTE_SINGLE),$(flag)$(BUILD_PATH_PHYSICAL)=+build))) + # Default flags to use to compile C files -OC_CFLAGS = @oc_cflags@ +OC_CFLAGS = @oc_cflags@ $(BUILD_MAP_CFLAGS) # Flags to use when compiling C files to be linked with bytecode -OC_BYTECODE_CFLAGS = @oc_bytecode_cflags@ +OC_BYTECODE_CFLAGS = @oc_bytecode_cflags@ $(BUILD_MAP_CFLAGS) # Flags to use when compiling C files to be linked with native code -OC_NATIVE_CFLAGS = @oc_native_cflags@ +OC_NATIVE_CFLAGS = @oc_native_cflags@ $(BUILD_MAP_CFLAGS) # The submodules should be searched *before* any other external -I paths OC_INCLUDES = $(addprefix -I $(ROOTDIR)/, \ @@ -104,6 +112,9 @@ OC_DLL_LDFLAGS=@oc_dll_ldflags@ MKEXE_VIA_CC=$(CC) @mkexe_via_cc_ldflags@ @mkexe_via_cc_extra_cmd@ +LAUNCH_METHOD = @launch_method@ +SUFFIXING = @suffixing@ + # Which tool to use to display differences between files DIFF=@DIFF@ # Which flags to pass to the diff tool @@ -133,6 +144,10 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ datarootdir = @datarootdir@ DOCDIR=@docdir@ +### Where to look for the standard library on target +TARGET_LIBDIR=@TARGET_LIBDIR@ +TARGET_LIBDIR_IS_RELATIVE=@target_libdir_is_relative@ + unix_directory = @unix_directory@ unix_library = @unix_library@ @@ -171,6 +186,11 @@ OC_NATIVE_COMPFLAGS = @oc_native_compflags@ OC_NATIVE_LINKFLAGS = -g +BUILD_TRIPLET = @build@ + +# Zinc Runtime ID is needed for installation only +ZINC_RUNTIME_ID = @zinc_runtime_id@ + # Platform-dependent command to create symbolic links LN = @ln@ @@ -188,3 +208,5 @@ TSAN=@tsan@ # Contains TSan-specific runtime files, or nothing if TSan support is # disabled TSAN_NATIVE_RUNTIME_C_SOURCES = @tsan_native_runtime_c_sources@ + +RUNTIME_SEARCH = @runtime_search@ diff --git a/Makefile.common b/Makefile.common index 1e6a7eff8903..192daa858f6b 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" @@ -83,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) @@ -141,9 +480,29 @@ ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" ifeq "$(filter $(REAL_ROOT_DIR)/$(BYTE_BINDIR), $(subst :, ,$(PATH)))" "" export PATH := \ $(REAL_ROOT_DIR)/$(OPT_BINDIR):$(REAL_ROOT_DIR)/$(BYTE_BINDIR):$(PATH) + # $(BUILD_COMPANY_SYSTEM) is the last two parts of $(BUILD_TRIPLET) + BUILD_TRIPLET_FIELDS := $(subst -,$(SPACE),$(BUILD_TRIPLET)) + BUILD_COMPANY_SYSTEM := $(subst $(SPACE),-,$\ + $(wordlist 2, $(words $(BUILD_TRIPLET_FIELDS)), $(BUILD_TRIPLET_FIELDS))) + # Use the FLEXDIR environment variable to tell flexlink where the support + # objects are located. Passing this location using -I would defeat the whole + # purpose of the PATH-trick (bootstrapped flexlink is indistinguishable from + # an installed flexlink). flexlink also looks for the objects in the same + # directory as the executable, but this is slightly irritating as it requires + # copying them to both byte/bin/ and opt/bin/ but also doesn't work if + # opt/bin/flexlink.exe is a symlink to flexlink.opt.exe, as flexlink can end + # up looking in the directory for the target of the symlink, rather than the + # symlink itself. +ifeq "" "$(filter pc-msys pc-cygwin%, $(BUILD_COMPANY_SYSTEM))" + export FLEXDIR := $(REAL_ROOT_DIR) +else + export FLEXDIR := $(shell cygpath -w "$(REAL_ROOT_DIR)") endif - undefine REAL_ROOT_DIR + undefine BUILD_TRIPLET_FIELDS + undefine BUILD_COMPANY_SYSTEM endif + undefine REAL_ROOT_DIR +endif # ifeq "$(BOOTSTRAPPING_FLEXDLL)" "true" # List of other libraries ALL_OTHERLIBS = dynlink str systhreads unix runtime_events @@ -156,6 +515,22 @@ ifeq "$(FUNCTION_SECTIONS)" "true" OPTCOMPFLAGS += -function-sections endif +ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" + SRCDIR_ENCODED = $(subst =,%+,$(subst :,%.,$(subst %,%$(HASH),$(SRCDIR_ABS)))) + SRCDIR_ABS_REAL := $(shell realpath $(SRCDIR_ABS) 2>/dev/null) + SRCDIR_REAL_ENCODED = \ + $(subst =,%+,$(subst :,%.,$(subst %,%$(HASH),$(SRCDIR_ABS_REAL)))) + BUILD_PATH_PREFIX_MAP ?= + export BUILD_PATH_PREFIX_MAP := \ + $(BUILD_PATH_PREFIX_MAP)$\ + :.=$(SRCDIR_ENCODED)$\ + $(if $(SRCDIR_REAL_ENCODED),:.=$(SRCDIR_REAL_ENCODED)) +endif # ifeq "$(TARGET_LIBDIR_IS_RELATIVE)" "true" + +OC_COMMON_LINKFLAGS += \ + -set-runtime-default \ + $(call QUOTE_SINGLE,standard_library_default=$(TARGET_LIBDIR)) + # The rule to compile C files # This rule is similar to GNU make's implicit rule, except that it is more @@ -301,8 +676,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 @@ -326,8 +713,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) @@ -453,12 +844,11 @@ $(eval $(call _OCAML_BYTECODE_LIBRARY,$(1))) $(eval $(call _OCAML_NATIVE_LIBRARY,$(1))) endef # OCAML_LIBRARY -# Installing a bytecode executable, with debug information removed -define INSTALL_STRIPPED_BYTE_PROG -$(OCAMLRUN) $(ROOTDIR)/tools/stripdebug $(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 @@ -467,3 +857,20 @@ endef # INSTALL_STRIPPED_BYTE_PROG # boot/ as part of coldstart. See read_runtime_launch_info in # bytecomp/bytelink.ml for further details. HEADER_NAME = runtime-launch-info + +ifeq "$(UNIX_OR_WIN32)" "win32" +# Ensure that no command can create Cygwin symbolic links by ensuring that +# symlink(2) will fail if native NTFS symlinks aren't available. +CYGWIN ?= +MSYS ?= +export CYGWIN := $(strip \ + $(filter-out winsymlinks winsymlinks:%, $(CYGWIN)) winsymlinks:nativestrict) +export MSYS := $(strip \ + $(filter-out winsymlinks winsymlinks:%, $(MSYS)) winsymlinks:nativestrict) +endif + +# Invocations of the compilers during the build use -nostdlib which ensures that +# existing installations and the OCAMLLIB/CAMLLIB environment variables don't +# affect the build. There isn't an equivalent flag for the toplevel's +# OCAMLTOP_INCLUDE_PATH, so it's scrubbed from the environment instead. +unexport OCAMLTOP_INCLUDE_PATH diff --git a/Makefile.config.in b/Makefile.config.in index e432851cdea5..10b6917ada44 100644 --- a/Makefile.config.in +++ b/Makefile.config.in @@ -219,6 +219,10 @@ FUNCTION_SECTIONS=@function_sections@ AWK=@AWK@ NAKED_POINTERS=false +# Runtime ID values +BYTECODE_RUNTIME_ID=@bytecode_runtime_id@ +NATIVE_RUNTIME_ID=@native_runtime_id@ + # Deprecated variables ## Variables deprecated since OCaml 5.3 diff --git a/VERSION b/VERSION index b562d9f5108f..65b318906844 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -5.3.1+dev0-2025-01-06 +5.3.1+relocatable # Starting with OCaml 4.14, although the version string that appears above is # still correct and this file can thus still be used to figure it out, diff --git a/aclocal.m4 b/aclocal.m4 index 7353e09a96ef..791a49432ed5 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -543,3 +543,14 @@ AC_DEFUN([OCAML_CC_SUPPORTS_LABELS_AS_VALUES], [ [Define if the C compiler supports the labels as values extension.]) fi ]) + +AC_DEFUN([OCAML_CHECK_LN_ON_WINDOWS], [ + AC_MSG_CHECKING([for a workable solution for ln -sf]) + AS_IF([m4_normalize(MSYS=winsymlinks:nativestrict + CYGWIN=winsymlinks:nativestrict + ln -sf configure conftestLink 2>/dev/null)], + [ln='ln -sf'], + [ln='cp -pf'] + ) + AC_MSG_RESULT([$ln]) +]) diff --git a/api_docgen/Makefile b/api_docgen/Makefile index 07254645812a..4ad898ea6bfd 100644 --- a/api_docgen/Makefile +++ b/api_docgen/Makefile @@ -14,6 +14,7 @@ #************************************************************************** # Used by included Makefiles ROOTDIR = .. +SUBDIR_NAME = api_docgen -include ../Makefile.build_config odoc-%: diff --git a/api_docgen/ocamldoc/Makefile b/api_docgen/ocamldoc/Makefile index 5b1f8946d802..a9781f1aba2d 100644 --- a/api_docgen/ocamldoc/Makefile +++ b/api_docgen/ocamldoc/Makefile @@ -14,6 +14,7 @@ #************************************************************************** # Used by included Makefiles ROOTDIR = ../.. +SUBDIR_NAME = api_docgen/ocamldoc include ../Makefile.common vpath %.mli ../../stdlib $(DOC_COMPILERLIBS_DIRS) $(DOC_STDLIB_DIRS) @@ -120,7 +121,4 @@ build/latex/compilerlibs_input.tex: | build/latex .PHONY: install install: - $(MKDIR) "$(INSTALL_LIBRARIES_MAN_DIR)" - if test -d build/man; then \ - $(INSTALL_DATA) build/man/*.3o "$(INSTALL_LIBRARIES_MAN_DIR)"; \ - fi + $(call INSTALL_ITEMS_OPT, build/man/*.3o, man, $(INSTALL_MANDIR_LIBRARIES)) diff --git a/api_docgen/odoc/Makefile b/api_docgen/odoc/Makefile index c40ed778c41e..8f2c0195571a 100644 --- a/api_docgen/odoc/Makefile +++ b/api_docgen/odoc/Makefile @@ -15,6 +15,7 @@ # Used by included Makefiles ROOTDIR = ../.. +SUBDIR_NAME = api_docgen/odoc include ../Makefile.common @@ -191,13 +192,10 @@ $(ALL_PAGED_DOC:%=build/%.3o.stamp):build/%.3o.stamp:build/%.odocl | build/ # Man pages are the only installed documentation .PHONY: install install: - $(MKDIR) "$(INSTALL_LIBRARIES_MAN_DIR)" - if test -d build/man/libref ; then \ - $(INSTALL_DATA) build/man/libref/* "$(INSTALL_LIBRARIES_MAN_DIR)"; \ - fi - if test -d build/man/compilerlibref ; then \ - $(INSTALL_DATA) build/man/libref/* "$(INSTALL_LIBRARIES_MAN_DIR)"; \ - fi + $(call INSTALL_ITEMS_OPT, \ + build/man/libref/*, man, $(INSTALL_MANDIR_LIBRARIES)) + $(call INSTALL_ITEMS_OPT, \ + build/man/compilerlibref/*, man, $(INSTALL_MANDIR_LIBRARIES)) # Dependencies for stdlib modules. # Use the same dependencies used for compiling .cmx files. diff --git a/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..060ae7ac4f3c 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 *) @@ -198,6 +204,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, _, _) = Compilenv.needs_stdlib_location ui 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 +234,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 + (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 compile_phrase (Cmm_helpers.globals_map globals_map); diff --git a/asmcomp/asmpackager.ml b/asmcomp/asmpackager.ml index f0e2148f447c..3745eb0d61f6 100644 --- a/asmcomp/asmpackager.ml +++ b/asmcomp/asmpackager.ml @@ -217,6 +217,7 @@ let build_package_cmx members cmxfile = else Clambda (get_approx ui) 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; @@ -238,9 +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_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/boot/ocamlc b/boot/ocamlc index 852a0ec90168..e93db9ae174f 100755 Binary files a/boot/ocamlc and b/boot/ocamlc differ diff --git a/boot/ocamllex b/boot/ocamllex index 1c1de472dd66..e725ca13139a 100755 Binary files a/boot/ocamllex and b/boot/ocamllex differ diff --git a/build-aux/ocaml_version.m4 b/build-aux/ocaml_version.m4 index 4079bf3bca87..e689cf8ca302 100644 --- a/build-aux/ocaml_version.m4 +++ b/build-aux/ocaml_version.m4 @@ -29,14 +29,17 @@ m4_define([OCAML__DEVELOPMENT_VERSION], [true]) # The three following components (major, minor and patch level) MUST be # integers. They MUST NOT be left-padded with zeros and all of them, -# including the patchlevel, are mandatory. +# including the patchlevel, are mandatory. OCAML__RELEASE_NUMBER must be +# incremented with each minor release, and likewise must be an unpadded integer. m4_define([OCAML__VERSION_MAJOR], [5]) m4_define([OCAML__VERSION_MINOR], [3]) +m4_define([OCAML__RELEASE_NUMBER], [19]) m4_define([OCAML__VERSION_PATCHLEVEL], [1]) + # Note that the OCAML__VERSION_EXTRA string defined below is always empty # for officially-released versions of OCaml. -m4_define([OCAML__VERSION_EXTRA], [dev0-2025-01-06]) +m4_define([OCAML__VERSION_EXTRA], [relocatable]) # The OCAML__VERSION_EXTRA_PREFIX macro defined below should be a # single character: diff --git a/bytecomp/bytegen.ml b/bytecomp/bytegen.ml index 4665ac84bcac..ef054c2c9be4 100644 --- a/bytecomp/bytegen.ml +++ b/bytecomp/bytegen.ml @@ -439,7 +439,8 @@ let comp_primitive stack_info p sz args = | Ostype_unix -> "ostype_unix" | Ostype_win32 -> "ostype_win32" | Ostype_cygwin -> "ostype_cygwin" - | Backend_type -> "backend_type" in + | Backend_type -> "backend_type" + | Standard_library_default -> "standard_library_default" in Kccall(Printf.sprintf "caml_sys_const_%s" const_name, 1) | Pisint -> Kisint | Pisout -> Kisout diff --git a/bytecomp/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 db4fa18e76ce..dbc93c0e8b33 100644 --- a/bytecomp/bytelink.ml +++ b/bytecomp/bytelink.ml @@ -55,7 +55,7 @@ let lib_ccobjs = ref [] let lib_ccopts = ref [] let lib_dllibs = ref [] -let add_ccobjs origin l = +let add_ccobjs origin l dllibs = if not !Clflags.no_auto_link then begin if String.length !Clflags.use_runtime = 0 @@ -68,7 +68,7 @@ let add_ccobjs origin l = in lib_ccopts := List.map replace_origin l.lib_ccopts @ !lib_ccopts; end; - lib_dllibs := l.lib_dllibs @ !lib_dllibs + lib_dllibs := dllibs @ !lib_dllibs end (* A note on ccobj ordering: @@ -139,8 +139,9 @@ let scan_file ldeps obj_name tolink = let pos_toc = input_binary_int ic in (* Go to table of contents *) seek_in ic pos_toc; let toc = (input_value ic : library) in + let dllibs = Dll.read_suffixed_dllibs_from_channel ic toc in close_in ic; - add_ccobjs (Filename.dirname file_name) toc; + add_ccobjs (Filename.dirname file_name) toc dllibs; let required = List.fold_right (fun compunit reqd -> @@ -199,7 +200,7 @@ let debug_info = ref ([] : (int * Instruct.debug_event list * string list) list) (* Link in a compilation unit *) -let link_compunit output_fun currpos_fun inchan file_name compunit = +let link_compunit accu output_fun currpos_fun inchan file_name compunit = check_consistency file_name compunit; seek_in inchan compunit.cu_pos; let code_block = @@ -225,46 +226,44 @@ let link_compunit output_fun currpos_fun inchan file_name compunit = debug_info := (currpos_fun(), debug_event_list, debug_dirs) :: !debug_info end; output_fun code_block; - if !Clflags.link_everything then - List.iter Symtable.require_primitive compunit.cu_primitives + let fold_primitive needs_stdlib name = + if !Clflags.link_everything then + Symtable.require_primitive name; + (needs_stdlib || name = "%standard_library_default") + in + List.fold_left fold_primitive accu compunit.cu_primitives (* Link in a .cmo file *) -let link_object output_fun currpos_fun file_name compunit = - let inchan = open_in_bin file_name in - try - link_compunit output_fun currpos_fun inchan file_name compunit; - close_in inchan - with - Symtable.Error msg -> - close_in inchan; raise(Error(Symbol_error(file_name, msg))) - | x -> - close_in inchan; raise x +let link_object accu output_fun currpos_fun file_name compunit = + In_channel.with_open_bin file_name @@ fun inchan -> + try link_compunit accu output_fun currpos_fun inchan file_name compunit + with Symtable.Error msg -> raise(Error(Symbol_error(file_name, msg))) (* Link in a .cma file *) -let link_archive output_fun currpos_fun file_name units_required = - let inchan = open_in_bin file_name in - try - List.iter - (fun cu -> +let link_archive accu output_fun currpos_fun file_name units_required = + In_channel.with_open_bin file_name @@ fun inchan -> + List.fold_left + (fun accu cu -> let n = Compunit.name cu.cu_name in let name = file_name ^ "(" ^ n ^ ")" in try - link_compunit output_fun currpos_fun inchan name cu + link_compunit accu output_fun currpos_fun inchan name cu with Symtable.Error msg -> raise(Error(Symbol_error(name, msg)))) - units_required; - close_in inchan - with x -> close_in inchan; raise x + accu units_required (* Link in a .cmo or .cma file *) -let link_file output_fun currpos_fun = function +let link_file output_fun currpos_fun accu = function Link_object(file_name, unit) -> - link_object output_fun currpos_fun file_name unit + link_object accu output_fun currpos_fun file_name unit | Link_archive(file_name, units) -> - link_archive output_fun currpos_fun file_name units + link_archive accu output_fun currpos_fun file_name units + +let link_files output_fun currpos_fun = + List.fold_left (link_file output_fun currpos_fun) false (* Output the debugging information *) (* Format is: @@ -297,13 +296,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,60 +308,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 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 "" @@ -379,75 +334,189 @@ let find_bin_sh () = remove_file output_file; result +(* Writes the shell script version of the bytecode launcher to outchan *) +let write_sh_launcher outchan bin_sh bindir search runtime = + let open struct type tag = DFE | F | FE end in + let l tag fmt = + let output s = + match tag, search with + | DFE, _ + | F, Config.Fallback + | FE, (Config.Fallback | Config.Enable) -> + output_string outchan (String.trim s); + output_char outchan '\n' + | _ -> + () + in + Printf.ksprintf output fmt + in + let runtime = Filename.quote runtime in + let bin = Filename.quote (Filename.concat bindir "") in + let exec = + if search = Config.Disable then + runtime + else + {|"$c"|} + in + let release = + Printf.sprintf "%d.%d" Sys.ocaml_release.major Sys.ocaml_release.minor + in + (* Each of the three search modes requires a slightly different shell script. + However, these shell scripts do have one very useful property: the script + for Fallback adds lines to the script for Enable which adds lines to the + script for Disable, but none of them change lines (apart from a trivial + tweak to the exec line for the Disable script). + The lines below are laid out to reflect this, with the tag letters + D(isable), F(allback) and E(nable) for the lines in each script. If a line + is emitted, it is first passed to String.trim, which allows indentation and + a column-based layout to be used. + + The Disable script just needs to exec the runtime. The two searching modes + do a few more calculations and will ultimately exec the contents of $c + (which is why exec_arg above is set to the literal string {v "$c" v}). + + In the script itself: + - $r is the name of the runtime ('ocamlrun', 'ocamlrund', etc.) + - $d is calculated in the script as $(dirname "$0") - i.e. the directory + containing the bytecode executable itself + - $c will ultimately be the runtime to exec. If it is empty, then the + script displays an error message. For Fallback, $c will be the first + runtime to try (i.e. the runtime in bindir), and the bindir passed must + end with a separator (which is ensured by Filename.concat above) + + The script tries up to three options: + - exec $c, if it exists (prefer the runtime in bindir) + - exec $d/$r, if it exists (prefer a runtime in the same directory + as the bytecode executable) + - otherwise try $(command -v "$r") (search PATH for the runtime) + + If the script fails to find an interpreter, $c will always be empty + (since [command -v] will have returned an empty string) and an + error message can be displayed. *) + l DFE {|#!%s |} bin_sh; + l FE {|r=%s |} runtime; + l F {|c=%s"$r" |} bin; + l F {|if ! test -f "$c"; then |}; + l FE {| d="$(dirname "$0" 2>/dev/null)" |}; + l FE {| test -z "$d" || d="${d%%/}/" |}; + l FE {| c="$(command -v "$d$r")" |}; + l FE {| test -n "$c" || c="$(command -v "$r")" |}; + l F {|fi |}; + l FE {|if test -z "$c"; then |}; + l FE {| echo 'This program requires an OCaml %s interpreter'>&2|} release; + l FE {| echo "$r not found either alongside $0 or in \$PATH">&2|}; + l FE {|else |}; + l DFE {| exec %s "$0" "$@" |} exec; + l FE {|fi |}; + l FE {|exit 126 |} + (* Writes the executable header to outchan and writes the RNTM section, if needed. Returns a toc_writer (i.e. Bytesections.init_record is always called) *) let write_header outchan = - let use_runtime, runtime = - if String.length !Clflags.use_runtime > 0 then - (true, make_absolute !Clflags.use_runtime) - else - (false, "ocamlrun" ^ !Clflags.runtime_variant) - in - (* Write the header *) - let runtime_info = - let header = "runtime-launch-info" in - try read_runtime_launch_info (Load_path.find header) - with Not_found -> raise (Error (File_not_found header)) + let zinc_runtime_id, write_exe_launcher = + let header = + let header = "runtime-launch-info" in + try Load_path.find header + with Not_found -> raise (Error (File_not_found header)) + in + let data = + try In_channel.with_open_bin header In_channel.input_all + with Sys_error msg -> raise (Error (Camlheader (msg, header))) + in + let zinc_runtime_id, offset = + if String.length data < 2 then + raise (Error (Camlheader ("corrupt header", header))) + else if data.[0] = '\000' then + None, 1 + else + let zinc = Misc.RuntimeID.of_string (String.sub data 0 4) in + if Option.fold ~none:false ~some:Misc.RuntimeID.is_zinc zinc then + zinc, 4 + else + raise (Error (Camlheader ("corrupt header", header))) + in + let write_exe_header outchan = + let len = String.length data in + Out_channel.output_substring outchan data offset (len - offset) + in + zinc_runtime_id, write_exe_header in - let runtime = - (* Historically, the native Windows ports are assumed to be finding - ocamlrun using a PATH search. *) - if use_runtime || Sys.win32 then - runtime + let runtime, search = + if String.length !Clflags.use_runtime > 0 then + let runtime = !Clflags.use_runtime in + if Filename.is_relative runtime then + Filename.concat (Sys.getcwd ()) runtime, Config.Disable + else + make_absolute runtime, Config.Disable else - Filename.concat runtime_info.bindir runtime + let runtime = + let runtime = "ocamlrun" ^ !Clflags.runtime_variant in + let some = Misc.RuntimeID.ocamlrun !Clflags.runtime_variant in + Option.fold ~none:runtime ~some zinc_runtime_id + in + let runtime = + if !Clflags.search_method = Config.Disable then + Filename.concat !Clflags.target_bindir runtime + else + runtime + in + runtime, !Clflags.search_method in (* Determine which method will be used for launching the executable: Executable: concatenate the bytecode image to the executable stub Shebang_runtime: #! line with the required runtime Shebang_bin_sh: #! for a shell script calling exec *) let launcher = - if runtime_info.launcher = Executable then - Executable - else - if invalid_for_shebang_line runtime then - match runtime_info.launcher with - | Shebang_bin_sh sh -> - let sh = - if sh = "sh" then - find_bin_sh () - else - sh in - if sh = "" || invalid_for_shebang_line sh then - Executable - else - Shebang_bin_sh sh - | _ -> + match !Clflags.launch_method with + | Config.Executable -> + Executable + | Config.Shebang sh -> + if search <> Config.Disable || invalid_for_shebang_line runtime then + let sh = + match sh with + | Some sh -> sh + | None -> find_bin_sh () + in + if sh = "" || invalid_for_shebang_line sh then Executable - else - Shebang_runtime + else + Shebang_bin_sh sh + else + Shebang_runtime in + (* Write the header *) match launcher with | Shebang_runtime -> + assert (search = Config.Disable); (* Use the runtime directly *) Printf.fprintf outchan "#!%s\n" runtime; Bytesections.init_record outchan | Shebang_bin_sh bin_sh -> - (* exec the runtime using sh *) - Printf.fprintf outchan "\ - #!%s\n\ - exec %s \"$0\" \"$@\"\n" bin_sh (Filename.quote runtime); + (* Use the shebang launcher *) + write_sh_launcher outchan bin_sh bindir search runtime; Bytesections.init_record outchan | Executable -> (* Use the executable stub launcher *) - let pos = runtime_info.executable_offset in - let len = String.length runtime_info.buffer - pos in - Out_channel.output_substring outchan runtime_info.buffer pos len; + write_exe_launcher outchan; (* The runtime name needs recording in RNTM *) let toc_writer = Bytesections.init_record outchan in - Printf.fprintf outchan "%s\000" runtime; + (* stdlib/header.c determines which mode is needed based on whether the + RNTM section contains an embedded NUL character. For Disable, the path + is written verbatim (no extra NUL), otherwise the directory separator + just before the basename is effectively turned into a NUL (for Enable, + there is no dirname, so the string "begins" with a NUL character). *) + if search = Disable then + output_string outchan runtime + else begin + if search = Fallback then + (* Ensure bindir does _not_ end up with a separator *) + output_string outchan + (Filename.(dirname (concat bindir current_dir_name))); + output_char outchan '\000'; + output_string outchan runtime + end; Bytesections.record toc_writer RNTM; toc_writer @@ -483,19 +552,36 @@ 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 + (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 = 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; @@ -504,11 +590,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; @@ -518,6 +613,18 @@ let link_bytecode ?final_name tolink exec_name standalone = ~filename:final_name ~kind:"bytecode executable" outchan (Symtable.initial_global_table()); Bytesections.record toc_writer DATA; + (* -custom executables don't need OSLD sections - the correct value is + already included in the runtime. *) + if standalone && needs_stdlib then begin + (* OCaml Standard Library Default location *) + let standard_library_default = + Option.value + ~default:Config.standard_library_default + !Clflags.standard_library_default + in + output_string outchan standard_library_default; + Bytesections.record toc_writer OSLD + end; (* The map of global identifiers *) Symtable.output_global_map outchan; Bytesections.record toc_writer SYMB; @@ -589,6 +696,59 @@ 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. *) +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 Sys.win32 && (c < 32 (* ' ' *) || c >= 127) -> + (* Convert u to UTF-16LE, allowing for surrogate pairs *) + let len = Bytes.set_utf_16le_uchar utf16le 0 u in + for i = 1 to len / 2 do + Printf.bprintf b "\\x%04x" (Bytes.get_uint16_le utf16le ((i - 1) * 2)) + done + | _ -> + Buffer.add_utf_8_uchar b u + in + if Sys.win32 then + Buffer.add_char b 'L'; + Buffer.add_char b '"'; + Seq.iter escape (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 = @@ -603,6 +763,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 @@ -612,6 +774,11 @@ extern "C" { #include #include +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[] = { |}; Symtable.init(); @@ -621,7 +788,7 @@ static int caml_code[] = { output_code_string outchan code; currpos := !currpos + (Bigarray.Array1.dim code) and currpos_fun () = !currpos in - List.iter (link_file output_fun currpos_fun) tolink; + ignore (link_files output_fun currpos_fun tolink); (* The final STOP instruction *) Printf.fprintf outchan "\n0x%x};\n" Opcodes.opSTOP; (* The table of global data *) @@ -649,6 +816,7 @@ static char caml_sections[] = { }; |}; + emit_runtime_standard_library_default outchan; (* The table of primitives *) Symtable.output_primitive_table outchan; (* The entry point *) @@ -656,7 +824,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), @@ -714,13 +881,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" @@ -798,11 +972,22 @@ let link objfiles output_name = extern "C" { #endif +#define CAML_INTERNALS #define CAML_INTERNALS_NO_PRIM_DECLARATIONS + +#include + #include +#include + +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; + emit_runtime_standard_library_default poc; output_string poc {| #ifdef __cplusplus } @@ -857,7 +1042,8 @@ extern "C" { 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 61dffee8779c..9a8d7a4da47a 100644 --- a/bytecomp/bytelink.mli +++ b/bytecomp/bytelink.mli @@ -30,6 +30,8 @@ val linkdeps_unit : val extract_crc_interfaces: unit -> crcs +val to_utf_8_seq : string -> Uchar.t Seq.t + type error = | File_not_found of filepath | Not_an_object_file of filepath diff --git a/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/bytecomp/dll.ml b/bytecomp/dll.ml index f86efc6e93be..24028f21bd8a 100644 --- a/bytecomp/dll.ml +++ b/bytecomp/dll.ml @@ -51,13 +51,41 @@ 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 + +(* 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. *) @@ -138,22 +166,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 +182,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/bytecomp/dll.mli b/bytecomp/dll.mli index 216fea828014..e92be9f08dbb 100644 --- a/bytecomp/dll.mli +++ b/bytecomp/dll.mli @@ -15,8 +15,14 @@ (* 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 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 + +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; diff --git a/configure b/configure index 2b8e81bc3d2b..a763699fcac3 100755 --- a/configure +++ b/configure @@ -56,7 +56,7 @@ if test -e '.git' ; then : fi fi # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for OCaml 5.3.1+dev0-2025-01-06. +# Generated by GNU Autoconf 2.71 for OCaml 5.3.1+relocatable. # # Report bugs to . # @@ -677,8 +677,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='OCaml' PACKAGE_TARNAME='ocaml' -PACKAGE_VERSION='5.3.1+dev0-2025-01-06' -PACKAGE_STRING='OCaml 5.3.1+dev0-2025-01-06' +PACKAGE_VERSION='5.3.1+relocatable' +PACKAGE_STRING='OCaml 5.3.1+relocatable' PACKAGE_BUGREPORT='caml-list@inria.fr' PACKAGE_URL='http://www.ocaml.org' @@ -785,10 +785,23 @@ build_os build_vendor build_cpu build +runtime_search_target +runtime_search +suffixing +native_runtime_id +bytecode_runtime_id +zinc_runtime_id +build_map_flags +srcdir_abs_real +srcdir_abs +target_libdir_is_relative ar_supports_response_files QS +TARGET_LIBDIR ocaml_libdir +TARGET_BINDIR ocaml_bindir +ocaml_prefix compute_deps build_libraries_manpages PACKLD @@ -832,6 +845,7 @@ build_ocamldoc build_ocamltex build_ocamldebug with_debugger +as_is_cc as_has_debug_prefix_map cc_has_debug_prefix_map unix_directory @@ -858,6 +872,8 @@ natdynlink supports_shared_libraries mklib AR +launch_method_target +launch_method shebangscripts winpthreads_source_include_dir winpthreads_source_dir @@ -928,6 +944,7 @@ CMO_MAGIC_NUMBER CMI_MAGIC_NUMBER EXEC_MAGIC_NUMBER MAGIC_LENGTH +OCAML_RELEASE_NUMBER OCAML_VERSION_SHORT OCAML_VERSION_EXTRA OCAML_VERSION_PATCHLEVEL @@ -1012,6 +1029,7 @@ enable_flambda_invariants enable_cmm_invariants with_target_bindir with_target_sh +with_additional_stublibsdir enable_reserved_header_bits enable_stdlib_manpages enable_warn_error @@ -1019,6 +1037,10 @@ enable_force_safe_string 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 @@ -1602,7 +1624,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures OCaml 5.3.1+dev0-2025-01-06 to adapt to many kinds of systems. +\`configure' configures OCaml 5.3.1+relocatable to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1669,7 +1691,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of OCaml 5.3.1+dev0-2025-01-06:";; + short | recursive ) echo "Configuration of OCaml 5.3.1+relocatable:";; esac cat <<\_ACEOF @@ -1721,6 +1743,13 @@ Optional Features: --disable-function-sections do not emit each function in a separate section --enable-mmap-map-stack use mmap to allocate stacks instead of malloc + --disable-suffixing disable suffixing of runtime executables and shared + libraries + --enable-runtime-search allow the distribution's bytecode executables to + search for ocamlrun + --enable-runtime-search-target + allow bytecode executables produced by ocamlc to + search for ocamlrun --enable-shared[=PKGS] build shared libraries [default=yes] --enable-static[=PKGS] build static libraries [default=yes] --enable-fast-install[=PKGS] @@ -1734,6 +1763,12 @@ Optional Packages: --with-target-bindir location of the runtime binaries on the target system --with-target-sh location of Posix sh on the target system + --with-additional-stublibsdir + additional directory for searching for bytecode stub + libraries + --with-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 @@ -1851,7 +1886,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -OCaml configure 5.3.1+dev0-2025-01-06 +OCaml configure 5.3.1+relocatable generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2508,7 +2543,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by OCaml $as_me 5.3.1+dev0-2025-01-06, which was +It was created by OCaml $as_me 5.3.1+relocatable, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3264,8 +3299,28 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.3.1+dev0-2025-01-06" >&5 -printf "%s\n" "$as_me: Configuring OCaml version 5.3.1+dev0-2025-01-06" >&6;} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: Configuring OCaml version 5.3.1+relocatable" >&5 +printf "%s\n" "$as_me: Configuring OCaml version 5.3.1+relocatable" >&6;} + +# It's important for the setting up of defaults and the checking of the +# --with-relative-libdir option to know whether the user specified --libdir. +# Unfortunately, autoconf doesn't provide an easy way to determine this, so we +# simply repeat autoconf's argument parsing for the libdir and mandir options. +# This is done early in the script to ensure that only autoconf and site-config +# scripts have run. +libdir_given=no +mandir_given=no +for ac_arg +do + case $ac_arg in + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=* | \ + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + mandir_given=yes;; + -libdir=* | --libdir=* | --libdi=* | --libd=* | \ + -libdir | --libdir | --libdi | --libd) + libdir_given=yes;; + esac +done # Configuration variables @@ -3325,6 +3380,12 @@ ocamltest_libunix=None ocamltest_unix_impl="dummy" unix_library="" unix_directory="" +target_libdir_is_relative=false +srcdir_abs='' +srcdir_abs_real='' +build_map_flags='' +runtime_search='' +runtime_search_target='' # Information about the package @@ -3339,11 +3400,11 @@ unix_directory="" -VERSION=5.3.1+dev0-2025-01-06 +VERSION=5.3.1+relocatable OCAML_DEVELOPMENT_VERSION=true -OCAML_RELEASE_EXTRA='Some (Plus, "dev0-2025-01-06")' +OCAML_RELEASE_EXTRA='Some (Plus, "relocatable")' OCAML_VERSION_MAJOR=5 @@ -3351,10 +3412,12 @@ OCAML_VERSION_MINOR=3 OCAML_VERSION_PATCHLEVEL=1 -OCAML_VERSION_EXTRA=dev0-2025-01-06 +OCAML_VERSION_EXTRA=relocatable OCAML_VERSION_SHORT=5.3 +OCAML_RELEASE_NUMBER=19 + printf "%s\n" "#define MAGIC_NUMBER_PREFIX \"Caml1999\"" >>confdefs.h printf "%s\n" "#define MAGIC_NUMBER_VERSION \"035\"" >>confdefs.h @@ -3470,6 +3533,9 @@ LINEAR_MAGIC_NUMBER=Caml1999L035 + + + @@ -3508,6 +3574,19 @@ LINEAR_MAGIC_NUMBER=Caml1999L035 + + + + + + + + + + + + + @@ -3530,10 +3609,6 @@ ac_config_files="$ac_config_files Makefile.config" ac_config_files="$ac_config_files stdlib/sys.ml" -ac_config_files="$ac_config_files manual/src/version.tex" - -ac_config_files="$ac_config_files manual/src/html_processing/src/common.ml" - ac_config_files="$ac_config_files ocamltest/ocamltest_config.ml" ac_config_files="$ac_config_files otherlibs/dynlink/dynlink_config.ml" @@ -3548,6 +3623,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" @@ -3560,6 +3637,13 @@ ac_config_files="$ac_config_files stdlib/META" ac_config_files="$ac_config_files testsuite/tools/toolchain.ml" +if test -d manual +then : + ac_config_files="$ac_config_files manual/src/version.tex" + + ac_config_files="$ac_config_files manual/src/html_processing/src/common.ml" + +fi # Definitions related to the version of OCaml printf "%s\n" "#define OCAML_VERSION_MAJOR 5" >>confdefs.h @@ -3568,13 +3652,15 @@ printf "%s\n" "#define OCAML_VERSION_MINOR 3" >>confdefs.h printf "%s\n" "#define OCAML_VERSION_PATCHLEVEL 1" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_ADDITIONAL \"dev0-2025-01-06\"" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_ADDITIONAL \"relocatable\"" >>confdefs.h - printf "%s\n" "#define OCAML_VERSION_EXTRA \"dev0-2025-01-06\"" >>confdefs.h + printf "%s\n" "#define OCAML_VERSION_EXTRA \"relocatable\"" >>confdefs.h printf "%s\n" "#define OCAML_VERSION 50301" >>confdefs.h -printf "%s\n" "#define OCAML_VERSION_STRING \"5.3.1+dev0-2025-01-06\"" >>confdefs.h +printf "%s\n" "#define OCAML_VERSION_STRING \"5.3.1+relocatable\"" >>confdefs.h + +printf "%s\n" "#define OCAML_RELEASE_NUMBER 19" >>confdefs.h # Works out how many "o"s are needed in quoted strings @@ -3699,12 +3785,47 @@ test -n "$target_alias" && NONENONEs,x,x, && program_prefix=${target_alias}- +# $cygwin_build_env=true if the build is taking place in any kind of Cygwin-like +# environment (which may include cross-compiling _from_ Cygwin) +# All patterns end with * (cf. build-aux/config.sub) +# +# In Cygwin itself, the mingw-w64 compilers are cross-compilers +# (host=x86_64-pc-cygwin; target=*-w64-mingw32) and $build when running from +# within Cygwin is always *-pc-cygwin. +# +# In MSYS2, the mingw-w64 compilers are normal host compilers, which MSYS2 makes +# available through different Environments (similar to the Microsoft Visual +# Studio Tools Command Prompts; see https://www.msys2.org/docs/environments/). +# It is possible to use MSYS2's "Cygwin" gcc (the equivalent of compiling native +# Cygwin), in which case $build is *-*-cygwin (some older MSYS2 installations +# may report the legacy *-*-msys*) +# The mingw-w64 Environments manually set $build to *-w64-mingw32, but the +# _native_ value inferred by config.guess (which uses uname -s) will be +# *-pc-mingw32 (for the 32-bit _target_ environments, even though MSYS2 is a +# 64-bit build environment) and *-pc-mingw64 (for the 64-bit _target_ +# environments). +# +# This leads to the four patterns below, all of which imply that the build is +# taking place on a system where Cygwin's utilities (cygpath, etc.) can be +# expected to be found and its semantics (CYGWIN=winsymlinks:native, etc.) be +# expected to apply. +# +# Note that although build=x86_64-pc-mingw64 will be accepted here, it is highly +# likely that that's a misconfigured environment, and the script will +# subsequently fail if host has not been altered to x86_64-w64-mingw32. +case $build in #( + *-*-cygwin*|*-*-msys*|*-*-mingw32*|*-*-mingw64*) : + cygwin_build_env=true ;; #( + *) : + cygwin_build_env=false ;; +esac + # Ensure that AC_CONFIG_LINKS will either create symlinks which are compatible # with native Windows (i.e. NTFS symlinks, not WSL or Cygwin-emulated ones) or # use its fallback mechanisms. Native Windows versions of ocamlc/ocamlopt cannot # interpret either WSL or Cygwin-emulated symlinks. -case $host in #( - *-pc-windows|*-w64-mingw32*) : +case $cygwin_build_env,$host in #( + true,*-pc-windows|true,*-w64-mingw32*) : ac_config_commands="$ac_config_commands native-symlinks" ;; #( *) : @@ -3803,6 +3924,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 @@ -4060,9 +4190,9 @@ fi # Check whether --with-target-bindir was given. if test ${with_target_bindir+y} then : - withval=$with_target_bindir; target_bindir=$withval + withval=$with_target_bindir; TARGET_BINDIR=$withval else $as_nop - target_bindir='' + TARGET_BINDIR='' fi @@ -4072,12 +4202,29 @@ 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 + + + +# 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 @@ -4151,6 +4298,82 @@ 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 --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=auto +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,,) : + ;; #( + 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 ;; #( + *) : + ;; +esac + + # Check whether --with-afl was given. if test ${with_afl+y} then : @@ -13816,8 +14039,8 @@ esac # See https://lists.gnu.org/archive/html/autoconf/2019-07/msg00002.html # for the detailed explanation. -ocamlsrcdir=$(unset CDPATH; cd -- "$srcdir" && printf %sX "$PWD") || fail -ocamlsrcdir=${ocamlsrcdir%X} +srcdir_abs=$(unset CDPATH; cd -- "$srcdir" && printf %sX "$PWD") || fail +srcdir_abs=${srcdir_abs%X} # Whether ar supports @FILE arguments @@ -13830,17 +14053,30 @@ esac # Libraries to build depending on the host -case $host in #( - *-*-mingw32*|*-pc-windows) : +case $cygwin_build_env,$host in #( + true,*-*-mingw32*|true,*-pc-windows) : unix_or_win32="win32" - ln='cp -pf' + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a workable solution for ln -sf" >&5 +printf %s "checking for a workable solution for ln -sf... " >&6; } + if MSYS=winsymlinks:nativestrict CYGWIN=winsymlinks:nativestrict ln -sf configure conftestLink 2>/dev/null +then : + ln='ln -sf' +else $as_nop + ln='cp -pf' + +fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ln" >&5 +printf "%s\n" "$ln" >&6; } + ocamltest_libunix="Some false" - ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$ocamlsrcdir")" + ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$srcdir_abs")" ocamlyacc_wstr_module="yacc/wstr" ;; #( *) : unix_or_win32="unix" ln='ln -sf' ocamltest_libunix="Some true" + ocamlsrcdir="$srcdir_abs" ocamlyacc_wstr_module="" ;; esac @@ -13924,9 +14160,9 @@ then : # not use shebang scripts shebangscripts=true launch_method='sh' - if test x"$target_launch_method" = 'x' + if test x"$launch_method_target" = 'x' then : - target_launch_method='exe' + launch_method_target='exe' fi ;; #( *-*-mingw32*|*-pc-windows) : ;; #( @@ -13938,21 +14174,6 @@ esac fi -# stdlib/runtime.info and stdlib/target_runtime.info are generated by commands -# in config.status, rather than by the .in mechanism, since the latter cannot -# reliably process binary files. -ac_config_commands="$ac_config_commands shebang" - - -# Are we building a cross-compiler - -if test x"$host" = x"$target" -then : - cross_compiler=false -else $as_nop - cross_compiler=true -fi - # Checks for programs ## Check for the C compiler: done by libtool @@ -14489,16 +14710,16 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test x"$build" != x"$host" then : - case $build in #( - *-pc-msys|*-pc-cygwin*) : - flexlink_where="$(cmd /c "$flexlink" -where 2>/dev/null)" - if test -z "$flexlink_where" + if $cygwin_build_env +then : + flexlink_where="$(cmd /c "$flexlink" -where 2>/dev/null)" + if test -z "$flexlink_where" then : as_fn_error $? "$flexlink is not executable from a native Win32 process" "$LINENO" 5 -fi ;; #( - *) : - ;; -esac + +fi + +fi fi @@ -14993,6 +15214,13 @@ then : fi +ac_fn_c_check_header_compile "$LINENO" "libgen.h" "ac_cv_header_libgen_h" "$ac_includes_default" +if test "x$ac_cv_header_libgen_h" = xyes +then : + printf "%s\n" "#define HAS_LIBGEN_H 1" >>confdefs.h + +fi + ac_fn_c_check_header_compile "$LINENO" "pthread_np.h" "ac_cv_header_pthread_np_h" "$ac_includes_default" if test "x$ac_cv_header_pthread_np_h" = xyes then : @@ -16568,30 +16796,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 @@ -16937,6 +17180,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" @@ -18409,8 +18661,6 @@ fi ## -fdebug-prefix-map support by the C compiler case $ocaml_cc_vendor,$host in #( - *,*-*-mingw32*) : - cc_has_debug_prefix_map=false ;; #( *,*-pc-windows) : cc_has_debug_prefix_map=false ;; #( xlc*,powerpc-ibm-aix*) : @@ -18460,6 +18710,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 @@ -20979,12 +21323,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 @@ -21004,16 +21348,86 @@ then : *) : ;; esac -else $as_nop - if test x"$unix_or_win32" = "xwin32" \ - && test "$host_vendor-$host_os" != "$build_vendor-$build_os" -then : - case $build in #( - *-pc-cygwin) : - prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")" ;; #( +fi + +# Normalise $prefix, if necessary, on Windows. There are 7 variables to be +# considered: +# - $prefix and $exec_prefix. These are autoconf variables containing the values +# specified for --prefix and --exec-prefix respectively, or NONE if these +# flags were not given on the command line. These two variables are written to +# Makefile.config and are in _build_ format. On native Windows, the values +# must be suitable to pass to _both_ native Windows processes and Cygwin/MSYS2 +# commands. The values are ultimately converted to Windows paths using slashes +# (i.e. C:/foo) +# - $bindir and $libdir. These similarly contain the values specified for +# --bindir and --libdir, or defaults relative to $exec_prefix otherwise. +# Unlike --prefix, values specified to configure for --bindir and --libdir are +# assumed to be suitable for both native Windows processes and Cygwin/MSYS2. +# These two variables ultimately end up in Makefile.config as $(BINDIR) and +# $(LIBDIR) and are used for installation commands only. +# - $ocaml_prefix, $ocaml_bindir and $ocaml_libdir are the _expanded_ values of +# $prefix, $bindir and $libdir so that the values can be inserted into OCaml +# strings (typically in utils/config.generated.ml). On Windows, these preserve +# the backslashes present in the value passed to --prefix. +ocaml_bindir="$bindir" +ocaml_libdir="$libdir" +ocaml_prefix="$prefix" +case $cygwin_build_env,$host in #( + true,*-w64-mingw32*|true,*-pc-windows) : + prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")" + if test "x${ocaml_prefix%%/*}" != "x$ocaml_prefix" +then : + # $prefix contained a slash - normalise it with cygpath. The rationale for + # this allows both `./configure --prefix $PWD/install` (which will be a + # Cygwin path) and also systems lazily using slashes instead of + # backslashes (i.e. C:/Backslashes/Scare/Us) to work. + ocaml_prefix="$prefix" +else $as_nop + # $prefix was using backslashes - preserve these in the build, but + # continue to use slashes for the Makefile variables. + if test "x$bindir" = 'x${exec_prefix}/bin' +then : + ocaml_bindir='${exec_prefix}\bin' +else $as_nop + ocaml_bindir="$bindir" +fi + if test "x$libdir" = 'x${exec_prefix}/lib/ocaml' +then : + ocaml_libdir='${exec_prefix}\lib\ocaml' +else $as_nop + ocaml_libdir="$libdir" +fi +fi ;; #( *) : ;; esac + +if test x"$libdir_given" = 'xno' +then : + if test x"$bindir_to_libdir" != 'x' +then : + ocaml_libdir="$bindir_to_libdir" + target_libdir_is_relative=true + case $cygwin_build_env,$host in #( + true,*-w64-mingw32*|true,*-pc-windows) : + build_bindir_to_libdir="$(LC_ALL=C.UTF-8 cygpath \ + "$bindir_to_libdir")" ;; #( + *) : + build_bindir_to_libdir="$bindir_to_libdir" ;; +esac + case $build_bindir_to_libdir in #( + ./*) : + libdir="$bindir${build_bindir_to_libdir#.}" ;; #( + ../*) : + libdir="$bindir/$build_bindir_to_libdir" ;; #( + *) : + as_fn_error $? "--with-relative-libdir requires an explicit relative path" "$LINENO" 5 ;; +esac +fi +else $as_nop + if test x"$bindir_to_libdir" != 'x' +then : + as_fn_error $? "--with-relative-libdir and --libdir cannot both be specified" "$LINENO" 5 fi fi @@ -21044,6 +21458,88 @@ case $host in #( ;; esac +# Determine the three Runtime IDs (see runtime/Mangling.md) + + + + + +# Bits 0-4 (dev + low 4 bits of release) + + +# Bits 5-6 (high 2 bits of release) + + +# Bits 7-9 (low 3 bits of reserved) +quintet1="1 + $(expr \( $reserved_header_bits \* 4 \) % 32)" +quintet1="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet1 \) + 1))" + +# Bits 10-11 (high 2 bits of reserved) +quintet2_byte="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $reserved_header_bits / 8 \) + 1))" + +# Bit 12 (no-flat-float-array) +if $flat_float_array +then : + quintet2_zinc='0' +else $as_nop + quintet2_byte="4 + $quintet2_byte" + quintet2_zinc='4' +fi +# Bit 13 (fp) +if $frame_pointers +then : + quintet2_native="8 + $quintet2_byte" +else $as_nop + quintet2_native="$quintet2_byte" +fi +# Bit 14 (tsan) +if $tsan +then : + quintet2_native="16 + $quintet2_native" +fi + +quintet2_zinc="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet2_zinc \) + 1))" +quintet2_byte="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet2_byte \) + 1))" +quintet2_native="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet2_native \) + 1))" + +# Bit 15 (int31) +if $arch64 +then : + quintet3_zinc='0' +else $as_nop + quintet3_zinc='1' +fi +# Bit 16 (static) +if ! $supports_shared_libraries +then : + quintet3_zinc="2 + $quintet3_zinc" +fi +# Bit 17 (no-compression) +if test x"$zstd_status" != 'xok' +then : + quintet3_zinc="4 + $quintet3_zinc" +fi +# Bit 18 (ansi) +case $target,$windows_unicode in #( + *-*-mingw32,0|*-pc-windows,0) : + quintet3="8 + $quintet3_zinc" ;; #( + *) : + quintet3="$quintet3_zinc" ;; +esac +# Bit 19 (mutable-string) cannot be set since OCaml 5.0 + +quintet3_zinc="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet3_zinc \) + 1))" +quintet3="$(echo '0123456789abcdefghijklmnopqrstuv' | cut -c $(expr \( $quintet3 \) + 1))" + +zinc_runtime_id="71${quintet2_zinc}${quintet3_zinc}" +bytecode_runtime_id="7${quintet1}${quintet2_byte}${quintet3}" +native_runtime_id="7${quintet1}${quintet2_native}${quintet3}" + +# Update the values for is_official_release and release_number in +# utils/config.common.ml.in (this is done when tools/autogen is run, not each +# time configure is run!) + + # Do not permanently cache the result of flexdll.h unset ac_cv_header_flexdll_h @@ -21051,6 +21547,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 @@ -21195,6 +21695,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" @@ -21204,15 +21705,42 @@ then : exec_prefix="$prefix" fi eval "exec_prefix=\"$exec_prefix\"" - eval "ocaml_bindir=\"$bindir\"" - eval "ocaml_libdir=\"$libdir\"" - if test x"$target_bindir" = 'x' + # Set variables necessary to create utils/config.generated.ml. + # $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) + eval "HOST_BINDIR=\"$ocaml_bindir\"" + if test "x$bindir_to_libdir" = 'x' then : - target_bindir="$ocaml_bindir" + ocaml_bindir="$HOST_BINDIR" +else $as_nop + ocaml_bindir='' fi - if test x"$target_launch_method" = 'x' + if test x"$TARGET_BINDIR" = 'x' +then : + if test "x$bindir_to_libdir" = 'x' then : - target_launch_method="$launch_method" + TARGET_BINDIR="$HOST_BINDIR" +else $as_nop + TARGET_BINDIR='.' +fi +fi + eval "ocaml_libdir=\"$ocaml_libdir\"" + if test "x$bindir_to_libdir" = 'x' +then : + TARGET_LIBDIR="$ocaml_libdir" +else $as_nop + TARGET_LIBDIR="$bindir_to_libdir" +fi + if test x"$launch_method_target" = 'x' +then : + launch_method_target="$launch_method" fi prefix="$saved_prefix" exec_prefix="$saved_exec_prefix" @@ -21691,7 +22219,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by OCaml $as_me 5.3.1+dev0-2025-01-06, which was +This file was extended by OCaml $as_me 5.3.1+relocatable, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -21764,7 +22292,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -OCaml config.status 5.3.1+dev0-2025-01-06 +OCaml config.status 5.3.1+relocatable configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" @@ -22167,11 +22695,11 @@ fi - launch_method='$(echo "$launch_method" | sed -e "s/'/'\"'\"'/g")' - target_launch_method=\ -'$(echo "$target_launch_method" | sed -e "s/'/'\"'\"'/g")' - ocaml_bindir='$(echo "$ocaml_bindir" | sed -e "s/'/'\"'\"'/g")' - target_bindir='$(echo "$target_bindir" | sed -e "s/'/'\"'\"'/g")' +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 @@ -22184,8 +22712,6 @@ do "Makefile.build_config") CONFIG_FILES="$CONFIG_FILES Makefile.build_config" ;; "Makefile.config") CONFIG_FILES="$CONFIG_FILES Makefile.config" ;; "stdlib/sys.ml") CONFIG_FILES="$CONFIG_FILES stdlib/sys.ml" ;; - "manual/src/version.tex") CONFIG_FILES="$CONFIG_FILES manual/src/version.tex" ;; - "manual/src/html_processing/src/common.ml") CONFIG_FILES="$CONFIG_FILES manual/src/html_processing/src/common.ml" ;; "ocamltest/ocamltest_config.ml") CONFIG_FILES="$CONFIG_FILES ocamltest/ocamltest_config.ml" ;; "otherlibs/dynlink/dynlink_config.ml") CONFIG_FILES="$CONFIG_FILES otherlibs/dynlink/dynlink_config.ml" ;; "utils/config.common.ml") CONFIG_FILES="$CONFIG_FILES utils/config.common.ml" ;; @@ -22193,12 +22719,15 @@ 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" ;; "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" ;; @@ -22208,9 +22737,9 @@ 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" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac @@ -23341,10 +23870,13 @@ ltmain=$ac_aux_dir/ltmain.sh chmod +x "$ofile" ;; - "shebang":C) printf '%s\n%s\000\n' "$launch_method" "$ocaml_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 + 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 a1ce09dc58bf..b3deb70abec6 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,26 @@ AC_INIT([OCaml], AC_MSG_NOTICE([Configuring OCaml version AC_PACKAGE_VERSION]) +# It's important for the setting up of defaults and the checking of the +# --with-relative-libdir option to know whether the user specified --libdir. +# Unfortunately, autoconf doesn't provide an easy way to determine this, so we +# simply repeat autoconf's argument parsing for the libdir and mandir options. +# This is done early in the script to ensure that only autoconf and site-config +# scripts have run. +libdir_given=no +mandir_given=no +for ac_arg +do + case $ac_arg in + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=* | \ + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + mandir_given=yes;; + -libdir=* | --libdir=* | --libdi=* | --libd=* | \ + -libdir | --libdir | --libdi | --libd) + libdir_given=yes;; + esac +done + # Configuration variables ## Command-line arguments passed to configure @@ -83,6 +103,12 @@ ocamltest_libunix=None ocamltest_unix_impl="dummy" unix_library="" unix_directory="" +target_libdir_is_relative=false +srcdir_abs='' +srcdir_abs_real='' +build_map_flags='' +runtime_search='' +runtime_search_target='' # Information about the package @@ -105,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]) @@ -184,6 +211,8 @@ AC_SUBST([flexdll_dir]) AC_SUBST([winpthreads_source_dir]) AC_SUBST([winpthreads_source_include_dir]) AC_SUBST([shebangscripts]) +AC_SUBST([launch_method]) +AC_SUBST([launch_method_target]) AC_SUBST([AR]) AC_SUBST([mklib]) AC_SUBST([supports_shared_libraries]) @@ -210,6 +239,7 @@ AC_SUBST([unix_library]) AC_SUBST([unix_directory]) AC_SUBST([cc_has_debug_prefix_map]) AC_SUBST([as_has_debug_prefix_map]) +AC_SUBST([as_is_cc]) AC_SUBST([with_debugger]) # TODO: rename this variable AC_SUBST([build_ocamldebug]) AC_SUBST([build_ocamltex]) @@ -253,18 +283,29 @@ 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([TARGET_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]) +AC_SUBST([zinc_runtime_id]) +AC_SUBST([bytecode_runtime_id]) +AC_SUBST([native_runtime_id]) +AC_SUBST([suffixing]) +AC_SUBST([runtime_search]) +AC_SUBST([runtime_search_target]) ## Generated files AC_CONFIG_FILES([Makefile.build_config]) AC_CONFIG_FILES([Makefile.config]) AC_CONFIG_FILES([stdlib/sys.ml]) -AC_CONFIG_FILES([manual/src/version.tex]) -AC_CONFIG_FILES([manual/src/html_processing/src/common.ml]) AC_CONFIG_FILES([ocamltest/ocamltest_config.ml]) AC_CONFIG_FILES([otherlibs/dynlink/dynlink_config.ml]) AC_CONFIG_FILES([utils/config.common.ml]) @@ -272,12 +313,16 @@ 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]) AC_CONFIG_FILES([otherlibs/runtime_events/META]) AC_CONFIG_FILES([stdlib/META]) AC_CONFIG_FILES([testsuite/tools/toolchain.ml]) +AS_IF([test -d manual], + [AC_CONFIG_FILES([manual/src/version.tex]) + AC_CONFIG_FILES([manual/src/html_processing/src/common.ml])]) # Definitions related to the version of OCaml AC_DEFINE([OCAML_VERSION_MAJOR], [OCAML__VERSION_MAJOR]) @@ -288,6 +333,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) @@ -298,12 +344,45 @@ AC_CANONICAL_BUILD AC_CANONICAL_HOST AC_CANONICAL_TARGET +# $cygwin_build_env=true if the build is taking place in any kind of Cygwin-like +# environment (which may include cross-compiling _from_ Cygwin) +# All patterns end with * (cf. build-aux/config.sub) +# +# In Cygwin itself, the mingw-w64 compilers are cross-compilers +# (host=x86_64-pc-cygwin; target=*-w64-mingw32) and $build when running from +# within Cygwin is always *-pc-cygwin. +# +# In MSYS2, the mingw-w64 compilers are normal host compilers, which MSYS2 makes +# available through different Environments (similar to the Microsoft Visual +# Studio Tools Command Prompts; see https://www.msys2.org/docs/environments/). +# It is possible to use MSYS2's "Cygwin" gcc (the equivalent of compiling native +# Cygwin), in which case $build is *-*-cygwin (some older MSYS2 installations +# may report the legacy *-*-msys*) +# The mingw-w64 Environments manually set $build to *-w64-mingw32, but the +# _native_ value inferred by config.guess (which uses uname -s) will be +# *-pc-mingw32 (for the 32-bit _target_ environments, even though MSYS2 is a +# 64-bit build environment) and *-pc-mingw64 (for the 64-bit _target_ +# environments). +# +# This leads to the four patterns below, all of which imply that the build is +# taking place on a system where Cygwin's utilities (cygpath, etc.) can be +# expected to be found and its semantics (CYGWIN=winsymlinks:native, etc.) be +# expected to apply. +# +# Note that although build=x86_64-pc-mingw64 will be accepted here, it is highly +# likely that that's a misconfigured environment, and the script will +# subsequently fail if host has not been altered to x86_64-w64-mingw32. +AS_CASE([$build], + [*-*-cygwin*|*-*-msys*|*-*-mingw32*|*-*-mingw64*], + [cygwin_build_env=true], + [cygwin_build_env=false]) + # Ensure that AC_CONFIG_LINKS will either create symlinks which are compatible # with native Windows (i.e. NTFS symlinks, not WSL or Cygwin-emulated ones) or # use its fallback mechanisms. Native Windows versions of ocamlc/ocamlopt cannot # interpret either WSL or Cygwin-emulated symlinks. -AS_CASE([$host], - [*-pc-windows|*-w64-mingw32*], +AS_CASE([$cygwin_build_env,$host], + [true,*-pc-windows|true,*-w64-mingw32*], [AC_CONFIG_COMMANDS([native-symlinks], [], [export CYGWIN="\$CYGWIN\${CYGWIN:+ }winsymlinks:nativestrict" export MSYS="\$MSYS\${MSYS:+ }winsymlinks:nativestrict"])]) @@ -341,6 +420,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]) @@ -511,16 +601,27 @@ AC_ARG_ENABLE([cmm-invariants], AC_ARG_WITH([target-bindir], [AS_HELP_STRING([--with-target-bindir], [location of the runtime binaries on the target system])], - [target_bindir=$withval], - [target_bindir='']) + [TARGET_BINDIR=$withval], + [TARGET_BINDIR='']) AC_ARG_WITH([target-sh], [AS_HELP_STRING([--with-target-sh], [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], + [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], @@ -566,6 +667,52 @@ 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_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=auto]) + +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,,],[], + [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]))]) + AC_ARG_WITH([afl], [AS_HELP_STRING([--with-afl], [use the AFL fuzzer])]) @@ -713,8 +860,8 @@ AS_CASE([$ocaml_cc_vendor], # See https://lists.gnu.org/archive/html/autoconf/2019-07/msg00002.html # for the detailed explanation. -ocamlsrcdir=$(unset CDPATH; cd -- "$srcdir" && printf %sX "$PWD") || fail -ocamlsrcdir=${ocamlsrcdir%X} +srcdir_abs=$(unset CDPATH; cd -- "$srcdir" && printf %sX "$PWD") || fail +srcdir_abs=${srcdir_abs%X} # Whether ar supports @FILE arguments @@ -724,16 +871,17 @@ AS_CASE([$lt_cv_ar_at_file], # Libraries to build depending on the host -AS_CASE([$host], - [*-*-mingw32*|*-pc-windows], +AS_CASE([$cygwin_build_env,$host], + [true,*-*-mingw32*|true,*-pc-windows], [unix_or_win32="win32" - ln='cp -pf' + OCAML_CHECK_LN_ON_WINDOWS ocamltest_libunix="Some false" - ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$ocamlsrcdir")" + ocamlsrcdir="$(LC_ALL=C.UTF-8 cygpath -w -- "$srcdir_abs")" ocamlyacc_wstr_module="yacc/wstr"], [unix_or_win32="unix" ln='ln -sf' ocamltest_libunix="Some true" + ocamlsrcdir="$srcdir_abs" ocamlyacc_wstr_module=""]) AS_CASE([$host], @@ -789,39 +937,14 @@ AS_IF([test "x$interpval" = "xyes"], # not use shebang scripts shebangscripts=true launch_method='sh' - AS_IF([test x"$target_launch_method" = 'x'], - [target_launch_method='exe'])], + AS_IF([test x"$launch_method_target" = 'x'], + [launch_method_target='exe'])], [*-*-mingw32*|*-pc-windows], [], [shebangscripts=true launch_method='sh'] )] ) -# stdlib/runtime.info and stdlib/target_runtime.info are generated by commands -# in config.status, rather than by the .in mechanism, since the latter cannot -# reliably process binary files. -AC_CONFIG_COMMANDS([shebang], - [printf '%s\n%s\000\n' "$launch_method" "$ocaml_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")' - ocaml_bindir='$(echo "$ocaml_bindir" | sed -e "s/'/'\"'\"'/g")' - target_bindir='$(echo "$target_bindir" | sed -e "s/'/'\"'\"'/g")']) - -# Are we building a cross-compiler - -AS_IF( - [test x"$host" = x"$target"], - [cross_compiler=false], - [cross_compiler=true]) - # Checks for programs ## Check for the C compiler: done by libtool @@ -1035,12 +1158,13 @@ AS_IF([test x"$supports_shared_libraries" != 'xfalse'], [ # ensure it can be executed from a native Windows process. The check # is only necessary when cross-compiling. AS_IF([test x"$build" != x"$host"],[ - AS_CASE([$build], - [*-pc-msys|*-pc-cygwin*], - [flexlink_where="$(cmd /c "$flexlink" -where 2>/dev/null)" - AS_IF([test -z "$flexlink_where"], - [AC_MSG_ERROR(m4_normalize([$flexlink is not executable from a - native Win32 process]))])]) + AS_IF([$cygwin_build_env], + [flexlink_where="$(cmd /c "$flexlink" -where 2>/dev/null)" + AS_IF([test -z "$flexlink_where"], + [AC_MSG_ERROR(m4_normalize([$flexlink is not executable from a + native Win32 process])) + ]) + ]) ]) ]) @@ -1172,6 +1296,7 @@ AS_CASE([$host], [AC_CHECK_HEADERS([unistd.h],[AC_DEFINE([HAS_UNISTD], [1])])]) AC_CHECK_HEADER([math.h]) +AC_CHECK_HEADER([libgen.h],[AC_DEFINE([HAS_LIBGEN_H], [1])]) AC_CHECK_HEADER([pthread_np.h],[AC_DEFINE([HAS_PTHREAD_NP_H], [1])]) AC_CHECK_HEADER([dirent.h], [AC_DEFINE([HAS_DIRENT], [1])], [], [#include ]) @@ -1637,24 +1762,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]) @@ -1714,6 +1848,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" @@ -2204,7 +2341,6 @@ AC_CHECK_FUNC([pwrite], [AC_DEFINE([HAS_PWRITE], [1])]) ## -fdebug-prefix-map support by the C compiler AS_CASE([$ocaml_cc_vendor,$host], - [*,*-*-mingw32*], [cc_has_debug_prefix_map=false], [*,*-pc-windows], [cc_has_debug_prefix_map=false], [xlc*,powerpc-ibm-aix*], [cc_has_debug_prefix_map=false], [sunc*,sparc-sun-*], [cc_has_debug_prefix_map=false], @@ -2212,6 +2348,20 @@ AS_CASE([$ocaml_cc_vendor,$host], [cc_has_debug_prefix_map=true], [cc_has_debug_prefix_map=false], [$warn_error_flag])]) +## -ffile-prefix-map support by the C compiler - used in the build, not by +## the compiler +AS_IF([test x"$bindir_to_libdir" != 'x'], + [srcdir_abs_real="$(realpath "$srcdir_abs" 2>/dev/null)" + AS_IF([test x"$srcdir_abs_real" = "x$srcdir_abs"], + [srcdir_abs_real='']) + AX_CHECK_COMPILE_FLAG([-Wa,--debug-prefix-map=old=new], + [build_map_flags='-Wa,--debug-prefix-map='], [], [$warn_error_flag]) + AX_CHECK_COMPILE_FLAG([-ffile-prefix-map=old=new], + [build_map_flags="$build_map_flags -ffile-prefix-map="], + [AS_IF([$cc_has_debug_prefix_map], + [build_map_flags="$build_map_flags -fdebug-prefix-map="])], + [$warn_error_flag])]) + ## Does stat support nanosecond precision stat_has_ns_precision=false @@ -2656,10 +2806,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 @@ -2668,11 +2818,65 @@ AS_IF([test x"$prefix" = "xNONE"], [i686-w64-mingw32*], [prefix='C:/ocamlmgw'], [x86_64-w64-mingw32*], [prefix='C:/ocamlmgw64'], [i686-pc-windows], [prefix='C:/ocamlms'], - [x86_64-pc-windows], [prefix='C:/ocamlms64'])], - [AS_IF([test x"$unix_or_win32" = "xwin32" \ - && test "$host_vendor-$host_os" != "$build_vendor-$build_os" ], - [AS_CASE([$build], - [*-pc-cygwin], [prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")"])])]) + [x86_64-pc-windows], [prefix='C:/ocamlms64'])]) + +# Normalise $prefix, if necessary, on Windows. There are 7 variables to be +# considered: +# - $prefix and $exec_prefix. These are autoconf variables containing the values +# specified for --prefix and --exec-prefix respectively, or NONE if these +# flags were not given on the command line. These two variables are written to +# Makefile.config and are in _build_ format. On native Windows, the values +# must be suitable to pass to _both_ native Windows processes and Cygwin/MSYS2 +# commands. The values are ultimately converted to Windows paths using slashes +# (i.e. C:/foo) +# - $bindir and $libdir. These similarly contain the values specified for +# --bindir and --libdir, or defaults relative to $exec_prefix otherwise. +# Unlike --prefix, values specified to configure for --bindir and --libdir are +# assumed to be suitable for both native Windows processes and Cygwin/MSYS2. +# These two variables ultimately end up in Makefile.config as $(BINDIR) and +# $(LIBDIR) and are used for installation commands only. +# - $ocaml_prefix, $ocaml_bindir and $ocaml_libdir are the _expanded_ values of +# $prefix, $bindir and $libdir so that the values can be inserted into OCaml +# strings (typically in utils/config.generated.ml). On Windows, these preserve +# the backslashes present in the value passed to --prefix. +ocaml_bindir="$bindir" +ocaml_libdir="$libdir" +ocaml_prefix="$prefix" +AS_CASE([$cygwin_build_env,$host], + [true,*-w64-mingw32*|true,*-pc-windows], + [prefix="$(LC_ALL=C.UTF-8 cygpath -m "$prefix")" + AS_IF([test "x${ocaml_prefix%%/*}" != "x$ocaml_prefix"], + # $prefix contained a slash - normalise it with cygpath. The rationale for + # this allows both `./configure --prefix $PWD/install` (which will be a + # Cygwin path) and also systems lazily using slashes instead of + # backslashes (i.e. C:/Backslashes/Scare/Us) to work. + [ocaml_prefix="$prefix"], + # $prefix was using backslashes - preserve these in the build, but + # continue to use slashes for the Makefile variables. + [AS_IF([test "x$bindir" = 'x${exec_prefix}/bin'], + [ocaml_bindir='${exec_prefix}\bin'], + [ocaml_bindir="$bindir"]) + AS_IF([test "x$libdir" = 'x${exec_prefix}/lib/ocaml'], + [ocaml_libdir='${exec_prefix}\lib\ocaml'], + [ocaml_libdir="$libdir"])])]) + +AS_IF([test x"$libdir_given" = 'xno'], + [AS_IF([test x"$bindir_to_libdir" != 'x'], + [ocaml_libdir="$bindir_to_libdir" + target_libdir_is_relative=true + AS_CASE([$cygwin_build_env,$host], + [true,*-w64-mingw32*|true,*-pc-windows], + [build_bindir_to_libdir="$(LC_ALL=C.UTF-8 cygpath \ + "$bindir_to_libdir")"], + [build_bindir_to_libdir="$bindir_to_libdir"]) + AS_CASE([$build_bindir_to_libdir], + [./*],[libdir="$bindir${build_bindir_to_libdir[#].}"], + [../*],[libdir="$bindir/$build_bindir_to_libdir"], + [AC_MSG_ERROR(m4_normalize([--with-relative-libdir requires an explicit + relative path]))])])], + [AS_IF([test x"$bindir_to_libdir" != 'x'], + [AC_MSG_ERROR(m4_normalize([--with-relative-libdir and --libdir cannot both + be specified]))])]) # Define a few macros that were defined in config/m-nt.h # but whose value is not guessed properly by configure @@ -2691,6 +2895,79 @@ AS_CASE([$host], # as "Infinity" and "Inf" instead of the expected "inf" [AC_DEFINE([HAS_BROKEN_PRINTF], [1])]) +# Determine the three Runtime IDs (see runtime/Mangling.md) +m4_define([ALPHABET], [0123456789abcdefghijklmnopqrstuv]) +AC_DEFUN([BASE32], ["$(echo 'ALPHABET' | cut -c $(expr \( $1 \) + 1))"]) + +m4_cond(OCAML__DEVELOPMENT_VERSION, [true], + [m4_define([ID_VERSION], m4_eval((OCAML__RELEASE_NUMBER << 1) + 1))], + [m4_define([ID_VERSION], m4_eval((OCAML__RELEASE_NUMBER << 1)))]) + +# Bits 0-4 (dev + low 4 bits of release) +m4_define([QUINTET0], + [m4_substr(ALPHABET, m4_eval(ID_VERSION & 31), [1])]) + +# Bits 5-6 (high 2 bits of release) +m4_define([QUINTET1_ZINC], + [m4_substr(ALPHABET, m4_eval(ID_VERSION >> 5), [1])]) + +# Bits 7-9 (low 3 bits of reserved) +quintet1="QUINTET1_ZINC + $(expr \( $reserved_header_bits \* 4 \) % 32)" +quintet1=BASE32([$quintet1]) + +# Bits 10-11 (high 2 bits of reserved) +quintet2_byte=BASE32([$reserved_header_bits / 8]) + +# Bit 12 (no-flat-float-array) +AS_IF([$flat_float_array], + [quintet2_zinc='0'], + [quintet2_byte="4 + $quintet2_byte" + quintet2_zinc='4']) +# Bit 13 (fp) +AS_IF([$frame_pointers], + [quintet2_native="8 + $quintet2_byte"], + [quintet2_native="$quintet2_byte"]) +# Bit 14 (tsan) +AS_IF([$tsan], + [quintet2_native="16 + $quintet2_native"]) + +quintet2_zinc=BASE32([$quintet2_zinc]) +quintet2_byte=BASE32([$quintet2_byte]) +quintet2_native=BASE32([$quintet2_native]) + +# Bit 15 (int31) +AS_IF([$arch64], + [quintet3_zinc='0'], + [quintet3_zinc='1']) +# Bit 16 (static) +AS_IF([! $supports_shared_libraries], + [quintet3_zinc="2 + $quintet3_zinc"]) +# Bit 17 (no-compression) +AS_IF([test x"$zstd_status" != 'xok'], + [quintet3_zinc="4 + $quintet3_zinc"]) +# Bit 18 (ansi) +AS_CASE([$target,$windows_unicode], + [*-*-mingw32,0|*-pc-windows,0], + [quintet3="8 + $quintet3_zinc"], + [quintet3="$quintet3_zinc"]) +# Bit 19 (mutable-string) cannot be set since OCaml 5.0 + +quintet3_zinc=BASE32([$quintet3_zinc]) +quintet3=BASE32([$quintet3]) + +zinc_runtime_id="QUINTET0[]QUINTET1_ZINC${quintet2_zinc}${quintet3_zinc}" +bytecode_runtime_id="QUINTET0${quintet1}${quintet2_byte}${quintet3}" +native_runtime_id="QUINTET0${quintet1}${quintet2_native}${quintet3}" + +# Update the values for is_official_release and release_number in +# utils/config.common.ml.in (this is done when tools/autogen is run, not each +# time configure is run!) +m4_syscmd([sed -e '/^let is_official_release =/s/=.*/= ]'\ +'m4_if(OCAML__DEVELOPMENT_VERSION,true,false,true)[/' \ + -e '/^let release_number =/s/=.*/= ]OCAML__RELEASE_NUMBER[/' \ + utils/config.common.ml.in > utils/config.common.ml.in.new +mv -f utils/config.common.ml.in.new utils/config.common.ml.in]) + # Do not permanently cache the result of flexdll.h unset ac_cv_header_flexdll_h @@ -2699,17 +2976,52 @@ 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"]) - AS_IF([test x"$target_launch_method" = 'x'], - [target_launch_method="$launch_method"]) + # 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) + eval "HOST_BINDIR=\"$ocaml_bindir\"" + AS_IF([test "x$bindir_to_libdir" = 'x'], + [ocaml_bindir="$HOST_BINDIR"], + [ocaml_bindir='']) + AS_IF([test x"$TARGET_BINDIR" = 'x'], + [AS_IF([test "x$bindir_to_libdir" = 'x'], + [TARGET_BINDIR="$HOST_BINDIR"], + [TARGET_BINDIR='.'])]) + eval "ocaml_libdir=\"$ocaml_libdir\"" + AS_IF([test "x$bindir_to_libdir" = 'x'], + [TARGET_LIBDIR="$ocaml_libdir"], + [TARGET_LIBDIR="$bindir_to_libdir"]) + AS_IF([test x"$launch_method_target" = 'x'], + [launch_method_target="$launch_method"]) 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/driver/compenv.ml b/driver/compenv.ml index d61d2b19fd0f..73f24bd605eb 100644 --- a/driver/compenv.ml +++ b/driver/compenv.ml @@ -594,7 +594,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 @@ -627,8 +627,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 @@ -641,7 +641,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 := (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 a5958554e76e..7260eb8affb5 100644 --- a/driver/compenv.mli +++ b/driver/compenv.mli @@ -52,7 +52,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 ea07d15cf19f..7543eb874bea 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" @@ -160,6 +165,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" @@ -517,6 +526,25 @@ let mk_unsafe_string = in "-unsafe-string", Arg.Unit err, " (option not available)" +let mk_launch_method f = + "-launch-method", Arg.String f, + " Specify the mechanism for the bytecode launcher:\n\ + \ exe - use the executable launcher in runtime-launch-info\n\ + \ sh - use a #!, using sh if the interpreter path cannot be used\n\ + \ /path/interpreter - use #!, or the given sh-compatible \n\ + \ interpreter if the interpreter path cannot be used" + +let mk_search_method f = + "-runtime-search", Arg.Symbol (["disable"; "fallback"; "enable"], f), + Printf.sprintf + " Control the way the bytecode header searches for the interpreter\n\ + \ The following settings are supported:\n\ + \ disable use a fixed absolute path to the interpreter\n\ + \ fallback search for interpreter only if not found at the absolute \ + path\n\ + \ enable always search for the interpreter\n\ + \ The default setting is 'disable'." + let mk_use_runtime f = "-use-runtime", Arg.String f, " Generate bytecode for the given runtime system" @@ -872,6 +900,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 @@ -912,10 +941,13 @@ module type Bytecomp_options = sig val _custom : unit -> unit val _no_check_prims : unit -> unit val _dllib : string -> unit + val _dllib_suffixed : string -> unit val _dllpath : string -> unit val _make_runtime : unit -> unit val _vmthread : unit -> unit val _use_runtime : string -> unit + val _launch_method : string -> unit + val _search_method : string -> unit val _output_complete_exe : unit -> unit val _dinstr : unit -> unit @@ -1046,6 +1078,7 @@ struct mk_config_var F._config_var; mk_custom F._custom; mk_dllib F._dllib; + mk_dllib_suffixed F._dllib_suffixed; mk_dllpath F._dllpath; mk_dtypes F._annot; mk_for_pack_byt F._for_pack; @@ -1099,6 +1132,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; @@ -1111,6 +1145,8 @@ struct mk_unsafe_string; mk_use_runtime F._use_runtime; mk_use_runtime_2 F._use_runtime; + mk_launch_method F._launch_method; + mk_search_method F._search_method; mk_v F._v; mk_verbose F._verbose; mk_version F._version; @@ -1314,6 +1350,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; @@ -1756,6 +1793,19 @@ module Default = struct let _verbose = set verbose end + let fatalf fmt = Printf.ksprintf Compenv.fatal fmt + + let parse_runtime_parameter opt = + let k, setting = + try Misc.cut_at opt '=' + with Not_found -> + fatalf "-set-runtime-default: invalid runtime parameter '%s'. \ + Expected =." opt in + if k = "standard_library_default" then + Clflags.standard_library_default := Some setting + else + fatalf "-set-runtime-default: unrecognized runtime parameter %s." k + module Compiler = struct let _a = set make_archive let _annot = set annotations @@ -1794,6 +1844,7 @@ module Default = struct let _plugin _p = plugin := true let _pp s = preprocessor := (Some s) let _runtime_variant s = runtime_variant := s + let _set_runtime_default s = parse_runtime_parameter s let _stop_after pass = let module P = Compiler_pass in match P.of_string pass with @@ -1923,7 +1974,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 @@ -1937,6 +1990,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 554b1fa36bad..14a69c6f45cc 100644 --- a/driver/main_args.mli +++ b/driver/main_args.mli @@ -113,6 +113,7 @@ module type Compiler_options = sig val _runtime_variant : string -> unit val _with_runtime : unit -> unit val _without_runtime : unit -> unit + val _set_runtime_default : string -> unit val _short_paths : unit -> unit val _thread : unit -> unit val _v : unit -> unit @@ -154,10 +155,13 @@ module type Bytecomp_options = sig val _custom : unit -> unit val _no_check_prims : unit -> unit val _dllib : string -> unit + val _dllib_suffixed : string -> unit val _dllpath : string -> unit val _make_runtime : unit -> unit val _vmthread : unit -> unit val _use_runtime : string -> unit + val _launch_method : string -> unit + val _search_method : string -> unit val _output_complete_exe : unit -> unit val _dinstr : unit -> unit diff --git a/lambda/lambda.ml b/lambda/lambda.ml index 9fdbc8146e9f..20074a1828f3 100644 --- a/lambda/lambda.ml +++ b/lambda/lambda.ml @@ -25,6 +25,7 @@ type compile_time_constant = | Ostype_win32 | Ostype_cygwin | Backend_type + | Standard_library_default type immediate_or_pointer = | Immediate diff --git a/lambda/lambda.mli b/lambda/lambda.mli index 6f5d9717914b..eb72cf09eaff 100644 --- a/lambda/lambda.mli +++ b/lambda/lambda.mli @@ -26,6 +26,7 @@ type compile_time_constant = | Ostype_win32 | Ostype_cygwin | Backend_type + | Standard_library_default type immediate_or_pointer = | Immediate diff --git a/lambda/printlambda.ml b/lambda/printlambda.ml index 76989a4035f7..c5153580e1ce 100644 --- a/lambda/printlambda.ml +++ b/lambda/printlambda.ml @@ -266,7 +266,8 @@ let primitive ppf = function | Ostype_unix -> "ostype_unix" | Ostype_win32 -> "ostype_win32" | Ostype_cygwin -> "ostype_cygwin" - | Backend_type -> "backend_type" in + | Backend_type -> "backend_type" + | Standard_library_default -> "standard_library_default" in fprintf ppf "sys.constant_%s" const_name | Pisint -> fprintf ppf "isint" | Pisout -> fprintf ppf "isout" diff --git a/lambda/translprim.ml b/lambda/translprim.ml index df46ea3a44af..ca50c898a0a8 100644 --- a/lambda/translprim.ml +++ b/lambda/translprim.ml @@ -148,6 +148,8 @@ let primitives_table = "%ostype_unix", Primitive ((Pctconst Ostype_unix), 1); "%ostype_win32", Primitive ((Pctconst Ostype_win32), 1); "%ostype_cygwin", Primitive ((Pctconst Ostype_cygwin), 1); + "%standard_library_default", + Primitive ((Pctconst Standard_library_default), 1); "%frame_pointers", Frame_pointers; "%negint", Primitive (Pnegint, 1); "%succint", Primitive ((Poffsetint 1), 1); diff --git a/man/Makefile b/man/Makefile index 10cc8bbe417b..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) '$(INSTALL_PROGRAMS_MAN_DIR)' - $(INSTALL_DATA) $(MANPAGES) '$(INSTALL_PROGRAMS_MAN_DIR)' + $(call INSTALL_ITEMS, $(MANPAGES), man, $(INSTALL_MANDIR_PROGRAMS)) diff --git a/man/ocamlc.1 b/man/ocamlc.1 index f503e948c79b..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 @@ -674,6 +720,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/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/manual/src/cmds/unified-options.etex b/manual/src/cmds/unified-options.etex index 96cc29ce3323..08d2fd3a70f7 100644 --- a/manual/src/cmds/unified-options.etex +++ b/manual/src/cmds/unified-options.etex @@ -229,6 +229,14 @@ Arrange for the C shared library "dll"\var{libname}".so" by the run-time system "ocamlrun" at program start-up time. }%comp +\comp{ +\item["-dllib-suffixed" "-l"\var{libname}] +As for "-dllib" but the name recorded is mangled with the Runtime ID and host of +the interpreter before being loaded by the run-time system "ocamlrun" at program +start-up time. Ths is used for C stub libraries, for example by DLLs produced +with "ocamlmklib -suffixed". +}%comp + \comp{\item["-dllpath" \var{dir}] Adds the directory \var{dir} to the run-time search path for shared C libraries. At link-time, shared libraries are searched in the @@ -381,6 +389,23 @@ source code. Labels are not ignored in types, labels may be used in applications, and labelled parameters can be given in any order. This is the default. +\comp{% +\item["-launch-method" \var{method}] +Specifies the mechanism used by normal bytecode executables to find the +interpreter. +The following methods are supported: +\begin{description} + \item["exe"] A small executable stub launcher contained in the + runtime-launch-info file is prepended to the bytecode image. + \item["sh"] A "#!" header is used. If the full path to the required + interpreter is not suitable for a "#!" line, a small shell script is + generated instead, using the "sh" interpreter found in "PATH". + \item["/path/interpreter"] As for "sh", but if the interpreter cannot be used + in "#!" line then "/path/intepreter" is used instead of searching for "sh" + in "PATH". +\end{description} +}%comp + \notop{% \item["-linkall"] Force all modules contained in libraries to be linked in. If this @@ -663,6 +688,23 @@ only recursive types where the recursion goes through an object type are supported. \notop{Note that once you have created an interface using this flag, you must use it again for all dependencies.} +\comp{% +\item["-runtime-search" \var{method}] +Controls whether the header used by normal bytecode executables is permitted to +search for the interpreter, or requires it to be at a fixed location. +The following methods are supported: +\begin{description} + \item["disable"] A fixed absolute path to the interpreter is used, and the + executable will not launch if the interpreter is not found at this location. + \item["fallback"] A fixed absolute path to the interpreter is used, but if the + executable cannot find the interpreter at this location then it will search + first in the directory containing the executable and then in "PATH". + \item["enable"] The executable searches for the interpreter first in the + directory containing the executable and then in "PATH". No absolute path to + the interpreter is recorded. +\end{description} +}%comp + \notop{% \item["-runtime-variant" \var{suffix}] Add the \var{suffix} string to the name of the runtime library used by @@ -706,6 +748,17 @@ using "compiler-libs" library (see ). }%nat +\notop{% +\item["-set-runtime-default" \var{name=value}] +When linking an executable, override the default value for a runtime setting. +The currently supported settings are: +\begin{description} + \item["standard_library_default"] Specifies the default location used by the + executable to locate the Standard Library. By default, this is the absolute + path to the Standard Library the compiler itself was configured with. +\end{description} +}%notop + \nat{% \item["-shared"] Build a plugin (usually ".cmxs") that can be dynamically loaded with diff --git a/middle_end/closure/closure.ml b/middle_end/closure/closure.ml index 880773fbac09..26e380767573 100644 --- a/middle_end/closure/closure.ml +++ b/middle_end/closure/closure.ml @@ -1051,22 +1051,29 @@ let rec close ({ backend; fenv; cenv ; mutable_vars } as env) lam = None ubody), approx) (* Compile-time constants *) - | Lprim(Pctconst c, [arg], _loc) -> - let cst, approx = - match c with - | Big_endian -> make_const_bool B.big_endian - | Word_size -> make_const_int (8*B.size_int) - | Int_size -> make_const_int (8*B.size_int - 1) - | Max_wosize -> make_const_int ((1 lsl ((8*B.size_int) - 10)) - 1 ) - | Ostype_unix -> make_const_bool (Sys.os_type = "Unix") - | Ostype_win32 -> make_const_bool (Sys.os_type = "Win32") - | Ostype_cygwin -> make_const_bool (Sys.os_type = "Cygwin") - | Backend_type -> - make_const_int 0 (* tag 0 is the same as Native here *) + | Lprim(Pctconst c, [arg], loc) -> + let cst f v = + let cst, approx = f v in + let arg, _approx = close env arg in + let id = Ident.create_local "dummy" in + Ulet(Immutable, Pgenval, VP.create id, arg, cst), approx in - let arg, _approx = close env arg in - let id = Ident.create_local "dummy" in - Ulet(Immutable, Pgenval, VP.create id, arg, cst), approx + begin match c with + | Big_endian -> cst make_const_bool B.big_endian + | Word_size -> cst make_const_int (8*B.size_int) + | Int_size -> cst make_const_int (8*B.size_int - 1) + | Max_wosize -> cst make_const_int ((1 lsl ((8*B.size_int) - 10)) - 1) + | Ostype_unix -> cst make_const_bool (Sys.os_type = "Unix") + | Ostype_win32 -> cst make_const_bool (Sys.os_type = "Win32") + | Ostype_cygwin -> cst make_const_bool (Sys.os_type = "Cygwin") + | Backend_type -> + cst make_const_int 0 (* tag 0 is the same as Native here *) + | Standard_library_default -> + Compilenv.need_stdlib_location (); + let dbg = Debuginfo.from_location loc in + let id = Ident.name Compilenv.stdlib_symbol_name in + Uprim(P.Pread_symbol id, [], dbg), Value_const (Uconst_ref (id, None)) + end | Lprim(Pignore, [arg], _loc) -> let expr, approx = make_const_int 0 in Usequence(fst (close env arg), expr), approx @@ -1458,7 +1465,9 @@ let collect_exported_structured_constants a = | Uconst_ref (s, (Some c)) -> Compilenv.add_exported_constant s; structured_constant c - | Uconst_ref (_s, None) -> assert false (* Cannot be generated *) + | Uconst_ref (s, None) -> + (* Only generated in one context *) + assert (s = Ident.name Compilenv.stdlib_symbol_name) | Uconst_int _ -> () and structured_constant = function | Uconst_block (_, ul) -> List.iter const ul diff --git a/middle_end/compilenv.ml b/middle_end/compilenv.ml index d975c1781ce6..b7287aa67e96 100644 --- a/middle_end/compilenv.ml +++ b/middle_end/compilenv.ml @@ -89,6 +89,7 @@ let current_unit = ui_force_link = false; ui_export_info = default_ui_export_info; ui_for_pack = None } +let current_ui_need_stdlib = ref false let symbol_separator = match Config.ccomp_type with @@ -128,6 +129,7 @@ let reset ?packname name = current_unit.ui_send_fun <- []; current_unit.ui_force_link <- !Clflags.link_everything; current_unit.ui_for_pack <- packname; + current_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,11 +355,42 @@ 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_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 @@ -361,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 2bcbca98b830..36d4adc3a0ef 100644 --- a/middle_end/compilenv.mli +++ b/middle_end/compilenv.mli @@ -104,6 +104,17 @@ 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 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 *) + val new_const_symbol : unit -> string val closure_symbol : Closure_id.t -> Symbol.t (* Symbol of a function if the function is @@ -135,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/middle_end/flambda/closure_conversion.ml b/middle_end/flambda/closure_conversion.ml index 6b9e8d967019..4c68b6c11e35 100644 --- a/middle_end/flambda/closure_conversion.ml +++ b/middle_end/flambda/closure_conversion.ml @@ -390,23 +390,30 @@ let rec close t env (lam : Lambda.lambda) : Flambda.t = ~name:Names.raise) | Lprim (Pctconst c, [arg], _loc) -> let module Backend = (val t.backend) in - let const = - begin match c with - | Big_endian -> lambda_const_bool Backend.big_endian - | Word_size -> lambda_const_int (8*Backend.size_int) - | Int_size -> lambda_const_int (8*Backend.size_int - 1) - | Max_wosize -> - lambda_const_int ((1 lsl ((8*Backend.size_int) - 10)) - 1) - | Ostype_unix -> lambda_const_bool (String.equal Sys.os_type "Unix") - | Ostype_win32 -> lambda_const_bool (String.equal Sys.os_type "Win32") - | Ostype_cygwin -> lambda_const_bool (String.equal Sys.os_type "Cygwin") - | Backend_type -> - Lambda.const_int 0 (* tag 0 is the same as Native *) - end - in - close t env - (Lambda.Llet(Strict, Pgenval, Ident.create_local "dummy", + let cst f v = + let const = f v in + close t env (Lambda.Llet(Strict, Pgenval, Ident.create_local "dummy", arg, Lconst const)) + in + begin match c with + | Big_endian -> cst lambda_const_bool Backend.big_endian + | Word_size -> cst lambda_const_int (8*Backend.size_int) + | Int_size -> cst lambda_const_int (8*Backend.size_int - 1) + | Max_wosize -> + cst lambda_const_int ((1 lsl ((8*Backend.size_int) - 10)) - 1) + | Ostype_unix -> + cst lambda_const_bool (String.equal Sys.os_type "Unix") + | Ostype_win32 -> + cst lambda_const_bool (String.equal Sys.os_type "Win32") + | Ostype_cygwin -> + cst lambda_const_bool (String.equal Sys.os_type "Cygwin") + | Backend_type -> cst Lambda.const_int 0 (* tag 0 is the same as Native *) + | Standard_library_default -> + Compilenv.need_stdlib_location (); + let symbol = t.symbol_for_global' Compilenv.stdlib_symbol_name in + t.imported_symbols <- Symbol.Set.add symbol t.imported_symbols; + name_expr (Symbol symbol) ~name:Names.pgetglobal + end | Lprim (Pfield _, [Lprim (Pgetglobal id, [],_)], _) when Ident.same id t.current_unit_id -> Misc.fatal_errorf "[Pfield (Pgetglobal ...)] for the current compilation \ diff --git a/ocaml-variants.opam b/ocaml-variants.opam index 6787e2bc41bf..e1c3079357c5 100644 --- a/ocaml-variants.opam +++ b/ocaml-variants.opam @@ -1,7 +1,7 @@ opam-version: "2.0" -version: "5.3.1+trunk" +version: "5.3.1+relocatable" license: "LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception" -synopsis: "OCaml 5.3 development version" +synopsis: "Relocatable OCaml 5.3 branch" maintainer: "caml-list@inria.fr" authors: [ "Xavier Leroy" @@ -9,6 +9,7 @@ authors: [ "Alain Frisch" "Jacques Garrigue" "Didier Rémy" + "KC Sivaramakrishnan" "Jérôme Vouillon" ] homepage: "https://github.com/ocaml/ocaml/" @@ -38,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}) @@ -75,8 +76,12 @@ build: [ "--host=i686-w64-mingw32" {os-distribution = "cygwin" & system-mingw:installed & arch-x86_32:installed} "--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} + "--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} @@ -102,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/ocamltest/ocaml_actions.ml b/ocamltest/ocaml_actions.ml index 4c261d93144f..30ef16c15d5a 100644 --- a/ocamltest/ocaml_actions.ml +++ b/ocamltest/ocaml_actions.ml @@ -629,7 +629,9 @@ let mklib log env = Ocaml_commands.ocamlrun_ocamlmklib; "-ocamlc '" ^ ocamlc_command ^ "'"; "-o " ^ program - ] @ modules env in + ] @ (if Ocamltest_config.suffixing then ["-suffixed"] else []) + @ modules env + in let expected_exit_status = 0 in let exit_status = Actions_helpers.run_cmd diff --git a/ocamltest/ocaml_tests.ml b/ocamltest/ocaml_tests.ml index baf643ec33c9..75930567159d 100644 --- a/ocamltest/ocaml_tests.ml +++ b/ocamltest/ocaml_tests.ml @@ -46,7 +46,16 @@ let bytecode = check_program_output; ] @ (if not Sys.win32 && Ocamltest_config.native_compiler then - opt_build @ [compare_bytecode_programs] + (* If the compiler is configured using --with-relative-libdir then at + present we can't compare the bytecode programs because ocamlc.opt and + ocamlrun are at different levels in the build tree, but they're both + configured with the same relative directory path. + This problem will disappear when ocamltest runs the testsuite against a + compiler in an install-tree like way. *) + if Ocamltest_config.has_relative_libdir then + opt_build + else + opt_build @ [compare_bytecode_programs] else [] ) diff --git a/ocamltest/ocamltest_config.ml.in b/ocamltest/ocamltest_config.ml.in index 2c53e45ce39f..03a3ca7b8052 100644 --- a/ocamltest/ocamltest_config.ml.in +++ b/ocamltest/ocamltest_config.ml.in @@ -95,3 +95,7 @@ let instrumented_runtime = @instrumented_runtime@ 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 d75ae87fbe39..ec5f97abf468 100644 --- a/ocamltest/ocamltest_config.mli +++ b/ocamltest/ocamltest_config.mli @@ -133,3 +133,10 @@ 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 *) + +val suffixing : bool +(** Whether C stub library filenames are being mangled with the Bytecode + Runtime ID and {!Config.target}. *) 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 f08dd13ef8cc..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 @@ -35,6 +36,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 +56,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 +71,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 @@ -95,49 +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:: - if test -f dll$(CLIBNAME_BYTECODE)$(EXT_DLL); then \ - $(INSTALL_PROG) \ - dll$(CLIBNAME_BYTECODE)$(EXT_DLL) "$(INSTALL_STUBLIBDIR)"; \ - fi ifneq "$(STUBSLIB_BYTECODE)" "" - $(INSTALL_DATA) $(STUBSLIB_BYTECODE) "$(INSTALL_LIBDIR)/" +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" + $(call INSTALL_ITEMS, $(STUBSDLL), stublibs) +endif + $(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/dynlink/byte/dynlink.ml b/otherlibs/dynlink/byte/dynlink.ml index 0f315d3ab0d0..a3c553e8dae5 100644 --- a/otherlibs/dynlink/byte/dynlink.ml +++ b/otherlibs/dynlink/byte/dynlink.ml @@ -194,7 +194,7 @@ module Bytecode = struct let toc_pos = input_binary_int ic in (* Go to table of contents *) seek_in ic toc_pos; let lib = (input_value ic : library) in - Symtable.open_dlls lib.lib_dllibs; + Symtable.open_dlls ic lib; handle, lib.lib_units end else begin raise (DT.Error (Not_a_bytecode_file file_name)) diff --git a/otherlibs/dynlink/byte/dynlink_symtable.ml b/otherlibs/dynlink/byte/dynlink_symtable.ml index e275a2856466..ec90f2a1c63d 100644 --- a/otherlibs/dynlink/byte/dynlink_symtable.ml +++ b/otherlibs/dynlink/byte/dynlink_symtable.ml @@ -89,14 +89,40 @@ 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 + +#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 = @@ -129,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 @@ -233,12 +259,12 @@ let patch_object buff patchlist = (* Functions for toplevel use *) (* Update the in-core table of globals *) -#237 "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" -#242 "otherlibs/dynlink/byte/dynlink_symtable.ml" +#268 "otherlibs/dynlink/byte/dynlink_symtable.ml" end #332 "bytecomp/symtable.ml" let update_global_table () = @@ -264,7 +290,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" +#294 "otherlibs/dynlink/byte/dynlink_symtable.ml" Dll.init ~dllpaths:sect.dlpt ~prims:sect.prim; #358 "bytecomp/symtable.ml" sect.crcs @@ -317,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 -#321 "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 686cb4c44d41..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 : 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/otherlibs/dynlink/dynlink_config.ml.in b/otherlibs/dynlink/dynlink_config.ml.in index 60132fe50356..e51fa45bc383 100644 --- a/otherlibs/dynlink/dynlink_config.ml.in +++ b/otherlibs/dynlink/dynlink_config.ml.in @@ -24,3 +24,7 @@ let ext_dll = "." ^ {@QS@|@SO@|@QS@} and cmo_magic_number = {@QS@|@CMO_MAGIC_NUMBER@|@QS@} and cma_magic_number = {@QS@|@CMA_MAGIC_NUMBER@|@QS@} and cmxs_magic_number = {@QS@|@CMXS_MAGIC_NUMBER@|@QS@} + +let bytecode_runtime_id = {@QS@|@bytecode_runtime_id@|@QS@} + +let target = {@QS@|@target@|@QS@} diff --git a/otherlibs/dynlink/dynlink_config.mli b/otherlibs/dynlink/dynlink_config.mli index ad44848ebc20..30dbdf4df6bf 100644 --- a/otherlibs/dynlink/dynlink_config.mli +++ b/otherlibs/dynlink/dynlink_config.mli @@ -21,3 +21,7 @@ val ext_dll: string val cmo_magic_number: string val cma_magic_number: string val cmxs_magic_number: string + +val bytecode_runtime_id: string + +val target : string diff --git a/otherlibs/systhreads/Makefile b/otherlibs/systhreads/Makefile index 64d41cc19d66..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 @@ -27,6 +28,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 @@ -97,30 +104,22 @@ clean: partialclean distclean: clean rm -f META -INSTALL_THREADSLIBDIR=$(INSTALL_LIBDIR)/$(LIBNAME) - install: - if test -f dllthreads$(EXT_DLL); then \ - $(INSTALL_PROG) dllthreads$(EXT_DLL) "$(INSTALL_STUBLIBDIR)"; \ - fi - $(INSTALL_DATA) libthreads.$(A) "$(INSTALL_LIBDIR)" - $(MKDIR) "$(INSTALL_THREADSLIBDIR)" - $(INSTALL_DATA) \ - $(CMIFILES) threads.cma META \ - "$(INSTALL_THREADSLIBDIR)" +ifeq "$(SUPPORTS_SHARED_LIBRARIES)" "true" + $(call INSTALL_ITEMS, $(DLLTHREADS), stublibs) +endif + $(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/release-info/howto.md b/release-info/howto.md index 3b67474ac676..79b75326e0a8 100644 --- a/release-info/howto.md +++ b/release-info/howto.md @@ -165,6 +165,10 @@ git branch $BRANCH # update build-aux/ocaml_version.m4 with the new future branch, # 4.07.0+dev1-2018-06-26 => 4.08.0+dev0-2018-06-30 +# Also increment OCAML__RELEASE_NUMBER in build-aux/ocaml_version.m4 +# If the major version number is changing, the logic in p_runtime_id in +# tools/objinfo.ml mapping release number to major/minor version will need +# altering. # Update ocaml-variants.opam with new version. make -B configure # Add a "Working version" section" to Changes diff --git a/runtime/Mangling.md b/runtime/Mangling.md new file mode 100644 index 000000000000..fd2f7c30abb0 --- /dev/null +++ b/runtime/Mangling.md @@ -0,0 +1,135 @@ +# Filename Mangling + +## Background + +OCaml compiler installations exist in isolation. When running the compiler, it +is assumed that the caller will have configured the environment of the compiler +such that files and settings related to other compiler installations will not +interfere. + +This is not true of the runtime. Shared libraries are loaded from a global +namespace (dynamically loaded bytecode stub libraries and the shared versions of +both the native and bytecode runtimes) and programs may be searched in a global +PATH. To allow programs compiled against different coinstalled versions of the +runtime to be executed, a name mangling scheme is used for the runtime's +executables and shared libraries. + +## Filename Mangling + +Filenames are mangled using one or both of two pieces of configuration +information. The first is the standard "autoconf" triplet on which the runtime +executes (e.g. `x86_64-pc-linux-gnu`). The other is a summary of the runtime +version and configuration called the Runtime ID. This information is a series of +bits encoded in base32 using the alphabet `[0-9a-v]` and with the quintets laid +out little-endian. + +Mangling is applied to the name of any file which will be searched for at +runtime: + +- `ocamlrun` (and variants) are triplet-prefixed and Bytecode-suffixed. For + example, `x86_64-pc-linux-gnu-ocamlrun-a140` is OCaml 5.5 configured with + `--disable-flat-float-array` on 64-bit Intel/AMD Linux. A symbolic link is + still created for `ocamlrun` pointing to this mangled name. Additionally, a + symbolic link is also created for `ocamlrun-a140`, using the Zinc-suffix. +- C stub libraries loaded by both the bytecode runtime and bytecode `Dynlink` + library are triplet- and Bytecode-suffixed. For example, + `dllunixbyt-x86_64-pc-linux-gnu-a140.so` contains the C stubs for the Unix + library for OCaml 5.5 configured with `--disable-flat-float-array` on 64-bit + Intel/AMD Linux. +- Shared versions of the bytecode and native runtimes (`libcamlrun_shared.so` + and `libasmrun_shared.so`) are triplet- and Bytecode/Native-suffixed + respectively. For example, `libasmrun-x86_64-pc-linux-gnu-a1k0.so` and + `libcamlrun-x86_64-pc-linux-gnu-a140.so` are OCaml 5.5 configured with + `--disable-flat-float-array` and `--enable-tsan` on 64-bit Intel/AMD Linux + (note the **tsan** bit not being set for the name of libcamlrun). + Additionally, symbolic links are also created for `libasmrun_shared.so` and + `libcamlrun_shared.so`. + +## Runtime ID + +A Runtime ID is a bit string describing a given OCaml runtime. At present, +20 bits are used, but the format is intended to be trivially extensible. +Ultimately, the only requirement is that each version and configuration +generates some kind of unique identifier which can then be used in filenames. + +- Bit 0 (**dev**): Development bit. This should be set for development versions + of OCaml or for customised compilers. If it is not set, the compiler should be + an unaltered official release. +- Bits 1-6 (**release**): OCaml release number. This is incremented for each + minor release of the compiler, with OCaml 3.12.0[^1] being release 0. At + present, the ordering of release numbers matches the semantic ordering of the + version numbers, but this is not guaranteed and should not be assumed[^2]. +- Bits 7-11 (**reserved**): Number of reserved bits in the OCaml value header. + This is the number passed to `--enable-reserved-header-bits` when the compiler + distribution was configured. +- Bit 12 (**no-flat-float-array**): Set if the compiler distribution was + configured with `--disable-flat-float-array`. +- Bit 13 (**fp**): Set if the compiler distribution was configured with + `--enable-frame-pointers`. Affects the **native** runtime only. +- Bit 14 (**tsan**): For OCaml 5.2 onwards, set if the compiler distribution was + configured with `--enable-tsan`. Prior to OCaml 5.2, set if the compiler + distribution was configured with `--enable-spacetime` (this option was removed + in OCaml 4.12, meaning this bit is always unset for OCaml 4.12-5.1). Affects + the **native** runtime only. +- Bit 15 (**int31**): Set if the runtime uses 31-bit `int` values (i.e. runtimes + running on 32-bit systems). +- Bit 16 (**static**): Set if the runtime does not support shared libraries, + meaning dynamic loading of C code is not supported in bytecode, and native + dynlink is not supported at all. +- Bit 17 (**no-compression**): For OCaml 5.1 onwards, set if the runtime does + not support compressed marshalling. Prior to OCaml 5.1, set if the compiler + distribution was configured with `--enable-naked-pointers` (this bit was + always unset for OCaml 5.0, since it supports neither naked pointers nor + compressed marshalling). +- Bit 18 (**ansi**): Set if the compiler distribution was configured with the + legacy support `WINDOWS_UNICODE=ansi`. +- Bit 19 (**mutable-string**): Set if the compiler distribution was configured + with `--disable-force-safe-string`. This option was removed in OCaml 5.0, and + the bit is available for re-use. When this bit is unset, strings are + guaranteed to be immutable. + +The bit descriptions are designed such that the default configuration of the +latest version of the compiler has unset bits. The ordering of the bits is +designed to mean ID values in the same version of OCaml will usually have the +same opening sequence of characters (since `--enable-reserved-header-bits` is +now rarely used) and laying out the characters little-endian in the mangling +scheme means that the opening two characters of the Runtime ID define its +version (and consequently its length, should that change in future). + +[^1]: OCaml 3.12.0 was the first version where `ocamlrun` supported the `-vnum` +argument; the original author had a fantasy of backporting the scheme to the +entire 4.x series, but following some therapy stopped at 4.08. The release +numbering persists to allow for future madness. +[^2]: In particular, should there be any additional releases in the OCaml 4.x +series, these will have higher release numbers than releases already made in the +OCaml 5.x series. + +## Masks + +A particular configuration of the compiler has one Runtime ID, but this is used +in three different contexts where certain bits are masked out: + +1. _Bytecode Mask_: masks out bits which are only ever set by the native runtime + (at present, **fp** and **tsan**). +2. _Native Mask_: masks out bits which are only ever set by the bytecode runtime + (at present there aren't any). +3. _Zinc Mask_: masks out bits which are not related to bytecode portability. + Where the _Bytecode_ and _Native_ masks relate to _runtimes_, the _Zinc_ mask + relates to _bytecode images_. The Zinc ID therefore includes: + - **release** and **dev** (a given bytecode image targets a specific version + of OCaml) + - **no-flat-float-array** (code compiled assuming that float arrays are boxed + will segfault on runtimes which unbox them) + - **int31**, **static**, and **no-compression** (a bytecode image using + 63-bit integers, dynamically loaded C stubs and compressed marshalling will + be rejected by an interpreter which doesn't support any of these features) + +Note that the inclusion of a bit in a mask is determined by whether that +property affects the ability to load and execute the code, rather than whether +it is semantically affected by it. For example, the **reserved** bits affects +the value representation, and therefore both runtimes. It does not directly +affect bytecode (although a bytecode program may use unsafe features to observe +it). **reserved** is therefore part of both the _Bytecode_ and _Native Masks_, +but not part of the _Zinc Mask_. Similarly, although **no-flat-float-array** +affects code generation for bytecode, **mutable-string** never did, and so would +not be included in the _Zinc Mask_. diff --git a/runtime/backtrace_byt.c b/runtime/backtrace_byt.c index e8cb4b3999dd..982dfe47eba1 100644 --- a/runtime/backtrace_byt.c +++ b/runtime/backtrace_byt.c @@ -451,7 +451,7 @@ 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. diff --git a/runtime/caml/dynlink.h b/runtime/caml/dynlink.h index d5f7170e6fcb..a7441fcc0e4f 100644 --- a/runtime/caml/dynlink.h +++ b/runtime/caml/dynlink.h @@ -41,11 +41,10 @@ extern void caml_build_primitive_table_builtin(void); /* Unload all the previously loaded shared libraries */ extern void caml_free_shared_libs(void); -/* Return the effective location of the standard library */ -extern char_os * caml_get_stdlib_location(void); - -/* Parse ld.conf and add the lines read to caml_shared_libs_path */ -extern char_os * caml_parse_ld_conf(void); +/* If found, parse $OCAMLLIB/ld.conf, $CAMLLIB/ld.conf and stdlib/ld.conf in + that order and add the lines read to table. */ +extern char_os * caml_parse_ld_conf(const char_os * stdlib, + struct ext_table * table); #endif /* CAML_INTERNALS */ diff --git a/runtime/caml/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/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 c06f8adb9aac..621b71dea830 100644 --- a/runtime/dynlink.c +++ b/runtime/dynlink.c @@ -43,12 +43,12 @@ #include "build_config.h" -#ifndef NATIVE_CODE - #ifndef O_BINARY #define O_BINARY 0 #endif +#ifndef NATIVE_CODE + /* The table of primitives */ struct ext_table caml_prim_table; @@ -78,23 +78,63 @@ static c_primitive lookup_primitive(char * name) return NULL; } +#endif /* NATIVE_CODE */ + /* Parse the ld.conf file and add the directories listed there to the search path */ #define LD_CONF_NAME T("ld.conf") -CAMLexport char_os * caml_get_stdlib_location(void) +#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 + beginning "./" or "../". Note that the function does not necessarily + canonicalise the path. */ +static char_os *make_relative_path_absolute(char_os *path, char_os *root) { - char_os * stdlib; - stdlib = caml_secure_getenv(T("OCAMLLIB")); - if (stdlib == NULL) stdlib = caml_secure_getenv(T("CAMLLIB")); - if (stdlib == NULL) stdlib = OCAML_STDLIB_DIR; - return stdlib; + if (path[0] == '.') { + if (path[1] == '\0') { + /* path is exactly "." => return root */ + return caml_stat_strdup_os(root); + } else if (Is_separator(path[1])) { + /* path is exactly "./" or begins "./". In both cases, replace the "." + with root */ + return caml_stat_strconcat_os(2, root, (path + 1)); + } else if (path[1] == '.' && (path[2] == '\0' || Is_separator(path[2]))) { + /* path is either exactly ".." or begins "../" => prefix it with root + (which has no trailing separator) */ + return caml_stat_strconcat_os(3, root, CAML_DIR_SEP, path); + } else { + /* path is not explicit-relative, but simply begins with a dot + => return a copy */ + return caml_stat_strdup_os(path); + } + } else { + /* path is not explicit-relative => return a copy */ + return caml_stat_strdup_os(path); + } } -CAMLexport char_os * caml_parse_ld_conf(void) +CAMLexport char_os * caml_parse_ld_conf(const char_os * stdlib, + struct ext_table *table) { - char_os * stdlib, * ldconfname, * wconfig, * p, * q; + const char_os * const locations[3] = { + caml_secure_getenv(T("OCAMLLIB")), + caml_secure_getenv(T("CAMLLIB")), + stdlib}; + char_os * libroot, * ldconfname, * wconfig, * p, * q; char * config; #ifdef _WIN32 struct _stati64 st; @@ -102,48 +142,127 @@ 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) { - char_os * realname; + char_os * realname, * suffixed = NULL; char * u8; void * handle; + if (*name == '\0') + caml_fatal_error("corrupt DLLS section"); + + if (*name == '-') { + char * suffix = + caml_stat_strconcat(4, "-", HOST, "-", BYTECODE_RUNTIME_ID); + char_os * suffix_os = caml_stat_strdup_to_os(suffix); + name = suffixed = caml_stat_strconcat_os(2, name + 1, suffix_os); + caml_stat_free(suffix_os); + caml_stat_free(suffix); + } else { + name++; + } + realname = caml_search_dll_in_path(&caml_shared_libs_path, name); u8 = caml_stat_strdup_of_os(realname); caml_gc_message(0x100, "Loading shared library %s\n", u8); @@ -160,9 +279,12 @@ 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); } +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. */ @@ -174,6 +296,8 @@ void caml_build_primitive_table(char_os * lib_path, - directories specified on the command line with the -I option - directories specified in the CAML_LD_LIBRARY_PATH - directories specified in the executable + - directories specified in OCAMLLIB/ld.conf + - directories specified in CAMLLIB/ld.conf - directories specified in the file /ld.conf caml_shared_libs_path and caml_prim_name_table are not freed afterwards: @@ -183,7 +307,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(); + 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..95c6a7dee98e 100755 --- a/runtime/gen_primsc.sh +++ b/runtime/gen_primsc.sh @@ -28,9 +28,13 @@ esac cat <<'EOF' /* Generated file, do not edit */ +#include + #define CAML_INTERNALS #include "caml/mlvalues.h" #include "caml/prims.h" +#include "caml/startup.h" +#include "build_config.h" EOF @@ -61,3 +65,13 @@ 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' + +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 ffb4eb0bebdd..f2be3343b73d 100644 --- a/runtime/startup_byt.c +++ b/runtime/startup_byt.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include "caml/config.h" @@ -72,6 +73,10 @@ #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]; /* Print the specified error message followed by an end-of-line and exit */ @@ -113,8 +118,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) { @@ -374,6 +377,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 = caml_runtime_standard_library_effective; + 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) @@ -383,13 +395,14 @@ 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(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); 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"); @@ -420,7 +433,8 @@ static void do_print_config(void) /* Parse ld.conf and print the effective search path */ puts("shared_libs_path:"); - caml_parse_ld_conf(); + caml_parse_ld_conf(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) @@ -445,17 +459,24 @@ 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, 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(); @@ -476,9 +497,21 @@ CAMLexport void caml_main(char_os **argv) /* Determine position of bytecode file */ pos = 0; - /* First, try argv[0] (when ocamlrun is called by a bytecode program) */ - exe_name = argv[0]; - fd = caml_attempt_open(&exe_name, &trail, 0); + argv0 = proc_self_exe = caml_executable_name(); + + /* In APPENDED mode (i.e. with -custom), we always want to load the bytecode + from the running executable, and argv[0] should never be used. However, + some platforms still don't implement caml_executable_name, so there is an + escape hatch here to fallback to checking argv[0] if proc_self_exe is + NULL. + For STANDARD mode (i.e. the current executable is ocamlrun), argv[0] is + tried first, as this should be the path to shebang-script/executable + originally executed by the user. */ + CAMLassert(caml_byte_program_mode != COMPLETE_EXE); + if (!caml_byte_program_mode_custom || proc_self_exe == NULL) { + exe_name = argv[0]; + fd = caml_attempt_open(&exe_name, &trail, 0); + } /* Little grasshopper wonders why we do that at all, since "The current executable is ocamlrun itself, it's never a bytecode @@ -486,14 +519,26 @@ CAMLexport void caml_main(char_os **argv) With -custom, we have an executable that is ocamlrun itself concatenated with the bytecode. So, if the attempt with argv[0] failed, it is worth trying again with executable_name. */ - if (fd < 0 && (proc_self_exe = caml_executable_name()) != NULL) { - exe_name = proc_self_exe; - fd = caml_attempt_open(&exe_name, &trail, 0); + if (caml_byte_program_mode_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_custom) + error("unable to open file '%s'", caml_stat_strdup_of_os(exe_name)); } + if (argv0 == NULL) + argv0 = caml_search_exe_in_path(exe_name); + if (fd < 0) { pos = parse_command_line(argv); if (caml_params->print_config) { + caml_runtime_standard_library_effective = + caml_locate_standard_library(argv0, + caml_runtime_standard_library_default, + NULL); + do_print_config(); exit(0); } @@ -524,6 +569,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 (); @@ -622,6 +685,10 @@ CAMLexport value caml_startup_code_exn( exe_name = caml_executable_name(); if (exe_name == NULL) exe_name = caml_search_exe_in_path(argv[0]); + caml_runtime_standard_library_effective = + caml_locate_standard_library(exe_name, + caml_runtime_standard_library_default, NULL); + Caml_state->external_raise = NULL; /* Setup signal handling */ caml_init_signals(); diff --git a/runtime/sys.c b/runtime/sys.c index 3356664ecdbd..edb99c8918d4 100644 --- a/runtime/sys.c +++ b/runtime/sys.c @@ -700,6 +700,53 @@ 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; + +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( + caml_standard_library_default ? caml_standard_library_default + : caml_runtime_standard_library_default); +} +#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); + 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/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 e19d5a26a86d..5e62839e143e 100644 --- a/runtime/unix.c +++ b/runtime/unix.c @@ -28,6 +28,7 @@ #include #include #include "caml/config.h" +#include "sys_int.h" #ifdef HAS_GETTIMEOFDAY #include #endif @@ -54,6 +55,9 @@ #else #include #endif +#ifdef HAS_LIBGEN_H +#include +#endif #ifdef __APPLE__ #include #endif @@ -541,3 +545,76 @@ 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 +} + +#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) +{ + if (Is_relative_dir(stdlib_default)) { + char * root = caml_dirname(exe_name); + char * candidate = + 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 + dirname and performs the equivalent of realpath as a side-effect of + determining the root path. */ +#ifdef HAS_REALPATH + char * resolved_candidate = realpath(candidate, NULL); + /* If realpath fails, use the non-normalised path for error messages. */ + if (resolved_candidate != NULL) { + caml_stat_free(candidate); + /* realpath uses malloc */ + candidate = caml_stat_strdup(resolved_candidate); + free(resolved_candidate); + } +#endif + if (dirname == NULL) + caml_stat_free(root); + else + *dirname = root; + return candidate; + } else { + return caml_stat_strdup(stdlib_default); + } +} diff --git a/runtime/win32.c b/runtime/win32.c index 3ec659d42ad9..5df267790339 100644 --- a/runtime/win32.c +++ b/runtime/win32.c @@ -41,6 +41,7 @@ #include #include #include +#include #include "caml/alloc.h" #include "caml/codefrag.h" #include "caml/fail.h" @@ -1272,3 +1273,96 @@ value caml_win32_xdg_defaults(void) CAMLreturn(result); } + +#define Is_separator(c) (c == '\\' || c == '/') + +#define Is_relative_dir(dir) \ + (dir[0] == '.' \ + && (dir[1] == '\0' \ + || Is_separator(dir[1]) \ + || (dir[1] == '.' && (dir[2] == '\0' || Is_separator(dir[2]))))) + +CAMLextern char_os* caml_locate_standard_library (const wchar_t *exe_name, + const wchar_t *stdlib_default, + wchar_t **dirname) +{ + 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, T("\\"), 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/stdlib/Makefile b/stdlib/Makefile index a7f2e60a7aa0..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)) @@ -54,49 +58,72 @@ 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 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) target_$(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)" - -ifeq "$(UNIX_OR_WIN32)" "unix" -HEADERPROGRAM = header -else # Windows -HEADERPROGRAM = headernt + $(call INSTALL_ITEMS, stdlib.cmxa stdlib.$(A) std_exit.$(O) *.cmx, lib) + +MANGLING = $(filter true,$(SUFFIXING)) +runtime-launch-info: tmpheader.exe + @{ printf '$(if $(MANGLING),$(ZINC_RUNTIME_ID),\000)'; \ + cat $^; } > $@ + +# The mingw-w64 and MSVC versions of tmpheader.exe are linked with special flags +# to reduce their size (considerably). In particular, the entry point is +# 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 - -%-launch-info: %.info tmpheader.exe - @cat $^ >> $@ .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 $@ $^ @@ -106,11 +133,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 164bf9de2f56..c194105034ee 100644 --- a/stdlib/header.c +++ b/stdlib/header.c @@ -13,33 +13,172 @@ /* */ /**************************************************************************/ -#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 + +#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 + +/* 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; + +#define unsafe_copy(dst, src, dstsize) lstrcpy(dst, src) + +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; +} + +static int exec_file(wchar_t *file, wchar_t *cmdline) +{ + wchar_t truename[MAX_PATH]; + STARTUPINFO stinfo; + PROCESS_INFORMATION procinfo; + DWORD retcode; -/* The launcher for bytecode executables (if #! is not working) */ + 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; + 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 "caml/s.h" +#include "sys_int.h" #include #include #include -#include "caml/s.h" -#ifdef HAS_UNISTD #include -#endif #include +#include +#ifdef HAS_LIBGEN_H +#include +#endif #include #include -#include "caml/mlvalues.h" -#include "caml/exec.h" -#ifndef MAXPATHLEN -#define MAXPATHLEN 1024 +/* O_BINARY is defined in Gnulib, but is not POSIX */ +#ifndef O_BINARY +#define O_BINARY 0 #endif -#ifndef S_ISREG -#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) -#endif +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" -#ifndef SEEK_END -#define SEEK_END 2 +#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__ @@ -48,7 +187,7 @@ static char * searchpath(char * name) { - static char fullname[MAXPATHLEN + 1]; + static char fullname[PATH_MAX + 1]; char * path; struct stat st; @@ -60,11 +199,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,68 +262,219 @@ 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) +{ + if (str1) fputs(str1, stderr); + if (str2) fputs(str2, stderr); + if (str3) fputs(str3, stderr); + fputs("\n", stderr); + 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" + +static uint32_t read_size(const char *ptr) { - unsigned char * p = (unsigned char *) ptr; - return ((unsigned long) p[0] << 24) + ((unsigned long) p[1] << 16) + - ((unsigned long) p[2] << 8) + p[3]; + 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(int 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[MAXPATHLEN]; + static char runtime_path[PATH_MAX * RNTM_ENCODING_LENGTH]; 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); - path_size = 0; + if (lseek(fd, -ofs, SEEK_END) == -1) return NULL; + *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 >= MAXPATHLEN) return NULL; - lseek(fd, -ofs, SEEK_END); - if (read(fd, runtime_path, path_size) != path_size) 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, *rntm_strlen) != *rntm_strlen) return NULL; + return runtime_path; } -static void errwrite(char * msg) +/* 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) { - fputs(msg, stderr); + 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], *dirname; + HANDLE h; + + if (GetModuleFileName(NULL, module, sizeof(module)/sizeof(wchar_t)) == 0) + exit_with_error(L"Out of memory", NULL, NULL); + + h = CreateFile(module, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + + + /* 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 (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; + } + + search_and_exec_runtime(wruntime_path, rntm_bsz, GetCommandLine(), dirname); } -#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, *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) { - errwrite(truename); - errwrite(" not found or is not a bytecode executable file\n"); - return 2; - } + 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); + +#ifdef HAS_LIBGEN_H + argv0_dirname = dirname(strdup(truename)); +#else + argv0_dirname = NULL; +#endif + argv[0] = truename; - execv(runtime_path, argv); - errwrite("Cannot exec "); - errwrite(runtime_path); - errwrite("\n"); - return 2; + /* 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/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/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 diff --git a/testsuite/in_prefix/Makefile.test b/testsuite/in_prefix/Makefile.test index cf8d2039c77b..7c968f8a93d8 100644 --- a/testsuite/in_prefix/Makefile.test +++ b/testsuite/in_prefix/Makefile.test @@ -23,10 +23,11 @@ DRIVER = ../tools/test_in_prefix$(EXE) endif DRIVER_ARGS = \ - $(VERBOSE_FLAG) --bindir "$(BINDIR)" --libdir "$(LIBDIR)" \ + $(VERBOSE_FLAG) --bindir "$(BINDIR)" --libdir '$(TARGET_LIBDIR)' \ $(call bool_to_with, shebangscripts, $(SHEBANGSCRIPTS)) \ $(call bool_to_with, ocamlnat, $(INSTALL_OCAMLNAT)) \ $(call bool_to_with, ocamlopt, $(NATIVE_COMPILER)) \ + $(RUNTIME_SEARCH_FLAG) \ $(OTHERLIBRARIES) --pwd "$(SRCDIR_ABS)/testsuite/in_prefix" default: $(DRIVER) @@ -39,8 +40,30 @@ else VERBOSE_FLAG = endif -test-in-prefix: $(DRIVER) ../tools/main_in_c.$(O) ../tools/dump_load_path$(EXE) +# Generates --without-$(1) if $(2) is empty or --with-$(1)=$(2) otherwise +RUNTIME_SEARCH_FLAG = \ + $(if $(RUNTIME_SEARCH),$\ + --with-runtime-search=$(RUNTIME_SEARCH),$\ + --without-runtime-search) + +export PATH := $(SRCDIR_ABS)/testsuite/in_prefix/poisoned-runtime:$(PATH) + +ifeq "$(SUFFIXING)" "true" +RUNTIME_NAME = ocamlrun-$(ZINC_RUNTIME_ID)$(EXE) +else +RUNTIME_NAME = ocamlrun$(EXE) +endif + +test-in-prefix: $(DRIVER) ../tools/main_in_c.$(O) \ + ../tools/dump_load_path$(EXE) ../tools/poisonedruntime$(EXE) + @rm -f ocamlrun* + @$(LN) $(ROOTDIR)/runtime/ocamlrun$(EXE) test-$(RUNTIME_NAME) + @$(MKDIR) poisoned-runtime + @cd poisoned-runtime \ + && $(LN) ../../tools/poisonedruntime$(EXE) $(RUNTIME_NAME) @$< $(DRIVER_ARGS) + @rm -rf poisoned-runtime + @rm -f test-ocamlrun* SCRUB_ENV = \ CAML_LD_LIBRARY_PATH OCAMLLIB CAMLLIB OCAMLPARAM OCAMLRUNPARAM CAMLRUNPARAM diff --git a/testsuite/in_prefix/README.md b/testsuite/in_prefix/README.md index da889954b1d0..a7a063652a10 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 @@ -70,11 +71,11 @@ 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 -- `ld.conf` contains the absolute location of the `stublibs` directory, - requiring `CAML_LD_LIBRARY_PATH` to be adjusted + requiring `OCAMLLIB` to be set, unless the compiler was configured with + `--with-relative-libdir` ### Loading archives/plugins (.cma / .cmxs) with `Dynlink` @@ -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 + 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` 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 + `ocamlrun`, so must be explicitly invoked via `ocamlrun`, unless the + compiler is configured with `--enable-runtime-search-target` ### Executing installed bytecode binaries with `-vnum` @@ -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" @@ -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/tests/tool-debugger/find-artifacts/debuggee.ml b/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml index 37544574d91a..eafe82261272 100644 --- a/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml +++ b/testsuite/tests/tool-debugger/find-artifacts/debuggee.ml @@ -1,4 +1,5 @@ (* TEST + unset BUILD_PATH_PREFIX_MAP; ocamldebug_script = "${test_source_directory}/input_script"; debugger; shared-libraries; diff --git a/testsuite/tools/cmdline.ml b/testsuite/tools/cmdline.ml index 874d259692da..b091f9c83177 100644 --- a/testsuite/tools/cmdline.ml +++ b/testsuite/tools/cmdline.ml @@ -104,10 +104,10 @@ let parse argv = in let config = ref {has_ocamlnat = false; has_ocamlopt = false; has_relative_libdir = None; - has_runtime_search = None; launcher_searches_for_ocamlrun = false; + has_runtime_search = Disable; launcher_searches_for_ocamlrun = false; target_launcher_searches_for_ocamlrun = false; bytecode_shebangs_by_default = false; shebangscripts = false; - libraries = []} + filename_mangling = false; libraries = []} in let error fmt = Printf.ksprintf (fun s -> raise (Arg.Bad s)) fmt in let check_tree () = @@ -163,23 +163,22 @@ 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 [ "--pwd", Arg.Set_string pwd, "\tCurrent working directory to use"; "--bindir", Arg.String (check_exists ~absolute:true bindir), "\ \tDirectory containing programs (must share a prefix with --libdir)"; - "--libdir", Arg.String (check_exists ~absolute:true libdir), "\ + "--libdir", Arg.String (check_exists ~absolute:false libdir), "\ \tDirectory containing stdlib.cma (must share a prefix with --bindir)"; "--summary", Arg.Set summary, ""; "--verbose", Arg.Set verbose, ""; diff --git a/testsuite/tools/dump_load_path.c b/testsuite/tools/dump_load_path.c index 45c70ffc3347..f38a75859b9e 100644 --- a/testsuite/tools/dump_load_path.c +++ b/testsuite/tools/dump_load_path.c @@ -22,7 +22,13 @@ #include #include #include +#include +extern const char_os *caml_runtime_standard_library_default; +extern const char_os *caml_runtime_standard_library_effective; +CAMLextern char_os *caml_locate_standard_library (const char_os *exe_name, + const char_os *stdlib_default, + char_os **dirname); int main_os(int argc, char_os **argv) { const char_os *dir; @@ -30,7 +36,11 @@ int main_os(int argc, char_os **argv) caml_ext_table_init(&caml_shared_libs_path, 8); caml_decompose_path(&caml_shared_libs_path, caml_secure_getenv(T("CAML_LD_LIBRARY_PATH"))); - caml_parse_ld_conf(); + caml_runtime_standard_library_effective = + caml_locate_standard_library(argv[0], + caml_runtime_standard_library_default, NULL); + caml_parse_ld_conf(caml_runtime_standard_library_effective, + &caml_shared_libs_path); for (int i = 0; i < caml_shared_libs_path.size; i++) { dir = caml_shared_libs_path.contents[i]; if (dir[0] == 0) diff --git a/testsuite/tools/environment.ml b/testsuite/tools/environment.ml index 8f867887a8ae..108f14860c45 100644 --- a/testsuite/tools/environment.ml +++ b/testsuite/tools/environment.ml @@ -46,7 +46,7 @@ let libdir_suffix {libdir_suffix; _} = libdir_suffix (* Derived properties *) -let is_renamed {phase; _} = (phase = Renamed) +let is_renamed {phase; _} = (phase <> Original) let bindir {prefix; bindir_suffix; _} = Filename.concat prefix bindir_suffix @@ -67,19 +67,6 @@ let in_libdir env path = let in_test_root {test_root; _} path = Filename.concat test_root path -(* Reverse the quoting of single quotes done by Filename.quote on Unix (which is - used for the runtime name when embedded in sh-scripts. Any single quote - characters are transformed to "'\\''". If the string is split on the single - quote characters, the sequence ["\\"; ""] is a single quote character in the - unescaped version. *) -let dequote s = - let[@tail_mod_cons] rec loop = function - | "\\" :: "" :: rest -> "'" :: loop rest - | chunk :: rest -> chunk :: loop rest - | [] -> [] - in - String.concat "" (loop (String.split_on_char '\'' s)) - (* [classify_executable file] determines if [file] is : - Tendered bytecode with an executable header - Scripted bytecode invoking ocamlrun with a #! header @@ -91,49 +78,18 @@ let classify_executable file = try In_channel.with_open_bin file (fun ic -> let start = really_input_string ic 2 in - let is_RNTM = function - | Bytesections.{name = Name.RNTM; _} -> true - | _ -> false - in + let toc = Bytesections.read_toc ic in + let sections = Bytesections.all toc in let is_DLLS = function | Bytesections.{name = Name.DLLS; len} when len > 0 -> true | _ -> false in - let toc = Bytesections.read_toc ic in - let sections = Bytesections.all toc in - if start = "#!" then - let runtime = - seek_in ic 2; - let shebang = String.trim (input_line ic) in - if Filename.basename shebang = "sh" then - let exec_line = input_line ic in - if String.starts_with ~prefix:"exec '" exec_line - && String.ends_with ~suffix:"' \"$0\" \"$@\"" exec_line then - (* When the path to the runtime can't be directly used in a - shebang, the shell is used instead, the next line is then: - exec '' "$0" "$@" *) - dequote (String.sub exec_line 6 (String.length exec_line - 17)) - else - Harness.fail_because "%s contains an unexpected exec line: %S" - file exec_line - else - shebang - in - Tendered {header = Header_shebang; - dlls = List.exists is_DLLS sections; - runtime} - else if List.exists is_RNTM sections then - let rntm = - Bytesections.read_section_string toc ic Bytesections.Name.RNTM in - let len = String.length rntm in - if len = 0 || rntm.[len - 1] <> '\000' then - Harness.fail_because "%s contains corrupt RNTM: %S" file rntm; - let runtime = String.sub rntm 0 (len - 1) in - Tendered {header = Header_exe; - dlls = List.exists is_DLLS sections; - runtime} - else - Custom) + let tendered (runtime, id, search) = + let header = if start = "#!" then Header_shebang else Header_exe in + let dlls = List.exists is_DLLS sections in + Tendered {header; dlls; runtime; id; search} + in + Option.fold ~none:Custom ~some:tendered (Byterntm.read_runtime toc ic)) with End_of_file | Bytesections.Bad_magic_number -> Vanilla @@ -224,7 +180,7 @@ let make pp_path ~verbose ~test_root ~test_root_logical let value = String.sub binding (equals + 1) (String.length binding - equals - 1) in - if is_path_env name then + if phase <> Execution && is_path_env name then if Sys.win32 then if String.index_opt bindir ';' <> None then Printf.sprintf "%s=\"%s\";%s" name bindir value @@ -270,7 +226,7 @@ let string_of_process_status = function highlighted. If argv0 is specified, then the original program executable is also shown. *) let display_execution level status pid ~runtime program argv0 args - ({pp_path; verbose; serial; _} as env) = + ({pp_path; verbose; serial; phase; _} as env) = let pp_program style program f = function | Some argv0 -> Format.fprintf f "@{<%s>%s (from %a)@}" @@ -313,9 +269,11 @@ let display_execution level status pid ~runtime program argv0 args if serial <> !last_environment then begin last_environment := serial; Format.printf "\ - @{> @}@{Environment@}\n\ - @{> @} @{PATH=%a:$PATH@}\n" - pp_path (bindir env); + @{> @}@{Environment@}\n"; + if phase <> Execution then + Format.printf "\ + @{> @} @{PATH=%a:$PATH@}\n" + pp_path (bindir env); if not Sys.win32 then Format.printf "\ @{> @} @{%s=%a:$%s@}\n" @@ -472,9 +430,9 @@ let run_process ?(runtime = false) ?(stubs = false) ?(stdlib = false) (* The tests are easier to write with the assumption that shims are simply ignored in the Original phase (otherwise they all begin [Env.is_renamed env && (* ... *)] *) - let runtime = runtime && phase = Renamed in + let runtime = runtime && phase <> Original in let env = - if phase = Renamed && (stubs || stdlib) then + if phase <> Original && (stubs || stdlib) then apply_shims ~stubs ~stdlib env else env @@ -493,7 +451,7 @@ let run_process ?(runtime = false) ?(stubs = false) ?(stdlib = false) fails without each shim in turn. The final entry in the strategy must be the request itself. *) let test_without cond shim strategy = - if phase = Renamed && cond then + if phase <> Original && cond then shim env :: strategy else strategy diff --git a/testsuite/tools/environment.mli b/testsuite/tools/environment.mli index 192718ee3594..217b98f6d56d 100644 --- a/testsuite/tools/environment.mli +++ b/testsuite/tools/environment.mli @@ -37,7 +37,7 @@ val make : (Format.formatter -> string -> unit) -> verbose:bool with [LD_LIBRARY_PATH] / [DYLD_LIBRARY_PATH] set or updated). *) val is_renamed : t -> bool -(** [is_renamed t] if [~phase = Renamed] *) +(** [is_renamed t] if [~phase <> Original] *) val test_root : t -> string (** Retrieves the [~test_root] passed to {!make}. *) diff --git a/testsuite/tools/harness.ml b/testsuite/tools/harness.ml index 6cac18613f36..f57f8e7e1c1d 100644 --- a/testsuite/tools/harness.ml +++ b/testsuite/tools/harness.ml @@ -82,11 +82,15 @@ module Import = struct type launch_mode = Header_exe | Header_shebang type executable = - | Tendered of {header: launch_mode; dlls: bool; runtime: string} + | Tendered of {header: launch_mode; + dlls: bool; + runtime: string; + id: Misc.RuntimeID.t option; + search: Byterntm.search_method} | Custom | Vanilla - type phase = Original | Renamed + type phase = Original | Execution | Renamed type mode = Bytecode | Native @@ -94,11 +98,12 @@ module Import = struct has_ocamlnat: bool; has_ocamlopt: bool; has_relative_libdir: string option; - has_runtime_search: bool option; + has_runtime_search: Config.search_method; launcher_searches_for_ocamlrun: bool; target_launcher_searches_for_ocamlrun: bool; bytecode_shebangs_by_default: bool; shebangscripts: bool; + filename_mangling: bool; libraries: string list list } @@ -151,7 +156,7 @@ let files_for ?(source_and_cmi = true) mode name files = |> add_if source_and_cmi (name ^ ".ml") let fail_because fmt = - Format.ksprintf (fun s -> prerr_endline s; exit 1) fmt + Format.ksprintf (fun s -> flush stdout; prerr_endline s; exit 1) fmt (* ocamlc cannot be directly executed after renaming the prefix if native compilation is disabled (because ocamlc will be ocamlc.byte, since ocamlc.opt diff --git a/testsuite/tools/harness.mli b/testsuite/tools/harness.mli index 67d8336026cf..3cdc335fd082 100644 --- a/testsuite/tools/harness.mli +++ b/testsuite/tools/harness.mli @@ -23,7 +23,11 @@ module Import : sig (** Kinds of executable *) type executable = - | Tendered of {header: launch_mode; dlls: bool; runtime: string} + | Tendered of {header: launch_mode; + dlls: bool; + runtime: string; + id: Misc.RuntimeID.t option; + search: Byterntm.search_method} (** Tendered bytecode image. Executable uses the given mechanism to locate a suitable runtime to execute the image. [dlls] is [true] if the bytecode image requires additional C libraries to be loaded. [runtime] @@ -36,8 +40,11 @@ module Import : sig (** Test harness phases. *) type phase = - | Original (* Compiler installed in its original configured prefix. *) - | Renamed (* Compiler moved to a different prefix from its configuration. *) + | Original (* Compiler installed in its original configured prefix. *) + | Execution (* Executing programs built by the compiler installed in its + original prefix after the compiler has been moved to a + different prefix. *) + | Renamed (* Compiler moved to a different prefix from its configuration. *) (* Tooling modes. *) type mode = @@ -53,23 +60,26 @@ module Import : sig has_ocamlopt: bool; (** {v [$(NATIVE_COMPILER)] v} - {v Makefile.config v} *) has_relative_libdir: string option; - (** Not implemented; always None. *) - has_runtime_search: bool option; - (** Not implemented; always None. *) + (** {v $(TARGET_LIBDIR_IS_RELATIVE) v} and {v $(TARGET_LIBDIR) v} - + {v Makefile.build_config v} *) + has_runtime_search: Config.search_method; + (** {v $(RUNTIME_SEARCH) v} - {v Makefile.build_config v} *) launcher_searches_for_ocamlrun: bool; (** Indicates whether bytecode executables in the compiler distribution - use a launcher that is capable of searching PATH to find ocamlrun. At - present, only native Windows has this behaviour. *) + use a launcher that is capable of searching PATH to find ocamlrun. + This used to be the behaviour for native Windows. *) target_launcher_searches_for_ocamlrun: bool; (** Indicates whether the executable launcher used by ocamlc is capable of - searching PATH to find ocamlrun. At present, only native Windows has - this behaviour. *) + searching PATH to find ocamlrun. This used to be the behaviour for + native Windows. *) bytecode_shebangs_by_default: bool; (** True if ocamlc uses a shebang-style header rather than an executable header for tendered bytecode executables. *) shebangscripts: bool; (** {v $(SHEBANGSCRIPTS) v} - {v Makefile.config v} *) - libraries: string list list + filename_mangling: bool; + (** True if the Runtime ID is being used for filename mangling. *) + libraries: string list list; (** Sorted list of basenames of libraries to test. Derived from {v [$(OTHERLIBRARIES)] v} - {v Makefile.config v} *) } diff --git a/testsuite/tools/poisonedruntime.c b/testsuite/tools/poisonedruntime.c new file mode 100644 index 000000000000..ef5f5be95ea0 --- /dev/null +++ b/testsuite/tools/poisonedruntime.c @@ -0,0 +1,27 @@ +/**************************************************************************/ +/* */ +/* OCaml */ +/* */ +/* David Allsopp, University of Cambridge & Tarides */ +/* */ +/* Copyright 2025 David Allsopp Ltd. */ +/* */ +/* All rights reserved. This file is distributed under the terms of */ +/* the GNU Lesser General Public License version 2.1, with the */ +/* special exception on linking described in the file LICENSE. */ +/* */ +/**************************************************************************/ + +/* Micro-program used to sit in PATH to test local path search for bytecode + executables. */ + +#define CAML_INTERNALS +#include +#include + +int main_os(int argc, char_os **argv) +{ + printf("The poisoned runtime has been invoked!\n" + "This suggests something is wrong in stdlib/header.c\n"); + return 1; +} diff --git a/testsuite/tools/testBytecodeBinaries.ml b/testsuite/tools/testBytecodeBinaries.ml index 536483369ffa..5bc4a97cc0f3 100644 --- a/testsuite/tools/testBytecodeBinaries.ml +++ b/testsuite/tools/testBytecodeBinaries.ml @@ -35,126 +35,184 @@ let run config env = let exec_magic = Environment.run_process env ocamlrun ["-M"] in - let test_binary binary = + let test_binary failed binary = if String.starts_with ~prefix:"ocaml" binary - || String.starts_with ~prefix:"flexlink" binary then - let program = Filename.concat bindir binary in - if is_executable program then - let classification = Environment.classify_executable program in - if classification <> Vanilla then - let fails = - (* After the prefix has been renamed, bytecode executables compiled - with -custom will still work. Otherwise, only executables where the - header can search for ocamlrun and which do not require any C stubs - to be loaded will still work. *) - Environment.is_renamed env - && match classification with - | Tendered {dlls; _} -> - not config.launcher_searches_for_ocamlrun || dlls - | _ -> - false - in - match Environment.run_process ~fails env program ["-vnum"] with - | (0, ((output::rest) as all_output)) when not fails -> - if rest <> [] then begin - Environment.display_output all_output; - Harness.fail_because "%s: expected only one line of output" - program - end; - let runtime = - let compiled_by_boot_ocamlc = - let name = - if Filename.extension binary = ".exe" then - Filename.remove_extension binary - else - binary - in - name <> "ocamldoc" && name <> "ocamldebug" - in - match classification with - | Vanilla -> assert false - | Custom -> - if Config.supports_shared_libraries - || compiled_by_boot_ocamlc then - Harness.fail_because "%s: unexpected -custom runtime" - program - else - "compiled with -custom" - | Tendered {runtime; header; _} -> - let is_expected_runtime = - if Sys.win32 then - runtime = "ocamlrun" - else - runtime = ocamlrun - in - let expected_launch_mode = - if config.shebangscripts then - Header_shebang + || String.starts_with ~prefix:"flexlink" binary then + let program = Filename.concat bindir binary in + if is_executable program then + let classification = Environment.classify_executable program in + if classification <> Vanilla then + let fails = + (* After the prefix has been renamed, bytecode executables compiled + with -custom will still work. Otherwise, the header needs to be + able to search for ocamlrun and, if applicable, ocamlrun needs to + be able to load C stubs (which will only happen if the runtime + locates the Standard Library using a relative directory, so that + it can find ld.conf) *) + Environment.is_renamed env + && match classification with + | Tendered {dlls; _} -> + not config.launcher_searches_for_ocamlrun + || dlls && config.has_relative_libdir = None + | _ -> + false + in + match Environment.run_process ~fails env program ["-vnum"] with + | (0, ((output::rest) as all_output)) when not fails -> + if rest <> [] then begin + Environment.display_output all_output; + Harness.fail_because "%s: expected only one line of output" + program + end; + let failed, runtime = + let compiled_by_boot_ocamlc = + let name = + if Filename.extension binary = ".exe" then + Filename.remove_extension binary else - Header_exe + binary in - if is_expected_runtime then - if header = expected_launch_mode then - runtime - else - Harness.fail_because "%s: unexpected launch mode" program - else - Harness.fail_because "%s: unexpected runtime %S" - program runtime - in - Printf.printf " Runtime: %s\n Output: %s\n" runtime output; - if Sys.win32 && Filename.extension binary = ".exe" then - (* This additional part of the test ensures that the executable - launcher on Windows can correctly hand-over to ocamlrun on - Windows. The check is that a binary named ocamlc.byte.exe - can be invoked as ocamlc.byte. -M is used as a previous bug - caused ocamlc.byte to act solely as ocamlrun, the test being - that ocamlrun -M returning the runtime's magic number would - be likely distinct from the behaviour of any of the - distribution's tools when called with -M. *) - let without_exe = Filename.remove_extension binary in - let (this_exit_code, _) as this = - let fails = - without_exe <> "ocamlmklib" - && not (String.contains without_exe '.') + name <> "ocamldoc" && name <> "ocamldebug" in - Environment.run_process - ~fails env program ~argv0:without_exe ["-M"] + match classification with + | Vanilla -> assert false + | Custom -> + if Config.supports_shared_libraries + || compiled_by_boot_ocamlc then + Harness.fail_because "%s: unexpected -custom runtime" + program + else + failed, "compiled with -custom" + | Tendered {runtime; id; header; search; _} -> + let reported_runtime, search = + let id = + Option.map + (fun t -> "-" ^ Misc.RuntimeID.to_string t) id + |> Option.value ~default:"" + in + match search with + | Disable dir -> + dir ^ runtime ^ id, Config.Disable + | Fallback dir -> + Printf.sprintf "[%s]%s%s" dir runtime id, + Config.Fallback + | Enable -> + runtime ^ id, Config.Enable + in + let expected_id = + if config.filename_mangling then + Some (Misc.RuntimeID.make_zinc ()) + else + None + in + let expected_launch_mode = + if config.shebangscripts then + Header_shebang + else + Header_exe + in + let pp_runtime_id f = function + | None -> + Format.pp_print_string f "" + | Some id -> + Format.pp_print_string f (Misc.RuntimeID.to_string id) + in + let pp_search f = function + | Config.Disable -> + Format.pp_print_string f "disable" + | Config.Fallback -> + Format.pp_print_string f "fallback" + | Config.Enable -> + Format.pp_print_string f "enable" + in + let pp_launch f = function + | Header_shebang -> Format.pp_print_string f "shebang" + | Header_exe -> Format.pp_print_string f "executable" + in + let check expected actual description print failed = + if expected = actual then + failed + else + Format.kfprintf (Fun.const true) Format.err_formatter + " *** Unexpected %s (Expected: %a; got %a)\n%!" + description print expected print actual + in + let failed = + failed + |> check config.has_runtime_search search + "search mechanism" pp_search + |> check expected_id id + "runtime ID" pp_runtime_id + |> check "ocamlrun" runtime + "runtime" Format.pp_print_string + |> check expected_launch_mode header + "launch mode" pp_launch + in + failed, reported_runtime in - if this_exit_code = 0 then - if this = exec_magic then - let (that_exit_code, _) as that = - let fails = without_exe <> "ocamlmklib" in - Environment.run_process - ~fails env program ~argv0:binary ["-M"] - in - if this = that then - Harness.fail_because - "Neither %s nor %s seem to load the bytecode image" - without_exe binary - else if that_exit_code = 0 then - Harness.fail_because - "%s is not expected to return with exit code 0" - binary - else if not (String.contains without_exe '.') then + Printf.printf " Runtime: %s\n Output: %s\n" runtime output; + if Sys.win32 && Filename.extension binary = ".exe" then begin + (* This additional part of the test ensures that the executable + launcher on Windows can correctly hand-over to ocamlrun on + Windows. The check is that a binary named ocamlc.byte.exe + can be invoked as ocamlc.byte. -M is used as a previous bug + caused ocamlc.byte to act solely as ocamlrun, the test being + that ocamlrun -M returning the runtime's magic number would + be likely distinct from the behaviour of any of the + distribution's tools when called with -M. *) + let without_exe = Filename.remove_extension binary in + let (this_exit_code, _) as this = + let fails = + without_exe <> "ocamlmklib" + && not (String.contains without_exe '.') in + Environment.run_process + ~fails env program ~argv0:without_exe ["-M"] + in + if this_exit_code = 0 then + if this = exec_magic then + let (that_exit_code, _) as that = + let fails = without_exe <> "ocamlmklib" in + Environment.run_process + ~fails env program ~argv0:binary ["-M"] + in + if this = that then + Harness.fail_because + "Neither %s nor %s seem to load the bytecode image" + without_exe binary + else if that_exit_code = 0 then + Harness.fail_because + "%s is not expected to return with exit code 0" + binary + else if not (String.contains without_exe '.') then + Harness.fail_because + "%s is not expected to return the exec magic number!" + without_exe + else () (* Expected outcome was the exec magic number *) + else if without_exe <> "ocamlmklib" then Harness.fail_because - "%s is not expected to return the exec magic number!" + "%s is expected to return with a non-zero exit code" without_exe - else () (* Expected outcome was the exec magic number *) - else if without_exe <> "ocamlmklib" then + else () (* Expected outcome is a zero exit code *) + else if without_exe = "ocamlmklib" then Harness.fail_because - "%s is expected to return with a non-zero exit code" + "%s is expected to return with exit code 0" without_exe - else () (* Expected outcome is a zero exit code *) - else if without_exe = "ocamlmklib" then - Harness.fail_because - "%s is expected to return with exit code 0" - without_exe - else () (* Expected outcome is a non-zero exit code *) - | _ -> - if not fails then - Harness.fail_because "%s: not expected to have failed" program + else () (* Expected outcome is a non-zero exit code *) + end; + failed + | _ -> + if not fails then + Harness.fail_because "%s: not expected to have failed" program + else + failed + else + failed + else + failed + else + failed in let binaries = Sys.readdir bindir in Array.sort String.compare binaries; - Array.iter test_binary binaries + if Array.fold_left test_binary false binaries then + Harness.fail_because "Binaries didn't all match expectation" diff --git a/testsuite/tools/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 a7c4d0be1fb5..7fbfbf1afdc4 100644 --- a/testsuite/tools/testLinkModes.ml +++ b/testsuite/tools/testLinkModes.ml @@ -125,7 +125,7 @@ let () = around some problems with shared runtimes on s390x and riscv which don't reliably fail. *) -let run_program env _config = +let run_program env config = let prefix = Environment.prefix env in let libdir_suffix = Environment.libdir_suffix env in let prefix, libdir_suffix = @@ -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 @@ -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,16 @@ 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 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] *) + (* stdlib/header.c does not preserve argv[0] for Unix *) Success {executable_name = argv0_resolved; argv0 = argv0_resolved} | Custom -> @@ -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,23 +368,53 @@ 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 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 in run_program env config ~runtime:via_ocamlrun ~stubs test_program_path ~prefix_path_with_cwd expected_executable_name @@ -394,14 +426,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 @@ -470,8 +502,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 -> @@ -493,7 +551,7 @@ let compile_test usr_bin_sh config env test test_program description = | Output_obj(C_ocamlopt, Static) -> f ~mode:Native ~clibs:["-lcomprmarsh"; "-lunixnat"; - Toolchain.compression_c_libraries] + Config.compression_c_libraries] ["-output-obj"] | Output_obj(C_ocamlopt, Shared) -> (* cf. ocaml/ocaml#13693 - on Fedora/RHEL, this executable @@ -504,7 +562,7 @@ let compile_test usr_bin_sh config env test test_program description = let linker_exit_code = fails_if (Sys.win32 || Sys.cygwin) in f ~mode:Native ~use_shared_runtime:true ~may_segfault ~clibs:["-lcomprmarsh"; "-lunixnat"; - Toolchain.compression_c_libraries] + Config.compression_c_libraries] ~linker_exit_code ["-output-obj"] | Output_complete_obj(C_ocamlc, Static) -> (* At the moment, the partial linker will pass -lws2_32 and -ladvapi32 @@ -534,15 +592,16 @@ let compile_test usr_bin_sh config env test test_program description = (* At the moment, the partial linker will pass -lzstd to ld -r which will (normally) fail). Until this is done, pass the libraries manually, using -noautolink. *) - f ~mode:Native ~clibs:[Toolchain.compression_c_libraries] + f ~mode:Native ~clibs:[Config.compression_c_libraries] ~linker_exit_code ["-output-complete-obj"; "-noautolink"; "-cclib"; "-lunixnat"; "-cclib"; "-lcomprmarsh"] | Output_complete_obj(C_ocamlopt, Shared) -> - (* ocamlopt doesn't correctly implement -runtime-variant _shared *) - let compilation_exit_code = fails_if true in + (* ocamlopt allows the .so to be passed to the partial linker which + fails with GNU ld, but not with the macOS linker *) + let compilation_exit_code = fails_if (Config.system <> "macosx") in f ~mode:Native ~use_shared_runtime:true - ~compilation_exit_code ~clibs:[Toolchain.compression_c_libraries] + ~compilation_exit_code ~clibs:[Config.compression_c_libraries] ["-output-complete-obj"; "-noautolink"; "-cclib"; "-lunixnat"; "-cclib"; "-lcomprmarsh"] | Output_complete_exe Static -> @@ -579,6 +638,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 @@ -605,14 +679,11 @@ 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 + 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; exit_code @@ -639,9 +710,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 @@ -673,15 +756,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) @@ -709,5 +790,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 aa3439d9228f..6a2ff3ce6c42 100644 --- a/testsuite/tools/testRelocation.ml +++ b/testsuite/tools/testRelocation.ml @@ -27,12 +27,17 @@ module LocationMap = Map.Make(Location) (* Augment toolchain properties with information from the configuration (this essentially goes from "is foo capable of doing bar" to "foo does bar in this context". *) -let effective_toolchain _config = +let effective_toolchain config = let c_compiler_debug_paths_are_absolute = Toolchain.c_compiler_debug_paths_can_be_absolute + && (not Config.c_has_debug_prefix_map || config.has_relative_libdir = None) in let assembler_embeds_build_path = Toolchain.assembler_embeds_build_path + && (not Config.as_has_debug_prefix_map + || Config.architecture = "riscv" + || Toolchain.as_is_cc + || config.has_relative_libdir = None) in c_compiler_debug_paths_are_absolute, assembler_embeds_build_path @@ -62,18 +67,20 @@ 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 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 @@ -107,7 +114,7 @@ let bindir_rules config file = else (* Bytecode runtimes and ocamlyacc of which only ocamlrund is linked with -g *) - `Other, (basename = "ocamlrund") + `Other, (List.mem "ocamlrund" (String.split_on_char '-' basename)) in (* Combine this with the properties of the platform to determine whether the executable will contain the build path. *) @@ -127,7 +134,7 @@ let bindir_rules config file = stripped. However, since the C objects in libcamlrun are compiled with -g, this will still result in debug information for -custom runtime executables. *) - linked_with_debug + linked_with_debug && config.has_relative_libdir = None || (classification = Custom && Toolchain.linker_propagates_debug_information && c_compiler_debug_paths_are_absolute) @@ -161,18 +168,20 @@ let libdir_rules config file = - contains objects which have been created by the assembler *) let (embeds_stdlib_location, has_ocaml_debug_info, has_c_debug_info, contains_assembled_objects) = - if List.mem basename ["Makefile.config"; - "ld.conf"; - "runtime-launch-info"] then - (* These files all embed the Standard Library location *) + if basename = "Makefile.config" then + (* Embeds the Standard Library location *) (true, false, false, false) else if basename = "config.cmx" then (* config.cmx contains Config.standard_library for inlining *) - (true, false, false, false) + let stdlib = + config.has_relative_libdir = None && not Config.flambda in + (stdlib, false, false, false) else if List.mem ext [".cma"; ".cmo"; ".cmt"; ".cmti"] then let stdlib = (* via Config.standard_library *) - List.mem basename ["config.cmt"; "config_main.cmt"; - "ocamlcommon.cma"] in + config.has_relative_libdir = None + && List.mem basename ["config.cmt"; "config_main.cmt"; + "ocamlcommon.cma"] in + (* The compiler's artefacts are all compiled with -g *) (stdlib, true, false, false) else if ext = ".cmxs" then (* All the .cmxs files built by the distribution at present include C @@ -188,16 +197,6 @@ let libdir_rules config file = not (is_ocaml || String.starts_with ~prefix:"flexdll_" basename) in (false, false, c_debug, is_ocaml) else if ext = Config.ext_lib || ext = Config.ext_dll then - (* Based on the filename, is this one of the bytecode runtime libraries - (libcamlrun.a, libcamlrund.a, libcamlrun_shared.so, etc. - Note that these properties are _not_ used for libasmrun* (see - below) *) - let is_camlrun = - let dir = Filename.basename (Filename.dirname file) in - dir <> "stublibs" - && String.starts_with ~prefix:"libcamlrun" basename - && not (String.starts_with ~prefix:"libcamlruntime" basename) - in if ext = Config.ext_lib then (* Any archive produced by ocamlopt will have a .cmxa file with it *) let is_ocaml = @@ -205,14 +204,13 @@ let libdir_rules config file = (* Config.standard_library is in ocamlcommon and the bytecode runtime embeds the Standard Library location *) let stdlib = - is_camlrun - || Filename.remove_extension basename = "ocamlcommon" - in + config.has_relative_libdir = None + && Filename.remove_extension basename = "ocamlcommon" in (stdlib, false, (not is_ocaml), is_ocaml) else (* DLLs are either the shared versions of the runtime libraries or C stubs. All of these are compiled with -g *) - (is_camlrun, false, true, false) + (false, false, true, false) else (false, false, false, false) in @@ -230,7 +228,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 @@ -242,8 +240,29 @@ let libdir_rules config file = else LocationMap.empty in + let prefix = + if config.has_relative_libdir <> None + && basename = "Makefile.config" then + LocationMap.add Relative false prefix + else + prefix + in if contains_build_path then LocationMap.add Build false prefix + (* Prior to #13828 (OCaml 5.4.0), .cmt and .cmti embed the absolute location + of the compiler without using BUILD_PATH_PREFIX_MAP. However, this + embedding is not entirely predictable, because it only happens when + ocamlc.opt or ocamlopt.opt is used for compilation, rather than when the + bytecode version of the tool is passed is explicitly to ocamlrun (in this + case, Sys.argv.(0) always retains the relative path used in the build + system). For this reason, when configured relatively, on Windows, with + native compilation available, accept that the Build directory may appear + in .cmt/.cmti files. *) + else if config.has_relative_libdir <> None + && Sys.win32 + && config.has_ocamlopt + && (ext = ".cmt" || ext = ".cmti") then + LocationMap.add Build true prefix else prefix @@ -288,18 +307,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 @@ -503,17 +512,6 @@ let run ~reproducible config env = |> scan Environment.libdir "$libdir" libdir_rules in flush stderr; - (* Abort the harness if there are files which didn't match a ruleset *) - let () = - if results_are_reproducible && not consistent then - Harness.fail_because - "Internal error: bindir_rules and libdir_rules disagree with \ - reproducible_rules" - else if results_are_reproducible <> reproducible then - Harness.fail_because - "The build is %sexpected to be reproducible" - (if not reproducible then "not " else "") - in (* Summarise the results, using wildcards to bring them to a readable length *) let sections = @@ -632,7 +630,15 @@ let run ~reproducible config env = let pp_results = Format.(pp_print_list ~pp_sep pp_print_string) in Format.printf "@[ %a@]@." pp_results results in + (* Abort the harness if there are files which didn't match a ruleset *) if failed then - Harness.fail_because "Installed files don't match expectation" - else - List.iter display sections + Harness.fail_because "Installed files don't match expectation"; + List.iter display sections; + if results_are_reproducible && not consistent then + Harness.fail_because + "Internal error: bindir_rules and libdir_rules disagree with \ + reproducible_rules" + else if results_are_reproducible <> reproducible then + Harness.fail_because + "The build is %sexpected to be reproducible" + (if not reproducible then "not " else "") diff --git a/testsuite/tools/testToplevel.ml b/testsuite/tools/testToplevel.ml index 665371261129..699ead0dd8b5 100644 --- a/testsuite/tools/testToplevel.ml +++ b/testsuite/tools/testToplevel.ml @@ -97,8 +97,7 @@ 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 + ~stdlib:(config.has_relative_libdir = None) env toplevel args in Environment.display_output output; if exit_code <> expected_exit_code then diff --git a/testsuite/tools/test_in_prefix.ml b/testsuite/tools/test_in_prefix.ml index 743ea1996505..4764b185d2fa 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,41 +68,9 @@ 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)) let () = let config, pwd, prefix, _, bindir_suffix, libdir, libdir_suffix, @@ -151,21 +119,23 @@ let () = in List.map add_dependencies libraries in - let runtime_launch_info = + let header_size, filename_mangling = let file = Filename.concat libdir "runtime-launch-info" in - read_runtime_launch_info file in - let header_size = - let {buffer; executable_offset; _} = runtime_launch_info in - String.length buffer - executable_offset in + In_channel.with_open_bin file @@ fun ic -> + In_channel.length ic, (input_char ic <> '\000') + in let bytecode_shebangs_by_default = - runtime_launch_info.launcher <> Executable in - let launcher_searches_for_ocamlrun = Sys.win32 in - let target_launcher_searches_for_ocamlrun = Sys.win32 in + Config.launch_method <> Config.Executable in + let launcher_searches_for_ocamlrun = + (config.has_runtime_search <> Config.Disable) in + let target_launcher_searches_for_ocamlrun = + (Config.search_method <> Config.Disable) in let config = {config with libraries; launcher_searches_for_ocamlrun; target_launcher_searches_for_ocamlrun; - bytecode_shebangs_by_default} + bytecode_shebangs_by_default; + filename_mangling} in (* A compiler distribution is _Relocatable_ if its build, for a given system, satisfies the following three properties: @@ -180,20 +150,31 @@ 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.has_runtime_search <> Config.Disable + 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") && not Toolchain.linker_embeds_build_path && (not Toolchain.c_compiler_always_embeds_build_path || not Toolchain.c_compiler_debug_paths_can_be_absolute) + (* Prior to #13828 (OCaml 5.4.0), .cmt and .cmti embed the absolute location + of the compiler without using BUILD_PATH_PREFIX_MAP. However, this + embedding is not entirely predictable, because it only happens when + ocamlc.opt or ocamlopt.opt is used for compilation, rather than when the + bytecode version of the tool is passed is explicitly to ocamlrun (in this + case, Sys.argv.(0) always retains the relative path used in the build + system). For this reason, when configured relatively, on Windows, with + native compilation available, accept that the Build directory may appear + in .cmt/.cmti files, and therefore that the output may not be + reproducible. *) + && (not Sys.win32 || not config.has_ocamlopt) in - let target_relocatable = false 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 +236,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/testsuite/tools/test_ld_conf.ml b/testsuite/tools/test_ld_conf.ml index c866dde4ca58..9a74ae529bba 100644 --- a/testsuite/tools/test_ld_conf.ml +++ b/testsuite/tools/test_ld_conf.ml @@ -39,12 +39,13 @@ type ld_conf_test = { and var_setting = Unset | Empty | Set of string list (* Set of tests to run in a given environment *) -let tests _config env = +let tests config env = (* Convenience function - [if_ld_conf_found outcome] returns the empty list in the Renamed phase. *) let if_ld_conf_found outcome = - (* ocamlrun can't find ld.conf after the prefix has been renamed *) - if Environment.is_renamed env then + (* ocamlrun can only find ld.conf after the prefix has been renamed if it's + configured with --with-relative-libdir *) + if Environment.is_renamed env && config.has_relative_libdir = None then [] else outcome @@ -63,22 +64,29 @@ 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 *) "/", "/", 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 +96,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 +185,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 +227,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 +256,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 @@ -333,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; @@ -349,104 +351,32 @@ let () = in (* In the Renamed phase, the test driver will need to be launched with ocamlrun, unless executables produced by the compiler are capable of - searching for the runtime (as the Windows executable launcher does) *) + searching for the runtime (as the Windows executable launcher does) or + the compiler has been configured with a relative libdir (as in this mode + the bytecode header will have the correct location) *) let runtime = mode = Bytecode - && not config.target_launcher_searches_for_ocamlrun in + && not config.target_launcher_searches_for_ocamlrun + && config.has_relative_libdir = None in let run run_process test = let code, lines = run_process ?argv0:None ~runtime test_program [] 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/testsuite/tools/toolchain.ml.in b/testsuite/tools/toolchain.ml.in index e976fe59b46a..7c8e7a456269 100644 --- a/testsuite/tools/toolchain.ml.in +++ b/testsuite/tools/toolchain.ml.in @@ -15,7 +15,7 @@ open Harness.Import let c_compiler_vendor = {@QS@|@ocaml_cc_vendor@|@QS@} -let compression_c_libraries = {@QS@|@zstd_libs@|@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 ca5a71948cfa..625bdf5057c4 100644 --- a/testsuite/tools/toolchain.mli +++ b/testsuite/tools/toolchain.mli @@ -50,5 +50,6 @@ val c_compiler_vendor : string (** The vendor and version of the C compiler, as determined by configure.ac (see Config.c_compiler_vendor in OCaml 5.5) *) -val compression_c_libraries : string -(** The C libraries needed with -lcomprmarsh *) +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/tools/byterntm.mli b/tools/byterntm.mli new file mode 100644 index 000000000000..4a9c206c4eed --- /dev/null +++ b/tools/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/tools/byterntm.mll b/tools/byterntm.mll new file mode 100644 index 000000000000..43b43c1806b1 --- /dev/null +++ b/tools/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/tools/ci/actions/runner.sh b/tools/ci/actions/runner.sh index 512e57d23829..05805ae7e20c 100755 --- a/tools/ci/actions/runner.sh +++ b/tools/ci/actions/runner.sh @@ -16,7 +16,8 @@ set -xe -PREFIX=~/local +# The prefix is designed to be usable as an opam local switch +PREFIX=~/local/_opam MAKE="make $MAKE_ARG" SHELL=dash @@ -56,11 +57,12 @@ EOF # $CONFIG_ARG also appears last to allow settings specified here to be # overridden by the workflows. call-configure --prefix="$PREFIX" \ + --docdir="$PREFIX/doc/ocaml" \ --enable-flambda-invariants \ --enable-ocamltest \ --enable-native-toplevel \ --disable-dependency-generation \ - $CONFIG_ARG + -C $CONFIG_ARG } Build () { @@ -125,13 +127,116 @@ API_Docs () { } Install () { - $MAKE install + $MAKE INSTALL_MODE=list install | grep '^->' | sort | uniq -d > duplicates + if [ -s duplicates ]; then + echo "The installation duplicates targets:" + cat duplicates + exit 1 + fi + rm duplicates + $MAKE DESTDIR="$PWD/install" install + find $PWD/install -name _opam -type d + $MAKE INSTALL_MODE=clone install + ret="$PWD" + script="$PWD/ocaml-compiler-clone.sh" + cd "$(find $PWD/install -name _opam -type d)" + mkdir -p "share/ocaml" + cp "$ret/config.status" "$ret/config.cache" "share/ocaml" + cp "$ret/ocaml-compiler-clone.sh" "share/ocaml/clone" + sh $script "$PWD" ~/local/_opam + cd "$ret" + rm -rf install + rm ocaml-compiler-clone.sh } +target_libdir_is_relative='^ *TARGET_LIBDIR_IS_RELATIVE *= *false' + 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 \ +--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 + $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 @@ -141,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 @@ -150,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)" @@ -214,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;; @@ -223,9 +348,11 @@ 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;; +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 5d29916eaf9d..3d0f21981996 100755 --- a/tools/ci/appveyor/appveyor_build.sh +++ b/tools/ci/appveyor/appveyor_build.sh @@ -70,7 +70,10 @@ function set_configuration { CACHE_FILE_PREFIX="$CACHE_DIRECTORY/config.cache-$1" CACHE_FILE="$CACHE_FILE_PREFIX-$CACHE_KEY" - args=('--cache-file' "$CACHE_FILE" '--prefix' "$2" '--enable-ocamltest') + args=('--cache-file' "$CACHE_FILE" \ + '--prefix' "$2/_opam" \ + '--docdir' "$2/_opam/doc/ocaml" \ + '--enable-ocamltest') case "$1" in cygwin*) @@ -87,6 +90,10 @@ function set_configuration { args+=('--host=x86_64-pc-windows' '--enable-dependency-generation' \ '--enable-native-toplevel');; esac + if [[ $RELOCATABLE = 'true' ]]; then + args+=('--with-relative-libdir' \ + '--enable-runtime-search' '--enable-runtime-search-target=fallback') + fi # Remove old configure cache if the configure script or the OS # have changed @@ -103,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 } @@ -110,7 +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" -OCAMLROOT=$(echo "$OCAMLROOT" | cygpath -f - -m) +export OPAMSWITCH="$OCAMLROOT" if [[ $BOOTSTRAP_FLEXDLL = 'false' ]] ; then case "$PORT" in @@ -195,6 +204,39 @@ case "$1" in make -C "$FULL_BUILD_PREFIX-$PORT/testsuite" SHOW_TIMINGS=1 all fi run "install $PORT" $MAKE -C "$FULL_BUILD_PREFIX-$PORT" install + make -C "$FULL_BUILD_PREFIX-$PORT" INSTALL_MODE=clone install + ( + cd "$OCAMLROOT" + mv _opam destdir + #ret="$PWD" + #script="$PWD/ocaml-compiler-clone.sh" + #cd "$(find $PWD/install -name _opam -type d)" + mkdir -p "destdir/share/ocaml" + cp "$FULL_BUILD_PREFIX-$PORT/config."{cache,status} 'destdir/share/ocaml/' + cp "$FULL_BUILD_PREFIX-$PORT/ocaml-compiler-clone.sh" \ + 'destdir/share/ocaml/clone' + cd destdir + sh "$FULL_BUILD_PREFIX-$PORT/ocaml-compiler-clone.sh" "$PWD" \ + "$OCAMLROOT/_opam" + ) + rm -rf "$OCAMLROOT" + $MAKE -C "$FULL_BUILD_PREFIX-$PORT" OPAM_PACKAGE_NAME=ocaml-variants \ + INSTALL_MODE=opam install + ( + cd "$FULL_BUILD_PREFIX-$PORT" + export PATH="$FLEXDLLROOT:$PATH" + opam init --cli=2.4 --bare --yes --disable-sandboxing --auto-setup \ + --cygwin-local-install + # These commands intentionally run using opam's "default" CLI + opam switch create "$OPAMSWITCH" --empty + opam pin add --no-action --kind=path ocaml-variants . + opam pin add --no-action flexdll flexdll + opam install --yes flexdll winpthreads + opam install --yes --assume-built ocaml-variants + git checkout -- ocaml-variants.install + rm -f config.cache ocaml-variants-fixup.sh ocaml-compiler-clone.sh + opam exec -- ocamlc -v + ) run "test $PORT in prefix" \ $MAKE -f Makefile.test -C "$FULL_BUILD_PREFIX-$PORT/testsuite/in_prefix" \ test-in-prefix diff --git a/tools/objinfo.ml b/tools/objinfo.ml index 83e36593141b..8096e581cac7 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,19 @@ 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"); +let dllib (suffixed, name) = + if suffixed then + Printf.sprintf "%s--" name + else + name + +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 *) List.iter print_spaced_string (List.rev lib.lib_ccobjs); @@ -81,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_dllibs); printf "\n"; List.iter print_cmo_infos lib.lib_units @@ -249,11 +257,14 @@ 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 (Compilenv.needs_stdlib_location ui) let print_cmxa_infos (lib : Cmx_format.library_infos) = printf "Extra C object files:"; @@ -288,9 +299,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 @@ -317,10 +376,15 @@ 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 _ -> () ) - all + (Bytesections.all toc) let find_dyn_offset filename = match Binutils.read filename with @@ -361,8 +425,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/tools/ocamlmklib.ml b/tools/ocamlmklib.ml index f6b2a2d16ec9..398d62e6ba64 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 *) @@ -51,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 && @@ -164,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 @@ -210,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 @@ -286,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) @@ -308,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 "") @@ -317,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/tools/opam/generate.ml b/tools/opam/generate.ml new file mode 100644 index 000000000000..5c8d78e69f24 --- /dev/null +++ b/tools/opam/generate.ml @@ -0,0 +1,237 @@ +(**************************************************************************) +(* *) +(* OCaml *) +(* *) +(* David Allsopp, University of Cambridge & Tarides *) +(* *) +(* Copyright 2025 David Allsopp Ltd. *) +(* *) +(* All rights reserved. This file is distributed under the terms of *) +(* the GNU Lesser General Public License version 2.1, with the *) +(* special exception on linking described in the file LICENSE. *) +(* *) +(**************************************************************************) + +(* This script is called from the root of the repository at the end of + `make INSTALL_MODE= install` and is responsible for converting + the various files generated by the installation backend into final output. + Parameters are the following Makefile variables: + $1 = $(INSTALL_MODE) (opam or clone) + $2 = $(OPAM_PACKAGE_NAME) + $3 = $(LN) *) + +let exit_because fmt = Printf.ksprintf (fun s -> prerr_endline s; exit 1) fmt + +let () = + if Array.length Sys.argv <> 4 + || Sys.argv.(1) <> "clone" && Sys.argv.(1) <> "opam" then begin + exit_because "Invalid command line arguments" + end + +let mode = Sys.argv.(1) +let package = Sys.argv.(2) +let ln_command = Sys.argv.(3) + +let output_endline oc = Printf.kfprintf (fun oc -> output_char oc '\n') oc + +let write_install_lines oc file = + In_channel.with_open_text file @@ + In_channel.fold_lines (fun _ -> output_endline oc " %s") () + +let remove_file = Sys.remove + +let output_section oc section = + let file = "opam-" ^ section in + if Sys.file_exists file then begin + let section = + if section = "lib" || section = "libexec" then + section ^ "_root" + else + section + in + output_endline oc {|%s: [ +%a]|} section write_install_lines file; + remove_file file + end + +(* See note in Makefile.common *) +let valid_in_path = function '\'' | '"' | '\\' -> false | _ -> true +let valid_in_section c = c <> '@' && valid_in_path c +let valid_path path = + if String.for_all valid_in_path path then + path + else + exit_because "%S contains characters invalid in a path" path +let valid_section dir = + if String.for_all valid_in_section dir then + dir + else + exit_because "%S contains characters invalid in a section" dir + +(* [generate_install file] processes then erases opam-bin, opam-lib opam-libexec + and opam-man to produce [file] *) +let generate_install file = + Out_channel.with_open_text file @@ fun oc -> + List.iter (output_section oc) ["bin"; "lib"; "libexec"; "man"]; + output_endline oc {|share_root: [ + "config.cache" {"ocaml/config.cache"} + "config.status" {"ocaml/config.status"} +]|} + +(* [process_clone oc process] processes clone-* in the current directory, + emitting mkdir commands to [oc] and passing the directory name and a channel + set to the start of each clone file to [process]. The clone files are erased + after processing. [prefix] is the literal string to place in double-quotes + for the installation prefix (either ["$1"] or ["$2"]). *) +let process_clone oc ~prefix process = + let process_file file = + if String.starts_with ~prefix:"clone-" file then begin + let dir = + String.map (function '@' -> '/' | c -> c) + (String.sub file 6 (String.length file - 6)) + |> valid_section + in + output_endline oc {|mkdir -p "%s"'/%s'|} prefix dir; + In_channel.with_open_text file @@ process oc dir; + remove_file file + end + in + let files = Sys.readdir Filename.current_dir_name in + Array.sort String.compare files; + Array.iter process_file files + +(* [process_symlinks oc ~mkdir ~prefix] processes create-symlinks, if it exists, + writing any required mkdir commands to [oc] if [mkdir = true] and also the + appropriate ln / mklink commands. create-symlinks is erased after + processing. [prefix] is the literal string to place in double-quotes for the + installation prefix (either ["$1"] or ["$2"]). *) +let process_symlinks oc ~mkdir ~prefix = + let module StringSet = Set.Make(String) in + let file = "create-symlinks" in + if Sys.file_exists file then + let lines = + let parse acc line = + match String.split_on_char ' ' line with + | [dir; target; source] -> + (valid_section dir, valid_path target, valid_path source)::acc + | _ -> + exit_because "Invalid line encountered in create-symlinks" + in + In_channel.with_open_text file @@ fun ic -> + List.rev (In_channel.fold_lines parse [] ic) + in + output_endline oc {|cd "%s"|} prefix; + let _ = + let create_dir seen (dir, _, _) = + if not (StringSet.mem dir seen) && String.contains dir '/' then + output_endline oc {|mkdir -p '%s'|} dir; + StringSet.add dir seen + in + List.fold_left create_dir StringSet.empty (if mkdir then lines else []) + in + if not Sys.win32 then + let ln (dir, target, source) = + output_endline oc {|%s '%s' '%s/%s'|} ln_command target dir source + in + List.iter ln lines + else begin + let mklink (dir, target, source) = + (* Convert all slashes to _two_ backslashes *) + let to_backslashes oc s = + output_string oc (String.concat {|\\|} (String.split_on_char '/' s)) + in + output_endline oc + {| cmd /c "mklink %a\\%s %s"|} to_backslashes dir source target + and cp (dir, target, source) = + output_endline oc {| $CP '%s/%s' '%s/%s'|} dir target dir source + in + output_endline oc {|cmd /c "mklink __ln_test mklink-test"|}; + output_endline oc {|if test -L "%s/__ln_test"; then|} prefix; + List.iter mklink lines; + output_endline oc {|else|}; + List.iter cp lines; + output_endline oc {|fi|}; + output_endline oc {|rm -f __ln_test|} + end; + remove_file file + +let copy_files oc dir = + In_channel.fold_lines (fun _ line -> + match String.split_on_char ' ' line with + | [source; dest] -> + let source = valid_path source in + let dest = valid_path dest in + output_endline oc {|cp '%s' "$1"'/%s/%s'|} source dir dest + | _ -> + exit_because "Invalid line encountered in clone files") () + +let clone_files oc dir ic = + output_endline oc + {|src="$1" dest="$2"'/%s' xargs sh "$2/clone-files" <<'EOF'|} dir; + In_channel.fold_lines (fun _ -> output_endline oc "%s") () ic; + output_endline oc {|EOF|} + +let () = + if mode = "opam" then begin + generate_install (package ^ ".install"); + (* The script must be written with Unix line-endings on Windows *) + Out_channel.with_open_bin (package ^ "-fixup.sh") @@ fun oc -> + output_endline oc {|#!/bin/sh +set -eu|}; + process_clone oc ~prefix:"$1" copy_files; + process_symlinks oc ~mkdir:true ~prefix:"$1" + end else begin + (* Don't pass -p to cp on Windows - it's never going to be relevant (no + execute bit which needs preserving) and there are scenarios in which it's + more likely to fail than add anything useful (especially if copying from + a Cygwin-managed build directory to /cygdrive) *) + let preserve = if Sys.win32 then "" else "p" in + (* The script must be written with Unix line-endings on Windows *) + Out_channel.with_open_bin (package ^ "-clone.sh") @@ fun oc -> + output_endline oc {|#!/bin/sh +set -eu +mkdir -p "$2" +rm -f "$2/__cp_test" "$2/__ln_test" +if cp --reflink=always "$1/doc/ocaml/LICENSE" "$2/__cp_test" 2>/dev/null; then + rm -f "$2/__cp_test" + CP='cp --reflink=always -%sf' + if ! test -e "$2/clone-files"; then + echo 'cd "$src" && '"$CP"' "$@" "$dest/"' > "$2/clone-files" + fi +else + CP='cp -%sf' + if ! test -e "$2/clone-files"; then + if ln -f "$1/doc/ocaml/LICENSE" "$2/__ln_test" 2>/dev/null; then + rm -f "$2/__ln_test" + echo 'cd "$src" && ln -f "$@" "$dest/"' > "$2/clone-files" + else + echo 'cd "$src" && '"$CP"' "$@" "$dest/"' > "$2/clone-files" + fi + fi +fi|} preserve preserve; + Out_channel.with_open_text "clone-share@ocaml" (fun oc -> + output_endline oc "share/ocaml/clone"; + if Sys.file_exists "config.cache" then + output_endline oc "share/ocaml/config.cache"); + process_clone oc ~prefix:"$2" clone_files; + (* ld.conf is a configuration file, so is always copied. + Makefile.config and config.status will both contain the original + prefix, which must be updated. *) + output_endline oc {|cp "$1/lib/ocaml/ld.conf" "$2/lib/ocaml/ld.conf" +cat > "$2/prefix.awk" <<'ENDAWK' +{ + rest = $0 + while ((p = index(rest, ENVIRON["O"]))) { + printf "%%s%%s", substr(rest, 1, p-1), ENVIRON["N"] + rest = substr(rest, p + length(ENVIRON["O"])) + } + print rest +} +ENDAWK +prefix="$(sed -ne 's/^prefix *= *//p' "$1/lib/ocaml/Makefile.config")" +for file in lib/ocaml/Makefile.config share/ocaml/config.status; do + O="$prefix" N="$2" awk -f "$2/prefix.awk" "$1/$file" > "$2/$file" +done +rm -f "$2/clone-files" "$2/prefix.awk"|}; + process_symlinks oc ~mkdir:false ~prefix:"$2" + end diff --git a/toplevel/byte/topeval.ml b/toplevel/byte/topeval.ml index 14c605039d56..b3823bd1a985 100644 --- a/toplevel/byte/topeval.ml +++ b/toplevel/byte/topeval.ml @@ -301,7 +301,7 @@ and really_load_file recursive ppf name filename ic = "Cannot load required shared library %s.@.Reason: %s.@." name reason; raise Load_failed) - lib.lib_dllibs; + (Dll.read_suffixed_dllibs_from_channel ic lib); List.iter (load_compunit ic filename ppf) lib.lib_units; true end else begin diff --git a/utils/ccomp.ml b/utils/ccomp.ml index defe4d2a4b92..d6818ef093a5 100644 --- a/utils/ccomp.ml +++ b/utils/ccomp.ml @@ -90,7 +90,13 @@ let compile_file ?output ?(opt="") ?stable_name name = ("", "") in let debug_prefix_map = match stable_name with - | Some stable when Config.c_has_debug_prefix_map -> + | Some stable + when Config.c_has_debug_prefix_map + && not (String.starts_with ~prefix:"mingw" Config.system) -> + (* -fdebug-prefix-map exists on mingw-w64 but at present it is not used + for BUILD_PATH_PREFIX_MAP because there isn't yet a good story for how + to deal with Cygwin, where the paths are Cygwin-style paths and MSYS2, + where they are native Windows paths. *) Printf.sprintf " -fdebug-prefix-map=%s=%s" name stable | Some _ | None -> "" in let exit = diff --git a/utils/clflags.ml b/utils/clflags.ml index be10f2352219..ab48a33138d1 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 ([] : (bool * string) list) + (* .so, -dllib -lxxx and + -dllib-suffixed -lxxx *) let cmi_file = ref None @@ -48,6 +50,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 *) @@ -85,6 +88,12 @@ and noinit = ref false (* -noinit *) and open_modules = ref [] (* -open *) and use_prims = ref "" (* -use-prims ... *) and use_runtime = ref "" (* -use-runtime ... *) +and target_bindir = (* -launch-method ... *) + ref Config.target_bindir +and launch_method = + ref Config.launch_method +and search_method = (* -search-method ... *) + ref Config.search_method and plugin = ref false (* -plugin ... *) and principal = ref false (* -principal *) and real_paths = ref true (* -short-paths *) diff --git a/utils/clflags.mli b/utils/clflags.mli index 248a7d86e620..48fad440b062 100644 --- a/utils/clflags.mli +++ b/utils/clflags.mli @@ -70,12 +70,13 @@ 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 : (bool * string) list ref val cmi_file : string option ref 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 @@ -113,6 +114,9 @@ val noinit : bool ref val noversion : bool ref val use_prims : string ref val use_runtime : string ref +val target_bindir : string ref +val launch_method : Config.launch_method ref +val search_method : Config.search_method ref val plugin : bool ref val principal : bool ref val real_paths : bool ref diff --git a/utils/config.common.ml.in b/utils/config.common.ml.in index 3603fe6c60c7..c5459b93efc0 100644 --- a/utils/config.common.ml.in +++ b/utils/config.common.ml.in @@ -20,6 +20,28 @@ (* The main OCaml version string has moved to ../build-aux/ocaml_version.m4 *) let version = Sys.ocaml_version +(* is_official_release and release_number are automatically updated autoconf + from values in ../build-aux/ocaml_version.m4 - do not edit these lines + directly. *) +let is_official_release = false +let release_number = 19 + +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 +51,13 @@ let standard_library = with Not_found -> 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 *) and cmi_magic_number = {magic|@CMI_MAGIC_NUMBER@|magic} @@ -46,6 +75,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 @@ -71,9 +115,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; @@ -110,6 +158,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 807b92935531..02894bc6b3b0 100644 --- a/utils/config.fixed.ml +++ b/utils/config.fixed.ml @@ -21,7 +21,7 @@ let boot_cannot_call s = "/ The boot compiler should not call " ^ s let bindir = "/tmp" -let standard_library_default = "/tmp" +let target_bindir = bindir let ccomp_type = "n/a" let c_compiler = boot_cannot_call "the C compiler" let c_output_obj = "" @@ -35,6 +35,7 @@ let bytecomp_c_libraries = "" let bytecomp_c_compiler = "" let native_c_compiler = c_compiler let native_c_libraries = "" +let compression_c_libraries = "" let native_ldflags = "" let native_pack_linker = boot_cannot_call "the linker" let default_rpath = "" @@ -52,6 +53,8 @@ let windows_unicode = false let flat_float_array = true let function_sections = false let afl_instrument = false +let bytecode_runtime_id = "" +let native_runtime_id = "" let native_compiler = false let tsan = false let architecture = "none" @@ -71,3 +74,6 @@ let target = host let systhread_supported = false let flexdll_dirs = [] let ar_supports_response_files = true +let suffixing = false +let launch_method = "sh" +let search_method = "always" diff --git a/utils/config.generated.ml.in b/utils/config.generated.ml.in index aa0345540968..71093a1e5dbb 100644 --- a/utils/config.generated.ml.in +++ b/utils/config.generated.ml.in @@ -19,8 +19,7 @@ than compiled on its own *) let bindir = {@QS@|@ocaml_bindir@|@QS@} - -let standard_library_default = {@QS@|@ocaml_libdir@|@QS@} +let target_bindir = {@QS@|@TARGET_BINDIR@|@QS@} let ccomp_type = {@QS@|@ccomptype@|@QS@} let c_compiler = {@QS@|@CC@|@QS@} @@ -44,6 +43,7 @@ let bytecomp_c_compiler = let native_c_compiler = c_compiler ^ " " ^ native_cflags ^ " " ^ native_cppflags let native_c_libraries = {@QS@|@cclibs@|@QS@} +let compression_c_libraries = {@QS@|@zstd_libs@|@QS@} let native_ldflags = {@QS@|@native_ldflags@|@QS@} let native_pack_linker = {@QS@|@PACKLD@|@QS@} let default_rpath = {@QS@|@rpath@|@QS@} @@ -65,6 +65,9 @@ let flat_float_array = @flat_float_array@ let function_sections = @function_sections@ let afl_instrument = @afl@ +let bytecode_runtime_id = {@QS@|@bytecode_runtime_id@|@QS@} +let native_runtime_id = {@QS@|@native_runtime_id@|@QS@} + let native_compiler = @native_compiler@ let architecture = {@QS@|@arch@|@QS@} @@ -92,3 +95,9 @@ let flexdll_dirs = [@flexdll_dir@] let ar_supports_response_files = @ar_supports_response_files@ let tsan = @tsan@ + +let suffixing = @suffixing@ + +let launch_method = {@QS@|@launch_method_target@|@QS@} + +let search_method = {@QS@|@runtime_search_target@|@QS@} diff --git a/utils/config.mli b/utils/config.mli index 51e31a37294c..7ccbd70f80d4 100644 --- a/utils/config.mli +++ b/utils/config.mli @@ -23,11 +23,43 @@ val version: string (** The current version number of the system *) +val release_number: int +(** The release number for the compiler + + @since 5.4.2 *) + +val is_official_release: bool +(** True if the compiler is an unmodified official OCaml release + + @since 5.4.2 *) + 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.3.2 *) + +val target_bindir: string +(** The directory containing the runtime binaries on the target system + + @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.3.2 *) val standard_library: string -(** The 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 @@ -65,6 +97,12 @@ val bytecomp_c_libraries: string val native_c_libraries: string (** The C libraries to link with native-code programs *) +val compression_c_libraries: string +(** The C libraries needed with -lcomprmarsh (should appear before + {!native_c_libraries} in a call to the C compiler) + + @since 5.3.2 *) + val native_ldflags : string (* Flags to pass to the system linker *) @@ -253,6 +291,57 @@ val ar_supports_response_files: bool val tsan : bool (** Whether ThreadSanitizer instrumentation is enabled *) +(** Launch mechanisms for bytecode executables + + @since 5.4.2 *) +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.4.2 *) + +(** Mechanisms used by tendered bytecode executables to locate the interpreter + + @since 5.4.2 *) +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.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.4.2 *) + +val bytecode_runtime_id : string +(** The Runtime ID for this build of the bytecode runtime system + + @since 5.4.2 *) + +val native_runtime_id : string +(** The Runtime ID for this build of the native runtime system + + @since 5.4.2 *) + (** Access to configuration values *) val print_config : out_channel -> unit diff --git a/utils/misc.ml b/utils/misc.ml index b3d75dbb8664..5d9afaa80bed 100644 --- a/utils/misc.ml +++ b/utils/misc.ml @@ -1390,3 +1390,130 @@ module Magic_number = struct | Error err -> Error (Unexpected_error err) | Ok () -> Ok info end + +module RuntimeID = struct + type t = { + dev: bool; + release: int; + reserved: int; + no_flat_float_array: bool; + fp: bool; + tsan: bool; + int31: bool; + static: bool; + no_compression: bool; + ansi: bool; + } + + let make fn ?(dev = not Config.is_official_release) + ?(release = Config.release_number) + ?(reserved = Config.reserved_header_bits) + ?(no_flat_float_array = not Config.flat_float_array) + ?(fp = Config.with_frame_pointers) + ?(tsan = Config.tsan) + ?(int31 = (Sys.int_size = 31)) + ?(static = not Config.supports_shared_libraries) + ?(no_compression = (Config.compression_c_libraries = "")) + ?(ansi = Sys.win32 && not Config.windows_unicode) () = + if release < 0 || release > 63 || reserved < 0 || reserved > 31 then + invalid_arg fn + else + {dev; release; reserved; no_flat_float_array; fp; tsan; int31; static; + no_compression; ansi} + + let make_zinc = + make "Misc.RuntimeID.make_zinc" + ~reserved:0 ~fp:false ~tsan:false ~ansi:false + + let make_bytecode = + make "Misc.RuntimeID.make_bytecode" ~fp:false ~tsan:false + + let make_native = make "Misc.RuntimeID.make_native" + + let is_zinc = function + | {dev = _; release = _; reserved = 0; no_flat_float_array = _; fp = false; + tsan = false; int31 = _; static = _; no_compression = _; ansi = false} -> + true + | _ -> + false + + let is_bytecode = function + | {dev = _; release = _; reserved = _; no_flat_float_array = _; fp = false; + tsan = false; int31 = _; static = _; no_compression = _; ansi = _} -> true + | _ -> false + + let is_native _ = true + + let to_string t = + let alpha = "0123456789abcdefghijklmnopqrstuv" in + let bit bit cond = if cond then 1 lsl bit else 0 in + let q0 = + (bit 0 t.dev) lor + ((t.release lsl 1) land 0b11110) (* 4 bits *) + in + let q1 = + t.release lsr 4 lor (* 2 bits *) + ((t.reserved lsl 2) land 0b11100) (* 3 bits *) + in + let q2 = + t.reserved lsr 3 lor (* 2 bits *) + bit 2 t.no_flat_float_array lor + bit 3 t.fp lor + bit 4 t.tsan + in + let q3 = + bit 0 t.int31 lor + bit 1 t.static lor + bit 2 t.no_compression lor + bit 3 t.ansi + (* bit 4 is unused *) + in + Printf.sprintf "%c%c%c%c" alpha.[q0] alpha.[q1] alpha.[q2] alpha.[q3] + + let of_string s = + if String.length s <> 4 then + None + else + let convert c = + match c with + | '0'..'9' -> Char.code c - Char.code '0' + | 'a'..'v' -> Char.code c - Char.code 'a' + 10 + | _ -> min_int + in + let set bit q = (q land (1 lsl bit) <> 0) in + let q0 = convert s.[0] in + let q1 = convert s.[1] in + let q2 = convert s.[2] in + let q3 = convert s.[3] in + if q0 + q1 + q2 + q3 >= 0 then + Some {dev = set 0 q0; release = ((q1 land 0b11) lsl 4) lor (q0 lsr 1); + reserved = ((q2 land 0b11) lsl 2) lor (q1 lsr 2); + no_flat_float_array = set 2 q2; fp = set 3 q2; tsan = set 4 q2; + int31 = set 0 q3; static = set 1 q3; no_compression = set 2 q3; + ansi = set 3 q3; (* bit 4 of q3 is unused *)} + else + None + + let ocamlrun variant runtime_id = + if is_zinc runtime_id then + Printf.sprintf "ocamlrun%s-%s" variant (to_string runtime_id) + else + invalid_arg "Misc.RuntimeID.ocamlrun" + + let shared_runtime ?runtime_id ?(host = Config.target) ?(prefix = "-l") + backend_type = + match backend_type with + | Sys.Native -> + let runtime_id = Option.value ~default:(make_native ()) runtime_id in + Printf.sprintf "%sasmrun-%s-%s" prefix host (to_string runtime_id) + | Sys.Bytecode -> + let runtime_id = Option.value ~default:(make_bytecode ()) runtime_id in + Printf.sprintf "%scamlrun-%s-%s" prefix host (to_string runtime_id) + | Sys.Other _ -> + invalid_arg "Misc.RuntimeID.shared_runtime" + + let stubslib ?(runtime_id = make_bytecode ()) + ?(host = Config.target) + name = + Printf.sprintf "%s-%s-%s" name host (to_string runtime_id) +end diff --git a/utils/misc.mli b/utils/misc.mli index 54354eba56ab..8bf42334eed9 100644 --- a/utils/misc.mli +++ b/utils/misc.mli @@ -823,6 +823,106 @@ module Utf8_lexeme: sig are not checked. *) end +module RuntimeID : sig + (** Manipulation of the Runtime ID values used to mangle the filenames of + shared libraries and the bytecode interpreters. + + @since 5.3.2 *) + + (** Runtime IDs *) + type t = private { + dev: bool; + (** [true] if this not an unaltered official release of OCaml *) + release: int; + (** Release number (OCaml 5.5 is release 21) *) + reserved: int; + (** The number of reserved bits (0-31) in the {v value v} header *) + no_flat_float_array: bool; + (** [true] if float arrays must be boxed (i.e. configured with + {v --disable-flat-float-array v}) *) + fp: bool; + (** [true] if frame pointers are required (i.e. configured with + {v --enable-frame-pointers v} *) + tsan: bool; + (** [true] if ThreadSanitizer (TSAN) is required (i.e. configured with + {v --enable-tsan v}) *) + int31: bool; + (** [true] if the platform has 31-bit [int]s (i.e. 32-bit systems) *) + static: bool; + (** [true] if dynamic loading of libraries is not supported *) + no_compression: bool; + (** [true] if compressed marshalling is not supported *) + ansi: bool; + (** [true] if Unicode support on Windows is disabled *) + } + + val make_zinc: ?dev:bool -> ?release:int + -> ?no_flat_float_array:bool + -> ?int31:bool -> ?static:bool -> ?no_compression:bool + -> unit -> t + (** Returns the Zinc Runtime ID for the given parameters (using default values + from {!Config} and {!Sys} as necessary) *) + + val make_bytecode: ?dev:bool -> ?release:int + -> ?reserved:int -> ?no_flat_float_array:bool + -> ?int31:bool -> ?static:bool -> ?no_compression:bool + -> ?ansi:bool + -> unit -> t + (** Returns the Bytecode Runtime ID for the given parameters (using default + values from {!Config} and {!Sys} as necessary) *) + + val make_native: ?dev:bool -> ?release:int + -> ?reserved:int -> ?no_flat_float_array:bool -> ?fp:bool -> ?tsan:bool + -> ?int31:bool -> ?static:bool -> ?no_compression:bool + -> ?ansi:bool + -> unit -> t + (** Returns the Native Runtime ID for the given parameters (using default + values from {!Config} and {!Sys} as necessary) *) + + val is_zinc: t -> bool + (** [is_zinc t] is true if [t] can be used as a Zinc Runtime ID *) + + val is_bytecode: t -> bool + (** [is_bytecode t] is true if [t] can be used as a Bytecode Runtime ID *) + + val is_native: t -> bool + (** [is_native t] is true if [t] can be used as a Native Runtime ID *) + + val to_string: t -> string + (** Returns the 4-character representation of a {!t} *) + + val of_string: string -> t option + (** Converts the 4-character representation back to a {!t} *) + + val ocamlrun: string -> t -> string + (** [ocamlrun variant runtime_id] returns the name for the runtime for the + given Zinc Runtime ID. *) + + val shared_runtime: ?runtime_id:t -> ?host:string + -> ?prefix:string -> Sys.backend_type -> string + (** [shared_runtime ?runtime_id ?host ?prefix backend] returns the name of the + shared runtime for the given [backend]. [runtime_id] defaults to + {!make_bytecode} if [backend = Sys.Bytecode] and {!make_native} if + [backend = Sys.Native] and [host] to {!Config.target}. [prefix] defaults + to ["-l"] and the function does not append {!Config.ext_dll}. + + e.g. [shared_runtime ~host:"x86_64-pc-linux-gnu" Native + = "-lasmrun-x86_64-pc-linux-gnu-b100"] for a default OCaml 5.5 + build on a 64-bit system with shared library support and compressed + marshalling. *) + + val stubslib: ?runtime_id:t -> ?host:string -> string -> string + (** [stublibs ?runtime_id ?host dllname] returns the name for the given DLL + basename. [dllname] should not include {!Config.ext_dll} (and the result + does not include it either). [host] and [runtime_id] default to + {!Config.target} and {!make_bytecode} respectively. + + e.g. [stubslib ~host:"x86_64-pc-linux-gnu" "dllunixbyt" + = "dllunixbyt-x86_64-pc-linux-gnu-001b"] for a default OCaml 5.5 + build on a 64-bit system with shared library support and compressed + marshalling. *) +end + (** {1 Miscellaneous type aliases} *) type filepath = string